@evident-ai/runner-synchroniser 3.1.1-dev.997b038 → 3.2.0

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 (3) hide show
  1. package/README.md +73 -2
  2. package/dist/cli.js +567 -209
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -23,7 +23,7 @@ bundle directly (`node cli.js <command>`).
23
23
  ## Install
24
24
 
25
25
  The package is published to npm as `@evident-ai/runner-synchroniser`, exposing one
26
- `runner-synchroniser` binary and requiring Node >= 22:
26
+ `runner-synchroniser` binary and requiring Node >= 22.13.0:
27
27
 
28
28
  ```bash
29
29
  npx @evident-ai/runner-synchroniser@dev env
@@ -63,6 +63,7 @@ version at all.
63
63
  | `model-auth-ready` | — | `0` = ready, `10` = not ready; other = tool broken |
64
64
  | `self-stop` | — | `0` = stopped, `20` = keep the task; other = tool broken |
65
65
  | `session-db-classify <litestream-restore-exit-code> <attempt> [--on-unusable-replica=<prune\|leave\|clear\|crash>]` | — | `0` = restored/no-replica/disabled, `32` = re-run and ask again, `31` = unusable (booted fresh, starts a fresh backup chain), `30` = fatal; other = usage/tool broken |
66
+ | `session-db-verify <litestream-config-path>` | — | `0` = healthy/skipped/walked-back, `33` = every retained restore point was exhausted (booted fresh, starts a fresh backup chain); other = usage/tool broken |
66
67
 
67
68
  `self-stop` scales this agent's own ECS service to `desiredCount=0` on a clean idle exit
68
69
  (see Configuration for `CLUSTER`/`SERVICE`/`EVIDENT_SELFSTOP_ROLE_ARN`). It exits `0`
@@ -220,11 +221,79 @@ at boot the newest L0 is normally *absent* (`noL0Present`) or freshly written an
220
221
  (`targetHealthy`) — `prune`'s real-world reach is narrower than the code alone suggests,
221
222
  and it can never repair corruption at a higher compaction level.
222
223
 
223
- `EVIDENT_ON_UNUSABLE_REPLICA` is a `packages/runner-image` (entrypoint) variable translated into the flag above at boot — this CLI never reads it, so it has no row in this README's Configuration table below; see [runner-image's README](../runner-image/README.md#when-the-replica-is-unusable-evident_on_unusable_replica). An unrecognised value (including wrong casing, e.g. `Prune`) exits `2`, outside `entrypoint.sh`'s `0|10|20|30|31|32` allow-list — a typo **crash-loops the task on attempt 1** rather than falling back to `prune`, and also logs the misleading "credential persistence is DEGRADED" ERROR.
224
+ `EVIDENT_ON_UNUSABLE_REPLICA` is a `packages/runner-image` (entrypoint) variable translated into the flag above at boot — this CLI never reads it, so it has no row in this README's Configuration table below; see [runner-image's README](../runner-image/README.md#when-the-replica-is-unusable-evident_on_unusable_replica). An unrecognised value (including wrong casing, e.g. `Prune`) exits `2`, outside `entrypoint.sh`'s `0|10|20|30|31|32|33` allow-list — a typo **crash-loops the task on attempt 1** rather than falling back to `prune`, and also logs the misleading "credential persistence is DEGRADED" ERROR.
224
225
 
225
226
  See `specs/local-runner.feature`'s "Recovering session history at startup" scenarios for
226
227
  the behavioural anchor (31 comes online, 30 does not).
227
228
 
229
+ ### `session-db-verify`
230
+
231
+ The 8th command: it runs *after* the restore loop above has settled and *before*
232
+ `opencode` opens the DB. The restore loop only proves litestream could write a file;
233
+ this command is what proves that file is actually intact (#1345 — a leaf-page-corrupt
234
+ but openable `opencode.db` was re-restored unchanged every boot, forever, because
235
+ nothing had ever run a real `PRAGMA integrity_check`).
236
+
237
+ #### Positional
238
+
239
+ | Positional | Meaning |
240
+ | --- | --- |
241
+ | `<litestream-config-path>` | Path to the `litestream.yml` this CLI itself generated (`litestream-config`); used to enumerate and restore from retained points. |
242
+
243
+ #### What it does
244
+
245
+ 1. `PRAGMA integrity_check` the local `opencode.db`. Healthy → done, `0`.
246
+ 2. On failure, enumerate litestream's retained restore points (`litestream ltx`) and
247
+ walk them back from newest to oldest (excluding the one that just failed), restoring
248
+ each into a scratch path and integrity-checking it there. The first one that passes
249
+ is adopted — renamed over `opencode.db`, replacing it and its stale sidecars.
250
+ 3. If nothing retained passes (or there's nothing to try, or the search runs out of
251
+ time/points), the corrupt local DB is discarded and the boot proceeds with a
252
+ genuinely fresh one — exactly `session-db-classify`'s `31` guarantee, so
253
+ `litestream replicate` starting against it afterwards is just as safe.
254
+
255
+ A walked-back boot keeps replicating into the **same** replica — no S3 mutation, no new
256
+ prefix. litestream re-bases to the replica's high-water mark and continues the txid
257
+ chain from there; the pre-walkback lineage stays independently restorable at its old
258
+ txids. (Measured end-to-end against a `file` replica with a tiny DB; not measured
259
+ against S3 at the ~1 GB scale a production replica can reach.)
260
+
261
+ #### Outcome → exit code
262
+
263
+ | Outcome | Code | When |
264
+ | --- | --- | --- |
265
+ | `skipped` | `0` | local DB absent or zero bytes — nothing to verify |
266
+ | `healthy` | `0` | the local DB passed its integrity check as-is |
267
+ | `walkedBack` | `0` | the local DB failed, but an older retained restore point passed and was adopted |
268
+ | `exhausted` (`noCandidates`) | `33` | the local DB failed and litestream retains no older restore point |
269
+ | `exhausted` (`allFailed`) | `33` | every retained restore point was tried and every one failed |
270
+ | `exhausted` (`budgetExhausted`) | `33` | the search stopped on `EVIDENT_SESSION_DB_WALKBACK_MAX_POINTS`/`_BUDGET_SECONDS` before trying every retained point |
271
+ | `exhausted` (`enumerationFailed`) | `33` | `litestream ltx` could not be read (bad exit, non-JSON, malformed listing) |
272
+
273
+ `healthy`/`walkedBack`/`skipped` all share exit `0` deliberately: the shell's behaviour
274
+ afterwards — start `litestream replicate` — is identical either way, so a further split
275
+ would be a distinction with no consumer. `noCandidates`/`allFailed` are kept distinct
276
+ from `budgetExhausted` for the same reason `unusableReplica`'s reasons are kept distinct
277
+ above: conflating "we looked and nothing was good" with "we ran out of time/points
278
+ before looking at everything" would report the wrong conclusion to the operator.
279
+
280
+ #### Boot markers
281
+
282
+ | Marker | Emitted from | When it fires | Fields |
283
+ | --- | --- | --- | --- |
284
+ | `WARNING: SESSION-DB-INTEGRITY-FAILED` | `verifySessionDb` (`session-db-verify.ts`) | The local DB fails its check, before the walkback starts | — (prose carries the PRAGMA's own detail) |
285
+ | `WARNING: SESSION-DB-WALKBACK-ENUMERATION-FAILED` | the walkback loop (`session-db-verify.ts`) | `litestream ltx` exited non-zero (prose carries its stderr — missing binary, bad `-config`, S3 error) or answered with a listing that could not be parsed | — (prose carries litestream's own stderr) |
286
+ | `WARNING: SESSION-DB-WALKBACK-CANDIDATE-FAILED` | the walkback loop (`session-db-verify.ts`) | A candidate restore point fails to restore, or restores but also fails its own check | `txid` |
287
+ | `INFO: SESSION-DB-WALKBACK-ADOPTED` | `adopt` (`session-db-verify.ts`) | A candidate passes and is adopted over `opencode.db` | `txid`, `candidates`, `bytes` |
288
+ | `SESSION-DB-INTEGRITY` | `describeSessionDbVerification` (`diagnostics.ts`), logged once per invocation as the final outcome | The local DB passed its check as-is | `bytes` |
289
+ | `SESSION-DB-INTEGRITY-WALKBACK` | `describeSessionDbVerification` (`diagnostics.ts`), logged once per invocation as the final outcome | Same event as `SESSION-DB-WALKBACK-ADOPTED` above, restated as the command's outcome | `txid`, `candidates`, `bytes` |
290
+ | `SESSION-DB-INTEGRITY-EXHAUSTED` | `describeSessionDbVerification` (`diagnostics.ts`), logged once per invocation as the final outcome | Nothing retained passed (any `exhausted` reason); the boot proceeds with a fresh DB | `candidatesTried` (in prose, not `k=v`) |
291
+
292
+ The first four fire only while the walkback loop runs; the last three are always the
293
+ one message this command logs as its own final word on the outcome — `entrypoint.sh`
294
+ reuses `SESSION-DB-INTEGRITY-EXHAUSTED` verbatim when it logs its own `33` handling, the
295
+ same way it already reuses `SESSION-DB-REPLICA-UNUSABLE` for `session-db-classify`'s `31`.
296
+
228
297
  ### Why the contract is asymmetric
229
298
 
230
299
  `restore` and `sync-once` **never** report a domain outcome through the exit code. A
@@ -276,6 +345,8 @@ key, so they never appear in `env`'s output or `litestream.yml`.
276
345
  | `LITESTREAM_BUCKET` / `LITESTREAM_PREFIX` | Object-store bucket and key prefix. | Persistence off (needs **both**): `restore`/`sync-once` no-op, `env` warns. |
277
346
  | `AWS_REGION` | Region for the AWS clients (S3, and the ECS/STS calls `self-stop` makes). | Left to the SDK's own resolution. |
278
347
  | `CREDS_SYNC_INTERVAL` | Seconds between sync ticks, reported by `env` for the caller's loop. | Falls back to `60`; also on unparseable or non-positive values. |
348
+ | `EVIDENT_SESSION_DB_WALKBACK_MAX_POINTS` | Distinct restore points `session-db-verify`'s walkback will try before giving up. | Falls back to `10`; also on unparseable or non-positive values. |
349
+ | `EVIDENT_SESSION_DB_WALKBACK_BUDGET_SECONDS` | Wall-clock budget, in seconds, for the whole walkback loop. | Falls back to `180`; also on unparseable or non-positive values. |
279
350
  | `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` | Presence alone counts as configured model auth (see `model-auth-ready`). | No API-key fallback; `model-auth-ready` then depends solely on the credential files. |
280
351
  | `CLUSTER` † / `SERVICE` † | ECS cluster/service `self-stop` scales to `desiredCount=0`. | Warns "cannot self-stop" and exits `20` (keep the task). Ignored by every other command. |
281
352
  | `EVIDENT_SELFSTOP_ROLE_ARN` † | Role `self-stop` assumes for its ECS calls. | Optional: falls back to the task role's own credentials. A failed/incomplete assume-role → `20`. |