Skip to content

feat(schema): add Attestation, full Evidence and the C06 claim schema… - #90

Open
blushi wants to merge 10 commits into
feat/84-linkml-1.11from
feat/73-attestation-evidence-c06
Open

blushi wants to merge 10 commits into
feat/84-linkml-1.11from
feat/73-attestation-evidence-c06

Conversation

@blushi

@blushi blushi commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Closes #73 (WP1-06). Stacked on #87 (base feat/84-linkml-1.11).
It needs #85's base Claim and vocabulary, and #87's uriorcurie references and LinkML 1.11 SHACL output.

Implements ADR: ADR 0001 — D1 at 0cfe1c52 (#56, Proposed), applied to the Attestation: no own hash or IRI, no mutable review or anchoring state in content.

Differences from ADR (if any) and why: none.

The design is in docs/attestation-and-evidence.md. It was derived from the review records of the WP8-01 workflow (regen-network/claims#49). Every example in this PR uses synthetic values.

Please also review the internal evidence page: Claims Engine — CSSCP evidence for the WP1-06 schema design (Notion, internal). It holds what cannot be public: the source documents, which records map to which classes and fields, and the open questions specific to the workflow. Story IDs in this PR (CS-3, CS-4, AD-1, EX-2, PG-1) refer to the Claims Engine — Layered User Story Map (v2).

Changes

  • Attestation.yaml, rewritten. Attestation is_a Claim. The issuer is hasClaimant and the time is assertedAt; it is about a subject and cites evidence. It adds:

    • hasTarget (exact versions of claims, attestations or a snapshot), reliesOn, requirement, appliesRuleSet, outcome and rationale;
    • verificationMethod, required, one per attestation, with a descriptor when it is OTHER;
    • an inlined Scope (appliesTo, required, plus exclusion and limitation), and unbounded for a judgment with explicitly no limits.

    It holds nothing specific to one program.

  • RegistryReviewAttestation.yaml, new. The Regen Registry review vocabulary, is_a Attestation: findingLabel, issuerRole (required), conditions with milestones, and the RegistryReviewOutcome terms. Findings are a subclass, RegistryFindingAttestation, which requires a findingType (CAR, CL, FAR, registry issue) and at least one piece of evidence. Another program would define its own subclasses. There is no attestation-type field.

  • Evidence.yaml. Adds sourceType (dcterms:type, DCMI Type Vocabulary), mediaType, contentHash (algorithm and hex digest of the cited version), resolver, locator, licence (dcterms:license, the IRI of the terms in effect), issued, and wasGeneratedBy (prov:wasGeneratedBy, the activity that produced the source). Also adds the EvidenceCheckOutcome terms (intact, altered, unreachable, access restricted), which the resolver reports and claims never store.

  • Activity.yaml, new. Activity (ProvActivity mixin): an IRI, name, description, a period as dates (startDate, endDate) and who carried it out (wasAssociatedWith). wasAssociatedWith moves here from ClaimVocabulary, which imports this module, and startDate/endDate move here from C06Claim.yaml. It is a separate module because Evidence needs it and ClaimVocabulary imports Evidence. A claim reaches an activity through its evidence (claim → hasEvidence → wasGeneratedBy); the C06 claims need no domain activity of their own, because no C06 requirement checks who carried out a practice.

  • ClaimVocabulary.yaml. Adds hasTarget, reliesOn (⊑ dcterms:references), requirement (named within a checklist version), appliesRuleSet (PG-1, distinct from schema-version declarations), and verificationMethod with verificationMethodDescriptor and the CS-4 enumeration (OTHER with a descriptor is the extension path).

  • C06Claim.yaml, new (version 0.1.0). Named after its credit class, like C01ProjectInfo.yaml.

    • Subjects: Project, Cohort, Site and Plot, is_a ClaimSubject, holding only identifying fields.
    • Claims: C06ProjectClaim, C06CohortClaim, C06SiteClaim, C06PlotClaim and C06ProjectStatementClaim, is_a Claim.
    • A field is added only where a C06 registration requirement checks a value the claimant states. Statement-only requirements use C06ProjectStatementClaim. Rules (the project's mandatory and complementary practices), reviewers' conclusions (plot eligibility), values computed from other claims (project and cohort areas, project ecosystem types), evidence (land register records, land cover datasets, historic activity records) and lifecycle state (plot enrolment status) are not fields.
    • Areas (area) use the shared QuantityValue (below).
  • QuantityValue moves from ProjectInfo.yaml to core.yaml, so C06Claim.yaml reuses it without importing ProjectInfo. A separate C06 area class would have shared the class IRI qudt:QuantityValue, so the composed schema's SHACL would have applied both shapes to every quantity node. numericValue maps to qudt:value, and the unit slot is renamed unit → hasUnit and maps to qudt:hasUnit, now a uriorcurie; both are required and the number cannot be negative. The rename frees the unit: prefix in JSON-LD, which the unit term shadowed, so unit:HA expands there as in YAML and Turtle; core.yaml and C06Claim.yaml declare the prefix, because a generated context only carries the root module's prefixes. qudt:unit is deprecated in QUDT 2.1 for qudt:hasUnit and removed in 3.0.0 (sources in the inline comment on core.yaml). In the project YAML fixtures the key unit: becomes hasUnit:; their RDF changes: unit:HA becomes the QUDT unit IRI instead of a string literal, and the number is qudt:value.

  • Claim.yaml. hasClaimType is removed (Claim model gaps found when applied outside ecology (research-literature test) #86): a single required enum could not cover every kind of claim, and the specialized class says what kind of claim it is. The ClaimType enum stays in the taxonomy.

  • ClaimSubject.yaml. Description updated: the module is no longer a skeleton.

  • scripts/claim-examples.py. Checks examples of several classes, each against its own module's context and shapes. The class is taken from the fixture name, or from a # class: line in an invalid example. Slots a class narrows to an enum get the same context correction as other enum-valued slots. An invalid example marked # shacl: not enforced breaks a LinkML rule, so only JSON Schema must reject it.

  • Examples (all synthetic).

    • c06-mvp-claim.jsonld (a C06SiteClaim, with field records and the activity that generated them), c06-project-claim.jsonld, c06-cohort-claim.jsonld, c06-plot-claim.jsonld, c06-project-statement-claim.jsonld, generic-attestation.jsonld (a base Attestation with an OTHER method and its descriptor), registry-review-attestation.jsonld (a scoped confirmation) and registry-finding-attestation.jsonld (a clarification request). The evidence in them uses versioned licence and rule-set references.
    • Ten new invalid examples: an attestation with its own hash, an attestation without a verification method, an OTHER method without a descriptor, a scope without subjects, a finding without evidence, evidence without a hash, an evidence activity without an IRI, a site without its key, an undeclared field, and a negative area.
    • The generic fixtures' evidence now has the required hash, resolver and type.
  • Docs.

#73 checklist

Item Status
Producer, consumers and reuse decision per type Done: design doc, "How the types relate"
Extensions only for gaps in the base Claim Done: C06 fields only where a requirement checks a value
Attestation: issuer, target, scope, time, verdict, rationale, evidence Done
Decide whether the existing Evidence, Requirement and Attestation classes remain bases Done: Evidence extended; Attestation rewritten; no Requirement class (requirements are rule-set data, referenced by IRI)
Verification-method terms with an extension path; rule-version references Done, in ClaimVocabulary; a descriptor is required with OTHER
No ValidationResult schema or ingestion-validation Attestation profile Respected
Evidence hash, resolver, licence terms, unspecified — all rights reserved Done for what the MVP needs: each piece of evidence carries the hash of the cited version, where to fetch it, and the IRI of the licence terms that applied (a new IRI when the terms change). No licence means unspecified — all rights reserved. Not done: licence terms as structured fields (permitted uses, prohibitions, attribution, fees). That is story EX-2, in the story map's Layer 3 (exchange and discovery: publishing data and its usage terms for others to reuse), which the Work Packages say to test with data owners before building. The MVP's source records state no licence terms to model.
Integrity/access outcomes; availability and current licence outside content Done
RDF terms, types, cardinalities, collections; imports; field migrations Done; new terms are rfs: unless an existing term fits (dcterms:type, dcterms:format, dcterms:license, dcterms:issued, schema:startDate/endDate, schema:addressCountry, qudt:QuantityValue, qudt:value, qudt:hasUnit, prov:wasGeneratedBy)
JSON-LD examples of the types, evidence, versioned terms, verification methods and rule references, plus invalid examples Done: eight valid, ten new invalid. No derivation reference is needed: a claimant states a derived value with its basis, and a reviewer's derivation is an attestation.
Own-hash/IRI and mutable-state exclusions Done
One versioned domain schema importing the base and shared modules Done: C06Claim.yaml
Validation entry point, target nodes, inherited constraints, extension fields Done: design doc, "Validation entry points"
c06-mvp-claim.jsonld with synthetic values Done

Validation

Run locally with the pinned toolchain (requirements.txt: linkml 1.11.1, Python 3.10):

Check Result
make -C schema lint No problems
make -C schema check-claim-examples Passes: 69 checks, plus the CS-4 rule example, rejected by JSON Schema and, as marked, accepted by SHACL
make -C schema gen-rdf 136 of 136 pass (the 120 existing conversions plus the 8 new fixtures in 2 formats)
make -C schema gen-doc Succeeds

Not run: update-graph (needs a graph store), the site build, and a graph comparison of the existing fixtures' RDF against the base branch. The project fixtures' quantity triples change by design (see QuantityValue above).

Known limitations

  • Outcome values are not checked against the review vocabulary. LinkML 1.11 cannot narrow a uriorcurie slot to an enum in a subclass: the generated Python model of the base class rejects the value. So RegistryReviewAttestation.outcome accepts any IRI, and RegistryReviewOutcome documents the vocabulary.
  • CS-4 is enforced by JSON Schema only. CS-4 requires that a verification method is never empty, so an attestation whose method is OTHER must describe it. This is a LinkML rule, which the generated JSON Schema enforces. LinkML's SHACL generator does not translate it (linkml/linkml#2464): 1.11.1 ignores rules, and the unreleased rule support (linkml/linkml#3451) covers other patterns. SHACL itself can express it with sh:or. The other two conditional rules, evidence on a finding (CS-3) and a scope that names its subjects (AD-1), are modelled as a subclass and a required slot, which both validators enforce.
  • Subject references are plain IRIs (appliesTo, project, cohort, site). A typed node of a ClaimSubject subclass fails the generated sh:class check unless the validator is given the class hierarchy.
  • gen-rdf's playground JSON-LD does not match its Turtle. gen-rdf's playground .jsonld output writes every uriorcurie value, units and wasRevisionOf included, as an xsd:anyURI literal, while its .ttl has IRIs, because linkml-convert builds its own context without --xsd-anyuri-as-iri. Those files are gitignored and update-graph pushes only .ttl; WP1-07 — Generate and version schema artifacts and validation examples #74 will build them with the published context and check them against the Turtle. A richer quantity structure (uncertainty, rate denominators) is open (Claim model gaps found when applied outside ecology (research-literature test) #86).
  • Operators have no IRI yet. wasAssociatedWith names an Entity, which has no identifier until feat(Entity): add optional rid identifier slot #58, so an activity's operator is a node with a name and type and cannot be joined across activities.
  • Open for review: claim granularity (one claim per plot, or site claims carrying their plot records), and how evidence IRIs are minted (WP6-04, claims#1).

🤖 Generated with Claude Code

blushi added 2 commits October 1, 2026 16:21
…#73)

- Attestation.yaml: rewrite the base Attestation as is_a Claim with
  hasTarget, reliesOn, requirement, appliesRuleSet, outcome, rationale,
  verificationMethod and an inlined Scope. Drop contentHash, graphIri
  and the PENDING verdict.
- RegistryReviewAttestation.yaml: add the Regen Registry review
  vocabulary (findingLabel, findingType, issuerRole, conditions with
  milestones, outcome terms) as is_a Attestation.
- Evidence.yaml: add the source content hash, resolver, DCMI type,
  format, locator, licence reference and issue date, and the
  integrity/access outcome terms.
- ClaimVocabulary.yaml: add the shared reference terms and the
  verification-method enumeration (CS-4).
- C06Claim.yaml: add the C06 subject classes (Project, Cohort, Site,
  Plot) and claim classes (C06ProjectClaim, C06CohortClaim,
  C06SiteClaim, C06PlotClaim, C06ProjectStatementClaim).
- Claim.yaml: remove hasClaimType (#86); the kind of claim is its class.
- claim-examples.py: check examples of several classes and modules,
  including slots a class narrows to an enum.
- Add synthetic C06 site claim and registry confirmation fixtures,
  c06-mvp-claim.jsonld and registry-review-attestation.jsonld, and five
  invalid examples. Update the generic fixtures for the new Evidence
  fields.
- docs: add attestation-and-evidence.md; update claim-base.md.
… rules (#73)

- ClaimVocabulary.yaml: make verificationMethod single-valued (one method
  per record, with that record's party and date).
- Attestation.yaml: require verificationMethodDescriptor when the method
  is OTHER (CS-4). RegistryReviewAttestation.yaml: require evidence on a
  finding (CS-3). Both are LinkML rules: the generated JSON Schema
  enforces them, the generated SHACL does not express them.
- C06Claim.yaml: state areas as QUDT quantity values (area, with
  qudt:numericValue and qudt:unit unit:HA) instead of a bare number.
- Add synthetic examples: c06-project-claim, c06-cohort-claim,
  c06-plot-claim, c06-project-statement-claim and generic-attestation,
  with versioned licence and rule-set references; and two invalid
  examples for the rules.
- claim-examples.py: an invalid example marked "# shacl: not enforced"
  must be rejected by JSON Schema only.
- docs: licence terms are referenced by IRI, and structured terms are
  left to story EX-2; no derivation reference is needed; claim-base.md
  records hasPrimaryImpact and hasCoBenefits as removed (project fields)
  and points the other moved fields to C06Claim.
- RegistryReviewAttestation.yaml: add RegistryFindingAttestation
  (is_a RegistryReviewAttestation), which requires findingType and at
  least one piece of evidence (CS-3), and drop the LinkML rule that the
  generated SHACL ignored. findingType moves to the new class.
- Attestation.yaml: a Scope requires appliesTo, and unbounded moves from
  Scope to the Attestation (AD-1): a scope always names its subjects, no
  scope means the judgment applies only to its targets, and
  unbounded: true states explicitly that it has no limits.
- Add a synthetic finding (registry-finding-attestation.jsonld) and
  invalid examples for a finding without evidence and a scope without
  subjects, both rejected by JSON Schema and SHACL.
- docs: CS-4 (a descriptor with OTHER) remains the only rule enforced by
  JSON Schema only; LinkML's SHACL generator does not translate it
  (linkml/linkml#2464).
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

blushi added 2 commits October 5, 2026 11:26
…edBy (#73)

- Drop C06 fields that are rules, reviewers' conclusions, values computed
  from other claims, evidence or lifecycle state; the aggregation basis and
  enrolment cutoff become statement claims.
- Add Activity.yaml (Activity, wasAssociatedWith, startDate, endDate) and
  Evidence.wasGeneratedBy, so evidence names the activity that produced it,
  its period and its operator.
blushi added 2 commits October 5, 2026 13:51
…asUnit and qudt:value (#73)

Move QuantityValue from ProjectInfo.yaml to core.yaml so C06Claim can use it
without importing ProjectInfo, and drop AreaQuantity: both used class_uri
qudt:QuantityValue, so the composed schema's SHACL applied both shapes to
every quantity node.

QuantityValue.unit is now a uriorcurie mapped to qudt:hasUnit, so unit:HA is
the QUDT unit IRI rather than a string literal; qudt:unit is deprecated in
QUDT 2.1 for qudt:hasUnit and removed in 3.0.0. numericValue maps to
qudt:value, which current QUDT constrains on QuantityValue.
…ue (#73)

Restore the constraints AreaQuantity had, now on the shared QuantityValue,
and add an invalid example that both validators reject.
Comment thread schema/src/core.yaml
The numeric value of the quantity. It cannot be negative: the
quantities stated so far are sizes and areas.
unit:
slot_uri: qudt:hasUnit

@blushi blushi Oct 5, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QUDT properties: qudt:hasUnit and qudt:value replace qudt:unit and qudt:numericValue

QuantityValue (moved here from ProjectInfo.yaml) previously mapped unit to qudt:unit with range: string, so a fixture's unit: unit:HA became the string literal "unit:HA", not the QUDT unit.

  • The unit must be an IRI. In QUDT 2.1, qudt:unit is an owl:ObjectProperty with rdfs:range qudt:Unit (SCHEMA_QUDT-v2.1.ttl#L3041-L3050, release v2.1.30). In OWL, object properties connect individuals and only data properties take literals (OWL 2 Structural Specification §5.3–5.4). http://qudt.org/vocab/unit/HA dereferences to unit:HA a qudt:Unit.
  • qudt:unit is deprecated, then removed. The same QUDT 2.1 definition has dcterms:isReplacedBy qudt:hasUnit ; qudt:deprecated true. QUDT 3.0.0 "Removed all previously deprecated entities" (CHANGELOG.md#L838), so qudt:unit no longer exists in current QUDT.
  • Current QUDT constrains qudt:hasUnit and qudt:value. On main, qudt:QuantityValue is a subclass of qudt:Quantifiable (SCHEMA_QUDT_NoOWL.ttl#L672-L681), whose shape requires qudt:hasUnit values to be qudt:Unit nodes (#L2506-L2511) and constrains the number on qudt:value (#L2534). qudt:numericValue was never deprecated, but current QUDT puts no constraint on it. QUDT's own example uses qudt:hasUnit unit:KiloM-PER-HR ; qudt:value "90.0"^^xsd:DECIMAL (EXAMPLES_QUDT-DATATYPES.ttl#L146-L147).

So the unit slot is now hasUnit, a uriorcurie mapped to qudt:hasUnit (renamed from unit in 5eb8171), and numericValue maps to qudt:value. In the project YAML fixtures the key unit: becomes hasUnit:, and their RDF changes: the CreditProjectInfo-C06-019 fixture goes from qudt:numericValue "186.41"^^xsd:float ; qudt:unit "unit:HA" to qudt:hasUnit unit:HA ; qudt:value "186.41"^^xsd:float.

The rename also matters for JSON-LD: a term named unit shadowed the unit: prefix, so unit:HA did not expand there. With hasUnit, unit:HA expands to http://qudt.org/vocab/unit/HA in JSON-LD as in YAML and Turtle; c06-plot-claim.jsonld uses it, and its graph matches the fixture's Turtle.

The unit term shadowed the unit: prefix in JSON-LD, so unit:HA did not
expand there. hasUnit matches the QUDT property it maps to and frees the
prefix. Declare unit: in core.yaml and C06Claim.yaml, because a generated
context only carries the root module's prefixes, and write unit:HA in the
C06 plot fixture so the example check covers the expansion.

This branch was successfully deployed

1 active deployment
preview — 5eb8171c Deployed Oct 5, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WP1-06 — Define the MVP claim, attestation and evidence schema requirements

1 participant