rapier-runtime 0.6.2__tar.gz → 0.6.3__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.
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/CHANGELOG.md +13 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/PKG-INFO +1 -1
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/STATUS.md +18 -1
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/chatlogs/202611141130.inflight.accountable-agents-gaps.md +1 -1
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/pyproject.toml +1 -1
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/cli.py +5 -5
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/mcp/tools.py +3 -5
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/operator_policy.py +30 -7
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_operator_minimum.py +44 -2
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.claude/scripts/loom-deploy-nonprod.sh +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.claude/scripts/loom-deploy-prod.sh +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.claude/scripts/loom-env-check.sh +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.claude/scripts/loom-state-docs.sh +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.githooks/pre-commit +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.github/workflows/secret-scan.yml +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.gitignore +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.gitleaks.toml +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.loom/audits/smuggled-virtue.done +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.loom/launcher.conf +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/.loom/project.json +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/LICENSE +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/README.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/SECURITY.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/chatlogs/.gitkeep +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/chatlogs/202607301413.checkpoint.handoff-cut-0-5-0-to-pypi.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/chatlogs/20260802-explainability-reminder.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/chatlogs/202610162038.five-releases-defaults-report-mcp2.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/chatlogs/202610162128.reconcile-false-alarms-0.6.1.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/mcp-server-scope.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/threat-model.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/versioning.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/manifests/echo.yaml +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/manifests/sparring.frame.yaml +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/manifests/sparring.full.yaml +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/manifests/sparring.proposer.yaml +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/manifests/sparring.spar.yaml +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/site/README.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/site/coming-soon.html +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/site/index.html +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/site/rapier-logo.png +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/__init__.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/_json.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/convergence.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/envelope.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/ledger.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/manifest.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/mcp/__init__.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/mcp/server.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/models.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/onboarding.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/pipeline.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/presets.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/secrets.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stage.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/__init__.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/echo.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/frame.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/proposer/__init__.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/proposer/phase_stages.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/proposer/phases.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/proposer/traceability_gate.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/reconcile.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/__init__.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/_binding.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/_extract.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/anchored_fix.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/author.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/citation_gate.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/compose.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/cross_review.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/definitiveness_gate.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/__init__.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_bootstrap.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_llm_shim.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_vendor/PROVENANCE.md +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_vendor/cite_check.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_vendor/lib_llm.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_vendor/spar_cross_review.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_vendor/spar_definitiveness_gate.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_vendor/spar_verify_gate.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_vendor/verify_grounding.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/rider_record.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/service.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_cite_check_evidence_union.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_cli.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_convergence.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_correctness_outcome.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_declared_vs_ran.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_envelope.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_extract_json.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_final_state.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_frame_stage.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_full_ceremony.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_manifest.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_mcp.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_mcp_resources.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_method_version.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_models_vendors.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_onboarding.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_persistence_default.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_pipeline.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_policy.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_proposer_g2.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_proposer_stages.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_proposer_traceability_gate.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_reconcile.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_require_independent.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_resolver_stages.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_review_outcome.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_rider_record.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_secrets.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_single_vendor_report.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_vendor_policy_v4.py +0 -0
- {rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/tests/test_verify_service.py +0 -0
|
@@ -4,6 +4,19 @@ Notable changes to `rapier-runtime`. Earlier releases are summarized in
|
|
|
4
4
|
[`docs/STATUS.md`](docs/STATUS.md) and on the
|
|
5
5
|
[Releases](https://github.com/muddyone/rapier-runtime/releases) page.
|
|
6
6
|
|
|
7
|
+
## 0.6.3 — 2026-11-14
|
|
8
|
+
|
|
9
|
+
### Fixed: an unreadable policy file is no longer reported as the minimum refusing the run
|
|
10
|
+
|
|
11
|
+
When the operator policy file could not be read or checked (bad JSON, an
|
|
12
|
+
unknown level, a non-boolean `require_independent`, a folder Rapier can't
|
|
13
|
+
search), 0.6.2 stopped the run, correctly, but credited the stop to the
|
|
14
|
+
operator minimum (`refused_by: "operator_minimum"`, `final_state: "refuse"`).
|
|
15
|
+
The minimum was never read, so it decided nothing. The run still stops (MCP
|
|
16
|
+
`ok: false`, CLI exit 2), now with `final_state: "defer"`, no `refused_by`, and
|
|
17
|
+
`operator_policy: "did-not-run: <reason>"`. A readable policy that refuses a
|
|
18
|
+
request is unchanged: `refuse`, credited to the control that decided.
|
|
19
|
+
|
|
7
20
|
## 0.6.2 — 2026-11-14
|
|
8
21
|
|
|
9
22
|
### Added: every run names its final state
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rapier-runtime
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
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
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
# Rapier — dev status
|
|
2
2
|
|
|
3
|
-
_Last updated: 2026-
|
|
3
|
+
_Last updated: 2026-11-14 (tag v0.6.2, 382 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-11-14) — `rapier-runtime` 0.6.2 on PyPI. The six Accountable
|
|
8
|
+
Agents changes** (from Thomas Gessler's two concept papers,
|
|
9
|
+
doi:10.5281/zenodo.20687710 and doi:10.5281/zenodo.21850919). Git tag `v0.6.2`
|
|
10
|
+
(1020a765); https://pypi.org/project/rapier-runtime/0.6.2/. Built by
|
|
11
|
+
rapier-runtime-2 and passed item by item by the rapier-runtime-1 verifier in
|
|
12
|
+
the first multi-project team test (D-61); Bart chose 0.6.2 (D-69). In short:
|
|
13
|
+
an operator minimum for the citation check that a caller can't lower
|
|
14
|
+
(`/etc/rapier/policy.json`, reported as out of reach only when Rapier neither
|
|
15
|
+
owns nor can write it), and `require_independent` there; every check reports
|
|
16
|
+
`ran` / `nothing-to-check` / `did-not-run: <reason>`, judged on its last run;
|
|
17
|
+
a WHAT RAN section and pre-run manifest checks (`rapier doctor --manifest`);
|
|
18
|
+
a final state in the papers' six names, with a labelled NON-DECISION (D-67 a);
|
|
19
|
+
and a method hash (declared and as-run, including vendor bindings and the
|
|
20
|
+
package code). Full list in `CHANGELOG.md`. Verified from PyPI: a clean
|
|
21
|
+
install reports 0.6.2 and `rapier doctor` shows the operator minimum and the
|
|
22
|
+
per-ceremony checks. Follow-up, not blocking: key the "gate before the answer"
|
|
23
|
+
rule on the pipeline's stage order rather than writers' own traces.
|
|
7
24
|
- **RELEASED (2026-10-16) — `rapier-runtime` 0.6.1 on PyPI. The arithmetic
|
|
8
25
|
check ("WHETHER THE NUMBERS ADD UP") stops flagging correct formulas.** Git
|
|
9
26
|
tag `v0.6.1`; https://pypi.org/project/rapier-runtime/0.6.1/. Reported by
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
**Project**: rapier-runtime
|
|
5
5
|
**HEAD**: 9214c14b (chatlog: reconcile false alarms fixed, 0.6.1 released)
|
|
6
6
|
**Captured by**: Bart (from a vitae session, vit-1)
|
|
7
|
-
**Status**:
|
|
7
|
+
**Status**: closed (all six released in 0.6.2, 2026-11-14, tag v0.6.2 at 1020a765)
|
|
8
8
|
|
|
9
9
|
## Source
|
|
10
10
|
|
|
@@ -9,7 +9,7 @@ build-backend = "hatchling.build"
|
|
|
9
9
|
|
|
10
10
|
[project]
|
|
11
11
|
name = "rapier-runtime"
|
|
12
|
-
version = "0.6.
|
|
12
|
+
version = "0.6.3"
|
|
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"
|
|
@@ -377,7 +377,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
377
377
|
if err: # no vendor keys — fail loudly, not silently
|
|
378
378
|
print(err, file=sys.stderr)
|
|
379
379
|
return 2
|
|
380
|
-
from .operator_policy import PolicyError, decide_independence, decide_verify
|
|
380
|
+
from .operator_policy import PolicyError, decide_independence, decide_verify, stop_message
|
|
381
381
|
|
|
382
382
|
try:
|
|
383
383
|
decision = decide_verify(
|
|
@@ -388,7 +388,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
388
388
|
else "default",
|
|
389
389
|
)
|
|
390
390
|
except PolicyError as exc:
|
|
391
|
-
print(
|
|
391
|
+
print(stop_message(exc), file=sys.stderr)
|
|
392
392
|
return 2
|
|
393
393
|
preset = load_preset(
|
|
394
394
|
args.cmd, settle=getattr(args, "settle", 0), verify=decision.mode,
|
|
@@ -398,7 +398,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
398
398
|
try:
|
|
399
399
|
decide_independence(getattr(preset, "policy", None))
|
|
400
400
|
except PolicyError as exc:
|
|
401
|
-
print(
|
|
401
|
+
print(stop_message(exc), file=sys.stderr)
|
|
402
402
|
return 2
|
|
403
403
|
# Carry context the pipeline can't observe onto the envelope for the
|
|
404
404
|
# ceremony-ledger row: the resolver knobs, and the front-door Frame
|
|
@@ -422,7 +422,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
422
422
|
report_all=getattr(args, "report_all", False), seed_meta=seed_meta,
|
|
423
423
|
no_save=getattr(args, "no_save", False))
|
|
424
424
|
if args.cmd == "run":
|
|
425
|
-
from .operator_policy import PolicyError, decide_independence, decide_manifest
|
|
425
|
+
from .operator_policy import PolicyError, decide_independence, decide_manifest, stop_message
|
|
426
426
|
|
|
427
427
|
manifest = Manifest.load(args.manifest)
|
|
428
428
|
try:
|
|
@@ -431,7 +431,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
431
431
|
for s in manifest.stages):
|
|
432
432
|
decide_independence(manifest.policy)
|
|
433
433
|
except PolicyError as exc:
|
|
434
|
-
print(
|
|
434
|
+
print(stop_message(exc), file=sys.stderr)
|
|
435
435
|
return 2
|
|
436
436
|
return _run(manifest, request, args.ledger_dir, seed_meta=decision.meta(),
|
|
437
437
|
no_save=getattr(args, "no_save", False))
|
|
@@ -69,21 +69,19 @@ def _run(
|
|
|
69
69
|
return {"ok": False, "error": err}
|
|
70
70
|
# The operator minimum is applied here, before anything runs. Its sources are
|
|
71
71
|
# a fixed system file and the process environment; no tool argument reaches it.
|
|
72
|
-
from ..operator_policy import PolicyError, decide_independence, decide_verify
|
|
72
|
+
from ..operator_policy import PolicyError, decide_independence, decide_verify, stop_result
|
|
73
73
|
|
|
74
74
|
set_by = ("fixed by the proposer tool" if name == "proposer"
|
|
75
75
|
else "caller (MCP tool argument)")
|
|
76
76
|
try:
|
|
77
77
|
decision = decide_verify(verify, set_by)
|
|
78
78
|
except PolicyError as exc:
|
|
79
|
-
return
|
|
80
|
-
"final_state": "refuse"}
|
|
79
|
+
return stop_result(exc, "operator_minimum")
|
|
81
80
|
preset = load_preset(name, settle=settle, verify=decision.mode, seed=seed, depth=depth)
|
|
82
81
|
try:
|
|
83
82
|
decide_independence(getattr(preset, "policy", None))
|
|
84
83
|
except PolicyError as exc:
|
|
85
|
-
return
|
|
86
|
-
"final_state": "refuse"}
|
|
84
|
+
return stop_result(exc, "operator_require_independent")
|
|
87
85
|
# Carry context the pipeline can't observe onto the envelope for the ceremony-ledger
|
|
88
86
|
# row: the resolver knobs and the front-door Frame classification (a separate call).
|
|
89
87
|
seed_meta: dict[str, Any] = {"settle": settle, **decision.meta()}
|
|
@@ -59,8 +59,31 @@ POLICY_PATH = _default_policy_path()
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
class PolicyError(Exception):
|
|
62
|
-
"""The run is refused:
|
|
63
|
-
|
|
62
|
+
"""The run is refused: the operator's policy DECIDED against it (a caller
|
|
63
|
+
asked for less than the minimum, or no independent review is possible)."""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class PolicyUnreadable(PolicyError):
|
|
67
|
+
"""The run is stopped because the operator's policy could not be read or
|
|
68
|
+
checked (fail closed). The control did not run, so it decided nothing: the
|
|
69
|
+
stop holds, but it is never credited to the minimum, and the final state is
|
|
70
|
+
``defer``, not ``refuse``. Subclasses PolicyError so a caller that only
|
|
71
|
+
wants "stopped or not" still catches both."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def stop_result(exc: PolicyError, refused_by: str) -> dict:
|
|
75
|
+
"""The MCP result for a policy stop, attributing it only where a control decided."""
|
|
76
|
+
if isinstance(exc, PolicyUnreadable):
|
|
77
|
+
return {"ok": False, "error": str(exc), "final_state": "defer",
|
|
78
|
+
"operator_policy": f"did-not-run: {exc}"}
|
|
79
|
+
return {"ok": False, "error": str(exc), "refused_by": refused_by, "final_state": "refuse"}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def stop_message(exc: PolicyError) -> str:
|
|
83
|
+
"""The CLI's stderr line for a policy stop (exit code 2 either way)."""
|
|
84
|
+
if isinstance(exc, PolicyUnreadable):
|
|
85
|
+
return f"stopped (operator policy did not run, final state defer): {exc}"
|
|
86
|
+
return f"refused: {exc}"
|
|
64
87
|
|
|
65
88
|
|
|
66
89
|
@dataclass(frozen=True)
|
|
@@ -87,7 +110,7 @@ class VerifyDecision:
|
|
|
87
110
|
|
|
88
111
|
def _check_level(level, where: str) -> str:
|
|
89
112
|
if level not in VERIFY_RANK:
|
|
90
|
-
raise
|
|
113
|
+
raise PolicyUnreadable(f"operator policy {where}: unknown verify level {level!r}; "
|
|
91
114
|
f"known: {sorted(VERIFY_RANK)}")
|
|
92
115
|
return level
|
|
93
116
|
|
|
@@ -136,19 +159,19 @@ def _read_policy_file() -> tuple[dict, str, bool] | None:
|
|
|
136
159
|
except OSError as exc:
|
|
137
160
|
# Can't tell whether a policy exists (e.g. its directory can't be searched).
|
|
138
161
|
# An operator who locked the directory down meant a policy to apply: fail closed.
|
|
139
|
-
raise
|
|
162
|
+
raise PolicyUnreadable(f"operator policy file {path} could not be checked ({exc}); "
|
|
140
163
|
"refusing to run without the minimum it may set") from exc
|
|
141
164
|
try:
|
|
142
165
|
with open(path, encoding="utf-8") as fh:
|
|
143
166
|
data = json.load(fh)
|
|
144
167
|
except (OSError, ValueError) as exc:
|
|
145
|
-
raise
|
|
168
|
+
raise PolicyUnreadable(f"operator policy file {path} could not be read ({exc}); "
|
|
146
169
|
"refusing to run without the minimum it sets") from exc
|
|
147
170
|
if not isinstance(data, dict) or not any(k in data for k in _POLICY_KEYS):
|
|
148
|
-
raise
|
|
171
|
+
raise PolicyUnreadable(f"operator policy file {path} sets none of {list(_POLICY_KEYS)}; "
|
|
149
172
|
"refusing to run without the minimum it was meant to set")
|
|
150
173
|
if "require_independent" in data and not isinstance(data["require_independent"], bool):
|
|
151
|
-
raise
|
|
174
|
+
raise PolicyUnreadable(f"operator policy file {path}: require_independent must be "
|
|
152
175
|
"true or false; refusing to run")
|
|
153
176
|
return data, path, _out_of_my_reach(path)
|
|
154
177
|
|
|
@@ -212,7 +212,9 @@ def test_unsearchable_policy_dir_fails_closed(policy, mcp_stubbed):
|
|
|
212
212
|
if os.access(path.parent, os.X_OK):
|
|
213
213
|
pytest.skip("this filesystem ignores chmod")
|
|
214
214
|
out = tools.run_spar("ship it?", verify="off")
|
|
215
|
-
|
|
215
|
+
# R4-3: the minimum was never read, so the stop holds but is not credited to it
|
|
216
|
+
assert out["ok"] is False and out["final_state"] == "defer"
|
|
217
|
+
assert "refused_by" not in out and out["operator_policy"].startswith("did-not-run: ")
|
|
216
218
|
assert "could not be checked" in out["error"]
|
|
217
219
|
with pytest.raises(PolicyError):
|
|
218
220
|
decide_verify("gate", "caller") # can't know the floor: refuse every run
|
|
@@ -226,7 +228,9 @@ def test_bad_policy_file_fails_closed(policy, mcp_stubbed, body):
|
|
|
226
228
|
|
|
227
229
|
pathlib.Path(OP.POLICY_PATH).write_text(body)
|
|
228
230
|
out = tools.run_spar("ship it?", verify="gate")
|
|
229
|
-
|
|
231
|
+
# R4-3: the minimum was never read, so the stop holds but is not credited to it
|
|
232
|
+
assert out["ok"] is False and out["final_state"] == "defer"
|
|
233
|
+
assert "refused_by" not in out and out["operator_policy"].startswith("did-not-run: ")
|
|
230
234
|
|
|
231
235
|
|
|
232
236
|
# ── the report says who set the level, and whether the minimum is external ──
|
|
@@ -262,3 +266,41 @@ def test_report_says_an_in_reach_minimum_is_not_independent():
|
|
|
262
266
|
env = _env(verify="gate", verify_set_by="default", verify_minimum="gate",
|
|
263
267
|
verify_minimum_source="RAPIER_MIN_VERIFY", verify_minimum_external=False)
|
|
264
268
|
assert "not an independent control" in " ".join(_render_report(env).split())
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# ── R4-3: a stop is credited to a control only when that control decided ────
|
|
272
|
+
|
|
273
|
+
@pytest.mark.parametrize("body", ['{"min_verify":', '{"min_verify": "loud"}', "{}",
|
|
274
|
+
'{"require_independent": "yes"}'])
|
|
275
|
+
def test_unreadable_policy_stops_as_defer_not_refuse(policy, mcp_stubbed, body):
|
|
276
|
+
import pathlib
|
|
277
|
+
|
|
278
|
+
pathlib.Path(OP.POLICY_PATH).write_text(body)
|
|
279
|
+
out = tools.run_spar("decide X", verify="gate")
|
|
280
|
+
assert out["ok"] is False
|
|
281
|
+
assert out["final_state"] == "defer"
|
|
282
|
+
assert "refused_by" not in out
|
|
283
|
+
assert out["operator_policy"].startswith("did-not-run: ")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def test_control_readable_minimum_refuses_and_is_credited(policy, mcp_stubbed):
|
|
287
|
+
policy("gate")
|
|
288
|
+
out = tools.run_spar("decide X", verify="off")
|
|
289
|
+
assert out["final_state"] == "refuse" and out["refused_by"] == "operator_minimum"
|
|
290
|
+
assert "operator_policy" not in out
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def test_cli_unreadable_policy_says_defer(policy, monkeypatch, capsys):
|
|
294
|
+
import pathlib
|
|
295
|
+
|
|
296
|
+
from rapier import cli, onboarding
|
|
297
|
+
|
|
298
|
+
monkeypatch.setattr(onboarding, "preflight_error", lambda: None)
|
|
299
|
+
pathlib.Path(OP.POLICY_PATH).write_text('{"min_verify":')
|
|
300
|
+
assert cli.main(["spar", "--request", "x"]) == 2
|
|
301
|
+
err = capsys.readouterr().err
|
|
302
|
+
assert "operator policy did not run, final state defer" in err and "refused:" not in err
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def test_unreadable_policy_is_a_policy_error_subclass():
|
|
306
|
+
assert issubclass(OP.PolicyUnreadable, OP.PolicyError)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/docs/chatlogs/20260802-explainability-reminder.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/proposer/traceability_gate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/stages/resolver/definitiveness_gate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_vendor/spar_cross_review.py
RENAMED
|
File without changes
|
{rapier_runtime-0.6.2 → rapier_runtime-0.6.3}/src/rapier/verify/_vendor/spar_definitiveness_gate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|