Skip to content

feat(guardian): recovery dispatcher (shadow) and the pending-rotation recheck exit - #800

Open
WiktorStarczewski wants to merge 12 commits into
wiktor/guardian-seam-c-attempt-ledgerfrom
wiktor/guardian-seam-d-dispatcher
Open

feat(guardian): recovery dispatcher (shadow) and the pending-rotation recheck exit#800
WiktorStarczewski wants to merge 12 commits into
wiktor/guardian-seam-c-attempt-ledgerfrom
wiktor/guardian-seam-d-dispatcher

Conversation

@WiktorStarczewski

Copy link
Copy Markdown
Collaborator

Guardian recovery gains its one trigger table — and the acknowledged no-owner wedge (a guardian switch submitted but never confirmed on chain) gets its exit. This closes the last of the four defect classes from #786's review: states every recovery mechanism declined at once, where rounds 17–20 each moved the wedge because no single predicate could prove the union covered the space.

  • back/guardian-recovery-dispatcher.tsclassifyGuardianRecovery(facts) → route, one total function over typed GuardianFacts with typed prompt reasons. The totality test enumerates the full 512-row fact product and asserts the invariant the class violated: a blocking status or pending rotation is never left without an exit, and a spent repair budget is never silent even when the unrepairable flag write was missed.
  • Shadow-first: the classifier observes; the sync loop builds facts per tick and counts disagreements with the legacy predicates (noteRecoveryDivergence, logged once per kind). Trigger relocation happens only after a release of divergence data reading ~zero.
  • The W1 exit is live from the start (it had no legacy owner): the sync loop rechecks each submitted-unconfirmed rotation against the node — readDirectSwitchCommitState (the throwing sibling of didDirectSwitchLand, so fuse evidence survives), bounded by a Seam-C ledger (15 reads on a flat 120s cadence), labeled at the sync ceiling, gated on and reporting into its own sync-fuse key per the Mobile: the app can freeze permanently while idle-syncing, apparently after RPC rate-limiting #777 discipline. committed → the row upgrades (title, chip, receipt copy all follow via the Seam-A verdict); discarded → the row honestly demotes to Failed and drift reconciliation repairs the endpoint on its next tick.
  • The durable intent is the Dexie row itself — it survives realm churn and vault locks and already carries the transaction id and target endpoint, so there is no separate marker store to drift from it (this also resolves the review's vault-vs-extension-storage debate by needing neither), and historical rows get the recheck automatically.
  • An exhausted recheck budget surfaces through the guardian screen via its own evidence set, deliberately NOT the operator-earned unrepairable flag: a healthy operator sync answers nothing about the chain, so it must not clear this prompt (the sibling-success-erases-the-evidence shape, F-137's rule — caught by this PR's own tests).
  • Both fences fired during development and their allowlists grew by exactly the two justified entries (facts assembly; the prompt-evidence set) — the fences work.

606 suites / 10,013 tests, ts, lint, i18n all green.

Part 4 of 4 of the seam plan from #786's review. Stacked on #799 (Seam C).

Reviewers: the recheck's settle rules are the part worth your time — a non-verdict (pending/not-found) charges the bounded budget, a failed read refunds it, and only a real chain answer clears it; wrong bookkeeping here either prompts a healthy rotation or waits forever on a dead one.

F-001 P0  recheck passed the Dexie row uuid to getTransactionCommitState, which
          matches the on-chain hash — it could only answer 'not-found', so the
          budget drained without ever looking and raised a permanent prompt
F-002 P1  a discarded rotation left the vault naming an operator with no
          on-chain authority; drift cannot see it, so the recheck reverts
F-003 P1  shadow dispatcher read two hardcoded budgets and no pendingRotation
F-004 P1  claim fence missed destructuring and destructured parameters
F-005 P1  ledger fence missed export const, var, Weak*, LHS annotations
F-006 P1  commit-state read had no post-sync hold re-check
F-007 P1  backfillGuardianEndpoints bypassed the accounts write queue
F-008 P1  dispatcher totality test was true of any total function
F-010 P1  ledger charged against a captured count, losing concurrent charges
F-014 P2  a missing vault record reported 'no-onchain-guardian', a claim about
          the chain it had established nothing about
F-018 P2  the 429 log re-derived its own clamp beside the one imposed
…ewer binding

F-032 P0  rollback force-wrote a ≤30min-stale endpoint over whatever was bound;
          now a guarded RevertGuardianEndpoint action, conditional on the account
          still naming the discarded target and on the guardianEpoch CAS
F-033 P1  rollback ran after the demote, the point of no return; vault first now
F-034 P1  one exhausted pending row condemned its siblings — exhaustion is keyed
          per row id, not per account
F-035 P1  a late refund reopened a budget another handle had closed; closure is
          a monotonic flag, not a counter value
F-036 P1  a WASM eviction in drift was swallowed and the pass took more locks
F-037 P2  an explicitly-undefined patch field erased a bound endpoint/baseline
F-038 P2  a failed advisory status stamp reported the landed binding as failed
…nces

F-031 P1  finalize preflight never re-checked its WASM hold after the sync
F-032 P1  asPreflight rewrapped WasmClientPoisonedError, blinding every caller
F-033 P1  three arms swallowed an eviction and kept taking locks
F-034 P1  URL-equality rollback undid a successful retry to the same operator
F-035 P2  claim fence rewritten on the AST; regex missed most destructuring
F-036 P2  ledger fence rewritten on the AST, glob-discovered, default-deny
F-037 P2  shadow classifier read a pass-start snapshot two writers had moved
F-038 P2  pending-rotation listing no longer retains the whole history
F-040 P2  exhausted-rotation prompt now retracts once the row stops pending
…ndles

F-041 P1  claim fence parsed every file as TSX, unfencing five modules
F-042 P1  discarded rotation with no rollback target was demoted anyway
F-043 P1  a rollback stuck on 'stale' retried forever and never surfaced
F-044 P1  an eviction from the rollback landed in an unclassified catch
F-045 P1  four holds read a borrowed Account handle after a parking await
F-046 P2  rollback probe used the 20s user timeout on the 3s tick
F-047 P2  a missing vault record answered 'superseded', spending the demote
F-048 P2  two shadow facts asked per account where the subject is narrower
F-049 P2  five JSDoc blocks bound to the wrong declaration
F-060 P1  five eviction-seam tests; every guard was deletable with the suite green
F-061 P1  AttemptLedger.write dropped `endpoint`, so anySpentForAccount read high forever
F-063 P1  legacy discarded row kept pending so the manual-recovery prompt stays up
F-064 P1  rollback probe back to the 20s timeout — 5s condemned slow-but-healthy operators
F-062 P2  claim fence misparsed .ts as TSX in the allowlist-minimality check
F-065 P2  cap pending-rotation node syncs per pass
F-066 P2  shadow classifier prefers an exhausted row over a merely unsettled one
F-067 P2  ledger fence: object-literal and numeric-init budget bypasses closed
F-068 P2  a watchdog eviction from the rollback now books against the fuse
F-069 P2  drift test mocked a probe its subject never calls
…to the frontend

F-071 P0  two discarded rotations could strand the account on the middle operator
F-072 P0  a cold-re-register eviction was charged to the operator and zeroed the fuse
F-073 P1  the intercom dropped the error class, so poison could not be classified
F-074 P1  the eviction breaks jumped past the only fuse report on their key
F-075 P1  a guardian round-trip eviction did not stop the pass
F-081 P0  the adopt arm swallowed poison, so the pass classified the 401 and zeroed the count
F-082 P0  drift ran unfused and broke the loop, starving every account behind it
F-083 P1  a realm-error trap was booked as evidence the node had parked us
F-084 P1  the internal envelope dropped `reason`, so the watchdog test was always false
F-085 P1  the missing-registration snapshot threw past its own "evicted" contract
F-086 P1  a per-row fuse note erased what the rest of the recheck had accumulated
F-087 P1  an eviction before the /configure was charged to the operator's budget
F-088 P1  the new eviction paths had no tests
F-089 P2  the closed-budget test could not fail
F-090 P2  three presence-test spellings and .tsx walked through the fences
F-091 P2  an unreachable arm implied this line fed the eviction count
F-092 P2  the register preflight took the five-minute backstop from a 3s loop
F-093 P2  a borrowed account handle was read after its hold released
F-096 P2  an empty patch spent an epoch and turned concurrent repairs stale
F-097 P3  one abandoned attempt retired the hardening check for the session
F-098 P3  the bindingChanged comment justified something the code does not do
…bsence

F-111 P0  a poisoned hardening call was swallowed, and success booked over the eviction
F-102 P1  the legacy object envelope lost `name` and `reason`, so both classifiers read false
F-103 P1  an eviction before the /configure was still charged to the operator's budget
F-104 P1  guardian-manager read a borrowed account handle after its hold released
F-105 P1  the cold service build read signer details with no hold at all
F-107 P1  the register preflight derived its payload on a handle it never re-checked
F-108 P1  one shared recheck fuse key let a healthy sibling erase a parked account's evidence
F-116 P1  seven of the above reverted cleanly with the whole suite still green
F-117 P1  the poison break sat behind the recording gate, so a retired pass kept borrowing
F-106 P2  passMayRecord checked the generation only on entry, never after its awaits
F-112 P2  a realm-error poison fell through all three arms and booked nothing
F-114 P2  a 3s-cadence service build took the five-minute backstop
F-118 P2  MultisigService.init took an unbounded, unlabelled lock
F-115 P3  the recheck's outermost catch reported the eviction and recorded none of it
…first probe

F-116 P1  the first mutation pass left five reversions green; these fail under each

M1 the register preflight's post-account-read hold re-check
M2 guardian-manager's re-check between the account and signer reads
M3 onBeforeRegister firing on the POST side, and not at all when evicted
M5 the poison re-throw out of ensureGuardianProcedureThresholds
M6 the recheck outer catch's fuse booking
…g fences

F-121 P0  runSync stage-2 swallowed an eviction and reported it as a canonicalization failure
F-122 P1  reRegisterCurrentStateOnGuardian held the client unbounded and unlabelled on the sync cadence
F-123 P1  the failure arm's stale-pass continue withheld the evidence the fuse needs to light
F-124 P1  service coalescing shared one init across both lock ceilings, defeating boundAtSyncCeiling
F-125 P1  the 429 cooldown test advanced Date.now while the code read performance.now
F-126 P1  the post-account-read hold guard was deletable with the suite green
F-127 P1  the post-sync hold guard was deletable with the suite green
F-128 P2  a non-poison hardening failure aborted a guardian sync that had already succeeded
F-129 P2  the empty-rows exit left fuse evidence for a subject that no longer exists
F-130 P2  syncGeneration only ever moved in test code, so the retirement check was inert
F-131 P2  a stale attempt-ledger handle could refund or close the incarnation that replaced it
F-133 P2  the claim fence read through parentheses but not through type assertions
F-134 P2  the ledger fence missed arrays, static class fields, and signed-literal counters
F-135 P3  two guardian log prefixes out of forty were spelled differently
F-136 P3  two of this round's fixes landed without their reasoning reachable from the code
F-138 P0  sync coalescing cleared its handle by generation, so a retired pass
          wedged guardian sync on a promise nobody settles
F-139 P1  sync fuse had no way to report a probe that never reached the node
F-140 P1  a trap eviction was booked as evidence against the endpoint
F-141 P1  pending-rotation recheck never booked an abandoned pass
F-142 P1  the recheck's outer catch booked nothing for a local failure
F-143 P1  a lit fuse blocked local prompt retirement, not just node work
F-144 P1  registration budget was charged for a parked node read, disabling the
          only automatic repair an unregistered account has
F-145 P1  didDirectSwitchLand folded an eviction into "no verdict"
F-146 P1  the commit wait swallowed poison and carried on
F-147 P1  a borrowed account handle was dereferenced past its hold, for a log line
F-148 P1  claim fence failed open for hasOwnProperty.call/.apply
F-149 P1  claim fence failed open for destructuring assignments
F-150 P1  ledger fence failed open for nested stores and nested writes
F-151 P1  drift rollback read the raw endpoint field, condemning legacy-global
          accounts to the unrepairable prompt
F-152 P2  self-heal suite leaked the module-scoped fuse between tests
F-153 P2  charge-timing comment contradicted itself after F-144
F-154 P2  fuse-key comment claimed a shared key where it is per-account
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