Skip to content

Complete the Onomancy fork - #1

Merged
expede merged 16 commits into
mainfrom
onomancy
Sep 2, 2026
Merged

Complete the Onomancy fork#1
expede merged 16 commits into
mainfrom
onomancy

Conversation

@expede

@expede expede commented Sep 2, 2026

Copy link
Copy Markdown
Member

No description provided.

Pins the toolchain (node, pnpm, playwright-from-nixpkgs) so the checks run
identically across machines. Playwright is deliberately taken from nixpkgs
rather than npm so the browser binaries match the driver.
Directory entries may claim a DNS name; a verifying directory wrapper
resolves the claim through onomancy (DNSSEC from the IANA root) and
decorates entries with a status the components render.

The library keeps the vocabulary and sheds the mechanism: it knows what
a claim is and what the statuses mean, and resolves nothing itself. The
onomancy Wasm arrives by injection, so the host application owns the
only instance.

Includes the DnsNameBadge, the claim field on ProfileEditor, a
deterministic hostname-split stub for the test app, and Playwright e2e
coverage.
Everything that performs DNS moves behind ./onomancy; the main entry
keeps the status vocabulary and the rendering. An application that
computes dnsNameStatus itself can use the components without importing
any resolution machinery.

Also: documentDelegatesTo asks whether a document delegates to an
identity (no DNS, no presentation); the VerificationCache gets listener
plumbing so checks finishing after a directory rebuild still notify;
peer dep moves to @automerge/automerge-repo-keyhive >=0.5.0-alpha.6.
DnsNameStatus grows from six values to twelve, organised by what the
reader should do: a typo, a domain making no claim, and a zone whose
records failed validation previously all rendered as "could not reach",
sending users to retry over faults retrying cannot fix.

Record selection now parses all TXT records, takes the highest serial
(BigInt: the space is u64 and Number equates neighbours near the top),
and surfaces same-serial disagreement as contested rather than picking.

The serial ratchet remembers the highest serial accepted per name so a
stale chain bearing a lower one is refused as replayed. Deferral
precedes movement: records dated past the skew bound are set aside
before selection, since a ratchet without the bound is jammed by one
forged serial. A fresh chain may move the ratchet in either direction,
which is what lets a poisoned ratchet heal. The ratchet survives cache
clears deliberately: a replay defence that revalidation erases defends
nothing.

Also: freshness gains magnitude (lapsedSeconds) and clock-skew fields;
failure classification reads the runtime's typed reason rather than
message text, falling back conservatively for older builds;
listMembers unions transitive members with direct ones, closing a
measured invisible-Admin path through the generated owner group; the
verified badge copy states that the check is one-directional (no
certificate is consulted); both apps carry a matching disclosure banner.
The branch graded a p= equal to the identity's own key as designates.
The spec requires p= to name a document (specs/anchoring/dns-anchor.md:72)
and is explicit that the key alone is not an identity, since the same
key bytes may be delegated in more than one document (:144). The "solo
publisher" allowance that motivated the branch is about g=, not p=.

A bare-key p= now reaches documentDelegatesTo, finds no document, and
grades unknown: not refuted, not proven. idEqualityDesignation survives
as an explicitly-labelled stub for tests.
lookup and list already filter the reserved key, so a write under it
succeeded and then became unreadable: it landed in the document and
every read path hid it, while each retry wrote again into the region
onomancy uses for protocol data. The spec reserves that region
deliberately - whoever can write the document holding a certificate can
remove or replace it, a naming-layer capability otherwise reserved to
admin-delegated keys - and a profile write must not be a route to it.

Throwing rather than dropping: a silent no-op is indistinguishable from
a write that worked when the read paths hide it either way.
The function read document.members() - direct delegations only - so
absence proved nothing and every non-member graded unknown, which
rendered as "not synced yet": the badge told people to wait for a
document that had already arrived. The doc comment justifying this
claimed no API exposes transitive delegations with capabilities, which
stopped being true when docMemberCapabilities landed.

With a transitive walk, a held document that reaches nobody by any path
is positive evidence of non-membership (insufficient -> mismatch), and
unknown goes back to meaning the document is not here to ask. This also
fixes the inverse case: an admin through a group was unknown too, so a
legitimate member saw "not synced" about a document they could read.
First registry version whose identifier names exactly one artifact;
byte-identical to the local tree both apps verified against.
Comments narrated the edits that produced the code - old copy quoted,
earlier versions described, session names and artifact shas cited. The
tree now states current rules; the narrative lives in the decision log
and the commit messages.

Also fixes text that had drifted from the code: README and the onomancy
entry point said six statuses where there are twelve, and an e2e
assertion held the pre-split tooltip copy ("could not be resolved")
that no status renders anymore.
publish removed dnsNameStatus but let dnsNameFreshness and
dnsNameLapsedSeconds through to the base directory, persisting stale
verification data the docs say is never stored. All three are computed
per lookup; none survives a write.

Found by Copilot review on #4.
subscribe passed the raw callback through to base.subscribe. A base
directory that deduplicates listeners by identity (as the demo's
localDirectory does) collapses two subscriptions sharing one callback,
and the first unsubscribe cancels the second subscriber's base updates.
The cache-side Set was already wrapper-per-subscription; the base side
now is too.

Found by Copilot review on #4.
Copilot AI lite review requested due to automatic review settings September 2, 2026 20:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The verification cache can be repopulated by in-flight async resolutions/verdicts after clearing, and ProfileEditor has concrete state-sync/normalization edge cases that can silently misrepresent or clear DNS claims.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR adds first-class support for DNS name claims and onomancy-backed verification to the keyhive-react library, including a new @automerge/keyhive-react/onomancy entrypoint, directory schema extensions, UI badges, and updated test app + e2e coverage.

Changes:

  • Introduces an onomancy runtime + verified directory wrapper with caching and a React hook to keep verification results across directory rebuilds.
  • Extends directory entries/documents to store dnsName claims and renders verification via a new DnsNameBadge across key UI surfaces.
  • Updates the component test app, e2e tests, and tooling (Playwright/Tailwind/Nix/ESLint/Prettier ignores) to exercise and ship the new functionality.
File summaries
File Description
src/runtime.ts Adds DocumentId to injected Keyhive runtime.
src/onomancy/verified-directory.ts Implements DNS resolution + designation verification with shared cache.
src/onomancy/useOnomancyDirectory.ts React hook wrapper preserving verification cache across rebuilds.
src/onomancy/runtime.ts Onomancy runtime adapter + record parsing helpers.
src/onomancy/index.ts New @…/onomancy entrypoint exports.
src/onomancy/designation.ts Designation strategies (id equality + keyhive delegation).
src/index.ts Exposes DNS-related types/components + reserved onomancy key.
src/directory/types.ts Adds DnsNameStatus + DNS claim/decoration fields to entries.
src/directory/automerge-directory.ts Persists dnsName and reserves onomancy top-level doc key.
src/components/ProfileEditor.tsx Adds DNS name claim field + publish behavior.
src/components/primitives/DnsNameBadge.tsx New badge component rendering claim status/freshness.
src/components/ContactBook.tsx Searches and displays DNS name badges in results.
src/components/AccountView.tsx Wires DNS name claim UI into the account editor.
src/components/AccessEditor.tsx Displays DNS name badges next to members.
src/access/targets.ts Improves member listing to include transitive holders.
src/access/delegation.ts Adds reusable “document delegates to identity” helper.
scripts/check-prefix.mjs Makes Tailwind CLI resolution shell-independent; expands false positives.
README.md Documents DNS names/onomancy entrypoint and usage.
pnpm-lock.yaml Bumps deps (keyhive repo keyhive, Playwright) and adds onomancy.
playwright.config.ts Sets workers to 2 universally for stability.
package.json Adds ./onomancy export + bumps versions/deps.
flake.nix Adds Nix devshell + CI command wrappers.
flake.lock Locks Nix inputs for the new flake.
eslint.config.mjs Expands ignore set for scratch/output directories.
e2e/smoke.spec.ts Tightens selector matching for Name textbox.
e2e/shared-directory.spec.ts New test ensuring names sync via shared directory doc.
e2e/names.spec.ts New tests for path binding/resolution behavior.
e2e/dns-names.spec.ts New tests for DNS claim/verification UI behavior.
apps/component-test-app/vite.config.ts Ensures onomancy dependency is pre-bundled.
apps/component-test-app/src/onomancyStub.ts Adds deterministic .test DNS outcomes for e2e.
apps/component-test-app/src/nameResolution.ts Adds local onomancy-style name parsing + path walk resolver.
apps/component-test-app/src/localDirectory.ts Persists dnsName in localStorage directory.
apps/component-test-app/src/composeDirectories.ts Composes shared+local directories (read merge, write both).
apps/component-test-app/src/App.tsx Adds shared directory doc, name binding/resolution UI, onomancy runtime wiring.
apps/component-test-app/package.json Adds onomancy dependency and bumps keyhive repo keyhive.
.prettierignore Ignores scratch/output dirs Prettier would otherwise traverse.
.gitignore Ignores scratch/output dirs produced by tooling.
Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (1)

src/onomancy/verified-directory.ts:321

  • Same in-flight issue as the success path: if clearVerificationCache runs (or another request supersedes this one) while resolveBoundIds is pending, the rejection handler can still write a stale phase back into resolutions and notify subscribers.

Guard the write so only the currently-active pending request can settle the entry.

  • Files reviewed: 34/37 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/components/ProfileEditor.tsx
Comment thread src/components/ProfileEditor.tsx
Comment thread src/onomancy/verified-directory.ts
Comment thread src/onomancy/verified-directory.ts
Three reviewers over 2b43e01..HEAD. The findings:

- Docs stated pre-transitive-walk semantics the code no longer has:
  DelegationVerdict said insufficient could never mean non-membership,
  designation.ts inherited a removed limit, and the README's group-admin
  gap paragraph claimed no API exposes transitive delegations with
  capabilities (docMemberCapabilities is that API and the code uses it).
- composeDirectories forwarded the raw listener to both children - the
  identity-collapse bug class fixed one layer above in 6869a1c.
- Both recent bug fixes were unpinned, and the e2e suite provably passes
  with them reverted (verified empirically against a pre-fix bundle).
  tests/verified-directory.test.mjs pins them with bare node --test
  against dist/, wired into pnpm check; both pins fail on pre-fix code.
- Wording repairs: the non-answers grouping no longer claims chain-failed
  and replayed as absences; the g=-vs-p= solo-allowance distinction is
  restored; two doc comments reattached to the declarations they
  describe; stale phase counts and the p= description corrected.
Published as a fork of @automerge/keyhive-react. The npm name, repository
metadata, CSS artifact (dist/onomancy-react.css), flake derivation names,
and every import in the test app and e2e suite move to the new name.

The technology names are unchanged on purpose: KeyhiveRuntime, the
@automerge/automerge-repo-keyhive peer dependency, and the kh- CSS class
prefix all refer to keyhive the system, which this package still wraps.
The version-adjust commit hand-collapsed the files and sideEffects
arrays onto single lines; prettier --check refuses that shape, which is
what failed CI on 74f712d. Content unchanged.
@expede
expede merged commit bc15116 into main Sep 2, 2026
1 check passed
@expede
expede deleted the onomancy branch September 2, 2026 21:36
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.

2 participants