@akagilnc/pi-workflow-roles 0.1.3932 → 0.1.3975

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 (162) hide show
  1. package/CLAUDE.md +1 -1
  2. package/dist/acp-host/production-host.js +2389 -6018
  3. package/dist/analyst-gate-cycles-read.js +15 -19
  4. package/dist/audit-escalation.js +16 -46
  5. package/dist/auditor-dossier-tool.js +7 -62
  6. package/dist/collector-identity.js +0 -157
  7. package/dist/collector-ledger.js +2 -42
  8. package/dist/compliance-transport.js +40 -14
  9. package/dist/diarist-contracts.js +2 -40
  10. package/dist/doctor-contracts.js +39 -108
  11. package/dist/doctor-evidence.js +1 -5
  12. package/dist/engine-detour-tool.js +1 -1
  13. package/dist/gatekeeper-role.js +49 -38
  14. package/dist/headless-host/production-host.js +4814 -8450
  15. package/dist/host-contracts.js +0 -5
  16. package/dist/merger-contracts.js +4 -6
  17. package/dist/navigator-attendance.js +30 -52
  18. package/dist/navigator-invocation-identity.js +0 -1
  19. package/dist/navigator-public-session.js +2 -1
  20. package/dist/notary-contracts.js +4 -10
  21. package/dist/package-contracts/auditor-output.js +5 -23
  22. package/dist/package-contracts/fixer-output.js +25 -49
  23. package/dist/package-contracts/gatekeeper-output.js +3 -13
  24. package/dist/package-contracts/navigator-output.js +4 -7
  25. package/dist/package-contracts/reviewer-output.js +1 -95
  26. package/dist/package-contracts/terminating-tools.js +33 -144
  27. package/dist/package-contracts/worker-output.js +1 -3
  28. package/dist/public-cli/auto-resume.js +30 -68
  29. package/dist/public-cli/case-dossier-delivery.js +5 -17
  30. package/dist/public-cli/diarist-run.js +9 -4
  31. package/dist/public-cli/inspector-run.js +2 -8
  32. package/dist/public-cli/instruction-seat-run.js +3 -7
  33. package/dist/public-cli/invocation.js +5 -2
  34. package/dist/public-cli/main.js +6068 -6964
  35. package/dist/public-cli/notary-run.js +1 -8
  36. package/dist/public-cli/post-admission.js +314 -113
  37. package/dist/public-cli/run-lifecycle.js +14 -38
  38. package/dist/public-cli/settlement.js +209 -390
  39. package/dist/public-cli/terminal.js +51 -45
  40. package/dist/public-role-summons.js +1 -12
  41. package/dist/reviewer-dispatch.js +12 -258
  42. package/dist/submission-correctable-error.js +0 -12
  43. package/dist/submission-errors.js +6 -1
  44. package/dist/submission-ledger.js +125 -168
  45. package/dist/ticket-provenance-contracts.js +2 -49
  46. package/dist/ticket-provenance.js +82 -31
  47. package/extensions/role-runtime.ts +1 -6
  48. package/package.json +1 -1
  49. package/resources/836-deleted-machine-instruction-inventory.md +18 -0
  50. package/src/acp-host/role-turn-host.ts +8 -3
  51. package/src/analyst-gate-cycles-read.ts +17 -29
  52. package/src/audit-escalation.ts +20 -50
  53. package/src/auditor-dossier-tool.ts +5 -103
  54. package/src/canonical-skill-binding.ts +3 -2
  55. package/src/collector-identity.ts +0 -179
  56. package/src/collector-ledger.ts +3 -46
  57. package/src/collector-role.ts +32 -61
  58. package/src/collector-tool-schemas.ts +20 -14
  59. package/src/compliance-transport.ts +55 -17
  60. package/src/countersign-role.ts +17 -8
  61. package/src/diarist-contracts.ts +3 -36
  62. package/src/diarist-role.ts +13 -7
  63. package/src/diarist.ts +4 -16
  64. package/src/doctor-contracts.ts +73 -94
  65. package/src/doctor-evidence.ts +1 -1
  66. package/src/doctor-role.ts +3 -3
  67. package/src/engine-detour-tool.ts +4 -1
  68. package/src/external-host-turn-loop.ts +9 -3
  69. package/src/gatekeeper-pass-envelope.ts +7 -12
  70. package/src/gatekeeper-role.ts +56 -50
  71. package/src/gleaner-left-role.ts +4 -2
  72. package/src/headless-host/role-turn-host.ts +4 -5
  73. package/src/host-contracts.ts +3 -8
  74. package/src/judge-role.ts +19 -13
  75. package/src/merger-contracts.ts +10 -6
  76. package/src/merger-role.ts +1 -5
  77. package/src/navigator-attendance.ts +37 -74
  78. package/src/navigator-invocation-identity.ts +0 -6
  79. package/src/navigator-public-session.ts +2 -1
  80. package/src/notary-contracts.ts +7 -10
  81. package/src/package-contracts/auditor-output.ts +4 -21
  82. package/src/package-contracts/fixer-output.ts +34 -52
  83. package/src/package-contracts/gatekeeper-output.ts +5 -22
  84. package/src/package-contracts/navigator-output.ts +7 -7
  85. package/src/package-contracts/reviewer-output.ts +2 -150
  86. package/src/package-contracts/terminating-tools.ts +32 -147
  87. package/src/package-contracts/worker-output.ts +0 -1
  88. package/src/pi/adapter.ts +3 -8
  89. package/src/public-cli/auto-resume.ts +41 -94
  90. package/src/public-cli/case-dossier-delivery.ts +5 -15
  91. package/src/public-cli/countersign-run.ts +14 -6
  92. package/src/public-cli/diarist-run.ts +11 -9
  93. package/src/public-cli/inspector-run.ts +2 -18
  94. package/src/public-cli/instruction-seat-run.ts +3 -17
  95. package/src/public-cli/invocation.ts +5 -2
  96. package/src/public-cli/merger-run.ts +1 -1
  97. package/src/public-cli/notary-run.ts +1 -18
  98. package/src/public-cli/post-admission.ts +385 -115
  99. package/src/public-cli/run-lifecycle.ts +13 -44
  100. package/src/public-cli/settlement.ts +314 -562
  101. package/src/public-cli/terminal.ts +66 -72
  102. package/src/public-role-summons.ts +5 -30
  103. package/src/reviewer-dispatch.ts +29 -331
  104. package/src/reviewer-role.ts +16 -83
  105. package/src/role-envelope.ts +16 -24
  106. package/src/role-runtime-dependencies.ts +1 -6
  107. package/src/role-runtime.ts +65 -201
  108. package/src/submission-correctable-error.ts +0 -14
  109. package/src/submission-errors.ts +6 -1
  110. package/src/submission-ledger.ts +187 -187
  111. package/src/ticket-provenance-contracts.ts +4 -60
  112. package/src/ticket-provenance.ts +98 -32
  113. package/src/worker-role.ts +35 -36
  114. package/dist/activation-ledger-session.js +0 -105
  115. package/dist/activation-ledger.js +0 -139
  116. package/dist/activation-trace.js +0 -33
  117. package/dist/atomic-write.js +0 -23
  118. package/dist/auditor-role.js +0 -12
  119. package/dist/canonical-skill-binding.js +0 -61
  120. package/dist/collector-handbook.js +0 -142
  121. package/dist/collector-receipt.js +0 -107
  122. package/dist/collector-role.js +0 -566
  123. package/dist/collector-tool-schemas.js +0 -107
  124. package/dist/countersign-role.js +0 -28
  125. package/dist/diarist-role.js +0 -44
  126. package/dist/diarist.js +0 -66
  127. package/dist/doctor-role.js +0 -75
  128. package/dist/fixer-bash-seatbelt.js +0 -13
  129. package/dist/gatekeeper-pass-envelope.js +0 -77
  130. package/dist/gleaner-left-role.js +0 -26
  131. package/dist/inspector-role.js +0 -29
  132. package/dist/judge-role.js +0 -104
  133. package/dist/merger-role.js +0 -55
  134. package/dist/navigator-role.js +0 -12
  135. package/dist/notary-role.js +0 -134
  136. package/dist/oauth-keepalive.js +0 -158
  137. package/dist/pi/adapter.js +0 -223
  138. package/dist/reviewer-agent.js +0 -122
  139. package/dist/reviewer-child-executor.js +0 -194
  140. package/dist/reviewer-construction.js +0 -103
  141. package/dist/reviewer-execution-ledger.js +0 -166
  142. package/dist/reviewer-failure-diagnostic.js +0 -17
  143. package/dist/reviewer-role.js +0 -159
  144. package/dist/reviewer-scope-prompt.js +0 -21
  145. package/dist/reviewer-settlement.js +0 -66
  146. package/dist/reviewer-workspace.js +0 -132
  147. package/dist/role-runtime.js +0 -1604
  148. package/dist/stderr-jsonl.js +0 -26
  149. package/dist/tool-execution-observation.js +0 -141
  150. package/dist/worker-role.js +0 -345
  151. package/dist/worker-submission-gates.js +0 -305
  152. package/src/collector-receipt.ts +0 -140
  153. package/src/diagnostic-tail.ts +0 -18
  154. package/src/reviewer-agent.ts +0 -135
  155. package/src/reviewer-child-executor.ts +0 -252
  156. package/src/reviewer-construction.ts +0 -195
  157. package/src/reviewer-execution-ledger.ts +0 -212
  158. package/src/reviewer-failure-diagnostic.ts +0 -18
  159. package/src/reviewer-scope-prompt.ts +0 -24
  160. package/src/reviewer-settlement.ts +0 -74
  161. package/src/reviewer-workspace.ts +0 -105
  162. package/src/shape-unreadable-failure.ts +0 -49
@@ -56,25 +56,51 @@ export function ticketProvenanceEntryIdentity(
56
56
 
57
57
  export type AppendTicketProvenanceInput = {
58
58
  readonly ticketNumber: number;
59
- readonly entry: TicketProvenanceEntry;
60
59
  readonly cwd: string;
60
+ /** Original diarist row (or `{ original, unprojected: true }` envelope). */
61
+ readonly payload: unknown;
61
62
  /** Explicit package home (tests / admitted run); never process.env.HOME (#604). */
62
63
  readonly home?: string;
63
64
  readonly host?: string;
64
65
  readonly source?: string;
65
66
  };
66
67
 
67
- /** Append one transcribed block; returns existing pointer on identity hit. */
68
+ function identityFromPayload(ticketNumber: number, payload: unknown): string {
69
+ if (
70
+ typeof payload === "object"
71
+ && payload !== null
72
+ && !Array.isArray(payload)
73
+ ) {
74
+ const record = payload as Record<string, unknown>;
75
+ if (typeof record.sourceKind === "string" && typeof record.transcript === "string") {
76
+ const sourceRef = typeof record.sourceRef === "object" && record.sourceRef !== null && !Array.isArray(record.sourceRef)
77
+ ? record.sourceRef as TicketProvenanceIdentityInput["sourceRef"]
78
+ : {};
79
+ return ticketProvenanceEntryIdentity({
80
+ ticketNumber,
81
+ sourceKind: record.sourceKind,
82
+ sourceRef,
83
+ transcript: record.transcript,
84
+ });
85
+ }
86
+ }
87
+ return createHash("sha256")
88
+ .update(`${String(ticketNumber)}\u0000${JSON.stringify(payload)}`, "utf8")
89
+ .digest("hex");
90
+ }
91
+
92
+ /** Append one original row; returns existing pointer on identity hit. */
68
93
  export function appendTicketProvenanceEntry(
69
94
  input: AppendTicketProvenanceInput,
70
95
  ): RecordPointer {
71
96
  const subject = ticketProvenanceSubject(input.ticketNumber);
72
- const identity = ticketProvenanceEntryIdentity({
73
- ticketNumber: input.ticketNumber,
74
- sourceKind: input.entry.sourceKind,
75
- sourceRef: input.entry.sourceRef,
76
- transcript: input.entry.transcript,
77
- });
97
+ const identity = identityFromPayload(input.ticketNumber, input.payload);
98
+ const record = typeof input.payload === "object" && input.payload !== null && !Array.isArray(input.payload)
99
+ ? input.payload as Record<string, unknown>
100
+ : undefined;
101
+ const sourceRef = record !== undefined && typeof record.sourceRef === "object" && record.sourceRef !== null && !Array.isArray(record.sourceRef)
102
+ ? record.sourceRef as { sessionFile?: unknown; entryId?: unknown }
103
+ : undefined;
78
104
  return sitianReport({
79
105
  level: "event",
80
106
  kind: TICKET_PROVENANCE_KIND,
@@ -84,13 +110,14 @@ export function appendTicketProvenanceEntry(
84
110
  ...(input.home === undefined ? {} : { home: input.home }),
85
111
  host: input.host ?? "diarist",
86
112
  source: input.source ?? "diarist",
87
- payload: input.entry,
113
+ payload: input.payload,
88
114
  raw:
89
- input.entry.sourceRef.sessionFile !== undefined &&
90
- input.entry.sourceRef.entryId !== undefined
115
+ sourceRef !== undefined
116
+ && typeof sourceRef.sessionFile === "string"
117
+ && (typeof sourceRef.entryId === "string" || typeof sourceRef.entryId === "number")
91
118
  ? {
92
- sessionFile: input.entry.sourceRef.sessionFile,
93
- entryId: input.entry.sourceRef.entryId,
119
+ sessionFile: sourceRef.sessionFile,
120
+ entryId: sourceRef.entryId,
94
121
  }
95
122
  : undefined,
96
123
  });
@@ -125,6 +152,11 @@ export function resolveTicketProvenanceVolume(
125
152
  export type ReadTicketProvenanceResult = {
126
153
  /** Readable diary body entries only. */
127
154
  readonly entries: readonly TicketProvenanceEntry[];
155
+ /**
156
+ * Original payloads that did not project — kept beside typed entries (#836 / ADR 0075).
157
+ * Never dropped from the volume.
158
+ */
159
+ readonly unprojected: readonly unknown[];
128
160
  /** Typed diagnostics on the same partition (collector/issue-source failures). */
129
161
  readonly diagnostics: readonly TicketProvenanceDiagnostic[];
130
162
  readonly records: readonly SitianRecord[];
@@ -142,6 +174,7 @@ export async function readTicketProvenance(
142
174
  const { recordFile } = resolveTicketProvenanceVolume(ticketNumber, cwd, home);
143
175
  const { records } = await readSitianRecords(recordFile);
144
176
  const entries: TicketProvenanceEntry[] = [];
177
+ const unprojected: unknown[] = [];
145
178
  const diagnostics: TicketProvenanceDiagnostic[] = [];
146
179
  let skipped = 0;
147
180
  for (const record of records) {
@@ -157,12 +190,12 @@ export async function readTicketProvenance(
157
190
  }
158
191
  const entry = projectTicketProvenanceEntry(record.payload);
159
192
  if (entry === undefined) {
160
- skipped += 1;
193
+ unprojected.push(record.payload);
161
194
  continue;
162
195
  }
163
196
  entries.push(entry);
164
197
  }
165
- return { entries, diagnostics, records, recordFile, skipped };
198
+ return { entries, unprojected, diagnostics, records, recordFile, skipped };
166
199
  }
167
200
 
168
201
  /**
@@ -187,45 +220,76 @@ function markdownFenceFor(text: string): string {
187
220
  export function renderTicketProvenanceMarkdown(input: {
188
221
  readonly ticketNumber: number;
189
222
  readonly entries: readonly TicketProvenanceEntry[];
223
+ readonly unprojected?: readonly unknown[];
190
224
  }): string {
225
+ const unprojected = input.unprojected ?? [];
191
226
  const lines: string[] = [
192
227
  `# 起居录 · #${input.ticketNumber}`,
193
228
  "",
194
- `条目数:${input.entries.length}`,
229
+ `条目数:${input.entries.length + unprojected.length}`,
195
230
  "",
196
231
  ];
197
232
  let index = 0;
198
233
  for (const entry of input.entries) {
199
234
  index += 1;
200
- lines.push(`## ${index}. ${entry.sourceKind} · ${entry.timestamp}`);
235
+ const sourceKind = typeof entry.sourceKind === "string" ? entry.sourceKind : "未投影";
236
+ const timestamp = typeof entry.timestamp === "string" ? entry.timestamp : "";
237
+ lines.push(`## ${index}. ${sourceKind} · ${timestamp}`);
201
238
  lines.push("");
202
- lines.push(`- basis.method: \`${entry.basis.method}\``);
203
- if (entry.basis.anchors !== undefined && entry.basis.anchors.length > 0) {
204
- lines.push(`- anchors: ${entry.basis.anchors.map((a) => `\`${a}\``).join(", ")}`);
239
+ const basis = entry.basis;
240
+ const method =
241
+ typeof basis === "object" && basis !== null && !Array.isArray(basis) && typeof basis.method === "string"
242
+ ? basis.method
243
+ : "未投影";
244
+ lines.push(`- basis.method: \`${method}\``);
245
+ if (
246
+ typeof basis === "object" &&
247
+ basis !== null &&
248
+ !Array.isArray(basis) &&
249
+ Array.isArray(basis.anchors) &&
250
+ basis.anchors.length > 0
251
+ ) {
252
+ lines.push(`- anchors: ${basis.anchors.map((a) => `\`${String(a)}\``).join(", ")}`);
205
253
  }
206
- if (entry.basis.note !== undefined) {
207
- lines.push(`- note: ${entry.basis.note}`);
254
+ if (typeof basis === "object" && basis !== null && !Array.isArray(basis) && basis.note !== undefined) {
255
+ lines.push(`- note: ${String(basis.note)}`);
208
256
  }
257
+ const sourceRef =
258
+ typeof entry.sourceRef === "object" && entry.sourceRef !== null && !Array.isArray(entry.sourceRef)
259
+ ? entry.sourceRef as { sessionFile?: unknown; entryId?: unknown; path?: unknown; url?: unknown }
260
+ : {};
209
261
  const refParts: string[] = [];
210
- if (entry.sourceRef.sessionFile !== undefined) {
211
- refParts.push(`sessionFile=${entry.sourceRef.sessionFile}`);
262
+ if (sourceRef.sessionFile !== undefined) {
263
+ refParts.push(`sessionFile=${String(sourceRef.sessionFile)}`);
212
264
  }
213
- if (entry.sourceRef.entryId !== undefined) {
214
- refParts.push(`entryId=${String(entry.sourceRef.entryId)}`);
265
+ if (sourceRef.entryId !== undefined) {
266
+ refParts.push(`entryId=${String(sourceRef.entryId)}`);
215
267
  }
216
- if (entry.sourceRef.path !== undefined) {
217
- refParts.push(`path=${entry.sourceRef.path}`);
268
+ if (sourceRef.path !== undefined) {
269
+ refParts.push(`path=${String(sourceRef.path)}`);
218
270
  }
219
- if (entry.sourceRef.url !== undefined) {
220
- refParts.push(`url=${entry.sourceRef.url}`);
271
+ if (sourceRef.url !== undefined) {
272
+ refParts.push(`url=${String(sourceRef.url)}`);
221
273
  }
222
274
  if (refParts.length > 0) {
223
275
  lines.push(`- sourceRef: ${refParts.join(" · ")}`);
224
276
  }
225
277
  lines.push("");
226
- const fence = markdownFenceFor(entry.transcript);
278
+ const transcript = typeof entry.transcript === "string" ? entry.transcript : JSON.stringify(entry);
279
+ const fence = markdownFenceFor(transcript);
280
+ lines.push(fence);
281
+ lines.push(transcript);
282
+ lines.push(fence);
283
+ lines.push("");
284
+ }
285
+ for (const payload of unprojected) {
286
+ index += 1;
287
+ lines.push(`## ${index}. 未投影`);
288
+ lines.push("");
289
+ const text = typeof payload === "string" ? payload : JSON.stringify(payload);
290
+ const fence = markdownFenceFor(text);
227
291
  lines.push(fence);
228
- lines.push(entry.transcript);
292
+ lines.push(text);
229
293
  lines.push(fence);
230
294
  lines.push("");
231
295
  }
@@ -257,11 +321,13 @@ export function writeTicketProvenanceHumanView(input: {
257
321
  readonly cwd: string;
258
322
  readonly home?: string;
259
323
  readonly entries: readonly TicketProvenanceEntry[];
324
+ readonly unprojected?: readonly unknown[];
260
325
  }): string {
261
326
  const volume = ensureTicketProvenanceVolume(input.ticketNumber, input.cwd, input.home);
262
327
  const md = renderTicketProvenanceMarkdown({
263
328
  ticketNumber: input.ticketNumber,
264
329
  entries: input.entries,
330
+ ...(input.unprojected === undefined ? {} : { unprojected: input.unprojected }),
265
331
  });
266
332
  writeFileSync(volume.humanViewFile, md, "utf8");
267
333
  return volume.humanViewFile;
@@ -1,5 +1,4 @@
1
1
  import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } from "./host-contracts.ts";
2
- import { stringEnum } from "./host-contracts.ts";
3
2
  import { Type, type Static } from "typebox";
4
3
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
5
4
  import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
@@ -20,7 +19,7 @@ import {
20
19
  type WorkerOutput,
21
20
  type WorkerRoleLabel,
22
21
  } from "./package-contracts/worker-output.ts";
23
- import { fixerOutputSchema, validateFixerOutput, validateFixerOutputForPacket, type FixerPhase } from "./package-contracts/fixer-output.ts";
22
+ import { fixerOutputSchema, validateFixerOutput, type FixerPhase } from "./package-contracts/fixer-output.ts";
24
23
  import {
25
24
  FixerPacketValidationError,
26
25
  parseFixerPrerequisites,
@@ -45,25 +44,28 @@ export {
45
44
  };
46
45
  export type { WorkerOutput };
47
46
 
47
+ // #836 r16 class 1: report/remainingScope are LLM/human-read narrative content —
48
+ // no code branches on their length. `reason` alone keeps minLength: the worker
49
+ // gate reads `reason.trim().length > 0` to pick typed-reminder-bounce vs accept
50
+ // (src/worker-submission-gates.ts:159-163,297-302).
51
+ // #836 (ADR 0003 Amendment): status kept open like countersignStatus
52
+ // (src/countersign-role.ts) — one shared description across every variant
53
+ // so openToolObjectFromUnion's identical-declaration collapse drops none of it.
54
+ const CODER_STATUS_DESCRIPTION =
55
+ "planned | completed | refused | unfinished — 形状指引,非 schema 闸;completed 回执含 TDD、同模式、引入回归、行为事实四项证据;unfinished 缺前置或违宪约束致本局未完成时可用,缺待决 owner 决定或答复属缺前置。" as const;
48
56
  const coderOutputVariants = Type.Union([
49
57
  Type.Object({
50
- status: stringEnum(["planned"] as const, { description: "planned — 形状指引,非 schema 闸" }),
51
- report: Type.String({ minLength: 1, description: "如实结果报告" }),
58
+ status: Type.Unknown({ description: CODER_STATUS_DESCRIPTION }),
59
+ report: Type.String({ description: "如实结果报告" }),
52
60
  }, { additionalProperties: false }),
53
61
  Type.Object({
54
- status: stringEnum(["completed", "refused"] as const, {
55
- description:
56
- "completed | refused — 形状指引,非 schema 闸;completed 回执含 TDD、同模式、引入回归、行为事实四项证据",
57
- }),
58
- report: Type.String({ minLength: 1, description: "如实结果报告" }),
62
+ status: Type.Unknown({ description: CODER_STATUS_DESCRIPTION }),
63
+ report: Type.String({ description: "如实结果报告" }),
59
64
  }, { additionalProperties: false }),
60
65
  Type.Object({
61
- status: stringEnum(["unfinished"] as const, {
62
- description:
63
- "unfinished — 形状指引,非 schema 闸;缺前置或违宪约束致本局未完成时可用。缺待决 owner 决定或答复属缺前置。",
64
- }),
65
- report: Type.String({ minLength: 1, description: "如实结果报告" }),
66
- remainingScope: Type.String({ minLength: 1, description: "本局后剩余工作" }),
66
+ status: Type.Unknown({ description: CODER_STATUS_DESCRIPTION }),
67
+ report: Type.String({ description: "如实结果报告" }),
68
+ remainingScope: Type.String({ description: "本局后剩余工作" }),
67
69
  reason: Type.Optional(Type.String({
68
70
  minLength: 1,
69
71
  description:
@@ -151,6 +153,17 @@ function isRecord(value: unknown): value is Record<string, unknown> {
151
153
  return typeof value === "object" && value !== null && !Array.isArray(value);
152
154
  }
153
155
 
156
+ /**
157
+ * Read the accepted receipt's own `status` word as submitted (#836: `status`
158
+ * stays an open Type.Unknown provider field, so FixerOutput's Static type no
159
+ * longer narrows it to a literal union — code still just reads whatever
160
+ * string the role wrote; a non-string status reads as "" and simply misses
161
+ * every known-status gate below, same as any other unrecognized status).
162
+ */
163
+ function workerStatusOf(output: WorkerOutput): string {
164
+ return typeof output.status === "string" ? output.status : "";
165
+ }
166
+
154
167
  function deepFreeze<T>(value: T): T {
155
168
  if (typeof value !== "object" || value === null || Object.isFrozen(value)) return value;
156
169
  for (const child of Object.values(value)) deepFreeze(child);
@@ -261,16 +274,16 @@ export function createFixerRoleRuntime(
261
274
  if (packet === undefined || phase === undefined) {
262
275
  throw new Error("修内司修理包与阶段未装载");
263
276
  }
264
- const output = deepFreeze(validateFixerOutputForPacket(parameters, phase, packet));
277
+ const output = deepFreeze(validateFixerOutput(parameters, phase));
265
278
  assertAcceptableThroughHost(
266
279
  submissionGate,
267
- output.status,
280
+ workerStatusOf(output),
268
281
  output,
269
282
  hostActions,
270
283
  ctx,
271
284
  toolCallId,
272
285
  );
273
- if (WORKER_DONE_STATUSES.has(output.status)) {
286
+ if (WORKER_DONE_STATUSES.has(workerStatusOf(output))) {
274
287
  await pi.requireGatekeeperPass!({
275
288
  context: ctx,
276
289
  subject: { kind: "worker_completion" },
@@ -331,7 +344,6 @@ export function createCoderRoleRuntime(
331
344
  let tddInvocationInjected = false;
332
345
  let originalRequest: string | undefined;
333
346
  let expansionPending = false;
334
- let expansionCaptured = false;
335
347
  let lifecycleRegistered = false;
336
348
  const submissionGate = createWorkerSubmissionGate();
337
349
 
@@ -352,7 +364,6 @@ export function createCoderRoleRuntime(
352
364
  tddInvocationInjected = false;
353
365
  originalRequest = undefined;
354
366
  expansionPending = false;
355
- expansionCaptured = false;
356
367
  soul = (await dependencies.loadSoul()).trim();
357
368
  if (soul.length === 0) throw new Error("Coder soul is empty");
358
369
  const selectedPhase = pi.getFlag("ak-coder-phase");
@@ -400,23 +411,17 @@ export function createCoderRoleRuntime(
400
411
  throw new Error("将作监任务与阶段未装载");
401
412
  }
402
413
  const output = validateWorkerOutput(parameters, phase, "Coder");
403
- if (
404
- phase === "apply" && output.status === "completed" &&
405
- !expansionCaptured
406
- ) {
407
- const rejection = new CoderSkillExpansionEvidenceMissingError();
408
- hostActions.bindSubmissionNonPass(toolCallId, rejection.result);
409
- throw rejection;
410
- }
414
+ // #836: skill-expansion evidence rejection deleted (陛下「2.4/5 删」).
415
+ // Skill still ships with the package (ADR 0052); code no longer refuses on it.
411
416
  assertAcceptableThroughHost(
412
417
  submissionGate,
413
- output.status,
418
+ workerStatusOf(output),
414
419
  output,
415
420
  hostActions,
416
421
  ctx,
417
422
  toolCallId,
418
423
  );
419
- if (WORKER_DONE_STATUSES.has(output.status)) {
424
+ if (WORKER_DONE_STATUSES.has(workerStatusOf(output))) {
420
425
  await pi.requireGatekeeperPass!({
421
426
  context: ctx,
422
427
  subject: { kind: "worker_completion" },
@@ -459,12 +464,6 @@ export function createCoderRoleRuntime(
459
464
  }
460
465
  if (expansionPending) {
461
466
  expansionPending = false;
462
- if (originalRequest !== undefined) {
463
- expansionCaptured = binding.captureExpansion(
464
- pi.capabilities?.skillExpansion(event.prompt),
465
- originalRequest,
466
- ) !== undefined;
467
- }
468
467
  }
469
468
  }
470
469
  return {
@@ -1,105 +0,0 @@
1
- import { lstatSync, realpathSync, statSync, writeFileSync, } from "node:fs";
2
- import { isAbsolute, resolve } from "node:path";
3
- import { errnoCode, errorText, } from "./activation-ledger-topology.js";
4
- /**
5
- * Typed missing durable session principal. Callers discriminate with instanceof/code;
6
- * never by parsing message prose. Original filesystem causes are retained.
7
- */
8
- export class ActivationSessionFileMissingError extends Error {
9
- code = "AK_ACTIVATION_SESSION_FILE_MISSING";
10
- path;
11
- constructor(path, options) {
12
- super(`Workflow role activation durable session file does not exist: ${path}`, options?.cause === undefined ? undefined : { cause: options.cause });
13
- this.name = "ActivationSessionFileMissingError";
14
- this.path = path;
15
- }
16
- }
17
- function materializeDeferredSessionFile(sessionManager, resolvedFile) {
18
- const header = sessionManager.getHeader?.();
19
- if (header === null || header === undefined || header.type !== "session") {
20
- throw new ActivationSessionFileMissingError(resolvedFile);
21
- }
22
- // Activation does not own record placement (ADR 0065 / #221). Parent directory is the
23
- // SessionManager principal's own dir — write the header in place; do not pull ledgerHome
24
- // containment or ensureRealDirectoryTree back onto this admission path.
25
- try {
26
- writeFileSync(resolvedFile, `${JSON.stringify(header)}\n`, { flag: "wx" });
27
- }
28
- catch (error) {
29
- if (errnoCode(error) !== "EEXIST") {
30
- throw new Error(`Workflow role activation failed to materialize durable session file (${resolvedFile}): ${errorText(error)}`, { cause: error });
31
- }
32
- // Lost a create race — validate the winner below.
33
- }
34
- // Rebind full SessionManager so subsequent Pi appends use O_APPEND (flushed=true)
35
- // instead of exclusive wx create against the file we just wrote.
36
- if (typeof sessionManager.setSessionFile === "function") {
37
- sessionManager.setSessionFile(resolvedFile);
38
- }
39
- }
40
- /**
41
- * Admit a durable Pi session file principal (ADR 0048/0065).
42
- * Requires an existing regular file at admission (or a live SessionManager header that
43
- * can be materialized onto the same path). Rejects relative paths, directories,
44
- * non-files, and nonexistent paths that cannot be materialized. Original filesystem
45
- * causes are retained.
46
- *
47
- * Record-placement (session must live under the ledger book) is NOT enforced here —
48
- * that check lives on the archivist record entry (createRecordSession). Activation only
49
- * binds the durable principal identity for the role run.
50
- *
51
- * Upstream Pi defers exclusive create until the first assistant message. When the path
52
- * is the live SessionManager principal and only the header is in memory, admission
53
- * materializes that header onto the same path before the fact is written so the role
54
- * fact never points at a session that may be created later.
55
- */
56
- export function durableSessionPointer(sessionManager) {
57
- const file = sessionManager.getSessionFile?.();
58
- if (typeof file !== "string" || file.length === 0) {
59
- throw new Error("Workflow role activation requires a durable Pi session file principal (getSessionFile); directory-only or --no-session invocations are rejected");
60
- }
61
- if (!isAbsolute(file)) {
62
- throw new Error(`Workflow role activation requires an absolute durable session file path; got relative path: ${file}`);
63
- }
64
- const resolvedFile = resolve(file);
65
- try {
66
- lstatSync(resolvedFile);
67
- }
68
- catch (error) {
69
- if (errnoCode(error) !== "ENOENT") {
70
- throw new Error(`Workflow role activation failed to stat durable session file (${resolvedFile}): ${errorText(error)}`, { cause: error });
71
- }
72
- try {
73
- materializeDeferredSessionFile(sessionManager, resolvedFile);
74
- }
75
- catch (materializeError) {
76
- // Missing principal: rethrow typed missing-file identity with the original ENOENT cause.
77
- // Other materialize failures keep their own typed/native identity.
78
- if (materializeError instanceof ActivationSessionFileMissingError) {
79
- throw new ActivationSessionFileMissingError(resolvedFile, { cause: error });
80
- }
81
- throw materializeError;
82
- }
83
- }
84
- let realFile;
85
- try {
86
- realFile = realpathSync(resolvedFile);
87
- }
88
- catch (error) {
89
- throw new ActivationSessionFileMissingError(resolvedFile, { cause: error });
90
- }
91
- let info;
92
- try {
93
- info = statSync(realFile);
94
- }
95
- catch (error) {
96
- throw new Error(`Workflow role activation failed to stat durable session file (${realFile}): ${errorText(error)}`, { cause: error });
97
- }
98
- if (info.isDirectory()) {
99
- throw new Error(`Workflow role activation durable session principal must be a file, not a directory: ${realFile}`);
100
- }
101
- if (!info.isFile()) {
102
- throw new Error(`Workflow role activation durable session principal is not a regular file: ${realFile}`);
103
- }
104
- return { kind: "session-file", path: realFile };
105
- }
@@ -1,139 +0,0 @@
1
- import { constants, closeSync, fstatSync, openSync, writeSync, } from "node:fs";
2
- import { dirname, isAbsolute, resolve } from "node:path";
3
- import { ActivationLedgerError, activationWaitingLedgerPath, assertLedgerFileInsideHome, ensureRealDirectoryTree, errorText, } from "./activation-ledger-topology.js";
4
- /** Private open flags: O_NOFOLLOW closes the lstat→open TOCTOU window. */
5
- const ACTIVATION_LEDGER_APPEND_OPEN_FLAGS = constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_NOFOLLOW;
6
- export { ActivationGitRepositoryRequiredError, resolveBookKeyFromGit, } from "./activation-ledger-git.js";
7
- export { ActivationSessionFileMissingError, durableSessionPointer, } from "./activation-ledger-session.js";
8
- export { ActivationLedgerError, activationBookDirectory, activationWaitingLedgerPath, resolveActivationLedgerHome, } from "./activation-ledger-topology.js";
9
- export const ACCEPTED_ACTIVATION_EVENT = "accepted-activation";
10
- /**
11
- * Package-owned index-only top-level keys for accepted-activation facts (ADR 0049).
12
- * Sole machine key contract: projection and tests consume this descriptor — not prose markers.
13
- */
14
- export const ACCEPTED_ACTIVATION_FACT_KEYS = Object.freeze([
15
- "event",
16
- "role",
17
- "observedAt",
18
- "bookKey",
19
- "session",
20
- "correlation",
21
- ]);
22
- const _acceptedActivationFactKeysMatch = true;
23
- void _acceptedActivationFactKeysMatch;
24
- /**
25
- * Host correlation channel (not a CLI flag): a non-blank AK_CORRELATION_ID carries
26
- * the caller id verbatim; missing/blank/whitespace-only yields the typed absent identity.
27
- */
28
- export function correlationIdentityFromEnv(env = process.env) {
29
- const raw = env.AK_CORRELATION_ID;
30
- if (typeof raw === "string" && raw.trim().length > 0) {
31
- return { kind: "caller", id: raw };
32
- }
33
- return { kind: "absent" };
34
- }
35
- /**
36
- * Descriptor-driven top-level pick: only ACCEPTED_ACTIVATION_FACT_KEYS leave this boundary.
37
- * Nested session/correlation are rebuilt closed before the pick (ADR 0049 zero-content by construction).
38
- */
39
- function projectAcceptedActivationFact(input) {
40
- const closed = {
41
- event: ACCEPTED_ACTIVATION_EVENT,
42
- role: input.role,
43
- observedAt: input.observedAt,
44
- bookKey: input.bookKey,
45
- session: { kind: "session-file", path: input.session.path },
46
- correlation: input.correlation.kind === "caller"
47
- ? { kind: "caller", id: input.correlation.id }
48
- : { kind: "absent" },
49
- };
50
- // Descriptor is the sole top-level emission contract (typed pick; no content keys).
51
- return Object.fromEntries(ACCEPTED_ACTIVATION_FACT_KEYS.map((key) => [key, closed[key]]));
52
- }
53
- /** Construct the closed fact from trusted typed inputs only (descriptor projection — no content keys). */
54
- export function buildAcceptedActivationFact(input) {
55
- return projectAcceptedActivationFact(input);
56
- }
57
- /** Serialize only the closed index fields (descriptor projection — no content keys). */
58
- export function serializeAcceptedActivationFact(fact) {
59
- return `${JSON.stringify(projectAcceptedActivationFact(fact))}\n`;
60
- }
61
- /**
62
- * Bare O_APPEND write of one complete line under an absolute ledger home.
63
- * Private helper: production-bound to writeSync; owns open/write/close honesty only.
64
- * Close failure cannot mask open/write; simultaneous close is nested evidence.
65
- */
66
- function appendActivationLedgerLine(ledgerPath, line, options) {
67
- if (!isAbsolute(options.ledgerHome)) {
68
- throw new ActivationLedgerError(`activation ledger home must be absolute: ${options.ledgerHome}`);
69
- }
70
- const resolvedLedger = resolve(ledgerPath);
71
- const resolvedHome = resolve(options.ledgerHome);
72
- const parent = dirname(resolvedLedger);
73
- ensureRealDirectoryTree(resolvedHome, parent);
74
- assertLedgerFileInsideHome(resolvedLedger, resolvedHome);
75
- // Fail-closed guard (same shape as PR #418's publication seam): on platforms
76
- // without O_NOFOLLOW (e.g. Windows, nodejs/node#41590) the JS bitwise-or in
77
- // ACTIVATION_LEDGER_APPEND_OPEN_FLAGS would silently drop the flag and the
78
- // lstat→open TOCTOU anti-symlink protection would vanish while the comment
79
- // above still claims it. Refuse loudly via the existing typed ledger failure
80
- // channel instead of appending unprotected; never silently degrade.
81
- if (typeof constants.O_NOFOLLOW !== "number") {
82
- throw new ActivationLedgerError("activation ledger append requires O_NOFOLLOW open-flag support (anti-symlink TOCTOU protection must not be silently dropped); refusing to append");
83
- }
84
- const bytes = Buffer.isBuffer(line) ? line : Buffer.from(line);
85
- let ledgerFd;
86
- let primaryFailure;
87
- try {
88
- try {
89
- ledgerFd = openSync(resolvedLedger, ACTIVATION_LEDGER_APPEND_OPEN_FLAGS, 0o644);
90
- }
91
- catch (error) {
92
- // Native ELOOP/filesystem cause retained; no errno dispatch.
93
- throw new ActivationLedgerError(`activation ledger failed to open ledger file (${resolvedLedger}): ${errorText(error)}`, { cause: error });
94
- }
95
- let opened;
96
- try {
97
- opened = fstatSync(ledgerFd);
98
- }
99
- catch (error) {
100
- throw new ActivationLedgerError(`activation ledger failed to fstat ledger file (${resolvedLedger}): ${errorText(error)}`, { cause: error });
101
- }
102
- if (!opened.isFile()) {
103
- throw new ActivationLedgerError(`activation ledger is not a regular file: ${resolvedLedger}`);
104
- }
105
- const written = writeSync(ledgerFd, bytes, 0, bytes.length, null);
106
- if (written !== bytes.length) {
107
- throw new ActivationLedgerError(`activation ledger short write: wrote ${written} of ${bytes.length} bytes to ${resolvedLedger}`);
108
- }
109
- }
110
- catch (error) {
111
- primaryFailure = error;
112
- }
113
- if (ledgerFd !== undefined) {
114
- try {
115
- closeSync(ledgerFd);
116
- }
117
- catch (closeFailure) {
118
- if (primaryFailure !== undefined) {
119
- throw new AggregateError([primaryFailure, closeFailure], "activation ledger operation and close failed", { cause: primaryFailure });
120
- }
121
- throw closeFailure;
122
- }
123
- }
124
- if (primaryFailure !== undefined)
125
- throw primaryFailure;
126
- }
127
- /**
128
- * Append one complete JSONL record with one O_APPEND write of the full record.
129
- * Shared-ledger contract: concurrent successful append-only producers cannot
130
- * overwrite one another. A short write is an honest infrastructure failure
131
- * (ADR 0049) — no non-append rollback/truncate. Close failure cannot mask the
132
- * primary write cause; simultaneous close evidence is retained.
133
- */
134
- export function appendAcceptedActivationFact(ledgerPath, fact, options) {
135
- appendActivationLedgerLine(ledgerPath, Buffer.from(serializeAcceptedActivationFact(fact), "utf8"), { ledgerHome: options.ledgerHome });
136
- }
137
- export function appendAcceptedActivationToBook(options) {
138
- appendAcceptedActivationFact(activationWaitingLedgerPath(options.ledgerHome, options.fact.bookKey), options.fact, { ledgerHome: options.ledgerHome });
139
- }
@@ -1,33 +0,0 @@
1
- import { Type } from "typebox";
2
- const causeSchema = Type.Object({
3
- identity: Type.String({ minLength: 1 }),
4
- name: Type.String({ minLength: 1 }),
5
- message: Type.String(),
6
- evidenceId: Type.Optional(Type.String({ minLength: 1 })),
7
- }, { additionalProperties: false });
8
- export const activationTraceRecordSchema = Type.Object({
9
- role: Type.String({ minLength: 1 }),
10
- stageId: Type.String({ pattern: "^[a-z][a-z0-9-]*$" }),
11
- status: Type.Literal("failed"),
12
- timestamp: Type.String({ format: "date-time" }),
13
- cause: causeSchema,
14
- }, { additionalProperties: false });
15
- let activationCauseEvidence = 0;
16
- const retainedActivationCauses = new Map();
17
- export function namedActivationCause(error) {
18
- const evidenceId = `activation-cause-${++activationCauseEvidence}`;
19
- retainedActivationCauses.set(evidenceId, error);
20
- if (error instanceof Error) {
21
- const code = error.code;
22
- const name = error.name || "Error";
23
- return { identity: typeof code === "string" && code.length > 0 ? code : name, name, message: error.message, evidenceId };
24
- }
25
- let message;
26
- try {
27
- message = typeof error === "string" ? error : JSON.stringify(error) ?? String(error);
28
- return { identity: "UnknownThrownCause", name: "UnknownThrownCause", message, evidenceId };
29
- }
30
- catch {
31
- return { identity: "UnknownThrownCause", name: "UnknownThrownCause", message: String(error), evidenceId };
32
- }
33
- }