@akagilnc/pi-workflow-roles 0.1.4321 → 0.1.4363

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 (37) hide show
  1. package/dist/acp-host/production-host.js +81 -99
  2. package/dist/doctor-contracts.js +2 -2
  3. package/dist/headless-host/production-host.js +81 -99
  4. package/dist/merger-contracts.js +2 -2
  5. package/dist/migrate-book-topology.js +13 -8
  6. package/dist/notary-contracts.js +2 -2
  7. package/dist/package-contracts/auditor-output.js +2 -2
  8. package/dist/package-contracts/fixer-output.js +2 -2
  9. package/dist/package-contracts/gatekeeper-output.js +2 -2
  10. package/dist/package-contracts/navigator-output.js +2 -2
  11. package/dist/package-contracts/terminating-infrastructure.js +7 -2
  12. package/dist/public-cli/case-dossier-delivery.js +45 -28
  13. package/dist/public-cli/main.js +71 -89
  14. package/dist/public-cli/post-admission.js +41 -41
  15. package/dist/public-cli/settlement.js +3 -53
  16. package/dist/sitian-appender.js +21 -4
  17. package/package.json +1 -1
  18. package/src/collector-tool-schemas.ts +2 -2
  19. package/src/countersign-role.ts +2 -2
  20. package/src/diarist-role.ts +2 -2
  21. package/src/doctor-contracts.ts +2 -2
  22. package/src/gleaner-left-role.ts +2 -2
  23. package/src/inspector-role.ts +2 -2
  24. package/src/judge-role.ts +2 -2
  25. package/src/merger-contracts.ts +2 -2
  26. package/src/notary-contracts.ts +2 -2
  27. package/src/package-contracts/auditor-output.ts +2 -2
  28. package/src/package-contracts/fixer-output.ts +2 -2
  29. package/src/package-contracts/gatekeeper-output.ts +2 -2
  30. package/src/package-contracts/navigator-output.ts +2 -2
  31. package/src/package-contracts/terminating-infrastructure.ts +13 -3
  32. package/src/public-cli/case-dossier-delivery.ts +48 -29
  33. package/src/public-cli/post-admission.ts +42 -50
  34. package/src/public-cli/settlement.ts +3 -53
  35. package/src/reviewer-role.ts +2 -2
  36. package/src/sitian-appender.ts +36 -3
  37. package/src/worker-role.ts +2 -2
@@ -18,9 +18,11 @@ import {
18
18
  import { CliUsageError } from "./cli-errors.ts";
19
19
  import type { RoleTurnRequestProjectionOptions } from "./turn-request.ts";
20
20
  import {
21
+ bindAdmittedTicketNumber,
21
22
  buildInstructionTransportPrompt,
22
23
  freezeAttachmentsIntoRun,
23
24
  } from "./invocation.ts";
25
+ import { readRecordedSubmissionRows } from "../submission-ledger.ts";
24
26
  import { pathContainedIn } from "../activation-ledger-topology.ts";
25
27
  import { pickEngineAxis } from "../package-resources/engine-material.ts";
26
28
  import { resolveHostAwareSessionAvailability } from "../session-identity.ts";
@@ -29,7 +31,6 @@ import type {
29
31
  ControlledFailureCause,
30
32
  DurablePrincipal,
31
33
  DurablePrincipalAuthority,
32
- RoleTurnContinuation,
33
34
  RoleTurnHost,
34
35
  RoleTurnKnownFailure,
35
36
  RoleTurnRequest,
@@ -37,10 +38,7 @@ import type {
37
38
  SessionCustomEntryAppender,
38
39
  } from "../host-contracts.ts";
39
40
  import { isOfficerReviewSeat } from "../host-contracts.ts";
40
- import {
41
- deliverCaseDossierAsAttachment,
42
- projectCaseDossierPointerSection,
43
- } from "./case-dossier-delivery.ts";
41
+ import { deliverCaseDossierAsAttachment } from "./case-dossier-delivery.ts";
44
42
 
45
43
  /** Original error bytes, never relabeled — a secondary fact riding beside a classified cause. */
46
44
  function describeCaughtError(error: unknown): { name?: string; message: string; code?: string | number } {
@@ -51,17 +49,6 @@ function describeCaughtError(error: unknown): { name?: string; message: string;
51
49
  return { message: String(error) };
52
50
  }
53
51
 
54
- /** Append one system section to a continuation prompt, keeping its kind. */
55
- function appendContinuationSection(
56
- continuation: RoleTurnContinuation,
57
- section: string,
58
- ): RoleTurnContinuation {
59
- const prompt = `${continuation.prompt}\n\n${section}`;
60
- return continuation.kind === "initial"
61
- ? { kind: "initial", prompt }
62
- : { kind: "resume", prompt };
63
- }
64
-
65
52
  /**
66
53
  * Nested gate summons (station child) on an officer seat: dialogue content is
67
54
  * peer words only (#879). ADR 0081 case dossier still hangs via the existing
@@ -592,10 +579,32 @@ export async function dispatchPostAdmissionTurn<
592
579
  */
593
580
  let afterDispatchApplied = false;
594
581
  const finishAfterTurn = async (result: DispatchOutcome): Promise<DispatchOutcome> => {
595
- if (adapters.afterDispatch === undefined || afterDispatchApplied) return result;
582
+ if (afterDispatchApplied) return result;
596
583
  afterDispatchApplied = true;
597
584
  try {
598
- await adapters.afterDispatch(admitted, lease);
585
+ // #858: an unbound seat may assert its ticket on the existing receipt.
586
+ // Read the original accepted payload; do not rewrite it, infer from prose,
587
+ // or reject missing/malformed declarations. An existing binding wins.
588
+ if (admitted.ticketNumber === undefined) {
589
+ const rows = await readRecordedSubmissionRows(
590
+ admitted.projectRoot,
591
+ admitted.runId,
592
+ { home: homeFromRunDirectory(admitted.runDirectory), sessionParent: join(admitted.runDirectory, "session", "session.jsonl") },
593
+ );
594
+ const asserted = rows
595
+ .filter((row) => row.kind === "accepted" && row.role === admitted.role)
596
+ .map((row) => row.accepted)
597
+ .find((payload) => {
598
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return false;
599
+ const value = (payload as { ticketNumber?: unknown }).ticketNumber;
600
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
601
+ });
602
+ const ticketNumber = asserted === undefined
603
+ ? undefined
604
+ : (asserted as { ticketNumber: number }).ticketNumber;
605
+ if (ticketNumber !== undefined) await bindAdmittedTicketNumber(admitted, ticketNumber);
606
+ }
607
+ if (adapters.afterDispatch !== undefined) await adapters.afterDispatch(admitted, lease);
599
608
  return result;
600
609
  } catch (error) {
601
610
  const primaryFailure =
@@ -728,10 +737,11 @@ export async function dispatchPostAdmissionTurn<
728
737
 
729
738
  // Turn request is assembled after beforeDispatch so this turn sees whatever it
730
739
  // settled — the seat's ticket bind re-projection and any court diarist station
731
- // writes (#742). Case dossier delivery (ADR 0081 / #709) rides here once for
732
- // every public entry. #879: station-child officer dialogue keeps peer body
733
- // intact 起居录 hangs via existing attachments freeze (not prompt wrap,
734
- // not RoleTurnRequest.materials). Other entries keep the neutral prompt section.
740
+ // writes (#742). Case dossier delivery (ADR 0081 / #709 / #858) rides here once
741
+ // for every public entry on the existing attachments readingMaterial face
742
+ // (station-child and ordinary share one seam). Dialogue continuation stays
743
+ // caller/peer opaque never splice system path sections into user dialogue.
744
+ // No package-resume parallel face or typed resume identity.
735
745
  let turnRequest: RoleTurnRequest =
736
746
  env.signal === undefined ? request : { ...request, signal: env.signal };
737
747
  if (env.stationChild !== undefined) {
@@ -745,34 +755,16 @@ export async function dispatchPostAdmissionTurn<
745
755
  if (typeof liveHost === "string" && liveHost.trim() !== "") {
746
756
  turnRequest = { ...turnRequest, host: liveHost.trim() };
747
757
  }
748
- if (isStationChildOfficerDialogue(admitted.role, env)) {
749
- // 0081 non-body face: freeze pointer section under run/attachments/.
750
- // Peer dialogue continuation.prompt stays parent payload only — the seat
751
- // consumes the freeze via loadCaseDossierReadingMaterial → existing
752
- // agent-start readingMaterial / systemPrompt.materials fold (not prompt splice,
753
- // not RoleTurnRequest.materials).
754
- await deliverCaseDossierAsAttachment({
755
- ticketNumber: admitted.ticketNumber,
756
- projectRoot: admitted.projectRoot,
757
- home: env.home,
758
- runDirectory: admitted.runDirectory,
759
- });
760
- } else {
761
- const dossierSection = await projectCaseDossierPointerSection({
762
- ticketNumber: admitted.ticketNumber,
763
- projectRoot: admitted.projectRoot,
764
- home: env.home,
765
- });
766
- if (dossierSection !== undefined) {
767
- turnRequest = {
768
- ...turnRequest,
769
- continuation: appendContinuationSection(
770
- turnRequest.continuation,
771
- dossierSection,
772
- ),
773
- };
774
- }
775
- }
758
+ // 0081 non-dialogue face: freeze pointer section under run/attachments/.
759
+ // Seat consumes via loadCaseDossierReadingMaterial existing agent-start
760
+ // readingMaterial / systemPrompt.materials fold. Caller instruction, empty
761
+ // request, and resume --message stay verbatim.
762
+ await deliverCaseDossierAsAttachment({
763
+ ticketNumber: admitted.ticketNumber,
764
+ projectRoot: admitted.projectRoot,
765
+ home: env.home,
766
+ runDirectory: admitted.runDirectory,
767
+ });
776
768
 
777
769
  // Authoritative host write happens here, at the real dispatch boundary —
778
770
  // immediately before the turn actually starts, after every retryable
@@ -1086,38 +1086,7 @@ async function loadBoundAuditorVolumes(
1086
1086
  }
1087
1087
  const parentId = parentEntries.find((entry) => entry.type === "session")?.id;
1088
1088
  if (parentId === undefined) return undefined;
1089
- const isResumeEnvelopeBytes = (value: unknown): boolean => {
1090
- if (typeof value !== "string") return false;
1091
- if (value.length === 0) return true;
1092
- const nl = value.indexOf("\n");
1093
- const firstLine = nl === -1 ? value : value.slice(0, nl);
1094
- const body = firstLine === "" && nl !== -1 ? value.slice(nl + 1) : value;
1095
- return body.startsWith("本次配置的劳务引擎及其手册:") || body.startsWith("- engine:");
1096
- };
1097
- const isResumeEnvelope = (msg: unknown): boolean => {
1098
- if (!isRecord(msg) || msg.role !== "user") return false;
1099
- const text = typeof msg.text === "string" ? msg.text : typeof (msg as { content?: unknown }).content === "string" ? (msg as { content: string }).content : undefined;
1100
- if (isResumeEnvelopeBytes(text)) return true;
1101
- const content = (msg as { content?: unknown }).content;
1102
- if (Array.isArray(content)) {
1103
- return content.some((p) => isRecord(p) && (isResumeEnvelopeBytes(p.text) || isResumeEnvelopeBytes(p.content)));
1104
- }
1105
- return false;
1106
- };
1107
- let latestParentUserIndex = -1;
1108
- for (let i = parentEntries.length - 1; i >= 0; i -= 1) {
1109
- const entry = parentEntries[i];
1110
- if (entry?.type !== "message" || entry.message?.role !== "user") continue;
1111
- if (isResumeEnvelope(entry.message)) continue;
1112
- latestParentUserIndex = i;
1113
- break;
1114
- }
1115
1089
  const childDirectories = [join(dirname(sessionFile), "auditor-roles")];
1116
- // Auto-resume seam (owner A): stale check must ignore resume envelope and
1117
- // prioritize retention. Previous `attemptEntryIndex < latest` discarded the
1118
- // first attempt's child after resume advanced latest, losing retentionFailure
1119
- // when retry had no compliance entry. Fix: ignore envelope for staleness and
1120
- // prefer any valid compliance failure before falling back to primary.
1121
1090
  const valid: BoundAuditorVolume[] = [];
1122
1091
  let sawAnyDirectory = false;
1123
1092
  for (const childDirectory of childDirectories) {
@@ -1166,10 +1135,6 @@ async function loadBoundAuditorVolumes(
1166
1135
  typeof bindingParent?.attemptEntryId === "string"
1167
1136
  ? bindingParent.attemptEntryId
1168
1137
  : undefined;
1169
- const attemptEntryIndex =
1170
- attemptEntryId === undefined
1171
- ? -1
1172
- : parentEntries.findIndex((entry) => entry.id === attemptEntryId);
1173
1138
  const boundSessionFile =
1174
1139
  typeof bindingParent?.sessionFile === "string"
1175
1140
  ? bindingParent.sessionFile
@@ -1177,12 +1142,7 @@ async function loadBoundAuditorVolumes(
1177
1142
  ? header.parentSession
1178
1143
  : undefined;
1179
1144
  if (boundSessionFile !== sessionFile) continue;
1180
- if (
1181
- bindingParent !== undefined &&
1182
- (bindingParent.sessionId !== parentId || attemptEntryIndex < latestParentUserIndex)
1183
- ) {
1184
- continue;
1185
- }
1145
+ if (bindingParent !== undefined && bindingParent.sessionId !== parentId) continue;
1186
1146
  if (bindingParent === undefined && header.parentSession !== sessionFile) continue;
1187
1147
  valid.push({
1188
1148
  entries: entries.slice(start, end),
@@ -1190,8 +1150,8 @@ async function loadBoundAuditorVolumes(
1190
1150
  sessionFile,
1191
1151
  ...(attemptEntryId === undefined ? {} : { attemptEntryId }),
1192
1152
  });
1193
- // Keep every qualifying interval in the current parent-user range.
1194
- // A single first-match break drops later same-user summons failures (#636).
1153
+ // Keep every interval bound to this parent. Auditor payload is relayed as
1154
+ // recorded; code does not expire it from later user-message shape (#858).
1195
1155
  }
1196
1156
  }
1197
1157
  }
@@ -1254,16 +1214,6 @@ function providerStopFallbackFromAuditorVolumes(
1254
1214
  return undefined;
1255
1215
  }
1256
1216
 
1257
- /** Recover a provider stop from the auditor child bound to the current parent attempt. */
1258
- export async function readBoundAuditorKnownFailure(
1259
- sessionFile: string,
1260
- ): Promise<RoleTurnKnownFailure | undefined> {
1261
- const volumes = await loadBoundAuditorVolumes(sessionFile);
1262
- if (volumes === undefined) return undefined;
1263
- return complianceFailureFromAuditorVolumes(volumes)
1264
- ?? providerStopFallbackFromAuditorVolumes(volumes);
1265
- }
1266
-
1267
1217
  /** Strong auditor tier only — retained compliance-failure entries, no provider-stop fallback. */
1268
1218
  async function readBoundAuditorComplianceFailure(
1269
1219
  sessionFile: string,
@@ -1,7 +1,7 @@
1
1
  import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts";
2
2
  import { Type } from "typebox";
3
3
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
4
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
4
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
5
5
 
6
6
  import type { AnyCanonicalSkillBinding, CanonicalSkillBinding } from "./canonical-skill-binding.ts";
7
7
  export type { CanonicalSkillBinding };
@@ -43,7 +43,7 @@ const reviewerOutputVariants = Type.Union([
43
43
  amendments: Type.Optional(reviewerAmendmentsSchema),
44
44
  }, { additionalProperties: false }),
45
45
  ]);
46
- export const reviewerOutputSchema = withInfrastructureFailureDeclaration(
46
+ export const reviewerOutputSchema = withTerminatingOutputDeclarations(
47
47
  openToolObjectFromUnion(reviewerOutputVariants),
48
48
  );
49
49
  export type ReviewerRoleDependencies = {
@@ -210,6 +210,22 @@ type SitianRecordPath = {
210
210
  readonly ledgerHome: string;
211
211
  };
212
212
 
213
+ /** Sole records leaf under every sitian volume directory. */
214
+ const SITIAN_RECORDS_LEAF = "records.jsonl" as const;
215
+
216
+ /**
217
+ * Ticket-provenance under-book paths (docs/dossier-topology.md sole authority).
218
+ * Writer ticket branch and owner-visible shape share this join — one code path.
219
+ */
220
+ function ticketProvenanceUnderBookPaths(
221
+ ledgerHome: string,
222
+ bookKey: string,
223
+ ticketId: string,
224
+ ): { sessionDir: string; recordFile: string } {
225
+ const sessionDir = join(activationBookDirectory(ledgerHome, bookKey), ticketId);
226
+ return { sessionDir, recordFile: join(sessionDir, SITIAN_RECORDS_LEAF) };
227
+ }
228
+
213
229
  /** Pure topology owner shared by ambient writes and explicit-home submission reads. */
214
230
  export function resolveSitianRecordPathInLedger(
215
231
  input: SitianRecordInput,
@@ -228,13 +244,16 @@ export function resolveSitianRecordPathInLedger(
228
244
  : undefined;
229
245
 
230
246
  let sessionDir: string;
247
+ let recordFile: string;
231
248
  if (ticketNumber !== undefined) {
232
249
  // docs/dossier-topology.md: ticket dir holds the unique records.jsonl directly (#900).
233
- const bookDir = activationBookDirectory(
250
+ const paths = ticketProvenanceUnderBookPaths(
234
251
  ledgerHome,
235
252
  resolveBookKeyFromGit(input.cwd ?? process.cwd()),
253
+ ticketNumber,
236
254
  );
237
- sessionDir = join(bookDir, ticketNumber);
255
+ sessionDir = paths.sessionDir;
256
+ recordFile = paths.recordFile;
238
257
  } else {
239
258
  if (
240
259
  input.sessionParent === undefined
@@ -244,12 +263,26 @@ export function resolveSitianRecordPathInLedger(
244
263
  throw new Error("Sitian record ownership requires a parent session inside the ledger home");
245
264
  }
246
265
  sessionDir = join(dirname(input.sessionParent), category);
266
+ recordFile = join(sessionDir, SITIAN_RECORDS_LEAF);
247
267
  }
248
268
 
249
- const recordFile = join(sessionDir, "records.jsonl");
250
269
  return { sessionDir, recordFile, ledgerHome };
251
270
  }
252
271
 
272
+ /**
273
+ * Owner-visible ticket 起居录 path shape via the writer ticket joins
274
+ * (ticketProvenanceUnderBookPaths). Ledger leaf is the package-owned `.ak-roles`
275
+ * name (ADR 0048); variable slots keep owner labels; no fake-home path reverse.
276
+ */
277
+ export function projectTicketRecordsPathShape(): string {
278
+ const { recordFile } = ticketProvenanceUnderBookPaths(
279
+ join("~", ".ak-roles"),
280
+ "<簿>",
281
+ "<票号>",
282
+ );
283
+ return recordFile.replace(/\\/g, "/");
284
+ }
285
+
253
286
  /** Compute a write destination from ambient ledger topology (ADR 0065). */
254
287
  export function resolveSitianRecordPath(input: SitianRecordInput): SitianRecordPath {
255
288
  const ledgerHome =
@@ -1,7 +1,7 @@
1
1
  import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } from "./host-contracts.ts";
2
2
  import { Type, type Static } from "typebox";
3
3
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
4
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
4
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
5
5
  import { CorrectableSubmissionError } from "./submission-correctable-error.ts";
6
6
 
7
7
  import type {
@@ -73,7 +73,7 @@ const coderOutputVariants = Type.Union([
73
73
  })),
74
74
  }, { additionalProperties: false }),
75
75
  ]);
76
- export const coderOutputSchema = withInfrastructureFailureDeclaration(
76
+ export const coderOutputSchema = withTerminatingOutputDeclarations(
77
77
  openToolObjectFromUnion(coderOutputVariants),
78
78
  );
79
79
  export type { FixerOutput, CoderOutput };