Consolidate libforge and the satellite services into the repository - #7
Draft
Peeja wants to merge 8 commits into
Draft
Consolidate libforge and the satellite services into the repository#7Peeja wants to merge 8 commits into
Peeja wants to merge 8 commits into
Conversation
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
github.com/fil-forge/forge/internal collects the packages the services share that are not part of the wire contract. The path makes the boundary compiler-enforced: only code under github.com/fil-forge/forge/ can import them. From libforge main (81372e7): jobqueue, identity, piece. From hilt main (f842aba), the newest copies of the S3 machinery that libforge PR #52 had extracted from an older hilt: sigv4, s3perm and hilt's RPC client, here client/hilt (PR #52's copies lacked WithBaseProofs, blob.Remove in the write grant, and the current ecdsa API). From PR #52 itself: zapucan. From delegator main (3d3525c): client -> client/delegator. From piri-signing-service main (8472ad9): pkg/types, pkg/signer, pkg/inprocess and pkg/client -> pdpsigner/{types,signer,inprocess,client} — piri runs the signer in-process in dev mode, so all four move and the service keeps only its server. From go-ipni-tools main (5f11c95): pkg/advertisement -> ipni/advertisement, the Forge->IPNI adapter that depends on commands/assert; its tests get local copies of the two helpers they had taken from go-ipni-tools' own internal/testutil. Import paths change; the code does not, apart from the package names of the two client packages (client -> hilt, delegator) so call sites read as hilt.New and delegator.New. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
github.com/fil-forge/forge/attestation is libforge's attestation and attestation/didmailto packages plus commands/ucan/attest (here attest/), from libforge main (81372e7). They form one extension — the attest command is the proof the attestation signer issues and the didmailto resolver consumes — and nothing in it is Forge-specific, so the module requires only ucantone and multiformats (plus the codec generators attest uses); never protocol or internal. attest.ProofOK used to alias commands.Unit; it now aliases a local Unit with its own generated codecs so the module stands alone. The one test that used libforge/identity builds its did:web document from ucantone's did and multikey primitives instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
Three services that were standalone repositories join as modules
github.com/fil-forge/forge/{delegator,piri-signing-service,indexing-service},
from fil-forge/delegator 3d3525c,
fil-forge/piri-signing-service 8472ad9 and
fil-forge/indexing-service a119ea8287397e3e013fceebd022988ff4178f86 (their current mains).
Taken the way the original five were: module path rewritten, per-repo
.github/, Dockerfile and Dockerfile.release dropped (CI and images come
from the shared workflows and docker/Dockerfile). Also dropped: deploy/
and .storoku.json — each service's storoku Terraform pipeline stays in
its original repository, which keeps deploying until it is pointed at
images built here — and indexing-service's tracked bootstrap, a 24 MB
aarch64 Lambda custom-runtime binary that was committed alongside its
Terraform and that nothing references. Source only.
What each keeps and what moved out:
- delegator: everything but client/, which became internal/client/delegator
(piri imports it at operator setup; nothing in delegator imports it).
- piri-signing-service: main.go, pkg/config, pkg/handlers, pkg/server.
pkg/types, pkg/signer, pkg/inprocess and pkg/client became
internal/pdpsigner/{types,signer,inprocess,client}; piri uses all four
(it runs the signer in-process in dev mode), so the library part of the
service is shared and the service is its HTTP shell.
- indexing-service: everything. Nothing in the repository imports it as a
library except ingot's use of its query client (pkg/client, pkg/types),
which now resolves in-repo.
libforge imports point at protocol/ and internal/; go-ipni-tools'
pkg/advertisement at internal/ipni/advertisement; ucantone at the pin the
rest of the repository uses. All three now declare go 1.27.0 (were
1.27.0, 1.25.3 and 1.25.7) because internal/ requires it.
Each Makefile gains the root Makefile's verb contract — vet, image,
image-dev (indexing-service also build and clean, aliasing its indexer
and clean-indexer targets). The images keep the binary names the compose
files and smelt's workspace table expect, via the Dockerfile's BIN arg:
registrar, signer, indexer.
Formatting: the trees are gofmt'd with this repository's Go 1.27 gofmt,
which reflows a handful of upstream files.
Verified standalone (GOWORK=off): build, vet, tidy, tests. indexing-
service's pkg/aws and pkg/redis tests need a Docker daemon (testcontainers)
and are expected to pass in CI.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
Every libforge import in piri, hilt, sprue, ingot and smelt becomes an
import of protocol/ (commands/**, blobindex, receipt, retrieval, bytemap,
digestutil), internal/ (identity, jobqueue, piece, sigv4, s3perm, zapucan,
client/*, pdpsigner/*, ipni/advertisement), attestation/, or ucantone
itself (ucanlib, testutil). libforge disappears from every go.mod; ucantone
is pinned to one version across the repository. The three module
dependencies the live polyrepo had grown between services — ingot on
fil-forge/hilt and fil-forge/indexing-service, hilt and ingot on
fil-forge/smelt for their integration suites — now resolve to the sibling
directories through replace directives instead of to published copies.
hilt is the one service whose code changes beyond import paths: this is
libforge PR #52's extraction, redone against hilt's current code rather
than the 07-31 snapshot that PR took. pkg/sigv4, pkg/s3perm,
pkg/lib/zapucan and the RPC client (pkg/client/client.go) leave for
internal/, and the S3 wire contract — the named error sentinels in
pkg/rpc/service/auth/errors.go and pkg/rpc/service/bucket/errors.go, and
Operation/OperationFor/ClassifyRequest in pkg/rpc/service/auth/operation.go
— leaves for protocol/commands/s3{,/request,/bucket}, where ingot already
consumes it. hilt keeps the server side (the Authorizer and bucket
services, stores, vault), its AdminClient and management/upload clients.
Call sites follow: auth.ErrX -> s3req.ErrX, bucketsvc.ErrX -> s3bkt.ErrX,
Operation -> s3.Operation, classifyRequest -> s3.ClassifyRequest.
Smaller adjustments: sprue's and hilt's internal/testutil aliases follow
ucantone/testutil's name (RandomDigest for what libforge called
RandomMultihash); smelt's tests/s3 imports internal/s3perm, so smelt no
longer needs the PR #52 branch of libforge that the previous commit's
go.mod carried for it; each service's AGENTS.md/CLAUDE.md/README describe
the modules by their new names.
Verified per module (GOWORK=off, piri with -tags skiff): build, vet, tidy
unchanged, tests — the only failures are the tests that need a Docker
daemon (piri's objectstore/minio, smelt's pkg/stack image builds), as on
the base branch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
Three harness changes for the monorepo shape: - pkg/workspace: "libforge in the go.work use-list rebuilds every service" becomes "protocol, internal or attestation in the use-list rebuilds every service" — those are the modules every service compiles in now. The error hint for a missing workspace points at the repository's own go.work. - pkg/stack.buildForgeImage builds with the repository root as the Docker context. Services reach the shared modules through replace directives, and a per-service context cannot see a sibling directory. - tests/compat.otherThan: the exclusion list it returns is in smelt service names (the workspace builder's vocabulary) while the caller's service names are repository names; the two differ for sprue, whose smelt service is "upload". Comparing them directly excluded nothing for sprue, so an "upgrade sprue" case would have run entirely on baseline images and passed vacuously. The function now translates. The developer docs (CLAUDE.md, README.md, docs/DEVELOPING.md, docs/EXTENDING.md) describe the shared-module rule in place of the libforge rule. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
CI (ci.yml): the unit tier runs a fixed matrix of all twelve modules — the seven services, smelt, its stress-tester, and the three shared modules — on every change. It used to be path-filtered per service, which was sound while no service module imported another and the shared surface lived in an external library. With shared in-repo modules and service-to-service dependencies, a path filter that misses a directory skips CI silently and reports green; running everything costs minutes. Two gates join: a replace-directive check (.github/scripts/check-replaces.sh — every github.com/fil-forge/forge/* require in every go.mod must have a replace pointing at the directory that holds that module; without one Go resolves the path through the proxy to the repository root, which is not a module) and the codegen gate for protocol and attestation (make codegen-build gen-check). The stack tier builds all seven service images from the commit under test and hands the satellites' to smelt through DELEGATOR_IMAGE, SIGNER_IMAGE and INDEXER_IMAGE, so the full-stack suites run against this commit's delegator, signer and indexer rather than floating :main images. docker/Dockerfile: the build context is the repository root — a service's replace targets have to be inside the context. Every module's go.mod and go.sum is copied before any source so dependency downloads stay cached, and a BIN arg names the binary inside the image where it differs from the directory (registrar, signer, indexer). The root .dockerignore keeps the context small. Each service Makefile's image targets pass the root as the context. publish-ghcr.yml publishes all seven services on every merge, for the same reason the unit tier stopped filtering. go.work lists every module. The root Makefile's lists gain the satellites and the shared modules, plus gen-check and check-replaces targets. README.md describes the layout. Not changed here: release.yml still releases piri, hilt, sprue and ingot only — whether the satellites are versioned and released from this repository, and what happens to their deploy pipelines, is a decision this branch does not make. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt
This was referenced Sep 4, 2026
The stack tier's first run on this branch failed with "container signing-service is unhealthy" while the signer's own log showed it up and answering every probe: HEAD /healthcheck → 405 Method Not Allowed. The probe is wget -q --spider. Under busybox wget — the alpine images the satellites' own repositories publish, which smelt ran until this branch — --spider sends a GET. Under GNU wget, which the shared debian image ships (docker/Dockerfile), it sends a HEAD, and echo answers HEAD on a GET-only route with 405, so wget exits 8 and Docker marks the container unhealthy. The signer and the delegator both register their health route with echo's GET; the indexer is on net/http's mux, whose GET patterns also serve HEAD, so its probe happened to pass. wget -q -O /dev/null is a GET under both wgets and still fails on any non-2xx (exit 8), so it is the same check with the request method pinned. All three satellite probes switch, the indexer's included so the probe does not depend on a router detail. Reproduced against echo v4.13.4 (the signer's version): --spider exits 8, -O /dev/null exits 0, and -O /dev/null against a 404 exits 8. The Dockerfile's note on the prod runtime records which wget it ships. 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-2
that referenced
this pull request
Sep 8, 2026
Changes to integrate the new w3infra compatible sprue implementation.
Peeja
added a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
See fil-forge/ucantone#30 --------- Co-authored-by: Alan Shaw <alan138@gmail.com>
Peeja
pushed a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
## Summary Adds an `AGENTS.md` agent guide and points `CLAUDE.md` at it via the `@AGENTS.md` convention. Written when the Forge workspace was reorganized; landing it now that write access is available. ## Read the docs rendered - [AGENTS.md](https://github.com/fil-forge/delegator/blob/feat/agents-md-docs/AGENTS.md) Generated with Claude Code
Peeja
pushed a commit
to fil-forge/forge-2
that referenced
this pull request
Sep 8, 2026
Adds a Ucanto server at `/` for getting your messages signed. You MUST have a delegation to `pdp/sign/*` from the signing service to use. This PR is currently backwards compatible with the existing service (I haven't removed the old routes). It also adds an `access/grant` handler so we don't need to ask the delegator for a delegation to use this service (Note: it is a TODO to actually validate the request). BREAKING CHANGE: The client for this service is now a Ucanto server client. Old server routes are still available temporarily. resolves storacha/piri-signing-service#2
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 #6 (
claude/forge-consolidation); review and merge that first. This PR is the consolidation itself: the code thatlibforgeheld becomes three in-repo modules, three satellite services join, the five existing services are rewired onto the in-repo modules, and CI, the container build and the root Makefile stop assuming a fixed set of four services. Nothing outside this repository changes:libforge,guppy,delegator,piri-signing-service,indexing-serviceandgo-ipni-toolskeep their repositories and pipelines until the team decides to retire them.Eight commits, each meant to be reviewed on its own — the commit messages carry the detail; this description is the map.
Commits
Add protocol: the wire contract as an in-repo moduleprotocol/(modulegithub.com/fil-forge/forge/protocol):commands/**,blobindex,receipt,retrieval,bytemap,digestutilfromlibforgemain81372e7plus PR fil-forge/libforge#52 (f4b13f7, rebased). Its ownMakefilewith the codegen gate (make codegen-build gen-check).Add internal: repo-private helpers shared by the servicesinternal/(modulegithub.com/fil-forge/forge/internal):jobqueue,identity,piece(libforge);sigv4,s3perm,client/hilt(from live hilt, which is newer than libforge#52's copies);zapucan(libforge#52);client/delegator(delegator'sclient/);pdpsigner/{types,signer,inprocess,client}(piri-signing-service'spkg/*);ipni/advertisement(a copy of go-ipni-tools'pkg/advertisement, see below). The Go toolchain enforces theinternal/rule across modules undergithub.com/fil-forge/forge/, so this module is repo-private by construction.Add attestation: the did:mailto attestation extension as its own moduleattestation/:attestation,didmailto,attest(waslibforge/commands/ucan/attest). Depends onucantoneonly.Add delegator, piri-signing-service and indexing-service as services3d3525c,8472ad9,a119ea8), module paths rewritten, per-repo.github/,Dockerfile*,deploy/,.storoku.jsondropped (source only; each storoku pipeline stays in its repo). indexing-service's tracked 24 MB Lambdabootstrapbinary is dropped too. The library halves of delegator and piri-signing-service live ininternal/(commit 2).Rewire the services onto the in-repo modulesprotocol/internal/attestationinstead oflibforge; hilt drops the copies that moved (pkg/sigv4,pkg/s3perm,pkg/lib/zapucan, the S3 RPC client, theauth/bucketerror sentinels) and keeps its server side,AdminClientand upload client.go.mods lose the externallibforge/hilt/smelt/indexing-servicerequires and gainreplacedirectives to the in-repo modules. Prose that still said "libforge" is updated.smelt: follow the shared modules and the root build contextpkg/workspacerebuilds every service when any ofprotocol,internal,attestationis in the workspace (was:libforge);pkg/stackbuilds images with the repository root as context;tests/compat'sotherThanmaps service names explicitly and panics on an unknown one instead of vacuously passing.Build and test every module in CI; images from the repository rootci.yml: areplacesjob (.github/scripts/check-replaces.sh), a static 12-moduleunitmatrix with the codegen gate onprotocol/attestation, and thestackjob building all 7 service images withcontext: ..docker/Dockerfilebecomes a root-context build (SERVICE,MAIN_PKG,BIN,BUILD_TAGS).publish-ghcr.ymlpublishes 7 services. RootMakefile(SERVICES,LIBRARIES,gen-check,check-replaces),.dockerignore,go.work(12 modules),README.md.smelt: probe the satellites' health with a GET, not wget --spider--spiderprobes with HEAD (busybox's, in the satellites' alpine images, probes with GET), and echo answers HEAD on a GET-only health route with 405 — so the signer (and the delegator, identically built) came up "unhealthy" andcompose upaborted. The three satellite probes becomewget -q -O /dev/null …, a GET under both wgets with the same pass/fail semantics; reproduced against the signer's echo version before pushing.How the pieces relate
go.work; CI and the container build useGOWORK=offwithreplacedirectives, so each module is hermetic on its owngo.mod. A missingreplaceresolvesgithub.com/fil-forge/forge/...through the proxy to the repository root and fails confusingly ("module found but does not contain package"), which is whatcheck-replaces.shguards against.protocol/commands/s3{,/request,/bucket}:Operation,OperationFor,ClassifyRequest, the error sentinels) is one module, so hilt, ingot andinternal/client/hiltshare identical types — the design of libforge#52, realised in-repo with live hilt's newersigv4/s3perm/client code.ErrBucketAlreadyOwned(live hilt) is ported intoprotocol/commands/s3/bucket.ucantoneis pinned tov0.0.0-20260904190501-7fca40e13941, the head of Add ucanlib (from libforge) and fixed test identities to testutil ucantone#57 (the fixtures anducanlibthat the rest of the tree needs). The pseudo-version resolves through the module proxy today; bump it once #57 merges.Deliberately not in this PR
release.ymlis untouched. Per-service tags for the three new services (and whether the library modules get tags at all) need a decision first.guppy'spkg/client/locator/tokenstoreand ingot'sforgeclientfork) — Phase 4, in Add forgeclient: ingot's Forge client as a shared module #8.go-ipni-toolsstill depends onlibforge:pkg/queueimportslibforge/jobqueueand its tests uselibforge/testutil. Forge therefore carries its own copy ofpkg/advertisementunderinternal/ipni/advertisement. Once go-ipni-tools movespkg/queueonto its own job queue (orinternal/jobqueue's successor) and its tests ontoucantone/testutil, the copy can go back to an import.swarfstays an external module and still pinslibforge, solibforgeremains an indirect dependency of hilt and ingot (viaswarf/pkg/client) and of indexing-service (viago-ipni-tools/pkg/queue). No in-repo package imports it.apidiffgate and nogo work synccheck yet.Verification (this branch,
GOWORK=offunless noted)go build ./...,go vet ./...,go mod tidyleaves no diff (piri with-tags skiff, as CI builds it).-tags itest ./tests/s3,-tags e2e ./tests/e2e,-tags compat ./tests/compat; hilt-tags itest ./itest.make -C protocol codegen-build gen-check,make -C attestation codegen-build gen-check: generated codecs are current..github/scripts/check-replaces.sh: every in-repo require has a matching replace.go.work): every module builds.pkg/stackimage builds, indexing-servicepkg/awsandpkg/redistestcontainers, piri's minio store), which this sandbox has no daemon for; CI runs them.Reviewing
Commit by commit. Commits 1–4 are mostly file moves:
git show --stat -Mshows the moves, and each can be checked against its source (diff -ragainstlibforgeat81372e7+PR 52, or the satellite's main at the SHA above; the differences are module paths, dropped deploy material and gofmt). Commit 5 is the one with hand edits, concentrated in hilt; commit 7 is the CI/Docker/Makefile change; commit 8 is four files.🤖 Generated with Claude Code
https://claude.ai/code/session_01CGAGAib517Ae1kg8SCdcEt