Add SEDI guardianship represented-presentation worked example - #1530
Add SEDI guardianship represented-presentation worked example#1530dhh1128 wants to merge 6 commits into
Conversation
ce21e46 to
b998147
Compare
b998147 to
e2e98ad
Compare
A worked, working ACDC v2 example (tests/acdc/test_guardianship_presentation.py) of a *represented presentation* -- a digital guardian presenting a fact about a ward under Utah's State-Endorsed Digital Identity law. It is the first example in the set whose HOLDER IS NOT THE SUBJECT, and it stacks on the E1E identity operator (PR WebOfTrust#1523), consuming its aggregate-far-node path end to end. Scenario: Bob, a custodial parent, proves to an age-restricted online service that his 14-year-old ward Cara is over 13 -- without disclosing her birthdate, which other age thresholds exist, or any ward attribute beyond the predicate, and while the service can prove afterward that a guardian (not the child) acted. Five phases, strict-TDD, each ACDC validated against a purpose-authored JSON Schema (Draft 2020-12) from its first commit: * Phase 1 -- the ward's attributive sedi-id and aggregative sedi-age, chained by an E1E identity edge (same subject, issuer != issuee); selective disclosure of the over-13 predicate with the birthdate and other thresholds withheld. * Phase 2 -- Bob's disclosed-whole sedi-guardian authority credential, holder != subject (issuee = Bob; the ward named only by a NI2I subject edge), grounded in a birth-certificate authorization edge. * Phase 3 -- the represented presentation with three pinned edge operators (I2I authority, NI2I ward-data, E1E identity) and a five-point verifier binding. The schema const-pins make the impersonation SHAPE unrepresentable (a mislabeled I2I ward edge is rejected); the holder != subject GUARANTEE is enforced by the verifier binding logic plus the issuee field and signature, not by the operator alone. A gated IPEX exchange discloses the over-13 flag only in the grant. * Phase 4 -- dynamic revocation via a blindable registry (state word blinded; issued honored, revoked refused) and provable accountability. * Phase 5 -- the invariants across JSON/CESR/CBOR/MessagePack. Uses the disclosure section each credential fits, per the resolved aggregate-vs- attribute criterion: aggregate for the homogeneous age-threshold vector, attribute for the fixed labeled identity and authority fields. Docstrings reviewed by the keri-review-panel and revised to scope the claims precisely: the operators mark intent/shape while the app-layer binding enforces the invariant; E1E is a keripy addition not yet in the ACDC spec operator set (rides WebOfTrust#1523); attribute minimization is achieved but identifier-level cross-verifier unlinkability needs deployment measures (per-facet AIDs / bulk issuance); the blindable registry blinds the state word, not the revocation timing; and the pre-agreement offer withholds issuer-committed source SAIDs. Signed-off-by: Daniel Hardman <daniel.hardman@gmail.com>
e2e98ad to
56849f2
Compare
|
Updated: rebased onto current
The represented-presentation case is the one where the joining paths carry real weight: the service asks for the issuee from both the guardian credential ( Same open question as #1561, flagged in a comment at the call site: #1549 says the zeroth tuple MUST be the DAG origin, but a first-contact apply cannot name the bespoke presentation's schema SAID (#1512). Origin tuple omitted for now; @SmithSamuelM, this is the question still open on #1542. Still a single commit adding a single file. |
Picks up the KWA NameError fix (WebOfTrust#1562) that was breaking tests/vdr/test_verifying.py on this branch's stale base. Signed-off-by: Daniel Hardman <daniel.hardman@gmail.com>
Tracks the sibling change on the CLC and bulk-issuance examples, so this example lands already carrying the settled construct rather than needing a follow-up. The `_` component (WebOfTrust#1549, 2026-08-02): a DAG-absolute path reaching a non-origin ACDC must cross the edge that links it, written as the virtual component `_`. No path data changes -- every path here is ACDC-relative and none traverses an edge -- but the comment now records the absolute rendering of this DAG, whose three edges make it the clearest of the three examples to read: /e/authority/_/a/i, /e/wardAge/_/A/over13. The zeroth origin tuple: WebOfTrust#1549 says the zeroth `dp` entry MUST be the DAG origin. This example previously omitted it on the grounds that a first-contact applicant cannot name a presenter-issued origin's schema; @SmithSamuelM answered that on WebOfTrust#1542 -- the origin is presenter-issued but its shape is governance, so the applicant does know it. The service now asks the origin for issuer and issuee, which in a represented presentation is the first place the holder != subject split is visible: the origin's issuer is Bob, and the ward does not appear in it at all. The five exn SAIDs move as a result; the apply -> offer -> agree -> grant -> admit cascade is the `p` binding working, and each was re-derived by running the example. The field-label conformance guard that comes with the same discussion (no `-` in a label, no label that is exactly `_`) lands on WebOfTrust#1561 rather than here, to keep it in one place. Test-only; no product code changes. Signed-off-by: Daniel Hardman <daniel.hardman@gmail.com>
|
Rebased onto the current branch head and updated to match #1561, which took two changes @SmithSamuelM settled after both PRs were opened. The apply's The The label-conformance guard that comes with the same discussion lands on #1561 rather than here, to keep it in one place. Both restrictions are proposed for the spec in trustoverip/kswg-acdc-specification#202, and Five exn SAIDs moved through the |
Utah has decided not to depend on a separate GCD (Generalized Cooperative Delegation) credential for guardianship, so the optional 'scope' edge that reached one is removed and the edge section is closed at subject + authorization. A new negative asserts that any other edge is now rejected, which makes the removal a property the suite holds rather than an absence. Removing a property from the schema map moves the schema's own SAID, which is embedded in the credential's 's' section, so the guardian credential SAID moves and the cascade reaches the presentation, the blinded registry state, and the apply/offer/agree/grant/admit chain linked by 'p'. Ten asserted digests were re-derived by running the example. The module docstring records why GCD is absent, since a reader coming from the bakobo/schema family will otherwise wonder where the two-layer factoring went, and notes what follows: 'powers' is now the whole scope vocabulary, which is coarse, and a guardianship needing more carries it inline (discussion WebOfTrust#1550). The identifier-unlinkability residual now cites the two bulk-issuance worked examples rather than promising them, names the independent-registry variant as the one Utah intends, and adds that the guardian's own AID needs partitioning under bulk issuance or two verifiers can join on it to re-link the ward. Signed-off-by: Daniel Hardman <daniel.hardman@gmail.com>
The example claims the service learns an over-13 predicate and not Cara's birthdate, and then shipped her birthdate anyway. The guardian credential is disclosed WHOLE, and its effectiveDate was the date the parental right arose, which for a custodial parent is exactly the ward's date of birth -- 2012-04-10 in both places. effectiveDate is now the date the State recorded the relationship, which is what a verifier needs (when this authority became checkable) and is not derived from the ward. The remaining leak is structural and is now asserted rather than hidden. A minor guardianship expires at majority, so expiryDate is the ward's 18th birthday and a verifier can subtract 18 to recover her birth month and day. Nothing in the represented-presentation shape fixes that; mitigations are deployment-level, either coarsening the date or carrying validity solely in the registry. The test asserts the residual is present so it cannot regress into being forgotten, and WARD_DOB is named once so the check is real rather than a comparison of two literals that could drift apart. Found while building the ward-presents sibling example, which had the same bug for the same reason. Signed-off-by: Daniel Hardman <daniel.hardman@gmail.com>
The disclosure-paths `dp` field of an IPEX apply's query section is an ordered list of tuples. Each tuple gains a middle element, so it goes from (schemaSAID, [paths]) to (schemaSAID, prefix, [paths]). The new middle element is a path prefix: the DAG-absolute route to the ACDC that the entry's schema SAID names. It is either the empty string or a route that both begins and ends with '/', and the effective path is prefix + entry concatenated with no delimiter inserted, so an entry in the path list never begins with '/'. Every prefix is empty in this example. That leaves the entry paths ACDC-relative, exactly as they were before this change, so an entry's ACDC is identified by the entry's position in the list -- breadth-first from the origin node, with the origin at index zero. That ordering is required even where a non-empty prefix would make it technically unnecessary. The disclosure semantics are unchanged: same schema SAIDs, same order, same paths. The solicited offer still carries an empty `dp` list and takes no prefix. The apply's serialization changes, so its pinned SAID changes, and with it the SAIDs of the offer, agree, grant and admit that chain onto it. Proposed by @SmithSamuelM in WebOfTrust#1549 (comment) and confirmed, including the retained ordering requirement, in WebOfTrust#1549 (reply in thread) Signed-off-by: Daniel Hardman <daniel.hardman@gmail.com>
This PR adds a worked example of a a digital guardian presenting a fact about a ward (a person who cannot fully consent) to a verifier, under SEDI). It is part of a set of changes, and is an example whose holder is not the subject, a sibling to
test_cp_disclosure.pyandtest_examples.py.Scenario. Bob, a custodial parent, proves to an age-restricted online service that his 14-year-old ward Cara is over 13 — without disclosing her birthdate, which other age thresholds exist, or any ward attribute beyond the predicate — while the service can prove afterward that a guardian (not the child) acted.
Three edge operators are interesting in this scenario:
issuer != issuee— consuming the operator added in Add E1E identity edge operator to credential chain verification #1527 over its aggregate far-node path (.iseaid→A[1].i), end to end.The code demonstrates: guardian-mediated aggregate predicate disclosure (unlinkable over-13, birthdate withheld), holder ≠ subject with a gated IPEX exchange, blindable-registry dynamic revocation, and a verifiable accountability chain back to an accountable human — things a document-centric mDL/EU-ARF flow cannot express.
The credential shapes are minimal ports of the draft
sedi-guardianfamily inbakobo/schema.One divergence worth a decision: the draft usesrifor the registry field, while keri v2 (acdcmap/acdcagg) emitsrd— this example usesrd(ground truth). Candidate revision to the bakobo first-cut schema.