feat(individuality): add the personhood read layer - #300
Merged
Conversation
📦 Bundle size impactComparing
Thresholds — warn: ≥10% or ≥5.0 KB · fail: ≥20% or ≥15.0 KB (bundled). Percentage only applies once the baseline is ≥ 10 KB. |
TarikGul
reviewed
Aug 17, 2026
TarikGul
reviewed
Aug 17, 2026
TarikGul
reviewed
Aug 17, 2026
TarikGul
reviewed
Aug 17, 2026
TarikGul
approved these changes
Aug 17, 2026
TarikGul
left a comment
Member
There was a problem hiding this comment.
All small nits, you can decide if they are important enough.
Nice job
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.
Description
Adds
@parity/product-sdk-individuality, a new published package that reads a person's personhood state on the individuality chain from a DotNS username, at one pinned finalized block. Ports the derivation and the read from humanity-spa.Part of #286. Refs #287, does not close it. The eligibility half is held behind #291, and #291 is not the only gap: five items from the issue body sit outside this PR, mapped item by item in the issue analysis.
Two of those are deliberate divergences from the issue text rather than omissions, worth stating so a reviewer diffing the PR against the issue does not read them as oversights:
PeopleLite.LitePeoplepresence, notResources.Consumers(account).credibility. The issue names the latter. Both exist in the descriptor, so this is a choice: it follows the reference implementation in humanity-spa, which is the code this ports and the only version verified against the runtime. The alias is read from both pallets'AccountToAliasfor the same reason.People.AccountToPersonalIdis also not read. Both are additive and neither blocks the seven-state answer.Changes
New package,
product-sdk/packages/individuality/:src/types.ts: the seven-statePersonhoodStateunion (NotEnrolled,Lite,Candidate,MembershipReady,Member,Caution,Suspended), wrapped byUsernameUnowned | Resolved, plusFinalizedSnapshot,AbsenceGracePolicy,PersonhoodParticipantandPersonhoodInputs. Imports nothing, so both other modules can sit on it.src/derive.ts: the pure state machine, no I/O and no chain types. 15 tests.src/decode.ts: raw PAPI values to domain shapes. Rejects unknownstreakandrecognitionvariants, and enforces the runtime's grace invariants (window <= 8,allowedMisses < window) so a wrong byte order fails loudly instead of makingCautionunreachable. 19 tests.src/read.ts: the batch. Pins one finalized block, resolvesResources.UsernameOwnerOf, runs six parallel reads, then a conditional alias-keyedScore.Participantslookup. Returns aResult. 18 tests.src/errors.ts:ProductIndividualityErrorandIndividualityDecodeError, both implementing theSdkErrormarker. 3 tests.src/index.ts: the public surface, exporting the derivation separately from the read.package.jsonat0.0.0so the changeset publishes0.1.0. Depends on@parity/product-sdk-errors,@parity/resultandpolkadot-api.tsconfig.json,tsconfig.typecheck.json,tsup.config.ts,vitest.config.ts: copied frompackages/statement-store.Umbrella wiring, mirroring
cloud-storage: dependency and./individualityexport inpackages/sdk/package.json, the build entry inpackages/sdk/tsup.config.ts, andpackages/sdk/src/individuality/index.ts.Also in
packages/sdk:src/individuality/contract.test.ts, type-level assertions that a realgetChainAPIclient for bothpaseoanddevnetstill satisfies the read's structural chain type, plus a negative control that fails if that type ever stops constraining. The assertions gate CI throughpnpm typecheck, and the one runtime test only stops vitest reporting an empty suite. It lives in the umbrella package rather than beside the type because the same assertions are vacuous inside the individuality package, see Risks.Docs and release:
skills/product-sdk-individuality/SKILL.md: quick start, the state table, the traps.README.md,product-sdk/README.md,CLAUDE.md: register the package and skill in the three hand-maintained lists.pending-changesets/individuality-read-layer.md: minor for the package and the umbrella.Why these changes
humanity-spa already had this logic, verified against the runtime, so anything else needing the same answer would have had to copy it. This lifts it into the SDK, keeps the derivation importable on its own for #291, and takes an already connected chain client so the caller picks the chain. That last choice matters beyond tidiness: the original plan resolved the chain itself, and with
chain.getChainInfounreachable at the pinned truapi version it called for a static chain table behind a seam. An injected client needs neither, which is why #298 landing requires no change here.Testing
Before: the package did not exist on
main, so nothing covered this. The source vectors lived in humanity-spa, 12 for the derivation and 18 for decode and assembly.After: 55 tests pass in
packages/individuality(15 derive, 19 decode, 18 read, 3 errors) and 19 inpackages/sdk, up from 18 with the contract assertion.pnpm checkclean over 296 files.pnpm buildandpnpm typecheckclean.Risks
Score.PersonhoodThresholdis au8, and nothing can guard that. PAPI typesu8andu32alike asnumber, so the wrong width typechecks and passes tests. Checked against the committed metadata, and commented at the point of use.Score.AbsenceGraceRatiobyte order is(allowed_misses, window). The metadata tuple is anonymous, so the order comes from the pallet doc comment. This one is guarded: the decode enforcesallowedMisses < window, so a swapped order throws instead of silently makingCautionunreachable.Resources.UsernameOwnerOfis read in two places, here and atresolvePeopleUsernameOwnerinpackages/sdk/src/identity/dotns.ts. Deliberate: that one cannot pin a block and returns hex rather than theSS58Stringthe next reads need.packages/sdkbecause the same assertion is vacuous inside the individuality package, where the descriptor types do not fully resolve. Both halves were tested before choosing the placement.getChainAPI(env)selected, which is the right layering but means this PR does not pick one.