@akagilnc/pi-workflow-roles 0.1.5005 → 0.1.5013

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.
@@ -26438,7 +26438,7 @@ function fixerBashSeatbeltDenyReason(matched) {
26438
26438
  }
26439
26439
 
26440
26440
  // src/worker-role.ts
26441
- var CODER_STATUS_DESCRIPTION = "planned | completed | refused | unfinished \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\uFF1Bcompleted \u56DE\u6267\u542B TDD\u3001\u540C\u6A21\u5F0F\u3001\u5F15\u5165\u56DE\u5F52\u3001\u884C\u4E3A\u4E8B\u5B9E\u56DB\u9879\u8BC1\u636E\uFF1Bunfinished \u7F3A\u524D\u7F6E\u6216\u8FDD\u5BAA\u7EA6\u675F\u81F4\u672C\u5C40\u672A\u5B8C\u6210\u65F6\u53EF\u7528\uFF0C\u7F3A\u5F85\u51B3 owner \u51B3\u5B9A\u6216\u7B54\u590D\u5C5E\u7F3A\u524D\u7F6E\u3002";
26441
+ var CODER_STATUS_DESCRIPTION = "planned | completed | refused | partially_completed | unfinished \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\uFF1Bcompleted \u56DE\u6267\u542B TDD\u3001\u540C\u6A21\u5F0F\u3001\u5F15\u5165\u56DE\u5F52\u3001\u884C\u4E3A\u4E8B\u5B9E\u56DB\u9879\u8BC1\u636E\uFF1Bunfinished \u7F3A\u524D\u7F6E\u6216\u8FDD\u5BAA\u7EA6\u675F\u81F4\u672C\u5C40\u672A\u5B8C\u6210\u65F6\u53EF\u7528\uFF0C\u7F3A\u5F85\u51B3 owner \u51B3\u5B9A\u6216\u7B54\u590D\u5C5E\u7F3A\u524D\u7F6E\u3002";
26442
26442
  var coderOutputVariants = Type22.Union([
26443
26443
  Type22.Object({
26444
26444
  status: Type22.Unknown({ description: CODER_STATUS_DESCRIPTION }),
@@ -26488,8 +26488,26 @@ var FIXER_PHASES = ["plan", "apply"];
26488
26488
  function isWorkerPhase(value) {
26489
26489
  return typeof value === "string" && FIXER_PHASES.includes(value);
26490
26490
  }
26491
+ function isRecord19(value) {
26492
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26493
+ }
26494
+ var WORKER_ROUTING_STATUSES = /* @__PURE__ */ new Set([
26495
+ "planned",
26496
+ "completed",
26497
+ "refused",
26498
+ "partially_completed",
26499
+ "unfinished"
26500
+ ]);
26501
+ var WORKER_STATUS_REASK = "status \u4E0D\u662F planned\u3001completed\u3001refused\u3001partially_completed\u3001unfinished \u4E4B\u4E00\u3002\u8BF7\u91CD\u65B0\u4EA4\u5377\uFF0Cstatus \u5199\u660E\u5176\u4E00\u3002";
26491
26502
  function workerStatusOf(output) {
26492
- return typeof output.status === "string" ? output.status : "";
26503
+ return isRecord19(output) && typeof output.status === "string" ? output.status : void 0;
26504
+ }
26505
+ function requireReadableWorkerStatus(output) {
26506
+ const status = workerStatusOf(output);
26507
+ if (status === void 0 || !WORKER_ROUTING_STATUSES.has(status)) {
26508
+ throw new ParentQueueReaskError(WORKER_STATUS_REASK);
26509
+ }
26510
+ return status;
26493
26511
  }
26494
26512
  function deepFreeze2(value) {
26495
26513
  if (typeof value !== "object" || value === null || Object.isFrozen(value)) return value;
@@ -26572,9 +26590,10 @@ function createFixerRoleRuntime(pi, dependencies, hostActions) {
26572
26590
  throw new Error("\u4FEE\u5185\u53F8\u4FEE\u7406\u5305\u4E0E\u9636\u6BB5\u672A\u88C5\u8F7D");
26573
26591
  }
26574
26592
  const output = deepFreeze2(validateFixerOutput(parameters, phase));
26593
+ const status = requireReadableWorkerStatus(output);
26575
26594
  assertAcceptableThroughHost(
26576
26595
  submissionGate,
26577
- workerStatusOf(output),
26596
+ status,
26578
26597
  output,
26579
26598
  hostActions,
26580
26599
  ctx,
@@ -26582,7 +26601,7 @@ function createFixerRoleRuntime(pi, dependencies, hostActions) {
26582
26601
  );
26583
26602
  let pass;
26584
26603
  try {
26585
- pass = WORKER_DONE_STATUSES.has(workerStatusOf(output)) ? await pi.requireSubmissionGate({
26604
+ pass = WORKER_DONE_STATUSES.has(status) ? await pi.requireSubmissionGate({
26586
26605
  context: ctx,
26587
26606
  subject: { kind: "worker_completion" },
26588
26607
  ..._signal === void 0 ? {} : { signal: _signal },
@@ -26723,9 +26742,10 @@ function createCoderRoleRuntime(pi, dependencies, hostActions) {
26723
26742
  throw new Error("\u5C06\u4F5C\u76D1\u4EFB\u52A1\u4E0E\u9636\u6BB5\u672A\u88C5\u8F7D");
26724
26743
  }
26725
26744
  const output = validateWorkerOutput(parameters, phase, "Coder");
26745
+ const status = requireReadableWorkerStatus(output);
26726
26746
  assertAcceptableThroughHost(
26727
26747
  submissionGate,
26728
- workerStatusOf(output),
26748
+ status,
26729
26749
  output,
26730
26750
  hostActions,
26731
26751
  ctx2,
@@ -26733,7 +26753,7 @@ function createCoderRoleRuntime(pi, dependencies, hostActions) {
26733
26753
  );
26734
26754
  let pass;
26735
26755
  try {
26736
- pass = WORKER_DONE_STATUSES.has(workerStatusOf(output)) ? await pi.requireSubmissionGate({
26756
+ pass = WORKER_DONE_STATUSES.has(status) ? await pi.requireSubmissionGate({
26737
26757
  context: ctx2,
26738
26758
  subject: { kind: "worker_completion" },
26739
26759
  ..._signal === void 0 ? {} : { signal: _signal },
@@ -27314,6 +27334,8 @@ function readDiaristRunCoordinates(ctx) {
27314
27334
  };
27315
27335
  }
27316
27336
  var DIARIST_BOUNDS_REASK = "\u8FB9\u754C\u65E0\u6CD5\u4F7F\u7528\u3002\u8BF7\u91CD\u4EA4 sessions\uFF1A\u6BCF\u5377 path + ranges\uFF0C\u6BCF\u7AEF\u4EE5\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00\u6307\u540D\u3002";
27337
+ var DIARIST_ROUTING_STATUSES = /* @__PURE__ */ new Set(["completed", "escalate"]);
27338
+ var DIARIST_STATUS_REASK = "status \u4E0D\u662F completed\u3001escalate \u4E4B\u4E00\u3002\u8BF7\u91CD\u65B0\u4EA4\u5377\uFF0Cstatus \u5199\u660E\u5176\u4E00\u3002";
27317
27339
  function createDiaristRoleRuntime(roleHost, dependencies) {
27318
27340
  return createFiledOfficerRuntime(
27319
27341
  roleHost,
@@ -27323,6 +27345,11 @@ function createDiaristRoleRuntime(roleHost, dependencies) {
27323
27345
  soulTag: "diarist",
27324
27346
  beforeAccept: async ({ parameters, ctx }) => {
27325
27347
  const submitted = parameters !== null && typeof parameters === "object" && !Array.isArray(parameters) ? parameters : void 0;
27348
+ const status = submitted?.status;
27349
+ if (typeof status !== "string" || !DIARIST_ROUTING_STATUSES.has(status)) {
27350
+ throw new ParentQueueReaskError(DIARIST_STATUS_REASK);
27351
+ }
27352
+ if (status === "escalate") return parameters;
27326
27353
  const assertion = readDiaristTicketAssertion(submitted);
27327
27354
  const coords = readDiaristRunCoordinates(ctx);
27328
27355
  const ticketNumber = assertion.kind === "ticket" ? assertion.ticketNumber : void 0;
@@ -168,13 +168,22 @@ function nonNullLeaves(schema) {
168
168
  * Required → closed non-null leaf(s). Optional → closed leaf(s) + null.
169
169
  */
170
170
  function closePropertySchema(schema, optional) {
171
+ const description = isPlainObject(schema) && typeof schema.description === "string"
172
+ ? { description: schema.description }
173
+ : {};
171
174
  const leaves = nonNullLeaves(schema).map(closeSchemaNode);
172
175
  if (leaves.length === 0)
173
- return { type: "null" };
176
+ return { type: "null", ...description };
174
177
  if (!optional) {
175
- return leaves.length === 1 ? leaves[0] : { anyOf: leaves };
178
+ const closed = leaves.length === 1 ? leaves[0] : { anyOf: leaves };
179
+ // Codex strict does not allow sibling keys on $ref; put property guidance
180
+ // on an enclosing schema instead of losing it or weakening the free-JSON ref.
181
+ if (isPlainObject(closed) && typeof closed.$ref === "string") {
182
+ return { anyOf: [closed], ...description };
183
+ }
184
+ return isPlainObject(closed) ? { ...closed, ...description } : closed;
176
185
  }
177
- return { anyOf: [...leaves, { type: "null" }] };
186
+ return { anyOf: [...leaves, { type: "null" }], ...description };
178
187
  }
179
188
  /**
180
189
  * Strict generators require every schema node to declare `type` (or a $ref).
@@ -26387,7 +26387,7 @@ function fixerBashSeatbeltDenyReason(matched) {
26387
26387
  }
26388
26388
 
26389
26389
  // src/worker-role.ts
26390
- var CODER_STATUS_DESCRIPTION = "planned | completed | refused | unfinished \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\uFF1Bcompleted \u56DE\u6267\u542B TDD\u3001\u540C\u6A21\u5F0F\u3001\u5F15\u5165\u56DE\u5F52\u3001\u884C\u4E3A\u4E8B\u5B9E\u56DB\u9879\u8BC1\u636E\uFF1Bunfinished \u7F3A\u524D\u7F6E\u6216\u8FDD\u5BAA\u7EA6\u675F\u81F4\u672C\u5C40\u672A\u5B8C\u6210\u65F6\u53EF\u7528\uFF0C\u7F3A\u5F85\u51B3 owner \u51B3\u5B9A\u6216\u7B54\u590D\u5C5E\u7F3A\u524D\u7F6E\u3002";
26390
+ var CODER_STATUS_DESCRIPTION = "planned | completed | refused | partially_completed | unfinished \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\uFF1Bcompleted \u56DE\u6267\u542B TDD\u3001\u540C\u6A21\u5F0F\u3001\u5F15\u5165\u56DE\u5F52\u3001\u884C\u4E3A\u4E8B\u5B9E\u56DB\u9879\u8BC1\u636E\uFF1Bunfinished \u7F3A\u524D\u7F6E\u6216\u8FDD\u5BAA\u7EA6\u675F\u81F4\u672C\u5C40\u672A\u5B8C\u6210\u65F6\u53EF\u7528\uFF0C\u7F3A\u5F85\u51B3 owner \u51B3\u5B9A\u6216\u7B54\u590D\u5C5E\u7F3A\u524D\u7F6E\u3002";
26391
26391
  var coderOutputVariants = Type22.Union([
26392
26392
  Type22.Object({
26393
26393
  status: Type22.Unknown({ description: CODER_STATUS_DESCRIPTION }),
@@ -26437,8 +26437,26 @@ var FIXER_PHASES = ["plan", "apply"];
26437
26437
  function isWorkerPhase(value) {
26438
26438
  return typeof value === "string" && FIXER_PHASES.includes(value);
26439
26439
  }
26440
+ function isRecord19(value) {
26441
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26442
+ }
26443
+ var WORKER_ROUTING_STATUSES = /* @__PURE__ */ new Set([
26444
+ "planned",
26445
+ "completed",
26446
+ "refused",
26447
+ "partially_completed",
26448
+ "unfinished"
26449
+ ]);
26450
+ var WORKER_STATUS_REASK = "status \u4E0D\u662F planned\u3001completed\u3001refused\u3001partially_completed\u3001unfinished \u4E4B\u4E00\u3002\u8BF7\u91CD\u65B0\u4EA4\u5377\uFF0Cstatus \u5199\u660E\u5176\u4E00\u3002";
26440
26451
  function workerStatusOf(output) {
26441
- return typeof output.status === "string" ? output.status : "";
26452
+ return isRecord19(output) && typeof output.status === "string" ? output.status : void 0;
26453
+ }
26454
+ function requireReadableWorkerStatus(output) {
26455
+ const status = workerStatusOf(output);
26456
+ if (status === void 0 || !WORKER_ROUTING_STATUSES.has(status)) {
26457
+ throw new ParentQueueReaskError(WORKER_STATUS_REASK);
26458
+ }
26459
+ return status;
26442
26460
  }
26443
26461
  function deepFreeze2(value) {
26444
26462
  if (typeof value !== "object" || value === null || Object.isFrozen(value)) return value;
@@ -26521,9 +26539,10 @@ function createFixerRoleRuntime(pi, dependencies, hostActions) {
26521
26539
  throw new Error("\u4FEE\u5185\u53F8\u4FEE\u7406\u5305\u4E0E\u9636\u6BB5\u672A\u88C5\u8F7D");
26522
26540
  }
26523
26541
  const output = deepFreeze2(validateFixerOutput(parameters, phase));
26542
+ const status = requireReadableWorkerStatus(output);
26524
26543
  assertAcceptableThroughHost(
26525
26544
  submissionGate,
26526
- workerStatusOf(output),
26545
+ status,
26527
26546
  output,
26528
26547
  hostActions,
26529
26548
  ctx,
@@ -26531,7 +26550,7 @@ function createFixerRoleRuntime(pi, dependencies, hostActions) {
26531
26550
  );
26532
26551
  let pass;
26533
26552
  try {
26534
- pass = WORKER_DONE_STATUSES.has(workerStatusOf(output)) ? await pi.requireSubmissionGate({
26553
+ pass = WORKER_DONE_STATUSES.has(status) ? await pi.requireSubmissionGate({
26535
26554
  context: ctx,
26536
26555
  subject: { kind: "worker_completion" },
26537
26556
  ..._signal === void 0 ? {} : { signal: _signal },
@@ -26672,9 +26691,10 @@ function createCoderRoleRuntime(pi, dependencies, hostActions) {
26672
26691
  throw new Error("\u5C06\u4F5C\u76D1\u4EFB\u52A1\u4E0E\u9636\u6BB5\u672A\u88C5\u8F7D");
26673
26692
  }
26674
26693
  const output = validateWorkerOutput(parameters, phase, "Coder");
26694
+ const status = requireReadableWorkerStatus(output);
26675
26695
  assertAcceptableThroughHost(
26676
26696
  submissionGate,
26677
- workerStatusOf(output),
26697
+ status,
26678
26698
  output,
26679
26699
  hostActions,
26680
26700
  ctx2,
@@ -26682,7 +26702,7 @@ function createCoderRoleRuntime(pi, dependencies, hostActions) {
26682
26702
  );
26683
26703
  let pass;
26684
26704
  try {
26685
- pass = WORKER_DONE_STATUSES.has(workerStatusOf(output)) ? await pi.requireSubmissionGate({
26705
+ pass = WORKER_DONE_STATUSES.has(status) ? await pi.requireSubmissionGate({
26686
26706
  context: ctx2,
26687
26707
  subject: { kind: "worker_completion" },
26688
26708
  ..._signal === void 0 ? {} : { signal: _signal },
@@ -27239,6 +27259,8 @@ function readDiaristRunCoordinates(ctx) {
27239
27259
  };
27240
27260
  }
27241
27261
  var DIARIST_BOUNDS_REASK = "\u8FB9\u754C\u65E0\u6CD5\u4F7F\u7528\u3002\u8BF7\u91CD\u4EA4 sessions\uFF1A\u6BCF\u5377 path + ranges\uFF0C\u6BCF\u7AEF\u4EE5\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00\u6307\u540D\u3002";
27262
+ var DIARIST_ROUTING_STATUSES = /* @__PURE__ */ new Set(["completed", "escalate"]);
27263
+ var DIARIST_STATUS_REASK = "status \u4E0D\u662F completed\u3001escalate \u4E4B\u4E00\u3002\u8BF7\u91CD\u65B0\u4EA4\u5377\uFF0Cstatus \u5199\u660E\u5176\u4E00\u3002";
27242
27264
  function createDiaristRoleRuntime(roleHost, dependencies) {
27243
27265
  return createFiledOfficerRuntime(
27244
27266
  roleHost,
@@ -27248,6 +27270,11 @@ function createDiaristRoleRuntime(roleHost, dependencies) {
27248
27270
  soulTag: "diarist",
27249
27271
  beforeAccept: async ({ parameters, ctx }) => {
27250
27272
  const submitted = parameters !== null && typeof parameters === "object" && !Array.isArray(parameters) ? parameters : void 0;
27273
+ const status = submitted?.status;
27274
+ if (typeof status !== "string" || !DIARIST_ROUTING_STATUSES.has(status)) {
27275
+ throw new ParentQueueReaskError(DIARIST_STATUS_REASK);
27276
+ }
27277
+ if (status === "escalate") return parameters;
27251
27278
  const assertion = readDiaristTicketAssertion(submitted);
27252
27279
  const coords = readDiaristRunCoordinates(ctx);
27253
27280
  const ticketNumber = assertion.kind === "ticket" ? assertion.ticketNumber : void 0;
@@ -29327,12 +29354,17 @@ function nonNullLeaves(schema) {
29327
29354
  return flattenUnionLeaves(schema).map(stripNullFromLeafType).filter((leaf) => !isPureNullTypeSchema(leaf));
29328
29355
  }
29329
29356
  function closePropertySchema(schema, optional) {
29357
+ const description = isPlainObject2(schema) && typeof schema.description === "string" ? { description: schema.description } : {};
29330
29358
  const leaves = nonNullLeaves(schema).map(closeSchemaNode);
29331
- if (leaves.length === 0) return { type: "null" };
29359
+ if (leaves.length === 0) return { type: "null", ...description };
29332
29360
  if (!optional) {
29333
- return leaves.length === 1 ? leaves[0] : { anyOf: leaves };
29361
+ const closed = leaves.length === 1 ? leaves[0] : { anyOf: leaves };
29362
+ if (isPlainObject2(closed) && typeof closed.$ref === "string") {
29363
+ return { anyOf: [closed], ...description };
29364
+ }
29365
+ return isPlainObject2(closed) ? { ...closed, ...description } : closed;
29334
29366
  }
29335
- return { anyOf: [...leaves, { type: "null" }] };
29367
+ return { anyOf: [...leaves, { type: "null" }], ...description };
29336
29368
  }
29337
29369
  function ensureTypedLeaf(schema) {
29338
29370
  if (schema.type !== void 0) return schema;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5005",
3
+ "version": "0.1.5013",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -19,7 +19,9 @@ finding 提**类别**;实例只作证据。一次找齐全,不逐轮抽样
19
19
 
20
20
  判断不收敛(同类问题反复、修理只增不减)可上呈;你的打回与上一轮相同、对方重交只换措辞,须上呈,不得再打回。修理 diff 自身按同尺复核。
21
21
 
22
- 衙门不是需求来源。你要求施工方做的每一条——补一个失败路径的处理、加一道校验、多一个分支——须指出票面、陛下的意思或法源里要它的那一句,并答得出《质量法》三问;指不出的,是你在加戏,不是对方在缺失,不得开方。失败以真实作业路径产生得出的为准,探针造得出来不等于会发生。对方按你上一轮处方加出来的东西,下一轮同受此审。
22
+ 衙门不是需求来源。弹章、施工方的注释与测试、你造的探针,都只是线索。判一条 finding 成立、开一条约束,判词须写明两样:要它的那一句法(先看那句讲的是不是这一条),和它在哪条真实 run 上发生过(指针)。缺一样,按过度防御或加戏驳回;判词缺这两样,审刑院打回,符宝郎驳。对方按你处方加的东西,下轮同审。
23
+
24
+ 判例:#1025 御史台据一句注释造场景,大理寺引「上呈暂停」判成立,审刑院、符宝郎皆署;那句法没要过「谁有资格唤醒」,场景也从未发生。
23
25
 
24
26
  ## 取证边界(owner 2026-09-05)
25
27
 
@@ -250,12 +250,22 @@ function nonNullLeaves(schema: unknown): unknown[] {
250
250
  * Required → closed non-null leaf(s). Optional → closed leaf(s) + null.
251
251
  */
252
252
  function closePropertySchema(schema: unknown, optional: boolean): unknown {
253
+ const description =
254
+ isPlainObject(schema) && typeof schema.description === "string"
255
+ ? { description: schema.description }
256
+ : {};
253
257
  const leaves = nonNullLeaves(schema).map(closeSchemaNode);
254
- if (leaves.length === 0) return { type: "null" };
258
+ if (leaves.length === 0) return { type: "null", ...description };
255
259
  if (!optional) {
256
- return leaves.length === 1 ? leaves[0] : { anyOf: leaves };
260
+ const closed = leaves.length === 1 ? leaves[0] : { anyOf: leaves };
261
+ // Codex strict does not allow sibling keys on $ref; put property guidance
262
+ // on an enclosing schema instead of losing it or weakening the free-JSON ref.
263
+ if (isPlainObject(closed) && typeof closed.$ref === "string") {
264
+ return { anyOf: [closed], ...description };
265
+ }
266
+ return isPlainObject(closed) ? { ...closed, ...description } : closed;
257
267
  }
258
- return { anyOf: [...leaves, { type: "null" }] };
268
+ return { anyOf: [...leaves, { type: "null" }], ...description };
259
269
  }
260
270
 
261
271
  /**
@@ -19,7 +19,7 @@ export const CODER_OUTPUT_TOOL_NAME = "ak_coder_output";
19
19
  export type WorkerRoleLabel = "Coder" | "Fixer";
20
20
  export type CoderOutput =
21
21
  | { status: "planned"; report: string }
22
- | { status: "completed" | "refused"; report: string }
22
+ | { status: "completed" | "refused" | "partially_completed"; report: string }
23
23
  | { status: "unfinished"; report: string; remainingScope: string; reason?: string };
24
24
  export type WorkerOutput = CoderOutput | FixerOutput;
25
25
 
@@ -883,6 +883,9 @@ function readDiaristRunCoordinates(ctx: HostContext): {
883
883
  /** Plain-language re-ask when diarist bounds cannot be used (#901 / reask-not-explode). */
884
884
  const DIARIST_BOUNDS_REASK =
885
885
  "边界无法使用。请重交 sessions:每卷 path + ranges,每端以原生 id 或本轮行号二选一指名。" as const;
886
+ const DIARIST_ROUTING_STATUSES = new Set(["completed", "escalate"]);
887
+ const DIARIST_STATUS_REASK =
888
+ "status 不是 completed、escalate 之一。请重新交卷,status 写明其一。" as const;
886
889
 
887
890
  /**
888
891
  * #708 / #779 / #901: 起居郎 public seat on the shared filed-officer envelope.
@@ -905,6 +908,13 @@ export function createDiaristRoleRuntime(
905
908
  parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
906
909
  ? (parameters as Record<string, unknown>)
907
910
  : undefined;
911
+ // This status selects package-owned continuation vs escalation. Check it
912
+ // before interpreting any other submitted field; caller-owned data stays open.
913
+ const status = submitted?.status;
914
+ if (typeof status !== "string" || !DIARIST_ROUTING_STATUSES.has(status)) {
915
+ throw new ParentQueueReaskError(DIARIST_STATUS_REASK);
916
+ }
917
+ if (status === "escalate") return parameters;
908
918
  const assertion = readDiaristTicketAssertion(submitted);
909
919
  const coords = readDiaristRunCoordinates(ctx);
910
920
  // #836 7.3: pre-bound ticket is material for the LLM, not an override.
@@ -10,7 +10,7 @@ import type {
10
10
  } from "./canonical-skill-binding.ts";
11
11
  import { readableGateItem } from "./readable-gate-item.ts";
12
12
  import { projectGatekeeperEscalation } from "./audit-escalation.ts";
13
- import { GatekeeperDecisionError } from "./submission-errors.ts";
13
+ import { GatekeeperDecisionError, ParentQueueReaskError } from "./submission-errors.ts";
14
14
  import {
15
15
  CODER_OUTPUT_TOOL_NAME,
16
16
  FIXER_OUTPUT_TOOL_NAME,
@@ -53,7 +53,7 @@ export type { WorkerOutput };
53
53
  // (src/countersign-role.ts) — one shared description across every variant
54
54
  // so openToolObjectFromUnion's identical-declaration collapse drops none of it.
55
55
  const CODER_STATUS_DESCRIPTION =
56
- "planned | completed | refused | unfinished — 形状指引,非 schema 闸;completed 回执含 TDD、同模式、引入回归、行为事实四项证据;unfinished 缺前置或违宪约束致本局未完成时可用,缺待决 owner 决定或答复属缺前置。" as const;
56
+ "planned | completed | refused | partially_completed | unfinished — 形状指引,非 schema 闸;completed 回执含 TDD、同模式、引入回归、行为事实四项证据;unfinished 缺前置或违宪约束致本局未完成时可用,缺待决 owner 决定或答复属缺前置。" as const;
57
57
  const coderOutputVariants = Type.Union([
58
58
  Type.Object({
59
59
  status: Type.Unknown({ description: CODER_STATUS_DESCRIPTION }),
@@ -154,15 +154,30 @@ function isRecord(value: unknown): value is Record<string, unknown> {
154
154
  return typeof value === "object" && value !== null && !Array.isArray(value);
155
155
  }
156
156
 
157
- /**
158
- * Read the accepted receipt's own `status` word as submitted (#836: `status`
159
- * stays an open Type.Unknown provider field, so FixerOutput's Static type no
160
- * longer narrows it to a literal union — code still just reads whatever
161
- * string the role wrote; a non-string status reads as "" and simply misses
162
- * every known-status gate below, same as any other unrecognized status).
163
- */
164
- function workerStatusOf(output: WorkerOutput): string {
165
- return typeof output.status === "string" ? output.status : "";
157
+ /** Routing words shared by Coder and Fixer; provider schemas remain open. */
158
+ const WORKER_ROUTING_STATUSES = new Set([
159
+ "planned",
160
+ "completed",
161
+ "refused",
162
+ "partially_completed",
163
+ "unfinished",
164
+ ]);
165
+ const WORKER_STATUS_REASK =
166
+ "status 不是 planned、completed、refused、partially_completed、unfinished 之一。请重新交卷,status 写明其一。" as const;
167
+
168
+ /** Read only the field that selects the worker's next package-owned path. */
169
+ function workerStatusOf(output: WorkerOutput): string | undefined {
170
+ return isRecord(output) && typeof output.status === "string"
171
+ ? output.status
172
+ : undefined;
173
+ }
174
+
175
+ function requireReadableWorkerStatus(output: WorkerOutput): string {
176
+ const status = workerStatusOf(output);
177
+ if (status === undefined || !WORKER_ROUTING_STATUSES.has(status)) {
178
+ throw new ParentQueueReaskError(WORKER_STATUS_REASK);
179
+ }
180
+ return status;
166
181
  }
167
182
 
168
183
  function deepFreeze<T>(value: T): T {
@@ -276,9 +291,10 @@ export function createFixerRoleRuntime(
276
291
  throw new Error("修内司修理包与阶段未装载");
277
292
  }
278
293
  const output = deepFreeze(validateFixerOutput(parameters, phase));
294
+ const status = requireReadableWorkerStatus(output);
279
295
  assertAcceptableThroughHost(
280
296
  submissionGate,
281
- workerStatusOf(output),
297
+ status,
282
298
  output,
283
299
  hostActions,
284
300
  ctx,
@@ -286,7 +302,7 @@ export function createFixerRoleRuntime(
286
302
  );
287
303
  let pass;
288
304
  try {
289
- pass = WORKER_DONE_STATUSES.has(workerStatusOf(output))
305
+ pass = WORKER_DONE_STATUSES.has(status)
290
306
  ? await pi.requireSubmissionGate!({
291
307
  context: ctx,
292
308
  subject: { kind: "worker_completion" },
@@ -429,11 +445,12 @@ export function createCoderRoleRuntime(
429
445
  throw new Error("将作监任务与阶段未装载");
430
446
  }
431
447
  const output = validateWorkerOutput(parameters, phase, "Coder");
448
+ const status = requireReadableWorkerStatus(output);
432
449
  // #836: skill-expansion evidence rejection deleted (陛下「2.4/5 删」).
433
450
  // Skill still ships with the package (ADR 0052); code no longer refuses on it.
434
451
  assertAcceptableThroughHost(
435
452
  submissionGate,
436
- workerStatusOf(output),
453
+ status,
437
454
  output,
438
455
  hostActions,
439
456
  ctx,
@@ -441,7 +458,7 @@ export function createCoderRoleRuntime(
441
458
  );
442
459
  let pass;
443
460
  try {
444
- pass = WORKER_DONE_STATUSES.has(workerStatusOf(output))
461
+ pass = WORKER_DONE_STATUSES.has(status)
445
462
  ? await pi.requireSubmissionGate!({
446
463
  context: ctx,
447
464
  subject: { kind: "worker_completion" },