@evident-ai/runner-synchroniser 0.1.1-dev.e4d9a44 → 3.1.1-dev.297b927
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 +187 -12
- package/dist/cli.js +687 -211
- 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
|
|
@@ -62,7 +62,8 @@ version at all.
|
|
|
62
62
|
| `sync-once <claude\|opencode>` | — | `0` = ran; non-zero = tool broken |
|
|
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
|
-
| `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,
|
|
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`
|
|
@@ -115,8 +116,11 @@ Implementation-facing reference for the 7th command: it decides what a just-run
|
|
|
115
116
|
| `recovered` | `32` | attempt 2, a successful `prune` (1 key) or any `clear` (including a partial one) |
|
|
116
117
|
| `unusableReplica(strategy)` | `31` | attempt 2, `leave` |
|
|
117
118
|
| `unusableReplica(attemptsExhausted)` | `31` | attempt ≥ 3, before the probe |
|
|
118
|
-
| `unusableReplica(targetHealthy
|
|
119
|
-
| `unusableReplica(
|
|
119
|
+
| `unusableReplica(targetHealthy)` | `31` | attempt 2, `prune`; the newest L0 isn't provably corrupt AND the escalation declined (unreadable, or nothing parses) |
|
|
120
|
+
| `unusableReplica(noL0Present)` | `31` | attempt 2, `prune`; the prefix is **empty**, so there is nothing to prune and nothing to quarantine |
|
|
121
|
+
| `unusableReplica(layoutMismatch)` | `31` | attempt 2, `prune`; objects exist under the prefix but **none** parse as an LTX key at all — **a bug**, see below |
|
|
122
|
+
| `unusableReplica(recoveryFailed)` | `31` | attempt 2, `prune`'s delete itself threw, or the quarantine escalation moved nothing |
|
|
123
|
+
| `recovered(quarantine)` | `32` | attempt 2, `prune` found nothing at L0 but the damage is deeper: the replica was moved aside |
|
|
120
124
|
| `fatal(misconfig)` | `30` | no object store while persistence is enabled, or the probe failed on attempt 2 |
|
|
121
125
|
| `fatal(deliberate)` | `30` | attempt 2, `crash` |
|
|
122
126
|
|
|
@@ -124,16 +128,171 @@ Implementation-facing reference for the 7th command: it decides what a just-run
|
|
|
124
128
|
|
|
125
129
|
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.
|
|
126
130
|
|
|
131
|
+
#### Boot size signals
|
|
132
|
+
|
|
133
|
+
Two markers report the numbers a boot already computes but used to throw away (#1113):
|
|
134
|
+
|
|
135
|
+
| Marker | Emitted from | When it fires | Fields |
|
|
136
|
+
| --- | --- | --- | --- |
|
|
137
|
+
| `SESSION-DB-SIZE` | `describeSessionDbClassification` (`diagnostics.ts`) | Once per `session-db-classify` invocation that reaches `restored` or `noReplica` — i.e. every boot where the restore exit code was `0`. `disabled` and every non-zero-exit outcome (`retryTransient`, `recovered`, `unusableReplica`, `fatal`) never `stat` the local DB, so they emit no size. | `state` (`restored`\|`empty`\|`absent`), `bytes` |
|
|
138
|
+
| `SESSION-DB-REPLICA-SIZE` | `probeReplica` (`replica-recovery.ts`), its `ok` branch | Only on attempt 2's probe, i.e. only after a **failed** first restore — see the caveat below. | `objects`, `bytes` |
|
|
139
|
+
|
|
140
|
+
**Format convention:** `<MARKER>: <prose> <k>=<v> … bytes=<n>`, with the machine-readable
|
|
141
|
+
fields appended at the end of the line and `bytes` always last:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
SESSION-DB-SIZE: <prose> state=<restored|empty|absent> bytes=<n>
|
|
145
|
+
SESSION-DB-REPLICA-SIZE: <prose> objects=<m> bytes=<n>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Numbers go last because a CloudWatch Logs Insights `parse` capture needs a **literal
|
|
149
|
+
delimiter after every capture except the final one**, which runs to end of line — free
|
|
150
|
+
prose after a capture would make the pattern ambiguous. `diagnostics.test.ts` and
|
|
151
|
+
`replica-recovery.test.ts` each pin a line matching `/ (state=\w+ )?bytes=\d+$/`, so an
|
|
152
|
+
edit that appends prose after the number breaks a test rather than silently breaking an
|
|
153
|
+
operator's saved query.
|
|
154
|
+
|
|
155
|
+
**Logs Insights queries** (copy-pasteable, and the reason the format above is fixed):
|
|
156
|
+
|
|
157
|
+
Session-DB size distribution across the fleet:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
fields @timestamp, @message
|
|
161
|
+
| parse @message "SESSION-DB-SIZE: * state=* bytes=*" as dbProse, dbState, dbBytes
|
|
162
|
+
| filter ispresent(dbBytes)
|
|
163
|
+
| stats count(*) as boots,
|
|
164
|
+
avg(dbBytes) as avgBytes,
|
|
165
|
+
pct(dbBytes, 50) as p50Bytes,
|
|
166
|
+
pct(dbBytes, 95) as p95Bytes,
|
|
167
|
+
max(dbBytes) as maxBytes
|
|
168
|
+
by bin(1d)
|
|
169
|
+
| sort @timestamp desc
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Growth curve, and how many boots restored nothing:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
fields @timestamp, @message
|
|
176
|
+
| parse @message "SESSION-DB-SIZE: * state=* bytes=*" as dbProse, dbState, dbBytes
|
|
177
|
+
| filter ispresent(dbState)
|
|
178
|
+
| stats count(*) as boots, max(dbBytes) as maxBytes by dbState, bin(1d)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Replica size + object count (ECS, failed-first-restore boots only — see the caveat below):
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
fields @timestamp, @message
|
|
185
|
+
| parse @message "SESSION-DB-REPLICA-SIZE: * objects=* bytes=*" as replicaProse, replicaObjects, replicaBytes
|
|
186
|
+
| filter ispresent(replicaBytes)
|
|
187
|
+
| stats max(replicaBytes) as maxReplicaBytes, max(replicaObjects) as maxObjects by bin(1d)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Caveat: the two markers have very different fleet coverage.** `SESSION-DB-SIZE` fires
|
|
191
|
+
on **every healthy boot on both runtimes** and is the fleet-wide growth-curve signal.
|
|
192
|
+
`SESSION-DB-REPLICA-SIZE` fires **only** on ECS's attempt 2 — i.e. only when a first
|
|
193
|
+
restore already failed — and essentially never on the MicroVM, whose hook hard-codes the
|
|
194
|
+
classify attempt to `1` (`common.sh:352`), which never reaches the probe. Chart
|
|
195
|
+
`SESSION-DB-SIZE` for growth; treat `SESSION-DB-REPLICA-SIZE` as a diagnostic for
|
|
196
|
+
failed-restore boots only, or an empty series will look like a broken signal instead of
|
|
197
|
+
what it is.
|
|
198
|
+
|
|
127
199
|
#### The four `--on-unusable-replica` strategies
|
|
128
200
|
|
|
129
|
-
- **`prune` (default)** — ≤1 guarded delete iff the newest L0's own bytes fail the LTX header check (`length >= 100` and magic `"LTX1"`) → `recovered`/`32`;
|
|
130
|
-
- **`leave`** — zero S3 mutation → `31`. Cost is
|
|
201
|
+
- **`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.
|
|
202
|
+
- **`leave`** — zero S3 mutation → `31`. Cost is "prior history lost this boot". Since #1106 it is **not** also ephemeral: the boot still starts a fresh backup chain, because the classifier guarantees the local DB is fresh rather than half-restored. Upside: replica left byte-intact for forensics, no crash loop.
|
|
131
203
|
- **`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.
|
|
132
204
|
- **`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.
|
|
133
205
|
|
|
134
|
-
|
|
206
|
+
#### The measured real-world key layout (litestream 0.5.13)
|
|
207
|
+
|
|
208
|
+
`parseLtxKey` (`src/replica-keys.ts`) expects
|
|
209
|
+
`<prefix>/opencode.db/<level:04d>/<minTxid>-<maxTxid>.ltx` — **no `ltx/` path segment**,
|
|
210
|
+
despite litestream.io's generic docs prose suggesting otherwise (that mismatch was
|
|
211
|
+
#1081: every real key failed to parse, silently disabling `prune` in production).
|
|
212
|
+
Verbatim key sampled live from the production replica:
|
|
213
|
+
`agents/e5d866ac-b699-4940-b369-633b25f8b601/opencode.db/0000/000000000001a978-000000000001a978.ltx`.
|
|
214
|
+
Levels observed on that replica: `0000`/`0001`/`0002`/`0003`/`0009`. The zero-padding is
|
|
215
|
+
an observation, not a contract — `parseLtxKey` accepts an unpadded level directory too.
|
|
216
|
+
|
|
217
|
+
**L0 objects are short-lived.** litestream's own startup log measured a level-0
|
|
218
|
+
retention of **5 minutes** (`starting L0 retention monitor interval=15s retention=5m0s`;
|
|
219
|
+
compaction runs L1=30s, L2=5m, L3=1h, L9=24h). litestream expires L0 objects itself, so
|
|
220
|
+
at boot the newest L0 is normally *absent* (`noL0Present`) or freshly written and sound
|
|
221
|
+
(`targetHealthy`) — `prune`'s real-world reach is narrower than the code alone suggests,
|
|
222
|
+
and it can never repair corruption at a higher compaction level.
|
|
223
|
+
|
|
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.
|
|
225
|
+
|
|
226
|
+
See `specs/local-runner.feature`'s "Recovering session history at startup" scenarios for
|
|
227
|
+
the behavioural anchor (31 comes online, 30 does not).
|
|
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.)
|
|
135
260
|
|
|
136
|
-
|
|
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`.
|
|
137
296
|
|
|
138
297
|
### Why the contract is asymmetric
|
|
139
298
|
|
|
@@ -186,6 +345,8 @@ key, so they never appear in `env`'s output or `litestream.yml`.
|
|
|
186
345
|
| `LITESTREAM_BUCKET` / `LITESTREAM_PREFIX` | Object-store bucket and key prefix. | Persistence off (needs **both**): `restore`/`sync-once` no-op, `env` warns. |
|
|
187
346
|
| `AWS_REGION` | Region for the AWS clients (S3, and the ECS/STS calls `self-stop` makes). | Left to the SDK's own resolution. |
|
|
188
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. |
|
|
189
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. |
|
|
190
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. |
|
|
191
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`. |
|
|
@@ -209,6 +370,19 @@ config. `env` emits `PERSISTENCE_BUCKET`, `CLAUDE_CREDS`, `OPENCODE_DB_PATH` and
|
|
|
209
370
|
`CREDS_SYNC_INTERVAL`, each single-quoted so a value containing shell metacharacters is
|
|
210
371
|
inert when `eval`'d.
|
|
211
372
|
|
|
373
|
+
`entrypoint.sh` asserts, right after it `eval`s this output, that each key is *defined*
|
|
374
|
+
(not merely non-empty) and splits the four into two groups: a key is **required** — the
|
|
375
|
+
shell dies naming it — if it has no safe degradation; it is **tolerated** — the shell logs
|
|
376
|
+
a WARNING naming it and carries on — if it does. `OPENCODE_DB_PATH` and
|
|
377
|
+
`CREDS_SYNC_INTERVAL` are required: both are dereferenced for real work (deleting/creating
|
|
378
|
+
the session DB, sizing the sync-loop sleep), so a guessed value would be actively wrong,
|
|
379
|
+
not merely absent. `PERSISTENCE_BUCKET` and `CLAUDE_CREDS` are tolerated: an empty
|
|
380
|
+
`PERSISTENCE_BUCKET` is already the designed "persistence disabled" state, and
|
|
381
|
+
`CLAUDE_CREDS` is dereferenced only in operator-facing message text. If you change
|
|
382
|
+
`renderEnv` to make a required key conditional, the entrypoint will brick every container
|
|
383
|
+
running that version — the runner installs this package from a floating npm tag (`dev` /
|
|
384
|
+
`latest`), so the shell and the CLI can genuinely be different versions (#779, #821).
|
|
385
|
+
|
|
212
386
|
## Behaviour worth knowing
|
|
213
387
|
|
|
214
388
|
- **A non-empty local file that is not valid JSON is protected**, never overwritten and
|
|
@@ -235,10 +409,11 @@ inert when `eval`'d.
|
|
|
235
409
|
|
|
236
410
|
`restore`/`sync` and `session-db-classify` talk to a four-method interface: `get(key)`
|
|
237
411
|
returning `null` when the object is absent, `put(key, body)`, `list(prefix)` and
|
|
238
|
-
`delete(key)` for session-DB recovery. `list`
|
|
239
|
-
`[]`
|
|
240
|
-
|
|
241
|
-
`
|
|
412
|
+
`delete(key)` for session-DB recovery. `list(prefix)` returns `{ key, size }` entries
|
|
413
|
+
(`ObjectSummary[]`) — every object under `prefix`, size in bytes. It throws on any error
|
|
414
|
+
and never maps one to `[]` (an empty array means "reached the store, nothing there"); the
|
|
415
|
+
adapter owns pagination, and `delete` is idempotent. S3 vocabulary (`@aws-sdk/client-s3`,
|
|
416
|
+
the typed `NoSuchKey`/`NotFound` errors) is confined to the single adapter in
|
|
242
417
|
`src/s3-object-store.ts`, so the storage backend can be swapped without touching the
|
|
243
418
|
restore/sync/classify logic.
|
|
244
419
|
|