Skip to content

Marketplace dependency management: depend on other projects, external APIs, and platform infrastructure#63

Draft
kaje94 wants to merge 60 commits into
cloud-testfrom
feat/dependency-management
Draft

Marketplace dependency management: depend on other projects, external APIs, and platform infrastructure#63
kaje94 wants to merge 60 commits into
cloud-testfrom
feat/dependency-management

Conversation

@kaje94

@kaje94 kaje94 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

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_search tool 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 a platform-resource dependency. 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 OC Resource + per-env ResourceReleaseBinding, a watcher completes the task when the binding reports Ready, then the connection details (<DEP>_HOST/PORT/DBNAME/USER/PASSWORD) are wired into the consumer's workload.yaml — secret values via secretKeyRef, 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

  • Proceed-gate: draft autosave is always allowed, but save-and-proceed returns 409 while any dependency is unresolved / blocked / ambiguous — so a design can't advance to tasks with a dangling dependency.
  • Tasks board: on-hold tasks explain why they're waiting, and platform (SYSTEM) tasks surface a Provision / Provide configuration CTA that opens the drawer; deployed platform tasks move to Done; a board-split race that could hide a task is fixed.
  • Cleaner starting point: the requirements sketch preserves user-stated external systems (a real DB, an org service, an external API) instead of collapsing them into a self-contained app; the Go skill defaults persistence to a platform-provisioned database rather than embedded SQLite.

Verification

The external-API and platform-database chains are both verified live end-to-end on the local cluster:

  • External API: web_search discovery → spec stored in-repo (survives the tagged save) → 409 proceed-gate → agent implements against the stored contract → merge → build → deploy → live GET /weather?city=London returns real data → secret rotation verified (replace → ESO re-sync → restart → still serves).
  • Platform database: provision → CNPG Postgres reaches Ready → task completes → consumer wired → agent deploys → live POST /orders persists + psql confirms 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

  • Marketplace design docs under docs/design/marketplace/ are gitignored and not in this PR.
  • deployments/docker-compose.yml's local AGENT_RUNNER_IMAGE override is intentionally not committed (local-dev workaround).

🤖 Generated with Claude Code

kaje94 and others added 30 commits June 28, 2026 09:35
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
kaje94 and others added 28 commits June 30, 2026 17:39
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
@kaje94 kaje94 changed the title Marketplace dependency management: cross-project access requests (P3.5) + external API discovery & spec collection (P4) Marketplace dependency management: depend on other projects, external APIs, and platform infrastructure Jul 2, 2026
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.

1 participant