Skip to content

The head is a bare token, and its commits live in the tree - #4

Merged
shruggr merged 2 commits into
mainfrom
feat/head-v2
Sep 21, 2026
Merged

shruggr merged 2 commits into
mainfrom
feat/head-v2

Conversation

@shruggr

@shruggr shruggr commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Makes the site correct against gib's new head format (1sat-stack#55, feat/gib-head-v2).

Do not merge before the stack deploys. The new format is not on api.1sat.app yet; merging this deploys a site that decodes nothing the live overlay serves.

The format

A commit head is a bare PushDrop coin — nothing inscribed on the output — with six fields: the five it had plus a branched-from outpoint. The commits moved into the published root: git's tree for the tip commit plus a .git object store holding every commit reachable from it, named by sha, with a . entry pointing at the tip. The old five-field head with an inscribed commit is a different format and no longer decodes; lib/gib-head.test.ts pins the refusal.

Where the repositories page gets its commit

/me decodes the wallet's own basket coins, and its whole point is that it works with no gib overlay at all. So it reads the commit from the tree, not from the overlay's head record: a second react-query walks root → .git → . through ORDFS, keyed on the roots, so the branch list renders from the token immediately and the subjects fill in. Going to /1sat/gib/head/{outpoint} would have been one fetch instead of two and would have made "my repositories" depend on an indexer having caught up — which is exactly what that page exists to avoid.

Server-rendered pages did not need to change: the overlay's head record carries the tip commit it read out of .git at admission.

Branched-from

The second parent, which the site has never been able to show. With a spend it is a merge; without one it is where a branch began. Badged in the push list, and named on the head page as merged in or branched from — where the source head is resolved so the link goes under its own repository origin rather than an assumed one.

Branches from the chain

lib/gib-lookup.ts wraps the BRC-24 branches query. Every page rendering RepoHeader fetches it: it is the only source that says which branches exist per publisher, which ones nobody extends any more (spent, shown in the picker), and which branch a clone starts from. .gib's defaultBranch is a label its publisher wrote; branches.defaultBranch is the genesis push's branch. Preference order is lookup → .gib → the main/master guess, and the REST branch heads remain the fallback when the lookup cannot answer.

headsSince and txs are not wrapped — they are what a client cloning a repository needs, and this site browses through ORDFS.

.git was not hidden

It is now. Every listing a user sees passes through withoutGitStore, and the tree and blob routes notFound() a path starting with .git. The store is read on purpose in one place, with a light manifest read rather than loadDirectory: a store names the whole history, and enriching thousands of entries with bulk metadata to read one would be absurd.

Beyond the brief: submission

The stack's feat/gib-head-v2 removes the event bridge, the queue and the JungleBus subscription — pkg/gib/README.md, "a head arrives by submission or not at all". Nothing indexes a head off the chain any more, so the Branch button would have minted coins nobody could ever see. mintHead now hands the transaction to tm_gib over BRC-22 with what admission needs to read the push (the root, its .git store, the branched-from head), as a BEEF V2 with the head last. A failed submission is not a failed mint and is reported as such.

@1sat/client's OverlayClient could not serve this: its submit hard-codes /1sat/overlay/submit, and gib's engine is mounted at /1sat/gib/overlay. Worth adding to the SDK as a path the caller picks.

BRC-169

Untouched. Verification is still per head against the identity that signed the head being viewed; the commit simply arrives from the tree or the overlay record instead of the token.

Tested against

Fixtures and local fakes, since production serves the old format:

  • heads locked exactly the way mintHead locks them, with and without branched-from, plus the raw-byte outpoint and identity forms the stack's reference encoder writes, and the legacy inscribed head asserted to be refused;
  • encoded ordfs/dir manifests behind a stubbed fetch, for the .git walk and the filters;
  • a stubbed ls_gib answering with the double-encoded result string pkg/gib/lookup_sync.go serializes, including the empty-repository and 500 cases;
  • real transactions for the submission, pinning that the head is serialized last.

bun run lint, bun run typecheck, bun test (55) and bun run build are clean.

One contradiction worth flagging

The gib CLI's internal/token is still the five-field format with the inscribed commit, and AGENTS.md says that if the site and that package disagree, the site is wrong. Here the stack is the authority and the CLI has not caught up — so the CLI needs the same change before a browser-minted head and a CLI push are indistinguishable again.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EM5trZ4BfZpW7pBjq2onqX

shruggr and others added 2 commits September 21, 2026 00:35
A commit head is now a bare PushDrop coin with six fields — the five it
had plus a branched-from outpoint — and nothing inscribed on the output.
The commits moved into the published root: git's tree for the tip commit
plus a `.git` object store holding every commit reachable from it, named
by sha, with a `.` entry pointing at the tip. The five-field head with an
inscribed commit is a different format and no longer decodes; there is no
migration path and the test suite pins the refusal.

`lib/gib-head.ts` decodes the bare token, tolerating the raw-byte forms
the stack's reference encoder writes as well as the text ones this site
writes, and reads the commit from the root instead of the output.
`/me` is the one place that needed it: it decodes the wallet's own basket
coins, so it now walks root → `.git` → `.` through ORDFS in a second
query keyed on the roots. That keeps the page's whole point — it is a
gateway read, not a gib overlay round trip, so a head no indexer has seen
still shows its commit. Server-rendered pages read the commit off the
overlay's head record, which carries what it read out of `.git` at
admission, and did not change.

Branched-from is the second parent, so the site can finally show what it
never could: with a spend it is a merge, without one it is where a branch
began. Both are badged in the push list and named on the head page, where
the source head is resolved so it links under its own repository origin.

`.git` is gib's, not the project's. Every listing a user sees passes
through `withoutGitStore`, and the tree and blob routes refuse a path
that starts with it. The store is read on purpose in exactly one place,
with a light manifest read rather than `loadDirectory`: a store names
every commit in the history, and enriching all of it with bulk metadata
to read one entry would be absurd.

Branches now come from the overlay's BRC-24 `branches` lookup rather than
from guessing. It is the only source that says which branches exist per
publisher, which ones nobody extends any more, and which branch a clone
starts from — `.gib`'s defaultBranch is a label its publisher wrote, and
this is what the chain shows. The REST branch heads stay as the fallback
when the lookup cannot answer, and as the source of the commit authors
the handle labels are verified against.

Nothing indexes a head off the chain any more: the gib module has no
queue, no event bridge and no feed, so a head that is minted and not
submitted is on chain and invisible. `mintHead` therefore hands the
transaction to `tm_gib` over BRC-22 with the transactions admission needs
to read the push — the root, its `.git` store, and the branched-from head
— as a BEEF V2 with the head last. A failed submission is not a failed
mint, so it is reported as such rather than thrown.

Branching is now a single wallet call: it points at the same root, which
already carries the commit, and names the head it came from. Nothing is
fetched and nothing is republished.

The new format is not on api.1sat.app yet, so the tests build the shapes
themselves: heads locked exactly the way `mintHead` locks them, encoded
`ordfs/dir` manifests behind a stubbed fetch, a stubbed `ls_gib` answering
with the double-encoded `result` the Go service serializes, and real
transactions for the submission ordering.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EM5trZ4BfZpW7pBjq2onqX
AGENTS.md carried the five-field head with an inscribed commit as the data
model, which is no longer true anywhere. It now describes the bare
six-field token, the `.git` object store and the rule that nobody browsing
a repository sees it, the branched-from discriminator, the BRC-24 lookups
and why only `branches` is wrapped here, and the submission — including
that `@1sat/client`'s `OverlayClient` cannot serve it, because its submit
hard-codes `/1sat/overlay/submit` while gib's engine is mounted at
`/1sat/gib/overlay`. That belongs in the SDK as a path the caller picks.

The "two wallet calls, one of which costs money" hazard is gone: branching
no longer fetches a commit object first. In its place is the one that
replaces it — a commit the overlay does not hold is normal, because a push
may cite an object already on chain rather than republish it.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EM5trZ4BfZpW7pBjq2onqX
@vercel

vercel Bot commented Sep 21, 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 21, 2026 4:35am UTC

Request Review

@shruggr
shruggr merged commit 250f3a8 into main Sep 21, 2026
3 checks passed
@shruggr
shruggr deleted the feat/head-v2 branch September 21, 2026 04:37

This branch was successfully deployed

1 active deployment
Preview — ca146dd8 Deployed Sep 21, 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