rapier-runtime 0.5.0__tar.gz → 0.5.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 (100) hide show
  1. rapier_runtime-0.5.2/.githooks/pre-commit +82 -0
  2. rapier_runtime-0.5.2/.github/workflows/secret-scan.yml +45 -0
  3. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/.gitignore +3 -0
  4. rapier_runtime-0.5.2/.gitleaks.toml +23 -0
  5. rapier_runtime-0.5.2/.loom/audits/smuggled-virtue.done +3 -0
  6. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/.loom/project.json +7 -1
  7. rapier_runtime-0.5.2/CHANGELOG.md +46 -0
  8. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/PKG-INFO +1 -1
  9. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/docs/STATUS.md +38 -1
  10. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/pyproject.toml +1 -1
  11. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/cli.py +26 -0
  12. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/models.py +13 -6
  13. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/resolver/citation_gate.py +2 -1
  14. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/resolver/compose.py +56 -11
  15. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/resolver/cross_review.py +18 -1
  16. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/resolver/definitiveness_gate.py +2 -1
  17. rapier_runtime-0.5.2/src/rapier/verify/rider_record.py +190 -0
  18. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_resolver_stages.py +2 -1
  19. rapier_runtime-0.5.2/tests/test_rider_record.py +140 -0
  20. rapier_runtime-0.5.2/tests/test_single_vendor_report.py +81 -0
  21. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/.claude/scripts/loom-deploy-nonprod.sh +0 -0
  22. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/.claude/scripts/loom-deploy-prod.sh +0 -0
  23. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/.claude/scripts/loom-env-check.sh +0 -0
  24. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/.claude/scripts/loom-state-docs.sh +0 -0
  25. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/LICENSE +0 -0
  26. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/README.md +0 -0
  27. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/SECURITY.md +0 -0
  28. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/docs/chatlogs/.gitkeep +0 -0
  29. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/docs/chatlogs/202607301413.checkpoint.handoff-cut-0-5-0-to-pypi.md +0 -0
  30. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/docs/chatlogs/20260802-explainability-reminder.md +0 -0
  31. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/docs/mcp-server-scope.md +0 -0
  32. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/docs/threat-model.md +0 -0
  33. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/manifests/echo.yaml +0 -0
  34. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/manifests/sparring.frame.yaml +0 -0
  35. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/manifests/sparring.full.yaml +0 -0
  36. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/manifests/sparring.proposer.yaml +0 -0
  37. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/manifests/sparring.spar.yaml +0 -0
  38. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/site/README.md +0 -0
  39. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/site/coming-soon.html +0 -0
  40. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/site/index.html +0 -0
  41. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/site/rapier-logo.png +0 -0
  42. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/__init__.py +0 -0
  43. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/_json.py +0 -0
  44. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/convergence.py +0 -0
  45. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/envelope.py +0 -0
  46. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/ledger.py +0 -0
  47. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/manifest.py +0 -0
  48. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/mcp/__init__.py +0 -0
  49. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/mcp/server.py +0 -0
  50. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/mcp/tools.py +0 -0
  51. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/onboarding.py +0 -0
  52. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/pipeline.py +0 -0
  53. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/presets.py +0 -0
  54. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/secrets.py +0 -0
  55. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stage.py +0 -0
  56. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/__init__.py +0 -0
  57. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/echo.py +0 -0
  58. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/frame.py +0 -0
  59. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/proposer/__init__.py +0 -0
  60. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/proposer/phase_stages.py +0 -0
  61. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/proposer/phases.py +0 -0
  62. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/proposer/traceability_gate.py +0 -0
  63. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/reconcile.py +0 -0
  64. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/resolver/__init__.py +0 -0
  65. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/resolver/_binding.py +0 -0
  66. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/resolver/_extract.py +0 -0
  67. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/resolver/anchored_fix.py +0 -0
  68. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/stages/resolver/author.py +0 -0
  69. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/__init__.py +0 -0
  70. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/_bootstrap.py +0 -0
  71. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/_llm_shim.py +0 -0
  72. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/_vendor/PROVENANCE.md +0 -0
  73. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/_vendor/cite_check.py +0 -0
  74. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/_vendor/lib_llm.py +0 -0
  75. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/_vendor/spar_cross_review.py +0 -0
  76. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/_vendor/spar_definitiveness_gate.py +0 -0
  77. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/_vendor/spar_verify_gate.py +0 -0
  78. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/_vendor/verify_grounding.py +0 -0
  79. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/src/rapier/verify/service.py +0 -0
  80. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_cite_check_evidence_union.py +0 -0
  81. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_cli.py +0 -0
  82. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_convergence.py +0 -0
  83. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_envelope.py +0 -0
  84. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_extract_json.py +0 -0
  85. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_frame_stage.py +0 -0
  86. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_full_ceremony.py +0 -0
  87. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_manifest.py +0 -0
  88. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_mcp.py +0 -0
  89. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_models_vendors.py +0 -0
  90. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_onboarding.py +0 -0
  91. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_persistence_default.py +0 -0
  92. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_pipeline.py +0 -0
  93. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_policy.py +0 -0
  94. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_proposer_g2.py +0 -0
  95. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_proposer_stages.py +0 -0
  96. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_proposer_traceability_gate.py +0 -0
  97. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_reconcile.py +0 -0
  98. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_secrets.py +0 -0
  99. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_vendor_policy_v4.py +0 -0
  100. {rapier_runtime-0.5.0 → rapier_runtime-0.5.2}/tests/test_verify_service.py +0 -0
@@ -0,0 +1,82 @@
1
+ #!/bin/bash
2
+ # Loom pre-commit secret-scan gate (project template).
3
+ #
4
+ # Lives in .githooks/ so it is git-tracked and shared across all clones.
5
+ # Activated per-clone with: git config core.hooksPath .githooks
6
+ # (projlaunch wires new worktrees automatically; /sync-now sets it on existing
7
+ # clones -- see loom-secret-scan-check.)
8
+ #
9
+ # Why this exists: the 2026-06-30 .env-leak incident (CR-0004). A `.env` backup
10
+ # with live keys was committed via `git add -A` and auto-pushed to origin within
11
+ # seconds -- no human gate. .gitignore is one typo from failing, and it did.
12
+ # This is the local backstop: it blocks the commit BEFORE any auto-push fires.
13
+ # The server-side scan (.github/workflows/secret-scan.yml) is the guarantee;
14
+ # this hook is defense-in-depth that catches a secret before it leaves the box.
15
+ #
16
+ # Three gates:
17
+ # 1. PATH gate -- refuses to stage any .env secret file (templates/examples
18
+ # excepted). This alone would have stopped the CR-0004 incident.
19
+ # 2. CONTENT gate -- scans added lines for high-signal credential signatures,
20
+ # including in chatlogs and ordinary markdown (the inline-paste vector).
21
+ # Skips only dirs that legitimately carry the pattern literals / key-format
22
+ # teaching prose: .githooks/, docs/security/, docs/onboarding/.
23
+ # 3. gitleaks -- run if installed, as broader defense in depth (uses this
24
+ # repo's .gitleaks.toml automatically).
25
+ #
26
+ # Bypass (intentional, rare -- e.g. a verified false positive):
27
+ # git commit --no-verify
28
+ # Prefer fixing the staged content over bypassing.
29
+
30
+ set -uo pipefail
31
+
32
+ staged=$(git diff --cached --name-only --diff-filter=AM)
33
+ [ -z "$staged" ] && exit 0
34
+
35
+ fail=0
36
+
37
+ # --- Gate 1: env-secret file PATHS (the CR-0004 incident class) -------------
38
+ while IFS= read -r f; do
39
+ [ -z "$f" ] && continue
40
+ base=$(basename "$f")
41
+ case "$base" in
42
+ *.example|*.template|*.sample) continue ;;
43
+ esac
44
+ if printf '%s\n' "$base" | grep -qE '^\.env($|[.-])'; then
45
+ echo "[pre-commit] BLOCKED: staged env-secret file: $f" >&2
46
+ fail=1
47
+ fi
48
+ done <<< "$staged"
49
+
50
+ # --- Gate 2: credential SIGNATURES in added lines --------------------------
51
+ # High-signal, prefix-anchored signatures (near-zero false positives). Generic
52
+ # high-entropy / base64 detection is deliberately left to gitleaks (Gate 3),
53
+ # where the false positives live.
54
+ patterns='sk-ant-[A-Za-z0-9_-]{16}|sk-proj-[A-Za-z0-9_-]{16}|sk-[A-Za-z0-9]{20}|xai-[A-Za-z0-9]{16}|SG\.[A-Za-z0-9_.-]{16}|AC[a-z0-9]{30}|(sk|rk|pk)_(live|test)_[A-Za-z0-9]{16}|whsec_[A-Za-z0-9]{16}|AKIA[0-9A-Z]{16}|gh[opsu]_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{20}|xox[baprs]-[A-Za-z0-9-]{10}|xapp-[A-Za-z0-9-]{10}|AIza[0-9A-Za-z_-]{35}|GOCSPX-[A-Za-z0-9_-]{20}|-----BEGIN [A-Z ]*PRIVATE KEY-----'
55
+ while IFS= read -r f; do
56
+ [ -z "$f" ] && continue
57
+ case "$f" in
58
+ .githooks/*|docs/security/*|docs/onboarding/*) continue ;;
59
+ esac
60
+ added=$(git diff --cached -U0 -- "$f" | grep '^+' | grep -v '^+++' || true)
61
+ if printf '%s\n' "$added" | grep -qE "$patterns"; then
62
+ echo "[pre-commit] BLOCKED: credential-like signature in staged change: $f" >&2
63
+ fail=1
64
+ fi
65
+ done <<< "$staged"
66
+
67
+ # --- Gate 3: gitleaks if available (defense in depth) ----------------------
68
+ if command -v gitleaks >/dev/null 2>&1; then
69
+ if ! gitleaks protect --staged --no-banner >/dev/null 2>&1; then
70
+ echo "[pre-commit] BLOCKED: gitleaks flagged staged content." >&2
71
+ fail=1
72
+ fi
73
+ fi
74
+
75
+ if [ "$fail" -ne 0 ]; then
76
+ echo "[pre-commit] -------------------------------------------------------------" >&2
77
+ echo "[pre-commit] Commit blocked by the secret-scan gate. Remove the secret from" >&2
78
+ echo "[pre-commit] the staged set -- env backups belong OUTSIDE the repo tree." >&2
79
+ echo "[pre-commit] If this is a verified false positive: git commit --no-verify" >&2
80
+ exit 1
81
+ fi
82
+ exit 0
@@ -0,0 +1,45 @@
1
+ name: Secret scan
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ gitleaks:
13
+ name: gitleaks
14
+ runs-on: ubuntu-24.04
15
+ steps:
16
+ - name: Checkout (full history for delta scan)
17
+ uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - name: Install gitleaks
22
+ run: |
23
+ set -euo pipefail
24
+ GITLEAKS_VERSION="8.21.2"
25
+ curl -sSfL -o /tmp/gitleaks.tar.gz \
26
+ "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
27
+ tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
28
+ sudo mv /tmp/gitleaks /usr/local/bin/gitleaks
29
+ gitleaks version
30
+
31
+ - name: Scan changes only (push diff or PR diff)
32
+ env:
33
+ BASE_SHA: ${{ github.event.before || github.event.pull_request.base.sha || '' }}
34
+ HEAD_SHA: ${{ github.sha }}
35
+ run: |
36
+ set -euo pipefail
37
+ if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
38
+ echo "[gitleaks] no base SHA; scanning HEAD commit"
39
+ gitleaks detect --redact --verbose --no-banner --exit-code 1 \
40
+ --log-opts="-1 HEAD"
41
+ else
42
+ echo "[gitleaks] scanning diff $BASE_SHA..$HEAD_SHA"
43
+ gitleaks detect --redact --verbose --no-banner --exit-code 1 \
44
+ --log-opts="$BASE_SHA..$HEAD_SHA"
45
+ fi
@@ -32,3 +32,6 @@ runs/
32
32
 
33
33
  # Nested git worktrees — registered checkouts, never committed into the repo
34
34
  .claude/worktrees/
35
+
36
+ # Loom per-machine stamp (when the explainability reminder last ran)
37
+ .loom/last-explainability-reminder
@@ -0,0 +1,23 @@
1
+ # gitleaks configuration for this repo.
2
+ #
3
+ # What this is: a small settings file (TOML format -- plain "key = value" lines
4
+ # grouped under [section] headers) that tells the secret scanner two things:
5
+ # which detection rules to use, and which files to deliberately skip so we don't
6
+ # raise false alarms. Read by BOTH the server-side scan
7
+ # (.github/workflows/secret-scan.yml) and the optional local pre-commit hook.
8
+
9
+ [extend]
10
+ # Use gitleaks' built-in rule set (Anthropic, OpenAI, AWS, Stripe, GitHub, ...).
11
+ useDefault = true
12
+
13
+ [allowlist]
14
+ description = "Known false-positive surfaces (safe to skip)"
15
+ paths = [
16
+ # Dependency lock files: integrity hashes, never real credentials.
17
+ '''(^|/)(package-lock\.json|composer\.lock|yarn\.lock|pnpm-lock\.yaml)$''',
18
+ # Redaction tests feed fake key-shaped strings (sk-..., sk-ant-...) on purpose.
19
+ '''^tests/test_secrets\.py$''',
20
+ ]
21
+ # Add project-specific entries above as real false positives appear, e.g.:
22
+ # '''database/migrations/.*\.sql$''' # bulk data with high-entropy strings
23
+ # '''(^|/)\.env\.example$''' # ONLY if its placeholders trip the scanner
@@ -0,0 +1,3 @@
1
+ audited 2026-10-15T16:40:23Z by DEMETER
2
+ fixed: 0 load-bearing (5 grep hits, all technical scope/behaviour qualifiers: compose.py:12,438; verify_grounding.py:516,608; rider_record.py:30)
3
+ left: 0 historical
@@ -4,5 +4,11 @@
4
4
  "tier": null,
5
5
  "source": "declared",
6
6
  "loom_commit": "c956f9c151c7303a190ec8d8c43b5ad6ca8a1e58",
7
- "stamped": "2026-07-30"
7
+ "stamped": "2026-07-30",
8
+ "pm": {
9
+ "exempt": "Public repo; Bart ruled exempt 2026-10-02"
10
+ },
11
+ "ownership": {
12
+ "owner": "bart"
13
+ }
8
14
  }
@@ -0,0 +1,46 @@
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.5.2 — 2026-10-16
8
+
9
+ ### Fixed: the report no longer calls a same-vendor review independent
10
+
11
+ Found on a live run with a single vendor's key, where the reviewer and the
12
+ author were the same model:
13
+
14
+ - **The report contradicted itself.** "Already fixed under challenge" said
15
+ "an independent, different-vendor reviewer raised each of these," and the
16
+ registry section pointed to "the cross-vendor challenge," while "How much to
17
+ trust this" correctly said the review was not independent. Every section
18
+ now says what actually happened; a cross-vendor run reads as before.
19
+ - **The progress warning blamed the wrong key.** It said "OPENAI_API_KEY
20
+ absent; falling back to a Claude reviewer" when the OpenAI key was set and
21
+ no Claude model was used. It now names the vendor that ran the review.
22
+ - **Arithmetic printed as raw LaTeX.** Math the model wrote as `\( H \times
23
+ 0.60 \)` now reads `H × 0.60` in the report. Only text inside `\( \)` or
24
+ `\[ \]` is rewritten.
25
+
26
+ ## 0.5.1 — 2026-10-16
27
+
28
+ ### Changed: default models for five OpenAI-compatible vendors
29
+
30
+ Five built-in defaults named models that a vendor has retired or is retiring. If
31
+ you relied on a vendor's default (no `model:` in your manifest and no
32
+ `RAPIER_MODEL_<VENDOR>` set), **your runs now use a different model.** To keep
33
+ the old one while it still works, name it in your manifest or set
34
+ `RAPIER_MODEL_<VENDOR>`.
35
+
36
+ | Vendor | Was | Now | Why |
37
+ |---|---|---|---|
38
+ | `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. |
39
+ | `deepseek` | `deepseek-chat` | `deepseek-flash` | `deepseek-chat` was retired 2026-07-24, so the old default already fails. |
40
+ | `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. |
41
+ | `together` | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | `openai/gpt-oss-120b` | Leaves Together's serverless tier 2026-10-22. |
42
+ | `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. |
43
+
44
+ `xai` (`grok-4.3`), `mistral` (`mistral-large-latest`) and `ollama` (`llama3.1`)
45
+ are unchanged. Each was checked against its vendor's deprecation page on
46
+ 2026-10-15.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rapier-runtime
3
- Version: 0.5.0
3
+ Version: 0.5.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
@@ -1,9 +1,46 @@
1
1
  # Rapier — dev status
2
2
 
3
- _Last updated: 2026-07-18 (tag v0.4.0, 186 tests)._ A running "where we are /
3
+ _Last updated: 2026-10-16 (tag v0.5.1, 230 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.5.1 on PyPI. Default models for
8
+ five vendors replaced.** Git tag `v0.5.1`;
9
+ https://pypi.org/project/rapier-runtime/0.5.1/. OpenRouter's default
10
+ `openai/gpt-4o` (stops working 2027-01-14, Loom Plan 26) is now
11
+ `openai/gpt-6-sol` (Bart's ruling). Checking every OpenAI-compatible default
12
+ against its vendor's deprecation page found two already failing (DeepSeek
13
+ `deepseek-chat`, Groq `llama-3.3-70b-versatile`), one leaving 2026-10-22
14
+ (Together's Llama 3.3 Turbo) and one closed to new users (`gemini-2.5-flash`).
15
+ The full table is in `CHANGELOG.md`, which starts with this release. None of
16
+ the new defaults has been tried on a live run yet; that needs vendor keys.
17
+ - **RELEASED (2026-08-28) — `rapier-runtime` 0.5.0 on PyPI. The verifier stops
18
+ calling real citations fabricated.** Git tag `v0.5.0`;
19
+ https://pypi.org/project/rapier-runtime/0.5.0/. Eleven commits, and the
20
+ headline is a correctness fix in the grounding verifier:
21
+ `backend_literature` ran only a Crossref *bibliographic search* and gated every
22
+ candidate on a cited-surname match — but a bare identifier carries no surname,
23
+ so it could never match and fell through to `GROUNDED_REFUTED / "fabricated"`.
24
+ A bare DOI and a bare arXiv id, the two most authoritative citation forms
25
+ there are, were the two that always failed. `cite_check` had exposed
26
+ `lookup_crossref_doi` and `lookup_arxiv` the whole time; the backend called
27
+ neither. A verifier that flags real citations as fake is worse than none — it
28
+ trains the reader to distrust the gate.
29
+ Also in: identifier-strength resolution (DOI → arXiv → search, decisive both
30
+ ways); new `law` (CourtListener) and `patent` (Google Patents) backends;
31
+ evidence unioned across every identifier a reference supplies; a Proposer-side
32
+ `traceability_gate` (RM §10.1/§13.1) so the framing step's own citations are
33
+ grounded — verification was Resolver-only, so a Proposer could cite a
34
+ nonexistent source and the run reported clean; the reconcile gate now runs as
35
+ a standing check in `/spar`; and `RAPIER_MODEL_<VENDOR>` overrides the default
36
+ model per vendor. 223 tests green.
37
+ **Build note:** `pyproject.toml` pins `hatchling<1.30`. 1.30 emits
38
+ Metadata-Version 2.5 (PEP 794), which the current publishing toolchain rejects
39
+ (`twine check`: "'2.5' is not a valid metadata version") — that blocked this
40
+ release at the gate until pinned. Lift only when `twine check` passes a 2.5 wheel.
41
+ **Site:** no deploy needed. The full landing has been live since 2026-07-16 and
42
+ carries no version string; verified byte-identical to `site/index.html` on
43
+ 2026-08-28.
7
44
  - **RELEASED (2026-07-18) — `rapier-runtime` 0.4.0 on PyPI. Governance: runs
8
45
  persist by default + THE RECORD provenance.** Git tag `v0.4.0`;
9
46
  https://pypi.org/project/rapier-runtime/0.4.0/.
@@ -9,7 +9,7 @@ build-backend = "hatchling.build"
9
9
 
10
10
  [project]
11
11
  name = "rapier-runtime"
12
- version = "0.5.0"
12
+ version = "0.5.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"
@@ -304,8 +304,34 @@ def main(argv: list[str] | None = None) -> int:
304
304
  ip.add_argument("--dir", default=".", help="directory to write .env.example into (default: cwd)")
305
305
  sub.add_parser("mcp", help="run the MCP server (stdio) exposing frame/proposer/spar/sparring as tools")
306
306
 
307
+ vr = sub.add_parser(
308
+ "verify-rider",
309
+ help="check a run's trust rider against its own transcript (model-free)",
310
+ )
311
+ vr.add_argument("run", help="path to a persisted run directory, or a run id under the runs root")
312
+ vr.add_argument("--json", action="store_true", help="emit the report as JSON")
313
+
307
314
  args = parser.parse_args(argv)
308
315
 
316
+ if args.cmd == "verify-rider":
317
+ import json as _json
318
+ import os as _os
319
+
320
+ from .ledger import default_runs_root
321
+ from .verify.rider_record import check_run, render
322
+
323
+ run_dir = args.run
324
+ if not _os.path.isdir(run_dir):
325
+ run_dir = _os.path.join(default_runs_root(), args.run)
326
+ if not _os.path.isdir(run_dir):
327
+ print(f"no such run: {args.run}", file=sys.stderr)
328
+ return 2
329
+ report = check_run(run_dir)
330
+ print(_json.dumps(report, indent=2) if args.json else render(report))
331
+ # failed -> 1 so CI can gate on it; unchecked -> 3, distinct from both
332
+ # a pass and a failure, because an unverifiable claim is neither.
333
+ return {"failed": 1, "unchecked": 3}.get(report["status"], 0)
334
+
309
335
  if args.cmd == "doctor":
310
336
  from .onboarding import doctor_report
311
337
 
@@ -178,14 +178,21 @@ class OpenAICompatibleModelClient(ModelClient):
178
178
  # vendor -> (base_url, key_env, default_model). Defaults are overridable in the
179
179
  # manifest; only xai's default is live-validated (2026-07-06 — Grok's API is
180
180
  # OpenAI-compatible). Others are best-effort until validated with a real key.
181
+ # Checked against each vendor's deprecation page 2026-10-15 (see CHANGELOG.md).
181
182
  _OPENAI_COMPATIBLE: dict[str, tuple[str, str, str]] = {
182
- "gemini": ("https://generativelanguage.googleapis.com/v1beta/openai", "GEMINI_API_KEY", "gemini-2.5-flash"), # frontier; also has a native API
183
+ # gemini-2.5-flash is closed to new API users; Google points new projects here, same price.
184
+ "gemini": ("https://generativelanguage.googleapis.com/v1beta/openai", "GEMINI_API_KEY", "gemini-3.5-flash-lite"), # also has a native API
183
185
  "xai": ("https://api.x.ai/v1", "XAI_API_KEY", "grok-4.3"),
184
- "deepseek": ("https://api.deepseek.com/v1", "DEEPSEEK_API_KEY", "deepseek-chat"),
185
- "mistral": ("https://api.mistral.ai/v1", "MISTRAL_API_KEY", "mistral-large-latest"),
186
- "groq": ("https://api.groq.com/openai/v1", "GROQ_API_KEY", "llama-3.3-70b-versatile"),
187
- "together": ("https://api.together.xyz/v1", "TOGETHER_API_KEY", "meta-llama/Llama-3.3-70B-Instruct-Turbo"),
188
- "openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY", "openai/gpt-4o"),
186
+ # deepseek-chat was retired 2026-07-24; deepseek-flash is DeepSeek's current name.
187
+ "deepseek": ("https://api.deepseek.com/v1", "DEEPSEEK_API_KEY", "deepseek-flash"),
188
+ "mistral": ("https://api.mistral.ai/v1", "MISTRAL_API_KEY", "mistral-large-latest"), # alias, follows Mistral's current Large
189
+ # llama-3.3-70b-versatile was shut down 2026-08-16; this is Groq's named replacement.
190
+ "groq": ("https://api.groq.com/openai/v1", "GROQ_API_KEY", "openai/gpt-oss-120b"),
191
+ # Llama-3.3-70B-Instruct-Turbo leaves serverless 2026-10-22; the cheapest current open model there.
192
+ "together": ("https://api.together.xyz/v1", "TOGETHER_API_KEY", "openai/gpt-oss-120b"),
193
+ # gpt-4o stops working 2027-01-14. gpt-6-sol is OpenAI's current flagship, the same class
194
+ # gpt-4o was and slightly cheaper, so no caller gets a weaker critic or a bigger bill.
195
+ "openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY", "openai/gpt-6-sol"),
189
196
  "ollama": ("http://localhost:11434/v1", "OLLAMA_API_KEY", "llama3.1"), # local, zero-egress
190
197
  }
191
198
 
@@ -79,7 +79,8 @@ def _reconcile_definitiveness_with_grounding(env: Envelope, verdicts: list) -> N
79
79
  env.verdict = verdict
80
80
  rider = dict(env.trust_rider or {})
81
81
  rider["assumptions_to_verify"] = defin["rider_lines"]
82
- rider["overall_confidence"] = verdict
82
+ rider["specifics_traceability"] = verdict
83
+ rider["overall_confidence"] = verdict # deprecated alias; remove after 0.6.x
83
84
  rider["verified_externally"] = grounded_specs
84
85
  env.trust_rider = rider
85
86
  env.add_trace(
@@ -5,6 +5,14 @@ Assembles the final report (recommendation + trust rider), where the rider's
5
5
  Proposer's forwarded standing objections (so the deliberation's dissent is
6
6
  visible to the user). Writes the /spar-parity named files and the derived
7
7
  ceremony-ledger row.
8
+
9
+
10
+ Trust-rider field note: ``specifics_traceability`` carries the definitiveness
11
+ gate's verdict -- whether every hard specific in the answer traces to a given
12
+ fact or is flagged as an estimate. It is deliberately NOT named
13
+ "overall confidence": it is one narrow evidence check, and says nothing about
14
+ coverage, soundness, or the decision. ``overall_confidence`` remains as a
15
+ deprecated alias with the same value.
8
16
  """
9
17
  from __future__ import annotations
10
18
 
@@ -40,6 +48,13 @@ def _verdict_sentence(verdict, gate) -> str:
40
48
  "to check, or the checker was unavailable), so treat the stated specifics as unverified.")
41
49
 
42
50
 
51
+ def _challenge_phrase(review) -> str:
52
+ """Who raised the challenge, worded so a same-vendor review is never called independent."""
53
+ if review.get("cross_vendor"):
54
+ return "an independent, different-vendor reviewer"
55
+ return "a reviewer on the same vendor as the author (not an independent check)"
56
+
57
+
43
58
  def _reviewer_sentence(review) -> str:
44
59
  """Whether the challenge was genuinely independent — in plain words."""
45
60
  if review.get("cross_vendor"):
@@ -124,11 +139,38 @@ def _bullets(items) -> str:
124
139
  """One finding per bullet, hanging-indented so the marker column stays a clean
125
140
  vertical scan-line."""
126
141
  return "\n".join(
127
- textwrap.fill(str(it).strip(), width=_W, initial_indent="• ", subsequent_indent=" ")
142
+ textwrap.fill(_plain_math(str(it)).strip(), width=_W, initial_indent="• ",
143
+ subsequent_indent=" ")
128
144
  for it in items
129
145
  )
130
146
 
131
147
 
148
+ # Models often write arithmetic as LaTeX (\( H \times 0.60 \)), which a terminal
149
+ # prints literally. Only text inside \( \) or \[ \] is rewritten, so a stray
150
+ # backslash elsewhere in the prose is left alone.
151
+ _MATH_SPAN_RE = re.compile(r"\\\((.+?)\\\)|\\\[(.+?)\\\]", re.S)
152
+ _MATH_SYMBOLS = {
153
+ "times": "×", "div": "÷", "cdot": "·", "approx": "≈", "pm": "±",
154
+ "leq": "≤", "le": "≤", "geq": "≥", "ge": "≥", "neq": "≠", "ne": "≠",
155
+ "to": "→", "rightarrow": "→", "infty": "∞", "%": "%", "$": "$",
156
+ ",": " ", ";": " ", "!": "", " ": " ", "quad": " ", "qquad": " ",
157
+ }
158
+
159
+
160
+ def _plain_math(text: str) -> str:
161
+ def span(m: re.Match) -> str:
162
+ t = m.group(1) if m.group(1) is not None else m.group(2)
163
+ for _ in range(3): # nested \frac / \text unwrap from the inside out
164
+ t = re.sub(r"\\[dt]?frac\{([^{}]*)\}\{([^{}]*)\}", r"(\1)/(\2)", t)
165
+ t = re.sub(r"\\(?:text|mathrm|mathbf|operatorname)\{([^{}]*)\}", r"\1", t)
166
+ t = re.sub(r"\\left|\\right", "", t)
167
+ t = re.sub(r"\\([A-Za-z]+|[%$,;! ])",
168
+ lambda k: _MATH_SYMBOLS.get(k.group(1), k.group(0)), t)
169
+ t = t.replace("{", "").replace("}", "")
170
+ return re.sub(r"\s+", " ", t).strip()
171
+ return _MATH_SPAN_RE.sub(span, text or "")
172
+
173
+
132
174
  def _demote_md(text: str) -> str:
133
175
  """Neutralize an embedded recommendation's own markdown so it cannot compete
134
176
  with the report's structure: ATX headings (`## X`) become bare Title-case
@@ -142,7 +184,7 @@ def _demote_md(text: str) -> str:
142
184
  s = re.sub(r"\*\*(.+?)\*\*", r"\1", s) # bold
143
185
  s = re.sub(r"(?<![\w*])\*(?!\s)([^*\n]+?)(?<!\s)\*(?![\w*])", r"\1", s) # italic
144
186
  s = re.sub(r"(?<!\w)`([^`]+)`(?!\w)", r"\1", s) # inline code
145
- return s
187
+ return _plain_math(s)
146
188
 
147
189
 
148
190
  # The stored verdicts carry the citation gate's shape (``status``:
@@ -175,7 +217,7 @@ def _grounding_body(env: Envelope) -> str:
175
217
  return ("The recommendation cited nothing externally checkable — no CVE, CWE, "
176
218
  "RFC, DOI, or URL — so there was nothing to confirm against a public "
177
219
  "registry. This is normal for judgment and strategy questions, where the "
178
- "load-bearing work is the cross-vendor challenge above, not citation.")
220
+ "load-bearing work is the challenge below, not citation.")
179
221
  lines = []
180
222
  nver = 0
181
223
  for v in verdicts:
@@ -291,7 +333,7 @@ def _render_report(env: Envelope) -> str:
291
333
  L += ["", "", _HEAVY, " TRUST RIDER", _HEAVY, "",
292
334
  "How far to trust the recommendation above: what is still yours to",
293
335
  "confirm, what was verified for you, and how the answer held up under",
294
- "an independent challenge."]
336
+ "challenge."]
295
337
 
296
338
  assumptions = rider.get("assumptions_to_verify") or []
297
339
  L += _sec("STILL YOURS TO CHECK",
@@ -314,13 +356,15 @@ def _render_report(env: Envelope) -> str:
314
356
 
315
357
  contested = _texts(review.get("objections"))
316
358
  L += _sec("ALREADY FIXED UNDER CHALLENGE",
317
- "Not action items — already folded into the recommendation above. An "
318
- "independent, different-vendor reviewer raised each of these, and the "
319
- "answer was revised to address it. Shown so you can see what the "
320
- "challenge caught and how the recommendation got stronger.",
359
+ "Not action items — already folded into the recommendation above. "
360
+ + _challenge_phrase(review)[0].upper() + _challenge_phrase(review)[1:]
361
+ + " raised each of these, and the answer was revised to address it. "
362
+ "Shown so you can see what the challenge caught and how the "
363
+ "recommendation got stronger.",
321
364
  _bullets(contested) if contested
322
- else "The independent review found nothing material to change — the "
323
- "recommendation held up as written.")
365
+ else "The review found nothing material to change — the recommendation "
366
+ "held up as written. See HOW MUCH TO TRUST THIS for how independent "
367
+ "that review was.")
324
368
 
325
369
  dissent = rider.get("proposer_dissent_forwarded")
326
370
  if dissent:
@@ -497,7 +541,8 @@ class ComposeStage(TransformStage):
497
541
  rider.setdefault("contested_and_resolved", _texts(review.get("objections")))
498
542
  if standing:
499
543
  rider["proposer_dissent_forwarded"] = _texts(standing)
500
- rider["overall_confidence"] = env.verdict
544
+ rider["specifics_traceability"] = env.verdict
545
+ rider["overall_confidence"] = env.verdict # deprecated alias; remove after 0.6.x
501
546
  env.trust_rider = rider
502
547
 
503
548
  env.meta["report"] = {
@@ -7,6 +7,10 @@ the anchored-correction stage to act on.
7
7
  """
8
8
  from __future__ import annotations
9
9
 
10
+ import contextlib
11
+ import io
12
+ import sys
13
+
10
14
  from ...envelope import Envelope
11
15
  from ...stage import StageContext, TransformStage, register_stage
12
16
 
@@ -24,10 +28,23 @@ class CrossReviewStage(TransformStage):
24
28
  primary_v, secondary_v = bind_pair(
25
29
  env, secondary_pref=ctx.config.get("reviewer_vendor"), policy=ctx.policy
26
30
  )
31
+ # The vendored reviewer names its two slots after Anthropic and OpenAI, so
32
+ # its fallback warning ("OPENAI_API_KEY absent; falling back to a Claude
33
+ # reviewer") is wrong once the slots are bound to other vendors. Capture
34
+ # it and say what actually happened instead; other stderr passes through.
35
+ captured = io.StringIO()
27
36
  try:
28
- result = B.review(env.request, env.recommendation, None, None)
37
+ with contextlib.redirect_stderr(captured):
38
+ result = B.review(env.request, env.recommendation, None, None)
29
39
  finally:
30
40
  B.reset_slots()
41
+ for line in captured.getvalue().splitlines():
42
+ if line.startswith("WARN spar-cross-review:") and not result.get("cross_vendor"):
43
+ if result.get("reviewer_vendor") is None:
44
+ continue # no keys at all; the pipeline's own preflight reports that
45
+ line = (f"WARN cross_review: no second vendor available; the reviewer runs on "
46
+ f"{primary_v}, the author's vendor (NOT cross-vendor).")
47
+ sys.stderr.write(line + "\n")
31
48
 
32
49
  env.meta["review"] = result
33
50
  n = len(result.get("objections") or [])
@@ -34,7 +34,8 @@ class DefinitivenessGateStage(TransformStage):
34
34
 
35
35
  rider = dict(env.trust_rider or {})
36
36
  rider["assumptions_to_verify"] = result.get("rider_lines") or []
37
- rider["overall_confidence"] = env.verdict
37
+ rider["specifics_traceability"] = env.verdict
38
+ rider["overall_confidence"] = env.verdict # deprecated alias; remove after 0.6.x
38
39
  review = env.meta.get("review") or {}
39
40
  if review.get("objections"):
40
41
  rider["contested_and_resolved"] = [