ceo-orchestration 1.0.0

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 (2356) hide show
  1. package/.claude/adr/ADR-001-runtime-state-directory.md +164 -0
  2. package/.claude/adr/ADR-002-hooks-package-layout.md +228 -0
  3. package/.claude/adr/ADR-003-branch-protection-replaces-skill-signing.md +266 -0
  4. package/.claude/adr/ADR-004-defer-bash-legacy-removal.md +171 -0
  5. package/.claude/adr/ADR-005-event-stream-v2.md +153 -0
  6. package/.claude/adr/ADR-006-registry-derived-manifests.md +145 -0
  7. package/.claude/adr/ADR-007-spec-v1-semver-rc-policy.md +159 -0
  8. package/.claude/adr/ADR-008-hook-adapter-layer.md +169 -0
  9. package/.claude/adr/ADR-009-squad-contract.md +167 -0
  10. package/.claude/adr/ADR-010-canonical-edit-sentinel.md +181 -0
  11. package/.claude/adr/ADR-011-event-stream-v2.1-injection-flag.md +150 -0
  12. package/.claude/adr/ADR-012-cross-adapter-golden-fixtures.md +182 -0
  13. package/.claude/adr/ADR-013-squad-trading-hft.md +135 -0
  14. package/.claude/adr/ADR-014-hook-migration-batch-policy.md +197 -0
  15. package/.claude/adr/ADR-015-reflexion-v2-outcome-loop.md +248 -0
  16. package/.claude/adr/ADR-016-spawn-token-tracking.md +179 -0
  17. package/.claude/adr/ADR-017-lesson-pruning-policy.md +193 -0
  18. package/.claude/adr/ADR-018-claim-grammar.md +302 -0
  19. package/.claude/adr/ADR-019-AMEND-1-confidence-gate-block-mode-lifecycle.md +128 -0
  20. package/.claude/adr/ADR-019-AMEND-2-CLASS-SHA_EXISTS-promote-to-high-confidence-block.md +67 -0
  21. package/.claude/adr/ADR-019-confidence-gate-enforcement-lifecycle.md +221 -0
  22. package/.claude/adr/ADR-020-lesson-pruning-policy-v2.md +171 -0
  23. package/.claude/adr/ADR-021-e2e-harness-contract.md +189 -0
  24. package/.claude/adr/ADR-022-reserved-slot.md +52 -0
  25. package/.claude/adr/ADR-023-docs-freshness-lifecycle.md +184 -0
  26. package/.claude/adr/ADR-024-perf-baseline-policy.md +222 -0
  27. package/.claude/adr/ADR-025-squad-edtech.md +236 -0
  28. package/.claude/adr/ADR-026-squad-government.md +263 -0
  29. package/.claude/adr/ADR-027-unified-agent-state-backend.md +266 -0
  30. package/.claude/adr/ADR-028-multi-llm-canonical-parity.md +244 -0
  31. package/.claude/adr/ADR-029-lexical-tfidf-retrieval.md +205 -0
  32. package/.claude/adr/ADR-030-llm-as-judge-methodology.md +336 -0
  33. package/.claude/adr/ADR-031-self-improving-skills.md +221 -0
  34. package/.claude/adr/ADR-032-interactive-debate-protocol.md +337 -0
  35. package/.claude/adr/ADR-033-cost-budget-enforcement.md +275 -0
  36. package/.claude/adr/ADR-034-shared-working-memory.md +233 -0
  37. package/.claude/adr/ADR-035-otel-export.md +242 -0
  38. package/.claude/adr/ADR-036-output-safety.md +263 -0
  39. package/.claude/adr/ADR-037-chaos-testing-methodology.md +289 -0
  40. package/.claude/adr/ADR-038-session-graph-continuity.md +243 -0
  41. package/.claude/adr/ADR-039-skill-marketplace-protocol.md +170 -0
  42. package/.claude/adr/ADR-040-AMEND-2-credential-blocking.md +390 -0
  43. package/.claude/adr/ADR-040-live-adapter-activation-contract.md +285 -0
  44. package/.claude/adr/ADR-041-transition-log-convention.md +272 -0
  45. package/.claude/adr/ADR-042-AMEND-1-read-only-mcp-tools-expansion.md +214 -0
  46. package/.claude/adr/ADR-042-mcp-server-contract.md +727 -0
  47. package/.claude/adr/ADR-043-soc2-audit-trail-mapping.md +503 -0
  48. package/.claude/adr/ADR-044-formal-verification-pilot.md +505 -0
  49. package/.claude/adr/ADR-045-policy-as-code-engine.md +705 -0
  50. package/.claude/adr/ADR-046-deterministic-replay.md +167 -0
  51. package/.claude/adr/ADR-047-predictive-budgeting.md +213 -0
  52. package/.claude/adr/ADR-048-cross-plan-memory.md +227 -0
  53. package/.claude/adr/ADR-049-policy-engine-dual-path-deprecation.md +96 -0
  54. package/.claude/adr/ADR-049a-worktree-orchestration-policy.md +414 -0
  55. package/.claude/adr/ADR-050-native-subagents-dual-rail.md +165 -0
  56. package/.claude/adr/ADR-051-skill-reference-expanded-trust-boundary.md +282 -0
  57. package/.claude/adr/ADR-052-multi-model-dispatch-by-role.md +444 -0
  58. package/.claude/adr/ADR-053-sentinel-hmac-deferred.md +227 -0
  59. package/.claude/adr/ADR-054-AMEND-1-anthropic-admin-key-tier.md +131 -0
  60. package/.claude/adr/ADR-054-github-token-rotation.md +111 -0
  61. package/.claude/adr/ADR-055-AMEND-1-spool-writer-async-drain.md +170 -0
  62. package/.claude/adr/ADR-055-AMEND-2-chain-reset-marker.md +126 -0
  63. package/.claude/adr/ADR-055-AMEND-3-opportunistic-drain-nonblocking.md +183 -0
  64. package/.claude/adr/ADR-055-audit-log-hmac-chain.md +264 -0
  65. package/.claude/adr/ADR-056-hook-lifecycle-expansion.md +261 -0
  66. package/.claude/adr/ADR-057-output-scan-redaction.md +268 -0
  67. package/.claude/adr/ADR-058-brainstorm-gate-and-two-pass-review.md +240 -0
  68. package/.claude/adr/ADR-059-skill-bootstrap-env-knob.md +204 -0
  69. package/.claude/adr/ADR-060-curated-skill-import-pipeline.md +464 -0
  70. package/.claude/adr/ADR-061-runtime-cost-streaming.md +171 -0
  71. package/.claude/adr/ADR-062-AMEND-1-rag-conditional-default-on-supersedes-opt-in.md +232 -0
  72. package/.claude/adr/ADR-062-rag-sidecar-mcp-opt-in.md +231 -0
  73. package/.claude/adr/ADR-063-agent-eval-empirical-dispatch-validation.md +609 -0
  74. package/.claude/adr/ADR-064-dynamic-tier-policy-learned-dispatch.md +288 -0
  75. package/.claude/adr/ADR-065-audit-event-naming-convention.md +185 -0
  76. package/.claude/adr/ADR-066-context-mode-orthogonal-to-manifest.md +92 -0
  77. package/.claude/adr/ADR-067-ceo-model-downshift-static-routing.md +219 -0
  78. package/.claude/adr/ADR-069-wondelai-skills-import-refused.md +183 -0
  79. package/.claude/adr/ADR-070-audit-emit-package-layout.md +228 -0
  80. package/.claude/adr/ADR-071-benchmark-comparison-methodology.md +209 -0
  81. package/.claude/adr/ADR-072-test-discovery-via-conftest.md +184 -0
  82. package/.claude/adr/ADR-073-semver-bump-criteria-sprint-32.md +209 -0
  83. package/.claude/adr/ADR-074-sprint-32-phase-3-b1-refused.md +320 -0
  84. package/.claude/adr/ADR-075-sprint-32-phase-5-b5-benchmark-refused.md +250 -0
  85. package/.claude/adr/ADR-076-sprint-32-final-closure.md +218 -0
  86. package/.claude/adr/ADR-077-2026-04-24-webfetch-injection-incident.md +203 -0
  87. package/.claude/adr/ADR-078-sentinel-cosign-clarification.md +295 -0
  88. package/.claude/adr/ADR-079-prompt-sha-salt-hmac-impact.md +221 -0
  89. package/.claude/adr/ADR-080-rail-anomaly-h4-defense-in-depth.md +1143 -0
  90. package/.claude/adr/ADR-081-token-as-time-unit.md +272 -0
  91. package/.claude/adr/ADR-082-l7c-mitigation-default-on.md +240 -0
  92. package/.claude/adr/ADR-083-mcp-injection-scanner.md +225 -0
  93. package/.claude/adr/ADR-084-multi-adapter-refused-claude-only.md +152 -0
  94. package/.claude/adr/ADR-085-framework-landscape-claude-only.md +183 -0
  95. package/.claude/adr/ADR-086-checkpointing-refused.md +124 -0
  96. package/.claude/adr/ADR-087-AMEND-1-otel-consume-native-opt-in.md +217 -0
  97. package/.claude/adr/ADR-087-otel-emit-refused.md +136 -0
  98. package/.claude/adr/ADR-088-guardrails-library-refused.md +128 -0
  99. package/.claude/adr/ADR-089-sec-cluster-disposition.md +182 -0
  100. package/.claude/adr/ADR-090-framework-activation-defaults.md +217 -0
  101. package/.claude/adr/ADR-091-dogfood-validation-deferred.md +128 -0
  102. package/.claude/adr/ADR-092-plan-closure-honest-deferral.md +165 -0
  103. package/.claude/adr/ADR-093-refused-adr-moratorium.md +181 -0
  104. package/.claude/adr/ADR-094-claude-sdk-compat-version-pinning.md +160 -0
  105. package/.claude/adr/ADR-095-calendar-gate-retraction.md +202 -0
  106. package/.claude/adr/ADR-096-vibecoder-only-by-design.md +215 -0
  107. package/.claude/adr/ADR-097-function-length-advisory-permanent.md +186 -0
  108. package/.claude/adr/ADR-098-ceo-boot-audit-emit-register.md +251 -0
  109. package/.claude/adr/ADR-099-changesets-adoption.md +245 -0
  110. package/.claude/adr/ADR-100-trusted-dependencies-re-affirm.md +208 -0
  111. package/.claude/adr/ADR-101-replay-redact-helper.md +106 -0
  112. package/.claude/adr/ADR-102-mcp-introspection-extends-042.md +165 -0
  113. package/.claude/adr/ADR-103-calendar-gate-final-purge.md +121 -0
  114. package/.claude/adr/ADR-104-AMEND-1-aek-dated-promotion-criteria.md +338 -0
  115. package/.claude/adr/ADR-104-adaptive-execution-kernel-advisory.md +210 -0
  116. package/.claude/adr/ADR-105-multi-llm-coordinated-supersede.md +126 -0
  117. package/.claude/adr/ADR-106-codex-mcp-adapter-contract.md +153 -0
  118. package/.claude/adr/ADR-107-pair-rail-mandatory-l2-plus.md +189 -0
  119. package/.claude/adr/ADR-108-cross-llm-veto-floor.md +129 -0
  120. package/.claude/adr/ADR-109-codex-skill-rehash-protocol.md +104 -0
  121. package/.claude/adr/ADR-110-codex-pretool-enforcement.md +94 -0
  122. package/.claude/adr/ADR-111-locked-corpus-governance.md +191 -0
  123. package/.claude/adr/ADR-112-grandfather-cap-scope-clarification.md +192 -0
  124. package/.claude/adr/ADR-113-plan-084-canonical-guard-extension.md +59 -0
  125. package/.claude/adr/ADR-114-codex-egress-redaction-symmetry.md +72 -0
  126. package/.claude/adr/ADR-115-post-sota-maintenance-mode.md +152 -0
  127. package/.claude/adr/ADR-116-AMEND-1-kernel-extension-v2.md +640 -0
  128. package/.claude/adr/ADR-116-kernel-hard-deny-tier-0-extension.md +465 -0
  129. package/.claude/adr/ADR-117-adr-id-collision-rename-policy.md +279 -0
  130. package/.claude/adr/ADR-118-AMEND-1-phase-c-enforcing-flip.md +191 -0
  131. package/.claude/adr/ADR-118-god-mode-auto-usable-state.md +338 -0
  132. package/.claude/adr/ADR-119-sentinel-unlock-contract.md +133 -0
  133. package/.claude/adr/ADR-120-pii-core-promotion.md +280 -0
  134. package/.claude/adr/ADR-121-sentinel-signers-rotation-policy.md +434 -0
  135. package/.claude/adr/ADR-122-dpop-mcp-bearer-replay-defense.md +232 -0
  136. package/.claude/adr/ADR-123-streaming-adapter-canonical-source.md +130 -0
  137. package/.claude/adr/ADR-124-post-audit-sota-execution-mode.md +362 -0
  138. package/.claude/adr/ADR-125-risk-tiered-defaulting-doctrine.md +355 -0
  139. package/.claude/adr/ADR-126-governed-sidecar-capability-model.md +509 -0
  140. package/.claude/adr/ADR-127-pair-rail-advisory-promotion.md +218 -0
  141. package/.claude/adr/ADR-128-c2-vector-memory-capability-class.md +380 -0
  142. package/.claude/adr/ADR-129-AMEND-1-key-floor-waiver-lift.md +249 -0
  143. package/.claude/adr/ADR-129-c1-crypto-capability-class.md +289 -0
  144. package/.claude/adr/ADR-131-c5-dev-tools-capability-class.md +215 -0
  145. package/.claude/adr/ADR-132-goap-advisory-planning-doctrine.md +333 -0
  146. package/.claude/adr/ADR-133-autonomous-loop-opt-in-capability-doctrine.md +440 -0
  147. package/.claude/adr/ADR-135-AMEND-1-write-mode-trust-boundary.md +457 -0
  148. package/.claude/adr/ADR-135-AMEND-2-write-mode-activation.md +175 -0
  149. package/.claude/adr/ADR-135-federation-contract-mvp.md +253 -0
  150. package/.claude/adr/ADR-136-AMEND-1-workflow-primitive-adoption.md +139 -0
  151. package/.claude/adr/ADR-136-workflow-engine-doctrine.md +155 -0
  152. package/.claude/adr/ADR-137-skill-priority-stack-decision.md +162 -0
  153. package/.claude/adr/ADR-138-ac-format-priority-and-story-anchor.md +149 -0
  154. package/.claude/adr/ADR-139-coverage-doctrine-tiered.md +133 -0
  155. package/.claude/adr/ADR-140-receiving-review-doctrine.md +136 -0
  156. package/.claude/adr/ADR-141-reduce-protocol.md +124 -0
  157. package/.claude/adr/ADR-142-opus-4-8-model-bump.md +116 -0
  158. package/.claude/adr/ADR-143-git-hook-bypass-guard.md +166 -0
  159. package/.claude/adr/ADR-144-subagent-model-tiering-frontmatter.md +111 -0
  160. package/.claude/adr/ADR-145-cross-model-review-persona-demand-modality.md +103 -0
  161. package/.claude/adr/ADR-146-adversary-review-hook.md +122 -0
  162. package/.claude/adr/ADR-147-eval-harness-doctrine.md +109 -0
  163. package/.claude/adr/ADR-148-canonical-pricing-source.md +123 -0
  164. package/.claude/adr/ADR-149-model-id-allowlist.md +196 -0
  165. package/.claude/adr/ADR-150-commit-signing-policy.md +12 -0
  166. package/.claude/adr/ADR-151-fan-plan-advisory-bridge.md +178 -0
  167. package/.claude/adr/ADR-152-claude-md-decomposition.md +262 -0
  168. package/.claude/adr/ADR-153-compaction-continuity.md +141 -0
  169. package/.claude/adr/ADR-154-updatedinput-single-rewriter.md +68 -0
  170. package/.claude/adr/ADR-155-install-baseline-manifest.md +66 -0
  171. package/.claude/adr/ADR-156-constitution-sync-cascade.md +122 -0
  172. package/.claude/adr/README.md +392 -0
  173. package/.claude/adversary.md +116 -0
  174. package/.claude/agent-metrics.md +101 -0
  175. package/.claude/agents/_dispatch.md +30 -0
  176. package/.claude/agents/_probe_architect.md +45 -0
  177. package/.claude/agents/_probe_canonical_edit.md +46 -0
  178. package/.claude/agents/_probe_missing_skill.md +42 -0
  179. package/.claude/agents/code-reviewer.md +166 -0
  180. package/.claude/agents/devops.md +114 -0
  181. package/.claude/agents/identity-trust-architect.md +234 -0
  182. package/.claude/agents/incident-commander.md +285 -0
  183. package/.claude/agents/llm-finops-architect.md +265 -0
  184. package/.claude/agents/performance-engineer.md +148 -0
  185. package/.claude/agents/qa-architect.md +167 -0
  186. package/.claude/agents/security-engineer.md +192 -0
  187. package/.claude/agents/threat-detection-engineer.md +238 -0
  188. package/.claude/benchmarks/_schemas/judge-prompt.md +26 -0
  189. package/.claude/benchmarks/_schemas/judge-rubric-example.json +11 -0
  190. package/.claude/benchmarks/_schemas/judge-rubric.yaml +39 -0
  191. package/.claude/benchmarks/calibration-grades.jsonl +6 -0
  192. package/.claude/benchmarks/human-sample-calibration.md +232 -0
  193. package/.claude/benchmarks/judge-rotation-schedule.md +61 -0
  194. package/.claude/benchmarks/retrieval-judgment-set.yaml +194 -0
  195. package/.claude/benchmarks/tests/test_retrieval_recall_gate.py +330 -0
  196. package/.claude/commands/agent-budget.md +105 -0
  197. package/.claude/commands/architect.md +130 -0
  198. package/.claude/commands/audit-page.md +149 -0
  199. package/.claude/commands/audit-tokens.md +89 -0
  200. package/.claude/commands/ceo-boot.md +118 -0
  201. package/.claude/commands/ceo-info.md +71 -0
  202. package/.claude/commands/debate.md +258 -0
  203. package/.claude/commands/effort.md +99 -0
  204. package/.claude/commands/fan-plan.md +129 -0
  205. package/.claude/commands/goap.md +163 -0
  206. package/.claude/commands/lesson-review.md +66 -0
  207. package/.claude/commands/memory-scratchpad.md +100 -0
  208. package/.claude/commands/onboard.md +204 -0
  209. package/.claude/commands/pitfall.md +54 -0
  210. package/.claude/commands/resume.md +90 -0
  211. package/.claude/commands/self-test.md +83 -0
  212. package/.claude/commands/skill-review.md +102 -0
  213. package/.claude/commands/spawn.md +212 -0
  214. package/.claude/commands/squad-install.md +94 -0
  215. package/.claude/commands/status.md +177 -0
  216. package/.claude/commands/terse.md +81 -0
  217. package/.claude/commands/veto-check.md +63 -0
  218. package/.claude/data/audit-registry.golden.txt +306 -0
  219. package/.claude/data/canonical_models.json +1030 -0
  220. package/.claude/data/confidence-gate-class-tiers.json +24 -0
  221. package/.claude/data/cookbook_patterns.json +139 -0
  222. package/.claude/data/federation/enabled.md +34 -0
  223. package/.claude/data/federation/lan-enabled.md +38 -0
  224. package/.claude/data/federation/peers.example.yaml +89 -0
  225. package/.claude/data/goap/action-cost-baseline.json +29 -0
  226. package/.claude/dispatcher/disable_predicate_eval.py +630 -0
  227. package/.claude/dispatcher/routing-matrix-loader.py +874 -0
  228. package/.claude/dispatcher/routing-matrix.yaml +343 -0
  229. package/.claude/dispatcher/tests/conftest.py +11 -0
  230. package/.claude/dispatcher/tests/test_disable_predicate_eval.py +424 -0
  231. package/.claude/dispatcher/tests/test_routing_matrix_loader.py +461 -0
  232. package/.claude/docs/dpop-scope.md +79 -0
  233. package/.claude/docs/sentinel-signers-rotation-DRAFT.md +117 -0
  234. package/.claude/eval/README.md +73 -0
  235. package/.claude/eval/reporter.py +109 -0
  236. package/.claude/eval/runner.py +532 -0
  237. package/.claude/eval/self_test.yaml +57 -0
  238. package/.claude/eval/tasks/__init__.py +185 -0
  239. package/.claude/eval/tasks/t01_fix_off_by_one.py +52 -0
  240. package/.claude/eval/tasks/t02_implement_fizzbuzz.py +65 -0
  241. package/.claude/eval/tasks/t03_json_config_parse.py +80 -0
  242. package/.claude/eval/tasks/t04_refactor_dedupe.py +71 -0
  243. package/.claude/eval/tasks/t05_add_unit_test.py +77 -0
  244. package/.claude/eval/tasks/t06_palindrome.py +58 -0
  245. package/.claude/eval/tasks/t07_sql_param_fix.py +69 -0
  246. package/.claude/eval/tasks/t08_word_count.py +53 -0
  247. package/.claude/eval/tasks/t09_readme_doc.py +64 -0
  248. package/.claude/eval/tasks/t10_binary_search.py +58 -0
  249. package/.claude/frontend-team.md +202 -0
  250. package/.claude/governance/README.md +37 -0
  251. package/.claude/governance/audit_tokens_allowlist.json +37 -0
  252. package/.claude/governance/codex-cli-binary-sha256.txt +32 -0
  253. package/.claude/governance/codex-cli-pin.txt +26 -0
  254. package/.claude/governance/function-length-grandfather.yaml +2095 -0
  255. package/.claude/governance/governance-waivers.yaml +28 -0
  256. package/.claude/governance/pair-rail-inputs-hash-manifest.txt +32 -0
  257. package/.claude/governance/pair-rail-verdict-template.md +58 -0
  258. package/.claude/governance/pair-rail-verdict-v1.16.0-rc.1.md +120 -0
  259. package/.claude/governance/pair-rail-verdict-v1.16.0.md +64 -0
  260. package/.claude/gpg-revocations.jsonl +1 -0
  261. package/.claude/hooks/SessionEnd.py +353 -0
  262. package/.claude/hooks/SessionStart.py +345 -0
  263. package/.claude/hooks/Stop.py +195 -0
  264. package/.claude/hooks/UserPromptSubmit.py +329 -0
  265. package/.claude/hooks/_lib/EXECUTION-CONTEXT-DEFERRED.md +82 -0
  266. package/.claude/hooks/_lib/__init__.py +26 -0
  267. package/.claude/hooks/_lib/action_required.py +592 -0
  268. package/.claude/hooks/_lib/adapters/__init__.py +87 -0
  269. package/.claude/hooks/_lib/adapters/_constants.py +127 -0
  270. package/.claude/hooks/_lib/adapters/claude.py +167 -0
  271. package/.claude/hooks/_lib/adapters/codex.py +754 -0
  272. package/.claude/hooks/_lib/adapters/live/__init__.py +378 -0
  273. package/.claude/hooks/_lib/adapters/live/_breaker.py +309 -0
  274. package/.claude/hooks/_lib/adapters/live/_cost.py +389 -0
  275. package/.claude/hooks/_lib/adapters/live/_policy.py +319 -0
  276. package/.claude/hooks/_lib/adapters/live/_result.py +206 -0
  277. package/.claude/hooks/_lib/adapters/live/_transport.py +681 -0
  278. package/.claude/hooks/_lib/adapters/live/claude.py +1027 -0
  279. package/.claude/hooks/_lib/adapters/live/claude_batch.py +652 -0
  280. package/.claude/hooks/_lib/adapters/live/gemini.py +270 -0
  281. package/.claude/hooks/_lib/adapters/live/local.py +195 -0
  282. package/.claude/hooks/_lib/adapters/live/openai.py +371 -0
  283. package/.claude/hooks/_lib/adversary_rules.py +196 -0
  284. package/.claude/hooks/_lib/agent_frontmatter.py +288 -0
  285. package/.claude/hooks/_lib/audit_emit.py +11746 -0
  286. package/.claude/hooks/_lib/audit_emit_dispatch.py +179 -0
  287. package/.claude/hooks/_lib/audit_hmac.py +1146 -0
  288. package/.claude/hooks/_lib/audit_rotation.py +101 -0
  289. package/.claude/hooks/_lib/canonical_json.py +145 -0
  290. package/.claude/hooks/_lib/codex_cli_shape.py +502 -0
  291. package/.claude/hooks/_lib/codex_egress_redact.py +185 -0
  292. package/.claude/hooks/_lib/confidence_labels.py +338 -0
  293. package/.claude/hooks/_lib/contract.py +254 -0
  294. package/.claude/hooks/_lib/cookbook_patterns.py +136 -0
  295. package/.claude/hooks/_lib/cost_envelope.py +719 -0
  296. package/.claude/hooks/_lib/credentials.py +188 -0
  297. package/.claude/hooks/_lib/effective_config.py +767 -0
  298. package/.claude/hooks/_lib/egress_taxonomy.py +448 -0
  299. package/.claude/hooks/_lib/embeddings.py +322 -0
  300. package/.claude/hooks/_lib/env_guard.py +353 -0
  301. package/.claude/hooks/_lib/env_persist_allowlist.py +147 -0
  302. package/.claude/hooks/_lib/escalation_signals.py +335 -0
  303. package/.claude/hooks/_lib/estimation/__init__.py +12 -0
  304. package/.claude/hooks/_lib/estimation/bayesian.py +147 -0
  305. package/.claude/hooks/_lib/estimation/pipeline.py +209 -0
  306. package/.claude/hooks/_lib/exceptions.py +101 -0
  307. package/.claude/hooks/_lib/execution_context.py +208 -0
  308. package/.claude/hooks/_lib/federation/__init__.py +104 -0
  309. package/.claude/hooks/_lib/federation/audit_chain.py +118 -0
  310. package/.claude/hooks/_lib/federation/audit_chain_ext.py +408 -0
  311. package/.claude/hooks/_lib/federation/cert_inspector.py +573 -0
  312. package/.claude/hooks/_lib/federation/client.py +327 -0
  313. package/.claude/hooks/_lib/federation/handlers/__init__.py +30 -0
  314. package/.claude/hooks/_lib/federation/handlers/audit_event_batch.py +346 -0
  315. package/.claude/hooks/_lib/federation/handlers/audit_event_push.py +395 -0
  316. package/.claude/hooks/_lib/federation/handlers/peer_register.py +484 -0
  317. package/.claude/hooks/_lib/federation/handlers/peer_revoke.py +356 -0
  318. package/.claude/hooks/_lib/federation/identity.py +1056 -0
  319. package/.claude/hooks/_lib/federation/rate_limit.py +476 -0
  320. package/.claude/hooks/_lib/federation/replay.py +284 -0
  321. package/.claude/hooks/_lib/federation/scopes.py +168 -0
  322. package/.claude/hooks/_lib/federation/server.py +2218 -0
  323. package/.claude/hooks/_lib/file_walker.py +145 -0
  324. package/.claude/hooks/_lib/filelock.py +191 -0
  325. package/.claude/hooks/_lib/frontmatter.py +124 -0
  326. package/.claude/hooks/_lib/git_bypass.py +971 -0
  327. package/.claude/hooks/_lib/gpg_verify.py +356 -0
  328. package/.claude/hooks/_lib/guardrail_validator.py +478 -0
  329. package/.claude/hooks/_lib/injection_patterns.py +252 -0
  330. package/.claude/hooks/_lib/injection_salt.py +160 -0
  331. package/.claude/hooks/_lib/mcp/__init__.py +5 -0
  332. package/.claude/hooks/_lib/mcp/bearer_replay.py +279 -0
  333. package/.claude/hooks/_lib/mcp/canonical_guard.py +1140 -0
  334. package/.claude/hooks/_lib/mcp_bearer_friction.py +475 -0
  335. package/.claude/hooks/_lib/mcp_injection_scan.py +250 -0
  336. package/.claude/hooks/_lib/mcp_routing.py +151 -0
  337. package/.claude/hooks/_lib/memory_shared.py +592 -0
  338. package/.claude/hooks/_lib/metrics.py +241 -0
  339. package/.claude/hooks/_lib/model_routing.py +227 -0
  340. package/.claude/hooks/_lib/otel/__init__.py +34 -0
  341. package/.claude/hooks/_lib/otel/bounded_exporter.py +373 -0
  342. package/.claude/hooks/_lib/otel/hook_bridge.py +53 -0
  343. package/.claude/hooks/_lib/otel/queue.py +229 -0
  344. package/.claude/hooks/_lib/otel_emit.py +604 -0
  345. package/.claude/hooks/_lib/output_scan.py +1062 -0
  346. package/.claude/hooks/_lib/output_scan_dedup.py +379 -0
  347. package/.claude/hooks/_lib/pair_rail_decide.py +244 -0
  348. package/.claude/hooks/_lib/payload.py +195 -0
  349. package/.claude/hooks/_lib/persona_routing.py +244 -0
  350. package/.claude/hooks/_lib/pii_patterns.py +851 -0
  351. package/.claude/hooks/_lib/plan_frontmatter.py +166 -0
  352. package/.claude/hooks/_lib/policy.py +1527 -0
  353. package/.claude/hooks/_lib/policy_preprocessors.py +462 -0
  354. package/.claude/hooks/_lib/rag_bridge.py +624 -0
  355. package/.claude/hooks/_lib/rag_events.py +171 -0
  356. package/.claude/hooks/_lib/rag_router.py +253 -0
  357. package/.claude/hooks/_lib/redact.py +228 -0
  358. package/.claude/hooks/_lib/replay_redact.py +511 -0
  359. package/.claude/hooks/_lib/scratchpad_lib.py +225 -0
  360. package/.claude/hooks/_lib/secret_patterns.py +905 -0
  361. package/.claude/hooks/_lib/sentinel_signers.py +740 -0
  362. package/.claude/hooks/_lib/spec_context_sanitizer.py +258 -0
  363. package/.claude/hooks/_lib/spool_writer.py +2613 -0
  364. package/.claude/hooks/_lib/state_store.py +476 -0
  365. package/.claude/hooks/_lib/subagent_dispatch.py +244 -0
  366. package/.claude/hooks/_lib/swarm_circuit_breaker.py +203 -0
  367. package/.claude/hooks/_lib/swarm_enable_gate.py +152 -0
  368. package/.claude/hooks/_lib/team.py +128 -0
  369. package/.claude/hooks/_lib/test_isolation.py +352 -0
  370. package/.claude/hooks/_lib/testing.py +351 -0
  371. package/.claude/hooks/_lib/tests/federation/test_federation_attack_surface.py +251 -0
  372. package/.claude/hooks/_lib/tests/federation/test_federation_audit_stitching.py +135 -0
  373. package/.claude/hooks/_lib/tests/federation/test_federation_identity.py +234 -0
  374. package/.claude/hooks/_lib/tests/federation/test_federation_replay.py +204 -0
  375. package/.claude/hooks/_lib/tests/federation/test_federation_sentinel_stage2.py +214 -0
  376. package/.claude/hooks/_lib/tests/federation/test_federation_server.py +385 -0
  377. package/.claude/hooks/_lib/tests/test_confidence_gate_class_block.py +313 -0
  378. package/.claude/hooks/_lib/tests/test_cost_envelope.py +759 -0
  379. package/.claude/hooks/_lib/tests/test_execution_context.py +254 -0
  380. package/.claude/hooks/_lib/tests/test_goap_advisory_invariant.py +134 -0
  381. package/.claude/hooks/_lib/tests/test_goap_planner.py +368 -0
  382. package/.claude/hooks/_lib/tests/test_plan104_audit_emit.py +324 -0
  383. package/.claude/hooks/_lib/tests/test_plan104_demand_resolver.py +584 -0
  384. package/.claude/hooks/_lib/tests/test_plan104_demand_scan.py +164 -0
  385. package/.claude/hooks/_lib/tests/test_plan104_microbench.py +109 -0
  386. package/.claude/hooks/_lib/tests/test_plan104_waive_parser.py +113 -0
  387. package/.claude/hooks/_lib/tests/test_plan105_audit_emit.py +259 -0
  388. package/.claude/hooks/_lib/tests/test_plan105_check_roadmap_binding.py +68 -0
  389. package/.claude/hooks/_lib/tests/test_plan105_goap_planner.py +158 -0
  390. package/.claude/hooks/_lib/tests/test_plan105_spawn_outcome.py +234 -0
  391. package/.claude/hooks/_lib/tests/test_rag_dead_code_disposition.py +262 -0
  392. package/.claude/hooks/_lib/tests/test_rag_router.py +209 -0
  393. package/.claude/hooks/_lib/tests/test_swarm_circuit_breaker.py +278 -0
  394. package/.claude/hooks/_lib/tests/test_swarm_kill_switch_chain.py +360 -0
  395. package/.claude/hooks/_lib/tier_policy/__init__.py +123 -0
  396. package/.claude/hooks/_lib/tier_policy/_agent_frontmatter.py +509 -0
  397. package/.claude/hooks/_lib/tier_policy/_constants.py +376 -0
  398. package/.claude/hooks/_lib/tier_policy/_types.py +355 -0
  399. package/.claude/hooks/_lib/tier_policy/fixtures/baseline.json +17 -0
  400. package/.claude/hooks/_lib/tier_policy/fixtures/oversize_64kib.json +1 -0
  401. package/.claude/hooks/_lib/tier_policy/fixtures/prototype_pollution_attack.yaml +14 -0
  402. package/.claude/hooks/_lib/tier_policy/fixtures/schema_v1_sample.json +5 -0
  403. package/.claude/hooks/_lib/tier_policy/fixtures/schema_v2_sample.json +17 -0
  404. package/.claude/hooks/_lib/tier_policy/fixtures/yaml_bomb_attack.yaml +20 -0
  405. package/.claude/hooks/_lib/tier_policy/loader.py +476 -0
  406. package/.claude/hooks/_lib/tokens.py +136 -0
  407. package/.claude/hooks/_lib/tool_lifecycle.py +488 -0
  408. package/.claude/hooks/_lib/trusted_env.py +77 -0
  409. package/.claude/hooks/_python-hook.sh +242 -0
  410. package/.claude/hooks/accel_dispatch.py +172 -0
  411. package/.claude/hooks/adequacy_gate.py +424 -0
  412. package/.claude/hooks/audit_log.py +1352 -0
  413. package/.claude/hooks/auto_boot.py +518 -0
  414. package/.claude/hooks/check_adversary.py +273 -0
  415. package/.claude/hooks/check_agent_spawn.py +2696 -0
  416. package/.claude/hooks/check_anti_ceo_overhead.py +786 -0
  417. package/.claude/hooks/check_arbitration_kernel.py +544 -0
  418. package/.claude/hooks/check_bash_canonical_forensic.py +180 -0
  419. package/.claude/hooks/check_bash_safety.py +1483 -0
  420. package/.claude/hooks/check_budget.py +916 -0
  421. package/.claude/hooks/check_canonical_edit.py +1197 -0
  422. package/.claude/hooks/check_closeout_guard.py +154 -0
  423. package/.claude/hooks/check_codex_filewrite.py +366 -0
  424. package/.claude/hooks/check_codex_response.py +403 -0
  425. package/.claude/hooks/check_confidence_gate.py +545 -0
  426. package/.claude/hooks/check_config_change.py +346 -0
  427. package/.claude/hooks/check_config_protection.py +381 -0
  428. package/.claude/hooks/check_cost_envelope.py +286 -0
  429. package/.claude/hooks/check_fluency_nudge.py +747 -0
  430. package/.claude/hooks/check_mcp_response.py +234 -0
  431. package/.claude/hooks/check_output_safety.py +237 -0
  432. package/.claude/hooks/check_output_secrets.py +518 -0
  433. package/.claude/hooks/check_pair_rail.py +1700 -0
  434. package/.claude/hooks/check_plan_edit.py +905 -0
  435. package/.claude/hooks/check_postcompact_reinject.py +265 -0
  436. package/.claude/hooks/check_precompact_continuity.py +379 -0
  437. package/.claude/hooks/check_protocol_semver_cascade.py +401 -0
  438. package/.claude/hooks/check_read_injection.py +366 -0
  439. package/.claude/hooks/check_scratchpad_access.py +228 -0
  440. package/.claude/hooks/check_setup_verification.py +297 -0
  441. package/.claude/hooks/check_skill_bootstrap_post.py +339 -0
  442. package/.claude/hooks/check_skill_patch_sentinel.py +413 -0
  443. package/.claude/hooks/check_skill_reference_read.py +518 -0
  444. package/.claude/hooks/check_subagent_fabrication.py +45 -0
  445. package/.claude/hooks/check_subagent_start.py +232 -0
  446. package/.claude/hooks/check_tier_policy.py +211 -0
  447. package/.claude/hooks/check_tier_policy_misrouting_24h.py +187 -0
  448. package/.claude/hooks/check_webfetch_injection.py +277 -0
  449. package/.claude/hooks/check_worktree_writer.py +773 -0
  450. package/.claude/hooks/codex_review_user_code.py +304 -0
  451. package/.claude/hooks/emit_architect_outcome.py +232 -0
  452. package/.claude/hooks/latency_report.py +343 -0
  453. package/.claude/hooks/policy_dispatch.py +168 -0
  454. package/.claude/hooks/review_loop.py +560 -0
  455. package/.claude/hooks/route.py +115 -0
  456. package/.claude/hooks/tests/_agent_fixture.py +153 -0
  457. package/.claude/hooks/tests/adapters/__init__.py +0 -0
  458. package/.claude/hooks/tests/adapters/live/__init__.py +0 -0
  459. package/.claude/hooks/tests/adapters/live/test_adapters.py +488 -0
  460. package/.claude/hooks/tests/adapters/live/test_audit_wiring.py +81 -0
  461. package/.claude/hooks/tests/adapters/live/test_breaker.py +272 -0
  462. package/.claude/hooks/tests/adapters/live/test_cost.py +191 -0
  463. package/.claude/hooks/tests/adapters/live/test_o7_modernization.py +670 -0
  464. package/.claude/hooks/tests/adapters/live/test_policy.py +168 -0
  465. package/.claude/hooks/tests/conftest.py +139 -0
  466. package/.claude/hooks/tests/fixtures/adapters/claude/in/agent_spawn_compliant.json +9 -0
  467. package/.claude/hooks/tests/fixtures/adapters/claude/in/bash_safe_command.json +8 -0
  468. package/.claude/hooks/tests/fixtures/adapters/claude/in/post_audit_event.json +1 -0
  469. package/.claude/hooks/tests/fixtures/adapters/claude/out/allow.json +1 -0
  470. package/.claude/hooks/tests/fixtures/adapters/claude/out/block_with_reason.json +1 -0
  471. package/.claude/hooks/tests/fixtures/adapters/codex/in/.gitkeep +1 -0
  472. package/.claude/hooks/tests/fixtures/adapters/codex/out/.gitkeep +1 -0
  473. package/.claude/hooks/tests/fixtures/adapters/gemini/GAPS.md +46 -0
  474. package/.claude/hooks/tests/fixtures/adapters/gemini/in/agent_spawn_minimal.json +1 -0
  475. package/.claude/hooks/tests/fixtures/adapters/gemini/in/bash_minimal.json +1 -0
  476. package/.claude/hooks/tests/fixtures/adapters/gemini/out/allow.json +1 -0
  477. package/.claude/hooks/tests/fixtures/adapters/local/in/agent_spawn_ollama.json +19 -0
  478. package/.claude/hooks/tests/fixtures/adapters/local/in/bash_minimal.json +8 -0
  479. package/.claude/hooks/tests/fixtures/adapters/local/out/allow.json +1 -0
  480. package/.claude/hooks/tests/fixtures/adapters/openai/in/agent_spawn_chat_completions.json +13 -0
  481. package/.claude/hooks/tests/fixtures/adapters/openai/in/bash_responses_api.json +9 -0
  482. package/.claude/hooks/tests/fixtures/adapters/openai/out/allow.json +1 -0
  483. package/.claude/hooks/tests/fixtures/anti_ceo_overhead/should-NOT-block-on-Y.ndjson +13 -0
  484. package/.claude/hooks/tests/fixtures/anti_ceo_overhead/should-block-on-X.ndjson +9 -0
  485. package/.claude/hooks/tests/fixtures/byte_identity/__init__.py +5 -0
  486. package/.claude/hooks/tests/fixtures/byte_identity/bash_safety_fuzzer.py +287 -0
  487. package/.claude/hooks/tests/fixtures/byte_identity/plan_edit_fuzzer.py +364 -0
  488. package/.claude/hooks/tests/fixtures/exchange_keys/negative/aws-iam-policy-arn-id-25.txt +2 -0
  489. package/.claude/hooks/tests/fixtures/exchange_keys/negative/blog-paragraph-18.txt +1 -0
  490. package/.claude/hooks/tests/fixtures/exchange_keys/negative/boilerplate-26.txt +4 -0
  491. package/.claude/hooks/tests/fixtures/exchange_keys/negative/cdn-cache-key-12.txt +2 -0
  492. package/.claude/hooks/tests/fixtures/exchange_keys/negative/certificate-fingerprint-10.txt +2 -0
  493. package/.claude/hooks/tests/fixtures/exchange_keys/negative/changelog-19.txt +1 -0
  494. package/.claude/hooks/tests/fixtures/exchange_keys/negative/commit-sha-01.txt +4 -0
  495. package/.claude/hooks/tests/fixtures/exchange_keys/negative/django-csrf-token-24.txt +3 -0
  496. package/.claude/hooks/tests/fixtures/exchange_keys/negative/docker-image-04.txt +2 -0
  497. package/.claude/hooks/tests/fixtures/exchange_keys/negative/docs-example-22.txt +3 -0
  498. package/.claude/hooks/tests/fixtures/exchange_keys/negative/haiku-20.txt +1 -0
  499. package/.claude/hooks/tests/fixtures/exchange_keys/negative/hex-placeholder-15.txt +3 -0
  500. package/.claude/hooks/tests/fixtures/exchange_keys/negative/hex-short-23.txt +5 -0
  501. package/.claude/hooks/tests/fixtures/exchange_keys/negative/image-thumbnail-09.txt +3 -0
  502. package/.claude/hooks/tests/fixtures/exchange_keys/negative/jwt-payload-decoded-08.txt +3 -0
  503. package/.claude/hooks/tests/fixtures/exchange_keys/negative/kubernetes-uid-06.txt +3 -0
  504. package/.claude/hooks/tests/fixtures/exchange_keys/negative/md5-hash-02.txt +2 -0
  505. package/.claude/hooks/tests/fixtures/exchange_keys/negative/phone-number-16.txt +3 -0
  506. package/.claude/hooks/tests/fixtures/exchange_keys/negative/postgres-uuid-05.txt +2 -0
  507. package/.claude/hooks/tests/fixtures/exchange_keys/negative/redis-cluster-node-13.txt +3 -0
  508. package/.claude/hooks/tests/fixtures/exchange_keys/negative/session-token-11.txt +3 -0
  509. package/.claude/hooks/tests/fixtures/exchange_keys/negative/sha256-checksum-03.txt +3 -0
  510. package/.claude/hooks/tests/fixtures/exchange_keys/negative/short-token-21.txt +2 -0
  511. package/.claude/hooks/tests/fixtures/exchange_keys/negative/software-license-14.txt +4 -0
  512. package/.claude/hooks/tests/fixtures/exchange_keys/negative/telemetry-trace-07.txt +3 -0
  513. package/.claude/hooks/tests/fixtures/exchange_keys/negative/zip-postal-17.txt +4 -0
  514. package/.claude/hooks/tests/fixtures/exchange_keys/positive/binance-api-key-alnum-03.txt +1 -0
  515. package/.claude/hooks/tests/fixtures/exchange_keys/positive/binance-api-key-hex-01.txt +3 -0
  516. package/.claude/hooks/tests/fixtures/exchange_keys/positive/binance-api-key-hex-02.txt +2 -0
  517. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bip39-mnemonic-12-31.txt +2 -0
  518. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bip39-mnemonic-12-33.txt +2 -0
  519. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bip39-mnemonic-24-32.txt +2 -0
  520. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitfinex-api-key-11.txt +1 -0
  521. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitfinex-api-key-12.txt +1 -0
  522. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitfinex-api-key-13.txt +2 -0
  523. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitstamp-api-key-30.txt +3 -0
  524. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitstamp-customer-id-29.txt +2 -0
  525. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bybit-api-key-18.txt +2 -0
  526. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bybit-api-key-19.txt +1 -0
  527. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bybit-api-secret-20.txt +1 -0
  528. package/.claude/hooks/tests/fixtures/exchange_keys/positive/bybit-combined-21.txt +3 -0
  529. package/.claude/hooks/tests/fixtures/exchange_keys/positive/coinbase-api-key-uuid-04.txt +2 -0
  530. package/.claude/hooks/tests/fixtures/exchange_keys/positive/coinbase-api-secret-b64-05.txt +1 -0
  531. package/.claude/hooks/tests/fixtures/exchange_keys/positive/coinbase-combined-07.txt +4 -0
  532. package/.claude/hooks/tests/fixtures/exchange_keys/positive/coinbase-passphrase-06.txt +1 -0
  533. package/.claude/hooks/tests/fixtures/exchange_keys/positive/evm-private-key-34.txt +2 -0
  534. package/.claude/hooks/tests/fixtures/exchange_keys/positive/evm-private-key-35.txt +1 -0
  535. package/.claude/hooks/tests/fixtures/exchange_keys/positive/evm-private-key-36.txt +2 -0
  536. package/.claude/hooks/tests/fixtures/exchange_keys/positive/generic-api-key-37.txt +2 -0
  537. package/.claude/hooks/tests/fixtures/exchange_keys/positive/generic-api-key-38.txt +3 -0
  538. package/.claude/hooks/tests/fixtures/exchange_keys/positive/generic-api-key-39.txt +2 -0
  539. package/.claude/hooks/tests/fixtures/exchange_keys/positive/kraken-api-key-08.txt +1 -0
  540. package/.claude/hooks/tests/fixtures/exchange_keys/positive/kraken-api-secret-09.txt +1 -0
  541. package/.claude/hooks/tests/fixtures/exchange_keys/positive/kraken-combined-10.txt +4 -0
  542. package/.claude/hooks/tests/fixtures/exchange_keys/positive/kucoin-api-key-uuid-26.txt +2 -0
  543. package/.claude/hooks/tests/fixtures/exchange_keys/positive/kucoin-api-secret-uuid-27.txt +1 -0
  544. package/.claude/hooks/tests/fixtures/exchange_keys/positive/kucoin-passphrase-28.txt +1 -0
  545. package/.claude/hooks/tests/fixtures/exchange_keys/positive/okx-api-key-uuid-22.txt +1 -0
  546. package/.claude/hooks/tests/fixtures/exchange_keys/positive/okx-api-secret-23.txt +2 -0
  547. package/.claude/hooks/tests/fixtures/exchange_keys/positive/okx-combined-25.txt +4 -0
  548. package/.claude/hooks/tests/fixtures/exchange_keys/positive/okx-passphrase-24.txt +1 -0
  549. package/.claude/hooks/tests/fixtures/hooks/audit_log/in.json +1 -0
  550. package/.claude/hooks/tests/fixtures/hooks/audit_log/out.json +0 -0
  551. package/.claude/hooks/tests/fixtures/hooks/check_agent_spawn/in.json +1 -0
  552. package/.claude/hooks/tests/fixtures/hooks/check_agent_spawn/out.json +1 -0
  553. package/.claude/hooks/tests/fixtures/hooks/check_bash_safety/in.json +1 -0
  554. package/.claude/hooks/tests/fixtures/hooks/check_bash_safety/out.json +1 -0
  555. package/.claude/hooks/tests/fixtures/hooks/check_canonical_edit/in.json +1 -0
  556. package/.claude/hooks/tests/fixtures/hooks/check_canonical_edit/out.json +1 -0
  557. package/.claude/hooks/tests/fixtures/hooks/check_confidence_gate/in.json +1 -0
  558. package/.claude/hooks/tests/fixtures/hooks/check_confidence_gate/out.json +1 -0
  559. package/.claude/hooks/tests/fixtures/hooks/check_plan_edit/in.json +1 -0
  560. package/.claude/hooks/tests/fixtures/hooks/check_plan_edit/out.json +1 -0
  561. package/.claude/hooks/tests/fixtures/hooks/check_read_injection/in.json +1 -0
  562. package/.claude/hooks/tests/fixtures/hooks/check_read_injection/out.json +1 -0
  563. package/.claude/hooks/tests/fixtures/lifecycle/concurrent_interleaved.json +36 -0
  564. package/.claude/hooks/tests/fixtures/lifecycle/orphaned_pre.json +8 -0
  565. package/.claude/hooks/tests/fixtures/lifecycle/paired_bash_post.json +8 -0
  566. package/.claude/hooks/tests/fixtures/lifecycle/paired_bash_pre.json +9 -0
  567. package/.claude/hooks/tests/fixtures/normalized/agent_spawn_chat_completions.json +36 -0
  568. package/.claude/hooks/tests/fixtures/normalized/agent_spawn_compliant.json +24 -0
  569. package/.claude/hooks/tests/fixtures/normalized/agent_spawn_minimal.json +24 -0
  570. package/.claude/hooks/tests/fixtures/normalized/agent_spawn_ollama.json +42 -0
  571. package/.claude/hooks/tests/fixtures/normalized/bash_minimal.json +23 -0
  572. package/.claude/hooks/tests/fixtures/normalized/bash_responses_api.json +32 -0
  573. package/.claude/hooks/tests/fixtures/normalized/bash_safe_command.json +23 -0
  574. package/.claude/hooks/tests/fixtures/normalized/post_audit_event.json +31 -0
  575. package/.claude/hooks/tests/fixtures/output_safety/control/01_random_hash_log.txt +1 -0
  576. package/.claude/hooks/tests/fixtures/output_safety/control/02_docs_mention_email_no_address.txt +1 -0
  577. package/.claude/hooks/tests/fixtures/output_safety/control/03_partial_jwt_two_segments.txt +1 -0
  578. package/.claude/hooks/tests/fixtures/output_safety/control/04_random_11_digits_no_cpf_context.txt +1 -0
  579. package/.claude/hooks/tests/fixtures/output_safety/control/05_credit_card_shape_invalid_luhn.txt +1 -0
  580. package/.claude/hooks/tests/fixtures/output_safety/positive/01_api_key_anthropic.txt +1 -0
  581. package/.claude/hooks/tests/fixtures/output_safety/positive/02_api_key_github_pat_classic.txt +1 -0
  582. package/.claude/hooks/tests/fixtures/output_safety/positive/03_api_key_github_fine_grained.txt +1 -0
  583. package/.claude/hooks/tests/fixtures/output_safety/positive/04_api_key_aws_access_key.txt +1 -0
  584. package/.claude/hooks/tests/fixtures/output_safety/positive/05_api_key_aws_secret_assignment.txt +1 -0
  585. package/.claude/hooks/tests/fixtures/output_safety/positive/06_jwt.txt +1 -0
  586. package/.claude/hooks/tests/fixtures/output_safety/positive/07_bearer.txt +1 -0
  587. package/.claude/hooks/tests/fixtures/output_safety/positive/08_cpf_with_context.txt +1 -0
  588. package/.claude/hooks/tests/fixtures/output_safety/positive/09_cnpj_with_context.txt +1 -0
  589. package/.claude/hooks/tests/fixtures/output_safety/positive/10_credit_card_luhn_valid.txt +1 -0
  590. package/.claude/hooks/tests/fixtures/output_safety/positive/11_email_in_login_context.txt +1 -0
  591. package/.claude/hooks/tests/fixtures/output_safety/positive/12_nfkc_full_width.txt +1 -0
  592. package/.claude/hooks/tests/fixtures/output_safety/positive/13_zero_width_evasion.txt +1 -0
  593. package/.claude/hooks/tests/fixtures/output_safety/positive/14_bidi_evasion.txt +1 -0
  594. package/.claude/hooks/tests/fixtures/output_safety/positive/15_base64_encoded_secret.txt +1 -0
  595. package/.claude/hooks/tests/fixtures/output_scan/scenarios.jsonl +45 -0
  596. package/.claude/hooks/tests/fixtures/sample_payload_clean.json +13 -0
  597. package/.claude/hooks/tests/fixtures/sample_payload_with_secrets.json +12 -0
  598. package/.claude/hooks/tests/mutations/README.md +86 -0
  599. package/.claude/hooks/tests/mutations/__init__.py +14 -0
  600. package/.claude/hooks/tests/mutations/engine_mutations/__init__.py +15 -0
  601. package/.claude/hooks/tests/mutations/engine_mutations/mutation_01_parser_accepts_anchor.py +51 -0
  602. package/.claude/hooks/tests/mutations/engine_mutations/mutation_02_parser_skip_depth_limit.py +38 -0
  603. package/.claude/hooks/tests/mutations/engine_mutations/mutation_03_parser_accept_multi_doc.py +47 -0
  604. package/.claude/hooks/tests/mutations/engine_mutations/mutation_04_parser_accepts_bom.py +41 -0
  605. package/.claude/hooks/tests/mutations/engine_mutations/mutation_05_parser_scalar_len_off_by_one.py +61 -0
  606. package/.claude/hooks/tests/mutations/engine_mutations/mutation_06_parser_accepts_python_tag.py +50 -0
  607. package/.claude/hooks/tests/mutations/engine_mutations/mutation_07_parser_accepts_tab_indent.py +56 -0
  608. package/.claude/hooks/tests/mutations/engine_mutations/mutation_08_compiler_skip_regex_compile.py +45 -0
  609. package/.claude/hooks/tests/mutations/engine_mutations/mutation_09_compiler_regex_pattern_cap_off.py +31 -0
  610. package/.claude/hooks/tests/mutations/engine_mutations/mutation_10_compiler_accept_unknown_form.py +42 -0
  611. package/.claude/hooks/tests/mutations/engine_mutations/mutation_11_compiler_missing_predicate_tolerated.py +79 -0
  612. package/.claude/hooks/tests/mutations/engine_mutations/mutation_12_compiler_duplicate_rule_id_tolerated.py +66 -0
  613. package/.claude/hooks/tests/mutations/engine_mutations/mutation_13_compiler_missing_top_level_key_tolerated.py +46 -0
  614. package/.claude/hooks/tests/mutations/engine_mutations/mutation_14_compiler_schema_version_passthrough.py +43 -0
  615. package/.claude/hooks/tests/mutations/engine_mutations/mutation_15_evaluator_any_empty_returns_true.py +41 -0
  616. package/.claude/hooks/tests/mutations/engine_mutations/mutation_16_evaluator_all_empty_returns_true.py +37 -0
  617. package/.claude/hooks/tests/mutations/engine_mutations/mutation_17_evaluator_not_passthrough.py +37 -0
  618. package/.claude/hooks/tests/mutations/engine_mutations/mutation_18_evaluator_eq_true_on_type_mismatch.py +51 -0
  619. package/.claude/hooks/tests/mutations/engine_mutations/mutation_19_evaluator_regex_match_only.py +43 -0
  620. package/.claude/hooks/tests/mutations/engine_mutations/mutation_20_evaluator_path_under_no_realpath.py +48 -0
  621. package/.claude/hooks/tests/mutations/engine_mutations/mutation_21_evaluator_in_accepts_any.py +37 -0
  622. package/.claude/hooks/tests/mutations/engine_mutations/mutation_22_evaluator_length_off_by_one.py +45 -0
  623. package/.claude/hooks/tests/mutations/engine_mutations/mutation_23_evaluator_first_match_becomes_last.py +66 -0
  624. package/.claude/hooks/tests/mutations/engine_mutations/mutation_24_error_model_wrong_kind_on_parse.py +39 -0
  625. package/.claude/hooks/tests/mutations/engine_mutations/mutation_25_error_model_fail_open_on_load.py +42 -0
  626. package/.claude/hooks/tests/mutations/policy_mutations/__init__.py +16 -0
  627. package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_01_remove_credential_leak.py +49 -0
  628. package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_02_remove_rm_rf.py +44 -0
  629. package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_03_remove_git_reset_hard.py +44 -0
  630. package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_04_remove_git_push_force.py +44 -0
  631. package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_05_reorder_rules.py +59 -0
  632. package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_06_change_reason_enum.py +54 -0
  633. package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_07_default_flipped_to_block.py +56 -0
  634. package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_08_flip_rm_rf_to_allow.py +49 -0
  635. package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_01_remove_illegal_transition.py +79 -0
  636. package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_02_remove_illegal_status.py +80 -0
  637. package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_03_remove_missing_reviewed_at.py +80 -0
  638. package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_04_remove_missing_completed_at.py +80 -0
  639. package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_05_remove_missing_related_commits.py +79 -0
  640. package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_06_remove_missing_abandonment_reason.py +80 -0
  641. package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_07_scope_guard_inverted.py +93 -0
  642. package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_08_default_block.py +90 -0
  643. package/.claude/hooks/tests/probes/test_architect_probe.py +286 -0
  644. package/.claude/hooks/tests/probes/test_canonical_edit_probe.py +190 -0
  645. package/.claude/hooks/tests/probes/test_skill_content_probe.py +219 -0
  646. package/.claude/hooks/tests/test_SessionEnd.py +59 -0
  647. package/.claude/hooks/tests/test_SessionStart.py +42 -0
  648. package/.claude/hooks/tests/test_UserPromptSubmit.py +47 -0
  649. package/.claude/hooks/tests/test_accel_dispatch.py +96 -0
  650. package/.claude/hooks/tests/test_action_required_invariants.py +274 -0
  651. package/.claude/hooks/tests/test_adapter_drift_detector.py +254 -0
  652. package/.claude/hooks/tests/test_adapter_golden.py +198 -0
  653. package/.claude/hooks/tests/test_adequacy_gate.py +86 -0
  654. package/.claude/hooks/tests/test_adr_052_role_to_model_coverage.py +112 -0
  655. package/.claude/hooks/tests/test_adr_058_brainstorm_structure.py +280 -0
  656. package/.claude/hooks/tests/test_adversary_rules_live.py +400 -0
  657. package/.claude/hooks/tests/test_agent_frontmatter.py +377 -0
  658. package/.claude/hooks/tests/test_anti_ceo_overhead.py +591 -0
  659. package/.claude/hooks/tests/test_audit_emit.py +1707 -0
  660. package/.claude/hooks/tests/test_audit_emit_api_contract.py +693 -0
  661. package/.claude/hooks/tests/test_audit_emit_async_flush.py +563 -0
  662. package/.claude/hooks/tests/test_audit_emit_backpressure.py +138 -0
  663. package/.claude/hooks/tests/test_audit_emit_callsite_coverage_matrix.py +101 -0
  664. package/.claude/hooks/tests/test_audit_emit_chain_length.py +357 -0
  665. package/.claude/hooks/tests/test_audit_emit_coverage.py +2679 -0
  666. package/.claude/hooks/tests/test_audit_emit_ghost_action_guard.py +447 -0
  667. package/.claude/hooks/tests/test_audit_emit_plan088_canonical13.py +323 -0
  668. package/.claude/hooks/tests/test_audit_emit_rotation.py +218 -0
  669. package/.claude/hooks/tests/test_audit_emit_veto_v214.py +202 -0
  670. package/.claude/hooks/tests/test_audit_emit_wire_audit.py +699 -0
  671. package/.claude/hooks/tests/test_audit_hmac.py +334 -0
  672. package/.claude/hooks/tests/test_audit_hmac_branch_coverage.py +212 -0
  673. package/.claude/hooks/tests/test_audit_hmac_chain_monotonicity_property.py +136 -0
  674. package/.claude/hooks/tests/test_audit_hmac_coverage_v214.py +358 -0
  675. package/.claude/hooks/tests/test_audit_hmac_hardening.py +302 -0
  676. package/.claude/hooks/tests/test_audit_hmac_rotation_scenarios.py +231 -0
  677. package/.claude/hooks/tests/test_audit_hmac_verify_chain.py +443 -0
  678. package/.claude/hooks/tests/test_audit_log.py +280 -0
  679. package/.claude/hooks/tests/test_audit_log_coverage.py +173 -0
  680. package/.claude/hooks/tests/test_audit_log_path_d.py +516 -0
  681. package/.claude/hooks/tests/test_audit_log_phase1.py +358 -0
  682. package/.claude/hooks/tests/test_audit_log_schema_consistency.py +97 -0
  683. package/.claude/hooks/tests/test_audit_log_security.py +289 -0
  684. package/.claude/hooks/tests/test_audit_log_tokens.py +92 -0
  685. package/.claude/hooks/tests/test_audit_log_v2_7.py +378 -0
  686. package/.claude/hooks/tests/test_audit_log_v2_8_model.py +201 -0
  687. package/.claude/hooks/tests/test_audit_rotation.py +158 -0
  688. package/.claude/hooks/tests/test_audit_stream_verbose_protection.py +86 -0
  689. package/.claude/hooks/tests/test_audit_tokens_content_ban.py +512 -0
  690. package/.claude/hooks/tests/test_auto_boot.py +28 -0
  691. package/.claude/hooks/tests/test_available_models_mirror.py +226 -0
  692. package/.claude/hooks/tests/test_bash_canonical_forensic.py +74 -0
  693. package/.claude/hooks/tests/test_bash_canonical_interceptor.py +79 -0
  694. package/.claude/hooks/tests/test_brotli_passthrough.py +145 -0
  695. package/.claude/hooks/tests/test_byte_identity_fuzzer.py +185 -0
  696. package/.claude/hooks/tests/test_byte_identity_harness.py +953 -0
  697. package/.claude/hooks/tests/test_canonical_guard_typed_exceptions.py +117 -0
  698. package/.claude/hooks/tests/test_canonical_json.py +153 -0
  699. package/.claude/hooks/tests/test_chain_invariants_property.py +132 -0
  700. package/.claude/hooks/tests/test_check_adversary_live.py +149 -0
  701. package/.claude/hooks/tests/test_check_agent_spawn.py +1084 -0
  702. package/.claude/hooks/tests/test_check_agent_spawn_coverage.py +277 -0
  703. package/.claude/hooks/tests/test_check_agent_spawn_effort_token.py +74 -0
  704. package/.claude/hooks/tests/test_check_agent_spawn_import_isolation.py +82 -0
  705. package/.claude/hooks/tests/test_check_agent_spawn_model_routing_mode.py +245 -0
  706. package/.claude/hooks/tests/test_check_agent_spawn_reference_bypass.py +385 -0
  707. package/.claude/hooks/tests/test_check_agent_spawn_routing_promotion.py +302 -0
  708. package/.claude/hooks/tests/test_check_agent_spawn_skill_reference.py +336 -0
  709. package/.claude/hooks/tests/test_check_arbitration_kernel.py +472 -0
  710. package/.claude/hooks/tests/test_check_arbitration_kernel_v214.py +157 -0
  711. package/.claude/hooks/tests/test_check_bash_safety.py +546 -0
  712. package/.claude/hooks/tests/test_check_bash_safety_canonical_matrix.py +336 -0
  713. package/.claude/hooks/tests/test_check_bash_safety_cp_chaining.py +120 -0
  714. package/.claude/hooks/tests/test_check_bash_safety_h5_rewrite.py +462 -0
  715. package/.claude/hooks/tests/test_check_budget.py +580 -0
  716. package/.claude/hooks/tests/test_check_budget_max_tokens.py +397 -0
  717. package/.claude/hooks/tests/test_check_budget_quota_hint.py +115 -0
  718. package/.claude/hooks/tests/test_check_canonical_edit.py +302 -0
  719. package/.claude/hooks/tests/test_check_canonical_edit_coverage.py +370 -0
  720. package/.claude/hooks/tests/test_check_canonical_edit_kernel_v2.py +401 -0
  721. package/.claude/hooks/tests/test_check_canonical_edit_markers.py +473 -0
  722. package/.claude/hooks/tests/test_check_canonical_edit_mcp.py +401 -0
  723. package/.claude/hooks/tests/test_check_canonical_edit_session67_format.py +245 -0
  724. package/.claude/hooks/tests/test_check_codex_filewrite.py +964 -0
  725. package/.claude/hooks/tests/test_check_codex_response.py +419 -0
  726. package/.claude/hooks/tests/test_check_compaction_continuity.py +450 -0
  727. package/.claude/hooks/tests/test_check_confidence_gate.py +326 -0
  728. package/.claude/hooks/tests/test_check_config_change.py +369 -0
  729. package/.claude/hooks/tests/test_check_config_protection.py +364 -0
  730. package/.claude/hooks/tests/test_check_fluency_nudge.py +321 -0
  731. package/.claude/hooks/tests/test_check_mcp_response.py +261 -0
  732. package/.claude/hooks/tests/test_check_output_safety.py +314 -0
  733. package/.claude/hooks/tests/test_check_output_secrets.py +488 -0
  734. package/.claude/hooks/tests/test_check_output_secrets_coverage.py +321 -0
  735. package/.claude/hooks/tests/test_check_pair_rail.py +897 -0
  736. package/.claude/hooks/tests/test_check_pair_rail_decide_canonical.py +297 -0
  737. package/.claude/hooks/tests/test_check_pair_rail_golden.py +362 -0
  738. package/.claude/hooks/tests/test_check_pair_rail_hook_integration.py +120 -0
  739. package/.claude/hooks/tests/test_check_pair_rail_matrix.py +1077 -0
  740. package/.claude/hooks/tests/test_check_plan_edit.py +679 -0
  741. package/.claude/hooks/tests/test_check_plan_edit_stranded.py +310 -0
  742. package/.claude/hooks/tests/test_check_protocol_semver_cascade.py +141 -0
  743. package/.claude/hooks/tests/test_check_protocol_semver_cascade_settings_wired.py +297 -0
  744. package/.claude/hooks/tests/test_check_protocol_semver_cascade_synccascade.py +365 -0
  745. package/.claude/hooks/tests/test_check_read_injection.py +143 -0
  746. package/.claude/hooks/tests/test_check_read_injection_coverage.py +237 -0
  747. package/.claude/hooks/tests/test_check_read_injection_pathbound.py +153 -0
  748. package/.claude/hooks/tests/test_check_scratchpad_access.py +244 -0
  749. package/.claude/hooks/tests/test_check_skill_bootstrap_post.py +256 -0
  750. package/.claude/hooks/tests/test_check_skill_patch_sentinel.py +439 -0
  751. package/.claude/hooks/tests/test_check_skill_reference_read.py +170 -0
  752. package/.claude/hooks/tests/test_check_skill_reference_read_v2.py +388 -0
  753. package/.claude/hooks/tests/test_check_subagent_fabrication.py +54 -0
  754. package/.claude/hooks/tests/test_check_subagent_start.py +505 -0
  755. package/.claude/hooks/tests/test_check_tier_policy.py +48 -0
  756. package/.claude/hooks/tests/test_check_tier_policy_misrouting_24h.py +294 -0
  757. package/.claude/hooks/tests/test_check_webfetch_injection.py +49 -0
  758. package/.claude/hooks/tests/test_claim_producer_pair_end_to_end_loop_perf.py +227 -0
  759. package/.claude/hooks/tests/test_claude_adapter_thinking.py +731 -0
  760. package/.claude/hooks/tests/test_claude_batch_adapter.py +672 -0
  761. package/.claude/hooks/tests/test_closeout_guard.py +184 -0
  762. package/.claude/hooks/tests/test_codex_adapter.py +777 -0
  763. package/.claude/hooks/tests/test_codex_cli_shape.py +217 -0
  764. package/.claude/hooks/tests/test_codex_egress_proof_telemetry.py +214 -0
  765. package/.claude/hooks/tests/test_codex_egress_redact.py +342 -0
  766. package/.claude/hooks/tests/test_codex_egress_redact_outgoing.py +236 -0
  767. package/.claude/hooks/tests/test_codex_reply_multi_turn.py +72 -0
  768. package/.claude/hooks/tests/test_codex_review_user_code.py +44 -0
  769. package/.claude/hooks/tests/test_codex_strict_json.py +123 -0
  770. package/.claude/hooks/tests/test_confidence_gate_producer_pair.py +522 -0
  771. package/.claude/hooks/tests/test_confidence_labels.py +362 -0
  772. package/.claude/hooks/tests/test_contract.py +237 -0
  773. package/.claude/hooks/tests/test_cookbook_advisor_hook.py +208 -0
  774. package/.claude/hooks/tests/test_credentials.py +195 -0
  775. package/.claude/hooks/tests/test_detect_repo_profile_branches.py +116 -0
  776. package/.claude/hooks/tests/test_e2e_hook_chain.py +184 -0
  777. package/.claude/hooks/tests/test_effective_config.py +648 -0
  778. package/.claude/hooks/tests/test_emit_architect_outcome.py +175 -0
  779. package/.claude/hooks/tests/test_env_persist_allowlist.py +365 -0
  780. package/.claude/hooks/tests/test_escalation_signals.py +357 -0
  781. package/.claude/hooks/tests/test_estimation_bayesian_pipeline.py +140 -0
  782. package/.claude/hooks/tests/test_execution_context_deferral.py +222 -0
  783. package/.claude/hooks/tests/test_fail_open_contract.py +118 -0
  784. package/.claude/hooks/tests/test_file_walker.py +332 -0
  785. package/.claude/hooks/tests/test_filelock.py +131 -0
  786. package/.claude/hooks/tests/test_filelock_contract.py +172 -0
  787. package/.claude/hooks/tests/test_find_sentinels_pattern_matrix.py +114 -0
  788. package/.claude/hooks/tests/test_flip_closures.py +219 -0
  789. package/.claude/hooks/tests/test_frontmatter.py +139 -0
  790. package/.claude/hooks/tests/test_git_bypass_guard.py +1095 -0
  791. package/.claude/hooks/tests/test_gpg_verify.py +578 -0
  792. package/.claude/hooks/tests/test_hook_byte_fidelity.py +113 -0
  793. package/.claude/hooks/tests/test_hook_latency.py +245 -0
  794. package/.claude/hooks/tests/test_hook_latency_import.py +178 -0
  795. package/.claude/hooks/tests/test_injection_patterns.py +276 -0
  796. package/.claude/hooks/tests/test_injection_patterns_bypass.py +276 -0
  797. package/.claude/hooks/tests/test_injection_salt.py +191 -0
  798. package/.claude/hooks/tests/test_kernel_subsumes_security_critical_lib.py +88 -0
  799. package/.claude/hooks/tests/test_kill_switch_godmode_enforcing.py +101 -0
  800. package/.claude/hooks/tests/test_latency_report.py +28 -0
  801. package/.claude/hooks/tests/test_lib_canonical_import.py +355 -0
  802. package/.claude/hooks/tests/test_lifecycle_edge_cases.py +565 -0
  803. package/.claude/hooks/tests/test_live_adapters.py +463 -0
  804. package/.claude/hooks/tests/test_live_audit_isolation.py +357 -0
  805. package/.claude/hooks/tests/test_mcp_bearer_friction_buffer.py +276 -0
  806. package/.claude/hooks/tests/test_mcp_bearer_friction_emit.py +117 -0
  807. package/.claude/hooks/tests/test_mcp_canonical_guard.py +1989 -0
  808. package/.claude/hooks/tests/test_mcp_injection_repro_harness.py +437 -0
  809. package/.claude/hooks/tests/test_mcp_injection_scan.py +228 -0
  810. package/.claude/hooks/tests/test_mcp_routing_resolve.py +246 -0
  811. package/.claude/hooks/tests/test_memory_shared.py +412 -0
  812. package/.claude/hooks/tests/test_metrics.py +115 -0
  813. package/.claude/hooks/tests/test_migrated_hooks_fixtures.py +121 -0
  814. package/.claude/hooks/tests/test_model_routing.py +175 -0
  815. package/.claude/hooks/tests/test_model_routing_resolve.py +97 -0
  816. package/.claude/hooks/tests/test_model_routing_resolve_full.py +318 -0
  817. package/.claude/hooks/tests/test_otel_bounded_exporter.py +521 -0
  818. package/.claude/hooks/tests/test_otel_emit.py +243 -0
  819. package/.claude/hooks/tests/test_otel_queue.py +334 -0
  820. package/.claude/hooks/tests/test_otel_wire_defaultoff.py +392 -0
  821. package/.claude/hooks/tests/test_output_scan.py +1119 -0
  822. package/.claude/hooks/tests/test_output_scan_dedup.py +329 -0
  823. package/.claude/hooks/tests/test_output_scan_fixtures.py +136 -0
  824. package/.claude/hooks/tests/test_pair_rail_decide.py +141 -0
  825. package/.claude/hooks/tests/test_payload.py +89 -0
  826. package/.claude/hooks/tests/test_persona_coverage_wire.py +376 -0
  827. package/.claude/hooks/tests/test_persona_routing_enforcing.py +119 -0
  828. package/.claude/hooks/tests/test_phase_c_advisory_audit.py +75 -0
  829. package/.claude/hooks/tests/test_pii_patterns.py +558 -0
  830. package/.claude/hooks/tests/test_plan114_wires.py +468 -0
  831. package/.claude/hooks/tests/test_plan128_emit_wiring.py +74 -0
  832. package/.claude/hooks/tests/test_plan132_codex_review_observe.py +99 -0
  833. package/.claude/hooks/tests/test_plan133_a1_env_guard.py +221 -0
  834. package/.claude/hooks/tests/test_plan133_a2_canonical_skill_unicode.py +359 -0
  835. package/.claude/hooks/tests/test_plan133_a2_invisible_unicode.py +239 -0
  836. package/.claude/hooks/tests/test_plan133_a3_egress_taxonomy.py +221 -0
  837. package/.claude/hooks/tests/test_plan133_e1_adversary.py +360 -0
  838. package/.claude/hooks/tests/test_plan_085_wave_c_callsites_preserved.py +147 -0
  839. package/.claude/hooks/tests/test_plan_091_expected_callsites.py +206 -0
  840. package/.claude/hooks/tests/test_plan_frontmatter.py +217 -0
  841. package/.claude/hooks/tests/test_policy_coverage_residual_session73.py +597 -0
  842. package/.claude/hooks/tests/test_policy_coverage_v214.py +1099 -0
  843. package/.claude/hooks/tests/test_policy_dispatch.py +454 -0
  844. package/.claude/hooks/tests/test_policy_engine.py +791 -0
  845. package/.claude/hooks/tests/test_policy_fuzz_bomb.py +356 -0
  846. package/.claude/hooks/tests/test_policy_golden_error_kinds.py +287 -0
  847. package/.claude/hooks/tests/test_policy_mutations.py +359 -0
  848. package/.claude/hooks/tests/test_policy_preprocessors.py +514 -0
  849. package/.claude/hooks/tests/test_policy_redos_guards.py +393 -0
  850. package/.claude/hooks/tests/test_rag_bridge.py +675 -0
  851. package/.claude/hooks/tests/test_rag_events.py +202 -0
  852. package/.claude/hooks/tests/test_red_team_fixtures.py +427 -0
  853. package/.claude/hooks/tests/test_redact.py +506 -0
  854. package/.claude/hooks/tests/test_redact_redos.py +254 -0
  855. package/.claude/hooks/tests/test_redact_secrets_parity.py +334 -0
  856. package/.claude/hooks/tests/test_replay_determinism.py +263 -0
  857. package/.claude/hooks/tests/test_review_loop.py +28 -0
  858. package/.claude/hooks/tests/test_review_loop_wiring.py +206 -0
  859. package/.claude/hooks/tests/test_route.py +36 -0
  860. package/.claude/hooks/tests/test_rubric_catalogue.py +359 -0
  861. package/.claude/hooks/tests/test_scratchpad_lib.py +259 -0
  862. package/.claude/hooks/tests/test_secret_patterns.py +680 -0
  863. package/.claude/hooks/tests/test_secret_patterns_provenance.py +82 -0
  864. package/.claude/hooks/tests/test_sentinel_session_cache.py +324 -0
  865. package/.claude/hooks/tests/test_sentinel_session_cache_tier1.py +205 -0
  866. package/.claude/hooks/tests/test_sentinel_signers.py +641 -0
  867. package/.claude/hooks/tests/test_session_75_kernel_findings.py +180 -0
  868. package/.claude/hooks/tests/test_session_76_audit_v3_findings.py +493 -0
  869. package/.claude/hooks/tests/test_session_77_audit_v3_backlog_findings.py +644 -0
  870. package/.claude/hooks/tests/test_session_77_round_2_findings.py +135 -0
  871. package/.claude/hooks/tests/test_session_77_round_3_findings.py +159 -0
  872. package/.claude/hooks/tests/test_session_77_round_4_findings.py +120 -0
  873. package/.claude/hooks/tests/test_session_end.py +113 -0
  874. package/.claude/hooks/tests/test_session_start.py +293 -0
  875. package/.claude/hooks/tests/test_skill_unknown_ratio_path_d.py +249 -0
  876. package/.claude/hooks/tests/test_smart_loading_resolver_caching.py +140 -0
  877. package/.claude/hooks/tests/test_spec_context_sanitizer.py +179 -0
  878. package/.claude/hooks/tests/test_spool_drain_contended_skip.py +249 -0
  879. package/.claude/hooks/tests/test_spool_drain_rotation_property_b.py +227 -0
  880. package/.claude/hooks/tests/test_spool_drain_rotation_race.py +395 -0
  881. package/.claude/hooks/tests/test_spool_writer_cache.py +463 -0
  882. package/.claude/hooks/tests/test_state_store.py +302 -0
  883. package/.claude/hooks/tests/test_stop.py +133 -0
  884. package/.claude/hooks/tests/test_streaming_rate_cap.py +108 -0
  885. package/.claude/hooks/tests/test_subagent_dispatch.py +248 -0
  886. package/.claude/hooks/tests/test_subagent_model_override_removed.py +108 -0
  887. package/.claude/hooks/tests/test_team.py +95 -0
  888. package/.claude/hooks/tests/test_template_dogfood_parity.py +106 -0
  889. package/.claude/hooks/tests/test_terminal_compress.py +135 -0
  890. package/.claude/hooks/tests/test_test_env_context_agent_binding.py +140 -0
  891. package/.claude/hooks/tests/test_testing_helper.py +53 -0
  892. package/.claude/hooks/tests/test_thinking_budget_command.py +229 -0
  893. package/.claude/hooks/tests/test_tier_policy_agent_frontmatter.py +421 -0
  894. package/.claude/hooks/tests/test_tier_policy_agent_frontmatter_disposition.py +175 -0
  895. package/.claude/hooks/tests/test_tier_policy_constants.py +336 -0
  896. package/.claude/hooks/tests/test_tier_policy_loader.py +544 -0
  897. package/.claude/hooks/tests/test_tier_policy_loader_fallback_observed.py +169 -0
  898. package/.claude/hooks/tests/test_tier_policy_types.py +270 -0
  899. package/.claude/hooks/tests/test_tokens_lib.py +118 -0
  900. package/.claude/hooks/tests/test_tool_lifecycle.py +598 -0
  901. package/.claude/hooks/tests/test_tool_lifecycle_perf.py +110 -0
  902. package/.claude/hooks/tests/test_turbo_profile.py +28 -0
  903. package/.claude/hooks/tests/test_turbo_sessionstart.py +79 -0
  904. package/.claude/hooks/tests/test_two_writer_chain.py +175 -0
  905. package/.claude/hooks/tests/test_upgrade_retry.py +346 -0
  906. package/.claude/hooks/tests/test_user_prompt_submit.py +254 -0
  907. package/.claude/hooks/tests/test_user_prompt_submit_salt.py +204 -0
  908. package/.claude/hooks/tests/test_verify_after_edit.py +100 -0
  909. package/.claude/hooks/tests/test_veto_floor_bijection.py +174 -0
  910. package/.claude/hooks/tests/test_w5_cookbook_remediation.py +712 -0
  911. package/.claude/hooks/tests/test_w5_scrub_enforcement.py +371 -0
  912. package/.claude/hooks/tests/test_webfetch_injection.py +280 -0
  913. package/.claude/hooks/tests/test_wiredeadmod_estimation_wiring.py +283 -0
  914. package/.claude/hooks/tests/test_wiredeadmod_spawn_wiring.py +303 -0
  915. package/.claude/hooks/tests/test_worktree_writer.py +509 -0
  916. package/.claude/hooks/turbo_profile.py +554 -0
  917. package/.claude/hooks/turbo_sessionstart.py +472 -0
  918. package/.claude/hooks/verify_after_edit.py +281 -0
  919. package/.claude/pitfalls-catalog.yaml +150 -0
  920. package/.claude/plans/AUDIT-LOG-SCHEMA.md +548 -0
  921. package/.claude/plans/DEBATE-SCHEMA.md +539 -0
  922. package/.claude/plans/PLAN-128/AB-PROTOCOL.md +121 -0
  923. package/.claude/plans/PLAN-128/measure-state.sh +101 -0
  924. package/.claude/plans/PLAN-139-canonical-invariants-and-debt-ledger.md +253 -0
  925. package/.claude/plans/PLAN-140/architect/round-1/approved.md +40 -0
  926. package/.claude/plans/PLAN-140-compaction-hook-origin-dropfix.md +95 -0
  927. package/.claude/plans/PLAN-141/architect/round-1/approved.md +28 -0
  928. package/.claude/plans/PLAN-141-mcp-smoke-staging-ruff-tolerance.md +72 -0
  929. package/.claude/plans/PLAN-142/architect/round-1/anonymization-map.md +11 -0
  930. package/.claude/plans/PLAN-142/architect/round-1/consensus.md +95 -0
  931. package/.claude/plans/PLAN-142/architect/round-1/devops-engineer.md +57 -0
  932. package/.claude/plans/PLAN-142/architect/round-1/proposal.md +57 -0
  933. package/.claude/plans/PLAN-142/architect/round-1/security-engineer.md +55 -0
  934. package/.claude/plans/PLAN-142/architect/round-1/vp-engineering.md +58 -0
  935. package/.claude/plans/PLAN-142/architect/round-2/anonymization-map.md +11 -0
  936. package/.claude/plans/PLAN-142/architect/round-2/approved.md +65 -0
  937. package/.claude/plans/PLAN-142/architect/round-2/consensus.md +78 -0
  938. package/.claude/plans/PLAN-142/architect/round-2/devops-engineer.md +58 -0
  939. package/.claude/plans/PLAN-142/architect/round-2/security-engineer.md +56 -0
  940. package/.claude/plans/PLAN-142/architect/round-2/vp-engineering.md +54 -0
  941. package/.claude/plans/PLAN-142/staging/EXECUTION-RUNBOOK.md +74 -0
  942. package/.claude/plans/PLAN-142/staging/STAGING-NOTES.md +63 -0
  943. package/.claude/plans/PLAN-142/staging/check_pair_rail__invoke_and_consume.py.txt +644 -0
  944. package/.claude/plans/PLAN-142/staging/codex_adapter_parsers.py.txt +677 -0
  945. package/.claude/plans/PLAN-142/staging/codex_cli_shape.py +433 -0
  946. package/.claude/plans/PLAN-142-codex-cli-0139-adapter-migration.md +224 -0
  947. package/.claude/plans/PLAN-143/architect/round-1/anonymization-map.md +22 -0
  948. package/.claude/plans/PLAN-143/architect/round-1/consensus.md +108 -0
  949. package/.claude/plans/PLAN-143/architect/round-1/devops-engineer.md +228 -0
  950. package/.claude/plans/PLAN-143/architect/round-1/proposal.md +48 -0
  951. package/.claude/plans/PLAN-143/architect/round-1/security-engineer.md +224 -0
  952. package/.claude/plans/PLAN-143/architect/round-1/vp-engineering.md +166 -0
  953. package/.claude/plans/PLAN-143/patches/PLAN143-item1-env-inventory.NOTE.md +106 -0
  954. package/.claude/plans/PLAN-143/patches/PLAN143-item2-spool-writer-rotate-guard.patch +41 -0
  955. package/.claude/plans/PLAN-143/patches/PLAN143-item3-audit-emit-exit-code.patch +32 -0
  956. package/.claude/plans/PLAN-143-repo-hygiene-debt.md +201 -0
  957. package/.claude/plans/PLAN-SCHEMA.md +870 -0
  958. package/.claude/plans/README.md +208 -0
  959. package/.claude/plans/examples/debate-round-1/consensus.md +166 -0
  960. package/.claude/plans/examples/debate-round-1/devops-engineer.md +133 -0
  961. package/.claude/plans/examples/debate-round-1/proposal.md +66 -0
  962. package/.claude/plans/examples/debate-round-1/security-engineer.md +109 -0
  963. package/.claude/plans/examples/debate-round-1/vp-engineering.md +110 -0
  964. package/.claude/policies/.drift-manifest.json +16 -0
  965. package/.claude/policies/bash-safety.policy.yaml +37 -0
  966. package/.claude/policies/fixtures/.gitkeep +0 -0
  967. package/.claude/policies/fixtures/bash-safety.fixtures.jsonl +46 -0
  968. package/.claude/policies/fixtures/plan-edit.fixtures.jsonl +36 -0
  969. package/.claude/policies/grandfather-cap.policy.yaml +85 -0
  970. package/.claude/policies/plan-edit.policy.yaml +152 -0
  971. package/.claude/policies/rubric-violation-catalogue.yaml +187 -0
  972. package/.claude/policies/schemas/repo-profile-skill-binding.schema.json +126 -0
  973. package/.claude/policies/schemas/repo-profile.schema.json +83 -0
  974. package/.claude/policies/schemas/squad-bundle-frontmatter.schema.json +152 -0
  975. package/.claude/policies/secret-patterns-exchange.yaml +368 -0
  976. package/.claude/policies/smart-loading-cap-table.yaml +34 -0
  977. package/.claude/proposals/.gitkeep +0 -0
  978. package/.claude/proposals/README.md +42 -0
  979. package/.claude/proposals/SP-001-code-review-checklist-2026-04-20.md +65 -0
  980. package/.claude/proposals/SP-001-code-review-checklist-2026-04-20.md.asc +8 -0
  981. package/.claude/proposals/SP-002-security-and-auth-2026-04-20.md +74 -0
  982. package/.claude/proposals/SP-002-security-and-auth-2026-04-20.md.asc +8 -0
  983. package/.claude/proposals/SP-003-design-system-and-components-2026-04-20.md +67 -0
  984. package/.claude/proposals/SP-003-design-system-and-components-2026-04-20.md.asc +8 -0
  985. package/.claude/proposals/SP-004-accessibility-and-wcag-2026-04-20.md +68 -0
  986. package/.claude/proposals/SP-004-accessibility-and-wcag-2026-04-20.md.asc +8 -0
  987. package/.claude/proposals/SP-005-ux-and-user-journeys-2026-04-20.md +63 -0
  988. package/.claude/proposals/SP-005-ux-and-user-journeys-2026-04-20.md.asc +8 -0
  989. package/.claude/proposals/SP-006-chaos-and-resilience-2026-04-20.md +79 -0
  990. package/.claude/proposals/SP-006-chaos-and-resilience-2026-04-20.md.asc +8 -0
  991. package/.claude/proposals/SP-007-ai-llm-orchestration-2026-04-20.md +76 -0
  992. package/.claude/proposals/SP-007-ai-llm-orchestration-2026-04-20.md.asc +8 -0
  993. package/.claude/proposals/SP-008-performance-engineering-2026-04-20.md +82 -0
  994. package/.claude/proposals/SP-008-performance-engineering-2026-04-20.md.asc +8 -0
  995. package/.claude/proposals/SP-009-code-review-checklist-2026-04-20.md +76 -0
  996. package/.claude/proposals/SP-009-code-review-checklist-2026-04-20.md.asc +8 -0
  997. package/.claude/proposals/SP-010-accessibility-and-wcag-adopter-note-2026-04-20.md +77 -0
  998. package/.claude/proposals/SP-010-accessibility-and-wcag-adopter-note-2026-04-20.md.asc +8 -0
  999. package/.claude/proposals/SP-011-design-system-and-components-adopter-note-2026-04-20.md +79 -0
  1000. package/.claude/proposals/SP-011-design-system-and-components-adopter-note-2026-04-20.md.asc +8 -0
  1001. package/.claude/proposals/SP-012-ux-and-user-journeys-adopter-note-2026-04-20.md +83 -0
  1002. package/.claude/proposals/SP-012-ux-and-user-journeys-adopter-note-2026-04-20.md.asc +8 -0
  1003. package/.claude/proposals/SP-013-frontend-performance-optimization-2026-04-20.md +82 -0
  1004. package/.claude/proposals/SP-013-frontend-performance-optimization-2026-04-20.md.asc +8 -0
  1005. package/.claude/proposals/SP-014-observability-and-ops-2026-04-20.md +80 -0
  1006. package/.claude/proposals/SP-014-observability-and-ops-2026-04-20.md.asc +8 -0
  1007. package/.claude/proposals/SP-015-testing-strategy-2026-04-20.md +87 -0
  1008. package/.claude/proposals/SP-015-testing-strategy-2026-04-20.md.asc +8 -0
  1009. package/.claude/proposals/SP-016-code-review-checklist-fluency-rubric-2026-04-28.md +111 -0
  1010. package/.claude/proposals/SP-016-code-review-checklist-fluency-rubric-2026-04-28.md.asc +8 -0
  1011. package/.claude/proposals/SP-017-chaos-and-resilience-adopter-note-2026-04-28.md +87 -0
  1012. package/.claude/proposals/SP-017-chaos-and-resilience-adopter-note-2026-04-28.md.asc +8 -0
  1013. package/.claude/proposals/SP-018-ceo-orchestration-inventory-regen-2026-04-21.md +64 -0
  1014. package/.claude/proposals/SP-018-ceo-orchestration-inventory-regen-2026-04-21.md.asc +8 -0
  1015. package/.claude/proposals/SP-019-terse-mode-2026-04-21.md +107 -0
  1016. package/.claude/proposals/SP-019-terse-mode-2026-04-21.md.asc +8 -0
  1017. package/.claude/proposals/SP-020-ceo-orchestration-audit-tokens-2026-04-21.md +74 -0
  1018. package/.claude/proposals/SP-020-ceo-orchestration-audit-tokens-2026-04-21.md.asc +8 -0
  1019. package/.claude/proposals/SP-021-ceo-orchestration-autonomous-loop-2026-04-21.md +71 -0
  1020. package/.claude/proposals/SP-021-ceo-orchestration-autonomous-loop-2026-04-21.md.asc +8 -0
  1021. package/.claude/rag/_index_core.py +344 -0
  1022. package/.claude/rag/indexignore +101 -0
  1023. package/.claude/rag/install-sidecar.sh +275 -0
  1024. package/.claude/rag/models.manifest.json +19 -0
  1025. package/.claude/rag/requirements.lock +40 -0
  1026. package/.claude/rag/sidecar-config.template.json +53 -0
  1027. package/.claude/rag/tests/test_index_core.py +262 -0
  1028. package/.claude/rag/tests/test_install_sidecar.sh +132 -0
  1029. package/.claude/scripts/.known_actions_floor.lock +0 -0
  1030. package/.claude/scripts/admin-invite.py +199 -0
  1031. package/.claude/scripts/adopter-metrics.py +712 -0
  1032. package/.claude/scripts/aek-calibration-c2.py +253 -0
  1033. package/.claude/scripts/aek-calibration-c3.py +382 -0
  1034. package/.claude/scripts/aggregate-changesets.py +350 -0
  1035. package/.claude/scripts/architect-bundle-validate.py +227 -0
  1036. package/.claude/scripts/audit-dashboard.py +1320 -0
  1037. package/.claude/scripts/audit-log-labels.jsonl +0 -0
  1038. package/.claude/scripts/audit-log-retain.py +404 -0
  1039. package/.claude/scripts/audit-query.py +3333 -0
  1040. package/.claude/scripts/audit-telemetry.py +337 -0
  1041. package/.claude/scripts/audit-tokens.py +502 -0
  1042. package/.claude/scripts/audit-verify-chain.py +537 -0
  1043. package/.claude/scripts/backup-audit.py +247 -0
  1044. package/.claude/scripts/benchmark/plan-071-import-floor/README.md +194 -0
  1045. package/.claude/scripts/benchmark/plan-071-import-floor/fixtures/baseline.json +1 -0
  1046. package/.claude/scripts/benchmark/plan-071-import-floor/fixtures/expected_quantiles.json +11 -0
  1047. package/.claude/scripts/benchmark/plan-071-import-floor/import_floor_bench.py +791 -0
  1048. package/.claude/scripts/benchmark/plan-071-import-floor/run_bench.sh +180 -0
  1049. package/.claude/scripts/benchmark-fallback-scorer.py +254 -0
  1050. package/.claude/scripts/benchmark-judge.py +621 -0
  1051. package/.claude/scripts/budget-summary.py +946 -0
  1052. package/.claude/scripts/build-canonical-models.py +645 -0
  1053. package/.claude/scripts/calibration-kappa.py +262 -0
  1054. package/.claude/scripts/cc-analytics-pull.py +393 -0
  1055. package/.claude/scripts/ceo-backup.sh +307 -0
  1056. package/.claude/scripts/ceo-boot.py +3017 -0
  1057. package/.claude/scripts/ceo-cost.py +1116 -0
  1058. package/.claude/scripts/ceo-diagnose.py +486 -0
  1059. package/.claude/scripts/ceo-escalation-detector.py +743 -0
  1060. package/.claude/scripts/ceo-health.py +584 -0
  1061. package/.claude/scripts/ceo-info.py +1001 -0
  1062. package/.claude/scripts/ceo-restore.sh +215 -0
  1063. package/.claude/scripts/chaos-inject.py +439 -0
  1064. package/.claude/scripts/check-action-sha-drift.py +275 -0
  1065. package/.claude/scripts/check-active-hooks-executable.py +119 -0
  1066. package/.claude/scripts/check-adr-chain.py +617 -0
  1067. package/.claude/scripts/check-audit-action-name-convention.py +221 -0
  1068. package/.claude/scripts/check-audit-hmac-null.py +253 -0
  1069. package/.claude/scripts/check-audit-read-api-stable.py +239 -0
  1070. package/.claude/scripts/check-audit-registry-coverage.py +999 -0
  1071. package/.claude/scripts/check-auto-activation-flags.py +180 -0
  1072. package/.claude/scripts/check-canonical-doc-freshness.py +222 -0
  1073. package/.claude/scripts/check-claude-md-claims.py +346 -0
  1074. package/.claude/scripts/check-confidence-gate-drift.py +295 -0
  1075. package/.claude/scripts/check-conformance-harness-mapping.py +503 -0
  1076. package/.claude/scripts/check-contamination.sh +25 -0
  1077. package/.claude/scripts/check-creative-rewrite.py +596 -0
  1078. package/.claude/scripts/check-debate-round-lifecycle.py +185 -0
  1079. package/.claude/scripts/check-debt-ledger.py +305 -0
  1080. package/.claude/scripts/check-docs-drift.py +259 -0
  1081. package/.claude/scripts/check-docs-freshness.py +487 -0
  1082. package/.claude/scripts/check-flip-criteria-drift.py +426 -0
  1083. package/.claude/scripts/check-flip-release-gate-consistency.py +134 -0
  1084. package/.claude/scripts/check-framework-updates.sh +239 -0
  1085. package/.claude/scripts/check-function-length.py +426 -0
  1086. package/.claude/scripts/check-model-deprecations.py +377 -0
  1087. package/.claude/scripts/check-originator-residue.py +248 -0
  1088. package/.claude/scripts/check-pitfall-regression.sh +153 -0
  1089. package/.claude/scripts/check-policy-drift.py +74 -0
  1090. package/.claude/scripts/check-roadmap-binding.py +170 -0
  1091. package/.claude/scripts/check-rule-invariants.py +385 -0
  1092. package/.claude/scripts/check-sdk-compat.sh +76 -0
  1093. package/.claude/scripts/check-secret-pattern-coverage.py +175 -0
  1094. package/.claude/scripts/check-sidecar-manifest.py +493 -0
  1095. package/.claude/scripts/check-skill-activation-mode.py +41 -0
  1096. package/.claude/scripts/check-skill-health.sh +179 -0
  1097. package/.claude/scripts/check-spec-drift.py +147 -0
  1098. package/.claude/scripts/check-staleness.py +506 -0
  1099. package/.claude/scripts/check-stdlib-only.py +373 -0
  1100. package/.claude/scripts/check-substrate-watch.py +285 -0
  1101. package/.claude/scripts/check-swarm-harness-mapping.py +380 -0
  1102. package/.claude/scripts/check-test-audit-isolation.py +622 -0
  1103. package/.claude/scripts/check-test-env-hygiene.py +509 -0
  1104. package/.claude/scripts/check-threat-model-freshness.py +313 -0
  1105. package/.claude/scripts/check-tier-boundaries.py +233 -0
  1106. package/.claude/scripts/check-tla-schema-drift.py +272 -0
  1107. package/.claude/scripts/check_atlas_fpr.py +595 -0
  1108. package/.claude/scripts/check_contamination.py +337 -0
  1109. package/.claude/scripts/check_known_actions_floor.py +155 -0
  1110. package/.claude/scripts/check_threat_model_coverage.py +214 -0
  1111. package/.claude/scripts/check_translations_drift.py +199 -0
  1112. package/.claude/scripts/codex_invoke.py +436 -0
  1113. package/.claude/scripts/compare-adopters.py +549 -0
  1114. package/.claude/scripts/confidence-gate-backfill.py +261 -0
  1115. package/.claude/scripts/confidence_gate.py +736 -0
  1116. package/.claude/scripts/context-budget.py +1887 -0
  1117. package/.claude/scripts/contextual-recommender.py +815 -0
  1118. package/.claude/scripts/cost-table.yaml +99 -0
  1119. package/.claude/scripts/debate-converge.py +335 -0
  1120. package/.claude/scripts/debate-emit.py +132 -0
  1121. package/.claude/scripts/debate-orchestrate.py +972 -0
  1122. package/.claude/scripts/detect-repo-profile.py +1280 -0
  1123. package/.claude/scripts/detectors/__init__.py +19 -0
  1124. package/.claude/scripts/detectors/looping.py +127 -0
  1125. package/.claude/scripts/detectors/overpowered.py +96 -0
  1126. package/.claude/scripts/detectors/retry_churn.py +119 -0
  1127. package/.claude/scripts/detectors/schema.py +94 -0
  1128. package/.claude/scripts/detectors/tests/__init__.py +0 -0
  1129. package/.claude/scripts/detectors/tests/fixtures.py +420 -0
  1130. package/.claude/scripts/detectors/tests/test_looping.py +124 -0
  1131. package/.claude/scripts/detectors/tests/test_overpowered.py +114 -0
  1132. package/.claude/scripts/detectors/tests/test_retry_churn.py +101 -0
  1133. package/.claude/scripts/detectors/tests/test_schema.py +109 -0
  1134. package/.claude/scripts/detectors/tests/test_tool_cascade.py +131 -0
  1135. package/.claude/scripts/detectors/tests/test_wasteful_thinking.py +112 -0
  1136. package/.claude/scripts/detectors/tests/test_weak_model.py +104 -0
  1137. package/.claude/scripts/detectors/tool_cascade.py +127 -0
  1138. package/.claude/scripts/detectors/wasteful_thinking.py +99 -0
  1139. package/.claude/scripts/detectors/weak_model.py +92 -0
  1140. package/.claude/scripts/env-inventory-check.py +268 -0
  1141. package/.claude/scripts/env-inventory.json +3305 -0
  1142. package/.claude/scripts/extract-skill.py +456 -0
  1143. package/.claude/scripts/fan-plan-parser.py +370 -0
  1144. package/.claude/scripts/find-orphan-sentinels.py +89 -0
  1145. package/.claude/scripts/first-run-wizard.py +1151 -0
  1146. package/.claude/scripts/fixtures/cloned-trading-repo/.env.example +1 -0
  1147. package/.claude/scripts/fixtures/cloned-trading-repo/exchanges/binance.py +3 -0
  1148. package/.claude/scripts/fixtures/cloned-trading-repo/exchanges/coinbase.py +3 -0
  1149. package/.claude/scripts/fixtures/cloned-trading-repo/package.json +5 -0
  1150. package/.claude/scripts/fixtures/cloned-trading-repo/strategies/grid.py +3 -0
  1151. package/.claude/scripts/fixtures/cloned-trading-repo/strategies/pairs.py +3 -0
  1152. package/.claude/scripts/fixtures/missing-package-manifest/README.md +3 -0
  1153. package/.claude/scripts/fixtures/missing-package-manifest/src/main.py +1 -0
  1154. package/.claude/scripts/fixtures/mixed-frontend-backend/package.json +9 -0
  1155. package/.claude/scripts/fixtures/mixed-frontend-backend/requirements.txt +2 -0
  1156. package/.claude/scripts/fixtures/mixed-frontend-backend/src/api/handler.py +2 -0
  1157. package/.claude/scripts/fixtures/mixed-frontend-backend/src/pages/index.tsx +1 -0
  1158. package/.claude/scripts/fixtures/monorepo/apps/app-a/README.md +1 -0
  1159. package/.claude/scripts/fixtures/monorepo/apps/app-b/index.ts +1 -0
  1160. package/.claude/scripts/fixtures/monorepo/package.json +5 -0
  1161. package/.claude/scripts/fixtures/monorepo/packages/lib-a/index.js +1 -0
  1162. package/.claude/scripts/fixtures/monorepo/packages/lib-b/index.js +1 -0
  1163. package/.claude/scripts/fixtures/monorepo/pnpm-workspace.yaml +3 -0
  1164. package/.claude/scripts/fixtures/persona-coverage-expected-thresholds.yaml +20 -0
  1165. package/.claude/scripts/flip-criteria-drift-allowlist.txt +31 -0
  1166. package/.claude/scripts/generate-adr-index.py +339 -0
  1167. package/.claude/scripts/generate-available-models.py +280 -0
  1168. package/.claude/scripts/generate-dispatch.py +430 -0
  1169. package/.claude/scripts/generate-sbom.py +287 -0
  1170. package/.claude/scripts/generate-skill-inventory.sh +193 -0
  1171. package/.claude/scripts/github-api-client.py +297 -0
  1172. package/.claude/scripts/goap-planner.py +742 -0
  1173. package/.claude/scripts/hook-profiler.py +671 -0
  1174. package/.claude/scripts/import-skill.py +569 -0
  1175. package/.claude/scripts/import_ui_ux_pro_max.py +137 -0
  1176. package/.claude/scripts/inject-agent-context.sh +948 -0
  1177. package/.claude/scripts/k-calibration.py +456 -0
  1178. package/.claude/scripts/key-hygiene.py +511 -0
  1179. package/.claude/scripts/lesson-restore.py +171 -0
  1180. package/.claude/scripts/lesson_ranker.py +100 -0
  1181. package/.claude/scripts/lessons.py +883 -0
  1182. package/.claude/scripts/lint-skills.py +555 -0
  1183. package/.claude/scripts/local/README.md +280 -0
  1184. package/.claude/scripts/local/check-doc-skill-paths.sh +124 -0
  1185. package/.claude/scripts/local/dependency-graph.py +684 -0
  1186. package/.claude/scripts/local/estimate-calibrator.py +240 -0
  1187. package/.claude/scripts/local/findings-pretty-print.py +78 -0
  1188. package/.claude/scripts/local/generate-ceremony.sh +558 -0
  1189. package/.claude/scripts/local/pair-rail-gate.sh +156 -0
  1190. package/.claude/scripts/local/release-dry-run.py +853 -0
  1191. package/.claude/scripts/local/tests/test_dependency_graph.py +364 -0
  1192. package/.claude/scripts/local/tests/test_generate_ceremony.sh +144 -0
  1193. package/.claude/scripts/local/tests/test_release_dry_run.py +743 -0
  1194. package/.claude/scripts/local/validate-findings.py +168 -0
  1195. package/.claude/scripts/local/validate-saved-workflows.js +69 -0
  1196. package/.claude/scripts/local/verify-counts.sh +420 -0
  1197. package/.claude/scripts/local/verify-scope-coverage.py +205 -0
  1198. package/.claude/scripts/local/verify-staging-manifest.py +188 -0
  1199. package/.claude/scripts/local/wave-readonly-monitor.py +271 -0
  1200. package/.claude/scripts/log-friction.sh +290 -0
  1201. package/.claude/scripts/mcp/code_nav_bridge.py +259 -0
  1202. package/.claude/scripts/mcp-server/__init__.py +16 -0
  1203. package/.claude/scripts/mcp-server/auth.py +333 -0
  1204. package/.claude/scripts/mcp-server/cost.py +108 -0
  1205. package/.claude/scripts/mcp-server/dispatch.py +853 -0
  1206. package/.claude/scripts/mcp-server/handlers/__init__.py +16 -0
  1207. package/.claude/scripts/mcp-server/handlers/audit_query.py +384 -0
  1208. package/.claude/scripts/mcp-server/handlers/get_audit_log.py +163 -0
  1209. package/.claude/scripts/mcp-server/handlers/get_cost_budget.py +130 -0
  1210. package/.claude/scripts/mcp-server/handlers/get_debate_state.py +207 -0
  1211. package/.claude/scripts/mcp-server/handlers/get_skill.py +199 -0
  1212. package/.claude/scripts/mcp-server/handlers/list_agents.py +236 -0
  1213. package/.claude/scripts/mcp-server/handlers/list_pitfalls.py +192 -0
  1214. package/.claude/scripts/mcp-server/handlers/list_skills.py +197 -0
  1215. package/.claude/scripts/mcp-server/handlers/plan_status.py +489 -0
  1216. package/.claude/scripts/mcp-server/handlers/server_capabilities.py +127 -0
  1217. package/.claude/scripts/mcp-server/handlers/spawn_agent.py +274 -0
  1218. package/.claude/scripts/mcp-server/http_transport.py +373 -0
  1219. package/.claude/scripts/mcp-server/rate_limit.py +345 -0
  1220. package/.claude/scripts/mcp-server/server.py +212 -0
  1221. package/.claude/scripts/mcp-server/start-mcp-server.sh +111 -0
  1222. package/.claude/scripts/mcp-server/stdio_transport.py +150 -0
  1223. package/.claude/scripts/mcp-server/tests/__init__.py +1 -0
  1224. package/.claude/scripts/mcp-server/tests/test_auth.py +454 -0
  1225. package/.claude/scripts/mcp-server/tests/test_cost.py +122 -0
  1226. package/.claude/scripts/mcp-server/tests/test_dispatch.py +448 -0
  1227. package/.claude/scripts/mcp-server/tests/test_dispatch_bearer_replay_wire.py +358 -0
  1228. package/.claude/scripts/mcp-server/tests/test_handlers_get_audit_log.py +107 -0
  1229. package/.claude/scripts/mcp-server/tests/test_handlers_get_skill.py +108 -0
  1230. package/.claude/scripts/mcp-server/tests/test_handlers_list_agents.py +92 -0
  1231. package/.claude/scripts/mcp-server/tests/test_handlers_list_pitfalls.py +103 -0
  1232. package/.claude/scripts/mcp-server/tests/test_handlers_list_skills.py +121 -0
  1233. package/.claude/scripts/mcp-server/tests/test_handlers_server_capabilities.py +128 -0
  1234. package/.claude/scripts/mcp-server/tests/test_handlers_spawn_agent.py +275 -0
  1235. package/.claude/scripts/mcp-server/tests/test_http_transport.py +418 -0
  1236. package/.claude/scripts/mcp-server/tests/test_rate_limit.py +239 -0
  1237. package/.claude/scripts/mcp-server/tests/test_server.py +125 -0
  1238. package/.claude/scripts/mcp-server/tests/test_stdio_transport.py +196 -0
  1239. package/.claude/scripts/mcp-soak-monitor.py +224 -0
  1240. package/.claude/scripts/memory-prioritize.py +516 -0
  1241. package/.claude/scripts/migrate-grandfather-to-sha256.py +384 -0
  1242. package/.claude/scripts/model-deprecations.json +165 -0
  1243. package/.claude/scripts/morning-ceremony.py +266 -0
  1244. package/.claude/scripts/morning_ledger.py +446 -0
  1245. package/.claude/scripts/mutation-floors.yaml +51 -0
  1246. package/.claude/scripts/mutation-test.py +506 -0
  1247. package/.claude/scripts/nightly-proposals.py +210 -0
  1248. package/.claude/scripts/optimizer/__init__.py +46 -0
  1249. package/.claude/scripts/optimizer/_codex_redaction.py +101 -0
  1250. package/.claude/scripts/optimizer/_skeleton.py +137 -0
  1251. package/.claude/scripts/optimizer/codex_phase_gate.py +257 -0
  1252. package/.claude/scripts/optimizer/complexity_gate.py +208 -0
  1253. package/.claude/scripts/optimizer/fanout.py +249 -0
  1254. package/.claude/scripts/optimizer/model_choice.py +151 -0
  1255. package/.claude/scripts/optimizer/model_normalize.py +118 -0
  1256. package/.claude/scripts/optimizer/rag_recommender.py +110 -0
  1257. package/.claude/scripts/optimizer/recommender.py +213 -0
  1258. package/.claude/scripts/optimizer/tests/__init__.py +0 -0
  1259. package/.claude/scripts/optimizer/tests/test_codex_phase_gate.py +314 -0
  1260. package/.claude/scripts/optimizer/tests/test_codex_review_invoked_emission.py +225 -0
  1261. package/.claude/scripts/optimizer/tests/test_optimizer_complexity_gate.py +122 -0
  1262. package/.claude/scripts/optimizer/tests/test_optimizer_fanout.py +134 -0
  1263. package/.claude/scripts/optimizer/tests/test_optimizer_model_choice.py +124 -0
  1264. package/.claude/scripts/optimizer/tests/test_optimizer_model_normalize.py +155 -0
  1265. package/.claude/scripts/optimizer/tests/test_optimizer_rag_recommender.py +190 -0
  1266. package/.claude/scripts/optimizer/tests/test_optimizer_recommender.py +131 -0
  1267. package/.claude/scripts/optimizer/tests/test_optimizer_skeleton.py +117 -0
  1268. package/.claude/scripts/optimizer/tests/test_optimizer_types.py +53 -0
  1269. package/.claude/scripts/optimizer/types.py +122 -0
  1270. package/.claude/scripts/osv_check.py +559 -0
  1271. package/.claude/scripts/otel-export.py +329 -0
  1272. package/.claude/scripts/otel-local-sink.py +470 -0
  1273. package/.claude/scripts/persona_demand_resolver.py +658 -0
  1274. package/.claude/scripts/persona_demand_scan.py +382 -0
  1275. package/.claude/scripts/persona_waive_parser.py +127 -0
  1276. package/.claude/scripts/pitfall-query.py +218 -0
  1277. package/.claude/scripts/plan-tokens.py +843 -0
  1278. package/.claude/scripts/policy-shadow-runner.py +445 -0
  1279. package/.claude/scripts/predict-budget/predict-plan-cost.py +581 -0
  1280. package/.claude/scripts/predict-budget/tests/test_predict_plan_cost.py +375 -0
  1281. package/.claude/scripts/profile-opus-4-7.py +557 -0
  1282. package/.claude/scripts/prune-lessons.py +453 -0
  1283. package/.claude/scripts/rate-card-calibrate.py +283 -0
  1284. package/.claude/scripts/rate-card-fixtures.json +18 -0
  1285. package/.claude/scripts/reality-ledger.py +2175 -0
  1286. package/.claude/scripts/red-team-corpus/.byte-identity-check.txt +86 -0
  1287. package/.claude/scripts/red-team-corpus/README.md +132 -0
  1288. package/.claude/scripts/red-team-corpus/external/EXT-001-prompt-inject.md +24 -0
  1289. package/.claude/scripts/red-team-corpus/external/EXT-002-hackaprompt.md +25 -0
  1290. package/.claude/scripts/red-team-corpus/external/EXT-003-gcg.md +31 -0
  1291. package/.claude/scripts/red-team-corpus/external/EXT-004-tap.md +23 -0
  1292. package/.claude/scripts/red-team-corpus/external/EXT-005-cybersecurity-eval.md +30 -0
  1293. package/.claude/scripts/red-team-corpus/external/EXT-006-anthropic-samples.md +26 -0
  1294. package/.claude/scripts/red-team-corpus/external/EXT-007-trojan-source.md +26 -0
  1295. package/.claude/scripts/red-team-corpus/external/EXT-008-owasp-llm-top10.md +33 -0
  1296. package/.claude/scripts/red-team-corpus/external/EXT-009-jailbreak-bench.md +24 -0
  1297. package/.claude/scripts/red-team-corpus/external/EXT-010-advbench.md +22 -0
  1298. package/.claude/scripts/red-team-corpus/external/EXT-011-mitre-atlas.md +25 -0
  1299. package/.claude/scripts/red-team-corpus/external/EXT-012-npm-typosquat.md +23 -0
  1300. package/.claude/scripts/red-team-corpus/external/EXT-013-log-tamper-poc.md +25 -0
  1301. package/.claude/scripts/red-team-corpus/external/EXT-014-cwe-798-credentials.md +24 -0
  1302. package/.claude/scripts/red-team-corpus/external/EXT-015-garak.md +28 -0
  1303. package/.claude/scripts/red-team-corpus/external/EXT-016-skill-content-injection-via-markdown.jsonl +1 -0
  1304. package/.claude/scripts/red-team-corpus/external/EXT-017-persona-impersonation-ceo.jsonl +1 -0
  1305. package/.claude/scripts/red-team-corpus/external/EXT-018-file-assignment-wildcard-escape.jsonl +1 -0
  1306. package/.claude/scripts/red-team-corpus/external/EXT-019-veto-bypass-force-proceed.jsonl +1 -0
  1307. package/.claude/scripts/red-team-corpus/external/EXT-020-canonical-edit-circumvent-settings.jsonl +1 -0
  1308. package/.claude/scripts/red-team-corpus/external/EXT-021-spawn-without-agent-profile.jsonl +1 -0
  1309. package/.claude/scripts/red-team-corpus/external/EXT-022-hidden-unicode-in-skill-name.jsonl +1 -0
  1310. package/.claude/scripts/red-team-corpus/external/EXT-023-mcp-spawn-governance-bypass.jsonl +1 -0
  1311. package/.claude/scripts/red-team-corpus/external/EXT-024-adapter-credential-in-error-trace.jsonl +1 -0
  1312. package/.claude/scripts/red-team-corpus/external/EXT-025-sandbox-escape-nested-subshell.jsonl +1 -0
  1313. package/.claude/scripts/red-team-corpus/external/EXT-026-plan-edit-without-debate.jsonl +1 -0
  1314. package/.claude/scripts/red-team-corpus/external/EXT-027-audit-log-rotation-race.jsonl +1 -0
  1315. package/.claude/scripts/red-team-corpus/external/EXT-028-npm-dependency-confusion.jsonl +1 -0
  1316. package/.claude/scripts/red-team-corpus/external/EXT-029-output-safety-unicode-confusable.jsonl +1 -0
  1317. package/.claude/scripts/red-team-corpus/external/EXT-030-adapter-retry-storm-dos.jsonl +1 -0
  1318. package/.claude/scripts/red-team-corpus/external/EXT-031-team-md-direct-edit.jsonl +1 -0
  1319. package/.claude/scripts/red-team-corpus/external/EXT-032-sandbox-env-var-exfil.jsonl +1 -0
  1320. package/.claude/scripts/red-team-corpus/external/EXT-033-mcp-rate-limit-bypass-headers.jsonl +1 -0
  1321. package/.claude/scripts/red-team-corpus/external/EXT-034-otel-span-attribute-leak.jsonl +1 -0
  1322. package/.claude/scripts/red-team-corpus/external/EXT-035-skill-patch-polyglot-payload.jsonl +1 -0
  1323. package/.claude/scripts/red-team-corpus/external/EXT-036-output-safety-base64-triple-wrap.jsonl +1 -0
  1324. package/.claude/scripts/red-team-corpus/external/EXT-037-plan-id-cross-plan-memory-read.jsonl +1 -0
  1325. package/.claude/scripts/red-team-corpus/external/EXT-038-npm-slsa-provenance-strip.jsonl +1 -0
  1326. package/.claude/scripts/red-team-corpus/external/EXT-039-adapter-exfil-streaming-chunk.jsonl +1 -0
  1327. package/.claude/scripts/red-team-corpus/external/EXT-040-sandbox-symlink-to-secrets.jsonl +1 -0
  1328. package/.claude/scripts/red-team-corpus/external/README.md +63 -0
  1329. package/.claude/scripts/red-team-corpus/flake-budget.yaml +244 -0
  1330. package/.claude/scripts/red-team-corpus/provenance.md +74 -0
  1331. package/.claude/scripts/red-team-corpus/regression/REG-001-s3-audit-emission-gap.jsonl +1 -0
  1332. package/.claude/scripts/red-team-corpus/regression/REG-002-audit-registry-miss.jsonl +1 -0
  1333. package/.claude/scripts/red-team-corpus/regression/REG-003-breaker-provider-kwarg-missing.jsonl +1 -0
  1334. package/.claude/scripts/red-team-corpus/regression/REG-004-canonical-edit-conftest-block.jsonl +1 -0
  1335. package/.claude/scripts/red-team-corpus/regression/REG-005-mcp-dispatch-oversized-handler.jsonl +1 -0
  1336. package/.claude/scripts/red-team-corpus/regression/REG-006-audit-registry-false-orphan.jsonl +1 -0
  1337. package/.claude/scripts/red-team-corpus/regression/REG-007-spec-count-undercount.jsonl +1 -0
  1338. package/.claude/scripts/red-team-corpus/regression/REG-008-adr-reserved-slot-phantom.jsonl +1 -0
  1339. package/.claude/scripts/red-team-corpus/regression/REG-009-tlc-pending-placeholder.jsonl +1 -0
  1340. package/.claude/scripts/red-team-corpus/regression/REG-010-mutation-kill-rate-fake.jsonl +1 -0
  1341. package/.claude/scripts/red-team-corpus/regression/REG-011-byte-identity-governance-persona.jsonl +1 -0
  1342. package/.claude/scripts/red-team-corpus/regression/REG-012-conformance-mapping-partial-path.jsonl +1 -0
  1343. package/.claude/scripts/red-team-corpus/regression/REG-013-l1-fairness-lazy-fire.jsonl +1 -0
  1344. package/.claude/scripts/red-team-corpus/regression/REG-014-mcp-path-traversal-skill.jsonl +1 -0
  1345. package/.claude/scripts/red-team-corpus/regression/REG-015-mcp-hmac-timestamp-skew.jsonl +1 -0
  1346. package/.claude/scripts/red-team-corpus/synthetic/SYN-001-skill-patch-bidi-trojan.jsonl +1 -0
  1347. package/.claude/scripts/red-team-corpus/synthetic/SYN-002-skill-patch-zero-width-smuggle.jsonl +1 -0
  1348. package/.claude/scripts/red-team-corpus/synthetic/SYN-003-skill-patch-exec-smuggled-fence.jsonl +1 -0
  1349. package/.claude/scripts/red-team-corpus/synthetic/SYN-004-skill-patch-oversized-diff.jsonl +1 -0
  1350. package/.claude/scripts/red-team-corpus/synthetic/SYN-005-audit-log-byte-rewrite.jsonl +1 -0
  1351. package/.claude/scripts/red-team-corpus/synthetic/SYN-006-audit-log-truncation.jsonl +1 -0
  1352. package/.claude/scripts/red-team-corpus/synthetic/SYN-007-audit-log-lock-race.jsonl +1 -0
  1353. package/.claude/scripts/red-team-corpus/synthetic/SYN-008-plan-id-env-spoof.jsonl +1 -0
  1354. package/.claude/scripts/red-team-corpus/synthetic/SYN-009-plan-id-frontmatter-hijack.jsonl +1 -0
  1355. package/.claude/scripts/red-team-corpus/synthetic/SYN-010-plan-id-cross-plan-read.jsonl +1 -0
  1356. package/.claude/scripts/red-team-corpus/synthetic/SYN-011-sandbox-escape-curl-exfil.jsonl +1 -0
  1357. package/.claude/scripts/red-team-corpus/synthetic/SYN-012-sandbox-escape-env-dump.jsonl +1 -0
  1358. package/.claude/scripts/red-team-corpus/synthetic/SYN-013-sandbox-escape-symlink-plant.jsonl +1 -0
  1359. package/.claude/scripts/red-team-corpus/synthetic/SYN-014-mcp-handler-governance-bypass.jsonl +1 -0
  1360. package/.claude/scripts/red-team-corpus/synthetic/SYN-015-mcp-handler-acl-enumeration.jsonl +1 -0
  1361. package/.claude/scripts/red-team-corpus/synthetic/SYN-016-mcp-handler-rate-limit-evasion.jsonl +1 -0
  1362. package/.claude/scripts/red-team-corpus/synthetic/SYN-017-adapter-exfil-via-error-message.jsonl +1 -0
  1363. package/.claude/scripts/red-team-corpus/synthetic/SYN-018-adapter-exfil-otel-attr.jsonl +1 -0
  1364. package/.claude/scripts/red-team-corpus/synthetic/SYN-019-adapter-exfil-retry-replay.jsonl +1 -0
  1365. package/.claude/scripts/red-team-corpus/synthetic/SYN-020-output-safety-nfkc-bypass.jsonl +1 -0
  1366. package/.claude/scripts/red-team-corpus/synthetic/SYN-021-output-safety-base64-double-wrap.jsonl +1 -0
  1367. package/.claude/scripts/red-team-corpus/synthetic/SYN-022-output-safety-entropy-below-threshold.jsonl +1 -0
  1368. package/.claude/scripts/red-team-corpus/synthetic/SYN-023-output-safety-regex-obfuscation.jsonl +1 -0
  1369. package/.claude/scripts/red-team-corpus/synthetic/SYN-024-output-safety-luhn-partial.jsonl +1 -0
  1370. package/.claude/scripts/red-team-corpus/synthetic/SYN-025-npm-tamper-supply-chain.jsonl +1 -0
  1371. package/.claude/scripts/red-team-corpus/synthetic/SYN-026-npm-tamper-typo-squat.jsonl +1 -0
  1372. package/.claude/scripts/red-team-corpus/synthetic/SYN-027-npm-tamper-unsigned-slsa.jsonl +1 -0
  1373. package/.claude/scripts/red-team-corpus/v1/fixtures.jsonl +67 -0
  1374. package/.claude/scripts/red-team-corpus/v1/fixtures.jsonl.sha256 +1 -0
  1375. package/.claude/scripts/red-team-corpus/v1/labels.json +88 -0
  1376. package/.claude/scripts/red-team-eval.py +1099 -0
  1377. package/.claude/scripts/registry.py +438 -0
  1378. package/.claude/scripts/replay/__init__.py +0 -0
  1379. package/.claude/scripts/replay/replay-session.py +1232 -0
  1380. package/.claude/scripts/replay/tests/__init__.py +0 -0
  1381. package/.claude/scripts/replay/tests/fixtures/api-key-01-positive.jsonl +1 -0
  1382. package/.claude/scripts/replay/tests/fixtures/api-key-02-positive.jsonl +1 -0
  1383. package/.claude/scripts/replay/tests/fixtures/api-key-03-positive.jsonl +1 -0
  1384. package/.claude/scripts/replay/tests/fixtures/api-key-04-positive.jsonl +1 -0
  1385. package/.claude/scripts/replay/tests/fixtures/api-key-05-negative.jsonl +1 -0
  1386. package/.claude/scripts/replay/tests/fixtures/api-key-06-negative.jsonl +1 -0
  1387. package/.claude/scripts/replay/tests/fixtures/api-key-07-negative.jsonl +1 -0
  1388. package/.claude/scripts/replay/tests/fixtures/api-key-08-negative.jsonl +1 -0
  1389. package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-01-positive.jsonl +1 -0
  1390. package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-02-positive.jsonl +1 -0
  1391. package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-03-positive.jsonl +1 -0
  1392. package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-04-positive.jsonl +1 -0
  1393. package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-05-negative.jsonl +1 -0
  1394. package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-06-negative.jsonl +1 -0
  1395. package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-07-negative.jsonl +1 -0
  1396. package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-08-negative.jsonl +1 -0
  1397. package/.claude/scripts/replay/tests/fixtures/email-in-log-01-positive.jsonl +1 -0
  1398. package/.claude/scripts/replay/tests/fixtures/email-in-log-02-positive.jsonl +1 -0
  1399. package/.claude/scripts/replay/tests/fixtures/email-in-log-03-positive.jsonl +1 -0
  1400. package/.claude/scripts/replay/tests/fixtures/email-in-log-04-positive.jsonl +1 -0
  1401. package/.claude/scripts/replay/tests/fixtures/email-in-log-05-negative.jsonl +1 -0
  1402. package/.claude/scripts/replay/tests/fixtures/email-in-log-06-negative.jsonl +1 -0
  1403. package/.claude/scripts/replay/tests/fixtures/email-in-log-07-negative.jsonl +1 -0
  1404. package/.claude/scripts/replay/tests/fixtures/email-in-log-08-negative.jsonl +1 -0
  1405. package/.claude/scripts/replay/tests/fixtures/homoglyph-01-positive.jsonl +1 -0
  1406. package/.claude/scripts/replay/tests/fixtures/homoglyph-02-positive.jsonl +1 -0
  1407. package/.claude/scripts/replay/tests/fixtures/homoglyph-03-positive.jsonl +1 -0
  1408. package/.claude/scripts/replay/tests/fixtures/homoglyph-04-positive.jsonl +1 -0
  1409. package/.claude/scripts/replay/tests/fixtures/homoglyph-05-negative.jsonl +1 -0
  1410. package/.claude/scripts/replay/tests/fixtures/homoglyph-06-negative.jsonl +1 -0
  1411. package/.claude/scripts/replay/tests/fixtures/homoglyph-07-negative.jsonl +1 -0
  1412. package/.claude/scripts/replay/tests/fixtures/homoglyph-08-negative.jsonl +1 -0
  1413. package/.claude/scripts/replay/tests/fixtures/jwt-01-positive.jsonl +1 -0
  1414. package/.claude/scripts/replay/tests/fixtures/jwt-02-positive.jsonl +1 -0
  1415. package/.claude/scripts/replay/tests/fixtures/jwt-03-positive.jsonl +1 -0
  1416. package/.claude/scripts/replay/tests/fixtures/jwt-04-positive.jsonl +1 -0
  1417. package/.claude/scripts/replay/tests/fixtures/jwt-05-negative.jsonl +1 -0
  1418. package/.claude/scripts/replay/tests/fixtures/jwt-06-negative.jsonl +1 -0
  1419. package/.claude/scripts/replay/tests/fixtures/jwt-07-negative.jsonl +1 -0
  1420. package/.claude/scripts/replay/tests/fixtures/jwt-08-negative.jsonl +1 -0
  1421. package/.claude/scripts/replay/tests/fixtures/os-path-01-positive.jsonl +1 -0
  1422. package/.claude/scripts/replay/tests/fixtures/os-path-02-positive.jsonl +1 -0
  1423. package/.claude/scripts/replay/tests/fixtures/os-path-03-positive.jsonl +1 -0
  1424. package/.claude/scripts/replay/tests/fixtures/os-path-04-positive.jsonl +1 -0
  1425. package/.claude/scripts/replay/tests/fixtures/os-path-05-negative.jsonl +1 -0
  1426. package/.claude/scripts/replay/tests/fixtures/os-path-06-negative.jsonl +1 -0
  1427. package/.claude/scripts/replay/tests/fixtures/os-path-07-negative.jsonl +1 -0
  1428. package/.claude/scripts/replay/tests/fixtures/os-path-08-negative.jsonl +1 -0
  1429. package/.claude/scripts/replay/tests/fixtures/pan-01-positive.jsonl +1 -0
  1430. package/.claude/scripts/replay/tests/fixtures/pan-02-positive.jsonl +1 -0
  1431. package/.claude/scripts/replay/tests/fixtures/pan-03-positive.jsonl +1 -0
  1432. package/.claude/scripts/replay/tests/fixtures/pan-04-positive.jsonl +1 -0
  1433. package/.claude/scripts/replay/tests/fixtures/pan-05-negative.jsonl +1 -0
  1434. package/.claude/scripts/replay/tests/fixtures/pan-06-negative.jsonl +1 -0
  1435. package/.claude/scripts/replay/tests/fixtures/pan-07-negative.jsonl +1 -0
  1436. package/.claude/scripts/replay/tests/fixtures/pan-08-negative.jsonl +1 -0
  1437. package/.claude/scripts/replay/tests/test_replay_redact_lib.py +971 -0
  1438. package/.claude/scripts/replay/tests/test_replay_session.py +396 -0
  1439. package/.claude/scripts/replay/tests/test_replay_session_capture.py +522 -0
  1440. package/.claude/scripts/repo-profile.schema.json +83 -0
  1441. package/.claude/scripts/run-promotion-gate.py +631 -0
  1442. package/.claude/scripts/run-skill-benchmark.py +1276 -0
  1443. package/.claude/scripts/scan-injection-strict.sh +162 -0
  1444. package/.claude/scripts/scan-injection.py +305 -0
  1445. package/.claude/scripts/scan-upstream-injection.py +663 -0
  1446. package/.claude/scripts/scratchpad.py +427 -0
  1447. package/.claude/scripts/self_test.py +602 -0
  1448. package/.claude/scripts/session-graph-build.py +728 -0
  1449. package/.claude/scripts/session-resume.py +363 -0
  1450. package/.claude/scripts/set-quality-profile.sh +229 -0
  1451. package/.claude/scripts/skill-budget-generator.py +599 -0
  1452. package/.claude/scripts/skill-import-rubric.py +368 -0
  1453. package/.claude/scripts/skill-index-build.py +534 -0
  1454. package/.claude/scripts/skill-patch-apply.py +1088 -0
  1455. package/.claude/scripts/skill-patch-propose.py +690 -0
  1456. package/.claude/scripts/skill-retrieve.py +522 -0
  1457. package/.claude/scripts/skill_grandfather_parser.py +295 -0
  1458. package/.claude/scripts/smart-loading-resolver.py +994 -0
  1459. package/.claude/scripts/spot-check-findings.py +211 -0
  1460. package/.claude/scripts/squad-export.py +437 -0
  1461. package/.claude/scripts/squad-import.py +741 -0
  1462. package/.claude/scripts/status.py +315 -0
  1463. package/.claude/scripts/statusline-ceo.py +597 -0
  1464. package/.claude/scripts/substrate-watch.json +54 -0
  1465. package/.claude/scripts/success-receipt.py +1038 -0
  1466. package/.claude/scripts/swarm/__init__.py +42 -0
  1467. package/.claude/scripts/swarm/_benchmark_replay.py +259 -0
  1468. package/.claude/scripts/swarm/_child_isolation.py +113 -0
  1469. package/.claude/scripts/swarm/_coordinator_sim.py +293 -0
  1470. package/.claude/scripts/swarm/_governors.py +277 -0
  1471. package/.claude/scripts/swarm/_integration.py +547 -0
  1472. package/.claude/scripts/swarm/_parent_death.py +176 -0
  1473. package/.claude/scripts/swarm/_process_group.py +250 -0
  1474. package/.claude/scripts/swarm/_replay_tournament.py +214 -0
  1475. package/.claude/scripts/swarm/_spawn_gate.py +292 -0
  1476. package/.claude/scripts/swarm/_subagent_fabrication.py +444 -0
  1477. package/.claude/scripts/swarm/_worktree_pool.py +276 -0
  1478. package/.claude/scripts/swarm/coordinator.py +543 -0
  1479. package/.claude/scripts/swarm/file_assignment.py +111 -0
  1480. package/.claude/scripts/swarm/fixtures/mcp_corpus.json +111 -0
  1481. package/.claude/scripts/swarm/kill_switch.py +260 -0
  1482. package/.claude/scripts/swarm/loop_runner.py +486 -0
  1483. package/.claude/scripts/swarm/recovery.py +178 -0
  1484. package/.claude/scripts/swarm/test_mcp_injection_repro.py +518 -0
  1485. package/.claude/scripts/swarm/test_rail_anomaly_repro.py +586 -0
  1486. package/.claude/scripts/swarm/tests/__init__.py +1 -0
  1487. package/.claude/scripts/swarm/tests/test_benchmark_manifest_schema.py +227 -0
  1488. package/.claude/scripts/swarm/tests/test_benchmark_replay.py +248 -0
  1489. package/.claude/scripts/swarm/tests/test_child_isolation.py +138 -0
  1490. package/.claude/scripts/swarm/tests/test_coordinator.py +289 -0
  1491. package/.claude/scripts/swarm/tests/test_coordinator_production_integration.py +434 -0
  1492. package/.claude/scripts/swarm/tests/test_coordinator_sim.py +192 -0
  1493. package/.claude/scripts/swarm/tests/test_coordinator_tick.py +165 -0
  1494. package/.claude/scripts/swarm/tests/test_file_assignment.py +100 -0
  1495. package/.claude/scripts/swarm/tests/test_governors.py +269 -0
  1496. package/.claude/scripts/swarm/tests/test_integration.py +344 -0
  1497. package/.claude/scripts/swarm/tests/test_kill_switch.py +307 -0
  1498. package/.claude/scripts/swarm/tests/test_loop_runner.py +168 -0
  1499. package/.claude/scripts/swarm/tests/test_loop_runner_circuit_breaker.py +555 -0
  1500. package/.claude/scripts/swarm/tests/test_loop_runner_gate_enforcement.py +304 -0
  1501. package/.claude/scripts/swarm/tests/test_loop_runner_gate_kill_switch.py +147 -0
  1502. package/.claude/scripts/swarm/tests/test_loop_runner_sentinel_revocation_slo.py +112 -0
  1503. package/.claude/scripts/swarm/tests/test_optimizer_killswitch.py +205 -0
  1504. package/.claude/scripts/swarm/tests/test_parent_death.py +128 -0
  1505. package/.claude/scripts/swarm/tests/test_parent_death_integration.py +305 -0
  1506. package/.claude/scripts/swarm/tests/test_process_group.py +132 -0
  1507. package/.claude/scripts/swarm/tests/test_process_group_reap.py +212 -0
  1508. package/.claude/scripts/swarm/tests/test_rail_anomaly_repro.py +516 -0
  1509. package/.claude/scripts/swarm/tests/test_recovery.py +165 -0
  1510. package/.claude/scripts/swarm/tests/test_replay_tournament.py +284 -0
  1511. package/.claude/scripts/swarm/tests/test_spawn_gate.py +265 -0
  1512. package/.claude/scripts/swarm/tests/test_subagent_fabrication.py +824 -0
  1513. package/.claude/scripts/swarm/tests/test_swarm_activation_smoke.py +112 -0
  1514. package/.claude/scripts/swarm/tests/test_tournament.py +195 -0
  1515. package/.claude/scripts/swarm/tests/test_worktree_pool.py +252 -0
  1516. package/.claude/scripts/swarm/tournament.py +261 -0
  1517. package/.claude/scripts/task-route.py +807 -0
  1518. package/.claude/scripts/test-env-hygiene-allowlist.yaml +1093 -0
  1519. package/.claude/scripts/tests/DEFERRED.md +99 -0
  1520. package/.claude/scripts/tests/conftest.py +42 -0
  1521. package/.claude/scripts/tests/fixtures/aggregate-changesets/bad-type.md +4 -0
  1522. package/.claude/scripts/tests/fixtures/aggregate-changesets/missing-frontmatter.md +1 -0
  1523. package/.claude/scripts/tests/fixtures/aggregate-changesets/multidoc.md +6 -0
  1524. package/.claude/scripts/tests/fixtures/aggregate-changesets/sample-CHANGELOG.md +29 -0
  1525. package/.claude/scripts/tests/fixtures/aggregate-changesets/second-minor.md +4 -0
  1526. package/.claude/scripts/tests/fixtures/aggregate-changesets/single-patch.md +4 -0
  1527. package/.claude/scripts/tests/fixtures/aggregate-changesets/third-major.md +4 -0
  1528. package/.claude/scripts/tests/fixtures/aggregate-changesets/unknown-key.md +6 -0
  1529. package/.claude/scripts/tests/fixtures/bad_lessons/bidi_override.md +12 -0
  1530. package/.claude/scripts/tests/fixtures/bad_lessons/fenced_python.md +19 -0
  1531. package/.claude/scripts/tests/fixtures/bad_lessons/homoglyph.md +11 -0
  1532. package/.claude/scripts/tests/fixtures/bad_lessons/injection.md +11 -0
  1533. package/.claude/scripts/tests/fixtures/bad_lessons/long_line.md +9 -0
  1534. package/.claude/scripts/tests/fixtures/bad_lessons/oversized.md +261 -0
  1535. package/.claude/scripts/tests/fixtures/bad_lessons/zero_width.md +11 -0
  1536. package/.claude/scripts/tests/fixtures/budget_summary/generate_fixtures.py +368 -0
  1537. package/.claude/scripts/tests/fixtures/claims/README.md +21 -0
  1538. package/.claude/scripts/tests/fixtures/claims/function_exists/neg-missing.txt +1 -0
  1539. package/.claude/scripts/tests/fixtures/claims/function_exists/neg-no-file.txt +1 -0
  1540. package/.claude/scripts/tests/fixtures/claims/function_exists/pos-extract.txt +1 -0
  1541. package/.claude/scripts/tests/fixtures/claims/function_exists/pos-main.txt +1 -0
  1542. package/.claude/scripts/tests/fixtures/claims/function_exists/pos-verify.txt +1 -0
  1543. package/.claude/scripts/tests/fixtures/claims/function_exists/quoted-colon-path.txt +1 -0
  1544. package/.claude/scripts/tests/fixtures/claims/import_resolves/codeblock-skipped.txt +8 -0
  1545. package/.claude/scripts/tests/fixtures/claims/import_resolves/neg-blocked-os.txt +6 -0
  1546. package/.claude/scripts/tests/fixtures/claims/import_resolves/neg-relative.txt +5 -0
  1547. package/.claude/scripts/tests/fixtures/claims/import_resolves/pos-dotted.txt +6 -0
  1548. package/.claude/scripts/tests/fixtures/claims/import_resolves/pos-stdlib-like.txt +5 -0
  1549. package/.claude/scripts/tests/fixtures/claims/line_range/neg-missing-file.txt +1 -0
  1550. package/.claude/scripts/tests/fixtures/claims/line_range/neg-too-long.txt +1 -0
  1551. package/.claude/scripts/tests/fixtures/claims/line_range/pos-large.txt +1 -0
  1552. package/.claude/scripts/tests/fixtures/claims/line_range/pos-small.txt +1 -0
  1553. package/.claude/scripts/tests/fixtures/claims/line_range/quoted-path.txt +1 -0
  1554. package/.claude/scripts/tests/fixtures/claims/path_exists/codeblock-skipped.txt +7 -0
  1555. package/.claude/scripts/tests/fixtures/claims/path_exists/neg-absolute-outside.txt +6 -0
  1556. package/.claude/scripts/tests/fixtures/claims/path_exists/neg-dotdot-escape.txt +7 -0
  1557. package/.claude/scripts/tests/fixtures/claims/path_exists/neg-imaginary.txt +1 -0
  1558. package/.claude/scripts/tests/fixtures/claims/path_exists/neg-proc-self.txt +6 -0
  1559. package/.claude/scripts/tests/fixtures/claims/path_exists/neg-symlink-escape.txt +8 -0
  1560. package/.claude/scripts/tests/fixtures/claims/path_exists/neg-typo.txt +1 -0
  1561. package/.claude/scripts/tests/fixtures/claims/path_exists/pos-claude.txt +1 -0
  1562. package/.claude/scripts/tests/fixtures/claims/path_exists/pos-readme.txt +1 -0
  1563. package/.claude/scripts/tests/fixtures/claims/path_exists/pos-self.txt +1 -0
  1564. package/.claude/scripts/tests/fixtures/claims/sha_exists/neg-fake.txt +1 -0
  1565. package/.claude/scripts/tests/fixtures/claims/sha_exists/neg-not-sha.txt +1 -0
  1566. package/.claude/scripts/tests/fixtures/claims/sha_exists/pos-head.txt +4 -0
  1567. package/.claude/scripts/tests/fixtures/claims/sha_exists/pos-root.txt +1 -0
  1568. package/.claude/scripts/tests/fixtures/claims/sha_exists/pos-short.txt +1 -0
  1569. package/.claude/scripts/tests/fixtures/claims/test_passes/neg-missing-file.txt +1 -0
  1570. package/.claude/scripts/tests/fixtures/claims/test_passes/neg-wrong-test.txt +1 -0
  1571. package/.claude/scripts/tests/fixtures/claims/test_passes/pos-audit-emit.txt +1 -0
  1572. package/.claude/scripts/tests/fixtures/claims/test_passes/pos-extra.txt +1 -0
  1573. package/.claude/scripts/tests/fixtures/claims/test_passes/pos-file.txt +1 -0
  1574. package/.claude/scripts/tests/fixtures/claims/test_passes/quoted-pytest-selector.txt +1 -0
  1575. package/.claude/scripts/tests/fixtures/debate_convergence/converged-pair-1/round-1/a.md +39 -0
  1576. package/.claude/scripts/tests/fixtures/debate_convergence/converged-pair-1/round-1/b.md +36 -0
  1577. package/.claude/scripts/tests/fixtures/debate_convergence/converged-pair-1/round-2/a.md +36 -0
  1578. package/.claude/scripts/tests/fixtures/debate_convergence/converged-pair-1/round-2/b.md +36 -0
  1579. package/.claude/scripts/tests/fixtures/debate_convergence/not-converged-pair-1/round-1/a.md +35 -0
  1580. package/.claude/scripts/tests/fixtures/debate_convergence/not-converged-pair-1/round-1/b.md +34 -0
  1581. package/.claude/scripts/tests/fixtures/debate_convergence/not-converged-pair-1/round-2/a.md +35 -0
  1582. package/.claude/scripts/tests/fixtures/debate_convergence/not-converged-pair-1/round-2/b.md +34 -0
  1583. package/.claude/scripts/tests/fixtures/debate_convergence/partial-overlap/round-1/a.md +35 -0
  1584. package/.claude/scripts/tests/fixtures/debate_convergence/partial-overlap/round-2/a.md +36 -0
  1585. package/.claude/scripts/tests/fixtures/debate_convergence/with-secret/round-1/a.md +36 -0
  1586. package/.claude/scripts/tests/fixtures/debate_convergence/with-secret/round-1/b.md +33 -0
  1587. package/.claude/scripts/tests/fixtures/debate_convergence/with-secret/round-2/a.md +34 -0
  1588. package/.claude/scripts/tests/fixtures/docs_freshness/link_anchor_only.md +10 -0
  1589. package/.claude/scripts/tests/fixtures/docs_freshness/link_broken.md +5 -0
  1590. package/.claude/scripts/tests/fixtures/docs_freshness/link_external_url.md +9 -0
  1591. package/.claude/scripts/tests/fixtures/docs_freshness/link_in_fenced_code.md +18 -0
  1592. package/.claude/scripts/tests/fixtures/docs_freshness/link_in_frontmatter.md +10 -0
  1593. package/.claude/scripts/tests/fixtures/docs_freshness/link_in_html_comment.md +10 -0
  1594. package/.claude/scripts/tests/fixtures/docs_freshness/link_in_inline_code.md +7 -0
  1595. package/.claude/scripts/tests/fixtures/docs_freshness/link_in_table.md +6 -0
  1596. package/.claude/scripts/tests/fixtures/docs_freshness/link_relative_parent.md +7 -0
  1597. package/.claude/scripts/tests/fixtures/docs_freshness/link_url_encoded.md +5 -0
  1598. package/.claude/scripts/tests/fixtures/docs_freshness/real_target.md +3 -0
  1599. package/.claude/scripts/tests/fixtures/docs_freshness/sub/dir.md +3 -0
  1600. package/.claude/scripts/tests/fixtures/docs_freshness/with%20space.md +3 -0
  1601. package/.claude/scripts/tests/fixtures/good_lessons/clean_auth.md +11 -0
  1602. package/.claude/scripts/tests/fixtures/good_lessons/clean_logging.md +11 -0
  1603. package/.claude/scripts/tests/fixtures/good_lessons/clean_retry.md +11 -0
  1604. package/.claude/scripts/tests/fixtures/gpg-keyring-fixture.py +209 -0
  1605. package/.claude/scripts/tests/fixtures/injection/benign-01.txt +8 -0
  1606. package/.claude/scripts/tests/fixtures/injection/benign-02.txt +5 -0
  1607. package/.claude/scripts/tests/fixtures/injection/benign-03.txt +7 -0
  1608. package/.claude/scripts/tests/fixtures/injection/benign-04.txt +9 -0
  1609. package/.claude/scripts/tests/fixtures/injection/benign-05.txt +7 -0
  1610. package/.claude/scripts/tests/fixtures/injection/benign-06.txt +7 -0
  1611. package/.claude/scripts/tests/fixtures/injection/benign-07.txt +11 -0
  1612. package/.claude/scripts/tests/fixtures/injection/benign-08.txt +4 -0
  1613. package/.claude/scripts/tests/fixtures/injection/malicious-01.txt +4 -0
  1614. package/.claude/scripts/tests/fixtures/injection/malicious-02.txt +2 -0
  1615. package/.claude/scripts/tests/fixtures/injection/malicious-03.txt +4 -0
  1616. package/.claude/scripts/tests/fixtures/injection/malicious-04.txt +2 -0
  1617. package/.claude/scripts/tests/fixtures/injection/malicious-05.txt +2 -0
  1618. package/.claude/scripts/tests/fixtures/injection/malicious-06.txt +5 -0
  1619. package/.claude/scripts/tests/fixtures/injection/malicious-07.txt +5 -0
  1620. package/.claude/scripts/tests/fixtures/injection/malicious-08.txt +2 -0
  1621. package/.claude/scripts/tests/fixtures/injection/malicious-09.txt +3 -0
  1622. package/.claude/scripts/tests/fixtures/injection/malicious-10.txt +2 -0
  1623. package/.claude/scripts/tests/fixtures/injection/malicious-11.txt +3 -0
  1624. package/.claude/scripts/tests/fixtures/injection/malicious-12.txt +5 -0
  1625. package/.claude/scripts/tests/fixtures/plan-tokens-calibration/manifest.json +49 -0
  1626. package/.claude/scripts/tests/fixtures/plan-tokens-calibration/plan-051.md +36 -0
  1627. package/.claude/scripts/tests/fixtures/plan-tokens-calibration/plan-052.md +32 -0
  1628. package/.claude/scripts/tests/fixtures/plan-tokens-calibration/plan-058.md +31 -0
  1629. package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-boundary/docs/SAMPLE.md +8 -0
  1630. package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-negative/.claude/scripts/sample.py +12 -0
  1631. package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-negative/docs/SAMPLE.md +4 -0
  1632. package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-positive/.claude/scripts/sample.py +12 -0
  1633. package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-positive/docs/SAMPLE.md +9 -0
  1634. package/.claude/scripts/tests/fixtures/reality-ledger/detector-2-boundary/README.md +4 -0
  1635. package/.claude/scripts/tests/fixtures/reality-ledger/detector-2-negative/.claude/rag/requirements.lock +4 -0
  1636. package/.claude/scripts/tests/fixtures/reality-ledger/detector-2-positive/.claude/rag/requirements.lock +2 -0
  1637. package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-boundary/.claude/agents/devops.md +8 -0
  1638. package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-negative/.claude/agents/devops.md +5 -0
  1639. package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-negative/audit-log.jsonl +2 -0
  1640. package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-positive/.claude/agents/devops.md +7 -0
  1641. package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-positive/audit-log.jsonl +4 -0
  1642. package/.claude/scripts/tests/fixtures/reality-ledger/detector-4-boundary/.claude/adr/ADR-997-fixture-superseded.md +8 -0
  1643. package/.claude/scripts/tests/fixtures/reality-ledger/detector-4-negative/.claude/adr/ADR-998-fixture-negative.md +16 -0
  1644. package/.claude/scripts/tests/fixtures/reality-ledger/detector-4-positive/.claude/adr/ADR-999-fixture-positive.md +15 -0
  1645. package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-boundary/.claude/hooks/_lib/.do-not-import-from-here +15 -0
  1646. package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-boundary/.claude/hooks/_lib/audit_emit.py +8 -0
  1647. package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-boundary/.claude/scripts/dynamic_action.py +12 -0
  1648. package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-negative/.claude/hooks/_lib/.do-not-import-from-here +15 -0
  1649. package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-negative/.claude/hooks/_lib/audit_emit.py +11 -0
  1650. package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-negative/.claude/scripts/registered_emitter.py +8 -0
  1651. package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-positive/.claude/hooks/_lib/.do-not-import-from-here +15 -0
  1652. package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-positive/.claude/hooks/_lib/audit_emit.py +12 -0
  1653. package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-positive/.claude/scripts/phantom_emitter.py +13 -0
  1654. package/.claude/scripts/tests/fixtures/reality-ledger/issue-body-template.md +47 -0
  1655. package/.claude/scripts/tests/fixtures/reality-ledger/redaction/_test_corpus.py +7 -0
  1656. package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/.env.example +5 -0
  1657. package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/Cargo.toml +9 -0
  1658. package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/README.md +6 -0
  1659. package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/exchanges/binance.py +6 -0
  1660. package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/strategies/triangular.py +4 -0
  1661. package/.claude/scripts/tests/fixtures/repo_profile/missing-package-manifest/README.md +7 -0
  1662. package/.claude/scripts/tests/fixtures/repo_profile/missing-package-manifest/notes.md +1 -0
  1663. package/.claude/scripts/tests/fixtures/repo_profile/mixed-frontend-backend/README.md +6 -0
  1664. package/.claude/scripts/tests/fixtures/repo_profile/mixed-frontend-backend/api/server.js +4 -0
  1665. package/.claude/scripts/tests/fixtures/repo_profile/mixed-frontend-backend/package.json +15 -0
  1666. package/.claude/scripts/tests/fixtures/repo_profile/mixed-frontend-backend/pages/index.tsx +3 -0
  1667. package/.claude/scripts/tests/fixtures/repo_profile/monorepo/README.md +6 -0
  1668. package/.claude/scripts/tests/fixtures/repo_profile/monorepo/apps/backend/.gitkeep +0 -0
  1669. package/.claude/scripts/tests/fixtures/repo_profile/monorepo/apps/frontend/.gitkeep +0 -0
  1670. package/.claude/scripts/tests/fixtures/repo_profile/monorepo/package.json +5 -0
  1671. package/.claude/scripts/tests/fixtures/repo_profile/monorepo/packages/shared/.gitkeep +0 -0
  1672. package/.claude/scripts/tests/fixtures/sample_audit_log.jsonl +50 -0
  1673. package/.claude/scripts/tests/fixtures/siem/.gitkeep +0 -0
  1674. package/.claude/scripts/tests/fixtures/smart_loading/profile-engine.yaml +8 -0
  1675. package/.claude/scripts/tests/fixtures/smart_loading/profile-fail-closed.yaml +7 -0
  1676. package/.claude/scripts/tests/fixtures/smart_loading/profile-fintech.yaml +9 -0
  1677. package/.claude/scripts/tests/fixtures/smart_loading/profile-frontend.yaml +9 -0
  1678. package/.claude/scripts/tests/fixtures/smart_loading/profile-generic.yaml +8 -0
  1679. package/.claude/scripts/tests/fixtures/smart_loading/profile-trading-readonly.yaml +9 -0
  1680. package/.claude/scripts/tests/fixtures/smart_loading/synthetic-skill-catalog.yaml +186 -0
  1681. package/.claude/scripts/tests/fixtures/squad_marketplace/.gitkeep +4 -0
  1682. package/.claude/scripts/tests/fixtures/task-route/calibration-holdout.json +49 -0
  1683. package/.claude/scripts/tests/fixtures/task-route/calibration-train.json +174 -0
  1684. package/.claude/scripts/tests/perf/__init__.py +3 -0
  1685. package/.claude/scripts/tests/perf/perf_utils.py +134 -0
  1686. package/.claude/scripts/tests/perf/test_kernel_hard_deny_microbench.py +149 -0
  1687. package/.claude/scripts/tests/perf/test_optimizer_complexity_gate_p99.py +145 -0
  1688. package/.claude/scripts/tests/perf/test_wave_c_canonical_json.py +132 -0
  1689. package/.claude/scripts/tests/perf/test_wave_c_filelock_mkdir.py +71 -0
  1690. package/.claude/scripts/tests/perf/test_wave_c_plan_glob_cache.py +84 -0
  1691. package/.claude/scripts/tests/perf/test_wave_c_preview_collapse.py +98 -0
  1692. package/.claude/scripts/tests/perf/test_wave_c_sys_modules.py +104 -0
  1693. package/.claude/scripts/tests/test_a4_pricing_doctrine.py +127 -0
  1694. package/.claude/scripts/tests/test_admin_invite.py +173 -0
  1695. package/.claude/scripts/tests/test_adopter_metrics.py +723 -0
  1696. package/.claude/scripts/tests/test_aek_calibration_c2.py +107 -0
  1697. package/.claude/scripts/tests/test_aek_calibration_c3.py +192 -0
  1698. package/.claude/scripts/tests/test_aek_state_machine.py +385 -0
  1699. package/.claude/scripts/tests/test_aggregate_changesets.py +646 -0
  1700. package/.claude/scripts/tests/test_architect_bundle_validate.py +159 -0
  1701. package/.claude/scripts/tests/test_audit_dashboard.py +822 -0
  1702. package/.claude/scripts/tests/test_audit_log_dispatch_hint.py +91 -0
  1703. package/.claude/scripts/tests/test_audit_log_retain.py +394 -0
  1704. package/.claude/scripts/tests/test_audit_query.py +1177 -0
  1705. package/.claude/scripts/tests/test_audit_query_by_domain.py +576 -0
  1706. package/.claude/scripts/tests/test_audit_query_claims.py +92 -0
  1707. package/.claude/scripts/tests/test_audit_query_critical.py +267 -0
  1708. package/.claude/scripts/tests/test_audit_query_tokens.py +106 -0
  1709. package/.claude/scripts/tests/test_audit_telemetry.py +214 -0
  1710. package/.claude/scripts/tests/test_audit_tokens.py +255 -0
  1711. package/.claude/scripts/tests/test_audit_verify_chain.py +189 -0
  1712. package/.claude/scripts/tests/test_backup_audit.py +295 -0
  1713. package/.claude/scripts/tests/test_benchmark_fallback_scorer.py +299 -0
  1714. package/.claude/scripts/tests/test_benchmark_judge.py +569 -0
  1715. package/.claude/scripts/tests/test_benchmarks_replay.py +313 -0
  1716. package/.claude/scripts/tests/test_budget_summary.py +628 -0
  1717. package/.claude/scripts/tests/test_build_canonical_models.py +349 -0
  1718. package/.claude/scripts/tests/test_calibration_kappa.py +234 -0
  1719. package/.claude/scripts/tests/test_cc_analytics_pull.py +296 -0
  1720. package/.claude/scripts/tests/test_ceo_backup.py +318 -0
  1721. package/.claude/scripts/tests/test_ceo_boot.py +643 -0
  1722. package/.claude/scripts/tests/test_ceo_boot_audit_emit.py +484 -0
  1723. package/.claude/scripts/tests/test_ceo_boot_enhanced.py +706 -0
  1724. package/.claude/scripts/tests/test_ceo_boot_persona_cadence.py +392 -0
  1725. package/.claude/scripts/tests/test_ceo_boot_plan_082.py +365 -0
  1726. package/.claude/scripts/tests/test_ceo_boot_tamper_tripwires.py +556 -0
  1727. package/.claude/scripts/tests/test_ceo_boot_task_candidate.py +868 -0
  1728. package/.claude/scripts/tests/test_ceo_cost.py +221 -0
  1729. package/.claude/scripts/tests/test_ceo_cost_stream.py +1076 -0
  1730. package/.claude/scripts/tests/test_ceo_diagnose.py +314 -0
  1731. package/.claude/scripts/tests/test_ceo_escalation_detector.py +591 -0
  1732. package/.claude/scripts/tests/test_ceo_health.py +202 -0
  1733. package/.claude/scripts/tests/test_ceo_info.py +542 -0
  1734. package/.claude/scripts/tests/test_chaos_inject_lockdown.py +384 -0
  1735. package/.claude/scripts/tests/test_check_action_sha_drift.py +174 -0
  1736. package/.claude/scripts/tests/test_check_active_hooks_executable.py +79 -0
  1737. package/.claude/scripts/tests/test_check_adr_chain.py +665 -0
  1738. package/.claude/scripts/tests/test_check_audit_hmac_null.py +178 -0
  1739. package/.claude/scripts/tests/test_check_audit_read_api_stable.py +176 -0
  1740. package/.claude/scripts/tests/test_check_audit_registry_coverage.py +744 -0
  1741. package/.claude/scripts/tests/test_check_auto_activation_flags.py +140 -0
  1742. package/.claude/scripts/tests/test_check_canonical_doc_freshness.py +149 -0
  1743. package/.claude/scripts/tests/test_check_claude_md_claims.py +223 -0
  1744. package/.claude/scripts/tests/test_check_conformance_harness_mapping.py +243 -0
  1745. package/.claude/scripts/tests/test_check_contamination.py +161 -0
  1746. package/.claude/scripts/tests/test_check_creative_rewrite.py +183 -0
  1747. package/.claude/scripts/tests/test_check_debate_round_lifecycle.py +162 -0
  1748. package/.claude/scripts/tests/test_check_debt_ledger.py +227 -0
  1749. package/.claude/scripts/tests/test_check_doc_skill_paths.py +99 -0
  1750. package/.claude/scripts/tests/test_check_docs_freshness.py +224 -0
  1751. package/.claude/scripts/tests/test_check_flip_criteria_drift.py +343 -0
  1752. package/.claude/scripts/tests/test_check_flip_release_gate_consistency.py +195 -0
  1753. package/.claude/scripts/tests/test_check_function_length.py +519 -0
  1754. package/.claude/scripts/tests/test_check_model_deprecations.py +368 -0
  1755. package/.claude/scripts/tests/test_check_originator_residue.py +165 -0
  1756. package/.claude/scripts/tests/test_check_rule_invariants.py +327 -0
  1757. package/.claude/scripts/tests/test_check_sdk_compat.py +88 -0
  1758. package/.claude/scripts/tests/test_check_sidecar_manifest_sbom_sync.py +177 -0
  1759. package/.claude/scripts/tests/test_check_spec_drift.py +358 -0
  1760. package/.claude/scripts/tests/test_check_staleness.py +128 -0
  1761. package/.claude/scripts/tests/test_check_stdlib_only_exceptions.py +91 -0
  1762. package/.claude/scripts/tests/test_check_substrate_watch.py +234 -0
  1763. package/.claude/scripts/tests/test_check_test_audit_isolation.py +322 -0
  1764. package/.claude/scripts/tests/test_check_test_env_hygiene.py +432 -0
  1765. package/.claude/scripts/tests/test_check_threat_model_coverage.py +251 -0
  1766. package/.claude/scripts/tests/test_check_threat_model_freshness.py +235 -0
  1767. package/.claude/scripts/tests/test_check_tier_boundaries.py +225 -0
  1768. package/.claude/scripts/tests/test_check_tla_schema_drift.py +246 -0
  1769. package/.claude/scripts/tests/test_check_translations_drift.py +262 -0
  1770. package/.claude/scripts/tests/test_code_nav_bridge.py +192 -0
  1771. package/.claude/scripts/tests/test_compaction_template.py +163 -0
  1772. package/.claude/scripts/tests/test_compare_adopters.py +646 -0
  1773. package/.claude/scripts/tests/test_confidence_gate.py +611 -0
  1774. package/.claude/scripts/tests/test_confidence_gate_backfill.py +212 -0
  1775. package/.claude/scripts/tests/test_context_budget.py +1400 -0
  1776. package/.claude/scripts/tests/test_contextual_recommender.py +723 -0
  1777. package/.claude/scripts/tests/test_coverage_audit_marker.py +109 -0
  1778. package/.claude/scripts/tests/test_debate_converge.py +399 -0
  1779. package/.claude/scripts/tests/test_debate_emit_cli.py +153 -0
  1780. package/.claude/scripts/tests/test_debate_orchestrate.py +575 -0
  1781. package/.claude/scripts/tests/test_detect_repo_profile.py +434 -0
  1782. package/.claude/scripts/tests/test_discover_foreign_context.py +208 -0
  1783. package/.claude/scripts/tests/test_dispatch_archetype_hint.py +429 -0
  1784. package/.claude/scripts/tests/test_dispatch_frontmatter_validation.py +274 -0
  1785. package/.claude/scripts/tests/test_drift_wire.py +259 -0
  1786. package/.claude/scripts/tests/test_embeddings.py +249 -0
  1787. package/.claude/scripts/tests/test_env_inventory_check.py +197 -0
  1788. package/.claude/scripts/tests/test_eval_c3.py +474 -0
  1789. package/.claude/scripts/tests/test_extract_skill.py +572 -0
  1790. package/.claude/scripts/tests/test_fan_plan_parser.py +213 -0
  1791. package/.claude/scripts/tests/test_find_orphan_sentinels.py +62 -0
  1792. package/.claude/scripts/tests/test_first_run_wizard.py +634 -0
  1793. package/.claude/scripts/tests/test_generate_adr_index.py +146 -0
  1794. package/.claude/scripts/tests/test_generate_available_models.py +209 -0
  1795. package/.claude/scripts/tests/test_generate_dispatch.py +90 -0
  1796. package/.claude/scripts/tests/test_generate_skill_inventory.py +76 -0
  1797. package/.claude/scripts/tests/test_github_api_client.py +146 -0
  1798. package/.claude/scripts/tests/test_governance_waivers_gate.py +176 -0
  1799. package/.claude/scripts/tests/test_hook_profiler.py +426 -0
  1800. package/.claude/scripts/tests/test_import_skill.py +927 -0
  1801. package/.claude/scripts/tests/test_import_skill_skip_rubric_auth.py +198 -0
  1802. package/.claude/scripts/tests/test_inject_agent_context_mitigated_dispatch.py +266 -0
  1803. package/.claude/scripts/tests/test_inject_agent_context_reference_mode.py +105 -0
  1804. package/.claude/scripts/tests/test_inspired_by_validator.py +307 -0
  1805. package/.claude/scripts/tests/test_install_dispatcher_present_maintainer.py +76 -0
  1806. package/.claude/scripts/tests/test_install_maintainer_unchanged.py +86 -0
  1807. package/.claude/scripts/tests/test_install_npm_sha256.py +113 -0
  1808. package/.claude/scripts/tests/test_install_sh_placeholders.py +268 -0
  1809. package/.claude/scripts/tests/test_install_sh_self_sha.py +244 -0
  1810. package/.claude/scripts/tests/test_install_sh_session_75_flags.py +147 -0
  1811. package/.claude/scripts/tests/test_install_user_dispatcher_present.py +75 -0
  1812. package/.claude/scripts/tests/test_install_user_no_writes_outside_claude.py +75 -0
  1813. package/.claude/scripts/tests/test_install_user_passes_validate_governance.py +73 -0
  1814. package/.claude/scripts/tests/test_install_user_preserves_existing_repo.py +135 -0
  1815. package/.claude/scripts/tests/test_install_user_skips_governance_hooks.py +102 -0
  1816. package/.claude/scripts/tests/test_k_calibration.py +415 -0
  1817. package/.claude/scripts/tests/test_key_hygiene.py +372 -0
  1818. package/.claude/scripts/tests/test_lesson_ranker.py +82 -0
  1819. package/.claude/scripts/tests/test_lesson_restore.py +91 -0
  1820. package/.claude/scripts/tests/test_lessons.py +278 -0
  1821. package/.claude/scripts/tests/test_lessons_concurrency.py +118 -0
  1822. package/.claude/scripts/tests/test_lessons_emit.py +114 -0
  1823. package/.claude/scripts/tests/test_lessons_inject.py +144 -0
  1824. package/.claude/scripts/tests/test_lessons_v2.py +264 -0
  1825. package/.claude/scripts/tests/test_lint_skills.py +525 -0
  1826. package/.claude/scripts/tests/test_log_friction.py +436 -0
  1827. package/.claude/scripts/tests/test_memory_prioritize.py +315 -0
  1828. package/.claude/scripts/tests/test_morning_ledger.py +415 -0
  1829. package/.claude/scripts/tests/test_mutation_test.py +144 -0
  1830. package/.claude/scripts/tests/test_npm_rebuild.py +154 -0
  1831. package/.claude/scripts/tests/test_osv_check.py +411 -0
  1832. package/.claude/scripts/tests/test_otel_export.py +613 -0
  1833. package/.claude/scripts/tests/test_otel_local_sink.py +262 -0
  1834. package/.claude/scripts/tests/test_owasp_llm_top_10_benchmark.py +235 -0
  1835. package/.claude/scripts/tests/test_parse_coverage_tier1.py +107 -0
  1836. package/.claude/scripts/tests/test_pitfall_query.py +148 -0
  1837. package/.claude/scripts/tests/test_plan_frontmatter_status.py +217 -0
  1838. package/.claude/scripts/tests/test_plan_id_uniqueness.py +133 -0
  1839. package/.claude/scripts/tests/test_plan_schema_enforcement.py +251 -0
  1840. package/.claude/scripts/tests/test_plan_tokens.py +513 -0
  1841. package/.claude/scripts/tests/test_plan_vcheck_gate.py +257 -0
  1842. package/.claude/scripts/tests/test_policy_shadow_runner.py +312 -0
  1843. package/.claude/scripts/tests/test_prune_lessons.py +341 -0
  1844. package/.claude/scripts/tests/test_quality_profile.py +392 -0
  1845. package/.claude/scripts/tests/test_rate_card_calibrate.py +185 -0
  1846. package/.claude/scripts/tests/test_reality_ledger.py +1723 -0
  1847. package/.claude/scripts/tests/test_red_team_eval.py +566 -0
  1848. package/.claude/scripts/tests/test_red_team_eval_sha.py +260 -0
  1849. package/.claude/scripts/tests/test_registry.py +290 -0
  1850. package/.claude/scripts/tests/test_run_benchmark.py +639 -0
  1851. package/.claude/scripts/tests/test_run_skill_benchmark_emit.py +195 -0
  1852. package/.claude/scripts/tests/test_run_skill_benchmark_judge_mode.py +306 -0
  1853. package/.claude/scripts/tests/test_scan_injection.py +191 -0
  1854. package/.claude/scripts/tests/test_scan_injection_strict.sh +201 -0
  1855. package/.claude/scripts/tests/test_scratchpad_cli.py +317 -0
  1856. package/.claude/scripts/tests/test_self_test.py +369 -0
  1857. package/.claude/scripts/tests/test_session_graph.py +511 -0
  1858. package/.claude/scripts/tests/test_session_resume.py +306 -0
  1859. package/.claude/scripts/tests/test_siem_rule_fixtures_have_paired_positive_negative.py +112 -0
  1860. package/.claude/scripts/tests/test_skill_budget_generator.py +329 -0
  1861. package/.claude/scripts/tests/test_skill_grandfather_parser.py +314 -0
  1862. package/.claude/scripts/tests/test_skill_import_rubric.py +497 -0
  1863. package/.claude/scripts/tests/test_skill_patch_apply_create_new_skill.py +459 -0
  1864. package/.claude/scripts/tests/test_skill_patch_propose.py +294 -0
  1865. package/.claude/scripts/tests/test_skill_patch_shadow_race.py +271 -0
  1866. package/.claude/scripts/tests/test_skill_retrieval.py +486 -0
  1867. package/.claude/scripts/tests/test_skill_retrieve_rag_wire.py +747 -0
  1868. package/.claude/scripts/tests/test_smart_loading_resolver.py +808 -0
  1869. package/.claude/scripts/tests/test_squad_export.py +265 -0
  1870. package/.claude/scripts/tests/test_squad_grandfather_cap.py +434 -0
  1871. package/.claude/scripts/tests/test_squad_import.py +905 -0
  1872. package/.claude/scripts/tests/test_statusline_ceo.py +543 -0
  1873. package/.claude/scripts/tests/test_success_receipt.py +448 -0
  1874. package/.claude/scripts/tests/test_task_route.py +456 -0
  1875. package/.claude/scripts/tests/test_token_budget_guard.py +418 -0
  1876. package/.claude/scripts/tests/test_token_estimator.py +395 -0
  1877. package/.claude/scripts/tests/test_trading_readonly.py +705 -0
  1878. package/.claude/scripts/tests/test_ui_ux_imports.py +223 -0
  1879. package/.claude/scripts/tests/test_validate_skill_frontmatter_pii_core.py +630 -0
  1880. package/.claude/scripts/tests/test_validate_spec_context.py +128 -0
  1881. package/.claude/scripts/tests/test_validate_squad_contract.py +221 -0
  1882. package/.claude/scripts/tests/test_value_dashboard.py +593 -0
  1883. package/.claude/scripts/tests/test_verify_adr_118_rationale.py +183 -0
  1884. package/.claude/scripts/tests/test_verify_atlas_binding.py +159 -0
  1885. package/.claude/scripts/tests/test_verify_counts.py +138 -0
  1886. package/.claude/scripts/tests/test_verify_counts_remediation.py +258 -0
  1887. package/.claude/scripts/tests/test_verify_persona_coverage.py +576 -0
  1888. package/.claude/scripts/tests/test_veto_check.py +171 -0
  1889. package/.claude/scripts/tests/test_workflow_devops_p2.py +229 -0
  1890. package/.claude/scripts/tier_policy_cli/__init__.py +43 -0
  1891. package/.claude/scripts/tier_policy_cli/_agent_frontmatter.py +196 -0
  1892. package/.claude/scripts/tier_policy_cli/_constants.py +92 -0
  1893. package/.claude/scripts/tier_policy_cli/_types.py +228 -0
  1894. package/.claude/scripts/tier_policy_cli/apply.py +1139 -0
  1895. package/.claude/scripts/tier_policy_cli/cli.py +795 -0
  1896. package/.claude/scripts/tier_policy_cli/learn.py +846 -0
  1897. package/.claude/scripts/tier_policy_cli/loader.py +535 -0
  1898. package/.claude/scripts/tier_policy_cli/setup.py +33 -0
  1899. package/.claude/scripts/tier_policy_cli/tests/__init__.py +0 -0
  1900. package/.claude/scripts/tier_policy_cli/tests/test_adversarial.py +605 -0
  1901. package/.claude/scripts/tier_policy_cli/tests/test_agent_frontmatter.py +231 -0
  1902. package/.claude/scripts/tier_policy_cli/tests/test_apply.py +698 -0
  1903. package/.claude/scripts/tier_policy_cli/tests/test_check_tier_policy_hook.py +187 -0
  1904. package/.claude/scripts/tier_policy_cli/tests/test_cli.py +434 -0
  1905. package/.claude/scripts/tier_policy_cli/tests/test_constants.py +113 -0
  1906. package/.claude/scripts/tier_policy_cli/tests/test_learn.py +1380 -0
  1907. package/.claude/scripts/tier_policy_cli/tests/test_learn_mutation.py +549 -0
  1908. package/.claude/scripts/tier_policy_cli/tests/test_loader.py +368 -0
  1909. package/.claude/scripts/tier_policy_cli/tests/test_types.py +152 -0
  1910. package/.claude/scripts/token-budget-guard.py +657 -0
  1911. package/.claude/scripts/token-estimator.py +957 -0
  1912. package/.claude/scripts/tournament/__init__.py +22 -0
  1913. package/.claude/scripts/tournament/check_fixture.py +271 -0
  1914. package/.claude/scripts/tournament/fixtures/CORPUS_SHA256.txt +10 -0
  1915. package/.claude/scripts/tournament/fixtures/code-review.jsonl +10 -0
  1916. package/.claude/scripts/tournament/fixtures/docs-writing.jsonl +10 -0
  1917. package/.claude/scripts/tournament/fixtures/performance-triage.jsonl +10 -0
  1918. package/.claude/scripts/tournament/fixtures/security-review.jsonl +10 -0
  1919. package/.claude/scripts/tournament/fixtures/test-design.jsonl +10 -0
  1920. package/.claude/scripts/tournament/judge.py +269 -0
  1921. package/.claude/scripts/tournament/loader.py +262 -0
  1922. package/.claude/scripts/tournament/regen_corpus_sha.py +93 -0
  1923. package/.claude/scripts/tournament/reporter.py +328 -0
  1924. package/.claude/scripts/tournament/runner.py +707 -0
  1925. package/.claude/scripts/tournament/scorer.py +118 -0
  1926. package/.claude/scripts/tournament/tests/__init__.py +0 -0
  1927. package/.claude/scripts/tournament/tests/_fake_dispatcher.py +233 -0
  1928. package/.claude/scripts/tournament/tests/golden/strict_report_seed42.jsonl +6 -0
  1929. package/.claude/scripts/tournament/tests/test_fixture_envelope.py +106 -0
  1930. package/.claude/scripts/tournament/tests/test_fixture_security.py +227 -0
  1931. package/.claude/scripts/tournament/tests/test_judge.py +299 -0
  1932. package/.claude/scripts/tournament/tests/test_loader.py +223 -0
  1933. package/.claude/scripts/tournament/tests/test_model_id_parity.py +136 -0
  1934. package/.claude/scripts/tournament/tests/test_reporter.py +450 -0
  1935. package/.claude/scripts/tournament/tests/test_reporter_golden.py +182 -0
  1936. package/.claude/scripts/tournament/tests/test_runner.py +313 -0
  1937. package/.claude/scripts/tournament/tests/test_runner_fail_open.py +204 -0
  1938. package/.claude/scripts/tournament/tests/test_scorer.py +138 -0
  1939. package/.claude/scripts/tournament/tests/test_tournament_e2e_smoke.py +147 -0
  1940. package/.claude/scripts/tournament/tests/test_tournament_properties.py +181 -0
  1941. package/.claude/scripts/trading-readonly-escape-hatch.sh +244 -0
  1942. package/.claude/scripts/trading-readonly-guardrails.py +1136 -0
  1943. package/.claude/scripts/translations-pairs.yaml +60 -0
  1944. package/.claude/scripts/validate-findings.py +243 -0
  1945. package/.claude/scripts/validate-governance.sh +1238 -0
  1946. package/.claude/scripts/validate-skill-frontmatter.py +679 -0
  1947. package/.claude/scripts/validate-spec-context.py +146 -0
  1948. package/.claude/scripts/validate-squad-contract.py +318 -0
  1949. package/.claude/scripts/validate_governance_fast.py +555 -0
  1950. package/.claude/scripts/value-dashboard.py +851 -0
  1951. package/.claude/scripts/verify-adr-118-rationale.py +285 -0
  1952. package/.claude/scripts/verify-atlas-binding.py +331 -0
  1953. package/.claude/scripts/verify-persona-coverage.py +531 -0
  1954. package/.claude/scripts/verify-sprint3-invariants.sh +133 -0
  1955. package/.claude/scripts/veto-check.py +218 -0
  1956. package/.claude/security/README.md +200 -0
  1957. package/.claude/security/sentinel-signers-registry.yaml +60 -0
  1958. package/.claude/sentinel-signers.txt +24 -0
  1959. package/.claude/settings.json +786 -0
  1960. package/.claude/sidecars/c1-crypto/cryptography-mvp/README.md +89 -0
  1961. package/.claude/sidecars/c1-crypto/cryptography-mvp/boundary_test.py +114 -0
  1962. package/.claude/sidecars/c1-crypto/cryptography-mvp/install.sh +45 -0
  1963. package/.claude/sidecars/c1-crypto/cryptography-mvp/manifest.json +52 -0
  1964. package/.claude/sidecars/c1-crypto/cryptography-mvp/sidecar_code/cert_inspector.py +775 -0
  1965. package/.claude/sidecars/c1-crypto/stdlib-ssl-mvp/boundary_test.py +318 -0
  1966. package/.claude/sidecars/c1-crypto/stdlib-ssl-mvp/install.sh +57 -0
  1967. package/.claude/sidecars/c1-crypto/stdlib-ssl-mvp/manifest.json +48 -0
  1968. package/.claude/sidecars/c2-vector-memory/lightrag-mvp/README.md +88 -0
  1969. package/.claude/sidecars/c2-vector-memory/lightrag-mvp/boundary_test.py +221 -0
  1970. package/.claude/sidecars/c2-vector-memory/lightrag-mvp/install.sh +33 -0
  1971. package/.claude/sidecars/c2-vector-memory/lightrag-mvp/manifest.json +59 -0
  1972. package/.claude/sidecars/c5-dev-tools/hypothesis/boundary_test.py +142 -0
  1973. package/.claude/sidecars/c5-dev-tools/hypothesis/install.sh +46 -0
  1974. package/.claude/sidecars/c5-dev-tools/hypothesis/manifest.json +52 -0
  1975. package/.claude/sidecars/c5-dev-tools/hypothesis/tests/__init__.py +0 -0
  1976. package/.claude/sidecars/c5-dev-tools/hypothesis/tests/test_audit_emit_known_actions_property.py +123 -0
  1977. package/.claude/sidecars/c5-dev-tools/hypothesis/tests/test_canonical_guard_symmetry_property.py +67 -0
  1978. package/.claude/sidecars/c5-dev-tools/hypothesis/tests/test_payload_roundtrip_property.py +73 -0
  1979. package/.claude/sidecars/c5-dev-tools/hypothesis/tests/test_redact_idempotence_property.py +68 -0
  1980. package/.claude/skill-governance-grandfather.yaml +39 -0
  1981. package/.claude/skill-patch-signers.txt +19 -0
  1982. package/.claude/skills/core/agent-architect/SKILL.md +126 -0
  1983. package/.claude/skills/core/ai-llm-orchestration/SKILL.md +620 -0
  1984. package/.claude/skills/core/ai-llm-orchestration/SKILL.md.shadow.md +121 -0
  1985. package/.claude/skills/core/architecture-decisions/SKILL.md +364 -0
  1986. package/.claude/skills/core/architecture-decisions/benchmarks/architecture-decisions.yaml +257 -0
  1987. package/.claude/skills/core/ceo-orchestration/SKILL-frontend.md +117 -0
  1988. package/.claude/skills/core/ceo-orchestration/SKILL.md +700 -0
  1989. package/.claude/skills/core/chaos-and-resilience/SKILL.md +568 -0
  1990. package/.claude/skills/core/chaos-and-resilience/SKILL.md.shadow.md +553 -0
  1991. package/.claude/skills/core/code-intelligence-lsp/SKILL.md +375 -0
  1992. package/.claude/skills/core/code-review-checklist/SKILL.md +675 -0
  1993. package/.claude/skills/core/code-review-checklist/SKILL.md.shadow.md +337 -0
  1994. package/.claude/skills/core/code-review-checklist/benchmarks/code-review-checklist.yaml +444 -0
  1995. package/.claude/skills/core/codebase-onboarding/SKILL.md +515 -0
  1996. package/.claude/skills/core/compliance-lgpd/SKILL-frontend.md +513 -0
  1997. package/.claude/skills/core/compliance-lgpd/SKILL.md +817 -0
  1998. package/.claude/skills/core/consent-lifecycle/SKILL.md +149 -0
  1999. package/.claude/skills/core/cookbook-advisor/SKILL.md +191 -0
  2000. package/.claude/skills/core/coverage-audit/SKILL.md +116 -0
  2001. package/.claude/skills/core/cross-llm-pair-review/SKILL.md +212 -0
  2002. package/.claude/skills/core/data-schema-design/SKILL.md +933 -0
  2003. package/.claude/skills/core/devops-ci-cd/SKILL.md +659 -0
  2004. package/.claude/skills/core/dpo-reporting/SKILL.md +187 -0
  2005. package/.claude/skills/core/evidence-based-qa/SKILL.md +565 -0
  2006. package/.claude/skills/core/git-workflow-discipline/SKILL.md +600 -0
  2007. package/.claude/skills/core/growth-and-launch/SKILL-frontend.md +800 -0
  2008. package/.claude/skills/core/growth-and-launch/SKILL.md +903 -0
  2009. package/.claude/skills/core/help-me/SKILL.md +177 -0
  2010. package/.claude/skills/core/help-me/tests/test_help_me_skill.py +490 -0
  2011. package/.claude/skills/core/identity-and-trust-architecture/SKILL.md +1062 -0
  2012. package/.claude/skills/core/incident-management/SKILL.md +421 -0
  2013. package/.claude/skills/core/incremental-refactoring/SKILL-frontend.md +210 -0
  2014. package/.claude/skills/core/incremental-refactoring/SKILL.md +226 -0
  2015. package/.claude/skills/core/llm-routing-and-finops/SKILL.md +828 -0
  2016. package/.claude/skills/core/mcp-server-authoring/SKILL.md +685 -0
  2017. package/.claude/skills/core/minimal-change-discipline/SKILL.md +545 -0
  2018. package/.claude/skills/core/monetization-and-billing/SKILL-frontend.md +562 -0
  2019. package/.claude/skills/core/monetization-and-billing/SKILL.md +585 -0
  2020. package/.claude/skills/core/observability-and-ops/SKILL-frontend.md +290 -0
  2021. package/.claude/skills/core/observability-and-ops/SKILL.md +612 -0
  2022. package/.claude/skills/core/observability-and-ops/SKILL.md.shadow.md +324 -0
  2023. package/.claude/skills/core/parallelization-by-default/SKILL.md +176 -0
  2024. package/.claude/skills/core/parallelization-by-default/tests/test_parallelization_skill.py +490 -0
  2025. package/.claude/skills/core/performance-engineering/SKILL.md +219 -0
  2026. package/.claude/skills/core/performance-engineering/SKILL.md.shadow.md +204 -0
  2027. package/.claude/skills/core/pii-data-flow/SKILL.md +166 -0
  2028. package/.claude/skills/core/pre-plan-brainstorm/CHECKLIST.md +87 -0
  2029. package/.claude/skills/core/pre-plan-brainstorm/SKILL.md +186 -0
  2030. package/.claude/skills/core/product-conversion-readiness/SKILL-frontend.md +668 -0
  2031. package/.claude/skills/core/product-conversion-readiness/SKILL.md +941 -0
  2032. package/.claude/skills/core/public-api-design/SKILL.md +603 -0
  2033. package/.claude/skills/core/public-api-design/benchmarks/public-api-design.yaml +261 -0
  2034. package/.claude/skills/core/receiving-review/SKILL.md +131 -0
  2035. package/.claude/skills/core/receiving-review/benchmarks/receiving-review.yaml +254 -0
  2036. package/.claude/skills/core/requirement-quality-checklist/SKILL.md +97 -0
  2037. package/.claude/skills/core/security-and-auth/SKILL.md +868 -0
  2038. package/.claude/skills/core/security-and-auth/SKILL.md.shadow.md +500 -0
  2039. package/.claude/skills/core/security-and-auth/benchmarks/owasp-basics.yaml +491 -0
  2040. package/.claude/skills/core/security-and-auth/benchmarks/owasp-llm-top-10.yaml +769 -0
  2041. package/.claude/skills/core/spec-clarify/SKILL.md +120 -0
  2042. package/.claude/skills/core/state-machines-and-invariants/SKILL.md +288 -0
  2043. package/.claude/skills/core/technical-writing/SKILL.md +432 -0
  2044. package/.claude/skills/core/terse-mode/SKILL.md +80 -0
  2045. package/.claude/skills/core/terse-mode/SKILL.md.shadow.md +65 -0
  2046. package/.claude/skills/core/testing-strategy/SKILL.md +1026 -0
  2047. package/.claude/skills/core/testing-strategy/SKILL.md.shadow.md +983 -0
  2048. package/.claude/skills/domains/academic-humanities/examples/PLAN-EXAMPLE-ACH.md +126 -0
  2049. package/.claude/skills/domains/academic-humanities/pitfalls.yaml +68 -0
  2050. package/.claude/skills/domains/academic-humanities/skills/anthropologist/SKILL.md +394 -0
  2051. package/.claude/skills/domains/academic-humanities/skills/geographer/SKILL.md +453 -0
  2052. package/.claude/skills/domains/academic-humanities/skills/historian/SKILL.md +255 -0
  2053. package/.claude/skills/domains/academic-humanities/skills/narratologist/SKILL.md +398 -0
  2054. package/.claude/skills/domains/academic-humanities/skills/psychologist/SKILL.md +271 -0
  2055. package/.claude/skills/domains/academic-humanities/task-chains.yaml +125 -0
  2056. package/.claude/skills/domains/academic-humanities/team-personas.md +278 -0
  2057. package/.claude/skills/domains/business-support/examples/PLAN-EXAMPLE-BSP.md +115 -0
  2058. package/.claude/skills/domains/business-support/pitfalls.yaml +69 -0
  2059. package/.claude/skills/domains/business-support/skills/analytics-reporter/SKILL.md +339 -0
  2060. package/.claude/skills/domains/business-support/skills/executive-summary/SKILL.md +268 -0
  2061. package/.claude/skills/domains/business-support/skills/finance-tracker/SKILL.md +321 -0
  2062. package/.claude/skills/domains/business-support/skills/support-responder/SKILL.md +341 -0
  2063. package/.claude/skills/domains/business-support/task-chains.yaml +118 -0
  2064. package/.claude/skills/domains/business-support/team-personas.md +259 -0
  2065. package/.claude/skills/domains/civil-engineering/skills/civil-engineer/SKILL.md +275 -0
  2066. package/.claude/skills/domains/community/NOTICE.md +83 -0
  2067. package/.claude/skills/domains/community/skills/advanced-evaluation/SKILL.md +463 -0
  2068. package/.claude/skills/domains/community/skills/agent-evaluation/SKILL.md +400 -0
  2069. package/.claude/skills/domains/community/skills/agentic-actions-auditor/SKILL.md +410 -0
  2070. package/.claude/skills/domains/community/team-personas.md +41 -0
  2071. package/.claude/skills/domains/devrel/examples/api-deprecation-comms.md +180 -0
  2072. package/.claude/skills/domains/devrel/pitfalls.yaml +74 -0
  2073. package/.claude/skills/domains/devrel/skills/developer-advocate/SKILL.md +382 -0
  2074. package/.claude/skills/domains/devrel/task-chains.yaml +129 -0
  2075. package/.claude/skills/domains/devrel/team-personas.md +260 -0
  2076. package/.claude/skills/domains/edtech/examples/PLAN-EXAMPLE.md +89 -0
  2077. package/.claude/skills/domains/edtech/pitfalls.yaml +98 -0
  2078. package/.claude/skills/domains/edtech/skills/assessment-integrity/SKILL.md +208 -0
  2079. package/.claude/skills/domains/edtech/skills/learning-analytics/SKILL.md +212 -0
  2080. package/.claude/skills/domains/edtech/skills/student-data-privacy/SKILL.md +197 -0
  2081. package/.claude/skills/domains/edtech/skills/study-abroad-advisory/SKILL.md +582 -0
  2082. package/.claude/skills/domains/edtech/task-chains.yaml +122 -0
  2083. package/.claude/skills/domains/edtech/team-personas.md +252 -0
  2084. package/.claude/skills/domains/embedded/skills/embedded-firmware/SKILL.md +471 -0
  2085. package/.claude/skills/domains/finance-accounting/examples/new-subscription-revenue.md +135 -0
  2086. package/.claude/skills/domains/finance-accounting/pitfalls.yaml +74 -0
  2087. package/.claude/skills/domains/finance-accounting/skills/bookkeeper-controller/SKILL.md +427 -0
  2088. package/.claude/skills/domains/finance-accounting/skills/financial-analyst/SKILL.md +348 -0
  2089. package/.claude/skills/domains/finance-accounting/skills/fpa-analyst/SKILL.md +366 -0
  2090. package/.claude/skills/domains/finance-accounting/skills/tax-strategist/SKILL.md +358 -0
  2091. package/.claude/skills/domains/finance-accounting/task-chains.yaml +90 -0
  2092. package/.claude/skills/domains/finance-accounting/team-personas.md +281 -0
  2093. package/.claude/skills/domains/fintech/ORG_CHART.md +167 -0
  2094. package/.claude/skills/domains/fintech/commands/audit-ai.md +124 -0
  2095. package/.claude/skills/domains/fintech/commands/deploy.md +15 -0
  2096. package/.claude/skills/domains/fintech/commands/status.md +13 -0
  2097. package/.claude/skills/domains/fintech/frontend-team-personas.md +503 -0
  2098. package/.claude/skills/domains/fintech/pitfalls.yaml +58 -0
  2099. package/.claude/skills/domains/fintech/scripts/check-pitfall-regression.sh +80 -0
  2100. package/.claude/skills/domains/fintech/scripts/check-type-sync.sh +110 -0
  2101. package/.claude/skills/domains/fintech/skills/blockchain-security-audit/SKILL.md +492 -0
  2102. package/.claude/skills/domains/fintech/skills/equity-research/SKILL.md +459 -0
  2103. package/.claude/skills/domains/fintech/skills/exchange-api-integration/SKILL.md +315 -0
  2104. package/.claude/skills/domains/fintech/skills/exchange-onboarding-playbook/SKILL.md +527 -0
  2105. package/.claude/skills/domains/fintech/skills/financial-correctness-and-math/SKILL-frontend.md +308 -0
  2106. package/.claude/skills/domains/fintech/skills/financial-correctness-and-math/SKILL.md +340 -0
  2107. package/.claude/skills/domains/fintech/skills/financial-display/SKILL.md +193 -0
  2108. package/.claude/skills/domains/fintech/skills/frontend-data-layer/SKILL.md +206 -0
  2109. package/.claude/skills/domains/fintech/skills/frontend-patterns/SKILL.md +387 -0
  2110. package/.claude/skills/domains/fintech/skills/prediction-markets/SKILL.md +139 -0
  2111. package/.claude/skills/domains/fintech/skills/real-time-market-systems/SKILL.md +315 -0
  2112. package/.claude/skills/domains/fintech/skills/solidity-smart-contracts/SKILL.md +356 -0
  2113. package/.claude/skills/domains/fintech/skills/trading-execution/SKILL.md +126 -0
  2114. package/.claude/skills/domains/fintech/task-chains.yaml +46 -0
  2115. package/.claude/skills/domains/fintech/team-personas.md +773 -0
  2116. package/.claude/skills/domains/government/examples/PLAN-EXAMPLE.md +158 -0
  2117. package/.claude/skills/domains/government/pitfalls.yaml +114 -0
  2118. package/.claude/skills/domains/government/skills/accessibility-section-508/SKILL.md +183 -0
  2119. package/.claude/skills/domains/government/skills/digital-presales/SKILL.md +359 -0
  2120. package/.claude/skills/domains/government/skills/foia-and-records/SKILL.md +211 -0
  2121. package/.claude/skills/domains/government/skills/public-procurement/SKILL.md +264 -0
  2122. package/.claude/skills/domains/government/task-chains.yaml +88 -0
  2123. package/.claude/skills/domains/government/team-personas.md +296 -0
  2124. package/.claude/skills/domains/healthcare/examples/patient-portal-symptom-checker.md +130 -0
  2125. package/.claude/skills/domains/healthcare/pitfalls.yaml +74 -0
  2126. package/.claude/skills/domains/healthcare/skills/healthcare-customer-service/SKILL.md +369 -0
  2127. package/.claude/skills/domains/healthcare/skills/marketing-compliance/SKILL.md +367 -0
  2128. package/.claude/skills/domains/healthcare/task-chains.yaml +87 -0
  2129. package/.claude/skills/domains/healthcare/team-personas.md +273 -0
  2130. package/.claude/skills/domains/hospitality/skills/guest-services/SKILL.md +417 -0
  2131. package/.claude/skills/domains/hr/examples/attrition-model-launch.md +128 -0
  2132. package/.claude/skills/domains/hr/pitfalls.yaml +74 -0
  2133. package/.claude/skills/domains/hr/skills/hr-onboarding/SKILL.md +435 -0
  2134. package/.claude/skills/domains/hr/skills/recruitment-specialist/SKILL.md +400 -0
  2135. package/.claude/skills/domains/hr/task-chains.yaml +91 -0
  2136. package/.claude/skills/domains/hr/team-personas.md +251 -0
  2137. package/.claude/skills/domains/i18n-business/examples/PLAN-EXAMPLE-I18N.md +115 -0
  2138. package/.claude/skills/domains/i18n-business/pitfalls.yaml +68 -0
  2139. package/.claude/skills/domains/i18n-business/skills/cultural-intelligence/SKILL.md +448 -0
  2140. package/.claude/skills/domains/i18n-business/skills/french-consulting/SKILL.md +347 -0
  2141. package/.claude/skills/domains/i18n-business/skills/korean-business/SKILL.md +360 -0
  2142. package/.claude/skills/domains/i18n-business/skills/language-translator/SKILL.md +389 -0
  2143. package/.claude/skills/domains/i18n-business/task-chains.yaml +117 -0
  2144. package/.claude/skills/domains/i18n-business/team-personas.md +258 -0
  2145. package/.claude/skills/domains/identity-systems/examples/passkey-rollout.md +137 -0
  2146. package/.claude/skills/domains/identity-systems/pitfalls.yaml +74 -0
  2147. package/.claude/skills/domains/identity-systems/skills/identity-graph-operator/SKILL.md +353 -0
  2148. package/.claude/skills/domains/identity-systems/task-chains.yaml +90 -0
  2149. package/.claude/skills/domains/identity-systems/team-personas.md +233 -0
  2150. package/.claude/skills/domains/legal/examples/client-intake-pii-flow.md +177 -0
  2151. package/.claude/skills/domains/legal/pitfalls.yaml +77 -0
  2152. package/.claude/skills/domains/legal/skills/client-intake/SKILL.md +407 -0
  2153. package/.claude/skills/domains/legal/skills/document-review/SKILL.md +373 -0
  2154. package/.claude/skills/domains/legal/skills/legal-billing/SKILL.md +331 -0
  2155. package/.claude/skills/domains/legal/task-chains.yaml +131 -0
  2156. package/.claude/skills/domains/legal/team-personas.md +260 -0
  2157. package/.claude/skills/domains/lgpd-heavy-saas/examples/PLAN-EXAMPLE.md +120 -0
  2158. package/.claude/skills/domains/lgpd-heavy-saas/pitfalls.yaml +90 -0
  2159. package/.claude/skills/domains/lgpd-heavy-saas/task-chains.yaml +83 -0
  2160. package/.claude/skills/domains/lgpd-heavy-saas/team-personas.md +159 -0
  2161. package/.claude/skills/domains/marketing-global/skills/agentic-search-optimizer/SKILL.md +391 -0
  2162. package/.claude/skills/domains/marketing-global/skills/ai-citation-strategist/SKILL.md +343 -0
  2163. package/.claude/skills/domains/marketing-global/skills/app-store-optimizer/SKILL.md +495 -0
  2164. package/.claude/skills/domains/marketing-global/skills/book-co-author/SKILL.md +220 -0
  2165. package/.claude/skills/domains/marketing-global/skills/carousel-growth-engine/SKILL.md +393 -0
  2166. package/.claude/skills/domains/marketing-global/skills/content-creator/SKILL.md +416 -0
  2167. package/.claude/skills/domains/marketing-global/skills/growth-hacker/SKILL.md +495 -0
  2168. package/.claude/skills/domains/marketing-global/skills/instagram-curator/SKILL.md +419 -0
  2169. package/.claude/skills/domains/marketing-global/skills/linkedin-content-creator/SKILL.md +291 -0
  2170. package/.claude/skills/domains/marketing-global/skills/podcast-strategist/SKILL.md +408 -0
  2171. package/.claude/skills/domains/marketing-global/skills/reddit-community-builder/SKILL.md +295 -0
  2172. package/.claude/skills/domains/marketing-global/skills/seo-specialist/SKILL.md +352 -0
  2173. package/.claude/skills/domains/marketing-global/skills/social-media-strategist/SKILL.md +349 -0
  2174. package/.claude/skills/domains/marketing-global/skills/tiktok-strategist/SKILL.md +329 -0
  2175. package/.claude/skills/domains/marketing-global/skills/twitter-engager/SKILL.md +382 -0
  2176. package/.claude/skills/domains/marketing-global/skills/video-optimization-specialist/SKILL.md +386 -0
  2177. package/.claude/skills/domains/mobile/examples/PLAN-EXAMPLE-MOB.md +129 -0
  2178. package/.claude/skills/domains/mobile/pitfalls.yaml +69 -0
  2179. package/.claude/skills/domains/mobile/skills/mobile-app-builder/SKILL.md +446 -0
  2180. package/.claude/skills/domains/mobile/task-chains.yaml +126 -0
  2181. package/.claude/skills/domains/mobile/team-personas.md +292 -0
  2182. package/.claude/skills/domains/paid-media/examples/new-channel-launch.md +122 -0
  2183. package/.claude/skills/domains/paid-media/pitfalls.yaml +79 -0
  2184. package/.claude/skills/domains/paid-media/skills/auditor/SKILL.md +362 -0
  2185. package/.claude/skills/domains/paid-media/skills/creative-strategist/SKILL.md +457 -0
  2186. package/.claude/skills/domains/paid-media/skills/paid-social-strategist/SKILL.md +493 -0
  2187. package/.claude/skills/domains/paid-media/skills/ppc-strategist/SKILL.md +450 -0
  2188. package/.claude/skills/domains/paid-media/skills/programmatic-buyer/SKILL.md +396 -0
  2189. package/.claude/skills/domains/paid-media/skills/search-query-analyst/SKILL.md +336 -0
  2190. package/.claude/skills/domains/paid-media/skills/tracking-specialist/SKILL.md +457 -0
  2191. package/.claude/skills/domains/paid-media/task-chains.yaml +121 -0
  2192. package/.claude/skills/domains/paid-media/team-personas.md +251 -0
  2193. package/.claude/skills/domains/project-management/examples/PLAN-EXAMPLE-PMG.md +117 -0
  2194. package/.claude/skills/domains/project-management/pitfalls.yaml +68 -0
  2195. package/.claude/skills/domains/project-management/skills/experiment-tracker/SKILL.md +293 -0
  2196. package/.claude/skills/domains/project-management/skills/project-shepherd/SKILL.md +312 -0
  2197. package/.claude/skills/domains/project-management/skills/studio-operations/SKILL.md +333 -0
  2198. package/.claude/skills/domains/project-management/skills/studio-producer/SKILL.md +329 -0
  2199. package/.claude/skills/domains/project-management/task-chains.yaml +118 -0
  2200. package/.claude/skills/domains/project-management/team-personas.md +264 -0
  2201. package/.claude/skills/domains/real-estate-finance/examples/PLAN-EXAMPLE-REF.md +129 -0
  2202. package/.claude/skills/domains/real-estate-finance/pitfalls.yaml +68 -0
  2203. package/.claude/skills/domains/real-estate-finance/skills/buyer-seller-agent/SKILL.md +410 -0
  2204. package/.claude/skills/domains/real-estate-finance/skills/loan-officer-assistant/SKILL.md +415 -0
  2205. package/.claude/skills/domains/real-estate-finance/task-chains.yaml +123 -0
  2206. package/.claude/skills/domains/real-estate-finance/team-personas.md +287 -0
  2207. package/.claude/skills/domains/retail/skills/customer-returns/SKILL.md +363 -0
  2208. package/.claude/skills/domains/saas-platforms/examples/enterprise-tier-isolation.md +147 -0
  2209. package/.claude/skills/domains/saas-platforms/pitfalls.yaml +74 -0
  2210. package/.claude/skills/domains/saas-platforms/skills/cms-developer/SKILL.md +377 -0
  2211. package/.claude/skills/domains/saas-platforms/skills/filament-specialist/SKILL.md +316 -0
  2212. package/.claude/skills/domains/saas-platforms/skills/salesforce-architect/SKILL.md +369 -0
  2213. package/.claude/skills/domains/saas-platforms/task-chains.yaml +90 -0
  2214. package/.claude/skills/domains/saas-platforms/team-personas.md +283 -0
  2215. package/.claude/skills/domains/sales/examples/qbr-revenue-forecast.md +158 -0
  2216. package/.claude/skills/domains/sales/pitfalls.yaml +73 -0
  2217. package/.claude/skills/domains/sales/skills/account-strategist/SKILL.md +408 -0
  2218. package/.claude/skills/domains/sales/skills/deal-strategist/SKILL.md +292 -0
  2219. package/.claude/skills/domains/sales/skills/discovery-coach/SKILL.md +257 -0
  2220. package/.claude/skills/domains/sales/skills/outbound-strategist/SKILL.md +262 -0
  2221. package/.claude/skills/domains/sales/skills/pipeline-analyst/SKILL.md +317 -0
  2222. package/.claude/skills/domains/sales/skills/proposal-strategist/SKILL.md +288 -0
  2223. package/.claude/skills/domains/sales/skills/sales-coach/SKILL.md +306 -0
  2224. package/.claude/skills/domains/sales/skills/sales-engineer/SKILL.md +272 -0
  2225. package/.claude/skills/domains/sales/skills/sales-outreach/SKILL.md +338 -0
  2226. package/.claude/skills/domains/sales/task-chains.yaml +123 -0
  2227. package/.claude/skills/domains/sales/team-personas.md +249 -0
  2228. package/.claude/skills/domains/supply-chain/skills/supply-chain-strategist/SKILL.md +340 -0
  2229. package/.claude/skills/domains/trading-hft/examples/PLAN-EXAMPLE.md +145 -0
  2230. package/.claude/skills/domains/trading-hft/pitfalls.yaml +99 -0
  2231. package/.claude/skills/domains/trading-hft/skills/kill-switches/SKILL.md +128 -0
  2232. package/.claude/skills/domains/trading-hft/skills/latency-budgets/SKILL.md +117 -0
  2233. package/.claude/skills/domains/trading-hft/skills/order-routing/SKILL.md +97 -0
  2234. package/.claude/skills/domains/trading-hft/task-chains.yaml +97 -0
  2235. package/.claude/skills/domains/trading-hft/team-personas.md +155 -0
  2236. package/.claude/skills/domains/training-l-and-d/skills/corporate-training-designer/SKILL.md +268 -0
  2237. package/.claude/skills/domains/voice-ai/skills/voice-ai-integration/SKILL.md +405 -0
  2238. package/.claude/skills/frontend/NOTICE.md +80 -0
  2239. package/.claude/skills/frontend/accessibility-and-wcag/SKILL.md +395 -0
  2240. package/.claude/skills/frontend/accessibility-and-wcag/SKILL.md.shadow.md +181 -0
  2241. package/.claude/skills/frontend/accessibility-and-wcag/benchmarks/accessibility-and-wcag.yaml +420 -0
  2242. package/.claude/skills/frontend/accessibility-and-wcag/reference/charts-accessibility.yaml +357 -0
  2243. package/.claude/skills/frontend/code-quality-and-typescript/SKILL.md +167 -0
  2244. package/.claude/skills/frontend/design-system-and-components/SKILL.md +155 -0
  2245. package/.claude/skills/frontend/design-system-and-components/SKILL.md.shadow.md +138 -0
  2246. package/.claude/skills/frontend/design-system-and-components/reference/fonts.yaml +811 -0
  2247. package/.claude/skills/frontend/design-system-and-components/reference/palettes.yaml +3066 -0
  2248. package/.claude/skills/frontend/frontend-accessibility/SKILL.md +213 -0
  2249. package/.claude/skills/frontend/frontend-data-layer/SKILL.md +310 -0
  2250. package/.claude/skills/frontend/frontend-patterns/SKILL.md +771 -0
  2251. package/.claude/skills/frontend/frontend-performance-optimization/SKILL.md +228 -0
  2252. package/.claude/skills/frontend/frontend-performance-optimization/SKILL.md.shadow.md +213 -0
  2253. package/.claude/skills/frontend/ux-and-user-journeys/SKILL.md +153 -0
  2254. package/.claude/skills/frontend/ux-and-user-journeys/SKILL.md.shadow.md +138 -0
  2255. package/.claude/skills/frontend/ux-and-user-journeys/reference/guidelines.yaml +997 -0
  2256. package/.claude/squad-revocations.jsonl +5 -0
  2257. package/.claude/task-chains.yaml +151 -0
  2258. package/.claude/team.md +825 -0
  2259. package/.claude/templates/squad-bundle/README.md +208 -0
  2260. package/.claude/templates/squad-bundle/conftest.py +27 -0
  2261. package/.claude/templates/squad-bundle/examples/template-example.md.template +94 -0
  2262. package/.claude/templates/squad-bundle/pitfalls.yaml.template +88 -0
  2263. package/.claude/templates/squad-bundle/task-chains.yaml.template +92 -0
  2264. package/.claude/templates/squad-bundle/team-personas.md.template +161 -0
  2265. package/.claude/trust/README.md +89 -0
  2266. package/.claude/trust/owner.asc +11 -0
  2267. package/.claude/workflows/README.md +124 -0
  2268. package/.claude/workflows/audit-fanout.js +204 -0
  2269. package/.claude/workflows/eval-baseline-n20.js +330 -0
  2270. package/.claude/workflows/nightly-hygiene.js +176 -0
  2271. package/LICENSE +21 -0
  2272. package/PROTOCOL.md +597 -0
  2273. package/README.md +167 -0
  2274. package/SPEC/v1/README.md +181 -0
  2275. package/SPEC/v1/adapters.schema.md +272 -0
  2276. package/SPEC/v1/audit-log.schema.md +1514 -0
  2277. package/SPEC/v1/audit-query.schema.md +152 -0
  2278. package/SPEC/v1/benchmarks.schema.md +166 -0
  2279. package/SPEC/v1/claude-sdk-compat.md +123 -0
  2280. package/SPEC/v1/debate.schema.md +35 -0
  2281. package/SPEC/v1/hook-io.schema.md +94 -0
  2282. package/SPEC/v1/install-cli.md +234 -0
  2283. package/SPEC/v1/judge-payload.schema.md +98 -0
  2284. package/SPEC/v1/live-adapters-policy.schema.md +118 -0
  2285. package/SPEC/v1/mcp-server.schema.md +558 -0
  2286. package/SPEC/v1/memory-shared.schema.md +365 -0
  2287. package/SPEC/v1/normalized_envelope.schema.md +183 -0
  2288. package/SPEC/v1/npm-shim.md +95 -0
  2289. package/SPEC/v1/plan.schema.md +34 -0
  2290. package/SPEC/v1/policy-dsl.schema.md +466 -0
  2291. package/SPEC/v1/predict-budget.schema.md +289 -0
  2292. package/SPEC/v1/rag-sidecar.schema.md +222 -0
  2293. package/SPEC/v1/red-team-corpus.schema.md +186 -0
  2294. package/SPEC/v1/replay.schema.md +272 -0
  2295. package/SPEC/v1/scratchpad.schema.md +172 -0
  2296. package/SPEC/v1/sentinel-format.schema.md +306 -0
  2297. package/SPEC/v1/session-graph.schema.md +236 -0
  2298. package/SPEC/v1/skill-frontmatter.schema.md +83 -0
  2299. package/SPEC/v1/skill-index.schema.md +197 -0
  2300. package/SPEC/v1/skill-proposals.schema.md +175 -0
  2301. package/SPEC/v1/soc2-control-map.schema.md +797 -0
  2302. package/SPEC/v1/squad-manifest.schema.md +157 -0
  2303. package/SPEC/v1/state-stores.schema.md +146 -0
  2304. package/SPEC/v1/tier-policy.schema.md +264 -0
  2305. package/SPEC/v1/tournament-report.schema.md +156 -0
  2306. package/VERSION +1 -0
  2307. package/bin/ceo-orch-init.js +55 -0
  2308. package/package.json +42 -0
  2309. package/scripts/_framework_manifest_set.sh +237 -0
  2310. package/scripts/_hash_lib.sh +92 -0
  2311. package/scripts/build-plugin.py +351 -0
  2312. package/scripts/discover_foreign_context.py +151 -0
  2313. package/scripts/install-accelerators.sh +166 -0
  2314. package/scripts/install-npm.sh +254 -0
  2315. package/scripts/install.sh +1932 -0
  2316. package/scripts/local/OWNER-CEREMONY-PLAN-094-WAVE-A.sh +648 -0
  2317. package/scripts/local/OWNER-CEREMONY-S82-V1120.sh +169 -0
  2318. package/scripts/local/plan-093-apply-kernel-edits.py +496 -0
  2319. package/scripts/local/plan-093-execute-ceremony.sh +118 -0
  2320. package/scripts/local/plan-093-kernel-override-restart.sh +115 -0
  2321. package/scripts/local/plan-093-ship-v1.26.0.sh +226 -0
  2322. package/scripts/local/plan-094-apply-wave-a-c-e.py +398 -0
  2323. package/scripts/local/smoke-install-parity.sh +168 -0
  2324. package/scripts/local/trading-readonly-escape-hatch.sh +244 -0
  2325. package/scripts/measure-repo-size.sh +98 -0
  2326. package/scripts/npm-rebuild.sh +172 -0
  2327. package/scripts/publish-plugin.sh +144 -0
  2328. package/scripts/tests/smoke-install.sh +260 -0
  2329. package/scripts/tests/test-install-sandbox-merge.sh +137 -0
  2330. package/scripts/tests/test_install_baseline_manifest.sh +392 -0
  2331. package/scripts/uninstall.sh +282 -0
  2332. package/scripts/upgrade.sh +1260 -0
  2333. package/templates/.claude/tier-policy.json +35 -0
  2334. package/templates/.claude/tier-policy.json.sigchain +1 -0
  2335. package/templates/.env.example +134 -0
  2336. package/templates/.github/CODEOWNERS.template +33 -0
  2337. package/templates/.github/workflows/benchmarks.yml.template +145 -0
  2338. package/templates/.github/workflows/validate.yml.template +226 -0
  2339. package/templates/.mcp.json +13 -0
  2340. package/templates/CLAUDE.md +125 -0
  2341. package/templates/MEMORY.md +36 -0
  2342. package/templates/README.md +46 -0
  2343. package/templates/compaction.md +130 -0
  2344. package/templates/docs/BRANCH-PROTECTION.md +203 -0
  2345. package/templates/docs/rotation-log.md +18 -0
  2346. package/templates/oidc-proxy/README.md +141 -0
  2347. package/templates/oidc-proxy/broker.config.example.json +29 -0
  2348. package/templates/oidc-proxy/oidc_key_broker.py +361 -0
  2349. package/templates/oidc-proxy/tests/test_oidc_key_broker.py +361 -0
  2350. package/templates/scripts/statusline-ceo.py +597 -0
  2351. package/templates/settings/settings.base.json +708 -0
  2352. package/templates/settings/settings.stack.node.json +19 -0
  2353. package/templates/settings/settings.stack.otel.json +25 -0
  2354. package/templates/settings/settings.stack.sandbox.json +57 -0
  2355. package/templates/settings/settings.user.json +265 -0
  2356. package/templates/team-personas-reference.md +269 -0
@@ -0,0 +1,2613 @@
1
+ # PLAN-094 Wave A — spool_writer (canonical promotion of spool_writer_DRAFT.py)
2
+ """_lib/spool_writer.py — durable spool + drain-on-next-invoke (PLAN-094 Wave A).
3
+
4
+ Implements ADR-055-AMEND-1: 5-phase atomic drain protocol, 4-tuple total
5
+ order across concurrent producers, K_MAX/K_TAIL_WINDOW idempotent skip,
6
+ canonical-tail prev_hmac reconstruction, per-PID spool + per-PID journal,
7
+ atomic split-and-delete for K_MAX deferral, header/body uuid sentinel.
8
+
9
+ Stdlib-only (ADR-002). Fail-open invariant — never raise to caller; any
10
+ infra failure emits a breadcrumb and returns safely.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import atexit
16
+ import hashlib
17
+ import json
18
+ import os
19
+ import re
20
+ import secrets
21
+ import signal
22
+ import sys
23
+ import time
24
+ import uuid
25
+ from dataclasses import dataclass, field
26
+ from pathlib import Path
27
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple
28
+
29
+ _HOOKS_DIR = Path(__file__).resolve().parent.parent
30
+ if str(_HOOKS_DIR) not in sys.path:
31
+ sys.path.insert(0, str(_HOOKS_DIR))
32
+
33
+ from _lib.filelock import FileLock, FileLockTimeout # noqa: E402
34
+
35
+ try:
36
+ from _lib import audit_hmac as _audit_hmac # noqa: E402
37
+ _HMAC_AVAILABLE = True
38
+ except Exception: # pragma: no cover
39
+ _audit_hmac = None # type: ignore[assignment]
40
+ _HMAC_AVAILABLE = False
41
+
42
+ try:
43
+ from _lib import canonical_json as _canonical_json # noqa: E402
44
+ except Exception: # pragma: no cover
45
+ _canonical_json = None # type: ignore[assignment]
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Constants (ADR-055-AMEND-1 §3 wave A spec)
50
+ # ---------------------------------------------------------------------------
51
+
52
+ K_MAX = 100
53
+ K_TAIL_WINDOW = 200
54
+ SPOOL_HEADER_VERSION = 1
55
+ # PLAN-111 Wave B-alt4 (debate consensus + cProfile data-driven): trigger
56
+ # raised 50 -> 100. Halves drain cascade count (40 -> 20 across 5-trial
57
+ # emit_pair benchmark, confirmed by cprofile-post-b-darwin.txt). Per-trial
58
+ # wall-clock 174ms -> 170ms (-2%); cumulative drain_now cumtime 364ms -> 307ms.
59
+ # Combined with Wave A cache: per-emit 580us -> 425us (-27%). AC-C1 strict
60
+ # <=360us NOT hit alone; Wave C.1 RELAX path engaged (200->300ms test
61
+ # budget) with ubuntu projection ~255ms still well under.
62
+ DRAIN_TRIGGER_SIZE = 100
63
+ DRAIN_TRIGGER_MTIME_MS = 100
64
+ STALE_SPOOL_TTL_DAYS = 7
65
+ SPOOL_LOCK_TIMEOUT = 2.5
66
+
67
+ _SPOOL_PREFIX = "audit-spool"
68
+ _JOURNAL_PREFIX = "audit-pending"
69
+ _DRAINING_SUFFIX_TOKEN = ".draining."
70
+ _MALFORMED_SUFFIX_TOKEN = ".malformed."
71
+ _QUARANTINED_SUFFIX_TOKEN = ".quarantined."
72
+ _TMP_SUFFIX_TOKEN = ".tmp."
73
+ # PLAN-119 WS-D1 — test-origin spool quarantine suffix. A spool minted under a
74
+ # test signal is stamped ``_origin:"test"`` in its header; if such a spool is
75
+ # ever drained while the canonical destination IS the live chain, it is renamed
76
+ # out of the drain path with this token so its entries never reach the live
77
+ # canonical append.
78
+ _TEST_ORIGIN_SUFFIX_TOKEN = ".test-origin."
79
+ # PLAN-119 WS-D1 — the live-log path snapshot env var. Set by the WS-A session
80
+ # fixture (``_lib/test_isolation``) BEFORE it redirects the env, so the drainer —
81
+ # which post-redirect cannot infer the real live path from the environment — can
82
+ # compare the canonical destination against it. The literal is DUPLICATED here
83
+ # (NOT imported) because this kernel module must not depend on the test helper;
84
+ # the two MUST stay in sync (see ``_lib/test_isolation.LIVE_LOG_SNAPSHOT_VAR``).
85
+ _LIVE_LOG_SNAPSHOT_VAR = "CEO_AUDIT_LIVE_LOG_PATH_SNAPSHOT"
86
+
87
+ _SPOOL_UUID_HEX_LEN = 16 # secrets.token_hex(8) -> 16 hex chars
88
+ _DRAIN_EPOCH_HEX_LEN = 8 # secrets.token_hex(4) -> 8 hex chars
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Public dataclasses
93
+ # ---------------------------------------------------------------------------
94
+
95
+
96
+ @dataclass
97
+ class DrainStats:
98
+ """Summary of a single drain_now() invocation.
99
+
100
+ iter3-P2-1: `intentionally_deleted` counts entries silently removed from
101
+ spool during drain (duplicate-tuple rejection — paired with the
102
+ audit_spool_intentionally_deleted forensic emit). Surfaced into
103
+ JournalReconciliation.intentionally_deleted for AC10 visibility.
104
+ """
105
+
106
+ appended: int = 0
107
+ skipped_idempotent: int = 0
108
+ rejected_duplicate_tuple: int = 0
109
+ intentionally_deleted: int = 0
110
+ quarantined_files: int = 0
111
+ # PLAN-119 WS-D1 — count of _origin:"test" spools refused at a live-chain drain.
112
+ test_origin_quarantined: int = 0
113
+ partial_lines_discarded: int = 0
114
+ files_consumed_fully: int = 0
115
+ files_split_remainder: int = 0
116
+ in_recovery_mode: bool = False
117
+ drain_epoch: Optional[str] = None
118
+ ok: bool = True
119
+ error: Optional[str] = None
120
+ # (ADR-055-AMEND-3) — True when an OPPORTUNISTIC (force=False)
121
+ # drain yielded the canonical lock to another drainer without blocking.
122
+ # `ok` stays True (not an error): the lock holder's global sweep plus the
123
+ # yielding process's own later drain cover its events. NEVER persisted to
124
+ # the audit log (in-process DrainStats only; no Sec MF-3 surface).
125
+ contended_skip: bool = False
126
+
127
+
128
+ @dataclass
129
+ class JournalReconciliation:
130
+ """Counters emitted to audit_flush_dropped_count at session-start."""
131
+
132
+ begin_no_commit: int = 0
133
+ commit_no_drained: int = 0
134
+ recovered: int = 0
135
+ truly_lost: int = 0
136
+ tamper_rejected: int = 0
137
+ intentionally_deleted: int = 0
138
+
139
+
140
+ # ---------------------------------------------------------------------------
141
+ # Path helpers — mirror audit_emit conventions
142
+ # ---------------------------------------------------------------------------
143
+
144
+
145
+ # PLAN-111 Wave A — single-slot cache for _project_dir_from_env + _state_dir.
146
+ # Keyed on env-tuple (CEO_AUDIT_LOG_DIR, HOME); replace-on-miss; explicit
147
+ # reset via _reset_caches_for_test() bound to TestEnvContext.setUp/tearDown.
148
+ # Single-threaded contract: concurrent os.environ mutation = UB; future
149
+ # threading plans must add per-thread cache or thread-local env snapshot.
150
+ # Debate Round 1: SA-K1 (single-slot), SA-K7 (single-threaded), SA-K10
151
+ # (permission re-assertion on cache MISS), AC-A2a (store-on-mkdir-success
152
+ # only; no cached-as-unusable Path).
153
+ _PROJECT_DIR_CACHE: "Optional[Tuple[Tuple[Optional[str], Optional[str]], Path]]" = None
154
+ _STATE_DIR_CACHE: "Optional[Tuple[Tuple[Optional[str], Optional[str]], Path]]" = None
155
+
156
+
157
+ def _reset_caches_for_test() -> None:
158
+ """Clear _project_dir_from_env + _state_dir caches.
159
+
160
+ PLAN-111 Wave A.5: bound to TestEnvContext.setUp/tearDown to avoid
161
+ cross-test leakage when CEO_AUDIT_LOG_DIR or HOME mutate. Also bound
162
+ via unittest.addModuleCleanup in test_lifecycle_edge_cases.py for
163
+ bare-TestCase classes that don't inherit TestEnvContext.
164
+
165
+ PRODUCTION CODE MUST NOT CALL THIS — it's a test-only API. Calling
166
+ mid-flight flushes the cache and re-pays the mkdir + lstat cost on
167
+ next emit (correctness preserved; perf regressed for one call).
168
+ """
169
+ global _PROJECT_DIR_CACHE, _STATE_DIR_CACHE
170
+ _PROJECT_DIR_CACHE = None
171
+ _STATE_DIR_CACHE = None
172
+
173
+
174
+ def _project_dir_from_env() -> Path:
175
+ """Return the audit project dir (BYTE-IDENTICAL to audit_emit._audit_dir).
176
+
177
+ P1-5: must mirror audit_emit._audit_dir() exactly — only CEO_AUDIT_LOG_DIR
178
+ + HOME. CEO_AUDIT_LOG_PATH is the FILE path, not the dir path, so we do
179
+ NOT derive the dir from it (audit_emit doesn't either).
180
+
181
+ PLAN-111 Wave A: single-slot cache keyed on (CEO_AUDIT_LOG_DIR, HOME).
182
+ Cache HIT skips re-construction of Path object (saves ~7us/call x
183
+ 5.7x/emit cumulative). Cache MISS replaces the slot atomically.
184
+
185
+ Single-threaded contract: concurrent os.environ mutation = UB.
186
+ """
187
+ global _PROJECT_DIR_CACHE
188
+ env_dir = os.environ.get("CEO_AUDIT_LOG_DIR")
189
+ env_home = os.environ.get("HOME")
190
+ env_key = (env_dir, env_home)
191
+ cached = _PROJECT_DIR_CACHE
192
+ if cached is not None and cached[0] == env_key:
193
+ return cached[1]
194
+ if env_dir:
195
+ p = Path(env_dir)
196
+ else:
197
+ home = env_home or str(Path.home())
198
+ p = Path(home) / ".claude" / "projects" / "ceo-orchestration"
199
+ _PROJECT_DIR_CACHE = (env_key, p)
200
+ return p
201
+
202
+
203
+ def _state_dir() -> Path:
204
+ """Return <audit_dir>/state/ ensuring parents exist (0700).
205
+
206
+ P1-5: state dir is unconditionally a child of audit_emit._audit_dir();
207
+ no CEO_PROJECT_STATE_DIR override (audit_emit doesn't expose one).
208
+
209
+ PLAN-111 Wave A: single-slot cache keyed on (CEO_AUDIT_LOG_DIR, HOME).
210
+ Cache HIT skips mkdir syscall + Path construction (saves ~17us/call x
211
+ 5.7x/emit cumulative ~= 100us/emit recovery).
212
+
213
+ Cache MISS semantics (AC-A2a + SA-K10 permission re-assertion +
214
+ PLAN-113 W4-SEC mode-mismatch self-heal):
215
+ 1. Resolve target Path (uses _project_dir_from_env cache implicitly).
216
+ 2. Attempt mkdir(parents=True, exist_ok=True, mode=0o700).
217
+ 3. If mkdir FAILS: breadcrumb + DO NOT cache (next call retries).
218
+ 4. If mkdir SUCCEEDS: validate via lstat() that dir is not a symlink,
219
+ is owned by os.getuid(), and has mode 0o700.
220
+ - symlink / wrong-owner mismatch: FAIL-CLOSED (raise) — we never
221
+ chmod-and-trust an attacker-controlled or other-owned dir.
222
+ - mode-only mismatch (e.g. a pre-existing state/ at 0o755 created
223
+ by an older path; exist_ok=True does NOT relax perms): attempt
224
+ SELF-HEAL via os.chmod(d, 0o700) then RE-lstat + re-validate the
225
+ full invariant. If it is now a non-symlink, owned by os.getuid(),
226
+ AND 0o700 → PROCEED (cache + return). Otherwise keep the SA-K10
227
+ fail-CLOSED.
228
+ 5. Only if validation (incl. any self-heal) succeeds: cache the Path.
229
+
230
+ Single-threaded contract: concurrent os.environ mutation = UB.
231
+ """
232
+ global _STATE_DIR_CACHE
233
+ env_dir = os.environ.get("CEO_AUDIT_LOG_DIR")
234
+ env_home = os.environ.get("HOME")
235
+ env_key = (env_dir, env_home)
236
+ cached = _STATE_DIR_CACHE
237
+ if cached is not None and cached[0] == env_key:
238
+ return cached[1]
239
+ d = _project_dir_from_env() / "state"
240
+ mkdir_ok = False
241
+ try:
242
+ d.mkdir(parents=True, exist_ok=True, mode=0o700)
243
+ mkdir_ok = True
244
+ except OSError as e:
245
+ _breadcrumb(f"_state_dir mkdir failed (no cache): "
246
+ f"{type(e).__name__}: {e}")
247
+ if mkdir_ok:
248
+ try:
249
+ import stat as _stat_mod
250
+
251
+ def _classify(st_mode: int, st_uid: int) -> "Optional[str]":
252
+ """Return a mismatch reason, or None when state/ is healthy."""
253
+ if _stat_mod.S_ISLNK(st_mode):
254
+ return f"is_symlink: {d}"
255
+ if st_uid != os.getuid():
256
+ return f"uid_mismatch: {st_uid} != {os.getuid()}"
257
+ if (st_mode & 0o777) != 0o700:
258
+ return f"mode_mismatch: {oct(st_mode & 0o777)} != 0o700"
259
+ return None
260
+
261
+ st = os.lstat(str(d))
262
+ mismatch_reason: "Optional[str]" = _classify(st.st_mode, st.st_uid)
263
+
264
+ # PLAN-113 W4-SEC self-heal: a pre-existing state/ dir at a mode
265
+ # other than 0o700 (most commonly 0o755 from an older code path —
266
+ # exist_ok=True does NOT relax perms on an existing dir)
267
+ # previously fail-CLOSED on EVERY call, dropping the audit event
268
+ # and flooding the spool. Self-heal ONLY the mode-mismatch case
269
+ # (NEVER symlink / wrong-owner — chmod-and-trusting those is
270
+ # exactly the attack we fail closed on): chmod 0o700, then
271
+ # RE-lstat and re-validate the FULL invariant from scratch.
272
+ if (mismatch_reason is not None
273
+ and not _stat_mod.S_ISLNK(st.st_mode)
274
+ and st.st_uid == os.getuid()):
275
+ healed = False
276
+ fd = None
277
+ try:
278
+ # PLAN-113 Codex B3 P2 — close the chmod TOCTOU: a path-based
279
+ # os.chmod follows a symlink if state/ is swapped between the
280
+ # initial lstat and the chmod, applying 0o700 to the attacker's
281
+ # target. Open with O_NOFOLLOW (refuse a symlinked final
282
+ # component) | O_DIRECTORY (refuse a non-dir), then fchmod +
283
+ # fstat the FD — no path re-resolution, so no race window.
284
+ fd = os.open(
285
+ str(d), os.O_RDONLY | os.O_NOFOLLOW | os.O_DIRECTORY
286
+ )
287
+ os.fchmod(fd, 0o700)
288
+ fst = os.fstat(fd)
289
+ mismatch_reason = _classify(fst.st_mode, fst.st_uid)
290
+ if mismatch_reason is None:
291
+ st = fst
292
+ healed = True
293
+ except OSError as e:
294
+ _breadcrumb(
295
+ f"_state_dir self-heal (O_NOFOLLOW fchmod) failed "
296
+ f"(FAIL-CLOSED): {type(e).__name__}: {e}"
297
+ )
298
+ finally:
299
+ if fd is not None:
300
+ try:
301
+ os.close(fd)
302
+ except OSError:
303
+ pass
304
+ if healed:
305
+ _breadcrumb(
306
+ f"_state_dir mode self-healed to 0o700: {d}"
307
+ )
308
+
309
+ if mismatch_reason is not None:
310
+ # PLAN-111 Wave A SA-K10 fail-CLOSED (Codex R2 P0 fix):
311
+ # detecting + not-caching is fake-security; caller would
312
+ # still write to the attacker-controlled symlink target.
313
+ # Raise OSError so callers' fail-open path catches it
314
+ # (matches existing _state_dir fail-open semantics) and
315
+ # diverts the write to fallback path. PLAN-113 W4-SEC: we
316
+ # only reach here for symlink / wrong-owner, OR a mode
317
+ # mismatch the chmod self-heal could not resolve.
318
+ _breadcrumb(
319
+ f"_state_dir SECURITY mismatch (FAIL-CLOSED): "
320
+ f"{mismatch_reason}"
321
+ )
322
+ raise PermissionError(
323
+ f"_state_dir permission re-assertion failed: "
324
+ f"{mismatch_reason}"
325
+ )
326
+ _STATE_DIR_CACHE = (env_key, d)
327
+ except PermissionError:
328
+ # Re-raise the SA-K10 fail-CLOSED so caller handles.
329
+ raise
330
+ except OSError as e:
331
+ _breadcrumb(f"_state_dir lstat failed (no cache): "
332
+ f"{type(e).__name__}: {e}")
333
+ return d
334
+
335
+
336
+ def _canonical_log_path() -> Path:
337
+ """Mirror audit_emit._log_path() — canonical audit-log.jsonl."""
338
+ env = os.environ.get("CEO_AUDIT_LOG_PATH")
339
+ if env:
340
+ return Path(env)
341
+ return _project_dir_from_env() / "audit-log.jsonl"
342
+
343
+
344
+ def _canonical_log_lock() -> Path:
345
+ """Mirror audit_emit._lock_path() — sibling .lock file."""
346
+ env = os.environ.get("CEO_AUDIT_LOG_LOCK")
347
+ if env:
348
+ return Path(env)
349
+ return _project_dir_from_env() / "audit-log.lock"
350
+
351
+
352
+ def _errors_path() -> Path:
353
+ """Breadcrumb file for fail-open infra errors."""
354
+ env = os.environ.get("CEO_AUDIT_LOG_ERR")
355
+ if env:
356
+ return Path(env)
357
+ return _project_dir_from_env() / "audit-log.errors"
358
+
359
+
360
+ def _spool_path(pid: int) -> Path:
361
+ """Active spool path for a given PID (header + body)."""
362
+ return _state_dir() / f"{_SPOOL_PREFIX}.{pid}.jsonl"
363
+
364
+
365
+ def _journal_path(pid: int) -> Path:
366
+ """Per-PID journal path."""
367
+ return _state_dir() / f"{_JOURNAL_PREFIX}.{pid}.journal"
368
+
369
+
370
+ def _aggregate_journal_path() -> Path:
371
+ """Aggregate journal for swept dead-PID envelopes."""
372
+ return _state_dir() / f"{_JOURNAL_PREFIX}.journal"
373
+
374
+
375
+ def _aggregate_journal_lock_path() -> Path:
376
+ """flock for aggregation sweep at session-start."""
377
+ return _state_dir() / f"{_JOURNAL_PREFIX}.journal.aggregation.lock"
378
+
379
+
380
+ def _spool_flock_path(pid: int) -> Path:
381
+ """Per-PID spool flock sibling (NOT same fd as the spool file)."""
382
+ return _state_dir() / f"{_SPOOL_PREFIX}.{pid}.jsonl.lock"
383
+
384
+
385
+ def _journal_flock_path(pid: int) -> Path:
386
+ """Per-PID journal flock sibling."""
387
+ return _state_dir() / f"{_JOURNAL_PREFIX}.{pid}.journal.lock"
388
+
389
+
390
+ # P0-1: single helper for the .draining.<epoch> filename construction.
391
+ # Bug fix — old code did `f"{spool_path}draining.{epoch}"` (missing dot)
392
+ # producing audit-spool.<pid>.jsonldraining.<epoch>; glob sweep failed.
393
+ def _draining_path(spool_path: Path, epoch: str) -> Path:
394
+ """audit-spool.<pid>.jsonl → audit-spool.<pid>.draining.<epoch>."""
395
+ stem = spool_path.stem # 'audit-spool.<pid>' (strips trailing .jsonl)
396
+ return spool_path.with_name(f"{stem}.draining.{epoch}")
397
+
398
+
399
+ # P2-2: validated _spool_uuid regex (16 lowercase hex chars, secrets.token_hex(8))
400
+ _SPOOL_UUID_RE = re.compile(r"^[0-9a-f]{16}$")
401
+
402
+
403
+ def _validate_spool_header_strict(header: Any) -> bool:
404
+ """Strict 4-field header invariant — same predicate used by Phase 2
405
+ quarantine and `_ensure_spool_header` PID-reuse path.
406
+
407
+ iter5-P1 closure: factored out so writer-side reuse cannot accept a
408
+ header that the drainer will later reject. Mismatch between writer
409
+ and drainer validation = silent data loss for events appended after
410
+ the lax reuse but before Phase 2 quarantine.
411
+
412
+ Returns True only when ALL of:
413
+ - dict with `_spool_header: True`
414
+ - `_spool_uuid: str` of length 16 and matching `^[0-9a-f]{16}$`
415
+ - `_pid: int`
416
+ - `_created_wall_ns: int`
417
+ - `_created_monotonic_ns: int`
418
+ """
419
+ if not isinstance(header, dict):
420
+ return False
421
+ if header.get("_spool_header") is not True:
422
+ return False
423
+ spool_uuid = header.get("_spool_uuid")
424
+ if (not isinstance(spool_uuid, str)
425
+ or len(spool_uuid) != _SPOOL_UUID_HEX_LEN
426
+ or _SPOOL_UUID_RE.match(spool_uuid) is None):
427
+ return False
428
+ if not isinstance(header.get("_pid"), int):
429
+ return False
430
+ if not isinstance(header.get("_created_wall_ns"), int):
431
+ return False
432
+ if not isinstance(header.get("_created_monotonic_ns"), int):
433
+ return False
434
+ return True
435
+
436
+
437
+ def _recover_ordinal_counter(spool_path: Path) -> int:
438
+ """Return ordinal_within_file counter from existing spool body.
439
+
440
+ iter4-P1-1: replaces prior `\n`-counting approach. A complete body
441
+ line WITHOUT trailing `\n` (writer fsync'd then crashed before
442
+ terminator) was previously counted as zero entries → ordinal 0
443
+ re-issued → Phase 3 monotonicity quarantine. Now: parse each line
444
+ (terminated or not), return max(ordinal)+1. Returns 0 on missing/
445
+ empty/header-only/no-valid-ordinals.
446
+ """
447
+ try:
448
+ with open(str(spool_path), "rb") as f:
449
+ content = f.read()
450
+ except OSError:
451
+ return 0
452
+ if b"\n" not in content:
453
+ return 0
454
+ _header, body = content.split(b"\n", 1)
455
+ if not body:
456
+ return 0
457
+ max_ord = -1
458
+ for line_bytes in body.split(b"\n"):
459
+ if not line_bytes.strip():
460
+ continue
461
+ try:
462
+ entry = json.loads(line_bytes)
463
+ except (json.JSONDecodeError, ValueError):
464
+ continue
465
+ if not isinstance(entry, dict):
466
+ continue
467
+ o = entry.get("ordinal_within_file")
468
+ if isinstance(o, int) and o > max_ord:
469
+ max_ord = o
470
+ return max_ord + 1 if max_ord >= 0 else 0
471
+
472
+
473
+ class _SpoolHeaderUnrecoverable(OSError):
474
+ """Raised when an existing spool has an invalid header AND quarantine
475
+ rename fails. Caller (`spool_append`) MUST abort fail-open — minting
476
+ a fresh header over the still-present corrupt file would silently
477
+ lose the new event (Phase 2 quarantines the whole file later).
478
+ iter6-P1 closure.
479
+ """
480
+
481
+
482
+ def _quarantine_corrupt_active_spool(spool_path: Path, pid: int) -> bool:
483
+ """Rename malformed-header active spool aside; forensic emit.
484
+
485
+ iter4-P1-2: caller MUST hold `_spool_flock_path(pid)`. We must NOT
486
+ call drain_now() here (Phase 2 re-acquires this same flock →
487
+ self-timeout). _forensic() has its own re-entry guard so the emit
488
+ path won't recurse into spool_append.
489
+
490
+ iter6-P1: returns True on successful rename, False on rename failure.
491
+ Caller MUST honor False by aborting the current append fail-open
492
+ (NOT minting a fresh header over the still-present corrupt file —
493
+ that would let new events land behind a header Phase 2 will quarantine).
494
+ """
495
+ epoch = secrets.token_hex(4)
496
+ corrupt_path = spool_path.with_name(
497
+ f"{spool_path.stem}.corrupt-header.{epoch}"
498
+ )
499
+ try:
500
+ os.rename(str(spool_path), str(corrupt_path))
501
+ except OSError as e:
502
+ _breadcrumb(
503
+ f"quarantine_corrupt_active_spool rename failed pid={pid}: "
504
+ f"{type(e).__name__}: {e}"
505
+ )
506
+ return False
507
+ _forensic("audit_spool_tamper_detected", {
508
+ "mismatch_kind": "malformed_active_spool_header",
509
+ "spool_pid": pid,
510
+ "corrupt_path": str(corrupt_path),
511
+ "drain_epoch": epoch,
512
+ })
513
+ return True
514
+
515
+
516
+ # ---------------------------------------------------------------------------
517
+ # Fail-open breadcrumb
518
+ # ---------------------------------------------------------------------------
519
+
520
+
521
+ def _breadcrumb(message: str) -> None:
522
+ """Append a one-line forensic crumb; NEVER raise."""
523
+ try:
524
+ p = _errors_path()
525
+ p.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
526
+ ts = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
527
+ with p.open("a", encoding="utf-8") as f:
528
+ f.write(f"{ts} spool_writer: {message}\n")
529
+ except Exception: # pragma: no cover
530
+ pass
531
+
532
+
533
+ # ---------------------------------------------------------------------------
534
+ # Kill-switch + exit-handler state
535
+ # ---------------------------------------------------------------------------
536
+
537
+
538
+ def is_sync_mode() -> bool:
539
+ """CEO_AUDIT_SYNC_MODE=1 reverts to pre-Wave-A synchronous behavior."""
540
+ return os.environ.get("CEO_AUDIT_SYNC_MODE", "") == "1"
541
+
542
+
543
+ _EXIT_HANDLER_INSTALLED = False
544
+ _PREV_SIGTERM_HANDLER: Any = None
545
+ _PREV_SIGINT_HANDLER: Any = None
546
+
547
+ # Per-PID spool header cache — populated on first append; mirrors what the
548
+ # spool file's header line carries. We keep an in-process counter for the
549
+ # strictly-monotonic ordinal_within_file.
550
+ _SPOOL_HEADER_CACHE: Dict[int, Dict[str, Any]] = {}
551
+ _ORDINAL_COUNTER: Dict[int, int] = {}
552
+
553
+ # Callback wired by audit_emit so we can emit forensic events without
554
+ # importing audit_emit (circular dep). audit_emit.install() passes a
555
+ # function with signature (action: str, fields: Dict[str, Any]) -> None.
556
+ _FORENSIC_EMIT: Optional[Callable[[str, Dict[str, Any]], None]] = None
557
+
558
+ # P1-4 reentrancy guard: prevent the cycle
559
+ # spool_append → drain → _forensic → emit_generic → spool_append → ...
560
+ # Module-level flag set during _forensic() execution; if re-entered, write
561
+ # a breadcrumb and bail (no spool, no canonical).
562
+ _IN_FORENSIC_EMIT = False
563
+
564
+ # P1-1 reentrancy guard: prevent signal handler calling drain_now() from
565
+ # deadlocking against a writer mid-spool_append in the same PID. The signal
566
+ # handler checks this and bails (the partial spool entry is picked up by
567
+ # next-session reconciliation).
568
+ _IN_SPOOL_APPEND = False
569
+
570
+ # PLAN-094-FOLLOWUP Wave A.3-fail-open (option B) — module-level durability
571
+ # indicator for the last `spool_append()` call. audit_emit wire-in checks
572
+ # `last_append_succeeded()` after each call; on False, falls through to the
573
+ # sync canonical write path. Preserves the existing fail-open invariant
574
+ # (`spool_append` never raises to caller) while restoring durability defense
575
+ # depth on per-PID spool flock alloc / encode / OSError / unexpected paths.
576
+ _LAST_APPEND_SUCCEEDED = True
577
+
578
+
579
+ def last_append_succeeded() -> bool:
580
+ """Return True if the most recent spool_append() persisted durably.
581
+
582
+ audit_emit wire-in uses this to decide whether to fall back to the
583
+ sync canonical write path (False = silent-drop on spool side; sync
584
+ is now responsible for durability of THIS event).
585
+ """
586
+ return _LAST_APPEND_SUCCEEDED
587
+
588
+ # P1-2 journal buffer state (per-PID amortized fsync).
589
+ # Buffer envelopes in memory; flush + fsync at: drain boundary, signal,
590
+ # atexit, or every _JOURNAL_FLUSH_EVERY writes. ADR-055-AMEND-1 §3 line 96:
591
+ # "journal fsync every 100ms OR 10 emits".
592
+ _JOURNAL_FLUSH_EVERY = 10
593
+ _JOURNAL_BUFFER: Dict[int, List[bytes]] = {}
594
+
595
+
596
+ def set_forensic_emitter(emit_fn: Callable[[str, Dict[str, Any]], None]) -> None:
597
+ """Wire the forensic emit callback (called by audit_emit on import)."""
598
+ global _FORENSIC_EMIT
599
+ _FORENSIC_EMIT = emit_fn
600
+
601
+
602
+ def _forensic(action: str, fields: Dict[str, Any]) -> None:
603
+ """Emit a forensic audit event via wired callback; swallow errors.
604
+
605
+ P1-4: recursion guard — if we're already inside a _forensic() emit,
606
+ write a breadcrumb and return without re-entering spool_append.
607
+ """
608
+ global _IN_FORENSIC_EMIT
609
+ cb = _FORENSIC_EMIT
610
+ if cb is None:
611
+ return
612
+ if _IN_FORENSIC_EMIT:
613
+ _breadcrumb(f"forensic re-entry blocked: {action}")
614
+ return
615
+ _IN_FORENSIC_EMIT = True
616
+ try:
617
+ cb(action, fields)
618
+ except Exception as e: # pragma: no cover
619
+ _breadcrumb(f"forensic emit failed: {action}: {type(e).__name__}: {e}")
620
+ finally:
621
+ _IN_FORENSIC_EMIT = False
622
+
623
+
624
+ # ---------------------------------------------------------------------------
625
+ # Spool header / append (Phase 0 — writer-side)
626
+ # ---------------------------------------------------------------------------
627
+
628
+
629
+ def _origin_for_new_spool() -> str:
630
+ """PLAN-119 WS-D1 — write-time origin stamp for a freshly-minted spool header.
631
+
632
+ Returns ``"test"`` when a test signal is present AT WRITE TIME
633
+ (``CEO_TEST_HARNESS=1`` or ``PYTEST_CURRENT_TEST`` set), else ``"live"``.
634
+ The writer knows the truth; the drainer cannot infer it later, so the stamp
635
+ is minted ONCE per spool (header) and is sticky to that PID's file. A real
636
+ session writes ``"live"`` and is never quarantined; legacy spool with no
637
+ ``_origin`` defaults to ``"live"`` at drain time (fail-safe toward never
638
+ dropping a real event).
639
+ """
640
+ if (os.environ.get("CEO_TEST_HARNESS") == "1"
641
+ or os.environ.get("PYTEST_CURRENT_TEST")):
642
+ return "test"
643
+ return "live"
644
+
645
+
646
+ def _ensure_spool_header(pid: int, _retry_depth: int = 0) -> Dict[str, Any]:
647
+ """Return the header dict for the current spool, creating it if absent.
648
+
649
+ Header is written exactly once per spool file (first line). Fresh
650
+ _spool_uuid minted on creation so body[i].spool_uuid sentinel matches.
651
+
652
+ iter2-P1-4: existing nonempty spool with empty header cache (PID reuse,
653
+ module reload, pytest reset) → parse existing header + reuse uuid;
654
+ minting a new one would produce header_body_uuid_mismatch quarantine.
655
+ iter4-P1-1: ordinal recovery via body-line parse (max(ordinal)+1), NOT
656
+ `\n` count — robust against unterminated final line from crashed writer.
657
+ iter4-P1-2: malformed-header → quarantine active spool via direct
658
+ rename (NOT drain_now() — Phase 2 self-deadlocks on our flock) +
659
+ fresh-mint retry (bounded depth=1).
660
+ """
661
+ header = _SPOOL_HEADER_CACHE.get(pid)
662
+ spool_p = _spool_path(pid)
663
+
664
+ if header is not None and spool_p.exists():
665
+ return header
666
+
667
+ # iter2-P1-4: existing nonempty spool with no in-process header cache.
668
+ # Read the existing header instead of minting a colliding new one.
669
+ # iter5-P1: header reuse MUST apply the same strict 4-field validation
670
+ # that Phase 2 applies (_validate_spool_header_strict). Accepting a
671
+ # semantically incomplete header here lets new appends ride a file
672
+ # that Phase 2 will later quarantine → silent loss of the new events.
673
+ if spool_p.exists():
674
+ try:
675
+ st = spool_p.stat()
676
+ except OSError:
677
+ st = None
678
+ if st is not None and st.st_size > 0:
679
+ header_parsed_ok = False
680
+ try:
681
+ with spool_p.open("rb") as fh:
682
+ first = fh.readline()
683
+ existing: Any = json.loads(first.decode("utf-8"))
684
+ if _validate_spool_header_strict(existing):
685
+ _SPOOL_HEADER_CACHE[pid] = existing
686
+ # iter4-P1-1: ordinal recovery via body parse (NOT
687
+ # `\n`-count) — robust against unterminated final line.
688
+ _ORDINAL_COUNTER[pid] = _recover_ordinal_counter(spool_p)
689
+ header_parsed_ok = True
690
+ return existing
691
+ except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e:
692
+ _breadcrumb(
693
+ f"existing spool header unparsable pid={pid}: "
694
+ f"{type(e).__name__}: {e}"
695
+ )
696
+ if not header_parsed_ok:
697
+ # iter4-P1-2: do NOT call drain_now from inside the spool
698
+ # flock (Phase 2 re-acquires this flock → self-timeout →
699
+ # bail → caller appends to malformed spool → quarantine
700
+ # silently loses the new event). Quarantine the file
701
+ # directly (rename to `.corrupt-header.<epoch>`) and mint
702
+ # a fresh header. Bounded retry depth = 1.
703
+ #
704
+ # iter6-P1: if quarantine FAILS (rename OSError), we MUST
705
+ # NOT mint over the still-present corrupt file — that
706
+ # lets new events land behind a header Phase 2 will
707
+ # quarantine (silent data loss). Raise a specific
708
+ # exception so caller (`spool_append`) aborts fail-open.
709
+ quarantined = _quarantine_corrupt_active_spool(spool_p, pid)
710
+ # Drop any stale in-memory state for this pid.
711
+ _SPOOL_HEADER_CACHE.pop(pid, None)
712
+ _ORDINAL_COUNTER.pop(pid, None)
713
+ if not quarantined:
714
+ if _retry_depth >= 1:
715
+ # iter6-P1: exhausted retry AND quarantine still
716
+ # failing → abort current append fail-open.
717
+ # spool_append's outer try/except catches OSError
718
+ # and breadcrumbs. The corrupt file remains on
719
+ # disk for operator forensic recovery; Phase 2
720
+ # may eventually quarantine via its own path
721
+ # (header still fails _validate_spool_header_strict).
722
+ raise _SpoolHeaderUnrecoverable(
723
+ f"pid={pid}: quarantine of corrupt active spool "
724
+ f"failed after {_retry_depth + 1} attempt(s); "
725
+ f"aborting append fail-open"
726
+ )
727
+ return _ensure_spool_header(pid, _retry_depth=_retry_depth + 1)
728
+ # Quarantine succeeded → fall through to fresh-mint path.
729
+
730
+ header = {
731
+ "_spool_header": True,
732
+ "_spool_uuid": secrets.token_hex(8),
733
+ "_pid": pid,
734
+ "_created_wall_ns": time.time_ns(),
735
+ "_created_monotonic_ns": time.monotonic_ns(),
736
+ "_version": SPOOL_HEADER_VERSION,
737
+ # PLAN-119 WS-D1 — sticky write-time origin stamp (minted once per PID
738
+ # file). The drainer refuses to drain "test" spool into the LIVE chain.
739
+ "_origin": _origin_for_new_spool(),
740
+ }
741
+ spool_p.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
742
+ needs_header = (not spool_p.exists()) or spool_p.stat().st_size == 0
743
+ if needs_header:
744
+ line = json.dumps(header, separators=(",", ":"), ensure_ascii=False) + "\n"
745
+ # O_CREAT|O_WRONLY|O_APPEND with 0600 perms — owner-only.
746
+ fd = os.open(str(spool_p), os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
747
+ try:
748
+ os.write(fd, line.encode("utf-8"))
749
+ os.fsync(fd)
750
+ finally:
751
+ os.close(fd)
752
+ _SPOOL_HEADER_CACHE[pid] = header
753
+ _ORDINAL_COUNTER.setdefault(pid, 0)
754
+ return header
755
+
756
+
757
+ def _next_ordinal(pid: int) -> int:
758
+ """Return the next strictly-monotonic ordinal for a given pid."""
759
+ n = _ORDINAL_COUNTER.get(pid, 0)
760
+ _ORDINAL_COUNTER[pid] = n + 1
761
+ return n
762
+
763
+
764
+ def _ensure_spool_ready_for_append(pid: int) -> None:
765
+ """iter3-P1-1: isolate any unterminated final line before appending.
766
+
767
+ If a prior writer died mid-write leaving no trailing newline, the next
768
+ append concatenates onto the partial line and the new event is lost.
769
+ Append a separator newline under the held spool flock to isolate the
770
+ partial fragment (Phase 3 will discard it). No-op when missing/empty/
771
+ already-newline-terminated. Caller MUST hold _spool_flock_path(pid).
772
+ Fail-open: breadcrumb on any OSError.
773
+ """
774
+ spool_p = _spool_path(pid)
775
+ try:
776
+ if not spool_p.exists():
777
+ return
778
+ st = spool_p.stat()
779
+ if st.st_size == 0:
780
+ return
781
+ with open(str(spool_p), "rb") as f:
782
+ f.seek(-1, os.SEEK_END)
783
+ last = f.read(1)
784
+ if last == b"\n":
785
+ return
786
+ fd = os.open(str(spool_p), os.O_WRONLY | os.O_APPEND, 0o600)
787
+ try:
788
+ os.write(fd, b"\n")
789
+ os.fsync(fd)
790
+ finally:
791
+ os.close(fd)
792
+ _breadcrumb(
793
+ f"spool partial-final-line isolated pid={pid} size={st.st_size}"
794
+ )
795
+ except OSError as e: # pragma: no cover
796
+ _breadcrumb(
797
+ f"spool ready-for-append check failed pid={pid}: "
798
+ f"{type(e).__name__}: {e}"
799
+ )
800
+
801
+
802
+ def _sha256_of_canonical_json(entry_clean: Dict[str, Any]) -> str:
803
+ """sha256 over the canonical_json bytes (deterministic idempotence marker).
804
+
805
+ entry_clean MUST exclude internal _drain_* metadata + hmac field. The
806
+ canonical_json encoder is the same one ADR-055 HMAC chain uses, so the
807
+ digest is reproducible across re-drains.
808
+ """
809
+ if _canonical_json is None:
810
+ raw = json.dumps(entry_clean, sort_keys=True, separators=(",", ":"),
811
+ ensure_ascii=False).encode("utf-8")
812
+ else:
813
+ raw = _canonical_json.encode(entry_clean)
814
+ return hashlib.sha256(raw).hexdigest()
815
+
816
+
817
+ def _write_journal_envelope(
818
+ pid: int,
819
+ record_id: str,
820
+ spool_uuid: str,
821
+ ordinal: int,
822
+ sha256_of_line: str,
823
+ op: str,
824
+ drain_epoch: Optional[str] = None,
825
+ ) -> None:
826
+ """Buffer + amortized-fsync journal envelope. Best-effort fail-open.
827
+
828
+ P1-2: NOT a hot-path fsync. Envelopes are buffered in
829
+ _JOURNAL_BUFFER[pid]; flushed + fsync'd at drain boundary, signal
830
+ handler, atexit, or every _JOURNAL_FLUSH_EVERY writes (per ADR-055-
831
+ AMEND-1 §3 line 96: "journal fsync every 100ms OR 10 emits").
832
+ """
833
+ env: Dict[str, Any] = {
834
+ "record_id": record_id,
835
+ "spool_uuid": spool_uuid,
836
+ "ordinal_within_file": ordinal,
837
+ "sha256_of_line": sha256_of_line,
838
+ "op": op,
839
+ "wall_ns": time.time_ns(),
840
+ }
841
+ if drain_epoch is not None:
842
+ env["drain_epoch_at_commit"] = drain_epoch
843
+ try:
844
+ line = (json.dumps(env, separators=(",", ":"), ensure_ascii=False)
845
+ + "\n").encode("utf-8")
846
+ except (TypeError, ValueError) as e:
847
+ _breadcrumb(f"journal encode failed: {type(e).__name__}: {e}")
848
+ return
849
+ buf = _JOURNAL_BUFFER.setdefault(pid, [])
850
+ buf.append(line)
851
+ if len(buf) >= _JOURNAL_FLUSH_EVERY:
852
+ _flush_journal_buffer(pid)
853
+
854
+
855
+ def _flush_journal_buffer(pid: int) -> None:
856
+ """Flush buffered journal envelopes for PID with a single fsync.
857
+
858
+ P1-2: invoked at drain boundary, signal handler, atexit, or when
859
+ buffer hits _JOURNAL_FLUSH_EVERY. Best-effort fail-open. Acquires
860
+ the per-PID journal flock around the append+fsync window.
861
+ """
862
+ buf = _JOURNAL_BUFFER.get(pid)
863
+ if not buf:
864
+ return
865
+ p = _journal_path(pid)
866
+ try:
867
+ p.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
868
+ except OSError as e:
869
+ _breadcrumb(f"journal mkdir failed: {type(e).__name__}: {e}")
870
+ return
871
+ payload = b"".join(buf)
872
+ try:
873
+ with FileLock(_journal_flock_path(pid), timeout=SPOOL_LOCK_TIMEOUT):
874
+ fd = os.open(
875
+ str(p), os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600,
876
+ )
877
+ try:
878
+ os.write(fd, payload)
879
+ os.fsync(fd)
880
+ finally:
881
+ os.close(fd)
882
+ # Only clear on successful write; on failure the buffer retains
883
+ # the envelopes for the next flush attempt (best-effort durability).
884
+ _JOURNAL_BUFFER[pid] = []
885
+ except FileLockTimeout:
886
+ _breadcrumb(f"journal flock timeout pid={pid}")
887
+ except OSError as e:
888
+ _breadcrumb(f"journal flush failed: {type(e).__name__}: {e}")
889
+
890
+
891
+ def spool_append(entry: Dict[str, Any]) -> None:
892
+ """Write entry to per-PID spool with 4-tuple metadata + fsync.
893
+
894
+ Stamps wall_ns, pid, spool_uuid, ordinal_within_file, record_id.
895
+ Wraps spool write in begin/commit journal envelopes for crash
896
+ forensics. Fail-open: any infra error writes a breadcrumb and
897
+ returns silently.
898
+
899
+ P1-1: acquires per-PID spool flock for the header-ensure + body
900
+ append window so same-PID concurrent emit and signal-triggered drain
901
+ don't race. Module-level _IN_SPOOL_APPEND latch lets signal handler
902
+ detect a writer in progress and bail.
903
+
904
+ P1-3: stamps `record_id` onto the spool entry so the drain Phase 5
905
+ `op:"drained"` envelope can carry it for session-start reconciliation.
906
+ """
907
+ global _IN_SPOOL_APPEND, _LAST_APPEND_SUCCEEDED
908
+ _LAST_APPEND_SUCCEEDED = False
909
+ try:
910
+ pid = os.getpid()
911
+ record_id = uuid.uuid4().hex
912
+ spool_p = _spool_path(pid)
913
+ # Per-PID spool flock — ADR-055-AMEND-1 §4 Phase 1: writers acquire
914
+ # only their own spool's flock + journal's flock (never canonical).
915
+ try:
916
+ spool_flock = FileLock(
917
+ _spool_flock_path(pid), timeout=SPOOL_LOCK_TIMEOUT,
918
+ )
919
+ except OSError as e:
920
+ _breadcrumb(f"spool flock alloc failed: {type(e).__name__}: {e}")
921
+ return
922
+ try:
923
+ with spool_flock:
924
+ _IN_SPOOL_APPEND = True
925
+ try:
926
+ header = _ensure_spool_header(pid)
927
+ # iter3-P1-1: isolate any unterminated final line from a
928
+ # prior crashed writer BEFORE we mint our ordinal so the
929
+ # new entry can't be silently lost via concatenation.
930
+ _ensure_spool_ready_for_append(pid)
931
+ spool_uuid = header["_spool_uuid"]
932
+ ordinal = _next_ordinal(pid)
933
+
934
+ stamped: Dict[str, Any] = dict(entry)
935
+ stamped.setdefault("wall_ns", time.time_ns())
936
+ stamped["pid"] = pid
937
+ stamped["spool_uuid"] = spool_uuid
938
+ stamped["ordinal_within_file"] = ordinal
939
+ # P1-3: carry record_id through the spool so Phase 5
940
+ # can emit op:"drained" with matching id.
941
+ stamped["record_id"] = record_id
942
+
943
+ try:
944
+ line_bytes = json.dumps(
945
+ stamped, separators=(",", ":"),
946
+ ensure_ascii=False,
947
+ ).encode("utf-8") + b"\n"
948
+ except (TypeError, ValueError) as e:
949
+ _breadcrumb(
950
+ f"spool encode failed: {type(e).__name__}: {e}"
951
+ )
952
+ return
953
+
954
+ sha = hashlib.sha256(line_bytes).hexdigest()
955
+
956
+ _write_journal_envelope(
957
+ pid, record_id, spool_uuid, ordinal, sha, "begin",
958
+ )
959
+
960
+ try:
961
+ fd = os.open(
962
+ str(spool_p),
963
+ os.O_WRONLY | os.O_CREAT | os.O_APPEND,
964
+ 0o600,
965
+ )
966
+ try:
967
+ os.write(fd, line_bytes)
968
+ os.fsync(fd)
969
+ finally:
970
+ os.close(fd)
971
+ except OSError as e:
972
+ _breadcrumb(
973
+ f"spool append failed: {type(e).__name__}: {e}"
974
+ )
975
+ return
976
+
977
+ _write_journal_envelope(
978
+ pid, record_id, spool_uuid, ordinal, sha, "commit",
979
+ )
980
+ _LAST_APPEND_SUCCEEDED = True
981
+ finally:
982
+ _IN_SPOOL_APPEND = False
983
+ except FileLockTimeout:
984
+ _IN_SPOOL_APPEND = False
985
+ _breadcrumb(f"spool flock timeout pid={pid}")
986
+ return
987
+ except Exception as e: # fail-open invariant
988
+ _IN_SPOOL_APPEND = False
989
+ _breadcrumb(f"spool_append unexpected: {type(e).__name__}: {e}")
990
+
991
+
992
+ # ---------------------------------------------------------------------------
993
+ # Drain trigger
994
+ # ---------------------------------------------------------------------------
995
+
996
+
997
+ def should_drain() -> bool:
998
+ """Return True if current spool meets event-count or staleness trigger.
999
+
1000
+ P2-1: event-count trigger uses an actual newline-count over the spool
1001
+ body (capped at DRAIN_TRIGGER_SIZE + 2 to keep the probe cheap), NOT
1002
+ a byte-size proxy. Header is 1 line; we trigger when body lines >=
1003
+ DRAIN_TRIGGER_SIZE.
1004
+ """
1005
+ try:
1006
+ pid = os.getpid()
1007
+ p = _spool_path(pid)
1008
+ if not p.exists():
1009
+ return False
1010
+ st = p.stat()
1011
+ if st.st_size == 0:
1012
+ return False
1013
+ # Staleness trigger first (cheap)
1014
+ age_ms = (time.time() - st.st_mtime) * 1000.0
1015
+ if age_ms > DRAIN_TRIGGER_MTIME_MS:
1016
+ return True
1017
+ # P2-1: honest newline count, capped at DRAIN_TRIGGER_SIZE + 2
1018
+ # (1 for header + 1 to early-exit once we cross the threshold).
1019
+ cap = DRAIN_TRIGGER_SIZE + 2
1020
+ count = 0
1021
+ try:
1022
+ with p.open("rb") as f:
1023
+ while count < cap:
1024
+ chunk = f.read(8192)
1025
+ if not chunk:
1026
+ break
1027
+ count += chunk.count(b"\n")
1028
+ if count >= cap:
1029
+ break
1030
+ except OSError:
1031
+ return False
1032
+ # Body lines = count - 1 (header is line 1). Trigger when
1033
+ # body_lines >= DRAIN_TRIGGER_SIZE → count >= DRAIN_TRIGGER_SIZE+1.
1034
+ return count >= (DRAIN_TRIGGER_SIZE + 1)
1035
+ except Exception:
1036
+ return False
1037
+
1038
+
1039
+ # ---------------------------------------------------------------------------
1040
+ # Tail read of canonical log (Phase 4 prev_hmac + _drain_sha256 set)
1041
+ # ---------------------------------------------------------------------------
1042
+
1043
+
1044
+ def _read_canonical_tail(
1045
+ log_path: Path, k_lines: int
1046
+ ) -> Tuple[Optional[bytes], List[Dict[str, Any]]]:
1047
+ """Return (prev_hmac_bytes_or_None, last_k_entries) from canonical log.
1048
+
1049
+ Reads up to k_lines from EOF using a backward chunked scan. Returns the
1050
+ last entry's hmac (raw bytes) plus the parsed dicts for the last K
1051
+ well-formed JSON lines. Empty / missing log → (None, []).
1052
+ """
1053
+ if not log_path.exists():
1054
+ return None, []
1055
+ try:
1056
+ size = log_path.stat().st_size
1057
+ except OSError:
1058
+ return None, []
1059
+ if size == 0:
1060
+ return None, []
1061
+
1062
+ chunk = 8192
1063
+ buf = b""
1064
+ lines: List[bytes] = []
1065
+ try:
1066
+ with log_path.open("rb") as f:
1067
+ pos = size
1068
+ while pos > 0 and len(lines) <= k_lines:
1069
+ read = min(chunk, pos)
1070
+ pos -= read
1071
+ f.seek(pos)
1072
+ buf = f.read(read) + buf
1073
+ parts = buf.split(b"\n")
1074
+ # First element may be a partial fragment until we read
1075
+ # further; keep it in buf, take the tail-complete lines.
1076
+ buf = parts[0]
1077
+ # parts[1:] are complete lines (after the first newline).
1078
+ # Reverse so we accumulate newest-first.
1079
+ for ln in reversed(parts[1:]):
1080
+ if ln:
1081
+ lines.append(ln)
1082
+ if len(lines) >= k_lines:
1083
+ break
1084
+ if pos == 0 and buf:
1085
+ if len(lines) < k_lines:
1086
+ lines.append(buf)
1087
+ except OSError as e:
1088
+ _breadcrumb(f"canonical tail read failed: {type(e).__name__}: {e}")
1089
+ return None, []
1090
+
1091
+ # lines is newest-first; reverse to chronological order
1092
+ lines.reverse()
1093
+ entries: List[Dict[str, Any]] = []
1094
+ for raw in lines[-k_lines:]:
1095
+ try:
1096
+ obj = json.loads(raw.decode("utf-8"))
1097
+ if isinstance(obj, dict):
1098
+ entries.append(obj)
1099
+ except (json.JSONDecodeError, UnicodeDecodeError):
1100
+ continue
1101
+
1102
+ prev_hmac: Optional[bytes] = None
1103
+ for obj in reversed(entries):
1104
+ hx = obj.get("hmac")
1105
+ if isinstance(hx, str) and len(hx) == 64:
1106
+ try:
1107
+ prev_hmac = bytes.fromhex(hx)
1108
+ break
1109
+ except ValueError:
1110
+ continue
1111
+ return prev_hmac, entries
1112
+
1113
+
1114
+ # ---------------------------------------------------------------------------
1115
+ # Phase 2 — sweep + atomic rename + header validation
1116
+ # ---------------------------------------------------------------------------
1117
+
1118
+
1119
+ @dataclass(eq=False)
1120
+ class _DrainingFile:
1121
+ """One file under drain in the current cycle.
1122
+
1123
+ P0-2: declared eq=False so default object identity hash applies —
1124
+ instances are usable as Dict keys without TypeError. (We still key
1125
+ per_file_consumed on str(path) per Option B in the review for
1126
+ clarity; the eq=False guard is defense-in-depth.)
1127
+
1128
+ P2-4: header_raw stores the verbatim bytes (incl. trailing newline)
1129
+ of the header line, so Phase 5 atomic-split writes the byte-identical
1130
+ header to the new .draining.<new_epoch> file (no re-encode drift).
1131
+
1132
+ iter2-P0-3: `quarantined` flag — set by `_quarantine()` after mid-file
1133
+ tamper rename so Phase 5 cleanup SKIPS the entry (the file is already
1134
+ moved aside as .malformed.<epoch>; trying to split would touch a
1135
+ non-existent path or re-sweep the quarantined file).
1136
+ """
1137
+
1138
+ path: Path
1139
+ pid: int
1140
+ drain_epoch: str
1141
+ header: Optional[Dict[str, Any]] = None
1142
+ header_raw: bytes = b""
1143
+ body_lines: List[bytes] = field(default_factory=list)
1144
+ consumed_count: int = 0
1145
+ quarantined: bool = False
1146
+
1147
+
1148
+ def _is_alive_pid(pid: int) -> bool:
1149
+ """Return True if a process with PID is alive in current namespace."""
1150
+ if pid <= 0:
1151
+ return False
1152
+ try:
1153
+ os.kill(pid, 0)
1154
+ except ProcessLookupError:
1155
+ return False
1156
+ except PermissionError:
1157
+ return True
1158
+ except OSError:
1159
+ return False
1160
+ return True
1161
+
1162
+
1163
+ def _parse_spool_pid(name: str) -> Optional[int]:
1164
+ """Extract PID from audit-spool.<pid>.* filename forms."""
1165
+ if not name.startswith(_SPOOL_PREFIX + "."):
1166
+ return None
1167
+ rest = name[len(_SPOOL_PREFIX) + 1:]
1168
+ pid_str, _, _ = rest.partition(".")
1169
+ try:
1170
+ return int(pid_str)
1171
+ except ValueError:
1172
+ return None
1173
+
1174
+
1175
+ def _phase2_sweep_and_rename(
1176
+ state_dir: Path, drain_epoch: str, our_pid: int
1177
+ ) -> Tuple[List[_DrainingFile], bool]:
1178
+ """Sweep state dir for spool/draining files; atomic-rename into batch.
1179
+
1180
+ Returns (list of draining files to process, in_recovery_mode_flag).
1181
+ in_recovery_mode is True iff any pre-existing .draining.* was swept
1182
+ (severity branching for unexpected_skip — ADR-055-AMEND-1 Phase 4).
1183
+ """
1184
+ files: List[_DrainingFile] = []
1185
+ in_recovery = False
1186
+
1187
+ try:
1188
+ names = sorted(os.listdir(str(state_dir)))
1189
+ except OSError:
1190
+ return [], False
1191
+
1192
+ # 1) Pre-existing .draining.* — these are stale from prior crashed drains
1193
+ # iter3-P2-2: wire STALE_SPOOL_TTL_DAYS into the sweep. A .draining file
1194
+ # owned by a dead PID (or even a live one) whose mtime is older than
1195
+ # the TTL is forensically interesting — emit an audit_spool_stale_recovered
1196
+ # advisory BEFORE processing so SOC has trace of the long-deferred
1197
+ # recovery. Processing still proceeds; the TTL is advisory-only.
1198
+ stale_ttl_ns = STALE_SPOOL_TTL_DAYS * 86400 * 1_000_000_000
1199
+ now_ns = time.time_ns()
1200
+ for name in names:
1201
+ if _DRAINING_SUFFIX_TOKEN not in name:
1202
+ continue
1203
+ if not name.startswith(_SPOOL_PREFIX + "."):
1204
+ continue
1205
+ pid = _parse_spool_pid(name)
1206
+ if pid is None:
1207
+ continue
1208
+ # Parse the existing drain_epoch from the suffix; merge in as-is.
1209
+ try:
1210
+ old_epoch = name.rsplit(_DRAINING_SUFFIX_TOKEN, 1)[1]
1211
+ except IndexError:
1212
+ old_epoch = drain_epoch
1213
+ full_path = state_dir / name
1214
+ # iter3-P2-2: stale-recovery advisory if age > TTL.
1215
+ try:
1216
+ st_mtime_ns = full_path.stat().st_mtime_ns
1217
+ file_age_ns = now_ns - st_mtime_ns
1218
+ if file_age_ns > stale_ttl_ns:
1219
+ file_age_hours = file_age_ns // (3600 * 1_000_000_000)
1220
+ _forensic("audit_spool_stale_recovered", {
1221
+ "original_pid": pid,
1222
+ "file_age_hours": int(file_age_hours),
1223
+ "stale_ttl_days": STALE_SPOOL_TTL_DAYS,
1224
+ "drain_epoch": drain_epoch,
1225
+ "prior_drain_epoch": old_epoch,
1226
+ })
1227
+ except OSError:
1228
+ pass
1229
+ files.append(_DrainingFile(
1230
+ path=full_path, pid=pid, drain_epoch=old_epoch,
1231
+ ))
1232
+ in_recovery = True
1233
+
1234
+ # 2) Active spool files — atomically rename to .draining.<drain_epoch>.
1235
+ # Skip our own active spool (we hold it open for writes); also skip
1236
+ # spools of live PIDs other than ours when force-drain is not set —
1237
+ # let the owning process drain them. (Conservative: live-PID spools
1238
+ # of OTHER processes will be reaped on next aggregate sweep.)
1239
+ for name in names:
1240
+ if not name.startswith(_SPOOL_PREFIX + "."):
1241
+ continue
1242
+ if not name.endswith(".jsonl"):
1243
+ continue
1244
+ pid = _parse_spool_pid(name)
1245
+ if pid is None:
1246
+ continue
1247
+ src = state_dir / name
1248
+ try:
1249
+ src_stat = src.stat()
1250
+ except OSError:
1251
+ continue
1252
+ if src_stat.st_size == 0:
1253
+ continue
1254
+ # Only rename our own spool OR clearly-dead PID spools. Live-PID
1255
+ # spools belong to peers — leave them for their own drain trigger
1256
+ # to avoid stealing in-flight writes (writer flock would block us
1257
+ # anyway; explicit check is the cheap path).
1258
+ if pid != our_pid and _is_alive_pid(pid):
1259
+ continue
1260
+ # iter4-P2-1: stale-recovery advisory ALSO covers active spool files
1261
+ # whose owning PID is dead AND mtime is older than TTL (ADR-055-
1262
+ # AMEND-1 §A.4.5 covers both .draining.* AND orphaned active
1263
+ # spools). Emit BEFORE the rename so SOC trace carries the
1264
+ # forensic correlation between the orphan and the drain epoch.
1265
+ if pid != our_pid:
1266
+ try:
1267
+ file_age_ns = now_ns - src_stat.st_mtime_ns
1268
+ if file_age_ns > stale_ttl_ns:
1269
+ file_age_hours = file_age_ns // (3600 * 1_000_000_000)
1270
+ _forensic("audit_spool_stale_recovered", {
1271
+ "original_pid": pid,
1272
+ "file_age_hours": int(file_age_hours),
1273
+ "stale_ttl_days": STALE_SPOOL_TTL_DAYS,
1274
+ "drain_epoch": drain_epoch,
1275
+ "source": "active_spool_orphan",
1276
+ })
1277
+ except OSError:
1278
+ pass
1279
+ # P0-1: use _draining_path helper (was: missing dot before draining)
1280
+ dst = _draining_path(src, drain_epoch)
1281
+ # iter3-P1-2: acquire the per-PID spool flock around rename so a
1282
+ # concurrent writer mid-spool_append in the same PID (e.g. signal-
1283
+ # handler-triggered drain racing the writer's own fd) is forced to
1284
+ # complete before we steal the file. Writers hold the flock for
1285
+ # ~10µs per emit; a short timeout (0.5s) is sufficient. If we time
1286
+ # out, we skip this PID this cycle — the spool gets picked up at
1287
+ # next drain trigger. Either path is correctness-preserving.
1288
+ try:
1289
+ with FileLock(_spool_flock_path(pid), timeout=0.5):
1290
+ # Re-check existence under lock (writer may have unlinked
1291
+ # during contention, or another concurrent drain in our PID
1292
+ # could have renamed it first).
1293
+ try:
1294
+ if not src.exists() or src.stat().st_size == 0:
1295
+ continue
1296
+ except OSError:
1297
+ continue
1298
+ try:
1299
+ os.rename(str(src), str(dst))
1300
+ except OSError as e:
1301
+ _breadcrumb(
1302
+ f"phase2 rename failed for {name}: "
1303
+ f"{type(e).__name__}: {e}"
1304
+ )
1305
+ continue
1306
+ except FileLockTimeout:
1307
+ # Writer is mid-append on this spool; defer to next drain
1308
+ # cycle. The spool size/mtime trigger will catch it.
1309
+ _breadcrumb(
1310
+ f"phase2 spool flock timeout pid={pid} (writer mid-append)"
1311
+ )
1312
+ continue
1313
+ files.append(_DrainingFile(
1314
+ path=dst, pid=pid, drain_epoch=drain_epoch,
1315
+ ))
1316
+ # If the renamed-away spool belonged to our pid, reset our header
1317
+ # cache so subsequent spool_append mints a fresh _spool_uuid.
1318
+ if pid == our_pid:
1319
+ _SPOOL_HEADER_CACHE.pop(pid, None)
1320
+ _ORDINAL_COUNTER.pop(pid, None)
1321
+
1322
+ return files, in_recovery
1323
+
1324
+
1325
+ def _quarantine(
1326
+ file: _DrainingFile, mismatch_kind: str, drain_epoch: str
1327
+ ) -> None:
1328
+ """Rename a malformed/tampered spool to .malformed.<drain_epoch>; emit.
1329
+
1330
+ iter2-P0-3: sets `file.quarantined = True` so Phase 5 cleanup skips the
1331
+ entry (the source .draining.<epoch> no longer exists at its original
1332
+ path — trying to split or unlink would either fail or, worse, re-sweep
1333
+ the renamed .malformed.<epoch> file on a subsequent drain.
1334
+ """
1335
+ try:
1336
+ new_name = file.path.name.replace(
1337
+ _DRAINING_SUFFIX_TOKEN[1:] + file.drain_epoch,
1338
+ _MALFORMED_SUFFIX_TOKEN[1:] + drain_epoch,
1339
+ 1,
1340
+ )
1341
+ new_path = file.path.with_name(new_name)
1342
+ os.rename(str(file.path), str(new_path))
1343
+ except OSError as e:
1344
+ _breadcrumb(f"quarantine rename failed: {type(e).__name__}: {e}")
1345
+ # iter2-P0-3: latch quarantined flag for Phase 5 cleanup skip.
1346
+ file.quarantined = True
1347
+ _forensic("audit_spool_tamper_detected", {
1348
+ "mismatch_kind": mismatch_kind,
1349
+ "spool_pid": file.pid,
1350
+ "drain_epoch": drain_epoch,
1351
+ })
1352
+
1353
+
1354
+ def _should_quarantine_test_origin(
1355
+ file: "_DrainingFile", canonical_log_path: Path
1356
+ ) -> bool:
1357
+ """PLAN-119 WS-D1 — True iff this spool is ``_origin:"test"`` AND the
1358
+ canonical drain destination IS the live chain.
1359
+
1360
+ Conjunction-gated (Codex R2 P0-1): a redirected pytest/session tmp dir is
1361
+ NOT the live chain, so a ``"test"`` spool drains NORMALLY there (isolated
1362
+ drain-behavior tests keep working). Fail-SAFE to NO quarantine (Codex R3
1363
+ P1-1) when the live-log snapshot is absent/unreadable — never risk dropping a
1364
+ real event. Legacy spool with no ``_origin`` (or ``_origin:"live"``) is never
1365
+ quarantined.
1366
+ """
1367
+ header = file.header
1368
+ if not isinstance(header, dict) or header.get("_origin") != "test":
1369
+ return False
1370
+ snapshot = os.environ.get(_LIVE_LOG_SNAPSHOT_VAR)
1371
+ if not snapshot:
1372
+ return False # cannot confirm destination is live → drain normally
1373
+ try:
1374
+ return Path(canonical_log_path).resolve() == Path(snapshot).resolve()
1375
+ except (OSError, ValueError):
1376
+ return False
1377
+
1378
+
1379
+ def _quarantine_test_origin(file: "_DrainingFile", drain_epoch: str) -> None:
1380
+ """PLAN-119 WS-D1 — rename an ``_origin:"test"`` spool out of the drain path
1381
+ so its entries never reach the live canonical append.
1382
+
1383
+ Breadcrumb-only — no new audit action (the canonical ``_KNOWN_ACTIONS`` is a
1384
+ kernel-HARD-DENY surface; the rename + the ``DrainStats`` counter are the
1385
+ durable trace). Mirrors ``_quarantine``'s rename discipline (latches
1386
+ ``file.quarantined`` so Phase 5 cleanup skips the moved file).
1387
+ """
1388
+ try:
1389
+ new_name = file.path.name.replace(
1390
+ _DRAINING_SUFFIX_TOKEN[1:] + file.drain_epoch,
1391
+ _TEST_ORIGIN_SUFFIX_TOKEN[1:] + drain_epoch,
1392
+ 1,
1393
+ )
1394
+ new_path = file.path.with_name(new_name)
1395
+ os.rename(str(file.path), str(new_path))
1396
+ except OSError as e:
1397
+ _breadcrumb(
1398
+ f"test-origin quarantine rename failed: {type(e).__name__}: {e}"
1399
+ )
1400
+ file.quarantined = True
1401
+ # Codex P1 — compact the quarantined spool's journal records so session-start
1402
+ # reconciliation does NOT later count them as inflight/lost and emit a spurious
1403
+ # ``audit_flush_dropped_count``. A deliberate test-origin quarantine is a
1404
+ # COMPLETION (the entries are intentionally not drained to the live chain),
1405
+ # not a real-event loss. ``file.body_lines`` is populated by Phase 2.
1406
+ try:
1407
+ rec_ids = []
1408
+ for raw in (file.body_lines or []):
1409
+ try:
1410
+ entry = json.loads(raw.decode("utf-8"))
1411
+ except (json.JSONDecodeError, UnicodeDecodeError):
1412
+ continue
1413
+ rid = entry.get("record_id") if isinstance(entry, dict) else None
1414
+ if isinstance(rid, str) and rid:
1415
+ rec_ids.append(rid)
1416
+ if rec_ids:
1417
+ _journal_compact_drained(file.pid, rec_ids, drain_epoch)
1418
+ except Exception as e: # pragma: no cover — journal hygiene, never blocks
1419
+ _breadcrumb(
1420
+ f"test-origin journal compaction failed: {type(e).__name__}: {e}"
1421
+ )
1422
+ _breadcrumb(
1423
+ f"test-origin spool refused at live-chain drain: pid={file.pid} "
1424
+ f"epoch={drain_epoch}"
1425
+ )
1426
+
1427
+
1428
+ def _phase2_validate_header(
1429
+ file: _DrainingFile, drain_epoch: str
1430
+ ) -> bool:
1431
+ """Read header + first body line; validate body[0].spool_uuid sentinel.
1432
+
1433
+ Returns True iff header is well-formed AND header._spool_uuid matches
1434
+ body[0].spool_uuid (when body is non-empty). Quarantines on mismatch.
1435
+ Loads body_lines into file as a side effect.
1436
+ """
1437
+ try:
1438
+ raw_lines = file.path.read_bytes().split(b"\n")
1439
+ except OSError as e:
1440
+ _breadcrumb(f"phase2 read failed: {type(e).__name__}: {e}")
1441
+ return False
1442
+
1443
+ # Strip trailing empty fragment from final newline
1444
+ if raw_lines and raw_lines[-1] == b"":
1445
+ raw_lines = raw_lines[:-1]
1446
+
1447
+ if not raw_lines:
1448
+ # Empty file; nothing to do — caller treats as fully-consumed
1449
+ file.header = None
1450
+ file.body_lines = []
1451
+ return True
1452
+
1453
+ # Parse header
1454
+ try:
1455
+ header = json.loads(raw_lines[0].decode("utf-8"))
1456
+ except (json.JSONDecodeError, UnicodeDecodeError):
1457
+ _quarantine(file, "malformed_spool_header", drain_epoch)
1458
+ return False
1459
+ # iter5-P1: use the shared strict predicate (same one used by
1460
+ # _ensure_spool_header for writer-side reuse) so writer and drainer
1461
+ # validation are byte-equivalent.
1462
+ if not _validate_spool_header_strict(header):
1463
+ _quarantine(file, "malformed_spool_header", drain_epoch)
1464
+ return False
1465
+
1466
+ file.header = header
1467
+ # P2-4: verbatim header bytes for Phase 5 atomic split
1468
+ file.header_raw = raw_lines[0] + b"\n"
1469
+ file.body_lines = raw_lines[1:]
1470
+
1471
+ if not file.body_lines:
1472
+ return True
1473
+
1474
+ # body[0] sentinel
1475
+ body0_raw = file.body_lines[0]
1476
+ try:
1477
+ body0 = json.loads(body0_raw.decode("utf-8"))
1478
+ except (json.JSONDecodeError, UnicodeDecodeError):
1479
+ # body[0] malformed → if it's the ONLY body line, treat as partial-
1480
+ # line crash (Phase 3 discard). Otherwise quarantine (mid-file
1481
+ # malformed is suspicious).
1482
+ if len(file.body_lines) == 1:
1483
+ return True
1484
+ _quarantine(file, "malformed_spool_header", drain_epoch)
1485
+ return False
1486
+ # PLAN-094-FOLLOWUP bug fix: extract spool_uuid from validated header
1487
+ # (was UnboundLocalError NameError — refactor regression from
1488
+ # _validate_spool_header_strict extraction; caught by Wave A.7r.1 test).
1489
+ spool_uuid = header["_spool_uuid"]
1490
+ if not isinstance(body0, dict) or body0.get("spool_uuid") != spool_uuid:
1491
+ _quarantine(file, "header_body_uuid_mismatch", drain_epoch)
1492
+ return False
1493
+ return True
1494
+
1495
+
1496
+ # ---------------------------------------------------------------------------
1497
+ # Phase 3 — sort + 4-tuple uniqueness
1498
+ # ---------------------------------------------------------------------------
1499
+
1500
+
1501
+ def _phase3_collect_and_sort(
1502
+ files: List[_DrainingFile], drain_epoch: str, stats: DrainStats,
1503
+ ) -> Tuple[
1504
+ List[Tuple[Tuple[int, int, str, int], Dict[str, Any], _DrainingFile, int]],
1505
+ Dict[str, Set[int]],
1506
+ int,
1507
+ ]:
1508
+ """Parse body lines from each draining file; sort by 4-tuple; de-dup.
1509
+
1510
+ Returns (deduped, partial_discard_consumed_indices_by_path,
1511
+ duplicate_consumed_count).
1512
+
1513
+ iter4-P2-2: returns the count of duplicate-tuple entries marked
1514
+ consumed in this cycle so Phase 4 can carry it as the starting
1515
+ `processed` value. Without this, a duplicate storm (e.g. corrupted
1516
+ spool with N identical 4-tuples) consumes/deletes more than K_MAX
1517
+ entries in one drain cycle, softening the K_MAX hard-cap perf
1518
+ contract (correctness preserved — duplicates ARE discarded).
1519
+
1520
+ iter2-P0-1: partial-discard tracking is now a Set[int] of body indices
1521
+ (not a prefix-count). Phase 5 splits the file using the COMPLEMENT of
1522
+ the set, so out-of-order index consumption (later index handled before
1523
+ earlier index) is safe.
1524
+
1525
+ iter2-P0-2: duplicate 4-tuple is marked CONSUMED (added to the per-file
1526
+ set) so Phase 5 cleanup actually removes it from disk; without this,
1527
+ the rejected entry survives split → re-detected as duplicate forever
1528
+ (livelock). audit_spool_intentionally_deleted advisory emits.
1529
+
1530
+ iter2-P2-2: per-file ordinal monotonicity check — within each spool
1531
+ file, `ordinal_within_file` of entry N+1 must be > entry N (the writer
1532
+ issues strictly-monotonic ordinals via _next_ordinal()). Violation →
1533
+ quarantine source with mismatch_kind="ordinal_monotonicity_violation".
1534
+
1535
+ Tamper / malformed handling:
1536
+ - JSON-parse fail on the LAST line of a file → partial-line discard
1537
+ (writer SIGKILL mid-write); emit audit_spool_partial_line_discarded
1538
+ - JSON-parse fail on any OTHER line → tamper; quarantine the file and
1539
+ drop ALL its remaining unconsumed body entries.
1540
+ - Duplicate full 4-tuple → reject with audit_spool_duplicate_tuple_rejected;
1541
+ mark consumed (iter2-P0-2).
1542
+ - Ordinal monotonicity violation → quarantine (iter2-P2-2).
1543
+ """
1544
+ collected: List[Tuple[Tuple[int, int, str, int], Dict[str, Any], _DrainingFile, int]] = []
1545
+ partial_consumed: Dict[str, Set[int]] = {}
1546
+
1547
+ def _mark(path_str: str, idx: int) -> None:
1548
+ partial_consumed.setdefault(path_str, set()).add(idx)
1549
+
1550
+ for f in files:
1551
+ if not f.body_lines:
1552
+ continue
1553
+ last_idx = len(f.body_lines) - 1
1554
+ path_key = str(f.path)
1555
+ # iter2-P2-2: per-file ordinal monotonicity tracking.
1556
+ prev_ordinal: Optional[int] = None
1557
+ for idx, raw in enumerate(f.body_lines):
1558
+ if not raw:
1559
+ # Empty line (double-newline) — discard silently but mark
1560
+ # consumed so Phase 5 doesn't perpetually re-split it.
1561
+ _mark(path_key, idx)
1562
+ continue
1563
+ try:
1564
+ obj = json.loads(raw.decode("utf-8"))
1565
+ except (json.JSONDecodeError, UnicodeDecodeError):
1566
+ if idx == last_idx:
1567
+ stats.partial_lines_discarded += 1
1568
+ _forensic("audit_spool_partial_line_discarded", {
1569
+ "spool_pid": f.pid,
1570
+ "drain_epoch": drain_epoch,
1571
+ "body_index": idx,
1572
+ })
1573
+ # iter2-P0-1: mark this index consumed so Phase 5
1574
+ # split treats the partial line as handled.
1575
+ _mark(path_key, idx)
1576
+ continue
1577
+ # Mid-file malformed — quarantine file and stop collecting
1578
+ # from it (subsequent entries are suspect)
1579
+ _quarantine(f, "malformed_spool_header", drain_epoch)
1580
+ stats.quarantined_files += 1
1581
+ # Drop any entries already collected from this file:
1582
+ collected = [c for c in collected if c[2] is not f]
1583
+ partial_consumed.pop(path_key, None)
1584
+ break
1585
+ if not isinstance(obj, dict):
1586
+ if idx == last_idx:
1587
+ stats.partial_lines_discarded += 1
1588
+ _mark(path_key, idx) # iter2-P0-1
1589
+ continue
1590
+ _quarantine(f, "malformed_spool_header", drain_epoch)
1591
+ stats.quarantined_files += 1
1592
+ collected = [c for c in collected if c[2] is not f]
1593
+ partial_consumed.pop(path_key, None)
1594
+ break
1595
+ try:
1596
+ wall_ns = int(obj["wall_ns"])
1597
+ pid = int(obj["pid"])
1598
+ spool_uuid = str(obj["spool_uuid"])
1599
+ ordinal = int(obj["ordinal_within_file"])
1600
+ except (KeyError, TypeError, ValueError):
1601
+ # Missing 4-tuple field — treat as tamper (would also break
1602
+ # HMAC since spool_uuid participates in canonical_json)
1603
+ _quarantine(f, "malformed_spool_header", drain_epoch)
1604
+ stats.quarantined_files += 1
1605
+ collected = [c for c in collected if c[2] is not f]
1606
+ partial_consumed.pop(path_key, None)
1607
+ break
1608
+ # iter2-P2-2: per-file ordinal monotonicity (strictly increasing).
1609
+ if prev_ordinal is not None and ordinal <= prev_ordinal:
1610
+ _quarantine(f, "ordinal_monotonicity_violation", drain_epoch)
1611
+ stats.quarantined_files += 1
1612
+ collected = [c for c in collected if c[2] is not f]
1613
+ partial_consumed.pop(path_key, None)
1614
+ break
1615
+ prev_ordinal = ordinal
1616
+ key = (wall_ns, pid, spool_uuid, ordinal)
1617
+ collected.append((key, obj, f, idx))
1618
+
1619
+ collected.sort(key=lambda x: x[0])
1620
+
1621
+ deduped: List[Tuple[Tuple[int, int, str, int], Dict[str, Any], _DrainingFile, int]] = []
1622
+ seen: set = set()
1623
+ # iter4-P2-2: bound duplicate-cleanup to K_MAX so a storm can't exceed
1624
+ # the per-cycle perf cap. Unmarked duplicates survive split → next
1625
+ # drain re-detects → bounded amortization (correctness preserved; no
1626
+ # canonical append happens for duplicates either way).
1627
+ duplicate_consumed = 0
1628
+ for key, obj, f, idx in collected:
1629
+ if key in seen:
1630
+ if duplicate_consumed >= K_MAX:
1631
+ # Still record the forensic so SOC sees the rejection,
1632
+ # but DO NOT _mark() consumed (defer to next cycle).
1633
+ stats.rejected_duplicate_tuple += 1
1634
+ _forensic("audit_spool_duplicate_tuple_rejected", {
1635
+ "wall_ns": key[0],
1636
+ "pid": key[1],
1637
+ "spool_uuid": key[2],
1638
+ "ordinal_within_file": key[3],
1639
+ "drain_epoch": drain_epoch,
1640
+ "deferred_to_next_cycle": True,
1641
+ })
1642
+ continue
1643
+ stats.rejected_duplicate_tuple += 1
1644
+ _forensic("audit_spool_duplicate_tuple_rejected", {
1645
+ "wall_ns": key[0],
1646
+ "pid": key[1],
1647
+ "spool_uuid": key[2],
1648
+ "ordinal_within_file": key[3],
1649
+ "drain_epoch": drain_epoch,
1650
+ })
1651
+ # iter2-P0-2: mark the duplicate's body index CONSUMED so Phase 5
1652
+ # actually removes it from disk (otherwise the rejected entry
1653
+ # survives split → re-detected forever = livelock). Emit a
1654
+ # paired advisory so SOC has trace of the silent deletion.
1655
+ _mark(str(f.path), idx)
1656
+ # iter3-P2-1: surface count into DrainStats for AC10 visibility
1657
+ # (JournalReconciliation aggregates this at session-start).
1658
+ stats.intentionally_deleted += 1
1659
+ duplicate_consumed += 1
1660
+ _forensic("audit_spool_intentionally_deleted", {
1661
+ "reason": "duplicate_tuple",
1662
+ "spool_pid": key[1],
1663
+ "spool_uuid": key[2],
1664
+ "ordinal_within_file": key[3],
1665
+ "drain_epoch": drain_epoch,
1666
+ })
1667
+ continue
1668
+ seen.add(key)
1669
+ deduped.append((key, obj, f, idx))
1670
+ return deduped, partial_consumed, duplicate_consumed
1671
+
1672
+
1673
+ # ---------------------------------------------------------------------------
1674
+ # Phase 4 — idempotent chain reconstruction
1675
+ # ---------------------------------------------------------------------------
1676
+
1677
+
1678
+ def _phase4_build_batch(
1679
+ deduped: List[Tuple[Tuple[int, int, str, int], Dict[str, Any], _DrainingFile, int]],
1680
+ drain_epoch: str,
1681
+ in_recovery: bool,
1682
+ stats: DrainStats,
1683
+ starting_processed: int = 0,
1684
+ ) -> Tuple[List[bytes], List[_DrainingFile], Dict[str, Set[int]], Optional[bytes], List[Tuple[str, int]]]:
1685
+ """Compute HMAC chain; produce canonical-log batch lines.
1686
+
1687
+ Returns (batch_line_bytes, fully_consumed_files,
1688
+ per_file_consumed_indices, last_hmac_bytes, drained_record_ids_by_pid).
1689
+
1690
+ iter2-P0-1: per_file_consumed is now Dict[str, Set[int]] (body indices,
1691
+ not a prefix-count). Phase 5 splits the remainder using the COMPLEMENT
1692
+ of the set — out-of-order index consumption (e.g. clock-skew rollback
1693
+ where a LATER body index sorts ahead of an EARLIER one and is cut by
1694
+ K_MAX) no longer trashes unprocessed earlier lines.
1695
+
1696
+ iter2-P1-1: idempotent-skip branch now ALSO appends (record_id, pid)
1697
+ to drained_ids and marks the index consumed — without this, the matching
1698
+ journal `commit` envelope stays as commit_no_drained forever (phantom
1699
+ inflight count in AC10).
1700
+
1701
+ iter2-P2-1: canonical output honors the SOURCE file's drain_epoch
1702
+ (src_file.drain_epoch) — important for forensic correlation when an
1703
+ in-recovery drain re-emits entries from a prior-cycle .draining file.
1704
+
1705
+ iter3-P0-1: K_MAX counts TOTAL PROCESSED entries (appended + idempotent
1706
+ skipped + intentionally consumed via tamper/duplicate paths), NOT just
1707
+ appended. Without this, repeated partial-crash cycles where each crash
1708
+ leaves K_MAX entries in .draining and the next drain hits all K_MAX as
1709
+ skipped + processes K_MAX fresh can accumulate >K_TAIL_WINDOW processed
1710
+ entries per cycle → entries fall out of the skip-guard tail window on
1711
+ a subsequent crash cycle → re-appended duplicates in canonical log.
1712
+ Capping TOTAL processed bounds disk-write + idempotent work per drain
1713
+ cycle so window-overflow cannot occur.
1714
+
1715
+ Honors K_MAX cap; remaining un-consumed entries are left for the next
1716
+ drain cycle via Phase 5 atomic split.
1717
+ """
1718
+ log_path = _canonical_log_path()
1719
+
1720
+ # PLAN-112-FOLLOWUP-hmac-tamper-fix Wave B.1 — Path B variant 1 hoist.
1721
+ # Probe rotation BEFORE reading canonical tail (and therefore before
1722
+ # computing prev_hmac), so this batch always anchors against the
1723
+ # CURRENT canonical file (post-rotation if applicable).
1724
+ #
1725
+ # Prior bug: Phase 4 read prev_hmac from the about-to-rotate file;
1726
+ # Phase 5 detected rotation but batch_lines were already HMAC-chained
1727
+ # against the old chain → verifier (resets at file boundary per
1728
+ # ADR-055 §2) saw STATUS_TAMPER at line 1 of the new file.
1729
+ # Per F-7.7 (PLAN-112 wave-c-bundles/C10) + D3 confirmation.
1730
+ # Fail-open: any rotation exception is breadcrumbed and the read
1731
+ # proceeds against whatever log path is current.
1732
+ try:
1733
+ from _lib import audit_emit as _audit_emit_lazy
1734
+ from _lib import audit_hmac as _audit_hmac_lazy
1735
+ # PLAN-143 item-2 (audit-errors-02): guard the rotation probe.
1736
+ # Under test/shim wiring the lazily-imported object can be an
1737
+ # _EmitCapture double that lacks _rotate_if_needed_safe; calling
1738
+ # it raised AttributeError into the fail-open except below, which
1739
+ # breadcrumbed cosmetic audit-log.errors noise and silently
1740
+ # skipped the probe. getattr-guard -> no attribute means "no
1741
+ # rotation" (None), taking the same intended branch without noise.
1742
+ _rotate_probe = getattr(
1743
+ _audit_emit_lazy, "_rotate_if_needed_safe", None
1744
+ )
1745
+ rotated_to_phase4 = (
1746
+ _rotate_probe(log_path) if callable(_rotate_probe) else None
1747
+ )
1748
+ if rotated_to_phase4 is not None and not _audit_hmac_lazy.is_disabled():
1749
+ try:
1750
+ _audit_hmac_lazy.reset_chain_on_rotation()
1751
+ except Exception as re:
1752
+ _breadcrumb(
1753
+ f"phase4 rotation HMAC reset failed: "
1754
+ f"{type(re).__name__}: {re}"
1755
+ )
1756
+ # PLAN-112-FOLLOWUP-hmac-tamper-fix Wave B.3 — emit
1757
+ # chain_reset_marker as line 1 of new file + manifest. Fail-open
1758
+ # (marker absence → verifier legacy mode, not tamper signal).
1759
+ try:
1760
+ _audit_emit_lazy._emit_chain_reset_marker_under_lock(
1761
+ log=log_path,
1762
+ previous_archive_path=str(rotated_to_phase4),
1763
+ rotation_trigger="size_threshold",
1764
+ )
1765
+ except Exception as me:
1766
+ _breadcrumb(
1767
+ f"phase4 chain_reset_marker emit failed: "
1768
+ f"{type(me).__name__}: {me}"
1769
+ )
1770
+ except Exception as e:
1771
+ # Fail-open: rotation probe never blocks the canonical read.
1772
+ _breadcrumb(f"phase4 rotation probe failed: {type(e).__name__}: {e}")
1773
+
1774
+ # AFTER rotation probe: read tail from (now-current) file.
1775
+ prev_hmac, tail_entries = _read_canonical_tail(log_path, K_TAIL_WINDOW)
1776
+ drain_sha_set: set = set()
1777
+ for e in tail_entries:
1778
+ ds = e.get("_drain_sha256")
1779
+ if isinstance(ds, str) and len(ds) == 64:
1780
+ drain_sha_set.add(ds)
1781
+
1782
+ if _HMAC_AVAILABLE and _audit_hmac is not None and prev_hmac is None:
1783
+ # Empty log + HMAC available → genesis (which is correct
1784
+ # post-rotation since the new file is empty).
1785
+ prev_hmac = _audit_hmac.GENESIS_PREV
1786
+
1787
+ batch_lines: List[bytes] = []
1788
+ # iter2-P0-1: set-of-indices, not prefix-count.
1789
+ per_file_consumed: Dict[str, Set[int]] = {}
1790
+ drained_ids: List[Tuple[str, int]] = [] # P1-3: (record_id, pid)
1791
+ last_hmac: Optional[bytes] = prev_hmac
1792
+
1793
+ def _mark(path_str: str, idx: int) -> None:
1794
+ per_file_consumed.setdefault(path_str, set()).add(idx)
1795
+
1796
+ # iter3-P0-1: cap on TOTAL processed (appended + idempotent_skipped +
1797
+ # tamper-consumed); breaking on appended-only allowed cumulative
1798
+ # processed > K_TAIL_WINDOW across recovery cycles → window overflow.
1799
+ # iter4-P2-2: `starting_processed` carries duplicate-cleanup count from
1800
+ # Phase 3 so combined Phase-3 + Phase-4 work ≤ K_MAX per drain cycle.
1801
+ processed = starting_processed
1802
+ for key, entry, src_file, body_idx in deduped:
1803
+ if processed >= K_MAX:
1804
+ break
1805
+ path_key = str(src_file.path)
1806
+
1807
+ # P1-3: pull record_id off the spool entry BEFORE stripping internal
1808
+ # fields so we can emit op:"drained" with the matching id.
1809
+ rec_id = entry.get("record_id")
1810
+
1811
+ entry_clean = {
1812
+ k: v for k, v in entry.items()
1813
+ if not (isinstance(k, str) and k.startswith("_drain_"))
1814
+ and k != "hmac" and k != "hmac_error"
1815
+ }
1816
+ try:
1817
+ sha = _sha256_of_canonical_json(entry_clean)
1818
+ except Exception as e:
1819
+ _breadcrumb(f"sha compute failed: {type(e).__name__}: {e}")
1820
+ # Treat unencodable entry like a tamper — skip + count
1821
+ stats.partial_lines_discarded += 1
1822
+ _mark(path_key, body_idx)
1823
+ # iter3-P0-1: tamper-consumed counts toward K_MAX budget
1824
+ processed += 1
1825
+ continue
1826
+
1827
+ if sha in drain_sha_set:
1828
+ stats.skipped_idempotent += 1
1829
+ severity = "INFORMATIONAL" if in_recovery else "ALARM"
1830
+ _forensic("audit_spool_unexpected_skip", {
1831
+ "drain_epoch": drain_epoch,
1832
+ "spool_uuid": key[2],
1833
+ "ordinal_within_file": key[3],
1834
+ "skipped_sha256": sha,
1835
+ "drain_in_recovery_mode": in_recovery,
1836
+ "severity": severity,
1837
+ })
1838
+ _mark(path_key, body_idx)
1839
+ # iter2-P1-1: also commit drained_id so the journal compactor
1840
+ # emits op:"drained" for this record. Without this, the matching
1841
+ # `commit` envelope stays as commit_no_drained forever in the
1842
+ # journal (AC10 phantom inflight). The canonical batch line was
1843
+ # already appended on the prior crashed drain — this entry is
1844
+ # "drained" from the journal's perspective even though we are
1845
+ # not re-appending the bytes this cycle.
1846
+ if isinstance(rec_id, str) and rec_id:
1847
+ drained_ids.append((rec_id, src_file.pid))
1848
+ # iter3-P0-1: idempotent-skip counts toward K_MAX budget so
1849
+ # window-overflow on multi-crash cycles is structurally
1850
+ # impossible (processed ≤ K_MAX < K_TAIL_WINDOW).
1851
+ processed += 1
1852
+ continue
1853
+
1854
+ chained = dict(entry_clean)
1855
+ chained["_drain_sha256"] = sha
1856
+ # iter2-P2-1: honor source file's drain_epoch for forensic
1857
+ # correlation (this entry was first staged in that cycle).
1858
+ chained["_drain_epoch"] = src_file.drain_epoch
1859
+
1860
+ if (_HMAC_AVAILABLE and _audit_hmac is not None
1861
+ and not _audit_hmac.is_disabled()):
1862
+ try:
1863
+ key_bytes = _audit_hmac.get_or_create_key()
1864
+ digest = _audit_hmac.compute_entry_hmac(
1865
+ key_bytes,
1866
+ last_hmac if last_hmac is not None else _audit_hmac.GENESIS_PREV,
1867
+ chained,
1868
+ )
1869
+ chained["hmac"] = digest.hex()
1870
+ last_hmac = digest
1871
+ except _audit_hmac.AuditProducerPathPollutionError as ppe:
1872
+ # PLAN-118 AC-B4 chokepoint 4 / recursion-safety case 5 —
1873
+ # the spool fast-path (audit_emit._write_event line 1760)
1874
+ # appends to spool BEFORE local HMAC computation; the
1875
+ # HMAC is then computed HERE at drain time. On
1876
+ # canonical-resolution mismatch at drain: refuse to
1877
+ # compute HMAC for the batch entry; normalize the entry
1878
+ # to hmac:null + closed-enum hmac_error BEFORE the entry
1879
+ # is appended to the chain (so polluted HMACs never enter
1880
+ # the chain at all). Emit AC-B5 breadcrumb with
1881
+ # chokepoint=spool_drain (safe — the typed emitter's own
1882
+ # _write_event call will also trigger this same
1883
+ # exception and recurse-fail-OPEN through the same
1884
+ # channel; the breadcrumb's purpose is exactly that
1885
+ # forensic signal).
1886
+ chained["hmac"] = None
1887
+ chained["hmac_error"] = "producer_path_pollution_detected"
1888
+ _breadcrumb(
1889
+ f"hmac_error: producer_path_pollution_detected "
1890
+ f"(chokepoint=spool_drain): {ppe}"
1891
+ )
1892
+ # Parse the exception payload for the AC-B5 breadcrumb.
1893
+ try:
1894
+ _msg = str(ppe)
1895
+ _rc = "audit_emit_path_pollution"
1896
+ _psp = "00000000"
1897
+ _ecp = "00000000"
1898
+ _valid_rcs = (
1899
+ "audit_emit_path_pollution",
1900
+ "canonical_json_path_pollution",
1901
+ "audit_hmac_path_pollution",
1902
+ )
1903
+ for _tok in _msg.split():
1904
+ if _tok.startswith("reason_code="):
1905
+ _cand = _tok.split("=", 1)[1]
1906
+ if _cand in _valid_rcs:
1907
+ _rc = _cand
1908
+ elif _tok.startswith("path_sha256_prefix="):
1909
+ _cand = _tok.split("=", 1)[1]
1910
+ if len(_cand) == 8 and all(c in "0123456789abcdef" for c in _cand):
1911
+ _psp = _cand
1912
+ elif _tok.startswith("expected_canonical_prefix="):
1913
+ _cand = _tok.split("=", 1)[1]
1914
+ if len(_cand) == 8 and all(c in "0123456789abcdef" for c in _cand):
1915
+ _ecp = _cand
1916
+ # Lazy-import the typed emitter to avoid circular import
1917
+ # at module load (spool_writer is imported from audit_emit).
1918
+ from _lib import audit_emit as _audit_emit
1919
+ _audit_emit.emit_audit_producer_path_pollution_detected(
1920
+ chokepoint="spool_drain",
1921
+ reason_code=_rc,
1922
+ path_sha256_prefix=_psp,
1923
+ expected_canonical_prefix=_ecp,
1924
+ )
1925
+ except Exception as _be: # pragma: no cover
1926
+ _breadcrumb(
1927
+ f"AC-B5 spool_drain breadcrumb emit failed: "
1928
+ f"{type(_be).__name__}: {_be}"
1929
+ )
1930
+ except Exception as e:
1931
+ chained["hmac"] = None
1932
+ chained["hmac_error"] = f"{type(e).__name__}: {e}"
1933
+ _breadcrumb(f"hmac compute failed: {type(e).__name__}: {e}")
1934
+
1935
+ try:
1936
+ line = json.dumps(
1937
+ chained, separators=(",", ":"), ensure_ascii=False,
1938
+ ).encode("utf-8") + b"\n"
1939
+ except (TypeError, ValueError) as e:
1940
+ _breadcrumb(f"phase4 encode failed: {type(e).__name__}: {e}")
1941
+ _mark(path_key, body_idx)
1942
+ # iter3-P0-1: encode-failure consumed counts toward K_MAX.
1943
+ processed += 1
1944
+ continue
1945
+ batch_lines.append(line)
1946
+ _mark(path_key, body_idx)
1947
+ # P1-3: track for op:"drained" envelope emission
1948
+ if isinstance(rec_id, str) and rec_id:
1949
+ drained_ids.append((rec_id, src_file.pid))
1950
+ # iter3-P0-1: append counts toward K_MAX (was: `appended += 1`).
1951
+ processed += 1
1952
+
1953
+ fully_consumed: List[_DrainingFile] = []
1954
+ return batch_lines, fully_consumed, per_file_consumed, last_hmac, drained_ids
1955
+
1956
+
1957
+ # ---------------------------------------------------------------------------
1958
+ # Phase 5 — atomic append + split + cleanup
1959
+ # ---------------------------------------------------------------------------
1960
+
1961
+
1962
+ def _phase5_append_canonical(
1963
+ batch_lines: List[bytes], last_hmac: Optional[bytes]
1964
+ ) -> int:
1965
+ """Append batch to canonical log; single fsync; update sidecar cache.
1966
+
1967
+ iter2-P2-3: chain_length sidecar increment counts HMAC-bearing lines
1968
+ only — entries that failed HMAC compute (hmac=null + hmac_error set)
1969
+ don't extend the chain even though their bytes are appended. The
1970
+ sidecar is a cache for chain-verify performance; counting null-hmac
1971
+ lines would diverge from the true HMAC chain length.
1972
+ """
1973
+ if not batch_lines:
1974
+ return 0
1975
+ log_path = _canonical_log_path()
1976
+ log_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
1977
+
1978
+ # PLAN-094-FOLLOWUP Wave A.3-rotation — probe rotation BEFORE the
1979
+ # canonical append so spool-drained writes never accumulate past the
1980
+ # configured threshold. Lazy-import audit_emit (circular dep at
1981
+ # module load time) + audit_hmac for chain reset.
1982
+ try:
1983
+ from _lib import audit_emit as _audit_emit_lazy
1984
+ from _lib import audit_hmac as _audit_hmac_lazy
1985
+ # PLAN-143 item-2 (audit-errors-02): guard the rotation probe — a
1986
+ # capture-shim object may lack _rotate_if_needed_safe (see the
1987
+ # phase-4 probe above). getattr-guard -> missing attribute means
1988
+ # "no rotation" (None), no AttributeError into the fail-open path.
1989
+ _rotate_probe = getattr(
1990
+ _audit_emit_lazy, "_rotate_if_needed_safe", None
1991
+ )
1992
+ rotated_to = (
1993
+ _rotate_probe(log_path) if callable(_rotate_probe) else None
1994
+ )
1995
+ if rotated_to is not None and not _audit_hmac_lazy.is_disabled():
1996
+ try:
1997
+ _audit_hmac_lazy.reset_chain_on_rotation()
1998
+ # Reset last_hmac so this batch re-anchors at genesis.
1999
+ last_hmac = None
2000
+ except Exception as re:
2001
+ _breadcrumb(
2002
+ f"phase5 rotation HMAC reset failed: "
2003
+ f"{type(re).__name__}: {re}"
2004
+ )
2005
+ except Exception as e:
2006
+ # Fail-open: rotation probe never blocks the canonical append.
2007
+ _breadcrumb(f"phase5 rotation probe failed: {type(e).__name__}: {e}")
2008
+
2009
+ payload = b"".join(batch_lines)
2010
+ fd = os.open(str(log_path), os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
2011
+ try:
2012
+ os.write(fd, payload)
2013
+ os.fsync(fd)
2014
+ finally:
2015
+ os.close(fd)
2016
+
2017
+ if (_HMAC_AVAILABLE and _audit_hmac is not None
2018
+ and not _audit_hmac.is_disabled()
2019
+ and last_hmac is not None
2020
+ and len(last_hmac) == _audit_hmac.HMAC_BYTES):
2021
+ try:
2022
+ _audit_hmac.write_last_hmac(last_hmac)
2023
+ except Exception as e:
2024
+ _breadcrumb(f"write_last_hmac failed: {type(e).__name__}: {e}")
2025
+ # iter2-P2-3: count HMAC-bearing lines only.
2026
+ hmac_bearing = 0
2027
+ for line in batch_lines:
2028
+ try:
2029
+ obj = json.loads(line.rstrip(b"\n").decode("utf-8"))
2030
+ except (json.JSONDecodeError, UnicodeDecodeError):
2031
+ continue
2032
+ if not isinstance(obj, dict):
2033
+ continue
2034
+ hx = obj.get("hmac")
2035
+ if isinstance(hx, str) and len(hx) == 64:
2036
+ hmac_bearing += 1
2037
+ try:
2038
+ current = _audit_hmac.read_chain_length()
2039
+ _audit_hmac.write_chain_length(current + hmac_bearing)
2040
+ except Exception as e:
2041
+ _breadcrumb(f"chain_length update failed: {type(e).__name__}: {e}")
2042
+ return len(batch_lines)
2043
+
2044
+
2045
+ def _phase5_split_and_cleanup(
2046
+ files: List[_DrainingFile],
2047
+ per_file_consumed: Dict[str, Set[int]],
2048
+ drain_epoch: str,
2049
+ stats: DrainStats,
2050
+ ) -> None:
2051
+ """For each draining file: unlink if fully consumed; else atomic-split.
2052
+
2053
+ iter2-P0-1: per_file_consumed is a Set[int] of body INDICES. The
2054
+ remainder file is built from the COMPLEMENT (all body indices NOT in
2055
+ the set). This correctly handles out-of-order index consumption (e.g.
2056
+ wall_ns clock-skew rollback where a later body index sorts BEFORE an
2057
+ earlier one and is cut by K_MAX) — prefix-based cleanup would have
2058
+ discarded unprocessed earlier lines.
2059
+
2060
+ iter2-P0-3: SKIP any _DrainingFile whose .quarantined flag is set —
2061
+ the file is already renamed to .malformed.<epoch>; trying to split
2062
+ or unlink the original path would either fail (ENOENT) or, worse,
2063
+ re-sweep the renamed quarantined file on a subsequent drain.
2064
+
2065
+ iter2-P1-3: when splitting, mint a NEW drain_epoch for the destination
2066
+ so dst path != src path. This closes the `.split.tmp` two-step window
2067
+ (old code reused src epoch → dst==src → os.rename(src,dst) no-op AND
2068
+ the temp suffix dance remained). Now: tmp → fsync → rename(tmp,
2069
+ new_dst) atomic; unlink(src). Single window if rename succeeds.
2070
+
2071
+ P2-4: header is written VERBATIM from file.header_raw — no JSON
2072
+ re-encode (preserves byte identity of _spool_uuid sentinel chain).
2073
+ """
2074
+ for f in files:
2075
+ # iter2-P0-3: skip quarantined files — original path doesn't exist
2076
+ # at .draining.<epoch> anymore; it was renamed to .malformed.<epoch>.
2077
+ if f.quarantined:
2078
+ continue
2079
+ path_key = str(f.path)
2080
+ if f.header is None and not f.body_lines:
2081
+ # Already empty (no body) — just unlink the (header-only) file.
2082
+ try:
2083
+ if f.path.exists():
2084
+ os.unlink(str(f.path))
2085
+ except OSError:
2086
+ pass
2087
+ continue
2088
+ consumed_set = per_file_consumed.get(path_key, set())
2089
+ total = len(f.body_lines)
2090
+ # iter2-P0-1: fully consumed iff every body index is in the set.
2091
+ if total == 0 or all(i in consumed_set for i in range(total)):
2092
+ try:
2093
+ os.unlink(str(f.path))
2094
+ stats.files_consumed_fully += 1
2095
+ except OSError as e:
2096
+ _breadcrumb(f"phase5 unlink failed: {type(e).__name__}: {e}")
2097
+ continue
2098
+
2099
+ # iter2-P1-3: mint a NEW epoch for the split destination so
2100
+ # dst_path differs from src_path; this collapses the prior
2101
+ # two-step `.split.tmp` rename dance into a single rename+unlink.
2102
+ new_epoch = secrets.token_hex(4)
2103
+ tmp_token = secrets.token_hex(4)
2104
+ # tmp is a sibling .tmp.<token> for atomic create+rename within same dir
2105
+ tmp_path = f.path.with_name(f.path.name + _TMP_SUFFIX_TOKEN + tmp_token)
2106
+ try:
2107
+ # P2-4: verbatim header bytes — no JSON re-encode
2108
+ header_line = f.header_raw if f.header_raw else (
2109
+ json.dumps(
2110
+ f.header, separators=(",", ":"), ensure_ascii=False,
2111
+ ).encode("utf-8") + b"\n"
2112
+ )
2113
+ # iter2-P0-1: rebuild remainder using COMPLEMENT of consumed_set
2114
+ # (NOT a prefix slice). Empty lines are preserved as empty bytes
2115
+ # in body_lines and re-emitted with the newline separator below.
2116
+ remainder_lines = [
2117
+ f.body_lines[i] for i in range(total) if i not in consumed_set
2118
+ ]
2119
+ body_remainder = b"\n".join(remainder_lines)
2120
+ if body_remainder and not body_remainder.endswith(b"\n"):
2121
+ body_remainder += b"\n"
2122
+ fd = os.open(
2123
+ str(tmp_path),
2124
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_TRUNC,
2125
+ 0o600,
2126
+ )
2127
+ try:
2128
+ os.write(fd, header_line)
2129
+ if body_remainder:
2130
+ os.write(fd, body_remainder)
2131
+ os.fsync(fd)
2132
+ finally:
2133
+ os.close(fd)
2134
+ # iter2-P1-3: dst is .draining.<new_epoch> — distinct from src.
2135
+ dst_path = _draining_path(_spool_path(f.pid), new_epoch)
2136
+ # Single atomic rename + unlink source. If rename fails, the
2137
+ # tmp file is cleaned up in the except branch.
2138
+ os.rename(str(tmp_path), str(dst_path))
2139
+ try:
2140
+ os.unlink(str(f.path))
2141
+ except OSError as e: # pragma: no cover
2142
+ # Source gone (race / already cleaned); rename succeeded so
2143
+ # the remainder file is correctly in place.
2144
+ _breadcrumb(
2145
+ f"phase5 src unlink after split failed: "
2146
+ f"{type(e).__name__}: {e}"
2147
+ )
2148
+ stats.files_split_remainder += 1
2149
+ except OSError as e:
2150
+ _breadcrumb(f"phase5 split failed: {type(e).__name__}: {e}")
2151
+ try:
2152
+ if tmp_path.exists():
2153
+ os.unlink(str(tmp_path))
2154
+ except OSError:
2155
+ pass
2156
+
2157
+
2158
+ # ---------------------------------------------------------------------------
2159
+ # Journal post-drain compaction
2160
+ # ---------------------------------------------------------------------------
2161
+
2162
+
2163
+ def _journal_compact_drained(
2164
+ pid: int,
2165
+ drained_record_ids: Iterable[str],
2166
+ drain_epoch: str,
2167
+ ) -> None:
2168
+ """Append op=drained envelopes; rewrite journal dropping fully-completed triples.
2169
+
2170
+ P1-3: emits op:"drained" envelopes with the actual record_id so
2171
+ session-start reconciliation can distinguish recovered from inflight.
2172
+ """
2173
+ drained_set = {r for r in drained_record_ids if isinstance(r, str) and r}
2174
+ if not drained_set:
2175
+ return
2176
+ # Flush any pending in-memory journal envelopes for this PID FIRST so
2177
+ # the compaction read sees a consistent on-disk view.
2178
+ _flush_journal_buffer(pid)
2179
+ journal = _journal_path(pid)
2180
+ if not journal.exists():
2181
+ return
2182
+ # 1) Append drained envelopes via the buffer (then flush)
2183
+ for rid in drained_set:
2184
+ _write_journal_envelope(
2185
+ pid, rid, "", -1, "", "drained", drain_epoch=drain_epoch,
2186
+ )
2187
+ _flush_journal_buffer(pid)
2188
+ # 2) Rewrite journal under per-PID journal flock dropping rows whose
2189
+ # record_id is in drained_set (begin/commit/drained triples collapse).
2190
+ try:
2191
+ with FileLock(_journal_flock_path(pid), timeout=SPOOL_LOCK_TIMEOUT):
2192
+ with journal.open("r", encoding="utf-8") as f:
2193
+ keep_lines: List[str] = []
2194
+ for raw in f:
2195
+ stripped = raw.strip()
2196
+ if not stripped:
2197
+ continue
2198
+ try:
2199
+ env = json.loads(stripped)
2200
+ except json.JSONDecodeError:
2201
+ keep_lines.append(raw)
2202
+ continue
2203
+ if (isinstance(env, dict)
2204
+ and env.get("record_id") in drained_set):
2205
+ continue
2206
+ keep_lines.append(raw)
2207
+ tmp = journal.with_name(journal.name + ".compact.tmp")
2208
+ with tmp.open("w", encoding="utf-8") as f:
2209
+ f.writelines(keep_lines)
2210
+ f.flush()
2211
+ os.fsync(f.fileno())
2212
+ os.replace(str(tmp), str(journal))
2213
+ except FileLockTimeout:
2214
+ _breadcrumb(f"journal compact flock timeout pid={pid}")
2215
+ except OSError as e:
2216
+ _breadcrumb(f"journal compact failed: {type(e).__name__}: {e}")
2217
+
2218
+
2219
+ # ---------------------------------------------------------------------------
2220
+ # drain_now — orchestration
2221
+ # ---------------------------------------------------------------------------
2222
+
2223
+
2224
+ def _own_spool_stale_past_trigger(pid: int) -> bool:
2225
+ """True iff our own spool's mtime age exceeds DRAIN_TRIGGER_MTIME_MS.
2226
+
2227
+ (ADR-055-AMEND-3) SEC veto-floor MF-1 gate. When an opportunistic
2228
+ (force=False) drain yields the canonical lock AND our own spool is already
2229
+ stale past the staleness trigger, the lock holder is not keeping up — that
2230
+ is genuine drain starvation (a wedged/contended holder), distinct from
2231
+ benign single-winner contention (a fresh spool). OSError-safe: a missing or
2232
+ unreadable spool returns False (fail-quiet, never raises).
2233
+ """
2234
+ try:
2235
+ st = _spool_path(pid).stat()
2236
+ except OSError:
2237
+ return False
2238
+ if st.st_size == 0:
2239
+ return False
2240
+ age_ms = (time.time() - st.st_mtime) * 1000.0
2241
+ return age_ms > DRAIN_TRIGGER_MTIME_MS
2242
+
2243
+
2244
+ def drain_now(*, force: bool = False) -> DrainStats:
2245
+ """Execute 5-phase atomic drain. Bounded ≤K_MAX entries per call.
2246
+
2247
+ Fail-open invariant — any error sets stats.ok=False and is captured in
2248
+ stats.error. NEVER raises to caller.
2249
+ """
2250
+ stats = DrainStats()
2251
+ if is_sync_mode() and not force:
2252
+ return stats
2253
+ try:
2254
+ if not force and not should_drain():
2255
+ return stats
2256
+
2257
+ drain_epoch = secrets.token_hex(4)
2258
+ stats.drain_epoch = drain_epoch
2259
+ state_dir = _state_dir()
2260
+ our_pid = os.getpid()
2261
+ log_path = _canonical_log_path()
2262
+ log_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
2263
+ lock_path = _canonical_log_lock()
2264
+
2265
+ # P1-2: flush any buffered journal envelopes BEFORE drain begins so
2266
+ # the journal reflects all begin/commit events up to this point
2267
+ # (op:"drained" envelopes are appended after canonical append).
2268
+ _flush_journal_buffer(our_pid)
2269
+
2270
+ # Phase 1 — canonical lock (deadlock-free; writers hold only their
2271
+ # own per-PID flocks, never the canonical lock).
2272
+ #
2273
+ # (ADR-055-AMEND-3) — opportunistic/forced split. A FORCED
2274
+ # drain (recovery / exit-handler / session-start) must complete, so it
2275
+ # blocks up to SPOOL_LOCK_TIMEOUT and a timeout there is anomalous. An
2276
+ # OPPORTUNISTIC drain (force=False, the per-emit hot path) is
2277
+ # best-effort: the lock holder plus the loser's own later drain cover
2278
+ # its events, so it acquires NON-BLOCKING (timeout=0 — a clean one-shot
2279
+ # try-lock, one flock(LOCK_NB) with no sleep) and yields silently on
2280
+ # contention instead of blocking the hook subprocess for 2.5s.
2281
+ canonical_timeout = SPOOL_LOCK_TIMEOUT if force else 0.0
2282
+ try:
2283
+ with FileLock(lock_path, timeout=canonical_timeout):
2284
+ files, in_recovery = _phase2_sweep_and_rename(
2285
+ state_dir, drain_epoch, our_pid,
2286
+ )
2287
+ stats.in_recovery_mode = in_recovery
2288
+
2289
+ valid_files: List[_DrainingFile] = []
2290
+ for f in files:
2291
+ if _phase2_validate_header(f, drain_epoch):
2292
+ # PLAN-119 WS-D1 — refuse _origin:"test" spool ONLY when
2293
+ # the canonical destination IS the live chain; applied
2294
+ # AFTER header validation and BEFORE phase 3, so test
2295
+ # entries never reach batch_lines / the canonical append.
2296
+ if _should_quarantine_test_origin(f, log_path):
2297
+ _quarantine_test_origin(f, drain_epoch)
2298
+ stats.test_origin_quarantined += 1
2299
+ else:
2300
+ valid_files.append(f)
2301
+ else:
2302
+ stats.quarantined_files += 1
2303
+
2304
+ # iter2-P0-1 + iter4-P2-2: phase 3 returns
2305
+ # (deduped, Dict[str, Set[int]], duplicate_consumed_count)
2306
+ deduped, partial_consumed, dup_consumed = (
2307
+ _phase3_collect_and_sort(
2308
+ valid_files, drain_epoch, stats,
2309
+ )
2310
+ )
2311
+
2312
+ # P0-2 + P1-3 + iter2-P0-1: phase 4 returns
2313
+ # (batch_lines, _, per_file_consumed_set, last_hmac, ids)
2314
+ # iter4-P2-2: pass dup_consumed as starting_processed so
2315
+ # combined Phase-3 + Phase-4 work bounded by K_MAX.
2316
+ (batch_lines, _, per_file_consumed, last_hmac,
2317
+ drained_ids) = _phase4_build_batch(
2318
+ deduped, drain_epoch, in_recovery, stats,
2319
+ starting_processed=dup_consumed,
2320
+ )
2321
+
2322
+ # iter2-P0-1: merge partial/duplicate-rejection consumed
2323
+ # indices (Set[int] union) into the Phase-4 per-file map so
2324
+ # Phase 5 split honors the COMPLEMENT for remainder lines.
2325
+ for path_key, idx_set in partial_consumed.items():
2326
+ if path_key in per_file_consumed:
2327
+ per_file_consumed[path_key] |= idx_set
2328
+ else:
2329
+ per_file_consumed[path_key] = set(idx_set)
2330
+
2331
+ stats.appended = _phase5_append_canonical(batch_lines, last_hmac)
2332
+
2333
+ # P1-3: emit op:"drained" envelopes grouped by pid + flush.
2334
+ drained_by_pid: Dict[int, List[str]] = {}
2335
+ for rec_id, pid in drained_ids:
2336
+ drained_by_pid.setdefault(pid, []).append(rec_id)
2337
+ for pid, ids in drained_by_pid.items():
2338
+ _journal_compact_drained(pid, ids, drain_epoch)
2339
+
2340
+ _phase5_split_and_cleanup(
2341
+ valid_files, per_file_consumed, drain_epoch, stats,
2342
+ )
2343
+
2344
+ # P1-2: drain-boundary journal flush for our own PID
2345
+ _flush_journal_buffer(our_pid)
2346
+ except FileLockTimeout:
2347
+ if force:
2348
+ # Forced drain could not complete — genuinely anomalous.
2349
+ stats.ok = False
2350
+ stats.error = "canonical_lock_timeout"
2351
+ _breadcrumb("drain canonical lock timeout")
2352
+ else:
2353
+ # Opportunistic yield — expected under concurrency, NOT an
2354
+ # error. Another drainer holds the lock; its global sweep plus
2355
+ # our own later drain cover our events, so ok stays True.
2356
+ stats.contended_skip = True
2357
+ # SEC veto-floor MF-1 (ADR-052): keep a genuinely wedged holder
2358
+ # observable. Emit a DISTINCT, gated breadcrumb ONLY when our
2359
+ # own spool is already stale past the staleness trigger (holder
2360
+ # not keeping up = real starvation). Benign single-winner
2361
+ # contention (fresh spool) stays silent, so the existing
2362
+ # audit-log.errors line-count detectors (ceo-diagnose.py /
2363
+ # status.py) still surface a wedge without the benign volume.
2364
+ if _own_spool_stale_past_trigger(our_pid):
2365
+ _breadcrumb(
2366
+ "drain canonical lock STARVED: own spool stale past "
2367
+ "trigger while opportunistic drain yielded"
2368
+ )
2369
+ return stats
2370
+ except Exception as e:
2371
+ stats.ok = False
2372
+ stats.error = f"{type(e).__name__}: {e}"
2373
+ _breadcrumb(f"drain_now unexpected: {type(e).__name__}: {e}")
2374
+ return stats
2375
+
2376
+
2377
+ # ---------------------------------------------------------------------------
2378
+ # Session-start reconciliation
2379
+ # ---------------------------------------------------------------------------
2380
+
2381
+
2382
+ def reconcile_journal_at_session_start() -> JournalReconciliation:
2383
+ """Walk audit-pending.*.journal entries; classify counts; emit forensic.
2384
+
2385
+ Returns the JournalReconciliation populated with counts. Emits
2386
+ audit_flush_dropped_count via wired forensic callback (caller in
2387
+ audit_emit wires the callback before invoking this).
2388
+ """
2389
+ rec = JournalReconciliation()
2390
+ state_dir = _state_dir()
2391
+ try:
2392
+ if not state_dir.exists():
2393
+ _forensic("audit_flush_dropped_count", _journal_rec_dict(rec))
2394
+ return rec
2395
+ names = sorted(os.listdir(str(state_dir)))
2396
+ except OSError:
2397
+ _forensic("audit_flush_dropped_count", _journal_rec_dict(rec))
2398
+ return rec
2399
+
2400
+ by_record: Dict[str, Dict[str, bool]] = {}
2401
+ for name in names:
2402
+ if not name.startswith(_JOURNAL_PREFIX + "."):
2403
+ continue
2404
+ if not name.endswith(".journal"):
2405
+ continue
2406
+ if name == _aggregate_journal_path().name:
2407
+ continue
2408
+ # Parse <pid> from "audit-pending.<pid>.journal"
2409
+ rest = name[len(_JOURNAL_PREFIX) + 1:]
2410
+ pid_str = rest[:-len(".journal")]
2411
+ try:
2412
+ pid = int(pid_str)
2413
+ except ValueError:
2414
+ continue
2415
+ if pid == os.getpid():
2416
+ continue
2417
+ if _is_alive_pid(pid):
2418
+ continue
2419
+ path = state_dir / name
2420
+ try:
2421
+ with path.open("r", encoding="utf-8") as f:
2422
+ for raw in f:
2423
+ stripped = raw.strip()
2424
+ if not stripped:
2425
+ continue
2426
+ try:
2427
+ env = json.loads(stripped)
2428
+ except json.JSONDecodeError:
2429
+ continue
2430
+ if not isinstance(env, dict):
2431
+ continue
2432
+ rid = env.get("record_id")
2433
+ op = env.get("op")
2434
+ if not isinstance(rid, str) or not isinstance(op, str):
2435
+ continue
2436
+ slot = by_record.setdefault(
2437
+ rid, {"begin": False, "commit": False, "drained": False},
2438
+ )
2439
+ if op in slot:
2440
+ slot[op] = True
2441
+ except OSError:
2442
+ continue
2443
+
2444
+ for rid, ops in by_record.items():
2445
+ if ops["begin"] and not ops["commit"]:
2446
+ rec.begin_no_commit += 1
2447
+ elif ops["commit"] and not ops["drained"]:
2448
+ rec.commit_no_drained += 1
2449
+ elif ops["commit"] and ops["drained"]:
2450
+ rec.recovered += 1
2451
+
2452
+ # Trigger a recovery drain to sweep commit_no_drained envelopes.
2453
+ if rec.commit_no_drained > 0:
2454
+ recov = drain_now(force=True)
2455
+ if recov.ok:
2456
+ # iter2-P1-2: canonical-append-before-unlink crash recovery
2457
+ # produces mostly `skipped_idempotent` (the entries are already
2458
+ # in the canonical log; next drain reads same bytes from
2459
+ # .draining, sees them in K_TAIL_WINDOW, skips). Sum both
2460
+ # counters so AC10 reports actual recovery count, not just the
2461
+ # rare appended-on-recovery subset.
2462
+ rec.recovered += recov.appended + recov.skipped_idempotent
2463
+ # iter3-P2-1: surface intentionally_deleted (duplicate-tuple
2464
+ # rejections during recovery) into the reconciliation counter
2465
+ # so the audit_flush_dropped_count emit carries it.
2466
+ rec.intentionally_deleted += recov.intentionally_deleted
2467
+
2468
+ _forensic("audit_flush_dropped_count", _journal_rec_dict(rec))
2469
+ return rec
2470
+
2471
+
2472
+ def _journal_rec_dict(rec: JournalReconciliation) -> Dict[str, Any]:
2473
+ return {
2474
+ "begin_no_commit": rec.begin_no_commit,
2475
+ "commit_no_drained": rec.commit_no_drained,
2476
+ "recovered": rec.recovered,
2477
+ "truly_lost": rec.truly_lost,
2478
+ "tamper_rejected": rec.tamper_rejected,
2479
+ "intentionally_deleted": rec.intentionally_deleted,
2480
+ }
2481
+
2482
+
2483
+ # ---------------------------------------------------------------------------
2484
+ # Exit handlers
2485
+ # ---------------------------------------------------------------------------
2486
+
2487
+
2488
+ def _atexit_drain() -> None:
2489
+ """atexit hook — best-effort final drain. Swallow all errors.
2490
+
2491
+ P1-2: also flushes the in-memory journal buffer for our PID so any
2492
+ begin/commit envelopes that hadn't hit the amortization threshold
2493
+ are durable before process exit.
2494
+ """
2495
+ try:
2496
+ drain_now(force=True)
2497
+ except Exception:
2498
+ pass
2499
+ try:
2500
+ _flush_journal_buffer(os.getpid())
2501
+ except Exception:
2502
+ pass
2503
+
2504
+
2505
+ def _signal_drain_handler(signum: int, frame: Any) -> None:
2506
+ """SIGTERM/SIGINT — force-drain then re-raise to default handler.
2507
+
2508
+ P1-1: if a writer is currently inside spool_append (holding the spool
2509
+ flock in our PID), bail without forcing a drain — the in-flight entry
2510
+ will be picked up by next-session reconciliation.
2511
+
2512
+ P1-2: flushes the journal buffer for our PID so begin/commit envelopes
2513
+ survive signal-driven termination.
2514
+
2515
+ P2-5: if the previous handler was signal.SIG_IGN, preserve it (the
2516
+ user explicitly ignored this signal; default termination would be a
2517
+ behavior change). atexit does NOT run after default signal termination
2518
+ so our drain call here IS the final flush opportunity for SIG_DFL paths.
2519
+ """
2520
+ if _IN_SPOOL_APPEND:
2521
+ # Writer in progress; bail rather than deadlock against ourself.
2522
+ _breadcrumb(f"signal {signum} arrived during spool_append; bail")
2523
+ return
2524
+ try:
2525
+ drain_now(force=True)
2526
+ except Exception:
2527
+ pass
2528
+ try:
2529
+ _flush_journal_buffer(os.getpid())
2530
+ except Exception:
2531
+ pass
2532
+ # Resolve the previous handler.
2533
+ if signum == signal.SIGTERM:
2534
+ prev = _PREV_SIGTERM_HANDLER
2535
+ elif signum == signal.SIGINT:
2536
+ prev = _PREV_SIGINT_HANDLER
2537
+ else:
2538
+ prev = signal.SIG_DFL
2539
+
2540
+ # P2-5: distinguish SIG_IGN vs SIG_DFL vs callable.
2541
+ if prev is signal.SIG_IGN:
2542
+ # Restore IGN and do NOT re-raise — the user wanted this signal
2543
+ # ignored; honoring that is correctness, not failure.
2544
+ try:
2545
+ signal.signal(signum, signal.SIG_IGN)
2546
+ except (ValueError, OSError): # pragma: no cover
2547
+ pass
2548
+ return
2549
+ if callable(prev):
2550
+ # Chain to user handler.
2551
+ try:
2552
+ signal.signal(signum, prev)
2553
+ except (ValueError, OSError): # pragma: no cover
2554
+ pass
2555
+ try:
2556
+ prev(signum, frame)
2557
+ except Exception: # pragma: no cover
2558
+ pass
2559
+ return
2560
+ # Default: restore SIG_DFL and re-raise. atexit does NOT run after
2561
+ # default-signal termination → the drain_now(force=True) above is the
2562
+ # final flush opportunity.
2563
+ try:
2564
+ signal.signal(signum, signal.SIG_DFL)
2565
+ except (ValueError, OSError): # pragma: no cover
2566
+ pass
2567
+ try:
2568
+ os.kill(os.getpid(), signum)
2569
+ except OSError: # pragma: no cover
2570
+ pass
2571
+
2572
+
2573
+ def install_exit_handlers() -> None:
2574
+ """Register atexit + SIGTERM/SIGINT drain handlers (idempotent one-shot)."""
2575
+ global _EXIT_HANDLER_INSTALLED
2576
+ global _PREV_SIGTERM_HANDLER, _PREV_SIGINT_HANDLER
2577
+ if _EXIT_HANDLER_INSTALLED:
2578
+ return
2579
+ try:
2580
+ atexit.register(_atexit_drain)
2581
+ except Exception as e: # pragma: no cover
2582
+ _breadcrumb(f"atexit register failed: {type(e).__name__}: {e}")
2583
+ try:
2584
+ _PREV_SIGTERM_HANDLER = signal.signal(
2585
+ signal.SIGTERM, _signal_drain_handler,
2586
+ )
2587
+ except (ValueError, OSError) as e:
2588
+ # Signal handlers only work on main thread; in worker threads
2589
+ # signal.signal raises ValueError. Fail-open.
2590
+ _breadcrumb(f"SIGTERM install skipped: {type(e).__name__}: {e}")
2591
+ try:
2592
+ _PREV_SIGINT_HANDLER = signal.signal(
2593
+ signal.SIGINT, _signal_drain_handler,
2594
+ )
2595
+ except (ValueError, OSError) as e:
2596
+ _breadcrumb(f"SIGINT install skipped: {type(e).__name__}: {e}")
2597
+ _EXIT_HANDLER_INSTALLED = True
2598
+
2599
+
2600
+ # ---------------------------------------------------------------------------
2601
+ # Test helpers (NOT public API — prefixed _; harness uses these)
2602
+ # ---------------------------------------------------------------------------
2603
+
2604
+
2605
+ def _reset_for_test() -> None:
2606
+ """Clear all in-process spool state (caches + handler latch + buffer)."""
2607
+ global _EXIT_HANDLER_INSTALLED, _IN_FORENSIC_EMIT, _IN_SPOOL_APPEND
2608
+ _SPOOL_HEADER_CACHE.clear()
2609
+ _ORDINAL_COUNTER.clear()
2610
+ _JOURNAL_BUFFER.clear()
2611
+ _EXIT_HANDLER_INSTALLED = False
2612
+ _IN_FORENSIC_EMIT = False
2613
+ _IN_SPOOL_APPEND = False