@akagilnc/pi-workflow-roles 0.1.3572 → 0.1.3621
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.
- package/dist/acp-host/production-host.js +20239 -10556
- package/dist/analyst-gate-cycles-read.js +387 -0
- package/dist/archivist-record-entry.js +22 -1
- package/dist/audit-escalation.js +6 -0
- package/dist/auditor-soul.js +74 -0
- package/dist/collector-config.js +86 -0
- package/dist/collector-evidence.js +316 -0
- package/dist/collector-github.js +527 -0
- package/dist/collector-ledger.js +853 -0
- package/dist/collector-tool-schemas.js +33 -0
- package/dist/compliance-transport.js +104 -52
- package/dist/diarist-mechanical.js +411 -0
- package/dist/diarist-ticket-resolution.js +177 -0
- package/dist/diarist.js +311 -0
- package/dist/doctor-auditor.js +25 -0
- package/dist/doctor-contracts.js +2 -0
- package/dist/doctor-evidence.js +142 -0
- package/dist/gatekeeper-role.js +104 -81
- package/dist/host-transition-prior-native.js +78 -0
- package/dist/institutional-resolution.js +1 -95
- package/dist/judge-auditor.js +26 -0
- package/dist/ledger-session-read.js +227 -0
- package/dist/merger-git-state.js +78 -0
- package/dist/navigator-attendance.js +15 -7
- package/dist/navigator-public-session.js +170 -0
- package/dist/navigator-session-contracts.js +36 -40
- package/dist/notary-source-run.js +122 -0
- package/dist/package-contracts/auditor-output.js +66 -0
- package/dist/package-contracts/evidence-child-output.js +34 -0
- package/dist/package-contracts/judge-output.js +2 -0
- package/dist/package-contracts/terminating-tools.js +25 -2
- package/dist/package-resources/method-skill.js +254 -0
- package/dist/packaged-role-registry.js +36 -0
- package/dist/pi/durable-principal.js +61 -0
- package/dist/pi/in-process-session.js +25 -6
- package/dist/pi/known-failure.js +52 -0
- package/dist/pi/role-turn-host.js +430 -0
- package/dist/public-cli/auto-resume.js +414 -0
- package/dist/public-cli/cli-errors.js +8 -0
- package/dist/public-cli/cli-io.js +1 -0
- package/dist/public-cli/command-renderer.js +5 -0
- package/dist/public-cli/doctor-run.js +84 -0
- package/dist/public-cli/inspector-run.js +136 -0
- package/dist/public-cli/instruction-seat-run.js +256 -0
- package/dist/public-cli/invocation.js +2225 -0
- package/dist/public-cli/judge-run.js +118 -0
- package/dist/public-cli/load-production-acp-host.js +40 -0
- package/dist/public-cli/main.js +1140 -652
- package/dist/public-cli/notary-run.js +161 -0
- package/dist/public-cli/option-definitions.js +1192 -0
- package/dist/public-cli/post-admission.js +667 -0
- package/dist/public-cli/public-run-credentials.js +49 -0
- package/dist/public-cli/registry.js +4 -1
- package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
- package/dist/public-cli/run-lifecycle.js +1344 -0
- package/dist/public-cli/seat-ticket-binding.js +113 -0
- package/dist/public-cli/settlement.js +3441 -0
- package/dist/public-cli/terminal.js +135 -0
- package/dist/public-cli/turn-request.js +25 -0
- package/dist/public-role-summons.js +301 -0
- package/dist/receipt-delivery-policy.js +10 -0
- package/dist/reviewer-child-executor.js +80 -8
- package/dist/reviewer-execution-ledger.js +2 -1
- package/dist/run-terminal-artifacts.js +195 -0
- package/dist/run-ticket-number.js +40 -0
- package/dist/session-assistant-usage.js +107 -0
- package/dist/shape-unreadable-failure.js +34 -0
- package/dist/submission-errors.js +3 -0
- package/dist/submission-ledger.js +388 -0
- package/dist/ticket-provenance-contracts.js +115 -0
- package/dist/ticket-provenance.js +340 -0
- package/extensions/role-runtime.ts +7 -0
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -1
- package/souls/doctor-auditor.md +1 -0
- package/souls/evidence-child.md +1 -0
- package/src/acp-host/production-host.ts +3 -0
- package/src/acp-host/role-envelope.ts +2 -2
- package/src/acp-host/role-turn-host.ts +9 -1
- package/src/analyst-gate-cycles-read.ts +37 -2
- package/src/archivist-record-entry.ts +45 -1
- package/src/audit-escalation.ts +17 -0
- package/src/auditor-role.ts +22 -0
- package/src/auditor-soul.ts +42 -0
- package/src/compliance-transport.ts +177 -66
- package/src/doctor-auditor.ts +10 -14
- package/src/doctor-contracts.ts +1 -0
- package/src/doctor-role.ts +2 -2
- package/src/evidence-child-role.ts +22 -0
- package/src/gatekeeper-pass-envelope.ts +109 -0
- package/src/gatekeeper-role.ts +145 -104
- package/src/host-contracts.ts +9 -1
- package/src/institutional-resolution.ts +8 -163
- package/src/judge-auditor.ts +10 -15
- package/src/judge-role.ts +8 -0
- package/src/navigator-attendance.ts +25 -9
- package/src/navigator-public-session.ts +217 -0
- package/src/navigator-session-contracts.ts +61 -54
- package/src/notary-source-run.ts +3 -1
- package/src/package-contracts/auditor-output.ts +82 -0
- package/src/package-contracts/evidence-child-output.ts +51 -0
- package/src/package-contracts/judge-output.ts +1 -0
- package/src/package-contracts/reviewer-output.ts +2 -2
- package/src/package-contracts/terminating-tools.ts +31 -1
- package/src/packaged-role-registry.ts +43 -0
- package/src/pi/adapter.ts +2 -1
- package/src/pi/in-process-session.ts +38 -12
- package/src/pi/role-turn-host.ts +34 -0
- package/src/public-cli/cli.ts +14 -6
- package/src/public-cli/instruction-seat-run.ts +289 -50
- package/src/public-cli/invocation.ts +66 -25
- package/src/public-cli/option-definitions.ts +61 -0
- package/src/public-cli/post-admission.ts +7 -1
- package/src/public-cli/registry.ts +4 -1
- package/src/public-cli/run-lifecycle.ts +22 -3
- package/src/public-cli/settlement.ts +120 -5
- package/src/public-cli/terminal.ts +2 -0
- package/src/public-role-summons.ts +431 -0
- package/src/receipt-delivery-policy.ts +10 -0
- package/src/reviewer-agent.ts +1 -1
- package/src/reviewer-child-executor.ts +114 -12
- package/src/reviewer-execution-ledger.ts +3 -2
- package/src/role-runtime.ts +158 -17
- package/src/session-assistant-usage.ts +119 -0
- package/src/session-opening-materials.ts +1 -1
- package/src/shape-unreadable-failure.ts +49 -0
- package/src/submission-errors.ts +3 -0
- package/dist/evidence-child-executor.js +0 -829
- package/src/evidence-child-executor.ts +0 -1140
package/dist/gatekeeper-role.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { auditorRunDirectory, createAuditorDossierTool, persistGateSubmissionCandidate, } from "./auditor-dossier-tool.js";
|
|
3
|
-
import { executeAuditorChild } from "./evidence-child-executor.js";
|
|
4
|
-
import { openToolObject } from "./open-tool-schema.js";
|
|
5
|
-
import { loadGatekeeperSessionMaterials } from "./session-opening-materials.js";
|
|
1
|
+
import { auditorRunDirectory, persistGateSubmissionCandidate, } from "./auditor-dossier-tool.js";
|
|
6
2
|
import { GatekeeperDecisionError } from "./submission-errors.js";
|
|
7
3
|
import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.js";
|
|
8
4
|
import { GATEKEEPER_OUTPUT_TOOL_NAME, gatekeeperDecisionSchema, gatekeeperOutputSchema, } from "./package-contracts/gatekeeper-output.js";
|
|
5
|
+
import { retainedShapeUnreadable } from "./shape-unreadable-failure.js";
|
|
9
6
|
export const INSPECTOR_OUTPUT_TOOL = INSPECTOR_OUTPUT_TOOL_NAME;
|
|
10
7
|
export const NOTARY_OUTPUT_TOOL = "ak_notary_output";
|
|
11
8
|
function gateSeatLabel(stage) {
|
|
@@ -20,13 +17,6 @@ export class GatekeeperEscalationError extends Error {
|
|
|
20
17
|
this.gatekeeper = gatekeeper;
|
|
21
18
|
}
|
|
22
19
|
}
|
|
23
|
-
// Unknown fields so wrong types/spellings still reach projection (ADR 0055/0057; 仓第 0 条).
|
|
24
|
-
// Opening goes through the sole openToolObject owner — no parallel transport helper.
|
|
25
|
-
const officerDecisionSchema = openToolObject(Type.Object({
|
|
26
|
-
status: Type.Unknown({ description: "pass | bounce | escalate — 形状指引,非 schema 闸" }),
|
|
27
|
-
reason: Type.Optional(Type.Unknown({ description: "status 为 escalate 时的理由" })),
|
|
28
|
-
findings: Type.Unknown({ description: "string[] findings,随 pass、bounce 或 escalate 留存" }),
|
|
29
|
-
}));
|
|
30
20
|
/**
|
|
31
21
|
* Direct-seat decision tool spec (#639). Lifecycle assembly stays on the
|
|
32
22
|
* registration envelope — src/role-runtime.ts (ADR 0018). Schema authority is
|
|
@@ -42,27 +32,17 @@ export const GATEKEEPER_TOOL_SPEC = {
|
|
|
42
32
|
function result(content, details) {
|
|
43
33
|
return { content: [{ type: "text", text: content }], details };
|
|
44
34
|
}
|
|
45
|
-
/** Shared officer decision tool — open transport; projection owns legality. */
|
|
46
|
-
export function createOfficerDecisionTool(name) {
|
|
47
|
-
return {
|
|
48
|
-
name,
|
|
49
|
-
description: "提交一份 typed pass/bounce/escalate 决议。",
|
|
50
|
-
parameters: officerDecisionSchema,
|
|
51
|
-
async execute(_id, args) { return result(`已收 ${String(args?.status)}`, args); },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
35
|
/** Gatekeeper province decision tool — open transport; package-contract projection owns legality. */
|
|
55
36
|
export function createGatekeeperOutputTool() {
|
|
56
37
|
return {
|
|
57
38
|
name: GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
58
39
|
description: "提交门下省派官决定。",
|
|
59
40
|
parameters: gatekeeperDecisionSchema,
|
|
60
|
-
async execute(_id, args) {
|
|
41
|
+
async execute(_id, args) {
|
|
42
|
+
return result(`已收 ${String(args?.status)}`, args);
|
|
43
|
+
},
|
|
61
44
|
};
|
|
62
45
|
}
|
|
63
|
-
async function defaultLoadSoul(role) {
|
|
64
|
-
return loadGatekeeperSessionMaterials(role);
|
|
65
|
-
}
|
|
66
46
|
function failureReason(error) {
|
|
67
47
|
if (error instanceof AggregateError)
|
|
68
48
|
return error.errors.map(failureReason).join("; ");
|
|
@@ -88,14 +68,15 @@ function retainedSubmission(decision) {
|
|
|
88
68
|
: decision;
|
|
89
69
|
}
|
|
90
70
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
71
|
+
* Shape-unreadable officer decision — retain original candidate + typed reason.
|
|
72
|
+
* Not a forged bounce, not transport abort (CLAUDE.md §0 / ADR 0055).
|
|
73
|
+
* Real provider/engine/disk failures stay transport_failure elsewhere.
|
|
93
74
|
*/
|
|
94
|
-
function
|
|
75
|
+
function shapeUnreadable(officer, decision, reason = "decision 无显式 pass/bounce/escalate") {
|
|
95
76
|
return {
|
|
96
|
-
status: "
|
|
97
|
-
|
|
98
|
-
reason
|
|
77
|
+
status: "unreadable",
|
|
78
|
+
officer,
|
|
79
|
+
reason,
|
|
99
80
|
submission: retainedSubmission(decision),
|
|
100
81
|
};
|
|
101
82
|
}
|
|
@@ -107,7 +88,7 @@ function readRecord(value) {
|
|
|
107
88
|
function projectOfficerDecision(officer, decision) {
|
|
108
89
|
const record = readRecord(decision);
|
|
109
90
|
if (record === undefined)
|
|
110
|
-
return
|
|
91
|
+
return shapeUnreadable(officer, decision);
|
|
111
92
|
if (record.status === "bounce") {
|
|
112
93
|
return {
|
|
113
94
|
status: "bounce",
|
|
@@ -133,63 +114,105 @@ function projectOfficerDecision(officer, decision) {
|
|
|
133
114
|
submission: retainedSubmission(decision),
|
|
134
115
|
};
|
|
135
116
|
}
|
|
136
|
-
return
|
|
117
|
+
return shapeUnreadable(officer, decision);
|
|
137
118
|
}
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
const
|
|
119
|
+
/** Map a public-role terminal onto the gate officer result surface. */
|
|
120
|
+
function projectOfficerTerminal(officer, summoned) {
|
|
121
|
+
const terminal = summoned.terminal;
|
|
122
|
+
const outcome = terminal?.roleOutcome;
|
|
123
|
+
if (outcome === undefined) {
|
|
124
|
+
const detail = summoned.stderr?.trim();
|
|
125
|
+
return {
|
|
126
|
+
status: "transport_failure",
|
|
127
|
+
stage: officer,
|
|
128
|
+
reason: detail && detail.length > 0
|
|
129
|
+
? `${gateSeatLabel(officer)} public summon exit ${summoned.exitCode}: ${detail}`
|
|
130
|
+
: `${gateSeatLabel(officer)} public summon produced no terminal (exit ${summoned.exitCode})`,
|
|
131
|
+
submission: summoned,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (outcome.kind === "no_receipt") {
|
|
135
|
+
return {
|
|
136
|
+
status: "no_receipt",
|
|
137
|
+
stage: officer,
|
|
138
|
+
reason: `${gateSeatLabel(officer)}未产生已接受回执即散局`,
|
|
139
|
+
facts: outcome,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (outcome.kind === "failure") {
|
|
143
|
+
// Single settlement marker only (ADR 0055 / #675) — no cause=output re-derivation.
|
|
144
|
+
const shape = retainedShapeUnreadable(outcome.decisiveFacts);
|
|
145
|
+
if (shape !== undefined) {
|
|
146
|
+
return shapeUnreadable(officer, shape.candidate, outcome.diagnostic);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
status: "transport_failure",
|
|
150
|
+
stage: officer,
|
|
151
|
+
reason: outcome.diagnostic,
|
|
152
|
+
submission: outcome.decisiveFacts,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (outcome.kind === "accepted") {
|
|
156
|
+
return projectOfficerDecision(officer, {
|
|
157
|
+
status: outcome.status,
|
|
158
|
+
...outcome.decisiveFacts,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
status: "transport_failure",
|
|
163
|
+
stage: officer,
|
|
164
|
+
reason: `${gateSeatLabel(officer)} public summon returned unusable terminal kind`,
|
|
165
|
+
submission: outcome,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Summon + project only. Lifecycle book and host abort face live on the shared
|
|
170
|
+
* submit envelope (`gatekeeper-pass-envelope.ts`, ADR 0018 / #675).
|
|
171
|
+
*/
|
|
172
|
+
export async function projectGatekeeperRun(options) {
|
|
141
173
|
const officer = options.subject.kind === "worker_completion" ? "inspector" : "notary";
|
|
142
|
-
// Resolve once so dossier tool and child session share the same run binding (#632).
|
|
143
174
|
const runDirectory = options.runDirectory ?? auditorRunDirectory(options.context);
|
|
175
|
+
if (runDirectory === undefined) {
|
|
176
|
+
return {
|
|
177
|
+
officer,
|
|
178
|
+
result: {
|
|
179
|
+
status: "transport_failure",
|
|
180
|
+
stage: officer,
|
|
181
|
+
reason: `${gateSeatLabel(officer)} requires a parent run directory pointer`,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
144
185
|
// Pointer-only summons need a resolvable leaf: Grok session.jsonl is header-only
|
|
145
186
|
// (#617 DK-4); write the in-memory tool-call candidate as a run artifact first (#632).
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
: persistGateSubmissionCandidate(runDirectory, options.context);
|
|
187
|
+
persistGateSubmissionCandidate(runDirectory, options.context);
|
|
188
|
+
let summoned;
|
|
149
189
|
try {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
});
|
|
162
|
-
if (officerRun.noReceiptLifecycle !== undefined) {
|
|
163
|
-
return { status: "no_receipt", stage: officer, reason: `${gateSeatLabel(officer)}未产生已接受回执即散局`, facts: officerRun.noReceiptLifecycle };
|
|
164
|
-
}
|
|
165
|
-
return projectOfficerDecision(officer, officerRun.decision);
|
|
190
|
+
const summon = options.summonOfficer
|
|
191
|
+
?? (async (nextOfficer, sourceRunDirectory, officerSignal) => {
|
|
192
|
+
const { summonGateOfficer } = await import("./public-role-summons.js");
|
|
193
|
+
return summonGateOfficer({
|
|
194
|
+
officer: nextOfficer,
|
|
195
|
+
sourceRunDirectory,
|
|
196
|
+
cwd: options.context.cwd ?? process.cwd(),
|
|
197
|
+
...(officerSignal === undefined ? {} : { signal: officerSignal }),
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
summoned = await summon(officer, runDirectory, options.signal);
|
|
166
201
|
}
|
|
167
202
|
catch (error) {
|
|
168
|
-
return {
|
|
203
|
+
return {
|
|
204
|
+
officer,
|
|
205
|
+
result: { status: "transport_failure", stage: officer, reason: failureReason(error) },
|
|
206
|
+
};
|
|
169
207
|
}
|
|
208
|
+
return {
|
|
209
|
+
officer,
|
|
210
|
+
result: projectOfficerTerminal(officer, summoned),
|
|
211
|
+
summoned,
|
|
212
|
+
};
|
|
170
213
|
}
|
|
171
|
-
/**
|
|
172
|
-
export async function
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
subject: options.subject,
|
|
176
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
177
|
-
});
|
|
178
|
-
if (gatekeeper.status === "pass")
|
|
179
|
-
return;
|
|
180
|
-
if (gatekeeper.status === "transport_failure") {
|
|
181
|
-
// Typed stage/reason/submission ride failInfrastructure → durable tool_result (#475).
|
|
182
|
-
const error = new Error(`交卷闸 transport_failure(${gatekeeper.stage}):${gatekeeper.reason}`);
|
|
183
|
-
error.stage = gatekeeper.stage;
|
|
184
|
-
error.reason = gatekeeper.reason;
|
|
185
|
-
if (gatekeeper.submission !== undefined)
|
|
186
|
-
error.submission = gatekeeper.submission;
|
|
187
|
-
options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
|
|
188
|
-
}
|
|
189
|
-
if (gatekeeper.status === "escalate") {
|
|
190
|
-
throw new GatekeeperEscalationError(gatekeeper);
|
|
191
|
-
}
|
|
192
|
-
// Envelope owns the execute→tool_result bridge; this module only projects + throws.
|
|
193
|
-
options.hostActions.bindSubmissionNonPass(options.toolCallId, gatekeeper);
|
|
194
|
-
throw new GatekeeperDecisionError(gatekeeper);
|
|
214
|
+
/** Submission-gate summons: subject kind → officer; activation is public role path (#675). Projection only. */
|
|
215
|
+
export async function runGatekeeper(options) {
|
|
216
|
+
const { result } = await projectGatekeeperRun(options);
|
|
217
|
+
return result;
|
|
195
218
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single authority for #617 DK-4 cross-host prior-native projection.
|
|
3
|
+
* Classifies the prior volume into the two record families that exist
|
|
4
|
+
* (Pi native session file / sitian run records), never by host name.
|
|
5
|
+
*/
|
|
6
|
+
import { access, readdir } from "node:fs/promises";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
function isEnoent(error) {
|
|
9
|
+
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
10
|
+
}
|
|
11
|
+
/** Present Pi native session path, or empty when ENOENT. */
|
|
12
|
+
async function listPiNativeRecordPaths(sessionFile) {
|
|
13
|
+
try {
|
|
14
|
+
await access(sessionFile);
|
|
15
|
+
return [sessionFile];
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (isEnoent(error))
|
|
19
|
+
return [];
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Present sitian records.jsonl paths under sessionParent topology (#717).
|
|
25
|
+
* resolveSitianRecordPathInLedger writes dirname(sessionParent)/<category>/records.jsonl
|
|
26
|
+
* when sessionParent is inside ledger home — never session.jsonl itself.
|
|
27
|
+
*/
|
|
28
|
+
async function listSitianRecordPaths(sessionParent) {
|
|
29
|
+
const sessionRoot = dirname(sessionParent);
|
|
30
|
+
let entries;
|
|
31
|
+
try {
|
|
32
|
+
entries = await readdir(sessionRoot, { withFileTypes: true });
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (isEnoent(error))
|
|
36
|
+
return [];
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
const recordPaths = [];
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (!entry.isDirectory())
|
|
42
|
+
continue;
|
|
43
|
+
const recordFile = join(sessionRoot, entry.name, "records.jsonl");
|
|
44
|
+
try {
|
|
45
|
+
await access(recordFile);
|
|
46
|
+
recordPaths.push(recordFile);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (!isEnoent(error))
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
recordPaths.sort();
|
|
54
|
+
return recordPaths;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Project one hostTransition only for a real host switch. Empty native volume
|
|
58
|
+
* still yields a typed switch (empty path list).
|
|
59
|
+
*
|
|
60
|
+
* Pi wrote its own session.jsonl; every other host's run volume is the sitian
|
|
61
|
+
* record set on the live run (ADR 0077 `record-scope-phase-two`, #717) — the CLI's
|
|
62
|
+
* own journals stay in the operator home and are never copied here.
|
|
63
|
+
*/
|
|
64
|
+
export async function projectHostTransitionPriorNative(input) {
|
|
65
|
+
if (input.previousHost === input.liveHost)
|
|
66
|
+
return undefined;
|
|
67
|
+
if (input.previousHost === "pi") {
|
|
68
|
+
return {
|
|
69
|
+
priorNativeKind: "pi-native",
|
|
70
|
+
priorNativePaths: await listPiNativeRecordPaths(input.piSessionFile),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Sitian path handoff only — do not read bytes.
|
|
74
|
+
return {
|
|
75
|
+
priorNativeKind: "sitian",
|
|
76
|
+
priorNativePaths: await listSitianRecordPaths(input.piSessionFile),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import { seatModelOnly } from "./public-cli/registry.js";
|
|
4
|
-
const INSTITUTIONAL_RESOLUTION_FILE = "institutional-resolution.json";
|
|
5
|
-
class InstitutionalResolutionError extends Error {
|
|
6
|
-
reason;
|
|
7
|
-
constructor(message, reason, options) {
|
|
8
|
-
super(message, options);
|
|
9
|
-
this.name = "InstitutionalResolutionError";
|
|
10
|
-
this.reason = reason;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
function cleanSelection(model) {
|
|
14
|
-
if (model === void 0) return void 0;
|
|
15
|
-
if (typeof model.provider !== "string" || typeof model.model !== "string") return void 0;
|
|
16
|
-
return {
|
|
17
|
-
provider: model.provider,
|
|
18
|
-
model: model.model,
|
|
19
|
-
...model.thinking === void 0 ? {} : { thinking: model.thinking }
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
2
|
function resolveConfiguredProvinceOfficer(config, officer) {
|
|
23
3
|
const ownModel = seatModelOnly(config.seats[officer]);
|
|
24
4
|
if (ownModel !== void 0) {
|
|
@@ -33,80 +13,6 @@ function resolveConfiguredProvinceOfficer(config, officer) {
|
|
|
33
13
|
}
|
|
34
14
|
return { source: "unconfigured" };
|
|
35
15
|
}
|
|
36
|
-
function resolveInstitutionalSeatSelections(config, parentEffectiveModel) {
|
|
37
|
-
const parentSelection = cleanSelection(parentEffectiveModel);
|
|
38
|
-
const gatekeeper = resolveConfiguredProvinceOfficer(config, "gatekeeper").selection ?? parentSelection;
|
|
39
|
-
const inspector = resolveConfiguredProvinceOfficer(config, "inspector").selection ?? parentSelection;
|
|
40
|
-
const notary = resolveConfiguredProvinceOfficer(config, "notary").selection ?? parentSelection;
|
|
41
|
-
const auditor = parentSelection;
|
|
42
|
-
const evidenceChild = parentSelection;
|
|
43
|
-
const navigator = cleanSelection(seatModelOnly(config.seats?.navigator));
|
|
44
|
-
return {
|
|
45
|
-
version: 1,
|
|
46
|
-
seats: {
|
|
47
|
-
...gatekeeper === void 0 ? {} : { gatekeeper },
|
|
48
|
-
...inspector === void 0 ? {} : { inspector },
|
|
49
|
-
...notary === void 0 ? {} : { notary },
|
|
50
|
-
...auditor === void 0 ? {} : { auditor },
|
|
51
|
-
...evidenceChild === void 0 ? {} : { evidenceChild },
|
|
52
|
-
...navigator === void 0 ? {} : { navigator }
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
async function writeInstitutionalResolutionPage(runDirectory, page) {
|
|
57
|
-
const filePath = join(runDirectory, INSTITUTIONAL_RESOLUTION_FILE);
|
|
58
|
-
await writeFile(filePath, `${JSON.stringify(page, null, 2)}
|
|
59
|
-
`, "utf8");
|
|
60
|
-
}
|
|
61
|
-
async function readInstitutionalSeatSelection(runDirectory, seat) {
|
|
62
|
-
const filePath = join(runDirectory, INSTITUTIONAL_RESOLUTION_FILE);
|
|
63
|
-
let raw;
|
|
64
|
-
try {
|
|
65
|
-
raw = await readFile(filePath, "utf8");
|
|
66
|
-
} catch (error) {
|
|
67
|
-
throw new InstitutionalResolutionError(
|
|
68
|
-
`institutional resolution page is missing at ${filePath}`,
|
|
69
|
-
"missing-page",
|
|
70
|
-
{ cause: error }
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
let page;
|
|
74
|
-
try {
|
|
75
|
-
page = JSON.parse(raw);
|
|
76
|
-
} catch (error) {
|
|
77
|
-
throw new InstitutionalResolutionError(
|
|
78
|
-
`institutional resolution page is corrupted at ${filePath}`,
|
|
79
|
-
"corrupted",
|
|
80
|
-
{ cause: error }
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
if (typeof page !== "object" || page === null || page.version !== 1) {
|
|
84
|
-
throw new InstitutionalResolutionError(
|
|
85
|
-
`institutional resolution page format is invalid at ${filePath}`,
|
|
86
|
-
"invalid-format"
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
const seats = page.seats;
|
|
90
|
-
if (typeof seats !== "object" || seats === null) {
|
|
91
|
-
throw new InstitutionalResolutionError(
|
|
92
|
-
`institutional resolution page missing seats object at ${filePath}`,
|
|
93
|
-
"invalid-format"
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
const selection = cleanSelection(seats[seat]);
|
|
97
|
-
if (selection === void 0) {
|
|
98
|
-
throw new InstitutionalResolutionError(
|
|
99
|
-
`institutional resolution page has no resolution for seat "${seat}" at ${filePath}`,
|
|
100
|
-
"missing-seat"
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
return selection;
|
|
104
|
-
}
|
|
105
16
|
export {
|
|
106
|
-
|
|
107
|
-
InstitutionalResolutionError,
|
|
108
|
-
readInstitutionalSeatSelection,
|
|
109
|
-
resolveConfiguredProvinceOfficer,
|
|
110
|
-
resolveInstitutionalSeatSelections,
|
|
111
|
-
writeInstitutionalResolutionPage
|
|
17
|
+
resolveConfiguredProvinceOfficer
|
|
112
18
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { runComplianceAudit, } from "./compliance-transport.js";
|
|
2
|
+
import { auditorRunDirectory } from "./auditor-dossier-tool.js";
|
|
3
|
+
import { readJudgeAuditSubjects, requireAuditMaterials, resolveAuditDossier, } from "./dossier-resolution.js";
|
|
4
|
+
export const JUDGE_AUDIT_TOOL_NAME = "ak_soul_audit_decision";
|
|
5
|
+
export const SOUL_AUDIT_TOOL_NAME = JUDGE_AUDIT_TOOL_NAME;
|
|
6
|
+
/**
|
|
7
|
+
* Judge compliance via public auditor activation (#675 / ADR 0062 / owner r11).
|
|
8
|
+
* 审刑院 is an independent role; subject=judge selects souls/judge-auditor.md.
|
|
9
|
+
*/
|
|
10
|
+
export function createPiJudgeAuditor() {
|
|
11
|
+
return async (options) => {
|
|
12
|
+
const dossier = resolveAuditDossier();
|
|
13
|
+
requireAuditMaterials(dossier);
|
|
14
|
+
const subjects = readJudgeAuditSubjects(options.context);
|
|
15
|
+
requireAuditMaterials(subjects);
|
|
16
|
+
return runComplianceAudit({
|
|
17
|
+
subject: "judge",
|
|
18
|
+
context: options.context,
|
|
19
|
+
...(auditorRunDirectory(options.context) === undefined
|
|
20
|
+
? {}
|
|
21
|
+
: { runDirectory: auditorRunDirectory(options.context) }),
|
|
22
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
23
|
+
...(options.summonAuditor === undefined ? {} : { summonAuditor: options.summonAuditor }),
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical ledger session JSONL read primitives (shared owner).
|
|
3
|
+
* Consumers (ticket-trajectory, analyst, …) must import here — no second parse kernel.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
/**
|
|
7
|
+
* Loud JSONL failure that still retains rows parsed before the bad line.
|
|
8
|
+
* Callers that only need the throw keep catching Error; owners that must
|
|
9
|
+
* surface partial typed facts (e.g. first-frame timestamp) read prefixRows.
|
|
10
|
+
*/
|
|
11
|
+
export class LedgerSessionJsonlError extends Error {
|
|
12
|
+
path;
|
|
13
|
+
line;
|
|
14
|
+
prefixRows;
|
|
15
|
+
constructor(message, init) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "LedgerSessionJsonlError";
|
|
18
|
+
this.path = init.path;
|
|
19
|
+
this.line = init.line;
|
|
20
|
+
this.prefixRows = init.prefixRows;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read session JSONL with honest live-tail semantics:
|
|
28
|
+
* a malformed line is tolerated only when it is an unfinished final
|
|
29
|
+
* fragment at EOF (no record terminator after it). Any malformed line
|
|
30
|
+
* completed by a line terminator must fail loudly with file and 1-based
|
|
31
|
+
* line context — even when no non-empty record follows — never silently
|
|
32
|
+
* under-count.
|
|
33
|
+
*
|
|
34
|
+
* Loud failures throw LedgerSessionJsonlError carrying prefixRows so the
|
|
35
|
+
* single parse kernel can still expose facts obtained before the bad line.
|
|
36
|
+
*/
|
|
37
|
+
export async function readLedgerSessionJsonl(path) {
|
|
38
|
+
const text = await readFile(path, "utf8");
|
|
39
|
+
// split keeps a trailing empty segment iff text ends with "\n", so
|
|
40
|
+
// index < lines.length - 1 means this segment was terminated.
|
|
41
|
+
const lines = text.split("\n");
|
|
42
|
+
const rows = [];
|
|
43
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
44
|
+
const line = lines[index];
|
|
45
|
+
if (!line.trim())
|
|
46
|
+
continue;
|
|
47
|
+
let row;
|
|
48
|
+
try {
|
|
49
|
+
row = JSON.parse(line);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (!(error instanceof SyntaxError))
|
|
53
|
+
throw error;
|
|
54
|
+
const completedByTerminator = index < lines.length - 1;
|
|
55
|
+
if (completedByTerminator) {
|
|
56
|
+
throw new LedgerSessionJsonlError(`malformed JSONL record in ${path} at line ${index + 1}: ${error.message}`, { path, line: index + 1, prefixRows: rows });
|
|
57
|
+
}
|
|
58
|
+
// unfinished fragment at EOF — keep prior complete rows
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
// Syntactically complete line: must be a session object. Silent omission
|
|
62
|
+
// would under-count ledger evidence (failure honesty).
|
|
63
|
+
if (!isRecord(row)) {
|
|
64
|
+
const kind = row === null ? "null" : Array.isArray(row) ? "array" : typeof row;
|
|
65
|
+
throw new LedgerSessionJsonlError(`complete non-object JSONL record in ${path} at line ${index + 1}: expected object, got ${kind}`, { path, line: index + 1, prefixRows: rows });
|
|
66
|
+
}
|
|
67
|
+
rows.push(row);
|
|
68
|
+
}
|
|
69
|
+
return rows;
|
|
70
|
+
}
|
|
71
|
+
/** First and last record timestamps in encounter order. */
|
|
72
|
+
export function extractSessionTimestampSpan(rows) {
|
|
73
|
+
let startedAt;
|
|
74
|
+
let endedAt;
|
|
75
|
+
for (const row of rows) {
|
|
76
|
+
if (typeof row.timestamp !== "string" || !row.timestamp)
|
|
77
|
+
continue;
|
|
78
|
+
if (startedAt === undefined)
|
|
79
|
+
startedAt = row.timestamp;
|
|
80
|
+
endedAt = row.timestamp;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
...(startedAt !== undefined ? { startedAt } : {}),
|
|
84
|
+
...(endedAt !== undefined ? { endedAt } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Ordered-unique model ids from session frames (first-seen order).
|
|
89
|
+
* Sources (same faces ticket-trajectory already reads — single parse kernel):
|
|
90
|
+
* - `model_change.modelId`
|
|
91
|
+
* - assistant `message.model`
|
|
92
|
+
* Blank / non-string values are skipped. Does not invent a default model.
|
|
93
|
+
*/
|
|
94
|
+
export function extractSessionModelSequence(rows) {
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
const ordered = [];
|
|
97
|
+
const push = (raw) => {
|
|
98
|
+
const model = raw.trim();
|
|
99
|
+
if (model === "" || seen.has(model))
|
|
100
|
+
return;
|
|
101
|
+
seen.add(model);
|
|
102
|
+
ordered.push(model);
|
|
103
|
+
};
|
|
104
|
+
for (const row of rows) {
|
|
105
|
+
if (row.type === "model_change" && typeof row.modelId === "string") {
|
|
106
|
+
push(row.modelId);
|
|
107
|
+
}
|
|
108
|
+
const message = isRecord(row.message) ? row.message : undefined;
|
|
109
|
+
if (message?.role === "assistant" && typeof message.model === "string") {
|
|
110
|
+
push(message.model);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return ordered;
|
|
114
|
+
}
|
|
115
|
+
/** First line of a bash `command` argument (sole owner of this summary). */
|
|
116
|
+
export function bashCommandFirstLine(command) {
|
|
117
|
+
const match = /^[^\r\n]*/.exec(command);
|
|
118
|
+
return match?.[0] ?? "";
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Pair toolCall frames → toolResult frames by toolCallId.
|
|
122
|
+
* Throws when a tool-bearing frame is structurally unreadable for association
|
|
123
|
+
* (toolCall missing string id, toolResult missing string toolCallId).
|
|
124
|
+
* Unpaired open calls remain without endedAt — that is incomplete, not unreadable.
|
|
125
|
+
*/
|
|
126
|
+
export function extractSessionToolIntervals(rows) {
|
|
127
|
+
const order = [];
|
|
128
|
+
const openById = new Map();
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
const rowTimestamp = typeof row.timestamp === "string" ? row.timestamp : undefined;
|
|
131
|
+
const message = isRecord(row.message) ? row.message : undefined;
|
|
132
|
+
if (message?.role === "assistant" && Array.isArray(message.content)) {
|
|
133
|
+
const callTimestamp = typeof message.timestamp === "string" && message.timestamp
|
|
134
|
+
? message.timestamp
|
|
135
|
+
: rowTimestamp;
|
|
136
|
+
for (const part of message.content) {
|
|
137
|
+
if (!isRecord(part) || part.type !== "toolCall")
|
|
138
|
+
continue;
|
|
139
|
+
if (typeof part.id !== "string" || part.id.length === 0) {
|
|
140
|
+
throw new Error("toolCall frame missing string id");
|
|
141
|
+
}
|
|
142
|
+
if (typeof part.name !== "string" || part.name.length === 0) {
|
|
143
|
+
throw new Error(`toolCall ${part.id} missing string name`);
|
|
144
|
+
}
|
|
145
|
+
if (callTimestamp === undefined || callTimestamp.length === 0) {
|
|
146
|
+
throw new Error(`toolCall ${part.id} missing timestamp`);
|
|
147
|
+
}
|
|
148
|
+
if (openById.has(part.id)) {
|
|
149
|
+
throw new Error(`duplicate toolCall id ${part.id}`);
|
|
150
|
+
}
|
|
151
|
+
const args = isRecord(part.arguments) ? part.arguments : undefined;
|
|
152
|
+
// Ticket surface: only bash first-line summary is authorized here.
|
|
153
|
+
const command = part.name === "bash" &&
|
|
154
|
+
args !== undefined &&
|
|
155
|
+
typeof args.command === "string"
|
|
156
|
+
? bashCommandFirstLine(args.command)
|
|
157
|
+
: undefined;
|
|
158
|
+
const interval = {
|
|
159
|
+
toolCallId: part.id,
|
|
160
|
+
toolName: part.name,
|
|
161
|
+
startedAt: callTimestamp,
|
|
162
|
+
...(command !== undefined ? { command } : {}),
|
|
163
|
+
};
|
|
164
|
+
order.push(interval);
|
|
165
|
+
openById.set(part.id, interval);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (message?.role === "toolResult") {
|
|
169
|
+
if (typeof message.toolCallId !== "string" || message.toolCallId.length === 0) {
|
|
170
|
+
throw new Error("toolResult frame missing string toolCallId");
|
|
171
|
+
}
|
|
172
|
+
const resultTimestamp = typeof message.timestamp === "string" && message.timestamp
|
|
173
|
+
? message.timestamp
|
|
174
|
+
: rowTimestamp;
|
|
175
|
+
if (resultTimestamp === undefined || resultTimestamp.length === 0) {
|
|
176
|
+
throw new Error(`toolResult ${message.toolCallId} missing timestamp`);
|
|
177
|
+
}
|
|
178
|
+
const open = openById.get(message.toolCallId);
|
|
179
|
+
if (open === undefined) {
|
|
180
|
+
// Result without a prior call is still associable as a closed interval
|
|
181
|
+
// once a name is known; keep structural readability without inventing a call.
|
|
182
|
+
const toolName = typeof message.toolName === "string" && message.toolName.length > 0
|
|
183
|
+
? message.toolName
|
|
184
|
+
: "unknown";
|
|
185
|
+
order.push({
|
|
186
|
+
toolCallId: message.toolCallId,
|
|
187
|
+
toolName,
|
|
188
|
+
startedAt: resultTimestamp,
|
|
189
|
+
endedAt: resultTimestamp,
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (open.endedAt !== undefined) {
|
|
194
|
+
throw new Error(`duplicate toolResult for toolCallId ${message.toolCallId}`);
|
|
195
|
+
}
|
|
196
|
+
open.endedAt = resultTimestamp;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return order.map((interval) => {
|
|
200
|
+
const base = {
|
|
201
|
+
toolCallId: interval.toolCallId,
|
|
202
|
+
toolName: interval.toolName,
|
|
203
|
+
startedAt: interval.startedAt,
|
|
204
|
+
...(interval.command !== undefined ? { command: interval.command } : {}),
|
|
205
|
+
};
|
|
206
|
+
return interval.endedAt === undefined
|
|
207
|
+
? base
|
|
208
|
+
: { ...base, endedAt: interval.endedAt };
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
export function intervalRowsAroundAnchor(rows, anchorIndex, isBindingRow) {
|
|
212
|
+
let start = 0;
|
|
213
|
+
for (let i = anchorIndex; i >= 0; i -= 1) {
|
|
214
|
+
if (isBindingRow(rows[i])) {
|
|
215
|
+
start = i;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
let end = rows.length;
|
|
220
|
+
for (let i = Math.max(anchorIndex, start) + 1; i < rows.length; i += 1) {
|
|
221
|
+
if (isBindingRow(rows[i])) {
|
|
222
|
+
end = i;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { rows: rows.slice(start, end), closed: end < rows.length };
|
|
227
|
+
}
|