Skip to content

feat: #109 two-tier audit — real-time SSE feed + autonomous 2-min tier-A on-chain anchor#281

Open
hanwencheng wants to merge 18 commits into
mainfrom
claude/adoring-lamport-7e7a4b
Open

feat: #109 two-tier audit — real-time SSE feed + autonomous 2-min tier-A on-chain anchor#281
hanwencheng wants to merge 18 commits into
mainfrom
claude/adoring-lamport-7e7a4b

Conversation

@hanwencheng

@hanwencheng hanwencheng commented Jun 11, 2026

Copy link
Copy Markdown
Member

Closes #109. Closes #209 — the config-worker quarantine tripwire fired on this branch's harness run (config-test reachable; stage-3 steps 19 + 21 ran LIVE and passed), so per the guard's own instructions the config-role-missing/config-worker-unreachable allowances and the self-dissolving Guard step are removed; CI now fails closed on config-worker drift. Builds on the #229 (data-plane emits + V2 queues) and #97/#270 (control-plane emits + receipts) substrate, and closes the #229-deferred open design item "audit-worker-initiated chain submission (tier-A relay wallet)". Plan: docs/plan/issue-109-two-tier-audit.md.

Design decision — anchor via ungated appendV2 + AuditRootAnchor (90), not appendRootV2

appendRootV2 requires msg.sender == operatorMasterWallet(omni), the registry rejects EOA masters (MasterMustBeAccount), and a prod master is a Touch-ID passkey that cannot sign on a 2-minute timer — so the documented "operator master commits the root" path can never run autonomously. Instead the relay wraps each batch root in an honest AuditRootAnchor envelope (body: {merkle_root, op_kind_bitmap, entry_count, relay_address}) and commits that envelope's hash via the ungated appendV2(realOperatorOmni, relayActorOmni, 90, envelopeHash) — one legacy tx per batch, the REAL operator omni stays an indexed topic, zero contract change (the §15.3b open-enum design's whole point). Genuine anchors are distinguished from spam by tx.from == relay_address (published at GET /v1/audit/relay-info), exactly arch.md §15.3 tier A's trust model ("only shared service-relay-wallet" on chain). The master-gated appendRoot/appendRootV2 remains the sovereign tier-B/C route (heima-worker-smoke.sh still exercises it). Rejected alternatives (contract relay-allowlist = mainnet redeploy ceremony; software-P256Account relay = re-couples audit to the bundler #241 decoupled) are recorded in the plan doc.

What landed (all 10 plan steps)

  1. op_kinds 90 AuditRootAnchor + 91 AuditBatchFailed per the §15.3b ritual — crates/agentkeys-core/src/audit/{op_kind,bodies,mod}.rs, CBOR roundtrip tests, vector exporter rows, arch.md table rows (family 90-99 claimed).
  2. legacy_tx moved bundler → core (crates/agentkeys-core/src/legacy_tx.rs, bundler re-exports) — one EOA signer for both emitters.
  3. Anchor relaycrates/agentkeys-worker-audit/src/anchor.rs: chain-profile-driven config (AGENTKEYS_AUDIT_RELAY_KEY_FILE, RPC/contract env overrides for the isolated test stack), raw JSON-RPC (Heima mixHash-safe), retry ×3 exponential backoff, receipt polling; AGENTKEYS_AUDIT_BATCH_SECONDS (default 120, legacy …FLUSH_INTERVAL_SECS honored); degraded log-only boot when unconfigured (feat: #230 bundler decouple (re-land #238) + wallet/funding docs, rotation runbook & balance monitor #241 posture). Persistent failure re-queues the batch + emits AuditBatchFailed into store+queue+feed + ERROR log. Anti-spam gate: anchors only for operators with a registered on-chain master (TTL-cached operatorMasterWallet eth_call) — the open append/v2 endpoint can no longer make fake omnis burn relay gas; transient RPC failures re-queue, never drop.
  4. Tier-1 feed — per-actor ring buffers (1000), GET /v1/audit/stream SSE (operator/actor filter + backfill), GET /v1/audit/anchors/:operator (per-entry Merkle proofs), GET /v1/audit/relay-info. Feed shape has ONE owner: agentkeys_types::audit_feed::AuditFeedEvent (Shared broker/worker client crate — collapse the duplicated chain impls (drift fix) #203 rule). HTTP flush handlers spawn anchoring (a flush response never waits out a chain confirmation); the timer awaits inline.
  5. S3 cold archivecrates/agentkeys-worker-audit/src/archive.rs (env-gated AGENTKEYS_AUDIT_S3_BUCKET): async PUTs of feed events + envelope CBOR, boot-time ring restore (the "last 1000/actor survive restart" criterion), get_envelope cold fallback.
  6. Daemon bridgeui_bridge.rs: SSE client folds worker events into the existing ApiAuditEvent web feed (dedup by envelope hash, either delivery order), flips /v1/anchor/status to REAL on anchor events; reconnect w/ backoff, session-change aware. Plus worker/anchor chips in apps/parent-control (typed, filter row).
  7. Broker host deploysetup-broker-host.sh: relay key generated once (0600, preserved — rotation would orphan the funded account), worker-audit env block (cadence/chain/contract/bucket), SSE-safe nginx location (proxy_buffering off, 1 h read timeout).
  8. Cloud + chain entry-point wiringscripts/provision-audit-archive.sh (dedicated audit bucket per the per-data-class rule + instance-role inline grant, EIP-tag-resolved) ← setup-cloud.sh step 13; scripts/heima-fund-audit-relay.sh (reads relay-info, delegates to the idempotent funder) ← setup-heima.sh step 14; AUDIT_BUCKET in both env files + the CI materializer (the Config data class + lazy, config-driven memory list (Phases 1–5) #201 hard rule).
  9. Harnessheima-worker-smoke.sh Phase 1: Two-tier audit wiring (real-time off-chain feed + 2-min on-chain anchor) #109 legs: SSE backfill must carry the appended envelope; idempotent relay top-up; poll the anchor record ≤90 s; cast receipt confirms the tx; bash Merkle-proof walk verifies the genuine envelope AND proves a tampered one fails (the acceptance tamper test). Tolerated skips (relay-not-configured, anchor-not-recorded) keep degraded hosts green. Runbook updated in the same change.
  10. Docs — arch.md §15.3a "Two-tier audit is LIVE" section + table rows; docs/user-manual.md "Live audit feed + on-chain anchor badge".

Tests: 22 worker-audit unit (incl. full flush→gate→anchor→feed against a fake RPC node, spam-drop, outage-requeue, ring caps, proof verify/tamper) + 7 integration, 191 core, 93 daemon ui_bridge (SSE parser, mapper, dedup-either-order, real-socket pump→anchor-status). fmt/clippy clean; fixture-drift, env-mutation, contracts-sync, web-api-drift gates all pass.

What did NOT land

  • Live CI verification of the anchor loop DONE in this PR's CI run (Heima mainnet, test stack): SSE backfill ✓, anchor txs confirmed on-chain (e.g. 0x9f0d95c3…79bf27) ✓, genuine-envelope Merkle proof ✓, tampered-event proof FAILS ✓ — in both the stage-1 and stage-2 smoke runs. Two live-run fixes folded in: the anchor gate now gets the env-aware SidecarRegistry (the compiled-in prod registry dropped every test batch as "unregistered"), and the smoke's cast receipt status match accepts true|1|0x1. Prod verification remains the operator's post-merge redeploy (commands below).
  • subscan-essentials explorer renderers for 90/91 — external repo (subscan-essentials#12); until then they render via the Unknown(byte) fallback (invariant v0.1: TEE-side per-session read rate limit (abuse defense) #4), and the new export vectors are ready for it.
  • Stage-3 numbered step for the feed (plan step 9 deviation): the assertions live in heima-worker-smoke.sh (run by setup-heima step 14 + stage-2) instead of a new stage-3 step — avoids renumbering the 23-step demo; stage-3 11-12 already assert the receipt-fetch path.
  • Out-of-scope per the issue: real-time on-chain audit, audit replay UX (M4), cross-actor regulator views, per-vendor retention.

To test this

Surface Needed? Command
Remote broker host YES — audit worker binary + env + nginx + relay key bash scripts/setup-broker-host.sh --ref main on the broker host
Cloud (AWS/IAM) YES (one-shot) — audit bucket + instance-role grant bash scripts/setup-cloud.sh --only-step 13 (laptop, agentkeys-admin); add --ci for the test stack
Chain (funding only) YES (one-shot) — fund the relay EOA bash scripts/setup-heima.sh --only-step 14 (or bash scripts/heima-fund-audit-relay.sh)
Local daemon + web app YES (local rebuild) — feed bridge + chips rebuild via dev.sh; no broker dependency
Chain contracts No — no .sol touched, no VERSION bump, no redeploy

🤖 Generated with Claude Code

…nt pulls the vaulted LLM key via its granted scope

The #216 gap: cred-fetch-demo/cred-wire-demo prove the chain master-self
(operator==actor skips the scope check, #195) and nothing fetched from
INSIDE the sandbox as the agent. This closes it across three layers:

- sandbox-agent-isolation.sh: + the #216 cred half — positive
  (agentkeys cred fetch of the P.3-granted service, in-sandbox identity
  from ~/.agentkeys/harness-env) and the scope-denial negative (an
  un-granted probe MUST fail service_not_in_scope; any other outcome
  fails loud).
- phase1-wire-demo.sh: 1.4b stages ~/.agentkeys/harness-env (0600) in
  the sandbox — also fixes the bare-shell env contract the isolation
  script silently lacked (it pointed at the stale :8088 MCP default);
  1.4c uploads the proof script (absolute path + verified — the bare
  filename upload was rejected by the aiosandbox file API and curl
  exited 0 anyway); Phase 4.0 now fetches + plants the LLM key
  IN-SANDBOX as the agent (plaintext never leaves the sandbox; host-CLI
  fetch is the compat fallback, operator env stays the labelled
  DEV-only fallback).
- v2-stage3-demo.sh step 18: the #216 cred-side scope triad on the
  granted agent — cred-fetch cap for the granted service (200),
  un-granted probe (ServiceNotInScope), and the CI/mock-only live
  REVOKE transition (setScope drops the service → the same mint is
  denied → restore), enforcing the '#216 revoke cuts the agent off'
  acceptance in CI. Also fixes upload_sandbox_isolation_test, which
  silently no-op'd (relative upload path + unchecked API body).

Verified live: prod broker /v1/cap/cred-fetch layered errors; the 1.4b
staging command (0600, 11 keys), the new script + the exact Phase 4.0
one-liner against a real aiosandbox; the fixed stage-3 upload. The
chain-gated positives run in CI (software master + mock agent) and on
the operator's next v2-demo.sh run (Touch ID register + pairing).

Docs synced in the same change: harness/CLAUDE.md (inventory rows,
sandbox role) + docs/operator-runbook-harness.md (On Sandbox, phase
3/5 proofs, two new Q&A entries).
…e aiosandbox side on the runner

CI used to set --wire none, so the entire post-wire agent runtime (the
MCP server + agentkeys cred fetch + the hook path) ran nowhere headless
— stage-3 steps 11-12 cover raw worker curls, not the runtime the
sandbox agent actually uses.

- NEW harness/mock-wire-demo.sh (phase 5 under --ci / --wire mock):
  ensure the sanctioned mock agent + the canonical scope grant → mint
  operator + agent sessions headless (wallet_sig SIWE) → boot the REAL
  agentkeys-mcp-server on localhost (http backend + per-actor STS
  relay, the phase1 1.4 shape) → master-self vault a probe cred under
  the DEDICATED mock-wire-llm service (never openrouter — can't clobber
  a real vault entry) → run THE SAME sandbox-agent-isolation.sh the
  sandbox runs, with EXPECTED_CRED_SHA256: memory roundtrip through the
  MCP + the #216 authorized cred fetch (sha-exact) + the un-granted
  scope denial. Key custody stays operator-only by design.
- v2-demo.sh: --wire gains 'mock' (CI default; 'none' stays the
  explicit off), WIRE_RESULT=mocked in the summary.
- v2-stage3-demo.sh: ONE canonical mock-agent grant MOCK_SCOPE_SERVICES
  (openrouter + memory:ci-wire-proof + mock-wire-llm), used by
  ensure_mock_agent AND the step-18 revoke restore — phases 3 and 5 no
  longer flip-flop setScope every CI run (set-replace semantics).
- _lib.sh: + wallet_sig_mint_jwt (the shared headless SIWE session
  primitive; temp-file based — jq chokes on multi-line SIWE in vars).
- harness-ci.yml: comments/step name now say phases 1-6 with phase 5
  mocked (the workflow already passes --ci; behavior switches with the
  new default). The build step already builds agentkeys-mcp-server.

Verified locally: bash -n all, YAML parse, fixture gate green, the MCP
server boots with the exact arg shape (healthz ok), and
'v2-demo --stage 5 --wire mock' dispatches preflight → mock-wire-demo
→ fails LOUD at the no-master chain gate (this laptop's registry has
no master — correct; CI's software master is registered).

Docs synced: harness/CLAUDE.md (rule 5, CI role, inventory rows) +
operator-runbook-harness.md (On CI, flag table, phase-5 bullet, role
mapping).
…dentity fetch never could

The mock-wire CI proof (first headless run of the post-wire agent
runtime) caught a REAL #216 gap, not a harness bug: the cred worker
keys S3 strictly by cap.payload.actor_omni, so an agent-identity fetch
(actor=agent) read bots/<agent>/credentials/ — but the #216 flow vaults
the key MASTER-SELF into bots/<operator>/credentials/. 502 s3_get.
Every prior #216 proof was master-self (operator==actor → same prefix),
and phase1's old host fetch swallowed the failure (2>/dev/null → env
fallback), so 'the agent fetches from the master's vault' had never
actually worked end-to-end.

Fix (worker, fetch only): try the actor's OWN vault first (#228
agent-owned creds — self-stored entries shadow delegated ones), then,
for a DELEGATED cap (actor != operator), fall back to the OPERATOR's
vault. The envelope AAD is keyed by the vault OWNER (each vault's
objects were encrypted with aad(operator, owner, service, epoch) at
store time), so decrypt matches either source. No IAM change: the S3
read still runs under the caller-relayed STS — reading the operator
prefix requires the operator session the wire context already holds;
the (device-bound, isServiceInScope-verified) cap narrows WHICH service
that session releases. Store/teardown/list stay strictly actor-keyed.

Unit tests: fetch_vault_owners (master-self = one vault, byte-identical
prior behavior; delegated = actor-then-operator). arch.md synced
(credential_envelope row + the cred-fetch sequence) per the
architecture-as-source-of-truth policy.

CI self-verifies: crates/agentkeys-worker-*/** trips the paths-filter →
the test EC2 auto-redeploys → the harness (incl. phase-5 mock wire
step 6) runs against the fixed worker.
… agent file

heima-agent-create.sh read .agent_private_key and fed it straight to
`cast wallet sign`. When the file is a §10.2 sandbox-paired record
(key_custody=sandbox-only, agent_private_key:null — written by the wire
phase's in-sandbox device-session), AGENT_KEY became the literal 'null'
→ 'Error: Failed to decode private key' at stage-1 step 12. The wire
phase and stage-1 share the 'demo-agent' label, so any operator who runs
the wire then re-runs stage 1 hits this.

Now: validate the key shape (0x+64hex or bare 64hex); if unusable
(sandbox custody / corrupt / legacy), back up the file to
<label>.json.bak.<ts> and regenerate a fresh master-held wallet — i.e.
behave as a clean machine would. Non-destructive to §10.2: the sandbox
holds the authoritative key and phase-5 pairing rebuilds the master-side
record. Shape guard tested standalone under set -e (null/empty/short/
non-hex → regenerate; valid → reused, bare key 0x-normalized).

Runbook fold-back: two new Q&A entries (the decode error + the earlier
QR-picker-instead-of-Touch-ID passkey case).
…eth_chainId curl (transient-RPC hardening)

A transient RPC blip (LibreSSL SSL_ERROR_SYSCALL to rpc.heima-parachain)
made the inline `LIVE_CHAIN_ID=$(printf '%d' "$(curl … eth_chainId …)")`
resolve to 0, and `cast send --chain-id 0` was then rejected with
'error code -32603: invalid chain id' (hit at v2-stage1 step 14,
heima-credential-audit.sh). 13 heima-*.sh scripts shared this fragile
pattern — any of them could fail the same way on a blip.

All 13 now resolve the chain id from the OFFLINE pinned profile
(`echo "$PROFILE_JSON" | jq -r '(.chain_id // 0)'` — PROFILE_JSON is
already loaded in each) and fail loud if it isn't a positive integer, so
a network blip can never corrupt the --chain-id passed to cast. The live
eth_chainId is still cross-checked once at flow start (v2-stage1 step 5 /
heima-bring-up.sh), so wrong-RPC detection is unchanged.

Inline (not a _lib.sh helper) on purpose: the chain_id line runs early in
each script while _lib.sh is sourced later for resolve_master_key — a
helper call would be a call-before-definition bug in 9 of 13. Inline
needs only PROFILE_JSON + die, both present before the line in all 13
(verified). Functional-tested against the real heima profile (→ 212013)
+ the null/0/missing fail-loud paths; bash -n all 13. Runbook Q&A added.
…ct change)

The on-chain AgentKeysScope stores only keccak(service) HASHES, so an
agent can verify a known service name (isServiceInScope) but cannot
enumerate its authorized NAMES or learn which is its default LLM key
from chain. Putting names/a-default on-chain (a contract change) is
unnecessary: the master KNOWS the plaintext names + default at grant
time. Record them OFF-CHAIN; the on-chain hash gate still runs on every
fetch, so authorization is unchanged — this is discovery only.

- agentkeys-types: CredManifest { services, default_service } + resolve
  precedence (explicit > --select N (1-based) > master default) +
  load/save. 11 unit tests.
- agentkeys cred manifest --services a,b,c --default a  → the master
  records the authorized names + designated default (public names only).
- agentkeys cred list  → the agent's off-chain discovery (the chain
  can't enumerate); marks the default.
- agentkeys cred fetch  → service is now OPTIONAL: no arg uses the
  master-designated default (the #216 no-UI path), --select N picks from
  the list, an explicit service is used as-is (still on-chain verified).

Smoke-tested: manifest write → list (default marked) → no-arg fetch
resolves the default before the network call → --select 1 picks the
first → --select 9 clean range error. fmt + clippy clean; types 47 /
cli 19 tests green; backend fixture gate unaffected (not a wire shape).

Harness e2e proof + docs follow in the next commit.
…I path)

Wires the off-chain cred manifest through the agent runtime so the
no-UI default-key path is proven end-to-end:

- phase1-wire-demo.sh 1.4b': the master records the sandbox cred
  manifest (agentkeys cred manifest --services <granted creds>
  --default $SERVICE → ~/.agentkeys/cred-manifest.json) so the
  in-sandbox no-arg fetch resolves the designated default. Skips
  gracefully on an older sandbox binary.
- sandbox-agent-isolation.sh: + the #216 default-key proof — cred list
  (off-chain discovery the chain can't do) + a bare 'cred fetch' (no
  service) asserting it resolves the master default to the SAME secret
  as the explicit fetch. Conditional on a manifest (the explicit cred
  half still stands alone).
- mock-wire-demo.sh: writes a temp manifest + points the isolation run
  at it via AGENTKEYS_CRED_MANIFEST (never the runner's real
  ~/.agentkeys), so CI proves the default-key path headless too.

Docs (same change, keep-in-sync): arch.md (off-chain default-key =
discovery, on-chain = verification), user-manual.md (cred
manifest/list/default verbs), harness/CLAUDE.md (sandbox role + phase1
+ mock-wire rows), operator-runbook-harness.md (On Sandbox proof).

Detection greps verified against real CLI output; bash -n all three
scripts. The CLI core (types + verbs) is the prior commit.
…control-vs-secrets, off-chain spend caps

New docs/wiki/master-recovery-and-guardians.md (indexed in Home under
Foundations). Came out of the architecture Q&A: the recovery process
wasn't documented in the wiki.

Covers:
- M-of-N guardian social recovery is EXECUTED on-chain by
  P256Account.recover() (in-contract WebAuthn verify, threshold +
  dedup + replay-bound challenge, atomic signer rotation, permissionless
  submit) — the chain is the executor here, not just an audit log.
- Setup (addGuardian + recoveryThreshold) + the K11-gated ceremonies.
- The lost-device timeline (revoke/rotate → broker SSE cap-drop →
  daemon cache zero).
- Control vs. secrets boundary: recover() restores CONTROL on-chain
  instantly; decrypting EXISTING vaulted secrets needs the TEE to
  re-wrap the K3 KEK (on-chain-coordinated, TEE-executed).
- The dev escape hatch (resetMaster) vs. real guardian recovery.
- Why spend caps are enforced OFF-chain (per the user's point): spend
  is a HIGH-FREQUENCY hot path, so the limits are on-chain (policy) but
  the usage accumulator is off-chain (meter) — the inverse of recovery
  (rare + high-stakes → on-chain execution worth the gas). States the
  principle + the future on-chain-accumulator option gated on frequency.

All 15 cross-links verified to resolve; wiki lint clean (no frontmatter,
no H1). Defers to arch.md §11 for the canonical spec.
…, not 'init died early'

agentkeys-init-email-demo.sh fires `agentkeys init --email` in the
background and its poll loop treats ANY early exit as failure. But
cmd_init_with_force returns Ok(existing) IMMEDIATELY (exit 0, no email
sent) when a usable session already exists, printing 'Already
initialized as 0x… (run --force)'. So a valid existing session was
mis-reported as 'init died early (likely broker rejection)' and the
script polled S3 forever for an email that was never sent (v2-stage1
step 6).

The fast-fail block now reaps the exit code and, when init exited 0 with
'Already initialized' in its log, reuses the on-disk session and exits 0
— the demo's goal (a live session for this --session-id) is already met.
A real non-zero exit / broker rejection still dies as before.

Trigger: a session aged past do_step_6's 1-hour reuse window but still
within the broker TTL → do_step_6 re-inits → init short-circuits. Re-run
'bash harness/v2-demo.sh --from 1.6' (now green). Runbook Q&A added.
bash -n clean; grep verified against the exact CLI message.
… ring buffer + S3 archive + daemon bridge

Tier 2 (default-on, AGENTKEYS_AUDIT_BATCH_SECONDS=120): the audit worker
anchors each per-operator V2 batch autonomously — AuditRootAnchor (90)
envelope, hash committed via ungated CredentialAudit.appendV2 signed by
the relay EOA (appendRootV2's master gate is unreachable for a hosted
relay: MasterMustBeAccount + Touch-ID masters can't sign on a timer).
Retry x3 exp backoff; persistent failure re-queues the batch + emits
AuditBatchFailed (91). Zero contract change (open-enum §15.3b).

Tier 1: per-actor ring buffers (1000), GET /v1/audit/stream SSE with
backfill, GET /v1/audit/anchors/:op (per-entry Merkle proofs — the
tamper check), GET /v1/audit/relay-info; AuditFeedEvent shape owned by
agentkeys-types (#203 one-owner). S3 cold archive (env-gated) restores
rings on boot + backs get_envelope across restarts.

Daemon: worker-feed SSE bridge folds worker-side events into the
existing ApiAuditEvent web feed (dedup by envelope hash) and flips
/v1/anchor/status to REAL on anchor events.

Deploy: setup-broker-host.sh generates the relay key (0600, preserved)
+ rewrites worker-audit.env (cadence/chain/bucket) + SSE-safe nginx
location; provision-audit-archive.sh (bucket + instance-role grant)
wired into setup-cloud.sh step 13; heima-fund-audit-relay.sh wired into
setup-heima.sh step 14; AUDIT_BUCKET in both env files + CI
materializer. legacy_tx moved bundler→core (shared EOA signer).
… feed/anchor/tamper legs + UI chips

- Anti-spam gate: anchors submit only for operators with a registered
  on-chain master (eth_call operatorMasterWallet, TTL-cached 10min/60s);
  unregistered batches drop with a WARN (envelopes stay fetchable by
  hash), transient RPC failures re-queue. Without this the open append/v2
  endpoint lets fake operator omnis each burn one relay tx per tick.
- HTTP flush handlers SPAWN anchoring (response never waits out a chain
  confirmation — existing --max-time 10 callers keep working); response
  carries anchor_scheduled; consumers poll /v1/audit/anchors. The timer
  path still awaits anchors inline.
- heima-worker-smoke.sh: #109 legs — SSE backfill must carry the appended
  envelope; idempotent relay top-up before flush; poll the anchor record
  (<=90s), cast-receipt confirm the appendV2 tx, walk the Merkle proof in
  bash (genuine verifies, tampered leaf FAILS — the #109 tamper check).
  Tolerated skips: relay-not-configured / anchor-not-recorded.
- parent-control: 'worker' + 'anchor' ChipKinds (styles + filter row).
- operator-runbook-harness.md: smoke two-tier wiring documented.
- service/anchor tests: registered-operator end-to-end anchor (fake RPC),
  spam-omni drop, RPC-outage re-queue.
…gates

The #209 tripwire fired on this branch's run exactly as designed: the test
config worker became reachable (this PR's broker redeploy converged it) and
stage-3 steps 19 (config bucket/role write + cross-bucket denials) and 21
(cross-data-class cap -> 403 cap_data_class_mismatch) both ran LIVE and
passed. Per the guard's own instructions: drop config-role-missing +
config-worker-unreachable from the v2-demo --allow-skip and delete the
now-inert self-dissolving Guard step. The demo's skip reasons remain for
operator-local runs; CI now fails closed on config-worker drift.
…est stack

The worker-audit env block passed the env-aware CredentialAudit address but
not the registry, so the anti-spam gate fell back to the compiled-in profile
(PROD registry) — on the test stack operatorMasterWallet(test-omni) is zero
there and every batch dropped as 'unregistered' (the anchor-not-recorded
skip in the first green harness run; relay was funded, anchor_enabled=true).
Pass AGENTKEYS_AUDIT_REGISTRY_ADDRESS=$REGISTRY_ADDR like the other workers'
SIDECAR_REGISTRY_ADDRESS_HEIMA.
… cast version

The previous CI run PROVED the #109 anchor loop live (registry-gate fix
worked: the relay anchored within seconds and the poll found the record
first try) — the leg then died on its own assertion: 'status: true' did
not match the *1*-only pattern. Accept true|1|0x1; false/0x0 still dies.
…t-7e7a4b

# Conflicts:
#	.github/workflows/harness-ci.yml
#	crates/agentkeys-types/src/lib.rs
#	docs/operator-runbook-harness.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant