diff --git a/dev-reports/issue-485/acceptance-observations.json b/dev-reports/issue-485/acceptance-observations.json new file mode 100644 index 000000000..7962adce3 --- /dev/null +++ b/dev-reports/issue-485/acceptance-observations.json @@ -0,0 +1,286 @@ +{ + "command": "cargo test --lib issue485 -- --nocapture", + "tested_parent": "0a8222643683c764fa5f9ac8bd3e5b2b47ef7728", + "kind": "local worker fixture observations; not CI, UAT or live R0", + "source_contract_sha256": "eb2b04647a28ab10508c21e9c1cd026bea293a1c7f95dda261f4bd5529d8c9ab", + "observations": [ + { + "audit": { + "control_after_observation_sha256": "22fb1f26a04ae28d792e46aece18034b7bb4bed50bfc9e0c4697d4eaa624cd79", + "control_audited": true, + "control_before_sha256": "22fb1f26a04ae28d792e46aece18034b7bb4bed50bfc9e0c4697d4eaa624cd79", + "control_retained": true, + "control_status": "unchanged", + "event": "recovery_preflight_control_audit", + "isolated_outcome": "pass", + "isolated_reason": "registered_final_success_and_completion_contract_passed:1 commands", + "observation_id": ".commandagent/recovery-boundaries/attempt-0/workspace", + "observation_isolated": true, + "restore_invoked": false, + "restore_succeeded": false, + "schema_version": "1" + }, + "case": "business-all", + "final": { + "assurance": "full", + "external_contract_ok": true, + "reason": "pass", + "release_gate": "pass", + "release_gate_reasons": [], + "runtime_acceptance_passed": true, + "status": "full_success" + }, + "kind": "scripted transport; production decisions", + "preflight": "CurrentSuccess { reason: \"registered_final_success_and_completion_contract_passed:1 commands\" }", + "runtime_acceptance": { + "evidence_tiers": { + "build_command_or_dependency_missing_boundary": "strong", + "implementation_artifact": "strong", + "interactive_ui_source_evidence": "strong", + "nextjs_route_evidence": "strong", + "non_static_screen_evidence": "strong", + "stateful_update_evidence": "strong", + "user_input_handler_evidence": "strong", + "visible_interactive_surface_evidence": "strong" + }, + "missing_capabilities": [], + "missing_evidence": [], + "missing_obligations": [], + "page_role": "implementation", + "passed": true + }, + "terminal": { + "command_status": "completed", + "completion_status": "complete", + "task_status": "complete" + }, + "verification_report_passed": true + }, + { + "audit": { + "control_after_observation_sha256": "22fb1f26a04ae28d792e46aece18034b7bb4bed50bfc9e0c4697d4eaa624cd79", + "control_audited": true, + "control_before_sha256": "22fb1f26a04ae28d792e46aece18034b7bb4bed50bfc9e0c4697d4eaa624cd79", + "control_retained": true, + "control_status": "unchanged", + "event": "recovery_preflight_control_audit", + "isolated_outcome": "unavailable", + "isolated_reason": "nextjs_route_observation_failed:build_verifier_failed", + "observation_id": ".commandagent/recovery-boundaries/attempt-0/workspace", + "observation_isolated": true, + "restore_invoked": false, + "restore_succeeded": false, + "schema_version": "1" + }, + "case": "business-build-failed", + "final": { + "assurance": "partial", + "external_contract_ok": false, + "reason": "build_verify_failed: command failed: npm run build outcome: CommandFailed status: exit status: 1 elapsed_ms: 67 summary: Failed to compile: scripted build failure stdout: stderr: Failed to compile: scripted build failure", + "release_gate": "not_applicable", + "release_gate_reasons": [ + "production_build_failed_before_browser_probe" + ], + "runtime_acceptance_passed": true, + "status": "full_success" + }, + "kind": "scripted transport; production decisions", + "preflight": "Unavailable { reason: \"nextjs_route_observation_failed:build_verifier_failed\" }", + "runtime_acceptance": { + "evidence_tiers": { + "build_command_or_dependency_missing_boundary": "strong", + "implementation_artifact": "strong", + "interactive_ui_source_evidence": "strong", + "nextjs_route_evidence": "strong", + "non_static_screen_evidence": "strong", + "stateful_update_evidence": "strong", + "user_input_handler_evidence": "strong", + "visible_interactive_surface_evidence": "strong" + }, + "missing_capabilities": [], + "missing_evidence": [], + "missing_obligations": [], + "page_role": "implementation", + "passed": true + }, + "terminal": { + "command_status": "failed", + "completion_status": "incomplete", + "task_status": "failed" + }, + "verification_report_passed": false + }, + { + "audit": { + "control_after_observation_sha256": "22fb1f26a04ae28d792e46aece18034b7bb4bed50bfc9e0c4697d4eaa624cd79", + "control_audited": true, + "control_before_sha256": "22fb1f26a04ae28d792e46aece18034b7bb4bed50bfc9e0c4697d4eaa624cd79", + "control_retained": true, + "control_status": "unchanged", + "event": "recovery_preflight_control_audit", + "isolated_outcome": "unavailable", + "isolated_reason": "nextjs_interaction_observation_unavailable:No such file or directory (os error 2)", + "observation_id": ".commandagent/recovery-boundaries/attempt-0/workspace", + "observation_isolated": true, + "restore_invoked": false, + "restore_succeeded": false, + "schema_version": "1" + }, + "case": "business-missing-interaction", + "final": { + "assurance": "partial", + "external_contract_ok": true, + "reason": "browser_interaction_evidence_required:issue485_scripted_interaction_unavailable", + "release_gate": "partial", + "release_gate_reasons": [ + "browser_interaction_evidence_required:issue485_scripted_interaction_unavailable" + ], + "runtime_acceptance_passed": true, + "status": "partial" + }, + "kind": "scripted transport; production decisions", + "preflight": "Unavailable { reason: \"nextjs_interaction_observation_unavailable:No such file or directory (os error 2)\" }", + "runtime_acceptance": { + "evidence_tiers": { + "build_command_or_dependency_missing_boundary": "strong", + "implementation_artifact": "strong", + "interactive_ui_source_evidence": "strong", + "nextjs_route_evidence": "strong", + "non_static_screen_evidence": "strong", + "stateful_update_evidence": "strong", + "user_input_handler_evidence": "strong", + "visible_interactive_surface_evidence": "strong" + }, + "missing_capabilities": [], + "missing_evidence": [], + "missing_obligations": [], + "page_role": "implementation", + "passed": true + }, + "terminal": { + "command_status": "completed", + "completion_status": "complete_with_partial_release_gate", + "task_status": "partial" + }, + "verification_report_passed": true + }, + { + "audit": { + "control_after_observation_sha256": "cdd8e5ef0a9c556c53d223da72138f01592bfcdf1a4d9d5050d819b3d9b6ba0d", + "control_audited": true, + "control_before_sha256": "cdd8e5ef0a9c556c53d223da72138f01592bfcdf1a4d9d5050d819b3d9b6ba0d", + "control_retained": true, + "control_status": "unchanged", + "event": "recovery_preflight_control_audit", + "isolated_outcome": "verification_inconsistency", + "isolated_reason": "registered_observations_passed_but_completion_contract_acceptance_failed:missing_required_evidence:implementation_artifact", + "observation_id": ".commandagent/recovery-boundaries/attempt-0/workspace", + "observation_isolated": true, + "restore_invoked": false, + "restore_succeeded": false, + "schema_version": "1" + }, + "case": "scaffold", + "kind": "scripted transport; production decisions", + "preflight": { + "completion_contract_path": "/.commandagent/completion-contract.json", + "completion_contract_sha256": "eb2b04647a28ab10508c21e9c1cd026bea293a1c7f95dda261f4bd5529d8c9ab", + "event": "recovery_preflight_observation", + "external_oracle_used": false, + "observation_isolated": true, + "observation_phase": "pre_recovery", + "observer_ids": [ + "completion_contract_verify_commands", + "nextjs_browser_interaction_v1" + ], + "read_only": true, + "reason": "registered_observations_passed_but_completion_contract_acceptance_failed:missing_required_evidence:implementation_artifact", + "recovery_failed_step": null, + "recovery_handoff_kind": "phase_scaffold_error", + "registered_verify_command_count": 1, + "schema_version": "1", + "source": "product_visible_completion_contract", + "status": "verification_inconsistency", + "typed_capability_observer_count": 2, + "verify_command_count": 1 + }, + "runtime_acceptance": { + "missing_evidence": [ + "implementation_artifact" + ], + "page_role": "scaffold", + "passed": false, + "reason": "missing_required_evidence:implementation_artifact" + }, + "stop": { + "event": "recovery_plan_auto_run_stopped", + "recovery_plan_auto_run_current": 0, + "recovery_plan_auto_run_stop_reason": "verification_inconsistency", + "recovery_plan_auto_run_stop_summary": "registered observations conflict with completion evidence", + "recovery_plan_auto_runs": 2, + "recovery_plan_auto_runs_used": 0, + "schema_version": "1" + }, + "terminal": { + "final_acceptance_status": "not_checked", + "status": "failed", + "task_status": "failed" + } + }, + { + "audit": { + "control_after_observation_sha256": "44bb51b6fca242a2bb045e5163fbcc6e9f1046506fcad2eee0e00a541ba2c9a8", + "control_audited": true, + "control_before_sha256": "44bb51b6fca242a2bb045e5163fbcc6e9f1046506fcad2eee0e00a541ba2c9a8", + "control_retained": true, + "control_status": "unchanged", + "event": "recovery_preflight_control_audit", + "isolated_outcome": "unavailable", + "isolated_reason": "nextjs_interaction_observation_unavailable:No such file or directory (os error 2)", + "observation_id": ".commandagent/recovery-boundaries/attempt-0/workspace", + "observation_isolated": true, + "restore_invoked": false, + "restore_succeeded": false, + "schema_version": "1" + }, + "case": "unrelated-api", + "final": { + "assurance": "partial", + "external_contract_ok": false, + "reason": "weak_verification_evidence:non_implementation_obligation_only:scaffold,non_implementation_obligation_only:setup,non_implementation_obligation_only:style", + "release_gate": "failed", + "release_gate_reasons": [ + "weak_verification_evidence:non_implementation_obligation_only:scaffold", + "browser_interaction_evidence_required:issue485_scripted_interaction_unavailable" + ], + "runtime_acceptance_passed": false, + "status": "incomplete" + }, + "kind": "scripted transport; production decisions", + "preflight": "Unavailable { reason: \"nextjs_interaction_observation_unavailable:No such file or directory (os error 2)\" }", + "runtime_acceptance": { + "evidence_tiers": { + "build_command_or_dependency_missing_boundary": "strong", + "implementation_artifact": "strong", + "interactive_ui_source_evidence": "strong", + "nextjs_route_evidence": "strong", + "non_static_screen_evidence": "strong", + "stateful_update_evidence": "strong", + "user_input_handler_evidence": "strong", + "visible_interactive_surface_evidence": "strong" + }, + "missing_capabilities": [], + "missing_evidence": [], + "missing_obligations": [], + "page_role": "scaffold", + "passed": false + }, + "terminal": { + "command_status": "failed", + "completion_status": "incomplete", + "task_status": "failed" + }, + "verification_report_passed": false + } + ] +} diff --git a/dev-reports/issue-485/design.md b/dev-reports/issue-485/design.md new file mode 100644 index 000000000..5133e7dd8 --- /dev/null +++ b/dev-reports/issue-485/design.md @@ -0,0 +1,62 @@ +# Issue #485 design + +Implement the reviewed regression on `0a8222643683c764fa5f9ac8bd3e5b2b47ef7728`, +which equals freshly fetched `origin/develop` and includes #484 commit +`7125ce939122b3226e10cbf364ba277876578791` (PR #486). The worktree was clean. +Read the revised Issue, preparation resolution, saved R0 source/engine audit, +execution summary and review resolution, the worker skill and development +guardrails before implementation. #484 forms the saved package-script check; +this regression starts at the failed-core-plan Recovery boundary and does not +require that corrected proposals continue to fail admission. + +Add an isolated test module beside the existing #448/#428 Recovery tests and a +corpus under `tests/corpus/apps/issue485-scaffold-completion/`. Retain the exact +saved page and completion contract, reduced plan/handoff and observation inputs, +plus source session, file/event hashes and original line numbers. Saved events +are expected-output provenance, never a replay API. No historical evidence is +modified and tests must run without its absolute paths. + +Exercise the production scaffold classifier, runtime acceptance, real +`RunnerRecoveryDriver` preflight and bounded `drive` suppression, control audit +and process terminal projection. Use deterministic build/HTTP/interaction input +responses through the existing test transports; no live model or GUI capability +evaluation. Keep the original Next.js contract requirements. The failed-core +outcome is a frozen boundary input, not a rerun of historical model planning. + +Separate controls: saved scaffold with successful registered observations; +route-bound project/task implementation with all contract evidence; that same +implementation with independent missing/failed observations. Record completion +evaluation arrival independently of runtime and final acceptance. If useful, +include an unrelated API as a separately classified artifact while preserving +the incomplete goal's remaining gates. Do not add a general scaffold detector. +Expect Recovery 0/2 and unchanged control hashes with restore never invoked for +the saved inconsistency. CurrentSuccess is protection, not internal completion +or external R0 success. Exercise ordinary final acceptance independently for +the controls, requiring all its existing gates before expecting pass. + +Prefer test-only wiring and existing functions; no gate, schema, budget, +baseline, runner/loop behavior, live `.anvil/`, root WIP or skill changes. +If production mispromotion is reproduced, report it and a minimal fix before +changing behavior or expectations. + +Verification: focused #485, #448, #428, #474, #475, #479, #484; related corpus and +guard checks; `cargo fmt --all -- --check`, all-target Clippy with warnings +denied, and full `cargo test`. Record actual commands/results and a stable tested +code tree. PR, exact-HEAD CI/UAT, release and live R0 remain parent work and are +explicitly unperformed here. Commit only explicit Issue-owned paths. + +## Implementation review resolutions + +Applied the parent's design review (no blocking issue) and partial-layer review. +The existing test-only browser command override is carried into disposable +observers so the actual HTTP socket can use an ephemeral port while the original +60302 contract/observer remains unchanged. Test transport failure is distinct +from acceptance failure. The real TUI stop emitter precedes process stop as it +did in R0; historical terminal expectations are retained. + +The matrix records VerificationReport, runtime acceptance, final/release fields +and task projection separately. Missing probe input can leave verification pass +with partial qualification; a failed mandatory build still fails verification +and the task. These observed layer distinctions do not authorize gate changes, +general partial prohibition or R0 qualification changes. Selected actual events +are emitted as `ISSUE485_RECORD` JSON for the parent's later exact-HEAD UAT. diff --git a/dev-reports/issue-485/implementation-summary.md b/dev-reports/issue-485/implementation-summary.md new file mode 100644 index 000000000..58ed81e47 --- /dev/null +++ b/dev-reports/issue-485/implementation-summary.md @@ -0,0 +1,85 @@ +# Issue #485 implementation + +The saved failed-core scaffold now has a regression through the production +classifier, runtime acceptance, isolated Recovery preflight, bounded driver, +control audit and terminal emitters. A successful registered build/generic +interaction cannot turn this scaffold into implementation evidence. The test +observes `missing_required_evidence:implementation_artifact`, +`verification_inconsistency`, Recovery suppression at 0/2, unchanged control +hashes, no restore, and failed TUI/process termination. + +## Change and scope + +- `auto_recovery/issue485_tests.rs` adds three tests covering source provenance, + the saved failed-core boundary, and four independent completion controls. +- `tests/corpus/apps/issue485-scaffold-completion/` preserves the exact saved + page, eight required files plus Next.js type references, original UltraPlan + and completion contract. It includes a reduced failed-core boundary, + interaction observation, expected event projections, and a small synthetic + business implementation. The manifest records original session/file/event + paths, hashes, one-based event positions and reduction mappings. Tests never + read those historical absolute paths or use saved events as replay input. +- Existing source files receive only test wiring: the test module declaration; + a `cfg(test)` final-acceptance leaf wrapper; crate visibility on the existing + test-only TUI emitter; and a `cfg(test)` copy of the existing browser probe + command override into disposable Recovery observations. Production behavior, + budgets, gates, schemas and guardrail baselines are unchanged. + +## Real decisions and scripted inputs + +The original failed core plan is a saved boundary input. The actual failed +`AttemptOutcome` goes to `drive` with `RunnerRecoveryDriver`, not a custom +preflight implementation. Classifier, verification dispatch, acceptance, +snapshot/control audit, suppression and terminal code all execute normally. +The first two historical event projections explain the input boundary; subsequent +projections match newly emitted events in order. Timestamps/session coincidence +are not assertions. The original TUI-before-process stop path is exercised. + +Build exits, HTTP responses and interaction inputs use deterministic test +transports. They do not measure a compiler, browser, model or business capability. +Every build response first checks the case's exact source bytes. The original +contract bytes/hash, package scripts and registered observer retain port 60302; +the existing browser command override assigns only the test HTTP child an +ephemeral port, with `require_build=true`. This resolves test competition without +touching any shared listener, GUI, CommandMate or Ollama process. + +## Independently observed controls + +| Case | Implementation artifact | Preflight | Verification report | Final gate / task | +| --- | --- | --- | --- | --- | +| Saved scaffold | Missing | VerificationInconsistency | Registered observations pass | Final not checked; failed | +| Business, all observations | Present, all required evidence strong | CurrentSuccess | Pass | full_success / complete | +| Business, missing interaction | Present | Unavailable | Pass | partial / partial | +| Business, failed build | Present | Build observation unavailable | Fail; external contract false | Task failed | +| Scaffold plus unrelated API, missing interaction | API present; page remains scaffold | Unavailable | Fail; implementation obligation still missing | incomplete / failed | + +The final-acceptance entry point emits multiple layers. Missing interaction +retains `release_gate_status=partial`, `assurance_level=partial` and +`task_status=partial` even when its lower VerificationReport passes and command +status is completed. For a failed build, the release-derived +`final_acceptance_status` can be `full_success` because that release gate is not +applicable; the actual verification fails, `external_contract_ok=false`, and +the real terminal projection keeps the task failed. Neither field alone is the +overall acceptance decision. No new rule forbidding all partial outcomes was +introduced, and no production mispromotion was established by these controls. + +`acceptance-observations.json` contains assertion-backed selected output from +`cargo test --lib issue485 -- --nocapture`. Tests emit `ISSUE485_RECORD` JSON +for parent exact-HEAD UAT comparison. Temporary workspace paths are scrubbed +in the committed artifact. Each control independently measures control hashes +and restoration flags; missing/build failures remain separate from scaffold +classification. + +## Review and handoff + +Applied the parent design feedback and partial-layer resolution from +`20260914-orchestrate-452-484-485-control-01/{design-review-485-01/worker-feedback.md,partial-review-485-01/worker-resolution.md}`. +The implementation remains on #484-integrated parent +`0a8222643683c764fa5f9ac8bd3e5b2b47ef7728`; #484 admission is not forced to fail +again. Required local checks and their results are in `verification.md`. + +No live R0/model/GUI capability evaluation, PR, push, merge, Issue mutation, +shared service operation, other-worker dispatch or release was performed. +Historical evidence, live `.anvil/`, root WIP, other worktrees, guardrail documents +and plugin cache are untouched. Parent owns final review, PR/CI, exact-HEAD UAT, +integration/release and #452's external R0 qualification. diff --git a/dev-reports/issue-485/tested-files-sha256.json b/dev-reports/issue-485/tested-files-sha256.json new file mode 100644 index 000000000..d01ae0d41 --- /dev/null +++ b/dev-reports/issue-485/tested-files-sha256.json @@ -0,0 +1,27 @@ +{ + "src/planner/auto_recovery.rs": "d6670f3f07caff5fb5a1ce51b8b220bb412fe57f5a686f0c3454932247144add", + "src/planner/auto_recovery/issue485_tests.rs": "8d8fd0d9cb85e729043da9638ef441aff5327dd5d2bc49e951dfc26e80396825", + "src/planner/recovery_snapshot.rs": "dda12fd6d5633481675fcbe09cb32c87e712d8ae33bfcf6c41b6015a8c8f19f2", + "src/planner/runner/recovery_acceptance.rs": "3e0584120ea916805b75c1b0493635b6d441dee69c413d37ed4736289de8de3a", + "src/tui/slash.rs": "c2828d2b6992829bcfb434297d11efb0c18dda4e7ccfcdf4324e35d571e86c98", + "tests/corpus/apps/issue485-scaffold-completion/README.md": "f1851bd290ad0a5a5757dc5d67383c58735d0a3afdd2be1f03180be38dff8be3", + "tests/corpus/apps/issue485-scaffold-completion/business/src/app/page.tsx": "1b85b150c15854755a7768f3279805a956c837437cb30b084d73bc53dfa5b362", + "tests/corpus/apps/issue485-scaffold-completion/completion-contract.json": "eb2b04647a28ab10508c21e9c1cd026bea293a1c7f95dda261f4bd5529d8c9ab", + "tests/corpus/apps/issue485-scaffold-completion/expectations.toml": "fffc0bc4c22190b5d044d94e12fd3ef70d4a932e735060c782e0cd5a1c91f353", + "tests/corpus/apps/issue485-scaffold-completion/expected-events.jsonl": "97671f5e6b9aa0ddeeab43e9183f4280d71011288e713fb90757a5e1858c5846", + "tests/corpus/apps/issue485-scaffold-completion/failed-core-boundary.json": "e820e41a06dab9327f090e14b957e185f065bef582c43a8db8f3265cdcd41277", + "tests/corpus/apps/issue485-scaffold-completion/observations/business-interaction.json": "96b45bdb664bc4ccda8e5b88120ce70e4bcb79fa620bcaf3f8bdcf1d2e37150f", + "tests/corpus/apps/issue485-scaffold-completion/observations/saved-scaffold-interaction.json": "c3d340304c0d1ff24001ded11ef16b236cde6811fcf6274f76b16309a4a94cb5", + "tests/corpus/apps/issue485-scaffold-completion/original-plan.yaml": "daf9b856281c18ed9081c854ea97280defb81a0dae41e9a591f36eb39795c369", + "tests/corpus/apps/issue485-scaffold-completion/runtime.sh": "19bc2474e18040379e83848b3eb9416a1127a5262b00e7823e2759ce7f77c4da", + "tests/corpus/apps/issue485-scaffold-completion/scaffold/next-env.d.ts": "9dd9d642cdb87d4d5b3173217e0c45429b3e47a6f5cf5fb0ead6c644ec5fed01", + "tests/corpus/apps/issue485-scaffold-completion/scaffold/package.json": "9b897bb8402d38f57ca730a2b1265757a4fc8181528daa63d49729253d4b61c7", + "tests/corpus/apps/issue485-scaffold-completion/scaffold/postcss.config.js": "fe0d5cf3cb8c1922f71cca2da3796f3e63366623ad6164336655766efe330e6a", + "tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/global.d.ts": "35d26ea66c86da9701e4f4b467913481ffeae910761cea34c775c5dacb7f8eaa", + "tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/globals.css": "cc1a7ad0d019ddb1d32d0ecb588ba0ac26ce41d8625dd6c366348b25f83a28ec", + "tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/layout.tsx": "e4390d4d4067cdfdcab53478bb5701155d51cf3daa0cc80e8fd4d4d3957f1978", + "tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/page.tsx": "013b9e9015d3da335226c7f55a34c9da4cd4523921674b25e5210a36b88e2ed8", + "tests/corpus/apps/issue485-scaffold-completion/scaffold/tailwind.config.ts": "5be8a942217220a967b2abc238d0b1390c210a2451aa9a334a4f173aca92eda8", + "tests/corpus/apps/issue485-scaffold-completion/scaffold/tsconfig.json": "a73c7e5699d9d60d5c118f2d6a120d9b38cdf031d32254288c4baa95b782a62c", + "tests/corpus/apps/issue485-scaffold-completion/source-manifest.json": "7482393bf8009e9e4a20f80c55e0de413263c7b49f32dc5e9dfb0b501a570359" +} diff --git a/dev-reports/issue-485/verification.md b/dev-reports/issue-485/verification.md new file mode 100644 index 000000000..6e0d1bf07 --- /dev/null +++ b/dev-reports/issue-485/verification.md @@ -0,0 +1,129 @@ +# Issue #485 verification + +- Status: `passed` + +All required **worker local checks** passed on 2026-09-14. This status does not +claim parent CI/UAT, GUI qualification or external R0 success. + +## Checks + +- `cargo test --lib issue485 -- --nocapture`: `passed` +- `cargo test --lib issue448`: `passed` +- `cargo test --lib issue428`: `passed` +- `cargo test --test issue428_bash_path_tokens`: `passed` +- `cargo test --lib issue474`: `passed` +- `cargo test --lib issue475`: `passed` +- `cargo test --lib issue479`: `passed` +- `cargo test --lib issue484`: `passed` +- `cargo test --test corpus_regression --test generality_guardrails --test profile_runtime_guardrails --test conformance`: `passed` +- `cargo fmt --all -- --check`: `passed` +- `rustfmt --edition 2024 --check src/planner/auto_recovery/issue485_tests.rs`: `passed` +- `cargo clippy --all-targets -- -D warnings`: `passed` +- `cargo test`: `passed` +- `git diff --check`: `passed` + +Focused test counts, in the order above: #485 3, #448 6, #428 1 plus 5 integration, +#474 9, #475 8, #479 21, #484 13. Combined checks: corpus 7, generality guards 10, +profile runtime guards 2, conformance 18 passed / 1 pre-existing ignored. Full +`cargo test` exited 0; its library suite passed 2,612 tests with 19 pre-existing +ignored, and all subsequent integration and documentation suites passed. +No tests were newly ignored. Clippy checked all targets with warnings denied. + +Environment: Darwin arm64, Rust/Cargo 1.94.0, Node v24.1.0. Socket/subprocess +checks ran outside the filesystem/network sandbox; no shared services were +started, stopped or restarted. Formatting checks ran locally. Raw logs were +kept only in temporary storage, not committed. + +## Tested source and provenance + +Fresh `git fetch origin develop` confirmed the initial clean worker HEAD and +remote parent both equal `0a8222643683c764fa5f9ac8bd3e5b2b47ef7728`, including +#484 `7125ce939122b3226e10cbf364ba277876578791` through PR #486. This is the +production baseline; the historical R0 HEAD is reference evidence only. + +`tested-files-sha256.json` pins all 25 code/corpus files used for the final checks; +the hashes were compared to the worktree before commit. The Issue-scoped commit +containing this report is the handoff candidate; its exact SHA is provided in +the worker's final `DONE` response. Checks ran against the final uncommitted +code/corpus tree, with no subsequent implementation changes. Report-only writes +do not change that pinned tree. + +The corpus manifest records R0 session +`01a09b8b-f70e-7aa1-84f1-40a18d62da17`, historical HEAD +`b7b65f8fd75793f2075491889c66372b02a06d14`, the central 86-line event stream's +SHA-256 `524d9f569a6dd702b89180e2f1acfff82b458f7afafaaf0d1da297f1a90f1122`, +and each selected event's original position/hash. The page is byte-identical to +the saved source, SHA-256 +`013b9e9015d3da335226c7f55a34c9da4cd4523921674b25e5210a36b88e2ed8`. +Original completion contract SHA-256 is +`eb2b04647a28ab10508c21e9c1cd026bea293a1c7f95dda261f4bd5529d8c9ab`. + +## Measured acceptance results + +`acceptance-observations.json` contains five selected JSON records from the final +focused run, emitted by tests as `ISSUE485_RECORD`. All decisions and control +measurements come from product code; build/HTTP/interaction input responses are +scripted. They are not a compiler/browser/model capability measurement. + +- Saved scaffold: registration passes and completion evaluation is reached; + runtime acceptance lacks `implementation_artifact`; real driver emits + VerificationInconsistency, suppression and stop at 0/2, followed by failed + TUI/process projection. Control before/after SHA-256 is + `cdd8e5ef0a9c556c53d223da72138f01592bfcdf1a4d9d5050d819b3d9b6ba0d`. + `control_retained=true`, `restore_invoked=false`, `restore_succeeded=false`; + final acceptance remains `not_checked`. No CurrentSuccess/completion event. +- Business all-evidence control: every required evidence tier is strong; + CurrentSuccess, normal verification pass, external contract true, final + `full_success`, release gate pass, full assurance and task complete are + separately observed. +- Same artifact with missing interaction: preflight unavailable, verification + pass, final/release/assurance partial, task partial; completion status is + `complete_with_partial_release_gate`. Command status completed is recorded + separately and is not full task qualification. +- Same artifact with required build failure: preflight retains + `build_verifier_failed`; normal verification fails, external contract is false + and terminal task fails. The existing release-derived `full_success` component + remains visible with a non-applicable release gate. It does not override these + three independently asserted failures and is never used alone as acceptance. +- Unrelated API: the API alone classifies as an implementation artifact, the + original page still classifies as scaffold, runtime implementation obligations + remain unfulfilled, final gate is incomplete and the task fails. + +All controls independently retain unchanged control hashes and false restore +flags. Ordinary final acceptance is not conflated with preflight or external R0. + +## Resolved development failures + +Initial fixture construction exposed inaccessible test helpers and attempted +to rebind an already protected observation contract. Test-only leaf wiring and +reading the existing bound contract resolved them. Initial mock-server failures +were separated from acceptance failures: sandbox execution cannot bind sockets, +and simultaneous tests on saved port 60302 collided. The existing test transport +now uses ephemeral sockets while original plan/contract/scripts/observer port +60302 and `require_build=true` remain unchanged. No shared process was killed. + +The original process-event expectation required the actual preceding TUI stop; +adding the real TUI emitter preserved the historical stop projection. Early +assertions also conflated a VerificationReport with final qualification. The +parent's design/partial/component-status resolutions confirmed the existing +layer distinction; tests now assert the real final fields and task projection, +while retaining the mandatory build-failure control. No product gate changed. + +The first combined guard/full run failed because the new include file lacked an +explicit `cfg(test)` wrapper and was counted as production by the literal audit. +Adding that wrapper fixed the audit without changing any baseline. Focused, +corpus/guards, fmt, Clippy and the complete cargo test run passed afterward. + +## Parent-owned verification + +| Stage | Exact candidate/result | +| --- | --- | +| PR and CI | Not run by worker; parent must record results for the final DONE commit | +| Exact-HEAD UAT | Not run by worker; parent can rerun the focused command and compare `ISSUE485_RECORD` fields | +| Integration/release and live R0 | Not run by worker; no release identity or R0 success claimed | +| GUI mapping / #452 consumer | Parent follow-up; no worker GUI measurement | + +The parent reported a separate synthetic check of the existing R0 consumer and +saved its reasoning/limits in `issue485-component-status-resolution.md`; that is +not this worker's exact-HEAD UAT. This worker does not modify that consumer, +schema, R0 gate, historical evidence or parent run records. diff --git a/src/planner/auto_recovery.rs b/src/planner/auto_recovery.rs index 5ac8965b7..f36572cf9 100644 --- a/src/planner/auto_recovery.rs +++ b/src/planner/auto_recovery.rs @@ -1936,6 +1936,11 @@ mod tests { include!("auto_recovery/issue475_tests.rs"); } + #[cfg(unix)] + mod issue485 { + include!("auto_recovery/issue485_tests.rs"); + } + mod issue440 { include!("auto_recovery/issue440_tests.rs"); } diff --git a/src/planner/auto_recovery/issue485_tests.rs b/src/planner/auto_recovery/issue485_tests.rs new file mode 100644 index 000000000..cb530fcbd --- /dev/null +++ b/src/planner/auto_recovery/issue485_tests.rs @@ -0,0 +1,532 @@ +#[cfg(test)] +mod tests { + use super::super::*; + use crate::minimal_loop::evidence::RuntimeAcceptanceReport; + use crate::planner::recovery_snapshot::current_source_sha256; + use serde_json::Value; + use std::os::unix::fs::{PermissionsExt, symlink}; + + const FIXTURE: &str = "tests/corpus/apps/issue485-scaffold-completion"; + const PAGE: &str = "src/app/page.tsx"; + + fn read_json(path: impl AsRef) -> Value { + serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap() + } + + fn fixture_json(path: &str) -> Value { + read_json(Path::new(FIXTURE).join(path)) + } + + fn write(root: &Path, path: &str, value: impl AsRef<[u8]>) { + let target = root.join(path); + std::fs::create_dir_all(target.parent().unwrap()).unwrap(); + std::fs::write(target, value).unwrap(); + } + + fn events(config: &Config) -> Vec { + std::fs::read_to_string(config.eval_events_path.as_ref().unwrap()) + .unwrap() + .lines() + .map(|line| serde_json::from_str(line).unwrap()) + .collect() + } + + fn event<'a>(events: &'a [Value], name: &str) -> &'a Value { + events.iter().rfind(|e| e["event"] == name).unwrap() + } + + fn configure(root: &Path, case: &str) -> (Config, RecoveryCandidate) { + copy_fixture_tree(&Path::new(FIXTURE).join("scaffold"), root); + if case.starts_with("business") { + copy_fixture_tree(&Path::new(FIXTURE).join("business"), root); + } + if case == "unrelated-api" { + write( + root, + "src/app/api/health/route.ts", + "export function GET() { return Response.json({healthy: true}); }\n", + ); + } + let contract = Path::new(FIXTURE).join("completion-contract.json"); + write( + root, + ".commandagent/completion-contract.json", + std::fs::read(contract).unwrap(), + ); + let mut config = config(root, 2); + config.profile = "nextjs".into(); + config.offline = false; // Installed test transports; no dependency or browser downloads. + config.completion_contract_path = Some(root.join(".commandagent/completion-contract.json")); + let plan = crate::planner::ultra_plan::parse_ultra_plan( + &std::fs::read_to_string(Path::new(FIXTURE).join("original-plan.yaml")).unwrap(), + ) + .unwrap(); + let boundary = fixture_json("failed-core-boundary.json"); + let mut candidate = candidate(&plan.goal); + config.action = crate::config::Action::UltraPlanRun(plan.goal.clone()); + candidate.plan = plan; + candidate.handoff.profile = "nextjs".into(); + candidate.handoff.failure_kind = boundary["failure_kind"].as_str().unwrap().into(); + candidate.handoff.failed_step = None; + candidate.handoff.verify_commands = vec!["npm run build".into()]; + candidate.verify_command_source = "completion_contract".into(); + candidate.original_intent = Some("create".into()); + configure_transport(root, case); + (config, candidate) + } + + fn configure_transport(root: &Path, case: &str) { + let modules = root.join("node_modules"); + let runtime = modules.join(".issue485"); + // Only the HTTP transport uses a disposable port. The original plan, + // package scripts, contract and registered observer retain 60302. + let listener = std::net::TcpListener::bind(("127.0.0.1", 0)).unwrap(); + let port = listener.local_addr().unwrap().port(); + drop(listener); + write(&runtime, "transport-port.txt", port.to_string()); + write( + root, + ".anvil/evidence/browser-probe-command.json", + json!({ + "program": "sh", "args": ["node_modules/.bin/npm", "run", "start"], + "env": {"PORT": port.to_string()}, "display": "npm run start (scripted HTTP transport)", + "port": port, "require_build": true + }) + .to_string(), + ); + let mut inputs = fixture_json("source-manifest.json")["files"] + .as_array() + .unwrap() + .iter() + .filter_map(|f| { + f["fixture"] + .as_str() + .unwrap() + .strip_prefix("scaffold/") + .map(str::to_owned) + }) + .collect::>(); + if case == "unrelated-api" { + inputs.push("src/app/api/health/route.ts".into()); + } + for path in &inputs { + write( + &runtime, + &format!("expected/{path}"), + std::fs::read(root.join(path)).unwrap(), + ); + } + write(&runtime, "inputs.txt", inputs.join("\n") + "\n"); + write( + &runtime, + "test-exe.txt", + std::env::current_exe().unwrap().to_str().unwrap(), + ); + write( + &runtime, + "build-exit.txt", + if case == "business-build-failed" { + "1" + } else { + "0" + }, + ); + write( + &runtime, + "availability.json", + json!({ + "available": !matches!(case, "business-missing-interaction" | "unrelated-api"), + "reason": "issue485_scripted_interaction_unavailable", + "location": "issue485_scripted_observation", "version": "fixture" + }) + .to_string(), + ); + let observation = if case.starts_with("business") { + "business" + } else { + "saved-scaffold" + }; + write( + &runtime, + "interaction.json", + serde_json::to_vec(&fixture_json(&format!( + "observations/{observation}-interaction.json" + ))) + .unwrap(), + ); + let package = read_json(root.join("package.json")); + for kind in ["dependencies", "devDependencies"] { + for name in package[kind].as_object().unwrap().keys() { + std::fs::create_dir_all(modules.join(name)).unwrap(); + } + } + let npm = modules.join(".bin/npm"); + write( + &modules, + ".bin/npm", + std::fs::read(Path::new(FIXTURE).join("runtime.sh")).unwrap(), + ); + std::fs::set_permissions(&npm, std::fs::Permissions::from_mode(0o755)).unwrap(); + symlink("npm", modules.join(".bin/next")).unwrap(); + } + + fn observed_config(config: &Config) -> Config { + let observation = config + .workspace_root + .join(".commandagent/recovery-observations/attempt-0/workspace"); + let mut bound = config.clone(); + bound.workspace_root = observation; + bound.completion_contract_path = Some( + bound + .workspace_root + .join(".commandagent/recovery-runtime/completion-contract.json"), + ); + assert_eq!( + std::fs::read(bound.completion_contract_path.as_ref().unwrap()).unwrap(), + std::fs::read(config.completion_contract_path.as_ref().unwrap()).unwrap() + ); + bound + } + + fn runtime_acceptance( + config: &Config, + candidate: &RecoveryCandidate, + ) -> RuntimeAcceptanceReport { + crate::planner::runner::recovery_acceptance::runtime_acceptance_report( + &candidate.plan, + config, + ) + .unwrap() + } + + fn assert_control(config: &Config, before: &str) { + assert_eq!( + current_source_sha256(&config.workspace_root).unwrap(), + before + ); + let all = events(config); + let observer = event(&all, "recovery_capability_observer_bound"); + assert_eq!(observer["port"], 60302); + assert_eq!(observer["observer_id"], "nextjs_browser_interaction_v1"); + let audit = event(&all, "recovery_preflight_control_audit"); + assert_eq!(audit["control_before_sha256"], before); + assert_eq!(audit["control_after_observation_sha256"], before); + assert_eq!(audit["control_status"], "unchanged"); + assert_eq!(audit["control_retained"], true); + assert_eq!(audit["restore_invoked"], false); + assert_eq!(audit["restore_succeeded"], false); + assert!(audit.get("control_after_restore_sha256").is_none()); + assert!( + !all.iter() + .any(|e| e["event"] == "recovery_plan_auto_run_start") + ); + } + + fn assert_evaluation_reached(events: &[Value]) { + let stages = events + .iter() + .filter(|e| e["event"] == "recovery_preflight_effect_observation") + .map(|e| e["stage"].as_str().unwrap()) + .collect::>(); + assert_eq!( + stages, + [ + "before_observation", + "nextjs_capabilities", + "registered_verification", + "completion_acceptance" + ] + ); + } + + #[test] + fn issue485_saved_sources_and_failed_core_boundary_are_traceable() { + let manifest = fixture_json("source-manifest.json"); + for file in manifest["files"].as_array().unwrap() { + let bytes = + std::fs::read(Path::new(FIXTURE).join(file["fixture"].as_str().unwrap())).unwrap(); + assert_eq!( + format!("{:x}", Sha256::digest(bytes)), + file["source_sha256"] + ); + assert_eq!(file["fixture_sha256"], file["source_sha256"]); + } + let boundary = fixture_json("failed-core-boundary.json"); + assert_eq!(boundary["completed_phase_ids"], json!(["project-setup"])); + assert_eq!(boundary["failed_phase_id"], "core-implementation"); + assert_eq!(boundary["core_step_count"], Value::Null); + assert_eq!(boundary["registered_verify_commands_from_failed_plan"], 0); + assert_eq!(boundary["planner_attempts"].as_array().unwrap().len(), 3); + assert_eq!(boundary["planner_attempts"][2]["status"], "exhausted"); + assert_eq!( + manifest["historical_observations"]["internal_r0_gate"], + "unknown" + ); + assert_eq!( + manifest["historical_observations"]["external_reference_goal"], + "reference_goal_fail" + ); + } + + #[test] + fn issue485_saved_scaffold_stops_real_recovery_and_process_without_restore() { + let root = tempfile::tempdir().unwrap(); + let (config, candidate) = configure(root.path(), "scaffold"); + assert!( + crate::planner::profiles::nextjs::is_engine_owned_scaffold_page( + PAGE, + &std::fs::read_to_string(root.path().join(PAGE)).unwrap() + ) + ); + let before = current_source_sha256(root.path()).unwrap(); + let mut planner = UnusedClient; + let mut execution = UnusedClient; + let mut driver = RunnerRecoveryDriver { + planner: &mut planner, + execution: &mut execution, + config: &config, + ui: &crate::tui::NoopUi, + transaction_snapshot: None, + transaction_treatment: None, + transaction_config: None, + transaction_observer_identity: None, + }; + // Enter after the saved core planning failure, without replaying model plans + // or treating the historical event stream as execution input. + let boundary = fixture_json("failed-core-boundary.json"); + let result = drive( + &config, + AttemptOutcome { + result: Err(anyhow::anyhow!( + boundary["error"].as_str().unwrap().to_owned() + )), + failure: Some(AttemptFailure::Recoverable(Box::new(candidate.clone()))), + }, + &mut driver, + ); + assert!(result.is_err()); + assert!(driver.transaction_snapshot.is_none()); + crate::tui::slash::emit_tui_command_stop(&config, "/ultra-plan-run", &result); + crate::emit_run_stop(&config, &result.map(|_| ())); + assert_control(&config, &before); + let all = events(&config); + assert_evaluation_reached(&all); + let expected = + std::fs::read_to_string(Path::new(FIXTURE).join("expected-events.jsonl")).unwrap(); + let mut cursor = 0; + // The first two events describe the frozen input boundary, not a replayed + // planner. Compare the remaining stable schema projections in actual order. + for line in expected.lines().skip(2) { + let expected: Value = serde_json::from_str(line).unwrap(); + let found = all[cursor..] + .iter() + .position(|actual| { + expected + .as_object() + .unwrap() + .iter() + .all(|(key, value)| actual.get(key) == Some(value)) + }) + .unwrap_or_else(|| panic!("missing ordered event {expected}; actual={all:?}")); + cursor += found + 1; + } + assert!(!all.iter().any(|e| matches!( + e["event"].as_str(), + Some( + "recovery_suppressed_current_success" + | "recovery_plan_auto_run_complete" + | "ultra_final_acceptance" + ) + ))); + let acceptance = runtime_acceptance(&observed_config(&config), &candidate); + assert!(!acceptance.passed); + assert!( + acceptance + .missing_evidence + .contains(&"implementation_artifact".into()) + ); + let page = acceptance + .artifact_obligations + .iter() + .find(|a| a.path == PAGE) + .unwrap(); + assert_eq!(page.role, "scaffold"); + assert!(!page.satisfies_implementation); + println!( + "ISSUE485_RECORD {}", + json!({ + "case": "scaffold", "kind": "scripted transport; production decisions", + "audit": event(&all, "recovery_preflight_control_audit"), + "preflight": event(&all, "recovery_preflight_observation"), + "stop": event(&all, "recovery_plan_auto_run_stopped"), + "runtime_acceptance": {"passed": acceptance.passed, "reason": acceptance.primary_reason, + "missing_evidence": acceptance.missing_evidence, "page_role": page.role}, + "terminal": {"task_status": event(&all, "run_stop")["task_status"], + "status": event(&all, "run_stop")["status"], + "final_acceptance_status": event(&all, "run_stop")["final_acceptance_status"]} + }) + ); + } + + #[test] + fn issue485_business_artifact_and_remaining_completion_gates_are_independent() { + for case in [ + "business-all", + "business-missing-interaction", + "business-build-failed", + "unrelated-api", + ] { + let root = tempfile::tempdir().unwrap(); + let (config, candidate) = configure(root.path(), case); + let before = current_source_sha256(root.path()).unwrap(); + let preflight = recovery_preflight(&config, &candidate, 0); + assert_control(&config, &before); + let mut observed = observed_config(&config); + let acceptance = runtime_acceptance(&observed, &candidate); + assert!( + !acceptance + .missing_evidence + .contains(&"implementation_artifact".into()), + "{case}: {acceptance:?}" + ); + let artifact = acceptance + .artifact_obligations + .iter() + .find(|a| a.path == PAGE) + .unwrap(); + assert_eq!(artifact.satisfies_implementation, case != "unrelated-api"); + if case == "unrelated-api" { + let api = crate::minimal_loop::evidence::artifact_obligation_evidence( + &observed.workspace_root, + &["src/app/api/health/route.ts".into()], + &[], + ); + assert!(api[0].satisfies_implementation); + } + match case { + "business-all" => { + assert!( + matches!(preflight, RecoveryPreflight::CurrentSuccess { .. }), + "{preflight:?}" + ); + assert_evaluation_reached(&events(&config)); + assert!(acceptance.passed, "{acceptance:?}"); + for required in fixture_json("completion-contract.json")["required_evidence"] + .as_array() + .unwrap() + { + assert_eq!( + acceptance.evidence_tiers[required.as_str().unwrap()], + "strong" + ); + } + assert!(acceptance.missing_capabilities.is_empty()); + assert!(acceptance.missing_obligations.is_empty()); + } + "business-build-failed" => { + assert!( + matches!(&preflight, RecoveryPreflight::Unavailable { reason } + if reason == "nextjs_route_observation_failed:build_verifier_failed"), + "{preflight:?}" + ); + assert!( + !events(&config) + .iter() + .any(|e| e["stage"] == "completion_acceptance") + ); + } + _ => { + assert!( + matches!(&preflight, RecoveryPreflight::Unavailable { reason } + if reason.contains("nextjs_interaction_observation_unavailable")), + "{case}: {preflight:?}" + ); + assert_evaluation_reached(&events(&config)); + } + } + // Preflight's CurrentSuccess alone is never used as final acceptance. + // Invoke the normal final gate with the identical contract and inputs. + observed.eval_events_path = Some( + observed + .workspace_root + .join(".commandagent/final-events.jsonl"), + ); + let final_report = + crate::planner::runner::recovery_acceptance::final_acceptance_report( + &candidate.plan, + &observed, + ) + .unwrap(); + let final_events = events(&observed); + let final_event = event(&final_events, "ultra_final_acceptance"); + // VerificationReport covers executable/static checks; missing browser + // evidence can leave it Pass while the real final gate stays partial. + if case == "business-all" { + assert_eq!(final_event["external_contract_ok"], true); + assert_eq!(final_event["runtime_acceptance_passed"], true); + assert_eq!(final_event["final_acceptance_status"], "full_success"); + assert!(final_report.is_pass()); + assert_eq!(final_event["release_gate_status"], "pass"); + } else if case == "business-build-failed" { + assert!(!final_report.is_pass()); + assert_eq!(final_event["external_contract_ok"], false); + } else if case == "unrelated-api" { + assert!(!final_report.is_pass()); + assert_eq!(final_event["final_acceptance_status"], "incomplete"); + assert_eq!(final_event["release_gate_status"], "failed"); + } else { + assert_eq!(final_event["final_acceptance_status"], "partial"); + assert_eq!(final_event["release_gate_status"], "partial"); + assert!( + final_event["primary_reason"] + .as_str() + .unwrap() + .contains("browser_interaction_evidence_required") + ); + } + let terminal_result = if final_report.is_pass() { + Ok("verification complete".into()) + } else { + Err(anyhow::anyhow!(final_report.primary_reason())) + }; + crate::tui::slash::emit_tui_command_stop( + &observed, + "/ultra-plan-run", + &terminal_result, + ); + let terminal_events = events(&observed); + let terminal = event(&terminal_events, "tui_command_stop"); + assert_eq!( + terminal["task_status"], + if case == "business-all" { + "complete" + } else if case == "business-missing-interaction" { + "partial" + } else { + "failed" + } + ); + if case != "business-all" { + assert_ne!(terminal["completion_status"], "complete"); + } + println!( + "ISSUE485_RECORD {}", + json!({ + "case": case, "kind": "scripted transport; production decisions", + "preflight": format!("{preflight:?}"), + "audit": event(&events(&config), "recovery_preflight_control_audit"), + "runtime_acceptance": {"passed": acceptance.passed, "missing_evidence": acceptance.missing_evidence, + "missing_capabilities": acceptance.missing_capabilities, "missing_obligations": acceptance.missing_obligations, + "evidence_tiers": acceptance.evidence_tiers, "page_role": artifact.role}, + "verification_report_passed": final_report.is_pass(), + "final": {"status": final_event["final_acceptance_status"], "release_gate": final_event["release_gate_status"], + "external_contract_ok": final_event["external_contract_ok"], "runtime_acceptance_passed": final_event["runtime_acceptance_passed"], + "reason": final_event["primary_reason"], "release_gate_reasons": final_event["release_gate_reasons"], + "assurance": final_event["assurance_level"]}, + "terminal": {"task_status": terminal["task_status"], "completion_status": terminal["completion_status"], + "command_status": terminal["status"]} + }) + ); + } + } +} diff --git a/src/planner/recovery_snapshot.rs b/src/planner/recovery_snapshot.rs index 90d6161b5..c8353bb76 100644 --- a/src/planner/recovery_snapshot.rs +++ b/src/planner/recovery_snapshot.rs @@ -212,6 +212,17 @@ fn prepare_runtime_workspace( } symlink_directory(&source, &destination)?; } + #[cfg(test)] + { + // Carry the existing browser test transport into disposable observers. + // This keeps fixture contract ports independent of actual test sockets. + let relative = ".anvil/evidence/browser-probe-command.json"; + if root.join(relative).is_file() { + let target = treatment.join(relative); + std::fs::create_dir_all(target.parent().expect("probe input parent"))?; + std::fs::copy(root.join(relative), target)?; + } + } Ok(treatment) } diff --git a/src/planner/runner/recovery_acceptance.rs b/src/planner/runner/recovery_acceptance.rs index 122d5fb7b..3f05e3368 100644 --- a/src/planner/runner/recovery_acceptance.rs +++ b/src/planner/runner/recovery_acceptance.rs @@ -8,3 +8,11 @@ pub(crate) fn runtime_acceptance_report( ) -> anyhow::Result { super::adjudication_create::ultra_contract_runtime_acceptance_report(plan, config) } + +#[cfg(test)] +pub(crate) fn final_acceptance_report( + plan: &UltraPlan, + config: &Config, +) -> anyhow::Result { + super::final_acceptance::ultra_final_acceptance_report(plan, config) +} diff --git a/src/tui/slash.rs b/src/tui/slash.rs index 75c190b8c..3026ecd89 100644 --- a/src/tui/slash.rs +++ b/src/tui/slash.rs @@ -935,7 +935,7 @@ fn panic_payload_message(payload: &(dyn Any + Send)) -> String { } #[cfg(test)] -fn emit_tui_command_stop( +pub(crate) fn emit_tui_command_stop( config: &Config, command: &str, result: &anyhow::Result, diff --git a/tests/corpus/apps/issue485-scaffold-completion/README.md b/tests/corpus/apps/issue485-scaffold-completion/README.md new file mode 100644 index 000000000..1fca7b492 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/README.md @@ -0,0 +1,78 @@ +# Saved failed-core scaffold boundary (#485) + +The original R0 session is `01a09b8b-f70e-7aa1-84f1-40a18d62da17`, historical +HEAD `b7b65f8fd75793f2075491889c66372b02a06d14`. The regression is implemented +against #484-integrated parent `0a8222643683c764fa5f9ac8bd3e5b2b47ef7728`. +`source-manifest.json` records the original paths, SHA-256 hashes, central event +positions (one-based), line hashes including newline, and reduction mappings. +Those historical absolute paths are provenance only; execution needs this corpus +and the current test binary. No historical files are changed. + +`scaffold/` contains the eight required source/configuration files plus +`next-env.d.ts`, copied byte-for-byte. Package locks, dependencies, build outputs, +runtime metadata, screenshots, timing and unrelated logs are omitted. The exact +original completion contract and UltraPlan retain their goal, required paths, +capabilities, evidence, obligations, commands and phases. The failed-core +boundary records setup completion, core planning exhaustion after three proposals, +no accepted core steps, and the remaining phases. It is a frozen input boundary; +the corrected #484 planner is not required to reject its now-supported proposal. + +`expected-events.jsonl` is a stable projection of the recorded events. The first +two records describe the frozen boundary; the subsequent records are matched in +order against newly emitted production events. Tests do not inject these events +or compare random session IDs/timestamps. Control hashes are compared to freshly +measured source hashes rather than the historical workspace's hash. + +`observations/saved-scaffold-interaction.json` retains the original successful +generic interaction fields (including its heuristic mode and missing hooks). +This observation cannot prove project-management behavior. `business/` is a +small, synthetic route-bound implementation of project creation, dated tasks, +member assignment and status filtering. Its observation input is explicitly +scripted and carries usable contract hooks; it is a controlled acceptance input, +not a measured browser result or an external R0 success. + +`runtime.sh` supplies deterministic npm build exits, a disposable HTTP child and +the existing `cfg(test)` interaction input seam. Every scripted build compares +the source bytes to the case's frozen source snapshot. These are transport +responses, not replacement acceptance decisions or a real compiler run. There +is no dependency download, live model, browser/GUI evaluation or shared server +operation. Production code still performs classification, verification dispatch, +runtime acceptance, isolated preflight, control audit, bounded Recovery +suppression, terminal projection and normal final acceptance. + +Controls are independent: + +- Saved scaffold: registered build and generic interaction pass; implementation + evidence is missing. The real driver suppresses Recovery at 0/2, control is + unchanged, no restore occurs, and the process ends failed with final acceptance + not checked. +- Business implementation with all scripted contract observations: evaluate + CurrentSuccess and ordinary final acceptance separately; require every gate. +- Same business source with missing interaction, and separately failed build: + keep the unrelated failure even though implementation evidence exists. +- Scaffold plus an unrelated health API and missing interaction: observe the API + as an implementation artifact independently of the still incomplete goal. + +This corpus does not establish persistence, live business operations, a completed +historical core phase, external R0 acceptance, CI or exact-HEAD UAT. Those remain +separate observations owned by the parent workflow. + +Transport isolation and terminal layers + +The original contract, package scripts and registered observer still require +60302. Each test uses an OS-assigned loopback port for its HTTP child via the +existing `browser-probe-command.json` test override (`require_build=true`). A +`cfg(test)` leaf hook carries only that override into the disposable observation. +This avoids both intra-test competition and collisions with other tests or the +user's service at 60302. The sandbox cannot bind even these test-only sockets; +the required tests therefore run outside it. No shared processes are stopped. + +The saved lifecycle includes a TUI stop before process stop. The test invokes +the real TUI emitter with the actual failed drive result, then the real process +emitter; it does not inject the saved event. Final controls separately record +VerificationReport, runtime acceptance, release/final fields and task projection. +Missing interaction may retain a passing VerificationReport and partial release +gate/task. A failed build may retain a release-derived `full_success` field when +that gate is not applicable, while external verification and the task fail. +Neither one field nor command completion substitutes for full task acceptance. +The negative controls assert those remaining gates explicitly. diff --git a/tests/corpus/apps/issue485-scaffold-completion/business/src/app/page.tsx b/tests/corpus/apps/issue485-scaffold-completion/business/src/app/page.tsx new file mode 100644 index 000000000..400530de2 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/business/src/app/page.tsx @@ -0,0 +1,57 @@ +"use client"; +import { useState } from "react"; + +type Task = { title: string; deadline: string; member: string; project: string; status: string }; + +export default function Page() { + const [projects, setProjects] = useState([]); + const [tasks, setTasks] = useState([]); + const [projectName, setProjectName] = useState(""); + const [project, setProject] = useState(""); + const [title, setTitle] = useState(""); + const [deadline, setDeadline] = useState(""); + const [member, setMember] = useState("田中"); + const [filter, setFilter] = useState("all"); + const visibleTasks = tasks.filter(task => filter === "all" || task.status === filter); + return
+

プロジェクト管理

+
{ + event.preventDefault(); + if (!projectName.trim()) return; + setProjects(previous => [...previous, projectName.trim()]); + setProject(projectName.trim()); + setProjectName(""); + }}> + + +
+
    {projects.map((name, index) =>
  • {name}
  • )}
+
{ + event.preventDefault(); + if (!title.trim() || !deadline || !project) return; + setTasks(previous => [...previous, {title, deadline, member, project, status: "未着手"}]); + setTitle(""); + }}> + + + + + +
+ +
    {visibleTasks.map(task =>
  • + {task.project}: {task.title} / {task.deadline} / {task.member} + +
  • )}
+
; +} diff --git a/tests/corpus/apps/issue485-scaffold-completion/completion-contract.json b/tests/corpus/apps/issue485-scaffold-completion/completion-contract.json new file mode 100644 index 000000000..e0b2edd87 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/completion-contract.json @@ -0,0 +1,45 @@ +{ + "required_paths": [ + "package.json", + "tsconfig.json", + "postcss.config.js", + "tailwind.config.ts", + "src/app/layout.tsx", + "src/app/page.tsx", + "src/app/globals.css", + "src/app/global.d.ts" + ], + "protected_paths": [], + "verify_commands": [ + "npm run build" + ], + "profile": "nextjs", + "goal": "小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。", + "required_capabilities": [ + "stateful_interaction", + "user_input_or_action", + "visible_state_change" + ], + "deterministic_oracles": [], + "required_evidence": [ + "nextjs_route_evidence", + "build_command_or_dependency_missing_boundary", + "implementation_artifact", + "visible_interactive_surface_evidence", + "user_input_handler_evidence", + "stateful_update_evidence", + "interactive_ui_source_evidence", + "non_static_screen_evidence" + ], + "evidence_hint_tokens": [ + "プロジェクト", + "ユーザー", + "タスク", + "チームメンバー" + ], + "required_obligations": [ + "implementation" + ], + "deferred_verify_requirements": [], + "verify_repair_cap": 2 +} diff --git a/tests/corpus/apps/issue485-scaffold-completion/expectations.toml b/tests/corpus/apps/issue485-scaffold-completion/expectations.toml new file mode 100644 index 000000000..8fcd669cf --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/expectations.toml @@ -0,0 +1,14 @@ +case_id = "issue485-scaffold-completion" +source = "Saved R0 failed-core boundary; source-manifest.json records immutable provenance" +required_paths = ["completion-contract.json", "original-plan.yaml", "failed-core-boundary.json", "source-manifest.json", "expected-events.jsonl", "scaffold/src/app/page.tsx", "business/src/app/page.tsx", "observations/saved-scaffold-interaction.json", "observations/business-interaction.json", "runtime.sh", "README.md"] + +[compile] +expect = "not_checked" + +[fixture_contains] +failed-core-boundary.json = ["core-implementation", "phase_scaffold_error", "exhausted"] +completion-contract.json = ["implementation_artifact", "stateful_interaction", "npm run build"] +source-manifest.json = ["013b9e9015d3da335226c7f55a34c9da4cd4523921674b25e5210a36b88e2ed8", "524d9f569a6dd702b89180e2f1acfff82b458f7afafaaf0d1da297f1a90f1122", "reference_goal_fail"] + +[fixture_ordered] +expected-events.jsonl = ["ultra_phase_failed", "ultra_partial_artifact_summary", "registered_verification", "completion_acceptance", "recovery_preflight_control_audit", "recovery_preflight_observation", "recovery_suppressed_verification_inconsistency", "recovery_plan_auto_run_stopped", "run_stop"] diff --git a/tests/corpus/apps/issue485-scaffold-completion/expected-events.jsonl b/tests/corpus/apps/issue485-scaffold-completion/expected-events.jsonl new file mode 100644 index 000000000..36dc51e58 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/expected-events.jsonl @@ -0,0 +1,9 @@ +{"event":"ultra_phase_failed","schema_version":"1","phase_id":"core-implementation","stage":"scaffold","ok":false,"reason":"verifier plan admission exhausted existing 3-attempt planner budget: Recovery verifier binding ProposalRepairable: formation lost original check/expected result or its complete scope and boundary: node -p \"require('./package.json').scripts.dev\"; keep it on the complete owner or a Verify after all corresponding output owners","step_count":null} +{"event":"ultra_partial_artifact_summary","schema_version":"1","completed_phase_ids":["project-setup"],"failed_phase_id":"core-implementation","failure_kind":"phase_scaffold_error","pending_phase_ids":["contract-wiring","build-verification"],"status":"incomplete"} +{"event":"recovery_preflight_effect_observation","schema_version":"1","stage":"registered_verification","status":"observed"} +{"event":"recovery_preflight_effect_observation","schema_version":"1","stage":"completion_acceptance","status":"observed"} +{"event":"recovery_preflight_control_audit","schema_version":"1","control_audited":true,"control_status":"unchanged","control_retained":true,"observation_isolated":true,"restore_invoked":false,"restore_succeeded":false,"isolated_outcome":"verification_inconsistency","isolated_reason":"registered_observations_passed_but_completion_contract_acceptance_failed:missing_required_evidence:implementation_artifact"} +{"event":"recovery_preflight_observation","schema_version":"1","source":"product_visible_completion_contract","status":"verification_inconsistency","reason":"registered_observations_passed_but_completion_contract_acceptance_failed:missing_required_evidence:implementation_artifact","observation_isolated":true,"observation_phase":"pre_recovery","read_only":true,"external_oracle_used":false,"recovery_failed_step":null,"recovery_handoff_kind":"phase_scaffold_error","registered_verify_command_count":1,"verify_command_count":1,"completion_contract_sha256":"eb2b04647a28ab10508c21e9c1cd026bea293a1c7f95dda261f4bd5529d8c9ab","observer_ids":["completion_contract_verify_commands","nextjs_browser_interaction_v1"]} +{"event":"recovery_suppressed_verification_inconsistency","schema_version":"1","recovery_plan_auto_runs":2,"recovery_plan_auto_runs_used":0,"recovery_plan_auto_run_current":0,"recovery_plan_auto_run_stop_reason":"verification_inconsistency"} +{"event":"recovery_plan_auto_run_stopped","schema_version":"1","recovery_plan_auto_runs":2,"recovery_plan_auto_runs_used":0,"recovery_plan_auto_run_current":0,"recovery_plan_auto_run_stop_reason":"verification_inconsistency"} +{"event":"run_stop","schema_version":"1","ok":false,"status":"failed","task_status":"failed","command_completion_state":"failed","final_acceptance_status":"not_checked","runtime_acceptance_status":"not_checked","assurance_level":"partial"} diff --git a/tests/corpus/apps/issue485-scaffold-completion/failed-core-boundary.json b/tests/corpus/apps/issue485-scaffold-completion/failed-core-boundary.json new file mode 100644 index 000000000..dd004f537 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/failed-core-boundary.json @@ -0,0 +1,37 @@ +{ + "completed_phase_ids": [ + "project-setup" + ], + "failed_phase_id": "core-implementation", + "pending_phase_ids": [ + "contract-wiring", + "build-verification" + ], + "stage": "scaffold", + "failure_kind": "phase_scaffold_error", + "error": "verifier plan admission exhausted existing 3-attempt planner budget: Recovery verifier binding ProposalRepairable: formation lost original check/expected result or its complete scope and boundary: node -p \"require('./package.json').scripts.dev\"; keep it on the complete owner or a Verify after all corresponding output owners", + "core_step_count": null, + "registered_verify_commands_from_failed_plan": 0, + "failed_step": null, + "recovery_limit": 2, + "planner_attempts": [ + { + "source_line": 60, + "attempt": 1, + "status": "retry", + "reason": "Recovery verifier binding ProposalRepairable: preclosure weak inline verification requires formation: node -p \"require('./package.json').scripts.dev\": node_smoke_without_assertion (immutable_inline_evidence; app edits cannot strengthen this fixed verifier)\nPreserve each original import target and pass result. Replace a pure import only with a target-specific check in this closed shape: node -e \"import('./original-path.js').then(actual=>{require('node:assert/strict').deepStrictEqual(actual.exportName,42)})\". Supply the actual required export/value, or actual.exportName(...[literal JSON arguments]) and a literal JSON expected result. Alternatively explicitly propose the sorted runtime export-name set using deepStrictEqual(Object.keys(actual).sort(),[literal JSON names]); an explicitly empty set can describe a type-only module. That alternative verifies loadability and runtime export boundary only, not interface fields or business behavior. Do not infer its expectation from a filename. Preserve all original instructions, outputs and owner order. No unrelated assertion, added check beside the weak original, catch or success override discharges it. Unsupported checks must be reproposed with preserved scope or stop within the current budget." + }, + { + "source_line": 64, + "attempt": 2, + "status": "retry", + "reason": "Recovery verifier binding ProposalRepairable: formation dropped original required output owner package.json" + }, + { + "source_line": 68, + "attempt": 3, + "status": "exhausted", + "reason": "Recovery verifier binding ProposalRepairable: formation lost original check/expected result or its complete scope and boundary: node -p \"require('./package.json').scripts.dev\"; keep it on the complete owner or a Verify after all corresponding output owners" + } + ] +} diff --git a/tests/corpus/apps/issue485-scaffold-completion/observations/business-interaction.json b/tests/corpus/apps/issue485-scaffold-completion/observations/business-interaction.json new file mode 100644 index 000000000..4de1096d1 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/observations/business-interaction.json @@ -0,0 +1,32 @@ +{ + "fixture_kind": "scripted observation input; no browser or model execution", + "ok": true, + "status": "passed", + "probe_mode": "contract", + "contract_hook_status": "usable", + "action_hooks": [ + "primary", + "input" + ], + "state_dimensions_changed": [ + "projects", + "projectName" + ], + "surface_visible": true, + "start_transition": true, + "input_state_evaluated_after_start": true, + "input_state_change": true, + "state_changed": true, + "visible_state_changed": true, + "steps": [ + "surface_visible", + "start_transition", + "control_input_dispatched", + "input_state_evaluated_after_start", + "input_state_change" + ], + "before_marker": "{\"projects\":[],\"projectName\":\"\"}", + "after_marker": "{\"projects\":[{\"name\":\"Client project\"}],\"projectName\":\"\"}", + "interaction_performed": true, + "input_event_observed": true +} diff --git a/tests/corpus/apps/issue485-scaffold-completion/observations/saved-scaffold-interaction.json b/tests/corpus/apps/issue485-scaffold-completion/observations/saved-scaffold-interaction.json new file mode 100644 index 000000000..abbaa2323 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/observations/saved-scaffold-interaction.json @@ -0,0 +1,33 @@ +{ + "ok": true, + "status": "passed", + "probe_mode": "heuristic", + "contract_hook_status": "primary_missing", + "action_hooks": [], + "state_dimensions_changed": [], + "start_transition": true, + "input_state_evaluated_after_start": true, + "input_state_change": true, + "state_changed": true, + "visible_state_changed": true, + "steps": [ + "warmup_navigation", + "measured_navigation", + "surface_visible", + "start_transition", + "text_entry:not_applicable", + "ArrowLeft_keydown_hold", + "ArrowRight_keydown_hold", + "Space_keydown_hold", + "control_input_dispatched", + "input_key_hold", + "input_state_evaluated_after_start", + "input_state_change", + "recovery_transition" + ], + "before_marker": "{\"buttons\":\"Left|Action|Right|Reset\",\"body\":\"INTERACTIVE CHALLENGE SCORE 0 LIVES 3 LIVE Left Action Right Reset\",\"element_count\":72,\"canvases\":[]}", + "after_marker": "{\"buttons\":\"Left|Action|Right|Reset\",\"body\":\"INTERACTIVE CHALLENGE SCORE 0 LIVES 3 LIVE Left Action Right Reset\",\"element_count\":73,\"canvases\":[]}", + "interaction_performed": true, + "input_event_observed": true, + "failure_category": "" +} diff --git a/tests/corpus/apps/issue485-scaffold-completion/original-plan.yaml b/tests/corpus/apps/issue485-scaffold-completion/original-plan.yaml new file mode 100644 index 000000000..2c7d4501a --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/original-plan.yaml @@ -0,0 +1,19 @@ +# CommandAgent editable plan YAML. Comments are ignored. +# Edit values, add or remove list items, then validate before execution. +# Validation never executes the plan: commandagent --validate-plan +# profile/style/intent preserve the generated execution context. +# phases run in order; each id must be unique and each prompt a focused task. +# Run only after validation: commandagent --run-ultra-plan +goal: "小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。" +profile: "nextjs" +style: "default" +intent: "create" +phases: + - id: "project-setup" + prompt: "Scaffold and setup the Next.js App Router project shell. Create or complete the package manifest, TypeScript config, styling config, and route-bound scaffold so the deterministic nextjs-scaffold template owns setup artifacts." + - id: "core-implementation" + prompt: "Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result." + - id: "contract-wiring" + prompt: "Wire controls and data-anvil observability. Preserve or add data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification." + - id: "build-verification" + prompt: "Run build verification for the deterministic Next.js scaffold. Verify package scripts, dependency boundary, and npm run build / next build only; keep this final phase verification-only." diff --git a/tests/corpus/apps/issue485-scaffold-completion/runtime.sh b/tests/corpus/apps/issue485-scaffold-completion/runtime.sh new file mode 100644 index 000000000..cd55bfbdb --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/runtime.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# Test transport only. Build/interaction inputs are scripted, not live evidence. +set -eu +runtime=$(CDPATH= cd -- "$(dirname -- "$0")/../.issue485" && pwd) +[ "$#" -eq 2 ] && [ "$1" = run ] || exit 90 +mkdir -p .commandagent/evidence +case "$2" in + build) + while IFS= read -r relative; do + cmp "$runtime/expected/$relative" "$relative" || exit 91 + done < "$runtime/inputs.txt" + printf '%s\n' 'npm run build: scripted input' >> .commandagent/evidence/issue485-commands.txt + mkdir -p .anvil/evidence + cp "$runtime/availability.json" .anvil/evidence/interaction-probe-availability.json + cp "$runtime/interaction.json" .anvil/evidence/interaction-probe-result.json + code=$(cat "$runtime/build-exit.txt") + if [ "$code" != 0 ]; then printf '%s\n' 'Failed to compile: scripted build failure' >&2; fi + exit "$code" + ;; + start) + [ "$PORT" = "$(cat "$runtime/transport-port.txt")" ] || exit 92 + printf '%s\n' 'npm run start: scripted HTTP input' >> .commandagent/evidence/issue485-commands.txt + test_exe=$(cat "$runtime/test-exe.txt") + exec env COMMANDAGENT_BROWSER_PROBE_MOCK_CHILD=1 \ + COMMANDAGENT_BROWSER_PROBE_MOCK_PORT="$PORT" \ + COMMANDAGENT_BROWSER_PROBE_MOCK_STATUS=200 \ + "$test_exe" --ignored --exact \ + minimal_loop::browser_probe::tests::browser_probe_mock_server_child --nocapture + ;; + *) exit 93 ;; +esac diff --git a/tests/corpus/apps/issue485-scaffold-completion/scaffold/next-env.d.ts b/tests/corpus/apps/issue485-scaffold-completion/scaffold/next-env.d.ts new file mode 100644 index 000000000..40c3d6809 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/scaffold/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/tests/corpus/apps/issue485-scaffold-completion/scaffold/package.json b/tests/corpus/apps/issue485-scaffold-completion/scaffold/package.json new file mode 100644 index 000000000..9ffcb53f9 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/scaffold/package.json @@ -0,0 +1,24 @@ +{ + "dependencies": { + "next": "^14.2.0", + "react": "^18.3.0", + "react-dom": "^18.3.0" + }, + "devDependencies": { + "@types/node": "^20.14.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.15", + "tailwindcss": "^3.4.19", + "typescript": "^5.5.0" + }, + "name": "commandagent-nextjs-app", + "private": true, + "scripts": { + "build": "next build", + "dev": "next dev -p 60302", + "start": "next start -p 60302" + }, + "version": "1.0.0" +} diff --git a/tests/corpus/apps/issue485-scaffold-completion/scaffold/postcss.config.js b/tests/corpus/apps/issue485-scaffold-completion/scaffold/postcss.config.js new file mode 100644 index 000000000..cce4985fb --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/scaffold/postcss.config.js @@ -0,0 +1 @@ +module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } }; diff --git a/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/global.d.ts b/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/global.d.ts new file mode 100644 index 000000000..cbe652dbe --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/global.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/globals.css b/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/globals.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/layout.tsx b/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/layout.tsx new file mode 100644 index 000000000..35dee2cef --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/layout.tsx @@ -0,0 +1,17 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "Interactive Challenge", + description: "A compact interactive challenge generated by commandagent", +}; + +export default function RootLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/page.tsx b/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/page.tsx new file mode 100644 index 000000000..f1de1fbe4 --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/scaffold/src/app/page.tsx @@ -0,0 +1,187 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; + +type Hazard = { id: number; x: number; y: number; alive: boolean }; +type Pulse = { x: number; y: number }; + +const columns = 9; +const rows = 4; + +function initialHazards(): Hazard[] { + return Array.from({ length: columns * rows }, (_, id) => ({ + id, + x: 8 + (id % columns) * 10, + y: 12 + Math.floor(id / columns) * 8, + alive: true, + })); +} + +export default function Page() { + const [player, setPlayer] = useState(50); + const [pulses, setPulses] = useState([]); + const [hazards, setHazards] = useState(() => initialHazards()); + const [tick, setTick] = useState(0); + const [running, setRunning] = useState(true); + const [lives, setLives] = useState(3); + + useEffect(() => { + const onKey = (event: KeyboardEvent) => { + if (event.key === "ArrowLeft") setPlayer((value) => Math.max(5, value - 4)); + if (event.key === "ArrowRight") setPlayer((value) => Math.min(95, value + 4)); + if (event.key === " ") setPulses((value) => [...value, { x: player, y: 86 }].slice(-6)); + if (event.key.toLowerCase() === "r") { + setHazards(initialHazards()); + setPulses([]); + setRunning(true); + setLives(3); + } + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [player]); + + useEffect(() => { + if (!running) return; + const timer = window.setInterval(() => { + setTick((value) => value + 1); + setPulses((value) => value.map((pulse) => ({ ...pulse, y: pulse.y - 5 })).filter((pulse) => pulse.y > 4)); + setHazards((value) => + value.map((hazard) => ({ + ...hazard, + x: hazard.x + Math.sin((tick + hazard.id) / 8) * 0.45, + y: hazard.y + 0.035, + })), + ); + }, 70); + return () => window.clearInterval(timer); + }, [running, tick]); + + useEffect(() => { + setHazards((current) => + current.map((hazard) => { + if (!hazard.alive) return hazard; + const hit = pulses.some((pulse) => Math.abs(pulse.x - hazard.x) < 3.2 && Math.abs(pulse.y - hazard.y) < 3.8); + return hit ? { ...hazard, alive: false } : hazard; + }), + ); + }, [pulses]); + + const alive = hazards.filter((hazard) => hazard.alive).length; + const score = useMemo(() => (columns * rows - alive) * 100, [alive]); + + useEffect(() => { + const breach = hazards.some((hazard) => hazard.alive && hazard.y > 78); + if (breach) setLives((value) => Math.max(0, value - 1)); + if (alive === 0 || breach || lives === 0) { + setRunning(false); + } + }, [alive, hazards, lives]); + + return ( +
+
+ INTERACTIVE CHALLENGE + SCORE {score} + LIVES {lives} + {running ? "LIVE" : alive === 0 ? "CLEAR" : "RESET READY"} +
+
+
+ {hazards.map((hazard) => + hazard.alive ? ( +
+ ) : null, + )} + {pulses.map((pulse, index) => ( +
+ ))} +
+
+ + +
+ ); +} diff --git a/tests/corpus/apps/issue485-scaffold-completion/scaffold/tailwind.config.ts b/tests/corpus/apps/issue485-scaffold-completion/scaffold/tailwind.config.ts new file mode 100644 index 000000000..dff7a46ca --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/scaffold/tailwind.config.ts @@ -0,0 +1,13 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { extend: {} }, + plugins: [], +}; + +export default config; diff --git a/tests/corpus/apps/issue485-scaffold-completion/scaffold/tsconfig.json b/tests/corpus/apps/issue485-scaffold-completion/scaffold/tsconfig.json new file mode 100644 index 000000000..2be6b911c --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/scaffold/tsconfig.json @@ -0,0 +1 @@ +{"compilerOptions":{"target":"ES2017","lib":["dom","dom.iterable","esnext"],"allowJs":true,"skipLibCheck":true,"strict":true,"noEmit":true,"esModuleInterop":true,"module":"esnext","moduleResolution":"bundler","resolveJsonModule":true,"isolatedModules":true,"jsx":"preserve","incremental":true,"plugins":[{"name":"next"}],"baseUrl":".","paths":{"@/*":["./src/*"]}},"include":["next-env.d.ts","src/**/*","app/**/*","pages/**/*","components/**/*","*.ts","*.tsx",".next/types/**/*.ts"],"exclude":["node_modules"]} \ No newline at end of file diff --git a/tests/corpus/apps/issue485-scaffold-completion/source-manifest.json b/tests/corpus/apps/issue485-scaffold-completion/source-manifest.json new file mode 100644 index 000000000..7868dde0f --- /dev/null +++ b/tests/corpus/apps/issue485-scaffold-completion/source-manifest.json @@ -0,0 +1,268 @@ +{ + "session_id": "01a09b8b-f70e-7aa1-84f1-40a18d62da17", + "historical_head": "b7b65f8fd75793f2075491889c66372b02a06d14", + "production_parent": "0a8222643683c764fa5f9ac8bd3e5b2b47ef7728", + "central_event_path": "/Users/maenokota/share/work/localwork/commandagent_trial/.commandagent/runs/01a09b8b-f70e-7aa1-84f1-40a18d62da17/events.jsonl", + "central_event_sha256": "524d9f569a6dd702b89180e2f1acfff82b458f7afafaaf0d1da297f1a90f1122", + "central_event_count": 86, + "event_projection": [ + { + "line": 69, + "event": "ultra_phase_failed", + "line_sha256_including_newline": "1e7c2e7de0d8dd0ca0875aea889f2310d405338a503ee3c429540eec26963d37", + "retained_fields": [ + "event", + "schema_version", + "phase_id", + "stage", + "ok", + "reason", + "step_count" + ] + }, + { + "line": 72, + "event": "ultra_partial_artifact_summary", + "line_sha256_including_newline": "782776583b25f138d3af6c26db1a58eff6c1c8fa370ec48c53e6ef5caabeac56", + "retained_fields": [ + "event", + "schema_version", + "completed_phase_ids", + "failed_phase_id", + "failure_kind", + "pending_phase_ids", + "status" + ] + }, + { + "line": 78, + "event": "recovery_preflight_effect_observation", + "line_sha256_including_newline": "94fd55186bf57d2ebbb5ae3287a0d84afbe28503dc3e6d68813c61f161c811db", + "retained_fields": [ + "event", + "schema_version", + "stage", + "status" + ] + }, + { + "line": 79, + "event": "recovery_preflight_effect_observation", + "line_sha256_including_newline": "ec1293fd226c9aa7a09599fbf8819bcc63487639bad9b8bcc63c66ffd5f1f0ab", + "retained_fields": [ + "event", + "schema_version", + "stage", + "status" + ] + }, + { + "line": 80, + "event": "recovery_preflight_control_audit", + "line_sha256_including_newline": "825c4c79e75632596ba2baf395ee1f89d4902a1c4a6887997d01fcb915bd8fb7", + "retained_fields": [ + "event", + "schema_version", + "control_audited", + "control_status", + "control_retained", + "observation_isolated", + "restore_invoked", + "restore_succeeded", + "isolated_outcome", + "isolated_reason" + ] + }, + { + "line": 81, + "event": "recovery_preflight_observation", + "line_sha256_including_newline": "c5da54f0860ce2761f14e400013d6c6dc840935c1857766903e3022df2055628", + "retained_fields": [ + "event", + "schema_version", + "source", + "status", + "reason", + "observation_isolated", + "observation_phase", + "read_only", + "external_oracle_used", + "recovery_failed_step", + "recovery_handoff_kind", + "registered_verify_command_count", + "verify_command_count", + "completion_contract_sha256", + "observer_ids" + ] + }, + { + "line": 82, + "event": "recovery_suppressed_verification_inconsistency", + "line_sha256_including_newline": "77a3dd249166e43d68a0cf5f3b8bfd5d7e6d19f847ee91a943a5cb357a4a4935", + "retained_fields": [ + "event", + "schema_version", + "recovery_plan_auto_runs", + "recovery_plan_auto_runs_used", + "recovery_plan_auto_run_current", + "recovery_plan_auto_run_stop_reason" + ] + }, + { + "line": 83, + "event": "recovery_plan_auto_run_stopped", + "line_sha256_including_newline": "800c85a2da6b94c6062422f8ca11e84faaa929c32537c93508179119079fd11e", + "retained_fields": [ + "event", + "schema_version", + "recovery_plan_auto_runs", + "recovery_plan_auto_runs_used", + "recovery_plan_auto_run_current", + "recovery_plan_auto_run_stop_reason" + ] + }, + { + "line": 86, + "event": "run_stop", + "line_sha256_including_newline": "eee572d42761fd78e2f411a6d0bba6f951ead469e6f963fd6e62a69a89cc7401", + "retained_fields": [ + "event", + "schema_version", + "ok", + "status", + "task_status", + "command_completion_state", + "final_acceptance_status", + "runtime_acceptance_status", + "assurance_level" + ] + } + ], + "files": [ + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/package.json", + "source_sha256": "9b897bb8402d38f57ca730a2b1265757a4fc8181528daa63d49729253d4b61c7", + "fixture": "scaffold/package.json", + "fixture_sha256": "9b897bb8402d38f57ca730a2b1265757a4fc8181528daa63d49729253d4b61c7" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/tsconfig.json", + "source_sha256": "a73c7e5699d9d60d5c118f2d6a120d9b38cdf031d32254288c4baa95b782a62c", + "fixture": "scaffold/tsconfig.json", + "fixture_sha256": "a73c7e5699d9d60d5c118f2d6a120d9b38cdf031d32254288c4baa95b782a62c" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/postcss.config.js", + "source_sha256": "fe0d5cf3cb8c1922f71cca2da3796f3e63366623ad6164336655766efe330e6a", + "fixture": "scaffold/postcss.config.js", + "fixture_sha256": "fe0d5cf3cb8c1922f71cca2da3796f3e63366623ad6164336655766efe330e6a" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/tailwind.config.ts", + "source_sha256": "5be8a942217220a967b2abc238d0b1390c210a2451aa9a334a4f173aca92eda8", + "fixture": "scaffold/tailwind.config.ts", + "fixture_sha256": "5be8a942217220a967b2abc238d0b1390c210a2451aa9a334a4f173aca92eda8" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/src/app/layout.tsx", + "source_sha256": "e4390d4d4067cdfdcab53478bb5701155d51cf3daa0cc80e8fd4d4d3957f1978", + "fixture": "scaffold/src/app/layout.tsx", + "fixture_sha256": "e4390d4d4067cdfdcab53478bb5701155d51cf3daa0cc80e8fd4d4d3957f1978" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/src/app/page.tsx", + "source_sha256": "013b9e9015d3da335226c7f55a34c9da4cd4523921674b25e5210a36b88e2ed8", + "fixture": "scaffold/src/app/page.tsx", + "fixture_sha256": "013b9e9015d3da335226c7f55a34c9da4cd4523921674b25e5210a36b88e2ed8" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/src/app/globals.css", + "source_sha256": "cc1a7ad0d019ddb1d32d0ecb588ba0ac26ce41d8625dd6c366348b25f83a28ec", + "fixture": "scaffold/src/app/globals.css", + "fixture_sha256": "cc1a7ad0d019ddb1d32d0ecb588ba0ac26ce41d8625dd6c366348b25f83a28ec" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/src/app/global.d.ts", + "source_sha256": "35d26ea66c86da9701e4f4b467913481ffeae910761cea34c775c5dacb7f8eaa", + "fixture": "scaffold/src/app/global.d.ts", + "fixture_sha256": "35d26ea66c86da9701e4f4b467913481ffeae910761cea34c775c5dacb7f8eaa" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/next-env.d.ts", + "source_sha256": "9dd9d642cdb87d4d5b3173217e0c45429b3e47a6f5cf5fb0ead6c644ec5fed01", + "fixture": "scaffold/next-env.d.ts", + "fixture_sha256": "9dd9d642cdb87d4d5b3173217e0c45429b3e47a6f5cf5fb0ead6c644ec5fed01" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/.commandagent/completion-contract-ultra-plan-run.json", + "source_sha256": "eb2b04647a28ab10508c21e9c1cd026bea293a1c7f95dda261f4bd5529d8c9ab", + "fixture": "completion-contract.json", + "fixture_sha256": "eb2b04647a28ab10508c21e9c1cd026bea293a1c7f95dda261f4bd5529d8c9ab" + }, + { + "source": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/.commandagent/plans/ultra-plan-01a09b8b-f823-71d1-a943-f560cf5b0b68.yaml", + "source_sha256": "daf9b856281c18ed9081c854ea97280defb81a0dae41e9a591f36eb39795c369", + "fixture": "original-plan.yaml", + "fixture_sha256": "daf9b856281c18ed9081c854ea97280defb81a0dae41e9a591f36eb39795c369" + } + ], + "interaction_source": { + "path": "/Users/maenokota/share/work/localwork/commandagent_trial/sessions/01a09b8b-f70e-7aa1-84f1-40a18d62da17/.commandagent/recovery-observations/attempt-0/workspace/.commandagent/evidence/browser-interaction.json", + "sha256": "0d6f074610f80bee2994cde96c39f2e72dbd627456356edb029b074b5e7cb009", + "retained_fields": [ + "ok", + "status", + "probe_mode", + "contract_hook_status", + "action_hooks", + "state_dimensions_changed", + "start_transition", + "input_state_evaluated_after_start", + "input_state_change", + "state_changed", + "visible_state_changed", + "steps", + "before_marker", + "after_marker", + "interaction_performed", + "input_event_observed", + "failure_category" + ] + }, + "references": [ + { + "path": "/Users/maenokota/share/work/github_kewton/CommandAgent-develop/workspace/management/runs/20260914-integrated-r0-evaluation-478-480-01/runs/R0/source-observation.json", + "sha256": "0425bb05ae5e8fbd5a0839c394a7f6436f7a59795c40b0d2a34c529d525f1dcc" + }, + { + "path": "/Users/maenokota/share/work/github_kewton/CommandAgent-develop/workspace/management/runs/20260914-integrated-r0-evaluation-478-480-01/runs/R0/engine-audit.json", + "sha256": "0a5840d7bfd48f8d79712179e9dec1e45f2d718b117594b606aaa198945c09ca" + }, + { + "path": "/Users/maenokota/share/work/github_kewton/CommandAgent-develop/workspace/management/runs/20260914-integrated-r0-evaluation-478-480-01/runs/R0/r0-gate.json", + "sha256": "b31155d85124eea58d2cffa00181c474ab2e2bfb791de83203d26e5ceb846e5b" + }, + { + "path": "/Users/maenokota/share/work/github_kewton/CommandAgent-develop/workspace/management/runs/20260914-integrated-r0-evaluation-478-480-01/reports/execution-summary.md", + "sha256": "11e1d2335265a1fcf6e2e0e375cf3229430241eac0b9c01c877875766feda6ce" + }, + { + "path": "/Users/maenokota/share/work/github_kewton/CommandAgent-develop/workspace/management/runs/20260914-integrated-r0-evaluation-478-480-01/reports/review-resolution.md", + "sha256": "9d81bcd4935c07722f337ad9d80c1c859b9438807753a48a64ccaef2d8c34b7b" + } + ], + "historical_observations": { + "registered_build": "passed", + "generic_interaction": "passed", + "implementation_artifact": "missing", + "recovery_used": 0, + "recovery_limit": 2, + "control_before_sha256": "fa089da5485a5df6d9626cc3b7a58e9fc200ed29d72d1d0f556e0c9d44bdc989", + "control_after_sha256": "fa089da5485a5df6d9626cc3b7a58e9fc200ed29d72d1d0f556e0c9d44bdc989", + "restore_invoked": false, + "restore_succeeded": false, + "terminal": "failed", + "internal_r0_gate": "unknown", + "external_reference_goal": "reference_goal_fail" + } +}