From 3cbd629cd8454a82533e05670099307ca7258788 Mon Sep 17 00:00:00 2001 From: Steven Ciraolo Date: Tue, 8 Sep 2026 09:17:55 -0700 Subject: [PATCH 1/3] example cleanup --- .../legacy-archive/pre-cutover-capture.json | 89 ------------------- 1 file changed, 89 deletions(-) delete mode 100644 examples/legacy-archive/pre-cutover-capture.json diff --git a/examples/legacy-archive/pre-cutover-capture.json b/examples/legacy-archive/pre-cutover-capture.json deleted file mode 100644 index 500310f..0000000 --- a/examples/legacy-archive/pre-cutover-capture.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "capture_kind": "pre-cutover-implementation", - "source_commit": "721c37facac64f12a164e510c9a0aa647a960cba", - "production_event_count_at_cutover": 0, - "events": [ - { - "actor": "capture-operator", - "created_at": "2026-08-11T19:07:04.074003Z", - "declaration_version": 1, - "event": "motion_proposed", - "event_schema": "paa-autonomy-event/0.1.0-draft", - "evidence_ref": "evidence/paa/76ea9113c9000f7fab53d48dd9149b101686cceda42798f84469a050f1294315/evidence.json", - "evidence_sha256": "76ea9113c9000f7fab53d48dd9149b101686cceda42798f84469a050f1294315", - "from_position": "hitl", - "id": "c8b91619-3f1f-4553-bcf4-3e9fa6838545", - "motion_id": "36205f56-87a5-4343-9d2c-44308b23a1a8", - "reason": "captured pre-cutover promotion", - "scope": "publish:bluesky", - "task": "outbound_content_publish", - "to_position": "hotl" - }, - { - "actor": "capture-operator", - "created_at": "2026-08-11T19:07:04.083086Z", - "declaration_version": 1, - "event": "motion_approved", - "event_schema": "paa-autonomy-event/0.1.0-draft", - "evidence_ref": "evidence/paa/76ea9113c9000f7fab53d48dd9149b101686cceda42798f84469a050f1294315/evidence.json", - "evidence_sha256": "76ea9113c9000f7fab53d48dd9149b101686cceda42798f84469a050f1294315", - "from_position": "hitl", - "id": "9384e9eb-1511-4059-937e-04c62b98d415", - "motion_id": "36205f56-87a5-4343-9d2c-44308b23a1a8", - "reason": "approved captured pre-cutover promotion", - "scope": "publish:bluesky", - "task": "outbound_content_publish", - "to_position": "hotl" - }, - { - "actor": "capture-operator", - "created_at": "2026-08-11T19:07:04.083179Z", - "declaration_version": 1, - "event": "position_changed", - "event_schema": "paa-autonomy-event/0.1.0-draft", - "evidence_ref": "evidence/paa/76ea9113c9000f7fab53d48dd9149b101686cceda42798f84469a050f1294315/evidence.json", - "evidence_sha256": "76ea9113c9000f7fab53d48dd9149b101686cceda42798f84469a050f1294315", - "from_position": "hitl", - "id": "1393f60e-3560-4000-ba51-ec92161db0bb", - "motion_id": "36205f56-87a5-4343-9d2c-44308b23a1a8", - "reason": "approved captured pre-cutover promotion", - "scope": "publish:bluesky", - "task": "outbound_content_publish", - "to_position": "hotl" - } - ], - "positions": [ - { - "current_position": "hotl", - "declaration_version": 1, - "deployment": "active", - "initial_position": "hitl", - "latest_position_event_id": "1393f60e-3560-4000-ba51-ec92161db0bb", - "scope": "publish:bluesky", - "task": "outbound_content_publish" - } - ], - "motions": [ - { - "approved_at": "2026-08-11T19:07:04.083086Z", - "approved_by": "capture-operator", - "approved_reason": "approved captured pre-cutover promotion", - "declaration_version": 1, - "evidence_ref": "evidence/paa/76ea9113c9000f7fab53d48dd9149b101686cceda42798f84469a050f1294315/evidence.json", - "evidence_sha256": "76ea9113c9000f7fab53d48dd9149b101686cceda42798f84469a050f1294315", - "executed_at": "2026-08-11T19:07:04.083179Z", - "from_position": "hitl", - "motion_id": "36205f56-87a5-4343-9d2c-44308b23a1a8", - "proposed_at": "2026-08-11T19:07:04.074003Z", - "proposed_by": "capture-operator", - "proposed_reason": "captured pre-cutover promotion", - "rejected_at": null, - "rejected_by": null, - "rejected_reason": null, - "scope": "publish:bluesky", - "status": "executed", - "task": "outbound_content_publish", - "to_position": "hotl" - } - ] -} From 1c683afa69f5c860355484d7bada54c6f7faa7a0 Mon Sep 17 00:00:00 2001 From: Steven Ciraolo Date: Tue, 8 Sep 2026 09:26:31 -0700 Subject: [PATCH 2/3] Finish removing the legacy archive, and stop CI double-running The capture deleted in the previous commit was still wired into four code paths, so its removal broke the build rather than completing it. paa_contracts lists every corpus directory in _REQUIRED_ROOTS and refuses to import over a partial set, which took down the conformance suite, the contract package tests and the wheel verification together. Drop the directory from _REQUIRED_ROOTS, the build hook, the wheel verifier and the roots test, and delete the conformance test that replayed it. The docs claimed more for that artifact than it could carry. It was generated from the same author's earlier implementation, and recorded production_event_count_at_cutover: 0 -- a cross-implementation continuity proof where both implementations are ours and the source system never ran a transition. PAA.md and README.md no longer cite it, and the scope-of-claim sentence drops the clause it was supporting. import_events stays. It is exported from paa_runtime and the replayed capture was its only test, so removing the fixture would have shipped a public API with no coverage. tests/test_replay.py now covers it directly -- fields preserved verbatim, one atomic transaction, empty input a no-op -- on events it builds itself, since what is worth pinning is that the fields survive the call and not that a particular file exists. Also scope the push trigger to main. Unfiltered, it fired alongside pull_request on every branch push, building each PR commit twice. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KF3srA4ZPg86FbETY3ZA2E --- .github/workflows/ci.yml | 5 ++ PAA.md | 18 +--- README.md | 5 +- conformance/test_legacy_archive.py | 50 ----------- examples/refund_quickstart/README.md | 3 +- packages/paa-contracts/hatch_build.py | 1 - .../scripts/verify_built_wheel.py | 1 - .../src/paa_contracts/__init__.py | 1 - .../paa-contracts/tests/test_contracts.py | 1 - tests/test_replay.py | 90 +++++++++++++++++++ 10 files changed, 100 insertions(+), 75 deletions(-) delete mode 100644 conformance/test_legacy_archive.py create mode 100644 tests/test_replay.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 492412b..165e31e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,12 @@ name: CI on: pull_request: + # Restricted to main so a branch with an open PR is not built twice for the + # same commit: an unfiltered `push` fires alongside `pull_request` on every + # branch push. Keeping main here still covers the post-merge commit and any + # push that lands without a PR. push: + branches: [main] permissions: contents: read diff --git a/PAA.md b/PAA.md index 686fbfe..be10cfc 100644 --- a/PAA.md +++ b/PAA.md @@ -19,8 +19,8 @@ where the implementation deliberately stops. | `SqliteEventStore` | Evidence/event log substrate | Supplies the default append-only SQLite store, including storage-level update/delete rejection. | | `store_evidence`, `verify_evidence` | Evidence record binding | Content-addresses exact evidence bytes with SHA-256 and fails closed on missing or changed bytes. | | `OperatingRecord`, `SqliteOperatingRecordStore` | Operating accounting | Stores usage, prices and provenance separately from evidence files and autonomy events, retrievable by subject; no transition rule reads it. | -| `import_events` | Archive replay | Imports already validated contract-shaped events without regenerating identifiers or timestamps. The legacy conformance capture proves field and projection continuity across extraction. | -| `paa-contracts` conformance suite | Published contract | Checks schema vocabulary, declarations, event histories, evidence addressing, invalid semantic cases, and the pre-cutover capture against the same packaged corpus. | +| `import_events` | Archive replay | Imports already validated contract-shaped events without regenerating identifiers or timestamps. | +| `paa-contracts` conformance suite | Published contract | Checks schema vocabulary, declarations, event histories, evidence addressing, and invalid semantic cases against the same packaged corpus. | ## Lifecycle coverage @@ -45,17 +45,6 @@ field loss, that the lifecycle can produce them, that content addresses are re-derived from bytes, and that runtime-owned negative cases fail for the published reason. -`examples/legacy-archive/pre-cutover-capture.json` adds the consumer-boundary -proof. It was generated with the source consumer's pre-cutover lifecycle implementation at -commit `721c37facac64f12a164e510c9a0aa647a960cba`, then imported into the -extracted runtime. The test reproduces its event rows, motion projection, and -resolved position exactly. - -The source consumer's production `autonomy_events` table contained zero rows at cutover. The -capture is therefore evidence from the real pre-cutover implementation, not a -claim that a production autonomy transition occurred. Keeping that distinction -in the artifact is part of the citation bar. - ## Honest non-matches ### Evaluator verdict production @@ -127,8 +116,7 @@ cross-document corpus validation remains in the published conformance tooling. The accurate claim is: > `paa-runtime` implements PAA's declared autonomy-transition lifecycle and -> passes the published conformance corpus, including replay of a history -> captured from the source consumer's pre-cutover implementation. +> passes the published conformance corpus. It is not a claim that the runtime implements evaluation, worker attestation, or every consumer's governed effect. diff --git a/README.md b/README.md index ab4a44e..a15a006 100644 --- a/README.md +++ b/README.md @@ -113,10 +113,7 @@ uv run mypy src/paa_runtime ``` The implementation-to-spec mapping, including explicit non-matches, is in -[`PAA.md`](PAA.md). The conformance corpus also includes a contract-shaped -history captured from the source consumer's pre-cutover implementation. Its production database had -zero autonomy events at cutover, so that artifact is deliberately labeled an -implementation capture rather than production transition history. +[`PAA.md`](PAA.md). ### Conformance diff --git a/conformance/test_legacy_archive.py b/conformance/test_legacy_archive.py deleted file mode 100644 index 9d55b9d..0000000 --- a/conformance/test_legacy_archive.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Replay a history captured from a consumer's pre-cutover implementation.""" - -from __future__ import annotations - -import json -from typing import Any - -import paa_contracts as contracts -from jsonschema import Draft202012Validator, FormatChecker - -from paa_runtime import RuntimeConfig, SqliteEventStore, list_motions, show -from paa_runtime.replay import import_events - - -def test_legacy_pre_cutover_capture_reproduces_its_projections( - runtime_config: RuntimeConfig, -) -> None: - archive_path = contracts.EXAMPLES_ROOT / "legacy-archive" / "pre-cutover-capture.json" - archive: dict[str, Any] = json.loads(archive_path.read_text(encoding="utf-8")) - - assert archive["capture_kind"] == "pre-cutover-implementation" - assert archive["production_event_count_at_cutover"] == 0 - - validator = Draft202012Validator( - contracts.load_schema("paa-autonomy-event"), format_checker=FormatChecker(), - ) - for event in archive["events"]: - validator.validate(event) - - store = SqliteEventStore(runtime_config.db_path) - try: - import_events(store, archive["events"]) - - stored = [event.to_json_dict() for event in store.get_autonomy_events()] - assert stored == archive["events"] - - positions = [] - for expected in archive["positions"]: - actual = show( - store, runtime_config, - task=expected["task"], scope=expected["scope"], - ) - latest = actual.pop("latest_position_event") - actual["latest_position_event_id"] = latest["id"] if latest else None - positions.append(actual) - assert positions == archive["positions"] - - assert [motion.to_json_dict() for motion in list_motions(store)] == archive["motions"] - finally: - store.close() diff --git a/examples/refund_quickstart/README.md b/examples/refund_quickstart/README.md index c40130a..0beb95e 100644 --- a/examples/refund_quickstart/README.md +++ b/examples/refund_quickstart/README.md @@ -16,5 +16,4 @@ The script creates an isolated temporary database and evidence tree, proposes repository. `refund_approval.v1.yaml` remains in the shared contract corpus so the quickstart, conformance suite, and paa.dev schema reference use one fixture. -This is adoption-oriented synthetic pedagogy. The separately labeled -pre-cutover capture is the cross-implementation evidence artifact. +The fixtures here are synthetic teaching examples, not captured history. diff --git a/packages/paa-contracts/hatch_build.py b/packages/paa-contracts/hatch_build.py index 355aa55..36daecd 100644 --- a/packages/paa-contracts/hatch_build.py +++ b/packages/paa-contracts/hatch_build.py @@ -35,7 +35,6 @@ ("schemas", "schemas"), ("examples/paa-tasks", "examples/paa-tasks"), ("examples/runtime-conformance", "examples/runtime-conformance"), - ("examples/legacy-archive", "examples/legacy-archive"), ) diff --git a/packages/paa-contracts/scripts/verify_built_wheel.py b/packages/paa-contracts/scripts/verify_built_wheel.py index fef7524..99a5d98 100644 --- a/packages/paa-contracts/scripts/verify_built_wheel.py +++ b/packages/paa-contracts/scripts/verify_built_wheel.py @@ -29,7 +29,6 @@ "schemas", "examples/paa-tasks", "examples/runtime-conformance", - "examples/legacy-archive", ) _DATA_MARKER = "/_data/" diff --git a/packages/paa-contracts/src/paa_contracts/__init__.py b/packages/paa-contracts/src/paa_contracts/__init__.py index 2a4bd48..4eb99b7 100644 --- a/packages/paa-contracts/src/paa_contracts/__init__.py +++ b/packages/paa-contracts/src/paa_contracts/__init__.py @@ -137,7 +137,6 @@ class InvalidCase(TypedDict): "schemas", "examples/paa-tasks", "examples/runtime-conformance", - "examples/legacy-archive", ) diff --git a/packages/paa-contracts/tests/test_contracts.py b/packages/paa-contracts/tests/test_contracts.py index b13270a..ed8ee1a 100644 --- a/packages/paa-contracts/tests/test_contracts.py +++ b/packages/paa-contracts/tests/test_contracts.py @@ -31,7 +31,6 @@ def test_resolution_requires_every_artifact_root(self, tmp_path: Path) -> None: assert contracts._missing_roots(tmp_path) == ( "examples/paa-tasks", "examples/runtime-conformance", - "examples/legacy-archive", ) def test_the_resolved_root_is_complete(self) -> None: diff --git a/tests/test_replay.py b/tests/test_replay.py new file mode 100644 index 0000000..0fc1b7e --- /dev/null +++ b/tests/test_replay.py @@ -0,0 +1,90 @@ +"""Tests for import_events, the archive-replay boundary.""" + +from __future__ import annotations + +import sqlite3 +from pathlib import Path +from typing import Any + +import pytest + +from paa_runtime.events import CURRENT_EVENT_SCHEMA +from paa_runtime.replay import import_events +from paa_runtime.sqlite_store import SqliteEventStore + +_TASK = "outbound_content_publish" +_SCOPE = "publish:bluesky" +_SHA = "b" * 64 +_EVIDENCE_REF = f"evidence/paa/{_SHA}/evidence.json" + + +def _make_store(tmp_path: Path) -> SqliteEventStore: + return SqliteEventStore(tmp_path / "autonomy_events.db") + + +def _event(**overrides: Any) -> dict[str, Any]: + """One contract-shaped event, in the exported JSON field names. + + Deliberately built here rather than read from a fixture: import_events + takes whatever a validated archive contains, so the thing worth pinning + is that the fields survive the call, not that some particular file exists. + """ + event: dict[str, Any] = { + "id": "11111111-1111-4111-8111-111111111111", + "motion_id": "22222222-2222-4222-8222-222222222222", + "task": _TASK, + "declaration_version": 1, + "scope": _SCOPE, + "event": "motion_proposed", + "from_position": "hitl", + "to_position": "hotl", + "evidence_ref": _EVIDENCE_REF, + "evidence_sha256": _SHA, + "actor": "capture-operator", + "reason": "imported", + "created_at": "2026-08-11T19:07:04.074003Z", + "event_schema": CURRENT_EVENT_SCHEMA, + } + event.update(overrides) + return event + + +class TestImportEvents: + def test_preserves_every_field_verbatim(self, tmp_path: Path) -> None: + # The point of the function: an archive's identifiers and timestamps + # are the record. Regenerating either would silently rewrite history + # that another implementation already published. + store = _make_store(tmp_path) + events = [ + _event(), + _event( + id="33333333-3333-4333-8333-333333333333", + event="motion_approved", + created_at="2026-08-11T19:07:04.083086Z", + ), + ] + try: + import_events(store, events) + assert [e.to_json_dict() for e in store.get_autonomy_events()] == events + finally: + store.close() + + def test_imports_atomically(self, tmp_path: Path) -> None: + # One transaction, so a bad row late in an archive cannot leave a + # partial history behind for the next run to append onto. + store = _make_store(tmp_path) + duplicate = [_event(), _event(reason="same id as the first")] + try: + with pytest.raises(sqlite3.IntegrityError): + import_events(store, duplicate) + assert store.get_autonomy_events() == [] + finally: + store.close() + + def test_importing_nothing_is_a_no_op(self, tmp_path: Path) -> None: + store = _make_store(tmp_path) + try: + import_events(store, []) + assert store.get_autonomy_events() == [] + finally: + store.close() From 5117269c966f785a6e48169693d34ebdffada911 Mon Sep 17 00:00:00 2001 From: Steven Ciraolo Date: Tue, 8 Sep 2026 09:30:51 -0700 Subject: [PATCH 3/3] Stop the docs congratulating themselves One move, repeated: state a fact, then add a clause about how disciplined it was to state it. "Honest non-matches", "required, not decoration", "the trade-off is named rather than hidden", "that is the part worth stating", "the one failure mode it must not have", "the ratchet that keeps this honest", "implementation-neutrality made mechanical instead of asserted". Each one asks the reader to admire the rigor instead of just showing it. Every fact survives; only the self-assessment is gone. "Scope of the claim" loses the pull-quote staging and says the same thing in prose. The conftest docstring drops a paragraph narrating its own development history, which no reader of the fixture needs. Left alone: the "deliberately"/"on purpose" uses that carry real information -- a deliberately tampered fixture, stages deliberately not checked, structural rules deliberately absent. Those distinguish intent from accident, which is worth saying in a spec. Docstrings under src/paa_runtime were already dry and are untouched. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KF3srA4ZPg86FbETY3ZA2E --- PAA.md | 12 +++---- README.md | 9 +++-- conformance/__init__.py | 12 +++---- conformance/conftest.py | 15 ++++---- conformance/test_corpus_integrity.py | 4 +-- packages/paa-contracts/README.md | 8 ++--- .../src/paa_contracts/__init__.py | 36 +++++++++---------- 7 files changed, 43 insertions(+), 53 deletions(-) diff --git a/PAA.md b/PAA.md index be10cfc..bdc96fc 100644 --- a/PAA.md +++ b/PAA.md @@ -45,7 +45,7 @@ field loss, that the lifecycle can produce them, that content addresses are re-derived from bytes, and that runtime-owned negative cases fail for the published reason. -## Honest non-matches +## Non-matches ### Evaluator verdict production @@ -113,10 +113,6 @@ cross-document corpus validation remains in the published conformance tooling. ## Scope of the claim -The accurate claim is: - -> `paa-runtime` implements PAA's declared autonomy-transition lifecycle and -> passes the published conformance corpus. - -It is not a claim that the runtime implements evaluation, worker attestation, -or every consumer's governed effect. +`paa-runtime` implements PAA's declared autonomy-transition lifecycle and +passes the published conformance corpus. It does not implement evaluation, +worker attestation, or any consumer's governed effect. diff --git a/README.md b/README.md index a15a006..62dfc3e 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ For a complete disposable propose → approve → demote walk, run the `SqliteEventStore` is the default and most consumers should use it. `EventStore` is a protocol so that a consumer whose governed effect and the position read authorizing it must commit in a *single lock domain* can implement it over its own connection — a runtime-owned database cannot offer that guarantee across process boundaries. It is one insert, four reads, and two transaction context managers. -The trade-off is named rather than hidden. With the default store, a consumer that resolves a position and then performs the effect it authorizes does so across two lock domains: a demotion committed in between is not seen by the effect already in flight. That window is small and the failure is a stale *permit*, not a corrupt history — but it is real, and a consumer for which it is unacceptable implements `EventStore` over the same connection its effect commits on. +With the default store, a consumer that resolves a position and then performs the effect it authorizes does so across two lock domains: a demotion committed in between is not seen by the effect already in flight. That window is small and the failure is a stale *permit*, not a corrupt history — but it is real, and a consumer for which it is unacceptable implements `EventStore` over the same connection its effect commits on. ## Development @@ -130,13 +130,12 @@ uv sync --extra conformance uv run pytest conformance ``` -The `conformance` path is required, not decoration. `testpaths` is `tests`, so +The `conformance` path is required. `testpaths` is `tests`, so a bare `uv run pytest` runs the unit suite and nothing else — which is what lets the unit suite stay green for someone who cloned only this repo. The conformance suite is opt-in by *invocation* rather than by skip marker: when it is asked to run and the artifacts are absent, `paa_contracts` raises at import -and the run fails loudly, because a conformance suite reporting green over an -empty corpus is the one failure mode it must not have. +and the run fails loudly rather than reporting green over an empty corpus. What it asserts, per fixture class: @@ -165,7 +164,7 @@ package has no import path to point them at — it governs motions it writes itself, enforcing those rules at write time rather than by inspecting a finished document. -One honest non-match: the published demotion history binds to a +One non-match: the published demotion history binds to a `paa-decision-artifact`, while `demote` generates and content-addresses its own evidence so an emergency demotion never blocks on an operator producing an artifact first. Its event stream reproduces the published one in every field diff --git a/conformance/__init__.py b/conformance/__init__.py index 48d4186..e00bc7d 100644 --- a/conformance/__init__.py +++ b/conformance/__init__.py @@ -2,9 +2,8 @@ Every fixture, schema, and negative case here comes from ``paa-contracts`` — the artifacts paa.dev publishes — rather than from fixtures of this -repo's own. That is the entire point: "passes the published conformance -suite" has to be a claim about the contract, not about this repo's idea -of it. +repo's own, so that "passes the published conformance suite" is a claim +about the contract, not about this repo's idea of it. This package is deliberately not collected by ``uv run pytest``. The unit suite under ``tests/`` must pass for someone who cloned only this repo, @@ -15,8 +14,7 @@ uv run --with ../paadotdev/packages/paa-contracts pytest conformance Opt-in by *invocation*, never by skip marker. A suite that skips when its -fixtures are missing reports green over an empty corpus, which is the one -failure mode a conformance suite must not have — so when these modules -are asked to run and the artifacts are absent, ``paa_contracts`` raises -at import and the run fails loudly. +fixtures are missing reports green over an empty corpus, so when these +modules are asked to run and the artifacts are absent, ``paa_contracts`` +raises at import and the run fails loudly. """ diff --git a/conformance/conftest.py b/conformance/conftest.py index 37c231d..a048829 100644 --- a/conformance/conftest.py +++ b/conformance/conftest.py @@ -18,10 +18,9 @@ def pytest_report_header() -> list[str]: Packaged data and a site checkout are the same bytes from the same commit, so which one is active never changes an answer — but it - changes what a failure *means*, which is why it belongs in the run - output. A header prints unconditionally; a fixture only reports when - something requests it, which is how this started life and why nothing - ever saw it. + changes what a failure *means*, so it belongs in the run output. A + header prints unconditionally, unlike a fixture, which reports only + when something requests it. """ return [ f"paa-contracts: {contracts.__version__} ({contracts.DATA_SOURCE})", @@ -54,10 +53,10 @@ def build_registry(documents: Sequence[dict[str, Any]]) -> tuple[ProducerRegistr The registry is consumer domain data the runtime deliberately does not own, so a conformance run has to supply one. Deriving it from the - corpus is the honest choice: it makes the claim "given a registry that - registers what these declarations reference, the loader accepts them", - which is the claim an implementation can actually make about fixtures - whose producers live in somebody else's codebase. + corpus makes the claim "given a registry that registers what these + declarations reference, the loader accepts them", which is the claim + an implementation can actually make about fixtures whose producers + live in somebody else's codebase. Every entry is registered ``implemented``. The implemented/future split governs whether a consumer has built the producer yet — a fact diff --git a/conformance/test_corpus_integrity.py b/conformance/test_corpus_integrity.py index 392372a..8f237ce 100644 --- a/conformance/test_corpus_integrity.py +++ b/conformance/test_corpus_integrity.py @@ -175,8 +175,8 @@ class TestUnclaimedStages: identity drift, illegal motion ordering — which is real logic, not a schema pass, and it does not exist in Python yet. - The ratchet that keeps this honest: the JS validator covering these - is not deleted until they are claimed here. Counts pinned so the gap + The ratchet: the JS validator covering these is not deleted until + they are claimed here. Counts pinned so the gap stays measured. """ diff --git a/packages/paa-contracts/README.md b/packages/paa-contracts/README.md index 949d521..ea560be 100644 --- a/packages/paa-contracts/README.md +++ b/packages/paa-contracts/README.md @@ -2,7 +2,7 @@ The published contract artifacts of the [Progressive Autonomy Architecture](https://www.paa.dev): five normative JSON Schemas, the positive fixture corpus every implementation is checked against, and the table-driven invalid-case matrices. -No runtime logic, no dependencies. This package is data and honest paths to it. +No runtime logic, no dependencies. This package is data, plus paths to it. ## Why it exists @@ -63,7 +63,7 @@ for case in contracts.invalid_cases("event", stage="event_semantic"): ## The stage split -Every invalid case declares `expected.stage`, and that field is an ownership boundary rather than a label: +Every invalid case declares `expected.stage`, an ownership boundary: | Stage | Owner | Why | |---|---|---| @@ -77,9 +77,9 @@ Filter with `invalid_cases(kind, stage=...)` and take only what you own. `case_s The artifacts are not checked into this directory. `hatch_build.py` pulls them from the repo root at build time — that hook is where the inclusion logic lives, not `pyproject.toml`, which only registers it. So the files paa.dev serves at their published URLs, the files the validator reads, and the files a conformance suite loads are the same bytes from the same commit. -Vendoring copies here would put two sources of truth one careless commit apart and make every conformance claim a claim about the copy. Build-time inclusion makes "one source of truth" a property of the build instead of a rule someone has to remember. +Vendoring copies here would put two sources of truth one careless commit apart and make every conformance claim a claim about the copy. Build-time inclusion makes one source of truth a property of the build. -The consequence: a wheel can only be built from a full checkout of this repo. That is correct — a release cut from anything less than the whole tree would be a partial contract. +The consequence: a wheel can only be built from a full checkout of this repo — a release cut from anything less than the whole tree would be a partial contract. ## Contents diff --git a/packages/paa-contracts/src/paa_contracts/__init__.py b/packages/paa-contracts/src/paa_contracts/__init__.py index 4eb99b7..9f396da 100644 --- a/packages/paa-contracts/src/paa_contracts/__init__.py +++ b/packages/paa-contracts/src/paa_contracts/__init__.py @@ -3,14 +3,12 @@ This package is the contract side of the PAA split: the five normative JSON Schemas, the positive fixture corpus every implementation is checked against, and the table-driven invalid-case matrices. It contains no runtime logic and -has no dependencies — it is data, plus honest paths to that data. +has no dependencies — it is data, plus paths to that data. -The dependency direction is the point. An implementation depends on the -contract; the contract never depends on an implementation. ``paa-runtime`` is -the first consumer, a task-schema conformance test is the second, and a -second implementation in any language gets its fixtures the same way the first -one does. That is implementation-neutrality made mechanical instead of -asserted. +An implementation depends on the contract; the contract never depends on an +implementation. ``paa-runtime`` is the first consumer, a task-schema +conformance test is the second, and a second implementation in any language +gets its fixtures the same way the first one does. Nothing here is a copy. The artifacts are pulled from the repo root at build time by hatch_build.py, so the files the site serves at https://www.paa.dev @@ -102,7 +100,7 @@ class SetMutation(TypedDict): class ExpectedFailure(TypedDict): """What the mutated document must be rejected for. - ``stage`` is the ownership boundary, not decoration. ``structural`` cases + ``stage`` is the ownership boundary. ``structural`` cases are Ajv's vocabulary — its error keywords and JSON pointers — and belong to the site's validator. Every other stage is expressed in the runtime's own vocabulary and belongs to an implementation's conformance suite. A @@ -132,7 +130,7 @@ class InvalidCase(TypedDict): #: Every directory that has to be present for a corpus to count as complete. #: Checked in full rather than by sampling one of them: a partial artifact set #: satisfies imports and then returns empty tuples from the accessors, which -#: is the silent-pass failure this module exists to refuse. +#: would let a suite iterate nothing and report success. _REQUIRED_ROOTS: tuple[str, ...] = ( "schemas", "examples/paa-tasks", @@ -157,14 +155,14 @@ def _resolve_data_root() -> tuple[Path, Literal["packaged", "worktree"]]: suite can see it. Both modes resolve to the same bytes from the same commit, so which one is - active never changes an answer — but it changes what a failure means, - which is why it is reported rather than hidden. - - Completeness is required of both, and that is the part worth stating. - Accepting a root because one expected directory is present would let a - wheel missing a fixture tree import cleanly as ``packaged``, after which - every accessor for the missing tree returns ``()`` and a conformance suite - iterates nothing and reports success. The build-time check in + active never changes an answer — but it changes what a failure means, so + it is reported. + + Completeness is required of both. Accepting a root because one expected + directory is present would let a wheel missing a fixture tree import + cleanly as ``packaged``, after which every accessor for the missing tree + returns ``()`` and a conformance suite iterates nothing and reports + success. The build-time check in scripts/verify_built_wheel.py cannot help a consumer who installs such a wheel; this can. """ @@ -241,8 +239,8 @@ def schema_version(schema_id: SchemaId) -> str: Read from the schema rather than mirrored into a constant here. Package version and schema-family versions drift independently on purpose — a - packaging fix should not imply a contract revision — so the only honest - source for a family version is the schema file that declares it. + packaging fix should not imply a contract revision — so the source for a + family version is the schema file that declares it. """ version = load_schema(schema_id).get("x-paa-schema-version") if not isinstance(version, str):