Skip to content

Show BRC-169 handles for commit authors - #1

Merged
shruggr merged 2 commits into
mainfrom
feat/brc169-handles
Sep 20, 2026
Merged

shruggr merged 2 commits into
mainfrom
feat/brc169-handles

Conversation

@shruggr

@shruggr shruggr commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

Shows commit authors as verified BRC-169 handles, modelled on git: the handle is whatever is in the commit's author/committer email slot. Nothing changes in the head token; nothing is required of users; resolution is best effort and never breaks a page.

Rules

  1. Parse (lib/handles.ts parseHandle): accepts handle@domain (paymail form, BRC-169 2.1.7) and @handle@domain, with an optional +tag, and normalises to lowercase @handle@domain. Plain names, dotless ecosystems (aliases), and grammar violations are left as plain text and never touch the network.
  2. Discover: https://<domain>/manifest.json must carry metanet.handles with major version 1; its resolve URL is used (default /.well-known/metanet-handles/resolve when absent). No metanet.handles means unresolvable, and the well-known path is not probed.
  3. Resolve: GET <resolve>?handle=<handle> with the tag stripped. Only a 200 with a well-formed identityKey and a certificate object counts (subject/handle/domain echoes are checked when present; revoked: true rejects). 404/410/other/timeouts are unresolved. Manifests and bindings are cached in memory server-side: ttl clamped to 60 s..24 h, failures 5 min, 3 s timeouts, in-flight lookups shared.
  4. Verify per head: a handle is verified only when the resolved identityKey equals the identity that signed the head being viewed. The same commit on someone else's head shows the author as plain text there. No cross-head lookups, no overlay index.
  5. Display (components/handle-link.tsx): verified handles render as @[email protected] with a verified mark, linking to the identity page. A host-supplied displayName is only surfaced in the tooltip, labelled unattested (2.4.8). Unverified authors render exactly as before. The repo header labels the owner and duplicate branch-picker entries with the handle from that identity's own verified current head on the origin; the pubkey display stays.
  6. Server-side: lib/handles-server.ts holds the shared resolver; server pages call authorHandles / commitHandles. The client-only "My repos" page goes through GET /api/handles and applies the same matchAuthorHandles rule locally.

Not implemented on purpose: search, reverse, aliases, messaging, payments, delegation, certificate signature/revocation checks.

Verification

  • lib/handles.test.ts (23 tests, mocked fetch): grammar and normalisation (valid, paymail, tagged, invalid, alias), manifest handling (resolve URL, default path, missing metanet.handles → no probing, bad major/non-https), ttl clamping, resolve-response validation, the verification rule (matching vs non-matching signer), and the resolver's caching (ttl expiry, negative and positive manifest ttl, shared in-flight lookups, error statuses).
  • bun run lint, bun run typecheck, bun test (37 pass), and bun run build all pass. Lint needed a one-line biome-ignore for a pre-existing control-character regex finding in branch-button.tsx.
  • README and CLAUDE.md document the rule.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EM5trZ4BfZpW7pBjq2onqX

The handle is whatever sits in the git commit's author/committer email
slot, as git shows it: `[email protected]` (paymail form) or
`@[email protected]`, normalised to lowercase `@handle@domain`. Anything
else stays plain text. Nothing is added to the head token and nothing is
required of users; resolution is best effort and never breaks a page.

Discovery follows BRC-169 sections 5.1-5.4: fetch the domain's
manifest.json, require metanet.handles with major version 1, use its
resolve URL (well-known default when absent), and treat a domain without
metanet.handles as unresolvable without probing. GET resolve?handle=
with the tag stripped; only a 200 with a well-formed identityKey and a
certificate counts. Manifests and bindings are cached in memory
server-side (ttl clamped to 60 s..24 h, failures 5 min, 3 s timeouts,
in-flight lookups shared).

A handle is verified for a head only when the resolved identityKey is
the identity that signed that head. The same commit on another
publisher's head shows the author as plain text there. No cross-head
lookups, no overlay index.

Verified handles render as `@[email protected]` with a verified mark,
linking to the identity page, on head lists (explore, user, commits,
commit DAG, my repos via /api/handles) and on the commit page's author
and committer rows. The repo header labels the owner and duplicate
branch entries with the handle from that identity's own verified head on
the origin; the pubkey display stays.

Also silences a pre-existing Biome control-character finding in
branch-button.tsx so `bun run lint` passes.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EM5trZ4BfZpW7pBjq2onqX
@vercel

vercel Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
gibhub Ready Ready Preview Sep 20, 2026 3:26am UTC

Request Review

The workflow pinned bun 1.3.14 while bun.lock has been lockfileVersion 2
since the initial commit, so `bun install --frozen-lockfile` has never
passed. Pin the version the project actually builds with.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EM5trZ4BfZpW7pBjq2onqX
@shruggr
shruggr merged commit 8fc19fc into main Sep 20, 2026
3 checks passed
@shruggr
shruggr deleted the feat/brc169-handles branch September 20, 2026 04:25

This branch was successfully deployed

1 active deployment
Preview — 0abe3fe7 Deployed Sep 20, 2026 by vercel[bot]
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.

1 participant