@akagilnc/pi-workflow-roles 0.1.4620 → 0.1.4641

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.
Files changed (41) hide show
  1. package/README.zh-CN.md +1 -1
  2. package/dist/acp-host/production-host.js +962 -547
  3. package/dist/activation-ledger-topology.js +0 -4
  4. package/dist/gatekeeper-role.js +76 -6
  5. package/dist/headless-host/production-host.js +923 -508
  6. package/dist/migrate-book-topology.js +9 -0
  7. package/dist/pi/role-turn-host.js +0 -3
  8. package/dist/public-cli/auto-resume.js +26 -0
  9. package/dist/public-cli/countersign-run.js +31 -10
  10. package/dist/public-cli/main.js +1456 -1068
  11. package/dist/public-cli/post-admission.js +101 -7
  12. package/dist/public-cli/process-cancel.js +57 -0
  13. package/dist/public-cli/settlement.js +95 -2
  14. package/dist/public-role-summons.js +19 -0
  15. package/dist/secretariat-contracts.js +4 -0
  16. package/package.json +1 -1
  17. package/scripts/build-package.mjs +0 -1
  18. package/src/activation-ledger-topology.ts +1 -6
  19. package/src/activation-ledger.ts +5 -236
  20. package/src/analyst-entry.ts +19 -2
  21. package/src/analyst-ledger.ts +111 -6
  22. package/src/archivist-record-entry.ts +3 -3
  23. package/src/audit-escalation.ts +1 -1
  24. package/src/gatekeeper-pass-envelope.ts +37 -5
  25. package/src/gatekeeper-role.ts +125 -20
  26. package/src/host-contracts.ts +22 -2
  27. package/src/pi/adapter.ts +5 -1
  28. package/src/pi/role-turn-host.ts +0 -3
  29. package/src/public-cli/auto-resume.ts +34 -0
  30. package/src/public-cli/cli.ts +30 -0
  31. package/src/public-cli/countersign-run.ts +50 -13
  32. package/src/public-cli/main.ts +25 -3
  33. package/src/public-cli/post-admission.ts +173 -6
  34. package/src/public-cli/process-cancel.ts +75 -0
  35. package/src/public-cli/settlement.ts +117 -1
  36. package/src/public-role-summons.ts +30 -4
  37. package/src/role-envelope.ts +27 -10
  38. package/src/role-runtime.ts +146 -55
  39. package/src/secretariat-contracts.ts +18 -0
  40. package/dist/activation-reconciliation.js +0 -61
  41. package/src/activation-reconciliation.ts +0 -163
@@ -37,10 +37,36 @@ import { mintEngineDetourInvocationScope, readInvocationSelectedHost, withEngine
37
37
  import { projectHostTransitionPriorNative } from "../host-transition-prior-native.js";
38
38
  import { missingCredentialPreDispatchFailure, postRunMissingCredentialFailure, } from "./public-run-credentials.js";
39
39
  import { acquireRunWriterLease, clearCurrentCourt, clearTypedProviderHttpObservation, describeErrorIdentity, markRunRunning, readCurrentCourt, recordCurrentCourt, renderResumeCommand, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
40
+ import { processCancelDiagnostic, processCancelSignalName, } from "./process-cancel.js";
40
41
  import { homeFromRunDirectory } from "../activation-ledger-topology.js";
41
42
  import { attemptProducedFreshSubmission, classifyPostAdmissionFailure, controlledFailureInputFromResolution, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatTerminalResult, inspectJudgeSession, isLawfulTypedTerminalOutcome, presentFailureTerminal, presentStructuralRejection, resolveAuditedRunnerFailureResolution, resolveControlledFailureResumeObservation, settleFailureTerminalResult, settleHostEndedNoReceipt, attachRecordedSubmissions, } from "./settlement.js";
42
43
  import {} from "./terminal.js";
43
44
  import { ensureRealArtifactsDirectory, persistReturnedRunState, runWithAutoResumeLoop, TurnDispatchedFailure, } from "./auto-resume.js";
45
+ /** #855: process-cancel settlement never re-enters auto-resume. */
46
+ function withProcessCancelSkipAutoResume(result, signal) {
47
+ if (processCancelSignalName(signal) === undefined) {
48
+ return result;
49
+ }
50
+ return { ...result, skipAutoResume: true };
51
+ }
52
+ /**
53
+ * #855: turn already started, then a catchable process signal arrived before
54
+ * lawful seal — settle as named cancel failure and skip auto-resume.
55
+ */
56
+ async function settleProcessCancelAfterTurn(input) {
57
+ const failed = await settleAfterTurnStarted(input.admitted, withEngineDetourInvocationScope({
58
+ timedOut: input.result.timedOut,
59
+ code: input.result.code,
60
+ stderr: input.result.stderr,
61
+ knownDiagnostic: processCancelDiagnostic(input.cancelName),
62
+ }, input.invocationScopeId), input.adapters, input.env.principalAuthority, input.io, input.persistRunState);
63
+ return {
64
+ ...failed,
65
+ turnDispatched: true,
66
+ skipAutoResume: true,
67
+ ...input.deferredPersist,
68
+ };
69
+ }
44
70
  /**
45
71
  * Nested station-child role already finished its own call-local loop.
46
72
  * Parent records that failure once and must not auto-resume into a re-summon
@@ -258,6 +284,26 @@ export async function dispatchPostAdmissionTurn(input) {
258
284
  if (afterDispatchApplied)
259
285
  return result;
260
286
  afterDispatchApplied = true;
287
+ // #855: re-read cancel before afterDispatch finalization — never present
288
+ // lawful success after a catchable process signal. skipRunStateWrite: run-state
289
+ // may already be terminal from a prior lawful persist; do not reopen as resumable.
290
+ const cancelBeforeFinish = processCancelSignalName(env.signal);
291
+ if (cancelBeforeFinish !== undefined &&
292
+ result.terminal !== undefined &&
293
+ isLawfulTypedTerminalOutcome(result.terminal.roleOutcome)) {
294
+ result = {
295
+ ...(await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
296
+ timedOut: false,
297
+ code: null,
298
+ stderr: "",
299
+ knownDiagnostic: processCancelDiagnostic(cancelBeforeFinish),
300
+ skipRunStateWrite: true,
301
+ }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState)),
302
+ turnDispatched: true,
303
+ skipAutoResume: true,
304
+ ...deferredPersist,
305
+ };
306
+ }
261
307
  try {
262
308
  // #858: an unbound seat may assert its ticket on the existing receipt.
263
309
  // Read the original accepted payload; do not rewrite it, infer from prose,
@@ -412,13 +458,16 @@ export async function dispatchPostAdmissionTurn(input) {
412
458
  result = await env.roleTurnHost.executeTurn(turnRequest);
413
459
  }
414
460
  catch (error) {
461
+ const processCancelName = processCancelSignalName(env.signal);
415
462
  const settled = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
416
463
  timedOut: false,
417
464
  code: null,
418
465
  stderr: "",
419
- thrown: error,
466
+ thrown: processCancelName === undefined
467
+ ? error
468
+ : new Error(processCancelDiagnostic(processCancelName), { cause: error }),
420
469
  }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
421
- return await finishAfterTurn({ ...settled, turnDispatched: true, ...deferredPersist });
470
+ return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...settled, turnDispatched: true, ...deferredPersist }, env.signal));
422
471
  }
423
472
  // `result.stderr` stays live in memory for the real classification below
424
473
  // regardless of whether this durable mirror write succeeds. A write
@@ -514,10 +563,12 @@ export async function dispatchPostAdmissionTurn(input) {
514
563
  // retroactively invalidate an acceptance that already sealed this turn
515
564
  // — a resolved 429 observed earlier in the same session is exactly
516
565
  // that.
566
+ const processCancelName = processCancelSignalName(env.signal);
517
567
  const directHostFailureSignal = result.timedOut
518
568
  || result.knownFailure !== undefined
519
569
  || runnerKnownFailure !== undefined
520
- || credentialFailure !== undefined;
570
+ || credentialFailure !== undefined
571
+ || processCancelName !== undefined;
521
572
  hostSignalFailed =
522
573
  directHostFailureSignal
523
574
  || (result.code !== null && result.code !== 0)
@@ -553,11 +604,15 @@ export async function dispatchPostAdmissionTurn(input) {
553
604
  // where a bare nonzero exit / resolution failure must not be outranked
554
605
  // by someone else's earlier success (#836 r12 class 2).
555
606
  const staleAcceptanceOutranksRealFailure = !settledIsFreshThisAttempt && hostSignalFailed;
607
+ // #855: re-read cancel after trySettle/attach awaits — a signal in this
608
+ // window must not land as lawful accepted.
609
+ const cancelAfterSettle = processCancelSignalName(env.signal);
556
610
  if (settled !== undefined
557
611
  && shouldPresent(settled)
558
612
  && !directHostFailureSignal
559
613
  && !staleAcceptanceOutranksRealFailure
560
- && stderrLogWriteFailure === undefined) {
614
+ && stderrLogWriteFailure === undefined
615
+ && cancelAfterSettle === undefined) {
561
616
  // This court sealed — drop open-court pointer (bare resume no longer continues it).
562
617
  if (settled.roleOutcome.kind === "accepted" &&
563
618
  request.courtAttemptId !== undefined &&
@@ -597,6 +652,22 @@ export async function dispatchPostAdmissionTurn(input) {
597
652
  return await finishAfterTurn({ ...settledFailure, turnDispatched: true, ...deferredPersist });
598
653
  }
599
654
  if (settledOutcome !== undefined) {
655
+ // #855: final cancel re-read before lawful seal — settlement window may
656
+ // have received SIGTERM/SIGINT/SIGHUP after the earlier snapshot.
657
+ const lateCancel = processCancelSignalName(env.signal);
658
+ if (lateCancel !== undefined) {
659
+ return await finishAfterTurn(await settleProcessCancelAfterTurn({
660
+ admitted,
661
+ cancelName: lateCancel,
662
+ result,
663
+ adapters,
664
+ env,
665
+ io,
666
+ persistRunState,
667
+ deferredPersist,
668
+ invocationScopeId: request.invocationScopeId,
669
+ }));
670
+ }
600
671
  if (persistRunState) {
601
672
  try {
602
673
  await persistReturnedRunState(admitted, env.principalAuthority, { lawful: true });
@@ -619,16 +690,18 @@ export async function dispatchPostAdmissionTurn(input) {
619
690
  thrown: error,
620
691
  skipRunStateWrite: true,
621
692
  }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
622
- return await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist });
693
+ return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...failed, turnDispatched: true, ...deferredPersist }, env.signal));
623
694
  }
624
695
  }
625
696
  // Lawful persist + present is the caller's stop seam (auto-resume loop /
626
697
  // manual resume), not this retried host-turn function.
698
+ // #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
627
699
  return await finishAfterTurn({ ...settledOutcome, ...deferredPersist });
628
700
  }
629
701
  // Host/runner true failure coexists with already-recorded payloads — never wash as accepted.
630
702
  if (hostSignalFailed) {
631
703
  const resolutionInput = controlledFailureInputFromResolution(resolution);
704
+ const processCancelName = processCancelSignalName(env.signal);
632
705
  const stderrLogWriteDetails = stderrLogWriteFailure === undefined
633
706
  ? undefined
634
707
  : { stderrLogWriteFailure: describeCaughtError(stderrLogWriteFailure) };
@@ -637,6 +710,9 @@ export async function dispatchPostAdmissionTurn(input) {
637
710
  code: result.code,
638
711
  stderr: result.stderr,
639
712
  ...resolutionInput,
713
+ ...(processCancelName === undefined
714
+ ? {}
715
+ : { knownDiagnostic: processCancelDiagnostic(processCancelName) }),
640
716
  // Secondary fact only — never the cause. Rides on whichever channel
641
717
  // classification actually reads (knownFailure.details owns it when
642
718
  // a knownFailure exists; the top-level knownDetails otherwise).
@@ -651,7 +727,7 @@ export async function dispatchPostAdmissionTurn(input) {
651
727
  }
652
728
  : { knownDetails: stderrLogWriteDetails }),
653
729
  }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
654
- return await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist });
730
+ return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...failed, turnDispatched: true, ...deferredPersist }, env.signal));
655
731
  }
656
732
  // Nothing else was wrong, but the durable stderr mirror itself failed to
657
733
  // write — that is the real (infrastructure) problem in this case, not a
@@ -666,6 +742,21 @@ export async function dispatchPostAdmissionTurn(input) {
666
742
  return await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist });
667
743
  }
668
744
  const noReceipt = await attachRecordedSubmissions(admitted, await settleHostEndedNoReceipt(admitted, env.principalAuthority, courtScope), courtScope);
745
+ // #855: cancel during no_receipt settlement window is not lawful success.
746
+ const noReceiptCancel = processCancelSignalName(env.signal);
747
+ if (noReceiptCancel !== undefined) {
748
+ return await finishAfterTurn(await settleProcessCancelAfterTurn({
749
+ admitted,
750
+ cancelName: noReceiptCancel,
751
+ result,
752
+ adapters,
753
+ env,
754
+ io,
755
+ persistRunState,
756
+ deferredPersist,
757
+ invocationScopeId: request.invocationScopeId,
758
+ }));
759
+ }
669
760
  if (persistRunState) {
670
761
  try {
671
762
  await persistReturnedRunState(admitted, env.principalAuthority, { lawful: true });
@@ -683,9 +774,10 @@ export async function dispatchPostAdmissionTurn(input) {
683
774
  thrown: error,
684
775
  skipRunStateWrite: true,
685
776
  }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
686
- return await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist });
777
+ return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...failed, turnDispatched: true, ...deferredPersist }, env.signal));
687
778
  }
688
779
  }
780
+ // #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
689
781
  return await finishAfterTurn({
690
782
  exitCode: exitCodeForTerminalOutcome(noReceipt.roleOutcome),
691
783
  admitted,
@@ -972,6 +1064,7 @@ export async function runPostAdmissionSeatResume(input) {
972
1064
  io: input.io,
973
1065
  sessionAppender: env.sessionAppender,
974
1066
  autoResumeLimit: env.autoResumeLimit,
1067
+ ...(env.signal === undefined ? {} : { signal: env.signal }),
975
1068
  buildInitialPayload: () => ({ resumeTurn: false }),
976
1069
  buildResumePayload: () => ({ resumeTurn: true }),
977
1070
  // Same as public manual resume: prior-court sealed acceptance is not a
@@ -1095,6 +1188,7 @@ export async function runPostAdmissionResumable(input) {
1095
1188
  io,
1096
1189
  sessionAppender: env.sessionAppender,
1097
1190
  autoResumeLimit: env.autoResumeLimit,
1191
+ ...(env.signal === undefined ? {} : { signal: env.signal }),
1098
1192
  buildInitialPayload: buildScopedInitial,
1099
1193
  buildResumePayload: buildScopedResume,
1100
1194
  dispatch: async (request, lease, _isFirst, attemptIo) => {
@@ -0,0 +1,57 @@
1
+ /**
2
+ * #855: catchable process signals for ak-role public entry.
3
+ * SIGTERM / SIGINT / SIGHUP abort the shared controller so hosts can gracefully
4
+ * terminate children (SIGTERM only, never SIGKILL). Uncatchable death (SIGKILL,
5
+ * crash, power loss) is reported via run-state + writer lease on analyst.
6
+ */
7
+ const CATCHABLE_PROCESS_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
8
+ /**
9
+ * Install once per process entry. First catchable signal wins; later signals
10
+ * are ignored so settlement can finish without re-entry storms.
11
+ */
12
+ export function installProcessCancelHandlers(processRef = process) {
13
+ const controller = new AbortController();
14
+ let received;
15
+ const listeners = new Map();
16
+ for (const sig of CATCHABLE_PROCESS_SIGNALS) {
17
+ const onSignal = () => {
18
+ if (received !== undefined)
19
+ return;
20
+ received = sig;
21
+ try {
22
+ controller.abort(sig);
23
+ }
24
+ catch {
25
+ // already aborted
26
+ }
27
+ };
28
+ listeners.set(sig, onSignal);
29
+ processRef.on(sig, onSignal);
30
+ }
31
+ return {
32
+ signal: controller.signal,
33
+ receivedSignal: () => received,
34
+ dispose() {
35
+ for (const [sig, onSignal] of listeners) {
36
+ processRef.off(sig, onSignal);
37
+ }
38
+ listeners.clear();
39
+ },
40
+ };
41
+ }
42
+ /** Signal name when this AbortSignal was aborted by installProcessCancelHandlers. */
43
+ export function processCancelSignalName(signal) {
44
+ if (signal === undefined || signal.aborted !== true)
45
+ return undefined;
46
+ const reason = signal.reason;
47
+ if (reason === "SIGTERM" ||
48
+ reason === "SIGINT" ||
49
+ reason === "SIGHUP") {
50
+ return reason;
51
+ }
52
+ return undefined;
53
+ }
54
+ /** Diagnostic line for a catchable process-signal termination (failure honesty). */
55
+ export function processCancelDiagnostic(signalName) {
56
+ return `ak-role terminated by ${signalName}`;
57
+ }
@@ -32,7 +32,7 @@ import { NOTARY_OUTPUT_TOOL_NAME, } from "../notary-contracts.js";
32
32
  import { COUNTERSIGN_OUTPUT_TOOL_NAME, } from "../countersign-contracts.js";
33
33
  import { GLEANER_LEFT_OUTPUT_TOOL_NAME, } from "../gleaner-left-contracts.js";
34
34
  import { DIARIST_OUTPUT_TOOL_NAME, } from "../diarist-contracts.js";
35
- import { SECRETARIAT_OUTPUT_TOOL_NAME, } from "../secretariat-contracts.js";
35
+ import { SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, SECRETARIAT_OUTPUT_TOOL_NAME, } from "../secretariat-contracts.js";
36
36
  import { INSPECTOR_OUTPUT_TOOL_NAME, } from "../inspector-contracts.js";
37
37
  import { GATEKEEPER_OUTPUT_TOOL_NAME, } from "../package-contracts/gatekeeper-output.js";
38
38
  import { NAVIGATOR_OUTPUT_TOOL_NAME, } from "../package-contracts/navigator-output.js";
@@ -2254,11 +2254,104 @@ async function settleLawfulDiaristTerminalResult(admitted, authority, scope) {
2254
2254
  export async function trySettleDiaristTerminalResult(admitted, authority, scope) {
2255
2255
  return settleLawfulDiaristTerminalResult(admitted, authority, scope);
2256
2256
  }
2257
+ /**
2258
+ * #969 seat projection sole authority: 给事中 terminal (署|上呈) booked as a
2259
+ * durable custom entry (envelope-safe) — ledger accepted stays LLM params (#836).
2260
+ * Never scan toolResult rows: those are memory-only on headless/ACP (#617/#959).
2261
+ * Receipt bytes stay original; nested runId rides beside them.
2262
+ * Caller must only apply this when the terminal-defining submission actually
2263
+ * produced the officer entry (gate-bound converged, or 给事中 audit_escalation).
2264
+ */
2265
+ function countersignTerminalFromEntries(entries) {
2266
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
2267
+ const entry = entries[index];
2268
+ if (entry?.type !== "custom")
2269
+ continue;
2270
+ if (entry.customType !== SECRETARIAT_GATE_OFFICER_ENTRY_TYPE)
2271
+ continue;
2272
+ const data = entry.data !== null && typeof entry.data === "object" && !Array.isArray(entry.data)
2273
+ ? entry.data
2274
+ : undefined;
2275
+ if (data?.officer !== "countersign")
2276
+ continue;
2277
+ if (data.receipt === undefined)
2278
+ continue;
2279
+ const runId = typeof data.runId === "string" && data.runId.trim() !== ""
2280
+ ? data.runId
2281
+ : undefined;
2282
+ return {
2283
+ receipt: data.receipt,
2284
+ ...(runId === undefined ? {} : { runId }),
2285
+ };
2286
+ }
2287
+ return undefined;
2288
+ }
2289
+ function withCountersignTerminalFact(roleOutcome, officer) {
2290
+ const prior = roleOutcome.decisiveFacts !== undefined && isRecord(roleOutcome.decisiveFacts)
2291
+ ? roleOutcome.decisiveFacts
2292
+ : {};
2293
+ return {
2294
+ ...roleOutcome,
2295
+ decisiveFacts: {
2296
+ ...prior,
2297
+ [SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY]: officer,
2298
+ },
2299
+ };
2300
+ }
2301
+ /**
2302
+ * Terminal-defining secretariat submission owns officer projection.
2303
+ * Last readable secretariatStatus on accepted payloads decides: only gate-bound
2304
+ * converged projects a prior durable officer entry. Parent escalate bypasses the
2305
+ * gate and must not inherit a stale pass entry from an earlier turn (#969).
2306
+ */
2307
+ function acceptedSecretariatDefinesOfficerProjection(roleOutcome) {
2308
+ const payloads = roleOutcome.payloads ?? [];
2309
+ for (let index = payloads.length - 1; index >= 0; index -= 1) {
2310
+ const payload = payloads[index];
2311
+ if (!isRecord(payload))
2312
+ continue;
2313
+ const status = payload.secretariatStatus;
2314
+ if (typeof status !== "string")
2315
+ continue;
2316
+ return status === "converged";
2317
+ }
2318
+ return false;
2319
+ }
2257
2320
  async function settleLawfulSecretariatTerminalResult(admitted, authority, scope) {
2258
- return settleLawfulSeatAcceptedTerminalResult(admitted, authority, {
2321
+ const settled = await settleLawfulSeatAcceptedTerminalResult(admitted, authority, {
2259
2322
  role: "secretariat",
2260
2323
  toolName: SECRETARIAT_OUTPUT_TOOL_NAME,
2261
2324
  }, scope);
2325
+ if (settled === undefined)
2326
+ return undefined;
2327
+ const coordinates = coordinatesFromAdmitted(authority, admitted);
2328
+ const entries = await readLawfulSettlementEntries(coordinates.sessionFile) ?? [];
2329
+ const officer = countersignTerminalFromEntries(entries);
2330
+ if (officer === undefined)
2331
+ return settled;
2332
+ // 给事中上呈: public payloads = officer receipt; runId + receipt fact beside.
2333
+ // This kind is produced only when beforeAccept booked the officer entry.
2334
+ if (settled.roleOutcome.kind === "audit_escalation") {
2335
+ return {
2336
+ ...settled,
2337
+ roleOutcome: withCountersignTerminalFact({
2338
+ ...settled.roleOutcome,
2339
+ payloads: [officer.receipt],
2340
+ }, officer),
2341
+ };
2342
+ }
2343
+ // Pass (署): keep 中书省 payloads; present 给事中 判词 + runId via decisiveFacts.
2344
+ // Parent escalate accepted terminal must not project a stale prior pass entry.
2345
+ if (settled.roleOutcome.kind === "accepted") {
2346
+ if (!acceptedSecretariatDefinesOfficerProjection(settled.roleOutcome)) {
2347
+ return settled;
2348
+ }
2349
+ return {
2350
+ ...settled,
2351
+ roleOutcome: withCountersignTerminalFact(settled.roleOutcome, officer),
2352
+ };
2353
+ }
2354
+ return settled;
2262
2355
  }
2263
2356
  /** Try to settle a lawful Secretariat Terminal; undefined only for genuine absence. */
2264
2357
  export async function trySettleSecretariatTerminalResult(admitted, authority, scope) {
@@ -691,6 +691,25 @@ async function summonGateOfficer(options) {
691
691
  ...common
692
692
  });
693
693
  }
694
+ if (options.officer === "countersign") {
695
+ const { runIdFromRunDirectory } = await import("./run-terminal-artifacts.js");
696
+ const correlationId = runIdFromRunDirectory(options.sourceRunDirectory);
697
+ if (correlationId === void 0 || correlationId.trim() === "") {
698
+ throw new Error(
699
+ `countersign gate summon requires parent runId from sourceRunDirectory: ${options.sourceRunDirectory}`
700
+ );
701
+ }
702
+ const { readBoardTicketNumber } = await import("./run-ticket-number.js");
703
+ const parentTicket = await readBoardTicketNumber(options.sourceRunDirectory);
704
+ const instruction = options.reask ?? gateReviewInstruction ?? "";
705
+ return summonPublicRole({
706
+ role: "countersign",
707
+ argv: ["--project", options.cwd, "--", instruction],
708
+ ...common,
709
+ correlationId,
710
+ ...parentTicket === void 0 ? {} : { boundTicketNumber: parentTicket }
711
+ });
712
+ }
694
713
  return summonPublicRole({
695
714
  role: "inspector",
696
715
  argv: [`\u5377\u5B97\u6307\u9488\uFF1A${options.sourceRunDirectory}`],
@@ -1,8 +1,12 @@
1
1
  const SECRETARIAT_OUTPUT_TOOL_NAME = "ak_secretariat_output";
2
2
  const SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME = "ak_secretariat_summon_countersign";
3
3
  const SECRETARIAT_ACCEPTED_TEXT = "\u4E2D\u4E66\u7701\u56DE\u6267\u5DF2\u63A5\u53D7";
4
+ const SECRETARIAT_GATE_OFFICER_ENTRY_TYPE = "ak-secretariat-gate-officer";
5
+ const SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY = "countersignTerminal";
4
6
  export {
5
7
  SECRETARIAT_ACCEPTED_TEXT,
8
+ SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY,
9
+ SECRETARIAT_GATE_OFFICER_ENTRY_TYPE,
6
10
  SECRETARIAT_OUTPUT_TOOL_NAME,
7
11
  SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME
8
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.4620",
3
+ "version": "0.1.4641",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -19,7 +19,6 @@ const entries = [
19
19
  "pi/in-process-session",
20
20
  "activation-ledger-git",
21
21
  "activation-ledger-topology",
22
- "activation-reconciliation",
23
22
  "archivist-record-entry",
24
23
  // Pure subject nest topology — static import of archivist-record-entry;
25
24
  // cold discovery without SessionManager (#636). Keep listed while that
@@ -91,11 +91,6 @@ export function activationBookDirectory(ledgerHome: string, bookKey: string): st
91
91
  return join(ledgerHome, "books", bookKey);
92
92
  }
93
93
 
94
- /** Append-only waiting ledger path for one book. */
95
- export function activationWaitingLedgerPath(ledgerHome: string, bookKey: string): string {
96
- return join(activationBookDirectory(ledgerHome, bookKey), "waiting.jsonl");
97
- }
98
-
99
94
  /** True when candidate resolves strictly inside root (boundary-safe; not a string-prefix check). */
100
95
  export function pathContainedIn(root: string, candidate: string): boolean {
101
96
  const rel = relative(root, candidate);
@@ -323,7 +318,7 @@ export function ensureRealDirectoryTree(root: string, targetDir: string): string
323
318
 
324
319
  /**
325
320
  * Reject a pre-existing ledger-file symlink before open/write follows it.
326
- * A computed book may only append to its own regular waiting.jsonl — any symlink,
321
+ * A computed book path may only write its own regular file — any symlink,
327
322
  * including a target still inside the machine ledger home (cross-book redirect),
328
323
  * violates ADR 0048 partition identity. Missing paths are admitted (O_CREAT).
329
324
  */