@gajae-code/coding-agent 0.18.5 → 0.18.6
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 +101 -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/coordinator-mcp/session-reaper.d.ts +5 -0
- 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/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/runtime.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +1 -0
- 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/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +13 -0
- package/dist/types/session/agent-session.d.ts +5 -1
- 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/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-ordinary.ts +197 -0
- package/src/cli.ts +25 -182
- package/src/commands/contribution-prep.ts +1 -3
- package/src/commands/sdk.ts +59 -0
- 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/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/edit/modes/replace.ts +6 -6
- package/src/edit/streaming.ts +15 -15
- package/src/exec/bash-shell-supervisor.ts +60 -3
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/memories/index.ts +8 -2
- package/src/modes/acp/acp-agent.ts +649 -367
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/prompts/tools/ask.md +2 -0
- package/src/sdk/broker/broker.ts +46 -0
- 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 +225 -341
- package/src/sdk/broker/runtime.ts +5 -1
- package/src/sdk/broker/transport.ts +5 -1
- 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 +34 -1
- 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/session-runtime.test.ts +40 -2
- package/src/sdk/host/session-runtime.ts +13 -5
- package/src/sdk/index.ts +1 -0
- package/src/sdk/lifecycle-session.ts +1 -0
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/router/session-router.ts +7 -1
- package/src/sdk/session.ts +37 -8
- package/src/session/agent-session.ts +114 -39
- package/src/session/blob-store.ts +78 -33
- package/src/session/internal/managed-session-storage.ts +96 -3
- 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
|
@@ -4833,7 +4833,6 @@ async function terminateSpawnedChild(
|
|
|
4833
4833
|
terminationStartDeadlineAt: number,
|
|
4834
4834
|
expected: EffectMarker | undefined,
|
|
4835
4835
|
timing: LifecycleTiming,
|
|
4836
|
-
reapUnregisteredCutoff = false,
|
|
4837
4836
|
): Promise<boolean> {
|
|
4838
4837
|
const pid = child.pid;
|
|
4839
4838
|
if (!pid || (expected && pid !== expected.pid)) return false;
|
|
@@ -4847,226 +4846,100 @@ async function terminateSpawnedChild(
|
|
|
4847
4846
|
await broker.index.refresh();
|
|
4848
4847
|
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
4849
4848
|
const registered = expected ? broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker) : false;
|
|
4850
|
-
//
|
|
4851
|
-
//
|
|
4852
|
-
|
|
4853
|
-
const processExitDeadlineAt = expected && !registered ? deadline -
|
|
4849
|
+
// Keep one poll interval inside the lifecycle deadline for the final exact
|
|
4850
|
+
// cleanup proof. Registered sessions retain the original deadline partition;
|
|
4851
|
+
// only an unregistered child needs this extra bounded margin.
|
|
4852
|
+
const processExitDeadlineAt = expected && !registered ? deadline - POLL_MS : deadline;
|
|
4854
4853
|
const unregisteredTerminationDeadlineAt =
|
|
4855
4854
|
processExitDeadlineAt - Math.max(POLL_MS, Math.floor((processExitDeadlineAt - terminationStartDeadlineAt) / 2));
|
|
4856
4855
|
const observe = (): ProcessObservation =>
|
|
4857
|
-
child.exitCode !== null
|
|
4856
|
+
child.exitCode !== null
|
|
4858
4857
|
? "exited"
|
|
4859
4858
|
: 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
|
-
|
|
4859
|
+
const waitForExit = async (until: number): Promise<ProcessObservation> => {
|
|
4882
4860
|
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
|
-
}
|
|
4861
|
+
while (observation !== "exited" && timing.now() < until) {
|
|
4862
|
+
await timing.sleep(Math.max(0, Math.min(POLL_MS, until - timing.now())));
|
|
4902
4863
|
observation = observe();
|
|
4903
4864
|
}
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4865
|
+
|
|
4866
|
+
return observation;
|
|
4867
|
+
};
|
|
4868
|
+
|
|
4869
|
+
let observation = observe();
|
|
4870
|
+
const recheckOwnedExitObservation = async (): Promise<void> => {
|
|
4871
|
+
if (observation !== "uncertain") return;
|
|
4872
|
+
// This direct ChildProcess is owned by this broker invocation. A process-incarnation
|
|
4873
|
+
// read can briefly lag its exit event, so recheck only this owned child before failing.
|
|
4874
|
+
observation = await waitForExit(processExitDeadlineAt);
|
|
4875
|
+
};
|
|
4876
|
+
if (observation === "alive") {
|
|
4877
|
+
if (expected && !registered) {
|
|
4878
|
+
// A child that has not registered yet owns the cutoff receipt. Give it
|
|
4879
|
+
// the bounded pre-registration window to publish that proof, but reserve
|
|
4880
|
+
// the final proof interval for post-signal observation inside the request
|
|
4881
|
+
// deadline. A valid receipt does not interrupt the child's own rollback.
|
|
4882
|
+
while (timing.now() < unregisteredTerminationDeadlineAt) {
|
|
4883
|
+
if (await readLifecycleFailureArtifact(lifecycleFailurePath(root, id, expected.effectMarker), expected))
|
|
4884
|
+
await waitUntil(timing, unregisteredTerminationDeadlineAt);
|
|
4885
|
+
else await timing.sleep(Math.max(0, Math.min(POLL_MS, unregisteredTerminationDeadlineAt - timing.now())));
|
|
4922
4886
|
}
|
|
4887
|
+
} else {
|
|
4888
|
+
await waitUntil(timing, terminationStartDeadlineAt);
|
|
4923
4889
|
}
|
|
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);
|
|
4890
|
+
observation = observe();
|
|
4891
|
+
}
|
|
4892
|
+
if (observation === "alive") {
|
|
4893
|
+
if (!(await signalVerifiedSession({ locator: { stateRoot: root }, pid }, id, "SIGTERM", expected))) {
|
|
4894
|
+
observation = observe();
|
|
4895
|
+
await recheckOwnedExitObservation();
|
|
4896
|
+
if (observation !== "exited") {
|
|
4897
|
+
await recordTerminalUncertain(broker, id, root, pid);
|
|
4898
|
+
return false;
|
|
4938
4899
|
}
|
|
4900
|
+
} else {
|
|
4901
|
+
const remaining = Math.max(0, processExitDeadlineAt - timing.now());
|
|
4902
|
+
const gracefulDeadline = timing.now() + Math.min(CLOSE_TIMEOUT_MS, Math.floor(remaining / 2));
|
|
4903
|
+
observation = await waitForExit(gracefulDeadline);
|
|
4939
4904
|
}
|
|
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
|
-
}
|
|
4905
|
+
}
|
|
4906
|
+
if (observation === "alive") {
|
|
4907
|
+
if (!(await signalVerifiedSession({ locator: { stateRoot: root }, pid }, id, "SIGKILL", expected))) {
|
|
4908
|
+
observation = observe();
|
|
4909
|
+
await recheckOwnedExitObservation();
|
|
4910
|
+
if (observation !== "exited") {
|
|
4911
|
+
await recordTerminalUncertain(broker, id, root, pid);
|
|
4912
|
+
return false;
|
|
4978
4913
|
}
|
|
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
|
-
}
|
|
4914
|
+
} else {
|
|
4915
|
+
observation = await waitForExit(processExitDeadlineAt);
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
await recheckOwnedExitObservation();
|
|
4919
|
+
if (observation !== "exited" || !lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
4920
|
+
let rollbackGeneration: number | null | undefined;
|
|
4921
|
+
if (expected) {
|
|
4922
|
+
const failure = await readLifecycleFailureArtifact(
|
|
4923
|
+
lifecycleFailurePath(root, id, expected.effectMarker),
|
|
4924
|
+
expected,
|
|
4925
|
+
);
|
|
4926
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
4927
|
+
const rollbackComplete =
|
|
4928
|
+
failure?.artifact.rollback.fenced === true &&
|
|
4929
|
+
failure.artifact.rollback.runtimeRemoved &&
|
|
4930
|
+
failure.artifact.rollback.hostStopped &&
|
|
4931
|
+
failure.artifact.rollback.brokerRegistrationReleased;
|
|
4932
|
+
if (!rollbackComplete) {
|
|
4933
|
+
if (!readyThenExitToleranceEnabled()) {
|
|
4934
|
+
// A child that exited with a non-zero status before publishing any
|
|
4935
|
+
// lifecycle receipt has no durable rollback to wait for. Prove the
|
|
4936
|
+
// owned artifacts are gone and release its registration, but retain
|
|
4937
|
+
// terminal uncertainty for timeouts, clean exits, and child-authored
|
|
4938
|
+
// startup receipts whose rollback is incomplete.
|
|
4939
|
+
if (failure || child.exitCode === null || child.exitCode === 0) return failClosed();
|
|
5067
4940
|
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5068
4941
|
const publishedReady = (await probePublishedReadyAuthority(root, id, expected)).kind === "matched";
|
|
5069
|
-
if (
|
|
4942
|
+
if (publishedReady) return failClosed();
|
|
5070
4943
|
const artifactsRemoved = await removeOwnedLifecycleArtifacts(
|
|
5071
4944
|
root,
|
|
5072
4945
|
id,
|
|
@@ -5081,50 +4954,21 @@ async function terminateSpawnedChild(
|
|
|
5081
4954
|
const registered = broker.index
|
|
5082
4955
|
.listSessions()
|
|
5083
4956
|
.sessions.find(session => session.sessionId === id && session.pid === pid);
|
|
5084
|
-
const registeredRowTerminal = registered?.terminal === true || registered?.terminalUncertain === true;
|
|
5085
4957
|
let registrationReleased =
|
|
5086
|
-
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker) ||
|
|
4958
|
+
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker) ||
|
|
4959
|
+
registered?.terminal === true ||
|
|
4960
|
+
registered?.terminalUncertain === true;
|
|
5087
4961
|
if (registered && !registrationReleased) {
|
|
5088
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5089
4962
|
registrationReleased = await broker.index.unregisterIfCurrent(registered);
|
|
5090
4963
|
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5091
4964
|
await broker.index.refresh();
|
|
5092
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5093
4965
|
registrationReleased =
|
|
5094
4966
|
registrationReleased || !broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker);
|
|
5095
4967
|
}
|
|
5096
4968
|
const stillExited =
|
|
5097
4969
|
observeProcess(pid, expected.incarnation, value => processIncarnationForBroker(broker, value)) ===
|
|
5098
4970
|
"exited";
|
|
5099
|
-
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5100
4971
|
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
4972
|
if (
|
|
5129
4973
|
stillExited &&
|
|
5130
4974
|
artifactsRemoved &&
|
|
@@ -5135,35 +4979,102 @@ async function terminateSpawnedChild(
|
|
|
5135
4979
|
return true;
|
|
5136
4980
|
return failClosed();
|
|
5137
4981
|
}
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
4982
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
4983
|
+
const publishedReady = (await probePublishedReadyAuthority(root, id, expected)).kind === "matched";
|
|
4984
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
4985
|
+
const artifactsRemoved = await removeOwnedLifecycleArtifacts(
|
|
4986
|
+
root,
|
|
4987
|
+
id,
|
|
4988
|
+
expected,
|
|
4989
|
+
undefined,
|
|
4990
|
+
undefined,
|
|
4991
|
+
proofBudget,
|
|
4992
|
+
);
|
|
4993
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
4994
|
+
await broker.index.refresh();
|
|
4995
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
4996
|
+
const registered = broker.index
|
|
4997
|
+
.listSessions()
|
|
4998
|
+
.sessions.find(session => session.sessionId === id && session.pid === pid);
|
|
4999
|
+
const registeredRowTerminal = registered?.terminal === true || registered?.terminalUncertain === true;
|
|
5000
|
+
let registrationReleased =
|
|
5001
|
+
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker) || registeredRowTerminal;
|
|
5002
|
+
if (registered && !registrationReleased) {
|
|
5003
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5004
|
+
registrationReleased = await broker.index.unregisterIfCurrent(registered);
|
|
5005
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5006
|
+
await broker.index.refresh();
|
|
5007
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5008
|
+
registrationReleased =
|
|
5009
|
+
registrationReleased || !broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker);
|
|
5010
|
+
}
|
|
5011
|
+
const stillExited =
|
|
5012
|
+
observeProcess(pid, expected.incarnation, value => processIncarnationForBroker(broker, value)) === "exited";
|
|
5013
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5014
|
+
const endpointGone = await endpointRemoved(root, id);
|
|
5015
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5016
|
+
try {
|
|
5017
|
+
if (publishedReady && !failure) {
|
|
5018
|
+
await writeSessionLifecycleFailure(
|
|
5019
|
+
root,
|
|
5020
|
+
id,
|
|
5021
|
+
expected.effectMarker,
|
|
5022
|
+
{
|
|
5023
|
+
phase: "startup",
|
|
5024
|
+
reason: "failed",
|
|
5025
|
+
message: `Session ${id} ${READY_THEN_EXIT_MESSAGE}.`,
|
|
5026
|
+
},
|
|
5027
|
+
{
|
|
5028
|
+
endpointGeneration: registered?.endpointGeneration ?? null,
|
|
5029
|
+
fenced: stillExited && registrationReleased,
|
|
5030
|
+
runtimeRemoved: artifactsRemoved && endpointGone,
|
|
5031
|
+
hostStopped: stillExited,
|
|
5032
|
+
brokerRegistrationReleased: registrationReleased,
|
|
5033
|
+
},
|
|
5034
|
+
undefined,
|
|
5035
|
+
expected.incarnation,
|
|
5036
|
+
expected.pid,
|
|
5037
|
+
);
|
|
5038
|
+
}
|
|
5039
|
+
} catch {
|
|
5040
|
+
// A missing broker-authored receipt must not hide a proven dead child.
|
|
5041
|
+
}
|
|
5042
|
+
if (
|
|
5043
|
+
stillExited &&
|
|
5044
|
+
artifactsRemoved &&
|
|
5045
|
+
endpointGone &&
|
|
5046
|
+
registrationReleased &&
|
|
5047
|
+
lifecycleProofWithinDeadline(proofBudget)
|
|
5048
|
+
)
|
|
5049
|
+
return true;
|
|
5159
5050
|
return failClosed();
|
|
5160
5051
|
}
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5052
|
+
rollbackGeneration = failure.artifact.rollback.endpointGeneration;
|
|
5053
|
+
}
|
|
5054
|
+
if (expected && !(await removeOwnedLifecycleArtifacts(root, id, expected, undefined, undefined, proofBudget)))
|
|
5055
|
+
return failClosed();
|
|
5056
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5057
|
+
await broker.index.refresh();
|
|
5058
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5059
|
+
if (
|
|
5060
|
+
rollbackGeneration === null &&
|
|
5061
|
+
expected &&
|
|
5062
|
+
!broker.index.hasHostRegistrationForLifecycle(id, pid, expected.effectMarker)
|
|
5063
|
+
)
|
|
5064
|
+
return lifecycleProofWithinDeadline(proofBudget) ? true : failClosed();
|
|
5065
|
+
const registeredBeforeTermination =
|
|
5066
|
+
rollbackGeneration === undefined || rollbackGeneration === null
|
|
5067
|
+
? undefined
|
|
5068
|
+
: broker.index.findHostRegistration(id, rollbackGeneration, pid, expected?.effectMarker);
|
|
5069
|
+
const unregistered = registeredBeforeTermination
|
|
5070
|
+
? broker.index.hostUnregisteredAfter(registeredBeforeTermination)
|
|
5071
|
+
: undefined;
|
|
5072
|
+
if (!registeredBeforeTermination || !unregistered) {
|
|
5073
|
+
return failClosed();
|
|
5166
5074
|
}
|
|
5075
|
+
if (!lifecycleProofWithinDeadline(proofBudget)) return failClosed();
|
|
5076
|
+
const endpointGone = await endpointRemoved(root, id);
|
|
5077
|
+
return endpointGone && lifecycleProofWithinDeadline(proofBudget) ? true : failClosed();
|
|
5167
5078
|
}
|
|
5168
5079
|
|
|
5169
5080
|
async function signalVerifiedSession(
|
|
@@ -6993,7 +6904,6 @@ async function executeLifecycleResponse(
|
|
|
6993
6904
|
knownSecrets,
|
|
6994
6905
|
)
|
|
6995
6906
|
: undefined;
|
|
6996
|
-
const ledgerUncertain = broker.ledger.get(identity)?.state === "terminal_uncertain";
|
|
6997
6907
|
const terminated = await terminateSpawnedChild(
|
|
6998
6908
|
child,
|
|
6999
6909
|
broker,
|
|
@@ -7003,8 +6913,8 @@ async function executeLifecycleResponse(
|
|
|
7003
6913
|
terminationStartDeadline,
|
|
7004
6914
|
spawnedAuthority,
|
|
7005
6915
|
timing,
|
|
7006
|
-
readiness.kind === "timeout",
|
|
7007
6916
|
);
|
|
6917
|
+
|
|
7008
6918
|
if (!terminated)
|
|
7009
6919
|
return readiness.kind === "ready_probe_failed"
|
|
7010
6920
|
? fail(
|
|
@@ -7015,11 +6925,6 @@ async function executeLifecycleResponse(
|
|
|
7015
6925
|
"terminal_uncertain",
|
|
7016
6926
|
`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
6927
|
);
|
|
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
6928
|
return readiness.kind === "startup_failed"
|
|
7024
6929
|
? fail(
|
|
7025
6930
|
readiness.failure.code ?? "spawn_failed",
|
|
@@ -8016,7 +7921,6 @@ async function exactCleanupProof(
|
|
|
8016
7921
|
evidence: { artifact: LifecycleFailureArtifact } | undefined,
|
|
8017
7922
|
proofBudget?: LifecycleProofBudget,
|
|
8018
7923
|
allowObservedWindowsReadyExit = false,
|
|
8019
|
-
allowObservedUnregisteredCutoff = false,
|
|
8020
7924
|
): Promise<LifecycleCleanupProof | undefined> {
|
|
8021
7925
|
const rollback = evidence?.artifact.rollback;
|
|
8022
7926
|
const durableRollbackComplete =
|
|
@@ -8024,16 +7928,6 @@ async function exactCleanupProof(
|
|
|
8024
7928
|
rollback.runtimeRemoved &&
|
|
8025
7929
|
rollback.hostStopped &&
|
|
8026
7930
|
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
7931
|
// Windows deliberately keeps the failure receipt incomplete because its
|
|
8038
7932
|
// directory-entry publication cannot be flushed. A broker-observed
|
|
8039
7933
|
// ready-then-exit can still authorize the separate ledger-backed exact
|
|
@@ -8051,7 +7945,7 @@ async function exactCleanupProof(
|
|
|
8051
7945
|
!root ||
|
|
8052
7946
|
!id ||
|
|
8053
7947
|
!expected ||
|
|
8054
|
-
(!durableRollbackComplete && !observedWindowsReadyExit
|
|
7948
|
+
(!durableRollbackComplete && !observedWindowsReadyExit) ||
|
|
8055
7949
|
observeProcess(expected.pid, expected.incarnation, value => processIncarnationForBroker(broker, value)) !==
|
|
8056
7950
|
"exited"
|
|
8057
7951
|
)
|
|
@@ -8266,28 +8160,28 @@ export async function executeLifecycle(
|
|
|
8266
8160
|
expected,
|
|
8267
8161
|
evidence,
|
|
8268
8162
|
proofBudget,
|
|
8269
|
-
!response.ok
|
|
8270
|
-
!response.ok && response.error.code === "readiness_timeout",
|
|
8163
|
+
!response.ok ? response.error.code === "ready_then_exited" : false,
|
|
8271
8164
|
);
|
|
8272
|
-
const startupFailure: LifecycleStartupFailureReceipt | undefined =
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8165
|
+
const startupFailure: LifecycleStartupFailureReceipt | undefined =
|
|
8166
|
+
evidence && !response.ok
|
|
8167
|
+
? {
|
|
8168
|
+
artifactDigest: evidence.digest,
|
|
8169
|
+
message: evidence.artifact.message,
|
|
8170
|
+
phase: evidence.artifact.phase,
|
|
8171
|
+
reason: evidence.artifact.reason,
|
|
8172
|
+
...(evidence.artifact.code === undefined
|
|
8173
|
+
? {}
|
|
8174
|
+
: { code: evidence.artifact.code, details: evidence.artifact.details }),
|
|
8175
|
+
rollback: {
|
|
8176
|
+
endpointGeneration: evidence.artifact.rollback.endpointGeneration,
|
|
8177
|
+
fenced: evidence.artifact.rollback.fenced,
|
|
8178
|
+
runtimeRemoved: evidence.artifact.rollback.runtimeRemoved,
|
|
8179
|
+
hostStopped: evidence.artifact.rollback.hostStopped,
|
|
8180
|
+
brokerRegistrationReleased: evidence.artifact.rollback.brokerRegistrationReleased,
|
|
8181
|
+
},
|
|
8182
|
+
...(cleanupProof ? { cleanupProof } : {}),
|
|
8183
|
+
}
|
|
8184
|
+
: undefined;
|
|
8291
8185
|
const durableEffectsBody: Omit<LifecycleDurableEffectsReceipt, "digest"> = {
|
|
8292
8186
|
...(priorDurableEffects?.worktree ? { worktree: priorDurableEffects.worktree } : {}),
|
|
8293
8187
|
...(evidence?.artifact.transcript
|
|
@@ -8367,54 +8261,44 @@ export async function executeLifecycle(
|
|
|
8367
8261
|
? response
|
|
8368
8262
|
: response.error.code === "endpoint_unreadable"
|
|
8369
8263
|
? response
|
|
8370
|
-
:
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
lifecycleCleanupResponse.error.code === "spawn_failed"
|
|
8264
|
+
: // An unreadable/corrupt endpoint is already the fail-closed honest
|
|
8265
|
+
// terminal classification (it names the artifact, not the
|
|
8266
|
+
// child); the reconciliation wrapper must not erase it.
|
|
8267
|
+
response.error.code === "ready_then_exited" && (cleanupProof || provenDeadCleanup)
|
|
8375
8268
|
? {
|
|
8376
8269
|
...response,
|
|
8377
8270
|
...(durableEffects ? { durableEffects } : {}),
|
|
8378
8271
|
...(startupFailure ? { startupFailure } : {}),
|
|
8379
8272
|
}
|
|
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)
|
|
8273
|
+
: startupFailure && cleanupProof
|
|
8384
8274
|
? {
|
|
8385
|
-
|
|
8275
|
+
ok: false,
|
|
8276
|
+
error: startupFailure.code
|
|
8277
|
+
? {
|
|
8278
|
+
code: startupFailure.code,
|
|
8279
|
+
message: startupFailure.message,
|
|
8280
|
+
details: startupFailure.details!,
|
|
8281
|
+
endpoint: "unavailable" as const,
|
|
8282
|
+
}
|
|
8283
|
+
: {
|
|
8284
|
+
code: "spawn_failed",
|
|
8285
|
+
message: startupFailure.message,
|
|
8286
|
+
endpoint: "unavailable" as const,
|
|
8287
|
+
},
|
|
8386
8288
|
...(durableEffects ? { durableEffects } : {}),
|
|
8387
|
-
|
|
8289
|
+
startupFailure,
|
|
8388
8290
|
}
|
|
8389
|
-
:
|
|
8390
|
-
?
|
|
8291
|
+
: provenDeadCleanup && response.error.code !== "terminal_uncertain"
|
|
8292
|
+
? response
|
|
8293
|
+
: {
|
|
8391
8294
|
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
|
-
},
|
|
8295
|
+
error: {
|
|
8296
|
+
code: "terminal_uncertain",
|
|
8297
|
+
message: terminalUncertainStartupMessage(response),
|
|
8298
|
+
},
|
|
8404
8299
|
...(durableEffects ? { durableEffects } : {}),
|
|
8405
|
-
startupFailure,
|
|
8300
|
+
...(startupFailure ? { startupFailure } : {}),
|
|
8406
8301
|
}
|
|
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
8302
|
: response;
|
|
8419
8303
|
// The extended proof window may reconcile artifacts after the original
|
|
8420
8304
|
// admission deadline, but cannot retroactively prove a timely spawn failure.
|