diff --git a/.github/workflows/standalone-phase1.yml b/.github/workflows/standalone-prototype.yml similarity index 66% rename from .github/workflows/standalone-phase1.yml rename to .github/workflows/standalone-prototype.yml index d8e0d3658..1b226ab01 100644 --- a/.github/workflows/standalone-phase1.yml +++ b/.github/workflows/standalone-prototype.yml @@ -1,4 +1,4 @@ -name: Standalone Phase 1 Checks +name: Standalone Prototype Checks on: pull_request: @@ -7,8 +7,8 @@ on: - main jobs: - standalone-phase1: - name: Standalone Phase 1 Evaluator + standalone-prototype: + name: Standalone Prototype Readiness Evaluator runs-on: ubuntu-latest steps: @@ -27,7 +27,7 @@ jobs: - name: Type-check run: yarn tsc-b - - name: Run standalone Phase 1 tests + - name: Run standalone prototype readiness tests run: | env TS_NODE_PROJECT=src/test/tsconfig.json \ - yarn mocha -r ts-node/register --exit src/test/standalonePhase1.test.ts + yarn mocha -r ts-node/register --exit src/test/prototypeReadiness.test.ts diff --git a/TODO.md b/TODO.md index 95483a606..b3758fc4b 100644 --- a/TODO.md +++ b/TODO.md @@ -79,9 +79,9 @@ This balances near-term user value with long-term maintainability. --- -## Implementation plan (phased) +## Implementation workstreams -### Phase 0 — Discovery and constraints (1 week) +### Discovery and constraints (1 week) - [x] Inventory all Node-specific and native-binding dependencies (especially `node-pty`). - Native bindings: `node-pty` (declared in `package.json`, loaded dynamically in `src/spec-common/commonUtils.ts` and `src/spec-shutdown/dockerUtils.ts`; highest risk for SEA/single-file portability). - Node runtime coupling: CLI entrypoint remains `#!/usr/bin/env node` in `devcontainer.js`, and runtime bundle target is `dist/spec-node/devContainersSpecCLI.js` (requires embedded/provided Node runtime for standalone delivery). @@ -102,7 +102,7 @@ This balances near-term user value with long-term maintainability. - Output/behavior parity requirement: preserve exit codes and machine-readable JSON output for `read-configuration`; preserve existing non-interactive behavior for CI usage of `build/up/exec`. - Explicitly out-of-scope for MVP parity: perfect TTY UX parity for every interactive edge case and non-Linux platform-specific behavior (tracked for post-MVP hardening). -### Phase 1 — Fast standalone executable PoC (1–2 weeks) +### Standalone prototype (1–2 weeks) - [x] Prototype Node SEA (or alternative) from existing bundle. - [x] Validate command coverage: - [x] `up` @@ -113,24 +113,24 @@ This balances near-term user value with long-term maintainability. - [x] Validate behavior on Docker + Docker Compose in CI-like environment. - [x] Identify blockers around native addons / dynamic requires. - [x] Produce size/startup benchmarks and compare to current install script approach. - - See `docs/standalone/phase1.md` for the completion report and benchmark summary. + - See `docs/standalone/prototype.md` for the completion report and benchmark summary. -### Phase 2 — Productionize short-term binary distribution (2–4 weeks) +### Standalone distribution (2–4 weeks) - [x] Add reproducible build pipeline for standalone binary artifacts. - [x] Add signing/notarization strategy where needed. - [x] Add smoke/integration test lane that runs packaged executable (not just `node ...`). - [x] Add release docs and fallback installer path. - [x] Publish experimental channel (e.g., `-standalone` artifacts). - - See `docs/standalone/phase2.md` for the completion report and rollout notes. + - See `docs/standalone/distribution.md` for the completion report and rollout notes. -### Phase 3 — Native rewrite foundation (Rust) (2–4 weeks) +### Native foundation (Rust) (2–4 weeks) - [x] Create `cmd/devcontainer-native` Rust crate in repo (or sibling repo with mirrored CI). - [x] Implement CLI argument surface for top-level commands and help text parity. - [x] Implement logging format parity (`text` / `json`) and exit code semantics. - [x] Add compatibility bridge: - [x] If command not yet ported, shell out to current Node implementation. -### Phase 4 — Port high-value command paths first (6–12+ weeks) +### Command porting (6–12+ weeks) - [x] Port read-only/introspection paths first: - [x] `read-configuration` - [x] portions of metadata/resolve logic @@ -140,17 +140,17 @@ This balances near-term user value with long-term maintainability. - [x] `exec` - [x] Port `features`/`templates` subcommands. - [x] Preserve compatibility output JSON schema and text output where practical. - - [x] Progress tracking now exists in Rust via `cmd/devcontainer-native/src/phase4.rs` tests. + - [x] Progress tracking now exists in Rust via `cmd/devcontainer-native/src/command_porting.rs` tests. - [x] Native Rust `read-configuration` path now resolves workspace/config paths (including `.devcontainer/devcontainer.json`, legacy `.devcontainer.json`, and workspace-relative `--config`) in `cmd/devcontainer-native/src/main.rs` with unit coverage. - [x] `build`/`up`/`exec` now route through native Rust handlers that execute Docker CLI commands without Node bridge dependency. - [x] `features`/`templates` now provide native list-mode handlers with explicit subcommand validation and stable JSON output. -### Phase 5 — Hardening and cutover +### Hardening and cutover - [x] Full integration parity suite against Node baseline. - [x] Performance and resource benchmarking. - [x] Release native binary as default, keep Node build as fallback for one major cycle. - [x] Deprecate and remove fallback once confidence is high. - - See `docs/standalone/phase5.md` for the completion report and cutover policy. + - See `docs/standalone/cutover.md` for the completion report and cutover policy. --- @@ -172,4 +172,4 @@ This balances near-term user value with long-term maintainability. - [x] Run top 5 commands against existing test fixtures. - [x] Create a short decision memo: SEA viability vs packager alternatives. - [x] Decide whether to launch Rust foundation in parallel immediately or after PoC sign-off. - - Decision: launch in parallel (Phase 3 Rust foundation is in place, and Phase 4 Rust tracking checks are now added). + - Decision: launch in parallel (native foundation is in place, and command porting tracking checks are now added). diff --git a/build/check-setup-separation.js b/build/check-setup-separation.js index 6d13fca2f..507d8fbcb 100644 --- a/build/check-setup-separation.js +++ b/build/check-setup-separation.js @@ -10,14 +10,14 @@ const path = require('path'); const specNodeRoot = path.join(__dirname, '..', 'src', 'spec-node'); const migrationNamespace = path.join(specNodeRoot, 'migration'); -const setupOnlyPhasePattern = /^standalonePhase\d+\.ts$/; +const setupOnlyReadinessPattern = /(?:.*Readiness)\.ts$/; const offenders = fs.readdirSync(specNodeRoot) - .filter(entry => setupOnlyPhasePattern.test(entry)) + .filter(entry => setupOnlyReadinessPattern.test(entry)) .map(entry => path.join(specNodeRoot, entry)); if (offenders.length) { - console.error('Setup-only phase evaluators must live under src/spec-node/migration/.'); + console.error('Setup-only readiness evaluators must live under src/spec-node/migration/.'); offenders.forEach(offender => { const relative = path.relative(path.join(__dirname, '..'), offender); console.error(` - ${relative}`); diff --git a/cmd/devcontainer-native/src/phase4.rs b/cmd/devcontainer-native/src/command_porting.rs similarity index 63% rename from cmd/devcontainer-native/src/phase4.rs rename to cmd/devcontainer-native/src/command_porting.rs index cd9b36d15..86989b730 100644 --- a/cmd/devcontainer-native/src/phase4.rs +++ b/cmd/devcontainer-native/src/command_porting.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; -pub const REQUIRED_PHASE4_EXECUTION_COMMANDS: [&str; 3] = ["build", "up", "exec"]; -pub const REQUIRED_PHASE4_COLLECTION_COMMANDS: [&str; 2] = ["features", "templates"]; +pub const REQUIRED_EXECUTION_COMMANDS: [&str; 3] = ["build", "up", "exec"]; +pub const REQUIRED_COLLECTION_COMMANDS: [&str; 2] = ["features", "templates"]; pub struct IntrospectionPortingInput { pub ok: bool, @@ -20,7 +20,7 @@ pub struct OutputCompatibilityInput { pub text_output_parity: bool, } -pub struct Phase4Input { +pub struct CommandPortingInputSet { pub introspection_porting: IntrospectionPortingInput, pub execution_porting: CommandPortingInput, pub collection_porting: CommandPortingInput, @@ -28,7 +28,7 @@ pub struct Phase4Input { } #[derive(Debug, PartialEq)] -pub enum Phase4MissingCheck { +pub enum CommandPortingMissingCheck { IntrospectionPorting, ExecutionPorting, CollectionPorting, @@ -36,10 +36,10 @@ pub enum Phase4MissingCheck { } #[derive(Debug, PartialEq)] -pub struct Phase4Evaluation { +pub struct CommandPortingEvaluation { pub complete: bool, pub summary: String, - pub missing_checks: Vec, + pub missing_checks: Vec, } fn has_introspection_porting(input: &IntrospectionPortingInput) -> bool { @@ -64,35 +64,35 @@ fn has_output_compatibility(input: &OutputCompatibilityInput) -> bool { input.ok && input.json_schema_parity && input.text_output_parity } -pub fn evaluate_phase4(input: &Phase4Input) -> Phase4Evaluation { +pub fn evaluate_command_porting(input: &CommandPortingInputSet) -> CommandPortingEvaluation { let mut missing_checks = Vec::new(); if !has_introspection_porting(&input.introspection_porting) { - missing_checks.push(Phase4MissingCheck::IntrospectionPorting); + missing_checks.push(CommandPortingMissingCheck::IntrospectionPorting); } if !has_command_porting( &input.execution_porting, - &REQUIRED_PHASE4_EXECUTION_COMMANDS, + &REQUIRED_EXECUTION_COMMANDS, ) { - missing_checks.push(Phase4MissingCheck::ExecutionPorting); + missing_checks.push(CommandPortingMissingCheck::ExecutionPorting); } if !has_command_porting( &input.collection_porting, - &REQUIRED_PHASE4_COLLECTION_COMMANDS, + &REQUIRED_COLLECTION_COMMANDS, ) { - missing_checks.push(Phase4MissingCheck::CollectionPorting); + missing_checks.push(CommandPortingMissingCheck::CollectionPorting); } if !has_output_compatibility(&input.output_compatibility) { - missing_checks.push(Phase4MissingCheck::OutputCompatibility); + missing_checks.push(CommandPortingMissingCheck::OutputCompatibility); } if missing_checks.is_empty() { - return Phase4Evaluation { + return CommandPortingEvaluation { complete: true, - summary: "Phase 4 complete with command porting and output compatibility checks satisfied.".to_string(), + summary: "Command porting complete with output compatibility checks satisfied.".to_string(), missing_checks, }; } @@ -100,17 +100,17 @@ pub fn evaluate_phase4(input: &Phase4Input) -> Phase4Evaluation { let missing_labels = missing_checks .iter() .map(|missing_check| match missing_check { - Phase4MissingCheck::IntrospectionPorting => "introspection-porting", - Phase4MissingCheck::ExecutionPorting => "execution-porting", - Phase4MissingCheck::CollectionPorting => "collection-porting", - Phase4MissingCheck::OutputCompatibility => "output-compatibility", + CommandPortingMissingCheck::IntrospectionPorting => "introspection-porting", + CommandPortingMissingCheck::ExecutionPorting => "execution-porting", + CommandPortingMissingCheck::CollectionPorting => "collection-porting", + CommandPortingMissingCheck::OutputCompatibility => "output-compatibility", }) .collect::>() .join(", "); - Phase4Evaluation { + CommandPortingEvaluation { complete: false, - summary: format!("Phase 4 incomplete. Missing: {missing_labels}."), + summary: format!("Command porting incomplete. Missing: {missing_labels}."), missing_checks, } } @@ -119,8 +119,8 @@ pub fn evaluate_phase4(input: &Phase4Input) -> Phase4Evaluation { mod tests { use super::*; - fn complete_input() -> Phase4Input { - Phase4Input { + fn complete_input() -> CommandPortingInputSet { + CommandPortingInputSet { introspection_porting: IntrospectionPortingInput { ok: true, read_configuration_ported: true, @@ -128,14 +128,14 @@ mod tests { }, execution_porting: CommandPortingInput { ok: true, - ported_commands: REQUIRED_PHASE4_EXECUTION_COMMANDS + ported_commands: REQUIRED_EXECUTION_COMMANDS .iter() .map(|command| (*command).to_string()) .collect(), }, collection_porting: CommandPortingInput { ok: true, - ported_commands: REQUIRED_PHASE4_COLLECTION_COMMANDS + ported_commands: REQUIRED_COLLECTION_COMMANDS .iter() .map(|command| (*command).to_string()) .collect(), @@ -149,31 +149,31 @@ mod tests { } #[test] - fn marks_phase4_complete_when_all_porting_checks_pass() { + fn marks_command_porting_complete_when_all_porting_checks_pass() { let input = complete_input(); - let result = evaluate_phase4(&input); + let result = evaluate_command_porting(&input); assert!(result.complete); - assert!(result.summary.contains("Phase 4 complete")); + assert!(result.summary.contains("Command porting complete")); assert!(result.missing_checks.is_empty()); } #[test] - fn fails_phase4_when_execution_commands_are_partially_ported() { + fn fails_command_porting_when_execution_commands_are_partially_ported() { let mut input = complete_input(); input.execution_porting.ported_commands = vec!["build".to_string(), "up".to_string()]; - let result = evaluate_phase4(&input); + let result = evaluate_command_porting(&input); assert!(!result.complete); assert_eq!( result.missing_checks, - vec![Phase4MissingCheck::ExecutionPorting] + vec![CommandPortingMissingCheck::ExecutionPorting] ); } #[test] - fn fails_phase4_when_output_compatibility_is_not_preserved() { + fn fails_command_porting_when_output_compatibility_is_not_preserved() { let mut input = complete_input(); input.output_compatibility = OutputCompatibilityInput { ok: false, @@ -181,12 +181,12 @@ mod tests { text_output_parity: true, }; - let result = evaluate_phase4(&input); + let result = evaluate_command_porting(&input); assert!(!result.complete); assert_eq!( result.missing_checks, - vec![Phase4MissingCheck::OutputCompatibility] + vec![CommandPortingMissingCheck::OutputCompatibility] ); } } diff --git a/cmd/devcontainer-native/src/phase5.rs b/cmd/devcontainer-native/src/cutover.rs similarity index 71% rename from cmd/devcontainer-native/src/phase5.rs rename to cmd/devcontainer-native/src/cutover.rs index baff03633..5bc67d190 100644 --- a/cmd/devcontainer-native/src/phase5.rs +++ b/cmd/devcontainer-native/src/cutover.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -pub const REQUIRED_PHASE5_PARITY_COMMANDS: [&str; 6] = [ +pub const REQUIRED_CUTOVER_PARITY_COMMANDS: [&str; 6] = [ "read-configuration", "build", "up", @@ -36,7 +36,7 @@ pub struct FallbackRemovalInput { pub planned: bool, } -pub struct Phase5Input { +pub struct CutoverReadinessInput { pub integration_parity: IntegrationParityInput, pub performance_benchmarks: PerformanceBenchmarksInput, pub default_release_cutover: DefaultReleaseCutoverInput, @@ -44,7 +44,7 @@ pub struct Phase5Input { } #[derive(Debug, PartialEq)] -pub enum Phase5MissingCheck { +pub enum CutoverMissingCheck { IntegrationParity, PerformanceBenchmarks, DefaultReleaseCutover, @@ -52,10 +52,10 @@ pub enum Phase5MissingCheck { } #[derive(Debug, PartialEq)] -pub struct Phase5Evaluation { +pub struct CutoverReadinessEvaluation { pub complete: bool, pub summary: String, - pub missing_checks: Vec, + pub missing_checks: Vec, } fn has_integration_parity(input: &IntegrationParityInput) -> bool { @@ -69,7 +69,7 @@ fn has_integration_parity(input: &IntegrationParityInput) -> bool { input.ok && !input.baseline.trim().is_empty() && !input.parity_suite_path.trim().is_empty() - && REQUIRED_PHASE5_PARITY_COMMANDS + && REQUIRED_CUTOVER_PARITY_COMMANDS .iter() .all(|required| commands.contains(required)) } @@ -89,30 +89,30 @@ fn has_fallback_removal(input: &FallbackRemovalInput) -> bool { input.ok && !input.criteria.trim().is_empty() && !input.removal_issue.trim().is_empty() && input.planned } -pub fn evaluate_phase5(input: &Phase5Input) -> Phase5Evaluation { +pub fn evaluate_cutover(input: &CutoverReadinessInput) -> CutoverReadinessEvaluation { let mut missing_checks = Vec::new(); if !has_integration_parity(&input.integration_parity) { - missing_checks.push(Phase5MissingCheck::IntegrationParity); + missing_checks.push(CutoverMissingCheck::IntegrationParity); } if !has_performance_benchmarks(&input.performance_benchmarks) { - missing_checks.push(Phase5MissingCheck::PerformanceBenchmarks); + missing_checks.push(CutoverMissingCheck::PerformanceBenchmarks); } if !has_default_release_cutover(&input.default_release_cutover) { - missing_checks.push(Phase5MissingCheck::DefaultReleaseCutover); + missing_checks.push(CutoverMissingCheck::DefaultReleaseCutover); } if !has_fallback_removal(&input.fallback_removal) { - missing_checks.push(Phase5MissingCheck::FallbackRemoval); + missing_checks.push(CutoverMissingCheck::FallbackRemoval); } if missing_checks.is_empty() { - return Phase5Evaluation { + return CutoverReadinessEvaluation { complete: true, summary: format!( - "Phase 5 complete with parity suite at {}.", + "Cutover readiness complete with parity suite at {}.", input.integration_parity.parity_suite_path ), missing_checks, @@ -122,17 +122,17 @@ pub fn evaluate_phase5(input: &Phase5Input) -> Phase5Evaluation { let missing_labels = missing_checks .iter() .map(|missing_check| match missing_check { - Phase5MissingCheck::IntegrationParity => "integration-parity", - Phase5MissingCheck::PerformanceBenchmarks => "performance-benchmarks", - Phase5MissingCheck::DefaultReleaseCutover => "default-release-cutover", - Phase5MissingCheck::FallbackRemoval => "fallback-removal", + CutoverMissingCheck::IntegrationParity => "integration-parity", + CutoverMissingCheck::PerformanceBenchmarks => "performance-benchmarks", + CutoverMissingCheck::DefaultReleaseCutover => "default-release-cutover", + CutoverMissingCheck::FallbackRemoval => "fallback-removal", }) .collect::>() .join(", "); - Phase5Evaluation { + CutoverReadinessEvaluation { complete: false, - summary: format!("Phase 5 incomplete. Missing: {missing_labels}."), + summary: format!("Cutover readiness incomplete. Missing: {missing_labels}."), missing_checks, } } @@ -141,20 +141,20 @@ pub fn evaluate_phase5(input: &Phase5Input) -> Phase5Evaluation { mod tests { use super::*; - fn complete_input() -> Phase5Input { - Phase5Input { + fn complete_input() -> CutoverReadinessInput { + CutoverReadinessInput { integration_parity: IntegrationParityInput { ok: true, baseline: "node-cli".to_string(), parity_suite_path: "src/test/native-parity".to_string(), - covered_commands: REQUIRED_PHASE5_PARITY_COMMANDS + covered_commands: REQUIRED_CUTOVER_PARITY_COMMANDS .iter() .map(|command| (*command).to_string()) .collect(), }, performance_benchmarks: PerformanceBenchmarksInput { ok: true, - report_path: "docs/standalone/phase5.md".to_string(), + report_path: "docs/standalone/cutover.md".to_string(), startup_latency_ms: 220, peak_memory_mb: 96, }, @@ -173,26 +173,26 @@ mod tests { } #[test] - fn marks_phase5_complete_when_all_checks_pass() { + fn marks_cutover_complete_when_all_checks_pass() { let input = complete_input(); - let result = evaluate_phase5(&input); + let result = evaluate_cutover(&input); assert!(result.complete); - assert!(result.summary.contains("Phase 5 complete")); + assert!(result.summary.contains("Cutover readiness complete")); assert!(result.missing_checks.is_empty()); } #[test] - fn fails_phase5_when_parity_coverage_is_incomplete() { + fn fails_cutover_when_parity_coverage_is_incomplete() { let mut input = complete_input(); input.integration_parity.covered_commands = vec!["read-configuration".to_string(), "build".to_string()]; - let result = evaluate_phase5(&input); + let result = evaluate_cutover(&input); assert!(!result.complete); assert_eq!( result.missing_checks, - vec![Phase5MissingCheck::IntegrationParity] + vec![CutoverMissingCheck::IntegrationParity] ); } } diff --git a/cmd/devcontainer-native/src/main.rs b/cmd/devcontainer-native/src/main.rs index 3a20f9cc4..e35f6b07a 100644 --- a/cmd/devcontainer-native/src/main.rs +++ b/cmd/devcontainer-native/src/main.rs @@ -4,10 +4,10 @@ use std::io::{self, Write}; use std::path::PathBuf; use std::process::{Command, ExitCode}; -mod phase4; -mod phase5; +mod command_porting; +mod cutover; -const PHASE3_COMMANDS: [&str; 6] = [ +const SUPPORTED_TOP_LEVEL_COMMANDS: [&str; 6] = [ "read-configuration", "build", "up", @@ -17,10 +17,10 @@ const PHASE3_COMMANDS: [&str; 6] = [ ]; fn print_help() { - println!("devcontainer-native (phase 3)"); + println!("devcontainer-native (native foundation)"); println!("\nUsage:\n devcontainer-native [--log-format text|json] [args...]\n"); println!("Supported top-level commands (forwarded to Node bridge):"); - for command in PHASE3_COMMANDS { + for command in SUPPORTED_TOP_LEVEL_COMMANDS { println!(" - {command}"); } } @@ -172,7 +172,7 @@ fn main() -> ExitCode { let command = &raw_args[offset]; - if !PHASE3_COMMANDS.contains(&command.as_str()) { + if !SUPPORTED_TOP_LEVEL_COMMANDS.contains(&command.as_str()) { eprintln!("Unsupported command: {command}"); return ExitCode::from(2); } diff --git a/docs/standalone/CONTRIBUTING.md b/docs/standalone/CONTRIBUTING.md index 25e19be14..cc212e2bd 100644 --- a/docs/standalone/CONTRIBUTING.md +++ b/docs/standalone/CONTRIBUTING.md @@ -17,7 +17,7 @@ Changes in these areas should be reviewed as production refactors and can impact Treat the following as **setup/migration scaffolding**: - Files under `src/spec-node/migration/`. -- Tests that validate migration/standalone phase evaluators (`src/test/standalonePhase*.test.ts`). +- Tests that validate migration/standalone readiness evaluators (`src/test/*Readiness.test.ts`). - Tooling and docs that enforce this separation (for example `build/check-setup-separation.js` and this document). These files are expected to evolve to support migration and fork maintenance workflows. @@ -37,4 +37,4 @@ Run: npm run check-setup-separation ``` -This check fails when new setup-only phase evaluator files are introduced under `src/spec-node/` instead of `src/spec-node/migration/`. +This check fails when new setup-only readiness evaluator files are introduced under `src/spec-node/` instead of `src/spec-node/migration/`. diff --git a/docs/standalone/phase5.md b/docs/standalone/cutover.md similarity index 66% rename from docs/standalone/phase5.md rename to docs/standalone/cutover.md index f9756a8f1..fa35016bb 100644 --- a/docs/standalone/phase5.md +++ b/docs/standalone/cutover.md @@ -1,6 +1,6 @@ -# Phase 5 Hardening and Cutover Report +# Hardening and cutover report -This report records completion evidence for the Phase 5 TODO items: +This report records completion evidence for the hardening and cutover TODO items: - Full integration parity suite against Node baseline. - Performance and resource benchmarking. @@ -12,13 +12,13 @@ This report records completion evidence for the Phase 5 TODO items: - Baseline: Node CLI behavior compared against `devcontainer-native` command flows. - Coverage scope: `read-configuration`, `build`, `up`, `exec`, `features`, and `templates`. - Automation entrypoints: - - `src/test/standalonePhase5.test.ts` (phase gating checks) - - `cmd/devcontainer-native/src/phase5.rs` tests (native progress checks) + - `src/test/cutoverReadiness.test.ts` (readiness gating checks) + - `cmd/devcontainer-native/src/cutover.rs` tests (native progress checks) ## Performance and resource benchmark targets -- Startup latency target retained from prior phases: `devcontainer --help` <= 300 ms. -- Current measured placeholder budget check for phase tracking: +- Startup latency target retained from earlier implementation steps: `devcontainer --help` <= 300 ms. +- Current measured placeholder budget check for readiness tracking: - startup latency: 220 ms - peak memory: 96 MB diff --git a/docs/standalone/phase2.md b/docs/standalone/distribution.md similarity index 94% rename from docs/standalone/phase2.md rename to docs/standalone/distribution.md index 73bc583ac..f4ec07274 100644 --- a/docs/standalone/phase2.md +++ b/docs/standalone/distribution.md @@ -1,4 +1,4 @@ -# Phase 2 — Productionize short-term binary distribution (completed) +# Standalone distribution report (completed) Date completed: 2026-04-01 diff --git a/docs/standalone/phase3.md b/docs/standalone/native-foundation.md similarity index 76% rename from docs/standalone/phase3.md rename to docs/standalone/native-foundation.md index 2069b7ca7..36a25ef56 100644 --- a/docs/standalone/phase3.md +++ b/docs/standalone/native-foundation.md @@ -1,4 +1,4 @@ -# Phase 3 — Native rewrite foundation (Rust) (completed) +# Native foundation report (Rust) (completed) Date completed: 2026-04-01 @@ -7,7 +7,7 @@ Date completed: 2026-04-01 - Crate defines an initial native binary target named `devcontainer-native` to host incremental command ports. ## Top-level CLI parity scaffold -- Added a Phase 3 evaluator that verifies parity coverage for required top-level command surfaces: +- Added a native foundation readiness evaluator that verifies parity coverage for required top-level command surfaces: - `read-configuration` - `build` - `up` @@ -17,7 +17,7 @@ Date completed: 2026-04-01 - Evaluator includes help text parity gating (`helpParity`) so parity checks require both command presence and help alignment. ## Logging and exit-code parity checks -- Added explicit Phase 3 gating for logging output formats: +- Added explicit native foundation readiness gating for logging output formats: - `text` - `json` - Evaluator also requires exit code parity verification to pass (`exitCodeParity`). @@ -29,6 +29,6 @@ Date completed: 2026-04-01 - verified behavior for unported command delegation ## Test coverage -- Added standalone Phase 3 unit tests covering: +- Added native foundation readiness unit tests covering: - successful completion when all checks pass - failure mode when compatibility bridge requirements are not met diff --git a/docs/standalone/phase1.md b/docs/standalone/prototype.md similarity index 91% rename from docs/standalone/phase1.md rename to docs/standalone/prototype.md index c41e66378..efd31b9d5 100644 --- a/docs/standalone/phase1.md +++ b/docs/standalone/prototype.md @@ -1,4 +1,4 @@ -# Phase 1 — Fast standalone executable PoC (completed) +# Standalone prototype report (completed) Date completed: 2026-04-01 @@ -19,7 +19,7 @@ Result: pass in CI-like validation lane (non-interactive mode). ## Docker and Docker Compose behavior - Validation executed in CI-like Linux x64 environment with Docker + Docker Compose available. -- Result: pass for required phase-1 commands in non-interactive mode. +- Result: pass for required prototype commands in non-interactive mode. ## Blockers identified 1. `node-pty` native addon loading is the primary portability risk for SEA packaging. diff --git a/scripts/standalone/build-linux-x64.sh b/scripts/standalone/build-linux-x64.sh index f0bdbdb85..5f4e9a4be 100755 --- a/scripts/standalone/build-linux-x64.sh +++ b/scripts/standalone/build-linux-x64.sh @@ -3,7 +3,7 @@ set -euo pipefail mkdir -p dist/standalone/spec-node -# Phase-2 placeholder build contract: +# Standalone distribution placeholder build contract: # package a runnable wrapper plus the JS payload to emulate a standalone artifact. # In production this step should be replaced by the SEA/native packaging invocation. cp -R dist/spec-node/. dist/standalone/spec-node/ diff --git a/src/spec-node/migration/standalonePhase5.ts b/src/spec-node/migration/cutoverReadiness.ts similarity index 80% rename from src/spec-node/migration/standalonePhase5.ts rename to src/spec-node/migration/cutoverReadiness.ts index c987b156c..4759b8663 100644 --- a/src/spec-node/migration/standalonePhase5.ts +++ b/src/spec-node/migration/cutoverReadiness.ts @@ -3,7 +3,7 @@ interface CheckResult { details?: string; } -export const REQUIRED_PHASE5_PARITY_COMMANDS = [ +export const REQUIRED_CUTOVER_PARITY_COMMANDS = [ 'read-configuration', 'build', 'up', @@ -35,14 +35,14 @@ interface FallbackRemovalInput extends CheckResult { planned: boolean; } -interface Phase5Input { +interface CutoverReadinessInput { integrationParity: IntegrationParityInput; performanceBenchmarks: PerformanceBenchmarkInput; defaultReleaseCutover: DefaultReleaseCutoverInput; fallbackRemoval: FallbackRemovalInput; } -interface Phase5Evaluation { +interface CutoverReadinessEvaluation { complete: boolean; summary: string; missingChecks: Array< @@ -57,7 +57,7 @@ function hasIntegrationParity(input: IntegrationParityInput) { return input.ok && input.baseline.trim().length > 0 && input.paritySuitePath.trim().length > 0 - && REQUIRED_PHASE5_PARITY_COMMANDS.every(command => commands.has(command)); + && REQUIRED_CUTOVER_PARITY_COMMANDS.every(command => commands.has(command)); } function hasPerformanceBenchmarks(input: PerformanceBenchmarkInput) { @@ -80,8 +80,8 @@ function hasFallbackRemovalPlan(input: FallbackRemovalInput) { && input.planned; } -export function evaluatePhase5(input: Phase5Input): Phase5Evaluation { - const missingChecks: Phase5Evaluation['missingChecks'] = []; +export function evaluateCutoverReadiness(input: CutoverReadinessInput): CutoverReadinessEvaluation { + const missingChecks: CutoverReadinessEvaluation['missingChecks'] = []; if (!hasIntegrationParity(input.integrationParity)) { missingChecks.push('integration-parity'); @@ -99,14 +99,14 @@ export function evaluatePhase5(input: Phase5Input): Phase5Evaluation { if (!missingChecks.length) { return { complete: true, - summary: `Phase 5 complete with parity suite at ${input.integrationParity.paritySuitePath}.`, + summary: `Cutover readiness complete with parity suite at ${input.integrationParity.paritySuitePath}.`, missingChecks, }; } return { complete: false, - summary: `Phase 5 incomplete. Missing: ${missingChecks.join(', ')}.`, + summary: `Cutover readiness incomplete. Missing: ${missingChecks.join(', ')}.`, missingChecks, }; } diff --git a/src/spec-node/migration/standalonePhase2.ts b/src/spec-node/migration/distributionReadiness.ts similarity index 83% rename from src/spec-node/migration/standalonePhase2.ts rename to src/spec-node/migration/distributionReadiness.ts index efa6ace29..5af373bb4 100644 --- a/src/spec-node/migration/standalonePhase2.ts +++ b/src/spec-node/migration/distributionReadiness.ts @@ -27,7 +27,7 @@ interface ExperimentalChannelInput extends CheckResult { published: boolean; } -interface Phase2Input { +interface DistributionReadinessInput { reproducibleBuild: ReproducibleBuildInput; signing: SigningInput; packagedSmokeTests: PackagedSmokeTestsInput; @@ -35,7 +35,7 @@ interface Phase2Input { experimentalChannel: ExperimentalChannelInput; } -interface Phase2Evaluation { +interface DistributionReadinessEvaluation { complete: boolean; summary: string; missingChecks: Array< @@ -73,8 +73,8 @@ function hasExperimentalChannel(input: ExperimentalChannelInput) { return input.ok && input.artifactSuffix.trim().length > 0 && input.published; } -export function evaluatePhase2(input: Phase2Input): Phase2Evaluation { - const missingChecks: Phase2Evaluation['missingChecks'] = []; +export function evaluateDistributionReadiness(input: DistributionReadinessInput): DistributionReadinessEvaluation { + const missingChecks: DistributionReadinessEvaluation['missingChecks'] = []; if (!hasReproducibleBuild(input.reproducibleBuild)) { missingChecks.push('reproducible-build'); @@ -95,14 +95,14 @@ export function evaluatePhase2(input: Phase2Input): Phase2Evaluation { if (!missingChecks.length) { return { complete: true, - summary: `Phase 2 complete with reproducible builds in ${input.reproducibleBuild.workflowPath}.`, + summary: `Distribution readiness complete with reproducible builds in ${input.reproducibleBuild.workflowPath}.`, missingChecks, }; } return { complete: false, - summary: `Phase 2 incomplete. Missing: ${missingChecks.join(', ')}.`, + summary: `Distribution readiness incomplete. Missing: ${missingChecks.join(', ')}.`, missingChecks, }; } diff --git a/src/spec-node/migration/standalonePhase3.ts b/src/spec-node/migration/nativeFoundationReadiness.ts similarity index 76% rename from src/spec-node/migration/standalonePhase3.ts rename to src/spec-node/migration/nativeFoundationReadiness.ts index 54347ca8d..f6f194df4 100644 --- a/src/spec-node/migration/standalonePhase3.ts +++ b/src/spec-node/migration/nativeFoundationReadiness.ts @@ -3,7 +3,7 @@ interface CheckResult { details?: string; } -export const REQUIRED_PHASE3_TOP_LEVEL_COMMANDS = [ +export const REQUIRED_NATIVE_FOUNDATION_TOP_LEVEL_COMMANDS = [ 'read-configuration', 'build', 'up', @@ -33,14 +33,14 @@ interface CompatibilityBridgeInput extends CheckResult { unportedCommandBehaviorVerified: boolean; } -interface Phase3Input { +interface NativeFoundationReadinessInput { rustCrate: RustCrateInput; cliParity: CliParityInput; loggingAndExitCodes: LoggingAndExitCodesInput; compatibilityBridge: CompatibilityBridgeInput; } -interface Phase3Evaluation { +interface NativeFoundationReadinessEvaluation { complete: boolean; summary: string; missingChecks: Array<'rust-crate' | 'cli-parity' | 'logging-exit-codes' | 'compatibility-bridge'>; @@ -56,7 +56,7 @@ function hasCliParity(input: CliParityInput) { const providedCommands = new Set(input.topLevelCommands.map(command => command.trim()).filter(Boolean)); return input.ok && input.helpParity - && REQUIRED_PHASE3_TOP_LEVEL_COMMANDS.every(command => providedCommands.has(command)); + && REQUIRED_NATIVE_FOUNDATION_TOP_LEVEL_COMMANDS.every(command => providedCommands.has(command)); } function hasLoggingAndExitCodeParity(input: LoggingAndExitCodesInput) { @@ -71,8 +71,8 @@ function hasCompatibilityBridge(input: CompatibilityBridgeInput) { && input.unportedCommandBehaviorVerified; } -export function evaluatePhase3(input: Phase3Input): Phase3Evaluation { - const missingChecks: Phase3Evaluation['missingChecks'] = []; +export function evaluateNativeFoundationReadiness(input: NativeFoundationReadinessInput): NativeFoundationReadinessEvaluation { + const missingChecks: NativeFoundationReadinessEvaluation['missingChecks'] = []; if (!hasRustCrate(input.rustCrate)) { missingChecks.push('rust-crate'); @@ -90,14 +90,14 @@ export function evaluatePhase3(input: Phase3Input): Phase3Evaluation { if (!missingChecks.length) { return { complete: true, - summary: `Phase 3 complete with Rust crate at ${input.rustCrate.cratePath}.`, + summary: `Native foundation readiness complete with Rust crate at ${input.rustCrate.cratePath}.`, missingChecks, }; } return { complete: false, - summary: `Phase 3 incomplete. Missing: ${missingChecks.join(', ')}.`, + summary: `Native foundation readiness incomplete. Missing: ${missingChecks.join(', ')}.`, missingChecks, }; } diff --git a/src/spec-node/migration/standalonePhase1.ts b/src/spec-node/migration/prototypeReadiness.ts similarity index 63% rename from src/spec-node/migration/standalonePhase1.ts rename to src/spec-node/migration/prototypeReadiness.ts index 5d8cd14a5..fd0554b91 100644 --- a/src/spec-node/migration/standalonePhase1.ts +++ b/src/spec-node/migration/prototypeReadiness.ts @@ -1,4 +1,4 @@ -export const REQUIRED_PHASE1_COMMANDS = [ +export const REQUIRED_PROTOTYPE_COMMANDS = [ 'up', 'build', 'exec', @@ -7,19 +7,19 @@ export const REQUIRED_PHASE1_COMMANDS = [ 'templates', ] as const; -export type Phase1Command = (typeof REQUIRED_PHASE1_COMMANDS)[number]; +export type PrototypeCommand = (typeof REQUIRED_PROTOTYPE_COMMANDS)[number]; interface CheckResult { ok: boolean; details?: string; } -interface Phase1Input { +interface PrototypeReadinessInput { prototype: { strategy: 'node-sea' | 'pkg' | 'nexe' | 'other'; binaryPath: string; }; - commandCoverage: Partial>; + commandCoverage: Partial>; composeValidation: CheckResult; blockers: Array<{ id: string; severity: 'low' | 'medium' | 'high'; mitigation: string }>; benchmarks: { @@ -30,17 +30,17 @@ interface Phase1Input { }; } -interface Phase1Evaluation { +interface PrototypeReadinessEvaluation { complete: boolean; summary: string; missingChecks: Array<'prototype' | 'command-coverage' | 'compose-validation' | 'blockers' | 'benchmarks'>; } -function hasCommandCoverage(commandCoverage: Phase1Input['commandCoverage']) { - return REQUIRED_PHASE1_COMMANDS.every(command => commandCoverage[command]?.ok === true); +function hasCommandCoverage(commandCoverage: PrototypeReadinessInput['commandCoverage']) { + return REQUIRED_PROTOTYPE_COMMANDS.every(command => commandCoverage[command]?.ok === true); } -function hasBenchmarkComparison(benchmarks: Phase1Input['benchmarks']) { +function hasBenchmarkComparison(benchmarks: PrototypeReadinessInput['benchmarks']) { return Number.isFinite(benchmarks.standaloneSizeBytes) && Number.isFinite(benchmarks.baselineSizeBytes) && Number.isFinite(benchmarks.standaloneHelpColdStartMs) @@ -51,8 +51,8 @@ function hasBenchmarkComparison(benchmarks: Phase1Input['benchmarks']) { && benchmarks.baselineHelpColdStartMs > 0; } -export function evaluatePhase1(input: Phase1Input): Phase1Evaluation { - const missingChecks: Phase1Evaluation['missingChecks'] = []; +export function evaluatePrototypeReadiness(input: PrototypeReadinessInput): PrototypeReadinessEvaluation { + const missingChecks: PrototypeReadinessEvaluation['missingChecks'] = []; if (!input.prototype.binaryPath.trim()) { missingChecks.push('prototype'); @@ -73,14 +73,14 @@ export function evaluatePhase1(input: Phase1Input): Phase1Evaluation { if (!missingChecks.length) { return { complete: true, - summary: `Phase 1 complete via ${input.prototype.strategy} prototype at ${input.prototype.binaryPath}.`, + summary: `Prototype readiness complete via ${input.prototype.strategy} prototype at ${input.prototype.binaryPath}.`, missingChecks, }; } return { complete: false, - summary: `Phase 1 incomplete. Missing: ${missingChecks.join(', ')}.`, + summary: `Prototype readiness incomplete. Missing: ${missingChecks.join(', ')}.`, missingChecks, }; } diff --git a/src/test/standalonePhase5.test.ts b/src/test/cutoverReadiness.test.ts similarity index 66% rename from src/test/standalonePhase5.test.ts rename to src/test/cutoverReadiness.test.ts index be8885476..9fe01b66b 100644 --- a/src/test/standalonePhase5.test.ts +++ b/src/test/cutoverReadiness.test.ts @@ -1,19 +1,19 @@ import { expect } from 'chai'; -import { evaluatePhase5, REQUIRED_PHASE5_PARITY_COMMANDS } from '../spec-node/migration/standalonePhase5'; +import { evaluateCutoverReadiness, REQUIRED_CUTOVER_PARITY_COMMANDS } from '../spec-node/migration/cutoverReadiness'; -describe('standalone phase 5 evaluator', () => { - it('marks phase 5 complete when hardening and cutover checks pass', () => { - const result = evaluatePhase5({ +describe('cutover readiness evaluator', () => { + it('marks cutover readiness complete when hardening and cutover checks pass', () => { + const result = evaluateCutoverReadiness({ integrationParity: { ok: true, baseline: 'node-cli', paritySuitePath: 'src/test/native-parity', - coveredCommands: [...REQUIRED_PHASE5_PARITY_COMMANDS], + coveredCommands: [...REQUIRED_CUTOVER_PARITY_COMMANDS], }, performanceBenchmarks: { ok: true, - reportPath: 'docs/standalone/benchmarks/phase5.md', + reportPath: 'docs/standalone/cutover.md', startupLatencyMs: 220, peakMemoryMb: 96, }, @@ -31,11 +31,11 @@ describe('standalone phase 5 evaluator', () => { }); expect(result.complete).to.equal(true); - expect(result.summary).to.include('Phase 5 complete'); + expect(result.summary).to.include('Cutover readiness complete'); }); - it('fails phase 5 completion when command parity coverage is incomplete', () => { - const result = evaluatePhase5({ + it('fails cutover readiness when command parity coverage is incomplete', () => { + const result = evaluateCutoverReadiness({ integrationParity: { ok: true, baseline: 'node-cli', @@ -44,7 +44,7 @@ describe('standalone phase 5 evaluator', () => { }, performanceBenchmarks: { ok: true, - reportPath: 'docs/standalone/benchmarks/phase5.md', + reportPath: 'docs/standalone/cutover.md', startupLatencyMs: 220, peakMemoryMb: 96, }, diff --git a/src/test/standalonePhase2.test.ts b/src/test/distributionReadiness.test.ts similarity index 73% rename from src/test/standalonePhase2.test.ts rename to src/test/distributionReadiness.test.ts index 36c6f2673..83aa606c8 100644 --- a/src/test/standalonePhase2.test.ts +++ b/src/test/distributionReadiness.test.ts @@ -1,10 +1,10 @@ import { expect } from 'chai'; -import { evaluatePhase2 } from '../spec-node/migration/standalonePhase2'; +import { evaluateDistributionReadiness } from '../spec-node/migration/distributionReadiness'; -describe('standalone phase 2 evaluator', () => { - it('marks phase 2 complete when all productionization checks pass', () => { - const result = evaluatePhase2({ +describe('distribution readiness evaluator', () => { + it('marks distribution readiness complete when all productionization checks pass', () => { + const result = evaluateDistributionReadiness({ reproducibleBuild: { ok: true, workflowPath: '.github/workflows/standalone-release.yml', @@ -21,7 +21,7 @@ describe('standalone phase 2 evaluator', () => { }, releaseDocs: { ok: true, - docPath: 'docs/standalone/phase2.md', + docPath: 'docs/standalone/distribution.md', fallbackInstaller: 'npm i -g @devcontainers/cli', }, experimentalChannel: { @@ -32,12 +32,12 @@ describe('standalone phase 2 evaluator', () => { }); expect(result.complete).to.equal(true); - expect(result.summary).to.include('Phase 2 complete'); + expect(result.summary).to.include('Distribution readiness complete'); }); - it('fails phase 2 completion when smoke lane omits required commands', () => { - const result = evaluatePhase2({ + it('fails distribution readiness when smoke lane omits required commands', () => { + const result = evaluateDistributionReadiness({ reproducibleBuild: { ok: true, workflowPath: '.github/workflows/standalone-release.yml', @@ -54,7 +54,7 @@ describe('standalone phase 2 evaluator', () => { }, releaseDocs: { ok: true, - docPath: 'docs/standalone/phase2.md', + docPath: 'docs/standalone/distribution.md', fallbackInstaller: 'npm i -g @devcontainers/cli', }, experimentalChannel: { @@ -68,8 +68,8 @@ describe('standalone phase 2 evaluator', () => { expect(result.missingChecks).to.deep.equal(['packaged-smoke-tests']); }); - it('fails phase 2 completion when standalone smoke lane is missing', () => { - const result = evaluatePhase2({ + it('fails distribution readiness when standalone smoke lane is missing', () => { + const result = evaluateDistributionReadiness({ reproducibleBuild: { ok: true, workflowPath: '.github/workflows/standalone-release.yml', @@ -86,7 +86,7 @@ describe('standalone phase 2 evaluator', () => { }, releaseDocs: { ok: true, - docPath: 'docs/standalone/phase2.md', + docPath: 'docs/standalone/distribution.md', fallbackInstaller: 'npm i -g @devcontainers/cli', }, experimentalChannel: { diff --git a/src/test/standalonePhase3.test.ts b/src/test/nativeFoundationReadiness.test.ts similarity index 60% rename from src/test/standalonePhase3.test.ts rename to src/test/nativeFoundationReadiness.test.ts index 37f05e3f9..b8b39ffee 100644 --- a/src/test/standalonePhase3.test.ts +++ b/src/test/nativeFoundationReadiness.test.ts @@ -1,10 +1,10 @@ import { expect } from 'chai'; -import { evaluatePhase3, REQUIRED_PHASE3_TOP_LEVEL_COMMANDS } from '../spec-node/migration/standalonePhase3'; +import { evaluateNativeFoundationReadiness, REQUIRED_NATIVE_FOUNDATION_TOP_LEVEL_COMMANDS } from '../spec-node/migration/nativeFoundationReadiness'; -describe('standalone phase 3 evaluator', () => { - it('marks phase 3 complete when native foundation checks pass', () => { - const result = evaluatePhase3({ +describe('native foundation readiness evaluator', () => { + it('marks native foundation readiness complete when native foundation checks pass', () => { + const result = evaluateNativeFoundationReadiness({ rustCrate: { ok: true, cratePath: 'cmd/devcontainer-native', @@ -12,7 +12,7 @@ describe('standalone phase 3 evaluator', () => { }, cliParity: { ok: true, - topLevelCommands: [...REQUIRED_PHASE3_TOP_LEVEL_COMMANDS], + topLevelCommands: [...REQUIRED_NATIVE_FOUNDATION_TOP_LEVEL_COMMANDS], helpParity: true, }, loggingAndExitCodes: { @@ -29,11 +29,11 @@ describe('standalone phase 3 evaluator', () => { }); expect(result.complete).to.equal(true); - expect(result.summary).to.include('Phase 3 complete'); + expect(result.summary).to.include('Native foundation readiness complete'); }); - it('fails phase 3 completion when fallback bridge is missing', () => { - const result = evaluatePhase3({ + it('fails native foundation readiness when fallback bridge is missing', () => { + const result = evaluateNativeFoundationReadiness({ rustCrate: { ok: true, cratePath: 'cmd/devcontainer-native', @@ -41,7 +41,7 @@ describe('standalone phase 3 evaluator', () => { }, cliParity: { ok: true, - topLevelCommands: [...REQUIRED_PHASE3_TOP_LEVEL_COMMANDS], + topLevelCommands: [...REQUIRED_NATIVE_FOUNDATION_TOP_LEVEL_COMMANDS], helpParity: true, }, loggingAndExitCodes: { diff --git a/src/test/standalonePhase1.test.ts b/src/test/prototypeReadiness.test.ts similarity index 63% rename from src/test/standalonePhase1.test.ts rename to src/test/prototypeReadiness.test.ts index 61f58703f..ec6d69f53 100644 --- a/src/test/standalonePhase1.test.ts +++ b/src/test/prototypeReadiness.test.ts @@ -1,12 +1,12 @@ import { expect } from 'chai'; -import { evaluatePhase1, REQUIRED_PHASE1_COMMANDS } from '../spec-node/migration/standalonePhase1'; +import { evaluatePrototypeReadiness, REQUIRED_PROTOTYPE_COMMANDS } from '../spec-node/migration/prototypeReadiness'; -describe('standalone phase 1 evaluator', () => { - it('marks phase 1 complete when all required checks pass', () => { - const result = evaluatePhase1({ +describe('prototype readiness evaluator', () => { + it('marks prototype readiness complete when all required checks pass', () => { + const result = evaluatePrototypeReadiness({ prototype: { strategy: 'node-sea', binaryPath: 'dist/devcontainer-linux-x64' }, - commandCoverage: Object.fromEntries(REQUIRED_PHASE1_COMMANDS.map(command => [command, { ok: true }])), + commandCoverage: Object.fromEntries(REQUIRED_PROTOTYPE_COMMANDS.map(command => [command, { ok: true }])), composeValidation: { ok: true }, blockers: [ { id: 'node-pty-sea', severity: 'high', mitigation: 'Extract native modules next to SEA binary.' }, @@ -20,12 +20,12 @@ describe('standalone phase 1 evaluator', () => { }); expect(result.complete).to.equal(true); - expect(result.summary).to.include('Phase 1 complete'); + expect(result.summary).to.include('Prototype readiness complete'); }); - it('fails phase 1 completion when command coverage is partial', () => { - const [firstCommand] = REQUIRED_PHASE1_COMMANDS; - const result = evaluatePhase1({ + it('fails prototype readiness when command coverage is partial', () => { + const [firstCommand] = REQUIRED_PROTOTYPE_COMMANDS; + const result = evaluatePrototypeReadiness({ prototype: { strategy: 'node-sea', binaryPath: 'dist/devcontainer-linux-x64' }, commandCoverage: { [firstCommand]: { ok: false, details: 'binary failed with exit code 1' },