Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
20c0b09
docs(p3.5): cross-project access-requests design spec
kaje94 Jun 28, 2026
488106e
docs(p3.5): move access-requests design into docs/design/marketplace
kaje94 Jun 28, 2026
aeee277
feat(p3.5): reason split + AccessRequest data layer (P3.5-1)
kaje94 Jun 28, 2026
fa2857f
fix(skills): quote api-management description (unbreaks frontmatter)
kaje94 Jun 28, 2026
cdfe7cb
feat(p3.5): access-request endpoint + cross-project publish task (P3.…
kaje94 Jun 28, 2026
c64f414
feat(p3.5): grant/reject status sync + consumer GET (P3.5-3)
kaje94 Jun 28, 2026
eadc0bc
feat(console): cross-project access-request UX (P3.5-4)
kaje94 Jun 28, 2026
a325b69
fix(architect): use the exact project-prefixed org-service name from …
kaje94 Jun 28, 2026
9dbeceb
fix(console): strip null bytes + fix providerIssueUrl casing (P3.5 UX)
kaje94 Jun 28, 2026
ae3095d
fix(p3.5): commit the orgPublished durability write (was working-tree…
kaje94 Jun 28, 2026
b02ac2a
feat(marketplace): persist needsSpec/specPath on external dependencies
kaje94 Jun 29, 2026
4aea349
feat(marketplace): gate save on external deps needing a spec
kaje94 Jun 29, 2026
92fb048
feat(marketplace): 4-state resolution model (blocked) + proceed-gate
kaje94 Jun 29, 2026
a74d844
refactor(marketplace): retire org-service On-Hold gating (block-at-pr…
kaje94 Jun 29, 2026
ae9f50b
feat(marketplace): OpenAPI spec fetch/validate/normalize/store pipeline
kaje94 Jun 29, 2026
b036447
fix(marketplace): harden spec-fetch SSRF (TOCTOU, redirects) + valida…
kaje94 Jun 29, 2026
221ddd8
feat(marketplace): endpoint to collect a consumed OpenAPI spec (paste…
kaje94 Jun 29, 2026
ad04dab
fix(marketplace): map spec-collect storage failures to 500 (not 400)
kaje94 Jun 29, 2026
80dfe83
feat(marketplace): architect web-search discovery + needsSpec/specUrl…
kaje94 Jun 29, 2026
88e1330
feat(marketplace): auto-fetch+store architect-discovered specs on save
kaje94 Jun 29, 2026
60d738c
feat(marketplace): coding agent consumes stored external API specs
kaje94 Jun 29, 2026
f9ea919
feat(console): dependencies section + drawer scaffold on architecture…
kaje94 Jun 29, 2026
e0f87b9
feat(console): org-service request-access resolution in the drawer
kaje94 Jun 29, 2026
34e7c79
feat(console): provide-API-spec affordance in the drawer
kaje94 Jun 29, 2026
321c5b4
fix(console): wire specs token accessor so collectSpec is authenticated
kaje94 Jun 29, 2026
94a3094
feat(console): per-env value entry + rotation in the drawer
kaje94 Jun 29, 2026
6cb3f48
refactor(console): retire the Dependencies tab (folded into the archi…
kaje94 Jun 29, 2026
10a8ba5
fix(console): replace stale B3–B4 placeholder with platform-resolved …
kaje94 Jun 29, 2026
2c83409
fix(marketplace): final-review fixes — proceed-gate 409, taxonomy doc…
kaje94 Jun 29, 2026
f610440
fix(marketplace): store consumed specs in the design draft, not a sep…
kaje94 Jun 29, 2026
37d130e
fix(console): make the dependency drawer surface opaque (was transluc…
kaje94 Jun 29, 2026
6c2af02
fix(marketplace): SSRF review fixes — block NAT64 + tighten to global…
kaje94 Jun 29, 2026
4044456
docs(p5): ADR-0007 platform-resource provisioning + dependency glossary
kaje94 Jun 30, 2026
df65eb0
feat(p5): platform-resource-type discovery (catalog + list_platform_r…
kaje94 Jun 30, 2026
f73e2bd
fix(p5): nil-guard MCP catalog tool cases + assert second catalog entry
kaje94 Jun 30, 2026
1aa2080
feat(p5): ResourceProvisioner seam + OC-native impl (authors Resource…
kaje94 Jun 30, 2026
3c4efb9
feat(p5): resource-provisioning task type — DependsOnResources gating…
kaje94 Jun 30, 2026
ff056d3
feat(p5): async provision endpoint — authors Resource+binding, marks …
kaje94 Jun 30, 2026
b69dbbc
fix(p5): GetStatus tolerates not-yet-created binding (404) + log List…
kaje94 Jun 30, 2026
8a0a314
feat(p5): resource readiness watcher — completes resource-provisionin…
kaje94 Jun 30, 2026
d44841c
test(p5): make resource-watcher nil-binding test exercise handleTask;…
kaje94 Jun 30, 2026
360735e
feat(p5): consumer wiring — render platform-resource into workload.sp…
kaje94 Jun 30, 2026
eca0c77
feat(p5): console provisioning client + dependency outputs type + tok…
kaje94 Jun 30, 2026
2921c31
feat(p5): PlatformResourcePanel — provision form, async status pollin…
kaje94 Jun 30, 2026
e508673
fix(p5): PlatformResourcePanel robust ready-state rendering + meaning…
kaje94 Jun 30, 2026
da841cb
feat(p5): install CloudNativePG operator in local cluster setup
kaje94 Jun 30, 2026
5cfb8ca
feat(p5): sample postgres-cnpg ClusterResourceType (CNPG-backed real …
kaje94 Jun 30, 2026
04f648a
fix(p5): restore guarded readyWhen on CNPG Cluster health + bound pro…
kaje94 Jun 30, 2026
481cc59
fix(agents): surface Anthropic safety refusals instead of opaque errors
kaje94 Jul 1, 2026
b5ba433
fix(p5): architect prompt — emit platform-resource dep for datastore …
kaje94 Jul 1, 2026
ca58338
fix(p5): tech-lead task-gen — use jsonTool structured output (object-…
kaje94 Jul 1, 2026
53fcd23
fix(p5): resource watcher progressing-reason + consumer env-var sanit…
kaje94 Jul 1, 2026
2270078
fix(requirements): preserve user-stated external dependencies in the …
kaje94 Jul 1, 2026
6d46d94
fix(board): stop tasks vanishing when the GitHub Project board splits
kaje94 Jul 1, 2026
8f96cd1
fix(tasks): explain on-hold reason + route SYSTEM tasks to the drawer
kaje94 Jul 1, 2026
a3e0659
fix(tasks): open the dependency drawer in place, not via navigation
kaje94 Jul 1, 2026
1eff93f
fix(board+diagram): move done SYSTEM tasks to Done; show platform-res…
kaje94 Jul 1, 2026
4c2aa56
fix(tasks/drawer): open drawer by dep-name lookup; show Provision unt…
kaje94 Jul 1, 2026
75ad45e
fix(go-skill): default persistence to platform-resource DB, not embed…
kaje94 Jul 1, 2026
fe2a3fe
fix(p5): tear down platform-resource databases on project delete
kaje94 Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions .superpowers/sdd/final-fixes-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Final-Review Fixes Report — marketplace P4

## FIX 1 — proceed-gate must return 409, not 500

**File changed:** `asdlc-service/internal/feature/design/design_huma.go` (line ~207)

Added before the catch-all 500 in the `save-design` handler:
```go
if errors.Is(err, ErrUnresolvedDependency) {
return nil, huma.Error409Conflict(err.Error())
}
```
`errors` was already imported. The `ErrUnresolvedDependency` doc-comment in `design_service.go` already stated "surfaced (as 409 by the controller)" — now true.

**New handler test file:** `asdlc-service/internal/feature/design/design_huma_test.go`

Two tests added:
- `TestSaveAndProceedHandler_UnresolvedDependency_Returns409`: wraps the sentinel with `fmt.Errorf("%w: ...", ErrUnresolvedDependency, ...)`, drives the Huma handler via `humatest`, asserts 409 and the gate message in the response body.
- `TestSaveAndProceedHandler_SpecNotApproved_Returns409`: regression guard for the existing ErrSpecNotApproved → 409 mapping.

**Test result:** PASS (`go test ./internal/feature/design/... → ok`)

---

## FIX 2/3 — access-pending / Reason taxonomy doc alignment

**File changed:** `asdlc-service/models/design.go` (Reason field doc-comment, lines ~60-69)

Updated to enumerate ALL reasons with their origin:
- Platform-computed: `access-required`, `not-found`, `needs-spec`
- Client-derived (NOT emitted by platform): `access-pending` (console derives from in-flight AccessRequest), `needs-input`
- `""` (n/a or resolved)

`resolveOrgServices` behavior unchanged — comment only.

---

## FIX 4 — open drawer shows stale dep after onChanged

**File changed:** `console/src/pages/ProjectArchitecturePage.tsx`

Added a `useEffect` keyed on `effectiveComponents` (after the `const effectiveComponents = ...` line):
```tsx
useEffect(() => {
if (!activeDep) return;
for (const comp of effectiveComponents) {
if (comp.name === activeDep.component) {
const freshDep = comp.dependencies?.find((d) => d.name === activeDep.dependency.name);
if (freshDep) {
setActiveDep({ component: activeDep.component, dependency: freshDep });
}
return;
}
}
}, [effectiveComponents]);
```
When no match is found (dep removed), `activeDep` is left as-is — no crash. Drawer props/contract unchanged.

**Build result:** `pnpm run build` ✓ — `pnpm vitest run src/pages/architecture/` → 4 files, 19 tests PASS

---

## FIX 5 — stale doc reference

**File changed:** `console/src/services/api/specs.ts` (line ~23)

Changed:
```
See asdlc-service/internal/feature/dependencies/spec_huma.go (A4).
```
to:
```
See asdlc-service/internal/feature/design/design_huma.go (A4).
```

---

## FIX 6 — stale comment in dispatch_cascade_hook.go

**File changed:** `asdlc-service/internal/feature/codingagent/dispatch_cascade_hook.go` (~line 197)

Updated the comment from "consumer resumes on its OWN org-service gate independently" to reflect the block-at-proceed model (A2c): "The consumer cannot proceed past the save-and-proceed gate until the provider is granted (block-at-proceed model, A2c); there is no separate org-service On-Hold gate." Behavior unchanged.

---

## FIX 7 — SSRF guard: reject CGNAT 100.64.0.0/10

**File changed:** `asdlc-service/internal/feature/artifacts/spec_collect.go`

Added package-level `cgnatNet` var (lazy init):
```go
var cgnatNet = func() *net.IPNet {
_, n, _ := net.ParseCIDR("100.64.0.0/10")
return n
}()
```

Added to the IP guard in `FetchSpecFromURL`'s `DialContext`:
```go
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() || cgnatNet.Contains(ip) {
```

**Test result:** `go test ./internal/feature/artifacts/...` → ok (existing tests cover the guard; the CGNAT range is not tested by the existing stubs, but the production code change is build-verified and logic is trivially correct).

---

## Verify-command outputs

### 1. Go tests
```
cd asdlc-service && go test ./internal/feature/design/... ./internal/feature/artifacts/... ./internal/feature/codingagent/...
ok github.com/wso2/asdlc/asdlc-service/internal/feature/design 0.643s
ok github.com/wso2/asdlc/asdlc-service/internal/feature/artifacts 0.543s
ok github.com/wso2/asdlc/asdlc-service/internal/feature/codingagent 0.593s
```

### 2. make openapi
```
wrote api/openapi.yaml (147086 bytes)
```
(Not committed — gitignored.)

### 3. Console build + vitest
```
pnpm run build → ✓ built in 17.71s
pnpm vitest run src/pages/architecture/ → 4 files, 19 tests PASS
```
154 changes: 154 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,157 @@ pattern**: per-org OAuth client-secret + per-run ExternalSecret +
definitions.
- `openchoreo/` — OC source. Authoritative for what
`ClusterWorkflow`/`WorkflowRun`/`SecretReference`/`GitSecret` actually mean.

## Dependencies ("marketplace")

### `Marketplace`
**Working title** for app-factory's dependency-management subsystem:
identifying what each user component needs during the design phase, collecting
the config/secrets required to reach those needs, and wiring them through to
the coding agent and the deployed component. Not a browsable storefront —
there is no publish/consume surface.

### `Dependency`
A single, unified, kind-discriminated entry on a component's design
(frontmatter `dependencies`), **subsuming the legacy `dependsOn`
(same-project siblings) and `dependentApis` (external HTTP APIs) fields**.
Authored during the design phase. Invariant carried over from the
cross-component-wiring work: the dependency identifier must be a primitive
the LLM didn't invent.

### `internal dependency`
A dependency that runs within wso2cloud: a sibling component in the same
project (`component`), a service in another project of the same org
(`org-service`), or (future) a [[#platform-resource]] the platform provisions
on demand. The platform resolves URLs/credentials itself; the user is never
asked for them.

### `platform-resource`
An internal dependency that wso2cloud **provisions on demand** — future, not
yet available in wso2cloud, so its shape is not pinned down here. One
dependency kind sub-typed by `resourceType` (e.g. `database`, `message-queue`,
`cache`, `identity-provider`, …); the provisionable set is a **platform
catalog** so new types are added without changing the taxonomy. Scope (per
project vs. per organization) is undecided. Provisioning maps onto OpenChoreo's
`ResourceType` system.

### `external dependency`
A dependency that does not run within wso2cloud — a SaaS (Salesforce, GitHub),
a public/corporate API, or user-managed infrastructure (a MySQL, a queue).
**One generic kind** (`kind: external`); the old `external-api` / `saas` /
`external-resource` split is retired. The platform asks the user for the
config/secret **values**; how to use it (which SDK, which auth, where the spec
lives) is carried in the dependency's free-form `description`, not a kind enum.
A library with no external service behind it is not a dependency.

### `connection`
A registered [[#external-dependency]] the org reuses. Two layers: a **registry**
(app-factory-owned, **org-level**) holding the definition — `name`,
`description`, the config **key schema**, and "used by" bindings, read by the
architect so other projects reuse the shape; and **values + wiring** (OpenChoreo,
**per project** — see [[#connection-resourcetype]]) created when the user fills
values via a config-collection task. "Reuse" = reuse of the *schema*, not the
*credentials* (values are per-project; #3245 Resources can't be shared
cross-project). `component` / `org-service` need no connection — the platform
resolves them per consumer.

### `connection form`
The shape of an external connection = its **`config` key schema** (which keys,
which `secret`). It determines the [[#connection-resourcetype]]'s wiring
(ConfigMap + ESO ExternalSecret + outputs); `authType` is **not** part of it —
the auth *mechanism* is the agent's code, carried in `description`. A schema
change for a connection = a new ResourceType (suffix), never an edit, so RTs are
effectively immutable.

### `connection ResourceType`
The OpenChoreo `ResourceType` (namespaced, per-org) backing an external
connection's values + wiring, per the **Resource model**
([openchoreo#3245](https://github.com/openchoreo/openchoreo/discussions/3245),
shipped v1.1). Get-or-created **per connection** (named after it; wiring from its
[[#connection-form]]) in the org NS; a `Resource` is cut per project, a
`ResourceReleaseBinding` per env (BFF-authored + pinned —
the Resource path has no `AutoDeploy`). Secrets land via a store-backed ESO
`ExternalSecret` the RT emits into the workload's release NS (value written
through SM-API); the workload consumes outputs via
`Workload.spec.dependencies.resources[].ref` + `envBindings`, gated by
`ResourceDependenciesReady`. Living in the shared org NS, it is visible to both
app-factory and Devant ([[#oc-project]]s are a single shared entity per org).
Supersedes the earlier BFF-env / ConfigurationGroup (#3595) approaches.

### `credential class`
Whether an external connection's credential may be exposed to the browser:
**publishable** (a client-side key, fine in a SPA's `window._env_`) or
**secret** (server-side only — a backend `service`, never a SPA). The primary
signal is the per-key `secret: true|false`; `credential class` is the SPA
browser-exposure advisory layered on top.

### `image-based component`
A project component whose **origin** is a prebuilt container image (e.g.
Keycloak), not agent-written source. Resolved (image + version) and configured
via the same resolution + config-collection flow as a [[#connection]], deployed
as an ordinary OC Component; other components reach it via the `component`
dependency + OC Connections. **Not** a dependency kind, and **not** a
[[#platform-resource]] identity-provider (which the *platform* provisions and
shares) — an image component is a project running *its own* image.

### `component config`
Runtime config **variables a user supplies** on a component (source or image) —
plain settings and/or secrets that are *not* an external service. Collected via a
config-collection task + the per-env value form, injected into the component's
own ReleaseBinding (plain as env literals; secrets via the **component-native**
path — the ComponentType template emits an ESO `ExternalSecret` + `secretKeyRef`).
The Resource model is reserved for external [[#connection]]s; component config
never needs a Resource.

### `dependency task`
A typed task on the project board representing the work needed to satisfy a
dependency: **config-collection** (done by the user — completes when values
are saved in the console) or **resource-provisioning** (future — done by the
platform; completes when the OC `Resource` is ready and its outputs resolve).
Part of one task graph with component-implementation tasks; any task type can
gate any other. **Only task types that produce repo artifacts get GitHub
issues** (component implementation today); config-collection tasks are
platform-only — nothing dependency-related is written to (currently public)
project repos.

### `resolution` / `scoped resolution chat`
How a dependency the (single-turn) architect couldn't resolve alone gets
settled on the Architecture page. The dependency shows as a **tile**
(`ambiguous` — has candidates; `unresolved` — needs input). The target UI is a
**scoped resolution chat** — a per-dependency side chat (own conversational
agent + tools) that presents candidates as **chips** and talks through
discovery; an interim inline-card form does the same before that ships.
Resolving **pins** the choice; an **Apply / regenerate** action folds all pins
into one architect re-run. A design with `ambiguous`/`unresolved` dependencies
cannot be saved.

---


### `ClusterResourceType`
The **cluster-scoped, platform-engineer-authored** OpenChoreo provisioning
template (`openchoreo.dev/v1alpha1`, `kind: ClusterResourceType`) a
[[#platform-resource]] references. app-factory **discovers** the installed set
read-only (`ListClusterResourceTypes`) and **never authors** them — the cluster
operator installs them (wso2cloud in cloud; the local `deployments/` scripts
stand in for the platform-engineer locally, e.g. the `postgres-cnpg` sample
backed by the CloudNativePG operator). Distinct from a
[[#connection-resourcetype]], which is **namespaced, per-org, and
app-factory-authored** for an external connection's wiring. The dependency's
`resourceType` open-string IS the discovered `ClusterResourceType.metadata.name`
— no abstract→concrete indirection. See
[[adr-platform-resource-via-direct-oc-resource-model]].

### `ResourceProvisioner`
The BFF seam (P5) that fills a [[#platform-resource]]'s outputs.
**`OCNativeProvisioner`** (built) authors a `Resource` + per-env
`ResourceReleaseBinding` against a discovered [[#clusterresourcetype]] and lets
the OC controller + the cluster's real provisioner fill the binding outputs
**asynchronously** (a readiness watcher observes the native `Ready` condition —
a real DB takes minutes). **`PlatformProvisioner`** is a **reserved** second
implementation for `wso2-enterprise/wso2cloud#638` ("Platform Services
Provisioning") — designed-for, not built. Consumption
(`workload.spec.dependencies.resources[]`) is identical regardless of which
implementation provisioned. The P1-era "platform-resource = future" note above
is realized by P5 **locally**; it remains future in wso2cloud (#638). See
[[adr-platform-resource-via-direct-oc-resource-model]].
67 changes: 66 additions & 1 deletion agents/src/agents/architect/architect.eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
*/

import { test } from "node:test";
import assert from "node:assert/strict";
import { runArchitect } from "./run.js";
import type { FinalizeResolver } from "./tools.js";
import { ArchitectInput, ArchitectOutput } from "./schema.js";
import { ArchitectInput, ArchitectOutput, Dependency } from "./schema.js";
import {
modelFromEnv,
makeCollectingSink,
Expand All @@ -43,6 +44,70 @@ import {
type SampleResult,
} from "../../eval/harness.js";

// ── Schema-level unit tests for needsSpec / specUrl (A5) ────────────────────
// These run offline (no model call) and verify the Zod schema additions.
// The full eval (real model + web_search) is nondeterministic and only runs
// when ANTHROPIC_API_KEY is set (see "architect eval" test below).

test("external dependency schema: needsSpec + specUrl are optional on external variant", () => {
// Base external without new fields — must still validate (backwards-compat).
const base = {
kind: "external",
name: "openweather",
description: "OpenWeatherMap REST API",
config: [{ key: "OPENWEATHER_API_KEY", secret: true }],
};
const baseResult = Dependency.safeParse(base);
assert.ok(baseResult.success, `base external failed: ${JSON.stringify(baseResult.error?.issues)}`);

// External with needsSpec: true and a specUrl — must validate.
const withSpec = {
...base,
needsSpec: true,
specUrl: "https://openweathermap.org/openapi.json",
candidates: [{ label: "OpenWeatherMap API", url: "https://openweathermap.org/api" }],
};
const withSpecResult = Dependency.safeParse(withSpec);
assert.ok(withSpecResult.success, `external with needsSpec+specUrl failed: ${JSON.stringify(withSpecResult.error?.issues)}`);
if (withSpecResult.success && withSpecResult.data.kind === "external") {
assert.strictEqual(withSpecResult.data.needsSpec, true);
assert.strictEqual(withSpecResult.data.specUrl, "https://openweathermap.org/openapi.json");
}

// External with needsSpec: true but NO specUrl — must validate (status unresolved).
const noSpecUrl = {
...base,
needsSpec: true,
status: "unresolved" as const,
};
const noSpecUrlResult = Dependency.safeParse(noSpecUrl);
assert.ok(noSpecUrlResult.success, `external with needsSpec+no specUrl failed: ${JSON.stringify(noSpecUrlResult.error?.issues)}`);

// SaaS-SDK style: needsSpec omitted — must validate.
const sdkStyle = {
kind: "external",
name: "salesforce",
description: "Salesforce CRM — use @salesforce/core npm package v6.x",
config: [
{ key: "SF_CLIENT_ID", secret: false },
{ key: "SF_CLIENT_SECRET", secret: true },
],
};
const sdkResult = Dependency.safeParse(sdkStyle);
assert.ok(sdkResult.success, `SaaS-SDK external without needsSpec failed: ${JSON.stringify(sdkResult.error?.issues)}`);
if (sdkResult.success && sdkResult.data.kind === "external") {
assert.strictEqual(sdkResult.data.needsSpec, undefined);
}
});

// NOTE: The full eval that tests model behavior (web_search calls, needsSpec
// emitted for a REST API prompt) requires a PAID Anthropic API call and
// nondeterministic web_search results. It cannot reliably assert on exact
// URLs. A fixture-driven eval for this would belong in the "architect eval"
// test below, gated by ANTHROPIC_API_KEY. The schema-level test above covers
// the structural contract deterministically.
// ─────────────────────────────────────────────────────────────────────────────

interface ArchitectFixture {
name: string;
difficulty?: string;
Expand Down
Loading
Loading