Skip to content

Test each node end to end after its smoke test - #154

Draft
bajtos wants to merge 2 commits into
mainfrom
claude/fil-1180-implementation-218ca4
Draft

bajtos wants to merge 2 commits into
mainfrom
claude/fil-1180-implementation-218ca4

Conversation

@bajtos

@bajtos bajtos commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Written by Claude.

Test each dev and staging node end to end after its smoke test. The smoke test proves the services answer and run the pinned images. This proves a node can serve a customer.

Fixes FIL-1180.

The test

scripts/ci/e2e-test.sh <node> checks each node on every push to main and on the hourly schedule. It does six things:

  • creates a tenant through central's Hilt;
  • mints an S3 key for the tenant that expires after 2 hours;
  • creates a bucket on the node's Ingot;
  • uploads a 4 KiB object with PutObject and a 16 MiB one with a multipart upload, checking that the ETag shows two parts;
  • downloads both and compares their SHA-256 sums;
  • deletes everything it created, in a separate --cleanup run.

The test does not retry: a new tenant on every run means each run tests tenant creation, and the next hourly run tests again.

Cleanup runs even after a failed test. It deletes only what the test recorded creating, in the order Hilt requires: objects, then the bucket, then it disables the tenant and deletes it. Each delete is retried 3 times. If one still fails, cleanup stops and keeps the tenant, because Hilt cannot recover the storage of a tenant deleted with data in it.

The Slack alert now tells four cases apart: the node never reached the commit, it failed the smoke test, it failed the end-to-end test (naming the failed operation), or cleanup left something behind (naming the tenant and bucket, with a link to the manual cleanup in the runbook).

The credential

The test calls Hilt with FilOne's partner key for the stage, hilt/partner-key in central's SSM, copied into the HILT_PARTNER_KEY secret. That key can manage every tenant on its Hilt (FIL-1489), so it lives in a GitHub Environment per stage (dev, staging) that admits only main. As a result, this workflow can no longer be dispatched from a branch. Every run also adds a deployment record to the repository's Deployments view.

Production stays out of the matrix until FIL-1491 finds a credential that cannot reach customer tenants.

The script never puts the partner key on a command line, and it masks the minted S3 secret before doing anything else with it. Each new tenant leaves records in Sprue and the PLC directory that Hilt does not delete yet (FIL-1488).

Before merging

Create the dev and staging Environments here, limited to main, then set HILT_PARTNER_KEY in each. The runbook section "Setting up the end-to-end test's credential" has the steps. A workflow that names a missing Environment creates it with no branch rule, so this has to happen first.

Testing

  • make check-shell passes.
  • The script has not run against a real Hilt or node yet. I ran it against a fake Hilt and a fake aws CLI, which covered:
    • a clean run;
    • a tenant that already exists, which is left untouched;
    • a failed key creation, after which cleanup deletes only the tenant;
    • corrupted downloads, caught by the comparison;
    • a bucket that cannot be deleted, after which cleanup tries 4 times, keeps the tenant and names both leftovers;
    • Hilt failing twice before deleting the tenant, which the retries absorb.
  • Before merging, run the test and its cleanup by hand against dev, as the runbook's "Run the end-to-end test by hand" shows. Then confirm that GET /tenants/<name> on Hilt answers 404.
  • After merging, watch the first push run and one hourly run on both nodes.

The smoke test proves a node's services answer and run the pinned
images. Nothing proved that a node can serve a customer.

After the smoke test passes, smoke.yml now runs scripts/ci/e2e-test.sh
on each node. It creates a tenant through central's Hilt and a bucket
on the node's Ingot, uploads a 4 KiB object with PutObject and a
16 MiB one with a multipart upload, downloads both and compares them.
A cleanup step that runs even after a failure deletes the objects and
the bucket, then disables and deletes the tenant. Each delete is
retried three times. If one still fails, cleanup stops and keeps the
tenant, because Hilt cannot recover the storage of a tenant deleted
with data in it. The alert names whatever was left behind.

The test calls Hilt with its own partner key, read from the
HILT_PARTNER_KEY secret of a GitHub Environment per stage. Hilt lets
any partner key manage any tenant (FIL-1489), so each Environment
admits only main. As a result, smoke.yml can no longer be dispatched
from a branch.

Fixes FIL-1180

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
The end-to-end test now authenticates with FilOne's existing Hilt
partner key, hilt/partner-key, instead of a dedicated CI key. That
key can delete every tenant on its Hilt, so the test runs on dev and
staging only, where the stakes are low. FIL-1491 tracks finding a
credential safe enough to run it against production.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
@bajtos

bajtos commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

I ran the E2E tests against dev and already discovered a misconfiguration. Fixed here:

bajtos added a commit to fil-forge/hilt that referenced this pull request Oct 20, 2026
The first run of E2E tests
(fil-forge/infra-nodes#154) discovered that Hilt
in dev cannot delete tenants because it was configured to use the
production revocation service. I decided to make the revocation service
config required to avoid similar surprises in the future.

---

_Written by Claude._

**Do not merge until fil-forge/infra-central#264, which sets
`HILT_REVOCATION_SERVICE_ID` and `HILT_REVOCATION_SERVICE_URL` per
stage, is applied to dev, staging and prod.** After this lands, a Hilt
task without them exits at startup.

Require `revocation.service_id` and `revocation.service_url` instead of
defaulting them to prod's Swarf (`did:web:revoke.fil-forge.com` /
`https://revoke.fil-forge.com`).

Dev's ECS task sets neither variable, so dev Hilt published revocations
to prod's Swarf. Prod Swarf does not know dev tenants' did:plc DIDs and
answers 404, which turned every dev tenant delete into a 500. Staging
has the same gap.

- Remove both `SetDefault` calls in `pkg/config` and the
`--revocation-service-*` flag defaults in `cmd/main.go`.
- `NewRevocationClient` rejects an empty DID or URL, so `hilt serve`
exits during fx construction with `revocation.service_id is required:
set HILT_REVOCATION_SERVICE_ID or --revocation-service-id` (or the
`_url` equivalent). The check is explicit because `url.Parse("")`
succeeds and would otherwise build a client that fails only on its first
publish.
- The `hilt client` commands load config but never build the revocation
client, so they keep working without these settings.
- Tests: `config.Load` leaves both fields empty when nothing sets them;
`NewRevocationClient` errors on each missing field; an fx app with
`RevocationModule` and empty config fails to construct.
- README and AGENTS.md mark both settings required.

The integration stack keeps booting: smelt's `systems/hilt/compose.yml`
already sets `HILT_REVOCATION_SERVICE_ID=did:web:swarf` and
`HILT_REVOCATION_SERVICE_URL=http://swarf:80`.

`upload.service_id`, `upload.service_url` and `upload.product_id` still
default to prod values in the same way. They are left for a separate
change.

Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
bajtos added a commit to fil-forge/hilt that referenced this pull request Oct 20, 2026
The first run of E2E tests
(fil-forge/infra-nodes#154) discovered that Hilt
in dev cannot delete tenants because it was configured to use the
production revocation service. I decided to make the revocation service
config required in #99. Claude
flagged that there is a similar footgun related to the configuration of
the upload service, so in this PR I am making it required too.

---

_Written by Claude._

Require `upload.service_id`, `upload.service_url` and
`upload.product_id` instead of defaulting them to prod's Sprue
(`did:web:upload.fil-forge.com`, `https://upload.fil-forge.com`) and
prod's product DID (`did:web:hilt.fil-forge.com`).

A stage that forgot these settings would register its tenants and
provision its buckets on prod Sprue. #99 fixes the same
pattern for the revocation settings, which turned every dev tenant
delete into a 500.

- Remove the three `SetDefault` calls in `pkg/config` and the
`--upload-*` flag defaults in `cmd/main.go`.
- `NewUploadClient` rejects an empty service DID, URL or product DID, so
`hilt serve` exits during fx construction with, for example,
`upload.product_id is required: set HILT_UPLOAD_PRODUCT_ID or
--upload-product-id`.
- `upload.product_id` was optional in the constructor. Tenant
provisioning always passes it to Sprue's `/customer/add`, so a `serve`
without it cannot create tenants, and it is now required too.
`upload.proofs` stays optional.
- The `hilt client` commands load config but never build the upload
client, so they keep working without these settings.
- Tests: `config.Load` leaves the upload config empty when nothing sets
it; `NewUploadClient` errors on each missing field and on an invalid
product DID; an fx app with the upload client and empty config fails to
construct.
- README and AGENTS.md mark the three settings required.

Safe to merge once reviewed. infra-central's shared `modules/apps`, used
by dev, staging and prod, has set all three `HILT_UPLOAD_*` variables
since 2026-08-19. Smelt's `systems/hilt/compose.yml` sets them for the
integration stack.

This PR and #99 edit neighbouring lines in `pkg/config/config.go`
`SetDefaults` and the same "Run locally" line in AGENTS.md. Whichever
merges second needs a small conflict resolution.

Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
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.

1 participant