Repository navigation
M2/E4/#11 — Contract Schemas - #14
Merged
Merged
Conversation
Canonical: Milestone M2 · Epic E4 · Issue #11. Deps: #10 (complete). Eligibility recomputed mechanically from the committed plan: exactly one eligible issue, #11. No abort condition. Ten object contracts plus shared definitions, in contracts/: observation dataset-release label-release protocol environment-release method-release evaluation evidence-binding supersession provenance-record common (definitions; nothing validates against it directly) CONVENTIONS $id is urn:adityanet:contract:<name>:<major>. A URN rather than an https:// URL deliberately — an https:// $id implies a resolvable endpoint, this project serves no such endpoint, and ADR-0001 forbids implying capability the platform lacks. Every object sets additionalProperties: false. An unrecognised field is how a producer and a consumer come to disagree about a payload while both believe they conform. The single exception is environment-release.thread_counts, whose keys are set by the libraries installed rather than by the contract, and which says so. null and absent are different, and the difference is load-bearing. ingest_time is nullable with exactly one meaning (ADR-0022, L-11) and REQUIRED as a field, so absence cannot be confused with unknown. value is nullable meaning observed-to-be-absent, never imputed (ADR-0017, L-07). THREE DEFECTS, ALL MINE, ALL FOUND BY TESTS IMPL-009 Registry built with a nonexistent bulk constructor, and the no-domain-terms check scanned $id — which legitimately contains the project name. Both in the tests, not the contracts. CONTRACT `format: date-time` is ANNOTATION-ONLY in JSON Schema. No validator enforces DEFECT it unless explicitly configured with a format checker, so a timestamp constrained only by `format` is validated by some consumers and not others — worse than no constraint. The timestamp definition now carries a `pattern` as well, which every validator enforces. IMPL-010 Two bugs in the additive-diff gate, both silently disabling a check: - walk() recursed into scalars inside lists instead of capturing them, so enum members, required names and union type arrays vanished from the flattened form entirely - the added-required-field check derived its container path by string splitting, producing "//required/" at the document root, matching nothing Neither could have been found against the real corpus, because every contract here is new and the gate compared nothing. They were found only because the diff logic is tested against synthetic baselines. THE GATE NO LONGER OVERSTATES With no baseline it now reports that the additive-only comparison was NOT exercised, rather than printing "every change additive". A gate claiming a property it did not check is the failure mode this project exists to refuse. A test pins that wording. TESTS: 141 for contracts alone Per contract: valid example, unknown-field rejection, closed-object assertion, and every required field individually dropped to prove it is enforced. Plus 19 invalid values, 5 boundary values, nullability semantics, the five pinned inputs of ADR-0021 individually, the three reproduction classes, the three supersession severities, and a check that no domain term appears in any constraint. The diff logic has 12 breaking cases and 7 additive cases, all synthetic. tests/architecture 352 passed tests/integration 24 passed kernel 78 passed research/tests/v2 188 passed links / imports / contracts gates clean CI: `jsonschema` added to the architecture job. No new job, no new context. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rexy-5097
added a commit
that referenced
this pull request
Jul 30, 2026
Canonical: Milestone M2 · Epic E4 · Issue #12. Deps: #11 (complete, merged as 6c26b33). Eligibility recomputed by parsing origin/main:docs/tis/TIS-v1.0.md §15: eligible set was {#12 order 12, #14 order 14, #15 order 15}; lowest Order selects #12. WHAT THIS IS 14 entities, 8 value types and 19 invariants, under domain/. Imports the standard library and nothing else — no third party, no kernel, no context — so every test here runs with no fixtures, no mocks and no infrastructure (ADR-0026, TIS E4 §11(i), §16). TWO IDENTITY REGIMES, WHICH IS THE POINT (ADR-0005) names.py Dataset · Method · Source · Instrument · LabelSource Mutable identifiers, permitted only because no Evaluation references them. TIS E4 §11(ii) states the list; the module is the list, and a sixth fails a test. everything else Immutable, addressed by content. What an Evaluation pins. THE DOMAIN CANNOT MINT A DIGEST ADR-0005 reserves minting to the provenance kernel; ADR-0026 forbids importing it. Both hold at once because holding a content address and computing one are different operations. `domain.values.Digest` validates the 64-hex form and computes nothing — `hashlib` is stdlib and would import cleanly, so the absence is a decision, and test_the_domain_cannot_mint_a_digest pins it. Same shape for the clock: `Timestamp` parses, and there is no `now()` (TIS §0.4). THREE FIELDS ENFORCED BY THE SHAPE OF THE TYPE, NOT BY A CHECK ingest_time has NO DEFAULT. `= None` would default a provenance value for any caller who omitted it, which is precisely what ADR-0022 forbids. Omitting it raises TypeError from Python itself. Score.interval required. If the type cannot represent a score without its uncertainty, no code path can produce one (STD-05). the five inputs positional fields on Evaluation. ADR-0009 claimed four sufficed; ADR-0021 superseded it. The type cannot be built with four. 19 INVARIANTS, EVERY ONE WITH A PROPERTY TEST ALL_INVARIANTS pairs each predicate with the decision it enforces, and test_every_invariant_has_a_property_test reads that tuple — exporting a predicate without a test is a red build, not a gap. Predicates return bool and never raise: a rule that raises cannot be quantified over. "Property" here means quantified over the finite space each rule is defined on — three reproduction classes, three severities, both settings of requires_bitemporal, both states of a nullable field, and the full cross-product where two interact. There is no hypothesis in this suite and the docstring says so rather than letting "property test" imply more than it is. FEATURE-LEVEL INTEGRATION tests/integration/test_domain_contract_conformance.py is the first test to cross domain + contracts + kernel. Each subsystem was green on its own and nothing had ever exercised a path through all three. - every entity's to_dict() validates against its real contract (ADR-0019) - the two agree on REJECTION, not only acceptance — agreement on the happy path alone would be satisfied by a schema that accepted everything - a serialised observation walks the full path: contract-validated, registered in the kernel's store, chained into a provenance record, recovered via the DAG - the two Digest types, separate by architecture, agree on form - a backfilled ingest_time passes the contract and fails the invariant — the division of labour between the layers, shown on the case that motivated ADR-0022 DEFECT FOUND IN #11's MERGED CONTRACT — REPORTED, NOT REPAIRED ADR-0024 §2 states supersession records are "themselves immutable and content-addressed". supersession.schema.json carries no digest field. The gap is real. A contract is #11's to change and #12 may not edit one silently, so the identity invariant records Supersession under ADDRESSED_BY_CONTAINMENT with the discrepancy named in the comment, and the report classifies it. WORKFLOW: TWO ONE-LINE CHANGES, BOTH SO THIS ISSUE'S TESTS RUN AT ALL unit pytest kernel → pytest kernel domain integration pip install pytest → pip install pytest jsonschema Without the first, 244 tests would exist and never execute — the exact defect recorded as EPD-004 when the kernel landed, repeating one issue later. Reported as EPD-006. No new job, no new required context; still eight. conftest.py at the root: tests/integration/ is not a package, so pytest inserted that directory rather than the repo root and `import domain` failed at collection. The root conftest is pytest's own mechanism for this and fixes it for every suite at once. VERIFICATION domain 244 passed kernel 78 passed tests/architecture 405 passed (+53) tests/integration 57 passed (+33) research/tests/v2 188 passed links / imports / contracts gates clean clean export (STD-12) all suites green The contracts gate now reports "every change additive within its major" for the first time — #11's schemas are on origin/main, so the comparison ran against a real baseline instead of reporting that it had not been exercised. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rexy-5097
added a commit
that referenced
this pull request
Jul 30, 2026
Canonical: Milestone M2 · Epic E4 · Issue #12. Deps: #11 (complete, merged as 6c26b33). Eligibility recomputed by parsing origin/main:docs/tis/TIS-v1.0.md §15: eligible set was {#12 order 12, #14 order 14, #15 order 15}; lowest Order selects #12. WHAT THIS IS 14 entities, 8 value types and 19 invariants, under domain/. Imports the standard library and nothing else — no third party, no kernel, no context — so every test here runs with no fixtures, no mocks and no infrastructure (ADR-0026, TIS E4 §11(i), §16). TWO IDENTITY REGIMES, WHICH IS THE POINT (ADR-0005) names.py Dataset · Method · Source · Instrument · LabelSource Mutable identifiers, permitted only because no Evaluation references them. TIS E4 §11(ii) states the list; the module is the list, and a sixth fails a test. everything else Immutable, addressed by content. What an Evaluation pins. THE DOMAIN CANNOT MINT A DIGEST ADR-0005 reserves minting to the provenance kernel; ADR-0026 forbids importing it. Both hold at once because holding a content address and computing one are different operations. `domain.values.Digest` validates the 64-hex form and computes nothing — `hashlib` is stdlib and would import cleanly, so the absence is a decision, and test_the_domain_cannot_mint_a_digest pins it. Same shape for the clock: `Timestamp` parses, and there is no `now()` (TIS §0.4). THREE FIELDS ENFORCED BY THE SHAPE OF THE TYPE, NOT BY A CHECK ingest_time has NO DEFAULT. `= None` would default a provenance value for any caller who omitted it, which is precisely what ADR-0022 forbids. Omitting it raises TypeError from Python itself. Score.interval required. If the type cannot represent a score without its uncertainty, no code path can produce one (STD-05). the five inputs positional fields on Evaluation. ADR-0009 claimed four sufficed; ADR-0021 superseded it. The type cannot be built with four. 19 INVARIANTS, EVERY ONE WITH A PROPERTY TEST ALL_INVARIANTS pairs each predicate with the decision it enforces, and test_every_invariant_has_a_property_test reads that tuple — exporting a predicate without a test is a red build, not a gap. Predicates return bool and never raise: a rule that raises cannot be quantified over. "Property" here means quantified over the finite space each rule is defined on — three reproduction classes, three severities, both settings of requires_bitemporal, both states of a nullable field, and the full cross-product where two interact. There is no hypothesis in this suite and the docstring says so rather than letting "property test" imply more than it is. FEATURE-LEVEL INTEGRATION tests/integration/test_domain_contract_conformance.py is the first test to cross domain + contracts + kernel. Each subsystem was green on its own and nothing had ever exercised a path through all three. - every entity's to_dict() validates against its real contract (ADR-0019) - the two agree on REJECTION, not only acceptance — agreement on the happy path alone would be satisfied by a schema that accepted everything - a serialised observation walks the full path: contract-validated, registered in the kernel's store, chained into a provenance record, recovered via the DAG - the two Digest types, separate by architecture, agree on form - a backfilled ingest_time passes the contract and fails the invariant — the division of labour between the layers, shown on the case that motivated ADR-0022 DEFECT FOUND IN #11's MERGED CONTRACT — REPORTED, NOT REPAIRED ADR-0024 §2 states supersession records are "themselves immutable and content-addressed". supersession.schema.json carries no digest field. The gap is real. A contract is #11's to change and #12 may not edit one silently, so the identity invariant records Supersession under ADDRESSED_BY_CONTAINMENT with the discrepancy named in the comment, and the report classifies it. WORKFLOW: TWO ONE-LINE CHANGES, BOTH SO THIS ISSUE'S TESTS RUN AT ALL unit pytest kernel → pytest kernel domain integration pip install pytest → pip install pytest jsonschema Without the first, 244 tests would exist and never execute — the exact defect recorded as EPD-004 when the kernel landed, repeating one issue later. Reported as EPD-006. No new job, no new required context; still eight. conftest.py at the root: tests/integration/ is not a package, so pytest inserted that directory rather than the repo root and `import domain` failed at collection. The root conftest is pytest's own mechanism for this and fixes it for every suite at once. VERIFICATION domain 244 passed kernel 78 passed tests/architecture 405 passed (+53) tests/integration 57 passed (+33) research/tests/v2 188 passed links / imports / contracts gates clean clean export (STD-12) all suites green The contracts gate now reports "every change additive within its major" for the first time — #11's schemas are on origin/main, so the comparison ran against a real baseline instead of reporting that it had not been exercised. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Rexy-5097
added a commit
that referenced
this pull request
Jul 30, 2026
Canonical: Milestone M2 · Epic E4 · Issue #13. Deps: #6 ✔, #12 ✔ (530a412). Eligibility recomputed by parsing origin/main:docs/tis/TIS-v1.0.md §15: eligible set was {#13 order 13, #14 order 14, #15 order 15}; lowest Order selects #13. ADR-0026's Verification asks for "a kernel-zero-imports test plus six import-direction tests, each with a deliberate-violation case". The kernel test landed with #10 and the domain stdlib-only rule with #12. This is the six. THE RULE WAS INEXPRESSIBLE, NOT MERELY UNENFORCED `check` permitted an import whose top-level root matched `policy.root`. For `contexts.evaluation` that root is `contexts` — so importing `contexts.method` counted as importing itself, and ADR-0026's fifth dependency rule could not be *stated*, let alone violated. Demonstrated before changing anything: policy.package : contexts.evaluation policy.root : contexts permitted : ['contexts', 'contracts', 'domain'] Fixed by matching self-imports against the policy's dotted package rather than its root, via a new `imported_paths` that keeps full dotted paths. `imported_roots` is retained — root granularity is still correct for classifying stdlib and third party. This is a strict tightening. #6's own 17 self-tests pass unchanged, and the real gate still exits 0. THE SIX RULES, ENCODED AS ADR-0026 WRITES THEM ingest · curation · groundtruth · method contracts, domain, kernel evidence contracts, domain, kernel (+ writes nothing) evaluation contracts, domain ← as written No policy grants the `contexts` root, so no context may import another. Third-party roots are not pre-granted: the ADR grants none, and a context needing astropy should add one reviewable line rather than inherit a permission nobody voted for (STD-11). ARCHITECTURE CONTRADICTION — REPORTED, NOT RESOLVED (AC-001) ADR-0026 says "Evaluation imports contracts and domain only". TIS E10 §7 requires an Evaluation to carry its own digest; ADR-0005 reserves minting to the kernel. Those three cannot all hold. Encoded exactly as frozen and reported for resolution by ADR before M7/#30. Quietly adding `kernel` to R5 would be weakening a gate to fit an unwritten need, which is the one thing this project has repeatedly refused to do. Separately, ADR-0026 states R5 twice and inconsistently: the context table says "Depends on contracts only" and the Decision says "contracts and domain only". The precise Decision wording is taken as operative, following the DR-006 precedent. EMPTY CONTEXTS, AND WHY THESE RULES ARE NOT DECORATION contexts/* holds six READMEs and no Python, so none of the 55 architecture tests assert over the real tree — each one builds a violating module on disk and asserts the analyser reports it. The verdict is about the analyser, which exists. Two tests make the rules bind on the day code arrives rather than the day someone remembers them. Every context declares populated=False, and the gate rejects that declaration the moment a module appears — so the first .py file in any context goes red until that issue states what it may import. And a seventh context added without a policy fails, because `undeclared` finds packages by __init__.py and these directories have none. FEATURE-LEVEL INTEGRATION Six rejection tests are all satisfied by a rule set that rejects everything. So tests/integration/test_context_rules_with_platform.py asks the complementary question, which needs three real subsystems at once: does the rule set PERMIT the composition the architecture calls for? A plausible Ingest module builds a domain Observation, validates it against the real contract, and registers it in the kernel's provenance store. The gate permits it; the same source then genuinely runs, producing a schema-valid, DAG-linked artifact with ingest_time null and nothing fabricated. Then the point of the whole gate, as a test: the violating variant differs by one import and EXECUTES PERFECTLY. Strip that line and the remainder is byte-identical. No test that runs the context would ever notice the coupling. And the same text is permitted under R1 and refused under R5 — the verdict changes with the context, which is what makes these rules directional rather than one allow-list. VERIFICATION unit (kernel + domain) 322 passed tests/architecture 460 passed (+55) tests/integration 63 passed (+6) research/tests/v2 188 passed links / imports / contracts gates clean imports gate: 30 modules, 134 imports, 9 policies CI: no workflow change. Both new files land in jobs that already run. Still eight contexts. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Rexy-5097
added a commit
that referenced
this pull request
Jul 30, 2026
Canonical: Milestone M2 · Epic E4 · Issue #14. Deps: #11 (complete). Eligibility recomputed by parsing origin/main:docs/tis/TIS-v1.0.md §15 — complete {1..13}, eligible {#14 order 14, #15 order 15}; lowest Order selects #14. Completes Epic E4. The twelfth contract, and the one git actually carries: a Tier 2 manifest is what the repository holds in place of ~600 MB it deliberately does not (ADR-0023, E6 §19). ALMOST EVERYTHING THIS SCHEMA DOES IS A REFUSAL ADR-0023 defines three tiers with three different homes, and each must forbid the others. Expressed as prose, "a Tier 0 manifest must not carry a deposition" is a sentence someone has to read. Expressed as `if tier = 0 then not deposition`, a manifest that would redistribute another organisation's raw archive CANNOT BE WRITTEN. tier 0 retrieval descriptor required; deposition and path impossible (STD-23, E6 §11(iii) — identified, never stored, never redistributed) tier 1 deposition required; retrieval and path impossible; retention must be permanent, because a prunable Tier 1 release makes a DOI resolve to nothing tier 2 path required, and constrained so it cannot leave the repository root RETENTION IS UNREPRESENTABLE WHEN IT WOULD BE WRONG E6 §11(iv): an artifact referenced by an Evidence Binding is NEVER pruned. So `prunable` with a non-empty `referenced_by` is refused by the contract rather than discouraged in a comment — it is the one combination that would let a published claim lose the bytes underneath it. A `permanent` entry likewise cannot carry a `prune_after`: permitting both would let a manifest state a contradiction that a retention run would then arbitrate silently, the same way every time. DOI IS REQUIRED AS A FIELD AND NULLABLE IN VALUE ADR-0023's fallback degrades citability, and says the degradation is published rather than hidden. Optional would make "no DOI" and "nobody recorded whether there is one" the same document — the ingest_time reasoning of ADR-0022 applied to citability. FEATURE-LEVEL INTEGRATION — THE DIGEST IS THE SAME DIGEST tests/integration/test_manifest_with_platform.py checks the one thing no schema can: that the digest in a manifest is the digest of the bytes it claims to describe. It needs four components at once — the kernel mints it (#10, the only thing permitted to), the contract validates the release (#11), the domain holds the digest without being able to compute one (#12), and the import rules say who may do all of it (#13). Every guarantee downstream — citation, re-acquisition, supersession, evidence binding — rests on that equality, and nothing had ever checked it end to end. Also included: a manifest that is schema-valid and FALSE, pointing at bytes the store has never seen, and the demonstration that the store refuses it while the schema cannot. A FINDING ABOUT MY OWN TEST, NOT THE SCHEMA `tier: 1.0` validates, and that is correct. JSON has one number type and JSON Schema compares enum members by value, so 1.0 and 1 are the same number. The first version of that test asserted rejection and was wrong; it now asserts acceptance and records why. NO PRODUCER, NO PARSER, NO REGISTRY, NO INGEST The schema and its tests. `contexts/curation/manifest` is E6/#20's; registry/*/*.json instances are E6/#21, E7/#23, E8/#25-#26, E9/#27's; the retention gate is E6/#22's. The manifest gets no domain entity, deliberately — row 14's Unit column reads "manifest validity", a schema test, and an entity would be a producer. Two existing assertions updated because #14 changes the contract set: the expected count 11 -> 12, and manifest added to the documented no-domain-entity exclusions. VERIFICATION unit (kernel + domain) 322 passed tests/architecture 562 passed (+102) tests/integration 71 passed (+8) research/tests/v2 188 passed contracts gate: 12 schemas, 11 compared, every change additive links / imports gates clean CI: no workflow change. Both new files land in jobs that already run. Still eight. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Canonical identifier: Milestone M2 · Epic E4 · Issue #11 — Contract Schemas
Dependencies: #10 (complete). Eligibility recomputed mechanically from the committed plan before starting: #11 was the only eligible issue. No abort condition triggered.
What this adds
Ten object contracts plus a shared definitions file, under
contracts/:observation·dataset-release·label-release·protocol·environment-release·method-release·evaluation·evidence-binding·supersession·provenance-record— andcommon, which nothing validates against directly.No producers. No consumers. Schemas and their gate only, per the issue's explicit scope.
Decisions worth reviewing
$idis a URN, not a URL.urn:adityanet:contract:<name>:<major>. Anhttps://$idimplies a resolvable endpoint; this project serves none, and ADR-0001 forbids implying a capability the platform lacks.Every object is
additionalProperties: false. An unrecognised field is exactly how a producer and a consumer disagree about a payload while both believe they conform. One exception,environment-release.thread_counts, whose keys are set by the libraries installed rather than by the contract — and which says so in its own description.nulland absent are different, and the difference is load-bearing.ingest_timeis nullable with exactly one meaning (ADR-0022, L-11) and is a required field, so absence cannot be mistaken for unknown.valueis nullable meaning observed-to-be-absent, never imputed (ADR-0017, L-07).Defects found, all mine, all found by tests
Contract defect —
formatis annotation-only. JSON Schema does not require any validator to enforceformat: date-timeunless explicitly configured with a format checker. A timestamp constrained only byformatis therefore validated by some consumers and silently not by others — worse than no constraint, because it looks like one. The timestamp definition now carries apatternas well, which every validator enforces.IMPL-010 — two bugs in the additive-diff gate, both silently disabling a check.
walk()recursed into scalars inside lists rather than capturing them, soenummembers,requirednames and uniontypearrays vanished from the flattened form entirely. And the added-required-field check derived its container path by string-splitting a pointer, producing//required/at the document root, matching nothing. Neither could have been found against the real corpus — every contract here is new, so the gate compared nothing. They were found only because the diff logic is tested against synthetic baselines.IMPL-009 — test-side only. A
Registrybulk constructor that does not exist, and a no-domain-terms scan that flagged$id, which legitimately contains the project name.The gate no longer overstates. With no baseline it now reports that the additive-only comparison was not exercised, rather than printing "every change additive". A gate asserting a property it did not check is the failure mode this project exists to refuse. A test pins that wording.
Verification
Contract tests: per contract, a valid example, an unknown-field rejection, a closed-object assertion, and every required field individually dropped to prove it is enforced. Plus 19 invalid values, 5 boundary values, nullability semantics, the five pinned inputs of ADR-0021 individually, the three reproduction classes, the three supersession severities. Diff logic: 12 breaking cases, 7 additive cases, all synthetic.
CI impact
jsonschemaadded to thearchitecturejob's install. No new job, no new required context — still eight.🤖 Generated with Claude Code