@evident-ai/runner-synchroniser 3.4.1-dev.aa71e9a → 3.4.1-dev.b9f71fa
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/README.md +47 -28
- package/dist/cli.js +33862 -34653
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -56,11 +56,11 @@ build time.
|
|
|
56
56
|
| `env` | resolved config as shell-eval'able vars | `0` = ran; non-zero = tool broken |
|
|
57
57
|
| `litestream-config` | the generated `litestream.yml` | `0` = ran; non-zero = tool broken |
|
|
58
58
|
| `restore <claude\|opencode>` | — | `0` = ran; non-zero = tool broken |
|
|
59
|
-
| `sync-once <claude\|opencode>` | — | `0` =
|
|
59
|
+
| `sync-once <claude\|opencode>` | — | `0` = uploaded/unchanged/absent/disabled; `40` = not persisted; other = tool broken |
|
|
60
60
|
| `model-auth-ready` | — | `0` = ready, `10` = not ready; other = tool broken |
|
|
61
61
|
| `self-stop` | — | `0` = stopped, `20` = keep the task; other = tool broken |
|
|
62
|
-
| `session-db-classify <litestream-restore-exit-code> <attempt> [--on-unusable-replica=<prune\|leave\|clear\|crash>] [--recovery-occurred] [--fresh-db-fallback]` | — | `0` = restored/no-replica/disabled, `32` = re-run and ask again, `31` = unusable (booted fresh,
|
|
63
|
-
| `session-db-verify <litestream-config-path>` | — | `0` = healthy/skipped/walked-back, `33` =
|
|
62
|
+
| `session-db-classify <litestream-restore-exit-code> <attempt> [--on-unusable-replica=<prune\|leave\|clear\|crash>] [--recovery-occurred] [--fresh-db-fallback]` | — | `0` = restored/no-replica/disabled, `32` = re-run and ask again, `31` = unusable (booted fresh, replicates into the existing prefix), `30` = fatal; other = usage/tool broken |
|
|
63
|
+
| `session-db-verify <litestream-config-path>` | — | `0` = healthy/skipped/walked-back, `33` = exhausted after verified separation and local disposal, `34` = separation or disposal could not be proven; other = usage/tool broken |
|
|
64
64
|
|
|
65
65
|
`self-stop` scales this agent's own ECS service to `desiredCount=0` on a clean idle exit
|
|
66
66
|
(see Configuration for `CLUSTER`/`SERVICE`/`EVIDENT_SELFSTOP_ROLE_ARN`). It exits `0`
|
|
@@ -75,7 +75,7 @@ saved query.
|
|
|
75
75
|
|
|
76
76
|
### `session-db-classify`
|
|
77
77
|
|
|
78
|
-
Implementation-facing reference for the 7th command: it decides what a just-run `litestream restore` of `opencode.db` means and, on attempt 2 only, may run a recovery strategy against S3. `
|
|
78
|
+
Implementation-facing reference for the 7th command: it decides what a just-run `litestream restore` of `opencode.db` means and, on attempt 2 only, may run a recovery strategy against S3. `runner/docker-images/fargate/README.md`'s [Strategy/What/Cost table](../docker-images/fargate/README.md#when-the-replica-is-unusable-evident_on_unusable_replica) is the operator-facing view of the same command — this section doesn't restate it.
|
|
79
79
|
|
|
80
80
|
#### Activity recovery report
|
|
81
81
|
|
|
@@ -86,6 +86,13 @@ a warning `restore_retried` record before the retry's result is known. Writing i
|
|
|
86
86
|
and never changes this command's exit code. The CLI reader owns this record contract: add a new
|
|
87
87
|
`v` rather than repurposing a field.
|
|
88
88
|
|
|
89
|
+
MicroVM boot-shell give-ups use that same JSONL contract and path. The hook truncates the
|
|
90
|
+
report at the start of each `/run`, so a later startup decision supersedes an earlier one;
|
|
91
|
+
`/resume` preserves the current report. Their `replication_suspended` field is `true`, meaning
|
|
92
|
+
that start is not backing up its new session history. The normal mapper defaults the field to
|
|
93
|
+
`false`; the MicroVM `--fresh-db-fallback` outcome is also `true` because it cannot safely
|
|
94
|
+
replicate that boot.
|
|
95
|
+
|
|
89
96
|
#### Positionals
|
|
90
97
|
|
|
91
98
|
| Positional | Meaning |
|
|
@@ -119,6 +126,9 @@ marks a runtime that intentionally has no retry budget and will therefore boot f
|
|
|
119
126
|
otherwise-transient failure. Each flag may appear once, does not change the command's exit code,
|
|
120
127
|
and is rejected when repeated.
|
|
121
128
|
|
|
129
|
+
On the MicroVM's one-attempt path, `--fresh-db-fallback` also means the fresh database does not
|
|
130
|
+
replicate during that boot.
|
|
131
|
+
|
|
122
132
|
#### Outcome → exit code
|
|
123
133
|
|
|
124
134
|
| Outcome | Code | When |
|
|
@@ -138,6 +148,9 @@ and is rejected when repeated.
|
|
|
138
148
|
| `fatal(misconfig)` | `30` | no object store while persistence is enabled, or the probe failed on attempt 2 |
|
|
139
149
|
| `fatal(deliberate)` | `30` | attempt 2, `crash` |
|
|
140
150
|
|
|
151
|
+
Fatal exit codes 30 and 34 are reported directly to `POST /v1/runners/self/startup-failure`
|
|
152
|
+
before the CLI starts. Reporting is best-effort and never changes the exit code.
|
|
153
|
+
|
|
141
154
|
`31` also guarantees local debris (`opencode.db`, `-wal`, `-shm`) is discarded, one `try` per path, at the classifier's single return point. Note `32` isn't purely "transient" — `recovered` shares it with `retryTransient` even though a `recovered` outcome already mutated S3.
|
|
142
155
|
|
|
143
156
|
The probe is a `list`, not a `get`: S3 answers a wrong bucket name with `NoSuchBucket`, also a 404, so a `get`-based probe would read a misconfigured bucket as healthy and unlock recovery against it.
|
|
@@ -213,11 +226,11 @@ what it is.
|
|
|
213
226
|
#### The four `--on-unusable-replica` strategies
|
|
214
227
|
|
|
215
228
|
- **`prune` (default)** — ≤1 guarded delete iff the newest L0's own bytes fail the LTX header check (`length >= 100` and magic `"LTX1"`) → `recovered`/`32`; a failed delete (`recoveryFailed`) or a declined escalation → `31`. **Header-only** check: a valid header with deeper corruption is judged sound and left alone by the *delete* — and since #1106 that is no longer the end of the road. When prune finds nothing to delete (`targetHealthy`/`noL0Present`) **and** at least one key parses as an LTX object, it **escalates to quarantine**: every object under `<prefix>/opencode.db/` is moved to `<prefix>/quarantine/opencode.db/<timestamp>/` → `recovered(quarantine)`/`32`. It does **not** escalate on `layoutMismatch` (we don't understand the layout), on an unreadable object (absent evidence), or on an empty prefix. Never prunes twice.
|
|
216
|
-
- **`leave`** — zero S3 mutation → `31`. Cost is "prior history lost this boot".
|
|
229
|
+
- **`leave`** — zero S3 mutation → `31`. Cost is "prior history lost this boot". The fresh local DB replicates into the existing prefix; no new chain is started. Upside: the runner still comes online without a crash loop.
|
|
217
230
|
- **`clear`** — deletes every key under `<prefix>/opencode.db/` passing `isDeletableReplicaKey`; that guard, not the `list()` prefix, is the boundary (IAM grants `s3:DeleteObject*` bucket-wide). One `try` per key. A **partial** clear still reports `recovered`/`32`. Cost: all saved history, unconditionally.
|
|
218
231
|
- **`crash`** — first in the switch, no S3 mutation even considered → `fatal(deliberate)`/`30` → `entrypoint.sh` `die`s → task replaced → **crash loop** until an operator intervenes.
|
|
219
232
|
|
|
220
|
-
#### The measured real-world key layout (litestream 0.5.13)
|
|
233
|
+
#### The measured real-world key layout (litestream 0.5.13 historical sample)
|
|
221
234
|
|
|
222
235
|
`parseLtxKey` (`src/replica-keys.ts`) expects
|
|
223
236
|
`<prefix>/opencode.db/<level:04d>/<minTxid>-<maxTxid>.ltx` — **no `ltx/` path segment**,
|
|
@@ -235,7 +248,7 @@ at boot the newest L0 is normally *absent* (`noL0Present`) or freshly written an
|
|
|
235
248
|
(`targetHealthy`) — `prune`'s real-world reach is narrower than the code alone suggests,
|
|
236
249
|
and it can never repair corruption at a higher compaction level.
|
|
237
250
|
|
|
238
|
-
`EVIDENT_ON_UNUSABLE_REPLICA` is a `
|
|
251
|
+
`EVIDENT_ON_UNUSABLE_REPLICA` is a `runner/docker-images/fargate` (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 [the Fargate image README](../docker-images/fargate/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.
|
|
239
252
|
|
|
240
253
|
See `specs/local-runner.feature`'s "Recovering session history at startup" scenarios for
|
|
241
254
|
the behavioural anchor (31 comes online, 30 does not).
|
|
@@ -262,9 +275,12 @@ nothing had ever run a real `PRAGMA integrity_check`).
|
|
|
262
275
|
each into a scratch path and integrity-checking it there. The first one that passes
|
|
263
276
|
is adopted — renamed over `opencode.db`, replacing it and its stale sidecars.
|
|
264
277
|
3. If nothing retained passes (or there's nothing to try, or the search runs out of
|
|
265
|
-
time/points), the
|
|
266
|
-
|
|
267
|
-
|
|
278
|
+
time/points), the active replica prefix is separated first: its history is moved aside,
|
|
279
|
+
then the prefix is re-listed as empty before the corrupt local DB and sidecars are
|
|
280
|
+
discarded. Only both proofs permit a fresh boot. Otherwise exit `34` stops the boot before
|
|
281
|
+
OpenCode or Litestream starts; the backup history remains readable at its quarantine
|
|
282
|
+
destination or original key, and surviving local files remain in place without a process
|
|
283
|
+
opening or writing them.
|
|
268
284
|
|
|
269
285
|
A walked-back boot keeps replicating into the **same** replica — no S3 mutation, no new
|
|
270
286
|
prefix. litestream re-bases to the replica's high-water mark and continues the txid
|
|
@@ -279,10 +295,8 @@ against S3 at the ~1 GB scale a production replica can reach.)
|
|
|
279
295
|
| `skipped` | `0` | local DB absent or zero bytes — nothing to verify |
|
|
280
296
|
| `healthy` | `0` | the local DB passed its integrity check as-is |
|
|
281
297
|
| `walkedBack` | `0` | the local DB failed, but an older retained restore point passed and was adopted |
|
|
282
|
-
| `exhausted` (`noCandidates`) | `33` | the
|
|
283
|
-
| `exhausted`
|
|
284
|
-
| `exhausted` (`budgetExhausted`) | `33` | the search stopped on `EVIDENT_SESSION_DB_WALKBACK_MAX_POINTS`/`_BUDGET_SECONDS` before trying every retained point |
|
|
285
|
-
| `exhausted` (`enumerationFailed`) | `33` | `litestream ltx` could not be read (bad exit, non-JSON, malformed listing) |
|
|
298
|
+
| `exhausted` (`noCandidates`, `allFailed`, `budgetExhausted`, or `enumerationFailed`) | `33` | the active prefix was proven separated (or persistence is disabled) **and** the corrupt local DB and sidecars were proven discarded |
|
|
299
|
+
| `exhausted` | `34` | either the replica separation or local discard could not be proven; the boot stops before OpenCode and Litestream start |
|
|
286
300
|
|
|
287
301
|
`healthy`/`walkedBack`/`skipped` all share exit `0` deliberately: the shell's behaviour
|
|
288
302
|
afterwards — start `litestream replicate` — is identical either way, so a further split
|
|
@@ -301,7 +315,10 @@ before looking at everything" would report the wrong conclusion to the operator.
|
|
|
301
315
|
| `INFO: SESSION-DB-WALKBACK-ADOPTED` | `adopt` (`session-db-verify.ts`) | A candidate passes and is adopted over `opencode.db` | `txid`, `candidates`, `bytes` |
|
|
302
316
|
| `SESSION-DB-INTEGRITY` | `describeSessionDbVerification` (`diagnostics.ts`), logged once per invocation as the final outcome | The local DB passed its check as-is | `bytes` |
|
|
303
317
|
| `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` |
|
|
304
|
-
| `SESSION-DB-INTEGRITY-EXHAUSTED` | `describeSessionDbVerification` (`diagnostics.ts`), logged once per invocation as the final outcome | Nothing retained passed
|
|
318
|
+
| `SESSION-DB-INTEGRITY-EXHAUSTED` | `describeSessionDbVerification` (`diagnostics.ts`), logged once per invocation as the final outcome | Nothing retained passed; a fresh boot follows only when both separation and local disposal were proven | `candidatesTried` (in prose, not `k=v`) |
|
|
319
|
+
| `SESSION-DB-REPLICA-SEPARATED` | `separateCorruptReplica` (`replica-recovery.ts`) | The active prefix was re-listed empty after separation | replica root and quarantine destination |
|
|
320
|
+
| `SESSION-DB-REPLICA-SEPARATION-UNVERIFIED` | `separateCorruptReplica` (`replica-recovery.ts`) | A separation could not be proven | remaining objects or list error |
|
|
321
|
+
| `SESSION-DB-LOCAL-DISCARD-FAILED` | `verifySessionDb` (`session-db-verify.ts`) | The local DB or a sidecar could not be proven gone | removed and surviving paths |
|
|
305
322
|
|
|
306
323
|
The first four fire only while the walkback loop runs; the last three are always the
|
|
307
324
|
one message this command logs as its own final word on the outcome — `entrypoint.sh`
|
|
@@ -310,15 +327,16 @@ same way it already reuses `SESSION-DB-REPLICA-UNUSABLE` for `session-db-classif
|
|
|
310
327
|
|
|
311
328
|
### Why the contract is asymmetric
|
|
312
329
|
|
|
313
|
-
`restore` and
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
330
|
+
`restore` logs domain outcomes and still exits `0`: a missing remote object or a
|
|
331
|
+
restore failure should not abort a boot, because a runner with no credentials yet
|
|
332
|
+
is a runner a human can still log into. `sync-once` returns `40` when the current
|
|
333
|
+
credential file is not persisted (`failed`, `hashFailed`, or `localInvalid`), while
|
|
334
|
+
`absent` and `disabled` remain legitimate `0` outcomes.
|
|
317
335
|
|
|
318
|
-
The consequence is the point: **any non-zero status from `restore
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
336
|
+
The consequence is the point: **any non-zero status from `restore` means the tool itself
|
|
337
|
+
broke** — and `sync-once` uses `40` for its typed persistence answer; other non-zero
|
|
338
|
+
statuses still mean bad arguments (`2`), an uncaught throw (`1`), or a bundle that would
|
|
339
|
+
not run at all. The shell keeps that distinction in one helper.
|
|
322
340
|
|
|
323
341
|
The **two predicates** — `model-auth-ready` and `self-stop` — each need to distinguish
|
|
324
342
|
"the answer is no" from "the tool is broken", so each answers no with its own dedicated
|
|
@@ -334,13 +352,14 @@ task, and only a `0` — returned solely on a confirmed `desiredCount` of 0 —
|
|
|
334
352
|
`session-db-classify` is neither a domain outcome nor a predicate — it's a **third category**: a typed classification with four actionable answers (`0` ran, `32` retry, `31` unusable, `30` fatal), numbered above the two predicates' codes so they can't collide with a future one (`cli.ts:42-47`; see `diagnostics.ts`'s `sessionDbExitCode` for the single outcome → code mapping). The fail-safe direction inverts here: the two predicates above treat an unexpected code as the *safe* answer, but `entrypoint.sh` treats an unexpected code from `session-db-classify` as **fatal** (it `die`s) — correctly, because a boot that can't classify its own replica must not guess about deleting S3 objects.
|
|
335
353
|
|
|
336
354
|
`session-db-verify` appends the same best-effort versioned report for a walkback or exhausted
|
|
337
|
-
history.
|
|
338
|
-
|
|
355
|
+
history. A refused boot records `session_db_boot_refused`, but `evident run` has not started to
|
|
356
|
+
drain it, so operators see that refusal in boot logs and the restart loop. The report does not
|
|
357
|
+
alter the command's exit code.
|
|
339
358
|
|
|
340
359
|
`src/shell-contract.json` is the machine-checked source of truth for the command list, and
|
|
341
360
|
`shell-contract.test.ts` holds **every** shell that speaks it to it — Fargate's
|
|
342
|
-
`
|
|
343
|
-
`
|
|
361
|
+
`runner/docker-images/fargate/entrypoint.sh` and the MicroVM's
|
|
362
|
+
`runner/docker-images/microvm/hooks` (#608), discovered by grep so a third one
|
|
344
363
|
cannot go unchecked. Each shell must: call only subcommands the CLI implements (and, for
|
|
345
364
|
`entrypoint.sh`, call all of them); route every call through one `run_synchroniser`; report
|
|
346
365
|
a broken tool; and take each answer code the commands it calls can return **silently and by
|
|
@@ -370,7 +389,7 @@ key, so they never appear in `env`'s output or `litestream.yml`.
|
|
|
370
389
|
| `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. |
|
|
371
390
|
| `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`. |
|
|
372
391
|
|
|
373
|
-
`
|
|
392
|
+
`runner/docker-images/fargate/README.md` documents these same three from the deployment side —
|
|
374
393
|
keep them in sync.
|
|
375
394
|
|
|
376
395
|
Requiring **both** `LITESTREAM_BUCKET` and `LITESTREAM_PREFIX` (never just one) means
|