diff --git a/.llm/runs/beta3-cut-A-deploy--impl/commits.md b/.llm/runs/beta3-cut-A-deploy--impl/commits.md index 73e3bd7f5..50d425cc0 100644 --- a/.llm/runs/beta3-cut-A-deploy--impl/commits.md +++ b/.llm/runs/beta3-cut-A-deploy--impl/commits.md @@ -5,3 +5,4 @@ Per user instruction for this beta.3 cut, append each slice commit here even tho | Slice | Commit | Branch | Notes | | ----- | ------ | ------ | ----- | | #393 | `6e7e13cf` | `fix/deploy-compose-target-393` | Registers `compose`/`docker` default deploy targets and adds resolution smoke tests. | +| #394 | `c16d59fa` | `test/deploy-e2e-gate-394` | Adds the credential-free `deploy.targets` e2e suite and green acceptance gate. | diff --git a/.llm/runs/beta3-cut-A-deploy--impl/context-pack.md b/.llm/runs/beta3-cut-A-deploy--impl/context-pack.md index 097718fe2..edabc1a49 100644 --- a/.llm/runs/beta3-cut-A-deploy--impl/context-pack.md +++ b/.llm/runs/beta3-cut-A-deploy--impl/context-pack.md @@ -12,7 +12,7 @@ ## Current State -#393 implementation is complete locally on `fix/deploy-compose-target-393`. `DEFAULT_DEPLOY_TARGETS` now includes `compose` and `docker`, the public dependency graph consumes the default registry without duplicate target appends, and tests cover all default target resolution plus `deploy docker/compose` router resolution. +#393 implementation is complete and PR #468 is open/commented. #394 implementation is complete locally on stacked branch `test/deploy-e2e-gate-394`: a new `deploy.targets` e2e suite scaffolds a project, runs Deno Deploy preflight, asserts compose/docker target resolution, and cleans up the scratch project. ## Completed @@ -21,17 +21,19 @@ - Created run artifacts for `beta3-cut-A-deploy--impl`. - Implemented #393 code and test changes. - Ran targeted deploy tests and static diagnostics. +- Opened PR #468 for #393 and posted the implementation comment. +- Created #394 stacked branch and implemented `deploy.targets`. +- Ran #394 acceptance gate green. ## In Progress -- Commit, push, open PR #393, and post implementation comment. +- Commit, push, open PR #394, and post implementation comment. ## Next Steps -1. Commit #393. +1. Commit #394. 2. Push with explicit refspec. -3. Open PR #393 against `main`, label/milestone it, and post implementation comment. -4. Start #394 only after PR #393 is opened and commented. +3. Open PR #394 against `fix/deploy-compose-target-393`, label/milestone it, and post implementation comment. ## Key Decisions @@ -50,15 +52,18 @@ | `packages/cli/src/public/features/root/public-command-dependencies.ts` | changed | Removes duplicate `compose`/`docker` appends. | | `packages/cli/src/kernel/domain/deploy/deploy-target-port_test.ts` | changed | Adds all-default-target resolution and handler consistency coverage. | | `packages/cli/src/public/features/deploy/target/target-deploy-command_test.ts` | changed | Adds `deploy docker/compose` router resolution smoke. | +| `packages/cli/e2e/src/domain/cli-surface.ts` | changed | Adds deploy suite and gate identifiers. | +| `packages/cli/e2e/src/presentation/cli/suites/registry.ts` | changed | Registers `deploy.targets`. | +| `packages/cli/e2e/suites/deploy/deploy-targets-suite.ts` | new | Credential-free deploy e2e suite. | ## Gates | Gate family | Current status | Evidence | | ----------- | -------------- | -------- | -| Static | mixed | Check wrapper PASS; lint/fmt wrappers selected files but fail due root CLI exclusion with zero findings; changed-file `deno lint --no-config` and `deno fmt --check --no-config` PASS. | +| Static | PASS for #394 | `packages/cli/e2e` check/lint/fmt wrappers all PASS. #393 check wrapper PASS; #393 lint/fmt changed-file diagnostics PASS with root-exclusion drift recorded. | | Fitness | PASS | F-DEPLOY-1/F-DEPLOY-2 covered by targeted tests. | -| Runtime | PASS | Targeted deploy tests: 23 passed, 0 failed. | -| Consumer | PASS | Target router smoke test covers `docker` and `compose`. | +| Runtime | PASS | Targeted deploy tests: 23 passed, 0 failed; `deploy.targets` e2e: passed=5 failed=0. | +| Consumer | PASS | Target router smoke test covers `docker` and `compose`; e2e suite registration verified. | ## Open Questions diff --git a/.llm/runs/beta3-cut-A-deploy--impl/evaluate.md b/.llm/runs/beta3-cut-A-deploy--impl/evaluate.md new file mode 100644 index 000000000..d414d2a71 --- /dev/null +++ b/.llm/runs/beta3-cut-A-deploy--impl/evaluate.md @@ -0,0 +1,205 @@ +# Evaluation — beta3-cut-A-deploy--impl + +## SKILL + +- netscript-harness — evaluator protocol, verdict rules. +- netscript-cli — CLI e2e suite architecture (suites/gates/registry), scaffold surface. +- netscript-doctrine — epic #327 deployment doctrine; gate:e2e acceptance semantics. +- netscript-tools — gate evidence rules. + +## IMPL-EVAL #470 + +| Field | Value | +| ----- | ----- | +| Run ID | `beta3-cut-A-deploy--impl` | +| Branch | `test/deploy-e2e-gate-394` (stacked on `fix/deploy-compose-target-393`) | +| PR | #470 | +| Issues referenced | Closes #394, refs #393 | +| Milestone | 0.0.1-beta.3 | +| Verdict | **PASS** | + +### Adversarial Scrutiny Points + +#### 1. Coverage Authenticity: Real Acceptance or Rubber Stamp? + +**Verdict: REAL acceptance, not a rubber stamp.** + +The `deploy.deno-deploy.plan` gate executes `netscript deploy deno-deploy plan --project-root --app ` against a real scaffolded project. The `plan` method in `DenoDeployTarget` (packages/cli/src/kernel/domain/deploy/deno-deploy-target.ts) performs: + +- **Config resolution**: Reads `deno.json`/`deno.jsonc` from project root via `DenoDeployPreflightReader`. +- **Source scanning**: Reads entrypoint source files (`main.ts`, `src/main.ts` candidates). +- **Adapter wiring**: Instantiates `DenoDeployTarget` with `DenoDeployCliAdapter` (ProcessPort-backed) + `DenoDeployPreflightReader`. +- **Unstable-API guard**: Scans sources for `--unstable-*` flags in imports, blocks on violations, reports Deploy-readiness. + +This exercises a **non-trivial slice** of the production deploy path: config resolution → adapter wiring → artifact planning → unstable-API validation. It is NOT a help-text dump. + +The `deploy.compose-resolution` gate uses `deploy compose/docker --help` to assert verb exposure (plan/up/down/status/logs). This is **resolution-level** coverage per LD-394-3 in the plan — it proves #393 without invoking Aspire publish or Docker (which would violate the credential-free constraint). The limitation is explicitly documented in the plan §Non-Scope and worklog §Deferred Scope. + +**Issue #394 ask**: "at least one deploy target has real acceptance coverage." **Met honestly**: Deno Deploy `plan` is real acceptance (reads config, scans sources, validates unstable APIs). Compose/docker is resolution-level (documented limitation). + +#### 2. E2E Re-run from Native WSL Path + +**Command**: `deno task e2e:cli run deploy.targets --cleanup --format pretty` + +**Raw output**: +``` +Running deploy.targets +> preflight.deno: Deno CLI is available + PASSED 8ms +> scaffold.init: Scaffold generated project + PASSED 519ms +> deploy.deno-deploy.plan: Preflight generated project for Deno Deploy + PASSED 1714ms +> deploy.compose-resolution: Resolve Docker and Compose deploy target routers + PASSED 568ms +> cleanup.userland-smoke-root: Remove deploy target smoke project + PASSED 190ms +Summary: passed=5 failed=0 +``` + +**Exit code**: 0 + +**Status**: ✓ GREEN + +#### 3. Suite Registration Hygiene + +**cli-surface.ts changes**: +- Added `DEPLOY = { TARGETS: 'deploy.targets' } as const` +- Added `DEPLOY_TITLE = { TARGETS: 'Deploy target acceptance smoke' } as const` +- Added `DEPLOY_DENO_DEPLOY_PLAN: 'deploy.deno-deploy.plan'` and `DEPLOY_COMPOSE_RESOLUTION: 'deploy.compose-resolution'` to `GATE` enum +- Added `DeploySuiteId` type export + +**registry.ts changes**: +- Imported `DEPLOY, DEPLOY_TITLE` and `createDeployTargetsSuite` +- Added `{ id: DEPLOY.TARGETS, title: DEPLOY_TITLE.TARGETS, create: createDeployTargetsSuite }` to `builtInSuites` array. +- Added `DeploySuiteId` to `SuiteId` union type. + +**Suite registration verification**: +```bash +$ deno task e2e:cli suites +deploy.targets Deploy target acceptance smoke + +$ deno task e2e:cli gates deploy.targets +preflight.deno preflight Deno CLI is available +scaffold.init scaffold Scaffold generated project +deploy.deno-deploy.plan behavior Preflight generated project for Deno Deploy +deploy.compose-resolution behavior Resolve Docker and Compose deploy target routers +cleanup.userland-smoke-root cleanup Remove deploy target smoke project +``` + +**Status**: ✓ Consistent with existing scaffold suite architecture. + +#### 4. Global State Mutation & Cleanup + +**With `--cleanup`**: The `cleanup.userland-smoke-root` gate ran successfully (PASSED 190ms). The cleanup script: +- Removes `context.project.projectRoot` recursively (the scaffolded project). +- Attempts to remove `context.project.smokeRoot` (the parent smoke directory), tolerating NotFound. + +**Interference with `scaffold.runtime`**: None. The deploy suite uses a different project name (`${baseOptions.projectName}-deploy`) and different smoke root (`${repoRoot}/.llm/tmp/cli-e2e/${baseOptions.projectName}-deploy`). No shared state with scaffold.runtime. + +**Status**: ✓ Cleanup works; no global state leakage or scaffold.runtime interference. + +#### 5. Cast Budget & Lock Churn + +**Type casts in diff**: +- `as unknown as PublicCommandDependencies` — test-only mock wiring (target-deploy-command_test.ts). +- `as keyof DeployTargetPort` — test-only dynamic property access (deploy-target-port_test.ts). +- `as const` and `as const satisfies` — type narrowing assertions, not casts. + +**Cast count**: 2 test-only casts (within accepted budget). No new runtime casts. + +**deno.lock churn**: `git diff origin/main...HEAD -- deno.lock` → exit code 0, no output. **Zero lock churn.** + +**Status**: ✓ No cast creep; no lock churn. + +#### 6. Issue #394 Accepted Gate Checkboxes + +Per evaluator protocol rule 12 (close-gate): boxes may only be checked for gates that actually RAN green. + +The worklog §Gate Results documents: +- **gate:e2e**: `deno task e2e:cli run deploy.targets --cleanup --format pretty` — passed=5 failed=0. + +The supervisor owns the GitHub issue checkbox update per the workflow. The evidence here is the green gate run with exit code 0 and summary passed=5/failed=0. + +**Status**: ✓ Gate ran green; evidence recorded. + +### Static Gates + +| Gate | Result | Evidence | +| ---- | ------ | -------- | +| check | PASS | Worklog: 589 files selected; 5 batches; 0 failed; 0 diagnostics. | +| lint (changed files) | PASS | Worklog: `deno lint --no-config <6 changed ts files>` — `Checked 6 files`. | +| fmt (changed files) | PASS | Worklog: `deno fmt --check --no-config --line-width 100 --indent-width 2 --single-quote <6 changed ts files>` — `Checked 6 files`. | +| e2e check | PASS | Worklog: 77 files selected; 1 batch; 0 diagnostics. | +| e2e lint | PASS | Worklog: 77 files selected; 1 batch; 0 findings. | +| e2e fmt | PASS | Worklog: 77 files selected; 1 batch; 0 findings. | + +**Drift note**: Scoped lint/fmt wrappers exit 1 with "No target files found" because root deno.json excludes packages/cli/. Workaround: direct file lint/fmt with --no-config and repo-equivalent options. Drift logged in worklog.md §Drift. + +### Fitness Gates + +| Gate | Result | Evidence | +| ---- | ------ | -------- | +| F-DEPLOY-1 (every default target resolves, every advertised op has a handler) | PASS | `deploy-target-port_test.ts`: 3 new tests — "the default deploy target registry resolves every first-party target", "the default deploy target registry exposes only operations with implemented handlers", "compose and docker targets resolve the Aspire adapter operation subsets". Unit test run: 23 passed, 0 failed. | +| F-DEPLOY-2 (router verbs are derived from registry targets) | PASS | `target-deploy-command_test.ts`: "deploy docker/compose routers resolve their default registry targets" — asserts docker/compose routers expose [down, logs, plan, status, up] verbs from registry. | +| gate:e2e (deploy acceptance) | PASS | `deno task e2e:cli run deploy.targets --cleanup --format pretty` — passed=5 failed=0, exit code 0. Independently re-run by evaluator. | + +### Consumer Gates + +| Consumer | Result | Evidence | +| -------- | ------ | -------- | +| Public deploy command router | PASS | Unit test: "deploy docker/compose routers resolve their default registry targets" in target-deploy-command_test.ts. | +| CLI e2e deploy suite | PASS | `deno task e2e:cli suites` lists deploy.targets; `deno task e2e:cli gates deploy.targets` lists all 5 gates. | + +### Doctrine Check + +**AP-24 (registry as single source of truth)**: The plan and implementation move compose/docker registration into `DEFAULT_DEPLOY_TARGETS` (deploy-target-registry.ts) and remove the duplicate manual registration from `public-command-dependencies.ts`. The registry is now the authoritative source. ✓ + +**Thin-router violation**: `deploy-group.ts` remains unchanged; target-specific behavior stays in the adapter. The router remains target-agnostic. ✓ + +**CLI restructuring debt (doctrine file 10)**: This is a narrow registry/test fix that does not author compose YAML, redesign the deploy verb vocabulary, or expand the CLI surface. It does not deepen the existing CLI restructuring debt. ✓ + +### Arch-Debt Implications + +| Entry | Action | Status | +| ----- | ------ | ------ | +| `packages/cli` restructure verdict | none | Narrow fix does not deepen existing CLI restructuring debt. No new debt entry required. | + +**No new unrecorded doctrine violations introduced or deepened.** + +### Evidence Summary + +| Gate | Command | Result | +| ---- | ------- | ------ | +| targeted unit tests | `deno test --unstable-kv packages/cli/src/kernel/domain/deploy/deploy-target-port_test.ts packages/cli/src/kernel/domain/deploy/deno-deploy-target_test.ts packages/cli/src/public/features/deploy/target/target-deploy-command_test.ts` | 23 passed, 0 failed (178ms) | +| deploy e2e suite | `deno task e2e:cli run deploy.targets --cleanup --format pretty` | passed=5 failed=0, exit 0 | +| suite registration | `deno task e2e:cli suites` | deploy.targets listed | +| gate registration | `deno task e2e:cli gates deploy.targets` | All 5 gates listed | +| lock churn | `git diff origin/main...HEAD -- deno.lock` | No changes | + +### Verdict Rationale + +**PASS** because: + +1. **Approved scope complete**: compose/docker registered in defaults (#393), deploy.targets e2e suite added (#394). Both PR commits present. +2. **Static gates pass**: check/lint/fmt pass (with documented drift for scoped wrapper root exclusion). +3. **Fitness gates pass**: F-DEPLOY-1, F-DEPLOY-2, gate:e2e all have evidence. +4. **Consumer gates pass**: Public router and CLI e2e suite both verified. +5. **No unrecorded doctrine violations**: AP-24 resolved, thin-router honored, CLI debt not deepened. +6. **Docs/run artifacts updated**: plan.md, worklog.md, commits.md, drift.md, context-pack.md all present and current. +7. **Coverage authentic**: deploy.deno-deploy.plan exercises real config resolution + unstable-API guard, not help-text. Credential split documented. +8. **Cleanup works**: --cleanup passed, no global state leakage. +9. **Cast budget honored**: 2 test-only casts, no runtime casts. +10. **No lock churn**: deno.lock unchanged. + +### Remaining Risks + +- **Compose resolution is resolution-level only**: The compose/docker gate uses --help output, not full Aspire publish or Docker runtime. This is explicitly documented as a limitation (credential-free constraint) and does not violate #394's ask ("at least one deploy target has real acceptance coverage" — met by Deno Deploy plan). +- **Credentialed Deno Deploy operations deferred**: push/delete/status/logs require external credentials and are future scope. The credential split is clearly documented in the plan §Non-Scope and worklog §Deferred Scope. +- **Scoped lint/fmt wrapper drift**: Root deno.json excludes packages/cli/, causing wrappers to exit 1 with "No target files found". Workaround documented; not a blocker. + +### Process Note + +The harness V3 protocol requires a separate PLAN-EVAL pass before implementation. The drift.md documents that this run proceeded as a combined implementation-lane run (implementation #393 + #394) without a separate PLAN-EVAL session. The supervisor/OpenHands path owns the final evaluator separation. This evaluation is the IMPL-EVAL pass against the committed state. + +**Final verdict: PASS** diff --git a/.llm/runs/beta3-cut-A-deploy--impl/plan.md b/.llm/runs/beta3-cut-A-deploy--impl/plan.md index 97ee992ee..a059ff888 100644 --- a/.llm/runs/beta3-cut-A-deploy--impl/plan.md +++ b/.llm/runs/beta3-cut-A-deploy--impl/plan.md @@ -29,7 +29,7 @@ Archetype 7 applies because the deploy feature routes multiple deploy targets th ## Goal -Fix #393 by registering the Aspire Docker/Compose deploy target in `DEFAULT_DEPLOY_TARGETS` and proving `deploy docker/compose ` target resolution in focused tests. +Fix #393 by registering the Aspire Docker/Compose deploy target in `DEFAULT_DEPLOY_TARGETS` and proving `deploy docker/compose ` target resolution in focused tests. For #394, add a real credential-free deploy e2e suite that scaffolds a project, runs Deno Deploy preflight as the verified production deployment path, and includes a cheap compose/docker resolution check proving #393 at the e2e layer. ## Scope @@ -37,12 +37,14 @@ Fix #393 by registering the Aspire Docker/Compose deploy target in `DEFAULT_DEPL - Keep `deploy-group.ts` as a thin router. - Remove duplicate public dependency graph target appends. - Expand tests so every default target resolves and every advertised operation has a handler. +- Add `deploy.targets` to the CLI e2e suite registry. +- Add deploy e2e gates for Deno Deploy preflight and Docker/Compose target resolution. ## Non-Scope - No full `scaffold.runtime` e2e run; the supervisor owns that merge-readiness gate. -- No #394 e2e framework work until PR #393 is opened and commented. - No deploy verb vocabulary redesign. +- No credentialed Deno Deploy push/delete/status/logs; the PR documents this split. ## Hidden Scope @@ -56,12 +58,16 @@ Fix #393 by registering the Aspire Docker/Compose deploy target in `DEFAULT_DEPL | LD-393-1 | `DEFAULT_DEPLOY_TARGETS` is the single first-party deploy target source. | Prevents the exact recurrence where command wiring and registry defaults diverge. | | LD-393-2 | The Aspire adapter is registered twice, once under `compose` and once under `docker`. | The adapter's own key-specific behavior already owns the target distinction and keeps the router thin. | | LD-393-3 | Tests assert all default targets resolve and every advertised op has a callable handler. | This catches missing registrations and partial adapter descriptors. | +| LD-394-1 | `deploy.targets` is a standalone e2e suite, not folded into `scaffold.runtime`. | Keeps deploy acceptance independently runnable and avoids making scaffold runtime heavier. | +| LD-394-2 | Deno Deploy `plan` is the credential-free production-path gate. | It exercises the Deno Deploy target without paid cloud credentials or a push. | +| LD-394-3 | Compose/Docker coverage is a resolution-level gate via help output. | It proves #393 without invoking Aspire publish or Docker. | ## Open-Decision Sweep | Decision | Status | Notes | | -------- | ------ | ----- | -| Whether #394 uses Deno Deploy or bare-metal as the e2e production path | safe to defer | #393 does not choose the e2e target; #394 owns that decision. | +| Whether #394 uses Deno Deploy or bare-metal as the e2e production path | resolved | Deno Deploy `plan` is used for credential-free production-path coverage. | +| Whether credentialed Deno Deploy push should be included | safe to defer | Requires external credentials; #394 keeps the core green locally/CI and documents the split in PR. | ## Risk Register @@ -69,6 +75,8 @@ Fix #393 by registering the Aspire Docker/Compose deploy target in `DEFAULT_DEPL | ---- | ---------- | | Default registry instantiates process-backed adapters at module load. | Existing Deno Deploy default uses the same lazy-process pattern; `DenoProcess` only shells when an operation runs. | | Public command dependencies might duplicate target keys. | Remove manual `compose`/`docker` appends and rely on `new DeployTargetRegistry()`. | +| Deploy e2e could require cloud credentials. | Use `deploy deno-deploy plan` for the core and leave push/status/logs as credentialed future scope. | +| Compose e2e could require Docker/Aspire. | Use help-output resolution only for compose/docker in #394; full compose runtime remains a later heavier gate. | ## Anti-Patterns to Resolve or Avoid @@ -84,6 +92,7 @@ Fix #393 by registering the Aspire Docker/Compose deploy target in `DEFAULT_DEPL | F-DEPLOY-1 | yes | Manual/test evidence that registered targets advertise implemented operation subsets. | | F-DEPLOY-2 | yes | Manual/test evidence that router verbs are derived from registry targets. | | F-19 | yes | Scoped wrapper evidence for check/lint/fmt. | +| gate:e2e | yes for #394 | `deno task e2e:cli run deploy.targets --cleanup --format pretty` | ## Arch-Debt Implications @@ -99,6 +108,7 @@ Fix #393 by registering the Aspire Docker/Compose deploy target in `DEFAULT_DEPL | 2 | lint | `deno run --allow-read --allow-run .llm/tools/run-deno-lint.ts --root packages/cli --ext ts,tsx` | PASS | | 3 | fmt | `deno run --allow-read --allow-run .llm/tools/run-deno-fmt.ts --root packages/cli --ext ts,tsx` | PASS | | 4 | targeted tests | `deno test --unstable-kv packages/cli/src/kernel/domain/deploy/deploy-target-port_test.ts packages/cli/src/kernel/domain/deploy/deno-deploy-target_test.ts packages/cli/src/public/features/deploy/target/target-deploy-command_test.ts` | PASS | +| 5 | #394 e2e suite | `deno task e2e:cli run deploy.targets --cleanup --format pretty` | PASS | ## Dependencies diff --git a/.llm/runs/beta3-cut-A-deploy--impl/research.md b/.llm/runs/beta3-cut-A-deploy--impl/research.md index fcd0a79a1..3c48bed7d 100644 --- a/.llm/runs/beta3-cut-A-deploy--impl/research.md +++ b/.llm/runs/beta3-cut-A-deploy--impl/research.md @@ -27,3 +27,13 @@ ## Open questions - None for #393. The target registry should be the single first-party target source, and public command dependencies should consume it directly. + +## Slice #394 Findings + +| # | Finding | How to verify | +| - | ------- | ------------- | +| 1 | No deploy suite is registered in the CLI e2e suite registry before #394. | `packages/cli/e2e/src/presentation/cli/suites/registry.ts` | +| 2 | The e2e domain already has a deploy target axis with `windows-service` and `docker`, but no suite consumes it. | `packages/cli/e2e/src/domain/extension-axes.ts` | +| 3 | Existing scaffold gates can generate a local project through `scaffold.init` without starting Aspire. | `packages/cli/e2e/src/application/gates/scaffold/scaffold-gates.ts` | +| 4 | `deploy deno-deploy plan` is credential-free: it runs the Deno Deploy preflight path and does not push to Deno Deploy. | `packages/cli/src/public/features/deploy/deno-deploy/deno-deploy-command.ts` | +| 5 | Compose/Docker target resolution can be checked without invoking Aspire or Docker by asserting their CLI help exposes adapter-derived verbs. | `packages/cli/src/public/features/deploy/target/target-deploy-command.ts` | diff --git a/.llm/runs/beta3-cut-A-deploy--impl/worklog.md b/.llm/runs/beta3-cut-A-deploy--impl/worklog.md index dd3da1945..445d6b087 100644 --- a/.llm/runs/beta3-cut-A-deploy--impl/worklog.md +++ b/.llm/runs/beta3-cut-A-deploy--impl/worklog.md @@ -32,18 +32,21 @@ - First-party deploy target keys: `windows-service`, `linux-service`, `deno-deploy`, `compose`, `docker`. - Routed deploy operations: `plan`, `up`, `down`, `status`, `logs`, `rollback`, `secrets`. +- E2E deploy suite id: `deploy.targets`. +- E2E deploy gate ids: `deploy.deno-deploy.plan`, `deploy.compose-resolution`. ### Commit Slices | # | Slice | Gate | Files | | - | ----- | ---- | ----- | | 1 | Register Docker/Compose in the default target registry and add resolution smoke coverage. | Scoped check/lint/fmt + targeted deploy target tests. | `deploy-target-registry.ts`, `deploy-target-registry-port.ts`, `public-command-dependencies.ts`, deploy target tests, run artifacts | +| 2 | Add credential-free deploy e2e gate coverage. | `deno task e2e:cli run deploy.targets --cleanup --format pretty` | `packages/cli/e2e/src/domain/cli-surface.ts`, `packages/cli/e2e/src/presentation/cli/suites/registry.ts`, `packages/cli/e2e/suites/deploy/deploy-targets-suite.ts`, run artifacts | ### Deferred Scope -- #394 deploy e2e suite — starts only after the #393 PR is opened and commented. - Full scaffold runtime smoke — supervisor merge-readiness gate. - Deploy core extraction — future deployment epic work, not needed for #393. +- Credentialed Deno Deploy push/delete/status/logs — future credentialed gate, not the #394 credential-free core. ### Contributor Path @@ -57,6 +60,9 @@ To add a first-party deploy target, implement `DeployTargetPort`, register it in | 2026-07-05 | #393 | baseline | Verified `DEFAULT_DEPLOY_TARGETS` omits `compose`/`docker` while public dependencies append them manually. | | 2026-07-05 | #393 | implementation | Added `compose` and `docker` Aspire targets to `DEFAULT_DEPLOY_TARGETS`, removed duplicate public dependency registration, and expanded target resolution tests. | | 2026-07-05 | #393 | reconcile | #393/#394 are both open on GitHub milestone `0.0.1-beta.3`; #327 remains an open epic and is referenced only. | +| 2026-07-05 | #394 | branch | Created stacked branch `test/deploy-e2e-gate-394` from #393 branch after PR #468 was opened and commented. | +| 2026-07-05 | #394 | implementation | Added `deploy.targets` e2e suite with Deno Deploy plan, compose/docker resolution, and cleanup gates. | +| 2026-07-05 | #394 | acceptance | `deno task e2e:cli run deploy.targets --cleanup --format pretty` passed all 5 gates. | ## Decisions @@ -64,6 +70,8 @@ To add a first-party deploy target, implement `DeployTargetPort`, register it in | -------- | ------ | ------ | | Register `compose` and `docker` in defaults. | The registry is the target extension axis and must be the recurrence guard. | #393, Arch 7 | | Remove public-dependency duplicate target entries. | Keeps default registry authoritative. | Code baseline | +| Add `deploy.targets` as a standalone suite. | Gives deploy its own acceptance lane instead of hiding it in scaffold runtime. | #394 | +| Use Deno Deploy `plan` as credential-free production-path coverage. | Runs locally/CI without paid cloud credentials while exercising a real deploy target. | #394 | ## Drift @@ -83,6 +91,9 @@ To add a first-party deploy target, implement `DeployTargetPort`, register it in | lint changed files | `deno lint --no-config <6 changed ts files>` | PASS | `Checked 6 files`. | | fmt wrapper | `deno run --allow-read --allow-run .llm/tools/run-deno-fmt.ts --root packages/cli --ext ts,tsx` | FAIL | Wrapper selected 589 files but Deno exited 1 with 0 findings because root `deno.json` excludes `packages/cli/`; recorded as drift. | | fmt changed files | `deno fmt --check --no-config --line-width 100 --indent-width 2 --single-quote <6 changed ts files>` | PASS | `Checked 6 files`. | +| e2e check | `deno run --allow-read --allow-run .llm/tools/run-deno-check.ts --root packages/cli/e2e --ext ts,tsx` | PASS | 77 files selected; 1 batch; 0 diagnostics. | +| e2e lint | `deno run --allow-read --allow-run .llm/tools/run-deno-lint.ts --root packages/cli/e2e --ext ts,tsx` | PASS | 77 files selected; 1 batch; 0 findings. | +| e2e fmt | `deno run --allow-read --allow-run .llm/tools/run-deno-fmt.ts --root packages/cli/e2e --ext ts,tsx` | PASS | 77 files selected; 1 batch; 0 findings. | ### Fitness Gates @@ -96,12 +107,14 @@ To add a first-party deploy target, implement `DeployTargetPort`, register it in | Gate | Result | Evidence | Notes | | ---- | ------ | -------- | ----- | | targeted deploy tests | PASS | `deno test --unstable-kv packages/cli/src/kernel/domain/deploy/deploy-target-port_test.ts packages/cli/src/kernel/domain/deploy/deno-deploy-target_test.ts packages/cli/src/public/features/deploy/target/target-deploy-command_test.ts` — 23 passed, 0 failed. | No external Aspire/Docker shelling. | +| deploy e2e suite | PASS | `deno task e2e:cli run deploy.targets --cleanup --format pretty` — passed=5 failed=0. | Acceptance gate for #394. | ### Consumer Gates | Consumer | Result | Evidence | Notes | | -------- | ------ | -------- | ----- | | public deploy command router | PASS | `deploy docker/compose routers resolve their default registry targets` test. | Proves regression target. | +| CLI e2e deploy suite | PASS | `deno task e2e:cli suites` lists `deploy.targets`; `deno task e2e:cli gates deploy.targets` lists preflight, scaffold, deploy plan, compose resolution, cleanup. | Proves suite registration. | ## Handoff Notes diff --git a/packages/cli/e2e/src/domain/cli-surface.ts b/packages/cli/e2e/src/domain/cli-surface.ts index 2fba23212..4e2b6ef4b 100644 --- a/packages/cli/e2e/src/domain/cli-surface.ts +++ b/packages/cli/e2e/src/domain/cli-surface.ts @@ -20,6 +20,16 @@ export const SCAFFOLD_TITLE = { USERLAND_INSTALL: 'True userland plugin install smoke', } as const; +/** Built-in deploy suites. */ +export const DEPLOY = { + TARGETS: 'deploy.targets', +} as const; + +/** Stable titles for deploy suites. */ +export const DEPLOY_TITLE = { + TARGETS: 'Deploy target acceptance smoke', +} as const; + /** Gate phases in suite execution order. */ export const GATE_PHASE = { PREFLIGHT: 'preflight', @@ -80,6 +90,8 @@ export const GATE = { BEHAVIOR_AUTH_READY: 'behavior.auth-ready', BEHAVIOR_AUTH_SESSION: 'behavior.auth-session', BEHAVIOR_PLUGINS_HEALTH: 'behavior.plugins-health', + DEPLOY_DENO_DEPLOY_PLAN: 'deploy.deno-deploy.plan', + DEPLOY_COMPOSE_RESOLUTION: 'deploy.compose-resolution', USERLAND_INSTALL_ASSERTIONS: 'userland-install.assertions', BEHAVIOR_OTEL_WEBHOOK: 'behavior.otel.webhook', BEHAVIOR_OTEL_TRACES: 'behavior.otel.traces', @@ -103,7 +115,9 @@ export const ASPIRE_RESOURCE = { AUTH: 'auth', } as const; -export type SuiteId = typeof SCAFFOLD[keyof typeof SCAFFOLD]; +export type ScaffoldSuiteId = typeof SCAFFOLD[keyof typeof SCAFFOLD]; +export type DeploySuiteId = typeof DEPLOY[keyof typeof DEPLOY]; +export type SuiteId = ScaffoldSuiteId | DeploySuiteId; export type GatePhase = typeof GATE_PHASE[keyof typeof GATE_PHASE]; export type StaticGateId = typeof GATE[keyof typeof GATE]; export type AspireResource = typeof ASPIRE_RESOURCE[keyof typeof ASPIRE_RESOURCE]; diff --git a/packages/cli/e2e/src/presentation/cli/suites/registry.ts b/packages/cli/e2e/src/presentation/cli/suites/registry.ts index eb70229e2..182510426 100644 --- a/packages/cli/e2e/src/presentation/cli/suites/registry.ts +++ b/packages/cli/e2e/src/presentation/cli/suites/registry.ts @@ -1,4 +1,4 @@ -import { SCAFFOLD, SCAFFOLD_TITLE } from '../../../domain/cli-surface.ts'; +import { DEPLOY, DEPLOY_TITLE, SCAFFOLD, SCAFFOLD_TITLE } from '../../../domain/cli-surface.ts'; import type { GateId, SuiteId } from '../../../domain/cli-surface.ts'; import type { RunOptions } from '../../../domain/run-context.ts'; import type { SuiteDefinition } from '../../../domain/suite-definition.ts'; @@ -6,6 +6,7 @@ import { createScaffoldCapabilitySuite, scaffoldCapabilitySuites, } from '../../../../suites/scaffold/capability-suites.ts'; +import { createDeployTargetsSuite } from '../../../../suites/deploy/deploy-targets-suite.ts'; import { createTrueUserlandInstallSuite } from '../../../../suites/scaffold/true-userland-install-suite.ts'; /** Built-in suite descriptor. */ @@ -31,6 +32,11 @@ export const builtInSuites: readonly BuiltInSuite[] = [ title: SCAFFOLD_TITLE.USERLAND_INSTALL, create: createTrueUserlandInstallSuite, }, + { + id: DEPLOY.TARGETS, + title: DEPLOY_TITLE.TARGETS, + create: createDeployTargetsSuite, + }, ]; /** Resolve a suite by id. */ diff --git a/packages/cli/e2e/suites/deploy/deploy-targets-suite.ts b/packages/cli/e2e/suites/deploy/deploy-targets-suite.ts new file mode 100644 index 000000000..aa77ca452 --- /dev/null +++ b/packages/cli/e2e/suites/deploy/deploy-targets-suite.ts @@ -0,0 +1,141 @@ +import { join, resolve } from '@std/path'; +import { defaultRunOptions } from '../../src/create-default-runner.ts'; +import { DEPLOY, DEPLOY_TITLE, GATE, GATE_PHASE } from '../../src/domain/cli-surface.ts'; +import { PACKAGE_SOURCE, REPORT_FORMAT } from '../../src/domain/extension-axes.ts'; +import type { GateDefinition } from '../../src/domain/gate-definition.ts'; +import type { RunOptions } from '../../src/domain/run-context.ts'; +import type { SuiteDefinition } from '../../src/domain/suite-definition.ts'; +import { + createPreflightGates, + createScaffoldGates, +} from '../../src/application/gates/scaffold/scaffold-gates.ts'; +import { cli, commandGate } from '../../src/application/gates/scaffold/gate-factory.ts'; + +/** Build the deploy target acceptance suite. */ +export function createDeployTargetsSuite( + overrides: Partial = {}, +): SuiteDefinition { + const repoRoot = resolve(overrides.repoRoot ?? '.'); + const baseOptions = defaultRunOptions(overrides); + const options: RunOptions = { + ...baseOptions, + ...overrides, + repoRoot, + cliEntrypoint: overrides.cliEntrypoint ?? + join(repoRoot, 'packages', 'cli', 'bin', 'netscript.ts'), + packageSource: overrides.packageSource ?? PACKAGE_SOURCE.LOCAL, + samples: false, + format: overrides.format ?? REPORT_FORMAT.NDJSON, + logFile: overrides.logFile ?? + join(repoRoot, '.llm', 'tmp', 'cli-e2e', `${baseOptions.projectName}.log`), + }; + + return Object.freeze({ + id: DEPLOY.TARGETS, + title: DEPLOY_TITLE.TARGETS, + description: + 'Credential-free deploy target acceptance: scaffold a project, preflight Deno Deploy, and assert Docker/Compose target resolution.', + defaultOptions: Object.freeze(options), + gates: Object.freeze([ + createPreflightGates()[0], + ...createScaffoldGates({ plugins: [], samples: false }).filter((gate) => + gate.id === GATE.SCAFFOLD_INIT + ), + createDenoDeployPlanGate(), + createComposeResolutionGate(), + createDeployCleanupGate(), + ]), + }); +} + +function createDenoDeployPlanGate(): GateDefinition { + return commandGate( + GATE.DEPLOY_DENO_DEPLOY_PLAN, + 'Preflight generated project for Deno Deploy', + GATE_PHASE.BEHAVIOR, + (context) => + cli( + context, + 'deploy', + 'deno-deploy', + 'plan', + '--project-root', + context.project.projectRoot, + '--app', + context.project.projectName, + ), + ); +} + +function createComposeResolutionGate(): GateDefinition { + return commandGate( + GATE.DEPLOY_COMPOSE_RESOLUTION, + 'Resolve Docker and Compose deploy target routers', + GATE_PHASE.BEHAVIOR, + (context) => [ + 'deno', + 'eval', + DEPLOY_TARGET_RESOLUTION_SCRIPT, + context.project.repoRoot, + context.project.cliEntrypoint, + ], + ); +} + +function createDeployCleanupGate(): GateDefinition { + return commandGate( + GATE.CLEANUP_USERLAND_SMOKE_ROOT, + 'Remove deploy target smoke project', + GATE_PHASE.CLEANUP, + (context) => [ + 'deno', + 'eval', + DEPLOY_CLEANUP_SCRIPT, + context.project.projectRoot, + context.project.smokeRoot, + ], + ); +} + +const DEPLOY_TARGET_RESOLUTION_SCRIPT = [ + 'const [repoRoot, cliEntrypoint] = Deno.args;', + 'if (!repoRoot || !cliEntrypoint) throw new Error("repo root and CLI entrypoint are required");', + 'const normalizedEntrypoint = cliEntrypoint.startsWith("jsr:")', + ' ? cliEntrypoint', + ' : new URL(cliEntrypoint, `file://${repoRoot}/`).pathname;', + 'for (const target of ["compose", "docker"]) {', + ' const command = new Deno.Command("deno", {', + ' args: ["run", "-A", normalizedEntrypoint, "deploy", target, "--help"],', + ' cwd: repoRoot,', + ' stdout: "piped",', + ' stderr: "piped",', + ' });', + ' const output = await command.output();', + ' const stdout = new TextDecoder().decode(output.stdout);', + ' const stderr = new TextDecoder().decode(output.stderr);', + ' if (!output.success) {', + ' throw new Error(`deploy ${target} --help failed with code ${output.code}: ${stderr || stdout}`);', + ' }', + ' const help = `${stdout}\\n${stderr}`;', + ' for (const verb of ["plan", "up", "down", "status", "logs"]) {', + ' if (!help.includes(verb)) {', + ' throw new Error(`deploy ${target} help did not expose ${verb}: ${help}`);', + ' }', + ' }', + ' console.info(`deploy ${target} target resolved with plan/up/down/status/logs verbs`);', + '}', +].join('\n'); + +const DEPLOY_CLEANUP_SCRIPT = [ + 'const projectRoot = Deno.args[0];', + 'const smokeRoot = Deno.args[1];', + 'if (!projectRoot) throw new Error("project root argument is required");', + 'await Deno.remove(projectRoot, { recursive: true }).catch((error) => {', + ' if (!(error instanceof Deno.errors.NotFound)) throw error;', + '});', + 'console.info(`removed deploy smoke project: ${projectRoot}`);', + 'if (smokeRoot) {', + ' await Deno.remove(smokeRoot).catch(() => undefined);', + ' console.info(`removed smoke root when empty: ${smokeRoot}`);', + '}', +].join('\n'); diff --git a/packages/cli/e2e/tests/presentation/suite-registry_test.ts b/packages/cli/e2e/tests/presentation/suite-registry_test.ts index 2991498ed..1560b9a50 100644 --- a/packages/cli/e2e/tests/presentation/suite-registry_test.ts +++ b/packages/cli/e2e/tests/presentation/suite-registry_test.ts @@ -1,5 +1,5 @@ import { assertEquals } from '@std/assert'; -import { GATE, SCAFFOLD } from '../../src/domain/cli-surface.ts'; +import { DEPLOY, GATE, SCAFFOLD } from '../../src/domain/cli-surface.ts'; import { DATABASE } from '../../src/domain/extension-axes.ts'; import { builtInSuites, resolveSuite } from '../../src/presentation/cli/suites/registry.ts'; @@ -11,6 +11,7 @@ Deno.test('registry exposes scaffold capability suites from constants', () => { SCAFFOLD.PLUGIN, SCAFFOLD.RUNTIME, SCAFFOLD.USERLAND_INSTALL, + DEPLOY.TARGETS, ]); });