v0.5.0: e2e faucet verify, local network, aggregator critical checks#3
Conversation
Adds a sixth probe to the suite for the Unicity test faucet (https://faucet.unicity.network). Many SDK e2e suites depend on the faucet for wallet funding (uxf-send-receive, pointer-roundtrip, migrate-to-profile-conservation, profile-export-roundtrip); when the faucet is down, those suites silently time out at 240 s with a generic "Faucet top-up timed out" message. Probing upfront converts that into a clean 1-2 s SKIP with a precise diagnostic. The probe issues POST /api/v1/faucet/request with a deliberately- invalid probe nametag (`infra-probe-do-not-mint-zk7q3xa9p2v`). Healthy faucet returns HTTP 4xx + structured `{success: false, error: "Nametag not found: ..."}`. This exercises the full HTTP/parse/nametag-resolve/response-shaping pipeline WITHOUT consuming actual faucet quota or requiring a real wallet — the same "empty cost, full coverage" pattern the aggregator probe uses with its known-bad shard ID. Defense-in-depth: if the faucet ever returns success:true for the probe nametag, the probe DOWNGRADES to 'degraded' (validation may be broken — the probe nametag is supposed to be invalid). Network config: - testnet, dev: faucet = "https://faucet.unicity.network" - mainnet: faucet = null (no faucet by design) The probe layer treats null as a clean skip with verdict 'healthy' — cleaner than emitting a misleading "unreachable" verdict against a default URL that doesn't apply to the network being probed. SERVICES expands from 5 to 6: ['nostr', 'aggregator', 'ipfs', 'fulcrum', 'market', 'faucet']. The 'faucet' entry is appended at the end so existing --only invocations are unaffected. Tests: - 21 smoke tests pass (added 1 for mainnet/testnet faucet contract) - Live testnet probe: 6/6 services healthy - Live mainnet probe with --only faucet: skipped cleanly with "network mainnet has no faucet by design — skipped"
…res as unreachable
The verdict logic counted ALL failed checks uniformly — one fail = degraded,
two fails = unreachable. That treated submit_commitment (the canonical
write-path functional check the aggregator exists to serve) as no more
important than a diagnostic JSON-RPC plane probe.
Symptom: testnet returns HTTP 401 Unauthorized on submit_commitment while
/health and get_block_height keep working. Old verdict: `DEGRADED` (exit 1).
Downstream e2e pre-flight gates that only fire on exit code 2 silently let
test suites run against an aggregator that cannot accept commitments,
producing 35 phantom `Submit failed: [object Object]` failures on the
sphere-sdk side (companion issue: sphere-sdk #191).
Fix:
- Mark submit_commitment + get_inclusion_proof checks with `critical: true`.
- Extract verdict computation into a pure `computeAggregatorStatus(checks)`
function, exported so the rule is testable network-free.
- Rule: any critical-check fail → 'unreachable'. Non-critical fails follow
the legacy "≥ 2 → unreachable, exactly 1 → degraded" rule. Warns
continue to drive 'degraded'.
- 8 new smoke tests pin the rule: healthy / non-critical degraded / warn /
critical-fail unreachable (both checks independently) / multi-fail legacy
preserved / critical-fail-wins-over-warn / explicit critical:false.
Encodes the CLAUDE.md "False-negative discipline" principle directly: the
functional layer "is what catches real outages that liveness misses" — and
when it catches them, the verdict MUST reflect that the service is
unusable for its intended purpose, not merely slow.
Bumps to 0.4.1.
Companion work for the sphere-sdk hermetic e2e stack (tests/e2e/local-infra/) which boots a local aggregator in BFT_ENABLED=false standalone mode. Two changes layered on top of #1's critical-check verdict fix: 1. NETWORKS.local — endpoints for the docker-compose stack: - aggregator http://127.0.0.1:3001 - nostr ws://127.0.0.1:7777 - ipfs http://127.0.0.1:8082 Fulcrum/Market intentionally non-local (no local counterpart yet). Faucet null — the local faucet is DM-driven, not HTTP, so the existing faucet probe doesn't apply. 2. aggregator /health body parser accepts both shapes: - BFT mode: {"status":"healthy","database":"ok","aggregators":{...}} - Standalone mode: {"status":"ok","role":"standalone","details":{"database":"connected",...}} Previously the standalone shape was reported as `degraded` even when the aggregator was fully functional (submit_commitment + get_inclusion_proof both passing). Verified against the live local stack: unicity-infra-probe --network local --only aggregator → HEALTHY (4/4 checks passed), exit 0. Tests: 30/30 pass. Two new tests cover NETWORKS.local shape + NETWORKS.local.faucet === null. Bumps to 0.4.2.
…ical checks) The agent-facing guide had drifted from the code: faucet probe added in 0.4.0, `local` docker-compose network in 0.4.2, and the aggregator critical-check verdict rule (sphere-sdk #191 follow-up) were all documented in commits but not in the contributors' single source of truth. New agents were re-deriving these from git log — which is what this file exists to prevent. Also adds the required Claude Code header, a Common commands section, and pins the `faucet: null` clean-skip pattern so the next "optional service" addition follows precedent instead of inventing a new convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous faucet probe sent a deliberately-invalid nametag and treated the faucet's "Nametag not found" rejection as proof-of-life. That correctly exercised the HTTP/parse/resolve pipeline but couldn't catch the failure mode that actually matters to downstream e2e suites: the faucet accepts a real mint request, returns success:true, and yet no token ever lands at the recipient. A live testnet run also surfaced a confusing UX consequence — the "Nametag not found" string next to a green-tick check reads as a contradiction even when the verdict is correct, leading operators to distrust the probe. This commit replaces the rejection-handshake with the full mint round- trip. The probe now spins up an ephemeral Sphere wallet, mints a single-use nametag on the L3 aggregator, publishes the kind:30078 binding, requests 1 raw unit (1e-6) of USDU from the faucet, and waits up to 10s for the corresponding kind:31113 token-transfer event to arrive. The SDK handles NIP-04 decryption + Token deserialization. We then compare the delivered token's coinId + amount against the faucet's own HTTP-response declaration (amountInSmallestUnits) — independent proof the mint actually landed. The faucet has no probe-only mode and no direct-pubkey shortcut, so verifying real delivery requires running as a one-shot wallet. The trade-off taken to keep the implementation tractable was pulling in @unicitylabs/sphere-sdk as a dependency, which violates three of the project's "Hard rules" in CLAUDE.md (minimal-deps, no-SDK-coupling, stateless-on-relay). All three rules are now explicitly scoped down with "with one exception" carve-outs and a "The faucet exception" section that records the rationale and what to revisit if the faucet ever grows a probe-only mode. BREAKING CHANGE: the faucet probe's check names changed (request/health → wallet-setup/request/receipt) and all three are now critical:true. JSON consumers that filter on the previous check names will need to update. End-to-end wall-clock is now ~8–12s (up from <500ms); the orchestration layer auto-bumps the faucet's timeout ceiling to at least 30s. The probe now leaves a kind:30078 event on the Nostr relay + a nametag NFT on the L3 aggregator + consumes 1 USDU raw unit (≈ economically zero) per run. Documented in CLAUDE.md "Stateless on the relay/gateway side, with one exception". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a significant update to the infrastructure probe tool, most notably adding a new end-to-end Faucet probe that performs a real mint-and-verify cycle using the @unicitylabs/sphere-sdk. It also enhances the aggregator probe by implementing a "critical check" logic, ensuring that functional failures (such as submission rejections) correctly drive an unreachable status rather than a merely degraded one. Additionally, a new local network profile has been added to support testing against hermetic docker-compose stacks. Review feedback correctly identified a potential TypeError in the faucet probe when handling token amounts, suggesting a more robust null check before BigInt conversion.
| // about what its async send actually delivered — a real bug to | ||
| // surface, but the faucet DID deliver something, so degraded | ||
| // (not unreachable). | ||
| if (expectedRawAmount != null && BigInt(minted.amount) !== BigInt(expectedRawAmount)) { |
There was a problem hiding this comment.
The BigInt constructor throws a TypeError if its argument is null or undefined. Adding a check for minted.amount ensures the probe doesn't crash if the SDK returns an unexpected token structure.
| if (expectedRawAmount != null && BigInt(minted.amount) !== BigInt(expectedRawAmount)) { | |
| if (expectedRawAmount != null && minted.amount != null && BigInt(minted.amount) !== BigInt(expectedRawAmount)) { |
Summary
coinId+amountmatch the faucet's declaredamountInSmallestUnits. The previous bogus-nametag check correctly exercised the HTTP/parse/resolve pipeline but couldn't catch the failure mode that actually breaks downstream e2e suites: the faucet accepts a real mint request, returnssuccess:true, and yet no token ever lands at the recipient.localnetwork added for the sphere-sdk docker-compose stack (E2E_FULL_LOCAL_STACK=1). Aggregator/Nostr/IPFS point at loopback; Fulcrum + Market keep public testnet URLs so a single command can surface "your local stack is fine; one of its public dependencies is down."submit_commitmentandget_inclusion_proofnow carrycritical:true; any single critical fail forcesunreachable(CLI exit 2), notdegraded(exit 1). Catches the sphere-sdk #191 incident class where a broken write path was silently classified as merely "degraded" and let e2e gates pass. The aggregator's/healthparser also now accepts both BFT ({status:"healthy"}) and standalone ({status:"ok", role:"standalone"}) response shapes.@unicitylabs/sphere-sdk≈ 74 MB), per-run side effects (one nametag NFT, one kind:30078 event, 1e-6 USDU consumed), and the trigger to revisit (faucet growing a probe-only mode). All other probes still uphold the original rules.Test plan
--only faucetreports the new three-check shape (wallet-setup/request/receipt) and verifies real USDU delivery in ~9s\$\$ref to "The faucet exception"\$\$link target resolves🤖 Generated with Claude Code