Skip to content
65 changes: 65 additions & 0 deletions .llm/harness/debt/arch-debt.md
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,71 @@ match the merged exemplars). IMPL-EVAL must not FAIL a slice for retaining eithe
- **Status:** closed 2026-06-30 by #181 Slices 1-6. The connector now backs the six deferred routes
through triggers-core seams: enabled/name fields + enabled-state store, manual dispatcher, webhook
test delivery, cron preview engine, and in-process event subscription.

## packages/cli deploy — DEPLOY-S7-APPHOST-COMPOSE-GEN (#343)

- **ID:** `DEPLOY-S7-APPHOST-COMPOSE-GEN`
- **Title:** Aspire AppHost compose-publishing generation (compose environment + Deno
`denoland/deno:2` Dockerfile weave + config→`Parameters__*`) is deferred as a shared,
cross-slice primitive.
- **Context:** Deploy-S7 (#343) shipped the config member (S1), the Aspire compose/docker
adapter (S5, `aspire-compose-deploy-target.ts`), and the thin `deploy docker|compose` router
(S6). The adapter delegates `plan`→`aspire publish`. For `aspire publish` to actually emit a
docker-compose artifact, the generated `aspire/apphost.mts` must (a) declare the official
`Aspire.Hosting.Docker` NuGet in `aspire.config.json` and (b) call
`await builder.addDockerComposeEnvironment("compose")`. Aspire then auto-publishes all
resources as compose services. **But** NetScript registers Deno resources as
`builder.addExecutable('deno', …)` (see `generate-aspire-config.ts` L44-56); executables are
not containers, so publishing them to compose requires a `denoland/deno:2`
`addDockerfileBuilder` / container-resource weave across the shared `register-services` /
`register-apps` / `register-background` generators, plus config→`Parameters__*` mapping.
- **Why deferred:** (1) Runtime correctness — whether `aspire publish` emits a valid compose for
NetScript's Deno executable resources — cannot be validated without the Aspire .NET SDK + a
Docker daemon (`aspire restore`→`aspire publish`→`docker compose config`), i.e. the S8/S9
merge-readiness environment; a snapshot test would lock the string but not prove Aspire
accepts it. (2) The per-resource compose-publishing generation is a SHARED publishing
convention (R-DEPLOY-3 / core-centralization law), and the concurrent #342 Deno Deploy adapter
needs the same apphost-publishing surface — forking it per-target on one branch would violate
the centralization law and risk a merge collision with the live deployment-epic agents.
- **Owner:** NetScript deployment epic (#327) coordinator; implement once as a shared
apphost-publishing primitive in coordination with #342.
- **Target:** Deployment-epic merge-readiness pass, before the Docker/Compose target is declared
end-to-end.
- **Linked plan:** `.llm/tmp/run/deploy-s7-aspire/plan.md` (S4); drift `D7`.
- **Aspire API grounding:** aspire.dev `docker-integration` (TS AppHost:
`addDockerComposeEnvironment`, `publishAsDockerComposeService`, `addParameterFromConfiguration`,
`addDockerfileBuilder`).
- **Created:** 2026-07-03.
- **Status:** open — S5 adapter + S6 router shipped and work unchanged the moment the apphost
gains the compose environment; the generation is cleanly separable.
- **Gate:** Close when scaffolded `apphost.mts` emits the compose environment + Deno
`denoland/deno:2` service, `deno task e2e:cli run scaffold.runtime` stays green, and
`netscript deploy compose plan` produces a `docker-compose.yaml` that passes
`docker compose config`.

## packages/cli deploy — DEPLOY-SECRETS-ROLLBACK-CORE (#341 / #343)

- **ID:** `DEPLOY-SECRETS-ROLLBACK-CORE`
- **Title:** Centralized `secrets` + `rollback` deploy-convention primitives are not yet on main;
adapters declare them unsupported until the core lands.
- **Context:** The 7-op `DeployTargetPort` (landed by S0/#370) includes `rollback` and `secrets`
as optional ops. Doctrine R-DEPLOY-3 requires both to be centralized in the deploy core
(shared, restricted-perm secret env-file backed by Aspire `Parameters__*`; rollback = redeploy
the last-good emitted artifact/image), never re-implemented per adapter. Those core primitives
do not exist on main yet — they are the #341 deployment-hardening work (PR #364). Per the port
doc comment, adapters may omit the two methods (declare unsupported) rather than provide silent
no-ops.
- **Why deferred:** Cross-slice dependency — forking secrets/rollback inside the Aspire adapter
would violate R-DEPLOY-3. The Deploy-S7 adapter therefore ships the supported subset
(`plan`/`emit`/`up`/`down`/`status`/`logs`) and omits `rollback`/`secrets`.
- **Owner:** #341 deployment-hardening core (deploy core in `packages/cli/src/kernel/domain/deploy`).
- **Target:** When #341/#364 lands the shared primitives; adapters then advertise the ops and call
the core.
- **Linked plan:** `.llm/tmp/run/deploy-s7-aspire/plan.md` (S3); drift `D2`.
- **Created:** 2026-07-03.
- **Status:** open — blocked on #341/#364 core conventions.
- **Gate:** Close when the deploy core exposes shared `secrets`/`rollback` primitives and the
Aspire compose/docker adapter advertises both ops by delegating to them.
- **Gate:** Close each route as its triggers-core seam lands (manual/test-fire helper -> `fireTrigger`
+ `testWebhook`; enabled-state store + `enabled`/`name` fields -> `enableTrigger`/`disableTrigger`
+ un-synthesized `listTriggers`/`getTrigger`; cron preview engine -> `previewSchedule`; event SSE
Expand Down
9 changes: 9 additions & 0 deletions .llm/tmp/run/deploy-s7-aspire/commits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Commits — deploy-s7-aspire (#343 / PR #363)

Append-only. Format: `- <sha>: <message>`

- 8438b7d6: feat(deploy): add docker/compose deploy target config member (S1)
- e7dcfa33: feat(deploy): add Aspire docker/compose deploy target adapter (S5)
- 10f4eba9: feat(deploy): add thin docker/compose deploy router (S6)
- cc8dcb58: chore(harness): record S5/S6 slices in run artifacts
- e9724153: docs(deploy): record deferred apphost compose-gen + secrets/rollback as arch-debt (S7)
155 changes: 155 additions & 0 deletions .llm/tmp/run/deploy-s7-aspire/drift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Drift Log — deploy-s7-aspire (#343 / PR #363)

Append-only. Severity: minor | significant | architectural.

## D1 — S2 port-expansion reconciliation (Risk R1 materialized) — significant

The plan's **S2 (3-op → 7-op DeployTargetPort expansion)** was landed independently
by **S0 (#370, commit `95576c44`)** and merged into this branch (`e6c46758`) before
implementation began. This is exactly the planned Risk R1 / Open-Decision outcome:
"if a sibling lands the port expansion first, S7 rebaselines S2 — a merge
reconciliation, not a redesign."

Verified state of the merged S0 surface:

- `kernel/domain/deploy/deploy-target-port.ts` already defines the canonical 7-op
`DeployOperation` (`plan | emit | up | down | status | logs | rollback |
secrets`) plus retained `LegacyDeployOperation` (`build | install | uninstall`)
aliases. All op methods are **optional**; adapters advertise supported ops via
`operations`.
- `windows-service-deploy-target.ts` still compiles unchanged (it uses the legacy
`build/install/uninstall` verbs, which S0 kept as aliases).
- `deploy-target-registry.ts`, `deploy-target-registry-port.ts`, and
`deploy-target-port_test.ts` already reference the expanded surface.

**Reconciliation decision:** S7 does **NOT** re-expand or redefine the port. S2
collapses to a no-op verification. The windows stub, registry, and port test are
left untouched (S0 already migrated them). No rework required — consistent with
the plan's R1 mitigation.

## D2 — S3 core secrets/rollback convention is a cross-slice dependency (BLOCKED) — significant

The plan's **S3** centralizes the `secrets` + `rollback` convention primitives in
the deploy core (R-DEPLOY-3, shared not per-adapter). Those core primitives do
**not** exist on `main` yet. The S0 port doc comment is explicit:

> `rollback`/`secrets` bodies land with the deployment hardening slice (#341);
> until then adapters may declare them unsupported (omit the method) rather than
> provide a silent no-op.

Per the implementer guardrail ("if the core convention primitives do NOT yet exist,
STOP and report as a blocking cross-slice dependency rather than forking the
convention"), S7 does **NOT** fork/implement secrets or rollback itself.

**Reconciliation decision:** The Aspire compose adapter ships the supported subset
(`plan`/`emit`/`up`/`down`/`status`/`logs`) and **omits** `rollback`/`secrets`
(declares them unsupported), exactly as the port doc sanctions. S3 is deferred to
the #341/#364 core-conventions slice. Recorded as NEEDS-CORE dependency.

## D3 — S1 "re-home docker sub-block" interpreted non-destructively — minor

Plan L7 / S1 says "re-home the existing `docker` sub-block". The base
`deployTargetBaseShape.docker` image block (`denoBaseImage`/`dotnetBaseImage`) is
**consumed by `resolveWindowsDeploy`** (`kernel/adapters/config/deploy-config-resolvers.ts`),
so physically moving it off the base would break the Windows resolver. The new
`DockerComposeDeployTargetSchema` **spreads `deployTargetBaseShape`**, so it
inherits the `docker` image block (with the `denoland/deno:2` default) — that is
its natural home for container targets. The base block is left in place; no
breaking move. R-DEPLOY-4 (member spreads the base, no base class) is satisfied.

## D4 — F-9 permission manifest: CLI runs under `--allow-all` (no narrower manifest) — minor

The plan's F-9 asks for `--allow-run` declared for `aspire`/`docker` in the CLI
permission manifest. The NetScript CLI has **no fine-grained permission manifest**:
`packages/cli/deno.json` `dev`/`build`/`install`/`test` tasks and `bin/netscript.ts`
all run under `--allow-all`. The adapter shells `aspire`/`docker` via the existing
`DenoProcess` (`Deno.Command`), whose run authority is already granted ambiently by
`--allow-all`. No manifest exists to narrow, so S7 documents the external commands
in the adapter JSDoc instead. Narrowing the CLI to explicit permissions is a
pre-existing, CLI-wide concern outside this slice.

## D5 — S5 adapter registration lives in the composition root (layering) — minor

The plan's S5 lists `deploy-target-registry.ts` among the files to touch for
registration. The `AspireComposeDeployTarget` requires a `ProcessPort`, which only
exists in the composition root. `application/registries/deploy-target-registry.ts`
would have to import a `kernel/adapters/**` class to register it there, inverting
the layering (application → adapters). Instead the two adapter instances are
registered in `public/features/root/public-command-dependencies.ts` (the DI
composition root, which already constructs `process` and other adapters) via the
registry's public `register`/constructor API. The registry module is left generic.
This preserves F-3 layering; R-DEPLOY-2/A11 (registry as the named extension axis)
is unchanged.

## D6 — packages/cli is excluded from repo lint + fmt gates — minor

`deno.json` `lint.exclude` and `fmt.exclude` both list `packages/cli/`, and the
`deno task lint` / `deno task fmt:check` commands additionally `--exclude` cli.
Therefore `deno lint`/`deno fmt` report "No target files found" for cli paths and
the scoped wrappers exit non-zero with **zero findings** — a scoping artifact, not
a real finding (reproduced against the pre-existing, known-good windows stub). The
authoritative gate for cli source is `deno check` (type), which passes for all S5
changes, plus the co-located unit tests. cli code is still hand-kept to the repo
style (2-space, single quotes, semicolons, width 100).

## D7 — S4 (apphost compose-publishing generation) deferred as a coordinated cross-slice primitive — significant

**Grounding (Aspire docs MCP, `docker-integration`):** the TypeScript AppHost API
names in the plan are confirmed real — `await builder.addDockerComposeEnvironment("compose")`,
`resource.publishAsDockerComposeService(async (r, service) => …)`,
`builder.addParameterFromConfiguration(name, ENV)`, `builder.addDockerfileBuilder(name, ctx, cb, {stage})`.
Two decisive facts emerged:

1. **Auto-publish:** "When a Docker Compose environment is present, all resources
are automatically published as Docker Compose services — no additional opt-in
is required." So `publishAsDockerComposeService` is an *optional customization*,
and the minimal enabler is (a) adding the official `Aspire.Hosting.Docker` NuGet
to `aspire.config.json` `packages` (re-emits correctly, unlike the community Deno
one — see `generate-aspire-config.ts` L44-56) and (b) one
`builder.addDockerComposeEnvironment("compose")` call.
2. **Deno-resource blocker:** NetScript registers Deno services/apps/background as
`builder.addExecutable('deno', …)` (the deliberate SDK-primitive approach, same
file L44-56). Aspire's compose publisher containerizes resources; an *executable*
is not a container, so publishing Deno resources to compose requires the
`denoland/deno:2` `addDockerfileBuilder` / container-resource weave the plan
calls for. That weave is **cross-cutting** across the shared
`register-services` / `register-apps` / `register-background` generators.

**Why deferred (guardrail: STOP if risky/unvalidatable or cross-slice):**

- **Unvalidatable here.** Whether `aspire publish` emits a *valid* compose for
NetScript's Deno executable resources cannot be proven without the Aspire .NET
SDK + Docker daemon (`aspire restore` → `aspire publish` → `docker compose config`).
That is exactly the S8/S9 merge-readiness / `scaffold.runtime` gate the guardrails
say NOT to run per-slice — it needs the evaluator environment. A green snapshot
test would lock the *string* but not prove Aspire accepts it, risking a
false-green.
- **Shared convention, not a per-target fork (R-DEPLOY-3 / centralization law).**
The per-resource compose-publishing generation (Deno `denoland/deno:2` Dockerfile +
`publishAsDockerComposeService` + config→`Parameters__*`) is a SHARED publishing
primitive. The concurrent **#342 Deno Deploy adapter** needs the same
apphost-publishing surface; three deployment-epic agents (ace40639 coordinator,
a57afc0d #339/#340, a5a1f7ea #342) are live on it now. Landing an isolated fork of
this generation on this branch risks a merge collision and a doctrine violation
(forked convention).

**Decision:** S4 apphost-gen is deferred to a coordinated slice under the epic
coordinator, implemented ONCE as a shared primitive (compose env + Docker NuGet +
Deno `denoland/deno:2` Dockerfile weave + `Parameters__*`) with the full
`scaffold.runtime` + `docker compose config` E2E as its runtime gate. The S5 adapter
already delegates `plan`→`aspire publish`, so it works unchanged the moment the
apphost gains the compose environment — the deferral is cleanly separable and does
not block the adapter/router/config slices. Recorded as NEEDS-COORDINATION +
NEEDS-EVALUATOR-ENV. See also arch-debt entry DEPLOY-S7-APPHOST-COMPOSE-GEN.

## D8 — S8 merge-readiness E2E requires the evaluator/Docker environment — significant

The plan's S8 (Slice-9 gate) is the CI-safe compose-artifact emit +
`docker compose config` validation plus the full `deno task e2e:cli run
scaffold.runtime` smoke. Both require the Aspire .NET SDK and (for `docker compose
config`) a Docker daemon, which are not available in this implementation
environment, and the guardrails explicitly forbid running the expensive
`scaffold.runtime` smoke per-slice. This gate is therefore handed to the
evaluator/merge-readiness environment (OpenHands IMPL-EVAL / Docker-capable host).
It is additionally gated on D7 (there is no compose environment to publish until the
apphost-gen slice lands).
Loading
Loading