feat: dependency management — external, cross-project, and platform-provisioned dependencies; new agent-service as default#85
Merged
Conversation
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
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
…ming 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
…nt/reject 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
…ect hook 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
…-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
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
This was referenced Jul 6, 2026
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/agentsnow 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 endThe four dependency kinds
componentorg-serviceexternalplatform-resourcepostgres-cnpgvia CNPG operator,readyWhen-gated)Two load-bearing decisions (ADR-0003/0004 in
docs/decisions/):status/reasonare computed on every read against live platform state and are structurally unrepresentable indesign.json. No stale status, ever.workload.yaml. The platform never patches a deployed Workload CR;git checkoutreproduces 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 --> OCTask 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 threedepends_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/agentsnow 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 Anthropicweb_searchfallback 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-dslpackage.POST /internal/v1/agents/task-planner/{plan,detail}— renamed from "tech lead" (it plans, it doesn't manage); judgment lives in the new repo-rootskills/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-legacyis 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:
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: trueon 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.{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-gatedPOST /internal/v1/mcp/playground-tokenendpoint (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.connections[]in ComponentDesign with the unifieddependencies[](status/reason excluded from the schema by design). One divergence to decide together: the Go codec marks base scalars (exposuresharpest)omitemptywhile the TS schemas require them — either relax TS or commit aep-api to always populating them.Notable follow-ups
needsSpecdefault-true for unregistered externals (prevents hallucinated API paths)RetryTaskbackend guard for SYSTEM tasks; AutoMigrate-before-RunAll ordering (phase9's component_tasks section is currently shadowed by boot-time AutoMigrate — documented in-code)🤖 Generated with Claude Code
https://claude.ai/code/session_01HaM9hGtqua4ZC2bKD2d2Fq