conversation: release a cohort scope after a proven stop (row 50, #1536)
releaseCohort sends `release` only to a scope shim that answers hello with the recorded invocation ID, then waits for systemd to drop the unit. The controller releases once per claim, after a proven force stop and on close of a proven-stopped binding; every uncertain path keeps the scope as evidence. The harness's killShims refuses units outside ^mosaic-chat-, R5 checks liveShims positively, and K19's wait on proc.exited is bounded. Dewey's candidate, manifest 375594fc (8 files), approved by Filbert (comment 27053) and Darkwing (comment 27055). Normal engine exit still leaves the scope; the follow-up is #1537. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -23,7 +23,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { processStart } from "../../discord/src/journal.mjs";
|
||||
import { ALREADY_ACTIVE, ClaimStore, FOREIGN_HOST, UNSAFE_REPLACEMENT, machineId, seatKey, sessionKey } from "./claim.mjs";
|
||||
import { AUTHORITY, PgroupLauncher, bootProof, cohortProof, cohortRefOf, effectReport, forceStopCohort, systemdUnits } from "./cohort.mjs";
|
||||
import { AUTHORITY, PgroupLauncher, bootProof, cohortProof, cohortRefOf, effectReport, forceStopCohort, releaseCohort, systemdUnits } from "./cohort.mjs";
|
||||
import { EngineLink } from "./engine.mjs";
|
||||
import { DIALOG_METHODS, KNOWN_UNSHOWN, NOTIFY_METHODS, deltaBlocks, isFoldedUpdate, messageBlocks, partsOf, roleOf, toolResultText } from "./events.mjs";
|
||||
import { LineSplitter, encodeLine, parseLine } from "./framing.mjs";
|
||||
@@ -224,12 +224,13 @@ export class Controller {
|
||||
this.eligibility = new Map();
|
||||
this.outcomeUnknown = new Set();
|
||||
this.escalating = null;
|
||||
this.scopeReleases = new Map();
|
||||
this.abortWritten = new Set();
|
||||
this.preflightOk = false;
|
||||
this.server = null;
|
||||
this.sockets = new Set();
|
||||
this.events = [];
|
||||
this.evidence = { dropped: { lines: 0, bytes: 0 }, unknownEvents: {}, unshown: {}, folded: 0, dialogs: [], notices: 0, gaps: [], overlaps: [], uncertain: [], stops: [], refusedRevisions: [], internal: [], stderrTail: "" };
|
||||
this.evidence = { dropped: { lines: 0, bytes: 0 }, unknownEvents: {}, unshown: {}, folded: 0, dialogs: [], notices: 0, gaps: [], overlaps: [], uncertain: [], stops: [], releases: [], refusedRevisions: [], internal: [], stderrTail: "" };
|
||||
}
|
||||
|
||||
get binding() {
|
||||
@@ -1555,9 +1556,34 @@ export class Controller {
|
||||
this.#push({ kind: "binding", binding: b });
|
||||
this.#emit("stopped", { stop: stop.id });
|
||||
this.#evidenceAdd("stop", { stop: stop.id, mode: "force-stop", outcome: "stopped", proofs: { cohort: proof.id, effects: effects.id }, resumed });
|
||||
const proven = this.claim;
|
||||
await this.#pause("scope-release", { stop: stop.id });
|
||||
await this.#releaseScope("force-stop", proven);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Ends the scope of a claim recorded `stopped` on a cohort proof, once per
|
||||
// claim (#1536). The force stop calls it for the claim it proved, once
|
||||
// that claim is recorded `stopped`; close calls it again, so a release the
|
||||
// stop didn't reach still happens. Every `fail` path above returns before
|
||||
// it: after an uncertain stop the scope and its shim stay as evidence. It
|
||||
// never rejects: a failed release is an `unavailable` entry.
|
||||
#releaseScope(why, claim = this.claim) {
|
||||
const rec = claim?.record;
|
||||
if (!rec || rec.state !== "stopped" || rec.proof?.kind !== "cohortProof" || !rec.shim) return null;
|
||||
let pending = this.scopeReleases.get(claim.claimId);
|
||||
if (!pending) {
|
||||
pending = releaseCohort({ kind: rec.engine?.kind, unitName: rec.unitName, invocationId: rec.invocationId, shimSocket: rec.shim }).catch((err) => ({ outcome: "unavailable", reason: `release: ${err.code ?? err.message}` })).then((result) => {
|
||||
const entry = { kind: "release", claim: claim.claimId, unitName: rec.unitName, why, ...result, at: this.now().toISOString() };
|
||||
this.evidence.releases.push(entry);
|
||||
this.#push({ kind: "evidence", evidence: entry });
|
||||
return result;
|
||||
});
|
||||
this.scopeReleases.set(claim.claimId, pending);
|
||||
}
|
||||
return pending;
|
||||
}
|
||||
|
||||
// check.mjs `stopped`.
|
||||
#stopped(s) {
|
||||
const b = this.b, p = this.stoppedProof;
|
||||
@@ -1659,10 +1685,12 @@ export class Controller {
|
||||
return "revoked";
|
||||
}
|
||||
|
||||
// Test and shutdown hook. Never a release: the claim is unchanged.
|
||||
// Test and shutdown hook. Never a claim release: the claim is unchanged.
|
||||
// A binding proven stopped has its scope released (#1536).
|
||||
async close({ killEngine = false } = {}) {
|
||||
const exec = this.exec;
|
||||
if (exec) exec.closing = true;
|
||||
if (this.b?.state === "stopped" && this.#stopped(this.stops.get(this.b.stop))) await this.#releaseScope("close");
|
||||
if (killEngine && typeof exec?.proc?.kill === "function") exec.proc.kill();
|
||||
else if (killEngine && exec?.proc?.pid) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user