Skip to content

refactor: migrate to UCAN 1.0 (ucantone + libforge)#8

Open
frrist wants to merge 13 commits into
mainfrom
frrist/ucan1
Open

refactor: migrate to UCAN 1.0 (ucantone + libforge)#8
frrist wants to merge 13 commits into
mainfrom
frrist/ucan1

Conversation

@frrist
Copy link
Copy Markdown
Member

@frrist frrist commented May 18, 2026

Summary

This PR migrates piri from go-ucanto/go-libstoracha onto the new UCAN 1.0 stack — fil-forge/ucantone (envelopes, did, signer, validator) and fil-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 of main after PR #6 squash-merged.

Follow-on items

Items deferred out of this PR and tracked separately. Each in-code TODO references its issue number.

Open question (blob/accept authorization)

/blob/accept now verifies the invocation issuer is the upload service. The validator independently requires a proof chain when issuer ≠ subject, so the space must delegate /blob/accept to 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.

@frrist frrist changed the base branch from frrist/fix/deps-mess to main May 20, 2026 00:49
frrist and others added 7 commits May 19, 2026 18:02
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 frrist changed the title Frrist/ucan1 refactor: migrate piri-pdp to UCAN 1.0 (ucantone + libforge) May 20, 2026
@frrist frrist changed the title refactor: migrate piri-pdp to UCAN 1.0 (ucantone + libforge) refactor: migrate to UCAN 1.0 (ucantone + libforge) May 20, 2026
@frrist frrist self-assigned this May 20, 2026
Copy link
Copy Markdown
Member Author

@frrist frrist left a comment

Choose a reason for hiding this comment

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

few comments to guide review.

Comment thread cmd/cli/client/ucan/root.go Outdated
Comment thread cmd/cli/delegate/root.go Outdated
Comment thread pkg/client/receipts/client.go
Comment thread pkg/pdp/aggregation/types/aggregate.go
Comment thread pkg/ucanhandlers/access/grant.go
Comment thread pkg/ucanhandlers/blob/accept.go Outdated
Comment on lines +75 to +78
// /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.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

need to check my understanding of this, I assume the chain coming from guppy is used for validation somewhere?

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.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The invocation subject is the space to allocate/accept thought, yeah?

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.

Correct 👍

Comment thread go.mod
Comment thread go.mod Outdated
Comment thread cmd/cli/setup/register.go Outdated
Comment thread cmd/cli/delegate/util.go Outdated
// 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)
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.

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(...).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've punted implementing this to #13 for now the method panics.

Comment thread cmd/cli/delegate/util.go Outdated
// 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) {
Copy link
Copy Markdown
Member

@alanshaw alanshaw May 20, 2026

Choose a reason for hiding this comment

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

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>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment thread pkg/pdp/aggregation/commp/commp.go
Comment thread pkg/pdp/aggregation/manager/accepter.go Outdated
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{
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.

Receipt always references the task, not the invocation. There was no distinction in UCAN 0.9.

Suggested change
rcpt, err := receipt.IssueOK(issuer, inv.Link(), &pdp.AcceptOK{
rcpt, err := receipt.IssueOK(issuer, inv.Task().Link(), &pdp.AcceptOK{

Comment thread pkg/ucanhandlers/blob/module.go Outdated
Comment thread pkg/ucanhandlers/blob/retrieve.go Outdated
Comment thread pkg/ucanhandlers/pdp/info.go Outdated
Comment thread pkg/ucanhandlers/pdp/info.go Outdated
Comment thread pkg/ucanhandlers/ucanfx/fx.go
frrist and others added 3 commits May 20, 2026 19:32
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>
Comment thread pkg/ucanhandlers/blob/accept.go Outdated
frrist added a commit to fil-forge/libforge that referenced this pull request May 21, 2026
- 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
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.

2 participants