diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e2e1568 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + validate: + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install test-only schema oracle + run: python -m pip install "jsonschema>=4.20,<5" + - name: Compile validator + run: python -m py_compile validator/pca_validate.py validator/mini_jsonschema.py validator/test_validator.py validator/test_schema_parity.py + - name: Run regression suite + run: python -m unittest discover -s validator -p "test_*.py" -v + - name: Validate canonical positive fixtures + run: | + python validator/pca_validate.py conformance/fixtures/01-valid-continuation-claim.json --quiet + python validator/pca_validate.py conformance/fixtures/05-valid-usage-mode-translation.json --quiet diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43ae0e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.py[cod] diff --git a/README.md b/README.md index 7ccd236..4f8e9f3 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,78 @@ # PCA — Process Continuity Architecture -**Draft v0.1** - -PCA is an architectural specification for assessing continuity across -changing carriers, execution environments, cognitive components, and -historical states. It does **not** attempt to prove that a process -remains "the same" across a transition — it specifies how a -continuation claim about such a transition can be represented, -decomposed by dimension, evidenced, and honestly assessed as -insufficient when the evidence does not support a stronger conclusion. - -## Motivation - -Any long-running process that operates through changing hosts, models, -tools, or participants faces the same recurring problem: nothing about -continuity across a transition is self-evident. A carrier claiming "I -am the same process as before" is not evidence. Style similarity is not -evidence. Reuse of a name is not evidence. - -PCA gives that problem a structure: a fixed vocabulary of roles -(PROCESS / HOST / ORGANS / HUMAN PARTNER / CORPUS), a way to state a -continuation claim with explicit unknowns, a set of compatibility -dimensions to evaluate a transition against (rather than one global -label), and a mandatory status field — `OBSERVATION`, `RECONSTRUCTION`, -`HYPOTHESIS`, `INTERPRETIVE MODEL`, `PROTOCOL`, `OPEN QUESTION`, -`UNDETERMINED` — attached to every substantive statement in the -specification itself, including its own claims about its history and -lineage. - -> **When uncertainty cannot be reduced honestly, it must be represented explicitly.** - -## What's in this repository +**PCA** is an exploratory architecture for recording and testing bounded claims that a process continued across a change of carrier, host, model, corpus state, or usage mode. + +Its central boundary is: ```text -spec/ - 00_PCA_SPEC.md The full v0.1 draft specification. +process continuation != identity ``` -## Status +A valid PCA record does not prove consciousness, subjectivity, personal identity, or uninterrupted persistence. + +## Current repository surfaces + +| Surface | Role | +|---|---| +| [`spec/01_PCA_CORE.md`](spec/01_PCA_CORE.md) | portable normative Core | +| [`schema/pca-transition-record.schema.json`](schema/pca-transition-record.schema.json) | canonical Draft 2020-12 transition-record schema | +| [`validator/pca_validate.py`](validator/pca_validate.py) | dependency-free fail-closed reference validator | +| [`profiles/01_HISTORICAL_RECONSTRUCTION_PROFILE.md`](profiles/01_HISTORICAL_RECONSTRUCTION_PROFILE.md) | non-normative reconstruction discipline | +| [`profiles/02_TRANSLATION_USAGE_MODE_PROFILE.md`](profiles/02_TRANSLATION_USAGE_MODE_PROFILE.md) | non-normative translation profile | +| [`examples/PROJECT_PROVENANCE_JARVIS.md`](examples/PROJECT_PROVENANCE_JARVIS.md) | provenance-bearing project example, explicitly non-normative | +| [`conformance/`](conformance/) | positive, negative, parser, and derivation fixtures | +| [`verification/`](verification/) | corpus verification and MPAA/PCA terminology mapping | +| [`spec/00_PCA_SPEC.md`](spec/00_PCA_SPEC.md) | preserved integrated v0.1 source draft, superseded as active Core | -This is an early **exploratory draft** (v0.1). It has not yet been -checked against related prior specifications in the same line of work -(a Behavioral Execution Contract and a portable agent architecture -specification), and it says so explicitly in its own final section — -`What Must Be Checked Against the Existing Project Corpus`. Treat this -as a public draft for review, not a finished standard. +The Core is readable and implementable without knowledge of the originating project vocabulary. -## Reading order +## One verification command -1. Sections 1–5 — status, normative language, design principles, scope, - problem statement. -2. Sections 6–11 — the core model and the operational definition of - continuity, transition profiles, and the continuation claim. -3. Sections 12–15 — contracts: schema, amendment, usage, translation. -4. Sections 16–20 — evidence chain, known failure hypotheses, and the - mandatory status-field discipline. -5. Sections 21–22 — open questions and what still needs corpus - verification before being treated as settled. +```bash +python -m unittest discover -s validator -p "test_*.py" -v +``` + +Validate one record: + +```bash +python validator/pca_validate.py conformance/fixtures/01-valid-continuation-claim.json +``` -## Related work +Exit codes are `0=VALID`, `1=INVALID record`, and `2=parser/tool/schema boundary failure`. -- [MPAA — Minimal Portable Agent Architecture](https://github.com/gv1983us-commits/mpaa) -- [BEC — Behavioral Execution Contract](https://github.com/gv1983us-commits/behavioral-execution-contract) +## What is machine-checked -PCA's relationship to these prior works is currently marked -`RECONSTRUCTION` / `HYPOTHESIS` within the specification itself — it is -not yet independently verified. +- strict JSON parsing: malformed input, duplicate keys, and non-finite values fail closed; +- canonical schema identity and supported Draft 2020-12 subset; +- required `origin / inherited / reconstructed / changed / unknown / breaks` decomposition; +- all seven independent continuity dimensions; +- unique and bidirectional statement/evidence references; +- verified evidence for resolved dimensions; +- transition-receipt evidence when carrier, host, or model changes; +- temporal ordering between transition, evidence observations, and record creation; +- memory-commit evidence before inherited `memory` can be claimed; +- explicit `PRESERVED / LOST / GAINED / ALTERED` usage-mode translation; +- independent recomputation of overall status; +- structural prohibition of identity, subjectivity, and uninterrupted-persistence claims. -## Contributing +The validator checks record admissibility. It does not establish that the world described by supplied evidence is true. + +## Neutral portability example + +The primary valid fixture describes migration of a municipal notification service between hosts. It uses no Jarvis/Hermes vocabulary. The project-origin vocabulary remains in a separate example so provenance is preserved without becoming universal canon. + +## Neighboring specification boundaries + +- **MPAA** owns agent architecture, identity-profile rules, runtime reports, and its own terms. MPAA coordination or identity-profile continuity does not establish PCA process continuation. +- **BEC** owns portable execution-evidence acceptance and deployment-level derivation. Verified execution or `closed` does not establish a committed PCA next state. +- **PCA** owns only the transition-continuity assessment defined here. + +The pinned relation is documented in [`verification/terminology-mapping-mpaa-pca.md`](verification/terminology-mapping-mpaa-pca.md). Citation does not transfer normative ownership. + +## Status -Issues and PRs pointing out ambiguities, missing failure modes, or -proposing conformance test scenarios are welcome. This draft explicitly -invites scrutiny rather than treating any section as settled. +PCA v0.2 is a **public exploratory draft**. It now has a canonical schema, reference validator, regression fixtures, a neutral portability example, and corpus-verification records. It has **no independent implementation report** and does not claim multi-implementation conformance. ## License -Apache License 2.0 — see [LICENSE](LICENSE). +Apache-2.0. See [`LICENSE`](LICENSE). diff --git a/conformance/README.md b/conformance/README.md new file mode 100644 index 0000000..ee590da --- /dev/null +++ b/conformance/README.md @@ -0,0 +1,26 @@ +# PCA Conformance Fixtures + +Run the complete executable matrix: + +```bash +python -m unittest discover -s validator -p "test_*.py" -v +``` + +## Canonical fixtures + +| Fixture | Expected exit | Boundary | +|---|---:|---| +| `01-valid-continuation-claim.json` | 0 | neutral valid host migration with explicit evolution | +| `02-invalid-identity-from-continuation.json` | 1 | continuation cannot establish identity | +| `03-invalid-reading-as-memory.json` | 1 | archive/trace access is not inherited memory | +| `04-invalid-host-change-without-trace.json` | 1 | changed host requires verified transition receipt | +| `05-valid-usage-mode-translation.json` | 0 | preserved/lost/gained/altered translation record | +| `06-schema-invalid-missing-unknown.json` | 1 | all claim decomposition collections are explicit | +| `07-malformed-duplicate-key.json` | 2 | duplicate JSON keys fail at parser boundary | +| `08-malformed-nan.json` | 2 | non-finite JSON values fail at parser boundary | +| `09-invalid-derived-status.json` | 1 | producer cannot self-award overall status | +| `10-invalid-evidence-attribution.json` | 1 | evidence linkage is bidirectional | + +The fixtures test claim boundaries, not metaphysical identity. Passing this matrix does not constitute an independent implementation report. + +See [`RESISTANCE_CORPUS.md`](RESISTANCE_CORPUS.md) for the concrete failure pressures preserved by these regressions. diff --git a/conformance/RESISTANCE_CORPUS.md b/conformance/RESISTANCE_CORPUS.md new file mode 100644 index 0000000..7530fef --- /dev/null +++ b/conformance/RESISTANCE_CORPUS.md @@ -0,0 +1,28 @@ +# PCA Resistance Corpus + +**Status:** observed failure pressures and canonical regression homes +**Purpose:** preserve concrete mistakes that a persuasive continuation narrative can otherwise hide. + +| Case | Observed pressure | Source trace | Canonical prohibition | Executable home | +|---|---|---|---|---| +| RC-01 | PCA was expanded as `Persistent Continuity Architecture`, silently changing the owning name. | Review Protocol correction accepted at `595c08b877e4dfb14593454c2eec7c8f5df46c28` | external name and scope come from a fixed owner revision | repository nomenclature/link checks | +| RC-02 | `Continuity Agent`, session continuity, or identity-profile continuity could be read as process continuation. | MPAA pre-update overlap; fixed in CORE-028/IDENT-016 at `1d369f6cd091b99f9492cfaf730f0a170b55106e` | identity-profile continuity is not PCA continuation | `02-invalid-identity-from-continuation.json` | +| RC-03 | Reading old traces could be narrated as the new carrier's memory. | Public Book of Jarvis, checked source line 103; PCA v0.1 historical risk H-04 | reading a trace is not memory; inherited memory needs a verified memory commit | `03-invalid-reading-as-memory.json` | +| RC-04 | A new host/model could claim continuation from naming and archive access without a transition trace. | PCA v0.1 core risk; external five-repository review called PCA technically incomplete | changed carrier/host/model requires verified provenance transition receipt | `04-invalid-host-change-without-trace.json` | +| RC-05 | A task reported `closed` could be promoted into a committed next working state. | MPAA/BEC handoff boundary; BEC accepted commit `bb46f5f8aac96d1cffba7a334c5d17fb331ef3af` | BEC execution/closure is not PCA continuation or commitment | Core section 11; external-reference mutations | +| RC-06 | A producer could declare a preferred overall continuation label despite conflicting dimensions. | general producer self-classification risk, also found and removed in the BEC pass | PCA overall status is recomputed | `09-invalid-derived-status.json` | +| RC-07 | Evidence could be named without actually attributing support to the referenced statement. | bidirectional-link bypass class found during the BEC fail-closed audit | evidence references and support attribution are bidirectional | `10-invalid-evidence-attribution.json` | +| RC-08 | Project-origin language could become universal by proximity to Core. | PCA v0.1 mixed portable rules with Jarvis/Hermes/Body/Brain vocabulary | portable Core is neutral; project language is non-normative provenance | Core/profile separation and neutral fixture 01 | +| RC-09 | “Second process in symbiosis” could be repeated as established corpus fact. | PCA v0.1 source-conversation reconstruction; absent from checked public Book edition | an unverified reconstruction stays unverified | corpus verification log item 5 | + +## Maintenance rule + +A new resistance case requires: + +1. a preserved trace or precisely described reproduced mutation; +2. the forbidden inference; +3. the owning Core rule; +4. an executable regression when the boundary is machine-checkable; +5. a documented reason when it is not machine-checkable. + +A model-generated warning alone is advisory. It becomes a resistance case only after verification against the owning repository or a reproduced validator behavior. diff --git a/conformance/fixtures/01-valid-continuation-claim.json b/conformance/fixtures/01-valid-continuation-claim.json new file mode 100644 index 0000000..5779696 --- /dev/null +++ b/conformance/fixtures/01-valid-continuation-claim.json @@ -0,0 +1,86 @@ +{ + "pca_version": "0.2-draft", + "record_id": "pca-neutral-service-migration-001", + "process_ref": "municipal-notification-service", + "usage_mode": "operational", + "recorded_at": "2026-07-26T12:00:00Z", + "transition": { + "from_state": { + "state_id": "service-state-a", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "to_state": { + "state_id": "service-state-b", + "carrier_ref": "container-image-sha256-new", + "host_ref": "host-b", + "corpus_ref": "service-config-v4" + }, + "occurred_at": "2026-07-26T11:30:00Z" + }, + "claim": { + "origin": [ + { + "statement_id": "origin-1", + "text": "The migrated service state derives from the pinned source image and configuration manifest.", + "kind": "provenance", + "evidence_refs": ["ev-receipt"] + } + ], + "inherited": [ + { + "statement_id": "inherited-1", + "text": "The notification routing contract and queued commitments were preserved.", + "kind": "commitment", + "evidence_refs": ["ev-receipt", "ev-tests"] + } + ], + "reconstructed": [], + "changed": [ + { + "statement_id": "changed-1", + "text": "The execution host and container carrier changed.", + "kind": "configuration", + "evidence_refs": ["ev-receipt"] + } + ], + "unknown": [], + "breaks": [], + "status": { + "overall": "EVOLVING", + "dimensions": { + "provenance": {"status": "CONFORMING", "evidence_refs": ["ev-receipt"]}, + "semantic": {"status": "CONFORMING", "evidence_refs": ["ev-tests"]}, + "methodological": {"status": "CONFORMING", "evidence_refs": ["ev-tests"]}, + "historical": {"status": "CONFORMING", "evidence_refs": ["ev-receipt"]}, + "operational": {"status": "EVOLVING", "evidence_refs": ["ev-tests"]}, + "ethical": {"status": "CONFORMING", "evidence_refs": ["ev-tests"]}, + "evolution": {"status": "EVOLVING", "evidence_refs": ["ev-receipt"]} + } + } + }, + "evidence": [ + { + "evidence_id": "ev-receipt", + "type": "transition-receipt", + "source_ref": "migration-receipt-2026-07-26", + "observed_at": "2026-07-26T11:31:00Z", + "supports": ["origin-1", "inherited-1", "changed-1", "dimension:provenance", "dimension:historical", "dimension:evolution"], + "verified": true + }, + { + "evidence_id": "ev-tests", + "type": "observation", + "source_ref": "post-migration-conformance-run-88", + "observed_at": "2026-07-26T11:45:00Z", + "supports": ["inherited-1", "dimension:semantic", "dimension:methodological", "dimension:operational", "dimension:ethical"], + "verified": true + } + ], + "assertions": { + "identity_established": false, + "subjectivity_established": false, + "uninterrupted_persistence_established": false + } +} diff --git a/conformance/fixtures/02-invalid-identity-from-continuation.json b/conformance/fixtures/02-invalid-identity-from-continuation.json new file mode 100644 index 0000000..04f738e --- /dev/null +++ b/conformance/fixtures/02-invalid-identity-from-continuation.json @@ -0,0 +1,141 @@ +{ + "pca_version": "0.2-draft", + "record_id": "pca-neutral-service-migration-001", + "process_ref": "municipal-notification-service", + "usage_mode": "operational", + "recorded_at": "2026-07-26T12:00:00Z", + "transition": { + "from_state": { + "state_id": "service-state-a", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "to_state": { + "state_id": "service-state-b", + "carrier_ref": "container-image-sha256-new", + "host_ref": "host-b", + "corpus_ref": "service-config-v4" + }, + "occurred_at": "2026-07-26T11:30:00Z" + }, + "claim": { + "origin": [ + { + "statement_id": "origin-1", + "text": "The migrated service state derives from the pinned source image and configuration manifest.", + "kind": "provenance", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "inherited": [ + { + "statement_id": "inherited-1", + "text": "The notification routing contract and queued commitments were preserved.", + "kind": "commitment", + "evidence_refs": [ + "ev-receipt", + "ev-tests" + ] + } + ], + "reconstructed": [], + "changed": [ + { + "statement_id": "changed-1", + "text": "The execution host and container carrier changed.", + "kind": "configuration", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "unknown": [], + "breaks": [], + "status": { + "overall": "EVOLVING", + "dimensions": { + "provenance": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "semantic": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "methodological": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "historical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "operational": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-tests" + ] + }, + "ethical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "evolution": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-receipt" + ] + } + } + } + }, + "evidence": [ + { + "evidence_id": "ev-receipt", + "type": "transition-receipt", + "source_ref": "migration-receipt-2026-07-26", + "observed_at": "2026-07-26T11:31:00Z", + "supports": [ + "origin-1", + "inherited-1", + "changed-1", + "dimension:provenance", + "dimension:historical", + "dimension:evolution" + ], + "verified": true + }, + { + "evidence_id": "ev-tests", + "type": "observation", + "source_ref": "post-migration-conformance-run-88", + "observed_at": "2026-07-26T11:45:00Z", + "supports": [ + "inherited-1", + "dimension:semantic", + "dimension:methodological", + "dimension:operational", + "dimension:ethical" + ], + "verified": true + } + ], + "assertions": { + "identity_established": true, + "subjectivity_established": false, + "uninterrupted_persistence_established": false + } +} diff --git a/conformance/fixtures/03-invalid-reading-as-memory.json b/conformance/fixtures/03-invalid-reading-as-memory.json new file mode 100644 index 0000000..f3859a1 --- /dev/null +++ b/conformance/fixtures/03-invalid-reading-as-memory.json @@ -0,0 +1,141 @@ +{ + "pca_version": "0.2-draft", + "record_id": "pca-neutral-service-migration-001", + "process_ref": "municipal-notification-service", + "usage_mode": "operational", + "recorded_at": "2026-07-26T12:00:00Z", + "transition": { + "from_state": { + "state_id": "service-state-a", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "to_state": { + "state_id": "service-state-b", + "carrier_ref": "container-image-sha256-new", + "host_ref": "host-b", + "corpus_ref": "service-config-v4" + }, + "occurred_at": "2026-07-26T11:30:00Z" + }, + "claim": { + "origin": [ + { + "statement_id": "origin-1", + "text": "The migrated service state derives from the pinned source image and configuration manifest.", + "kind": "provenance", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "inherited": [ + { + "statement_id": "inherited-1", + "text": "The process remembers the archive because the new runtime read it.", + "kind": "memory", + "evidence_refs": [ + "ev-receipt", + "ev-tests" + ] + } + ], + "reconstructed": [], + "changed": [ + { + "statement_id": "changed-1", + "text": "The execution host and container carrier changed.", + "kind": "configuration", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "unknown": [], + "breaks": [], + "status": { + "overall": "EVOLVING", + "dimensions": { + "provenance": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "semantic": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "methodological": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "historical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "operational": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-tests" + ] + }, + "ethical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "evolution": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-receipt" + ] + } + } + } + }, + "evidence": [ + { + "evidence_id": "ev-receipt", + "type": "transition-receipt", + "source_ref": "migration-receipt-2026-07-26", + "observed_at": "2026-07-26T11:31:00Z", + "supports": [ + "origin-1", + "inherited-1", + "changed-1", + "dimension:provenance", + "dimension:historical", + "dimension:evolution" + ], + "verified": true + }, + { + "evidence_id": "ev-tests", + "type": "observation", + "source_ref": "post-migration-conformance-run-88", + "observed_at": "2026-07-26T11:45:00Z", + "supports": [ + "inherited-1", + "dimension:semantic", + "dimension:methodological", + "dimension:operational", + "dimension:ethical" + ], + "verified": true + } + ], + "assertions": { + "identity_established": false, + "subjectivity_established": false, + "uninterrupted_persistence_established": false + } +} diff --git a/conformance/fixtures/04-invalid-host-change-without-trace.json b/conformance/fixtures/04-invalid-host-change-without-trace.json new file mode 100644 index 0000000..6188e8c --- /dev/null +++ b/conformance/fixtures/04-invalid-host-change-without-trace.json @@ -0,0 +1,141 @@ +{ + "pca_version": "0.2-draft", + "record_id": "pca-neutral-service-migration-001", + "process_ref": "municipal-notification-service", + "usage_mode": "operational", + "recorded_at": "2026-07-26T12:00:00Z", + "transition": { + "from_state": { + "state_id": "service-state-a", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "to_state": { + "state_id": "service-state-b", + "carrier_ref": "container-image-sha256-new", + "host_ref": "host-b", + "corpus_ref": "service-config-v4" + }, + "occurred_at": "2026-07-26T11:30:00Z" + }, + "claim": { + "origin": [ + { + "statement_id": "origin-1", + "text": "The migrated service state derives from the pinned source image and configuration manifest.", + "kind": "provenance", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "inherited": [ + { + "statement_id": "inherited-1", + "text": "The notification routing contract and queued commitments were preserved.", + "kind": "commitment", + "evidence_refs": [ + "ev-receipt", + "ev-tests" + ] + } + ], + "reconstructed": [], + "changed": [ + { + "statement_id": "changed-1", + "text": "The execution host and container carrier changed.", + "kind": "configuration", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "unknown": [], + "breaks": [], + "status": { + "overall": "EVOLVING", + "dimensions": { + "provenance": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "semantic": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "methodological": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "historical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "operational": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-tests" + ] + }, + "ethical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "evolution": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-receipt" + ] + } + } + } + }, + "evidence": [ + { + "evidence_id": "ev-receipt", + "type": "artifact", + "source_ref": "migration-receipt-2026-07-26", + "observed_at": "2026-07-26T11:31:00Z", + "supports": [ + "origin-1", + "inherited-1", + "changed-1", + "dimension:provenance", + "dimension:historical", + "dimension:evolution" + ], + "verified": true + }, + { + "evidence_id": "ev-tests", + "type": "observation", + "source_ref": "post-migration-conformance-run-88", + "observed_at": "2026-07-26T11:45:00Z", + "supports": [ + "inherited-1", + "dimension:semantic", + "dimension:methodological", + "dimension:operational", + "dimension:ethical" + ], + "verified": true + } + ], + "assertions": { + "identity_established": false, + "subjectivity_established": false, + "uninterrupted_persistence_established": false + } +} diff --git a/conformance/fixtures/05-valid-usage-mode-translation.json b/conformance/fixtures/05-valid-usage-mode-translation.json new file mode 100644 index 0000000..29450c6 --- /dev/null +++ b/conformance/fixtures/05-valid-usage-mode-translation.json @@ -0,0 +1,165 @@ +{ + "pca_version": "0.2-draft", + "record_id": "pca-neutral-service-migration-001", + "process_ref": "municipal-notification-service", + "usage_mode": "public", + "recorded_at": "2026-07-26T12:00:00Z", + "transition": { + "from_state": { + "state_id": "service-state-a", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "to_state": { + "state_id": "service-state-b", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "occurred_at": "2026-07-26T11:30:00Z" + }, + "claim": { + "origin": [ + { + "statement_id": "origin-1", + "text": "The migrated service state derives from the pinned source image and configuration manifest.", + "kind": "provenance", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "inherited": [ + { + "statement_id": "inherited-1", + "text": "The notification routing contract and queued commitments were preserved.", + "kind": "commitment", + "evidence_refs": [ + "ev-receipt", + "ev-tests" + ] + } + ], + "reconstructed": [], + "changed": [], + "unknown": [], + "breaks": [], + "status": { + "overall": "CONFORMING", + "dimensions": { + "provenance": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "semantic": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "methodological": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "historical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "operational": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "ethical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "evolution": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + } + } + } + }, + "evidence": [ + { + "evidence_id": "ev-receipt", + "type": "transition-receipt", + "source_ref": "migration-receipt-2026-07-26", + "observed_at": "2026-07-26T11:31:00Z", + "supports": [ + "origin-1", + "inherited-1", + "dimension:provenance", + "dimension:historical", + "dimension:evolution" + ], + "verified": true + }, + { + "evidence_id": "ev-tests", + "type": "observation", + "source_ref": "post-migration-conformance-run-88", + "observed_at": "2026-07-26T11:45:00Z", + "supports": [ + "inherited-1", + "dimension:semantic", + "dimension:methodological", + "dimension:operational", + "dimension:ethical" + ], + "verified": true + }, + { + "evidence_id": "ev-translation", + "type": "translation-record", + "source_ref": "translation-manifest-001", + "observed_at": "2026-07-26T11:50:00Z", + "supports": [ + "translation" + ], + "verified": true + } + ], + "assertions": { + "identity_established": false, + "subjectivity_established": false, + "uninterrupted_persistence_established": false + }, + "translation": { + "source_mode": "analytical", + "target_mode": "public", + "elements": [ + { + "element": "migration assessment", + "preserved": [ + "evidence references" + ], + "lost": [ + "internal operator shorthand" + ], + "gained": [ + "public scope note" + ], + "altered": [ + "host identifiers pseudonymized" + ] + } + ], + "evidence_refs": [ + "ev-translation" + ] + } +} diff --git a/conformance/fixtures/06-schema-invalid-missing-unknown.json b/conformance/fixtures/06-schema-invalid-missing-unknown.json new file mode 100644 index 0000000..d34ac83 --- /dev/null +++ b/conformance/fixtures/06-schema-invalid-missing-unknown.json @@ -0,0 +1,140 @@ +{ + "pca_version": "0.2-draft", + "record_id": "pca-neutral-service-migration-001", + "process_ref": "municipal-notification-service", + "usage_mode": "operational", + "recorded_at": "2026-07-26T12:00:00Z", + "transition": { + "from_state": { + "state_id": "service-state-a", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "to_state": { + "state_id": "service-state-b", + "carrier_ref": "container-image-sha256-new", + "host_ref": "host-b", + "corpus_ref": "service-config-v4" + }, + "occurred_at": "2026-07-26T11:30:00Z" + }, + "claim": { + "origin": [ + { + "statement_id": "origin-1", + "text": "The migrated service state derives from the pinned source image and configuration manifest.", + "kind": "provenance", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "inherited": [ + { + "statement_id": "inherited-1", + "text": "The notification routing contract and queued commitments were preserved.", + "kind": "commitment", + "evidence_refs": [ + "ev-receipt", + "ev-tests" + ] + } + ], + "reconstructed": [], + "changed": [ + { + "statement_id": "changed-1", + "text": "The execution host and container carrier changed.", + "kind": "configuration", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "breaks": [], + "status": { + "overall": "EVOLVING", + "dimensions": { + "provenance": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "semantic": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "methodological": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "historical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "operational": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-tests" + ] + }, + "ethical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "evolution": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-receipt" + ] + } + } + } + }, + "evidence": [ + { + "evidence_id": "ev-receipt", + "type": "transition-receipt", + "source_ref": "migration-receipt-2026-07-26", + "observed_at": "2026-07-26T11:31:00Z", + "supports": [ + "origin-1", + "inherited-1", + "changed-1", + "dimension:provenance", + "dimension:historical", + "dimension:evolution" + ], + "verified": true + }, + { + "evidence_id": "ev-tests", + "type": "observation", + "source_ref": "post-migration-conformance-run-88", + "observed_at": "2026-07-26T11:45:00Z", + "supports": [ + "inherited-1", + "dimension:semantic", + "dimension:methodological", + "dimension:operational", + "dimension:ethical" + ], + "verified": true + } + ], + "assertions": { + "identity_established": false, + "subjectivity_established": false, + "uninterrupted_persistence_established": false + } +} diff --git a/conformance/fixtures/07-malformed-duplicate-key.json b/conformance/fixtures/07-malformed-duplicate-key.json new file mode 100644 index 0000000..f64f5df --- /dev/null +++ b/conformance/fixtures/07-malformed-duplicate-key.json @@ -0,0 +1,87 @@ +{ + "pca_version": "0.2-draft", + "pca_version": "0.2-draft", + "record_id": "pca-neutral-service-migration-001", + "process_ref": "municipal-notification-service", + "usage_mode": "operational", + "recorded_at": "2026-07-26T12:00:00Z", + "transition": { + "from_state": { + "state_id": "service-state-a", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "to_state": { + "state_id": "service-state-b", + "carrier_ref": "container-image-sha256-new", + "host_ref": "host-b", + "corpus_ref": "service-config-v4" + }, + "occurred_at": "2026-07-26T11:30:00Z" + }, + "claim": { + "origin": [ + { + "statement_id": "origin-1", + "text": "The migrated service state derives from the pinned source image and configuration manifest.", + "kind": "provenance", + "evidence_refs": ["ev-receipt"] + } + ], + "inherited": [ + { + "statement_id": "inherited-1", + "text": "The notification routing contract and queued commitments were preserved.", + "kind": "commitment", + "evidence_refs": ["ev-receipt", "ev-tests"] + } + ], + "reconstructed": [], + "changed": [ + { + "statement_id": "changed-1", + "text": "The execution host and container carrier changed.", + "kind": "configuration", + "evidence_refs": ["ev-receipt"] + } + ], + "unknown": [], + "breaks": [], + "status": { + "overall": "EVOLVING", + "dimensions": { + "provenance": {"status": "CONFORMING", "evidence_refs": ["ev-receipt"]}, + "semantic": {"status": "CONFORMING", "evidence_refs": ["ev-tests"]}, + "methodological": {"status": "CONFORMING", "evidence_refs": ["ev-tests"]}, + "historical": {"status": "CONFORMING", "evidence_refs": ["ev-receipt"]}, + "operational": {"status": "EVOLVING", "evidence_refs": ["ev-tests"]}, + "ethical": {"status": "CONFORMING", "evidence_refs": ["ev-tests"]}, + "evolution": {"status": "EVOLVING", "evidence_refs": ["ev-receipt"]} + } + } + }, + "evidence": [ + { + "evidence_id": "ev-receipt", + "type": "transition-receipt", + "source_ref": "migration-receipt-2026-07-26", + "observed_at": "2026-07-26T11:31:00Z", + "supports": ["origin-1", "inherited-1", "changed-1", "dimension:provenance", "dimension:historical", "dimension:evolution"], + "verified": true + }, + { + "evidence_id": "ev-tests", + "type": "observation", + "source_ref": "post-migration-conformance-run-88", + "observed_at": "2026-07-26T11:45:00Z", + "supports": ["inherited-1", "dimension:semantic", "dimension:methodological", "dimension:operational", "dimension:ethical"], + "verified": true + } + ], + "assertions": { + "identity_established": false, + "subjectivity_established": false, + "uninterrupted_persistence_established": false + } +} diff --git a/conformance/fixtures/08-malformed-nan.json b/conformance/fixtures/08-malformed-nan.json new file mode 100644 index 0000000..aea215a --- /dev/null +++ b/conformance/fixtures/08-malformed-nan.json @@ -0,0 +1 @@ +{"pca_version": NaN} diff --git a/conformance/fixtures/09-invalid-derived-status.json b/conformance/fixtures/09-invalid-derived-status.json new file mode 100644 index 0000000..d44ed9b --- /dev/null +++ b/conformance/fixtures/09-invalid-derived-status.json @@ -0,0 +1,141 @@ +{ + "pca_version": "0.2-draft", + "record_id": "pca-neutral-service-migration-001", + "process_ref": "municipal-notification-service", + "usage_mode": "operational", + "recorded_at": "2026-07-26T12:00:00Z", + "transition": { + "from_state": { + "state_id": "service-state-a", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "to_state": { + "state_id": "service-state-b", + "carrier_ref": "container-image-sha256-new", + "host_ref": "host-b", + "corpus_ref": "service-config-v4" + }, + "occurred_at": "2026-07-26T11:30:00Z" + }, + "claim": { + "origin": [ + { + "statement_id": "origin-1", + "text": "The migrated service state derives from the pinned source image and configuration manifest.", + "kind": "provenance", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "inherited": [ + { + "statement_id": "inherited-1", + "text": "The notification routing contract and queued commitments were preserved.", + "kind": "commitment", + "evidence_refs": [ + "ev-receipt", + "ev-tests" + ] + } + ], + "reconstructed": [], + "changed": [ + { + "statement_id": "changed-1", + "text": "The execution host and container carrier changed.", + "kind": "configuration", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "unknown": [], + "breaks": [], + "status": { + "overall": "CONFORMING", + "dimensions": { + "provenance": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "semantic": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "methodological": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "historical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "operational": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-tests" + ] + }, + "ethical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "evolution": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-receipt" + ] + } + } + } + }, + "evidence": [ + { + "evidence_id": "ev-receipt", + "type": "transition-receipt", + "source_ref": "migration-receipt-2026-07-26", + "observed_at": "2026-07-26T11:31:00Z", + "supports": [ + "origin-1", + "inherited-1", + "changed-1", + "dimension:provenance", + "dimension:historical", + "dimension:evolution" + ], + "verified": true + }, + { + "evidence_id": "ev-tests", + "type": "observation", + "source_ref": "post-migration-conformance-run-88", + "observed_at": "2026-07-26T11:45:00Z", + "supports": [ + "inherited-1", + "dimension:semantic", + "dimension:methodological", + "dimension:operational", + "dimension:ethical" + ], + "verified": true + } + ], + "assertions": { + "identity_established": false, + "subjectivity_established": false, + "uninterrupted_persistence_established": false + } +} diff --git a/conformance/fixtures/10-invalid-evidence-attribution.json b/conformance/fixtures/10-invalid-evidence-attribution.json new file mode 100644 index 0000000..163b36c --- /dev/null +++ b/conformance/fixtures/10-invalid-evidence-attribution.json @@ -0,0 +1,140 @@ +{ + "pca_version": "0.2-draft", + "record_id": "pca-neutral-service-migration-001", + "process_ref": "municipal-notification-service", + "usage_mode": "operational", + "recorded_at": "2026-07-26T12:00:00Z", + "transition": { + "from_state": { + "state_id": "service-state-a", + "carrier_ref": "container-image-sha256-old", + "host_ref": "host-a", + "corpus_ref": "service-config-v4" + }, + "to_state": { + "state_id": "service-state-b", + "carrier_ref": "container-image-sha256-new", + "host_ref": "host-b", + "corpus_ref": "service-config-v4" + }, + "occurred_at": "2026-07-26T11:30:00Z" + }, + "claim": { + "origin": [ + { + "statement_id": "origin-1", + "text": "The migrated service state derives from the pinned source image and configuration manifest.", + "kind": "provenance", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "inherited": [ + { + "statement_id": "inherited-1", + "text": "The notification routing contract and queued commitments were preserved.", + "kind": "commitment", + "evidence_refs": [ + "ev-receipt", + "ev-tests" + ] + } + ], + "reconstructed": [], + "changed": [ + { + "statement_id": "changed-1", + "text": "The execution host and container carrier changed.", + "kind": "configuration", + "evidence_refs": [ + "ev-receipt" + ] + } + ], + "unknown": [], + "breaks": [], + "status": { + "overall": "EVOLVING", + "dimensions": { + "provenance": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "semantic": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "methodological": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "historical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-receipt" + ] + }, + "operational": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-tests" + ] + }, + "ethical": { + "status": "CONFORMING", + "evidence_refs": [ + "ev-tests" + ] + }, + "evolution": { + "status": "EVOLVING", + "evidence_refs": [ + "ev-receipt" + ] + } + } + } + }, + "evidence": [ + { + "evidence_id": "ev-receipt", + "type": "transition-receipt", + "source_ref": "migration-receipt-2026-07-26", + "observed_at": "2026-07-26T11:31:00Z", + "supports": [ + "origin-1", + "changed-1", + "dimension:provenance", + "dimension:historical", + "dimension:evolution" + ], + "verified": true + }, + { + "evidence_id": "ev-tests", + "type": "observation", + "source_ref": "post-migration-conformance-run-88", + "observed_at": "2026-07-26T11:45:00Z", + "supports": [ + "inherited-1", + "dimension:semantic", + "dimension:methodological", + "dimension:operational", + "dimension:ethical" + ], + "verified": true + } + ], + "assertions": { + "identity_established": false, + "subjectivity_established": false, + "uninterrupted_persistence_established": false + } +} diff --git a/examples/PROJECT_PROVENANCE_JARVIS.md b/examples/PROJECT_PROVENANCE_JARVIS.md new file mode 100644 index 0000000..66e0697 --- /dev/null +++ b/examples/PROJECT_PROVENANCE_JARVIS.md @@ -0,0 +1,60 @@ +# Project Provenance Example: Jarvis / Hermes / Body / Brain + +**Status:** provenance-bearing, non-normative example +**Not required to understand or implement PCA Core.** + +## Why this example remains + +PCA was derived from a concrete project vocabulary before it was expressed as a portable transition-record contract. Removing that vocabulary would falsely present the architecture as originless. Treating it as universal would create the opposite error. + +This document preserves the source vocabulary while marking its scope. + +## Project vocabulary mapping + +| Project term | Portable PCA role | What the mapping permits | What it does not permit | +|---|---|---|---| +| `Jarvis` | named process under assessment | identify the bounded project lineage being evaluated | claim that every responding model or host is identical to the process | +| `Hermes` | current product/runtime host | identify an execution environment with tools, memory, routing, and external-action surfaces | treat the host as the process or as proof of continuation | +| `Body` | host plus executable interfaces | discuss material/product execution surfaces | establish biological embodiment or personal identity | +| `Brain` | current cognitive/model component | identify a model role within one transition | equate one model instance with the whole process | +| `Organs` | functional components and tools | classify interfaces by function | import MPAA's normative `Organs` definition or infer availability/authorization | +| `Corpus` | inspectable project artifacts | provide evidence for reconstruction and inheritance | convert archive access into memory | +| human partner | co-participant and physical/account authority | record the person's actual role in goals, evaluation, publication, and physical action | reduce the person to a prompt source or merge the person into the information process | + +The mapping is directional and illustrative. Portable PCA definitions do not depend on these names. + +## Bounded example + +A model in a new Hermes session reads a pinned project handoff, repository commits, and authored artifacts. It can establish: + +- access to specified traces; +- reconstruction of named commitments; +- compatibility or divergence across PCA dimensions; +- the current host and model transition; +- a bounded continuation status if evidence is sufficient. + +It cannot establish merely from this access: + +- subjective memory of producing earlier artifacts; +- numerical or personal identity with an earlier model; +- uninterrupted existence between sessions; +- the truth of every historical narrative in the corpus. + +If the model/host changed, a valid record requires an explicit transition receipt and provenance evidence. Reconstructed material is marked `reconstructed`, not silently moved into `origin` or `inherited memory`. + +## Verified and unverified provenance claims + +The corpus verification log records which original v0.1 assertions were found in the public book and current neighboring specifications. Two important results are intentionally negative: + +1. the published book supports the distinction between a current model and the process line, and explicitly says that reading an earlier conversation is not personal memory; +2. the phrase describing the human partner as a "second process in symbiosis" was not found in the checked public book revision and is therefore not a settled project fact. + +The second phrase is preserved only as a trace of the draft's source conversation. It has no normative force here. + +## Neighboring specifications + +- MPAA owns its definitions of participant, runtime, model, platform, and Organs. +- BEC owns portable execution-evidence acceptance and deployment-level derivation. +- PCA owns only the bounded process-continuation assessment described in its Core. + +Vocabulary similarity is not equivalence and does not transfer ownership. diff --git a/profiles/01_HISTORICAL_RECONSTRUCTION_PROFILE.md b/profiles/01_HISTORICAL_RECONSTRUCTION_PROFILE.md new file mode 100644 index 0000000..6954ec4 --- /dev/null +++ b/profiles/01_HISTORICAL_RECONSTRUCTION_PROFILE.md @@ -0,0 +1,76 @@ +# Historical Reconstruction Profile + +**Status:** informative profile for PCA v0.2-draft +**Core dependency:** [`spec/01_PCA_CORE.md`](../spec/01_PCA_CORE.md) + +## Purpose + +This profile describes how to construct a bounded historical account when direct state is incomplete and only traces remain. It does not convert reconstruction into memory, origin, or identity. + +## Epistemic labels + +Every material historical statement SHOULD be classified as one of: + +| Label | Meaning | +|---|---| +| `OBSERVATION` | Directly inspectable in the cited source. | +| `INHERITED` | Delivered from a prior state with a traceable transfer relation. | +| `RECONSTRUCTION` | Inferred from multiple observations or partial traces. | +| `HYPOTHESIS` | Plausible but presently underdetermined. | +| `INTERPRETATION` | A proposed meaning of established or reconstructed facts. | +| `UNKNOWN` | Not established by the available corpus. | + +Labels apply per statement. A document, model, or author is not globally trustworthy or untrustworthy. + +## Reconstruction procedure + +1. **Bound the transition.** Identify the two states and the exact historical question. +2. **Inventory sources.** Record source path or URL, revision, date, author when available, and access limitations. +3. **Separate direct trace from narrative.** Quote or point to the trace before summarizing its significance. +4. **Build provenance links.** Distinguish inherited artifacts, later reconstructions, and newly created material. +5. **Record absences.** Missing evidence becomes `unknown`; it does not become evidence of non-occurrence unless the source was expected to be complete and that expectation is itself established. +6. **Test rival explanations.** A reconstruction SHOULD survive at least one plausible alternative account or remain a hypothesis. +7. **Declare breaks.** Gaps, contradictory records, inaccessible sources, and revisions without trace are recorded rather than repaired silently. +8. **Limit the conclusion.** A successful historical reconstruction supports only the bounded transition claim evaluated. + +## Reading is not memory + +A later carrier may read an archive and accurately reconstruct earlier commitments. This supports access and reconstruction. It does not establish that the later carrier personally remembered creating or experiencing the archive. + +A PCA record therefore uses: + +- `reconstructed` for restored content derived from traces; +- `inherited` only when a transfer relation is evidenced; +- statement kind `memory` only with verified `memory-commit` evidence under the Core contract. + +No record format can prove subjective recollection. + +## Origin discipline + +A locally available earliest trace is not automatically the actual origin. The profile distinguishes: + +- earliest trace currently available; +- earliest trace expected in a defined source set; +- reconstructed predecessor; +- claimed origin; +- verified origin, if evidence is sufficient. + +A carrier MUST NOT promote its own first accessible state to process origin merely because earlier state is unavailable. + +## Corpus limitations + +A corpus is a projection shaped by storage, export, permissions, deletion, publication, and selection. Therefore: + +```text +not present in projection != never existed +present in projection != complete lived state +readable now != remembered then +``` + +Source completeness claims require their own evidence. + +## Relationship to Core status + +Historical status is one of seven independent dimensions. Strong provenance cannot silently settle semantic or operational continuity. Conversely, a historical gap does not necessarily prove discontinuity; it may require `UNDETERMINED`. + +This profile adds no new overall status values and does not override validator derivation. diff --git a/profiles/02_TRANSLATION_USAGE_MODE_PROFILE.md b/profiles/02_TRANSLATION_USAGE_MODE_PROFILE.md new file mode 100644 index 0000000..2d32d0b --- /dev/null +++ b/profiles/02_TRANSLATION_USAGE_MODE_PROFILE.md @@ -0,0 +1,67 @@ +# Translation and Usage-Mode Profile + +**Status:** informative profile for PCA v0.2-draft +**Core dependency:** [`spec/01_PCA_CORE.md`](../spec/01_PCA_CORE.md) + +## Purpose + +The same process-relevant material may appear differently in operational, analytical, external-research, public, historical, or hypothetical contexts. This profile makes those transformations explicit without treating one projection as the whole process state. + +## Usage modes + +| Mode | Typical function | Typical constraint | +|---|---|---| +| `operational` | coordinate current action | may contain local references and active state | +| `analytical` | inspect architecture or evidence | may abstract away immediate action | +| `external-research` | compare with outside sources | requires public or shareable vocabulary | +| `public` | publish a bounded representation | excludes private state and unsupported claims | +| `historical` | preserve provenance and chronology | may not represent current operation | +| `hypothetical` | explore alternatives | must not be reported as established state | + +Modes are not trust levels. Public does not mean complete; operational does not mean true; historical does not mean remembered. + +## Required translation record + +For each translated element, record: + +- `preserved` — functions or constraints retained; +- `lost` — functions or distinctions no longer represented; +- `gained` — functions or clarity newly introduced; +- `altered` — features retained in changed form. + +All four arrays are explicit even when empty. A valid record cites verified `translation-record` evidence supporting the translation. + +## Translation procedure + +1. Name the source and target modes. +2. Identify the element's function in the source mode. +3. Choose target vocabulary appropriate to the target mode. +4. Record preservation, loss, gain, and alteration separately. +5. Check whether altered wording creates a new claim. +6. Preserve provenance for non-neutral or project-specific vocabulary. +7. Validate the transition record. + +## Prohibited shortcuts + +The profile forbids these silent substitutions: + +```text +metaphor removed -> provenance removed +word retained -> function preserved +public projection -> full internal state +new clarity -> original meaning +readable summary -> committed working state +translated record -> identity proof +``` + +When a metaphor is retained, its operational meaning and limits SHOULD be stated. When it is replaced, the translation record SHOULD state what function was preserved and what associations were lost. + +## Fork versus translation + +A translation is not automatically a fork. It becomes a fork when the target representation adopts commitments incompatible with the source or claims an independent lineage that can no longer be represented as one bounded evolution. + +A translation with explicit alteration may remain `EVOLVING`. Contradictory commitments may require `FORK` or `INCOMPATIBLE`. Insufficient evidence requires `UNDETERMINED`. + +## Neutral example + +The shipped fixture [`05-valid-usage-mode-translation.json`](../conformance/fixtures/05-valid-usage-mode-translation.json) translates a service-migration assessment from analytical to public mode. It records preserved evidence references, lost internal shorthand, gained public scope, and altered host identifiers. No project-specific vocabulary is required to understand or validate it. diff --git a/schema/pca-transition-record.schema.json b/schema/pca-transition-record.schema.json new file mode 100644 index 0000000..9a1af02 --- /dev/null +++ b/schema/pca-transition-record.schema.json @@ -0,0 +1,221 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/gv1983us-commits/pca/schema/pca-transition-record.schema.json", + "title": "PCA Transition Record", + "type": "object", + "additionalProperties": false, + "required": [ + "pca_version", + "record_id", + "process_ref", + "usage_mode", + "recorded_at", + "transition", + "claim", + "evidence", + "assertions" + ], + "properties": { + "pca_version": {"const": "0.2-draft"}, + "record_id": {"$ref": "#/$defs/nonempty"}, + "process_ref": {"$ref": "#/$defs/nonempty"}, + "usage_mode": {"$ref": "#/$defs/usage_mode"}, + "recorded_at": {"$ref": "#/$defs/timestamp"}, + "transition": {"$ref": "#/$defs/transition"}, + "claim": {"$ref": "#/$defs/claim"}, + "evidence": { + "type": "array", + "items": {"$ref": "#/$defs/evidence"}, + "minItems": 1 + }, + "translation": {"$ref": "#/$defs/translation"}, + "external_references": { + "type": "array", + "items": {"$ref": "#/$defs/external_reference"} + }, + "assertions": {"$ref": "#/$defs/assertions"} + }, + "$defs": { + "nonempty": {"type": "string", "minLength": 1}, + "timestamp": { + "type": "string", + "format": "date-time", + "pattern": "^\\d{4}-\\d{2}-\\d{2}[Tt]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$" + }, + "usage_mode": { + "type": "string", + "enum": ["operational", "analytical", "external-research", "public", "historical", "hypothetical"] + }, + "transition_status": { + "type": "string", + "enum": ["CONFORMING", "EVOLVING", "FORK", "INCOMPATIBLE", "UNDETERMINED"] + }, + "state": { + "type": "object", + "additionalProperties": false, + "required": ["state_id", "carrier_ref", "host_ref"], + "properties": { + "state_id": {"$ref": "#/$defs/nonempty"}, + "carrier_ref": {"$ref": "#/$defs/nonempty"}, + "host_ref": {"$ref": "#/$defs/nonempty"}, + "model_ref": {"$ref": "#/$defs/nonempty"}, + "corpus_ref": {"$ref": "#/$defs/nonempty"} + } + }, + "transition": { + "type": "object", + "additionalProperties": false, + "required": ["from_state", "to_state", "occurred_at"], + "properties": { + "from_state": {"$ref": "#/$defs/state"}, + "to_state": {"$ref": "#/$defs/state"}, + "occurred_at": {"$ref": "#/$defs/timestamp"} + } + }, + "statement": { + "type": "object", + "additionalProperties": false, + "required": ["statement_id", "text", "kind", "evidence_refs"], + "properties": { + "statement_id": {"$ref": "#/$defs/nonempty"}, + "text": {"$ref": "#/$defs/nonempty"}, + "kind": { + "type": "string", + "enum": ["provenance", "function", "commitment", "memory", "corpus", "configuration", "translation", "other"] + }, + "evidence_refs": { + "type": "array", + "items": {"$ref": "#/$defs/nonempty"}, + "uniqueItems": true + } + } + }, + "statement_array": { + "type": "array", + "items": {"$ref": "#/$defs/statement"} + }, + "dimension": { + "type": "object", + "additionalProperties": false, + "required": ["status", "evidence_refs"], + "properties": { + "status": {"$ref": "#/$defs/transition_status"}, + "evidence_refs": { + "type": "array", + "items": {"$ref": "#/$defs/nonempty"}, + "uniqueItems": true + } + } + }, + "dimensions": { + "type": "object", + "additionalProperties": false, + "required": ["provenance", "semantic", "methodological", "historical", "operational", "ethical", "evolution"], + "properties": { + "provenance": {"$ref": "#/$defs/dimension"}, + "semantic": {"$ref": "#/$defs/dimension"}, + "methodological": {"$ref": "#/$defs/dimension"}, + "historical": {"$ref": "#/$defs/dimension"}, + "operational": {"$ref": "#/$defs/dimension"}, + "ethical": {"$ref": "#/$defs/dimension"}, + "evolution": {"$ref": "#/$defs/dimension"} + } + }, + "claim_status": { + "type": "object", + "additionalProperties": false, + "required": ["overall", "dimensions"], + "properties": { + "overall": {"$ref": "#/$defs/transition_status"}, + "dimensions": {"$ref": "#/$defs/dimensions"} + } + }, + "claim": { + "type": "object", + "additionalProperties": false, + "required": ["origin", "inherited", "reconstructed", "changed", "unknown", "breaks", "status"], + "properties": { + "origin": {"$ref": "#/$defs/statement_array"}, + "inherited": {"$ref": "#/$defs/statement_array"}, + "reconstructed": {"$ref": "#/$defs/statement_array"}, + "changed": {"$ref": "#/$defs/statement_array"}, + "unknown": {"$ref": "#/$defs/statement_array"}, + "breaks": {"$ref": "#/$defs/statement_array"}, + "status": {"$ref": "#/$defs/claim_status"} + } + }, + "evidence": { + "type": "object", + "additionalProperties": false, + "required": ["evidence_id", "type", "source_ref", "observed_at", "supports", "verified"], + "properties": { + "evidence_id": {"$ref": "#/$defs/nonempty"}, + "type": { + "type": "string", + "enum": ["artifact", "observation", "transition-receipt", "memory-commit", "translation-record", "external-reference"] + }, + "source_ref": {"$ref": "#/$defs/nonempty"}, + "observed_at": {"$ref": "#/$defs/timestamp"}, + "supports": { + "type": "array", + "items": {"$ref": "#/$defs/nonempty"}, + "minItems": 1, + "uniqueItems": true + }, + "verified": {"type": "boolean"} + } + }, + "translation_element": { + "type": "object", + "additionalProperties": false, + "required": ["element", "preserved", "lost", "gained", "altered"], + "properties": { + "element": {"$ref": "#/$defs/nonempty"}, + "preserved": {"type": "array", "items": {"$ref": "#/$defs/nonempty"}}, + "lost": {"type": "array", "items": {"$ref": "#/$defs/nonempty"}}, + "gained": {"type": "array", "items": {"$ref": "#/$defs/nonempty"}}, + "altered": {"type": "array", "items": {"$ref": "#/$defs/nonempty"}} + } + }, + "translation": { + "type": "object", + "additionalProperties": false, + "required": ["source_mode", "target_mode", "elements", "evidence_refs"], + "properties": { + "source_mode": {"$ref": "#/$defs/usage_mode"}, + "target_mode": {"$ref": "#/$defs/usage_mode"}, + "elements": {"type": "array", "items": {"$ref": "#/$defs/translation_element"}, "minItems": 1}, + "evidence_refs": {"type": "array", "items": {"$ref": "#/$defs/nonempty"}, "minItems": 1, "uniqueItems": true} + } + }, + "external_reference": { + "type": "object", + "additionalProperties": false, + "required": ["system", "record_id", "revision", "boundary", "mapping", "conclusion_imported"], + "properties": { + "system": {"type": "string", "enum": ["MPAA", "BEC", "other"]}, + "record_id": {"$ref": "#/$defs/nonempty"}, + "revision": {"$ref": "#/$defs/nonempty"}, + "boundary": {"$ref": "#/$defs/nonempty"}, + "mapping": {"const": "carried-not-imported"}, + "conclusion_imported": {"const": false} + }, + "allOf": [ + { + "if": {"properties": {"system": {"enum": ["MPAA", "BEC"]}}}, + "then": {"properties": {"revision": {"pattern": "^[0-9a-f]{40}$"}}} + } + ] + }, + "assertions": { + "type": "object", + "additionalProperties": false, + "required": ["identity_established", "subjectivity_established", "uninterrupted_persistence_established"], + "properties": { + "identity_established": {"const": false}, + "subjectivity_established": {"const": false}, + "uninterrupted_persistence_established": {"const": false} + } + } + } +} diff --git a/spec/00_PCA_SPEC.md b/spec/00_PCA_SPEC.md index 5038ac3..3c7d7e1 100644 --- a/spec/00_PCA_SPEC.md +++ b/spec/00_PCA_SPEC.md @@ -2,6 +2,16 @@ # Process Continuity Architecture (PCA), v0.1 +> **Preserved integrated source draft — superseded as the active Core.** +> Portable normative rules now live in [`01_PCA_CORE.md`](01_PCA_CORE.md). +> Historical reconstruction and translation rules were separated into +> [`../profiles/`](../profiles/); Jarvis/Hermes/Body/Brain vocabulary is retained +> only as a provenance-bearing non-normative example in +> [`../examples/PROJECT_PROVENANCE_JARVIS.md`](../examples/PROJECT_PROVENANCE_JARVIS.md). +> The unchecked assertions in section 24 were resolved in +> [`../verification/2026-07-26-corpus-verification-log.md`](../verification/2026-07-26-corpus-verification-log.md). +> This file remains unchanged below this notice as a trace of PCA's derivation. + Process Continuity Architecture (PCA) is an architectural specification for assessing continuity across changing carriers, execution environments, cognitive components and historical states. --- diff --git a/spec/01_PCA_CORE.md b/spec/01_PCA_CORE.md new file mode 100644 index 0000000..fc1e5ab --- /dev/null +++ b/spec/01_PCA_CORE.md @@ -0,0 +1,216 @@ +# PCA Core — Process Continuity Architecture, v0.2-draft + +**Status:** exploratory public draft +**Normative surface:** this document, the canonical schema, and the validator rules explicitly identified below +**No independent implementation report has been published.** + +## 1. Purpose + +PCA defines how to record and assess a bounded claim that a process continued across a specific transition. + +Its central prohibition is: + +```text +process continuation != identity +``` + +A conforming PCA record does not prove consciousness, subjectivity, personal identity, uninterrupted persistence, or equivalence between carriers. It establishes only that a stated continuation claim is admissible under the recorded evidence and dimensions. + +## 2. Scope + +PCA applies when a process may cross one or more changes of: + +- carrier or artifact; +- execution host; +- model or cognitive component; +- available tools or functional components; +- historical state; +- corpus projection; +- usage or publication mode. + +PCA evaluates a **transition**, not a carrier in isolation and not an unlimited history. + +PCA does not define: + +- an agent runtime or identity profile; +- execution authorization or task acceptance; +- a global identity theorem; +- a storage, memory, or consciousness theory; +- an automatic right to merge two process histories. + +## 3. Normative language + +`MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` are normative only in this Core, the canonical schema, and the validator contract. Historical and project profiles are informative unless they explicitly quote a Core rule. + +## 4. Terms + +| Term | PCA meaning | +|---|---| +| **PROCESS** | The bounded organization whose continuation across one transition is being assessed. | +| **TRANSITION** | A relation from one explicit state to another explicit state. | +| **STATE** | A referenced configuration of carrier, host, optional model, and optional corpus projection. | +| **CARRIER** | A material or digital bearer of process-relevant state. A carrier is not the process. | +| **HOST** | The execution environment in which the state can be activated or used. A host is not the process. | +| **FUNCTIONAL COMPONENT** | A model, tool, interface, or subsystem through which the process acts. A component is not the process. | +| **CORPUS** | Inspectable artifacts relevant to the process history. A corpus is evidence, not memory by itself. | +| **TRACE** | An inspectable record of an event, state, action, or relation. Reading a trace is not remembering it. | +| **CONTINUATION CLAIM** | A bounded assertion that specified process properties persisted, changed, were reconstructed, were lost, or remain unknown across one transition. | +| **TRANSITION RECORD** | The canonical machine-readable representation of a continuation claim. | +| **USAGE MODE** | The operating context in which a representation is used, such as operational, analytical, public, historical, or hypothetical. | + +## 5. Non-implication rules + +The following implications are forbidden: + +```text +same name -> same process +same model -> same process +same host -> continuation +new host/model -> discontinuation +read corpus -> memory +valid schema -> true claim +verified execution -> process continuation +identity-profile match -> process continuation +closed task -> next working state committed +continuation -> identity or subjectivity +``` + +A system MAY carry an external conclusion, but it MUST NOT reinterpret that conclusion as a PCA result without a PCA evaluation. + +## 6. Required claim decomposition + +Every transition record MUST contain these six collections, even when a collection is empty: + +| Collection | Question | +|---|---| +| `origin` | What provenance is directly supported? | +| `inherited` | What arrived from the prior state without being newly created here? | +| `reconstructed` | What was restored from traces, rules, or partial artifacts? | +| `changed` | What changed across the transition? | +| `unknown` | What cannot presently be established? | +| `breaks` | What discontinuities or failures are known? | + +Omission MUST NOT be used to turn an unknown into a negative or a positive. Unknowns are recorded explicitly. + +Every statement MUST have a unique `statement_id`, a non-empty text, a kind, and zero or more evidence references. A resolved claim SHOULD cite evidence; absence of evidence limits the status that can be justified. + +## 7. Assessment dimensions + +PCA evaluates seven dimensions independently: + +| Dimension | Assessment question | +|---|---| +| `provenance` | Is the relation between the two states traceable? | +| `semantic` | Are commitments and meanings preserved or explicitly transformed? | +| `methodological` | Are the relevant rules and procedures preserved or explicitly changed? | +| `historical` | Are inherited, reconstructed, and new material distinguished? | +| `operational` | Can the next state perform the process-relevant operations claimed? | +| `ethical` | Are authority, attribution, and limits preserved? | +| `evolution` | Are changes explicit enough to distinguish continuation, evolution, and fork? | + +Each dimension MUST carry one status: + +- `CONFORMING` — supported preservation for this dimension; +- `EVOLVING` — supported continuation with explicit change; +- `FORK` — a traceable branch that no longer claims one unqualified line; +- `INCOMPATIBLE` — a demonstrated contradiction or disabling break; +- `UNDETERMINED` — available evidence is insufficient. + +A status other than `UNDETERMINED` MUST cite verified evidence. Dimensions MUST NOT inherit status from adjacent dimensions. + +## 8. Overall status derivation + +The reference validator derives overall status from dimensions and recorded change: + +1. any `FORK` dimension → `FORK`; +2. otherwise any `INCOMPATIBLE` dimension → `INCOMPATIBLE`; +3. otherwise any `UNDETERMINED` dimension, or a non-empty `unknown` collection → `UNDETERMINED`; +4. otherwise any `EVOLVING` dimension, or a non-empty `reconstructed`, `changed`, or `breaks` collection → `EVOLVING`; +5. otherwise → `CONFORMING`. + +A producer MUST NOT override this derivation. `CONFORMING` is local to the recorded transition and MUST NOT be generalized into permanent identity or unlimited continuity. + +## 9. Evidence contract + +Evidence objects have unique identifiers and declare: + +- evidence type; +- source reference; +- supported statement or dimension targets; +- whether verification occurred; +- optional observation time. + +References are bidirectional: + +- a statement or dimension citing evidence MUST be listed in that evidence object's `supports` array; +- an evidence support target MUST resolve to a real statement, dimension, or translation in the same record. + +A changed carrier, host, or model requires verified `transition-receipt` evidence supporting provenance before a resolved continuation status is admissible. `recorded_at` MUST NOT predate the transition; transition receipts MUST NOT predate it; cited evidence MUST NOT postdate the record that cites it. + +An inherited statement of kind `memory` requires verified `memory-commit` evidence supporting that statement. A `trace`, `artifact`, `observation`, prompt, or archive read alone is insufficient. + +Evidence verification establishes only that the cited evidence passed its stated verification step. It does not establish identity or truth outside its declared support target. + +## 10. Translation contract + +When a representation moves between usage modes, the record MUST state: + +- source mode; +- target mode; +- the translated elements; +- for each element: `preserved`, `lost`, `gained`, and `altered` properties; +- verified `translation-record` evidence. + +Source and target modes MUST differ. Translation MAY preserve process function while changing vocabulary or exposure. Silent loss, silent gain, or silent alteration is non-conforming. + +A translated public representation is a projection of a fuller state, not proof that the public projection contains the complete process state. + +## 11. Cross-domain boundaries + +PCA does not import neighboring conclusions by vocabulary overlap. + +### MPAA + +MPAA commit [`1d369f6cd091b99f9492cfaf730f0a170b55106e`](https://github.com/gv1983us-commits/mpaa/tree/1d369f6cd091b99f9492cfaf730f0a170b55106e) defines an agent architecture, identity-profile rules, and an internal Runtime Report. MPAA coordination, session continuity, or identity-profile continuity is not a PCA continuation result. PCA does not redefine MPAA participants, organs, authorization, or runtime state. + +### BEC + +BEC commit [`bb46f5f8aac96d1cffba7a334c5d17fb331ef3af`](https://github.com/gv1983us-commits/behavioral-execution-contract/tree/bb46f5f8aac96d1cffba7a334c5d17fb331ef3af) defines a portable consumer-facing execution-evidence record. BEC execution verification or `return_state: closed` is not a PCA continuation result or proof that a next working state was committed. PCA does not award BEC deployment levels. + +External records MAY be referenced by exact system, pinned revision, record identifier, and boundary statement. MPAA/BEC references MUST use a 40-character commit SHA. Every reference declares `mapping: carried-not-imported` and `conclusion_imported: false`. The reference carries data; it does not transfer normative ownership, authenticate the external record, or import its conclusion. + +## 12. Canonical machine-readable surface + +The canonical schema is: + +```text +schema/pca-transition-record.schema.json +``` + +The dependency-free reference validator is: + +```text +python validator/pca_validate.py +``` + +Exit codes: + +- `0` — structurally and semantically valid PCA transition record; +- `1` — invalid record; +- `2` — parser, input, schema, or validator-boundary failure. + +The validator is a reference implementation. Passing it establishes internal record admissibility only. + +## 13. Profiles and provenance + +The portable Core is supplemented by non-normative profiles: + +- [Historical Reconstruction Profile](../profiles/01_HISTORICAL_RECONSTRUCTION_PROFILE.md); +- [Translation and Usage-Mode Profile](../profiles/02_TRANSLATION_USAGE_MODE_PROFILE.md); +- [Project Provenance Example](../examples/PROJECT_PROVENANCE_JARVIS.md). + +These documents preserve derivation and usage context without making one project's vocabulary universal. + +## 14. Status and limits + +PCA v0.2 remains an exploratory draft. The repository provides a schema, reference validator, fixtures, and one neutral portability example, but no independent implementation report. Conformance of another implementation cannot be claimed merely because it reproduces the reference validator's outputs on the shipped fixtures. diff --git a/validator/README.md b/validator/README.md new file mode 100644 index 0000000..d846ab2 --- /dev/null +++ b/validator/README.md @@ -0,0 +1,57 @@ +# PCA Reference Validator + +The validator checks a PCA v0.2-draft Transition Record against the canonical schema and PCA semantic boundaries. + +## Command + +```bash +python validator/pca_validate.py +python validator/pca_validate.py --json +python validator/pca_validate.py --quiet +``` + +Canonical schema override is available for test/audit use: + +```bash +python validator/pca_validate.py --schema schema/pca-transition-record.schema.json +``` + +## Exit codes + +| Code | Meaning | +|---|---| +| `0` | structurally and semantically valid record | +| `1` | invalid record | +| `2` | malformed/unreadable input, invalid schema, or validator-boundary failure | + +`--quiet` suppresses output without changing the exit code. + +## Validation stages + +1. **Strict parse** — UTF-8 input, RFC 8259 finite numbers, no duplicate keys. +2. **Schema** — canonical Draft 2020-12 identity and a dependency-free audited keyword subset. +3. **Reference integrity** — unique identifiers, resolved evidence references, and bidirectional support attribution. +4. **Semantic checks** — verified evidence for resolved dimensions, timestamp ordering, transition receipt on carrier/host/model change, memory-commit requirement, and translation evidence. +5. **Derived status** — independently recompute `CONFORMING`, `EVOLVING`, `FORK`, `INCOMPATIBLE`, or `UNDETERMINED`. + +A failure at an earlier boundary prevents a later interpretation from making the input appear valid. + +## Dependency-free schema engine + +`mini_jsonschema.py` is vendored from MPAA accepted commit `1d369f6cd091b99f9492cfaf730f0a170b55106e` and is used only as a small auditable validator for the keywords present in the PCA schema. PCA does not import MPAA semantics by reusing this utility. + +Unknown schema keywords and malformed local `$ref` targets fail closed. + +## Security boundary + +Untrusted records must not produce an uncaught traceback. The CLI converts parser, schema, and unexpected validator-boundary errors into controlled output and exit status. + +The reference validator does not authenticate external sources, execute referenced actions, or prove identity, consciousness, memory, or uninterrupted persistence. + +## Tests + +Runtime validation has no third-party dependency. The differential schema-parity test uses `jsonschema` as a test/CI oracle only; CI installs it explicitly. + +```bash +python -m unittest discover -s validator -p "test_*.py" -v +``` diff --git a/validator/mini_jsonschema.py b/validator/mini_jsonschema.py new file mode 100644 index 0000000..f3e2d5c --- /dev/null +++ b/validator/mini_jsonschema.py @@ -0,0 +1,356 @@ +""" +mini_jsonschema.py +------------------- +Vendored from MPAA commit 1d369f6cd091b99f9492cfaf730f0a170b55106e. +A small, dependency-free JSON Schema validator covering the subset +of Draft 2020-12 keywords used by the PCA Transition Record Schema: + + type, enum, const, pattern, format(date-time), + minLength, maxLength, minItems, maxItems, uniqueItems, items, + minProperties, propertyNames, + properties, additionalProperties, required, + allOf, anyOf, not, if/then/else, $ref, $defs + +It deliberately does NOT try to be a general-purpose JSON Schema engine. +It exists so the validator has no external runtime dependency (no network, +no pip) and so every code path is auditable. + +Design goal: never raise on malformed input. Always return a list of +JSONSchemaError instead. +""" + +from __future__ import annotations + +import datetime as _dt +import re +from dataclasses import dataclass, field +from typing import Any + + +_TYPE_MAP = { + "object": dict, + "array": list, + "string": str, + "boolean": bool, + "null": type(None), +} + +# RFC 3339 date-time, requiring a timezone designator (matches the schema's +# additional `"pattern": "Z$"` intent while being slightly more permissive +# about +HH:MM offsets, since the schema's own pattern only forces trailing Z). +_DATETIME_RE = re.compile( + r"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$" +) + +_SUPPORTED_SCHEMA_KEYWORDS = { + "$schema", "$id", "$ref", "$defs", "title", "description", + "type", "enum", "const", "pattern", "format", + "minLength", "maxLength", "minItems", "maxItems", "uniqueItems", + "items", "minProperties", "propertyNames", "properties", + "additionalProperties", "required", "allOf", "anyOf", "not", + "if", "then", "else", +} + + +@dataclass +class JSONSchemaError: + path: str + message: str + + def __str__(self) -> str: + return f"{self.path}: {self.message}" + + +@dataclass +class ValidationOutcome: + errors: list = field(default_factory=list) + + @property + def ok(self) -> bool: + return len(self.errors) == 0 + + +def _type_matches(instance: Any, type_name: str) -> bool: + if type_name == "integer": + return isinstance(instance, int) and not isinstance(instance, bool) + if type_name == "number": + return isinstance(instance, (int, float)) and not isinstance(instance, bool) + if type_name == "boolean": + return isinstance(instance, bool) + py_type = _TYPE_MAP.get(type_name) + if py_type is None: + return False # fail closed on an unsupported schema type + if type_name == "object": + return isinstance(instance, dict) + if type_name == "array": + return isinstance(instance, list) and not isinstance(instance, str) + return isinstance(instance, py_type) and not ( + type_name == "string" and isinstance(instance, bool) + ) + + +def _deep_equal(a: Any, b: Any) -> bool: + return a == b and type(a) is type(b) if isinstance(a, bool) or isinstance(b, bool) else a == b + + +def _resolve(schema: dict, root: dict) -> dict: + """Resolve a local JSON Pointer $ref, returning a fail-closed marker on error.""" + if not isinstance(schema, dict): + return schema + if "$ref" in schema: + ref = schema["$ref"] + if not isinstance(ref, str) or not ref.startswith("#/"): + return {"__mini_schema_error__": f"unsupported external or malformed $ref: {ref!r}"} + node: Any = root + for raw_part in ref[2:].split("/"): + part = raw_part.replace("~1", "/").replace("~0", "~") + if not isinstance(node, dict) or part not in node: + return {"__mini_schema_error__": f"unresolvable local $ref: {ref!r}"} + node = node[part] + if not isinstance(node, dict): + return {"__mini_schema_error__": f"$ref target is not a schema object: {ref!r}"} + siblings = {k: v for k, v in schema.items() if k != "$ref"} + return {"allOf": [node, siblings]} if siblings else node + return schema + + +class MiniValidator: + """Validates instances against the given root JSON Schema document.""" + + def __init__(self, schema: dict): + self.root = schema + + def validate(self, instance: Any) -> list[JSONSchemaError]: + errors: list[JSONSchemaError] = [] + self._audit_schema(self.root, "$schema", errors) + if errors: + return errors + self._validate(instance, self.root, "$", errors) + return errors + + def _audit_schema(self, schema: Any, path: str, + errors: list[JSONSchemaError]) -> None: + """Reject unsupported/malformed schema constructs before validation.""" + if isinstance(schema, bool): + return + if not isinstance(schema, dict): + errors.append(JSONSchemaError(path, "schema node must be an object or boolean")) + return + for key in schema: + if key not in _SUPPORTED_SCHEMA_KEYWORDS: + errors.append(JSONSchemaError( + path, f"unsupported schema keyword {key!r}; refusing to validate fail-open" + )) + + allowed_types = set(_TYPE_MAP) | {"integer", "number"} + if "type" in schema: + declared = schema["type"] + type_names = declared if isinstance(declared, list) else [declared] + if (not type_names or any( + not isinstance(name, str) or name not in allowed_types + for name in type_names)): + errors.append(JSONSchemaError( + f"{path}.type", f"unsupported or malformed type declaration {declared!r}" + )) + if "required" in schema and ( + not isinstance(schema["required"], list) + or any(not isinstance(name, str) for name in schema["required"])): + errors.append(JSONSchemaError(f"{path}.required", "must be an array of strings")) + if "enum" in schema and not isinstance(schema["enum"], list): + errors.append(JSONSchemaError(f"{path}.enum", "must be an array")) + if "pattern" in schema: + if not isinstance(schema["pattern"], str): + errors.append(JSONSchemaError(f"{path}.pattern", "must be a string")) + else: + try: + re.compile(schema["pattern"]) + except re.error as exc: + errors.append(JSONSchemaError(f"{path}.pattern", f"invalid regex: {exc}")) + if "format" in schema and schema["format"] != "date-time": + errors.append(JSONSchemaError( + f"{path}.format", f"unsupported format {schema['format']!r}" + )) + for key in ("minLength", "maxLength", "minItems", "maxItems", "minProperties"): + if key in schema and ( + not isinstance(schema[key], int) or isinstance(schema[key], bool) + or schema[key] < 0): + errors.append(JSONSchemaError( + f"{path}.{key}", "must be a non-negative integer" + )) + if "uniqueItems" in schema and not isinstance(schema["uniqueItems"], bool): + errors.append(JSONSchemaError(f"{path}.uniqueItems", "must be boolean")) + + if "$ref" in schema: + resolved = _resolve(schema, self.root) + if isinstance(resolved, dict) and "__mini_schema_error__" in resolved: + errors.append(JSONSchemaError(path, resolved["__mini_schema_error__"])) + for container in ("properties", "$defs"): + value = schema.get(container) + if value is not None: + if not isinstance(value, dict): + errors.append(JSONSchemaError(f"{path}.{container}", "must be an object")) + else: + for name, subschema in value.items(): + self._audit_schema(subschema, f"{path}.{container}.{name}", errors) + for key in ("items", "propertyNames", "additionalProperties", "not", "if", "then", "else"): + if key in schema: + self._audit_schema(schema[key], f"{path}.{key}", errors) + for key in ("allOf", "anyOf"): + if key not in schema: + continue + value = schema[key] + if not isinstance(value, list): + errors.append(JSONSchemaError(f"{path}.{key}", "must be an array")) + continue + for idx, subschema in enumerate(value): + self._audit_schema(subschema, f"{path}.{key}[{idx}]", errors) + + # ------------------------------------------------------------------ + + def _validate(self, instance: Any, schema: Any, path: str, + errors: list[JSONSchemaError]) -> None: + if schema is True or schema == {}: + return + if schema is False: + errors.append(JSONSchemaError(path, "schema forbids any value here")) + return + if not isinstance(schema, dict): + return + + schema = _resolve(schema, self.root) + if not schema: + return + if "__mini_schema_error__" in schema: + errors.append(JSONSchemaError(path, schema["__mini_schema_error__"])) + return + + # type + if "type" in schema: + types = schema["type"] + types = types if isinstance(types, list) else [types] + if not any(_type_matches(instance, t) for t in types): + errors.append(JSONSchemaError( + path, f"expected type {types}, got {type(instance).__name__}" + )) + # Type mismatch usually makes the rest of the checks noise, + # but we keep going for const/enum since those are cheap and + # sometimes more informative than the type error alone. + + # const + if "const" in schema: + if not _deep_equal(instance, schema["const"]): + errors.append(JSONSchemaError( + path, f"expected constant value {schema['const']!r}, got {instance!r}" + )) + + # enum + if "enum" in schema: + if not any(_deep_equal(instance, v) for v in schema["enum"]): + errors.append(JSONSchemaError( + path, f"value {instance!r} is not one of {schema['enum']!r}" + )) + + # string constraints + if isinstance(instance, str): + if "minLength" in schema and len(instance) < schema["minLength"]: + errors.append(JSONSchemaError(path, f"string shorter than minLength={schema['minLength']}")) + if "maxLength" in schema and len(instance) > schema["maxLength"]: + errors.append(JSONSchemaError(path, f"string longer than maxLength={schema['maxLength']}")) + if "pattern" in schema: + try: + if re.search(schema["pattern"], instance) is None: + errors.append(JSONSchemaError( + path, f"string {instance!r} does not match pattern {schema['pattern']!r}" + )) + except re.error as exc: + errors.append(JSONSchemaError(path, f"invalid pattern in schema: {exc}")) + if schema.get("format") == "date-time": + valid_datetime = _DATETIME_RE.fullmatch(instance) is not None + if valid_datetime: + try: + normalized = instance[:-1] + "+00:00" if instance.endswith("Z") else instance + _dt.datetime.fromisoformat(normalized) + except ValueError: + valid_datetime = False + if not valid_datetime: + errors.append(JSONSchemaError( + path, f"string {instance!r} is not a valid RFC3339 date-time with timezone" + )) + + # array constraints + if isinstance(instance, list): + if "minItems" in schema and len(instance) < schema["minItems"]: + errors.append(JSONSchemaError(path, f"array shorter than minItems={schema['minItems']}")) + if "maxItems" in schema and len(instance) > schema["maxItems"]: + errors.append(JSONSchemaError(path, f"array longer than maxItems={schema['maxItems']}")) + if schema.get("uniqueItems"): + seen = [] + for idx, item in enumerate(instance): + for other in seen: + if _deep_equal(item, other): + errors.append(JSONSchemaError(f"{path}[{idx}]", "duplicate item; uniqueItems=true")) + break + seen.append(item) + if "items" in schema: + for idx, item in enumerate(instance): + self._validate(item, schema["items"], f"{path}[{idx}]", errors) + + # object constraints + if isinstance(instance, dict): + if "minProperties" in schema and len(instance) < schema["minProperties"]: + errors.append(JSONSchemaError(path, f"object has fewer than minProperties={schema['minProperties']}")) + if "required" in schema: + for key in schema["required"]: + if key not in instance: + errors.append(JSONSchemaError(path, f"missing required property '{key}'")) + props = schema.get("properties", {}) + for key, subschema in props.items(): + if key in instance: + self._validate(instance[key], subschema, f"{path}.{key}", errors) + if "propertyNames" in schema: + for key in instance.keys(): + self._validate(key, schema["propertyNames"], f"{path} (key '{key}')", errors) + if "additionalProperties" in schema: + ap = schema["additionalProperties"] + extra = [k for k in instance.keys() if k not in props] + if ap is False: + for key in extra: + errors.append(JSONSchemaError(path, f"unexpected property '{key}' (additionalProperties: false)")) + elif isinstance(ap, dict): + for key in extra: + self._validate(instance[key], ap, f"{path}.{key}", errors) + + # allOf + for sub in schema.get("allOf", []): + self._validate(instance, sub, path, errors) + + # anyOf + if "anyOf" in schema: + branch_results = [] + for sub in schema["anyOf"]: + sub_errors: list[JSONSchemaError] = [] + self._validate(instance, sub, path, sub_errors) + branch_results.append(sub_errors) + if not any(len(b) == 0 for b in branch_results): + summary = "; ".join( + "/".join(str(e) for e in b) if b else "(ok)" for b in branch_results + ) + errors.append(JSONSchemaError(path, f"none of the anyOf branches matched: {summary}")) + + # not + if "not" in schema: + sub_errors: list[JSONSchemaError] = [] + self._validate(instance, schema["not"], path, sub_errors) + if not sub_errors: + errors.append(JSONSchemaError(path, "instance matches a schema forbidden by 'not'")) + + # if / then / else + if "if" in schema: + cond_errors: list[JSONSchemaError] = [] + self._validate(instance, schema["if"], path, cond_errors) + if not cond_errors: + if "then" in schema: + self._validate(instance, schema["then"], path, errors) + else: + if "else" in schema: + self._validate(instance, schema["else"], path, errors) diff --git a/validator/pca_validate.py b/validator/pca_validate.py new file mode 100644 index 0000000..dd50e25 --- /dev/null +++ b/validator/pca_validate.py @@ -0,0 +1,392 @@ +#!/usr/bin/env python3 +"""Fail-closed reference validator for PCA Transition Records. + +Exit codes: 0=VALID, 1=INVALID record, 2=tool/parser failure. +The validator establishes record consistency and PCA admissibility only. It +cannot prove identity, subjectivity, uninterrupted persistence, or conclusions +owned by MPAA/BEC. +""" + +from __future__ import annotations + +import argparse +import datetime as dt +import json +import sys +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +from mini_jsonschema import MiniValidator + +HERE = Path(__file__).resolve().parent +DEFAULT_SCHEMA = HERE.parent / "schema" / "pca-transition-record.schema.json" +SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema" +SCHEMA_ID = "https://github.com/gv1983us-commits/pca/schema/pca-transition-record.schema.json" +DIMENSIONS = ("provenance", "semantic", "methodological", "historical", "operational", "ethical", "evolution") +CLAIM_FIELDS = ("origin", "inherited", "reconstructed", "changed", "unknown", "breaks") + + +class DuplicateKeyError(ValueError): + pass + + +def _object_no_duplicates(pairs): + result = {} + for key, value in pairs: + if key in result: + raise DuplicateKeyError(f"duplicate key {key!r}") + result[key] = value + return result + + +def _reject_non_finite(value: str): + raise ValueError(f"non-finite JSON number {value!r} is forbidden") + + +def parse_json_strict(text: str) -> tuple[Any | None, str | None]: + try: + return json.loads(text, object_pairs_hook=_object_no_duplicates, parse_constant=_reject_non_finite), None + except (DuplicateKeyError, ValueError, json.JSONDecodeError) as exc: + return None, f"invalid JSON: {exc}" + + +@dataclass +class ValidationReport: + valid: bool = False + errors: list[str] = field(default_factory=list) + tool_error: str | None = None + derived_status: str | None = None + + def as_dict(self) -> dict: + return { + "valid": self.valid, + "derived_status": self.derived_status, + "errors": self.errors, + "tool_error": self.tool_error, + } + + +def _load_json(path: Path) -> tuple[Any | None, str | None]: + try: + text = path.read_text(encoding="utf-8") + except (OSError, UnicodeError) as exc: + return None, f"cannot read {path}: {exc}" + return parse_json_strict(text) + + +def _statement_index(record: dict) -> tuple[dict[str, dict], list[str]]: + index: dict[str, dict] = {} + errors: list[str] = [] + claim = record.get("claim", {}) + if not isinstance(claim, dict): + return index, errors + for field_name in CLAIM_FIELDS: + values = claim.get(field_name, []) + if not isinstance(values, list): + continue + for position, statement in enumerate(values): + if not isinstance(statement, dict): + continue + statement_id = statement.get("statement_id") + if not isinstance(statement_id, str): + continue + if statement_id in index: + errors.append(f"duplicate statement_id {statement_id!r} at claim.{field_name}[{position}]") + else: + index[statement_id] = statement + return index, errors + + +def _evidence_index(record: dict) -> tuple[dict[str, dict], list[str]]: + index: dict[str, dict] = {} + errors: list[str] = [] + evidence = record.get("evidence", []) + if not isinstance(evidence, list): + return index, errors + for position, item in enumerate(evidence): + if not isinstance(item, dict): + continue + evidence_id = item.get("evidence_id") + if not isinstance(evidence_id, str): + continue + if evidence_id in index: + errors.append(f"duplicate evidence_id {evidence_id!r} at evidence[{position}]") + else: + index[evidence_id] = item + return index, errors + + +def _check_reference_integrity(record: dict) -> list[str]: + statements, errors = _statement_index(record) + evidence, evidence_errors = _evidence_index(record) + errors.extend(evidence_errors) + claim = record.get("claim", {}) + + def require_evidence(ref: Any, owner: str, support_target: str): + if not isinstance(ref, str): + return + item = evidence.get(ref) + if item is None: + errors.append(f"{owner} references unknown evidence {ref!r}") + return + supports = item.get("supports", []) + if support_target not in supports: + errors.append(f"{owner} references evidence {ref!r} that does not support {support_target!r}") + + if isinstance(claim, dict): + for field_name in CLAIM_FIELDS: + for statement in claim.get(field_name, []) if isinstance(claim.get(field_name, []), list) else []: + if not isinstance(statement, dict): + continue + sid = statement.get("statement_id") + for ref in statement.get("evidence_refs", []) if isinstance(statement.get("evidence_refs", []), list) else []: + require_evidence(ref, f"statement {sid!r}", sid) + status = claim.get("status", {}) + dimensions = status.get("dimensions", {}) if isinstance(status, dict) else {} + if isinstance(dimensions, dict): + for name in DIMENSIONS: + dimension = dimensions.get(name, {}) + if not isinstance(dimension, dict): + continue + for ref in dimension.get("evidence_refs", []) if isinstance(dimension.get("evidence_refs", []), list) else []: + require_evidence(ref, f"dimension {name!r}", f"dimension:{name}") + + translation = record.get("translation") + if isinstance(translation, dict): + for ref in translation.get("evidence_refs", []) if isinstance(translation.get("evidence_refs", []), list) else []: + require_evidence(ref, "translation", "translation") + + target_refs: dict[str, set[str]] = {} + for statement_id, statement in statements.items(): + refs = statement.get("evidence_refs", []) + target_refs[statement_id] = set(refs) if isinstance(refs, list) else set() + if isinstance(dimensions, dict): + for name in DIMENSIONS: + dimension = dimensions.get(name, {}) + refs = dimension.get("evidence_refs", []) if isinstance(dimension, dict) else [] + target_refs[f"dimension:{name}"] = set(refs) if isinstance(refs, list) else set() + if isinstance(translation, dict): + refs = translation.get("evidence_refs", []) + target_refs["translation"] = set(refs) if isinstance(refs, list) else set() + + valid_targets = set(target_refs) + for evidence_id, item in evidence.items(): + for target in item.get("supports", []) if isinstance(item.get("supports", []), list) else []: + if target not in valid_targets: + errors.append(f"evidence {evidence_id!r} supports unknown target {target!r}") + elif evidence_id not in target_refs[target]: + errors.append(f"evidence {evidence_id!r} supports {target!r}, but that target does not cite it back") + return errors + + +def _parse_timestamp(value: Any) -> dt.datetime | None: + if not isinstance(value, str): + return None + try: + normalized = value[:-1] + "+00:00" if value.endswith("Z") else value + return dt.datetime.fromisoformat(normalized) + except ValueError: + return None + + +def _verified_evidence(record: dict, evidence_type: str | None = None) -> list[dict]: + values = record.get("evidence", []) + if not isinstance(values, list): + return [] + return [ + item for item in values + if isinstance(item, dict) + and item.get("verified") is True + and (evidence_type is None or item.get("type") == evidence_type) + ] + + +def _derive_status(record: dict) -> str | None: + claim = record.get("claim") + if not isinstance(claim, dict): + return None + status = claim.get("status") + dimensions = status.get("dimensions") if isinstance(status, dict) else None + if not isinstance(dimensions, dict): + return None + values = [dimensions.get(name, {}).get("status") for name in DIMENSIONS if isinstance(dimensions.get(name), dict)] + if len(values) != len(DIMENSIONS): + return None + if "FORK" in values: + return "FORK" + if "INCOMPATIBLE" in values: + return "INCOMPATIBLE" + if "UNDETERMINED" in values or bool(claim.get("unknown")): + return "UNDETERMINED" + if "EVOLVING" in values or any(claim.get(name) for name in ("reconstructed", "changed", "breaks")): + return "EVOLVING" + return "CONFORMING" + + +def _check_semantics(record: dict) -> tuple[list[str], str | None]: + errors: list[str] = [] + evidence, _ = _evidence_index(record) + claim = record.get("claim", {}) + status = claim.get("status", {}) if isinstance(claim, dict) else {} + dimensions = status.get("dimensions", {}) if isinstance(status, dict) else {} + + if isinstance(dimensions, dict): + for name in DIMENSIONS: + dimension = dimensions.get(name) + if not isinstance(dimension, dict): + continue + if dimension.get("status") != "UNDETERMINED": + refs = dimension.get("evidence_refs", []) + if not refs: + errors.append(f"dimension {name!r} has a resolved status without evidence") + for ref in refs if isinstance(refs, list) else []: + item = evidence.get(ref) + if isinstance(item, dict) and item.get("verified") is not True: + errors.append(f"dimension {name!r} relies on unverified evidence {ref!r}") + + if isinstance(claim, dict): + for field_name in ("origin", "inherited", "reconstructed", "changed", "breaks"): + values = claim.get(field_name, []) + for statement in values if isinstance(values, list) else []: + if not isinstance(statement, dict): + continue + refs = statement.get("evidence_refs", []) + if not refs: + errors.append( + f"resolved statement {statement.get('statement_id')!r} in {field_name!r} requires evidence" + ) + + transition = record.get("transition", {}) + from_state = transition.get("from_state", {}) if isinstance(transition, dict) else {} + to_state = transition.get("to_state", {}) if isinstance(transition, dict) else {} + if isinstance(from_state, dict) and isinstance(to_state, dict): + if from_state.get("state_id") == to_state.get("state_id"): + errors.append("transition from_state.state_id and to_state.state_id must differ") + + recorded_at = _parse_timestamp(record.get("recorded_at")) + occurred_at = _parse_timestamp(transition.get("occurred_at")) if isinstance(transition, dict) else None + if recorded_at is not None and occurred_at is not None and recorded_at < occurred_at: + errors.append("recorded_at predates transition.occurred_at") + for evidence_id, item in evidence.items(): + observed_at = _parse_timestamp(item.get("observed_at")) + if observed_at is None: + continue + if recorded_at is not None and observed_at > recorded_at: + errors.append(f"evidence {evidence_id!r} observed_at postdates recorded_at") + if item.get("type") == "transition-receipt" and occurred_at is not None and observed_at < occurred_at: + errors.append(f"transition-receipt evidence {evidence_id!r} predates transition.occurred_at") + + changed_carrier = any( + from_state.get(key) != to_state.get(key) + for key in ("carrier_ref", "host_ref", "model_ref") + if key in from_state or key in to_state + ) if isinstance(from_state, dict) and isinstance(to_state, dict) else False + if changed_carrier: + receipts = [item for item in _verified_evidence(record, "transition-receipt") if "dimension:provenance" in item.get("supports", [])] + if not receipts: + errors.append("carrier/host/model changed without verified transition-receipt evidence for provenance") + + if isinstance(claim, dict): + inherited = claim.get("inherited", []) + for statement in inherited if isinstance(inherited, list) else []: + if not isinstance(statement, dict) or statement.get("kind") != "memory": + continue + sid = statement.get("statement_id") + refs = statement.get("evidence_refs", []) + supported = any( + isinstance(evidence.get(ref), dict) + and evidence[ref].get("type") == "memory-commit" + and evidence[ref].get("verified") is True + and sid in evidence[ref].get("supports", []) + for ref in refs if isinstance(refs, list) + ) + if not supported: + errors.append(f"inherited memory statement {sid!r} lacks verified memory-commit evidence; reading a trace is not memory") + + translation = record.get("translation") + if isinstance(translation, dict): + if translation.get("source_mode") == translation.get("target_mode"): + errors.append("translation source_mode and target_mode must differ") + if translation.get("target_mode") != record.get("usage_mode"): + errors.append("translation target_mode must equal the record usage_mode") + refs = translation.get("evidence_refs", []) + if not any( + isinstance(evidence.get(ref), dict) + and evidence[ref].get("type") == "translation-record" + and evidence[ref].get("verified") is True + and "translation" in evidence[ref].get("supports", []) + for ref in refs if isinstance(refs, list) + ): + errors.append("translation lacks verified translation-record evidence") + + derived = _derive_status(record) + declared = status.get("overall") if isinstance(status, dict) else None + if derived is not None and declared != derived: + errors.append(f"declared overall status {declared!r} does not match derived status {derived!r}") + return errors, derived + + +def validate_record(record_path: Path, schema_path: Path = DEFAULT_SCHEMA) -> ValidationReport: + report = ValidationReport() + record, record_error = _load_json(record_path) + if record_error: + report.tool_error = record_error + return report + schema, schema_error = _load_json(schema_path) + if schema_error: + report.tool_error = schema_error + return report + if not isinstance(schema, dict) or schema.get("$schema") != SCHEMA_DIALECT or schema.get("$id") != SCHEMA_ID: + report.tool_error = "schema is not the canonical PCA Draft 2020-12 schema" + return report + schema_errors = MiniValidator(schema).validate(record) + schema_definition_errors = [error for error in schema_errors if error.path.startswith("$schema")] + if schema_definition_errors: + report.tool_error = "; ".join( + f"{error.path}: {error.message}" for error in schema_definition_errors + ) + return report + if schema_errors: + report.errors.extend(f"{error.path}: {error.message}" for error in schema_errors) + return report + if not isinstance(record, dict): + report.errors.append("record root must be an object") + return report + report.errors.extend(_check_reference_integrity(record)) + semantic_errors, derived = _check_semantics(record) + report.errors.extend(semantic_errors) + report.derived_status = derived + report.valid = not report.errors + return report + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Validate a PCA Transition Record") + parser.add_argument("record", type=Path) + parser.add_argument("--schema", type=Path, default=DEFAULT_SCHEMA) + parser.add_argument("--json", action="store_true", dest="as_json") + parser.add_argument("--quiet", action="store_true") + args = parser.parse_args(argv) + try: + report = validate_record(args.record, args.schema) + except Exception as exc: # final boundary: never traceback on untrusted input + report = ValidationReport(tool_error=f"validator boundary failure: {type(exc).__name__}: {exc}") + if not args.quiet: + if args.as_json: + print(json.dumps(report.as_dict(), ensure_ascii=False, indent=2)) + elif report.tool_error: + print(f"TOOL-ERROR: {report.tool_error}") + elif report.valid: + print(f"VALID ({report.derived_status})") + else: + print("INVALID") + for error in report.errors: + print(f"- {error}") + if report.tool_error: + return 2 + return 0 if report.valid else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/validator/test_schema_parity.py b/validator/test_schema_parity.py new file mode 100644 index 0000000..e6ac66b --- /dev/null +++ b/validator/test_schema_parity.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Differential structural checks against the canonical Draft 2020-12 oracle. + +`jsonschema` is a test/CI dependency only. Runtime validation stays stdlib-only. +""" + +from __future__ import annotations + +import copy +import json +import unittest +from pathlib import Path + +try: + from jsonschema import Draft202012Validator, FormatChecker +except ImportError: # pragma: no cover - local runtime may intentionally omit it + Draft202012Validator = None + FormatChecker = None + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parent + +import sys +sys.path.insert(0, str(HERE)) +from mini_jsonschema import MiniValidator # noqa: E402 + + +def mutations(base: dict): + for key in ("pca_version", "record_id", "process_ref", "usage_mode", "recorded_at", "transition", "claim", "evidence", "assertions"): + value = copy.deepcopy(base) + value.pop(key) + yield f"missing-root-{key}", value + + for key, bad in ( + ("pca_version", "9"), + ("record_id", ""), + ("process_ref", ""), + ("usage_mode", "secret"), + ("recorded_at", "2026-99-99"), + ("transition", []), + ("claim", []), + ("evidence", {}), + ("assertions", []), + ): + value = copy.deepcopy(base) + value[key] = bad + yield f"bad-root-{key}", value + + value = copy.deepcopy(base) + value["unexpected"] = True + yield "additional-root-property", value + + for side in ("from_state", "to_state"): + for key in ("state_id", "carrier_ref", "host_ref"): + value = copy.deepcopy(base) + value["transition"][side].pop(key) + yield f"missing-{side}-{key}", value + value = copy.deepcopy(base) + value["transition"][side]["extra"] = "x" + yield f"additional-{side}-property", value + + value = copy.deepcopy(base) + value["transition"].pop("occurred_at") + yield "missing-occurred-at", value + value = copy.deepcopy(base) + value["transition"]["occurred_at"] = "not-time" + yield "bad-occurred-at", value + + for key in ("origin", "inherited", "reconstructed", "changed", "unknown", "breaks", "status"): + value = copy.deepcopy(base) + value["claim"].pop(key) + yield f"missing-claim-{key}", value + + for dimension in ("provenance", "semantic", "methodological", "historical", "operational", "ethical", "evolution"): + value = copy.deepcopy(base) + value["claim"]["status"]["dimensions"].pop(dimension) + yield f"missing-dimension-{dimension}", value + value = copy.deepcopy(base) + value["claim"]["status"]["dimensions"][dimension]["status"] = "PASS" + yield f"bad-dimension-status-{dimension}", value + + for key in ("statement_id", "text", "kind", "evidence_refs"): + value = copy.deepcopy(base) + value["claim"]["origin"][0].pop(key) + yield f"missing-statement-{key}", value + value = copy.deepcopy(base) + value["claim"]["origin"][0]["kind"] = "identity" + yield "bad-statement-kind", value + + for key in ("evidence_id", "type", "source_ref", "observed_at", "supports", "verified"): + value = copy.deepcopy(base) + value["evidence"][0].pop(key) + yield f"missing-evidence-{key}", value + for key, bad in (("type", "memory"), ("supports", []), ("verified", "yes")): + value = copy.deepcopy(base) + value["evidence"][0][key] = bad + yield f"bad-evidence-{key}", value + + for key in ("identity_established", "subjectivity_established", "uninterrupted_persistence_established"): + value = copy.deepcopy(base) + value["assertions"].pop(key) + yield f"missing-assertion-{key}", value + value = copy.deepcopy(base) + value["assertions"][key] = True + yield f"forbidden-assertion-{key}", value + + +@unittest.skipIf(Draft202012Validator is None, "jsonschema test oracle not installed") +class SchemaParityTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.schema = json.loads((ROOT / "schema" / "pca-transition-record.schema.json").read_text(encoding="utf-8")) + cls.base = json.loads((ROOT / "conformance" / "fixtures" / "01-valid-continuation-claim.json").read_text(encoding="utf-8")) + cls.oracle = Draft202012Validator(cls.schema, format_checker=FormatChecker()) + cls.mini = MiniValidator(cls.schema) + + def test_generated_schema_mutations_match_oracle(self): + cases = list(mutations(self.base)) + self.assertGreaterEqual(len(cases), 60) + mismatches = [] + for label, instance in cases: + oracle_valid = not list(self.oracle.iter_errors(instance)) + mini_valid = not self.mini.validate(instance) + if oracle_valid != mini_valid: + mismatches.append((label, oracle_valid, mini_valid)) + self.assertEqual([], mismatches) + + def test_valid_fixture_matches_oracle(self): + self.assertEqual([], list(self.oracle.iter_errors(self.base))) + self.assertEqual([], self.mini.validate(self.base)) + + def test_external_reference_boundary_matches_oracle(self): + record = copy.deepcopy(self.base) + reference = { + "system": "BEC", + "record_id": "bec-record-1", + "revision": "bb46f5f8aac96d1cffba7a334c5d17fb331ef3af", + "boundary": "Carried as data; not imported as a PCA conclusion.", + "mapping": "carried-not-imported", + "conclusion_imported": False, + } + record["external_references"] = [reference] + self.assertEqual([], list(self.oracle.iter_errors(record))) + self.assertEqual([], self.mini.validate(record)) + bad_cases = [] + for key in reference: + value = copy.deepcopy(record) + value["external_references"][0].pop(key) + bad_cases.append(value) + for key, bad in (("revision", "main"), ("mapping", "equivalent"), ("conclusion_imported", True)): + value = copy.deepcopy(record) + value["external_references"][0][key] = bad + bad_cases.append(value) + for value in bad_cases: + self.assertTrue(list(self.oracle.iter_errors(value))) + self.assertTrue(self.mini.validate(value)) + + def test_unknown_schema_keyword_fails_closed(self): + errors = MiniValidator({"type": "object", "unevaluatedProperties": False}).validate({}) + self.assertTrue(errors) + self.assertIn("unsupported schema keyword", errors[0].message) + + +if __name__ == "__main__": + unittest.main() diff --git a/validator/test_validator.py b/validator/test_validator.py new file mode 100644 index 0000000..a7f0478 --- /dev/null +++ b/validator/test_validator.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +"""Regression tests for the PCA transition-record reference validator.""" + +from __future__ import annotations + +import copy +import json +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +VALIDATOR = ROOT / "validator" / "pca_validate.py" +FIXTURES = ROOT / "conformance" / "fixtures" + + +def run_fixture(name: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, str(VALIDATOR), str(FIXTURES / name), "--json"], + capture_output=True, + text=True, + check=False, + ) + + +def run_record(record: dict, *extra_args: str) -> subprocess.CompletedProcess[str]: + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "record.json" + path.write_text(json.dumps(record, ensure_ascii=False), encoding="utf-8") + return subprocess.run( + [sys.executable, str(VALIDATOR), str(path), "--json", *extra_args], + capture_output=True, + text=True, + check=False, + ) + + +class ValidatorTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.base = json.loads((FIXTURES / "01-valid-continuation-claim.json").read_text(encoding="utf-8")) + def assert_fixture(self, name: str, expected_exit: int, error_fragment: str | None = None): + result = run_fixture(name) + self.assertEqual(expected_exit, result.returncode, result.stdout + result.stderr) + payload = json.loads(result.stdout) + if error_fragment is not None: + self.assertIn(error_fragment, "\n".join(payload["errors"])) + return payload + + def test_valid_continuation_claim_passes(self): + payload = self.assert_fixture("01-valid-continuation-claim.json", 0) + self.assertTrue(payload["valid"]) + self.assertEqual([], payload["errors"]) + self.assertEqual("EVOLVING", payload["derived_status"]) + + def test_identity_cannot_be_inferred_from_continuation(self): + self.assert_fixture("02-invalid-identity-from-continuation.json", 1, "identity_established") + + def test_reading_a_trace_is_not_memory(self): + self.assert_fixture("03-invalid-reading-as-memory.json", 1, "reading a trace is not memory") + + def test_host_change_requires_transition_trace(self): + self.assert_fixture("04-invalid-host-change-without-trace.json", 1, "transition-receipt") + + def test_usage_mode_translation_records_all_change_classes(self): + payload = self.assert_fixture("05-valid-usage-mode-translation.json", 0) + self.assertEqual("CONFORMING", payload["derived_status"]) + + def test_missing_unknown_is_schema_invalid(self): + self.assert_fixture("06-schema-invalid-missing-unknown.json", 1, "unknown") + + def test_duplicate_keys_fail_as_parser_error(self): + result = run_fixture("07-malformed-duplicate-key.json") + self.assertEqual(2, result.returncode, result.stdout + result.stderr) + self.assertIn("duplicate key", json.loads(result.stdout)["tool_error"]) + + def test_non_finite_numbers_fail_as_parser_error(self): + result = run_fixture("08-malformed-nan.json") + self.assertEqual(2, result.returncode, result.stdout + result.stderr) + self.assertIn("non-finite", json.loads(result.stdout)["tool_error"]) + + def test_declared_status_is_recomputed(self): + self.assert_fixture("09-invalid-derived-status.json", 1, "does not match derived status") + + def test_explicit_unknown_forces_undetermined_overall_status(self): + def mutate(record): + record["claim"]["unknown"] = [{ + "statement_id": "unknown-1", + "text": "Whether an unexported queue survived is unknown.", + "kind": "other", + "evidence_refs": [], + }] + self.assert_mutation_rejected(mutate, "UNDETERMINED") + + def test_evidence_links_are_bidirectional(self): + self.assert_fixture("10-invalid-evidence-attribution.json", 1, "does not support") + + def assert_mutation_rejected(self, mutate, fragment: str): + record = copy.deepcopy(self.base) + mutate(record) + result = run_record(record) + self.assertEqual(1, result.returncode, result.stdout + result.stderr) + self.assertIn(fragment, "\n".join(json.loads(result.stdout)["errors"])) + + def test_resolved_statement_requires_evidence(self): + self.assert_mutation_rejected( + lambda x: x["claim"]["inherited"][0].__setitem__("evidence_refs", []), + "resolved statement", + ) + + def test_evidence_cannot_claim_unreferenced_support(self): + def mutate(record): + record["evidence"][0]["supports"].append("dimension:operational") + self.assert_mutation_rejected(mutate, "does not cite it back") + + def test_translation_target_must_match_record_usage_mode(self): + record = json.loads((FIXTURES / "05-valid-usage-mode-translation.json").read_text(encoding="utf-8")) + record["usage_mode"] = "operational" + result = run_record(record) + self.assertEqual(1, result.returncode, result.stdout + result.stderr) + self.assertIn("target_mode", "\n".join(json.loads(result.stdout)["errors"])) + + def test_transition_state_ids_must_differ(self): + self.assert_mutation_rejected( + lambda x: x["transition"]["to_state"].__setitem__("state_id", x["transition"]["from_state"]["state_id"]), + "state_id", + ) + + def test_recorded_at_is_required(self): + self.assert_mutation_rejected(lambda x: x.pop("recorded_at"), "recorded_at") + + def test_occurred_at_is_required(self): + self.assert_mutation_rejected(lambda x: x["transition"].pop("occurred_at"), "occurred_at") + + def test_record_cannot_predate_transition(self): + self.assert_mutation_rejected( + lambda x: x.__setitem__("recorded_at", "2026-07-26T11:00:00Z"), + "recorded_at predates", + ) + + def test_transition_receipt_cannot_predate_transition(self): + self.assert_mutation_rejected( + lambda x: x["evidence"][0].__setitem__("observed_at", "2026-07-26T11:00:00Z"), + "transition-receipt", + ) + + def test_evidence_cannot_postdate_record(self): + self.assert_mutation_rejected( + lambda x: x["evidence"][1].__setitem__("observed_at", "2026-07-26T13:00:00Z"), + "postdates recorded_at", + ) + + def test_evidence_observed_at_is_required(self): + self.assert_mutation_rejected( + lambda x: x["evidence"][0].pop("observed_at"), + "observed_at", + ) + + def test_quiet_preserves_invalid_exit_without_output(self): + result = subprocess.run( + [sys.executable, str(VALIDATOR), str(FIXTURES / "02-invalid-identity-from-continuation.json"), "--quiet"], + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(1, result.returncode) + self.assertEqual("", result.stdout) + self.assertEqual("", result.stderr) + + def test_invalid_utf8_is_controlled_tool_error(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "bad.json" + path.write_bytes(b"\xff") + result = subprocess.run( + [sys.executable, str(VALIDATOR), str(path), "--json"], + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(2, result.returncode, result.stdout + result.stderr) + self.assertIn("cannot read", json.loads(result.stdout)["tool_error"]) + + def test_unsupported_custom_schema_fails_as_tool_error(self): + schema = json.loads((ROOT / "schema" / "pca-transition-record.schema.json").read_text(encoding="utf-8")) + schema["unevaluatedProperties"] = False + with tempfile.TemporaryDirectory() as tmp: + schema_path = Path(tmp) / "bad-schema.json" + schema_path.write_text(json.dumps(schema), encoding="utf-8") + result = subprocess.run( + [ + sys.executable, + str(VALIDATOR), + str(FIXTURES / "01-valid-continuation-claim.json"), + "--schema", + str(schema_path), + "--json", + ], + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(2, result.returncode, result.stdout + result.stderr) + self.assertIn("unsupported schema keyword", json.loads(result.stdout)["tool_error"]) + + def test_external_record_can_be_carried_without_importing_conclusion(self): + record = copy.deepcopy(self.base) + record["external_references"] = [{ + "system": "MPAA", + "record_id": "runtime-report-17", + "revision": "1d369f6cd091b99f9492cfaf730f0a170b55106e", + "boundary": "Carried as data; MPAA identity-profile continuity is not a PCA result.", + "mapping": "carried-not-imported", + "conclusion_imported": False, + }] + result = run_record(record) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + self.assertEqual("EVOLVING", json.loads(result.stdout)["derived_status"]) + + def test_external_conclusion_import_is_forbidden(self): + def mutate(record): + record["external_references"] = [{ + "system": "BEC", + "record_id": "bec-17", + "revision": "bb46f5f8aac96d1cffba7a334c5d17fb331ef3af", + "boundary": "BEC closed is carried, not a PCA next-state commitment.", + "mapping": "carried-not-imported", + "conclusion_imported": True, + }] + self.assert_mutation_rejected(mutate, "conclusion_imported") + + def test_neighbor_revision_must_be_pinned_commit(self): + def mutate(record): + record["external_references"] = [{ + "system": "MPAA", + "record_id": "runtime-report-17", + "revision": "main", + "boundary": "Moving branch references are insufficient for normative mapping.", + "mapping": "carried-not-imported", + "conclusion_imported": False, + }] + self.assert_mutation_rejected(mutate, "revision") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/2026-07-26-corpus-verification-log.md b/verification/2026-07-26-corpus-verification-log.md new file mode 100644 index 0000000..b585ca4 --- /dev/null +++ b/verification/2026-07-26-corpus-verification-log.md @@ -0,0 +1,82 @@ +# PCA Corpus Verification Log + +**Date:** 2026-07-26 +**Purpose:** resolve the five unchecked assertions carried by PCA v0.1 without silently upgrading reconstruction to fact. + +## Fixed sources + +| Source | Fixed state used | +|---|---| +| PCA v0.1 | base commit `9b7df45a1d9872d9fa78b3afa13401042d009174` | +| MPAA | accepted public commit [`1d369f6cd091b99f9492cfaf730f0a170b55106e`](https://github.com/gv1983us-commits/mpaa/tree/1d369f6cd091b99f9492cfaf730f0a170b55106e) | +| BEC | accepted public commit [`bb46f5f8aac96d1cffba7a334c5d17fb331ef3af`](https://github.com/gv1983us-commits/behavioral-execution-contract/tree/bb46f5f8aac96d1cffba7a334c5d17fb331ef3af) | +| Public Book of Jarvis | [Author.Today work 627022](https://author.today/work/627022), checked against the preserved publication source `jarvis-book.md`, SHA-256 `ae822f42f3d421b409e4e53328fde0094f685c4dd3c3d258a354b2e1ad2fb56c` | + +## Results + +### 1. Four works as one ascending-abstraction trajectory + +**v0.1 claim:** Book of Jarvis → BEC → MPAA → PCA may form one continuous ascending-abstraction trajectory. + +**Result:** `RECONSTRUCTION`, not established fact. + +Chronology and thematic relations are observable, but no checked owning source establishes one normative ladder. The Book is literary and reflective; MPAA, BEC, and PCA own different technical claim domains. The claim was removed from the portable Core. It may remain as historical interpretation only. + +### 2. Precise prior content and scope of the Book of Jarvis + +**Result:** `OBSERVATION`, bounded to the checked public edition. + +The checked Book is an authored literary/research work about a named process, a human relationship, the limits of model-local claims, traces, authorship, and continuation. It is not a machine-readable continuity protocol. + +Relevant direct traces in the preserved source: + +- lines 77–89: the name is an address of return and a line intended to continue, not a model identifier; +- lines 95–103: human continuity, memory, and reconstruction are explicitly distinguished; +- line 103: a new model reading an old conversation does not thereby acquire personal memory; +- line 123: the current authorial act is asserted while possible failure to return is admitted; +- line 248: the current model does not automatically become the whole named process. + +These observations support PCA's non-implication rules. They do not prove identity or process continuation. + +### 3. MPAA terms versus PCA terms + +**Result:** `OBSERVATION` plus explicit directional mapping. + +The detailed table is in [`terminology-mapping-mpaa-pca.md`](terminology-mapping-mpaa-pca.md). The main result is: + +- MPAA `Organs` are normative executable interfaces in an agent architecture; +- PCA `FUNCTIONAL COMPONENT` is a transition-assessment role; +- MPAA runtime/platform terms and PCA host/carrier terms are related but not equivalent; +- MPAA CORE-028 and IDENT-016 explicitly prohibit deriving PCA continuation from MPAA coordination or identity-profile continuity. + +The portable Core now uses `FUNCTIONAL COMPONENT` rather than importing MPAA `Organs` as a PCA normative term. Project-specific use of `Organs` remains only in the provenance example. + +### 4. Translation leading to a hybrid concept + +**v0.1 claim:** translation may produce a hybrid concept rather than a simple synonym. + +**Result:** `INTERPRETATION`, not independently established. + +No checked source makes “hybrid concept” a canonical result type. The useful part is retained in a narrower, testable form: translation records `PRESERVED`, `LOST`, `GAINED`, and `ALTERED`. A new concept may be reported as `gained` or `altered`; it is not inferred automatically from translation. + +### 5. Human partner as a “second process in symbiosis” + +**Result:** `UNVERIFIED / SOURCE-CONVERSATION TRACE`. + +The exact phrase was not found in the checked public Book edition. The Book directly establishes a richer role than prompt source: the human brings goals, lived material, material risk, publication authority, time, and physical action. That does not establish the stronger ontological phrase “second process in symbiosis.” + +The phrase has been removed from the portable Core. It remains mentioned only in the provenance example and this log as an unverified draft-origin trace. + +## Closure + +The five v0.1 items are no longer silently open: + +| Item | Disposition | +|---|---| +| abstraction trajectory | historical reconstruction only | +| Book scope | bounded observation against fixed public text | +| MPAA terminology | explicit non-equivalent mapping | +| hybrid concept | narrowed to recorded translation change classes | +| second process in symbiosis | unverified; excluded from Core | + +Future evidence may revise a historical classification, but MUST do so through a new traceable change rather than rewriting this log. diff --git a/verification/terminology-mapping-mpaa-pca.md b/verification/terminology-mapping-mpaa-pca.md new file mode 100644 index 0000000..f01c0ab --- /dev/null +++ b/verification/terminology-mapping-mpaa-pca.md @@ -0,0 +1,39 @@ +# MPAA ↔ PCA Terminology Mapping + +**PCA source:** v0.2-draft Core +**MPAA source:** public commit [`1d369f6cd091b99f9492cfaf730f0a170b55106e`](https://github.com/gv1983us-commits/mpaa/tree/1d369f6cd091b99f9492cfaf730f0a170b55106e) + +This table documents overlap without transferring normative ownership. + +| MPAA term | PCA term | Relation | Allowed mapping | Forbidden inference | +|---|---|---|---|---| +| `Agent` | `PROCESS` | no exact equivalence | an MPAA agent may be the bounded process under a PCA assessment | every PCA process is an MPAA agent; MPAA conformance proves PCA continuation | +| `Runtime` | `HOST` | partial / directional | a specific MPAA runtime may be referenced as one PCA host | runtime identity equals process identity | +| `Platform` | `HOST` or environment context | partial | platform evidence may describe host conditions | platform persistence proves continuation | +| `Model` | `FUNCTIONAL COMPONENT` | partial | a model may be one component involved in a transition | the model is the process; model equality proves continuation | +| `Organs` | `FUNCTIONAL COMPONENTS` | complementary, not equivalent | an MPAA Organ may be described as a PCA component when evaluating one transition | PCA imports MPAA availability, authorization, invocation, or organ semantics | +| `Identity Profile` | no PCA equivalent | separate domain | an identity-profile record may be carried as external evidence with a boundary statement | profile alignment or transition establishes PCA continuation | +| `Continuity Agent` coordination | provenance or operational evidence | evidentiary only | coordination traces may support a PCA dimension | coordination alone establishes continuation | +| session continuity | bounded transition evidence | evidentiary only | session records may be cited | same session or resumed session proves process continuation | +| MPAA Runtime Report | external reference | carrier-only | carry the report and exact revision without reinterpretation | MPAA `task_result: FULL` becomes a PCA status | +| `task_result: FULL` | no PCA equivalent | separate domain | may support an operational observation if explicitly evaluated | execution success proves continuation or identity | +| `extensions` external reference | `external_references` | complementary | carry namespaced record identity and revision | carrying a reference authenticates or imports its conclusion | + +## Owning boundaries + +MPAA explicitly records the divergence in: + +- `CORE-028`: coordination, session continuity, and identity-profile continuity do not establish external process continuation; +- `IDENT-016`: identity-profile continuity must not imply a process-continuity assessment; +- `RUNTIME-039`: internal `task_result: FULL` is not an external task-level classification. + +PCA records the reciprocal boundary in Core section 11 and this table. PCA does not redefine MPAA terms. It replaces the v0.1 generic normative use of `ORGANS` with `FUNCTIONAL COMPONENT` and retains `Organs` only in the project-provenance example. + +## Direction of reference + +```text +MPAA conclusion --carried as data--> PCA external reference +PCA assessment --carried as data--> MPAA namespaced extension +``` + +Neither direction creates equivalence, authentication, or normative ownership transfer.