Closes #90. Adds the integration test that exercises the full omega
bootstrap path — DNS resolution, signed-list verification, root
self-recognition, cluster bootstrap, IsRoot() 403-gate, peer
convergence — in a single test invocation.
This is the test that would have caught F1/F2/F3/F11 the moment the
spec landed. Every preflight finding was caught by humans during
burn-in because no test before this exercised the *composition* of
trust + cluster + bootstrap + root-gate. The unit tests in
internal/trust/ cover the resolver path in isolation; nothing pulled
the whole stack together.
Tests added (internal/cluster/omega_integration_test.go):
- TestOmegaBootstrap_RootsConvergeAndNonRootJoins — happy path:
two roots in the signed list converge with each other and accept
the non-root. All three nodes resolve via the stub DNS, the
self-recognition logic correctly classifies them, and the
production-shaped /v1/bootstrap handler enforces the 403 gate.
- TestOmegaBootstrap_NonRootRejectsWith403 — a node not in the
signed list returns 403 even when its endpoint is reachable.
Protects against rogue-node peer-topology harvesting.
- TestOmegaBootstrap_TamperedListRejected — list signed by a
different keypair fails FetchSigned at the verify step.
- TestOmegaBootstrap_ExpiredListRejected — list with valid signature
but past-expiration is rejected.
Test infrastructure:
- Reuses the testNode harness from integration_test.go but installs
a different /v1/bootstrap handler (makeOmegaBootstrapHandler) that
mirrors the production IsRoot() gate. The shared harness skips
the gate because it's used by tests where IsRoot is not a concept.
- signTestList, buildResolver, resolveAndApply helpers walk the same
code paths cmd/repram/main.go uses, with injectable DNS.
- Roots are flipped to IsRoot=true on every node before any node
calls Start. In production all roots come up before bootstrap
traffic flows; doing the resolve+SetRoot phase first matches that
ordering rather than racing the first starter against a peer that
hasn't yet flipped IsRoot=true.
Drive-by: stale comment in internal/trust/signedlist.go said
"host:gossip-port" — updated to "host:http-port" with a #82 reference.
Suite results:
Go: all packages green; 4 new tests in internal/cluster
Closes #90
// ticktockbent
Summary
Closes #90. Adds the integration test that exercises the full omega bootstrap path — DNS resolution, signed-list verification, root self-recognition, cluster bootstrap, IsRoot() 403-gate, and peer convergence — in a single test invocation.
This is the test that would have caught F1/F2/F3/F11 the moment the spec landed. Every preflight finding was caught by humans during the burn-in because no test before this exercised the composition of trust + cluster + bootstrap + root-gate. The unit tests in
internal/trust/cover the resolver path in isolation; nothing pulled the whole stack together.What's covered
TestOmegaBootstrap_RootsConvergeAndNonRootJoinsTestOmegaBootstrap_NonRootRejectsWith403/v1/bootstrapeven when reachable; protects against rogue-node topology harvestingTestOmegaBootstrap_TamperedListRejectedTestOmegaBootstrap_ExpiredListRejectedTest design
testNodeharness fromintegration_test.gobut installs a different/v1/bootstraphandler (makeOmegaBootstrapHandler) that mirrors the productionIsRoot()gate. The shared harness skips the gate because it's used by tests where IsRoot is not a concept.signTestList,buildResolver, andresolveAndApplyhelpers walk the same code pathscmd/repram/main.gouses, with injectable DNS via thestubTXTResolverpattern frominternal/trust/resolver_test.go.IsRoot=trueon every node before any node callsStart. In production all roots come up before bootstrap traffic flows; doing the resolve+SetRoot phase first matches that ordering rather than racing the first starter against a peer that hasn't yet flippedIsRoot=true.Drive-by
The
Nodesfield doc ininternal/trust/signedlist.gostill said "host:gossip-port" — predates #82's port-semantics fix. Updated to "host:http-port" with a #82 reference.What this doesn't do
cmd/repram/main.go'sresolveOmegaBootstrap()directly — that function is hardcoded to use the baked-in pubkey + production DNS config, which makes it untestable without refactoring. The integration test exercises the same composition (trust.FetchSigned+cluster.NewClusterNode+ bootstrap with 403-gate handler), with injection seams the production binary doesn't need.internal/trust/cache_test.go.repram-mcp/src/index.ts; a TS-side integration test would be a follow-on. Filed mentally as a candidate for if/when a similar finding surfaces in TS.Suite results
internal/clusterTest plan
addr()comparison inresolveAndApplywould fail to match any seed and no node would self-recognize as a root → cluster fails to bootstrap → test fails)Closes
Closes #90
// ticktockbent