@akagilnc/pi-workflow-roles 0.1.2521 → 0.1.2653-next.71811ca

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 (109) hide show
  1. package/README.md +129 -39
  2. package/README.zh-CN.md +128 -39
  3. package/dist/activation-ledger-topology.js +1 -1
  4. package/dist/archivist-record-entry.js +47 -26
  5. package/dist/auditor-dossier-tool.js +3 -1
  6. package/dist/compliance-transport.js +17 -26
  7. package/dist/doctor-contracts.js +1 -2
  8. package/dist/dossier-resolution.js +4 -8
  9. package/dist/engine-detour-tool.js +33 -8
  10. package/dist/engine-detour.js +1 -0
  11. package/dist/evidence-child-executor.js +277 -424
  12. package/dist/host-contracts.js +22 -0
  13. package/dist/institutional-resolution.js +88 -0
  14. package/dist/merger-contracts.js +1 -2
  15. package/dist/navigator-attendance.js +16 -0
  16. package/dist/notary-contracts.js +2 -3
  17. package/dist/package-contracts/fixer-output.js +1 -2
  18. package/dist/packaged-role-registry.js +102 -9
  19. package/dist/pi/in-process-session.js +671 -0
  20. package/dist/public-cli/config.js +7 -5
  21. package/dist/public-cli/main.js +3767 -4113
  22. package/dist/reviewer-child-executor.js +6 -1
  23. package/dist/session-opening-materials.js +10 -38
  24. package/dist/sitian-appender.js +146 -0
  25. package/dist/sitian-contracts.js +21 -0
  26. package/dist/sitian-facade.js +15 -0
  27. package/dist/sitian-reader.js +52 -0
  28. package/extensions/role-runtime.ts +9 -7
  29. package/package.json +5 -5
  30. package/resources/engines/cursor.md +12 -26
  31. package/resources/engines/hermes.md +2 -23
  32. package/resources/engines/opus.md +1 -16
  33. package/resources/navigator-route-playbook.md +2 -2
  34. package/scripts/build-package.mjs +0 -1
  35. package/souls/quality-law.md +1 -1
  36. package/src/activation-ledger-topology.ts +1 -1
  37. package/src/archivist-record-entry.ts +57 -45
  38. package/src/auditor-dossier-tool.ts +4 -2
  39. package/src/canonical-skill-binding.ts +37 -27
  40. package/src/collector-role.ts +11 -20
  41. package/src/collector-tool-schemas.ts +1 -4
  42. package/src/compliance-transport.ts +19 -44
  43. package/src/doctor-auditor.ts +1 -5
  44. package/src/doctor-contracts.ts +1 -4
  45. package/src/doctor-role.ts +31 -8
  46. package/src/dossier-resolution.ts +8 -8
  47. package/src/engine-detour-tool.ts +48 -19
  48. package/src/engine-detour.ts +3 -0
  49. package/src/evidence-child-executor.ts +277 -485
  50. package/src/factory-board.ts +3 -3
  51. package/src/gatekeeper-role.ts +21 -10
  52. package/src/host-contracts.ts +386 -0
  53. package/src/in-process-session.ts +1 -115
  54. package/src/institutional-resolution.ts +135 -0
  55. package/src/judge-auditor.ts +1 -5
  56. package/src/judge-role.ts +37 -51
  57. package/src/merger-contracts.ts +1 -4
  58. package/src/merger-role.ts +7 -11
  59. package/src/navigator-attendance.ts +20 -1
  60. package/src/notary-contracts.ts +8 -11
  61. package/src/notary-role.ts +7 -23
  62. package/src/notary-source-run.ts +4 -3
  63. package/src/package-contracts/fixer-output.ts +1 -4
  64. package/src/package-resources/method-skill-binding.ts +10 -34
  65. package/src/packaged-role-registry.ts +122 -12
  66. package/src/pi/adapter.ts +192 -0
  67. package/src/pi/durable-principal.ts +98 -0
  68. package/src/pi/in-process-session.ts +819 -0
  69. package/src/pi/known-failure.ts +77 -0
  70. package/src/pi/pi-normalization.ts +284 -0
  71. package/src/pi/role-turn-host.ts +459 -0
  72. package/src/public-cli/auto-resume.ts +42 -44
  73. package/src/public-cli/cli.ts +171 -257
  74. package/src/public-cli/coder-run.ts +115 -433
  75. package/src/public-cli/collector-run.ts +62 -314
  76. package/src/public-cli/config.ts +7 -5
  77. package/src/public-cli/doctor-run.ts +41 -52
  78. package/src/public-cli/fixer-run.ts +122 -427
  79. package/src/public-cli/invocation.ts +287 -96
  80. package/src/public-cli/judge-run.ts +92 -422
  81. package/src/public-cli/merger-run.ts +167 -429
  82. package/src/public-cli/notary-run.ts +41 -47
  83. package/src/public-cli/option-definitions.ts +136 -3
  84. package/src/public-cli/post-admission.ts +497 -0
  85. package/src/public-cli/public-run-credentials.ts +4 -4
  86. package/src/public-cli/reviewer-dispatch-rejection.ts +106 -0
  87. package/src/public-cli/reviewer-run.ts +132 -449
  88. package/src/public-cli/run-lifecycle.ts +339 -327
  89. package/src/public-cli/settlement.ts +251 -134
  90. package/src/public-cli/terminal.ts +3 -9
  91. package/src/public-cli/turn-request.ts +87 -0
  92. package/src/reviewer-child-executor.ts +9 -1
  93. package/src/reviewer-role.ts +11 -20
  94. package/src/role-runtime.ts +129 -78
  95. package/src/session-opening-materials.ts +25 -39
  96. package/src/sitian-appender.ts +171 -0
  97. package/src/sitian-contracts.ts +106 -0
  98. package/src/sitian-facade.ts +18 -0
  99. package/src/sitian-reader.ts +62 -0
  100. package/src/worker-role.ts +42 -40
  101. package/src/worker-submission-gates.ts +33 -0
  102. package/dist/archivist-role-run-coordinates.js +0 -28
  103. package/dist/package-contracts/terminating-infrastructure.js +0 -90
  104. package/resources/engines/sonnet.md +0 -27
  105. package/resources/engines/zcode.md +0 -63
  106. package/src/archivist-role-run-coordinates.ts +0 -45
  107. package/src/package-contracts/terminating-infrastructure.ts +0 -132
  108. package/src/public-cli/explicit-internal.ts +0 -427
  109. package/src/public-cli/one-shot-dispatch.ts +0 -300
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Sitian Appender kernel (ADR 0065).
3
+ * Computes destination automatically from ledger topology without destination parameters.
4
+ * Owns volume open, torn-tail recovery, entry-level idempotency, and commit boundary.
5
+ */
6
+ import { createHash, randomUUID } from "node:crypto";
7
+ import { appendFileSync, existsSync, readFileSync } from "node:fs";
8
+ import { basename, dirname, join, resolve } from "node:path";
9
+
10
+ import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
11
+ import {
12
+ activationBookDirectory,
13
+ ensureRealDirectoryTree,
14
+ errorText,
15
+ physicallyContainedIn,
16
+ resolveActivationLedgerHome,
17
+ } from "./activation-ledger-topology.ts";
18
+ import {
19
+ SitianInfrastructureError,
20
+ type RecordPointer,
21
+ type SitianRecord,
22
+ type SitianRecordInput,
23
+ } from "./sitian-contracts.ts";
24
+
25
+ function isRecord(value: unknown): value is Record<string, unknown> {
26
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27
+ }
28
+
29
+ /** Authorized S4 submission ledger kinds that share a common run submission volume. */
30
+ export const S4_SUBMISSION_LEDGER_KINDS = new Set([
31
+ "candidate",
32
+ "batchContext",
33
+ "outcome",
34
+ "sealed",
35
+ "post-seal-anomaly",
36
+ ]);
37
+
38
+ /** Compute the volume partition key for directory placement. */
39
+ export function resolveSitianVolumeCategory(kind: string): string {
40
+ if (S4_SUBMISSION_LEDGER_KINDS.has(kind)) {
41
+ return "submission-ledger";
42
+ }
43
+ return kind;
44
+ }
45
+
46
+ function safeBookKey(cwd: string): string {
47
+ try {
48
+ return resolveBookKeyFromGit(cwd);
49
+ } catch {
50
+ return basename(resolve(cwd)) || "default";
51
+ }
52
+ }
53
+
54
+ /** Compute the destination directory and record file from ledger topology (ADR 0065). */
55
+ export function resolveSitianRecordPath(input: SitianRecordInput): {
56
+ readonly sessionDir: string;
57
+ readonly recordFile: string;
58
+ readonly ledgerHome: string;
59
+ } {
60
+ const cwd = input.cwd ?? process.cwd();
61
+ const ledgerHome = resolveActivationLedgerHome();
62
+ const category = resolveSitianVolumeCategory(input.kind);
63
+
64
+ let sessionDir: string;
65
+
66
+ if (input.sessionParent !== undefined && input.sessionParent.length > 0 && physicallyContainedIn(ledgerHome, input.sessionParent)) {
67
+ sessionDir = join(dirname(input.sessionParent), category);
68
+ } else {
69
+ const bookKey = safeBookKey(cwd);
70
+ const bookDir = activationBookDirectory(ledgerHome, bookKey);
71
+ if (input.subject !== undefined) {
72
+ let subjectStr: string;
73
+ if (typeof input.subject === "string") {
74
+ subjectStr = input.subject;
75
+ } else if (typeof input.subject.runId === "string" && input.subject.runId.length > 0) {
76
+ subjectStr = input.subject.runId;
77
+ } else {
78
+ subjectStr = JSON.stringify(input.subject);
79
+ }
80
+ const digest = createHash("sha256").update(subjectStr).digest("hex").slice(0, 32);
81
+ sessionDir = join(bookDir, category, digest);
82
+ } else {
83
+ sessionDir = join(bookDir, category);
84
+ }
85
+ }
86
+
87
+ const recordFile = join(sessionDir, "records.jsonl");
88
+ return { sessionDir, recordFile, ledgerHome };
89
+ }
90
+
91
+ /**
92
+ * Appends a canonical record to its self-computed volume under the Sitian contract.
93
+ * - Idempotency: checks volume by deterministic canonical identity; returns existing pointer on hit.
94
+ * - Torn-tail recovery: checks file tail; un-terminated trailing bytes are sealed with a newline and re-parsed.
95
+ * Substate a (valid JSON): committed on recovery, returns existing pointer.
96
+ * Substate b (malformed): preserved as bad line, check misses, appends new row.
97
+ * - Commit point: full JSON string ending with newline.
98
+ */
99
+ export function appendSitianRecord(input: SitianRecordInput): RecordPointer {
100
+ try {
101
+ const { sessionDir, recordFile, ledgerHome } = resolveSitianRecordPath(input);
102
+ ensureRealDirectoryTree(ledgerHome, sessionDir);
103
+
104
+ const identity = input.identity ?? randomUUID();
105
+ const timestamp = input.timestamp ?? new Date().toISOString();
106
+ const host = input.host ?? "pi";
107
+
108
+ const record: SitianRecord = {
109
+ level: input.level,
110
+ kind: input.kind,
111
+ identity,
112
+ ...(input.subject === undefined ? {} : { subject: input.subject }),
113
+ ...(input.sessionParent === undefined ? {} : { sessionParent: input.sessionParent }),
114
+ ...(input.priorEventId === undefined ? {} : { priorEventId: input.priorEventId }),
115
+ timestamp,
116
+ host,
117
+ ...(input.source === undefined ? {} : { source: input.source }),
118
+ ...(input.payload === undefined ? {} : { payload: input.payload }),
119
+ ...(input.raw === undefined ? {} : { raw: input.raw }),
120
+ ...(input.usage === undefined ? {} : { usage: input.usage }),
121
+ };
122
+
123
+ if (existsSync(recordFile)) {
124
+ const buffer = readFileSync(recordFile);
125
+ if (buffer.length > 0) {
126
+ // Torn-tail check: if last byte is not newline, seal the fragment with \n
127
+ if (buffer[buffer.length - 1] !== 0x0a) {
128
+ appendFileSync(recordFile, "\n", "utf8");
129
+ }
130
+
131
+ // Self-check volume by canonical identity
132
+ const text = readFileSync(recordFile, "utf8");
133
+ for (const line of text.split("\n")) {
134
+ const trimmed = line.trim();
135
+ if (!trimmed) continue;
136
+ try {
137
+ const parsed = JSON.parse(trimmed);
138
+ if (isRecord(parsed) && parsed.identity === identity) {
139
+ // Existing record found (or substate a recovered record) -> return existing pointer
140
+ return {
141
+ identity,
142
+ recordFile,
143
+ kind: record.kind,
144
+ level: record.level,
145
+ };
146
+ }
147
+ } catch {
148
+ // Malformed lines (including substate b preserved bad lines) are ignored during self-check
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ // Not found -> append new canonical row terminating with newline
155
+ const row = `${JSON.stringify(record)}\n`;
156
+ appendFileSync(recordFile, row, "utf8");
157
+
158
+ return {
159
+ identity,
160
+ recordFile,
161
+ kind: record.kind,
162
+ level: record.level,
163
+ };
164
+ } catch (error) {
165
+ if (error instanceof SitianInfrastructureError) throw error;
166
+ throw new SitianInfrastructureError(
167
+ `Sitian appender persistence failure: ${errorText(error)}`,
168
+ { cause: error },
169
+ );
170
+ }
171
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Sitian (司天台) canonical contracts, Layout schema, and pointer definitions.
3
+ * ADR 0065 single record entry + ADR 0068 Taishi analysis.
4
+ */
5
+
6
+ /** Closed set of record levels. */
7
+ export type SitianLevel = "run-summary" | "event" | "protocol-snapshot";
8
+
9
+ /** Subject coordinate identifying the work identity or run attempt. */
10
+ export type SitianSubject =
11
+ | string
12
+ | {
13
+ readonly runId: string;
14
+ readonly attemptId?: string | undefined;
15
+ readonly [key: string]: unknown;
16
+ };
17
+
18
+ /** Raw reference pointing to original retained bytes/frames without duplication (#513). */
19
+ export type SitianRawReference = {
20
+ readonly sessionFile: string;
21
+ readonly entryId: string | number;
22
+ };
23
+
24
+ /** Optional usage metrics carried on canonical records. */
25
+ export type SitianUsage = {
26
+ readonly promptTokens?: number | undefined;
27
+ readonly completionTokens?: number | undefined;
28
+ readonly totalTokens?: number | undefined;
29
+ readonly [key: string]: unknown;
30
+ };
31
+
32
+ /** Canonical Sitian record layout. */
33
+ export type SitianRecord = {
34
+ readonly level: SitianLevel;
35
+ readonly kind: string;
36
+ readonly identity: string;
37
+ readonly subject?: SitianSubject | undefined;
38
+ /** Parent session principal link / nesting origin. */
39
+ readonly sessionParent?: string | undefined;
40
+ /** Prior event identity in a submission ledger chain (S4; distinct from sessionParent). */
41
+ readonly priorEventId?: string | undefined;
42
+ readonly timestamp: string;
43
+ readonly host: string;
44
+ readonly source?: string | undefined;
45
+ readonly payload?: unknown;
46
+ readonly raw?: SitianRawReference | undefined;
47
+ readonly usage?: SitianUsage | undefined;
48
+ };
49
+
50
+ /** Typed pointer returned upon durable acceptance of a record. */
51
+ export type RecordPointer = {
52
+ readonly identity: string;
53
+ readonly recordFile: string;
54
+ readonly kind: string;
55
+ readonly level: SitianLevel;
56
+ };
57
+
58
+ /** Input passed to the Sitian facade (sitianReport). */
59
+ export type SitianRecordInput = {
60
+ readonly level: SitianLevel;
61
+ readonly kind: string;
62
+ /** Deterministic or explicit record identity. Minted if omitted. */
63
+ readonly identity?: string | undefined;
64
+ readonly subject?: SitianSubject | undefined;
65
+ readonly sessionParent?: string | undefined;
66
+ readonly priorEventId?: string | undefined;
67
+ readonly timestamp?: string | undefined;
68
+ readonly host?: string | undefined;
69
+ readonly source?: string | undefined;
70
+ readonly payload?: unknown;
71
+ readonly raw?: SitianRawReference | undefined;
72
+ readonly usage?: SitianUsage | undefined;
73
+ readonly cwd?: string | undefined;
74
+ };
75
+
76
+ /** Typed malformed diagnostic emitted by the canonical Reader upon encountering damaged lines. */
77
+ export type SitianMalformedDiagnostic = {
78
+ readonly kind: "malformed";
79
+ readonly line: number;
80
+ readonly raw: string;
81
+ readonly error: string;
82
+ };
83
+
84
+ /** Canonical Reader result surfacing both valid records and non-destructive diagnostics. */
85
+ export type SitianReadResult = {
86
+ readonly records: readonly SitianRecord[];
87
+ readonly diagnostics: readonly SitianMalformedDiagnostic[];
88
+ };
89
+
90
+ /** Lift direct-cause fs errno onto a wrap error (no chain walk). */
91
+ export function attachDirectErrnoCode(error: Error, cause: unknown): void {
92
+ if (cause === null || typeof cause !== "object" || !("code" in cause)) return;
93
+ const code = (cause as { code?: unknown }).code;
94
+ if (typeof code === "string") (error as NodeJS.ErrnoException).code = code;
95
+ }
96
+
97
+ /** Typed infrastructure error for real ledger persistence / IO failures. */
98
+ export class SitianInfrastructureError extends Error {
99
+ readonly knownCause = "session" as const;
100
+
101
+ constructor(message: string, options?: ErrorOptions) {
102
+ super(message, options);
103
+ this.name = "SitianInfrastructureError";
104
+ attachDirectErrnoCode(this, options?.cause);
105
+ }
106
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Sitian Facade (司天门面) — Sole entrypoint for record reporting across all producers.
3
+ * ADR 0065 single record entry, #520 r8.
4
+ */
5
+ import { appendSitianRecord } from "./sitian-appender.ts";
6
+ import type { RecordPointer, SitianRecordInput } from "./sitian-contracts.ts";
7
+
8
+ export * from "./sitian-contracts.ts";
9
+ export * from "./sitian-appender.ts";
10
+ export * from "./sitian-reader.ts";
11
+
12
+ /**
13
+ * Sole write API for Sitian canonical records.
14
+ * Returns typed RecordPointer with durable record identity and readable file location.
15
+ */
16
+ export function sitianReport(input: SitianRecordInput): RecordPointer {
17
+ return appendSitianRecord(input);
18
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Sitian Reader kernel (ADR 0068 / #520 r8).
3
+ * Sole authoritative read path for canonical Sitian records.
4
+ * Traversal contract:
5
+ * - Malformed lines are exposed as typed malformed diagnostics and traversal continues.
6
+ * - Zero whitewashing, zero deduplication.
7
+ * - Canonical rows after malformed lines are always reachable.
8
+ */
9
+ import { existsSync } from "node:fs";
10
+ import { readFile } from "node:fs/promises";
11
+
12
+ import type {
13
+ SitianMalformedDiagnostic,
14
+ SitianReadResult,
15
+ SitianRecord,
16
+ } from "./sitian-contracts.ts";
17
+
18
+ function isRecord(value: unknown): value is Record<string, unknown> {
19
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20
+ }
21
+
22
+ /** Read a Sitian record volume with full traversal and non-destructive diagnostics. */
23
+ export async function readSitianRecords(recordFile: string): Promise<SitianReadResult> {
24
+ if (!existsSync(recordFile)) {
25
+ return { records: [], diagnostics: [] };
26
+ }
27
+
28
+ const text = await readFile(recordFile, "utf8");
29
+ const lines = text.split("\n");
30
+
31
+ const records: SitianRecord[] = [];
32
+ const diagnostics: SitianMalformedDiagnostic[] = [];
33
+
34
+ for (let index = 0; index < lines.length; index += 1) {
35
+ const line = lines[index]!;
36
+ if (!line.trim()) continue;
37
+
38
+ try {
39
+ const parsed = JSON.parse(line);
40
+ if (isRecord(parsed)) {
41
+ records.push(parsed as unknown as SitianRecord);
42
+ } else {
43
+ const typeDesc = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
44
+ diagnostics.push({
45
+ kind: "malformed",
46
+ line: index + 1,
47
+ raw: line,
48
+ error: `expected JSON object, got ${typeDesc}`,
49
+ });
50
+ }
51
+ } catch (error) {
52
+ diagnostics.push({
53
+ kind: "malformed",
54
+ line: index + 1,
55
+ raw: line,
56
+ error: error instanceof Error ? error.message : String(error),
57
+ });
58
+ }
59
+ }
60
+
61
+ return { records, diagnostics };
62
+ }
@@ -1,15 +1,7 @@
1
- import { StringEnum } from "@earendil-works/pi-ai";
2
- import type {
3
- ExtensionAPI,
4
- ExtensionContext,
5
- AgentToolResult,
6
- } from "@earendil-works/pi-coding-agent";
1
+ import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } from "./host-contracts.ts";
2
+ import { stringEnum } from "./host-contracts.ts";
7
3
  import { Type, type Static } from "typebox";
8
4
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
9
- import {
10
- failOnInfrastructureFailureDeclaration,
11
- withInfrastructureFailureDeclaration,
12
- } from "./package-contracts/terminating-infrastructure.ts";
13
5
 
14
6
  import type {
15
7
  AnyCanonicalSkillBinding,
@@ -32,7 +24,6 @@ import {
32
24
  parseFixerPrerequisites,
33
25
  type FixerInvocationInput,
34
26
  } from "./package-contracts/fixer-packet.ts";
35
- import { requireGatekeeperPass, type GatekeeperPassHostActions } from "./gatekeeper-role.ts";
36
27
  import {
37
28
  createWorkerSubmissionGate,
38
29
  WorkerCommitReminderError,
@@ -65,18 +56,18 @@ function matchFixerBashForbiddenLiteral(
65
56
 
66
57
  const coderOutputVariants = Type.Union([
67
58
  Type.Object({
68
- status: StringEnum(["planned"] as const, { description: "planned — 形状指引,非 schema 闸" }),
59
+ status: stringEnum(["planned"] as const, { description: "planned — 形状指引,非 schema 闸" }),
69
60
  report: Type.String({ minLength: 1, description: "如实结果报告" }),
70
61
  }, { additionalProperties: false }),
71
62
  Type.Object({
72
- status: StringEnum(["completed", "refused"] as const, {
63
+ status: stringEnum(["completed", "refused"] as const, {
73
64
  description:
74
65
  "completed | refused — 形状指引,非 schema 闸;completed 回执含 TDD、同模式、引入回归、行为事实四项证据",
75
66
  }),
76
67
  report: Type.String({ minLength: 1, description: "如实结果报告" }),
77
68
  }, { additionalProperties: false }),
78
69
  Type.Object({
79
- status: StringEnum(["unfinished"] as const, {
70
+ status: stringEnum(["unfinished"] as const, {
80
71
  description:
81
72
  "unfinished — 形状指引,非 schema 闸;缺前置或违宪约束致本局未完成时可用。缺待决 owner 决定或答复属缺前置。",
82
73
  }),
@@ -89,9 +80,7 @@ const coderOutputVariants = Type.Union([
89
80
  })),
90
81
  }, { additionalProperties: false }),
91
82
  ]);
92
- export const coderOutputSchema = withInfrastructureFailureDeclaration(
93
- openToolObjectFromUnion(coderOutputVariants),
94
- );
83
+ const coderOutputSchema = openToolObjectFromUnion(coderOutputVariants);
95
84
  export type { FixerOutput, CoderOutput };
96
85
  export const FIXER_FLAG_DEFINITIONS = {
97
86
  packet: {
@@ -125,7 +114,26 @@ function isWorkerPhase(value: unknown): value is WorkerPhase {
125
114
  return typeof value === "string" && (FIXER_PHASES as readonly string[]).includes(value);
126
115
  }
127
116
 
128
- export type WorkerRoleHostActions = GatekeeperPassHostActions;
117
+ export type WorkerRoleHostActions = HostGatekeeperActions;
118
+
119
+ /** Stable code for completed apply without host skill-expansion capability evidence (#525). */
120
+ export const CODER_SKILL_EXPANSION_EVIDENCE_MISSING_CODE =
121
+ "coder_skill_expansion_evidence_missing" as const;
122
+
123
+ export type CoderSkillExpansionEvidenceMissingResult = {
124
+ readonly code: typeof CODER_SKILL_EXPANSION_EVIDENCE_MISSING_CODE;
125
+ };
126
+
127
+ /** Correct completed rejection — not infrastructure; projected via submission non-pass bridge. */
128
+ export class CoderSkillExpansionEvidenceMissingError extends Error {
129
+ readonly code = CODER_SKILL_EXPANSION_EVIDENCE_MISSING_CODE;
130
+ readonly result: CoderSkillExpansionEvidenceMissingResult;
131
+ constructor() {
132
+ super("Coder completed requires host skill-expansion capability evidence");
133
+ this.name = "CoderSkillExpansionEvidenceMissingError";
134
+ this.result = Object.freeze({ code: CODER_SKILL_EXPANSION_EVIDENCE_MISSING_CODE });
135
+ }
136
+ }
129
137
 
130
138
  export type FixerRoleDependencies = {
131
139
  loadSoul(): Promise<string>;
@@ -141,7 +149,7 @@ export type CoderRoleDependencies = {
141
149
  };
142
150
 
143
151
  export type WorkerRoleRuntime = {
144
- activate(ctx?: ExtensionContext): Promise<void>;
152
+ activate(ctx?: HostContext): Promise<void>;
145
153
  /** Arm gate ① baseline after envelope places the worktree (coder/fixer). Parent feeds archivist durability. */
146
154
  armSubmissionGate(cwd: string, parent?: { getSessionFile(): string | undefined }): void;
147
155
  };
@@ -169,14 +177,14 @@ function requireSingletonSubmissionCall(
169
177
  toolCallId: string,
170
178
  expectedToolName: string,
171
179
  roleLabel: WorkerRoleLabel,
172
- ctx: ExtensionContext,
180
+ ctx: HostContext,
173
181
  ): void {
174
182
  const leaf = ctx.sessionManager.getLeafEntry();
175
183
  const seat = roleLabel === "Fixer" ? "修内司" : "将作监";
176
- if (leaf?.type !== "message" || leaf.message.role !== "assistant") {
184
+ if (leaf?.type !== "message" || leaf.message === undefined || leaf.message.role !== "assistant" || !Array.isArray(leaf.message.content)) {
177
185
  throw new Error(`${seat}回执非唯一终局工具调用`);
178
186
  }
179
- const calls = leaf.message.content.filter((part) => part.type === "toolCall");
187
+ const calls = leaf.message.content.filter((part): part is Extract<typeof part, { type: "toolCall" }> => part.type === "toolCall");
180
188
  const call = calls[0];
181
189
  if (
182
190
  calls.length !== 1 || call === undefined || call.id !== toolCallId ||
@@ -192,7 +200,7 @@ function assertAcceptableThroughHost(
192
200
  status: string,
193
201
  details: unknown,
194
202
  hostActions: WorkerRoleHostActions,
195
- ctx: ExtensionContext,
203
+ ctx: HostContext,
196
204
  toolCallId: string,
197
205
  ): void {
198
206
  try {
@@ -210,7 +218,7 @@ function assertAcceptableThroughHost(
210
218
  }
211
219
 
212
220
  export function createFixerRoleRuntime(
213
- pi: ExtensionAPI,
221
+ pi: RoleHost,
214
222
  dependencies: FixerRoleDependencies,
215
223
  hostActions: WorkerRoleHostActions,
216
224
  ): WorkerRoleRuntime {
@@ -268,16 +276,13 @@ export function createFixerRoleRuntime(
268
276
  pi.registerTool({
269
277
  name: FIXER_OUTPUT_TOOL_NAME,
270
278
  label: "修内司输出",
271
- description: "提交修内司终局回执",
279
+ description: "提交修内司终局回执;基础设施失败走 abort,不经本工具。",
272
280
  promptSnippet: "提交修内司终局回执",
273
281
  parameters: fixerOutputSchema,
274
- async execute(toolCallId, parameters, _signal, _onUpdate, ctx): Promise<AgentToolResult<unknown>> {
282
+ async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> {
275
283
  if (packet === undefined || phase === undefined) {
276
284
  throw new Error("修内司修理包与阶段未装载");
277
285
  }
278
- // #541: infra declaration fails via the shared host seam before any
279
- // submission gate + Gatekeeper work.
280
- failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId);
281
286
  requireSingletonSubmissionCall(
282
287
  toolCallId,
283
288
  FIXER_OUTPUT_TOOL_NAME,
@@ -293,7 +298,7 @@ export function createFixerRoleRuntime(
293
298
  ctx,
294
299
  toolCallId,
295
300
  );
296
- await requireGatekeeperPass({
301
+ await pi.requireGatekeeperPass!({
297
302
  context: ctx,
298
303
  subject: { kind: "worker_completion", material: JSON.stringify(output) },
299
304
  ...(_signal === undefined ? {} : { signal: _signal }),
@@ -336,7 +341,7 @@ export function createFixerRoleRuntime(
336
341
  }
337
342
 
338
343
  export function createCoderRoleRuntime(
339
- pi: ExtensionAPI,
344
+ pi: RoleHost,
340
345
  dependencies: CoderRoleDependencies,
341
346
  hostActions: WorkerRoleHostActions,
342
347
  ): WorkerRoleRuntime {
@@ -405,13 +410,10 @@ export function createCoderRoleRuntime(
405
410
  description: "提交将作监终局回执;本工具无 escalate 通道。",
406
411
  promptSnippet: "提交将作监终局回执",
407
412
  parameters: coderOutputSchema,
408
- async execute(toolCallId, parameters, _signal, _onUpdate, ctx) {
413
+ async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
409
414
  if (task === undefined || phase === undefined) {
410
415
  throw new Error("将作监任务与阶段未装载");
411
416
  }
412
- // #541: infra declaration fails via the shared host seam before any
413
- // submission gate + Gatekeeper work.
414
- failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId);
415
417
  requireSingletonSubmissionCall(
416
418
  toolCallId,
417
419
  CODER_OUTPUT_TOOL_NAME,
@@ -423,9 +425,9 @@ export function createCoderRoleRuntime(
423
425
  phase === "apply" && output.status === "completed" &&
424
426
  !expansionCaptured
425
427
  ) {
426
- throw new Error(
427
- "Coder completed requires the Matt tdd skill to be expanded through Pi /skill:tdd",
428
- );
428
+ const rejection = new CoderSkillExpansionEvidenceMissingError();
429
+ hostActions.bindSubmissionNonPass(toolCallId, rejection.result);
430
+ throw rejection;
429
431
  }
430
432
  assertAcceptableThroughHost(
431
433
  submissionGate,
@@ -435,7 +437,7 @@ export function createCoderRoleRuntime(
435
437
  ctx,
436
438
  toolCallId,
437
439
  );
438
- await requireGatekeeperPass({
440
+ await pi.requireGatekeeperPass!({
439
441
  context: ctx,
440
442
  subject: { kind: "worker_completion", material: JSON.stringify(output) },
441
443
  ...(_signal === undefined ? {} : { signal: _signal }),
@@ -483,7 +485,7 @@ export function createCoderRoleRuntime(
483
485
  expansionPending = false;
484
486
  if (originalRequest !== undefined) {
485
487
  expansionCaptured = binding.captureExpansion(
486
- event.prompt,
488
+ pi.capabilities?.skillExpansion(event.prompt),
487
489
  originalRequest,
488
490
  ) !== undefined;
489
491
  }
@@ -9,6 +9,7 @@ import {
9
9
  type RecordSessionParent,
10
10
  WORKER_SUBMISSION_GATE_KIND,
11
11
  } from "./archivist-record-entry.ts";
12
+ import { sitianReport } from "./sitian-facade.ts";
12
13
 
13
14
  export const WORKER_SUBMISSION_GATE_RECORD_KIND = WORKER_SUBMISSION_GATE_KIND;
14
15
  export const WORKER_COMMIT_BASELINE_ENTRY_TYPE = "commit-baseline";
@@ -275,6 +276,18 @@ export function createWorkerSubmissionGate(): {
275
276
  version: 1,
276
277
  head: baseline,
277
278
  });
279
+ sitianReport({
280
+ level: "event",
281
+ kind: "gate",
282
+ cwd,
283
+ ...(parent?.getSessionFile() ? { sessionParent: parent.getSessionFile() } : {}),
284
+ payload: {
285
+ type: WORKER_COMMIT_BASELINE_ENTRY_TYPE,
286
+ version: 1,
287
+ head: baseline,
288
+ },
289
+ source: "worker-submission-gates",
290
+ });
278
291
  },
279
292
  assertAcceptable(status, details) {
280
293
  if (status === "unfinished" && !unfinishedReasonPresent(details)) {
@@ -291,6 +304,16 @@ export function createWorkerSubmissionGate(): {
291
304
  if (!headMoved && !reminded) {
292
305
  reminded = true;
293
306
  record?.appendCustomEntry(WORKER_COMMIT_REMINDER_BOUNCE_ENTRY_TYPE, { version: 1 });
307
+ sitianReport({
308
+ level: "event",
309
+ kind: "gate",
310
+ cwd: root,
311
+ payload: {
312
+ type: WORKER_COMMIT_REMINDER_BOUNCE_ENTRY_TYPE,
313
+ version: 1,
314
+ },
315
+ source: "worker-submission-gates",
316
+ });
294
317
  throw new WorkerCommitReminderError();
295
318
  }
296
319
  reminded = true;
@@ -307,6 +330,16 @@ export function createWorkerSubmissionGate(): {
307
330
  }
308
331
  prefixReminded = true;
309
332
  record?.appendCustomEntry(WORKER_PREFIX_REMINDER_BOUNCE_ENTRY_TYPE, { version: 1 });
333
+ sitianReport({
334
+ level: "event",
335
+ kind: "gate",
336
+ cwd: root,
337
+ payload: {
338
+ type: WORKER_PREFIX_REMINDER_BOUNCE_ENTRY_TYPE,
339
+ version: 1,
340
+ },
341
+ source: "worker-submission-gates",
342
+ });
310
343
  throw new WorkerPrefixReminderError();
311
344
  },
312
345
  };
@@ -1,28 +0,0 @@
1
- import { join } from "node:path";
2
- import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
3
- import {
4
- activationBookDirectory,
5
- resolveActivationLedgerHome
6
- } from "./activation-ledger-topology.js";
7
- function roleRunSessionCoordinates(options) {
8
- const ledgerHome = resolveActivationLedgerHome(
9
- options.home === void 0 ? void 0 : () => options.home
10
- );
11
- const bookKey = resolveBookKeyFromGit(options.cwd);
12
- const runDirectory = join(
13
- activationBookDirectory(ledgerHome, bookKey),
14
- "runs",
15
- `${options.runId}@${options.role}`
16
- );
17
- const sessionDirectory = join(runDirectory, "session");
18
- return {
19
- ledgerHome,
20
- bookKey,
21
- runDirectory,
22
- sessionDirectory,
23
- sessionFile: join(sessionDirectory, "session.jsonl")
24
- };
25
- }
26
- export {
27
- roleRunSessionCoordinates
28
- };