diff --git a/CHANGELOG.md b/CHANGELOG.md index 844cbbf..576d9be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,14 @@ GovEngine follows conservative pre-1.0 versioning while the API boundary is stil and `validate_runtime_admission_result()`. The composer assembles policy, ticket, trust, guarded replay, runner profile, receipt obligation, and bounded artifact-reference summaries into one host-consumable decision record. -- Added runner profile and receipt admission gates. Controlled execution now - requires an approved runner profile together with a matching receipt before - granting live execution authority. +- Added runner-profile and receipt-obligation admission gates. Runtime admission + now requires an allowed runner profile and a receipt obligation; concrete + runner receipts are validated separately with + `validate_runner_receipt_binding()` against admission, ticket, request, and + receipt digests. +- Added these admission gates before any host-owned controlled runner path may + proceed; GovEngine still does not grant live execution authority or ship a live + subprocess backend. - Added inspect-only admission workflow and `scripts/inspect_runtime_admission.py` for read-only operator inspection of composed admission decisions without live execution. @@ -26,9 +31,10 @@ GovEngine follows conservative pre-1.0 versioning while the API boundary is stil in-memory development adapter. The port records replay claims (root tag, chain id, ticket/run id, key id) and can reject repeat roots in require-fresh mode without replacing existing guarded-root replay helpers from `0.12.1`. -- Added receipt-to-admission binding plus validators for runner receipt bindings - and admission evidence chains. -- Added `verify_evidence_review_chain()` and supporting validators for end-to-end +- Added receipt-to-admission binding plus `validate_runner_receipt_binding()` + and `validate_evidence_review_chain()` for bounded admission/ticket/request/ + receipt and receipt/evidence/review reference checks. +- Added `validate_evidence_review_chain()` and supporting validators for end-to-end evidence review chains (admission → receipt → evidence → review). - Added `AuditLedgerPort` contracts plus `JsonlAuditLedgerAdapter`, a development-only hash-chained append-only JSONL adapter without choosing a @@ -40,10 +46,11 @@ GovEngine follows conservative pre-1.0 versioning while the API boundary is stil `canonical_govengine_record()`, `signed_artifact_from_record()`, `verify_signed_govengine_record()`, and supporting trust/key-resolver ports. - Refined trust ports and normalized admission artifact references and digests. -- Added local subprocess runner readiness gating: - `LocalSubprocessRunnerReadiness`, `evaluate_local_subprocess_runner_readiness()`, - and `validate_runner_receipt_binding()`. The kernel can now mark the local - runner as not applicable and enforce that decision at the admission gate. +- Added local subprocess runner readiness gating with + `LocalSubprocessRunnerReadiness` and + `evaluate_local_subprocess_runner_readiness()`. The kernel keeps the optional + local subprocess runner at `not_applicable` until explicit host safety + prerequisites exist. ### Documentation, validation, and repository hygiene diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d07c2f..6784a3a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,7 @@ python -m venv .venv python -m pip install --upgrade pip python -m pip install -e '.[dev]' python -m pytest -q +python scripts/validate_public_truth.py ``` For dependency-consistency and readiness validation, run the clean installed-package gate from a new virtual environment path: diff --git a/PUBLIC_STATUS.md b/PUBLIC_STATUS.md index d78a001..9a8bda8 100644 --- a/PUBLIC_STATUS.md +++ b/PUBLIC_STATUS.md @@ -44,6 +44,16 @@ GovEngine is an **alpha governed-runtime kernel package** extracted from Ravencl decision surface. The helpers compose separate policy, ticket, trust, guarded-replay, runner, receipt-obligation, and bounded reference summaries before any live backend work. +- Receipt and evidence binding: `validate_runner_receipt_binding()` and + `validate_evidence_review_chain()` validate bounded admission/ticket/request/ + receipt and receipt/evidence/review reference chains without storing raw + evidence or replacing SCLite review verdict authority. +- Audit ledger port: `AuditLedgerPort` and development-only + `JsonlAuditLedgerAdapter` provide append/read/verify contracts without + production database, locking, or retention ownership. +- Inspect-only admission workflow: `scripts/inspect_runtime_admission.py` + validates and summarizes `RuntimeAdmissionResult` records without creating + runner requests, replay claims, audit entries, or live execution authority. - Public surface registry: tested `govengine.surfaces` metadata contains only neutral artifact-governance core, planning-contracts core, admission-policy core, evidence-review core, domain-profile SDK, runtime contract proofs, and controlled-execution core surfaces. - Security profile retirement: the published `0.12.0a0` alpha package removes the former optional Ravenclaw-derived facade and helper modules; security-domain behavior remains host-owned. - Deconfliction/state index: initial conflict/change-order helpers and lightweight artifact state summaries. diff --git a/README.md b/README.md index 47f97c2..50704af 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,16 @@ GovEngine is **not** Ravenclaw, Tecrax, Logdash, an LLM agent loop, a scanner, o - explicit SCLite integration seams; - focused standalone pytest coverage and GitHub Actions CI. +On current `main` (already in source, still listed under `Unreleased` in `CHANGELOG.md` until the next PyPI alpha release), the governed-runtime MVP also adds: + +- **one admission decision you can actually read** — a single `RuntimeAdmissionResult` record that summarizes whether a prepared request may proceed, what blocked it, and what to fix next; helpers compose and validate that record from separate policy, ticket, trust, guard, replay, runner, and receipt signals without running live work themselves; +- **replay freshness** — remember which verified SCLite guarded roots were already used, so the same protected bundle cannot silently count as “fresh” twice; +- **receipt and evidence chain checks** — confirm that a runner receipt still points at the right admission and ticket, and that later evidence or review references stay within the bounds of that receipt; +- **GovEngine-owned record signing for fixtures** — deterministic digests and signed-record helpers for tests and reviewer demos, not production PKI; +- **a development-only audit trail adapter** — append and verify a local hash-chained audit log during development, without claiming a production database; +- **runner safety posture** — supervision helpers that keep dry-run as the default and treat an optional local subprocess runner as not ready until explicit host safety gates exist; +- **operator inspect without executing** — `scripts/inspect_runtime_admission.py` lets you read and summarize an admission record read-only, with no runner request, replay claim, audit write, or live execution. + ## What it intentionally does not include yet - live subprocess execution backend; @@ -68,15 +78,19 @@ GovEngine is **not** Ravenclaw, Tecrax, Logdash, an LLM agent loop, a scanner, o - LLM provider integrations; - Ravenclaw-specific personas, workspace state, or campaign UX; - production-readiness claims; -- PKI, CA, KMS, key storage, or production identity proof. +- PKI, CA, KMS, key storage, or production identity proof; +- a shipped `LocalSubprocessRunner` implementation (`LocalSubprocessRunnerReadiness` is a gating contract only); +- production replay or audit persistence (`ReplayClaimStore` and `JsonlAuditLedgerAdapter` are host-owned or development-only adapters). ## Current status GovEngine is an **alpha package 0.12.2a0 (`0.12.2-alpha`)**. It keeps the neutral artifact-governance, planning, admission/policy, controlled-execution, runner-supervision, runtime-shell, evidence-review, profile, and proof surfaces while removing the former optional security-profile facade and Ravenclaw-derived helper modules. The published dependency line is `sclite-core>=1.0.1,<1.1`. +Source on `main` already includes the governed-runtime MVP described below; the last PyPI publish remains `0.12.2a0`, with the MVP changes recorded under `Unreleased` in `CHANGELOG.md` until the next alpha release. + ## Current roadmap direction -The governed-runtime MVP now includes a canonical `RuntimeAdmissionResult` +The governed-runtime MVP on `main` includes a canonical `RuntimeAdmissionResult` record as the bounded admission decision surface and `compose_runtime_admission_result()` as the neutral gate-summary composition helper. The helper composes prepared execution contract status, policy @@ -87,6 +101,8 @@ references into that record. `normalize_admission_artifact_refs()` is an alpha helper for bounded review references and existing digest strings; it does not compute content digests or claim SCLite canonicalization. +`compose_runtime_admission_result()` composes host-supplied gate summaries; it does not validate SCLite tickets, verify signatures, record replay state, or execute live work. + The operator-facing MVP flow is documented in [`docs/GOVERNED_RUNTIME_MVP_RUNBOOK.md`](docs/GOVERNED_RUNTIME_MVP_RUNBOOK.md). It ties admission, trust ports, guarded SCLite verification, replay freshness, @@ -115,6 +131,7 @@ python -m venv .venv . .venv/bin/activate python -m pip install -e '.[dev]' python -m pytest -q +python scripts/validate_public_truth.py ``` ## Minimal smoke example @@ -155,12 +172,16 @@ assert receipt["status"] == "dry-run" - [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — package shape and dependency boundaries. - [`docs/SCLITE_INTEGRATION.md`](docs/SCLITE_INTEGRATION.md) — how GovEngine consumes SCLite. - [`docs/API_BOUNDARY.md`](docs/API_BOUNDARY.md) — owned vs excluded surfaces. +- [`docs/API_STABILITY_MATRIX.md`](docs/API_STABILITY_MATRIX.md) — alpha vs fixture export classification. - [`docs/RUNTIME_ADMISSION.md`](docs/RUNTIME_ADMISSION.md) — canonical runtime admission contract direction. - [`docs/GOVERNED_RUNTIME_MVP_RUNBOOK.md`](docs/GOVERNED_RUNTIME_MVP_RUNBOOK.md) — operator-facing governed-runtime MVP chain. +- [`docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md`](docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md) — read-only admission inspect workflow. +- [`docs/GUARDED_FRESH_RUNTIME_ADMISSION_EXAMPLE.md`](docs/GUARDED_FRESH_RUNTIME_ADMISSION_EXAMPLE.md) — guarded-strict plus replay-fresh example. - [`docs/ADMISSION_POLICY.md`](docs/ADMISSION_POLICY.md) — neutral admission, policy, approval, audit, and audit-ledger contracts. - [`docs/RECEIPT_BINDING.md`](docs/RECEIPT_BINDING.md) — admission/ticket/request/receipt binding design. - [`docs/EVIDENCE_REVIEW.md`](docs/EVIDENCE_REVIEW.md) — receipt-bounded evidence/review contract. - [`docs/RUNNER_SUPERVISION.md`](docs/RUNNER_SUPERVISION.md) — runner request, receipt, supervision, and live-runner safety boundaries. +- [`docs/LOCAL_SUBPROCESS_RUNNER_DECISION.md`](docs/LOCAL_SUBPROCESS_RUNNER_DECISION.md) — local subprocess runner decision record. - [`docs/GOVENGINE_KERNEL_BOUNDARY.md`](docs/GOVENGINE_KERNEL_BOUNDARY.md) — kernel/profile/runtime/SCLite ownership split. - [`docs/DOMAIN_PROFILE_CONTRACT.md`](docs/DOMAIN_PROFILE_CONTRACT.md) — domain profile contract and conformance rules. - [`docs/ORCHESTRATOR_MODEL.md`](docs/ORCHESTRATOR_MODEL.md) — deterministic orchestration boundary and runtime non-claims. diff --git a/docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md b/docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md index f0ed5a0..be7b2d7 100644 --- a/docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md +++ b/docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md @@ -1,8 +1,8 @@ # Inspect-Only Admission Workflow This document defines the operator-facing inspect workflow for -`RuntimeAdmissionResult` records. It is a design contract for GE-034 and does -not implement execution. +`RuntimeAdmissionResult` records. The workflow is read-only: it validates and +summarizes an admission record without executing work. The workflow exists so an operator or host runtime can inspect a canonical runtime-admission record before any runner request is considered. @@ -32,25 +32,24 @@ It must not execute live work, contact targets, open raw evidence, resolve credentials, call a runner backend, mutate SCLite artifacts, claim replay keys, or store audit entries. -## Proposed Operator Shape +## Operator Command -GE-035 should implement this as a small inspect-only script or CLI surface. The -preferred initial command shape is: +The inspect-only surface is implemented as: ```bash python scripts/inspect_runtime_admission.py path/to/runtime-admission.json ``` -The command should be read-only and should exit non-zero only when the input -record is malformed or unsafe to summarize. +The command is read-only and exits with code `2` when the input record is +malformed or unsafe to summarize. -Optional future flags may be added only if they remain inspect-only: +Supported inspect-only flags: -- `--format text` for compact human output; +- `--format text` for compact human output (default); - `--format json` for bounded machine-readable output; - `--show-artifact-refs` to include already-bounded references and digests. -No future flag may grant execution authority. +No flag may grant execution authority. No future flag may grant execution authority. ## Output Contract @@ -70,9 +69,9 @@ artifact_refs: 2 bounded refs execution: not performed ``` -JSON output, if implemented, should contain the same bounded fields and should -omit raw payloads, credentials, prompts, stdout, stderr, commands, targets, and -raw evidence. It should never include environment variables or secret material. +JSON output contains the same bounded fields and omits raw payloads, +credentials, prompts, stdout, stderr, commands, targets, and raw evidence. It +never includes environment variables or secret material. ## Validation Rules @@ -122,9 +121,9 @@ execution enablement. - no raw evidence loading; - no Ravenclaw, OpenClaw, MCP, A2A, scheduler, carrier, or credential adapter. -## GE-035 Implementation Acceptance +## Test Coverage -The implementation task should add tests proving: +Standalone tests in `tests/test_admission_contracts.py` prove: - an allowed dry-run admission prints compact allowed output; - a blocked admission prints blockers and required next actions; @@ -135,7 +134,7 @@ The implementation task should add tests proving: ## Implementation Status -The initial inspect-only surface is implemented as: +The inspect-only surface is implemented as: ```bash python scripts/inspect_runtime_admission.py path/to/runtime-admission.json diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index bff1027..dcd2fef 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -89,20 +89,37 @@ profile-owned tool, policy, and UX semantics remain in Ravenclaw. New neutral extraction should land in typed core/profile surfaces only when the code and a second host prove it there. -## Post-0.12.2 governed-runtime MVP direction - -The internal 2026-06-06 audit points to one highest-leverage next target: -formalize a canonical runtime admission result before adding any live runner -surface. GovEngine already has useful pieces across policy, execution tickets, -signing/trust, guarded SCLite replay, runner requests/receipts, and dry-run -gates. The public kernel shape is one bounded machine-readable decision that -composes those pieces without turning intent into execution authority; the -initial record and composition helper now exist and need focused negative -coverage plus later trust/receipt/audit hardening. - -The MVP contract is now named `RuntimeAdmissionResult`; the roadmap may still -use `GovernedExecutionAdmission` as an equivalent concept name. It should -report: +## Post-0.12.2 governed-runtime MVP + +Status: implemented on current `main` and recorded in `CHANGELOG.md` under +`Unreleased` until the next PyPI alpha release. + +GovEngine already had useful pieces across policy, execution tickets, signing/trust, +guarded SCLite replay, runner requests/receipts, and dry-run gates. The public +kernel now exposes one bounded machine-readable decision that composes those +pieces without turning intent into execution authority. + +Delivered MVP surface: + +- `RuntimeAdmissionResult`, `compose_runtime_admission_result()`, + `validate_runtime_admission_result()`, and `normalize_admission_artifact_refs()`; +- `ReplayClaimStore`, `InMemoryReplayClaimStore`, and + `verify_guard_and_record_replay()`; +- `validate_runner_receipt_binding()` and `validate_evidence_review_chain()`; +- GovEngine-owned record digests and signed-record helpers; +- `AuditLedgerPort` and development-only `JsonlAuditLedgerAdapter`; +- `LocalSubprocessRunnerReadiness` with `not_applicable` as the current local + runner posture; +- `scripts/inspect_runtime_admission.py` and operator docs under + `docs/GOVERNED_RUNTIME_MVP_RUNBOOK.md`, + `docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md`, and + `docs/GUARDED_FRESH_RUNTIME_ADMISSION_EXAMPLE.md`; +- focused negative tests for admission composition, replay claim-once behavior, + receipt/evidence binding, audit tamper cases, inspect-only workflow, and + governed-runtime smoke coverage. + +The MVP contract is named `RuntimeAdmissionResult`; `GovernedExecutionAdmission` +remains an equivalent concept name for hosts and roadmap discussion. It reports: - status and `allowed`; - deterministic reason code; @@ -119,10 +136,18 @@ report: - bounded artifact references or digests. This admission result is not a live execution backend. It is the reviewable -decision surface that later trust, receipt, ledger, replay-store, inspect-only, -and optional runner work must use. Live subprocess execution remains disabled by -default and out of scope until admission, trust, replay freshness, receipt -binding, runner safety requirements, and negative tests are complete. +decision surface that trust, receipt, ledger, replay-store, inspect-only, and +optional runner work must use. Live subprocess execution remains disabled by +default and out of scope until a future host adapter satisfies the runner safety +requirements and negative tests for any optional live backend. + +Remaining follow-up for the next alpha release line: + +- publish the `Unreleased` governed-runtime MVP through the normal PyPI alpha + gates; +- keep production replay, audit, and evidence persistence host-owned; +- keep optional `LocalSubprocessRunner` out of the kernel while readiness stays + `not_applicable`. ## Version roadmap diff --git a/docs/RUNTIME_ADMISSION.md b/docs/RUNTIME_ADMISSION.md index ecdad17..32e9eba 100644 --- a/docs/RUNTIME_ADMISSION.md +++ b/docs/RUNTIME_ADMISSION.md @@ -55,7 +55,7 @@ The admission result composes existing GovEngine and SCLite-facing signals: ## Output Shape -The implementation should expose a small immutable record with fields equivalent +The implementation exposes a small immutable record with fields equivalent to: - `status`: `allowed`, `blocked`, `dry_run_only`, `needs_review`, or @@ -88,9 +88,9 @@ execution grant. The inspect-only operator workflow is documented in [INSPECT_ONLY_ADMISSION_WORKFLOW.md](INSPECT_ONLY_ADMISSION_WORKFLOW.md). It -defines a future read-only admission-record inspection surface that validates -and summarizes `RuntimeAdmissionResult` records without creating runner -requests, receipts, replay claims, audit entries, or live execution authority. +validates and summarizes `RuntimeAdmissionResult` records through +`scripts/inspect_runtime_admission.py` without creating runner requests, +receipts, replay claims, audit entries, or live execution authority. ## Gate Semantics @@ -130,7 +130,7 @@ ports needed by host runtimes. ## Relationship To Existing Modules -The implementation should compose the existing surfaces instead of replacing +The implementation composes the existing surfaces instead of replacing them: - `govengine.admission` for policy/admission/approval/audit record shapes; @@ -147,7 +147,7 @@ them: ## Implementation Status And Next Tasks -The first implementation is additive and backward-compatible: +Delivered on current `main` (CHANGELOG `Unreleased` until the next PyPI alpha): 1. `RuntimeAdmissionResult` exists as the core record; 2. `validate_runtime_admission_result()` enforces basic status/allowed/blocker @@ -157,26 +157,36 @@ The first implementation is additive and backward-compatible: 4. `normalize_admission_artifact_refs()` is exposed as an alpha bounded-reference helper for admission review output; 5. `canonical_govengine_record()` and `govengine_record_digest()` provide a - GovEngine-owned record serialization/digest boundary for future - admission/receipt binding without canonicalizing SCLite artifacts; + GovEngine-owned record serialization/digest boundary for admission/receipt + binding without canonicalizing SCLite artifacts; 6. `SignedArtifact` binds a GovEngine-owned record digest to signer metadata and a payload reference while leaving production verification to host-provided verifier ports; 7. key-resolver and trust-store port records carry references and decisions only, not private key material, credentials, KMS, CA, or trust-anchor storage; -8. existing helpers remain unchanged. - -The next implementation tasks should: - -1. add focused negative tests for each missing or failed gate; +8. `validate_runner_receipt_binding()` and `validate_evidence_review_chain()` + validate bounded receipt and evidence/review reference chains; +9. `AuditLedgerPort` and `JsonlAuditLedgerAdapter` provide a development-only + hash-chained audit append/read/verify surface; +10. `ReplayClaimStore`, `InMemoryReplayClaimStore`, and + `verify_guard_and_record_replay()` provide replay claim-once and + guarded-strict composition helpers; +11. `scripts/inspect_runtime_admission.py` implements the inspect-only operator + workflow; +12. focused negative tests cover missing policy, ticket, trust, replay, + runner-profile, and receipt-obligation blockers in admission composition. + +Remaining work: + +1. publish the governed-runtime MVP as the next alpha PyPI line when operator + release gates pass; 2. keep live backend support disabled by default; -3. keep serialization bounded and deterministic enough for later digest/signing - work; -4. implement the inspect-only admission workflow from - [INSPECT_ONLY_ADMISSION_WORKFLOW.md](INSPECT_ONLY_ADMISSION_WORKFLOW.md); -5. document any host-owned input that GovEngine validates but does not produce. - -The result should be usable by future receipt, audit-ledger, replay-store, -inspect-only, and optional runner tasks, but it must not claim production +3. keep production replay, audit, and evidence persistence host-owned beyond the + development adapters; +4. keep optional `LocalSubprocessRunner` out of the kernel until host-owned live + profile authorization and safety gates are implemented and tested. + +The result is usable by hosts for receipt, audit-ledger, replay-store, +inspect-only, and dry-run runner workflows, but it must not claim production runtime readiness. diff --git a/docs/SCLITE_INTEGRATION.md b/docs/SCLITE_INTEGRATION.md index 9d90fde..bad01e2 100644 --- a/docs/SCLITE_INTEGRATION.md +++ b/docs/SCLITE_INTEGRATION.md @@ -38,12 +38,11 @@ intent_contract GovEngine currently provides helpers around the runtime-facing parts of that lifecycle: -- action validation and compilation before contract shaping; -- policy decision normalization/evaluation; -- execution-contract shaping and redaction; -- approved-spec and execution-ticket checks; +- approved-spec and execution-ticket validation helpers; +- execution-contract shaping and redaction through `govengine.contracts.execution`; - dry-run result assembly; -- integration seams for SCLite verification; +- host-provided policy and trust decision shape validation through `govengine.admission` and `govengine.signing` (GovEngine does not own domain policy meaning); +- integration seams for SCLite lifecycle/review verification; - guarded-root replay checks for optional SCLite `kernel_guard_hmac_v1` sidecars after SCLite has verified the HMAC guard; - `ReplayClaimStore`, a host-neutral claim-once replay freshness port, plus an @@ -51,7 +50,7 @@ GovEngine currently provides helpers around the runtime-facing parts of that lif - `verify_guard_and_record_replay()`, a high-level adapter that verifies the SCLite guarded-strict profile and then records replay freshness for one runtime-consumable decision; -- review-bundle verdict mapping through SCLite `0.8.0b2` review and guarded-strict surfaces, preserving the review-bundle contract. +- review-bundle verdict mapping through the current `sclite-core>=1.0.1,<1.1` review and guarded-strict surfaces, preserving the review-bundle contract. Host-owned artifact projection is outside GovEngine. A runtime such as Ravenclaw constructs its domain-shaped lifecycle artifacts before consuming diff --git a/scripts/validate_public_truth.py b/scripts/validate_public_truth.py index 3593357..21240e7 100644 --- a/scripts/validate_public_truth.py +++ b/scripts/validate_public_truth.py @@ -97,6 +97,144 @@ ), } +VERSION_TRUTH_FIELDS = { + 'source_version': 'pyproject.toml:project.version', + 'package_init_version': 'govengine.__init__.__version__', + 'published_pypi_version': 'PyPI install pin / README badge', + 'release_label': 'README.md / PUBLIC_STATUS.md release label', + 'sclite_dependency': 'pyproject.toml dependencies[sclite-core]', + 'changelog_unreleased_heading': 'CHANGELOG.md:## Unreleased', +} + +GOVERNED_RUNTIME_UNRELEASED_MARKERS = ( + 'RuntimeAdmissionResult', + 'compose_runtime_admission_result()', + 'validate_evidence_review_chain()', +) + +SOURCE_PYPI_GAP_DOC_MARKERS = { + 'README.md': ( + 'Unreleased', + 'last PyPI publish remains', + '`compose_runtime_admission_result()` composes host-supplied gate summaries', + ), + 'docs/ROADMAP.md': ( + 'implemented on current `main`', + 'Unreleased', + ), +} + +FORBIDDEN_CURRENT_DOC_CLAIMS = ( + ('CHANGELOG.md', 'unreleased_api_name', 'verify_evidence_review_chain()'), + ('docs/SCLITE_INTEGRATION.md', 'retired_helper_claim', 'action validation and compilation'), + ('docs/SCLITE_INTEGRATION.md', 'stale_sclite_version', '0.8.0b2'), + ('docs/SCLITE_INTEGRATION.md', 'stale_policy_helper_claim', 'policy decision normalization/evaluation'), + ('docs/RUNTIME_ADMISSION.md', 'future_inspect_claim', 'future read-only'), + ('docs/RUNTIME_ADMISSION.md', 'future_implementation_tense', 'The implementation should expose'), + ('docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md', 'stale_plan_claim', 'GE-035 should implement'), + ('docs/ROADMAP.md', 'stale_mvp_direction_claim', 'need focused negative'), +) + +README_MVP_DOC_LINK_MARKERS = ( + 'docs/API_STABILITY_MATRIX.md', + 'docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md', + 'docs/GUARDED_FRESH_RUNTIME_ADMISSION_EXAMPLE.md', + 'docs/LOCAL_SUBPROCESS_RUNNER_DECISION.md', +) + +MVP_DELIVERY_DOC_MARKERS = { + 'docs/RUNTIME_ADMISSION.md': ( + 'Delivered on current `main`', + 'scripts/inspect_runtime_admission.py', + 'The implementation exposes a small immutable record', + ), + 'docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md': ( + 'The inspect-only surface is implemented as:', + 'scripts/inspect_runtime_admission.py', + ), +} + + +def _changelog_unreleased_section(changelog: str) -> str: + if '## Unreleased' not in changelog: + return '' + start = changelog.index('## Unreleased') + len('## Unreleased') + tail = changelog[start:] + match = re.search(r'\n## \d', tail) + if match: + return tail[: match.start()] + return tail + + +def _changelog_has_unreleased_governed_runtime_mvp(changelog: str) -> bool: + section = _changelog_unreleased_section(changelog) + return all(marker in section for marker in GOVERNED_RUNTIME_UNRELEASED_MARKERS) + + +def _assert_source_pypi_gap_docs( + version: str, + readme: str, + public_status: str, + roadmap: str, + changelog: str, +) -> None: + if version != PUBLISHED_VERSION: + return + if not _changelog_has_unreleased_governed_runtime_mvp(changelog): + return + for path, markers in SOURCE_PYPI_GAP_DOC_MARKERS.items(): + text = {'README.md': readme, 'docs/ROADMAP.md': roadmap}[path] + for marker in markers: + _assert_contains(path, text, marker) + _assert_contains( + 'PUBLIC_STATUS.md', + public_status, + f'Source/package version: `{version}`.', + ) + _assert_contains( + 'PUBLIC_STATUS.md', + public_status, + f'PyPI package: `govengine=={PUBLISHED_VERSION}` is the published current alpha package.', + ) + _assert_contains('README.md', readme, f'python -m pip install govengine=={PUBLISHED_VERSION}') + + +def _assert_changelog_unreleased_api_names(changelog: str) -> None: + section = _changelog_unreleased_section(changelog) + if not section: + return + if 'verify_evidence_review_chain()' in section: + raise AssertionError('CHANGELOG.md:unreleased_stale_api_name:verify_evidence_review_chain') + if _changelog_has_unreleased_governed_runtime_mvp(changelog): + _assert_contains('CHANGELOG.md', section, 'validate_evidence_review_chain()') + + +def _assert_forbidden_current_doc_claims(docs: Mapping[str, str]) -> None: + for path, field, forbidden in FORBIDDEN_CURRENT_DOC_CLAIMS: + text = docs[path] + if forbidden in text: + raise AssertionError(f'{path}:forbidden_current_claim:{field}:{forbidden}') + + +def _assert_sclite_integration_current_dependency_truth( + sclite_integration: str, + dependency: str, +) -> None: + _assert_contains('docs/SCLITE_INTEGRATION.md', sclite_integration, dependency) + _assert_contains('docs/SCLITE_INTEGRATION.md', sclite_integration, 'approved-spec and execution-ticket validation helpers') + + +def _assert_readme_mvp_doc_links(readme: str) -> None: + for marker in README_MVP_DOC_LINK_MARKERS: + _assert_contains('README.md', readme, marker) + + +def _assert_mvp_delivery_doc_truth(markers: Mapping[str, Iterable[str]] = MVP_DELIVERY_DOC_MARKERS) -> None: + for path, expected_markers in markers.items(): + text = _read(path) + for marker in expected_markers: + _assert_contains(path, text, marker) + def _read(path: str) -> str: return (ROOT / path).read_text(encoding='utf-8') @@ -297,6 +435,19 @@ def main() -> int: _assert_validation_current_gate_precedes_history(validation, version) _assert_clean_pip_check_guidance(contributing, validation, publishing) _assert_mvp_surface_docs() + changelog = _read('CHANGELOG.md') + _assert_changelog_unreleased_api_names(changelog) + _assert_source_pypi_gap_docs(version, readme, public_status, roadmap, changelog) + _assert_forbidden_current_doc_claims({ + 'CHANGELOG.md': changelog, + 'docs/SCLITE_INTEGRATION.md': sclite_integration, + 'docs/RUNTIME_ADMISSION.md': _read('docs/RUNTIME_ADMISSION.md'), + 'docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md': _read('docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md'), + 'docs/ROADMAP.md': roadmap, + }) + _assert_sclite_integration_current_dependency_truth(sclite_integration, dependency) + _assert_readme_mvp_doc_links(readme) + _assert_mvp_delivery_doc_truth() _assert_no_published_line_candidate_drift(( 'README.md', 'CONTRIBUTING.md', diff --git a/tests/test_public_truth_consistency.py b/tests/test_public_truth_consistency.py index 07c71e7..e3f499e 100644 --- a/tests/test_public_truth_consistency.py +++ b/tests/test_public_truth_consistency.py @@ -159,3 +159,71 @@ def fake_read(path: str) -> str: 'Intent is not execution authority.', ), }) + + +def test_public_truth_version_doc_truth_negative_guards() -> None: + """Positive version/doc truth is covered by test_public_truth_validator_passes.""" + validator = _load_validator() + + assert set(validator.VERSION_TRUTH_FIELDS) == { + 'source_version', + 'package_init_version', + 'published_pypi_version', + 'release_label', + 'sclite_dependency', + 'changelog_unreleased_heading', + } + assert {field for _, field, _ in validator.FORBIDDEN_CURRENT_DOC_CLAIMS} == { + 'unreleased_api_name', + 'retired_helper_claim', + 'stale_sclite_version', + 'stale_policy_helper_claim', + 'future_inspect_claim', + 'future_implementation_tense', + 'stale_plan_claim', + 'stale_mvp_direction_claim', + } + + unreleased_mvp_changelog = ( + '## Unreleased\n' + '- Added `RuntimeAdmissionResult` and `compose_runtime_admission_result()`.\n' + '- Added `validate_evidence_review_chain()`.\n' + '## 0.12.2-alpha\n' + ) + negative_cases = ( + ( + 'missing_source_pypi_gap_readme', + 'README.md:missing:Unreleased', + lambda: validator._assert_source_pypi_gap_docs( + validator.PUBLISHED_VERSION, + 'alpha package 0.12.2a0 without the gap note', + validator._read('PUBLIC_STATUS.md'), + validator._read('docs/ROADMAP.md'), + unreleased_mvp_changelog, + ), + ), + ( + 'stale_unreleased_api_name', + 'CHANGELOG.md:unreleased_stale_api_name:verify_evidence_review_chain', + lambda: validator._assert_changelog_unreleased_api_names( + '## Unreleased\n' + '- Added `verify_evidence_review_chain()`.\n' + '## 0.12.2-alpha\n' + ), + ), + ( + 'stale_sclite_integration_version', + 'docs/SCLITE_INTEGRATION.md:forbidden_current_claim:stale_sclite_version:0.8.0b2', + lambda: validator._assert_forbidden_current_doc_claims({ + 'CHANGELOG.md': validator._read('CHANGELOG.md'), + 'docs/SCLITE_INTEGRATION.md': 'review-bundle mapping through SCLite `0.8.0b2`', + 'docs/RUNTIME_ADMISSION.md': validator._read('docs/RUNTIME_ADMISSION.md'), + 'docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md': validator._read('docs/INSPECT_ONLY_ADMISSION_WORKFLOW.md'), + 'docs/ROADMAP.md': validator._read('docs/ROADMAP.md'), + }), + ), + ) + + for case_id, matcher, invoke in negative_cases: + with pytest.raises(AssertionError, match=matcher): + invoke()