rapier-runtime 0.6.1__tar.gz → 0.6.2__tar.gz

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 (116) hide show
  1. rapier_runtime-0.6.2/CHANGELOG.md +208 -0
  2. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/PKG-INFO +5 -3
  3. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/README.md +4 -2
  4. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/docs/STATUS.md +15 -1
  5. rapier_runtime-0.6.2/docs/chatlogs/202610162128.reconcile-false-alarms-0.6.1.md +91 -0
  6. rapier_runtime-0.6.2/docs/chatlogs/202611141130.inflight.accountable-agents-gaps.md +81 -0
  7. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/docs/mcp-server-scope.md +12 -0
  8. rapier_runtime-0.6.2/docs/versioning.md +68 -0
  9. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/pyproject.toml +1 -1
  10. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/cli.py +39 -6
  11. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/mcp/tools.py +21 -2
  12. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/onboarding.py +60 -0
  13. rapier_runtime-0.6.2/src/rapier/operator_policy.py +249 -0
  14. rapier_runtime-0.6.2/src/rapier/pipeline.py +332 -0
  15. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/echo.py +2 -0
  16. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/resolver/anchored_fix.py +2 -0
  17. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/resolver/author.py +2 -0
  18. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/resolver/compose.py +396 -12
  19. rapier_runtime-0.6.2/tests/test_correctness_outcome.py +303 -0
  20. rapier_runtime-0.6.2/tests/test_declared_vs_ran.py +240 -0
  21. rapier_runtime-0.6.2/tests/test_final_state.py +87 -0
  22. rapier_runtime-0.6.2/tests/test_method_version.py +68 -0
  23. rapier_runtime-0.6.2/tests/test_operator_minimum.py +264 -0
  24. rapier_runtime-0.6.2/tests/test_require_independent.py +111 -0
  25. rapier_runtime-0.6.2/tests/test_review_outcome.py +116 -0
  26. rapier_runtime-0.6.1/CHANGELOG.md +0 -104
  27. rapier_runtime-0.6.1/src/rapier/pipeline.py +0 -160
  28. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.claude/scripts/loom-deploy-nonprod.sh +0 -0
  29. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.claude/scripts/loom-deploy-prod.sh +0 -0
  30. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.claude/scripts/loom-env-check.sh +0 -0
  31. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.claude/scripts/loom-state-docs.sh +0 -0
  32. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.githooks/pre-commit +0 -0
  33. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.github/workflows/secret-scan.yml +0 -0
  34. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.gitignore +0 -0
  35. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.gitleaks.toml +0 -0
  36. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.loom/audits/smuggled-virtue.done +0 -0
  37. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.loom/launcher.conf +0 -0
  38. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/.loom/project.json +0 -0
  39. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/LICENSE +0 -0
  40. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/SECURITY.md +0 -0
  41. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/docs/chatlogs/.gitkeep +0 -0
  42. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/docs/chatlogs/202607301413.checkpoint.handoff-cut-0-5-0-to-pypi.md +0 -0
  43. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/docs/chatlogs/20260802-explainability-reminder.md +0 -0
  44. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/docs/chatlogs/202610162038.five-releases-defaults-report-mcp2.md +0 -0
  45. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/docs/threat-model.md +0 -0
  46. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/manifests/echo.yaml +0 -0
  47. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/manifests/sparring.frame.yaml +0 -0
  48. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/manifests/sparring.full.yaml +0 -0
  49. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/manifests/sparring.proposer.yaml +0 -0
  50. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/manifests/sparring.spar.yaml +0 -0
  51. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/site/README.md +0 -0
  52. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/site/coming-soon.html +0 -0
  53. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/site/index.html +0 -0
  54. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/site/rapier-logo.png +0 -0
  55. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/__init__.py +0 -0
  56. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/_json.py +0 -0
  57. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/convergence.py +0 -0
  58. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/envelope.py +0 -0
  59. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/ledger.py +0 -0
  60. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/manifest.py +0 -0
  61. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/mcp/__init__.py +0 -0
  62. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/mcp/server.py +0 -0
  63. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/models.py +0 -0
  64. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/presets.py +0 -0
  65. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/secrets.py +0 -0
  66. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stage.py +0 -0
  67. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/__init__.py +0 -0
  68. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/frame.py +0 -0
  69. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/proposer/__init__.py +0 -0
  70. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/proposer/phase_stages.py +0 -0
  71. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/proposer/phases.py +0 -0
  72. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/proposer/traceability_gate.py +0 -0
  73. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/reconcile.py +0 -0
  74. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/resolver/__init__.py +0 -0
  75. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/resolver/_binding.py +0 -0
  76. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/resolver/_extract.py +0 -0
  77. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/resolver/citation_gate.py +0 -0
  78. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/resolver/cross_review.py +0 -0
  79. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/stages/resolver/definitiveness_gate.py +0 -0
  80. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/__init__.py +0 -0
  81. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/_bootstrap.py +0 -0
  82. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/_llm_shim.py +0 -0
  83. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/_vendor/PROVENANCE.md +0 -0
  84. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/_vendor/cite_check.py +0 -0
  85. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/_vendor/lib_llm.py +0 -0
  86. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/_vendor/spar_cross_review.py +0 -0
  87. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/_vendor/spar_definitiveness_gate.py +0 -0
  88. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/_vendor/spar_verify_gate.py +0 -0
  89. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/_vendor/verify_grounding.py +0 -0
  90. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/rider_record.py +0 -0
  91. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/src/rapier/verify/service.py +0 -0
  92. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_cite_check_evidence_union.py +0 -0
  93. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_cli.py +0 -0
  94. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_convergence.py +0 -0
  95. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_envelope.py +0 -0
  96. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_extract_json.py +0 -0
  97. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_frame_stage.py +0 -0
  98. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_full_ceremony.py +0 -0
  99. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_manifest.py +0 -0
  100. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_mcp.py +0 -0
  101. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_mcp_resources.py +0 -0
  102. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_models_vendors.py +0 -0
  103. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_onboarding.py +0 -0
  104. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_persistence_default.py +0 -0
  105. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_pipeline.py +0 -0
  106. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_policy.py +0 -0
  107. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_proposer_g2.py +0 -0
  108. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_proposer_stages.py +0 -0
  109. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_proposer_traceability_gate.py +0 -0
  110. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_reconcile.py +0 -0
  111. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_resolver_stages.py +0 -0
  112. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_rider_record.py +0 -0
  113. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_secrets.py +0 -0
  114. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_single_vendor_report.py +0 -0
  115. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_vendor_policy_v4.py +0 -0
  116. {rapier_runtime-0.6.1 → rapier_runtime-0.6.2}/tests/test_verify_service.py +0 -0
@@ -0,0 +1,208 @@
1
+ # Changelog
2
+
3
+ Notable changes to `rapier-runtime`. Earlier releases are summarized in
4
+ [`docs/STATUS.md`](docs/STATUS.md) and on the
5
+ [Releases](https://github.com/muddyone/rapier-runtime/releases) page.
6
+
7
+ ## 0.6.2 — 2026-11-14
8
+
9
+ ### Added: every run names its final state
10
+
11
+ The report's BOTTOM LINE opens with a final state, using the six names from the
12
+ Accountable Agents paper, and the same value is in the trust rider, `ceremony.json`
13
+ (`final_state`, `final_state_next`) and the MCP result:
14
+
15
+ - `act`: the advice cleared the checks that ran, and the report names them and any
16
+ that were switched off. Rapier only advises; acting on it is yours.
17
+ - `block`: a specific failed the correctness check, or a reference was refuted.
18
+ - `defer`: a declared check or the challenge review did not run on the answer
19
+ (crashed, no key, a reviewer error, or ran before the answer was written), or
20
+ the operator requires an independent review and none happened. A control that
21
+ did not run decided nothing, so this is never `block` and never `act`.
22
+ - `escalate`: a specific reads ambiguously between fact and estimate; your call.
23
+ - `non-decision`: the Proposer hit its round cap without agreeing on one option.
24
+ The report says so at the top, and in the Proposer report. The Resolver still
25
+ tests the strongest candidate, but choosing between the options is yours.
26
+ - `refuse`: the run was refused before it started (operator minimum or
27
+ independence requirement).
28
+
29
+ A citation check that was switched off or never declared doesn't change the
30
+ state, but `act` says it didn't run. A review that failed at run time (the
31
+ reviewer is fail-soft) is reported as not run, never as an independent challenge.
32
+
33
+ ### Added: the operator can require an independent review
34
+
35
+ `"require_independent": true` in the operator policy file refuses any review
36
+ ceremony (spar, sparring, proposer, or a custom manifest with a review or
37
+ Proposer stage) when no second vendor is available. Without it, a single-vendor
38
+ run goes ahead and the report says the review was not independent, as before.
39
+ Only the operator can set this: there is no tool argument or environment
40
+ variable for it. The refusal names the policy file and whether it is out of the
41
+ caller's reach (MCP: `refused_by: "operator_require_independent"`; CLI: exit 2).
42
+ `rapier doctor` shows the setting and warns when only one vendor is configured.
43
+
44
+ ### Added: a hash of the method that ran
45
+
46
+ `ceremony.json` gains `method_hash` and `method_resolved_hash` (plus
47
+ `rapier_version`), and THE RECORD shows both, shortened. `method_hash` covers
48
+ the Rapier version, every stage in order with its config and roles, and the
49
+ vendor policy. `method_resolved_hash` is the same after vendor substitution, so
50
+ a run on different keys shows up as the same method on different vendors. To
51
+ check for drift, compare runs on the same inputs under the same `method_hash`.
52
+
53
+ ### Added: what the method declared, compared with what ran
54
+
55
+ - **WHAT RAN**, a new report section, lists every stage the method declared
56
+ and what happened to it: ran, FAILED (the run went on without it), skipped
57
+ (with the stage's reason) or not reached (cancelled). The pipeline records
58
+ this itself, so a stage that writes no trace still appears. Also in the trust
59
+ rider (`stages_run`) and `ceremony.json` (`stages_declared`,
60
+ `stages_failed_soft`, `stages_skipped`).
61
+ - **Checked before a run.** A pipeline that writes a report but declares no
62
+ correctness gate, or puts a gate after `compose` where its result can't reach
63
+ the report, gets a warning in the log, the report and `ceremony.json`
64
+ (`manifest_warnings`). `rapier doctor` checks every built-in ceremony and
65
+ shows the operator minimum. `rapier doctor --manifest m.yaml` checks a custom
66
+ manifest and exits 1 if it warns or would be refused.
67
+ - **Custom manifests meet the operator minimum.** `rapier run --manifest` with
68
+ no citation gate before `compose` counts as `verify=off` and is refused under
69
+ a `gate` minimum.
70
+
71
+ ### Added: an operator minimum for the checks, which the caller cannot lower
72
+
73
+ Over MCP the agent whose answer is being checked chose `verify`, so it could
74
+ switch the citation check off. The operator can now set a floor:
75
+
76
+ - **`/etc/rapier/policy.json`** (`%PROGRAMDATA%\rapier\policy.json` on
77
+ Windows), `{"min_verify": "gate"}`. The path is fixed, not taken from the
78
+ environment, so a caller cannot point Rapier at a different file. The report
79
+ calls the minimum out of the calling agent's reach only when Rapier is not
80
+ running as root and neither owns nor can write the file or any folder above
81
+ it (for example, owned by root, readable by the user that runs Rapier). A
82
+ file that can't be checked, read or parsed fails closed.
83
+ - **`RAPIER_MIN_VERIFY`** also sets a floor, but whoever launches the process
84
+ controls its environment, so the report always calls it within the caller's
85
+ reach.
86
+ - A request below the floor is **refused**, with an error naming the floor and
87
+ its source (MCP: `ok: false`, `refused_by: "operator_minimum"`; CLI: exit 2).
88
+ A policy file that can't be read fails closed. Levels: `off` < `gate` ≤
89
+ `round`.
90
+ - **The report says who set the level:** "switched off, by caller (MCP tool
91
+ argument)", the default or the `--verify` flag, and whether an operator
92
+ minimum stood behind it. `ceremony.json` gains `verify_set_by`,
93
+ `verify_minimum`, `verify_minimum_source` and `verify_minimum_external`.
94
+
95
+ ### Fixed: a check that did not run no longer reads like one that found nothing
96
+
97
+ The report used one sentence for "the correctness check found no specifics" and
98
+ "the checker was unavailable". With `verify=off` the registry section said the
99
+ answer "cited nothing externally checkable", though nothing had been looked up.
100
+
101
+ - **Three outcomes, kept apart**, for both the correctness check and the
102
+ citation check: `ran`, `nothing-to-check`, and `did-not-run: <reason>`. A
103
+ crash (caught fail-soft), a missing key, `verify=off`, and a check left out of
104
+ the pipeline are all `did-not-run`, and the report says it is not a pass.
105
+ - **A run with no hard specifics** now says the check found nothing to check,
106
+ instead of "every hard specific it states traces to a fact you gave".
107
+ - **Recorded everywhere the reader or a tool looks:** the report, the trust
108
+ rider (`correctness_check`, `citation_check`), `ceremony.json`
109
+ (`*_check` and `*_check_reason`) and the MCP result.
110
+
111
+ ## 0.6.1 — 2026-10-16
112
+
113
+ ### Fixed: "WHETHER THE NUMBERS ADD UP" no longer flags correct formulas
114
+
115
+ The arithmetic check summed the numbers in any stated result, so a correct
116
+ standard error, interval, sample size or difference was reported as wrong. In
117
+ a statistics-heavy answer that was five false alarms out of five.
118
+
119
+ - **Formulas are worked out as written.** Roots, powers, quotients and ±
120
+ bounds are evaluated in code, and a mismatch shows the formula it evaluated.
121
+ Differences are checked as differences.
122
+ - **"Not checked" instead of a guess.** Where the operation can't be recovered
123
+ from the text, or a "total" turns out to be a formula, the figure is counted
124
+ as not checked, never as a mismatch. The section says how many there were.
125
+ - **Small figures are checked properly.** The agreement margin used to be
126
+ measured against 1, so any figure under 1 agreed with almost anything. It is
127
+ now 0.5% of the figures themselves, or half a unit of the last digit the
128
+ text shows (`0.120` allows ±0.0005; `6×10⁻⁵` allows ±5×10⁻⁶).
129
+
130
+ ## 0.6.0 — 2026-10-16
131
+
132
+ ### Changed: the MCP server now runs on the mcp 2.x SDK
133
+
134
+ The `[mcp]` extra now requires `mcp>=2.3,<3` (it was `>=1.2,<2`), and the
135
+ server is built on the SDK's `MCPServer`. Tools, their results and the run
136
+ resources behave as before. Two visible differences:
137
+
138
+ - **Progress shows the stage.** Each stage's progress update now carries the
139
+ stage name as its message. mcp 2.x sends log messages only to clients that
140
+ ask for them, so the progress message is what every client will show.
141
+ - **`pip install -U "rapier-runtime[mcp]"` upgrades the SDK.** An environment
142
+ pinned to mcp 1.x will be moved to 2.x. If a 1.x SDK is installed anyway,
143
+ `rapier mcp` says so and gives the command that fixes it.
144
+
145
+ ## 0.5.3 — 2026-10-16
146
+
147
+ ### Added: saved runs as MCP resources
148
+
149
+ MCP clients can now browse saved runs instead of calling tools:
150
+
151
+ - `rapier://runs`: every saved run, newest first, with its kind, topic and
152
+ verdict (JSON).
153
+ - `rapier://runs/{run_id}`: a run's report, as the CLI printed it (plain text).
154
+ - `rapier://runs/{run_id}/record`: a run's full record, redacted when it was
155
+ saved (JSON).
156
+
157
+ Each saved run also appears in the client's resource list, titled by kind and
158
+ topic, including runs saved after the server started. The `list_runs` and
159
+ `get_run` tools still work.
160
+
161
+ ### Fixed: `rapier mcp` failed on a fresh install
162
+
163
+ The `[mcp]` extra allowed any `mcp>=1.2`, so a new install got the mcp 2.x SDK,
164
+ which renamed the server API this version uses. `rapier mcp` then exited with a
165
+ message telling you to install the extra you already had. The extra is now
166
+ capped at `mcp<2`, and if a 2.x SDK is present anyway, `rapier mcp` says so and
167
+ gives the command that fixes it.
168
+
169
+ ## 0.5.2 — 2026-10-16
170
+
171
+ ### Fixed: the report no longer calls a same-vendor review independent
172
+
173
+ Found on a live run with a single vendor's key, where the reviewer and the
174
+ author were the same model:
175
+
176
+ - **The report contradicted itself.** "Already fixed under challenge" said
177
+ "an independent, different-vendor reviewer raised each of these," and the
178
+ registry section pointed to "the cross-vendor challenge," while "How much to
179
+ trust this" correctly said the review was not independent. Every section
180
+ now says what actually happened; a cross-vendor run reads as before.
181
+ - **The progress warning blamed the wrong key.** It said "OPENAI_API_KEY
182
+ absent; falling back to a Claude reviewer" when the OpenAI key was set and
183
+ no Claude model was used. It now names the vendor that ran the review.
184
+ - **Arithmetic printed as raw LaTeX.** Math the model wrote as `\( H \times
185
+ 0.60 \)` now reads `H × 0.60` in the report. Only text inside `\( \)` or
186
+ `\[ \]` is rewritten.
187
+
188
+ ## 0.5.1 — 2026-10-16
189
+
190
+ ### Changed: default models for five OpenAI-compatible vendors
191
+
192
+ Five built-in defaults named models that a vendor has retired or is retiring. If
193
+ you relied on a vendor's default (no `model:` in your manifest and no
194
+ `RAPIER_MODEL_<VENDOR>` set), **your runs now use a different model.** To keep
195
+ the old one while it still works, name it in your manifest or set
196
+ `RAPIER_MODEL_<VENDOR>`.
197
+
198
+ | Vendor | Was | Now | Why |
199
+ |---|---|---|---|
200
+ | `openrouter` | `openai/gpt-4o` | `openai/gpt-6-sol` | gpt-4o stops working 2027-01-14. gpt-6-sol is OpenAI's current flagship and costs slightly less. |
201
+ | `deepseek` | `deepseek-chat` | `deepseek-flash` | `deepseek-chat` was retired 2026-07-24, so the old default already fails. |
202
+ | `groq` | `llama-3.3-70b-versatile` | `openai/gpt-oss-120b` | Shut down 2026-08-16, so the old default already fails. Groq names gpt-oss-120b as the replacement. |
203
+ | `together` | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | `openai/gpt-oss-120b` | Leaves Together's serverless tier 2026-10-22. |
204
+ | `gemini` | `gemini-2.5-flash` | `gemini-3.5-flash-lite` | 2.5 Flash is closed to new API users. Google recommends 3.5 Flash-Lite for new projects, at the same price. |
205
+
206
+ `xai` (`grok-4.3`), `mistral` (`mistral-large-latest`) and `ollama` (`llama3.1`)
207
+ are unchanged. Each was checked against its vendor's deprecation page on
208
+ 2026-10-15.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rapier-runtime
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: A code-orchestrated engine that runs the SPARRING method: grounded, cross-vendor adversarial review for AI-in-the-loop decisions.
5
5
  Project-URL: Homepage, https://rapierruntime.com
6
6
  Project-URL: Repository, https://github.com/muddyone/rapier-runtime
@@ -49,9 +49,11 @@ Free and open source (Apache-2.0). Not monetized.
49
49
 
50
50
  ---
51
51
 
52
- ## Status: pre-alpha — the full ceremony runs end-to-end (M0–M3 complete; M4 in progress)
52
+ ## Status: alpha — the full ceremony runs end-to-end (M0–M4 complete)
53
53
 
54
- This is early software, pinned pre-1.0 — but the whole method runs. What works
54
+ This is early software, pinned pre-1.0 — but the whole method runs. The interfaces can still
55
+ change in any minor release. How versions, maturity and evidence are kept apart:
56
+ [docs/versioning.md](docs/versioning.md). What works
55
57
  today:
56
58
 
57
59
  - **The full SPARRING ceremony, end-to-end.** The **Proposer** (divergent
@@ -19,9 +19,11 @@ Free and open source (Apache-2.0). Not monetized.
19
19
 
20
20
  ---
21
21
 
22
- ## Status: pre-alpha — the full ceremony runs end-to-end (M0–M3 complete; M4 in progress)
22
+ ## Status: alpha — the full ceremony runs end-to-end (M0–M4 complete)
23
23
 
24
- This is early software, pinned pre-1.0 — but the whole method runs. What works
24
+ This is early software, pinned pre-1.0 — but the whole method runs. The interfaces can still
25
+ change in any minor release. How versions, maturity and evidence are kept apart:
26
+ [docs/versioning.md](docs/versioning.md). What works
25
27
  today:
26
28
 
27
29
  - **The full SPARRING ceremony, end-to-end.** The **Proposer** (divergent
@@ -1,9 +1,23 @@
1
1
  # Rapier — dev status
2
2
 
3
- _Last updated: 2026-10-16 (tag v0.6.0, 250 tests with the mcp SDK)._ A running "where we are /
3
+ _Last updated: 2026-10-16 (tag v0.6.1, 264 tests)._ A running "where we are /
4
4
  what's next" so a new session can continue without reconstructing from git log.
5
5
 
6
6
  ## Done
7
+ - **RELEASED (2026-10-16) — `rapier-runtime` 0.6.1 on PyPI. The arithmetic
8
+ check ("WHETHER THE NUMBERS ADD UP") stops flagging correct formulas.** Git
9
+ tag `v0.6.1`; https://pypi.org/project/rapier-runtime/0.6.1/. Reported by
10
+ valkyrie-sparring-1: a stats-heavy spar
11
+ (sparring-framework `docs/spars/202610161853-gopnik-pilot-design-review/`)
12
+ got five false mismatches out of five, because every stated result was
13
+ checked as a sum of the numbers near it. Fixes: `difference` and
14
+ `expression` operations (the formula is evaluated as written, by a
15
+ literal-only evaluator); "not checked" where the operation can't be
16
+ recovered, or a "sum" quote shows a formula; the agreement margin is 0.5% of
17
+ the figures (no floor of 1) or half a unit of the last digit the quote
18
+ shows. Also: the ledger permission test skips where the filesystem ignores
19
+ chmod (WSL `/mnt/c`). Verified from PyPI: a clean install reports 0.6.1 and
20
+ the reported standard-error case agrees.
7
21
  - **RELEASED (2026-10-16) — `rapier-runtime` 0.6.0 on PyPI. The MCP server runs
8
22
  on the mcp 2.x SDK.** Git tag `v0.6.0`;
9
23
  https://pypi.org/project/rapier-runtime/0.6.0/. `MCPServer` replaces
@@ -0,0 +1,91 @@
1
+ # Fix the arithmetic check's false alarms on formulas and release 0.6.1
2
+
3
+ **Timestamp**: 2026-10-16 21:28 (local)
4
+ **Project**: rapier-runtime
5
+ **Machine**: Valkyrie
6
+ **User**: Bart Niedner
7
+ **Session**: worktree-valkyrie-rapier-runtime-1
8
+ **Previous session**: [202610162038.five-releases-defaults-report-mcp2.md](202610162038.five-releases-defaults-report-mcp2.md)
9
+
10
+ ## Session summary
11
+ valkyrie-sparring-1 reported that the reconcile stage ("WHETHER THE NUMBERS ADD UP")
12
+ flagged five correct formula results as mismatches in a stats-heavy spar. Fixed the
13
+ cause, two related weaknesses and a test that failed on this machine, then released
14
+ 0.6.1 to PyPI.
15
+
16
+ ## Decisions made
17
+ 1. **Root cause was the extraction schema, not the arithmetic.** The extractor could
18
+ only label a relation sum/product/count/percent_of, and a missing operation
19
+ defaulted to sum, so the model filed formulas as sums. Added `difference`,
20
+ `expression` (formula transcribed as written, evaluated in code by a literal-only
21
+ AST evaluator) and `other` (not checked). The model still only extracts; Python
22
+ still decides.
23
+ 2. **"Not checked" over a guess.** A missing operation, `other`, an expression that
24
+ won't evaluate, or a `sum` whose own quote carries a formula operator
25
+ (`· × ÷ √ ^ ± ² ³ ⁴`) is unverifiable, never a mismatch. `/` and Markdown `**`
26
+ are excluded from that guard so unit rates and bold totals are still checked.
27
+ 3. **Margin measured against the figures.** The old `max(|s|, |c|, 1.0)` floor let
28
+ any figure under 1 agree with almost anything. Now: 0.5% of the figures, or half
29
+ a unit of the last digit shown.
30
+ 4. **Precision read from the quote.** JSON drops trailing zeros (0.120 → 0.12), so
31
+ the allowance comes from the numeral in the aggregate quote that equals the
32
+ stated value (handles `1,200`, `6e-5`, `6×10⁻⁵`, `%`). Falls back to the number.
33
+ 5. **Ledger permission test skips where chmod is ignored** (Bart chose option 1 over
34
+ changing TMPDIR). It probes its tmp dir and skips only the mode assertion.
35
+ 6. **0.6.1** (patch): bug fixes to an existing check, no new interface.
36
+
37
+ ## Files changed
38
+ - Engine: `src/rapier/stages/reconcile.py`
39
+ - Report: `src/rapier/stages/resolver/compose.py` (formula shown on a mismatch;
40
+ count of unchecked figures; section blurb)
41
+ - Tests: `tests/test_reconcile.py` (+17 tests), `tests/test_pipeline.py`
42
+ - Release/docs: `pyproject.toml`, `CHANGELOG.md`, `docs/STATUS.md`
43
+
44
+ ## Current results
45
+ - Suite: 264 passed (263 + 1 skip when tmp is on /mnt/c).
46
+ - Replaying the stored extraction from the reported run
47
+ (sparring-framework `docs/spars/202610161853-gopnik-pilot-design-review/20261016185428-spar/`)
48
+ clears 4 of 5 false alarms. The 5th ("20-point effect") stays a mismatch because
49
+ that stored extraction labelled it `sum` with no operator in its quote; the new
50
+ prompt should extract it as `difference`. **Not confirmed with a live run.**
51
+ - PyPI: 0.6.1 published, tag `v0.6.1`. Clean install from PyPI reports 0.6.1 and
52
+ the reported SE case agrees.
53
+
54
+ ## Bugs found and fixed
55
+ - Reconcile summed formula operands (1857d50).
56
+ - Reconcile margin floor of 1 made sub-1 figures unfalsifiable (dfda3de).
57
+ - Trailing-zero precision lost through JSON (62365e2).
58
+ - `test_ledger_persists_redacted_run` failed on drvfs tmp dirs (f475d2a).
59
+
60
+ ## Environmental discoveries
61
+ - **TMPDIR=/mnt/c/Temp comes from `~/.bashrc:125`** (earlier in the session I
62
+ guessed it might be set by Claude Code; it is not). On that drive pytest's output
63
+ capture crashes ("no tests ran") and file modes read 0o777, so both deploy scripts
64
+ refuse with "suite is red" unless run with `TMPDIR` on the Linux filesystem.
65
+ - `!` commands from the Claude Code prompt get no stdin; the prod script's version
66
+ confirmation reads empty and refuses. Partner pipes it: `printf '0.6.1\n' | ...`.
67
+ - Recorded machine-specific in Claude memory `computer_Valkyrie.md`. No readiness
68
+ probe written: the project has no `.loom/readiness.d/`, and the repair (editing
69
+ `~/.bashrc`, or making the scripts set their own TMPDIR) is a partner decision.
70
+
71
+ ## Surfaced items routed
72
+ None -- no in-flight captures this session.
73
+
74
+ ## What's next
75
+ - Run a fresh spar with `--verify gate` on a stats-heavy topic under 0.6.1 and
76
+ confirm the "20-point effect" kind of figure is extracted as `difference`.
77
+ - Decide whether `loom-deploy-nonprod.sh` / `loom-deploy-prod.sh` should set a
78
+ Linux `TMPDIR` themselves when it points at `/mnt/*`, so deploys work from a
79
+ default shell on Valkyrie.
80
+ - Optional: give `loom-deploy-prod.sh` a `--preview` manifest and a
81
+ `loom-deploy-holds` check, which /deploy-prod expects and this script lacks.
82
+
83
+ _Run `/wn` for the ranked what's-next shortlist._
84
+
85
+ ## Commits
86
+ - 1857d50 reconcile: stop summing formulas — add difference/expression ops, "not checked" when unrecoverable
87
+ - dfda3de reconcile: measure the margin against the figures, not a floor of 1
88
+ - 62365e2 reconcile: read a figure's precision from the quote, not the JSON number
89
+ - f475d2a test: skip the owner-only mode check where the filesystem ignores chmod
90
+ - 55b983f release: bump to 0.6.1 (reconcile check: no more false alarms on formulas)
91
+ - 9136648 docs(status): 0.6.1 released — arithmetic check stops flagging correct formulas
@@ -0,0 +1,81 @@
1
+ # In-flight: six improvements from the Accountable Agents / Declarative AI concept papers
2
+
3
+ **Timestamp**: 2026-11-14 11:30 (local)
4
+ **Project**: rapier-runtime
5
+ **HEAD**: 9214c14b (chatlog: reconcile false alarms fixed, 0.6.1 released)
6
+ **Captured by**: Bart (from a vitae session, vit-1)
7
+ **Status**: open
8
+
9
+ ## Source
10
+
11
+ Thomas Gessler's two public concept papers. Cite these, and only these, in this repo:
12
+
13
+ - *Declarative AI Architecture: Knowledge Artifacts as System Logic for Generative Systems*, DOI 10.5281/zenodo.20687710
14
+ - *Accountable Agents: Declarative Control Architecture for Operational AI Systems*, DOI 10.5281/zenodo.21850919
15
+
16
+ The ideas used here, in our words:
17
+ - A control counts as external only if the governed agent cannot change, disable or get around it.
18
+ Sitting outside the model is not enough.
19
+ - A negative test passes only if the intended control was active and caused the failure.
20
+ - A failure to run is a different outcome from "no decision". The papers name six final states:
21
+ act, refuse, block, defer, escalate, non-decision.
22
+ - Declared controls should be checked against what actually runs, before deployment.
23
+ - To measure drift, record the version of the method that ran.
24
+
25
+ Rapier already meets much of this. The method is declared in a manifest outside the models. The
26
+ models only answer API calls and have no tools. Review is cross-vendor and says so when it isn't. The
27
+ run record names each stage, model and vendor. And a verifier error on one artifact gives `flagged`,
28
+ never `clean` (`src/rapier/verify/_vendor/spar_verify_gate.py`, `decide()`). The items below are at
29
+ the edges.
30
+
31
+ ## Items, most useful first
32
+
33
+ ### 1. SCOPE-CANDIDATE: the calling agent can switch a check off (about half a day)
34
+ Over MCP the calling agent picks `verify` (`src/rapier/mcp/server.py`, `spar` / `sparring`).
35
+ `verify="off"` drops the citation gate and the Proposer's traceability gate (`src/rapier/presets.py:73`
36
+ and `:117`). The definitiveness gate always runs. So the agent whose output is being checked chooses
37
+ which checks run.
38
+ - `verify_mode` is recorded in `ceremony.json`. Not yet checked: whether the report tells the reader.
39
+ - Proposal: a minimum level the operator sets (env or config) that a tool argument can't lower, and a
40
+ report line naming any check that was turned off, and by whom (caller argument or operator config).
41
+ - The operator setting must live where the calling agent can't write it. An env var or config file the
42
+ agent can edit fails the papers' own test (externality is about authority, not location).
43
+ - Promised publicly on 2026-11-14 (a check the operator requires can't be turned off by the caller; the
44
+ report says when a check is off and who turned it off). Item 2 was promised the same day.
45
+
46
+ ### 2. BUG-CANDIDATE: "didn't run" reads like "found nothing" (small)
47
+ When the definitiveness verdict is missing, `_verdict_sentence` in
48
+ `src/rapier/stages/resolver/compose.py` (around line 47) says "there were no hard specifics to check,
49
+ or the checker was unavailable". That merges a check that ran and found nothing with a check that
50
+ never ran. A stage that raises is also caught and logged, and the pipeline goes on
51
+ (`src/rapier/pipeline.py:150`, fail-soft), so a crashed gate lands in the same message.
52
+ - Proposal: separate outcomes (`not-run: <reason>` and `nothing-to-check`) in the verdict, the Trust
53
+ Rider and `ceremony.json`.
54
+
55
+ ### 3. SCOPE-CANDIDATE: compare what the manifest declared with what ran (about half a day)
56
+ - At run time: the Trust Rider lists each declared stage and whether it produced a result, failed soft,
57
+ or was skipped. `ledger.jsonl` holds the trace tails today, but nothing compares them with the
58
+ manifest.
59
+ - Before a run (`rapier doctor` or manifest load): warn when a manifest declares no correctness gate,
60
+ or when a gate's result never reaches the verdict.
61
+
62
+ ### 4. DESIGN-CALL: name the final outcomes
63
+ PASS / FAIL / REVIEW / unchecked map roughly onto block and defer. SPARRING has no formal
64
+ non-decision. A Proposer phase that exits at its cap still in disagreement (`stages/proposer/phase_stages.py`,
65
+ "disagreement-at-cap") is a non-decision: label it, and say what the reader should do next. Keep it
66
+ apart from "a check failed to run" (item 2).
67
+
68
+ ### 5. SCOPE-CANDIDATE: record the method version (small)
69
+ `ceremony.json` has `sparring_version`, and the transcript has the models. There's no hash of the
70
+ manifest (resolved pipeline plus config) that actually ran. Add one, so we can tell whether the same
71
+ inputs under the same method version still give the same result.
72
+
73
+ ### 6. DESIGN-CALL, optional: strict single-vendor mode
74
+ With one vendor available, the reviewer runs on the author's vendor and the report says it is "not an
75
+ independent check". A strict mode would stop and report that no independent check is possible,
76
+ instead of going ahead.
77
+
78
+ ## Out of scope
79
+ Rapier only gives advice and never acts on anything, so the papers' commit boundary and "act is not
80
+ commit" don't apply. Their rule that conflicting validators must not produce a commit is met by
81
+ reporting: objections appear in a "contested" section rather than being merged away.
@@ -35,12 +35,24 @@ Return **both** a human-readable markdown report *and* a structured payload, so
35
35
  client can render prose or branch on the result:
36
36
  - `report_md` — the composed two-part report (what the CLI prints).
37
37
  - `verdict` — `PASS | REVIEW | FAIL | unchecked` (the definitiveness gate).
38
+ - `correctness_check` / `citation_check` — what happened to each check: `ran`,
39
+ `nothing-to-check` (it ran and there was nothing to judge), or
40
+ `did-not-run: <reason>` (switched off, crashed, no key, or not in the pipeline).
41
+ A check that did not run is never reported as one that found nothing.
38
42
  - `grounding` — `{gate, grounding_rate, verified, refuted, unverifiable}` from the
39
43
  now-live citation gate (the external-canon check).
40
44
  - `cross_vendor` — bool, plus `author_vendor` / `reviewer_vendor` (honest
41
45
  degradation is visible, not hidden).
42
46
  - `standing_objections` — forwarded Proposer dissent, when present.
43
47
 
48
+ An operator minimum (`/etc/rapier/policy.json` `{"min_verify": "gate"}`, or
49
+ `RAPIER_MIN_VERIFY`) refuses a `verify` argument below it with
50
+ `{"ok": false, "refused_by": "operator_minimum"}`. No tool argument can change
51
+ the minimum or the file's path; see `src/rapier/operator_policy.py`. It binds callers
52
+ that reach Rapier through its MCP tools or CLI. Code that imports Rapier and
53
+ builds a `Pipeline` directly in its own process is not held to it, and Rapier
54
+ cannot prevent that: keep the policy for agents that only get the tools.
55
+
44
56
  All outputs pass through `secrets.redact_obj` (already the ledger's discipline)
45
57
  before leaving the process.
46
58
 
@@ -0,0 +1,68 @@
1
+ # Versioning and maturity
2
+
3
+ Rapier keeps three things separate, because they answer different questions:
4
+
5
+ 1. **The version number** says whether an upgrade can break you.
6
+ 2. **The maturity stage** says how settled the software is.
7
+ 3. **The evidence status** says how far the method itself has been shown to work.
8
+
9
+ The evidence never goes into the version number. A 1.0 release would mean the software
10
+ contract is stable, not that the method is proven.
11
+
12
+ ## 1. Version number: Semantic Versioning, written as PEP 440
13
+
14
+ Versions follow [Semantic Versioning 2.0](https://semver.org/), written in
15
+ [PEP 440](https://peps.python.org/pep-0440/) form for PyPI.
16
+
17
+ **The public contract** is what a version number protects:
18
+
19
+ - the CLI: commands, flags, and exit codes;
20
+ - the manifest format: stage names, roles, and config keys;
21
+ - the MCP server: tool names and their parameters;
22
+ - the run record: the files under `~/.rapier/runs/<run>/` and the fields in `ceremony.json`.
23
+
24
+ Anything else, including prompt text, report wording, and internal modules, can change in any
25
+ release.
26
+
27
+ **Before 1.0:** a breaking change to the contract bumps the minor number (0.6 → 0.7). Everything
28
+ else bumps the patch (0.6.1 → 0.6.2). A deprecated name keeps working, with a warning, for at least
29
+ one minor release before it is removed. The CHANGELOG says so when it goes.
30
+
31
+ **From 1.0:** a breaking change bumps the major number.
32
+
33
+ ## 2. Maturity stage
34
+
35
+ The stage is shown in two places that must agree: the PyPI `Development Status` classifier in
36
+ `pyproject.toml`, and the Status line in the README. A stage is earned by meeting its criteria, not
37
+ by declaring it.
38
+
39
+ | Stage | Classifier | Criteria |
40
+ |---|---|---|
41
+ | Pre-alpha | `2 - Pre-Alpha` | The method doesn't yet run end to end. |
42
+ | Alpha | `3 - Alpha` | The whole method runs end to end, is published, and is tested. The contract can still change in any minor release. |
43
+ | Beta | `4 - Beta` | The contract is frozen except for fixes. The deprecation policy holds. A security review is done. There are users beyond the maintainer. Every check reports whether it ran: a check that never ran is never reported as one that found nothing. The calling agent can't lower a check the operator requires. |
44
+ | Stable | `5 - Production/Stable` | Released as 1.0. The contract is under Semantic Versioning. |
45
+
46
+ ## 3. Evidence status, per instrument
47
+
48
+ Each governance instrument is rated on its own:
49
+
50
+ - **Exploratory:** built and used, with no pre-registered test of whether it helps.
51
+ - **Tested:** a pre-registered study was run, and its result is published, whatever it found.
52
+ - **Validated:** a pre-registered study found the effect it predicted.
53
+
54
+ The instruments are the definitiveness (correctness) gate, the citation gate, anchored correction,
55
+ and the two-part report with its trust rider.
56
+
57
+ ## Where Rapier is now (2026-11-14)
58
+
59
+ - **Version:** 0.6.1.
60
+ - **Stage:** Alpha. The full ceremony runs end to end, it is on PyPI, the tests pass, and
61
+ cross-vendor runs are live-proven. The contract is still changing.
62
+ - **Evidence:** every instrument is exploratory. Two pre-registered studies of the resolver are
63
+ published. In a blind rater test, raters didn't prefer the resolver's answers to a single AI pass.
64
+ Iterating review toward agreement gave no cleaner answers than one review. Neither study
65
+ validated the instruments above.
66
+ - **Open for Beta:** a stable contract, a security review, users beyond the maintainer, and the two
67
+ check-reporting criteria, which are open work today
68
+ (`docs/chatlogs/202611141130.inflight.accountable-agents-gaps.md`, items 1–3).
@@ -9,7 +9,7 @@ build-backend = "hatchling.build"
9
9
 
10
10
  [project]
11
11
  name = "rapier-runtime"
12
- version = "0.6.1"
12
+ version = "0.6.2"
13
13
  description = "A code-orchestrated engine that runs the SPARRING method: grounded, cross-vendor adversarial review for AI-in-the-loop decisions."
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.10"
@@ -299,7 +299,9 @@ def main(argv: list[str] | None = None) -> int:
299
299
  run.add_argument("--manifest", required=True, help="path to a pipeline manifest (YAML)")
300
300
  add_common(run)
301
301
 
302
- sub.add_parser("doctor", help="check which AI vendor keys are configured")
302
+ dp = sub.add_parser("doctor", help="check vendor keys, the operator minimum, and what the "
303
+ "ceremonies declare")
304
+ dp.add_argument("--manifest", help="also check a custom manifest before running it")
303
305
  ip = sub.add_parser("init", help="scaffold a .env.example for vendor keys")
304
306
  ip.add_argument("--dir", default=".", help="directory to write .env.example into (default: cwd)")
305
307
  sub.add_parser("mcp", help="run the MCP server (stdio) exposing frame/proposer/spar/sparring as tools")
@@ -333,10 +335,12 @@ def main(argv: list[str] | None = None) -> int:
333
335
  return {"failed": 1, "unchecked": 3}.get(report["status"], 0)
334
336
 
335
337
  if args.cmd == "doctor":
336
- from .onboarding import doctor_report
338
+ from .onboarding import doctor_report, method_report
337
339
 
338
340
  print(doctor_report())
339
- return 0
341
+ text, ok = method_report(getattr(args, "manifest", None))
342
+ print("\n" + text)
343
+ return 0 if ok else 1
340
344
  if args.cmd == "init":
341
345
  from .onboarding import init as _init
342
346
 
@@ -373,18 +377,36 @@ def main(argv: list[str] | None = None) -> int:
373
377
  if err: # no vendor keys — fail loudly, not silently
374
378
  print(err, file=sys.stderr)
375
379
  return 2
380
+ from .operator_policy import PolicyError, decide_independence, decide_verify
381
+
382
+ try:
383
+ decision = decide_verify(
384
+ getattr(args, "verify", "gate"),
385
+ "command-line flag --verify"
386
+ if any(a == "--verify" or a.startswith("--verify=")
387
+ for a in (argv if argv is not None else sys.argv[1:]))
388
+ else "default",
389
+ )
390
+ except PolicyError as exc:
391
+ print(f"refused: {exc}", file=sys.stderr)
392
+ return 2
376
393
  preset = load_preset(
377
- args.cmd, settle=getattr(args, "settle", 0), verify=getattr(args, "verify", "gate"),
394
+ args.cmd, settle=getattr(args, "settle", 0), verify=decision.mode,
378
395
  seed=getattr(args, "seed", None), depth=getattr(args, "depth", "standard"),
379
396
  reconcile=getattr(args, "reconcile", "gate"),
380
397
  )
398
+ try:
399
+ decide_independence(getattr(preset, "policy", None))
400
+ except PolicyError as exc:
401
+ print(f"refused: {exc}", file=sys.stderr)
402
+ return 2
381
403
  # Carry context the pipeline can't observe onto the envelope for the
382
404
  # ceremony-ledger row: the resolver knobs, and the front-door Frame
383
405
  # classification (from a separate call) via --frame.
384
406
  seed_meta: dict = {
385
407
  "settle": getattr(args, "settle", 0),
386
- "verify": getattr(args, "verify", "gate"),
387
408
  "reconcile": getattr(args, "reconcile", "gate"),
409
+ **decision.meta(),
388
410
  }
389
411
  frame_path = getattr(args, "frame", None)
390
412
  if frame_path:
@@ -400,7 +422,18 @@ def main(argv: list[str] | None = None) -> int:
400
422
  report_all=getattr(args, "report_all", False), seed_meta=seed_meta,
401
423
  no_save=getattr(args, "no_save", False))
402
424
  if args.cmd == "run":
403
- return _run(Manifest.load(args.manifest), request, args.ledger_dir,
425
+ from .operator_policy import PolicyError, decide_independence, decide_manifest
426
+
427
+ manifest = Manifest.load(args.manifest)
428
+ try:
429
+ decision = decide_manifest(manifest.stages)
430
+ if any(s.stage in ("cross_review", "spark", "pattern_lock", "cut")
431
+ for s in manifest.stages):
432
+ decide_independence(manifest.policy)
433
+ except PolicyError as exc:
434
+ print(f"refused: {exc}", file=sys.stderr)
435
+ return 2
436
+ return _run(manifest, request, args.ledger_dir, seed_meta=decision.meta(),
404
437
  no_save=getattr(args, "no_save", False))
405
438
  return 1 # pragma: no cover
406
439