@akagilnc/pi-workflow-roles 0.1.2479 → 0.1.2497

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.
@@ -1,6 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
  import { canonicalJson } from "./canonical-json.js";
3
3
  import { openToolObjectFromUnion } from "./open-tool-schema.js";
4
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
4
5
  export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
5
6
  export const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
6
7
  export const DOCTOR_ACCEPTED_TEXT = "太医署回执已接受";
@@ -49,7 +50,7 @@ const doctorSubmissionVariants = Type.Union([
49
50
  missingEvidence: Type.Array(Type.Object({ need: nonblank, targetKeys: Type.Array(nonblank, { minItems: 1 }) }, { additionalProperties: false }), { minItems: 1, description: "如实证词所需而尚缺的证据" }),
50
51
  }, { additionalProperties: false, description: "证据不足以支撑如实案证词" }),
51
52
  ]);
52
- export const doctorSubmissionSchema = openToolObjectFromUnion(doctorSubmissionVariants);
53
+ export const doctorSubmissionSchema = withInfrastructureFailureDeclaration(openToolObjectFromUnion(doctorSubmissionVariants));
53
54
  export const doctorOutputSchema = Type.Union([
54
55
  Type.Object({ status: Type.Literal("completed"), case: caseIdentity, findings: Type.Array(finding), cost }, { additionalProperties: false }),
55
56
  doctorSubmissionVariants.anyOf[1],
@@ -3,6 +3,7 @@ import { isFullGitObjectId } from "./git-object-id.js";
3
3
  import { exactUtf8 } from "./exact-utf8.js";
4
4
  import { sha256Hex } from "./sha256.js";
5
5
  import { openToolObjectFromUnion } from "./open-tool-schema.js";
6
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
6
7
  const oidPattern = "^(?:[0-9a-f]{40}|[0-9a-f]{64})$";
7
8
  const materialSchema = Type.Object({ bytesBase64: Type.String(), sha256: Type.String() }, { additionalProperties: false });
8
9
  const checkSchema = Type.Object({ name: Type.String({ minLength: 1 }), argv: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }) }, { additionalProperties: false });
@@ -18,7 +19,7 @@ const mergerOutputVariants = Type.Union([
18
19
  Type.Object({ status: Type.Literal("completed", { description: "completed — 形状指引,非 schema 闸" }), attemptId: Type.String({ minLength: 1, description: "已受理合并 attempt 身份" }), report: Type.String({ minLength: 1, description: "如实结果报告" }), mergeCommitId: Type.String({ pattern: oidPattern, description: "已核验的完成合并 commit object ID" }) }, { additionalProperties: false }),
19
20
  Type.Object({ status: Type.Literal("escalate", { description: "escalate — 形状指引,非 schema 闸" }), attemptId: Type.String({ minLength: 1, description: "已受理合并 attempt 身份" }), diagnosis: Type.String({ minLength: 1, description: "合并完成需升级的原因" }), report: Type.String({ minLength: 1, description: "如实结果报告" }) }, { additionalProperties: false }),
20
21
  ]);
21
- export const mergerOutputSchema = openToolObjectFromUnion(mergerOutputVariants);
22
+ export const mergerOutputSchema = withInfrastructureFailureDeclaration(openToolObjectFromUnion(mergerOutputVariants));
22
23
  export const MERGER_OUTPUT_TOOL_NAME = "ak_merger_output";
23
24
  export const MERGER_ACCEPTED_TEXT = "合并回执已接受";
24
25
  const record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { Type } from "typebox";
7
7
  import { openToolObject } from "./open-tool-schema.js";
8
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
8
9
  export const NOTARY_OUTPUT_TOOL_NAME = "ak_notary_output";
9
10
  export const NOTARY_ACCEPTED_TEXT = "符宝郎回执已接受";
10
11
  export const NOTARY_SOURCE_RUN_FLAG = {
@@ -16,14 +17,14 @@ export const NOTARY_SOURCE_RUN_FLAG = {
16
17
  };
17
18
  /** Package-owned kickoff only — callers supply zero prompt bytes (ADR 0067 / #448). */
18
19
  export const NOTARY_FIXED_KICKOFF = "符宝郎案卷已受理;来源 run 定位见会话材料。";
19
- export const notaryOutputSchema = openToolObject(Type.Object({
20
+ export const notaryOutputSchema = withInfrastructureFailureDeclaration(openToolObject(Type.Object({
20
21
  status: Type.Unknown({
21
22
  description: "pass | bounce — 形状指引,非 schema 闸",
22
23
  }),
23
24
  findings: Type.Unknown({
24
25
  description: "string[] findings,随 pass 或 bounce 留存",
25
26
  }),
26
- }));
27
+ })));
27
28
  function isRecord(value) {
28
29
  return typeof value === "object" && value !== null && !Array.isArray(value);
29
30
  }
@@ -1,6 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
  import { FIXER_PREREQUISITE_ID_PATTERN } from "./fixer-packet.js";
3
3
  import { openToolObjectFromUnion } from "../open-tool-schema.js";
4
+ import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.js";
4
5
  export const FIXER_OUTPUT_TOOL_NAME = "ak_fixer_output";
5
6
  export const FIXER_ACCEPTED_TEXT = "修内司回执已接受";
6
7
  const nonblankTransportString = Type.String({ minLength: 1 });
@@ -37,7 +38,7 @@ const fixerOutputVariants = Type.Union([
37
38
  Type.Object({ status: Type.Literal("refused", { description: "refused — 形状指引,非 schema 闸" }), report: Type.String({ minLength: 1, description: "如实结果报告" }), classResults: Type.Array(classResultSchema, { minItems: 1, description: "各类拒绝结算" }) }),
38
39
  Type.Object({ status: Type.Literal("partially_completed", { description: "partially_completed — 形状指引,非 schema 闸" }), report: Type.String({ minLength: 1, description: "如实结果报告" }), classResults: Type.Array(classResultSchema, { minItems: 1, description: "各类完成或拒绝结算" }), testEvidence: Type.Optional(testEvidenceSchema) }),
39
40
  ]);
40
- export const fixerOutputSchema = openToolObjectFromUnion(fixerOutputVariants);
41
+ export const fixerOutputSchema = withInfrastructureFailureDeclaration(openToolObjectFromUnion(fixerOutputVariants));
41
42
  export function validateFixerOutput(value, _phase) {
42
43
  return value;
43
44
  }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Package-owned shared infrastructure-failure declaration for every primary
3
+ * packaged-role output tool (#541).
4
+ *
5
+ * One module owns what the judge mandates be shared (not reimplemented per
6
+ * seat): the typed `infrastructureFailure.diagnostic` declaration composed into
7
+ * each output tool's schema, and the single early host `failInfrastructure`
8
+ * call each output `execute` makes before any role business validation / gate /
9
+ * audit / ledger / Git work. The accepted status sets are intentionally NOT
10
+ * extended: an infra declaration fails BEFORE accepted validation, never
11
+ * becomes an accepted receipt. The diagnostic is carried verbatim on the thrown
12
+ * Error so settlement keeps the original cause (kind=failure, exit 1).
13
+ */
14
+ import { Type } from "typebox";
15
+ export const INFRASTRUCTURE_FAILURE_DECLARATION_KEY = "infrastructureFailure";
16
+ export const INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY = "diagnostic";
17
+ /** Shared typed declaration fragment: `infrastructureFailure.diagnostic` = non-empty string. */
18
+ const infrastructureFailureDeclarationSchema = Type.Object({
19
+ [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: Type.Object({
20
+ [INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY]: Type.String({
21
+ minLength: 1,
22
+ description: "非空基础设施失败诊断",
23
+ }),
24
+ }, {
25
+ additionalProperties: true,
26
+ description: "基础设施失败声明",
27
+ }),
28
+ }, { additionalProperties: true });
29
+ /**
30
+ * Compose the shared infrastructure-failure declaration into an open output
31
+ * tool-object schema. Returns an open object (additionalProperties: true,
32
+ * required: []) with the base schema's properties plus the shared declaration.
33
+ * Static typing is preserved on the base (`as S`), so existing
34
+ * `Static<typeof ...>` derived parameter types are unchanged.
35
+ */
36
+ export function withInfrastructureFailureDeclaration(schema) {
37
+ const baseProperties = schema
38
+ .properties;
39
+ const properties = {
40
+ ...(baseProperties ?? {}),
41
+ [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureDeclarationSchema.properties[INFRASTRUCTURE_FAILURE_DECLARATION_KEY],
42
+ };
43
+ const object = Type.Object(properties, { additionalProperties: true });
44
+ object.required = [];
45
+ return object;
46
+ }
47
+ /** Safe recognition of the typed declaration; non-shapes / hostile input fail closed. */
48
+ function isInfrastructureFailureDeclaration(parameters) {
49
+ if (parameters === null ||
50
+ typeof parameters !== "object" ||
51
+ Array.isArray(parameters)) {
52
+ return false;
53
+ }
54
+ const record = parameters;
55
+ if (!Object.hasOwn(record, INFRASTRUCTURE_FAILURE_DECLARATION_KEY))
56
+ return false;
57
+ const declaration = record[INFRASTRUCTURE_FAILURE_DECLARATION_KEY];
58
+ if (declaration === null ||
59
+ typeof declaration !== "object" ||
60
+ Array.isArray(declaration)) {
61
+ return false;
62
+ }
63
+ const diagnostic = declaration[INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY];
64
+ return typeof diagnostic === "string" && diagnostic.trim().length > 0;
65
+ }
66
+ /** Non-empty trimmed diagnostic from the declaration, else undefined. */
67
+ function infrastructureFailureDiagnostic(parameters) {
68
+ if (!isInfrastructureFailureDeclaration(parameters))
69
+ return undefined;
70
+ const declaration = parameters[INFRASTRUCTURE_FAILURE_DECLARATION_KEY];
71
+ const diagnostic = declaration[INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY];
72
+ return typeof diagnostic === "string" ? diagnostic.trim() : undefined;
73
+ }
74
+ /** diagnostic → Error, name stamped so the host error identity is observable. */
75
+ function infrastructureFailureError(diagnostic) {
76
+ const error = new Error(diagnostic);
77
+ error.name = "InfrastructureFailure";
78
+ return error;
79
+ }
80
+ /**
81
+ * The one early call for every terminating output `execute`: if the parameters
82
+ * carry the infra declaration, hand the diagnostic error to the shared host
83
+ * `failInfrastructure` seam (which aborts the run). No-op otherwise.
84
+ */
85
+ export function failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId) {
86
+ const diagnostic = infrastructureFailureDiagnostic(parameters);
87
+ if (diagnostic === undefined)
88
+ return;
89
+ hostActions.failInfrastructure(infrastructureFailureError(diagnostic), ctx, toolCallId);
90
+ }
@@ -14012,6 +14012,44 @@ var init_open_tool_schema = __esm({
14012
14012
  }
14013
14013
  });
14014
14014
 
14015
+ // src/package-contracts/terminating-infrastructure.ts
14016
+ function withInfrastructureFailureDeclaration(schema) {
14017
+ const baseProperties = schema.properties;
14018
+ const properties = {
14019
+ ...baseProperties ?? {},
14020
+ [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureDeclarationSchema.properties[INFRASTRUCTURE_FAILURE_DECLARATION_KEY]
14021
+ };
14022
+ const object = typebox_exports.Object(properties, { additionalProperties: true });
14023
+ object.required = [];
14024
+ return object;
14025
+ }
14026
+ var INFRASTRUCTURE_FAILURE_DECLARATION_KEY, INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY, infrastructureFailureDeclarationSchema;
14027
+ var init_terminating_infrastructure = __esm({
14028
+ "src/package-contracts/terminating-infrastructure.ts"() {
14029
+ "use strict";
14030
+ init_build();
14031
+ INFRASTRUCTURE_FAILURE_DECLARATION_KEY = "infrastructureFailure";
14032
+ INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY = "diagnostic";
14033
+ infrastructureFailureDeclarationSchema = typebox_exports.Object(
14034
+ {
14035
+ [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: typebox_exports.Object(
14036
+ {
14037
+ [INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY]: typebox_exports.String({
14038
+ minLength: 1,
14039
+ description: "\u975E\u7A7A\u57FA\u7840\u8BBE\u65BD\u5931\u8D25\u8BCA\u65AD"
14040
+ })
14041
+ },
14042
+ {
14043
+ additionalProperties: true,
14044
+ description: "\u57FA\u7840\u8BBE\u65BD\u5931\u8D25\u58F0\u660E"
14045
+ }
14046
+ )
14047
+ },
14048
+ { additionalProperties: true }
14049
+ );
14050
+ }
14051
+ });
14052
+
14015
14053
  // src/package-contracts/fixer-output.ts
14016
14054
  function validateFixerOutput(value, _phase) {
14017
14055
  return value;
@@ -14023,6 +14061,7 @@ var init_fixer_output = __esm({
14023
14061
  init_build();
14024
14062
  init_fixer_packet();
14025
14063
  init_open_tool_schema();
14064
+ init_terminating_infrastructure();
14026
14065
  FIXER_OUTPUT_TOOL_NAME = "ak_fixer_output";
14027
14066
  nonblankTransportString = typebox_exports.String({ minLength: 1 });
14028
14067
  authorityBlockerSchema = typebox_exports.Object({ cause: typebox_exports.Literal("authority_violation"), evidence: nonblankTransportString });
@@ -14057,7 +14096,9 @@ var init_fixer_output = __esm({
14057
14096
  typebox_exports.Object({ status: typebox_exports.Literal("refused", { description: "refused \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8" }), report: typebox_exports.String({ minLength: 1, description: "\u5982\u5B9E\u7ED3\u679C\u62A5\u544A" }), classResults: typebox_exports.Array(classResultSchema, { minItems: 1, description: "\u5404\u7C7B\u62D2\u7EDD\u7ED3\u7B97" }) }),
14058
14097
  typebox_exports.Object({ status: typebox_exports.Literal("partially_completed", { description: "partially_completed \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8" }), report: typebox_exports.String({ minLength: 1, description: "\u5982\u5B9E\u7ED3\u679C\u62A5\u544A" }), classResults: typebox_exports.Array(classResultSchema, { minItems: 1, description: "\u5404\u7C7B\u5B8C\u6210\u6216\u62D2\u7EDD\u7ED3\u7B97" }), testEvidence: typebox_exports.Optional(testEvidenceSchema) })
14059
14098
  ]);
14060
- fixerOutputSchema = openToolObjectFromUnion(fixerOutputVariants);
14099
+ fixerOutputSchema = withInfrastructureFailureDeclaration(
14100
+ openToolObjectFromUnion(fixerOutputVariants)
14101
+ );
14061
14102
  }
14062
14103
  });
14063
14104
 
@@ -14116,6 +14157,7 @@ var init_doctor_contracts = __esm({
14116
14157
  init_build();
14117
14158
  init_canonical_json();
14118
14159
  init_open_tool_schema();
14160
+ init_terminating_infrastructure();
14119
14161
  DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
14120
14162
  DOCTOR_TARGET_KINDS = ["law", "gate", "template", "station", "seat"];
14121
14163
  nonblank = typebox_exports.String({ minLength: 1, pattern: "\\S" });
@@ -14166,7 +14208,9 @@ var init_doctor_contracts = __esm({
14166
14208
  missingEvidence: typebox_exports.Array(typebox_exports.Object({ need: nonblank, targetKeys: typebox_exports.Array(nonblank, { minItems: 1 }) }, { additionalProperties: false }), { minItems: 1, description: "\u5982\u5B9E\u8BC1\u8BCD\u6240\u9700\u800C\u5C1A\u7F3A\u7684\u8BC1\u636E" })
14167
14209
  }, { additionalProperties: false, description: "\u8BC1\u636E\u4E0D\u8DB3\u4EE5\u652F\u6491\u5982\u5B9E\u6848\u8BC1\u8BCD" })
14168
14210
  ]);
14169
- doctorSubmissionSchema = openToolObjectFromUnion(doctorSubmissionVariants);
14211
+ doctorSubmissionSchema = withInfrastructureFailureDeclaration(
14212
+ openToolObjectFromUnion(doctorSubmissionVariants)
14213
+ );
14170
14214
  doctorOutputSchema = typebox_exports.Union([
14171
14215
  typebox_exports.Object({ status: typebox_exports.Literal("completed"), case: caseIdentity, findings: typebox_exports.Array(finding), cost }, { additionalProperties: false }),
14172
14216
  doctorSubmissionVariants.anyOf[1]
@@ -14272,6 +14316,7 @@ var init_merger_contracts = __esm({
14272
14316
  init_exact_utf8();
14273
14317
  init_sha256();
14274
14318
  init_open_tool_schema();
14319
+ init_terminating_infrastructure();
14275
14320
  oidPattern = "^(?:[0-9a-f]{40}|[0-9a-f]{64})$";
14276
14321
  materialSchema = typebox_exports.Object({ bytesBase64: typebox_exports.String(), sha256: typebox_exports.String() }, { additionalProperties: false });
14277
14322
  checkSchema = typebox_exports.Object({ name: typebox_exports.String({ minLength: 1 }), argv: typebox_exports.Array(typebox_exports.String({ minLength: 1 }), { minItems: 1 }) }, { additionalProperties: false });
@@ -14289,7 +14334,9 @@ var init_merger_contracts = __esm({
14289
14334
  typebox_exports.Object({ status: typebox_exports.Literal("completed", { description: "completed \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8" }), attemptId: typebox_exports.String({ minLength: 1, description: "\u5DF2\u53D7\u7406\u5408\u5E76 attempt \u8EAB\u4EFD" }), report: typebox_exports.String({ minLength: 1, description: "\u5982\u5B9E\u7ED3\u679C\u62A5\u544A" }), mergeCommitId: typebox_exports.String({ pattern: oidPattern, description: "\u5DF2\u6838\u9A8C\u7684\u5B8C\u6210\u5408\u5E76 commit object ID" }) }, { additionalProperties: false }),
14290
14335
  typebox_exports.Object({ status: typebox_exports.Literal("escalate", { description: "escalate \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8" }), attemptId: typebox_exports.String({ minLength: 1, description: "\u5DF2\u53D7\u7406\u5408\u5E76 attempt \u8EAB\u4EFD" }), diagnosis: typebox_exports.String({ minLength: 1, description: "\u5408\u5E76\u5B8C\u6210\u9700\u5347\u7EA7\u7684\u539F\u56E0" }), report: typebox_exports.String({ minLength: 1, description: "\u5982\u5B9E\u7ED3\u679C\u62A5\u544A" }) }, { additionalProperties: false })
14291
14336
  ]);
14292
- mergerOutputSchema = openToolObjectFromUnion(mergerOutputVariants);
14337
+ mergerOutputSchema = withInfrastructureFailureDeclaration(
14338
+ openToolObjectFromUnion(mergerOutputVariants)
14339
+ );
14293
14340
  MERGER_OUTPUT_TOOL_NAME = "ak_merger_output";
14294
14341
  record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
14295
14342
  blank = (v) => typeof v !== "string" || v.trim().length === 0;
@@ -14351,17 +14398,20 @@ var init_notary_contracts = __esm({
14351
14398
  "use strict";
14352
14399
  init_build();
14353
14400
  init_open_tool_schema();
14401
+ init_terminating_infrastructure();
14354
14402
  NOTARY_OUTPUT_TOOL_NAME = "ak_notary_output";
14355
14403
  NOTARY_FIXED_KICKOFF = "\u7B26\u5B9D\u90CE\u6848\u5377\u5DF2\u53D7\u7406\uFF1B\u6765\u6E90 run \u5B9A\u4F4D\u89C1\u4F1A\u8BDD\u6750\u6599\u3002";
14356
- notaryOutputSchema = openToolObject(
14357
- typebox_exports.Object({
14358
- status: typebox_exports.Unknown({
14359
- description: "pass | bounce \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8"
14360
- }),
14361
- findings: typebox_exports.Unknown({
14362
- description: "string[] findings\uFF0C\u968F pass \u6216 bounce \u7559\u5B58"
14404
+ notaryOutputSchema = withInfrastructureFailureDeclaration(
14405
+ openToolObject(
14406
+ typebox_exports.Object({
14407
+ status: typebox_exports.Unknown({
14408
+ description: "pass | bounce \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8"
14409
+ }),
14410
+ findings: typebox_exports.Unknown({
14411
+ description: "string[] findings\uFF0C\u968F pass \u6216 bounce \u7559\u5B58"
14412
+ })
14363
14413
  })
14364
- })
14414
+ )
14365
14415
  );
14366
14416
  }
14367
14417
  });
@@ -20488,6 +20538,7 @@ var init_collector_tool_schemas = __esm({
20488
20538
  "use strict";
20489
20539
  init_build();
20490
20540
  init_collector_evidence();
20541
+ init_terminating_infrastructure();
20491
20542
  collectorObserveArgsSchema = typebox_exports.Object({}, { additionalProperties: false });
20492
20543
  collectorRequestArgsSchema = typebox_exports.Object({
20493
20544
  requestId: typebox_exports.String({ minLength: 1, description: "\u914D\u7F6E\u8BF7\u6C42\u8EAB\u4EFD" }),
@@ -20496,7 +20547,9 @@ var init_collector_tool_schemas = __esm({
20496
20547
  collectorWaitArgsSchema = typebox_exports.Object({
20497
20548
  durationMs: typebox_exports.Integer({ minimum: 1, maximum: COLLECTOR_ELIGIBILITY_MS, description: "\u7B49\u5F85\u6BEB\u79D2\uFF1B\u5355\u6B21\u4E0A\u9650\u4E94\u5206\u949F\u4E14\u4E0D\u8D85\u5269\u4F59\u8D44\u683C" })
20498
20549
  }, { additionalProperties: false });
20499
- collectorOutputArgsSchema = typebox_exports.Object({}, { additionalProperties: true });
20550
+ collectorOutputArgsSchema = withInfrastructureFailureDeclaration(
20551
+ typebox_exports.Object({}, { additionalProperties: true })
20552
+ );
20500
20553
  collectorOutputArgsSchema.required = [];
20501
20554
  }
20502
20555
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.2479",
3
+ "version": "0.1.2497",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -9,8 +9,8 @@
9
9
  御史台
10
10
 
11
11
  大理寺
12
- ├─ 有问题(通常) → 修内司 → 大理寺
13
- ├─ 需要再审 → 御史台 → 大理寺
12
+ ├─ 有问题(通常) → 修内司 → 大理寺(修内司收卷不经御史台,直回大理寺)
13
+ ├─ 需要再审(大理寺点名) → 御史台 → 大理寺
14
14
  └─ 收敛 → 调用者合并、关票
15
15
 
16
16
  ## 线上审查材料
@@ -30,6 +30,9 @@ import {
30
30
  buildCollectorReceipt,
31
31
  type CollectorReceipt,
32
32
  } from "./collector-receipt.ts";
33
+ import {
34
+ failOnInfrastructureFailureDeclaration,
35
+ } from "./package-contracts/terminating-infrastructure.ts";
33
36
  import {
34
37
  collectorObserveArgsSchema,
35
38
  collectorOutputArgsSchema,
@@ -388,6 +391,9 @@ export function createCollectorRoleRuntime(
388
391
  throw new Error("通进司未激活");
389
392
  }
390
393
  try {
394
+ // #541: infra declaration fails via the shared host seam before the
395
+ // operational ledger begins / a receipt is built.
396
+ failOnInfrastructureFailureDeclaration(params, hostActions, ctx, toolCallId);
391
397
  assertSoleFinalCollectorOutput(toolCallId, ctx);
392
398
  activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, toolCallId);
393
399
  const receipt: CollectorReceipt = buildCollectorReceipt(
@@ -1,5 +1,6 @@
1
1
  import { Type, type Static } from "typebox";
2
2
  import { COLLECTOR_ELIGIBILITY_MS } from "./collector-evidence.ts";
3
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
3
4
 
4
5
  export const collectorObserveArgsSchema = Type.Object({}, { additionalProperties: false });
5
6
  export const collectorRequestArgsSchema = Type.Object({
@@ -10,7 +11,9 @@ export const collectorWaitArgsSchema = Type.Object({
10
11
  durationMs: Type.Integer({ minimum: 1, maximum: COLLECTOR_ELIGIBILITY_MS, description: "等待毫秒;单次上限五分钟且不超剩余资格" }),
11
12
  }, { additionalProperties: false });
12
13
  /** Runtime owns the observed groups; the model only signals sole-final submission. */
13
- export const collectorOutputArgsSchema = Type.Object({}, { additionalProperties: true });
14
+ export const collectorOutputArgsSchema = withInfrastructureFailureDeclaration(
15
+ Type.Object({}, { additionalProperties: true }),
16
+ );
14
17
  (collectorOutputArgsSchema as unknown as { required: string[] }).required = [];
15
18
 
16
19
  export type CollectorObserveArgs = Static<typeof collectorObserveArgsSchema>;
@@ -1,6 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
  import { canonicalJson } from "./canonical-json.ts";
3
3
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
4
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
4
5
 
5
6
  export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
6
7
  export const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
@@ -87,7 +88,9 @@ const doctorSubmissionVariants = Type.Union([
87
88
  missingEvidence: Type.Array(Type.Object({ need: nonblank, targetKeys: Type.Array(nonblank, { minItems: 1 }) }, { additionalProperties: false }), { minItems: 1, description: "如实证词所需而尚缺的证据" }),
88
89
  }, { additionalProperties: false, description: "证据不足以支撑如实案证词" }),
89
90
  ]);
90
- export const doctorSubmissionSchema = openToolObjectFromUnion(doctorSubmissionVariants);
91
+ export const doctorSubmissionSchema = withInfrastructureFailureDeclaration(
92
+ openToolObjectFromUnion(doctorSubmissionVariants),
93
+ );
91
94
  export const doctorOutputSchema = Type.Union([
92
95
  Type.Object({ status: Type.Literal("completed"), case: caseIdentity, findings: Type.Array(finding), cost }, { additionalProperties: false }),
93
96
  doctorSubmissionVariants.anyOf[1]!,
@@ -2,6 +2,7 @@ import type { AgentToolResult, ExtensionAPI, ExtensionContext } from "@earendil-
2
2
  import { disposeComplianceDecision } from "./audit-escalation.ts";
3
3
  import { appendActiveSessionCustomEntry, type ComplianceDecision } from "./compliance-transport.ts";
4
4
  import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
5
+ import { failOnInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
5
6
  import { DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, DOCTOR_ACCEPTED_TEXT, DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
6
7
 
7
8
  export { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME };
@@ -12,7 +13,7 @@ export function createDoctorRoleRuntime(pi: ExtensionAPI, dependencies: DoctorRo
12
13
  let activation: { soul: string; patient: DoctorCase; store: DoctorEvidenceStore } | undefined; let registered = false; pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
13
14
  return { async activate() { const path = pi.getFlag(DOCTOR_CASE_FLAG.name); if (typeof path !== "string" || !path.trim()) throw new Error("Doctor requires --ak-doctor-case"); const soul = (await dependencies.loadSoul()).trim(); if (!soul) throw new Error("Doctor soul is empty"); const patient = await dependencies.loadCase(path); activation = { soul, patient, store: new DoctorEvidenceStore(patient) };
14
15
  if (!registered) { registered = true; pi.registerTool({ name: DOCTOR_EVIDENCE_TOOL_NAME, label: "太医署证据", description: "分页读取留存的 Pi session 字节。", parameters: doctorEvidenceReadSchema, async execute(_id, params: { evidenceId: string; offset?: number; limit?: number }) { if (!activation) throw new Error("太医署未激活"); const details = activation.store.read(params.evidenceId, params.offset, params.limit); return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details }; } });
15
- pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "太医署输出", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id, params, signal, _update, ctx): Promise<AgentToolResult<unknown>> { if (!activation) throw new Error("太医署未激活"); singleton(id, ctx); const testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendActiveSessionCustomEntry(ctx, DOCTOR_CANDIDATE_ENTRY_TYPE, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.patient.identity }, { unavailable: "太医署候选留存不可用", failed: "太医署候选留存失败" }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx } : { context: ctx, signal }); } catch (error) { host.failInfrastructure(error, ctx, id); } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision<AgentToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true as const, ...usageProjection }), revise: (violations) => { throw new Error(`太医署回执违 soul:${violations.join("; ")}`); }, escalate: (result) => result }, acceptedDetails); } });
16
+ pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "太医署输出", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id, params, signal, _update, ctx): Promise<AgentToolResult<unknown>> { if (!activation) throw new Error("太医署未激活"); failOnInfrastructureFailureDeclaration(params, host, ctx, id); singleton(id, ctx); const testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendActiveSessionCustomEntry(ctx, DOCTOR_CANDIDATE_ENTRY_TYPE, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.patient.identity }, { unavailable: "太医署候选留存不可用", failed: "太医署候选留存失败" }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx } : { context: ctx, signal }); } catch (error) { host.failInfrastructure(error, ctx, id); } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision<AgentToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true as const, ...usageProjection }), revise: (violations) => { throw new Error(`太医署回执违 soul:${violations.join("; ")}`); }, escalate: (result) => result }, acceptedDetails); } });
16
17
  pi.on("before_agent_start", (event) => { if (!activation) throw new Error("太医署未激活"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { provenance: "由留存 session 字节推导,封入受理回执。", cost: activation.patient.cost }, lawfulTargetKeys: ["case", ...activation.patient.cost.invocations.sources], evidence: activation.patient.evidence.map(({ id, kind, sha256, byteLength, contentLength }) => ({ id, kind, sha256, byteLength, contentLength })) }; return { systemPrompt: `${event.systemPrompt}\n\n<doctor_soul>\n${activation.soul}\n</doctor_soul>\n\n<doctor_case>\n${JSON.stringify(catalog)}\n</doctor_case>` }; }); }
17
18
  const required = [DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME]; const names = pi.getAllTools().map((tool) => tool.name); for (const name of required) if (names.filter((item) => item === name).length !== 1) throw new Error(`Doctor required tool collision or missing: ${name}`); pi.setActiveTools(required); const active = pi.getActiveTools?.() ?? required; if (active.length !== 2 || !required.every((name) => active.includes(name))) throw new Error("Doctor active tool narrowing failed"); } };
18
19
  }
package/src/judge-role.ts CHANGED
@@ -9,6 +9,10 @@ import { Type, type Static } from "typebox";
9
9
  import { disposeComplianceDecision } from "./audit-escalation.ts";
10
10
  import type { ComplianceDecision } from "./compliance-transport.ts";
11
11
  import { requireGatekeeperPass, type GatekeeperPassHostActions } from "./gatekeeper-role.ts";
12
+ import {
13
+ failOnInfrastructureFailureDeclaration,
14
+ withInfrastructureFailureDeclaration,
15
+ } from "./package-contracts/terminating-infrastructure.ts";
12
16
 
13
17
  import {
14
18
  JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT,
@@ -21,34 +25,36 @@ import {
21
25
  export { JUDGE_OUTPUT_TOOL_NAME };
22
26
  export type { JudgeVerdict };
23
27
 
24
- const judgeVerdictSchema = Type.Object(
25
- {
26
- judgeStatus: StringEnum(["converged", "continue", "escalate"] as const, { description: "converged | continue | escalate — 形状指引,非 schema 闸" }),
27
- fix: Type.Optional(
28
- Type.Object(
29
- { summary: Type.String({ minLength: 1, description: "continue 时的补救摘要" }) },
30
- { additionalProperties: false, description: "continue 时的补救说明" },
28
+ export const judgeVerdictSchema = withInfrastructureFailureDeclaration(
29
+ Type.Object(
30
+ {
31
+ judgeStatus: StringEnum(["converged", "continue", "escalate"] as const, { description: "converged | continue | escalate — 形状指引,非 schema 闸" }),
32
+ fix: Type.Optional(
33
+ Type.Object(
34
+ { summary: Type.String({ minLength: 1, description: "continue 时的补救摘要" }) },
35
+ { additionalProperties: false, description: "continue 时的补救说明" },
36
+ ),
31
37
  ),
32
- ),
33
- classes: Type.Optional(Type.Array(Type.Object({
34
- name: Type.String({ minLength: 1 }),
35
- owner: Type.String({ minLength: 1 }),
36
- boundary: Type.String({ minLength: 1 }),
37
- disposition: Type.String({ minLength: 1 }),
38
- }, { additionalProperties: false }), { minItems: 1, description: "已裁决 finding 类及其 owner 与修理边界" })),
39
- note: Type.Optional(Type.String({ minLength: 1, description: "可选裁决附注" })),
40
- evidence: Type.Optional(Type.Unknown({ description: "留存的裁决证据" })),
41
- decisionGate: Type.Optional(
42
- Type.Object(
43
- {
44
- question: Type.String({ minLength: 1 }),
45
- options: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
46
- },
47
- { additionalProperties: false, description: "需人权威处置的问题与选项" },
38
+ classes: Type.Optional(Type.Array(Type.Object({
39
+ name: Type.String({ minLength: 1 }),
40
+ owner: Type.String({ minLength: 1 }),
41
+ boundary: Type.String({ minLength: 1 }),
42
+ disposition: Type.String({ minLength: 1 }),
43
+ }, { additionalProperties: false }), { minItems: 1, description: "已裁决 finding 类及其 owner 与修理边界" })),
44
+ note: Type.Optional(Type.String({ minLength: 1, description: "可选裁决附注" })),
45
+ evidence: Type.Optional(Type.Unknown({ description: "留存的裁决证据" })),
46
+ decisionGate: Type.Optional(
47
+ Type.Object(
48
+ {
49
+ question: Type.String({ minLength: 1 }),
50
+ options: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
51
+ },
52
+ { additionalProperties: false, description: "需人权威处置的问题与选项" },
53
+ ),
48
54
  ),
49
- ),
50
- },
51
- { additionalProperties: true },
55
+ },
56
+ { additionalProperties: true },
57
+ ),
52
58
  );
53
59
  (judgeVerdictSchema as unknown as { required: string[] }).required = [];
54
60
 
@@ -109,6 +115,9 @@ export function createJudgeRoleRuntime(
109
115
  parameters: judgeVerdictSchema,
110
116
  async execute(toolCallId, parameters, signal, _onUpdate, ctx): Promise<AgentToolResult<unknown>> {
111
117
  if (soul === undefined) throw new Error("大理寺职分未装载");
118
+ // #541: infra declaration fails via the shared host seam before any
119
+ // Gatekeeper + audit courtyard work.
120
+ failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId);
112
121
  requireSingletonSubmissionCall(toolCallId, ctx);
113
122
  const verdict = validateVerdict(parameters);
114
123
  // Candidate verdict is already on the parent session books as this
@@ -3,6 +3,7 @@ import { isFullGitObjectId } from "./git-object-id.ts";
3
3
  import { exactUtf8 } from "./exact-utf8.ts";
4
4
  import { sha256Hex } from "./sha256.ts";
5
5
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
6
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
6
7
 
7
8
  const oidPattern = "^(?:[0-9a-f]{40}|[0-9a-f]{64})$";
8
9
  const materialSchema = Type.Object({ bytesBase64: Type.String(), sha256: Type.String() }, { additionalProperties: false });
@@ -19,7 +20,9 @@ const mergerOutputVariants = Type.Union([
19
20
  Type.Object({ status: Type.Literal("completed", { description: "completed — 形状指引,非 schema 闸" }), attemptId: Type.String({ minLength: 1, description: "已受理合并 attempt 身份" }), report: Type.String({ minLength: 1, description: "如实结果报告" }), mergeCommitId: Type.String({ pattern: oidPattern, description: "已核验的完成合并 commit object ID" }) }, { additionalProperties: false }),
20
21
  Type.Object({ status: Type.Literal("escalate", { description: "escalate — 形状指引,非 schema 闸" }), attemptId: Type.String({ minLength: 1, description: "已受理合并 attempt 身份" }), diagnosis: Type.String({ minLength: 1, description: "合并完成需升级的原因" }), report: Type.String({ minLength: 1, description: "如实结果报告" }) }, { additionalProperties: false }),
21
22
  ]);
22
- export const mergerOutputSchema = openToolObjectFromUnion(mergerOutputVariants);
23
+ export const mergerOutputSchema = withInfrastructureFailureDeclaration(
24
+ openToolObjectFromUnion(mergerOutputVariants),
25
+ );
23
26
 
24
27
  export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends readonly (infer U)[] ? readonly DeepReadonly<U>[] : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
25
28
  export type MergerMaterial = DeepReadonly<Static<typeof materialSchema>>;
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, mergerOutputSchema, validateMergerInput, validateMergerOutput, type MergerInput } from "./merger-contracts.ts";
3
3
  import type { MergerGitState } from "./merger-git-state.ts";
4
+ import { failOnInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
4
5
  import { exactUtf8 } from "./exact-utf8.ts";
5
6
  import { isFullGitObjectId } from "./git-object-id.ts";
6
7
 
@@ -28,10 +29,13 @@ export function createMergerRoleRuntime(pi: ExtensionAPI, dependencies: MergerRo
28
29
  if (state.targetObjectId !== input.targetObjectId || state.sourceObjectId !== input.sourceObjectId || !same(state.unmergedPaths, input.expectedConflictPaths) || state.unmergedPaths.length === 0 || !isFullGitObjectId(state.automaticMergeTreeId) || state.automaticMergeTreeId.length !== input.targetObjectId.length) throw new Error("Merger activation rejected repository parent, merge, automatic-result, or complete conflict-set drift");
29
30
  activation = { soul, input, automaticMergeTreeId: state.automaticMergeTreeId }; accepted = false;
30
31
  if (!registered) { registered = true;
31
- pi.registerTool({ name: MERGER_OUTPUT_TOOL_NAME, label: "合并输出", description: "提交合并结果;输出分支为 completed 与 escalate;基础设施及 Git 失败由 abort 通道承接。", promptSnippet: "提交合并结果", parameters: mergerOutputSchema,
32
+ pi.registerTool({ name: MERGER_OUTPUT_TOOL_NAME, label: "合并输出", description: "提交合并结果;输出分支为 completed 与 escalate", promptSnippet: "提交合并结果", parameters: mergerOutputSchema,
32
33
  async execute(id, params, _signal, _update, ctx) {
33
34
  if (!activation) throw new Error("校书郎未激活");
34
35
  if (accepted) throw new Error("合并回执已受理");
36
+ // #541: infra declaration fails via the shared host seam before output
37
+ // validation / Git verification / accepted=true.
38
+ failOnInfrastructureFailureDeclaration(params, host, ctx, id);
35
39
  let output;
36
40
  try {
37
41
  singleton(id, ctx);
@@ -6,6 +6,7 @@
6
6
  import { Type } from "typebox";
7
7
 
8
8
  import { openToolObject } from "./open-tool-schema.ts";
9
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
9
10
 
10
11
  export const NOTARY_OUTPUT_TOOL_NAME = "ak_notary_output";
11
12
  export const NOTARY_ACCEPTED_TEXT = "符宝郎回执已接受";
@@ -21,15 +22,17 @@ export const NOTARY_SOURCE_RUN_FLAG = {
21
22
  export const NOTARY_FIXED_KICKOFF =
22
23
  "符宝郎案卷已受理;来源 run 定位见会话材料。";
23
24
 
24
- export const notaryOutputSchema = openToolObject(
25
- Type.Object({
26
- status: Type.Unknown({
27
- description: "pass | bounce — 形状指引,非 schema 闸",
25
+ export const notaryOutputSchema = withInfrastructureFailureDeclaration(
26
+ openToolObject(
27
+ Type.Object({
28
+ status: Type.Unknown({
29
+ description: "pass | bounce — 形状指引,非 schema 闸",
30
+ }),
31
+ findings: Type.Unknown({
32
+ description: "string[] findings,随 pass 或 bounce 留存",
33
+ }),
28
34
  }),
29
- findings: Type.Unknown({
30
- description: "string[] findings,随 pass 或 bounce 留存",
31
- }),
32
- }),
35
+ ),
33
36
  );
34
37
 
35
38
  export type NotarySourceRunLocator = {
@@ -17,6 +17,9 @@ import {
17
17
  retainNotarySubmission,
18
18
  type NotarySourceRunLocator,
19
19
  } from "./notary-contracts.ts";
20
+ import {
21
+ failOnInfrastructureFailureDeclaration,
22
+ } from "./package-contracts/terminating-infrastructure.ts";
20
23
 
21
24
  export {
22
25
  NOTARY_ACCEPTED_TEXT,
@@ -98,6 +101,9 @@ export function createNotaryRoleRuntime(
98
101
  if (activation === undefined) {
99
102
  throw new Error("符宝郎未激活");
100
103
  }
104
+ // #541: infra declaration fails via the shared host seam before any
105
+ // pass/bounce projection.
106
+ failOnInfrastructureFailureDeclaration(parameters, host, ctx, toolCallId);
101
107
  // Unique submission + terminate only. Shape is not an admission gate
102
108
  // (第 0 条 / ADR 0055): lawful pass/bounce projected; else params as-is.
103
109
  requireSingletonSubmissionCall(toolCallId, ctx);
@@ -1,6 +1,7 @@
1
1
  import { Type, type Static } from "typebox";
2
2
  import { FIXER_PREREQUISITE_ID_PATTERN, type FixerInvocationInput } from "./fixer-packet.ts";
3
3
  import { openToolObjectFromUnion } from "../open-tool-schema.ts";
4
+ import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
4
5
 
5
6
  export const FIXER_OUTPUT_TOOL_NAME = "ak_fixer_output";
6
7
  export const FIXER_ACCEPTED_TEXT = "修内司回执已接受";
@@ -40,7 +41,9 @@ const fixerOutputVariants = Type.Union([
40
41
  Type.Object({ status: Type.Literal("refused", { description: "refused — 形状指引,非 schema 闸" }), report: Type.String({ minLength: 1, description: "如实结果报告" }), classResults: Type.Array(classResultSchema, { minItems: 1, description: "各类拒绝结算" }) }),
41
42
  Type.Object({ status: Type.Literal("partially_completed", { description: "partially_completed — 形状指引,非 schema 闸" }), report: Type.String({ minLength: 1, description: "如实结果报告" }), classResults: Type.Array(classResultSchema, { minItems: 1, description: "各类完成或拒绝结算" }), testEvidence: Type.Optional(testEvidenceSchema) }),
42
43
  ]);
43
- export const fixerOutputSchema = openToolObjectFromUnion(fixerOutputVariants);
44
+ export const fixerOutputSchema = withInfrastructureFailureDeclaration(
45
+ openToolObjectFromUnion(fixerOutputVariants),
46
+ );
44
47
 
45
48
  export type FixerBlocker = Static<typeof blockerSchema>;
46
49
  export type FixerClassResult = Static<typeof classResultSchema>;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Package-owned shared infrastructure-failure declaration for every primary
3
+ * packaged-role output tool (#541).
4
+ *
5
+ * One module owns what the judge mandates be shared (not reimplemented per
6
+ * seat): the typed `infrastructureFailure.diagnostic` declaration composed into
7
+ * each output tool's schema, and the single early host `failInfrastructure`
8
+ * call each output `execute` makes before any role business validation / gate /
9
+ * audit / ledger / Git work. The accepted status sets are intentionally NOT
10
+ * extended: an infra declaration fails BEFORE accepted validation, never
11
+ * becomes an accepted receipt. The diagnostic is carried verbatim on the thrown
12
+ * Error so settlement keeps the original cause (kind=failure, exit 1).
13
+ */
14
+ import { Type, type TSchema } from "typebox";
15
+
16
+ export const INFRASTRUCTURE_FAILURE_DECLARATION_KEY =
17
+ "infrastructureFailure" as const;
18
+ export const INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY = "diagnostic" as const;
19
+
20
+ /** Shared typed declaration fragment: `infrastructureFailure.diagnostic` = non-empty string. */
21
+ const infrastructureFailureDeclarationSchema = Type.Object(
22
+ {
23
+ [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: Type.Object(
24
+ {
25
+ [INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY]: Type.String({
26
+ minLength: 1,
27
+ description: "非空基础设施失败诊断",
28
+ }),
29
+ },
30
+ {
31
+ additionalProperties: true,
32
+ description: "基础设施失败声明",
33
+ },
34
+ ),
35
+ },
36
+ { additionalProperties: true },
37
+ );
38
+
39
+ /**
40
+ * Compose the shared infrastructure-failure declaration into an open output
41
+ * tool-object schema. Returns an open object (additionalProperties: true,
42
+ * required: []) with the base schema's properties plus the shared declaration.
43
+ * Static typing is preserved on the base (`as S`), so existing
44
+ * `Static<typeof ...>` derived parameter types are unchanged.
45
+ */
46
+ export function withInfrastructureFailureDeclaration<
47
+ S extends TSchema & { properties?: Record<string, TSchema> },
48
+ >(schema: S): S {
49
+ const baseProperties = (schema as { properties?: Record<string, TSchema> })
50
+ .properties;
51
+ const properties: Record<string, TSchema> = {
52
+ ...(baseProperties ?? {}),
53
+ [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]:
54
+ infrastructureFailureDeclarationSchema.properties[
55
+ INFRASTRUCTURE_FAILURE_DECLARATION_KEY
56
+ ],
57
+ };
58
+ const object = Type.Object(properties, { additionalProperties: true });
59
+ (object as unknown as { required: string[] }).required = [];
60
+ return object as unknown as S;
61
+ }
62
+
63
+ /** Structural host seam subset shared by every terminating execute path. */
64
+ type TerminatingInfrastructureHostActions<C> = {
65
+ failInfrastructure(error: unknown, ctx: C, toolCallId?: string): never;
66
+ };
67
+
68
+ /** Safe recognition of the typed declaration; non-shapes / hostile input fail closed. */
69
+ function isInfrastructureFailureDeclaration(
70
+ parameters: unknown,
71
+ ): boolean {
72
+ if (
73
+ parameters === null ||
74
+ typeof parameters !== "object" ||
75
+ Array.isArray(parameters)
76
+ ) {
77
+ return false;
78
+ }
79
+ const record = parameters as Record<string, unknown>;
80
+ if (!Object.hasOwn(record, INFRASTRUCTURE_FAILURE_DECLARATION_KEY)) return false;
81
+ const declaration = record[INFRASTRUCTURE_FAILURE_DECLARATION_KEY];
82
+ if (
83
+ declaration === null ||
84
+ typeof declaration !== "object" ||
85
+ Array.isArray(declaration)
86
+ ) {
87
+ return false;
88
+ }
89
+ const diagnostic = (declaration as Record<string, unknown>)[
90
+ INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY
91
+ ];
92
+ return typeof diagnostic === "string" && diagnostic.trim().length > 0;
93
+ }
94
+
95
+ /** Non-empty trimmed diagnostic from the declaration, else undefined. */
96
+ function infrastructureFailureDiagnostic(
97
+ parameters: unknown,
98
+ ): string | undefined {
99
+ if (!isInfrastructureFailureDeclaration(parameters)) return undefined;
100
+ const declaration = (parameters as Record<string, unknown>)[
101
+ INFRASTRUCTURE_FAILURE_DECLARATION_KEY
102
+ ] as Record<string, unknown>;
103
+ const diagnostic = declaration[INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY];
104
+ return typeof diagnostic === "string" ? diagnostic.trim() : undefined;
105
+ }
106
+
107
+ /** diagnostic → Error, name stamped so the host error identity is observable. */
108
+ function infrastructureFailureError(diagnostic: string): Error {
109
+ const error = new Error(diagnostic);
110
+ error.name = "InfrastructureFailure";
111
+ return error;
112
+ }
113
+
114
+ /**
115
+ * The one early call for every terminating output `execute`: if the parameters
116
+ * carry the infra declaration, hand the diagnostic error to the shared host
117
+ * `failInfrastructure` seam (which aborts the run). No-op otherwise.
118
+ */
119
+ export function failOnInfrastructureFailureDeclaration<C>(
120
+ parameters: unknown,
121
+ hostActions: TerminatingInfrastructureHostActions<C>,
122
+ ctx: C,
123
+ toolCallId: string,
124
+ ): void {
125
+ const diagnostic = infrastructureFailureDiagnostic(parameters);
126
+ if (diagnostic === undefined) return;
127
+ hostActions.failInfrastructure(
128
+ infrastructureFailureError(diagnostic),
129
+ ctx,
130
+ toolCallId,
131
+ );
132
+ }
@@ -10,6 +10,10 @@ import { ReviewerDispatchExecutionError, type ReviewerDispatchRunResult } from "
10
10
  import { createReviewerExecutionLedger, projectAcceptedDispatch, projectReviewerDispatchOutcome, type ReviewerExecutionRecord } from "./reviewer-execution-ledger.ts";
11
11
  import { assembleRuntimeReviewerReceipt } from "./reviewer-settlement.ts";
12
12
  import { REVIEWER_ACCEPTED_TEXT, REVIEWER_OUTPUT_TOOL_NAME, validateReviewerIntent, type ReviewerIntent } from "./package-contracts/reviewer-output.ts";
13
+ import {
14
+ failOnInfrastructureFailureDeclaration,
15
+ withInfrastructureFailureDeclaration,
16
+ } from "./package-contracts/terminating-infrastructure.ts";
13
17
 
14
18
  export { REVIEWER_OUTPUT_TOOL_NAME };
15
19
  export type { ReviewerIntent };
@@ -39,7 +43,9 @@ const reviewerOutputVariants = Type.Union([
39
43
  amendments: Type.Optional(reviewerAmendmentsSchema),
40
44
  }, { additionalProperties: false }),
41
45
  ]);
42
- const reviewerOutputSchema = openToolObjectFromUnion(reviewerOutputVariants);
46
+ export const reviewerOutputSchema = withInfrastructureFailureDeclaration(
47
+ openToolObjectFromUnion(reviewerOutputVariants),
48
+ );
43
49
  export type ReviewerRoleDependencies = {
44
50
  loadSoul(): Promise<string>;
45
51
  loadCanonicalSkillBinding(name: "code-review"): Promise<AnyCanonicalSkillBinding>;
@@ -147,6 +153,9 @@ export function createReviewerRoleRuntime(
147
153
  pi.registerTool({ name: REVIEWER_OUTPUT_TOOL_NAME, label: "御史台输出", description: "Standards/Spec 评审腿由 runtime 以取证子会话代跑,本席收腿报告后交薄回执。", promptSnippet: "提交御史台终局回执", parameters: reviewerOutputSchema,
148
154
  async execute(id, parameters, _signal, _update, toolCtx): Promise<AgentToolResult<unknown>> {
149
155
  if (!soul || !binding) throw new Error("御史台输入未装载");
156
+ // #541: infra declaration fails via the shared host seam before the
157
+ // ledger audit record / receipt assembly / shutdown.
158
+ failOnInfrastructureFailureDeclaration(parameters, hostActions, toolCtx, id);
150
159
  requireSoleReviewerOutputCall(id, toolCtx);
151
160
  const output = validateReviewerIntent(parameters);
152
161
  let record: ReviewerExecutionRecord;
@@ -6,6 +6,10 @@ import type {
6
6
  } from "@earendil-works/pi-coding-agent";
7
7
  import { Type, type Static } from "typebox";
8
8
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
9
+ import {
10
+ failOnInfrastructureFailureDeclaration,
11
+ withInfrastructureFailureDeclaration,
12
+ } from "./package-contracts/terminating-infrastructure.ts";
9
13
 
10
14
  import type {
11
15
  AnyCanonicalSkillBinding,
@@ -85,7 +89,9 @@ const coderOutputVariants = Type.Union([
85
89
  })),
86
90
  }, { additionalProperties: false }),
87
91
  ]);
88
- const coderOutputSchema = openToolObjectFromUnion(coderOutputVariants);
92
+ export const coderOutputSchema = withInfrastructureFailureDeclaration(
93
+ openToolObjectFromUnion(coderOutputVariants),
94
+ );
89
95
  export type { FixerOutput, CoderOutput };
90
96
  export const FIXER_FLAG_DEFINITIONS = {
91
97
  packet: {
@@ -262,13 +268,16 @@ export function createFixerRoleRuntime(
262
268
  pi.registerTool({
263
269
  name: FIXER_OUTPUT_TOOL_NAME,
264
270
  label: "修内司输出",
265
- description: "提交修内司终局回执;基础设施失败走 abort,不经本工具。",
271
+ description: "提交修内司终局回执",
266
272
  promptSnippet: "提交修内司终局回执",
267
273
  parameters: fixerOutputSchema,
268
274
  async execute(toolCallId, parameters, _signal, _onUpdate, ctx): Promise<AgentToolResult<unknown>> {
269
275
  if (packet === undefined || phase === undefined) {
270
276
  throw new Error("修内司修理包与阶段未装载");
271
277
  }
278
+ // #541: infra declaration fails via the shared host seam before any
279
+ // submission gate + Gatekeeper work.
280
+ failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId);
272
281
  requireSingletonSubmissionCall(
273
282
  toolCallId,
274
283
  FIXER_OUTPUT_TOOL_NAME,
@@ -400,6 +409,9 @@ export function createCoderRoleRuntime(
400
409
  if (task === undefined || phase === undefined) {
401
410
  throw new Error("将作监任务与阶段未装载");
402
411
  }
412
+ // #541: infra declaration fails via the shared host seam before any
413
+ // submission gate + Gatekeeper work.
414
+ failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId);
403
415
  requireSingletonSubmissionCall(
404
416
  toolCallId,
405
417
  CODER_OUTPUT_TOOL_NAME,