@evident-ai/runner-synchroniser 3.4.1-dev.15755a4 → 3.4.1-dev.1856549
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 +14 -1
- package/dist/cli.js +79 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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.
|
|
@@ -217,7 +230,7 @@ what it is.
|
|
|
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**,
|
package/dist/cli.js
CHANGED
|
@@ -51944,7 +51944,9 @@ function resolveConfig(env4) {
|
|
|
51944
51944
|
env4.EVIDENT_SESSION_DB_WALKBACK_BUDGET_SECONDS,
|
|
51945
51945
|
DEFAULT_WALKBACK_BUDGET_SECONDS
|
|
51946
51946
|
),
|
|
51947
|
-
hasModelApiKey: nonEmpty(env4.ANTHROPIC_API_KEY) !== null || nonEmpty(env4.OPENAI_API_KEY) !== null
|
|
51947
|
+
hasModelApiKey: nonEmpty(env4.ANTHROPIC_API_KEY) !== null || nonEmpty(env4.OPENAI_API_KEY) !== null,
|
|
51948
|
+
runnerKey: nonEmpty(env4.EVIDENT_RUNNER_KEY) ?? nonEmpty(env4.EVIDENT_AGENT_KEY),
|
|
51949
|
+
apiUrl: nonEmpty(env4.EVIDENT_API_URL)
|
|
51948
51950
|
};
|
|
51949
51951
|
}
|
|
51950
51952
|
function storeOf(config, name) {
|
|
@@ -52330,7 +52332,7 @@ function renderLitestreamConfig(config) {
|
|
|
52330
52332
|
# (no static keys here).
|
|
52331
52333
|
#
|
|
52332
52334
|
# INVARIANT (see README "Single-writer invariant"): exactly ONE writer per S3
|
|
52333
|
-
# prefix \u2014
|
|
52335
|
+
# prefix \u2014 we do not enable Litestream's server-side lease, so never run two
|
|
52334
52336
|
# replicators on one prefix. Only opencode.db is replicated.
|
|
52335
52337
|
|
|
52336
52338
|
dbs:
|
|
@@ -62682,7 +62684,8 @@ function base(at2, stage) {
|
|
|
62682
62684
|
quarantine_failed_objects: null,
|
|
62683
62685
|
quarantined_bytes: null,
|
|
62684
62686
|
verified_restore_point: null,
|
|
62685
|
-
restore_points_tried: null
|
|
62687
|
+
restore_points_tried: null,
|
|
62688
|
+
replication_suspended: false
|
|
62686
62689
|
};
|
|
62687
62690
|
}
|
|
62688
62691
|
function recordRestoreOutcome(outcome, at2, exitCode, attempt, recoveryOccurred, freshDbFallback = false) {
|
|
@@ -62693,7 +62696,8 @@ function recordRestoreOutcome(outcome, at2, exitCode, attempt, recoveryOccurred,
|
|
|
62693
62696
|
...record,
|
|
62694
62697
|
outcome: "fresh_session_db",
|
|
62695
62698
|
severity: "error",
|
|
62696
|
-
reason: "retry_budget_exhausted"
|
|
62699
|
+
reason: "retry_budget_exhausted",
|
|
62700
|
+
replication_suspended: true
|
|
62697
62701
|
} : {
|
|
62698
62702
|
...record,
|
|
62699
62703
|
outcome: "restore_retried",
|
|
@@ -62934,6 +62938,49 @@ async function walkBack(params) {
|
|
|
62934
62938
|
};
|
|
62935
62939
|
}
|
|
62936
62940
|
|
|
62941
|
+
// src/startup-failure-report.ts
|
|
62942
|
+
init_esm_shims();
|
|
62943
|
+
var STARTUP_FAILURE_REPORT_TIMEOUT_MS = 5e3;
|
|
62944
|
+
var MAX_RESPONSE_BODY_LENGTH = 1e3;
|
|
62945
|
+
async function reportStartupFailure(report, config, deps) {
|
|
62946
|
+
if (config.apiUrl === null || config.runnerKey === null) {
|
|
62947
|
+
deps.log(
|
|
62948
|
+
`Startup failure was not reported to Evident because ${config.apiUrl === null ? "EVIDENT_API_URL" : "a runner key"} is unavailable.`
|
|
62949
|
+
);
|
|
62950
|
+
return;
|
|
62951
|
+
}
|
|
62952
|
+
const endpoint = `${config.apiUrl.replace(/\/+$/, "").replace(/\/v1$/, "")}/v1/runners/self/startup-failure`;
|
|
62953
|
+
try {
|
|
62954
|
+
const response = await deps.fetchImpl(endpoint, {
|
|
62955
|
+
method: "POST",
|
|
62956
|
+
headers: {
|
|
62957
|
+
Authorization: `SandboxKey ${config.runnerKey}`,
|
|
62958
|
+
"Content-Type": "application/json"
|
|
62959
|
+
},
|
|
62960
|
+
body: JSON.stringify({
|
|
62961
|
+
classification: report.classification,
|
|
62962
|
+
stage: report.stage,
|
|
62963
|
+
litestream_exit_code: report.litestreamExitCode,
|
|
62964
|
+
attempt: report.attempt,
|
|
62965
|
+
restore_points_tried: report.restorePointsTried,
|
|
62966
|
+
report_id: deps.randomUUID()
|
|
62967
|
+
}),
|
|
62968
|
+
// The container is about to exit, so a long wait only delays ECS replacement.
|
|
62969
|
+
signal: AbortSignal.timeout(STARTUP_FAILURE_REPORT_TIMEOUT_MS)
|
|
62970
|
+
});
|
|
62971
|
+
if (!response.ok) {
|
|
62972
|
+
const body = (await response.text()).slice(0, MAX_RESPONSE_BODY_LENGTH);
|
|
62973
|
+
deps.log(
|
|
62974
|
+
`WARNING: fatal session-DB startup failure report was rejected (${response.status}): ${body}`
|
|
62975
|
+
);
|
|
62976
|
+
}
|
|
62977
|
+
} catch (error) {
|
|
62978
|
+
deps.log(
|
|
62979
|
+
`WARNING: could not report the fatal session-DB startup failure to Evident (${describeError(error)}); the failure is still shown above and in this machine's logs.`
|
|
62980
|
+
);
|
|
62981
|
+
}
|
|
62982
|
+
}
|
|
62983
|
+
|
|
62937
62984
|
// src/self-stop.ts
|
|
62938
62985
|
init_esm_shims();
|
|
62939
62986
|
function isComplete(credentials) {
|
|
@@ -63275,6 +63322,20 @@ async function commandSessionDbClassify(args, config, deps) {
|
|
|
63275
63322
|
log
|
|
63276
63323
|
});
|
|
63277
63324
|
log(describeSessionDbClassification(outcome, { bucket: config.bucket, region: config.region }));
|
|
63325
|
+
if (outcome.kind === "fatal") {
|
|
63326
|
+
await reportStartupFailure(
|
|
63327
|
+
{
|
|
63328
|
+
classification: outcome.cause === "misconfig" ? "session_db_restore_misconfigured" : "session_db_restore_crash_chosen",
|
|
63329
|
+
stage: "restore",
|
|
63330
|
+
litestreamExitCode: args.exitCode,
|
|
63331
|
+
attempt: args.attempt,
|
|
63332
|
+
restorePointsTried: null
|
|
63333
|
+
},
|
|
63334
|
+
config,
|
|
63335
|
+
{ fetchImpl: deps.fetchImpl ?? fetch, randomUUID: deps.randomUUID ?? crypto.randomUUID, log }
|
|
63336
|
+
);
|
|
63337
|
+
return sessionDbExitCode(outcome);
|
|
63338
|
+
}
|
|
63278
63339
|
const record = recordRestoreOutcome(
|
|
63279
63340
|
outcome,
|
|
63280
63341
|
(deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
@@ -63304,6 +63365,20 @@ async function commandSessionDbVerify(litestreamConfigPath, config, deps) {
|
|
|
63304
63365
|
budgetSeconds: config.walkbackBudgetSeconds
|
|
63305
63366
|
});
|
|
63306
63367
|
log(describeSessionDbVerification(outcome));
|
|
63368
|
+
if (outcome.kind === "exhausted" && sessionDbVerifyExitCode(outcome) === 34) {
|
|
63369
|
+
await reportStartupFailure(
|
|
63370
|
+
{
|
|
63371
|
+
classification: outcome.localDb.kind === "discardFailed" ? "session_db_local_discard_failed" : "session_db_separation_unproven",
|
|
63372
|
+
stage: "verify",
|
|
63373
|
+
litestreamExitCode: null,
|
|
63374
|
+
attempt: null,
|
|
63375
|
+
restorePointsTried: outcome.candidatesTried
|
|
63376
|
+
},
|
|
63377
|
+
config,
|
|
63378
|
+
{ fetchImpl: deps.fetchImpl ?? fetch, randomUUID: deps.randomUUID ?? crypto.randomUUID, log }
|
|
63379
|
+
);
|
|
63380
|
+
return sessionDbVerifyExitCode(outcome);
|
|
63381
|
+
}
|
|
63307
63382
|
const record = recordVerifyOutcome(outcome, (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString());
|
|
63308
63383
|
if (record !== null)
|
|
63309
63384
|
await appendSessionDbRecoveryRecord(record, config.sessionDbRecoveryReportPath, fileOps, log);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evident-ai/runner-synchroniser",
|
|
3
|
-
"version": "3.4.1-dev.
|
|
3
|
+
"version": "3.4.1-dev.1856549",
|
|
4
4
|
"description": "Restores and syncs the Evident runner's OpenCode credential stores (and litestream config) to an object store, so a runner survives task replacement with almost no state loss.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cli.js",
|