its-magic 0.1.3-0 → 0.1.3-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/installer.ps1 +8 -0
  2. package/installer.py +8 -0
  3. package/installer.sh +1 -0
  4. package/package.json +1 -1
  5. package/scripts/check_intake_template_parity.py +62 -0
  6. package/template/.cursor/agents/curator.mdc +1 -0
  7. package/template/.cursor/agents/po.mdc +1 -0
  8. package/template/.cursor/agents/release.mdc +1 -0
  9. package/template/.cursor/commands/auto.md +90 -0
  10. package/template/.cursor/commands/execute.md +26 -0
  11. package/template/.cursor/commands/refresh-context.md +32 -0
  12. package/template/.cursor/commands/sovereign-critic.md +104 -0
  13. package/template/.cursor/model-catalog.local.example.cursor-only.json +9 -0
  14. package/template/.cursor/model-catalog.local.example.json +9 -0
  15. package/template/.cursor/model-catalog.local.example.level-1-easy.json +9 -0
  16. package/template/.cursor/model-catalog.local.example.level-2-complex.json +9 -0
  17. package/template/.cursor/model-catalog.local.example.level-3-mega.json +9 -0
  18. package/template/.cursor/model-catalog.local.example.level-4-super.json +9 -0
  19. package/template/.cursor/model-catalog.local.example.role-based-balanced.json +18 -0
  20. package/template/.cursor/model-catalog.local.example.role-based-highend.json +18 -0
  21. package/template/.cursor/rules/sovereign-role-manifest.mdc.example +27 -0
  22. package/template/.cursor/scratchpad.local.example.md +55 -0
  23. package/template/.cursor/scratchpad.md +203 -0
  24. package/template/.cursor/sovereign-role-manifest.yaml.example +53 -0
  25. package/template/decisions/DEC-0104.md +279 -0
  26. package/template/decisions/DEC-0105.md +231 -0
  27. package/template/decisions/DEC-0107.md +246 -0
  28. package/template/docs/engineering/architecture.md +78 -0
  29. package/template/docs/engineering/auto-orchestration-reference.md +7 -0
  30. package/template/docs/engineering/context/installer-owned-paths.manifest +8 -0
  31. package/template/docs/engineering/reason_codes.md +411 -0
  32. package/template/docs/engineering/runbook.md +1119 -0
  33. package/template/docs/engineering/sovereign-memory/.gitkeep +0 -0
  34. package/template/docs/engineering/sovereign-memory/retrospectives/.gitkeep +0 -0
  35. package/template/handoffs/sovereign_decisions/.gitkeep +0 -0
  36. package/template/handoffs/sovereign_deferrals/.gitkeep +0 -0
  37. package/template/handoffs/sovereign_role_reviews.jsonl +0 -0
  38. package/template/scripts/__pycache__/decision_ledger_lib.cpython-312.pyc +0 -0
  39. package/template/scripts/check_intake_template_parity.py +181 -0
  40. package/template/scripts/decision_ledger_lib.py +732 -0
  41. package/template/scripts/ledger_validate.py +153 -0
  42. package/template/scripts/model_tier_lib.py +680 -0
  43. package/template/scripts/model_tier_validate.py +368 -0
  44. package/template/scripts/parallel_dev_arbiter.py +923 -0
  45. package/template/scripts/release_trigger_adapters.py +843 -0
  46. package/template/scripts/self_healing_deploy_lib.py +463 -0
  47. package/template/scripts/self_healing_deploy_validate.py +78 -0
  48. package/template/scripts/sovereign_convergence_lib.py +994 -0
  49. package/template/scripts/sovereign_convergence_validate.py +206 -0
  50. package/template/scripts/sovereign_critic_lib.py +629 -0
  51. package/template/scripts/sovereign_critic_validate.py +131 -0
  52. package/template/scripts/sovereign_loop_lib.py +828 -0
  53. package/template/scripts/sovereign_loop_validate.py +122 -0
  54. package/template/scripts/sovereign_memory_lib.py +869 -0
  55. package/template/scripts/sovereign_memory_validate.py +153 -0
  56. package/template/scripts/sovereign_role_manifest_lib.py +547 -0
  57. package/template/scripts/sovereign_role_manifest_validate.py +105 -0
  58. package/template/tests/us0108_contract_test.py +207 -0
  59. package/template/tests/us0109_contract_test.py +209 -0
  60. package/template/tests/us0109_us0110_compose_test.py +34 -0
  61. package/template/tests/us0111_contract_test.py +345 -0
@@ -650,6 +650,203 @@ When **`AUTO_DELIVERY_ROUTING=backlog_then_scratchpad`**, story row may declare
650
650
 
651
651
  **Release status (S0086 / US-0096)**: **`released`** (`2026-06-13T16:00:00Z`); **`US-0096`** **DONE** in canonical backlog. Operator verify: **`handoffs/releases/S0086-release-notes.md`** **## Verify**; publish skipped while **`RELEASE_PUBLISH_MODE=confirm`**.
652
652
 
653
+ ## Per-phase model tier selection (US-0101 / DEC-0086)
654
+
655
+ `MODEL_TIER` selects LLM model strength (which model runs per lifecycle phase).
656
+ Three operator-facing tiers map to stable Cursor aliases — no vendor slugs in
657
+ template files.
658
+
659
+ | Key | Values | Default | Role |
660
+ |-----|--------|---------|------|
661
+ | **`MODEL_TIER_DEFAULT`** | `cheap` \| `balanced` \| `strong` | `balanced` | Fallback when phase-specific key absent |
662
+ | **`MODEL_TIER_<PHASE>`** | `cheap` \| `balanced` \| `strong` | *(per matrix)* | Per-phase tier override |
663
+ | **`MODEL_CATALOG`** | path | `.cursor/model-catalog.local.json` | Path to local slug catalog |
664
+ | **`MODEL_RESOLVE`** | `alias_only` \| `local_catalog` | `alias_only` | Resolution strategy |
665
+ | **`MODEL_FALLBACK`** | `inherit` | `inherit` | Fallback when catalog lookup fails |
666
+ | **`MODEL_PROVIDER_MODE`** | `cursor` \| `api` | `cursor` | Provider routing |
667
+
668
+ ### Default phase→tier matrix (architecture-locked)
669
+
670
+ | Tier | Cursor alias | `model:` field | Phases |
671
+ |------|-------------|----------------|--------|
672
+ | **`cheap`** | `fast` | `model: fast` | `ask`, `refresh-context`, `memory-audit`, `status-reconcile`, `pause` |
673
+ | **`balanced`** | `inherit` | `model: inherit` | `intake`, `discovery`, `research`, `release`, `plan-verify` |
674
+ | **`strong`** | *(omit)* | no `model:` field | `architecture`, `execute`, `quick`, `qa`, `verify-work`, `security-review` |
675
+ | *(inherit parent)* | — | — | `auto` (orchestrator always inherits parent model) |
676
+
677
+ ### Provider mode (MODEL_PROVIDER_MODE)
678
+
679
+ | Mode | Description |
680
+ |------|-------------|
681
+ | **`cursor`** (default) | All subagents route through Cursor-managed infrastructure; tier aliases work as designed |
682
+ | **`api`** | Operator uses BYOK via Cursor Settings → Models → API Key |
683
+
684
+ **Known limitation (confirmed 2026-06)**: Cursor subagents do **NOT** inherit
685
+ custom API keys or base URLs — they always bill against the Cursor plan. When
686
+ `MODEL_PROVIDER_MODE=api`, the local catalog is the only operator-controlled
687
+ surface; subagent `model:` aliases still resolve through Cursor infrastructure.
688
+
689
+ **Workaround recipes**:
690
+
691
+ 1. **Parent model + `inherit`** — set the parent chat model to the desired BYOK
692
+ model; subagents using `model: inherit` (balanced tier) pick it up.
693
+ 2. **Manual phase runs** — run each phase in a separate chat with the desired
694
+ model selected at the chat level (no subagent spawn).
695
+ 3. **Local catalog override** — set `MODEL_RESOLVE=local_catalog` and populate
696
+ `.cursor/model-catalog.local.json` with vendor slugs; materializer applies
697
+ slugs at template materialization time (not at subagent runtime).
698
+
699
+ ### Non-substitution paragraph
700
+
701
+ MODEL_TIER ≠ TOKEN_PROFILE ≠ DELIVERY_MODE
702
+
703
+ > **`MODEL_TIER`** selects LLM model strength (which model runs).
704
+ > **`TOKEN_PROFILE`** selects context breadth / token cost (how much context the model sees).
705
+ > **`DELIVERY_MODE`** selects lifecycle shape (standard / ultra_lean / mega_quick).
706
+ > These are **independent axes** — none substitutes for another. A `strong` tier
707
+ > with `lean` profile is valid; a `cheap` tier with `full` profile is valid.
708
+ > Setting one does not change the others. Combine freely.
709
+
710
+ ### Reason codes — `MODEL_TIER_*` / `MODEL_CATALOG_*` / `MODEL_RESOLVE_*` / `MODEL_SLUG_*`
711
+
712
+ | Code | Trigger |
713
+ |------|---------|
714
+ | **`MODEL_TIER_INVALID`** | Unknown tier value (not `cheap`/`balanced`/`strong`) |
715
+ | **`MODEL_CATALOG_INVALID`** | Malformed catalog JSON (parse error, missing `schema_version`) |
716
+ | **`MODEL_SLUG_UNKNOWN`** | Tier key missing from catalog when `MODEL_RESOLVE=local_catalog` |
717
+ | **`MODEL_RESOLVE_FALLBACK`** | Catalog lookup failed but `MODEL_FALLBACK=inherit` → reason + fallback |
718
+
719
+ ### Template agent defaults
720
+
721
+ | Agent role | Tier | `model:` field |
722
+ |-----------|------|----------------|
723
+ | `curator` | cheap | `model: fast` |
724
+ | `po` | balanced | `model: inherit` |
725
+ | `release` | balanced | `model: inherit` |
726
+ | `tech-lead` | strong | *(omit)* |
727
+ | `dev` | strong | *(omit)* |
728
+ | `qa` | strong | *(omit)* |
729
+ | `security` | strong | *(omit)* |
730
+
731
+ **Forbidden in `template/.cursor/agents/`**: hardcoded vendor slugs
732
+ (`composer-*`, `claude-*`, `gpt-*`, `opus-*`). Template files use aliases only.
733
+
734
+ ### Local catalog schema (v1)
735
+
736
+ ```json
737
+ {
738
+ "schema_version": 1,
739
+ "tiers": {
740
+ "cheap": "<slug>",
741
+ "balanced": "<slug>",
742
+ "strong": "<slug>"
743
+ },
744
+ "notes": "optional free-text"
745
+ }
746
+ ```
747
+
748
+ - Path: `.cursor/model-catalog.local.json` (gitignored)
749
+ - Example: `.cursor/model-catalog.local.example.json` (committed, placeholder values)
750
+ - All three tier keys required; values are opaque vendor slug strings
751
+
752
+ ### Resolver algorithm
753
+
754
+ 1. Read `MODEL_TIER_<PHASE>` from merged scratchpad → tier value
755
+ 2. If `MODEL_RESOLVE=alias_only` (default): use built-in mapping (table above)
756
+ 3. If `MODEL_RESOLVE=local_catalog`: load catalog JSON → lookup tier key → slug
757
+ 4. If key missing → `MODEL_SLUG_UNKNOWN` fail-closed
758
+ 5. If `MODEL_FALLBACK=inherit` and lookup fails → emit `MODEL_RESOLVE_FALLBACK` + use `inherit`
759
+ 6. Unknown tier value → `MODEL_TIER_INVALID` fail-closed
760
+ 7. Malformed catalog JSON → `MODEL_CATALOG_INVALID` fail-closed
761
+
762
+ ### Validation commands
763
+
764
+ - `python scripts/model_tier_validate.py --repo .`
765
+ - `pytest -k us0101 tests/auto_command_contract_test.py`
766
+ - `python scripts/check_intake_template_parity.py --scope=model-tier`
767
+ - `tests/run-tests.ps1` / `tests/run-tests.sh` §26Z
768
+
769
+ Normative architecture: `docs/engineering/architecture.md` (**# US-0101**).
770
+
771
+ ## Direct per-phase model slug override + role catalog (US-0102 / DEC-0087)
772
+
773
+ Composes on **US-0101** / **DEC-0086** — tier-only operators need no migration.
774
+
775
+ ### 5-step precedence chain
776
+
777
+ | Step | Source | Outcome |
778
+ |------|--------|---------|
779
+ | **1** | `MODEL_<PHASE>` | Non-empty slug → return (validate per mode) or `MODEL_OVERRIDE_SLUG_UNKNOWN` |
780
+ | **2** | `MODEL_TIER_<PHASE>` | **DEC-0086** tier→alias / `local_catalog` chain |
781
+ | **3** | `MODEL_RESOLVE=role_catalog` | Phase→logical role → catalog `roles[<key>]`; miss → `MODEL_ROLE_SLUG_UNKNOWN` → fall through |
782
+ | **4** | `MODEL_TIER_DEFAULT` | **DEC-0086** tier chain |
783
+ | **5** | Cursor alias | **DEC-0086** built-in mapping |
784
+
785
+ When `MODEL_RESOLVE` is `alias_only` or `local_catalog`, step **3** is skipped.
786
+
787
+ ### Scratchpad keys (new)
788
+
789
+ | Key | Default | Role |
790
+ |-----|---------|------|
791
+ | **`MODEL_<PHASE>`** | *(absent)* | Direct vendor slug; includes `MODEL_ASK` |
792
+ | **`MODEL_RESOLVE`** | `alias_only` | Extended: `alias_only` \| `local_catalog` \| **`role_catalog`** |
793
+
794
+ Operator slugs live in `.cursor/scratchpad.local.md` and `.cursor/model-catalog.local.json` only (gitignored).
795
+
796
+ ### Role catalog enablement recipe
797
+
798
+ 1. Copy a v2 example catalog:
799
+ - `.cursor/model-catalog.local.example.role-based-balanced.json`, or
800
+ - `.cursor/model-catalog.local.example.role-based-highend.json`
801
+ 2. Save as `.cursor/model-catalog.local.json` with real vendor slugs.
802
+ 3. Set `MODEL_RESOLVE=role_catalog` in `.cursor/scratchpad.local.md`.
803
+ 4. Optional: set `MODEL_<PHASE>=<slug>` for direct overrides (step 1 wins over role lookup).
804
+ 5. Run `python scripts/model_tier_validate.py --repo .` to validate.
805
+
806
+ On role lookup miss → `MODEL_ROLE_SLUG_UNKNOWN` emitted; resolver falls through to `MODEL_TIER_DEFAULT` then Cursor alias.
807
+
808
+ **`dev_difficult`**: no automatic phase routing — use `MODEL_EXECUTE=<slug>` direct override or tier `strong` + catalog tier slug.
809
+
810
+ Non-normative role recommendations: `ai_modell_auslegung_cursor_highend.md`.
811
+
812
+ ### Catalog schema v2 (opt-in)
813
+
814
+ ```json
815
+ {
816
+ "schema_version": 2,
817
+ "tiers": { "cheap": "<slug>", "balanced": "<slug>", "strong": "<slug>" },
818
+ "roles": {
819
+ "po": "<slug>", "sa": "<slug>", "dev": "<slug>", "dev_difficult": "<slug>",
820
+ "qa": "<slug>", "security": "<slug>", "release": "<slug>"
821
+ }
822
+ }
823
+ ```
824
+
825
+ v1 catalogs (tiers only) unchanged. Malformed v2 → `MODEL_CATALOG_SCHEMA_V2_INVALID`.
826
+
827
+ ### New reason codes
828
+
829
+ | Code | Trigger |
830
+ |------|---------|
831
+ | **`MODEL_OVERRIDE_SLUG_UNKNOWN`** | Direct slug validation failure |
832
+ | **`MODEL_ROLE_SLUG_UNKNOWN`** | Role catalog lookup miss |
833
+ | **`MODEL_CATALOG_SCHEMA_V2_INVALID`** | v2 schema validation failure |
834
+
835
+ ### Non-substitution paragraph
836
+
837
+ MODEL_TIER ≠ TOKEN_PROFILE ≠ DELIVERY_MODE — unchanged from **US-0101**; model selection is orthogonal to context breadth.
838
+
839
+ ### Validation commands
840
+
841
+ - `python scripts/model_tier_validate.py --repo .`
842
+ - `pytest -k us0102 tests/auto_command_contract_test.py`
843
+ - `python scripts/check_intake_template_parity.py --scope=model-tier-overrides`
844
+ - `tests/run-tests.ps1` / `tests/run-tests.sh` §26AA
845
+
846
+ Normative architecture: `docs/engineering/architecture.md` (**# US-0102**); decision: **`DEC-0087`**.
847
+
848
+ Binding decision: `decisions/DEC-0086.md`.
849
+
653
850
  Context compaction policy:
654
851
 
655
852
  - `docs/engineering/state.md` is a compact hot surface for current execution
@@ -741,6 +938,83 @@ If the default set is insufficient, escalate with an explicit note citing
741
938
  - If unresolved after bounded expansion, answer with explicit "not found in
742
939
  current artifacts" rather than broad speculative reads.
743
940
 
941
+ ## Model-catalog example preset delivery (US-0112 / DEC-0112)
942
+
943
+ **Default state**: 8 committed `model-catalog.local.example*.json` presets ship with the
944
+ `its-magic` framework and are delivered to operator repos via the installer manifest
945
+ (`docs/engineering/context/installer-owned-paths.manifest` [install_include_paths]) under
946
+ **framework-file semantics** (US-0018 / US-0057 / US-0075 precedence).
947
+
948
+ ### Eight preset filenames (manifest rows)
949
+
950
+ 1. `.cursor/model-catalog.local.example.json` — base placeholder (three tiers, opaque vendor slugs)
951
+ 2. `.cursor/model-catalog.local.example.cursor-only.json` — Cursor-first alias mapping
952
+ 3. `.cursor/model-catalog.local.example.level-1-easy.json` — easy complexity tier set
953
+ 4. `.cursor/model-catalog.local.example.level-2-complex.json` — moderate complexity tier set
954
+ 5. `.cursor/model-catalog.local.example.level-3-mega.json` — heavy complexity tier set
955
+ 6. `.cursor/model-catalog.local.example.level-4-super.json` — maximal complexity tier set
956
+ 7. `.cursor/model-catalog.local.example.role-based-balanced.json` — role-catalog balanced preset
957
+ 8. `.cursor/model-catalog.local.example.role-based-highend.json` — role-catalog high-end preset
958
+
959
+ ### Installer behavior (framework-file semantics)
960
+
961
+ - **`missing` mode** (default new install): copies the 8 example presets into the target
962
+ `.cursor/` directory when absent; deterministic status log per file (names only; no content
963
+ inspection); same semantics as `scratchpad.local.example.md` per US-0075.
964
+ - **`upgrade` mode**: refreshes the example presets when the packaged template version
965
+ differs (byte-compare); skips byte-identical files; never modifies or removes the active
966
+ operator-owned `.cursor/model-catalog.local.json` (gitignored; not in manifest).
967
+ - **Triple installer parity**: `installer.py`, `installer.ps1`, `installer.sh` all read the
968
+ single manifest `[install_include_paths]` as source of truth; framework classification
969
+ for `.cursor/model-catalog.local.example*.json` is identical across Python / PowerShell /
970
+ Bash `classify_file` paths.
971
+
972
+ ### Operator usage recipe
973
+
974
+ 1. Inspect the 8 presets under `.cursor/` (shipped automatically by install or upgrade).
975
+ 2. Choose a preset matching your intent — complexity-based (levels 1–4) or role-based
976
+ (balanced / highend) for `MODEL_RESOLVE=role_catalog`; cursor-only alias mapping; or
977
+ base template for custom tiers.
978
+ 3. Copy the chosen preset: `cp .cursor/model-catalog.local.example.<preset>.json .cursor/model-catalog.local.json`.
979
+ 4. Edit real vendor slugs into `.cursor/model-catalog.local.json` (placeholder values
980
+ substituted; vendor-specific).
981
+ 5. Set `MODEL_RESOLVE=local_catalog` (or `role_catalog` for role-based presets) in
982
+ `.cursor/scratchpad.local.md`.
983
+ 6. Validate: `python scripts/model_tier_validate.py --repo .`.
984
+ 7. Parity: `python scripts/check_intake_template_parity.py --scope=model-catalog-examples`.
985
+
986
+ **Active catalog protection invariant**: `.cursor/model-catalog.local.json` remains
987
+ gitignored and outside `install_include_paths` + `clean_paths`. The installer never
988
+ auto-populates it. No installer mode copies, merges, or replaces the active catalog.
989
+ This is a DEC-0086 / DEC-0087 boundary (US-0101 catalog schema + US-0102 role precedence
990
+ remain unaltered; US-0112 only completes delivery path).
991
+
992
+ ### Parity scope
993
+
994
+ ```bash
995
+ python scripts/check_intake_template_parity.py --scope=model-catalog-examples
996
+ ```
997
+
998
+ Validates active vs template byte-parity for `installer-owned-paths.manifest` (16 manifest
999
+ rows total across active + template paths). On mismatch → `MODEL_CATALOG_EXAMPLE_PARITY_SCOPE_MISMATCH`.
1000
+ On pass → `[MODEL_CATALOG_EXAMPLE_PARITY_SCOPE_OK]`.
1001
+
1002
+ ### Reason codes
1003
+
1004
+ | Code | Trigger |
1005
+ |------|---------|
1006
+ | `MODEL_CATALOG_EXAMPLE_PARITY_SCOPE_OK` | Active vs template manifest byte-parity pass for 8 model-catalog.example paths |
1007
+ | `MODEL_CATALOG_EXAMPLE_PARITY_SCOPE_MISMATCH` | Active vs template manifest byte-parity fail |
1008
+
1009
+ ### Validation commands
1010
+
1011
+ - `python scripts/check_intake_template_parity.py --scope=model-catalog-examples`
1012
+ - `pytest -k us0112 tests/us0112_contract_test.py`
1013
+
1014
+ Normative architecture: `docs/engineering/architecture.md` (**# US-0112**); decision: **`DEC-0112`** (Accepted).
1015
+
1016
+ Binding decision: `decisions/DEC-0112.md`. Research anchor: `docs/engineering/research.md` **R-0090**.
1017
+
744
1018
  ## Configurable multi-target publish mode (US-0054 / DEC-0036)
745
1019
 
746
1020
  Post-release publish orchestration is configurable and default-safe:
@@ -2382,6 +2656,851 @@ Single-writer drift safety:
2382
2656
  sourcing `.env` — the process inherits normal environment variables.
2383
2657
  - Referencing env var **names** (not values) in documentation and handoffs.
2384
2658
 
2659
+ ## AI Decision Ledger (US-0103 / DEC-0103)
2660
+
2661
+ **Default-off sovereign-loop audit layer**. When `AI_DECISION_LEDGER=0` (default),
2662
+ zero overhead — no files read or written. When `AI_DECISION_LEDGER=1`, every
2663
+ autonomous AI deviation is recorded in an append-only JSONL ledger under
2664
+ `handoffs/sovereign_decisions/<orchestrator_run_id>.jsonl`.
2665
+
2666
+ ### Scratchpad keys
2667
+
2668
+ | Key | Values | Default | Behavior when off |
2669
+ |-----|--------|---------|-------------------|
2670
+ | `AI_DECISION_LEDGER` | `0` \| `1` | `0` | No reads / writes / schema checks. |
2671
+ | `AUTO_PLAN_FIDELITY` | `strict` \| `relaxed` \| `extended` | `strict` | Active only when ledger enabled. |
2672
+
2673
+ Set in `.cursor/scratchpad.local.md` (gitignored) or `.cursor/scratchpad.md`.
2674
+
2675
+ ### Enable / disable
2676
+
2677
+ ```bash
2678
+ # Enable ledger auditing
2679
+ echo "AI_DECISION_LEDGER=1" >> .cursor/scratchpad.local.md
2680
+
2681
+ # Switch fidelity mode (relaxed allows AC drop/reorder; extended allows scope add)
2682
+ echo "AUTO_PLAN_FIDELITY=relaxed" >> .cursor/scratchpad.local.md
2683
+
2684
+ # Disable (zero overhead)
2685
+ echo "AI_DECISION_LEDGER=0" >> .cursor/scratchpad.local.md
2686
+ ```
2687
+
2688
+ ### Audit ledger entries
2689
+
2690
+ ```bash
2691
+ # Validate all ledger files in handoffs/sovereign_decisions/
2692
+ python scripts/ledger_validate.py --repo .
2693
+
2694
+ # Validate a single ledger file
2695
+ python scripts/ledger_validate.py --file handoffs/sovereign_decisions/auto-20260628-01.jsonl
2696
+
2697
+ # Print QA findings block (JSON)
2698
+ python scripts/ledger_validate.py --qa-find --orchestrator-run-id auto-20260628-01
2699
+
2700
+ # Validate with fail-closed enforcement
2701
+ python scripts/ledger_validate.py --repo . --enforce
2702
+
2703
+ # Library self-test
2704
+ python scripts/decision_ledger_lib.py --self-test
2705
+ ```
2706
+
2707
+ ### Plan-fidelity modes
2708
+
2709
+ | Mode | Behavior | When to use |
2710
+ |------|----------|-------------|
2711
+ | `strict` | Any unapproved deviation from `resolved_phase_plan` → **`PLAN_FIDELITY_VIOLATION` hard stop**. Operator must approve override or revert. | Default. Production runs, high-risk stories. |
2712
+ | `relaxed` | AI may drop/reorder existing ACs with ledger entry + QA-verifiable; new ACs/stories → `PLAN_FIDELITY_SCOPE_GATE` hard stop. | Iterative development where AC refinement is expected. |
2713
+ | `extended` | AI may extend scope with new stories/features; documented non-blocking in ledger as `PLAN_FIDELITY_EXTENSION`. QA still cross-checks. | Exploratory work, prototyping, scope discovery. |
2714
+
2715
+ ### Typical audit workflow
2716
+
2717
+ 1. **Enable ledger** before long `/auto` run: set `AI_DECISION_LEDGER=1` in scratchpad.
2718
+ 2. **Run `/auto`** to completion — ledger writes accumulate in `handoffs/sovereign_decisions/<run_id>.jsonl`.
2719
+ 3. **Run `/qa`** — phase reads ledger, emits `ledger_findings` block in `sprints/Sxxxx/qa-findings.md` with decision counts, risk tier distribution, and fail-closed code summary.
2720
+ 4. **Inspect findings** — look for `violation_count > 0` (unapproved deviations) or `schema_invalid` status (corrupt ledger).
2721
+ 5. **Remediate or approve** — if deviations justified, set `AUTO_PLAN_FIDELITY=relaxed` or `=extended` and re-run; otherwise revert changes.
2722
+
2723
+ ### Common failure scenarios and recovery
2724
+
2725
+ | Reason code | Symptom | Recovery |
2726
+ |-------------|---------|----------|
2727
+ | `LEDGER_FILE_MISSING` | `AI_DECISION_LEDGER=1` but no ledger file for current run | Create `handoffs/sovereign_decisions/<run_id>.jsonl` or set `AI_DECISION_LEDGER=0` |
2728
+ | `LEDGER_SCHEMA_INVALID` | JSONL line fails 12-field schema validation | Open ledger, locate invalid line (validator prints line number), fix JSON syntax; if unrecoverable, truncate to last valid line |
2729
+ | `LEDGER_CORRUPT` | Whole file non-UTF-8 or broken JSON | Manual repair required; consider `git restore` from prior commit or truncate to known-good prefix |
2730
+ | `LEDGER_APPEND_FAILED` | Disk full, permission error, I/O error | Check disk space (`df -h`), file permissions (`ls -la handoffs/sovereign_decisions/`); retry after remediation |
2731
+ | `PLAN_FIDELITY_VIOLATION` | Unapproved deviation under strict mode | Review ledger last entry; if justified, switch to `AUTO_PLAN_FIDELITY=relaxed` in scratchpad and re-run `/execute`; otherwise revert AC changes |
2732
+ | `PLAN_FIDELITY_SCOPE_GATE` | New scope request under strict/relaxed mode | If scope-add intentional, switch to `AUTO_PLAN_FIDELITY=extended` and document in `sprints/Sxxxx/extension-report.md`; otherwise drop new scope |
2733
+ | `LEDGER_DISABLED` | Informational — `AI_DECISION_LEDGER=0` | No action required; zero overhead when off. Opt-in via scratchpad if ledger auditing desired. |
2734
+
2735
+ ### Parity enforcement
2736
+
2737
+ ```bash
2738
+ # Verify active ↔ template byte-parity for ledger scripts + scratchpad
2739
+ python scripts/check_intake_template_parity.py --scope=sovereign-ledger
2740
+ ```
2741
+
2742
+ Pair table (`SOVEREIGN_LEDGER_PAIRS` + scratchpad):
2743
+ - `scripts/decision_ledger_lib.py` ↔ `template/scripts/decision_ledger_lib.py`
2744
+ - `scripts/ledger_validate.py` ↔ `template/scripts/ledger_validate.py`
2745
+ - `.cursor/scratchpad.md` ↔ `template/.cursor/scratchpad.md`
2746
+ - `.cursor/scratchpad.local.example.md` ↔ `template/.cursor/scratchpad.local.example.md`
2747
+
2748
+ ### Related artifacts
2749
+
2750
+ - **Architecture**: `docs/engineering/architecture.md` `# US-0103`
2751
+ - **Reason codes**: `docs/engineering/reason_codes.md` § US-0103
2752
+ - **Decision record**: `decisions/DEC-0103.md`
2753
+ - **Contract tests**: `tests/us0103_contract_test.py` (8 tests)
2754
+
2755
+ ## Goal-Based Convergence (US-0110 / DEC-0110)
2756
+
2757
+ **Default-off sovereign-loop terminal predicate**. When `SOVEREIGN_GOAL_MODE=phase_driven`
2758
+ (default), zero overhead — no evaluation, no `goal_progress` block, no partial-delivery write.
2759
+ When `goal_convergence`, `evaluate_convergence` reads composed surfaces (backlog, deferrals,
2760
+ critic, smoke, ledger) and emits progress/timeout artifacts only.
2761
+
2762
+ ### Scratchpad keys
2763
+
2764
+ | Key | Values | Default | Behavior when off |
2765
+ |-----|--------|---------|-------------------|
2766
+ | `SOVEREIGN_GOAL_MODE` | `phase_driven` \| `goal_convergence` | `phase_driven` | No evaluation side effects. |
2767
+ | `SOVEREIGN_GOAL` | free-text | *(empty)* | Explicit goal wins over vision derive. |
2768
+ | `SOVEREIGN_GOAL_TOP_N` | int ≥ 1 | `3` | Vision paragraph count for auto-derive. |
2769
+ | `SOVEREIGN_GOAL_MAX_CHARS` | int ≥ 64 | `512` | Truncation cap for goal text. |
2770
+ | `SOVEREIGN_GOAL_TIMEOUT_MAX` | int ≥ 0 | `0` | Iteration-count cap (`0` = disabled). |
2771
+
2772
+ ### Enable / disable
2773
+
2774
+ ```bash
2775
+ # Enable goal-driven convergence
2776
+ echo "SOVEREIGN_GOAL_MODE=goal_convergence" >> .cursor/scratchpad.local.md
2777
+ echo "SOVEREIGN_GOAL=Ship sovereign-loop batch with zero regressions" >> .cursor/scratchpad.local.md
2778
+
2779
+ # Disable (zero overhead — US-0088/US-0092/US-0095/US-0044 stop matrix unchanged)
2780
+ echo "SOVEREIGN_GOAL_MODE=phase_driven" >> .cursor/scratchpad.local.md
2781
+ ```
2782
+
2783
+ ### Evaluate convergence
2784
+
2785
+ ```bash
2786
+ # Library self-test
2787
+ python scripts/sovereign_convergence_lib.py --self-test
2788
+
2789
+ # Evaluate five-conjunct predicate (JSON)
2790
+ python scripts/sovereign_convergence_lib.py --evaluate --repo . --orchestrator-run-id auto-20260628-04
2791
+
2792
+ # Dump goal_progress block
2793
+ python scripts/sovereign_convergence_lib.py --dump-progress --repo . --orchestrator-run-id auto-20260628-04
2794
+
2795
+ # Validator self-test + schema fixtures
2796
+ python scripts/sovereign_convergence_validate.py --self-test
2797
+
2798
+ # Validate artifacts in repo
2799
+ python scripts/sovereign_convergence_validate.py --repo . --enforce
2800
+ ```
2801
+
2802
+ ### Interpret `goal_progress` block
2803
+
2804
+ Curator **`/refresh-context`** emits a fenced JSON block under **`### goal_progress`** in
2805
+ `handoffs/resume_brief.md` when `SOVEREIGN_GOAL_MODE=goal_convergence` and the sovereign loop
2806
+ is active. Placement: after the latest orchestration pointer, before prior pointers.
2807
+
2808
+ Fields: `goal_text`, `goal_source`, `mode`, `converged`, `unmet_conditions[]`, `blocked_by[]`,
2809
+ `conjuncts`, `evaluated_at`, `orchestrator_run_id`, `schema_version`.
2810
+
2811
+ ### Partial delivery on timeout
2812
+
2813
+ When `SOVEREIGN_GOAL_TIMEOUT_MAX > 0` and iteration count reaches the cap without
2814
+ `converged=true`, the evaluator emits **`SOVEREIGN_GOAL_TIMEOUT`** and writes
2815
+ `handoffs/sovereign_partial_delivery.md` (sections: Goal, Evaluated At, Unmet Conditions,
2816
+ Blocked By, Completed Stories, Open Stories, Deferrals Summary, Remediation).
2817
+
2818
+ ### Troubleshooting reason codes
2819
+
2820
+ See `docs/engineering/reason_codes.md` § US-0110 for the 10-code inventory and remediation.
2821
+
2822
+ | Reason code | Symptom | Recovery |
2823
+ |-------------|---------|----------|
2824
+ | `CONVERGENCE_OPEN_STORIES_REMAIN` | OPEN stories in backlog | Complete stories or adjust goal scope |
2825
+ | `CONVERGENCE_SMOKE_PROBE_FAIL` | `tests/report.md` Fail > 0 or UAT smoke step failed | Fix tests; re-run `/verify-work` |
2826
+ | `SOVEREIGN_GOAL_TIMEOUT` | Iteration cap exhausted | Read partial-delivery report; increase cap or resolve blockers |
2827
+ | `SOVEREIGN_GOAL_DERIVE_FAILED` | Empty/unreadable vision | Set explicit `SOVEREIGN_GOAL` |
2828
+
2829
+ ### Parity enforcement
2830
+
2831
+ ```bash
2832
+ python scripts/check_intake_template_parity.py --scope=sovereign-convergence
2833
+ ```
2834
+
2835
+ Pair table (`SOVEREIGN_CONVERGENCE_PAIRS`):
2836
+ - `scripts/sovereign_convergence_lib.py` ↔ `template/scripts/sovereign_convergence_lib.py`
2837
+ - `scripts/sovereign_convergence_validate.py` ↔ `template/scripts/sovereign_convergence_validate.py`
2838
+
2839
+ ### Related artifacts
2840
+
2841
+ - **Architecture**: `docs/engineering/architecture.md` `# US-0110`
2842
+ - **Reason codes**: `docs/engineering/reason_codes.md` § US-0110
2843
+ - **Decision record**: `decisions/DEC-0110.md`
2844
+ - **Contract tests**: `tests/us0110_contract_test.py` (8 tests)
2845
+
2846
+ ### Cross-Model Adversarial Critic (US-0104)
2847
+
2848
+ **Default-off cross-model review**. When `CROSS_MODEL_REVIEW=0` (default), zero overhead — no
2849
+ critic spawn, no findings writes, no anti-slop gate. When `1`, `/auto` spawns `/sovereign-critic`
2850
+ after each producer phase.
2851
+
2852
+ #### Scratchpad keys
2853
+
2854
+ | Key | Values | Default | Behavior when off |
2855
+ |-----|--------|---------|-------------------|
2856
+ | `CROSS_MODEL_REVIEW` | `0` \| `1` | `0` | No critic spawn or findings writes. |
2857
+ | `CROSS_MODEL_ANTISLOP_THRESHOLD` | int 0–10 | `6` | Aggregate below → rework loop. |
2858
+ | `CROSS_MODEL_REWORK_MAX` | int ≥ 0 | `2` | Max producer re-spawns per `(run, phase)`. |
2859
+
2860
+ #### Enable / disable
2861
+
2862
+ ```bash
2863
+ # Enable cross-model critic
2864
+ echo "CROSS_MODEL_REVIEW=1" >> .cursor/scratchpad.local.md
2865
+
2866
+ # Disable (zero overhead)
2867
+ echo "CROSS_MODEL_REVIEW=0" >> .cursor/scratchpad.local.md
2868
+ ```
2869
+
2870
+ #### Validate findings JSONL
2871
+
2872
+ ```bash
2873
+ python scripts/sovereign_critic_lib.py --self-test
2874
+ python scripts/sovereign_critic_validate.py --self-test
2875
+ python scripts/sovereign_critic_validate.py --repo . --enforce
2876
+ python scripts/sovereign_critic_validate.py --open-blocking --repo .
2877
+ ```
2878
+
2879
+ #### Interpret findings JSONL
2880
+
2881
+ Canonical path: `handoffs/sovereign_critic_findings.jsonl` (append-only, 15-field v1 schema).
2882
+ Each line includes `lens` (`challenger` \| `architect` \| `subtractor`), `severity`, `confidence`
2883
+ (set by reconciliation), `anti_slop_score`, `blocking`, and `degraded_mode`.
2884
+
2885
+ Reconciliation: ≥2 lenses share `issue_key` → `confidence=high`; single lens → `medium`.
2886
+
2887
+ #### Anti-slop rework remediation
2888
+
2889
+ When aggregate `min(lens_scores) < CROSS_MODEL_ANTISLOP_THRESHOLD` and blocking findings exist:
2890
+
2891
+ 1. Producer phase re-spawns with fresh context (bounded by `CROSS_MODEL_REWORK_MAX`).
2892
+ 2. Reason **`CROSS_MODEL_ANTISLOP_FAIL`** during rework; **`CROSS_MODEL_REWORK_CAP_EXHAUSTED`**
2893
+ at cap → operator decision gate (waive or abort).
2894
+
2895
+ #### Degraded fallback troubleshooting
2896
+
2897
+ When `select_critic_model` resolves the same slug as producer (or catalog miss), framework sets
2898
+ `degraded_mode=true` and runs three sequential lens spawns on the same model. Informational reason
2899
+ **`CROSS_MODEL_DEGRADED_MODE`** — not a hard stop. Documented limitation per **R-0088**.
2900
+
2901
+ #### Isolation `model_id` v2
2902
+
2903
+ When `CROSS_MODEL_REVIEW=1`, producer **and** critic isolation evidence rows require additive
2904
+ `model_id`. Missing → **`ISOLATION_EVIDENCE_MODEL_ID_MISSING`**.
2905
+
2906
+ #### Parity enforcement
2907
+
2908
+ ```bash
2909
+ python scripts/check_intake_template_parity.py --scope=sovereign-critic
2910
+ ```
2911
+
2912
+ Pair table (`SOVEREIGN_CRITIC_PAIRS`): lib, validator, command, scratchpad, `DEC-0104.md`.
2913
+
2914
+ #### Related artifacts
2915
+
2916
+ - **Architecture**: `docs/engineering/architecture.md` `# US-0104`
2917
+ - **Reason codes**: `docs/engineering/reason_codes.md` § US-0104
2918
+ - **Decision record**: `decisions/DEC-0104.md`
2919
+ - **Contract tests**: `tests/us0104_contract_test.py` (8 tests + 2 compose guards)
2920
+
2921
+ ### Sovereign Memory (US-0105)
2922
+
2923
+ **Default-off institutional memory**. When `SOVEREIGN_MEMORY=0` (default), zero overhead —
2924
+ no JSONL writes, no injection reads, no spawn digest assembly. When `1`, bounded learnings
2925
+ inject into phase spawns via `scripts/sovereign_memory_lib.py`.
2926
+
2927
+ #### Scratchpad keys
2928
+
2929
+ | Key | Values | Default | Behavior when off |
2930
+ |-----|--------|---------|-------------------|
2931
+ | `SOVEREIGN_MEMORY` | `0` \| `1` | `0` | No reads / writes / digest assembly. |
2932
+ | `SOVEREIGN_MEMORY_TOP_N` | int ≥ 0 | `5` | Recent pool size (all four JSONL families). |
2933
+ | `SOVEREIGN_MEMORY_TOP_K` | int ≥ 0 | `3` | High-impact pool (`patterns` + `mistakes`). |
2934
+ | `SOVEREIGN_MEMORY_MAX_CHARS` | int ≥ 0 | `2048` | Hard cap on assembled `digest_text`. |
2935
+ | `SOVEREIGN_MEMORY_JSONL_MAX_LINES` | int ≥ 1 | `500` | Active JSONL line cap before archive rollover. |
2936
+
2937
+ #### Enable / disable
2938
+
2939
+ ```bash
2940
+ # Enable sovereign memory
2941
+ echo "SOVEREIGN_MEMORY=1" >> .cursor/scratchpad.local.md
2942
+
2943
+ # Disable (zero overhead)
2944
+ echo "SOVEREIGN_MEMORY=0" >> .cursor/scratchpad.local.md
2945
+ ```
2946
+
2947
+ #### JSONL families vs per-run ledger
2948
+
2949
+ | Artifact | Path | Scope | Injected v1? |
2950
+ |----------|------|-------|--------------|
2951
+ | Decisions log | `docs/engineering/sovereign-memory/decisions-log.jsonl` | Cross-run distilled learnings | yes |
2952
+ | Mistakes | `docs/engineering/sovereign-memory/mistakes.jsonl` | Orchestrator mistake hooks | yes (top-K) |
2953
+ | Patterns | `docs/engineering/sovereign-memory/patterns.jsonl` | Phase/curator consolidation | yes (top-K) |
2954
+ | Plan drift | `docs/engineering/sovereign-memory/plan-drift-register.jsonl` | Fidelity/scope hooks | yes (top-N) |
2955
+ | Retrospectives | `sovereign-memory/retrospectives/<sprint_id>.md` | Curator `/refresh-context` | **no** |
2956
+ | Per-run ledger (**US-0103**) | `handoffs/sovereign_decisions/<run_id>.jsonl` | Per-run audit | no (optional promotion) |
2957
+
2958
+ Promotion at `/refresh-context` copies ledger highlights to `decisions-log.jsonl` with
2959
+ `provenance_ref=ledger:<decision_id>` when both `SOVEREIGN_MEMORY=1` and `AI_DECISION_LEDGER=1`.
2960
+
2961
+ #### Validate / self-test
2962
+
2963
+ ```bash
2964
+ python scripts/sovereign_memory_lib.py --self-test
2965
+ python scripts/sovereign_memory_validate.py --self-test
2966
+ python scripts/sovereign_memory_validate.py --repo . --enforce
2967
+ ```
2968
+
2969
+ #### Injection char-cap troubleshooting
2970
+
2971
+ When digest appears truncated:
2972
+
2973
+ 1. Check `SOVEREIGN_MEMORY_MAX_CHARS` (default **2048**).
2974
+ 2. Reduce `SOVEREIGN_MEMORY_TOP_N` / `SOVEREIGN_MEMORY_TOP_K` if too many entries compete.
2975
+ 3. Informational **`SOVEREIGN_MEMORY_READ_BOUND`** when tail read truncates — digest still emitted.
2976
+
2977
+ #### Archive rollover remediation
2978
+
2979
+ When append fails with **`SOVEREIGN_MEMORY_ARCHIVE_REQUIRED`**:
2980
+
2981
+ 1. Check permissions on `docs/engineering/sovereign-memory-archive/`.
2982
+ 2. Verify disk space; active file rollover moves to `<basename>-<YYYYMMDDTHHMMSSZ>.jsonl`.
2983
+ 3. Rollover is **not** triad compaction (**US-0072** unchanged).
2984
+
2985
+ #### Parity enforcement
2986
+
2987
+ ```bash
2988
+ python scripts/check_intake_template_parity.py --scope=sovereign-memory
2989
+ ```
2990
+
2991
+ Pair table (`SOVEREIGN_MEMORY_PAIRS`): lib, validator, scratchpad, `.gitkeep`, `DEC-0105.md`.
2992
+
2993
+ #### Related artifacts
2994
+
2995
+ - **Architecture**: `docs/engineering/architecture.md` `# US-0105`
2996
+ - **Reason codes**: `docs/engineering/reason_codes.md` § US-0105
2997
+ - **Decision record**: `decisions/DEC-0105.md`
2998
+ - **Contract tests**: `tests/us0105_contract_test.py` (8 tests + 2 compose guards)
2999
+
3000
+ ### Sovereign Loop Mode (US-0107)
3001
+
3002
+ **Default-off project orchestration**. When `AUTO_SOVEREIGN=0` (default), zero overhead — no
3003
+ deferral reads/writes, no advance, no notifications. When enabled, requires
3004
+ `SOVEREIGN_GOAL_MODE=goal_convergence` (fail-closed **`SOVEREIGN_LOOP_GOAL_MODE_REQUIRED`**).
3005
+
3006
+ #### Scratchpad keys
3007
+
3008
+ | Key | Values | Default | Notes |
3009
+ |-----|--------|---------|-------|
3010
+ | `AUTO_SOVEREIGN` | `0` \| `1` | `0` | Master enable gate. |
3011
+ | `AUTO_SOVEREIGN_DEFERRAL_MAX` | int ≥ 1 | `50` | Max open deferral rows. |
3012
+ | `AUTO_SOVEREIGN_DRAIN_GENERATE_MAX` | int ≥ 0 | `3` | Drain-generate iterations per run. |
3013
+ | `AUTO_SOVEREIGN_DEFERRAL_POLICY` | `stop` \| `skip` \| `resolve_first` | `resolve_first` | Deferral gate policy. |
3014
+ | `SOVEREIGN_NOTIFY_TARGET` | `off` \| `ntfy` \| `email` \| `hook` | `off` | Notification adapter. |
3015
+ | `SOVEREIGN_NOTIFY_NTFY_TOPIC` | string | *(empty)* | Local-only ntfy topic. |
3016
+ | `SOVEREIGN_NOTIFY_NTFY_BASE` | URL | *(empty)* | Optional ntfy base override — local-only. |
3017
+ | `SOVEREIGN_NOTIFY_HOOK_URL` | URL | *(empty)* | Webhook POST target — local-only. |
3018
+ | `SOVEREIGN_NOTIFY_EMAIL_TO` | email | *(empty)* | Email v1 deferred. |
3019
+
3020
+ #### Enable / disable
3021
+
3022
+ ```bash
3023
+ # Enable sovereign loop (both keys required)
3024
+ echo "AUTO_SOVEREIGN=1" >> .cursor/scratchpad.local.md
3025
+ echo "SOVEREIGN_GOAL_MODE=goal_convergence" >> .cursor/scratchpad.local.md
3026
+
3027
+ # Disable (zero overhead)
3028
+ echo "AUTO_SOVEREIGN=0" >> .cursor/scratchpad.local.md
3029
+ ```
3030
+
3031
+ Notification topic/URL values belong in `.cursor/scratchpad.local.md` only — never commit secrets.
3032
+
3033
+ #### Deferral register operator workflow
3034
+
3035
+ | Action | Command / API |
3036
+ |--------|----------------|
3037
+ | Append deferral | `append_deferral(...)` via `sovereign_loop_lib.py` |
3038
+ | Resolve deferral | `resolve_deferral(repo, deferral_id, orchestrator_run_id=...)` |
3039
+ | List open rows | `list_open_deferrals(repo, scratchpad=...)` (latest-state-wins) |
3040
+ | Validate JSONL | `python scripts/sovereign_loop_validate.py --repo . --enforce` |
3041
+
3042
+ Path: `handoffs/sovereign_deferrals.jsonl` (create-on-first-write; bootstrap `.gitkeep` only).
3043
+ Sidecar: `handoffs/sovereign_loop_state.json` v1 — per-run drain-generate iteration counter.
3044
+
3045
+ #### Drain-generate decision gate
3046
+
3047
+ When backlog has zero OPEN stories but convergence is not met and iterations remain under cap,
3048
+ `/auto` spawns a fresh **PO** subagent (spawn-only **US-0095**) with ephemeral id
3049
+ `drain-gen-{orchestrator_run_id}-{iteration}`. PO proposes up to **3** candidates per iteration.
3050
+
3051
+ **Mandatory per-candidate decision gate**: accept → `/intake` or controlled backlog append;
3052
+ reject → discard. No auto-append without operator gate.
3053
+
3054
+ #### US-0109 integration (`DEPLOY_DEFERRED`)
3055
+
3056
+ **US-0109** (deploy smoke) is the downstream writer for `DEPLOY_DEFERRED` deferral rows when
3057
+ deploy smoke cap is exhausted. Schema v1 fields: `work_item_kind=deploy`, standard deferral
3058
+ register fields per **DEC-0107** §2. US-0107 owns schema, validator, and read paths only — no
3059
+ deploy smoke logic in this story.
3060
+
3061
+ #### Validate / self-test
3062
+
3063
+ ```bash
3064
+ python scripts/sovereign_loop_lib.py --self-test
3065
+ python scripts/sovereign_loop_validate.py --self-test
3066
+ python scripts/sovereign_loop_validate.py --repo . --enforce
3067
+ pytest -k us0107
3068
+ ```
3069
+
3070
+ #### Parity enforcement
3071
+
3072
+ ```bash
3073
+ python scripts/check_intake_template_parity.py --scope=sovereign-loop
3074
+ ```
3075
+
3076
+ Pair table (`SOVEREIGN_LOOP_PAIRS`): lib, validator, scratchpad, deferrals `.gitkeep`, `DEC-0107.md`.
3077
+
3078
+ #### Related artifacts
3079
+
3080
+ - **Architecture**: `docs/engineering/architecture.md` `# US-0107`
3081
+ - **Reason codes**: `docs/engineering/reason_codes.md` § US-0107
3082
+ - **Decision record**: `decisions/DEC-0107.md`
3083
+ - **Contract tests**: `tests/us0107_contract_test.py` (8 tests + 2 compose guards)
3084
+
3085
+ ### Sovereign Role-Behavior Manifest (US-0106)
3086
+
3087
+ **Default-off per-role objective + inter-role review obligations**. When `SOVEREIGN_ROLE_MANIFEST=0` (default),
3088
+ zero overhead — no manifest reads, no objective injection, no review dispatch. Review spawns are
3089
+ supplementary post-phase hooks — they never substitute for the US-0069 producer role.
3090
+
3091
+ #### Scratchpad keys
3092
+
3093
+ | Key | Values | Default | Notes |
3094
+ |-----|--------|---------|-------|
3095
+ | `SOVEREIGN_ROLE_MANIFEST` | `0` \| `1` | `0` | Master enable gate. |
3096
+ | `SOVEREIGN_ROLE_OBJECTIVE_MAX_CHARS` | int ≥ 1 | `512` | Hard truncate for injection block. |
3097
+ | `SOVEREIGN_ROLE_REVIEW_MAX_PER_PHASE` | int ≥ 0 | `2` | Per-phase review dispatch cap. |
3098
+ | `SOVEREIGN_ROLE_REVIEW_REWORK_MAX` | int ≥ 0 | `1` | Bounded rework before decision gate. |
3099
+
3100
+ #### Enable / disable
3101
+
3102
+ ```bash
3103
+ # Enable sovereign role manifest
3104
+ echo "SOVEREIGN_ROLE_MANIFEST=1" >> .cursor/scratchpad.local.md
3105
+
3106
+ # Disable (zero overhead)
3107
+ echo "SOVEREIGN_ROLE_MANIFEST=0" >> .cursor/scratchpad.local.md
3108
+ ```
3109
+
3110
+ #### Manifest edit operator workflow
3111
+
3112
+ 1. Open `.cursor/sovereign-role-manifest.yaml` (active) or `template/.cursor/sovereign-role-manifest.yaml.example` (template).
3113
+ 2. Add role: append `role_id` ∈ {`po`, `tech-lead`, `dev`, `qa`, `release`, `curator`} with `objective_function` (≤ 1024 chars at file; truncated at injection to `SOVEREIGN_ROLE_OBJECTIVE_MAX_CHARS`).
3114
+ 3. Add obligation: `obligation_id` (unique slug), `reviewer_role`, `target_role`, `trigger_phase` ∈ canonical phase ids, `review_focus` ∈ {`user_value_drift`, `testability`, `buildability`, `deployability`}, `artifact_refs[]`, optional `blocking` (default `false`).
3115
+ 4. Edit `cross_model_policy.default_order` ∈ {`role_review_first`, `critic_first`, `critic_only`, `role_review_only`} to set critic vs role-review ordering.
3116
+ 5. Edit `escalation_rules.rework_max` for bounded rework cap.
3117
+ 6. Run validator: `python scripts/sovereign_role_manifest_validate.py --repo . --enforce`.
3118
+
3119
+ #### Validator invocation
3120
+
3121
+ ```bash
3122
+ # Validate single manifest file
3123
+ python scripts/sovereign_role_manifest_validate.py --file .cursor/sovereign-role-manifest.yaml
3124
+
3125
+ # Validate repo active + template pair
3126
+ python scripts/sovereign_role_manifest_validate.py --repo . --enforce
3127
+
3128
+ # Lib self-test
3129
+ python scripts/sovereign_role_manifest_validate.py --self-test
3130
+ ```
3131
+
3132
+ Success: `[SOVEREIGN_ROLE_MANIFEST_VALIDATION_OK]`.
3133
+ Fail: reason codes `SOVEREIGN_ROLE_*` — `SCHEMA_INVALID`, `UNKNOWN_ROLE`, `UNKNOWN_PHASE`, `SECRET_DETECTED`, `OBJECTIVE_OVERFLOW`.
3134
+
3135
+ #### Review dispatch troubleshooting
3136
+
3137
+ | Reason code | Symptom | Remediation |
3138
+ |-------------|---------|-------------|
3139
+ | `SOVEREIGN_ROLE_MANIFEST_DISABLED` | `SOVEREIGN_ROLE_MANIFEST=0` — no dispatch | Set `SOVEREIGN_ROLE_MANIFEST=1` to enable |
3140
+ | `ROLE_REVIEW_DISPATCH_FAILED` | JSONL append I/O error | Check file permissions on `handoffs/sovereign_role_reviews.jsonl` |
3141
+ | `ROLE_REVIEW_SPAWN_FAILED` | Subagent spawn error | Retry spawn; check Task tool availability |
3142
+ | `ROLE_REVIEW_BLOCKED` | Blocking review verdict `fail` | Apply escalation: rework (bounded by `SOVEREIGN_ROLE_REVIEW_REWORK_MAX`) or operator decision gate |
3143
+ | `ROLE_REVIEW_DEFERRAL_FAILED` | US-0107 deferral append error | Check `handoffs/sovereign_deferrals.jsonl` permissions; fail-open logged |
3144
+ | `ROLE_REVIEW_REWORK_CAP` | Rework max exhausted | Operator decision gate: waive findings or abort |
3145
+
3146
+ Reviews JSONL: `handoffs/sovereign_role_reviews.jsonl` (v1 schema: `schema_version`, `obligation_id`, `reviewer_role`, `target_role`, `trigger_phase`, `review_focus`, `producer_evidence_ref`, `orchestrator_run_id`, `ts`, `verdict`, `blocking`, `findings_ref`).
3147
+
3148
+ #### Validate / self-test
3149
+
3150
+ ```bash
3151
+ python scripts/sovereign_role_manifest_lib.py --self-test
3152
+ python scripts/sovereign_role_manifest_validate.py --self-test
3153
+ python scripts/sovereign_role_manifest_validate.py --repo . --enforce
3154
+ pytest -k us0106
3155
+ ```
3156
+
3157
+ #### Parity enforcement
3158
+
3159
+ ```bash
3160
+ python scripts/check_intake_template_parity.py --scope=sovereign-role-manifest
3161
+ ```
3162
+
3163
+ Pair table (`SOVEREIGN_ROLE_MANIFEST_PAIRS`): scratchpad keys, manifest YAML, example manifest, validator, lib, reviews JSONL, template validator, template lib.
3164
+
3165
+ #### Related artifacts
3166
+
3167
+ - **Architecture**: `docs/engineering/architecture.md` `# US-0106`
3168
+ - **Reason codes**: `docs/engineering/reason_codes.md` § US-0106
3169
+ - **Decision record**: `decisions/DEC-0106.md`
3170
+ - **Contract tests**: `tests/us0106_contract_test.py` (8 tests + 2 compose guards)
3171
+
3172
+ ### Parallel Instance Arbitrage (US-0108)
3173
+
3174
+ **Default-off parallel execute-phase instance orchestration**. When `SOVEREIGN_PARALLEL_DEV=0` (default),
3175
+ zero overhead — no worktrees, no parallel QA, no pick JSON, no resource guard.
3176
+
3177
+ #### Scratchpad keys
3178
+
3179
+ | Key | Values | Default | Notes |
3180
+ |-----|--------|---------|-------|
3181
+ | `SOVEREIGN_PARALLEL_DEV` | `0` \| `1` | `0` | Master enable gate. |
3182
+ | `AUTO_SOVEREIGN_PARALLEL_N` | int ≥ 1 | `3` | Instances per execute cycle. |
3183
+ | `AUTO_SOVEREIGN_PARALLEL_MAX_TOTAL` | int ≥ 1 | `6` | System-wide instance cap. |
3184
+ | `AUTO_SOVEREIGN_MERGE_RESOLVE` | `first_pass_wins` \| `last_pass_wins` \| `winner_takes_all` \| `manual` | `first_pass_wins` | Merge policy. |
3185
+ | `AUTO_SOVEREIGN_WORKTREE_KEEP` | `0` \| `1` | `0` | Retain loser worktrees for debugging. |
3186
+ | `AUTO_SOVEREIGN_PARALLEL_QA` | `0` \| `1` | `0` | Enable parallel QA cross-review (v2). |
3187
+ | `AUTO_SOVEREIGN_PARALLEL_QA_ARBITER` | `critic_first_pass` \| `majority_vote` | `critic_first_pass` | QA arbitration strategy. |
3188
+ | `AUTO_SOVEREIGN_PARALLEL_ANTI_SLOP_THRESHOLD` | int 0-10 | `6` | Anti-slop floor. |
3189
+ | `AUTO_SOVEREIGN_PARALLEL_REWORK_MAX` | int ≥ 0 | `2` | Per-instance rework cap. |
3190
+ | `AUTO_SOVEREIGN_PARALLEL_MERGE_TIMEOUT_SEC` | int ≥ 10 | `60` | Merge timeout. |
3191
+ | `AUTO_SOVEREIGN_PARALLEL_MODEL_<idx>` | model slug | *(empty)* | Per-instance model override (optional). |
3192
+ | `AUTO_SOVEREIGN_PARALLEL_LENS_<idx>` | lens config | *(empty)* | Per-instance lens override (optional). |
3193
+
3194
+ #### Enable / disable
3195
+
3196
+ ```bash
3197
+ # Enable parallel dev (N=3 instances)
3198
+ echo "SOVEREIGN_PARALLEL_DEV=1" >> .cursor/scratchpad.local.md
3199
+ echo "AUTO_SOVEREIGN_PARALLEL_N=3" >> .cursor/scratchpad.local.md
3200
+
3201
+ # Disable (zero overhead)
3202
+ echo "SOVEREIGN_PARALLEL_DEV=0" >> .cursor/scratchpad.local.md
3203
+ ```
3204
+
3205
+ #### Parallel dev workflow
3206
+
3207
+ 1. **Create worktrees** (T-002/T-003): `create_worktrees(story_id, n_instances, base_branch)` → isolated
3208
+ `.git/worktrees/us0108-<story_id>-<idx>/` per instance.
3209
+ 2. **Simulated execute** in each worktree (T-008 step 25).
3210
+ 3. **Simulated QA** in each worktree (T-008 step 26).
3211
+ 4. **Select winner** (T-004): `select_winner(qa_results[])` → filter `qa_verdict=pass` → sort `-anti_slop_score`
3212
+ → tie-break earliest `proof_issued_at` → single winner.
3213
+ 5. **Read anti-slop score** (T-005): `read_anti_slop_score(lens_scores[])` via
3214
+ `sovereign_critic_lib.compute_anti_slop_aggregate` (read-only, US-0104 unchanged).
3215
+ 6. **Merge winner** (T-006): `merge_winner(winner_ctx, main_branch)` with bounded retry ≤2, then
3216
+ `PARALLEL_DEV_MERGE_CONFLICT` halt. Write-once `handoffs/parallel_dev_pick.json` v1.
3217
+ 7. **Cleanup** (T-003): winner removed; losers per `AUTO_SOVEREIGN_WORKTREE_KEEP`.
3218
+
3219
+ Pick JSON schema v1:
3220
+ ```json
3221
+ {
3222
+ "schema_version": 1,
3223
+ "story_id": "US-0108",
3224
+ "winner_instance_id": "US-0108-inst0",
3225
+ "worktree_path": ".git/worktrees/us0108-US-0108-0",
3226
+ "qa_verdict": "pass",
3227
+ "anti_slop_score": 8,
3228
+ "proof_issued_at": "2026-06-29T22:00:00Z",
3229
+ "merge_policy": "first_pass_wins",
3230
+ "runner_ts_utc": "2026-06-29T22:30:00Z",
3231
+ "orchestrator_run_id": "auto-20260628-04",
3232
+ "loser_instance_ids": ["US-0108-inst1", "US-0108-inst2"]
3233
+ }
3234
+ ```
3235
+
3236
+ #### Reason codes
3237
+
3238
+ | Code | Meaning |
3239
+ |------|---------|
3240
+ | `PARALLEL_DEV_DISABLED` | Feature off — backward compat path. |
3241
+ | `PARALLEL_DEV_WORKTREE_CREATE_FAILED` | Worktree creation error. |
3242
+ | `PARALLEL_DEV_WORKTREE_CLEANUP_FAILED` | Cleanup error (fail-open). |
3243
+ | `PARALLEL_DEV_SELECTION_NO_PASS` | No QA pass verdict in any instance. |
3244
+ | `PARALLEL_DEV_MERGE_CONFLICT` | Merge conflict after bounded retry. |
3245
+ | `PARALLEL_DEV_RESOURCE_CAP_EXHAUSTED` | System-wide cap reached. |
3246
+ | `PARALLEL_DEV_RESOURCE_LOCK_FAILED` | Lockfile error. |
3247
+ | `PARALLEL_DEV_EXECUTE_FAILED` | Instance execution error. |
3248
+ | `PARALLEL_DEV_ANTI_SLOP_BELOW_THRESHOLD` | Winner score below floor. |
3249
+ | `PARALLEL_DEV_MERGE_TIMEOUT` | Merge operation timeout. |
3250
+ | `PARALLEL_DEV_MANUAL_HALT` | Manual intervention required (`merge_resolve=manual`). |
3251
+ | `PARALLEL_DEV_PICK_SCHEMA_INVALID` | Pick JSON schema violation. |
3252
+
3253
+ #### Resource guard
3254
+
3255
+ - Lockfile: `.git/us0108_parallel_dev.lock`
3256
+ - `acquire_parallel_slot(slot_id, repo_root, max_total)` / `release_parallel_slot(slot_id, repo_root)`
3257
+ - System cap: `AUTO_SOVEREIGN_PARALLEL_MAX_TOTAL` (default 6)
3258
+ - Fail-fast `PARALLEL_DEV_RESOURCE_CAP_EXHAUSTED` when cap reached
3259
+
3260
+ #### Validator / self-test
3261
+
3262
+ ```bash
3263
+ # Self-test
3264
+ python scripts/parallel_dev_arbiter.py --self-test
3265
+
3266
+ # Contract tests
3267
+ python -m pytest tests/us0108_contract_test.py -v
3268
+
3269
+ # Parity check
3270
+ python scripts/check_intake_template_parity.py --scope=sovereign-parallel-dev
3271
+ ```
3272
+
3273
+ #### Compose guards (non-negotiable)
3274
+
3275
+ | Compose surface | Rule |
3276
+ |-----------------|------|
3277
+ | US-0047 (bulk execute) | Step 22 unchanged; system cap checked **after** bulk cap. |
3278
+ | US-0092 (full autonomy) | Outer driver unchanged; parallel is execute-phase internal. |
3279
+ | US-0103 (ledger) | Schema unchanged; US-0108 reads ledger only. |
3280
+ | US-0104 (critic) | Schema unchanged; US-0108 reads `anti_slop_score` only. |
3281
+ | US-0107 (sovereign loop) | Deferral register unchanged; US-0108 appends winner/loser outcomes as consumer. |
3282
+
3283
+ #### Related artifacts
3284
+
3285
+ - **Architecture**: `docs/engineering/architecture.md` `# US-0108`
3286
+ - **Reason codes**: `docs/engineering/reason_codes.md` § US-0108
3287
+ - **Decision record**: `decisions/DEC-0108.md`
3288
+ - **Contract tests**: `tests/us0108_contract_test.py` (8 tests)
3289
+ - **Standalone runbook**: `docs/sovereign-runbook-md/US-0108.md`
3290
+
3291
+ ---
3292
+
3293
+ ## Self-Healing Deploy Loop (US-0109 / DEC-0109)
3294
+
3295
+ **Default-off** (`AUTO_SOVEREIGN_SELF_HEALING_DEPLOY=0`) — zero overhead when off. When enabled,
3296
+ adds a post-deploy smoke probe + bounded retry loop on top of US-0054 publish chain.
3297
+
3298
+ ### Scratchpad keys (schema_v1)
3299
+
3300
+ | Key | Values | Default | Notes |
3301
+ |-----|--------|---------|-------|
3302
+ | `AUTO_SOVEREIGN_SELF_HEALING_DEPLOY` | `0` \| `1` | `0` | Global gate. When `0`, zero overhead — byte-identical US-0054 publish path. |
3303
+ | `AUTO_SOVEREIGN_DEPLOY_RETRY_MAX` | int ≥ 1 | `3` | Max retry attempts after probe FAIL. |
3304
+ | `AUTO_SOVEREIGN_DEPLOY_SMOKE_TIMEOUT_SEC` | int ≥ 1 | `30` | Per-stage HTTP timeout for health probe. |
3305
+ | `AUTO_SOVEREIGN_DEPLOY_PROBE_KIND` | `health_endpoint` \| `acceptance_smoke` \| `both` | `both` | Which stages run during smoke probe. |
3306
+ | `SOVEREIGN_DEPLOY_ACCEPTANCE_SMOKE_PATH` | repo-relative path | `tests/deploy_smoke/` | Acceptance smoke tests directory (pytest runner). |
3307
+ | `AUTO_SOVEREIGN_DEPLOY_HEALTH_ENDPOINT` | names-only env ref | *(empty)* | Key name in `os.environ` — value is a key name, NOT a URL literal. |
3308
+
3309
+ Fail-closed `DEPLOY_HEALING_PROBE_TARGET_MISSING` when health endpoint unresolvable.
3310
+
3311
+ ### Execute steps 29-31
3312
+
3313
+ | Step | Action |
3314
+ |------|--------|
3315
+ | **29 post-deploy smoke probe** | Two-stage chain `[DEPLOY_SMOKE_PROBE_OK]`. Skip when `0`. |
3316
+ | **30 retry loop** | Re-enter publish PASS path on probe FAIL; cap exhaustion → step 31. |
3317
+ | **31 DEPLOY_DEFERRED** | Via US-0107 `append_deferral(work_item_kind=deploy)`. Orchestrator continues. |
3318
+
3319
+ ### Operator remediation
3320
+
3321
+ | Reason code | Operator action |
3322
+ |-------------|-----------------|
3323
+ | `DEPLOY_HEALING_DISABLED` | No action; expected default. Set `=1` to enable. |
3324
+ | `DEPLOY_HEALING_SMOKE_HEALTH_FAIL` | Verify env key in scratchpad.local resolves to reachable URL. Re-run probe. |
3325
+ | `DEPLOY_HEALING_SMOKE_ACCEPTANCE_FAIL` | Inspect `SOVEREIGN_DEPLOY_ACCEPTANCE_SMOKE_PATH` smoke tests. Fix failures. |
3326
+ | `DEPLOY_HEALING_RETRY_ATTEMPT` | Informational per-attempt log; no action required. |
3327
+ | `DEPLOY_HEALING_RETRY_CAP_EXHAUSTED` | Raise `AUTO_SOVEREIGN_DEPLOY_RETRY_MAX` if transient; otherwise resolve root cause. |
3328
+ | `DEPLOY_HEALING_DEFERRED` | Resolve in `handoffs/sovereign_deferrals.jsonl`; re-run `/release` after fix. |
3329
+ | `DEPLOY_HEALING_PROBE_TARGET_MISSING` | Set `AUTO_SOVEREIGN_DEPLOY_HEALTH_ENDPOINT` to valid env key; ensure env var has URL. |
3330
+ | `DEPLOY_HEALING_TIMEOUT` | Raise `AUTO_SOVEREIGN_DEPLOY_SMOKE_TIMEOUT_SEC`; investigate startup latency. |
3331
+
3332
+ ### Validate / self-test
3333
+
3334
+ ```bash
3335
+ python scripts/self_healing_deploy_validate.py --self-test
3336
+ python scripts/self_healing_deploy_validate.py --repo . --enforce
3337
+ ```
3338
+
3339
+ ### Parity enforcement
3340
+
3341
+ ```bash
3342
+ python scripts/check_intake_template_parity.py --scope=sovereign-self-healing-deploy
3343
+ ```
3344
+
3345
+ Pair table (`SOVEREIGN_SELF_HEALING_DEPLOY_PAIRS`): scratchpad, lib, validator,
3346
+ contract tests, runbook, reason_codes — 6 pairs (active + template).
3347
+
3348
+ ### Compose surfaces (read-only)
3349
+
3350
+ | Composed story | US-0109 boundary |
3351
+ |----------------|------------------|
3352
+ | US-0054 (publish) | Re-enters publish PASS only; publish targets/schema/release-notes UNCHANGED. |
3353
+ | US-0100 (changelog) | No changelog writes triggered by US-0109. |
3354
+ | US-0103 (ledger) | Schema UNCHANGED; optional `deploy_deferral_id` additive (v1). |
3355
+ | US-0107 (sovereign loop) | Consumer of `append_deferral(...)` API only; `work_item_kind=deploy`. |
3356
+ | US-0110 (convergence) | Predicate UNCHANGED; US-0110 reads open deferrals (no new logic). |
3357
+
3358
+ ### Related artifacts
3359
+
3360
+ - **Architecture**: `docs/engineering/architecture.md` `# US-0109`
3361
+ - **Reason codes**: `docs/engineering/reason_codes.md` § US-0109
3362
+ - **Decision record**: `decisions/DEC-0109.md`
3363
+ - **Library**: `scripts/self_healing_deploy_lib.py`
3364
+ - **Validator**: `scripts/self_healing_deploy_validate.py`
3365
+ - **Contract tests**: `tests/us0109_contract_test.py` + `tests/us0109_us0110_compose_test.py`
3366
+
3367
+ ---
3368
+
3369
+ ## Release Trigger Adapters (US-0111 / DEC-0111)
3370
+
3371
+ **Default source**: `RELEASE_TRIGGER_SOURCE=manual` (zero behavior change vs pre-US-0111 /release path — byte-identical).
3372
+
3373
+ ### Adapter registry and dispatch
3374
+
3375
+ `scripts/release_trigger_adapters.py` provides four adapters:
3376
+ - **GitHub webhook** (`github`): Parse `release.tag_name`; query GitHub API for previous tag via `GET /repos/{owner}/{repo}/releases?per_page=100` (sorted by `created_at` desc, skip current); fallback `git ls-remote --tags origin` filtered for semver.
3377
+ - **npm publish** (`npm`): Read `npm_package_version` env var; query `npm view {pkg} versions --json` with `RELEASE_TRIGGER_TIMEOUT_SEC` (default 10s); offline fallback `package-lock.json` when `RELEASE_TRIGGER_FALLBACK_TO_LOCAL=1`.
3378
+ - **Git tag push** (`git_tag`): Parse `GITHUB_REF=refs/tags/vX.Y.Z` or local `git describe --tags --abbrev=0`; compute previous via `git for-each-ref --sort=-version:refname refs/tags` (semver sort, not date — handles annotated vs lightweight tags).
3379
+ - **Manual /release** (`manual`): `TriggerContext(source="manual", version=current, previous_version=None)` — byte-identical to pre-US-0111 /release.
3380
+
3381
+ Dispatch via `dispatch_to_adapter(source, env_vars)` → `TriggerContext`. Invalid source → `RELEASE_TRIGGER_SOURCE_INVALID` (fail-closed).
3382
+
3383
+ ### Scratchpad keys
3384
+
3385
+ | Key | Values | Default |
3386
+ |-----|--------|---------|
3387
+ | `RELEASE_TRIGGER_SOURCE` | `manual` \| `github` \| `npm` \| `git_tag` \| `auto` | `manual` |
3388
+ | `RELEASE_TRIGGER_TIMEOUT_SEC` | int ≥ 1 | `10` |
3389
+ | `RELEASE_TRIGGER_FALLBACK_TO_LOCAL` | `0` \| `1` | `0` |
3390
+
3391
+ ### TriggerContext dataclass
3392
+
3393
+ ```python
3394
+ @dataclass
3395
+ class TriggerContext:
3396
+ version: str
3397
+ previous_version: Optional[str]
3398
+ source: str # manual | github | npm | git_tag
3399
+ metadata: Dict[str, Any]
3400
+ ```
3401
+
3402
+ ### Version comparison integration (AC-6)
3403
+
3404
+ `compare_versions_from_trigger(trigger)` normalizes `trigger.version` and `trigger.previous_version` via `release_changelog_lib.normalize_semver()`. Fail-closed: `RELEASE_TRIGGER_COMPARE_VERSIONS_FAILED` on invalid semver.
3405
+
3406
+ ### Atomic promotion (AC-7, AC-8)
3407
+
3408
+ - **CHANGELOG.md promotion**: `promote_changelog_version(semver, sprint_ids, repo_root)` reuses `release_changelog_lib.promote_unreleased()` unchanged (US-0100 compose). Atomic write via `os.replace(temp, target)` with Windows best-effort retry (0.1s × 2). Fail-closed: `RELEASE_TRIGGER_ATOMIC_PROMOTION_FAILED`.
3409
+ - **Per-version notes**: `write_per_version_notes(semver, sprint_ids, repo_root)` writes `handoffs/releases/vX.Y.Z-release-notes.md` atomically. Reuses `release_changelog_lib.build_version_doc()` read-only compose. Fail-closed: `RELEASE_TRIGGER_NOTES_WRITE_FAILED`.
3410
+
3411
+ ### Ledger event emit (AC-9)
3412
+
3413
+ `emit_version_derivation_event(trigger, norm_version, norm_previous, repo_root, scratchpad)`:
3414
+ 1. Write `handoffs/release_events/{iso-timestamp}-{semver}.json` atomically.
3415
+ 2. Append to US-0103 ledger via `decision_ledger_lib.append_entry(ledger_path, entry, scratchpad)` with `decision_type="version_derivation"`. Ledger schema unchanged (consumer-only append compose).
3416
+ 3. Fail-closed: `RELEASE_TRIGGER_EVENT_EMIT_FAILED`.
3417
+
3418
+ ### Reason codes (9 total — DEC-0111 §7)
3419
+
3420
+ | Code | Trigger | Blocking? |
3421
+ |------|---------|-----------|
3422
+ | `RELEASE_TRIGGER_ADAPTER_FAILED` | Adapter dispatch error | **Yes** |
3423
+ | `RELEASE_TRIGGER_TAG_MISSING` | Cannot resolve current tag | **Yes** |
3424
+ | `RELEASE_TRIGGER_PREVIOUS_MISSING` | Cannot resolve previous tag | **Yes** |
3425
+ | `RELEASE_TRIGGER_PACKAGE_JSON_MISSING` | npm adapter: package.json missing | **Yes** |
3426
+ | `RELEASE_TRIGGER_ATOMIC_PROMOTION_FAILED` | Atomic write failed (Windows lock, I/O) | **Yes** |
3427
+ | `RELEASE_TRIGGER_NOTES_WRITE_FAILED` | Per-version notes write failed | **Yes** |
3428
+ | `RELEASE_TRIGGER_EVENT_EMIT_FAILED` | Ledger/event emit failed | **Yes** |
3429
+ | `RELEASE_TRIGGER_COMPARE_VERSIONS_FAILED` | Semver compare failed | **Yes** |
3430
+ | `RELEASE_TRIGGER_SOURCE_INVALID` | Unknown source value | **Yes** |
3431
+
3432
+ ### Operator troubleshooting
3433
+
3434
+ | Symptom | Diagnosis | Remediation |
3435
+ |---------|-----------|-------------|
3436
+ | `RELEASE_TRIGGER_SOURCE_INVALID` on startup | Invalid `RELEASE_TRIGGER_SOURCE` in scratchpad | Set to `manual` (default), `github`, `npm`, `git_tag`, or `auto` |
3437
+ | `RELEASE_TRIGGER_TAG_MISSING` in CI | `GITHUB_REF` not set or non-semver | Ensure CI passes `GITHUB_REF=refs/tags/vX.Y.Z` or run `git tag vX.Y.Z && git push --tags` |
3438
+ | `RELEASE_TRIGGER_ATOMIC_PROMOTION_FAILED` on Windows | File lock or antivirus holding CHANGELOG.md | Close IDE tabs; retry; check antivirus exclusion list |
3439
+ | `RELEASE_TRIGGER_EVENT_EMIT_FAILED` with ledger enabled | Ledger file permissions or schema mismatch | Check `handoffs/sovereign_decisions/decisions.jsonl` writable; verify 12-field schema |
3440
+
3441
+ ### Compose surfaces (read-only)
3442
+
3443
+ | Composed story | US-0111 boundary |
3444
+ |----------------|------------------|
3445
+ | US-0100 (changelog) | Reuses `promote_unreleased()` and `build_version_doc()` unchanged; no API modifications. |
3446
+ | US-0054 (publish) | Dispatches before `/release` path; `release-all.sh` UNCHANGED. |
3447
+ | US-0103 (ledger) | Consumer of `append_entry()` API only; `decision_type="version_derivation"` additive. |
3448
+ | US-0040 (runbook) | No runbook structure changes; adds new section. |
3449
+ | US-0008 (release-all.sh) | UNCHANGED; US-0111 is pre-release dispatch. |
3450
+ | US-0107 (sovereign loop) | No sovereign loop changes; US-0111 emits events to ledger. |
3451
+ | US-0110 (convergence) | `list_open_deferrals` UNCHANGED; no convergence predicate changes. |
3452
+
3453
+ ### Parity enforcement
3454
+
3455
+ ```bash
3456
+ python scripts/check_intake_template_parity.py --scope=release-trigger-adapter
3457
+ ```
3458
+
3459
+ Pair table (`RELEASE_TRIGGER_ADAPTER_PAIRS`): lib + template lib + contract tests + runbook + reason_codes + scratchpad + parity itself — 7 pairs (active + template).
3460
+
3461
+ ### Related artifacts
3462
+
3463
+ - **Architecture**: `docs/engineering/architecture.md` `# US-0111`
3464
+ - **Reason codes**: `docs/engineering/reason_codes.md` § US-0111
3465
+ - **Decision record**: `decisions/DEC-0111.md`
3466
+ - **Library**: `scripts/release_trigger_adapters.py`
3467
+ - **Contract tests**: `tests/us0111_contract_test.py` (12 tests)
3468
+
3469
+ ---
3470
+
3471
+ ## Compose Guards: US-0054, US-0100, US-0110 (read-only boundaries)
3472
+
3473
+ Contract tests enforce US-0109 does not modify these upstream surfaces:
3474
+
3475
+ ### US-0054 — Publish Targets
3476
+
3477
+ `test_us0109_us0054_compose_no_publish_semantics_change` verifies:
3478
+ - No `RELEASE_PUBLISH_OK` token written by US-0109 code
3479
+ - No `release_publish` function calls
3480
+ - No `publish_targets` schema modifications
3481
+
3482
+ US-0109 reads publish status as an observer; publish logic remains in `scripts/release_publish.py`.
3483
+
3484
+ ### US-0100 — Changelog / Unreleased Promotion
3485
+
3486
+ `test_us0109_us0100_compose_no_changelog_change` verifies:
3487
+ - No `changelog` string literals in US-0109 code
3488
+ - No `[Unreleased]` version promotion
3489
+ - No `changelog_lib` or `version_changelog` imports
3490
+
3491
+ US-0109 does not write changelog entries or promote versions.
3492
+
3493
+ ### US-0110 — Convergence Predicate
3494
+
3495
+ `test_us0109_us0110_compose_no_convergence_change` verifies:
3496
+ - No `convergence` string literals in US-0109 code
3497
+ - No `evaluate_convergence` function calls
3498
+ - No imports from `sovereign_convergence_lib`
3499
+
3500
+ US-0109 does not participate in convergence evaluation.
3501
+
3502
+ ---
3503
+
2385
3504
  ## Project run steps
2386
3505
 
2387
3506
  ### Prerequisites