@gajae-code/coding-agent 0.18.5 → 0.18.7
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/CHANGELOG.md +140 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli-commands.d.ts +10 -0
- package/dist/types/cli-ordinary.d.ts +13 -0
- package/dist/types/cli.d.ts +12 -3
- package/dist/types/commands/contribution-prep.d.ts +0 -3
- package/dist/types/commands/sdk.d.ts +3 -0
- package/dist/types/commands/update.d.ts +1 -0
- package/dist/types/config/model-profile-activation.d.ts +19 -0
- package/dist/types/config/settings.d.ts +4 -0
- package/dist/types/coordinator-mcp/session-reaper.d.ts +5 -0
- package/dist/types/discovery/helpers.d.ts +2 -2
- package/dist/types/edit/modes/replace.d.ts +3 -3
- package/dist/types/edit/streaming.d.ts +2 -2
- package/dist/types/exec/bash-shell-supervisor.d.ts +12 -0
- package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
- package/dist/types/sdk/broker/discovery.d.ts +8 -0
- package/dist/types/sdk/broker/ensure.d.ts +1 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +4 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +4 -2
- package/dist/types/sdk/cli/diagnostics-cli.d.ts +18 -0
- package/dist/types/sdk/diagnostics/observe-broker.d.ts +185 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +6 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/sdk/session.d.ts +13 -0
- package/dist/types/session/agent-session.d.ts +5 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -1
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +17 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +3 -0
- package/dist/types/session/session-retirement.d.ts +47 -0
- package/dist/types/session/session-storage.d.ts +0 -46
- package/dist/types/session/session-transcript-header.d.ts +2 -0
- package/dist/types/session/task-artifact-owner-codec.d.ts +107 -0
- package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
- package/dist/types/session/task-artifact-owner.d.ts +10 -0
- package/dist/types/tools/ask-contract.d.ts +75 -254
- package/dist/types/tools/bash.d.ts +1 -1
- package/package.json +7 -7
- package/scripts/build-sdk-package-smoke.ts +5 -0
- package/scripts/resident-memory-bench.ts +174 -20
- package/src/capability/index.ts +1 -0
- package/src/capability/types.ts +4 -0
- package/src/cli/config-cli.ts +8 -18
- package/src/cli/public-command-entry.ts +5 -0
- package/src/cli/ssh-cli.ts +6 -3
- package/src/cli/web-search-cli.ts +9 -3
- package/src/cli-commands.ts +14 -0
- package/src/cli-main.ts +7 -8
- package/src/cli-ordinary.ts +197 -0
- package/src/cli.ts +25 -182
- package/src/commands/contribution-prep.ts +1 -3
- package/src/commands/sdk.ts +171 -21
- package/src/commands/update.ts +15 -1
- package/src/config/file-lock.ts +91 -2
- package/src/config/model-profile-activation.ts +70 -4
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +23 -31
- package/src/config/model-resolver.ts +9 -1
- package/src/config/settings.ts +44 -4
- package/src/coordinator-mcp/event-webhook.ts +19 -14
- package/src/coordinator-mcp/policy.ts +14 -4
- package/src/coordinator-mcp/server.ts +142 -7
- package/src/coordinator-mcp/session-reaper.ts +9 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/discovery/helpers.ts +9 -3
- package/src/discovery/ssh.ts +8 -3
- package/src/edit/modes/replace.ts +6 -6
- package/src/edit/streaming.ts +15 -15
- package/src/exec/bash-shell-supervisor.ts +60 -3
- package/src/gjc-runtime/gc-runtime.ts +2 -1
- package/src/hindsight/config.ts +9 -3
- package/src/internal-urls/docs-index.generated.ts +11 -10
- package/src/memories/index.ts +8 -2
- package/src/modes/acp/acp-agent.ts +724 -370
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/prompts/tools/ask.md +2 -0
- package/src/runtime-mcp/client.ts +13 -4
- package/src/runtime-mcp/elicitation.ts +142 -0
- package/src/sdk/broker/broker.ts +54 -1
- package/src/sdk/broker/daemon-entry.ts +67 -6
- package/src/sdk/broker/discovery.ts +8 -0
- package/src/sdk/broker/ensure.ts +156 -20
- package/src/sdk/broker/lifecycle-ledger.ts +110 -12
- package/src/sdk/broker/lifecycle.ts +334 -348
- package/src/sdk/broker/runtime.ts +5 -1
- package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
- package/src/sdk/broker/transport.ts +5 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
- package/src/sdk/bus/index.ts +3 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/bus/telegram-daemon-control.ts +15 -12
- package/src/sdk/bus/telegram-daemon.ts +38 -3
- package/src/sdk/cli/diagnostics-cli.ts +247 -0
- package/src/sdk/diagnostics/observe-broker.ts +654 -0
- package/src/sdk/host/control/dispatch.ts +52 -4
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +6 -0
- package/src/sdk/host/session-runtime.test.ts +83 -4
- package/src/sdk/host/session-runtime.ts +15 -7
- package/src/sdk/index.ts +1 -0
- package/src/sdk/lifecycle-session.ts +1 -0
- package/src/sdk/mcp/server.ts +15 -3
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/router/session-router.ts +26 -3
- package/src/sdk/session.ts +51 -8
- package/src/session/agent-session.ts +169 -43
- package/src/session/blob-store.ts +78 -33
- package/src/session/internal/managed-session-storage.ts +203 -4
- package/src/session/internal/task-artifact-owner-access.ts +229 -0
- package/src/session/internal/task-artifact-owner-transcript.ts +68 -0
- package/src/session/session-retirement.ts +155 -0
- package/src/session/session-storage.ts +1 -160
- package/src/session/session-transcript-header.ts +17 -0
- package/src/session/task-artifact-owner-codec.ts +578 -0
- package/src/session/task-artifact-owner-retirement.ts +475 -0
- package/src/session/task-artifact-owner.ts +289 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +19 -1
- package/src/setup/hermes-setup.ts +4 -1
- package/src/slash-commands/builtin-registry.ts +8 -5
- package/src/tools/ask-contract.test.ts +76 -1
- package/src/tools/ask-contract.ts +57 -6
- package/src/tools/ask.ts +16 -7
- package/src/tools/bash.ts +38 -29
- package/src/tools/descriptors.test.ts +29 -0
- package/src/tools/tool-catalog.generated.ts +7 -4
- package/src/web/search/index.ts +2 -2
|
@@ -1667,9 +1667,9 @@ export async function reapDeadLifecycleMarkers(
|
|
|
1667
1667
|
let inspected = 0;
|
|
1668
1668
|
for await (const entry of directoryHandle) {
|
|
1669
1669
|
if (inspected >= inspectionLimit) break;
|
|
1670
|
-
inspected += 1;
|
|
1671
1670
|
const name = entry.name;
|
|
1672
1671
|
if (!entry.isFile() || !name.endsWith(".lifecycle.json")) continue;
|
|
1672
|
+
inspected += 1;
|
|
1673
1673
|
const id = name.slice(0, -".lifecycle.json".length);
|
|
1674
1674
|
if (!isCanonicalSessionId(id)) continue;
|
|
1675
1675
|
const markerPath = path.join(directory, name);
|
|
@@ -1741,6 +1741,108 @@ export async function reapDeadLifecycleMarkers(
|
|
|
1741
1741
|
return reaped;
|
|
1742
1742
|
}
|
|
1743
1743
|
|
|
1744
|
+
/**
|
|
1745
|
+
* Retires one launch id's lifecycle marker pair after its recorded owner has
|
|
1746
|
+
* exited. Unlike the bounded background sweep, this launch-local repair is
|
|
1747
|
+
* not age-gated and does not require the ready sibling to contain the same
|
|
1748
|
+
* effect marker: the primary marker is the owner authority for this id.
|
|
1749
|
+
*/
|
|
1750
|
+
type RetireExitedLifecycleMarkerPairHook = () => void | Promise<void>;
|
|
1751
|
+
|
|
1752
|
+
async function retireExitedLifecycleMarkerPairImpl(
|
|
1753
|
+
root: string,
|
|
1754
|
+
id: string,
|
|
1755
|
+
afterObservation?: RetireExitedLifecycleMarkerPairHook,
|
|
1756
|
+
): Promise<boolean> {
|
|
1757
|
+
if (!isCanonicalSessionId(id)) return false;
|
|
1758
|
+
let directory: string;
|
|
1759
|
+
let directoryIdentity: { dev: bigint; ino: bigint };
|
|
1760
|
+
try {
|
|
1761
|
+
const canonicalRoot = fsSync.realpathSync(root);
|
|
1762
|
+
directory = path.join(canonicalRoot, "sdk");
|
|
1763
|
+
const directoryStat = fsSync.lstatSync(directory, { bigint: true });
|
|
1764
|
+
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) return false;
|
|
1765
|
+
directoryIdentity = { dev: directoryStat.dev, ino: directoryStat.ino };
|
|
1766
|
+
} catch {
|
|
1767
|
+
return false;
|
|
1768
|
+
}
|
|
1769
|
+
const markerPath = lifecycleMarkerPath(path.dirname(directory), id);
|
|
1770
|
+
const primary = captureLifecycleFile(markerPath, true, true);
|
|
1771
|
+
if (!primary) return false;
|
|
1772
|
+
let marker: EffectMarker;
|
|
1773
|
+
try {
|
|
1774
|
+
const parsed = parseLifecycleJson(primary.bytes);
|
|
1775
|
+
if (!isExactEffectMarker(parsed)) return false;
|
|
1776
|
+
marker = parsed;
|
|
1777
|
+
} catch {
|
|
1778
|
+
return false;
|
|
1779
|
+
}
|
|
1780
|
+
if (observeProcess(marker.pid, marker.incarnation) !== "exited") return false;
|
|
1781
|
+
await afterObservation?.();
|
|
1782
|
+
const readyPath = lifecycleReadyPath(path.dirname(directory), id);
|
|
1783
|
+
const ready = captureLifecycleFile(readyPath, true, true);
|
|
1784
|
+
try {
|
|
1785
|
+
const currentParent = lifecycleParentIdentity(directory);
|
|
1786
|
+
if (
|
|
1787
|
+
!currentParent ||
|
|
1788
|
+
BigInt(currentParent.dev) !== directoryIdentity.dev ||
|
|
1789
|
+
BigInt(currentParent.ino) !== directoryIdentity.ino
|
|
1790
|
+
)
|
|
1791
|
+
return false;
|
|
1792
|
+
const currentPrimary = captureLifecycleFile(markerPath, true, true);
|
|
1793
|
+
await afterObservation?.();
|
|
1794
|
+
if (
|
|
1795
|
+
!currentPrimary ||
|
|
1796
|
+
!sameLifecycleCleanupIdentity(
|
|
1797
|
+
currentPrimary.identity,
|
|
1798
|
+
serializeCleanupIdentity({ ...primary.identity, size: Number(primary.identity.size) }),
|
|
1799
|
+
)
|
|
1800
|
+
)
|
|
1801
|
+
return false;
|
|
1802
|
+
const currentMarker = parseLifecycleJson(currentPrimary.bytes);
|
|
1803
|
+
if (!isExactEffectMarker(currentMarker) || !sameEffectMarker(currentMarker, marker)) return false;
|
|
1804
|
+
if (ready) {
|
|
1805
|
+
const currentReady = captureLifecycleFile(readyPath, true, true);
|
|
1806
|
+
if (
|
|
1807
|
+
!currentReady ||
|
|
1808
|
+
!sameLifecycleCleanupIdentity(
|
|
1809
|
+
currentReady.identity,
|
|
1810
|
+
serializeCleanupIdentity({ ...ready.identity, size: Number(ready.identity.size) }),
|
|
1811
|
+
)
|
|
1812
|
+
)
|
|
1813
|
+
return false;
|
|
1814
|
+
const removedReady = nativeLifecycle().exactUnlinkDirect(readyPath, {
|
|
1815
|
+
...ready.identity,
|
|
1816
|
+
parentDev: directoryIdentity.dev,
|
|
1817
|
+
parentIno: directoryIdentity.ino,
|
|
1818
|
+
quarantineName: `.gjc-reap-${randomUUID()}-${path.basename(readyPath)}`,
|
|
1819
|
+
});
|
|
1820
|
+
if (!removedReady.ok && removedReady.code !== "not_found") return false;
|
|
1821
|
+
}
|
|
1822
|
+
const removedMarker = nativeLifecycle().exactUnlinkDirect(markerPath, {
|
|
1823
|
+
...primary.identity,
|
|
1824
|
+
parentDev: directoryIdentity.dev,
|
|
1825
|
+
parentIno: directoryIdentity.ino,
|
|
1826
|
+
quarantineName: `.gjc-reap-${randomUUID()}-${path.basename(markerPath)}`,
|
|
1827
|
+
});
|
|
1828
|
+
return removedMarker.ok || removedMarker.code === "not_found";
|
|
1829
|
+
} catch {
|
|
1830
|
+
return false;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
export async function retireExitedLifecycleMarkerPair(root: string, id: string): Promise<boolean> {
|
|
1835
|
+
return retireExitedLifecycleMarkerPairImpl(root, id);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
export async function retireExitedLifecycleMarkerPairForTest(
|
|
1839
|
+
root: string,
|
|
1840
|
+
id: string,
|
|
1841
|
+
afterObservation: RetireExitedLifecycleMarkerPairHook,
|
|
1842
|
+
): Promise<boolean> {
|
|
1843
|
+
return retireExitedLifecycleMarkerPairImpl(root, id, afterObservation);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1744
1846
|
export async function writeEffectMarker(root: string, id: string, marker: EffectMarker): Promise<void> {
|
|
1745
1847
|
const directory = path.join(root, "sdk");
|
|
1746
1848
|
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
@@ -4833,7 +4935,6 @@ async function terminateSpawnedChild(
|
|
|
4833
4935
|
terminationStartDeadlineAt: number,
|
|
4834
4936
|
expected: EffectMarker | undefined,
|
|
4835
4937
|
timing: LifecycleTiming,
|
|
4836
|
-
reapUnregisteredCutoff = false,
|
|
4837
4938
|
): Promise<boolean> {
|
|
4838
4939
|
const pid = child.pid;
|
|
4839
4940
|
if (!pid || (expected && pid !== expected.pid)) return false;
|
|
@@ -4847,226 +4948,100 @@ async function terminateSpawnedChild(
|
|
|
4847
4948
|
await broker.index.refresh();
|
|
4848
4949
|
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
4849
4950
|
const registered = expected ? broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker) : false;
|
|
4850
|
-
//
|
|
4851
|
-
//
|
|
4852
|
-
|
|
4853
|
-
const processExitDeadlineAt = expected && !registered ? deadline -
|
|
4951
|
+
// Keep one poll interval inside the lifecycle deadline for the final exact
|
|
4952
|
+
// cleanup proof. Registered sessions retain the original deadline partition;
|
|
4953
|
+
// only an unregistered child needs this extra bounded margin.
|
|
4954
|
+
const processExitDeadlineAt = expected && !registered ? deadline - POLL_MS : deadline;
|
|
4854
4955
|
const unregisteredTerminationDeadlineAt =
|
|
4855
4956
|
processExitDeadlineAt - Math.max(POLL_MS, Math.floor((processExitDeadlineAt - terminationStartDeadlineAt) / 2));
|
|
4856
4957
|
const observe = (): ProcessObservation =>
|
|
4857
|
-
child.exitCode !== null
|
|
4958
|
+
child.exitCode !== null
|
|
4858
4959
|
? "exited"
|
|
4859
4960
|
: observeProcess(pid, incarnation, value => processIncarnationForBroker(broker, value));
|
|
4860
|
-
|
|
4861
|
-
const ownedExit = Promise.withResolvers<void>();
|
|
4862
|
-
const observeOwnedExit = (): void => {
|
|
4863
|
-
ownedExitObserved = true;
|
|
4864
|
-
ownedExit.resolve();
|
|
4865
|
-
};
|
|
4866
|
-
child.once("exit", observeOwnedExit);
|
|
4867
|
-
const cleanupOwnedExitListeners = (): void => {
|
|
4868
|
-
child.removeListener("exit", observeOwnedExit);
|
|
4869
|
-
};
|
|
4870
|
-
try {
|
|
4871
|
-
const waitForExit = async (until: number): Promise<ProcessObservation> => {
|
|
4872
|
-
let observation = observe();
|
|
4873
|
-
while (observation !== "exited" && timing.now() < until) {
|
|
4874
|
-
await Promise.race([ownedExit.promise, timing.sleep(Math.max(0, Math.min(POLL_MS, until - timing.now())))]);
|
|
4875
|
-
if (ownedExitObserved) observation = "exited";
|
|
4876
|
-
else observation = observe();
|
|
4877
|
-
}
|
|
4878
|
-
|
|
4879
|
-
return observation;
|
|
4880
|
-
};
|
|
4881
|
-
|
|
4961
|
+
const waitForExit = async (until: number): Promise<ProcessObservation> => {
|
|
4882
4962
|
let observation = observe();
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
// This direct ChildProcess is owned by this broker invocation. A process-incarnation
|
|
4886
|
-
// read can briefly lag its exit event, so recheck only this owned child before failing.
|
|
4887
|
-
observation = await waitForExit(processExitDeadlineAt);
|
|
4888
|
-
};
|
|
4889
|
-
if (observation === "alive") {
|
|
4890
|
-
if (expected && !registered) {
|
|
4891
|
-
await waitUntil(timing, terminationStartDeadlineAt);
|
|
4892
|
-
// A child with a published cutoff receipt owns its rollback. Without one,
|
|
4893
|
-
// waiting for a blocked module to publish is wasted signal/exit time.
|
|
4894
|
-
const failure = await readLifecycleFailureArtifact(
|
|
4895
|
-
lifecycleFailurePath(root, id, expected.effectMarker),
|
|
4896
|
-
expected,
|
|
4897
|
-
);
|
|
4898
|
-
if (failure) await waitUntil(timing, unregisteredTerminationDeadlineAt);
|
|
4899
|
-
} else {
|
|
4900
|
-
await waitUntil(timing, terminationStartDeadlineAt);
|
|
4901
|
-
}
|
|
4963
|
+
while (observation !== "exited" && timing.now() < until) {
|
|
4964
|
+
await timing.sleep(Math.max(0, Math.min(POLL_MS, until - timing.now())));
|
|
4902
4965
|
observation = observe();
|
|
4903
4966
|
}
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4967
|
+
|
|
4968
|
+
return observation;
|
|
4969
|
+
};
|
|
4970
|
+
|
|
4971
|
+
let observation = observe();
|
|
4972
|
+
const recheckOwnedExitObservation = async (): Promise<void> => {
|
|
4973
|
+
if (observation !== "uncertain") return;
|
|
4974
|
+
// This direct ChildProcess is owned by this broker invocation. A process-incarnation
|
|
4975
|
+
// read can briefly lag its exit event, so recheck only this owned child before failing.
|
|
4976
|
+
observation = await waitForExit(processExitDeadlineAt);
|
|
4977
|
+
};
|
|
4978
|
+
if (observation === "alive") {
|
|
4979
|
+
if (expected && !registered) {
|
|
4980
|
+
// A child that has not registered yet owns the cutoff receipt. Give it
|
|
4981
|
+
// the bounded pre-registration window to publish that proof, but reserve
|
|
4982
|
+
// the final proof interval for post-signal observation inside the request
|
|
4983
|
+
// deadline. A valid receipt does not interrupt the child's own rollback.
|
|
4984
|
+
while (timing.now() < unregisteredTerminationDeadlineAt) {
|
|
4985
|
+
if (await readLifecycleFailureArtifact(lifecycleFailurePath(root, id, expected.effectMarker), expected))
|
|
4986
|
+
await waitUntil(timing, unregisteredTerminationDeadlineAt);
|
|
4987
|
+
else await timing.sleep(Math.max(0, Math.min(POLL_MS, unregisteredTerminationDeadlineAt - timing.now())));
|
|
4922
4988
|
}
|
|
4989
|
+
} else {
|
|
4990
|
+
await waitUntil(timing, terminationStartDeadlineAt);
|
|
4923
4991
|
}
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
await
|
|
4932
|
-
|
|
4933
|
-
await recordTerminalUncertain(broker, id, root, pid);
|
|
4934
|
-
return false;
|
|
4935
|
-
}
|
|
4936
|
-
} else {
|
|
4937
|
-
observation = await waitForExit(processExitDeadlineAt);
|
|
4992
|
+
observation = observe();
|
|
4993
|
+
}
|
|
4994
|
+
if (observation === "alive") {
|
|
4995
|
+
if (!(await signalVerifiedSession({ locator: { stateRoot: root }, pid }, id, "SIGTERM", expected))) {
|
|
4996
|
+
observation = observe();
|
|
4997
|
+
await recheckOwnedExitObservation();
|
|
4998
|
+
if (observation !== "exited") {
|
|
4999
|
+
await recordTerminalUncertain(broker, id, root, pid);
|
|
5000
|
+
return false;
|
|
4938
5001
|
}
|
|
5002
|
+
} else {
|
|
5003
|
+
const remaining = Math.max(0, processExitDeadlineAt - timing.now());
|
|
5004
|
+
const gracefulDeadline = timing.now() + Math.min(CLOSE_TIMEOUT_MS, Math.floor(remaining / 2));
|
|
5005
|
+
observation = await waitForExit(gracefulDeadline);
|
|
4939
5006
|
}
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
4949
|
-
const rollbackComplete =
|
|
4950
|
-
failure?.artifact.rollback.fenced === true &&
|
|
4951
|
-
failure.artifact.rollback.runtimeRemoved &&
|
|
4952
|
-
failure.artifact.rollback.hostStopped &&
|
|
4953
|
-
failure.artifact.rollback.brokerRegistrationReleased;
|
|
4954
|
-
if (failure && expected && !rollbackComplete) {
|
|
4955
|
-
await broker.index.refresh();
|
|
4956
|
-
const hasRegistration = broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker);
|
|
4957
|
-
if (!hasRegistration) {
|
|
4958
|
-
const artifactsRemoved = await removeOwnedLifecycleArtifacts(
|
|
4959
|
-
root,
|
|
4960
|
-
id,
|
|
4961
|
-
expected,
|
|
4962
|
-
undefined,
|
|
4963
|
-
undefined,
|
|
4964
|
-
proofBudget,
|
|
4965
|
-
);
|
|
4966
|
-
const endpointGone = await endpointRemoved(root, id);
|
|
4967
|
-
// A signal-delivery error can be emitted while the child still runs. Recheck
|
|
4968
|
-
// exact process absence after cleanup before reporting a proven reap.
|
|
4969
|
-
if (
|
|
4970
|
-
artifactsRemoved &&
|
|
4971
|
-
endpointGone &&
|
|
4972
|
-
(observe() === "exited" || ownedExitObserved) &&
|
|
4973
|
-
lifecycleProofWithinDeadline(proofBudget)
|
|
4974
|
-
)
|
|
4975
|
-
return true;
|
|
4976
|
-
return failClosed();
|
|
4977
|
-
}
|
|
5007
|
+
}
|
|
5008
|
+
if (observation === "alive") {
|
|
5009
|
+
if (!(await signalVerifiedSession({ locator: { stateRoot: root }, pid }, id, "SIGKILL", expected))) {
|
|
5010
|
+
observation = observe();
|
|
5011
|
+
await recheckOwnedExitObservation();
|
|
5012
|
+
if (observation !== "exited") {
|
|
5013
|
+
await recordTerminalUncertain(broker, id, root, pid);
|
|
5014
|
+
return false;
|
|
4978
5015
|
}
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
id,
|
|
5006
|
-
expected,
|
|
5007
|
-
undefined,
|
|
5008
|
-
undefined,
|
|
5009
|
-
proofBudget,
|
|
5010
|
-
);
|
|
5011
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5012
|
-
await broker.index.refresh();
|
|
5013
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5014
|
-
const registered = broker.index
|
|
5015
|
-
.listSessions()
|
|
5016
|
-
.sessions.find(session => session.sessionId === id && session.pid === pid);
|
|
5017
|
-
let registrationReleased =
|
|
5018
|
-
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker) ||
|
|
5019
|
-
registered?.terminal === true ||
|
|
5020
|
-
registered?.terminalUncertain === true;
|
|
5021
|
-
if (registered && !registrationReleased) {
|
|
5022
|
-
registrationReleased = await broker.index.unregisterIfCurrent(registered);
|
|
5023
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5024
|
-
await broker.index.refresh();
|
|
5025
|
-
registrationReleased =
|
|
5026
|
-
registrationReleased ||
|
|
5027
|
-
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker);
|
|
5028
|
-
}
|
|
5029
|
-
const stillExited =
|
|
5030
|
-
observeProcess(pid, expected.incarnation, value => processIncarnationForBroker(broker, value)) ===
|
|
5031
|
-
"exited";
|
|
5032
|
-
const endpointGone = await endpointRemoved(root, id);
|
|
5033
|
-
if (
|
|
5034
|
-
stillExited &&
|
|
5035
|
-
artifactsRemoved &&
|
|
5036
|
-
endpointGone &&
|
|
5037
|
-
registrationReleased &&
|
|
5038
|
-
registered?.terminalUncertain !== true &&
|
|
5039
|
-
lifecycleProofWithinDeadline(proofBudget)
|
|
5040
|
-
) {
|
|
5041
|
-
if (!reapUnregisteredCutoff) return true;
|
|
5042
|
-
try {
|
|
5043
|
-
await writeSessionLifecycleFailure(
|
|
5044
|
-
root,
|
|
5045
|
-
id,
|
|
5046
|
-
expected.effectMarker,
|
|
5047
|
-
{ phase: "startup", reason: "failed", message: `Session ${id} exited at readiness cutoff.` },
|
|
5048
|
-
{
|
|
5049
|
-
endpointGeneration: null,
|
|
5050
|
-
fenced: true,
|
|
5051
|
-
runtimeRemoved: true,
|
|
5052
|
-
hostStopped: true,
|
|
5053
|
-
brokerRegistrationReleased: true,
|
|
5054
|
-
},
|
|
5055
|
-
undefined,
|
|
5056
|
-
expected.incarnation,
|
|
5057
|
-
expected.pid,
|
|
5058
|
-
);
|
|
5059
|
-
} catch {
|
|
5060
|
-
return failClosed();
|
|
5061
|
-
}
|
|
5062
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5063
|
-
return true;
|
|
5064
|
-
}
|
|
5065
|
-
return failClosed();
|
|
5066
|
-
}
|
|
5016
|
+
} else {
|
|
5017
|
+
observation = await waitForExit(processExitDeadlineAt);
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
await recheckOwnedExitObservation();
|
|
5021
|
+
if (observation !== "exited" || !lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5022
|
+
let rollbackGeneration: number | null | undefined;
|
|
5023
|
+
if (expected) {
|
|
5024
|
+
const failure = await readLifecycleFailureArtifact(
|
|
5025
|
+
lifecycleFailurePath(root, id, expected.effectMarker),
|
|
5026
|
+
expected,
|
|
5027
|
+
);
|
|
5028
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5029
|
+
const rollbackComplete =
|
|
5030
|
+
failure?.artifact.rollback.fenced === true &&
|
|
5031
|
+
failure.artifact.rollback.runtimeRemoved &&
|
|
5032
|
+
failure.artifact.rollback.hostStopped &&
|
|
5033
|
+
failure.artifact.rollback.brokerRegistrationReleased;
|
|
5034
|
+
if (!rollbackComplete) {
|
|
5035
|
+
if (!readyThenExitToleranceEnabled()) {
|
|
5036
|
+
// A child that exited with a non-zero status before publishing any
|
|
5037
|
+
// lifecycle receipt has no durable rollback to wait for. Prove the
|
|
5038
|
+
// owned artifacts are gone and release its registration, but retain
|
|
5039
|
+
// terminal uncertainty for timeouts, clean exits, and child-authored
|
|
5040
|
+
// startup receipts whose rollback is incomplete.
|
|
5041
|
+
if (failure || child.exitCode === null || child.exitCode === 0) return failClosed();
|
|
5067
5042
|
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5068
5043
|
const publishedReady = (await probePublishedReadyAuthority(root, id, expected)).kind === "matched";
|
|
5069
|
-
if (
|
|
5044
|
+
if (publishedReady) return failClosed();
|
|
5070
5045
|
const artifactsRemoved = await removeOwnedLifecycleArtifacts(
|
|
5071
5046
|
root,
|
|
5072
5047
|
id,
|
|
@@ -5081,50 +5056,21 @@ async function terminateSpawnedChild(
|
|
|
5081
5056
|
const registered = broker.index
|
|
5082
5057
|
.listSessions()
|
|
5083
5058
|
.sessions.find(session => session.sessionId === id && session.pid === pid);
|
|
5084
|
-
const registeredRowTerminal = registered?.terminal === true || registered?.terminalUncertain === true;
|
|
5085
5059
|
let registrationReleased =
|
|
5086
|
-
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker) ||
|
|
5060
|
+
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker) ||
|
|
5061
|
+
registered?.terminal === true ||
|
|
5062
|
+
registered?.terminalUncertain === true;
|
|
5087
5063
|
if (registered && !registrationReleased) {
|
|
5088
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5089
5064
|
registrationReleased = await broker.index.unregisterIfCurrent(registered);
|
|
5090
5065
|
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5091
5066
|
await broker.index.refresh();
|
|
5092
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5093
5067
|
registrationReleased =
|
|
5094
5068
|
registrationReleased || !broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker);
|
|
5095
5069
|
}
|
|
5096
5070
|
const stillExited =
|
|
5097
5071
|
observeProcess(pid, expected.incarnation, value => processIncarnationForBroker(broker, value)) ===
|
|
5098
5072
|
"exited";
|
|
5099
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5100
5073
|
const endpointGone = await endpointRemoved(root, id);
|
|
5101
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5102
|
-
try {
|
|
5103
|
-
if (publishedReady && !failure) {
|
|
5104
|
-
await writeSessionLifecycleFailure(
|
|
5105
|
-
root,
|
|
5106
|
-
id,
|
|
5107
|
-
expected.effectMarker,
|
|
5108
|
-
{
|
|
5109
|
-
phase: "startup",
|
|
5110
|
-
reason: "failed",
|
|
5111
|
-
message: `Session ${id} ${READY_THEN_EXIT_MESSAGE}.`,
|
|
5112
|
-
},
|
|
5113
|
-
{
|
|
5114
|
-
endpointGeneration: registered?.endpointGeneration ?? null,
|
|
5115
|
-
fenced: stillExited && registrationReleased,
|
|
5116
|
-
runtimeRemoved: artifactsRemoved && endpointGone,
|
|
5117
|
-
hostStopped: stillExited,
|
|
5118
|
-
brokerRegistrationReleased: registrationReleased,
|
|
5119
|
-
},
|
|
5120
|
-
undefined,
|
|
5121
|
-
expected.incarnation,
|
|
5122
|
-
expected.pid,
|
|
5123
|
-
);
|
|
5124
|
-
}
|
|
5125
|
-
} catch {
|
|
5126
|
-
// A missing broker-authored receipt must not hide a proven dead child.
|
|
5127
|
-
}
|
|
5128
5074
|
if (
|
|
5129
5075
|
stillExited &&
|
|
5130
5076
|
artifactsRemoved &&
|
|
@@ -5135,35 +5081,102 @@ async function terminateSpawnedChild(
|
|
|
5135
5081
|
return true;
|
|
5136
5082
|
return failClosed();
|
|
5137
5083
|
}
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5084
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5085
|
+
const publishedReady = (await probePublishedReadyAuthority(root, id, expected)).kind === "matched";
|
|
5086
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5087
|
+
const artifactsRemoved = await removeOwnedLifecycleArtifacts(
|
|
5088
|
+
root,
|
|
5089
|
+
id,
|
|
5090
|
+
expected,
|
|
5091
|
+
undefined,
|
|
5092
|
+
undefined,
|
|
5093
|
+
proofBudget,
|
|
5094
|
+
);
|
|
5095
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5096
|
+
await broker.index.refresh();
|
|
5097
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5098
|
+
const registered = broker.index
|
|
5099
|
+
.listSessions()
|
|
5100
|
+
.sessions.find(session => session.sessionId === id && session.pid === pid);
|
|
5101
|
+
const registeredRowTerminal = registered?.terminal === true || registered?.terminalUncertain === true;
|
|
5102
|
+
let registrationReleased =
|
|
5103
|
+
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker) || registeredRowTerminal;
|
|
5104
|
+
if (registered && !registrationReleased) {
|
|
5105
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5106
|
+
registrationReleased = await broker.index.unregisterIfCurrent(registered);
|
|
5107
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5108
|
+
await broker.index.refresh();
|
|
5109
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5110
|
+
registrationReleased =
|
|
5111
|
+
registrationReleased || !broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker);
|
|
5112
|
+
}
|
|
5113
|
+
const stillExited =
|
|
5114
|
+
observeProcess(pid, expected.incarnation, value => processIncarnationForBroker(broker, value)) === "exited";
|
|
5115
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5116
|
+
const endpointGone = await endpointRemoved(root, id);
|
|
5117
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5118
|
+
try {
|
|
5119
|
+
if (publishedReady && !failure) {
|
|
5120
|
+
await writeSessionLifecycleFailure(
|
|
5121
|
+
root,
|
|
5122
|
+
id,
|
|
5123
|
+
expected.effectMarker,
|
|
5124
|
+
{
|
|
5125
|
+
phase: "startup",
|
|
5126
|
+
reason: "failed",
|
|
5127
|
+
message: `Session ${id} ${READY_THEN_EXIT_MESSAGE}.`,
|
|
5128
|
+
},
|
|
5129
|
+
{
|
|
5130
|
+
endpointGeneration: registered?.endpointGeneration ?? null,
|
|
5131
|
+
fenced: stillExited && registrationReleased,
|
|
5132
|
+
runtimeRemoved: artifactsRemoved && endpointGone,
|
|
5133
|
+
hostStopped: stillExited,
|
|
5134
|
+
brokerRegistrationReleased: registrationReleased,
|
|
5135
|
+
},
|
|
5136
|
+
undefined,
|
|
5137
|
+
expected.incarnation,
|
|
5138
|
+
expected.pid,
|
|
5139
|
+
);
|
|
5140
|
+
}
|
|
5141
|
+
} catch {
|
|
5142
|
+
// A missing broker-authored receipt must not hide a proven dead child.
|
|
5143
|
+
}
|
|
5144
|
+
if (
|
|
5145
|
+
stillExited &&
|
|
5146
|
+
artifactsRemoved &&
|
|
5147
|
+
endpointGone &&
|
|
5148
|
+
registrationReleased &&
|
|
5149
|
+
lifecycleProofWithinDeadline(proofBudget)
|
|
5150
|
+
)
|
|
5151
|
+
return true;
|
|
5159
5152
|
return failClosed();
|
|
5160
5153
|
}
|
|
5161
|
-
|
|
5162
|
-
const endpointGone = await endpointRemoved(root, id);
|
|
5163
|
-
return endpointGone && lifecycleProofWithinDeadline(proofBudget) ? true : failClosed();
|
|
5164
|
-
} finally {
|
|
5165
|
-
cleanupOwnedExitListeners();
|
|
5154
|
+
rollbackGeneration = failure.artifact.rollback.endpointGeneration;
|
|
5166
5155
|
}
|
|
5156
|
+
if (expected && !(await removeOwnedLifecycleArtifacts(root, id, expected, undefined, undefined, proofBudget)))
|
|
5157
|
+
return failClosed();
|
|
5158
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5159
|
+
await broker.index.refresh();
|
|
5160
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5161
|
+
if (
|
|
5162
|
+
rollbackGeneration === null &&
|
|
5163
|
+
expected &&
|
|
5164
|
+
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker)
|
|
5165
|
+
)
|
|
5166
|
+
return lifecycleProofWithinDeadline(proofBudget) ? true : failClosed();
|
|
5167
|
+
const registeredBeforeTermination =
|
|
5168
|
+
rollbackGeneration === undefined || rollbackGeneration === null
|
|
5169
|
+
? undefined
|
|
5170
|
+
: broker.index.findHostRegistration(id, rollbackGeneration, pid, expected?.effectMarker);
|
|
5171
|
+
const unregistered = registeredBeforeTermination
|
|
5172
|
+
? broker.index.hostUnregisteredAfter(registeredBeforeTermination)
|
|
5173
|
+
: undefined;
|
|
5174
|
+
if (!registeredBeforeTermination || !unregistered) {
|
|
5175
|
+
return failClosed();
|
|
5176
|
+
}
|
|
5177
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5178
|
+
const endpointGone = await endpointRemoved(root, id);
|
|
5179
|
+
return endpointGone && lifecycleProofWithinDeadline(proofBudget) ? true : failClosed();
|
|
5167
5180
|
}
|
|
5168
5181
|
|
|
5169
5182
|
async function signalVerifiedSession(
|
|
@@ -5481,12 +5494,11 @@ async function currentReadyAuthority(
|
|
|
5481
5494
|
token?: unknown;
|
|
5482
5495
|
pid?: unknown;
|
|
5483
5496
|
};
|
|
5484
|
-
// Native-alive owned readiness is the admission authority.
|
|
5485
|
-
//
|
|
5486
|
-
//
|
|
5487
|
-
//
|
|
5488
|
-
await broker.
|
|
5489
|
-
await broker.index.refresh();
|
|
5497
|
+
// Native-alive owned readiness is the admission authority. The indexed
|
|
5498
|
+
// projection can lag a just-registered detached Windows host, so the
|
|
5499
|
+
// change-stamp refresh is deliberately separate from heartbeat liveness.
|
|
5500
|
+
// Never admit a terminal/uncertain or native-dead child.
|
|
5501
|
+
await broker.index.refreshIfChanged();
|
|
5490
5502
|
const record = broker.index
|
|
5491
5503
|
.listSessions()
|
|
5492
5504
|
.sessions.find(
|
|
@@ -6739,6 +6751,7 @@ async function executeLifecycleResponse(
|
|
|
6739
6751
|
terminationStartDeadline = childDeadlines.terminationStartDeadlineAt;
|
|
6740
6752
|
lifecycleDeadline = childDeadlines.lifecycleCleanupDeadlineAt;
|
|
6741
6753
|
}
|
|
6754
|
+
await retireExitedLifecycleMarkerPair(launch.root, launch.id);
|
|
6742
6755
|
await reapDeadLifecycleMarkers(launch.root);
|
|
6743
6756
|
} catch (error) {
|
|
6744
6757
|
if (launch.worktreePlan && worktreeReceipt?.created && !worktreeReceipt.reused) {
|
|
@@ -6993,7 +7006,6 @@ async function executeLifecycleResponse(
|
|
|
6993
7006
|
knownSecrets,
|
|
6994
7007
|
)
|
|
6995
7008
|
: undefined;
|
|
6996
|
-
const ledgerUncertain = broker.ledger.get(identity)?.state === "terminal_uncertain";
|
|
6997
7009
|
const terminated = await terminateSpawnedChild(
|
|
6998
7010
|
child,
|
|
6999
7011
|
broker,
|
|
@@ -7003,8 +7015,8 @@ async function executeLifecycleResponse(
|
|
|
7003
7015
|
terminationStartDeadline,
|
|
7004
7016
|
spawnedAuthority,
|
|
7005
7017
|
timing,
|
|
7006
|
-
readiness.kind === "timeout",
|
|
7007
7018
|
);
|
|
7019
|
+
|
|
7008
7020
|
if (!terminated)
|
|
7009
7021
|
return readiness.kind === "ready_probe_failed"
|
|
7010
7022
|
? fail(
|
|
@@ -7015,11 +7027,6 @@ async function executeLifecycleResponse(
|
|
|
7015
7027
|
"terminal_uncertain",
|
|
7016
7028
|
`Session ${launch.id} did not become ready and its spawned process could not be verified dead. ${diagnostic ?? "stage=readiness waiting_for=session_ready child=uncertain exit=not_observed signal=not_observed stderr=none"}`,
|
|
7017
7029
|
);
|
|
7018
|
-
if (ledgerUncertain)
|
|
7019
|
-
return fail(
|
|
7020
|
-
"terminal_uncertain",
|
|
7021
|
-
`Lifecycle startup cleanup could not be proven; retained artifacts require reconciliation. Original launch failure: Session ${launch.id} did not become ready and its spawned process could not be verified dead.`,
|
|
7022
|
-
);
|
|
7023
7030
|
return readiness.kind === "startup_failed"
|
|
7024
7031
|
? fail(
|
|
7025
7032
|
readiness.failure.code ?? "spawn_failed",
|
|
@@ -8016,7 +8023,6 @@ async function exactCleanupProof(
|
|
|
8016
8023
|
evidence: { artifact: LifecycleFailureArtifact } | undefined,
|
|
8017
8024
|
proofBudget?: LifecycleProofBudget,
|
|
8018
8025
|
allowObservedWindowsReadyExit = false,
|
|
8019
|
-
allowObservedUnregisteredCutoff = false,
|
|
8020
8026
|
): Promise<LifecycleCleanupProof | undefined> {
|
|
8021
8027
|
const rollback = evidence?.artifact.rollback;
|
|
8022
8028
|
const durableRollbackComplete =
|
|
@@ -8024,16 +8030,6 @@ async function exactCleanupProof(
|
|
|
8024
8030
|
rollback.runtimeRemoved &&
|
|
8025
8031
|
rollback.hostStopped &&
|
|
8026
8032
|
rollback.brokerRegistrationReleased;
|
|
8027
|
-
// An owned child reaped before registration cannot finish its cutoff receipt.
|
|
8028
|
-
// The broker may retire that receipt only after independently proving process,
|
|
8029
|
-
// endpoint, and registration absence through this exact cleanup transaction.
|
|
8030
|
-
const observedUnregisteredCutoff =
|
|
8031
|
-
allowObservedUnregisteredCutoff &&
|
|
8032
|
-
rollback?.endpointGeneration === null &&
|
|
8033
|
-
rollback.fenced === false &&
|
|
8034
|
-
rollback.runtimeRemoved === false &&
|
|
8035
|
-
rollback.hostStopped === false &&
|
|
8036
|
-
rollback.brokerRegistrationReleased === false;
|
|
8037
8033
|
// Windows deliberately keeps the failure receipt incomplete because its
|
|
8038
8034
|
// directory-entry publication cannot be flushed. A broker-observed
|
|
8039
8035
|
// ready-then-exit can still authorize the separate ledger-backed exact
|
|
@@ -8051,7 +8047,7 @@ async function exactCleanupProof(
|
|
|
8051
8047
|
!root ||
|
|
8052
8048
|
!id ||
|
|
8053
8049
|
!expected ||
|
|
8054
|
-
(!durableRollbackComplete && !observedWindowsReadyExit
|
|
8050
|
+
(!durableRollbackComplete && !observedWindowsReadyExit) ||
|
|
8055
8051
|
observeProcess(expected.pid, expected.incarnation, value => processIncarnationForBroker(broker, value)) !==
|
|
8056
8052
|
"exited"
|
|
8057
8053
|
)
|
|
@@ -8266,28 +8262,28 @@ export async function executeLifecycle(
|
|
|
8266
8262
|
expected,
|
|
8267
8263
|
evidence,
|
|
8268
8264
|
proofBudget,
|
|
8269
|
-
!response.ok
|
|
8270
|
-
!response.ok && response.error.code === "readiness_timeout",
|
|
8265
|
+
!response.ok ? response.error.code === "ready_then_exited" : false,
|
|
8271
8266
|
);
|
|
8272
|
-
const startupFailure: LifecycleStartupFailureReceipt | undefined =
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8267
|
+
const startupFailure: LifecycleStartupFailureReceipt | undefined =
|
|
8268
|
+
evidence && !response.ok
|
|
8269
|
+
? {
|
|
8270
|
+
artifactDigest: evidence.digest,
|
|
8271
|
+
message: evidence.artifact.message,
|
|
8272
|
+
phase: evidence.artifact.phase,
|
|
8273
|
+
reason: evidence.artifact.reason,
|
|
8274
|
+
...(evidence.artifact.code === undefined
|
|
8275
|
+
? {}
|
|
8276
|
+
: { code: evidence.artifact.code, details: evidence.artifact.details }),
|
|
8277
|
+
rollback: {
|
|
8278
|
+
endpointGeneration: evidence.artifact.rollback.endpointGeneration,
|
|
8279
|
+
fenced: evidence.artifact.rollback.fenced,
|
|
8280
|
+
runtimeRemoved: evidence.artifact.rollback.runtimeRemoved,
|
|
8281
|
+
hostStopped: evidence.artifact.rollback.hostStopped,
|
|
8282
|
+
brokerRegistrationReleased: evidence.artifact.rollback.brokerRegistrationReleased,
|
|
8283
|
+
},
|
|
8284
|
+
...(cleanupProof ? { cleanupProof } : {}),
|
|
8285
|
+
}
|
|
8286
|
+
: undefined;
|
|
8291
8287
|
const durableEffectsBody: Omit<LifecycleDurableEffectsReceipt, "digest"> = {
|
|
8292
8288
|
...(priorDurableEffects?.worktree ? { worktree: priorDurableEffects.worktree } : {}),
|
|
8293
8289
|
...(evidence?.artifact.transcript
|
|
@@ -8367,54 +8363,44 @@ export async function executeLifecycle(
|
|
|
8367
8363
|
? response
|
|
8368
8364
|
: response.error.code === "endpoint_unreadable"
|
|
8369
8365
|
? response
|
|
8370
|
-
:
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
lifecycleCleanupResponse.error.code === "spawn_failed"
|
|
8366
|
+
: // An unreadable/corrupt endpoint is already the fail-closed honest
|
|
8367
|
+
// terminal classification (it names the artifact, not the
|
|
8368
|
+
// child); the reconciliation wrapper must not erase it.
|
|
8369
|
+
response.error.code === "ready_then_exited" && (cleanupProof || provenDeadCleanup)
|
|
8375
8370
|
? {
|
|
8376
8371
|
...response,
|
|
8377
8372
|
...(durableEffects ? { durableEffects } : {}),
|
|
8378
8373
|
...(startupFailure ? { startupFailure } : {}),
|
|
8379
8374
|
}
|
|
8380
|
-
:
|
|
8381
|
-
// terminal classification (it names the artifact, not the
|
|
8382
|
-
// child); the reconciliation wrapper must not erase it.
|
|
8383
|
-
response.error.code === "ready_then_exited" && (cleanupProof || provenDeadCleanup)
|
|
8375
|
+
: startupFailure && cleanupProof
|
|
8384
8376
|
? {
|
|
8385
|
-
|
|
8377
|
+
ok: false,
|
|
8378
|
+
error: startupFailure.code
|
|
8379
|
+
? {
|
|
8380
|
+
code: startupFailure.code,
|
|
8381
|
+
message: startupFailure.message,
|
|
8382
|
+
details: startupFailure.details!,
|
|
8383
|
+
endpoint: "unavailable" as const,
|
|
8384
|
+
}
|
|
8385
|
+
: {
|
|
8386
|
+
code: "spawn_failed",
|
|
8387
|
+
message: startupFailure.message,
|
|
8388
|
+
endpoint: "unavailable" as const,
|
|
8389
|
+
},
|
|
8386
8390
|
...(durableEffects ? { durableEffects } : {}),
|
|
8387
|
-
|
|
8391
|
+
startupFailure,
|
|
8388
8392
|
}
|
|
8389
|
-
:
|
|
8390
|
-
?
|
|
8393
|
+
: provenDeadCleanup && response.error.code !== "terminal_uncertain"
|
|
8394
|
+
? response
|
|
8395
|
+
: {
|
|
8391
8396
|
ok: false,
|
|
8392
|
-
error:
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
details: startupFailure.details!,
|
|
8397
|
-
endpoint: "unavailable" as const,
|
|
8398
|
-
}
|
|
8399
|
-
: {
|
|
8400
|
-
code: "spawn_failed",
|
|
8401
|
-
message: startupFailure.message,
|
|
8402
|
-
endpoint: "unavailable" as const,
|
|
8403
|
-
},
|
|
8397
|
+
error: {
|
|
8398
|
+
code: "terminal_uncertain",
|
|
8399
|
+
message: terminalUncertainStartupMessage(response),
|
|
8400
|
+
},
|
|
8404
8401
|
...(durableEffects ? { durableEffects } : {}),
|
|
8405
|
-
startupFailure,
|
|
8402
|
+
...(startupFailure ? { startupFailure } : {}),
|
|
8406
8403
|
}
|
|
8407
|
-
: provenDeadCleanup && response.error.code !== "terminal_uncertain"
|
|
8408
|
-
? response
|
|
8409
|
-
: {
|
|
8410
|
-
ok: false,
|
|
8411
|
-
error: {
|
|
8412
|
-
code: "terminal_uncertain",
|
|
8413
|
-
message: terminalUncertainStartupMessage(response),
|
|
8414
|
-
},
|
|
8415
|
-
...(durableEffects ? { durableEffects } : {}),
|
|
8416
|
-
...(startupFailure ? { startupFailure } : {}),
|
|
8417
|
-
}
|
|
8418
8404
|
: response;
|
|
8419
8405
|
// The extended proof window may reconcile artifacts after the original
|
|
8420
8406
|
// admission deadline, but cannot retroactively prove a timely spawn failure.
|