refactor: migrate to UCAN 1.0 (ucantone + libforge)#8
Conversation
Phase 0 of the UCAN 1.0 migration. The migration develops cross-repo: libforge needs a merkletree fix and an access/grant binding; piri- signing-service needs its UCAN surface rebuilt on libforge + ucantone. Local replace directives let piri-pdp consume in-flight changes from those repos during development. Pin to released versions before the migration PR lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 5f (partial). Wires the path piri-pdp uses to obtain access/grant delegations and forward signing requests onto the migrated piri-signing- service. pkg/service/proofs - interface.ProofService now takes ucan.Signer + did.DID + ucan.Command (was ucan.Principal + ucan.Ability), and returns ucan.Delegation. The Option type drops client.Connection in favor of *client.HTTPClient passed via WithClient. - caching.go rebuilt against ucantone: invocations via libforge access. Grant.Invoke; receipts decoded by container.Decode; signed delegations recovered from response container metadata (not from receipt OK as before — UCAN 1.0 keeps envelopes separate from the result CIDs). pkg/service/signer - proofservicesigner is no longer an embed-and-extend shim; it now owns a *signerclient.Client, a did.DID, and a *client.HTTPClient explicitly. Each Sign* method fetches an access/grant delegation, appends it to any caller-supplied proofs, and forwards through the signing-service client. pkg/config/app + pkg/config + pkg/fx/pdp - app.SigningServiceConfig.Connection (go-ucanto) → .Client (*signerclient.Client). The loader at pkg/config/pdp.go constructs this via signerclient.New using ucantone did.Parse for the service DID. - ProvideSigningService unwraps the client and threads its parts into NewProofServiceSigner. The consumer call sites in pkg/pdp/service (proofset_create.go, roots_ add.go) still call the SigningService with the old signature shape and a go-ucanto identity. Migrating them is the next chunk; it cascades into the identity layer (Phase 5g) since p.id is currently a go-ucanto principal.Signer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Production-side build is green. Identity and adjacent layers switched
from go-ucanto principal/did to ucantone/{principal,did,ucan,delegation}.
Core identity chain
- app.IdentityConfig.Signer, pkg/fx/identity, lib/identity.SignerFrom
Ed25519PEMFile: ucantone principal.Signer. lib/identity derives the
ucantone signer from a Go 64-byte ed25519 private key via .Seed().
- pkg/admin/httpapi/client + pkg/pdp/httpapi/client JWT signing:
ed25519.NewKeyFromSeed(id.Raw()) rebuilds the 64-byte key from
ucantone's 32-byte seed.
- pkg/presets: ucantone did.DID (did.Undef preserved at the same name).
- cmd/cliutil/util.go and cmd/cli/serve/full.go: ucantone did types.
cmd/cli/delegate
- MakeDelegation (multi-cap) is gone; UCAN 1.0 delegations are single-
command. New MakeDelegations + EncodeDelegationsContainer produce a
container of single-command delegations encoded with the Raw
(uncompressed CBOR) container codec. FormatDelegationBytes wraps the
envelope in an identity-hash CIDv1, codec switched from CAR to dag-cbor.
- cmd/cli/setup/register.go consumes the container shape.
requestContractApproval signs the DID string directly (ucantone Sign
returns []byte; no more .Raw() wrapping).
pkg/client + cmd/client + cmd/cli/client/ucan/upload
- pkg/client (BlobAllocate/BlobAccept/PDPInfo) was a substantial
go-ucanto RPC implementation. Stubbed to ErrNotMigrated with
preserved public types so the CLI wiring builds. Full rebuild is
Phase 5f scope.
pkg/pdp/service
- proofset_create.go and roots_add.go pass the new []ucan.Delegation
parameter (nil for now; the signing service obtains its own access
grant via the proof-service path).
- The per-piece proof bundler getAddPieceProofs was removed; it pulled
blob/accept and pdp/accept invocations + receipts from go-ucanto
receipt/acceptance stores. Reintroduce a ucantone-shaped version when
the receipt+acceptance stores migrate (Phase 5a).
pkg/internal/testutil
- testutil.NewTestConfig switches to ucantone.testutil.RandomSigner per
test; the Upload.Connection field is left zero pending the wider
Phase 5 service-config migration. WithUploadServiceConfig becomes a
no-op stub with the same signature.
7 packages still have *_test.go files on go-ucanto — those compile
errors are Phase 5i (test migration), tracked separately. go build
./... is clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
go test ./... is now green on piri-pdp (excluding three heavy fx integration tests behind a build tag — see below). Migrated tests - pkg/pdp/httpapi/client/client_test.go: testutil import swap to ucantone. - pkg/service/proofs/caching_test.go: rebuilt against ucantone's server-as-roundtripper pattern with a libforge /access/grant handler that attaches signed delegations as response container metadata. Uses validator.WithCanIssue to make /access/grant self-issuable in tests (the bootstrap step has no prior delegation). - pkg/service/signer/proofservicesigner_test.go: same mock-server pattern, exercising all four /pdp/sign/* paths through the proof- service signer. - cmd/cli/setup/register_test.go: did import swap to ucantone. Removed - pkg/pdp/service/roots_add_test.go: its only test exercised getAddPieceProofs, which was deleted as part of the Phase 5g signer migration (the function depended on go-ucanto receipt/acceptance stores). Test returns when Phase 5a lands the ucantone-shaped rebuild. Gated behind `//go:build legacy_ucan` - pkg/service/retrieval/ucan_fx_test.go - pkg/service/storage/ucan_access_fx_test.go - pkg/service/storage/ucan_fx_test.go These are full fx integration tests against the UCAN retrieval and storage stacks. They exercise production code that's still on go-ucanto/go-libstoracha (Phases 5b–5e). Migrating them is premature until that production code moves; the build tag preserves the tests intact for the future re-enable rather than deleting them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pkg/client UCAN RPC client and its CLI wrappers were the outbound
piri-to-piri client. They aren't part of the current scope, so the
ErrNotMigrated stub from Phase 5g is removed entirely along with the
two consumer surfaces:
- cmd/client/client.go (the helper that adapted CLI flags into pkg/client)
- cmd/cli/client/ucan/{root,upload}.go (the `piri client ucan upload` subcommand)
- cmd/cli/client/root.go drops the ucan subcommand registration
pkg/client/receipts and pkg/client/status.go stay; they are independent
helpers consumed by egresstracker and the status command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- egress tracking is disabled - replication is disabled - still uses go.mod replace directives
frrist
left a comment
There was a problem hiding this comment.
few comments to guide review.
| // /blob/accept is space-scoped: the invocation subject IS | ||
| // the space being accepted into. Authorization is enforced | ||
| // by the validator's proof chain, not by an issuer-equals- | ||
| // service check. |
There was a problem hiding this comment.
need to check my understanding of this, I assume the chain coming from guppy is used for validation somewhere?
There was a problem hiding this comment.
Blob accept is performed by the upload service. All that need to happen here is a check that the invocation issuer is the upload service.
There was a problem hiding this comment.
The invocation subject is the space to allocate/accept thought, yeah?
| // container envelope using the Raw (uncompressed CBOR) codec. | ||
| func EncodeDelegationsContainer(dlgs []ucan.Delegation) ([]byte, error) { | ||
| ctr := container.New(container.WithDelegations(dlgs...)) | ||
| return container.Encode(container.Raw, ctr) |
There was a problem hiding this comment.
Container has it's own prefix code that is added when using Encode and expected when using Decode. If you want just the CBOR then you'll need to use ctr.MarshalCBOR(...).
There was a problem hiding this comment.
I've punted implementing this to #13 for now the method panics.
| // multibase-base64-encoded CIDv1 carrying the envelope as identity-hashed | ||
| // content (the same shape consumed by piri's existing delegation readers, | ||
| // just with a DagCBOR codec instead of CAR). | ||
| func FormatDelegationBytes(envelope []byte) (string, error) { |
There was a problem hiding this comment.
Not sure if this is worth it...? UCAN containers have their own serialization format (which includes a prefix code). I'd probably just use that?
str, _ := container.Encode(container.Base64Gzip, ctr)
// str = <header byte><cbor bytes, gzip compressed, base64 encoded>| return nil, fmt.Errorf("generating invocation: %w", err) | ||
| } | ||
| ok := result.Ok[pdp.AcceptOk, ipld.Builder](pdp.AcceptOk{ | ||
| rcpt, err := receipt.IssueOK(issuer, inv.Link(), &pdp.AcceptOK{ |
There was a problem hiding this comment.
Receipt always references the task, not the invocation. There was no distinction in UCAN 0.9.
| rcpt, err := receipt.IssueOK(issuer, inv.Link(), &pdp.AcceptOK{ | |
| rcpt, err := receipt.IssueOK(issuer, inv.Task().Link(), &pdp.AcceptOK{ |
UCAN 1.0 semantics + cleanup from the PR #8 review: - receipts/causes reference the task, not the invocation (accepter, transfer, blob/accept handler, acceptance + allocation stores) - blob/accept: verify issuer is the upload service; range End is inclusive (size-1) - blob/retrieve: Vary header uses Add, not Set - publisher CacheClaim: drop resolved TODOs + execution.WithProofs, decode receipt errors via errors/datamodel.ErrorModel - acceptance: replace hand-rolled Await/Promise with promise.AwaitOK - fx: blob module uses fx.Annotate/fx.As; did:key resolved via validator.ResolveDIDKeyVerifier instead of the HTTP resolver - pdp/info: return unexpected lookup errors directly (not SetFailure) - retrieval journal: skip failure receipts in Append - delete the cmd/cli/delegate command; registerWithDelegator panics pending reimplementation Deferred work is tracked and referenced from in-code TODOs: #9 PDP add-roots proof bundling #10 retrieval journal Append container #11 pdp/info receipt lookup cleanup #12 blob/accept blob-size type (non-nil range End) #13 reimplement delegator proof generation #14 re-enable egress tracker module #15 re-enable replication (replicator service + replica handler) fil-forge/ucantone#19 remove execution.WithProofs
Co-authored-by: ash <alan138@gmail.com>
Co-authored-by: ash <alan138@gmail.com>
- required for piri migration to ucan1 fil-forge/piri#8 - Does not support DAG-JSON on some types, which I have called out for review
- then make compile
Summary
This PR migrates piri from
go-ucanto/go-libstorachaonto the new UCAN 1.0 stack —fil-forge/ucantone(envelopes, did, signer, validator) andfil-forge/libforge(capability bindings) — and restructures the UCAN handler layer around per-capability fx modules. It is the piri-side counterpart to the cross-repo migration in libforge and piri-signing-service; together those three repos move us off the multi-cap UCAN 0.9 model onto single-command UCAN 1.0 invocations with container-based envelope encoding.Production code builds cleanly and
go test ./...is green, with three legacy fx integration tests gated behind//go:build legacy_ucan(their production code under test still lives on go-ucanto; they re-enable when those packages migrate). Two pieces of in-flight functionality — egress tracking and replication — are intentionally disabled at this checkpoint; both have unmigrated dependencies and are gated for a follow-up. Branch rebased on top ofmainafter PR #6 squash-merged.Follow-on items
Items deferred out of this PR and tracked separately. Each in-code TODO references its issue number.
libforge pdp/sign.PieceProofs(pkg/pdp/service/roots_add.go).Appendshould persist a container (receipt + invocation + proofs), not a bare receipt (pkg/store/local/retrievaljournal/fsjournal.go).pdp/info/pdp/acceptreceipt lookup once the receipt/acceptance stores are fully migrated (pkg/ucanhandlers/pdp/info.go).blob/acceptits own blob-size type whose rangeEndcannot be nil (pkg/ucanhandlers/blob/accept.go).delegatecommand;registerWithDelegatorcurrently panics (cmd/cli/setup/register.go).pkg/fx/app/ucan.go).pkg/fx/app/ucan.go,pkg/ucanhandlers/ucanfx/fx.go,pkg/ucanhandlers/blob/replica/module.go).execution.WithProofs(duplicate ofWithDelegations); migrate callers.Open question (blob/accept authorization)
/blob/acceptnow verifies the invocation issuer is the upload service. The validator independently requires a proof chain when issuer ≠ subject, so the space must delegate/blob/acceptto the upload service (the guppy chain) — the RPC suite was updated to carry that delegation. The handler retains a TODO noting the open question of whether/where that chain is otherwise validated and whether the issuer check is redundant or complementary.