GEN-B — institutional Subject recognition (SubjectRecognitionV1)
Slice: rung 3 of the #2694 semantic
convergence ladder · Protocol owner: #2602 (GEN) · Builds on:
GEN-A (#2695) ·
Code: icn/crates/icn-governance/src/subject_recognition.rs ·
Vectors: icn/crates/icn-governance/tests/subject_recognition.rs ·
Reference: icn/crates/icn-governance/tests/reference/subject_recognition_reference.py
This document is the byte-level contract. An implementation in another language can reproduce every value in §9 from this text and the published GEN-A §8 values alone.
1. The question this answers
A governance domain wants to record that one specific context-scoped human Subject is recognised at standing
Member, on a stated basis. What is the immutable, deterministic fact?
It answers that and nothing else. It is the second link after GEN-A in the chain Subject genesis → delegated device → institutional recognition → membership → … and does not implement any later link.
2. What a recognition proves — and what it does not
A verified SubjectRecognitionV1 proves exactly this:
The holder of a GEN-A genesis bundle that verifies for this exact governance-domain id names a Subject; a recognition of that Subject at
Memberunder basis kind K with basis hash H is committed byrecognition_hash.
It does not establish: that any institution actually decided to admit anyone; membership legitimacy; voting eligibility; economic authority or settlement capacity; that any device is authorized now (GEN-A §2.1: genesis is historical); delegation or session authorization; custody of, or control over, the Subject's continuity material; or production readiness.
A recognition is not a vote, a permission grant, a session token or an authorization decision.
Later facts may reference recognition_hash; none is entailed by it.
3. Rehearsal-policy boundary (case A, not case B)
Two different recognition questions exist. This slice implements only the first.
| A. Fresh Alpha Subject | A new Subject incepts via GEN-A; a separately governed domain records recognition under a declared rehearsal basis. Implemented. |
| B. Legacy-member migration | A legacy DID, continuity evidence and institutional records are bridged to a Subject (FACT A / FACT B evidence, #2626). Not implemented; no field, tag or API here anticipates it. |
The only basis in v1, BootstrapRehearsalV1 (0x01), is non-production. It is bounded to the
semantic rehearsal profile: it states "this Subject's genesis verifies for this domain" and
nothing about who authorised admission. It must not be confused with a production institution's
properly authorized decision. RecognitionBasisKind::is_production_grade() returns false for
it, and a consumer that must refuse rehearsal recognition for real membership should branch on
that, not on the variant. Later governance / credential / federated bases are separate work and
need a new basis kind and tag, never a reinterpretation of 0x01.
4. Constants (frozen for v1)
| Name | Value |
|---|---|
SUBJECT_RECOGNITION_TAG |
ASCII icn:gov:subject-recognition:v1 (30 bytes) |
BOOTSTRAP_REHEARSAL_BASIS_TAG |
ASCII icn:gov:recognition-basis:bootstrap-rehearsal:v1 (48 bytes) |
standing_class Member |
0x01 (u8) |
recognition_basis_kind BootstrapRehearsalV1 |
0x01 (u8) |
Hash function: BLAKE3, field-wise, per `CANONICAL_ENCODING.md` §4.1.3,
implemented by icn_kernel_api::canonical::CanonicalHasher. Both tags are in the icn:gov:
namespace, are registered in the workspace domain-separation audit
(icn/crates/icn-kernel-api/tests/domain_separation_audit.rs), are prefix-free with respect to
each other and to every other registered tag, and do not collide with the GEN-A SHA-256 domains
(icn.gen.*, length-first framing, different primitive).
Encodings used below: LE64(x) = u64 little-endian; STR(x) = LE64(len(x)) || x; b32(x) =
32 raw bytes with no prefix; u8(x) = one byte.
5. Fields
Committed, in this order:
domain_id— the exact UTF-8 bytes of the existingGovernanceDomainId.0. No case folding, no Unicode normalization (NFC and NFD spellings are different domains), no trimming, no display name. Empty is refused.SubjectId— 32 raw bytes: the N1 inception event id derived by GEN-A verification. Never a device Principal, never a legacy DID string.standing_class—u8,0x01=Member. Any other value is refused where it is parsed.recognition_basis_kind—u8,0x01=BootstrapRehearsalV1. Any other value is refused where it is parsed.recognition_basis_hash— 32 bytes, produced by the basis-specific derivation in §6.1.
No timestamp, counter, nonce, JSON, or implicit ordering enters any preimage.
6. Derivations
6.1 Bootstrap-rehearsal basis hash
basis_preimage :=
BOOTSTRAP_REHEARSAL_BASIS_TAG
|| STR(domain_id_utf8)
|| b32(SubjectId)
|| b32(subject_context_ref) -- GEN-A §6.4
recognition_basis_hash := BLAKE3(basis_preimage)
The basis commits the domain, the Subject and the GEN-A subject_context_ref, so it names which
context-bound inception it rests on. It therefore cannot be replayed for another domain, another
Subject, or another incarnation of the same context, and it is independent of the device grant
(replacing or revoking a device does not change it — subject_context_ref excludes the device).
6.2 Recognition hash
recognition_preimage :=
SUBJECT_RECOGNITION_TAG
|| STR(domain_id_utf8)
|| b32(SubjectId)
|| u8(standing_class)
|| u8(recognition_basis_kind)
|| b32(recognition_basis_hash)
recognition_hash := BLAKE3(recognition_preimage)
Every variable-length field is length-prefixed; every other field has a fixed width. The two tags make the two hashes domain-separated even over identical field bytes.
7. Construction and verification
SubjectRecognitionV1::recognize_bootstrap_rehearsal(domain, genesis) takes the GEN-A
SubjectContextGenesisV1 bundle, not the VerifiedSubjectContext result. The result type
omits the context id and has public fields, so a value of it cannot prove which domain it was
verified for. The constructor instead:
- refuses an empty
domain_id; - calls
verify_subject_context_genesis_v1(genesis, GovernanceDomainV1, domain_id_bytes), which enforces kind, a byte-exact context id, nonce recomputation, N1 admission, the bootstrap separation invariant and the derived live authority view (GEN-A §7); - takes the Subject from that verification — there is no parameter through which a caller supplies a Subject, Principal or legacy DID;
- derives §6.1 and §6.2.
Any refusal is final; there is no partial success. verify_against_genesis(&self, genesis)
re-derives the recognition from the recognition's own domain and requires equality in every
committed field (Mismatch otherwise). Fields are private; the type has no serde or wire form in
v1, so no non-canonical byte representation can become its identity.
Refusals: EmptyDomain, Genesis(GenesisVerifyError) (wrong domain, bad version, tampered salt,
bad witness, …), UnsupportedStandingClass(u8), UnsupportedBasisKind(u8), Mismatch.
8. Privacy, correlation and compatibility
- Recognition adds no global Person identifier and no cross-context link. It is scoped to one
(domain, SubjectId)pair; the SubjectId is already context-scoped by GEN-A. - It reveals nothing a holder of the GEN-A bundle did not already have (the domain and the
Subject);
recognition_hashis a commitment, not a new identifier for a person. - It confers no custody of continuity material and no control over the Subject.
- A new standing class, basis kind, or any field change requires a new tag version (Rule 2 of
CANONICAL_ENCODING §4.1.3); the bytes under
…:v1never change.0x01is never reinterpreted. - Unchanged by this slice: N1 wire formats, GEN-A hashes, every existing canonical domain and golden vector.
9. Test vectors
Inputs: domain coop.example.governance; Subject and subject_context_ref are the published GEN-A
§8 values.
| Input | Value |
|---|---|
domain_id |
coop.example.governance (23 ASCII bytes) |
SubjectId |
3b0e4c9ed8634dc5e3ed4a47ef09059fb99c89a0ac61a1ee6812894b8a190f5a |
subject_context_ref |
5e5f0deeb4cc8e0f1616a06509b7042828a85914597fad2b95d6a77f695faf86 |
| Output | Bytes |
|---|---|
basis_preimage (143 B) |
69636e3a676f763a7265636f676e6974696f6e2d62617369733a626f6f7473747261702d72656865617273616c3a76311700000000000000636f6f702e6578616d706c652e676f7665726e616e63653b0e4c9ed8634dc5e3ed4a47ef09059fb99c89a0ac61a1ee6812894b8a190f5a5e5f0deeb4cc8e0f1616a06509b7042828a85914597fad2b95d6a77f695faf86 |
recognition_basis_hash |
327aab543a13a4bda082ff2e921919b53e4b95b6c5ca4db965e5cde0a6dd252e |
recognition_preimage (127 B) |
69636e3a676f763a7375626a6563742d7265636f676e6974696f6e3a76311700000000000000636f6f702e6578616d706c652e676f7665726e616e63653b0e4c9ed8634dc5e3ed4a47ef09059fb99c89a0ac61a1ee6812894b8a190f5a0101327aab543a13a4bda082ff2e921919b53e4b95b6c5ca4db965e5cde0a6dd252e |
recognition_hash |
916ce2f5420ef322d570f07d76e57abce8085fde05cb66a46b2a0d2485b3139f |
Second vector (same Subject and context ref, domain coop.example.other, a pure hash-layout
vector — no genesis exists for that pairing): basis hash
a9676bd71551d9d26bcaf4a20e3ba79b902b6c0658c4af390087243726fe8b75, recognition hash
5e7bc9db8ef7246acd3e3d569ce14d885ee9e2a2bd255515bae8b06383d2c916.
The values were produced by subject_recognition_reference.py, written from this document with a
minimal single-chunk BLAKE3, sharing no code with Rust. Because the two preimages are 143 B and
127 B (three and two 64-byte blocks), the script self-checks its BLAKE3 against the published
empty-input hash and ten pinned pattern-input digests (lengths 1–1024, including 63/64/65,
127/128/129 and 143) before deriving anything. Those ten digests came from a one-off run of the
Rust blake3 crate, which also agreed with the script at every length 0–1024; only the ten pinned
digests, not that sweep, are reproducible from the repository. The script reads the EXPECT_* literals in the Rust test only as a comparison target and
exits non-zero on disagreement:
python3 -I icn/crates/icn-governance/tests/reference/subject_recognition_reference.py
What the vectors validate: the tags, field order, framing and widths of §6, and that the Rust
CanonicalHasher composition equals an independent byte-level derivation. The Rust test also
rebuilds both preimages by hand and hashes them with the raw BLAKE3 crate. What they do not
validate: GEN-A itself (pinned by its own vectors), any institutional decision, membership
legitimacy, or any later slice.
10. Negative cases covered
Wrong domain; empty domain; case, whitespace, NUL, trailing-delimiter and NFC/NFD alias spellings;
another Subject's genesis; a genesis for another domain; unsupported genesis version; mutated salt;
unsupported standing and basis tags; mutation of domain, Subject and basis hash; device
independence (a different initial device yields the identical recognition); a device Principal's
bytes forced into a Subject; a second context incarnation (different salt) giving a different Subject that does not cross-verify; tag prefix-freeness
and separation; rehearsal basis is never production-grade. Length-prefix framing is pinned by the hand-built preimage test and the
independent reference, not by a dedicated negative test. Type-level: a Subject is not a
PrincipalKey, SubjectRecognitionV1 has private fields and no Subject parameter, and the basis
hash type has no public constructor.
11. Deliberate scope boundaries
Not in this slice: legacy-member migration (#2626); the domain actor-model firewall (since added separately: `GOVERNANCE_HUMAN_ACTOR_MODEL.md`); membership roster;
VotingProcessSnapshotV1; proposal commitment; vote or decision records; settlement; persistence,
gateway routes, wire/serde representation, session claims; production recognition bases.
The next rung of #2694 consumes recognition_hash per eligible Subject in the frozen process
snapshot.