Skip to content

Add ucanlib (from libforge) and fixed test identities to testutil - #57

Draft
Peeja wants to merge 3 commits into
mainfrom
claude/forge-consolidation
Draft

Peeja wants to merge 3 commits into
mainfrom
claude/forge-consolidation

Conversation

@Peeja

@Peeja Peeja commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

[From Claude:]

Two additive commits that let libforge's ucan and testutil packages be retired as part of consolidating libforge into the forge monorepo. Nothing existing changes; the full module builds, vets and tests clean standalone (GOWORK=off).

ucanlib/ — invoker-side proof chain assembly

libforge's ucan package (Go package name ucanlib) moved here as ucanlib/. It is the invoker-side counterpart to validator/: validator walks a proof chain already attached to a token and checks it; ucanlib searches a delegation store and assembles one, in the order an invocation's prf list expects — DelegationMatcherFunc, ProofChain, ProofStore, and the container-backed ContainerProofStore. Both traverse the same Container type from opposite ends, which is why it belongs in this module rather than in a Forge-specific library.

The directory is ucanlib (the package's own name) because ucan/ is taken here. Source is byte-identical to libforge's ucan/proof_chain.go and ucan/proof_store.go apart from a package doc comment; the tests differ only in import paths.

Deliberately not included from libforge's ucan/ directory: ucan/retrieval (an HTTP transport binding used by Forge services, staying in the monorepo) and ucan/zapucan (depends on go.uber.org/zap, which this module's dependency policy excludes).

testutil/ — Must2 and fixed identities

Every symbol libforge/testutil exports is already a re-export of this package except two things dependents actually use: Must2 (the two-value form of Must) and six fixed identities — Alice, Bob, Carol, Mallory, Service, WebService — for tests that need stable DIDs. Both are added here (same throwaway ed25519 test keys as libforge's fixtures, so tests that hard-code the DIDs keep passing after switching imports). With these in place, dependents can import ucantone/testutil directly and libforge/testutil can be deleted rather than moved.

Verification

GOWORK=off go build ./... && GOWORK=off go vet ./... && GOWORK=off go test ./...

AGENTS.md gained a bullet for each addition. No other files touched.


Part of the forge consolidation; the forge PRs that consume this pin github.com/fil-forge/ucantone@v0.0.0-20260904190501-7fca40e13941 (this branch's head) and should be re-pinned to main once this merges.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt


Generated by Claude Code

Adds the two things dependents have been getting from libforge/testutil
that this package did not have: Must2, the two-value form of Must, and
six fixed identities (Alice, Bob, Carol, Mallory, Service, WebService)
for tests that need stable DIDs rather than fresh random ones. Every
other symbol libforge/testutil exports is already a re-export of this
package, so with these in place dependents can import ucantone/testutil
directly.

The identities are the same throwaway ed25519 keys libforge's fixtures
use, so tests that hard-code their DIDs keep passing after switching
import paths. WebService is Service under a did:web identity.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
Moves libforge's ucan package (Go package name ucanlib) into ucantone.
It is the invoker-side counterpart to validator: validator walks a proof
chain already attached to a token and checks it; ucanlib searches a
delegation store and assembles one — DelegationMatcherFunc, ProofChain,
ProofStore, and the container-backed ContainerProofStore — in the order
an invocation's prf list expects. Both traverse the same Container type
from opposite ends, so they belong in the same module.

The directory is ucanlib rather than ucan (the package's own name, and
ucan/ is taken here). Source is byte-identical to libforge's apart from
a package doc comment and the import paths in the tests, which now use
this module's testutil for the fixed identities they rely on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
Peeja pushed a commit to fil-forge/forge that referenced this pull request Sep 4, 2026
github.com/fil-forge/forge/protocol carries what libforge published as
the network's wire contract, taken from libforge main (81372e7) plus its
feat/hilt-s3-client branch (928cf2a, libforge PR #52): commands/** (every
command binding except ucan/attest, which belongs with attestation),
blobindex, receipt, retrieval (was ucan/retrieval), and the two small
helpers they depend on, bytemap and digestutil. Import paths change from
github.com/fil-forge/libforge/<pkg> to
github.com/fil-forge/forge/protocol/<pkg>; the code does not, with one
exception: commands/s3/bucket/errors.go is hilt main's newer copy of the
bucket error sentinels (it adds ErrBucketAlreadyOwned), not PR #52's.

The module depends only on ucantone and third-party libraries — on none
of the other in-repo modules — so anything can consume it. ucantone is
pinned to the branch that took libforge's ucan package
(fil-forge/ucantone#57); re-pin to main once that merges.

Codecs are generated (cbor-gen + dag-json-gen): `make gen` regenerates,
`make codegen-build gen-check` is the CI gate. Both pass here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
Peeja pushed a commit to fil-forge/forge that referenced this pull request Sep 4, 2026
github.com/fil-forge/forge/protocol carries what libforge published as
the network's wire contract, taken from libforge main (81372e7) plus its
feat/hilt-s3-client branch (928cf2a, libforge PR #52): commands/** (every
command binding except ucan/attest, which belongs with attestation),
blobindex, receipt, retrieval (was ucan/retrieval), and the two small
helpers they depend on, bytemap and digestutil. Import paths change from
github.com/fil-forge/libforge/<pkg> to
github.com/fil-forge/forge/protocol/<pkg>; the code does not, with one
exception: commands/s3/bucket/errors.go is hilt main's newer copy of the
bucket error sentinels (it adds ErrBucketAlreadyOwned), not PR #52's.

The module depends only on ucantone and third-party libraries — on none
of the other in-repo modules — so anything can consume it. ucantone is
pinned to the branch that took libforge's ucan package
(fil-forge/ucantone#57); re-pin to main once that merges.

Codecs are generated (cbor-gen + dag-json-gen): `make gen` regenerates,
`make codegen-build gen-check` is the CI gate. Both pass here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
Comment thread testutil/fixtures.go Outdated
Service = multikey.KeyIssuer(service)

// did:web:test.storacha.network
webServiceDID, _ = did.Parse("did:web:test.storacha.network")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe we should use example.com

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[From Claude:]

Agreed, and done in 3456649 — did:web:example.com and https://example.com.

You were pointing at a convention this fixture broke rather than a preference: did:web:example.com already appears 21 times in this tree against these 2. The fixture carried test.storacha.network over from libforge, where it was both a real domain and the old branding.

Checked before changing it, because the PR body promises dependents' hard-coded DIDs keep passing: nothing compares the literal. testutil.WebService and testutil.TestURL are used in piri's ucanhandlers suites and in indexing-service's contentclaims and blobindexlookup tests, always as values. The exact string did:web:test.storacha.network appears nowhere outside this file in ucantone, forge, guppy or indexing-service. The *.test.storacha.network hits a grep does turn up are different hostnames doing a different job — guppy's forge-test preset and piri's register test point at real staging endpoints, not at this fixture. The ed25519 keys are untouched, so that promise only ever concerned them.

Two up.storacha.network references remain in did/did_test.go. They are pre-existing and outside this diff, and they exercise DID method parsing rather than standing in as a service identity, so I left them.

make ci passes — build, codegen-build, gen-check, test.


Generated by Claude Code

Comment thread ucanlib/proof_chain.go
// invocation's "prf" list expects. This is the search-and-assemble
// counterpart to package validator, which walks a proof chain already
// attached to a token and checks it; ucanlib builds one from scratch.
package ucanlib

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a better place for these or a better name for this package?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[From Claude:]

Fair — ucanlib is a bad name, and it is a bad name for a bad reason: it is what the package was called in libforge, where the directory could not be ucan/ either. I carried the name across rather than choosing one. Not changing it unilaterally, since this is a naming decision for the module rather than a defect; a proposal and the tradeoff instead.

Proposal: a top-level proof/ package. It sits beside validator/ as the counterpart the doc comment already claims it is — validator walks a chain that is attached to a token, this assembles one from a store — and the names stop stuttering:

now proposed
ucanlib.ProofChain proof.Chain
ucanlib.ProofStore proof.Store
ucanlib.ContainerProofStore proof.ContainerStore
ucanlib.DelegationMatcherFunc proof.MatcherFunc

The alternative placement is under the existing tree as ucan/proof/, next to ucan/delegation and ucan/invocation. I'd argue against it: everything under ucan/ is a token type or a piece of one, and this is a search over a store, which is a different kind of thing. validator/, execution/ and binding/ are all top-level for that reason.

The timing argument, which is the part worth acting on. Every consumer of these symbols is changing its import path anyway in this migration — libforge/ucan is being retired and dependents move to ucantone. So a rename costs them one extra token per line they are already editing. Once they have migrated, the same rename is a breaking change to a published module with the blast radius AGENTS.md describes. This is the cheapest this decision will ever be, so it is worth settling now rather than filing.

Happy to push the rename to this branch if you and @Peeja like proof/; equally happy to leave ucanlib if you'd rather not spend the churn, in which case I'd at least drop the Proof prefix from ProofChain/ProofStore — that stutter is there regardless of what the package is called.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let's do proof, I think that resolves the names nicely.

…the repo

Review feedback from alanshaw. These fixtures came across from libforge with
`did:web:test.storacha.network` and `https://test.storacha.network`, which is
both a real domain and the old branding. This repository already uses the
RFC 2606 reserved domain everywhere else: `did:web:example.com` appears 21
times in the tree against these 2, so the fixture was the outlier.

Safe to change because nothing compares the literal. Every consumer goes
through the symbol -- `testutil.WebService` and `testutil.TestURL` are used in
piri's ucanhandlers suites and in indexing-service's contentclaims and
blobindexlookup tests, always as values, never as a hard-coded string. The
exact DID `did:web:test.storacha.network` appears nowhere outside this file in
ucantone, forge, guppy or indexing-service. The `*.test.storacha.network` hits
those greps do turn up are different hostnames serving a different purpose:
guppy's `forge-test` network preset and piri's register test point at real
staging endpoints, not at this fixture.

The PR body's promise that dependents' hard-coded DIDs keep passing was
therefore about the ed25519 keys, which are untouched; only the did:web
identity and the URL move.

Two `up.storacha.network` references remain in did/did_test.go. They are
pre-existing, outside this PR's diff, and they test DID METHOD PARSING rather
than standing in as a service identity, so they are left alone.

make ci passes: build, codegen-build, gen-check and test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
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.

3 participants