Tasks: Charter Phase 7 Release Closure

Mission: charter-p7-release-closure-01KQF9B9 Branch: main → PR on fix/charter-p7-release-closure Spec: spec.md | Plan: plan.md Generated: 2026-04-30T13:57:24Z


Summary

Two sequential work packages. WP01 wires validate_synthesis_state() from src/charter/bundle.py into the public charter bundle validate CLI command and fixes the --json stdout contract (currently Rich text leaks to stdout before the JSON write on sidecar failures). WP02 adds regression tests through the public CLI surface for every new failure mode.

WP02 depends on WP01.


Subtask Index

IDDescriptionWPParallel
T001Extend charter_bundle.py import to include validate_synthesis_state and BundleValidationResult from charter.bundleWP01
T002Refactor sidecar error handling: remove early exit before json_output branch; collect sidecar errors into a local list; redirect Rich rendering to err_consoleWP01
T003Call validate_synthesis_state(canonical_root) after sidecar collection; capture BundleValidationResultWP01
T004Build synthesis_state dict; compute mirrored errors list (sidecar errors + "synthesis_state: " prefixed synth errors); add both to reportWP01
T005Update result/passed in report to reflect combined compliance (bundle_compliant AND no provenance errors AND synth passed); single exit gateWP01
T006Extend _render_human to display synthesis_state section; confirm mypy --strict passes; existing tests passWP01
T007Add fixture helpers (_add_doctrine_artifact, _add_provenance_sidecar, _add_synthesis_manifest) to test_bundle_validate_cli.pyWP02
T008Test: doctrine artifact without sidecar → exits 1; synthesis_state.errors non-emptyWP02[D]
T009Test: provenance sidecar referencing absent artifact → exits 1WP02[D]
T010Test: synthesis manifest with mismatched content_hash → exits 1WP02[D]
T011Test: --json for each failure type → json.loads(result.stdout) succeeds; passed is False; synthesis_state key presentWP02[D]
T012Test: legacy bundle (no synthesis state) → exits 0; synthesis_state.present False; synthesis_state.passed TrueWP02[D]
T013Test: complete v2 bundle (valid artifacts, sidecars, manifest) → exits 0; synthesis_state.passed TrueWP02[D]
T014Update test_validate_json_shape_matches_contract to assert synthesis_state key in required keysWP02

Work Package 01 — Wire Synthesis-State Validation and Fix --json Stdout

Priority: P0 — blocks WP02 Estimated prompt size: ~350 lines Files: tasks/WP01-wire-synthesis-state-and-fix-json.md

Summary

Fix two bugs in src/specify_cli/cli/commands/charter_bundle.py: 1. validate_synthesis_state() is never called — synthesis-state failures silently pass validation. 2. When sidecar content errors occur, Rich output goes to stdout before the --json branch is reached — the JSON contract is broken.

Included subtasks

  • ✅ T001 Extend import to include validate_synthesis_state and BundleValidationResult (WP01)
  • ✅ T002 Refactor sidecar error handling: remove early exit, collect locally, fix stdout leakage (WP01)
  • ✅ T003 Call validate_synthesis_state(canonical_root) after sidecar collection (WP01)
  • ✅ T004 Build synthesis_state dict and mirrored errors list; add to report (WP01)
  • ✅ T005 Update result/passed to reflect combined compliance; single exit gate (WP01)
  • ✅ T006 Extend _render_human for synthesis_state; mypy --strict; existing tests green (WP01)

Implementation sketch

1. Update import line 24 to add validate_synthesis_state, BundleValidationResult. 2. Remove the if sidecar_errors: console.print(...); raise typer.Exit(code=1) block (lines 379–382) — this is the stdout leak and premature exit. 3. After sidecar collection, call validate_synthesis_state(canonical_root) and store result. 4. Build synthesis_state dict and errors list; add to report before the json_output branch. 5. Move exit logic to a single gate at the end, after rendering. 6. Extend _render_human to print a synthesis state section when synthesis_state is in the report.

Dependencies

None (first WP).

Risks

  • _render_human uses console (stdout by default). For human mode this is fine; only --json mode requires stdout to be pure JSON. Keep existing console for human output.
  • The existing test test_validate_fails_on_missing_tracked_file asserts payload["bundle_compliant"] is False — keep bundle_compliant semantics unchanged; add passed as the new overall gate.

Work Package 02 — Add Public-CLI Regression Tests

Priority: P0 Estimated prompt size: ~450 lines Depends on: WP01 Files: tasks/WP02-regression-tests.md

Summary

Extend tests/charter/test_bundle_validate_cli.py with regression tests that exercise every new failure mode through the public CLI surface. The existing test_bundle_validate_extension.py tests validate_synthesis_state() directly — these new tests must go through the CLI.

Included subtasks

  • ✅ T007 Add fixture helpers for synthesis state to test_bundle_validate_cli.py (WP02)
  • ✅ T008 Test: missing sidecar → exits 1, synthesis_state.errors non-empty (WP02)
  • ✅ T009 Test: sidecar referencing absent artifact → exits 1 (WP02)
  • ✅ T010 Test: manifest with bad content_hash → exits 1 (WP02)
  • ✅ T011 Test: --json for each failure type → strict JSON, passed=False (WP02)
  • ✅ T012 Test: legacy bundle → exits 0, synthesis_state.present=False (WP02)
  • ✅ T013 Test: complete v2 bundle → exits 0, synthesis_state.passed=True (WP02)
  • ✅ T014 Update test_validate_json_shape_matches_contract to assert synthesis_state key (WP02)

Implementation sketch

1. Add three helper functions above the fixture definitions to build synthesis state fixtures. 2. Write one test per failure mode; reuse compliant_repo fixture as the base and mutate state. 3. Verify JSON output with json.loads(result.stdout) — must not raise. 4. Update test_validate_json_shape_matches_contract required_keys set. 5. Run full suite including Phase 7 guards.

Dependencies

WP01 (validate_synthesis_state must be wired before tests can pass).

Risks

  • Fixture helpers must create synthesis-manifest.yaml with real content_hash and manifest_hash values so the valid-bundle test passes; corrupt fixture variants should target only the field under test.