instar 1.3.1311 → 1.3.1313

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.
Files changed (31) hide show
  1. package/dist/data/standards-guard-index.json +1 -1
  2. package/dist/data/standards-guard-index.meta.json +2 -2
  3. package/dist/data/standards-registry.meta.json +1 -1
  4. package/dist/feedback-factory/drain/FeedbackDrainService.d.ts.map +1 -1
  5. package/dist/feedback-factory/drain/FeedbackDrainService.js +7 -3
  6. package/dist/feedback-factory/drain/FeedbackDrainService.js.map +1 -1
  7. package/dist/feedback-factory/drain/FeedbackDrainStore.d.ts +27 -1
  8. package/dist/feedback-factory/drain/FeedbackDrainStore.d.ts.map +1 -1
  9. package/dist/feedback-factory/drain/FeedbackDrainStore.js +90 -32
  10. package/dist/feedback-factory/drain/FeedbackDrainStore.js.map +1 -1
  11. package/dist/feedback-factory/store/FeedbackSourceGenerations.d.ts.map +1 -1
  12. package/dist/feedback-factory/store/FeedbackSourceGenerations.js +4 -0
  13. package/dist/feedback-factory/store/FeedbackSourceGenerations.js.map +1 -1
  14. package/dist/feedback-factory/store/JsonlFeedbackStore.d.ts.map +1 -1
  15. package/dist/feedback-factory/store/JsonlFeedbackStore.js +8 -1
  16. package/dist/feedback-factory/store/JsonlFeedbackStore.js.map +1 -1
  17. package/dist/server/AgentServer.d.ts.map +1 -1
  18. package/dist/server/AgentServer.js +3 -1
  19. package/dist/server/AgentServer.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/data/builtin-manifest.json +3 -3
  22. package/src/data/standards-guard-index.json +1 -1
  23. package/src/data/standards-guard-index.meta.json +2 -2
  24. package/src/data/standards-registry.meta.json +1 -1
  25. package/src/scaffold/templates/jobs/instar/feedback-factory-process.md +113 -20
  26. package/upgrades/1.3.1312.md +25 -0
  27. package/upgrades/1.3.1313.md +65 -0
  28. package/upgrades/feedback-drain-end-to-end.eli16.md +42 -0
  29. package/upgrades/feedback-drain-epoch-meta.eli16.md +30 -0
  30. package/upgrades/side-effects/feedback-drain-end-to-end.md +245 -0
  31. package/upgrades/side-effects/feedback-drain-epoch-meta.md +94 -0
@@ -0,0 +1,94 @@
1
+ # Side-Effects Review — feedback drain: the recorded owner epoch follows the live owner epoch
2
+
3
+ **Version / slug:** `feedback-drain-epoch-meta`
4
+ **Date:** `2026-10-01`
5
+ **Author:** `Echo`
6
+ **Second-pass reviewer:** `independent reviewer subagent (see below)`
7
+
8
+ ## Summary of the change
9
+
10
+ Live defect on the Mac Studio, found during `feedback-readiness-demotion` (#2108). The drain DB's `drain_meta.owner_authority_epoch` is `1` while the drain runs at coordinator lease epoch 22677+. The value was written once on 2026-08-28, when the drain ran in single-machine mode at `localOwnerEpoch = 1` (the only checkpoint manifest, `feedback-drain-checkpoint.json`, records epoch 1). The coordinator was enabled later. Every later write compared the recorded epoch for exact equality and never advanced it. Result: the hourly `checkpointForBackup` throws `backup owner authority epoch is stale` (no fresh backup checkpoint since August, so the spec's RPO ≤ 1 hour is not met), and `pruneOperationalHistory` throws `owner authority epoch is stale` after every non-degraded run. A consumer `claimNext` would throw the same way once the consumer goes live.
11
+
12
+ Two fixes:
13
+
14
+ 1. `FeedbackDrainStore.ts`: `assertOrInitializeOwnerAuthorityEpoch` becomes `advanceOwnerAuthorityEpoch`. It is used by `claimNext`, `pruneOperationalHistory` and `checkpointForBackup`. A newer live epoch moves the recorded epoch forward and writes an `owner-epoch` audit row (`from → to`, reason `owner-epoch-advance`). An equal epoch is a no-op. An older epoch is refused as stale, exactly as before. Invalid epochs (< 1, non-integer) are refused as before.
15
+ 2. `AgentServer.ts`: in single-machine mode `localOwnerEpoch` started at 1 on every boot. After a finalized restore (epoch bumped to N+1) a restart presented 1 again, which the store refuses as stale (and which the old exact check also refused). It now starts from the store's recorded epoch (`store.ownerAuthorityEpoch() ?? 1`).
16
+
17
+ Existing stores self-heal on the next fenced write (the next run's prune, or the next hourly checkpoint). No migration and no operator action are needed.
18
+
19
+ ## Decision-point inventory
20
+
21
+ - Owner-epoch fence on store writes (`claimNext`, `pruneOperationalHistory`, `checkpointForBackup`) — modify — exact match → monotonic (advance on newer, refuse older).
22
+ - Single-machine owner epoch at boot (`AgentServer`) — modify — constant 1 → recorded epoch.
23
+ - `assertCurrentClaim` (settle with token + epoch) — unchanged: still an exact match against the recorded epoch, so advancing the epoch supersedes every older claim.
24
+ - `finalizeRestore` and `restorePending` — unchanged.
25
+
26
+ ---
27
+
28
+ ## 1. Over-block
29
+
30
+ The old code over-blocked every legitimate epoch increase: a coordinator lease re-acquire, the switch from single-machine to coordinator mode, and a single-machine restart after a restore. That is the live outage. After this change the only refused epochs are older than the recorded one, which is what "stale writer" means in the spec (monotonically increasing `authorityEpoch`; stale owner epochs cannot mutate).
31
+
32
+ ## 2. Under-block
33
+
34
+ - A writer at a NEWER epoch is now admitted where the exact check refused it. Who can present an epoch at all is unchanged: the service only runs on the canonical owner (`isCanonicalOwner` = configured operated host + coordinator lease, and no pending restore), and nonowners proxy. In coordinator mode the epoch is the coordinator's monotonic lease epoch, so a newer epoch is by construction the current lease holder, and every claim from the older epoch can no longer settle (`assertCurrentClaim` compares against the recorded epoch).
35
+ - Restore detection does not depend on the epoch fence. `restorePending()` runs at boot, compares the DB file identity (inode + birth time) against the checkpoint manifest, and blocks the writer (`isCanonicalOwner` false) until the PIN-gated `finalizeRestore`. A restored file that does not match its checkpoint throws. The integration test restores both the healed snapshot (detected, finalized to 22678) and the pre-heal epoch-1 bytes under the newer manifest (refused).
36
+ - Residual gap: a DB file swapped in with NO checkpoint manifest present is not detected by `restorePending`. In coordinator mode the old exact check refused claim/prune/checkpoint on such a file when its recorded epoch was older; the new code advances it (old claims are still fenced out, but `drain_owner_continuity` is not reset). In single-machine mode the old check caught nothing (the epoch was always 1). The spec's restore procedure always carries the manifest, so this path is outside the spec; it is named here rather than closed.
37
+ - A coordinator whose lease epoch went backward (lease state reset) is now refused as stale until its epoch passes the recorded one. That fails closed, as the spec's monotonic epoch requires.
38
+
39
+ ## 3. Level-of-abstraction fit
40
+
41
+ The store owns durable epoch state and already fenced on it; the advance sits in the same helper. The server only seeds its local epoch from the store, the same place it already resets it after a restore.
42
+
43
+ ## 4. Signal vs authority compliance
44
+
45
+ Deterministic fence logic over integers. No judgment, no model.
46
+
47
+ ## 4b. Judgment-point check
48
+
49
+ No new heuristic at a competing-signals point.
50
+
51
+ ## 5. Interactions
52
+
53
+ - `drain_owner_continuity` (#2108 readiness tenure) is keyed on the live owner epoch and run history, not on `owner_authority_epoch`. Unaffected.
54
+ - `finalizeRestore` requires the coordinator epoch to equal `restored + 1`. Checkpoints now record the live lease epoch, so a coordinator-mode restore needs the lease at the checkpoint's epoch + 1. This constraint already existed; it simply could not be reached before because no checkpoint could be written.
55
+ - One audit row per epoch advance, i.e. per coordinator lease re-acquire (live: 22677 → 22679 across 2026-09-30/10-01). Audit rows are pruned at 400 days. Once the consumer is live, a re-acquire mid-claim strands that claim until the lease reconciler requeues it; that was already true (the service's `assertOwner` aborts on an epoch change).
56
+ - Follow-up, not in this change: `finalizeFailoverRestore` in coordinator mode requires the live lease epoch to equal the checkpoint epoch + 1. Checkpoints now record the live lease epoch, which keeps climbing, so a coordinator-mode restore would usually stay restore-pending until that rule is relaxed. The rule predates this change (it was unreachable because no checkpoint could be written). Single-machine restore is unaffected and covered by the e2e test.
57
+ - `simulateClaims` runs on an in-memory copy and is unaffected.
58
+
59
+ ## 6. External surfaces
60
+
61
+ None changed. `/feedback-factory/drain/status` and the dashboard stop reporting the post-run prune failure; the hourly checkpoint warning in the server log stops.
62
+
63
+ ## 6b. Operator-surface quality
64
+
65
+ No operator surface changed.
66
+
67
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
68
+
69
+ Machine-local by design: the drain DB lives only on the operated host. In a mesh the epoch comes from the coordinator lease, which is monotonic across machines, so a takeover at a higher epoch advances the record and fences out the older owner. In single-machine mode the epoch now survives restarts.
70
+
71
+ ## 8. Rollback cost
72
+
73
+ A code revert. On the old code a healed store (recorded epoch now equal to the live lease epoch) keeps working until the next lease re-acquire, then fails as before. No data is rewritten.
74
+
75
+ ## Conclusion
76
+
77
+ Root cause: the recorded owner epoch was write-once and exact-matched, so it could never follow the coordinator lease. It now moves forward with the live owner and still refuses anything older. Backups and post-run pruning resume on the next run with no operator action.
78
+
79
+ ## Second-pass review (if required)
80
+
81
+ **Reviewer:** independent reviewer subagent (general-purpose, read-only)
82
+ **Independent read of the artifact:** concur, with wording corrections
83
+
84
+ The reviewer found no path where a stale writer, a foreign machine, or a restored DB can mutate the drain against the spec: every epoch-writing caller is behind `isCanonicalOwner()` (which includes `!restorePending`), `finalizeRestore` and `assertCurrentClaim` stay exact, and `LeaseCoordinator.getLeaseEpoch()` is monotonic (max of own and peer-observed epochs). Corrections applied: §2 now states what the old check caught in coordinator mode for a manifest-less DB swap; §5 gives the measured re-acquire rate; the coordinator-mode restore successor rule is logged as a follow-up.
85
+
86
+ ## Evidence pointers
87
+
88
+ - Live store (read-only): `drain_meta.owner_authority_epoch = 1`; `drain_runs` at owner epoch 22677; checkpoint manifest `ownerAuthorityEpoch: 1`, created 2026-08-28T01:40:46Z.
89
+ - Replay on a copy of the live DB (`sqlite3 .backup`), same calls the live drain makes, host `m_03b30f…`: origin/main throws on `checkpointForBackup(22677)` and `pruneOperationalHistory(22677)`; this change heals 1 → 22677, prunes, refuses 22676 as stale, advances to 22679 on a re-acquire, integrity ok, `restorePending` false.
90
+ - Tests: `tests/unit/feedback-drain-store.test.ts` (live shape 1 → 22677, superseded claim, stale refusal, audit row), `tests/integration/feedback-drain-backup-restore.test.ts` (heal then restore detected; pre-heal bytes refused), `tests/e2e/feedback-factory-drain-lifecycle.test.ts` (restart after a restore bump keeps epoch 2; fails on the pre-fix `AgentServer`).
91
+
92
+ ## Class-Closure Declaration (display-only mirror)
93
+
94
+ Runtime code defect, not an agent-authored artifact. Class: a durable fence value that is write-once while its source advances. The sibling sites of the same value (`claimNext`, `pruneOperationalHistory`, `checkpointForBackup`, the single-machine boot epoch) are all closed in this change; `assertCurrentClaim` and `finalizeRestore` intentionally stay exact.