Marketplace dependency management: depend on other projects, external APIs, and platform infrastructure#63
Draft
kaje94 wants to merge 60 commits into
Draft
Marketplace dependency management: depend on other projects, external APIs, and platform infrastructure#63kaje94 wants to merge 60 commits into
kaje94 wants to merge 60 commits into
Conversation
Provider-approved (GitHub task on the provider's repo); granting = a coding-agent publish task that adds namespace visibility; orgPublished set on completion for durability; AccessRequest record is the tracking/UX layer while the existing Phase-A gate handles functional resume. Captures the brainstorming decisions + the component-level changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
The marketplace design canon lives in docs/design/marketplace/ (git-excluded working docs), not docs/superpowers/. Relocate the P3.5 spec to access-requests.md alongside plan.md / declarative-wiring-refactor.md; add the not-found → P6 conversational-resolution seam. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
resolveOrgServices now refines an unresolved org-service into reason 'unpublished' (provider exists, project-only -> requestable) vs 'not-found' via a new catalog ExistsAnyVisibility; models.Dependency gains a read-time Reason field. Add the AccessRequest GORM model (+ AutoMigrate) and the access repo (Create/Get/List/FindOpenForTarget/UpdateStatus/ListByProviderTask). Additive — no endpoint/cascade/console yet. Part of P3.5 (docs/design/marketplace/access-requests.md). Increment 1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
The description contains `kind: component` (colon-space) which YAML read as a mapping → 'mapping values are not allowed in this context' → the builtin silently failed to parse + seed since B3 (the Phase B skills sync reported 'updated: 2' not 3). Quote the description value. Restores TestList_SeedsBuiltinsOnFirstRead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…5-2)
POST .../components/{c}/access-requests resolves the provider project from
the catalog (FindByComponent), and (idempotent on the target) creates an
org-publish ComponentTask + a purpose-built GitHub issue on the PROVIDER's
repo (BuildOrgPublishIssueBody: add namespace visibility to the deployed
component's workload.yaml), then records the AccessRequest. Dedup: N
consumers → 1 provider task/issue, N rows.
Part of P3.5. Increment 2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Grant: when a provider org-publish task lands deployed, the cascade flips
every AccessRequest riding on it to granted, sets exposesAPI.orgPublished
on the provider design (durability, via targeted WriteComponentDesign), and
closes the provider issue — all best-effort. Reject: an org-publish task's
PR closed-unmerged flips its AccessRequests to rejected (task webhook
handler). Consumer view: GET .../projects/{p}/access-requests. The consumer
component still resumes on its own Phase-A gate independently.
Part of P3.5. Increment 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Dependencies page now renders org-service deps: an unresolved+unpublished one shows a warning + Request access button (POST access-requests); once a matching AccessRequest exists it shows a status chip (pending/granted/denied) linking the provider issue; not-found shows a plain warning. New accessRequests API client; Dependency.reason on the design type. React Query mutation invalidates the design + access-requests queries. Part of P3.5. Increment 4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…list_org_endpoints The prompt's example showed a shortened name (employee-api) and called it 'the provider component name', so the LLM stripped the project prefix and emitted e.g. 'roster-api' instead of the catalog's 'org-roster-roster-api' — which resolves to not-found and can never be wired/requested. Direct the architect to copy the list_org_endpoints name verbatim (project-prefixed); fix the example to hr-directory-employee-api. Surfaced by the P3.5 E2E. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
ProjectDependenciesPage.tsx had 2 stray NUL bytes (template-literal key separators) that flipped git to binary mode (un-greppable, un-reviewable). And the AccessRequest type read providerIssueURL while the BFF serializes providerIssueUrl — so the 'View request' link never rendered. Both surfaced by the P3.5 UI E2E. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
… only) markOrgPublished's WriteComponentDesign → PutFile only atomicWrote to the local clone, never committing — so the durability flag was silently lost after a grant. Add CommitDesignFile (reuses the GitHub Data-API commit path GetRef→CreateBlob/Tree/Commit→UpdateRef, no version tag) + ArtifactStore SetComponentOrgPublished (idempotent, service-identity cred via the same resolver SaveDesign uses); markOrgPublished now calls it. Surfaced by the P3.5 E2E (no design commit landed after the grant). Part of P3.5. Fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Compute `unresolved` at read time (assembleDependencies) when an external dependency declares needsSpec=true but has no specPath set. Broaden the SaveAndProceed gate to reject ANY dependency with Status=="unresolved", not just org-service, by checking the assembled design before calling SaveDesign. Adds ErrUnresolvedDependency sentinel (409 at the controller). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Introduces the `blocked`/`access-required` state for project-only org-service deps (previously `unresolved`/`unpublished`). Adds `reason:"needs-spec"` to external deps with needsSpec+no-specPath. Corrects the SaveAndProceed gate to block on ambiguous|unresolved|blocked (not just unresolved). Draft autosave (UpdateDesignFile) is explicitly NOT gated. TDD: 6 new tests (a–f). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…oceed supersedes it) Under A2b (block-at-proceed) a consumer task can only be dispatched once its org-service deps are `resolved` (namespace-visible in the catalog). The old orgServiceGates / orgServiceVisible On-Hold check in depsAllDeployed was therefore a redundant double-gate. This commit: - Removes the orgServiceGates method (the per-sweep catalog resolver). - Removes the orgServiceVisible parameter from depsAllDeployed and its org-service branch; same-project component and connection gating are unchanged. - Adds dispatch_gating_test.go with 10 unit tests, including a dedicated A2c regression test (TestDepsAllDeployed_OrgServiceDepDoesNotGate) and tests confirming same-project component gating still holds. DependsOnOrgServices is retained on ComponentTask rows and used by resolveConsumerDependenciesYAML (workload.yaml dep-comment rendering). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…te depName - FetchSpecFromURL: resolve host once, validate all IPs, dial the validated IP directly (net.JoinHostPort) — eliminates TOCTOU DNS-rebinding window. - FetchSpecFromURL: add CheckRedirect that rejects non-https targets and caps hops at 5 — closes https→http downgrade-via-redirect vector. - StoreConsumedSpec: reject depName containing '/', '\', or '..' before constructing file paths (path-traversal defense-in-depth). - Tests: assert normalized op-count equals original (3) in PathAndCount test; add TestStoreConsumedSpec_RejectsPathTraversalDepName (5 sub-cases). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…/url)
POST /api/v1/organizations/{org}/projects/{proj}/components/{comp}/dependencies/{dep}/spec
accepts rawSpec (paste) or specUrl (HTTPS fetch); validates the OpenAPI
document, commits the spec blob via StoreConsumedSpec, then records
specPath on the dependency frontmatter (SetDependencySpecPath) so the
needsSpec gate clears on next design read. Returns { specPath, operationCount }.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Introduce ErrInvalidSpecContent sentinel in artifacts/spec_collect.go (wraps depName path-traversal + ValidateOpenAPI failures). Introduce ErrInvalidSpec in design_service.go; CollectSpec re-wraps ErrInvalidSpecContent as ErrInvalidSpec so the HTTP handler can distinguish validation (400) from infra failures (500). Handler now maps: ErrSpecFetchFailed→502, ErrInvalidSpec→400, else→500. Add two new tests: invalid-OpenAPI-doc→ErrInvalidSpec, commit-failure→infra (not ErrInvalidSpec, not ErrSpecFetchFailed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
… for externals
- schema.ts: add needsSpec + specUrl (both optional) to the external variant of the Dependency discriminated union; backwards-compatible with existing designs
- run.ts: inject anthropic.tools.webSearch_20250305({ maxUses: 4 }) into the merged tools alongside extraTools; Anthropic-only guard noted in comment (unconditional today, model.ts hard-wires createAnthropic)
- prompt.ts: add External dependency discovery section instructing the architect to call list_connections first, then web_search for new externals, classify REST/GraphQL (needsSpec:true, specUrl hint, config from securitySchemes) vs SaaS-SDK (omit needsSpec, name package+version), emit status:unresolved when needsSpec with no specUrl, and always populate candidates[]
- architect.eval.ts: add offline schema-level unit test asserting needsSpec/specUrl validate on the external variant (backwards-compat + new fields); full model eval deferred to paid ANTHROPIC_API_KEY run
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
When the architect emits an external dep with needsSpec:true and a specUrl hint, SaveAndProceed now auto-fetches the spec via an injectable fetchSpec func (defaults to artifacts.FetchSpecFromURL) before the unresolved-dep gate runs. Successful fetch calls CollectSpec (validate+normalize+commit spec blob + record specPath on design.md) and clears specUrl/status/reason so the gate sees the dep resolved. Fetch or CollectSpec failure is non-fatal: a warning is logged and the dep stays unresolved (user provides spec manually). SpecUrl round-trips through dependencyConfig + both converters so the hint survives the generate→save hop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Surface each external dependency's stored OpenAPI contract path in the component task's GitHub issue body, and add a matching instruction in SKILL.md so the coding agent implements the client against the exact spec operations rather than guessing endpoints. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
… page Adds DependenciesSection (one status row per component×dependency) and DependencyDrawer shell to the architecture page; updates Dependency type to the P4 4-state model (resolved/ambiguous/unresolved/blocked + reason enum); wires vitest + @testing-library/react for the architecture/ tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Implements B2 of marketplace P4: fills the DependencyDrawer body for org-service deps using the 4-state model (blocked/access-required → warning + Request access button; access-pending → status chip + provider issue link; unresolved/not-found → plain warning). Ports OrgServiceCard + AccessRequestAffordance logic from ProjectDependenciesPage into the new OrgServiceResolution component; uses correct providerIssueUrl casing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Adds ConnectionValues.tsx — a drawer-embedded form that replaces the now-retired ConnectionValuesDialog from ProjectDependenciesPage. Users can enter per-environment config/secret values for external dependencies directly from the architecture drawer, with disabled tab placeholders for staging/production. Secrets start blank on every open (write-only, rotation semantics). DependencyDrawer now renders ConnectionValues below any spec affordance for external deps with config keys, so B3 and B4 coexist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…tecture-page drawer) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…note Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…s, drawer refresh, SSRF CGNAT - FIX 1: map ErrUnresolvedDependency → HTTP 409 in save-and-proceed handler (was falling through to 500); add handler-level test asserting the 409 mapping and the gate message in the response body. - FIX 2/3: update Dependency.Reason doc-comment in models/design.go to enumerate all reasons with platform-computed vs client-derived taxonomy; access-pending is client-derived only, never emitted by resolveOrgServices. - FIX 4: add useEffect in ProjectArchitecturePage to re-derive activeDep from fresh effectiveComponents after onChanged refetches the design, so the open drawer reflects updated state (e.g. org-service after access request is created). - FIX 5: correct stale file-path comment in console/src/services/api/specs.ts (dependencies/spec_huma.go → design/design_huma.go). - FIX 6: update dispatch_cascade_hook.go comment to reflect block-at-proceed model (A2c); the retired org-service On-Hold gate is no longer mentioned. - FIX 7: add CGNAT 100.64.0.0/10 rejection to FetchSpecFromURL SSRF guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…arate commit (was tombstoned on save) StoreConsumedSpec and SetDependencySpecPath previously called CommitDesignFile, writing directly to remote HEAD. saveDesignViaAPI diffs the working tree against HEAD — so the spec file landed in HEAD-but-not-working-tree and was emitted as a DELETE tombstone on the next design save. Switch both methods to WriteDesignFile (→ PutFile, working-tree draft) so the spec blob and updated design.md are part of the draft and committed atomically by the v<N>-<M> SaveDesign call. SetComponentOrgPublished (P3.5 provider durability) is intentionally unchanged — it still uses CommitDesignFile to durably persist orgPublished on remote main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Records the P5 seam decision (direct OC Resource authoring; wso2cloud#638 reserved) and appends the dependency/platform-resource glossary to CONTEXT.md (additive). ADRs 0001-0006 stay on feature/marketplace; README points to them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…esource_types MCP tool) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
- Nil-guard list_platform_resource_types: if resourceTypes is nil, return
empty {"resourceTypes":[]} instead of panicking on nil pointer dereference.
- Nil-guard list_org_endpoints: if catalog is nil, return empty {"endpoints":[]}
mirroring the same pattern.
- mcp_server_test.go: new unit test that exercises the nil resourceTypes
contract via httptest — confirms no panic, success response, empty list.
- catalog_test.go: assert got[1] (redis-cnpg, no-spec zero-value path) has
correct name, nil Parameters, and empty Outputs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…+binding against a discovered ClusterResourceType, async) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
… + per-resource task generation Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…resource-provisioning task in-flight Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…ByProjectID errors GetBinding now returns (nil, nil) on HTTP 404 (mirrors DeleteBinding), so GetStatus no longer 500s during the window between POST-provision and the first watcher sweep. ListByProjectID errors are now logged via slog.WarnContext instead of being silently swallowed. Two new handler-level tests cover both paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…g on OC binding Ready + cascades Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
… honest test names + dbtest gate Fix 1: TestResourceWatcher_Sweep_NilBinding_LeavesTask now constructs a real ResourceWatcher (db=nil, fakeResourceClient returning nil binding) and calls w.handleTask() directly, asserting no panic, no redispatch, and status unchanged. The nil-binding branch never touches w.db (classifyBinding(nil)→(false,false)→ default), so db=nil is safe. Fix 2: Rename TestResourceWatcher_HandleTask_ReadyBinding_CallsRedispatch → TestClassifyBinding_ReadyBinding_ReturnsDone — the test only asserts classifyBinding returns (true,false); redispatch is not exercised here. Fix 3: Correct the dbtest stub comment — body is an incomplete placeholder (t.Skip only, no assertions); updated comment removes the false "-tags dbtest" instruction and honestly describes it as a future Postgres-backed placeholder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…ec.dependencies.resources[] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…en accessor Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…g, masked outputs, re-provision Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…ful secret-safety test
- Fix 1: add defensive 'deployed' to query enabled guard (dep.status union
has no 'deployed' — that's TaskStatus — but guard is future-proof); verify
resolved dep with getResourceStatus→{status:'deployed',ready:true} reaches
the provisioned view, not the fallback spinner (new test).
- Fix 2: replace vacuous secret-safety test with a fixture-driven assertion:
render outputs [{name:'DB_HOST'},{name:'DB_PASSWORD'}], assert exactly those
two name strings appear, confirm no value text leaks.
- Fix 3: remove redundant inputProps aria-label on param TextField; label={k}
already provides the accessible name; getByLabelText(k) tests still pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Add CNPG_VERSION="0.29.0" pin to env.sh and a step 7 block in setup-prerequisites.sh that idempotently installs the CloudNativePG operator (chart 0.29.0 / appVersion 1.30.0) into cnpg-system. This provides the clusters.postgresql.cnpg.io CRD needed by the postgres-cnpg ClusterResourceType authored in C2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…provisioner) Adds the sample platform-resource ClusterResourceType 'postgres-cnpg' that renders a CloudNativePG Cluster, plus the minimal RBAC its data-plane applier needs, and wires both into setup-asdlc.sh. Live-tuned on-cluster until a real Postgres provisioned and the OC ResourceReleaseBinding reached Ready=True with outputs resolved: - readyWhen OMITTED: OC 1.1.1 freezes only a minimal subset of a foreign CRD's status into applied.<id>.status (no .phase / .conditions), so a CEL readyWhen raises 'no such key'. Rely on OC per-Kind health inference instead. - RBAC grants postgresql.cnpg.io/clusters to the DATA-PLANE agent SA (cluster-agent-dataplane), the actual applier into the dp-* namespace -- not the control-plane controller-manager. Secrets reused from its existing grant. - outputs: host/port/dbname as values; user/password as secretKeyRefs into the CNPG-generated <cluster>-app secret (keys username/password). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…visioning params Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Anthropic maps a model-level safety refusal to finishReason "content-filter" — the model declines the input, emits ~no output, and never calls finish()/finalize(). The requirements-chat and architect routes then fell through to their generic "agent ended without calling finish/finalize" error, so a legitimate refusal read as an infra/agent fault (observed as `finish=content-filter steps=1 in≈4000 out=1-3`). Bisected via the live route: reconstructed the exact request (system + buildUserPrompt + buildTools) — benign prompts return tool-calls/stop normally; only inputs Anthropic refuses return content-filter, with stop_reason:"refusal" in the raw API response. Disabling the structured-outputs-2025-11-13 beta (supportsNativeStructuredOutput:false) did NOT change the refusal, ruling out the SDK beta / a version regression. Fix: detect finishReason === "content-filter" on the non-finalized fallthrough and emit a clear, actionable message telling the user to rephrase, in both requirements-chat and architect (finishReason is now threaded out of runArchitect). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…needs
The pre-P5 "no separate storage/database/persistence component" anti-pattern
was overriding A1's buried platform-resource guidance, so the architect folded
persistence into the component and emitted no dependency. Add an explicit
TRIGGER ("needs to persist data → emit a platform-resource dependency") and
reconcile the anti-pattern (no DB *component*, but the datastore IS a
platform-resource *dependency* on the owning component).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…wrapped)
The default `auto` picks Anthropic's native structured-outputs beta for
sonnet-4-5, which HANGS streamObject in the tech-lead planner (the object never
finalizes) → task generation never completes. Force structuredOutputMode
"jsonTool" (the stable path) + wrap the top-level PlanArraySchema in an object
(PlanResultSchema {tasks:[...]}) since a tool input_schema must be `type:object`
(Anthropic rejects a top-level array). Stream/final read `.tasks`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…ization A5 watcher: OC emits binding reason "ResourcesProgressing" (compound) while a CNPG Postgres is "Setting up primary"; the progressingReasons whitelist only had bare "Progressing", so the still-provisioning task was mis-marked FAILED. Match progressing substrings (Progressing/Pending/Waiting/Creating/…) + regression test. A6 consumer wiring: the env-var name used the kebab-case dep name verbatim (orders-db → "ORDERS-DB_HOST"), an invalid k8s C_IDENTIFIER (hyphen) that fails the deploy. Sanitize non-[A-Za-z0-9_] → '_' via envVarName (→ "ORDERS_DB_HOST"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…BA sketch The requirements-from-prompt "business analyst" prompt reduces a project prompt to a non-technical Overview/Personas/Features sketch, with a hard "no tech stack / no architecture / no non-functional concerns" rule. That rule was over-firing: when a user EXPLICITLY mandates an external dependency (a platform-provisioned database, consuming another project's org-service, calling an external API with a key), the model classified it as "tech stack" and dropped it — so the architect (which designs against the requirements only, never the original prompt) never saw the dependency and built a self-contained app instead (embedded SQLite, hardcoded rates, self-seeded data). Add a carve-out: user-stated external systems + data-ownership boundaries are product facts, not tech details, and MUST be preserved as plain feature bullets; this overrides the "no tech stack" rule (which is only about UNREQUESTED impl choices). Triggers only when the user names an external dependency, so ordinary prompts are unaffected. Also fix a stray "ho" typo in the Personas section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
A component task could silently disappear from the kanban board. Root cause: the GitHub Project board is created lazily, per-issue, with a check-then-act race (issue_service.go) — task generation creates several issues concurrently, each sees an empty GithubProjectID and creates its OWN board, so the project's issues get split across two boards and only one id is persisted. The board reader shows only the persisted board, so issues stranded on the orphan board (e.g. order-api) never appear. Two fixes: - Race: serialize lazy board creation per project (sync.Map of mutexes) and re-read GithubProjectID under the lock, so a concurrent creator reuses the just-persisted board instead of making a second one. - Reconcile (defense-in-depth + unblocks already-split projects): the board builder now surfaces any DB task that has an IssueURL but is absent from the canonical Project board, routed by its authoritative ComponentTask status — matched by neither the primary loop nor the unissued path, it previously vanished. Whole-DB fallback still covers the zero-items case. Tests: reconcile surfaces an off-board on_hold task (replicates the observed split) without double-counting on-board tasks; the per-project lock grants at most one holder per project and does not block across projects (-race). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
The tasks board left two dead ends for dependency-gated projects:
- An on_hold component task gated ONLY by a resource / org-service / connection
(not a sibling component) showed no reason — TaskRow's "Waiting for…" read
only dependsOnComponents, and the board payload didn't even carry the other
gate kinds.
- resource-provisioning ("Provision resource: …") and config-collection
("Provide configuration: …") tasks showed "Execute Now", which calls the
/tasks/{id}/exec endpoint — a no-op stub (task_service.go: "just logs and
doesn't perform any actions"). Clicking it did nothing; the real action lives
in the architecture-page drawer.
Fixes:
- Board payload now carries dependsOnResources/OrgServices/Connections, the
task type, and the resource/connection name (BoardTask + a boardTaskFromCT /
applyCTFields helper that DRYs the four task-building sites).
- TaskRow's On Hold reason lists every gate kind, annotating resource/connection
gates with the action needed ("needs provisioning" / "needs configuration").
- TaskDetailPanel replaces the no-op Execute Now for resource-provisioning /
config-collection with a "Provision resource" / "Provide configuration" CTA
that deep-links the architecture drawer (?dep=<name>); the architecture page
reads that param and opens the dependency's drawer.
Tests: board surfaces all gate kinds + SYSTEM task type/dep-name.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
The "Provision resource" / "Provide configuration" CTAs navigated to the architecture page to reach the resolution drawer — a jarring context switch out of the task view. The DependencyDrawer is self-contained (depRef + org/project + onClose/onChanged), so render it directly from TaskDetailPanel instead: on click, read the design to resolve the task's dep name to the full dependency object, then open the drawer as an overlay on the tasks page. No navigation. onChanged re-reads the design so the open drawer reflects the fresh dependency. Docs: record the concrete exchangerate config inputs for the storefront scenario (ExchangeRate-API.com → EXCHANGERATE_BASE_URL + EXCHANGERATE_API_KEY), noting the keys are whatever the architect emitted and to read them off the drawer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…ource node - Board: resource-provisioning / config-collection tasks have no GitHub issue, so they flow through the unissued path — which routed purely by lifecycle and pinned them to Todo even once deployed (showing a "deployed" label in the wrong column). Route unissued tasks by their own status: deployed → Done, building/ in_progress → In Progress, failed → Failed, on_hold → On Hold, else Todo. - Cell diagram: buildProjectModel filtered external nodes to external + org-service only, so a platform-resource (provisioned db/cache/queue) never rendered. Include platform-resource — it shows as a chain-link node like an external API/org-service. Tests: a deployed unissued SYSTEM task lands in Done; a pending one stays in Todo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…il provisioned Two bugs in the tasks-board → dependency-drawer flow, found + verified live: 1. Drawer never opened. TaskDetailPanel.openDrawer looked up the design component by task.componentName, but a resource-provisioning / config-collection task's componentName is the resource/connection NAME (orders-db / exchangerate), not the owning component — so the lookup found nothing and the error was swallowed. Search for the dependency by name across all components instead. 2. PlatformResourcePanel showed "Provisioned ✓" + "Re-provision" on an UNprovisioned resource, because it treated dep.status === 'resolved' (architect matched a resource type) as provisioned. Drive the panel from the real status (getResourceStatus → resource-provisioning task status + binding readiness): a loading check while it fetches, "Provisioning…" while building, "Provisioned ✓" only when the binding is Ready / task deployed, and otherwise the "Provision" form. So a resolved-but-unprovisioned resource now shows a Provision button. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
…ded SQLite
The `go` builtin skill predated P5 (platform-resource provisioning) and told
both the architect and tech-lead to default every Go component to embedded
modernc.org/sqlite ("Apply to every Go component"; "persistence embedded only —
there is no external Postgres for v1"). That directly conflicts with a
platform-resource database dependency: the architect declares a provisioned
Postgres, but the skill still injects SQLite guidance, so a design ends up with
BOTH — and the agent may write to an unused embedded /data/*.db instead of the
provisioned db.
Reconcile with the platform-resource model:
- Persistence default is now the platform-provisioned database (a
platform-resource dependency), reached via the injected
<DEP>_HOST/PORT/DBNAME/USER/PASSWORD env vars (database/sql + lib/pq/pgx).
- Embedded modernc.org/sqlite is now OPT-IN — used only when the design
explicitly calls for lightweight local/embedded storage.
- Updated the description, "what this skill does", platform facts, architect
guidance, and the tech-lead issue-body bullet accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
Deleting a project left its provisioned database orphaned on the cluster. Root cause: DeleteProject cleaned up the OC Project + git repo + tasks, but the OC Project delete does NOT cascade to platform-resources — the Resource/binding carry only a logical spec.owner.projectName, not a k8s ownerReference — so the Resource + ResourceReleaseBinding + the CNPG Postgres survived every delete. - resources: add OCNativeProvisioner.Deprovision (to the ResourceProvisioner seam) — deletes per-env bindings (retainPolicy cascades the rendered CNPG Cluster + generated Secret/PVC), then the Resource; best-effort + 404-tolerant. - project: DeleteProject now enumerates the project's resource-provisioning tasks (before deleting them) and Deprovisions each, via a narrow resourceDeprovisioner port + SetResourceDeprovisioner setter wired in main.go. Best-effort/non-fatal, mirroring the connections teardown. - test: Deprovision deletes <project>-<dep>-<env> bindings then <project>-<dep>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue1YXBAxKXEWaYuv6LYVpg
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.
Marketplace dependency management — depend on other projects, external APIs, and platform infrastructure
This PR lets a project declare a dependency, resolve it from the architecture view, and have the platform provision/wire it all the way through to a running deployment — no hand-editing of YAML, connection strings, or secrets. It builds on the earlier marketplace work already on
cloud-test(a project can model dependencies and consume another project's public service). 60 commits.A component can now depend on three kinds of thing, and each follows the same lifecycle — discover → resolve → provision/wire → inject as runtime config:
1. Another project's service
The architect discovers services published across the org. If the target is exposed project-only, the consumer can request access from its own design view: this opens a task + GitHub issue on the provider's repo; on grant, the platform publishes the endpoint, closes the issue, and records the grant durably (
exposesAPI.orgPublished). The cross-project URL is then resolved and injected into the consumer.2. An external API
The architect discovers the API via a
web_searchtool and flags whether it needs an OpenAPI contract. The BFF fetches (SSRF-hardened), validates, normalizes, and stores that spec inside the consumer's repo, and the coding agent implements the client against that exact contract. The API key is entered once in the drawer, stored as a secret, and injected by name only.3. Platform-provisioned infrastructure (e.g. a database)
The architect discovers what the platform can provision (a read-only catalog over
ClusterResourceTypes) and emits aplatform-resourcedependency. Provisioning goes through a narrow provisioner seam — an OC-native path (built now, with a sample CloudNativePG Postgres) behind an interface a future cloud provisioning-API can swap in. Provisioning is asynchronous: the BFF authors the OCResource+ per-envResourceReleaseBinding, a watcher completes the task when the binding reportsReady, then the connection details (<DEP>_HOST/PORT/DBNAME/USER/PASSWORD) are wired into the consumer'sworkload.yaml— secret values viasecretKeyRef, never in the diff. Credentials can be rotated (re-provision), and deleting the project tears the database down instead of orphaning it.One place to resolve everything — the dependency drawer
All resolution (pick-a-candidate / provide-a-spec / request-access / provision), plus per-environment value entry and secret rotation, now live in a single side drawer on the architecture page. The standalone Dependencies tab is retired. The same drawer opens in place from the tasks board when a task is waiting on a value or a provision.
Lifecycle correctness
409while any dependency is unresolved / blocked / ambiguous — so a design can't advance to tasks with a dangling dependency.Verification
The external-API and platform-database chains are both verified live end-to-end on the local cluster:
web_searchdiscovery → spec stored in-repo (survives the tagged save) → 409 proceed-gate → agent implements against the stored contract → merge → build → deploy → liveGET /weather?city=Londonreturns real data → secret rotation verified (replace → ESO re-sync → restart → still serves).Ready→ task completes → consumer wired → agent deploys → livePOST /orderspersists +psqlconfirms rows in the real Postgres, order total converted via the wired external rate API. Full Go suite + console build/tests green.Security
The SSRF-guarded spec fetch received a platform-design-expert review: TOCTOU-safe single DNS resolution, https-only, IP denylist (incl. CGNAT / NAT64
64:ff9b::/96),!IsGlobalUnicast()gate, redirect + size/timeout caps, resolved IP dropped from errors, and a YAML alias-bomb regression test.Notes
docs/design/marketplace/are gitignored and not in this PR.deployments/docker-compose.yml's localAGENT_RUNNER_IMAGEoverride is intentionally not committed (local-dev workaround).🤖 Generated with Claude Code