feat: adopt Hilt's S3 client and wire contract from the forge monorepo - #52
Draft
frrist wants to merge 1 commit into
Draft
feat: adopt Hilt's S3 client and wire contract from the forge monorepo#52frrist wants to merge 1 commit into
frrist wants to merge 1 commit into
Conversation
Ingot (and any future S3 gateway) consumes Hilt through a client and a wire contract that until now lived inside the hilt module, forcing ingot to carry a replace directive that breaks external consumers of the ingot library. This makes libforge their home, next to the commands/s3 bindings they are built from: - client/hilt: the Hilt S3 UCAN RPC client (Client, options) - commands/s3: Operation + OperationFor (request classification) - commands/s3/request, commands/s3/bucket: named error contract - sigv4: stdlib-only SigV4/SigV4a verification and key derivation - s3perm: S3 permission -> Forge command mapping - ucan/zapucan: zap logging helpers for ucan values Also bumps ucantone to the version the forge services already unified on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
frrist
added a commit
to fil-forge/forge
that referenced
this pull request
Jul 31, 2026
All six modules (hilt, ingot, ingot/itest, piri, smelt, sprue) now require libforge v0.0.0-20260731172903-928cf2a21b7e — the fil-forge/libforge#52 head — so the whole repo agrees on one libforge again, and hilt/ingot resolve the extracted client/contract packages without any workspace. hilt and ingot container images build standalone for the first time since the extraction. The bump crosses libforge's breaking change #50 (blob-release Cause), which this commit adopts: - sprue: /blob/release now carries Cause — the task link of the client /blob/remove it translates — and ships the remove invocation in the request container so the node can verify the release's origin. The remove handler threads both through forwardBlobRelease. - piri: release-handler tests construct wire-valid arguments (Cause set, following the RandomCID precedent the allocate tests use). The node-side verification #50 calls for (reject UnknownCause/InvalidCause) is not yet implemented — piri still accepts releases without checking the cause chain. After PR#52 merges, re-pin to the merged commit (or the next libforge tag) so go.mod references a commit on main rather than a PR head. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
frrist
added a commit
to fil-forge/forge
that referenced
this pull request
Jul 31, 2026
All six modules (hilt, ingot, ingot/itest, piri, smelt, sprue) now require libforge v0.0.0-20260731172903-928cf2a21b7e — the fil-forge/libforge#52 head — so the whole repo agrees on one libforge again, and hilt/ingot resolve the extracted client/contract packages without any workspace. hilt and ingot container images build standalone for the first time since the extraction. The bump crosses libforge's breaking change #50 (blob-release Cause), which this commit adopts: - sprue: /blob/release now carries Cause — the task link of the client /blob/remove it translates — and ships the remove invocation in the request container so the node can verify the release's origin. The remove handler threads both through forwardBlobRelease. - piri: release-handler tests construct wire-valid arguments (Cause set, following the RandomCID precedent the allocate tests use). The node-side verification #50 calls for (reject UnknownCause/InvalidCause) is not yet implemented — piri still accepts releases without checking the cause chain. After PR#52 merges, re-pin to the merged commit (or the next libforge tag) so go.mod references a commit on main rather than a PR head. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
frrist
added a commit
to fil-forge/forge
that referenced
this pull request
Jul 31, 2026
* refactor(hilt): consume the S3 client and wire contract from libforge The Hilt S3 RPC client, sigv4, s3perm, zapucan, and the /s3/* wire contract (named error sentinels, Operation/ClassifyRequest) moved to libforge, next to the commands/s3 bindings they are built from. Hilt keeps the server side: the Authorizer and bucket services, stores, vault, plus the hilt-internal AdminClient and Sprue UploadClient. This is the first half of removing ingot's replace directives: with the contract in libforge, ingot no longer needs to import the hilt module at all. hilt/go.mod still pins the pre-move libforge; it is bumped when the libforge change lands (until then, builds resolve via the workspace). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(ingot): drop the hilt replace directive, consume libforge instead ingot's hilt surface (client, sigv4, s3perm, auth/bucket wire contract) now lives in libforge, so the require + replace of the hilt module goes away. This is half of what makes the ingot library consumable outside the repo: a downstream 'go get' no longer resolves a nonexistent hilt@v0.0.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(ingot): split itest into a nested test-only module itest imports smelt's stack harness, and go.mod cannot mark a dependency as test-only — so as long as itest lived in the library module, ingot required the unpublished smelt module and needed a replace directive to resolve it. itest/ is now its own leaf module. Nothing imports a test module, so its two replace directives (ingot, smelt) are harmless — they never appear in any consumer's build. ingot's library go.mod is left with no intra-repo requires at all: with the hilt surface consumed from libforge (previous commit), both replace directives are gone and the library resolves like any published module. The itest module is added to go.work, and 'make itest' now enters itest/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * build: return every container build to its own service directory With ingot's replace directives gone, no Dockerfile needs a sibling module in its build context. Revert the repo-root-context machinery from the container work: piri/hilt/sprue Dockerfiles go back to their service-directory form, ingot's drops the hilt/smelt manifest and source copies, publish-ghcr.yml builds each service with context <service>/, and ingot no longer rebuilds on hilt or smelt changes. The root .dockerignore is gone (nothing builds from the root); each service's own .dockerignore applies again, now also ignoring goreleaser's dist/. smelt's BuildImage helpers revert to their one-directory-per-service shape: BuildPiriImage(t, "../piri") builds piri from its own directory, same as the out-of-repo services. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(ingot): match CLAUDE.md to the tags publish-ghcr actually pushes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * deps: pin libforge at the PR#52 commit across all services All six modules (hilt, ingot, ingot/itest, piri, smelt, sprue) now require libforge v0.0.0-20260731172903-928cf2a21b7e — the fil-forge/libforge#52 head — so the whole repo agrees on one libforge again, and hilt/ingot resolve the extracted client/contract packages without any workspace. hilt and ingot container images build standalone for the first time since the extraction. The bump crosses libforge's breaking change #50 (blob-release Cause), which this commit adopts: - sprue: /blob/release now carries Cause — the task link of the client /blob/remove it translates — and ships the remove invocation in the request container so the node can verify the release's origin. The remove handler threads both through forwardBlobRelease. - piri: release-handler tests construct wire-valid arguments (Cause set, following the RandomCID precedent the allocate tests use). The node-side verification #50 calls for (reject UnknownCause/InvalidCause) is not yet implemented — piri still accepts releases without checking the cause chain. After PR#52 merges, re-pin to the merged commit (or the next libforge tag) so go.mod references a commit on main rather than a PR head. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: move the S3 system suite and its glue from ingot into smelt The itest suite never tested ingot-the-module — it boots the whole Forge stack and drives it through ingot's S3 API, with the working-tree ingot binary mounted by the harness. It is a system suite, and it now lives with the system: the S3-client glue (former ingot/testing) becomes smelt/pkg/s3glue, and the conformance partition + scenarios (former ingot/itest) become smelt/tests/s3, inside smelt's own module next to where the compat suites are headed. This deletes the nested ingot/itest module and with it the last replace directives in the repository. Both of its imports (pkg/stack, the glue) are now same-module, so there is nothing left to resolve by path: every go.mod in the repo is resolvable by any consumer, and in-repo HEAD-vs-HEAD testing is preserved because tests and harness share a module and the service under test arrives as a working-tree binary, not an import. Cost, accepted deliberately: smelt's module graph gains aws-sdk-go-v2 and the versitygw fork (go.mod cannot mark test-only deps). smelt's consumers are themselves test suites, so graph leanness buys them nothing. ingot's 'make itest' forwards to smelt's new 'make test-s3'; docs updated to the new division: out-of-repo services keep behavior tests in their repos (pseudo-version imports of pkg/stack), in-repo system suites live in smelt/tests/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: unit tier per module + one full-stack run per PR Two tiers over one commit, mirroring the old per-repo split: unit per-module, path-filtered, GOWORK=off, CGO_ENABLED=0 (piri adds -tags=skiff). Each filter is just the service's own subtree — no module imports another, so nothing else can break its compile. stack one run per PR: smelt's e2e smoke suite, then the S3 gateway system suite (smelt/tests/s3), which compiles the working tree's ingot and bind-mounts it over the published image. Sequential in one job: the suites sweep smeltery-* containers, so two stacks must never share a Docker host. Container logs are dumped and uploaded on failure. Also fixes a latent path bug from the itest move: localIngotBinary built from '..' (correct when the suite lived at ingot/itest, wrong from smelt/tests/s3). Verified end-to-end by running TestForgeNativeProvision against a real stack boot: working-tree binary mounted, hilt tenant provisioned, 512KiB round-trip through sprue/piri. The stack job pulls ghcr.io/fil-forge/forge/<svc>:main for the sibling services, so it needs the container-build changes merged to main (which publishes those tags) before it can go green on a PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: remove failing piri (sqlite) smoke tests * test(smelt): skip the snapshot restore e2e until a postgres snapshot exists The only committed snapshot (3-piri-filesystem-sqlite) captures a sqlite-backed piri, and piri's curio PDP pipeline now refuses sqlite — the fixture can never boot healthy again. Skip with the unskip condition stated: capture and commit a postgres-backed snapshot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: serialize e2e subtests — one stack per Docker host Each e2e subtest boots a complete Forge stack. Without -parallel 1 Go runs them GOMAXPROCS-wide, and two simultaneous stack boots overwhelm the 2-vCPU runner: a container exec misses its readiness handshake and compose fails with runc's 'procReady not received'. Same guard ci/stack's ci.yml already carries; SMELT_E2E_MAX_PARALLEL stays as the escape hatch for beefier hosts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: name the unit steps so the job view reads as a checklist Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * build: one Go version across every module — 1.26.5 The six modules had drifted across five different go directives (1.24.0 to 1.26.4), which just failed PR CI in a subtle way: the stack job installs the toolchain from smelt/go.mod (1.25.7), setup-go pins GOTOOLCHAIN=local, and the S3 suite's in-test 'go build ./cmd/ingot' then refuses — ingot requires >= 1.26.4 and auto-download is disabled. All modules now declare go 1.26.5 (the latest release, and what go.work and the shared builder image already use), so whichever go.mod a workflow reads, the installed toolchain satisfies every module. Same rationale as the dependency unification: one commit should hold one view of the toolchain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(smelt): grant the conformance key hilt's multipart permissions The stack tier's whole point — every service at this commit — surfaced a drift the floating-baseline runs had been masking: hilt now classifies multipart operations distinctly and requires s3:AbortMultipartUpload, s3:ListMultipartUploadParts and s3:ListBucketMultipartUploads, but the test tenant's access key list predated them. Hilt rejected those operations as OperationNotPermitted before bucket/argument validation, so pass-table cases expecting NoSuchBucket / InvalidArgument saw AccessDenied. Adds the three permissions and a lockstep guard (TestHiltPermissionsCoverS3Perm): every listed permission must be one s3perm recognizes, and every classified operation's required permission must be in the list — so the key can never silently lag the permission model again. Verified against a live stack: the ListMultipartUploads, AbortMultipartUpload and CompleteMultipartUpload categories all pass. Also bumps sprue's (branch-local, soon-deleted) Dockerfile builder to golang:1.26 — the go 1.26.5 unification made the 1.25 base unable to compile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <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.
Ingot (and any future S3 gateway) consumes Hilt through a client and a wire contract that until now lived inside the hilt module, forcing ingot to carry a replace directive that breaks external consumers of the ingot library. This makes libforge their home, next to the commands/s3 bindings they are built from:
Also bumps ucantone to the version the forge services already unified on.