Skip to content

feat(core): identity model — entityId becomes a DID, did:key floor (#49)#104

Merged
cuibonobo merged 4 commits into
mainfrom
claude/issue-49-design-review-bivv2t
Jul 20, 2026
Merged

feat(core): identity model — entityId becomes a DID, did:key floor (#49)#104
cuibonobo merged 4 commits into
mainfrom
claude/issue-49-design-review-bivv2t

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Summary

Implements the @haverstack/core-scoped portion of #49: entityId becomes a DID string everywhere identity is meant, did:key (Ed25519) is the mandatory floor method, and _entity records become explicit local profile cards (petnames) about a DID rather than the identity itself.

  • New packages/core/src/did.tsdid:key generation, parsing/validation, sign/verify, and private-key JWK export/import, built entirely on Web Crypto (crypto.subtle) with zero new dependencies, matching core's existing zero-dependency design.
  • EntityId type alias (= string, purely additive) applied across Permission, GrantContent, ConfigContent, StackRecordAdapter.ownerEntityId, StackTokenStore, access.ts, and the public Stack/ScopedStack API — fixes a real type conflation the RFC calls out: an entity was previously typed as RecordId, but it's never actually a record within this stack.
  • EntityContent gains a required did field. _entity@1's runtime schema updated in place — no version bump, since there's no install base yet.
  • Stack.create(adapter, { ownerProfile }) idempotently creates the owner's own _entity profile record on first init — previously nothing did this at all, despite the spec assuming it existed.
  • docs/spec.md: new "Identity" section (DID model, did:key floor, petname pattern, challenge–response auth sketch, the verified-vs-anonymous 401/403 distinction from the issue's own comment thread, deferred key rotation). Both READMEs' quickstarts updated to the generateDidKeypair() + ownerProfile flow; also fixed a pre-existing stale @haverstack/adapter-sqlite/SQLiteAdapter reference in packages/core/README.md.

Scope

Deliberately limited to what's fully implementable and testable within @haverstack/core, per the RFC's own migration notes:

  • In scope: DID helpers, type/schema changes, owner bootstrap, docs.
  • Out of scope (follow-up work): group keypairs/signing, and the live server-side challenge–response endpoint (haverstack/server is a separate repo — this PR only documents the wire-level sketch).

Test plan

  • pnpm -r run build — all 9 workspace packages build clean
  • pnpm -r run typecheck — no errors
  • pnpm -r run test — 828 tests pass across all packages, no regressions
  • pnpm run lint (core) — clean
  • 16 new tests for did.ts (keypair generation, sign/verify round-trip, tampered-data/wrong-key rejection, malformed-input handling, a known W3C did:key test vector, JWK round-trip)
  • 6 new tests for Stack.create({ ownerProfile }) (creation, idempotency across reopen, no-overwrite-on-reopen, handle-omitted case, unauthored-record convention)

Closes #49 (core-scoped portion).


Generated by Claude Code

claude and others added 4 commits July 18, 2026 13:41
Everywhere entityId means "who" now carries a DID string. Adds did.ts
(Ed25519 did:key generation, parsing, signing, verification via Web
Crypto only, no dependency) and a new EntityId type alias distinct from
RecordId. EntityContent gains a required `did` field — _entity records
are now explicitly local profile cards (petnames) about a DID, not the
identity itself.

Stack.create() gains an ownerProfile option that idempotently creates
the owner's own _entity record, closing the gap where nothing
previously did. Updates README/spec quickstarts to the
generateDidKeypair() + ownerProfile flow, and documents the DID model,
challenge-response auth sketch, and deferred key rotation in
docs/spec.md's new Identity section.

Scoped to what's fully implementable in @haverstack/core: group
keypairs and the live server-side auth endpoint are left to follow-up
work, per the RFC's own migration notes.
The merge of main into this branch (bringing in #69's timezone/entityId
changes) left ConfigContent and StackRecordAdapter with duplicate
entityId/timezone members instead of actually reconciling the two
sides, and reverted the spec.md quickstart's entityId back to a plain
string after generating a DID for it. Neither was caught by the
compiler (duplicate members in a type literal aren't an error) — found
by manual review and fixed by hand; full build/typecheck/lint/test
suite verified clean across all packages afterward.
CI's format:check caught formatting drift introduced across this
branch's edits (mostly markdown table/line-width reflow in spec.md).
No content changes — pnpm run format applied.
@cuibonobo
cuibonobo merged commit bcef1ee into main Jul 20, 2026
5 checks passed
@cuibonobo
cuibonobo deleted the claude/issue-49-design-review-bivv2t branch July 20, 2026 12:43
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.

RFC: identity model — entityId becomes a DID (did:key floor), _entity records become local profiles

2 participants