instar 1.3.613 → 1.3.615
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +58 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +11 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/AccountFollowMeRevocationStore.d.ts +34 -0
- package/dist/core/AccountFollowMeRevocationStore.d.ts.map +1 -0
- package/dist/core/AccountFollowMeRevocationStore.js +76 -0
- package/dist/core/AccountFollowMeRevocationStore.js.map +1 -0
- package/dist/core/EnrollmentWizard.d.ts +48 -0
- package/dist/core/EnrollmentWizard.d.ts.map +1 -1
- package/dist/core/EnrollmentWizard.js +66 -7
- package/dist/core/EnrollmentWizard.js.map +1 -1
- package/dist/core/FrameworkLoginDriver.d.ts +7 -0
- package/dist/core/FrameworkLoginDriver.d.ts.map +1 -1
- package/dist/core/FrameworkLoginDriver.js +12 -3
- package/dist/core/FrameworkLoginDriver.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +9 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +30 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/accountFollowMeCooperativeWipe.d.ts +66 -0
- package/dist/core/accountFollowMeCooperativeWipe.d.ts.map +1 -0
- package/dist/core/accountFollowMeCooperativeWipe.js +179 -0
- package/dist/core/accountFollowMeCooperativeWipe.js.map +1 -0
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +6 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +72 -1
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/upgrades/1.3.614.md +22 -0
- package/upgrades/1.3.615.md +35 -0
- package/upgrades/side-effects/ws52-account-follow-me-r6b-reliability.md +66 -0
- package/upgrades/side-effects/ws52-account-follow-me-revocation-wiring.md +104 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Side-Effects Review — WS5.2 R6b: Phase-C headless-enrollment reliability
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `ws52-account-follow-me-r6b-reliability`
|
|
4
|
+
**Date:** `2026-06-17`
|
|
5
|
+
**Author:** `Echo (instar-dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `pending (high-risk: enrollment/credential path — EnrollmentWizard.start failure handling + the follow-me enroll-start route)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
WS5.2 R6b — the Phase-C headless-enrollment reliability contract, layered onto the existing (#1214) secure enroll-start route WITHOUT weakening its mandate-gate/authoritative-email guarantees. Three parts: (1) `FrameworkLoginDriver.drive()` accepts an optional per-call `scrapeTimeoutMs` override (non-finite/≤0 ignored → falls back to the constructor default); a new config knob `multiMachine.accountFollowMe.remoteScrapeTimeoutMs` (default 180000ms / 3min) is threaded into the FOLLOW-ME enroll path only — normal local enrollment keeps the 60s default. (2) The HONEST FAILURE SURFACE: `EnrollmentWizard.start()` now wraps `driveLogin` in try/catch and re-raises a drive failure as a typed `EnrollmentDriveError` (with `code`/`operatorMessage`/`cause`); because `start()` calls `driveLogin` BEFORE `store.issue()`, a drive failure never leaves a stuck pending-login. Both `start()` callers map `EnrollmentDriveError` → an honest, retryable 502 (the generic `/subscription-pool/enroll` route and the follow-me enroll-start route) instead of a bare 500. (3) The two-code Claude case: the follow-me/remote path prefers the device-code single-code flow where the provider supports it (`remoteKind()` → device-code for OpenAI/Codex; Anthropic has no device-code endpoint so it stays `url-code-paste`), and for the no-single-code provider the larger remote timeout covers a late-arriving verification URL (instar only scrapes the URL; the second code is operator→CLI, never scraped). Files: `FrameworkLoginDriver.ts`, `EnrollmentWizard.ts`, `ConfigDefaults.ts`, `routes.ts` (502 mapping + enhancements on #1214's route — the duplicate insecure route an earlier stale-base build added was removed; #1214's secure route is authoritative), + 4 test files.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `EnrollmentWizard.start()` drive-failure handling — **modify** — a drive failure becomes a typed error a caller can render honestly, never a silent stuck pending-login.
|
|
15
|
+
- Follow-me enroll-start route 502 mapping + remote timeout/device-code — **modify** (on #1214's route) — enrollment reliability; the mandate gate + authoritative email (the authorization decisions) are UNCHANGED.
|
|
16
|
+
- `remoteScrapeTimeoutMs` config knob — **add** — larger cloud scrape budget, follow-me path only.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Over-block
|
|
21
|
+
|
|
22
|
+
None. The larger timeout only ever makes the remote path WAIT LONGER before failing — it never rejects a legitimate enrollment that the old 60s would have accepted. The honest-failure path turns a previously-opaque drive throw into a clear 502 retry signal; it does not reject anything that previously succeeded. Local enrollment is byte-for-byte unchanged (the knob is read only on the follow-me path).
|
|
23
|
+
|
|
24
|
+
## 2. Under-block
|
|
25
|
+
|
|
26
|
+
The reliability layer does not add authorization — that remains #1214's mandate gate (unchanged). A drive that SUCCEEDS but mints the wrong account is still caught downstream by S7 at `/complete` (email validation) — R6b does not touch that. The honest-failure surface reports a START failure; it does not itself verify the eventual login outcome (by design — the completion gate owns that).
|
|
27
|
+
|
|
28
|
+
## 3. Level-of-abstraction fit
|
|
29
|
+
|
|
30
|
+
Correct layers: the timeout knob lives on the driver (which owns the scrape), the failure-typing lives on `EnrollmentWizard.start` (which owns the drive call), and the HTTP mapping lives on the routes (which own the operator-facing surface). The kind-preference (device-code) extends the existing `defaultKind` logic rather than re-implementing flow selection. No logic was duplicated; the earlier stale-base duplicate route was removed in favor of #1214's secure one.
|
|
31
|
+
|
|
32
|
+
## 4. Signal vs authority compliance
|
|
33
|
+
|
|
34
|
+
R6b adds NO new authority — it is purely reliability + honest reporting. The only authority on this path (the mandate gate) is #1214's and is untouched. `EnrollmentDriveError` is a signal (a typed failure the caller renders); it never blocks an authorized action — it reports that a drive could not START, with a retry hint. Deterministic (a timeout or a thrown driver error), not heuristic.
|
|
35
|
+
|
|
36
|
+
## 5. Interactions
|
|
37
|
+
|
|
38
|
+
The change composes with #1214's enroll-start route (enhances it) and the S7 `/complete` gate (unchanged downstream). The `start()`-before-`issue()` ordering is the load-bearing invariant for "no stuck pending-login on drive failure" — preserved. Both `start()` callers were updated to map the typed error (no caller left throwing an opaque 500). The background `reissueExpired` sweep already swallowed driver errors by design and was deliberately NOT changed (it is not the start path). No double-fire, no shadowing.
|
|
39
|
+
|
|
40
|
+
## 6. External surfaces
|
|
41
|
+
|
|
42
|
+
One new config knob (`remoteScrapeTimeoutMs`, additive, defaulted). The enroll-start + generic enroll routes now return 502 (retryable) on a drive failure instead of 500 — a more honest status for the same failure class; both remain dark behind `multiMachine.accountFollowMe` (the follow-me route) / unchanged-gating (the generic route). No new route. No cross-machine surface.
|
|
43
|
+
|
|
44
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
45
|
+
|
|
46
|
+
The reliability layer is per-machine by nature: the larger timeout + device-code preference apply to the enrollment running ON the target machine (the one minting its own login). The config knob is read locally. Nothing replicates. This directly serves the multi-machine case (a remote/slower target gets a realistic login budget + an honest failure message instead of a silent 60s timeout), which is exactly the Phase-C scenario R6b targets.
|
|
47
|
+
|
|
48
|
+
## 8. Rollback cost
|
|
49
|
+
|
|
50
|
+
Low. Dark behind `multiMachine.accountFollowMe` for the follow-me path; the generic-route 502 mapping is a status-code refinement (revert is trivial). The config knob is additive (absence → the driver's existing 60s default). No migration, no persisted state. Single-commit back-out.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Second-pass review
|
|
55
|
+
|
|
56
|
+
**Concur with the review.** Independent audit (the reconciliation was verified to NOT weaken #1214):
|
|
57
|
+
|
|
58
|
+
1. **(CRITICAL) Mandate authorization intact** — exactly one `/subscription-pool/follow-me/enroll/start` route; the mandate gate (`ctx.coordination.gate.evaluate({action:'account-follow-me',...})` → 403 on `decision !== 'allow'`) and `resolveFollowMeEnrollTarget` (409 fail-closed, body email never used) are fully preserved. R6b only inserted `remote:true` + `remoteScrapeTimeoutMs` INSIDE the already-gated block.
|
|
59
|
+
2. **No stuck pending-login** — `driveLogin` runs before `store.issue()`; a throw is caught + re-raised as `EnrollmentDriveError` before any persistence; tests assert `pending()` empty after a drive throw.
|
|
60
|
+
3. **Local enrollment unchanged** — the larger budget + `remoteKind` apply only when `remote:true`; the generic route keeps the 60s default.
|
|
61
|
+
4. **Both start() callers handle EnrollmentDriveError** → 502 retryable; none unhandled.
|
|
62
|
+
5. **Config knob** — `remoteScrapeTimeoutMs` default 180000, read on the follow-me path with a `Number.isFinite` guard.
|
|
63
|
+
6. **Device-code preference** — `remoteKind` returns device-code only for openai; Claude stays `url-code-paste` (no unsupported flow forced).
|
|
64
|
+
7. **No fail-open** — invalid timeout falls back to the safe default (tested); deny-by-default gate unchanged.
|
|
65
|
+
|
|
66
|
+
`tsc --noEmit` EXIT=0; 60/60 tests pass.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Side-Effects Review — WS5.2 Account Follow-Me, R12 revocation data-plane WIRING
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `ws52-account-follow-me-revocation-wiring`
|
|
4
|
+
**Date:** `2026-06-17`
|
|
5
|
+
**Author:** Echo (autonomous)
|
|
6
|
+
**Second-pass reviewer:** pending (high-risk: credentials, destructive local wipe, revocation honesty)
|
|
7
|
+
**Spec:** `docs/specs/ws52-account-follow-me-security.md` — R12 (revocation), OQ6 (per-server model), gap 9 (offline give-up deadline)
|
|
8
|
+
**Status:** wiring built + tested (46 tests across unit/wiring/Tier-2; tsc clean). This PR makes the already-merged PURE executor (`AccountFollowMeRevocation`) FUNCTIONAL by constructing it in the server with real deps, firing it from `/mandate/:id/revoke`, scheduling its deadline sweep, and adding the config default + migration.
|
|
9
|
+
|
|
10
|
+
## Summary of the change
|
|
11
|
+
|
|
12
|
+
The pure executor (`src/core/AccountFollowMeRevocation.ts`) was merged but nothing called it. This PR is the server-shell wiring, implemented per the spec's PER-SERVER model (OQ6): the operator revokes the `account-follow-me` mandate on the TARGET machine's OWN dashboard, so the target runs its OWN local revocation — LOCAL, never a cross-machine wipe-instruction.
|
|
13
|
+
|
|
14
|
+
Files added (logic + tests, dark behind `multiMachine.accountFollowMe`):
|
|
15
|
+
- `src/core/accountFollowMeCooperativeWipe.ts` — `buildCooperativeWipe(deps)` builds the REAL synchronous three-step local wipe (framework logout against the account's `CLAUDE_CONFIG_DIR` + delete the per-account config-home dir via SafeFsExecutor + `SubscriptionPool.remove`), fail-closed per step. Side-effecting primitives (logout, slot delete) are injected seams so it is unit-testable without spawning a CLI.
|
|
16
|
+
- `src/core/AccountFollowMeRevocationStore.ts` — `DurablePendingWipeStore`, a crash-safe JSON ledger under the state dir (SafeFsExecutor atomic write) implementing the executor's `PendingWipeStore` seam. NOT in-memory.
|
|
17
|
+
- `tests/unit/account-followme-revocation-wiring.test.ts` (10) — cooperative-wipe + durable store + composed end-to-end.
|
|
18
|
+
- `tests/unit/account-followme-revocation-server-wiring.test.ts` (17) — source-touchpoint integrity + migration unit.
|
|
19
|
+
- `tests/integration/account-followme-revocation-route.test.ts` (4) — Tier-2 route trigger + dark no-op.
|
|
20
|
+
|
|
21
|
+
Files modified (shared/sensitive — flagged for careful review):
|
|
22
|
+
- `src/commands/server.ts` — construct `AccountFollowMeRevocation` with real deps; schedule the deadline sweep timer (unref'd); thread into `AgentServer`.
|
|
23
|
+
- `src/server/routes.ts` — RouteContext member; `/mandate/:id/revoke` detects an `account-follow-me` authority and fires `revoke(...)` with `cooperative-online` posture; surfaces the honest outcome on the response.
|
|
24
|
+
- `src/server/AgentServer.ts` — options member + RouteContext threading.
|
|
25
|
+
- `src/config/ConfigDefaults.ts` — `multiMachine.accountFollowMe.revocationReconnectDeadlineMs` default (6h).
|
|
26
|
+
- `src/core/PostUpdateMigrator.ts` — `migrateConfigAccountFollowMeRevocationDeadline` (existence-checked, idempotent) + dispatch.
|
|
27
|
+
|
|
28
|
+
## Decision-point inventory
|
|
29
|
+
|
|
30
|
+
- `/mandate/:id/revoke` route — **modified** — after the existing PIN-gated control-plane revoke succeeds, if the revoked mandate carried an `account-follow-me` authority, fire the LOCAL data-plane wipe. Non-account-follow-me mandates are byte-for-byte unaffected.
|
|
31
|
+
- `buildCooperativeWipe(...)` — **add** — the real wipe; deny-by-default for "done" (every step fail-closed to false on throw).
|
|
32
|
+
- deadline sweep timer (server.ts) — **add** — drives `sweepDeadlines()` on a 5-min cadence; strict no-op while dark.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 1. Over-block
|
|
37
|
+
|
|
38
|
+
Not a block/allow gate. The closest surface is the cooperative-wipe fail-closed path: a partial/throwing wipe is reported `revocation-pending` (never `removed`), which can later escalate to `revocation-failed` ("rotate at provider") for a wipe that partially succeeded. SAFE direction — the worst outcome is the operator is told to rotate when they technically needn't have. We never claim `removed` we can't fully confirm.
|
|
39
|
+
|
|
40
|
+
## 2. Under-block
|
|
41
|
+
|
|
42
|
+
The per-server trigger only runs the LOCAL wipe (`cooperative-online` posture) — by design (OQ6). A de-paired/offline holder is reached on the cross-machine path, which the per-server model does not use; this is the honest scope. The remaining real-world gap is operator inaction after a `revocation-failed` item — outside any code's reach, which is why the item is HIGH priority.
|
|
43
|
+
|
|
44
|
+
## 3. Level-of-abstraction fit
|
|
45
|
+
|
|
46
|
+
Correct layer. The route is the trigger point (mirrors how PR2's scan route / EnrollmentWizard were wired); the wipe + durable store are injectable-deps modules (mirrors the executor's own style). The executor owns NO authority — the control-plane revoke stays with the PIN-gated MandateGate; this wiring consumes that decision and runs the side effects. `SubscriptionPool.remove` and the framework logout are reused, not re-implemented.
|
|
47
|
+
|
|
48
|
+
## 4. Signal vs authority compliance
|
|
49
|
+
|
|
50
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
51
|
+
|
|
52
|
+
- [x] No NEW block/allow authority. The wiring runs the data-plane effect of a decision the existing PIN-gated MandateGate already made; the only gate honored is the executor's own `enabled()` dark-flag (no-op when off). The route's existing `checkMandatePin` authority is unchanged.
|
|
53
|
+
|
|
54
|
+
Every uncertain path FAILS CLOSED toward "rotate at provider", never toward a false "removed". The trigger is wrapped so a data-plane error can never break the control-plane revoke that already succeeded.
|
|
55
|
+
|
|
56
|
+
## 5. Interactions
|
|
57
|
+
|
|
58
|
+
- **Shadowing:** none — first consumer of the executor.
|
|
59
|
+
- **Double-fire:** the durable store is keyed `${accountId}::${targetMachineId}`; a re-revoke or reconnect+sweep race cannot double-escalate the same pair (sweep removes on escalation; success removes on `removed`). Re-running `revoke` for the same pair upserts, never duplicates.
|
|
60
|
+
- **Races:** reconnect vs sweep — whichever runs first wins, both orderings honest (covered by the executor's merged tests). The new durable store preserves the in-memory upsert/remove semantics.
|
|
61
|
+
- **Feedback loops:** none — the sweep only removes records + emits attention items.
|
|
62
|
+
|
|
63
|
+
## 6. External surfaces
|
|
64
|
+
|
|
65
|
+
- **Other agents / mesh:** none — per the per-server model this trigger has NO mesh egress. It runs the framework logout + slot delete on the LOCAL target only.
|
|
66
|
+
- **Persistent state:** a new durable pending-wipe ledger (`account-follow-me-revocation-pending.json` under the state dir). Records are small and credential-free (account id, machine id, mandate id, provider name, operator email, nickname, two timestamps). PII note: operator email + machine nickname land at-rest, same posture as the §6.1a meta projection (email "never a secret"); no credential field exists in the shape. **Destructive local effect:** the wipe deletes the per-account `CLAUDE_CONFIG_DIR` (via SafeFsExecutor — audited, source-tree-guarded) and the keychain credential entry. This is the intended revocation effect and runs ONLY for a revoked account-follow-me mandate while the feature is enabled.
|
|
67
|
+
- **Operator surface (Mobile-Complete):** the control-plane revoke is the existing PIN-gated Mandates-tab control (already phone-complete). The new operator-facing output is the HIGH `RevocationFailedAttention` item via the existing attention-queue path. No new operator UI primitive; no API-only operator action.
|
|
68
|
+
- **External systems:** the honest end-state points the operator at the provider; it calls no provider API.
|
|
69
|
+
|
|
70
|
+
## 6b. Operator-surface quality
|
|
71
|
+
|
|
72
|
+
No operator-surface markup file (`dashboard/*.js`, `*.html`, approval/grant/secret-drop form) is touched by THIS PR. The dashboard `revocation-pending` / `revocation-failed` render is a tracked follow-on increment and will carry its own 6b review. Not applicable to this commit.
|
|
73
|
+
|
|
74
|
+
## 7. Multi-machine posture
|
|
75
|
+
|
|
76
|
+
Per-server: each machine revokes its OWN local account when its operator revokes the mandate on its OWN dashboard. Single-machine / flag-off is a strict no-op — the route trigger does nothing for a non-follow-me mandate and the sweep returns []. No cross-machine transport is introduced (and none was required — the per-server model is cleanly local). See "Could NOT wire" below for what the per-server model deliberately does NOT cover.
|
|
77
|
+
|
|
78
|
+
## 8. Rollback cost
|
|
79
|
+
|
|
80
|
+
Low. The feature is dark behind `multiMachine.accountFollowMe` (live-on-dev / dark-fleet). Disabling the flag makes `revoke()` a no-op and the sweep inert immediately (read live, no restart). The durable ledger is a single small JSON file; deleting it loses only pending-wipe bookkeeping (honest end-state then defaults to provider-rotation). The route trigger is additive and wrapped — reverting it restores the prior revoke behavior exactly. No schema migration beyond an existence-checked config default.
|
|
81
|
+
|
|
82
|
+
## What could NOT be fully wired (honest)
|
|
83
|
+
|
|
84
|
+
- **Offline / de-paired / hostile posture from the route trigger.** In the per-server model the operator revokes on the TARGET's own dashboard, so the target is online & cooperative by construction; the route always passes `cooperative-online`. The executor's `offline` / `revoked` branches (durable pending, provider-rotation instruction) are fully built and unit-tested but are reached by the CROSS-MACHINE revoke path, which does not exist (and is not part of the per-server model). They remain dormant-but-correct — a future cross-machine revoke increment would supply the computed posture. This is the clean-local boundary the prompt called for: I wired what IS local and did not stub the cross-machine path.
|
|
85
|
+
- **`onTargetReconnect` trigger.** The reconnect-fires-pending-wipe hook is built + tested in the executor but has no caller in the per-server model (a reconnecting machine revokes its own local account on its own dashboard, not via a peer's pending record). Left unwired deliberately — it belongs to the cross-machine increment.
|
|
86
|
+
- **Dashboard `revocation-pending` / `revocation-failed` render** — tracked follow-on (6b above).
|
|
87
|
+
|
|
88
|
+
## Second-pass review
|
|
89
|
+
|
|
90
|
+
**Independent reviewer (2026-06-17) — found ONE BLOCKING issue; FIXED + re-reviewed → CONCUR.**
|
|
91
|
+
|
|
92
|
+
The reviewer confirmed: `removed` is reachable only after a fully-successful local wipe; the trigger fires only for an `account-follow-me` mandate and a wipe error never breaks the already-succeeded control-plane revoke; dark = strict no-op (`enabled()` read live, not cached); the durable store holds no token material; the sweep timer is gated + unref'd + try/caught; the migration is idempotent + existence-checked.
|
|
93
|
+
|
|
94
|
+
**BLOCKING finding (now fixed):** `defaultDeleteSlot` did a `recursive,force` rm of the operator-provided `configHome` with NO guard — and `~/.claude` (the operator's PRIMARY/shared login) is a legitimate `configHome` value, so an `account-follow-me` revoke targeting the default account would have recursively deleted the operator's main login directory (worst case: `$HOME`/root). **Fix:** added an exported `isProtectedConfigHome()` guard that refuses (→ `slotDeleted:false` → durable pending, never a false `removed`) any deletion target resolving to a filesystem root, `$HOME` itself, an ANCESTOR of `$HOME`, or a framework DEFAULT config home (`~/.claude`/`.codex`/`.gemini`/`.pi`/`.config`), or an empty/whitespace path (which would otherwise `path.resolve('')`→cwd). The guard runs BEFORE any `safeRmSync`, is case-folded (macOS/Windows case-insensitive volumes: `~/.CLAUDE` ≡ `~/.claude`), and traversal-safe (`~/.claude/../.claude`, trailing slashes collapse to the canonical protected path). A re-review of the fix found NO bypass across an adversarial spelling battery and CONCURRED. A genuine per-account slot (`~/.claude-adriana`, `~/.instar/accounts/<id>`, tmpdir paths) is NOT matched and deletes normally. Covered by 3 new both-sides tests in `account-followme-revocation-wiring.test.ts`.
|
|
95
|
+
|
|
96
|
+
## Evidence pointers
|
|
97
|
+
|
|
98
|
+
- `npx tsc --noEmit` clean.
|
|
99
|
+
- 46 tests green: `account-followme-revocation-wiring` (10), `account-followme-revocation-server-wiring` (17), `account-followme-revocation-route` (4 — Tier-2 + dark no-op), plus the merged executor's 15. The existing `ws52-account-follow-me-wiring` (12) + `mandate-routes` (15) still pass (no regression).
|
|
100
|
+
- Tier-2 live-pipeline evidence: revoking an account-follow-me mandate over the real route returns `accountFollowMeRevocation.state === 'removed'` AND `pool.get('acct-x') === null` (the real data-plane effect); a non-follow-me revoke carries no revocation payload and leaves the account intact; flag-off returns `reason: 'feature-disabled'` with the account untouched.
|
|
101
|
+
|
|
102
|
+
## CI-green follow-up (same PR)
|
|
103
|
+
|
|
104
|
+
The `cooperativeWipe` fail-closed catches are tagged `@silent-fallback-ok` (each surfaces its failure through the function's return value → the executor keeps a durable pending, never a false `removed`), keeping the `no-silent-fallbacks` ratchet at baseline 476. The `revocationReconnectDeadlineMs` ConfigDefaults insertion shifted the `lint-dev-agent-dark-gate` hand-authored golden map by +4 lines for every `enabled:` entry below it (7 entries); the map was updated by hand to match. No behavior change — comment tags + a test line-number map.
|