Skip to content

feat: dependency management — external, cross-project, and platform-provisioned dependencies; new agent-service as default#85

Merged
xlight05 merged 48 commits into
aep-rewritefrom
aep-rewrite-kaj
Jul 6, 2026
Merged

feat: dependency management — external, cross-project, and platform-provisioned dependencies; new agent-service as default#85
xlight05 merged 48 commits into
aep-rewritefrom
aep-rewrite-kaj

Conversation

@kaje94

@kaje94 kaje94 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Adds dependency management to the platform: components can now declare what they need — a sibling component, another project's shared service, a third-party API, or platform-provisioned infrastructure — and the platform discovers, resolves, gates, provisions, and wires those needs automatically, end to end. The feature was proven in the previous-generation codebase (#63) and is brought into the rewrite here, restructured around OpenChoreo's own Workload taxonomy rather than ported verbatim.

It also completes the agent-service cutover: the new services/agents now serves architect, document-generation, and task-planner on the same wire contract the BFF already speaks, and ships as the compose default (the legacy service stays in-tree for rollback).

The feature in one flow

A component declares what it needs; the platform knows how to satisfy each kind; nothing deploys until every need is met — and nothing is ever wired by hand.

flowchart LR
    subgraph Design
        P[Prompt] --> A[Architect agent]
        A -- "MCP discovery:<br/>external resources, org endpoints,<br/>platform resource types" --> C[(Org catalogs)]
        A -- "web_search fallback" --> W((Web))
        A --> D["design.json<br/>dependencies[]"]
    end
    subgraph Resolve
        D --> G{Proceed-gate<br/>409 until resolved}
        G -- "external: spec + values → OpenBao" --> R1[Drawer]
        G -- "platform-resource: Provision → CNPG" --> R1
        G -- "org-service: catalog / access request" --> R1
    end
    subgraph Deliver
        G --> T["Typed task graph<br/>(config-collection, resource-provisioning gates)"]
        T --> DIS[Dispatch]
        DIS -- "platform-resolved comment" --> CA[Coding agent]
        CA -- "authors workload.yaml<br/>(declarative wiring)" --> DEP[Deploy]
        DEP -- "cascade releases held siblings" --> DIS
    end
Loading

The four dependency kinds

Kind Satisfied by Wired via
component Sibling component in the project WorkloadConnection (endpoints)
org-service Another project's org-published component (project-prefixed catalog name) WorkloadConnection, cross-namespace env injection
external Third-party service; org-registered schema + per-project values (secrets → OpenBao via SM-API, never the DB) OC Resource model → ResourceReleaseBinding → ExternalSecret → pod env
platform-resource Platform-provisioned infra from a typed catalog (postgres-cnpg via CNPG operator, readyWhen-gated) OC Resource model, binding pinned to controller-cut release

Two load-bearing decisions (ADR-0003/0004 in docs/decisions/):

  • Read-time resolutionstatus/reason are computed on every read against live platform state and are structurally unrepresentable in design.json. No stale status, ever.
  • Declarative wiring — dispatch posts a "Platform-resolved dependencies" comment on the task issue; the coding agent copies it verbatim into workload.yaml. The platform never patches a deployed Workload CR; git checkout reproduces the running system.

Architecture

flowchart TB
    subgraph console["console-legacy"]
        DR[DependencyDrawer + 5 panels]
        BB[Board CTAs → ?dep= deep-link]
        ST[External resources settings]
    end
    subgraph api["aep-api — internal/feature/dependencies/"]
        MCP["MCP server (authenticated,<br/>4 discovery tools, OC service identity)"]
        subgraph EP[endpoints/]
            CAT[Org endpoint catalog]
            ACC[Access requests<br/>single-owner state machine]
        end
        subgraph RES[resources/]
            REG[External registry + values]
            PROV[Two provisioners<br/>external / platform]
        end
    end
    subgraph agents["services/agents (NEW — compose default)"]
        AR[architect route + web_search]
        DG[document-generation route]
        TP["task-planner (plan/detail)<br/>guided by skills/task-breakdown"]
    end
    OC[(OpenChoreo<br/>Workload / Resource / Bindings)]
    BAO[(SM-API → OpenBao)]
    console --> api
    api -- "mcp {url, token} per call" --> agents
    agents -- "tools/call" --> MCP
    RES --> OC
    RES --> BAO
    EP --> OC
Loading

Task lifecycle is event-driven through internal/contracts (the Projector is the sole status writer): values.provisioned, provision.started, resource.ready, resource.provision_failed. SYSTEM rows (config-collection, resource-provisioning) gate component tasks via three depends_on_* JSONB columns; deploy events cascade-release held siblings — the whole chain runs hands-off (verified live: values save → gate deployed → CNPG Ready → watcher → cascade → dispatch, zero clicks).

Agent-service cutover

services/agents now serves the full BFF wire surface — cutover was literally the compose build-context flip, kept as the default per live E2E:

  • POST /internal/v1/agents/architect — the design core under the legacy SSE contract, MCP-catalogs-first discovery with an Anthropic web_search fallback for externals not found locally.
  • POST /internal/v1/agents/document-generation/{skillId} + /internal/v1/dsl/render — all 8 doc-gen skills ported byte-identical; DSL rendering reuses the shared @aep/excalidraw-dsl package.
  • POST /internal/v1/agents/task-planner/{plan,detail} — renamed from "tech lead" (it plans, it doesn't manage); judgment lives in the new repo-root skills/task-breakdown/SKILL.md (leading words: topological order, gate, brief; authored TDD-style with a RED baseline), pushed by callers exactly like other skills; shape stays in the schema/scaffolding. A Go test guards byte-identity between the repo-root skill and the aep-api embed.

services/agents-legacy is untouched and stays in-tree for rollback until post-merge cleanup.

Verification — live E2E on a fresh k3d stack

Every scenario was driven through the real console in a browser (agent-browser) against a fresh local cluster — first on the legacy agent service, then re-run after the cutover:

Scenario Proof
Third-party API (weather service + SPA) Live OpenWeatherMap data; API key present in pod env, 0 occurrences in HTML/bundle
Registry reuse + credential rotation Architect reused registered resource by exact name; key rotation re-pinned vault→binding→Secret (pod restart caveat noted)
Cross-project shared service Consumer discovered provider via catalog, called it cross-namespace using only the injected env var
Org settings / registry management Consumers listed, in-use delete blocked
Storefront demo (all four kinds in one project) One POST: catalog price × live FX rate → row in platform-provisioned Postgres (verified by direct psql); repeated end-to-end on the new agent service

The E2E runs caught and fixed real defects the test pyramid could not: an MCP identity bug that silently emptied every discovery catalog in production paths, a reconcile hang on idempotent re-saves, an OC authz binding skipped by helm-recovery, and two Thunder 1.1.1 bootstrap breaks. Both agent services independently hallucinated the identical wrong FX API path — systematic model-prior evidence for defaulting needsSpec: true on unregistered externals (follow-up).

Docs

CONTEXT.md (ubiquitous language: dependency kinds, resolution, gate, discovery, task-planner — "connection" is banned vocabulary), docs/glossary.md, ADR-0003/0004, services/aep-api/design/dependencies.md, services/agents/design/task-planner-contract-parity.md.

⚠️ Open questions for reviewers

  1. Agent→BFF authentication is deliberately NOT settled. The current mechanism (aep-api mints short-TTL MCP tokens per call, caller-supplied {url, token}) works but is not confirmed as the chosen model — needs a team decision before we harden it (mid-run refresh, service identity posture). Please treat it as provisional. For local development only, a flag-gated POST /internal/v1/mcp/playground-token endpoint (PLAYGROUND_TOKEN_ENABLED, default false, set only by docker-compose; never mounted otherwise, helm pins it false) lets the playground auto-mint a fresh token per turn — verified live against the four dependency discovery/breakdown checks. It deliberately does not prejudge the production model.
  2. @sachiniSam — this replaces the provisional connections[] in ComponentDesign with the unified dependencies[] (status/reason excluded from the schema by design). One divergence to decide together: the Go codec marks base scalars (exposure sharpest) omitempty while the TS schemas require them — either relax TS or commit aep-api to always populating them.
  3. New-console parity is tracked in New console parity: dependency-management UI (drawer, board CTAs, settings, diagram) #84 (the console changes land in console-legacy per the current wiring).

Notable follow-ups

  • needsSpec default-true for unregistered externals (prevents hallucinated API paths)
  • Secret rotation needs an automatic rollout (env-from-secret doesn't hot-reload)
  • Failed builds have no self-service rebuild path (retry re-runs the whole coding agent)
  • RetryTask backend guard for SYSTEM tasks; AutoMigrate-before-RunAll ordering (phase9's component_tasks section is currently shadowed by boot-time AutoMigrate — documented in-code)
  • New service: per-org Anthropic key resolver at boot; usage counts not plumbed into architect route logs

🤖 Generated with Claude Code

https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq

kaje94 and others added 30 commits July 4, 2026 19:23
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…uest models, typed ComponentTask

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…ovisioning completion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…cess_request repositories

Adds phase9_dependency_mgmt (external_resources + access_requests tables,
component_tasks typed-dependency-graph columns) and the two flat repositories
(ExternalResourceRepository, AccessRequestRepository) ported from
lab-app-factory's connections/registry.go and access/repository.go, renamed
per the no-"connection" terminology rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…ment query

The @> ?::jsonb containment operand for external_resources.Consumers was
built by string concatenation (`["`+name+`"]`), so a resource name
containing a double-quote or backslash produced invalid JSON and made
Postgres error (SQLSTATE 22P02) instead of matching. Encode the operand
with json.Marshal instead, and cover it with a dbtest case using a
`we"ird` resource name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Scaffold internal/feature/dependencies/{,endpoints,resources} (doc.go
only) as the umbrella for a design component's external dependency
graph, mirroring OpenChoreo's Workload.spec.dependencies.{endpoints[],
resources[]}. Extend the arch-boundary tests so discovery recurses one
level into a nested feature umbrella, edge detection matches the
longest feature-key prefix, and the allowlist gains rows for
dependencies (parent may import its own children only),
dependencies/endpoints, and dependencies/resources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
… exist

The nested-umbrella staleness exemption in TestFeatureEdgeAllowlist only
checked the SHAPE of an allowlist entry (strings.HasPrefix(stale, f+"/")),
so a parent->child row survived even if the named child feature no longer
existed on disk -- exactly the allowlist rot the test exists to catch.
Now also requires the child to be present in the discovered feature set.

Also tighten listFeatures' "*test" subdir skip to an exact "<top>test"
match so a future real feature (e.g. "latest") can't be mistaken for a
hand-fake test-support package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…dpoint listing)

Port lab-app-factory's Resource-model client (openchoreo.dev/v1alpha1) as
resource_client.go: hand-rolled REST over the pinned gen client, which
predates Resource CRs. Adds ResourceClient (EnsureResourceType, ApplyResource,
GetResource, EnsureBinding, GetBinding, DeleteBinding, DeleteResource,
ListClusterResourceTypes, ListWorkloadEndpoints) plus the shared
WaitForLatestRelease helper that dedupes the source's two provisioner copies.

Extracts buildRetryConfig/authRequestEditor out of transport.go's
newGenClient so the hand-rolled client reuses the exact same 401-retry and
user-JWT/service-identity auth selection as the generated client, and
extracts errors.go's status→sentinel mapping so both paths produce
errors.Is-compatible sentinels. Workload spec.dependencies patching is
deliberately out of scope — a later task's concern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…ponent design.md removed)

Components are now read/written as specs/design/components/<name>/design.json
(authored ComponentDesign) instead of the per-component design.md. The strict
JSON codec mirrors sachiniSam's TS ComponentDesign contract, extended with the
unified kind-discriminated dependencies[] (replacing connections[]) and the
platform-owned exposesAPI / callerIdentity / componentAgentInstructions blocks.

- New design_json.go: parse (DisallowUnknownFields; status/reason absent from the
  on-disk dependency struct so any occurrence is rejected; name-must-equal-dir,
  kebab-case) and marshal (stable key order, 2-space indent, trailing newline,
  never emits status/reason). Ports the read-time needs-spec computation
  (external + needsSpec + no specPath => status=unresolved, reason=needs-spec).
- Adds Version, Exposure, Description fields to models.DesignComponent.
- Allows .json under specs/design/; trait-sync gate + issue-body reference now
  point at design.json.
- Migrates every component design fixture/golden and adds design_json_test.go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…and bad exposure

assembleDependencies silently dropped dependency entries missing kind or
name instead of erroring, quietly losing data a writing agent authored.
Now a missing kind/name, or a kind outside the closed four-value set, is a
schema error naming the entry index and the fix. exposure is now enforced
as a closed enum ("internet" | "intranet" | absent) on read. All new error
messages are phrased for a writing agent's one-round-trip self-correction.
Also adds the missing write-side name-must-equal-dir test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Port asdlc-service's resolveOrgServices + consumer-side OrgServiceResolver
port into aep-api's artifacts feature. resolveOrgServices runs at the end of
ReadDesign, after design.json assembly, and mutates only org-service-kind
dependencies' Status/Reason: namespace-visible -> resolved; exists but
project-only -> blocked/access-required; absent -> unresolved/not-found.
Status/Reason are never persisted (read-time only).

This codebase has no static ExternalAPICatalog fallback (deleted in task
A1), so until SetOrgServiceResolver wires a concrete provider (a later
task), org-service dependencies simply keep their assembled (always empty)
Status/Reason. A resolver error is best-effort and never fails the design
read; ported the source's exact (slightly asymmetric) per-error status
behavior and added slog.WarnContext logging per this package's convention.

Added models.DependencyStatus{Resolved,Blocked,Unresolved} and
models.DependencyReason{AccessRequired,NotFound} constants (none existed
yet in A1's models package).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…sign.json

Ports the spec-collection SSRF-hardened fetch/validate pipeline
(FetchSpecFromURL, ValidateOpenAPI, StoreConsumedSpec) and the untagged
Git-Data-API single-file commit primitive (CommitDesignFile) from
lab-app-factory, then re-implements SetDependencySpecPath and
SetComponentOrgPublished as design.json edits (parse -> mutate -> marshal)
instead of the source's design.md frontmatter rewrite. Reuses the existing
NormalizeOpenAPIYAML rather than duplicating it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…resource registration on save

Ports lab-app-factory's B4 dependency-management slice into design_service.go
+ design_huma.go: SaveAndProceed now auto-fetches architect-hinted specUrl
values before checking the proceed-gate (blocks the save with the new
ErrUnresolvedDependency/409 whenever any dependency is ambiguous, unresolved,
or blocked); CollectSpec stores a pasted/fetched OpenAPI spec and clears the
needsSpec gate; StreamGenerateDesign now best-effort registers every distinct
`external` dependency into the org's external-resource catalog via a new
externalResourceRegistrar port (satisfied later by the composition root).
Adds models.DependencyStatusAmbiguous and the collect-dependency-spec route.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
… (SM-API secrets)

Port lab-app-factory's connections value/provision flow into
internal/feature/dependencies/resources with all terminology renames:

- ExternalResourceProvisioner: EnsureResourceType (version-pinned RT name)
  -> ApplyResource -> shared openchoreo.WaitForLatestRelease -> per-env
  SM-API secret write + pinned ResourceReleaseBinding; Deprovision (2-step
  delete, exported); ResolveRunnerSecrets for the dispatch path.
- ValueService.SaveValues: split values by the registered schema, provision,
  then complete the config-collection task via the contracts state machine
  (TaskCompleter port shaped after task.Projector.ApplyBuildResult applying
  TaskEventValuesProvisioned) — never a direct status UPDATE — and
  best-effort redispatch.
- naming.go: ExternalResourceName / ExternalResourceBindingName.
- ports.go: consumer-side ports only (empty arch-allowlist row holds); the
  A3 repo is consumed via a lookup port, no registry wrapper service.
- openchoreo.BuildExternalResourceType + ExternalResourceRTName: RT builder
  ported into the OC client (A5 had deliberately left it out).
- orgcreds.SMAPIWriter.WriteExternalResourceSecret: project-scoped SM-API
  upload returning the vault KV path (no DB triplet — the path lives on the
  per-env OC binding). SM-API entity naming: extres-<name>-<env>.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…TTP surface

dependencies/resources grows its platform-resource half and the whole
family's HTTP surface (org-implicit paths, token-derived org):

- platform_catalog.go: read-only ResourceTypeCatalog over the installed
  cluster-scoped ClusterResourceTypes (AEP discovers, never authors).
- platform_provisioner.go: ResourceProvisioner port + OCNativeProvisioner —
  async authoring against a DISCOVERED ClusterResourceType (no
  EnsureResourceType, locked by a recording-client test), release pinned via
  the shared openchoreo.WaitForLatestRelease, per-env bindings, Deprovision
  with errors.Join; pure BuildPlatformResource/BuildPlatformBinding builders.
- resources_service.go: ResourceService — design read (local DesignReader
  port returning models-typed components, keeping the arch allowlist row
  empty) → kind policy (unknown dep 404, wrong kind 400 naming both kinds) →
  provision → pending→building via contracts TaskEventProvisionStarted
  through the TaskCompleter (projector); status reads fall back to pending.
- resources_huma.go: GET/DELETE /dependencies/external-resources[/{name}]
  (consumers listed; 409 in-use guard), POST /projects/{p}/dependencies/
  external-resources/{name}/values (C2 ValueService; 404 unregistered),
  POST/GET /projects/{p}/components/{c}/dependencies/{dep}/provision|status
  (202 async; 200 mid-provision with binding-less fallback; outputs masked
  to names only). Registered in RegisterAllHuma with nil-guarded 503s.
- ports.go: ExternalResourceRegistry port (satisfied by the A3 repository —
  no wrapper service) + DesignReader.
- Committed OpenAPI contract regenerated (also catches up ops from earlier
  tasks that had not been re-generated into the committed spec).

Unit tier covers the catalog, the provisioner invariants and the service
transitions; the component tier drives all five routes through the real
handler chain (auth gate, validation, sentinel→status mapping, masking).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Port the connections MCP server from lab-app-factory as the parent
dependencies package's umbrella surface: a minimal JSON-RPC 2.0 MCP
server (protocol 2024-11-05; initialize/ping/tools/list/tools/call;
notifications -> 202) exposing four read-only discovery tools —
list_external_resources, get_external_resource_schema,
list_org_endpoints, list_platform_resource_types — over three narrow
ports (ExternalResourceReader, OrgEndpointLister, ResourceTypeLister)
wired concretely at the composition root.

Unlike the source's ungated {orgHandle}-path mount, the surface is
authenticated by construction: auth.AgentsScopedVerifier requires a
BFF-signed bearer (aud aep-api-mcp, minted via the TaskTokenManager's
new IssueMCPToken/IssueServiceToken path) and binds the acting org
SOLELY from the verified ocOrgId claim — never the path/query/header/
body. Missing/garbage/wrong-aud/expired/org-less tokens are all 401;
an unconfigured verifier is 503; without a token manager the path is
not mounted. surfaces.go's boundary table documents the new surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…nt cascade, runner secrets

Codingagent dispatch now gates component tasks on three status maps
(component / external-resource / platform-resource), never dispatches
config-collection or resource-provisioning rows, and leaves org-service
deps to block-at-proceed. dispatchOne posts the platform-resolved
`workload.yaml` dependencies block (org-service + same-project component
endpoints + external-resource + platform-resource outputs) to the task
issue, resolving env-var/ref names through the C1/C2 naming helpers. The
proxy path materialises external-resource secrets into per-run
ExternalSecrets mounted via envFrom. The deploy cascade fires C4's
GrantByProviderComponent (logical name, best-effort) and its doc comment
is tightened. resolveDependencyEndpoints relaxed to allow internal-only
providers. Adds codingagent->dependencies/{endpoints,resources} arch
allowlist edges (pure-naming imports) with rationale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Port of lab-app-factory's ResourceWatcher, adapted as the structural twin of
build_watcher.go: 10s tick, FOR UPDATE SKIP LOCKED batch claim of
type=resource-provisioning AND status=building, 10-min stale guard, lenient
progressing classifier (a mid-provision CNPG binding is not misread as failed).

Completion routes through the projector (contracts.TaskTransitions.ApplyBuildResult):
Ready=True -> TaskEventResourceReady (building->deployed); terminal-failure reason
-> TaskEventProvisionFailed (reason forwarded as errMsg). No direct status UPDATEs.

Sanctioned deviation from source: the explicit redispatch closure is dropped.
TaskEventResourceReady lands the task in `deployed`, on which the projector fires
its OnTaskDeployed cascade hook (DispatchCascadeHook) itself; re-dispatching here
would double-fire the cascade (C2 review). Mirrors BuildWatcher, which carries no
redispatch. Binding name derived via resources.ExternalResourceBindingName, the
exported single source of truth matching what the C3 platform provisioner authors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Add resourceDeprovisioner and externalResourceDeprovisioner ports (+ setters)
to projectService so DeleteProject tears down a project's platform-resource
and external-resource OC Resource models before purging the task rows that
name them. The OC Project delete doesn't cascade to these (no k8s
ownerReference), so without this fix they'd orphan on the cluster. Both
teardown loops are best-effort like the existing repo/task cleanup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
D5 composition root: turn every deferred nil-hook/503-stub into a working
system in app.Build():

- Construct A3 repos (external-resource, access-request), OC ResourceClient,
  C1 endpoint catalog, C2 external-resource provisioner + ValueService,
  C3 resource-type catalog + OC-native provisioner + ResourceService,
  C4 AccessService, D3 ResourceWatcher (appended to the watcher slice).
- Setters: artifactStore.SetOrgServiceResolver (before any design read),
  designService.SetExternalResourceRegistry, dispatch consumer-dependency +
  external-resource-secret resolvers, cascade SetAccessGrant, project
  resource/external-resource deprovisioners, RegisterHandlers accessRejector.
- DesignReader adapter (designComponentsReader) over ArtifactStore.ReadDesign
  keeps dependencies/{resources,endpoints} off the artifacts feature edge.
- ValueService RedispatchFunc wired nil: values_provisioned lands the task in
  deployed and the projector's OnTaskDeployed cascade already redispatches
  (same sanctioned rationale as D3's removed closure).
- HumaDeps + AppParams MCP ports filled — the dependencies HTTP surface and
  /internal/v1/mcp serve real services (401/200, no more 503 stubs).
- MCP propagation into the architect call: new AEP_API_INTERNAL_BASE_URL
  config derives the MCP URL; agents client attaches additive mcp{url,token}
  (IssueMCPToken) to the architect request — legacy ignores it until E4.
- Contract regen is a byte-identical no-op (routes were registered pre-D5).

Tests: Build-level dbtests (watcher set + MCP mount 401-not-503 + full
JSON-RPC round trip through the real A3 repo on real Postgres) and agents
client MCP-injection unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…gn (coordinated with schema owner)

Replace the provisional connections[] with the unified, kind-discriminated
dependencies[] in the agents-service ComponentDesign contract, mirroring the
aep-api Go models.Dependency (minus read-time status/reason). Adds the
platform-owned passthrough blocks (exposesAPI/callerIdentity/
componentAgentInstructions) so Go-written design.json round-trips validate.

- contracts/component-design.ts: Dependency/ConfigKey/DependencyCandidate/
  ExposesAPI/CallerIdentity types; dependencies[] + optional platform blocks.
- main/component-design.ts: flat strictObject Zod mirror (not a per-kind union
  — matches the lenient Go single-struct codec); status/reason & unknown keys
  rejected; drift guard kept.
- main/component-design.test.ts: four kinds, connections/status/reason ->
  SCHEMA_VIOLATION, platform blocks, kind-field leniency, entry strictness.
- prompt.ts: hello-api seed dependencies[]; platform-managed note.
- design-projection: cell-diagram edges now drawn from dependencies[] (kind-aware).
- skills/high-level-architecture: four kinds, discover-before-invent (MCP),
  external SDK-vs-spec, config-key conventions, resolution is platform-computed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Adds an optional TurnRequest.mcp { url, token } (mirrors Skill's per-turn,
caller-resolved pattern): when present, run-conversation-turn fetches
tools/list from the org's aep-api dependency-discovery MCP server
(best-effort — unreachable/401/malformed degrades to no discovery tools,
never fails the turn) and merges each as a dynamic tool. Omitted mcp is
byte-identical to today (proven by a no-fetch-attempted test).

Ports lab-app-factory's mcp-client.ts shim to this service's AI SDK v7 and
adds a bearer token (the source server had no auth). Adds a deterministic
mock MCP server (evals/mocks/mcp-server.ts) for the eval tree, and three
live-eval fixtures exercising list_external_resources/list_org_endpoints/
list_platform_resource_types against it. Wires the playground to push
AEP_MCP_URL/AEP_MCP_TOKEN when both are set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Flip the merge order in run-conversation-turn.ts so baseTools (the four
file-mutation tools + loadSkill/loadSkillReference) spread last — a
same-named MCP-discovered tool can no longer shadow a core tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…rror as tool error

- mock-model.ts: mockModel() now returns the concrete MockLanguageModelV4 so
  tests can inspect .doStreamCalls (the exact tools/prompt handed to the
  model), not just side effects.
- run-conversation-turn.test.ts: strengthen the mcp-omitted test to assert
  the tool set and system prompt are byte-identical to buildTools()/
  buildInstructions()'s baseline (not just fetchCalls === 0); add a
  shadow-guard regression test (an MCP tool literally named `addFile` must
  never win over the core tool); add an isError-in-turn test proving the
  turn still completes.
- mcp-client.ts: an MCP tools/call result with isError:true now throws
  inside execute() instead of being returned as an ordinary success string,
  mirroring the AI SDK's tool-error convention (thrown execute() errors
  surface as a `tool-error` stream part).
- mcp-client.test.ts: unit test for the isError -> throw behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…parity)

Port the tech-lead as a structured-output agent into services/agents with
byte-identical wire parity to agents-legacy, so the aep-api cutover is a URL
swap. Two SSE routes mounted in createApp:

  POST /internal/v1/agents/tech-lead/plan    → data-plan-item / data-plan-complete
  POST /internal/v1/agents/tech-lead/detail  → data-task-body-delta / -complete

Frames match aep-api task_stream.go's planItemFrame + task-body parsers; the
request bodies match client.go's TechLeadPlanRequest/TechLeadDetailRequest.

Dependency-awareness (the migration's point): the planner orders consumers
after providers via dependsOn and names external / platform-resource gates in
task rationale, WITHOUT inventing config-collection / resource-provisioning
tasks (those stay platform-authored in aep-api persistAndIssue). The slim-design
external/platform/org-service context fields are additive+optional, so the
current wire round-trips unchanged.

Auth: no JWT (matches the conversation route's "does not re-authenticate"
posture); per-org key read from X-Anthropic-Key, else the injected model.

Also: playground /tasks command runs the planner over the thread bundle;
deterministic route + schema + validator tests (mock model, no tokens); an
all-four-dependency-kinds eval fixture with a deterministic plumbing test and a
paid live report (eval:techlead, skips without a key).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Ports the dependency-management delta into the legacy architect + tech-lead
agents so the current production flow (aep-api -> agents-legacy) keeps
speaking the wire shape aep-api's Go codec now expects, ahead of the
new-service cutover.

- architect schema/doc/tools/prompt: dependsOn+dependentApis -> unified
  dependencies[] (component/org-service/external/platform-resource), plus
  version/exposure/description so the emitted component conforms to
  services/agents/src/contracts/component-design.ts and aep-api's
  design_json.go. status/reason are never authored (platform-computed) -
  dropped the resolve_dependency tool accordingly.
- run.ts: provider-executed web_search (Anthropic-gated via isAnthropicModel)
  + extraTools merge point for MCP-discovered tools; surfaces finishReason
  for the content-filter refusal case.
- shared/mcp-client.ts (new): JSON-RPC shim for aep-api's dependency-discovery
  MCP endpoint (list_external_resources, get_external_resource_schema,
  list_org_endpoints, list_platform_resource_types), bearer-token auth,
  best-effort throughout.
- server/routes/architect.ts: reads the additive mcp{url,token} block aep-api
  attaches and loads discovery tools before running the turn.
- tech-lead schema/prompt: additive optional resource-awareness context
  (externalResources/platformResources/orgServiceDependencies) mirroring the
  new agents service's tech-lead wire-parity contract; fixed stale
  design.md-with-frontmatter references to design.json.
- requirements-from-prompt skill: restored the "keep explicit external
  dependencies" guidance dropped from this port.

Tests: doc/validator unit tests updated for the unified model; new
mcp-client, isAnthropicModel, and design.json-contract conformance tests
(TS contract mirror + documented Go shape parity, status/reason absence).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
kaje94 and others added 17 commits July 5, 2026 10:31
…-driven cell-diagram edges

- Dependency.reason: replace the fabricated "unpublished" literal with the
  canonical five-value set from aep-api models/design.go (access-required |
  not-found | needs-spec | access-pending | needs-input), and correct the
  matching status/reason pairing in accessRequests.ts's doc comment.
- AccessRequest: make the five provider-side fields optional, matching the
  Go model's omitempty tags (resolved from the catalog at request time, not
  always present).
- cell-diagram-view/buildProjectModel: fix a silent regression where
  dependsOn/dependentApis (no longer emitted by DesignComponent) left every
  dependency edge un-rendered. Add dependencies?: {kind,name}[] and derive
  sibling edges from kind "component" and external-node edges from kind
  "external"/"org-service" (platform-resource nodes are later-task scope).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…ependencies tab

Port lab-app-factory's DependencyDrawer + resolution panels
(OrgServiceResolution, PlatformResourcePanel, ProvideSpec,
ConnectionValues→ExternalResourceValues, DependenciesSection) into
console-legacy, adapted to F1's org-implicit API clients and dep-addressed
access-request creation. Wire the drawer into ProjectArchitecturePage
(DependenciesSection under the cell diagram + deep-link ?dep=<name> support)
and the four new token accessors into App.tsx. ProjectDependenciesPage/its
route/nav entry were already absent in this rewrite (retired upstream before
the console-legacy fork) — confirmed no remnants, so no deletion needed.

Adapted for contract deltas found in aep-api that the source didn't have:
spec-collect response has no operationCount, platform-resource Dependency
never carries a read-time status or outputs field (only org-service gets
4-state resolution), so PlatformResourcePanel now always queries
provisioningApi.getStatus instead of gating on dep.status.

Added a targeted vitest + jsdom + Testing Library setup to console-legacy
(package.json devDeps, vite.config.ts test block, src/test-setup.ts) since
none existed, and ported all five component test files so `pnpm test`
actually runs (36 tests, all passing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…latform-resource nodes

- TaskDetailPanel/TaskRow: SYSTEM tasks (resource-provisioning /
  config-collection) render a Provision resource / Provide configuration CTA
  that deep-links the architecture page (?dep=<name>), reusing F2's already-
  wired dependency-drawer effect instead of navigating via a stale API-lookup
  path. Retry is excluded for these tasks (aep-api's RetryTask always
  re-dispatches via the coding-agent path, which SYSTEM tasks never use).
  On-hold "Waiting for" now surfaces every gate kind (components, resources,
  external resources, org-services) with an action hint.
- New ExternalResourcesSettings page: org-level list/delete of external
  resource definitions (renamed from the source's Connections settings),
  wired into Settings routing/nav; 409-in-use surfaces the server's
  consumer-list message.
- buildProjectModel: platform-resource dependencies now render as chain-link
  external nodes in the cell diagram, matching external/org-service deps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Ports the postgres-cnpg sample platform-resource ClusterResourceType
(hardened parameter schema: version/storage allowlists, instances cap) and
its data-plane RBAC grant from lab-app-factory verbatim, including the
load-bearing readyWhen CEL gate that holds ResourcesReady=False until the
CNPG Cluster's live status.phase reports "Cluster in healthy state" (OC's
unknown-Kind health inference would otherwise flip Ready=True at apply
time, before Postgres actually serves).

Wires both into setup-aep.sh (same insertion point as source, right after
the deployment/web-application ClusterComponentType), and adds a CNPG
operator install step to setup-prerequisites.sh (idempotent helm install
into cnpg-system, chart 0.29.0).

Bumps OPENCHOREO_VERSION 1.0.1-hotfix.1 -> 1.1.1: the Resource CRDs
(ResourceType/Resource/ResourceReleaseBinding/ClusterResourceType) that
postgres-cnpg depends on first ship in OC v1.1. aep-api's OC_SPEC_VERSION
stays pinned at v1.0.1-hotfix.1 since its client hand-rolls the Resource
types (A5) rather than generating them from the spec.

Also wires AEP_API_INTERNAL_BASE_URL (added in D5, previously only in
.env.example) into docker-compose.yml and the aep-api Helm Deployment
template, so agents-service can reach the BFF's internal MCP discovery
surface locally and in-cluster.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…der_api_call

The Thunder image shipped with OC 1.1.1 moved its bootstrap helpers to
/opt/thunder/bootstrap/ and gates the admin API — the unauthenticated
fallback curl 401s. Found during the G2 E2E stack bring-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
The Thunder image shipped with OC 1.1.1 locks its admin API (Bearer) after
its own bootstrap job; lift THUNDER_SKIP_SECURITY around the client
registration (trap-restored) and default THUNDER_API_BASE for in-pod calls.
Local-dev only. Found during G2 E2E bring-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…etry guard, spec-collect validation, docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…se change

An idempotent re-save of unchanged external-resource values hit the
409→PUT reconcile path with an identical spec; OC never cuts a new
release for an unchanged spec, so WaitForReleaseChange spun until the
poll timeout and stalled the /values endpoint (caught live in E2E S1).
Detect the no-op (canonical-JSON spec equality after Kind
normalisation), skip the PUT, and report create-equivalent semantics so
callers pin the existing release via wait-for-nonempty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…n setup

The control-plane chart only bootstraps this binding on a clean first
install; recovering from the 1.1.1 webhook-readiness race via helm
upgrade skips it and builds 403 at workload create. Port the source
repo's assertion so local setup is self-sufficient. Found live in G2
(weather-api build failed at generate-workload-cr).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…tity

The OC transport treated the request's MCP bearer (aud aep-api-mcp) as a
forwardable user JWT, so every OC-backed tool lookup 401'd and
list_org_endpoints silently returned an empty catalog — the consumer
architect couldn't discover org-services. Mark the tools/call context
with auth.WithServiceIdentity at the dispatch seam (covers all four
tools). Caught live in E2E S3. Includes a drive-by gofmt of
access_component_test.go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…search discovery fallback

Ports the architect agent into services/agents and mounts POST
/internal/v1/agents/architect with SSE frames byte-identical to
agents-legacy, so aep-api's StreamGenerateDesign cuts over by URL swap:
data-finish carries design.{overview,components} as models.DesignComponent
(componentType/openAPISpec), top-level error.errorText, [DONE] terminator,
x-vercel-ai-ui-message-stream:v1.

design.json parity is bound to THIS service's componentDesignSchema (the
single source of truth the main agent authors through) — no parallel schema;
status/reason never emitted. web_search is the Anthropic provider tool
(maxUses 4), gated on isAnthropicModel exactly as legacy and scoped to the
architect run, so the main agent's no-mcp tool set stays byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
Ports agents-legacy's generic document-generation route to services/agents,
wire-exact with aep-api's StreamDocumentGeneration client (client.go) and
requirements_service.go's frame consumer: POST
/internal/v1/agents/document-generation/{skillId} emits text-delta (delta,
optional replace) / finish (optional siblings) / error (errorText) frames,
terminated by [DONE] — the only chunk shapes aep-api and the console client
parse, so the route emits exactly those instead of the full AI SDK
UI-message-stream taxonomy legacy passes through verbatim.

aep-api sends only skillId + sources + prompt — never skill content — so all
8 document-generation skills (requirements-from-prompt,
functional/non-functional-requirements, user-stories, wireframes,
domain-model, component-design, component-openapi) are ported verbatim into
src/agents/document-generation/skills/. wireframes/domain-model's postProcess
now imports the shared @aep/excalidraw-dsl workspace package (the canonical
DSL->Excalidraw compiler) instead of a duplicated converter file, moved from
devDependencies to dependencies since it's now used in production src/.

Unknown skillId -> pre-stream 404; malformed body -> pre-stream 400 (both
read by aep-api's client.go as a non-200 "agents service error" body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…ose with shared compiler

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…ts service

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
… drift guard

- deployments/docker-compose.yml: agents-service build context -> repo root
  (`..` + dockerfile: services/agents/Dockerfile) so the image can resolve
  its workspace:* deps (@aep/excalidraw-dsl, @aep/design-projection); wire
  ANTHROPIC_API_KEY through (main.ts builds the model once at boot and the
  new @aep/agents package hasn't ported the legacy per-org resolver yet, so
  without it the container crash-loops regardless of the build fix).
- services/agents/Dockerfile: rewritten for the repo-root context —
  manifests-first layer (incl. tsconfig.base.json), scoped
  `pnpm install --filter @aep/agents...`, then build the two workspace deps
  @aep/agents consumes as compiled packages before starting via tsx.
- .dockerignore (new, repo root): keeps the agents-service build context sane
  (node_modules/dist/.git/etc.); scoped to root-context builds only.
- services/aep-api/skills/embed_test.go: guards the embedded
  planner/task-breakdown/SKILL.md against drifting from the repo-root
  skills/task-breakdown/SKILL.md it mirrors (go:embed can't reach outside the
  module), failing loud with a copy-paste-able fix.
- services/agents playground: buildPlanInput now pushes the task-breakdown
  skill's raw SKILL.md bytes (frontmatter included, via new
  evals/skills.ts#readSkillRaw), matching what aep-api's planner_skill.go
  pushes in production instead of the frontmatter-stripped loadSkill content.

Verified: docker compose build+up -d agents-service boots and serves
(curled from both the mapped port and the compose network); services/agents
typecheck+lint+tests and services/aep-api go vet + go test -short all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
…api design note

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30575424-7905-47d8-8c1f-aec23bc9c99a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aep-rewrite-kaj

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kaje94 kaje94 changed the title feat: dependency management (P1–P5) + new agent-service cutover feat: dependency management — external, cross-project, and platform-provisioned dependencies; new agent-service as default Jul 5, 2026
@kaje94 kaje94 marked this pull request as ready for review July 5, 2026 18:53
@kaje94 kaje94 requested a review from xlight05 July 5, 2026 18:53
@xlight05 xlight05 merged commit 1076be7 into aep-rewrite Jul 6, 2026
2 checks passed
xlight05 added a commit to xlight05/labs-agentic-engineer that referenced this pull request Jul 6, 2026
…laybook

Captures the accumulated design/ADR notes from the aep-rewrite line,
including docs/design/dependency-management-migration.md — the executable
playbook for integrating upstream PR wso2#85 (connections story).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xlight05 added a commit to xlight05/labs-agentic-engineer that referenced this pull request Jul 6, 2026
…write-improve

Phase 2 of the dependency-management migration (docs/design/dependency-management-migration.md).
Integrates upstream PR wso2#85 ("connections story") by MERGE (second parent = 1076be7) so future
`git merge upstream/aep-rewrite` sees the advanced common ancestor and stops re-surfacing these
323 files. This is the sole known-red checkpoint — brought green over Phases 3-4.

Resolution (per §4 grounded posture):
- keep-ours WHOLESALE: internal/feature/{task,execution,codingagent} + internal/contracts/taskmeta
  (our GitHub-native dispatch core), packages/contracts/api/v1/openapi.yaml (wso2#72 prompt/search/
  nextCursor kept; ComponentTask.type-required NOT taken). Temporarily-keep-ours (grafted later):
  design/{design_service,design_huma}.go + artifacts/artifact_store.go (P5), console types.ts (P3/P7),
  component-design-schema.ts + SKILL.md (P3).
- skip-delete (git rm): all upstream component_tasks-based files — codingagent/{resource_watcher,
  dispatch_service,dispatch_cascade_hook,+dbtests}, task/{board_service,handlers,task_diff,task_stream,
  planner_skill,+tests}, contracts/{dispatch,hooks,task_state}.go, models/{component_task,tasks}.go,
  testdata golden task JSON; services/agents-legacy/**; services/agents/src/agents/{architect,taskplanner,
  document-generation,dsl-render}/** (structured-output routes); the two task-coupled dependencies
  files (resources/{external_values,resources_service}) + resources_huma + endpoints/access_* (all
  rebuilt on our aep:provision funnel in P6).
- take-theirs (net-new, clean): internal/feature/dependencies/** AGNOSTIC subset (MCP server/tools,
  endpoints/{catalog,naming}, resources provisioner cores), clients/openchoreo/{resource_client,
  external_resource_type}, models/{external_resource,access_request,design,component}, artifacts/
  {design_json,spec_collect,commit_design_file}, platform/auth/agents_scoped, repositories/
  {external_resource,access_request}_repository, docs/**, skills/**, apps/console/** (accept-theirs).
- take-theirs-EDIT: phase9_dependency_mgmt.go (kept 2 CREATE TABLEs, stripped the ALTER component_tasks
  step — we have no such table), resources/ports.go (trimmed TaskStore/TaskCompleter/RedispatchFunc).

Expected-red: our code still uses connections[] vocab (P3 migrates), the dependencies feature is not
yet wired (P4), external_resource_repository's Consumers method still queries component_tasks (adapted
to design-scan in P6). No structured-output agents, no agents-legacy, no component_tasks ALTER, no wso2#72
removals leaked in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xlight05 added a commit to xlight05/labs-agentic-engineer that referenced this pull request Jul 6, 2026
… write path

Bring PR wso2#85's collect-dependency-spec route the rest of the way in. The SSRF
fetch / OpenAPI validate / normalize half was already ported (spec_collect.go);
only the commit was stubbed, deferred on "no committed-truth single-file write
surface" — which is now stale: files.FilesService.Apply (atomic apply → main,
per-file baseSha CAS) exists.

- design.CollectSpec: resolve the {component, dep} external target (unknown →
  404, non-external → 400), fetch (SSRF-guarded) when specUrl is set, validate +
  normalize, then commit TWO files in one atomic apply — the normalized spec at
  specs/design/components/<c>/dependencies/<dep>.openapi.yaml AND the component's
  design.json with the dependency's specPath recorded (+ the transient specUrl
  hint dropped). Recording specPath is what clears the external-needs-spec
  proceed-gate on the next read. Read-time status/reason are never persisted —
  the design.json codec (SplitDesign → dependencyJSON) omits them (ADR-0003).
- StoreConsumedSpec now returns the normalized blob (was discarded) so the design
  service can commit it; the validate/normalize logic stays in artifacts.
- Arch boundary held: design imports only artifacts. The Files commit surface
  reaches design through a narrow designFileCommitter port, adapted at the
  composition root (internal/app/design_adapters.go) over files.FilesService —
  no files import in the design feature.
- Route collect-dependency-spec (design_huma.go): POST .../components/{c}/
  dependencies/{dep}/spec, {rawSpec|specUrl} → {specPath}; error mapping
  404/400/502/409/500. Regenerated the code-first openapi.yaml (route +
  CollectSpecInput/OutputBody; wso2#72 fields retained). The console-legacy
  specs.ts ProvideSpec panel already targets this path.

Tests: CollectSpec (bad source, unknown target, wrong kind, invalid spec,
2-file atomic commit records specPath, commit-conflict → 409, nil committer).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xlight05 added a commit that referenced this pull request Jul 6, 2026
Dependency management on committed-truth + GitHub-native (PR #85 integration) + org-config consolidation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants