Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ release would carry the corpus.

## Status

`0.4.0` adds optional operating-record storage against `paa-operating-record/0.1.0-draft` and preserves optional worker attribution in `paa-evidence-record/0.2.0-draft`. Existing `paa-evidence-record/0.1.0-draft` records without worker attribution remain valid. No evidence files or event histories require migration. Task and event families remain `paa-task/0.2.1-draft` and `paa-autonomy-event/0.1.0-draft`. Package and schema-family versions are independent; this PR prepares release versions but does not publish packages.
`0.4.0` adds optional operating-record storage against `paa-operating-record/0.1.0-draft`. `paa-evidence-record/0.3.0-draft` adds native numeric verdicts while preserving strings and the optional worker attribution introduced in 0.2. Existing 0.1 and 0.2 records remain valid and string-only. No evidence files or event histories require migration. Task and event families remain `paa-task/0.2.1-draft` and `paa-autonomy-event/0.1.0-draft`. Package and schema-family versions are independent; this PR prepares release versions but does not publish packages.

`0.3.0` introduced a breaking change to the declaration access layer to align it with the published task family. `0.2.0` claimed the `paa-task/0.2.1-draft` family while implementing an older evaluator identity — a single `oracle` field where the contract has `evaluation_basis` and `epistemic_status` — and a `position_policy` requiring all four positions at fixed modes, where the contract admits any non-empty subset with per-evaluator placement overrides. It could not load a single published declaration. Building the conformance suite is what surfaced that; `PaaEvaluator`, `ProducerRegistration`, and `PaaPositionPolicy` changed shape to fix it. Nothing was published at `0.2.0`, so no consumer is stranded.

Expand Down
6 changes: 3 additions & 3 deletions conformance/test_corpus_integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,16 @@ class TestTheCorpusIsWhatItClaims:
"""

def test_the_case_table_count_is_pinned(self) -> None:
assert len(ALL_CASES) == 147
assert len(ALL_CASES) == 153

def test_the_structural_case_count_is_pinned(self) -> None:
assert len(STRUCTURAL_CASES) == 102
assert len(STRUCTURAL_CASES) == 108

def test_fifteen_of_them_are_pinned(self) -> None:
assert len(PINNED_CASES) == 15

def test_every_published_fixture_is_discoverable(self) -> None:
assert len(PUBLISHED_FIXTURES) == 22
assert len(PUBLISHED_FIXTURES) == 23


class TestFormatAssertionIsLive:
Expand Down
80 changes: 77 additions & 3 deletions conformance/test_evidence_integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

import paa_contracts as contracts
import pytest
from jsonschema import Draft202012Validator
import yaml
from jsonschema import Draft7Validator, Draft202012Validator, FormatChecker
from referencing import Registry, Resource

from paa_runtime import (
EvidenceError,
Expand All @@ -41,13 +43,13 @@ def _sha_from_path(path: Path) -> str:

class TestCorpusIsPresent:
def test_evidence_records_are_discoverable(self) -> None:
assert len(contracts.evidence_record_paths()) == 4
assert len(contracts.evidence_record_paths()) == 5

def test_decision_artifacts_are_discoverable(self) -> None:
assert len(contracts.decision_artifact_paths()) == 5

def test_payload_schemas_are_discoverable(self) -> None:
assert len(contracts.payload_schema_paths()) == 2
assert len(contracts.payload_schema_paths()) == 3


class TestContentAddressing:
Expand Down Expand Up @@ -132,6 +134,78 @@ class TestPayloadSchemas:
def test_payload_schema_is_a_valid_schema(self, path: Path) -> None:
Draft202012Validator.check_schema(json.loads(path.read_text(encoding="utf-8")))

@pytest.mark.parametrize(
"path", contracts.evidence_record_paths(), ids=lambda p: p.parent.name[:12],
)
def test_evidence_validates_against_its_companion_schema(self, path: Path) -> None:
document = json.loads(path.read_bytes())
_companion_validator(document["payload_schema"]).validate(document)

@pytest.mark.parametrize("value", [0, 0.875, 1])
def test_scalar_companion_accepts_inclusive_numeric_range(self, value: float) -> None:
document = _numeric_evidence()
document["verdict"]["value"] = value
_companion_validator(document["payload_schema"]).validate(document)

@pytest.mark.parametrize("value", [-0.01, 1.01, "0.875", True, None])
def test_scalar_companion_rejects_invalid_verdicts(self, value: object) -> None:
document = _numeric_evidence()
document["verdict"]["value"] = value
assert not _companion_validator(document["payload_schema"]).is_valid(document)

@pytest.mark.parametrize("field", ["run_id", "cell_id", "evaluator_id", "plan_ref"])
def test_scalar_companion_requires_lineage(self, field: str) -> None:
document = _numeric_evidence()
del document["payload"][field]
assert not _companion_validator(document["payload_schema"]).is_valid(document)


def _companion_validator(schema_id: str) -> Draft7Validator:
"""Resolve the base envelope entirely from the published local corpus."""
base = contracts.load_schema("paa-evidence-record")
registry = Registry().with_resource(base["$id"], Resource.from_contents(base))
companions = {
document["$id"]: document
for path in contracts.payload_schema_paths()
for document in [json.loads(path.read_bytes())]
}
return Draft7Validator(
companions[schema_id], registry=registry, format_checker=FormatChecker(),
)


def _numeric_evidence() -> dict:
return next(
document
for path in contracts.evidence_record_paths()
for document in [json.loads(path.read_bytes())]
if document["record_schema"] == "paa-evidence-record/0.3.0-draft"
)


class TestEvidenceDeclarations:
"""Positive fixtures must reference real declared task/evaluator identities.

These are corpus-integrity assertions, not a new foreign-record import API.
Schema-valid evidence alone cannot establish that its task exists.
"""

@pytest.mark.parametrize(
"path", contracts.evidence_record_paths(), ids=lambda p: p.parent.name[:12],
)
def test_evidence_matches_a_published_declaration(self, path: Path) -> None:
declarations = {
document["task"]: document
for task_path in contracts.task_declaration_paths()
for document in [yaml.safe_load(task_path.read_text(encoding="utf-8"))]
}
record = json.loads(path.read_bytes())
assert record["task"] in declarations, "evidence.unknown_task"
declaration = declarations[record["task"]]
assert record["declaration_version"] == declaration["version"]
assert record["scope"] in declaration.get("scopes", [None])
assert record["evaluator"] in declaration["evaluators"]


class TestUnownedStages:
"""Runtime-artifact semantics this implementation does not validate.
Expand Down
26 changes: 26 additions & 0 deletions conformance/test_operating_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,32 @@ def test_current_evidence_without_worker_remains_valid() -> None:
assert violations("evidence", record) == ()


def test_every_retained_evidence_version_has_a_valid_fixture() -> None:
versions = {
json.loads(path.read_bytes())["record_schema"]
for path in contracts.evidence_record_paths()
}
assert versions == {
"paa-evidence-record/0.1.0-draft",
"paa-evidence-record/0.2.0-draft",
"paa-evidence-record/0.3.0-draft",
}


def test_numeric_verdict_is_native_in_v03() -> None:
records = [json.loads(path.read_bytes()) for path in contracts.evidence_record_paths()]
numeric = next(record for record in records if isinstance(record["verdict"]["value"], float))
assert numeric["record_schema"] == "paa-evidence-record/0.3.0-draft"
assert violations("evidence", numeric) == ()


def test_string_verdict_remains_valid_in_v03() -> None:
record = json.loads(contracts.evidence_record_paths()[0].read_bytes())
record["record_schema"] = "paa-evidence-record/0.3.0-draft"
record["verdict"]["value"] = "pass"
assert violations("evidence", record) == ()


def test_operating_records_do_not_change_motion_outcomes(
runtime_config: RuntimeConfig, tmp_path: Path,
) -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"boundary":{"input_ref":"sha256:2222222222222222222222222222222222222222222222222222222222222222","output_ref":"sha256:3333333333333333333333333333333333333333333333333333333333333333"},"declaration_version":1,"evaluator":{"authority":"advisory","epistemic_status":"proxy","evaluation_basis":{"kind":"rubric","ref":"response_quality_rubric"},"property":"response_quality","target":"output","technique":"llm_judge","version":"1"},"payload":{"arm_id":"candidate","base_subject_ref":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","cell_id":"s7:candidate:w2","detail_refs":["sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"],"evaluator_id":"quality","evaluator_repeat":0,"plan_ref":"sha256:1111111111111111111111111111111111111111111111111111111111111111","run_id":"run-1","worker_repeat":2},"payload_schema":"https://paa.dev/payload-schemas/assay-scalar-quality.schema.json","producer":{"id":"assay","version":"0.1.0"},"record_id":"run-1:s7:candidate:w2:quality:e0","record_schema":"paa-evidence-record/0.3.0-draft","scope":null,"source_references":["sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"],"subject":{"id":"s7:candidate:w2","kind":"run"},"task":"inbound_reply_surfacing","timestamps":{"completed_at":"2026-09-10T17:00:02.000Z","recorded_at":"2026-09-10T17:00:02.500Z","started_at":"2026-09-10T17:00:00.000Z"},"verdict":{"reason_codes":["within_range"],"value":0.875},"worker":{"configuration_ref":"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","id":"jig-agent","version":"candidate-v1"}}
41 changes: 41 additions & 0 deletions examples/runtime-conformance/invalid/evidence-cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -237,5 +237,46 @@
"code": "not",
"path": ""
}
},
{
"id": "evidence_numeric_verdict_v01",
"base": "evidence/paa/772600716f1b221836cd5f0732e7633e38edb6903e48f463dbfb4c97f848672a/evidence.json",
"mutations": [
{ "kind": "set", "path": "/record_schema", "value": "paa-evidence-record/0.1.0-draft" },
{ "kind": "remove", "path": "/worker" }
],
"expected": { "stage": "structural", "code": "type", "path": "/verdict/value" }
},
{
"id": "evidence_numeric_verdict_v02",
"base": "evidence/paa/772600716f1b221836cd5f0732e7633e38edb6903e48f463dbfb4c97f848672a/evidence.json",
"mutations": [
{ "kind": "set", "path": "/record_schema", "value": "paa-evidence-record/0.2.0-draft" }
],
"expected": { "stage": "structural", "code": "type", "path": "/verdict/value" }
},
{
"id": "evidence_null_verdict_v03",
"base": "evidence/paa/772600716f1b221836cd5f0732e7633e38edb6903e48f463dbfb4c97f848672a/evidence.json",
"mutations": [{ "kind": "set", "path": "/verdict/value", "value": null }],
"expected": { "stage": "structural", "code": "type", "path": "/verdict/value" }
},
{
"id": "evidence_boolean_verdict_v03",
"base": "evidence/paa/772600716f1b221836cd5f0732e7633e38edb6903e48f463dbfb4c97f848672a/evidence.json",
"mutations": [{ "kind": "set", "path": "/verdict/value", "value": true }],
"expected": { "stage": "structural", "code": "type", "path": "/verdict/value" }
},
{
"id": "evidence_array_verdict_v03",
"base": "evidence/paa/772600716f1b221836cd5f0732e7633e38edb6903e48f463dbfb4c97f848672a/evidence.json",
"mutations": [{ "kind": "set", "path": "/verdict/value", "value": [0.875] }],
"expected": { "stage": "structural", "code": "type", "path": "/verdict/value" }
},
{
"id": "evidence_object_verdict_v03",
"base": "evidence/paa/772600716f1b221836cd5f0732e7633e38edb6903e48f463dbfb4c97f848672a/evidence.json",
"mutations": [{ "kind": "set", "path": "/verdict/value", "value": { "score": 0.875 } }],
"expected": { "stage": "structural", "code": "type", "path": "/verdict/value" }
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://paa.dev/payload-schemas/assay-scalar-quality.schema.json",
"title": "Assay scalar quality evidence payload",
"description": "Narrows paa-evidence-record/0.3.0-draft for an Assay scalar quality evaluator while keeping experiment lineage in the producer-owned payload.",
"allOf": [
{ "$ref": "https://paa.dev/paa-evidence-record.schema.json" },
{
"type": "object",
"properties": {
"record_schema": { "const": "paa-evidence-record/0.3.0-draft" },
"verdict": {
"type": "object",
"properties": {
"value": { "type": "number", "minimum": 0, "maximum": 1 }
}
},
"payload": {
"type": "object",
"required": [
"run_id",
"cell_id",
"evaluator_id",
"plan_ref",
"arm_id",
"base_subject_ref",
"worker_repeat",
"evaluator_repeat",
"detail_refs"
],
"additionalProperties": false,
"properties": {
"run_id": { "type": "string", "minLength": 1 },
"cell_id": { "type": "string", "minLength": 1 },
"evaluator_id": { "type": "string", "minLength": 1 },
"plan_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
"arm_id": { "type": "string", "minLength": 1 },
"base_subject_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
"worker_repeat": { "type": "integer", "minimum": 0 },
"evaluator_repeat": { "type": "integer", "minimum": 0 },
"detail_refs": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
}
}
}
}
}
]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@paadev/paa-contracts",
"version": "0.2.0",
"version": "0.3.0",
"description": "Published contract artifacts of the Progressive Autonomy Architecture: five normative JSON Schemas, the positive fixture corpus every implementation is checked against, and the table-driven invalid-case matrices.",
"license": "MIT",
"homepage": "https://www.paa.dev",
Expand Down
11 changes: 10 additions & 1 deletion packages/paa-contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ The consequence: a wheel can only be built from a full checkout of this repo —
|---|---|
| `schemas/` | `paa-task`, `paa-evidence-record`, `paa-decision-artifact`, `paa-autonomy-event`, `paa-operating-record` |
| `examples/paa-tasks/` | four valid declarations + 63 invalid cases |
| `examples/runtime-conformance/` | evidence and operating records, decision artifacts, autonomy-event sequences, payload companion schemas, 84 invalid cases |
| `examples/runtime-conformance/` | evidence and operating records, decision artifacts, autonomy-event sequences, payload companion schemas, 90 invalid cases |
| `examples/runtime-conformance/invalid/fixtures/tampered-evidence/` | a deliberately byte-mismatched artifact, so tamper detection has something real to fail on |

## Versioning
Expand All @@ -100,6 +100,15 @@ contracts.schema_version("paa-autonomy-event") # 'paa-autonomy-event/0.1.0-dra

## Development

### Contract changes in 0.3.0

`paa-evidence-record/0.3.0-draft` admits native JSON-number verdicts while
retaining string verdicts. The `0.1.0-draft` and `0.2.0-draft` stamps remain
string-only, and worker attribution follows the existing 0.2 rules. The
positive corpus includes an Assay scalar-quality record and companion schema;
negative cases pin the version boundary and reject null, boolean, array, and
object verdicts.

### Contract changes in 0.2.0

`paa-evidence-record/0.2.0-draft` admits optional `worker` attribution. When
Expand Down
2 changes: 1 addition & 1 deletion packages/paa-contracts/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "paa-contracts"
version = "0.2.0"
version = "0.3.0"
description = "Published Progressive Autonomy Architecture contract artifacts: the five normative JSON Schemas, the positive fixture corpus, and the invalid-case tables"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
2 changes: 1 addition & 1 deletion packages/paa-contracts/src/paa_contracts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from pathlib import Path
from typing import Any, Literal, NotRequired, TypedDict

__version__ = "0.2.0"
__version__ = "0.3.0"


class ContractsUnavailableError(RuntimeError):
Expand Down
6 changes: 3 additions & 3 deletions packages/paa-contracts/tests/test_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ class TestPositiveFixtures:
[
(contracts.task_declaration_paths, 4),
(contracts.autonomy_event_paths, 5),
(contracts.evidence_record_paths, 4),
(contracts.evidence_record_paths, 5),
(contracts.operating_record_paths, 4),
(contracts.decision_artifact_paths, 5),
(contracts.payload_schema_paths, 2),
(contracts.payload_schema_paths, 3),
],
)
def test_corpus_size_is_pinned(self, accessor: object, expected_count: int) -> None:
Expand Down Expand Up @@ -121,7 +121,7 @@ def test_task_fixtures_are_yaml_not_the_invalid_case_table(self) -> None:
class TestInvalidCases:
@pytest.mark.parametrize(
("kind", "expected_count"),
[("task", 63), ("evidence", 15), ("decision", 11), ("event", 15), ("operating", 43)],
[("task", 63), ("evidence", 21), ("decision", 11), ("event", 15), ("operating", 43)],
)
def test_case_table_size_is_pinned(self, kind: str, expected_count: int) -> None:
assert len(contracts.invalid_cases(kind)) == expected_count # type: ignore[arg-type]
Expand Down
Loading
Loading