Add forgeclient: ingot's Forge client as a shared module - #8
Draft
Peeja wants to merge 2 commits into
Draft
Conversation
ingot carried its own copies of guppy's Forge client (forgeclient/), token store (tokenstore/) and indexer-backed blob locator (blockstore/locator/), plus a trimmed copy of sprue's typed UCAN execute helper (internal/ucanexec/), because it could not import guppy. The copies long ago stopped tracking guppy: the client grew a per-call proof store, an explicit Content-Length on the blob PUT, deferred accept (WithConclude(false) and BlobConclude), BlobAbort and BlobRemove, while guppy's pkg/client has not changed since 2026-06-19 (as of guppy main e87812b). It is the network's most developed client, so it becomes the shared one. The three packages move to a new module, github.com/fil-forge/forge/forgeclient (packages forgeclient, forgeclient/tokenstore and forgeclient/locator), and ucanexec to internal/ucanexec, where both the client and ingot's read path (blockstore/forge.go) can import it — a module-level internal/ package cannot be shared across modules. The code is moved, not changed: the only edits are import paths, gofmt's import ordering, and the package documentation, which described the packages as copies to keep in sync with guppy and now describes what they are. ingot requires the module through a replace directive like the other in-repo modules; the moved packages' dependencies were already ingot's, so its go.mod otherwise only loses what it no longer imports directly. forgeclient/go.mod depends on protocol, internal, attestation (the login flow's attest command and did:mailto helpers) and indexing-service (the locator's query client), all through replace directives. The module joins go.work, the CI unit matrix, the root Makefile's LIBRARIES, the Dockerfile's manifest COPYs (ingot's image build needs the replace target present before go mod download) and smelt's sharedDirs (a change to it rebuilds every service in workspace mode, like the other shared modules). Docs that enumerated the shared modules or ingot's carried copies are updated. Not done here: none of guppy's surface that ingot's copy dropped (retrieval, spaces, progress reporting, /blob/replicate) is re-added; the locator keeps its go-log package logger, still named "client/dagservice" from guppy; guppy itself is untouched. Verified GOWORK=off: forgeclient, internal, ingot and smelt build, vet and tidy clean; forgeclient's and ingot's tests pass; check-replaces passes; workspace-mode builds pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
ingot's copy of the token store never carried guppy's tests, so the store has had none since the copy. The code is guppy's pkg/tokenstore byte-for-byte apart from header comments (fil-forge/guppy@e87812b), and its test file comes across with only the import path changed: TestStore runs one suite over MemStore and FsStore — delegations, invocations, receipts, ListDelegations filtering by audience, command and subject, Reset — and TestFsStorePersistence reopens a file-backed store and finds what was written. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
Peeja
force-pushed
the
claude/forge-consolidation-3
branch
from
September 4, 2026 21:00
898542f to
f86c5e6
Compare
Peeja
pushed a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
* chore: add replace directives for libforge, ucantone, signing-service
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>
* refactor(signer): migrate proofs + signer chain to ucantone + libforge
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>
* refactor: migrate identity + CLI + presets to ucantone (Phase 5g)
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>
* test: migrate identity/proofs/signer tests; gate fx integration tests
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>
* chore: remove pkg/client UCAN RPC and the cmd/client tooling
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>
* wip: doesn't work, lots of Qs and TODOs
* everything passing:
- egress tracking is disabled
- replication is disabled
- still uses go.mod replace directives
* fix: deps, remove replace statements
* fix: address alanshaw PR fil-forge#8 review comments
UCAN 1.0 semantics + cleanup from the PR fil-forge#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
* Apply suggestion from @alanshaw
Co-authored-by: ash <alan138@gmail.com>
* Apply suggestion from @alanshaw
Co-authored-by: ash <alan138@gmail.com>
* deps: update ucantone, libforge, and siging-service
- then make compile
* address feedback
* update delegator and fix libforge
* update piri-signing-service
* chore: cleanup
* chore: appease staticcheck
* wip: ucan1 integration snapshot for smelt e2e
Snapshots a working end-to-end upload flow through the smelt local-dev
stack. See smelt/docs/HANDOFF.md for full context.
* feat: add attestation verifier
---------
Co-authored-by: ash <alan138@gmail.com>
Peeja
pushed a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
… (fil-forge#3) (#4) (fil-forge#5) (fil-forge#6) (fil-forge#8) (#9) (#10) (#11) (#12) This PR introduces a new `pkg/store` layer with both in-memory and Postgres-backed implementations for core entities (provider, tenant, bucket, access key, delegation), plus embedded goose migrations and test utilities to run Postgres-backed store tests via testcontainers. **Changes:** - Add store interfaces + in-memory and Postgres implementations for provider/tenant/bucket/accesskey/delegation. - Add embedded goose migrations and a migrations runner to initialize Postgres schema. - Add test utilities and cross-implementation tests (memory + Postgres) for each store. --- Adds a new Go client for interacting with the upload service via UCAN RPC invocations, along with structured logging helpers to make invocation arguments/metadata easier to inspect in logs. **Changes:** - Introduce `pkg/client.UploadClient` with `RegisterCustomer` (`/customer/add`) and `ProvisionSpace` (`/provider/add`) operations. - Add `pkg/lib/zapucan` helpers for logging UCAN invocations and decoding CBOR-encoded IPLD maps into zap fields. - Bump `github.com/fil-forge/libforge` dependency version. --- Adds initial scaffolding for running hilt as a service: a Cobra-based `hilt serve` command that loads config via Viper and starts an Echo HTTP server wired together with Uber Fx. **Changes:** - Introduce Fx modules for config surfacing, zap logging, and Echo server lifecycle management. - Add Viper-based config loading with defaults, env var support, and pflag binding. - Add `cmd/main.go` CLI entrypoint plus basic Makefile/.gitignore and new dependencies (Echo, Cobra, Viper, Fx). --- Adds initial Tenant + Access Key management API scaffolding and wires store backends (memory/postgres) into the Fx app based on configuration, including new config types/flags and Postgres migration hooks. **Changes:** - Introduces API route stubs and request/response types for tenant + access key endpoints, and auto-registers routes onto the Echo server via an Fx group. - Adds storage backend selection (memory vs postgres) in `AppModule`, plus Fx modules to provide the corresponding store implementations. - Expands configuration to include `storage.*` and `storage.postgres.*` settings and flag bindings; standardizes PG unique-violation handling using `pgerrcode`. --- Adds a configurable “partner key” authentication mechanism to gate access to the Tenant API routes, wiring the key through config/flags and enforcing it via Echo middleware. **Changes:** - Introduces `AuthConfig` (`auth.partner_key`) and exposes it through the fx config module. - Wraps all Tenant API routes in partner-key bearer auth middleware while keeping `/` and `/health` open. - Adds new Echo middleware + unit tests for partner-key bearer authentication. --- Adds a KMS-agnostic `vault.Vault` interface plus in-memory and HashiCorp Vault (KV v2) implementations, and wires vault selection/config into the fx app and CLI so Hilt can persist private key material in a configurable backend. **Changes:** - Introduce `pkg/vault.Vault` interface with `ErrNotFound`, plus an in-memory implementation and a HashiCorp Vault (KV v2) implementation (including AppRole auth helper). - Add config schema + viper defaults + CLI flags for selecting/configuring the vault backend, and wire backend selection into the fx app graph. - Add tests for the vault interface contract and HashiCorp AppRole login using a testcontainers Vault dev container. --- Implements tenant provisioning via `PUT /tenants/{tenantId}` by introducing an external tenant identifier, wiring a did:plc directory client, generating/storing tenant keys in Vault, and persisting tenant records. **Changes:** - Added `external_id` to tenant storage model and introduced `GetByExternalID` for idempotent provisioning. - Added PLC directory configuration + fx wiring to provide a `plc.DirectoryClient`. - Implemented `ProvisionTenant` API handler with accompanying tests (memory stores + httptest PLC server). --- Implements the remaining tenant management endpoints by wiring real store-backed handlers and adding the store capabilities required to support tenant deletion cascades (buckets, access keys, delegations, vault secrets, and did:plc deactivation). **Changes:** - Implement `GET /tenants/:tenantId`, `POST /tenants/:tenantId/status`, and `DELETE /tenants/:tenantId` handlers (with did:plc deactivation + cascade cleanup). - Extend tenant/bucket/access-key stores with `Delete` and “list by tenant” capabilities, including pagination for buckets. - Add/extend unit tests for the new store methods and HTTP handlers; bump dependencies to newer `ucantone` and `dag-json-gen`. --- This PR implements the Management API access-key endpoints, including key material storage in Vault and issuing tenant→access-key UCAN delegations based on requested S3 permissions. It also extends the store layer to support access-key expiry and efficient bucket lookups by ID for response rendering. **Changes:** - Implement Create/List/Get/Delete access-key handlers, including Vault secret storage and delegation issuance/revocation. - Add `expires_at` support for access keys across schema, store implementations, and tests. - Extend bucket listing to support filtering by explicit bucket IDs via new `bucket.ListOption`/`bucket.ListConfig`. --- Adds initial scaffolding for Hilt’s UCAN-based RPC surface (used by Ingot for S3 tenant management), including an identity-backed UCAN server mounted on the existing Echo HTTP server. **Changes:** - Introduces UCAN RPC handler stubs and wires them into an `fx` module that builds a `ucantone` HTTP server. - Adds a configurable service identity (PEM-backed or ephemeral) and exposes a public `did:web` DID document endpoint. - Extends configuration/CLI flags to support identity settings.
Peeja
added a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
* fee/aesstream: range decryption API (FIL-472) Range decryption on top of the chunked AES-256-GCM STREAM body cipher (FIL-470). No new crypto — chunk-index arithmetic plus a single contiguous fetch of the chunks overlapping a request. The engine is sequential: a caller turns a plaintext range into one contiguous ciphertext fetch and streams the result through the decryptor, so the read path is one request per segment with O(chunkSize) memory. - CiphertextRange(ciphertextSize, chunkSize, off, length) → the single contiguous ciphertext byte span covering a plaintext range. Overlapping chunks are adjacent, so the bytes needed are one span — the consumer (FIL-487) fetches a segment's range in one request. Offsets are stream-relative; add any envelope-header length. Chunk-aligned, so the edge over-read is <2*chunkSize (GCM authenticates whole chunks). - SpanReader / OpenSpan: decrypt that span — delivered as a sequential io.Reader (e.g. a range-request body) — into exactly the requested plaintext. The total ciphertext length pins each chunk's index, final-chunk flag and length up front, so there is no last-chunk retry and the first/last chunks are trimmed to the range. OpenSpan guards its int64->int buffer with math.MaxInt (steering huge ranges to the streaming reader instead of panicking in make on 32-bit builds). - DecryptedSize: inverse of EncryptedSize; derives and validates the plaintext length from a ciphertext length without fetching any ciphertext. - chunkLayout: the shared geometry behind all three. No io.ReaderAt form: range reads are inherently sequential (each chunk read once, in order), so the random-access engine wasn't load-bearing. A random-access source can still feed SpanReader via io.NewSectionReader(src, start, n) using CiphertextRange's offsets — noted in the CiphertextRange doc. Range semantics are HTTP-range-like: a length past the end clamps; a negative offset/length or offset past the end is ErrRange; a structurally invalid declared ciphertext length is ErrCiphertextSize; a tampered/reordered/misframed chunk is ErrCorrupted; a span shorter than the geometry requires is ErrTruncated. Tests use testify require. TestSpanReader/Ranges is a table of hand-computed literal expectations (span start/length and decrypted length) — an independent oracle rather than a re-derivation of the range math — that also checks the span is consumed in full and no further. TestSpanReader/RoundTrip reads each sealed object back in full through adjacent range windows of varying widths (chunk-aligned and not), via both OpenSpan and SpanReader, and confirms the reassembly equals the original plaintext (length recovered through DecryptedSize). Alongside: tamper detection, short-span truncation, wrong-size detection, range/size validation, default 256 KiB chunks, CiphertextRange input validation, the internal chunkLayout geometry + MaxChunks guard, and EncryptedSize/DecryptedSize inversion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7GkuUdrMgiQhmpoY9FjKN * ci: trigger rerun --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Peeja
pushed a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
Peeja
pushed a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
) This is part of reverting the change to send allocate and accept delegations from the client. We decided this should in fact be made with the storage provider as the subject and that the upload service should be pre-authorized by the storage provider to invoke. This PR generates proofs from piri nodes to upload service and uses them at provider registration. Depends on: * fil-forge/sprue#21 * fil-forge/ucantool#2
Peeja
pushed a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
## Summary Bumps `github.com/fil-forge/libforge` to the latest commit on `main`. | | version | |---|---| | From | `v0.0.0-20260701162346-f0706e1641a3` (2026-07-01) | | To | `v0.0.0-20260724113901-7fc3b2cec1ef` (2026-07-24, commit `7fc3b2c`) | Updated via `go get github.com/fil-forge/libforge@7fc3b2cec1ef72e815ec8fb2e4f0530e5b0ebdab && go mod tidy`. ## Changelog (libforge `f0706e1` → `7fc3b2c`) Commits brought in ([`f0706e1...7fc3b2c`](fil-forge/libforge@f0706e1...7fc3b2c), newest first): - [`7fc3b2c`](fil-forge/libforge@7fc3b2c) Fix DID verification method ID encoding and add controller (#48) — stops the `#key-0` fragment from being double-encoded to `#%23key-0`, and sets the verification method `controller` to the document DID. - [`f06be16`](fil-forge/libforge@f06be16) fix: correct DID verification method fragment and set controller — the underlying fix landed in #48. - [`aba2bd2`](fil-forge/libforge@aba2bd2) feat: add `/ucan/revoke` command (#47). - [`aac837a`](fil-forge/libforge@aac837a) add space to blob remove, abort, and reject — makes `/blob/abort` carry a required `Cause`, adds `/blob/reject` (upload service → storage node) as the mutually-exclusive counterpart to `/blob/accept` for unwinding a parked blob. - [`a3aa293`](fil-forge/libforge@a3aa293) fix: make bucket optional. ## Verification - `go build ./...` — passes - `go test ./...` — passes (including the `test/` system suite) Only `go.mod` and `go.sum` change; no source changes were required. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Peeja
added a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
* ci: add Dependabot for go modules and GitHub Actions Weekly gomod + github-actions updates; part of the fil-forge CI standardization (pass 1: Dependabot org-wide). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HrVC6QSNKh9LM9LSFPkdUj * ci: pin Unified CI reusables to a commit SHA (fil-forge#8) Pin ipdxco/unified-github-workflows reusables from the floating @v1.0 tag to commit 63392e9 (v1.0). Part of the fil-forge CI standardization (pass 2); stacked on the Dependabot PR. Dependabot keeps the pin current. Claude-Session: https://claude.ai/code/session_01HrVC6QSNKh9LM9LSFPkdUj Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[From Claude:]
Stacked on #7 (which stacks on #6); merge in that order. This is the client half of the consolidation: ingot's carried copies of guppy's Forge client become a shared module, and ingot consumes them like any other in-repo module. Two commits.
What changes
Add forgeclient: the Forge protocol client as its own moduleingot/forgeclient→forgeclient/(modulegithub.com/fil-forge/forge/forgeclient),ingot/tokenstore→forgeclient/tokenstore,ingot/blockstore/locator→forgeclient/locator, andingot/internal/ucanexec→internal/ucanexec(both the client and ingot's read path import it, and a module-levelinternal/cannot be shared across modules). A pure move: import paths, gofmt's import ordering and the package docs are the only edits. ingot requires the module through areplace; the module joinsgo.work, the CI matrix, the root Makefile, the Dockerfile's manifestCOPYs and smelt'ssharedDirs.forgeclient/tokenstore: adopt guppy's store testspkg/tokenstore/store_test.go(270 lines) with its import path changed and nothing else. The store never had tests on the ingot side.Why ingot's copy and not guppy's
The plan's Phase 4 assumed guppy's
pkg/clientwas the source of truth and ingot's copy a trimmed fork. Measuring the two (forgeclient-divergence.mdon the POC branch) shows the flow reversed: guppy's client has not changed since 2026-06-19, while ingot's gained the per-call proof store (WithProofStore), an explicitContent-Lengthon the blob PUT, deferred accept (WithConclude(false)/BlobConclude),BlobAbortandBlobRemove, and dropped the hard requirement that the accept receipt carry a/pdp/acceptinvocation.tokenstoreis byte-identical between the two. So the shared module is extracted from ingot. What guppy has that ingot dropped — retrieval, spaces, progress/stall readers,/blob/replicate(the sole user of the pre-ucantonego-ucanto/go-libstorachastack) — is not re-added here.Deliberately not in this PR
forgeclient); smelt's e2e/compat suites still drive the network through theguppycontainer image (Phase 5 of the plan).BlobAdd(ctx, space, content, ...)keeps ingot's signature, logger injection is ingot'sWithLogger, and the locator keeps itsgo-logpackage logger (still namedclient/dagservice, from guppy).forgeclient.Dependencies
forgeclient→protocol,internal(ucanexec),attestation(the login flow's attest command and did:mailto helpers),indexing-service(the locator's query client),ucantone— the in-repo requires throughreplacedirectives, like every other module here. ingot'sgo.modgains theforgeclientrequire and replace;internal/go.modonly seescbor-genbecome a direct dependency.Verification (
GOWORK=off)forgeclient,internal,ingot,smelt: build, vet, andgo mod tidyis a no-op.forgeclient's tests (accounts,proofstore, the adoptedtokenstoresuite), ingot's unit tests and smelt'spkg/workspacetests pass..github/scripts/check-replaces.shpasses; workspace-mode builds pass for the four modules.COPY) is exercised by the stack job's ingot image build on this PR.Reviewing
Commit 1 with
git show -M --stat: 21 renames; the non-rename hunks are the import paths in ingot's eight importing files, the newforgeclient/go.modandgo.sum, the registration edits, and doc text. Commit 2 is one file —diffit againstfil-forge/guppy@e87812b:pkg/tokenstore/store_test.go.🤖 Generated with Claude Code
https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
Generated by Claude Code