oysterun 1.6.0-beta.4 → 1.6.0-beta.5
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/host-service/host-lifecycle-recovery.mjs +1 -1
- package/host-service/lifecycle-continuity-runner.mjs +1 -1
- package/host-service/lifecycle-continuity.mjs +10 -10
- package/host-service/package.json +1 -1
- package/host-service/server.mjs +2 -2
- package/package.json +1 -1
- package/tool_scripts/oysterun_service_common.sh +0 -20
- package/tool_scripts/p233_legacy_bootstrap.mjs +1 -1
|
@@ -108,7 +108,7 @@ export function buildHostLifecycleRecoveryPlan({
|
|
|
108
108
|
confirmation: "Finish this committed operation's cleanup. No additional restart is required.",
|
|
109
109
|
} : restartOnly ? action("restart_only", "Recover restart", "Restart recovery required",
|
|
110
110
|
"Retry the existing package with the retained fresh Session restore receipt.",
|
|
111
|
-
"Restart this Host and verify the original
|
|
111
|
+
"Restart this Host and verify the original live Sessions and history. Active work will not replay.")
|
|
112
112
|
: recovery ? { ...ACTIONS.restore_and_restart,
|
|
113
113
|
title: "Update qualification did not finish",
|
|
114
114
|
description: `Restore Oysterun ${r.source.version} using the retained package, definition and fresh Session snapshot.`,
|
|
@@ -72,7 +72,7 @@ export async function qualifyLifecycleContinuity({
|
|
|
72
72
|
result.tunnel = await waitFor("TUNNEL_QUALIFIED", async () => {
|
|
73
73
|
const observation = await observe({ configDir, operationId, origin: expectedOrigin });
|
|
74
74
|
// A provider can exit while the user signs back in. Commit only a fresh
|
|
75
|
-
// observation that still proves the same Host and complete
|
|
75
|
+
// observation that still proves the same Host and complete live Sessions.
|
|
76
76
|
const freshHost = checkHost(observation);
|
|
77
77
|
const freshSessions = checkSessions(observation);
|
|
78
78
|
// The scoped route probe proves transport and Host identity; a real
|
|
@@ -96,11 +96,11 @@ export function readContinuity(path, operationId) {
|
|
|
96
96
|
return receipt;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// The final
|
|
100
|
-
// its handoff gate closes. Installation may take minutes; the initial
|
|
99
|
+
// The final live Sessions are captured by the running coordinator immediately before
|
|
100
|
+
// its handoff gate closes. Installation may take minutes; the initial live Sessions
|
|
101
101
|
// must never silently stand in for this fresh capture.
|
|
102
102
|
export function assertFreshContinuitySnapshot(baseline, restartId, restartState) {
|
|
103
|
-
requireValue(restartId && Array.isArray(baseline?.sessions), "preparation", "fresh_roster_identity", "Fresh
|
|
103
|
+
requireValue(restartId && Array.isArray(baseline?.sessions), "preparation", "fresh_roster_identity", "Fresh live Session identity is missing.");
|
|
104
104
|
const ids = baseline.sessions.map((row) => row.session_id);
|
|
105
105
|
requireValue(new Set(ids).size === ids.length && baseline.sessions.every((row) => row.session_id &&
|
|
106
106
|
row.provider_resume_id && !row.observation_error && Array.isArray(row.history_anchors)),
|
|
@@ -111,16 +111,16 @@ export function assertFreshContinuitySnapshot(baseline, restartId, restartState)
|
|
|
111
111
|
const observed = baseline.sessions.find((item) => item.session_id === row.session_id);
|
|
112
112
|
return observed && ["provider", "provider_resume_id", "agent_id", "agent_folder"].every((key) => row[key] === observed[key]) &&
|
|
113
113
|
(row.provider_thread_id || row.provider_resume_id) === observed.provider_thread_id;
|
|
114
|
-
}), "preparation", "fresh_snapshot_roster", "Fresh live
|
|
114
|
+
}), "preparation", "fresh_snapshot_roster", "Fresh live Sessions differ from the exact prepared Session snapshot; stop before handoff.");
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
export function bindFreshContinuityBaseline(path, operationId, baseline, restartId, restartState) {
|
|
118
118
|
const receipt = readContinuity(path, operationId);
|
|
119
119
|
requireValue((["PREPARED", "PACKAGE_SWITCHED"].includes(receipt.state) ||
|
|
120
120
|
(receipt.state === "RECOVERY_REQUIRED" && !receipt.restart_id)) && !receipt.restart_attempted,
|
|
121
|
-
"preparation", "fresh_roster_boundary", "The fresh
|
|
121
|
+
"preparation", "fresh_roster_boundary", "The fresh live Sessions cannot change after restart begins.");
|
|
122
122
|
requireValue(baseline?.host?.pid === receipt.baseline.host.pid,
|
|
123
|
-
"preparation", "fresh_roster_identity", "Fresh
|
|
123
|
+
"preparation", "fresh_roster_identity", "Fresh live Sessions must come from the retained running Host.");
|
|
124
124
|
assertFreshContinuitySnapshot(baseline, restartId, restartState);
|
|
125
125
|
receipt.initial_baseline_digest ||= receipt.baseline_digest;
|
|
126
126
|
receipt.baseline = baseline;
|
|
@@ -135,10 +135,10 @@ export function prepareContinuity({ path, operationId, source, target, baseline,
|
|
|
135
135
|
source.runtime_identity_digest === target.runtime_identity_digest,
|
|
136
136
|
"preparation", "source_target_identity", "Source and target runtime identities must be complete and equal.");
|
|
137
137
|
requireValue(baseline?.host && Array.isArray(baseline.sessions) && baseline.captured_at,
|
|
138
|
-
"preparation", "
|
|
138
|
+
"preparation", "fresh_live_sessions", "A fresh Host and dynamic Session baseline is required.");
|
|
139
139
|
const ids = baseline.sessions.map((row) => row.session_id);
|
|
140
140
|
requireValue(ids.every(Boolean) && new Set(ids).size === ids.length,
|
|
141
|
-
"preparation", "unique_roster", "The fresh
|
|
141
|
+
"preparation", "unique_roster", "The fresh live Sessions have missing or duplicate identities.");
|
|
142
142
|
return publishDurableBoundary(path, writeContinuityAtomic(path, {
|
|
143
143
|
...authority, schema: CONTINUITY_SCHEMA, operation_id: operationId, transaction_kind: transactionKind,
|
|
144
144
|
source, target, baseline, baseline_digest: continuityDigest(baseline), operation_root: operationRoot,
|
|
@@ -234,7 +234,7 @@ export function qualifyContinuityHost(baseline, observed, { version, runtimeIden
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
export function qualifyContinuitySessions(expected, observed, restoreResult, { requireRestore = true } = {}) {
|
|
237
|
-
requireValue(Array.isArray(expected) && Array.isArray(observed), "session_qualification", "roster_missing", "Session qualification requires both exact
|
|
237
|
+
requireValue(Array.isArray(expected) && Array.isArray(observed), "session_qualification", "roster_missing", "Session qualification requires both exact live Sessions.");
|
|
238
238
|
if (requireRestore) {
|
|
239
239
|
requireValue(restoreResult && Array.isArray(restoreResult.restored) && Array.isArray(restoreResult.failed) && Array.isArray(restoreResult.skipped),
|
|
240
240
|
"session_qualification", "restore_result_missing", "Consumed state has no complete Session restore result.");
|
|
@@ -246,7 +246,7 @@ export function qualifyContinuitySessions(expected, observed, restoreResult, { r
|
|
|
246
246
|
}
|
|
247
247
|
const observedIds = observed.map((row) => row.session_id);
|
|
248
248
|
requireValue(new Set(observedIds).size === observedIds.length, "session_qualification", "duplicate_runtime", "Duplicate live Session identity.");
|
|
249
|
-
requireValue(observed.length === expected.length, "session_qualification", "roster_changed", "
|
|
249
|
+
requireValue(observed.length === expected.length, "session_qualification", "roster_changed", "Current live Sessions differ from the freshly retained live Sessions.");
|
|
250
250
|
for (const before of expected) {
|
|
251
251
|
const after = observed.find((row) => row.session_id === before.session_id);
|
|
252
252
|
requireValue(after, "session_qualification", "session_missing", `Session ${before.session_id} is missing.`);
|
package/host-service/server.mjs
CHANGED
|
@@ -8359,7 +8359,7 @@ function assertSessionRosterMutationAvailable() {
|
|
|
8359
8359
|
if (!operation.continuity_path || !isUpdateOperationBlocking(operation)) return;
|
|
8360
8360
|
const receipt = readContinuity(operation.continuity_path, operation.operation_id);
|
|
8361
8361
|
if (!receipt.restart_id || ["CLEANED", "ROLLED_BACK_HEALTHY"].includes(receipt.state)) return;
|
|
8362
|
-
const error = new Error("Host lifecycle recovery is preserving the fresh
|
|
8362
|
+
const error = new Error("Host lifecycle recovery is preserving the fresh live Sessions. Finish that operation before changing Sessions.");
|
|
8363
8363
|
error.code = "oysterun_session_roster_reserved";
|
|
8364
8364
|
throw error;
|
|
8365
8365
|
}
|
|
@@ -10548,7 +10548,7 @@ function scheduleContinuityRecovery({ kind, source }) {
|
|
|
10548
10548
|
process.pid === receipt.baseline.host.pid && receipt.operation_root) {
|
|
10549
10549
|
// Installation can finish while a newly created provider is still booting.
|
|
10550
10550
|
// If no handoff snapshot was ever bound, recover from a fresh complete
|
|
10551
|
-
//
|
|
10551
|
+
// live Sessions of the original running coordinator. Never replace a bound one.
|
|
10552
10552
|
const fresh = buildHostContinuityObservation().baseline;
|
|
10553
10553
|
if (fresh.sessions.some((row) => row.observation_error || !row.provider_resume_id)) throw continuityFailure("recovery", "fresh_roster_not_ready", "Wait for every current Session provider to be ready before recovering this unprepared handoff.");
|
|
10554
10554
|
const prepared = prepareHostRestartRestore({ trigger: "p233_recovery_before_first_handoff_snapshot" });
|
package/package.json
CHANGED
|
@@ -1815,7 +1815,6 @@ wait_for_http() {
|
|
|
1815
1815
|
local pid="$3"
|
|
1816
1816
|
local log_file="$4"
|
|
1817
1817
|
local launch_label="${5:-}"
|
|
1818
|
-
local attempts=0
|
|
1819
1818
|
|
|
1820
1819
|
until curl -fsS "${url}" >/dev/null 2>&1; do
|
|
1821
1820
|
local current_pid="${pid}"
|
|
@@ -1825,15 +1824,6 @@ wait_for_http() {
|
|
|
1825
1824
|
|
|
1826
1825
|
if [[ -n "${current_pid}" ]] && ! pid_is_running "${current_pid}" 2>/dev/null; then
|
|
1827
1826
|
if [[ -n "${launch_label}" ]] && launchctl_label_exists "${launch_label}"; then
|
|
1828
|
-
attempts=$((attempts + 1))
|
|
1829
|
-
if [[ "${attempts}" -ge 60 ]]; then
|
|
1830
|
-
echo "[oysterun-service] error: timed out waiting for ${label} at ${url}" >&2
|
|
1831
|
-
if [[ -f "${log_file}" ]]; then
|
|
1832
|
-
echo "[oysterun-service] Last ${label} log lines:" >&2
|
|
1833
|
-
tail -n 40 "${log_file}" >&2 || true
|
|
1834
|
-
fi
|
|
1835
|
-
exit 1
|
|
1836
|
-
fi
|
|
1837
1827
|
sleep 1
|
|
1838
1828
|
continue
|
|
1839
1829
|
fi
|
|
@@ -1845,16 +1835,6 @@ wait_for_http() {
|
|
|
1845
1835
|
exit 1
|
|
1846
1836
|
fi
|
|
1847
1837
|
|
|
1848
|
-
attempts=$((attempts + 1))
|
|
1849
|
-
if [[ "${attempts}" -ge 60 ]]; then
|
|
1850
|
-
echo "[oysterun-service] error: timed out waiting for ${label} at ${url}" >&2
|
|
1851
|
-
if [[ -f "${log_file}" ]]; then
|
|
1852
|
-
echo "[oysterun-service] Last ${label} log lines:" >&2
|
|
1853
|
-
tail -n 40 "${log_file}" >&2 || true
|
|
1854
|
-
fi
|
|
1855
|
-
exit 1
|
|
1856
|
-
fi
|
|
1857
|
-
|
|
1858
1838
|
sleep 1
|
|
1859
1839
|
done
|
|
1860
1840
|
}
|
|
@@ -239,7 +239,7 @@ export async function executeP233Bootstrap(action, reviewed) {
|
|
|
239
239
|
beforeForwardRestart: async (receipt) => {
|
|
240
240
|
// 1.5.7 has no Session admission reservation. The one-time released
|
|
241
241
|
// maintenance window is mandatory; this final read also rejects any
|
|
242
|
-
// observed
|
|
242
|
+
// observed live Sessions change after the pre-activation snapshot.
|
|
243
243
|
const live = await legacyObserve({ origin: new URL(healthUrl).origin, retained: receipt.baseline });
|
|
244
244
|
qualifyContinuitySessions(receipt.baseline.sessions, live.sessions, null, { requireRestore: false });
|
|
245
245
|
},
|