Fence each driver run so a timed-out driver cannot act, and trust its last stage #66

Merged
xicv merged 17 commits from feat/per-run-driver-fence into main 2026-09-23 10:33:46 +00:00
xicv commented 2026-09-23 05:01:01 +00:00 (Migrated from github.com)

Summary

Stacked on #65. A timed-out driver can no longer act, and its last stage becomes evidence.

Verified live on 2026-09-23 (Ego Lite 0.5.1.11):

  • a driver script keeps running inside Ego Lite's service after the adapter's timeout kills its client;

  • its output is not streamed, so a killed driver's stage never reached the adapter;

  • within one broker generation, only the lease fenced it.

  • Per-run fence (src/driver-run-fence.mjs, src/ego-adapter.mjs).

    • The adapter creates a private fence file for every driver run in a sibling of the mailbox (${mailbox}-fences, 0700, owner and symlink checked). A sibling keeps the 0.2.37 mailbox scanner untouched.
    • On every give-up path it revokes the fence first, then reads the driver's stage record, then kills.
    • Timeouts carry { runFenceRevoked, driverStage, sendClickStarted } only when revocation really happened and the record is complete.
    • Startup removes only dead brokers' fence files.
  • Stage before every mutation (src/ego-driver-source.mjs).

    • Each fenced wrapper writes { stage, phase, sendClickStarted } atomically before its authority check, and refuses the mutation if the write fails or the fence is gone.
    • Task-space reclaim now goes through a wrapper too.
    • An ESLint audit rule fails on any raw mutation global or DOM-mutating script outside the wrappers.
    • A read-only audit keeps the account scan's page code to plain GETs.
  • Proven pre-send (src/broker.mjs).

    • provenPreSendDriverFailure accepts a fenced pre-click stage whose click never started.
    • The exchange retries such a timeout automatically: the next attempt clears its own exact draft.
    • The streak is bounded by driverTimeoutRetryLimit (default 3). The third timeout in a row ends as an interrupted run that keeps its fenced evidence, so reconcile can prove absence at once.
    • A fenced create-once interruption can be reconciled in the same broker generation, without a restart.
  • Receipts. The fence and the account scan are now in RECEIPT_RELEVANT_RUNTIME_PATHS, because their evidence decides signed PROVEN_NOT_DISPATCHED receipts. A new test keeps every adapter-loaded module attested.

Browser contract 29, runtime generation 2026-09-23.6.

Verification

  • npm test 1100 tests, 1099 pass, 1 skipped; lint clean; receipt suite 16/16; cargo test 33 + 1.
  • Live on a scratch path: Ego Lite's script runtime creates the 0700 directory (owner matches) and writes, renames, checks and deletes the fence and stage files.
  • No test touches the real driver mailbox.

Not yet done

Not merged, released or installed: held until the operator's go. To be confirmed live after install: a genuinely timed-out driver is refused at its next mutation, and a fenced pre-click timeout retries and clears its own draft in the real composer.

## Summary Stacked on #65. A timed-out driver can no longer act, and its last stage becomes evidence. Verified live on 2026-09-23 (Ego Lite 0.5.1.11): - a driver script keeps running inside Ego Lite's service after the adapter's timeout kills its client; - its output is not streamed, so a killed driver's stage never reached the adapter; - within one broker generation, only the lease fenced it. - **Per-run fence** (`src/driver-run-fence.mjs`, `src/ego-adapter.mjs`). - The adapter creates a private fence file for every driver run in a sibling of the mailbox (`${mailbox}-fences`, 0700, owner and symlink checked). A sibling keeps the 0.2.37 mailbox scanner untouched. - On every give-up path it revokes the fence first, then reads the driver's stage record, then kills. - Timeouts carry `{ runFenceRevoked, driverStage, sendClickStarted }` only when revocation really happened and the record is complete. - Startup removes only dead brokers' fence files. - **Stage before every mutation** (`src/ego-driver-source.mjs`). - Each fenced wrapper writes `{ stage, phase, sendClickStarted }` atomically before its authority check, and refuses the mutation if the write fails or the fence is gone. - Task-space reclaim now goes through a wrapper too. - An ESLint audit rule fails on any raw mutation global or DOM-mutating script outside the wrappers. - A read-only audit keeps the account scan's page code to plain GETs. - **Proven pre-send** (`src/broker.mjs`). - `provenPreSendDriverFailure` accepts a fenced pre-click stage whose click never started. - The exchange retries such a timeout automatically: the next attempt clears its own exact draft. - The streak is bounded by `driverTimeoutRetryLimit` (default 3). The third timeout in a row ends as an interrupted run that keeps its fenced evidence, so reconcile can prove absence at once. - A fenced create-once interruption can be reconciled in the same broker generation, without a restart. - **Receipts.** The fence and the account scan are now in `RECEIPT_RELEVANT_RUNTIME_PATHS`, because their evidence decides signed `PROVEN_NOT_DISPATCHED` receipts. A new test keeps every adapter-loaded module attested. Browser contract 29, runtime generation 2026-09-23.6. ## Verification - `npm test` 1100 tests, 1099 pass, 1 skipped; lint clean; receipt suite 16/16; `cargo test` 33 + 1. - Live on a scratch path: Ego Lite's script runtime creates the 0700 directory (owner matches) and writes, renames, checks and deletes the fence and stage files. - No test touches the real driver mailbox. ## Not yet done Not merged, released or installed: held until the operator's go. To be confirmed live after install: a genuinely timed-out driver is refused at its next mutation, and a fenced pre-click timeout retries and clears its own draft in the real composer.
Sign in to join this conversation.
No description provided.