Skip to content

Epic 1: a second person can verify - #367

Merged
bordumb merged 4 commits into
mainfrom
epic1/second-person-can-verify
Jul 17, 2026
Merged

bordumb merged 4 commits into
mainfrom
epic1/second-person-can-verify

Conversation

@bordumb

@bordumb bordumb commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

The problem

Only the signer could verify anything. Established by running the product, not reading it:

$ git clone <repo> && auths verify HEAD
Verification failed: KEL not found for did:keri:EKjF-HM6…          exit 1
$ auths verify HEAD --remote origin
Verification failed: Root … is not a pinned trusted root.          exit 1
$ auths verify HEAD --identity-bundle bundle.json
Error: a bundle is evidence for a pinned root, never the source of the pin.  exit 2

That third error is correct — a bundle that bootstrapped its own trust would be circular. The design was right. Two small bugs and one missing default meant nothing could reach a second person.

Placing .auths/roots by hand made every path return exit 0. The engine was never the problem.

Root cause

init/mod.rs wrote <repo>/.auths/roots and never staged it. That file's mere existence then made the hook's [ ! -e ] guard at commit_hooks.rs:50 false forever, so the hook never staged it either. The two mechanisms were written to cooperate (see the comment at init/mod.rs:426) and instead cancelled out. The pin stayed untracked, never reached a cloner, and every third-party path dead-ended on it.

Separately, auths registry push had zero callers outside its own command — logged in docs/plans/blockers/fixes.md as "4D: nothing pushes the registry" — so the KEL never reached the remote either. And a plain git clone fetches only refs/heads/* and refs/tags/*, so even once mirrored, auths verify never looked.

The four commits

Commit What
47e4a8dc New RepoIndex port + roots::pin_root_in_repo workflow: pin and stage. Hook keys off tracked-ness, not existence, and stops claiming the pin is "committed with this commit" (it lands in N+1 — git snapshots the index before prepare-commit-msg). Hook → v2.
8d57d020 Managed pre-push hook mirrors refs/auths/registry to the code remote. Non-fatal, opt-out via auths.autopush=false, and chains to the repo's own pre-push — core.hooksPath replaces .git/hooks, so a non-chaining hook would silently disable husky/pre-commit/prek machine-wide.
dc12ee66 auths verify defaults its KEL transport to the repo's own origin. Free in the common case (KelResolverChain is local-first; nothing is fetched when the KEL is local) and fires exactly where verification would otherwise fail.
6a8d8f14 The tests that would have caught this.

Result — the acceptance criterion

On a machine with no ~/.auths, having never met the signer:

$ git clone <repo> && cd <repo>
$ auths verify HEAD
Commit 200a9c0… verified: signed by did:keri:EO0bMMH4XfQdwD1WYbssMjDi2016rcVVc98G0dQrVDBM
exit 0

No flags. No identity. No setup.

On the tests

Every existing verification test runs as the signer with a populated ~/.auths — which is exactly why a total failure of the core promise shipped with a green suite. The new tests play the second person, and the fixture uses plain git commit / git push (never auths sign / auths registry push), since a fixture reaching for the manual verbs would prove nothing about the default path.

Mutation-checked: restoring the old write-but-never-stage behaviour fails 3 of the 4.

Verification

  • cargo test -p auths-cli --test integration → 67 passed, 0 failed
  • cargo test -p auths-sdk --lib → 176 passed, 0 failed
  • cargo clippy -p auths-sdk -p auths-cli --all-targets → clean
  • cargo fmt --all → applied

Honesty note

Committing .auths/roots makes the pin travel; it does not make first-clone trust sound. A cloner trusting the repo's own pin is doing TOFU-on-the-root, which roots.rs:6-8 correctly warns is unbounded. What this buys is continuity — the same root signed throughout, rotations chained, later substitution detectable. That is what the flight-recorder use case needs, and it should be documented that way rather than sold as authenticity. Flagged for a second opinion.

bordumb added 4 commits July 17, 2026 10:12
`auths init` wrote `<repo>/.auths/roots` and never staged it. That file's mere
existence then made the hook's `[ ! -e ]` guard false forever, so the hook never
staged it either. The pin stayed untracked, never reached a cloner, and every
third-party verification path dead-ended on it:

    git clone && auths verify HEAD   -> KEL not found
    ... --remote origin              -> not a pinned trusted root
    ... --identity-bundle b.json     -> bundle is not a source of its own pin

The pin is the only trust anchor the delegation model permits (an Auths-Id
trailer may *select* among pinned roots, never *establish* one), so nothing
downstream could work. Placing the file by hand made every path return exit 0 —
the engine was never the problem.

- New `RepoIndex` port (stage/is_tracked), mirroring `GitConfigProvider`, with a
  subprocess-backed `SystemRepoIndex` adapter in the CLI.
- New `roots::pin_root_in_repo` workflow owns pin+stage and reports what it did.
  Staging, not committing: init must not author a commit on the user's behalf —
  the pin rides their next one, which they are about to make anyway.
- Hook keys the repair off tracked-ness, not existence, and no longer claims the
  pin is "committed with this commit". A `git add` in prepare-commit-msg lands in
  commit N+1: git has already snapshotted the index. Hook bumped to v2.

Verified end-to-end: `auths init` now reports `A  .auths/roots`, and the pin
lands in the user's next commit.
`auths registry push` worked and had zero callers outside its own command and
test — logged in the blockers doc as "4D: nothing pushes the registry". So the
signer's KEL never reached the remote their code went to, and a cloner had
nothing to resolve `Auths-Id` against.

Adds a managed `pre-push` hook installed alongside `prepare-commit-msg`. Three
properties are deliberate:

- Non-fatal: every failure path exits 0. No write access to the remote is the
  common, expected case and must never block a code push.
- Opt-out via `git config auths.autopush false`. A KEL is public by construction,
  but pushing to a remote you do not own should stay refusable.
- Chains to the repo's own pre-push. `core.hooksPath` *replaces* `.git/hooks`, so
  a managed hook that did not chain would silently disable every repo-local hook
  on the machine (husky, pre-commit, prek) — the same class of bug this repo is
  currently living with.

`hook_is_current` now covers both hooks, so `auths doctor` surfaces a stale
pre-push instead of leaving a silent gap. Write+chmod factored into one
`write_executable` helper.

Verified live: a plain `git push` to a bare origin now leaves
`refs/auths/registry` on it, and `auths.autopush=false` suppresses the mirror.
A repo now carries its signer's KEL on the same remote it carries its code (the
managed pre-push hook mirrors refs/auths/registry there) — but `git clone`
fetches only refs/heads/* and refs/tags/*. So in a fresh clone the KEL sits one
fetch away on the remote it was cloned from, and `auths verify` failed with "KEL
not found" without ever looking.

`resolve_signer_kel` now falls back to `git remote get-url origin` when neither
--remote nor --oobi is given. This is free in the common case: KelResolverChain
is local-first, so nothing is fetched when the KEL is already local, and the
local registry remains the trusted floor (a remote can only advance key-state,
never roll it back). The fetch fires exactly where verification would otherwise
have failed.

Both --remote docs updated: they claimed "without this flag, resolution is
local-only (no network)", which this makes false.

Epic 1's acceptance criterion now holds end-to-end. On a machine with no
~/.auths, having never met the signer:

    git clone <repo> && auths verify HEAD
    -> Commit 200a9c0… verified: signed by did:keri:EO0bMMH4…   (exit 0)

no flags, no identity, no setup.
Every existing verification test runs as the signer, with a populated ~/.auths.
That is precisely why a total failure of the product's core promise shipped
undetected: the suite was green and a fresh clone could not verify anything. A
test that always plays the signer can never catch it.

Adds tests that play the *second person* — no auths identity, a plain `git
clone`, no flags:

- a_second_person_verifies_a_plain_clone_with_no_identity_and_no_flags (headline)
- the_trust_pin_travels_with_the_repository (.auths/roots is in the clone)
- a_plain_push_mirrors_the_registry_to_the_remote (pre-push, not a manual push)
- an_unpinned_root_still_fails_closed (the TOFU-on-the-root guard holds)

The fixture pushes with a plain `git push` and commits with a plain `git commit`
— never `auths registry push` or `auths sign` — because a fixture that reaches
for the manual verbs would prove nothing about the default path.

Mutation-checked: restoring the old write-but-never-stage behaviour in `auths
init` fails 3 of the 4. These are tests that would have blocked the regression,
not tests that merely pass alongside it.
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
auths Ready Ready Preview, Comment Jul 17, 2026 9:38am

@github-actions

Copy link
Copy Markdown

Auths Commit Verification

Commit Status Details
6a8d8f14 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).
dc12ee66 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).
8d57d020 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).
47e4a8dc ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).

Result: ❌ 0/4 commits verified


How to fix

Commit 6a8d8f14 has no Auths signature (no Auths-Id/Auths-Device trailer).

1. Install auths

macOS: brew install auths
Linux: Download from releases

2. One-time setup (creates your identity and configures Git)

auths init

3. Sign this branch and push

auths sign origin/main..HEAD
git push --force-with-lease

For CI to verify the signer, commit an identity bundle:

auths id export-bundle --alias main --output .auths/ci-bundle.json --max-age-secs 31536000

Quickstart →

@bordumb
bordumb merged commit 85904b5 into main Jul 17, 2026
24 of 25 checks passed
@bordumb
bordumb deleted the epic1/second-person-can-verify branch July 17, 2026 09:49

This branch was successfully deployed

1 active deployment
Preview — 6a8d8f14 Deployed Jul 17, 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