Skip to content

fix(testnet/events-capture): derive per-suite root SK to unblock run-all/run-local#113

Merged
AquiGorka merged 1 commit into
mainfrom
fix/events-capture-per-suite-derivation
Jun 8, 2026
Merged

fix(testnet/events-capture): derive per-suite root SK to unblock run-all/run-local#113
AquiGorka merged 1 commit into
mainfrom
fix/events-capture-per-suite-derivation

Conversation

@AquiGorka

Copy link
Copy Markdown
Contributor

Summary

  • The events-capture harness shipped in feat(testnet): events-capture framework + EXPECTED_EVENTS for testnet-main + lifecycle-testnet-verify #112 required a wrapper-level MASTER_SECRET to derive operator/alice/bob keys ahead of subscriber bring-up.
  • When run-local.sh all runs suite 1 (testnet-main) and suite 3 (lifecycle-testnet-verify) back-to-back under the single root the wrapper exports, both suites derive the same PP operator key — suite 3's POST /dashboard/council/join then hits provider-platform's "PP already active for this council" check and 409s. run-all.sh has a related failure mode: it doesn't export MASTER_SECRET at all, so the harness throws at parse-args time.
  • Fix: layer one more SHA-256 stage on top of masterSeedFromSecret keyed on the scriptName, and fall back to a freshly-minted random root when MASTER_SECRET is unset.

What changed in testnet/events-capture/harness.ts

  • derivePerSuiteSecret(rootSecret, scriptName)SHA-256(rootSeed || suite/${scriptName}) → 32 bytes → Stellar SK via Keypair.fromRawEd25519Seed(...).secret(). Matches the SHA-256-of-concatenation style already in lib/master-seed.ts.
  • Before invoking the script's main(), the harness re-exports MASTER_SECRET to the per-suite SK so the script's own Deno.env.get("MASTER_SECRET") + masterSeedFromSecret(...) derives matching role keys.
  • parseArgs() no longer throws when MASTER_SECRET is unset — it mints an ephemeral root, restoring the pre-harness shape of the underlying scripts and letting run-all.sh invoke the harness without wrapper changes.

Properties

  • Caller's surface unchanged. Pin MASTER_SECRET once at the wrapper level → reproducible across re-runs; leave it unset → ephemeral per-invocation.
  • Same root + same scriptName ⇒ identical per-suite SK (reproducibility).
  • Different scriptName ⇒ disjoint role keys (suite 1 and suite 3 isolated).

Test plan

  • deno fmt --check clean (testnet)
  • deno lint clean (testnet)
  • deno check events-capture/harness.ts clean
  • ./testnet/run-local.sh all against local stack — full sweep PASS:
    • Suite 1 — payment flow: 12/12 per-PP events + 5/5 network events, flow in 76.4s
    • Suite 2 — OTEL verify (payment): 23/23 assertions
    • Suite 3 — lifecycle flow: 12/12 per-PP events + 5/5 network events, flow in 80.6s
    • Suite 4 — OTEL verify (lifecycle): 23/23 assertions
    • PP operator keys differ between suite 1 (GAFWC4R3...) and suite 3 (GAX77SRV...) — per-suite derivation confirmed working.
  • ./testnet/run-all.sh against deployed testnet — to verify the deployed-Tempo path still works end-to-end with the no-MASTER_SECRET fallback.

…all/run-local

The harness required a wrapper-level MASTER_SECRET to derive operator/alice/bob
keys ahead of subscriber bring-up. Wiring suites 1 and 3 under one root in
run-local.sh shared the same PP operator key — suite 3's POST /dashboard/council/join
hit provider-platform's "PP already active for this council" check and 409'd.

Layer one more SHA-256 stage on top of masterSeedFromSecret:
  perSuiteSeed = SHA-256(rootSeed || `suite/${scriptName}`)
  perSuiteSecret = Keypair.fromRawEd25519Seed(perSuiteSeed).secret()

Re-export MASTER_SECRET to the per-suite SK before script.main() runs so the
script's own Deno.env.get("MASTER_SECRET") + masterSeedFromSecret(...) derives
matching role keys. Same root + same scriptName ⇒ identical per-suite SK
(reproducible across re-runs). Different scriptName ⇒ disjoint role keys.

parseArgs falls back to a freshly-minted random root when MASTER_SECRET is
unset, restoring the pre-harness behavior of the underlying scripts and letting
run-all.sh (deployed Tempo, no MASTER_SECRET exported) invoke the harness
without wrapper changes.
@AquiGorka AquiGorka merged commit 8a594ba into main Jun 8, 2026
7 checks passed
@AquiGorka AquiGorka deleted the fix/events-capture-per-suite-derivation branch June 8, 2026 22:34
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