@akagilnc/pi-workflow-roles 0.1.5241 → 0.1.5252
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/README.md +2 -3
- package/README.zh-CN.md +2 -3
- package/dist/acp-host/production-host.js +482 -3141
- package/dist/collector-config.js +0 -1
- package/dist/headless-host/production-host.js +590 -3249
- package/dist/migrate-book-topology.js +40 -107
- package/dist/navigator-invocation-identity.js +0 -57
- package/dist/package-contracts/collector-output.js +1 -90
- package/dist/package-contracts/terminating-tools.js +2 -2
- package/dist/packaged-role-registry.js +8 -11
- package/dist/public-cli/invocation.js +2 -22
- package/dist/public-cli/main.js +256 -749
- package/dist/public-cli/option-definitions.js +4 -19
- package/dist/public-cli/settlement.js +12 -138
- package/package.json +1 -1
- package/resources/collector-bot-handbook.md +4 -2
- package/src/collector-config.ts +0 -1
- package/src/collector-role.ts +40 -535
- package/src/collector-tool-schemas.ts +7 -117
- package/src/gh-api-runner.ts +102 -0
- package/src/navigator-invocation-identity.ts +4 -121
- package/src/package-contracts/collector-output.ts +15 -174
- package/src/package-contracts/terminating-tools.ts +0 -2
- package/src/packaged-role-registry.ts +7 -10
- package/src/public-cli/invocation.ts +3 -27
- package/src/public-cli/option-definitions.ts +4 -19
- package/src/public-cli/settlement.ts +11 -177
- package/src/role-runtime-dependencies.ts +0 -4
- package/src/role-runtime.ts +39 -103
- package/src/ticket-snapshot.ts +1 -1
- package/dist/collector-evidence.js +0 -319
- package/dist/collector-github.js +0 -725
- package/dist/collector-identity.js +0 -92
- package/dist/collector-ledger.js +0 -888
- package/dist/collector-target.js +0 -169
- package/src/collector-evidence.ts +0 -466
- package/src/collector-github.ts +0 -1032
- package/src/collector-handbook.ts +0 -194
- package/src/collector-identity.ts +0 -151
- package/src/collector-ledger.ts +0 -1186
- package/src/collector-target.ts +0 -207
|
@@ -2,136 +2,26 @@ import { Type, type Static } from "typebox";
|
|
|
2
2
|
import { openToolObject } from "./open-tool-schema.ts";
|
|
3
3
|
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
4
4
|
|
|
5
|
-
// #836 r16 class 3: execute() reads no params for this tool
|
|
6
|
-
// (src/collector-role.ts:431-459) — a closed root only rejects the role for
|
|
7
|
-
// saying more; no reader consumes extra fields.
|
|
8
|
-
export const collectorObserveArgsSchema = Type.Object({}, { additionalProperties: true });
|
|
9
|
-
// #836 r16 class 4: the regex was a real provider shape rejection ahead of the
|
|
10
|
-
// ledger's own identity check — src/collector-ledger.ts:911-972 already trims
|
|
11
|
-
// and enforces non-empty/lookup/marker/attemptKey identity on requestId once
|
|
12
|
-
// the tool receives it, so the duplicate pattern-based rejection is deleted
|
|
13
|
-
// (r15b decision, not reopened here).
|
|
14
|
-
export const collectorRequestArgsSchema = Type.Object({
|
|
15
|
-
requestId: Type.String({
|
|
16
|
-
minLength: 1,
|
|
17
|
-
description: "请求身份(配置 id 或角色判定的稳定 id;首尾无空白)",
|
|
18
|
-
}),
|
|
19
|
-
snapshotId: Type.String({ minLength: 1, description: "最新留存观察快照" }),
|
|
20
|
-
// #836 r16 class 3: body.minLength deleted — manifest-configured requestId
|
|
21
|
-
// ignores body, and the unknown-requestId non-empty condition is a real
|
|
22
|
-
// branch in src/collector-ledger.ts:922-951, not a provider shape gate.
|
|
23
|
-
body: Type.Optional(Type.String({
|
|
24
|
-
description: "角色判定的请求正文;request-manifest 未收录该 requestId 时必填",
|
|
25
|
-
})),
|
|
26
|
-
}, { additionalProperties: true });
|
|
27
|
-
/** UTF-8 byte ceiling per handbook body — keeps next activation materials inside context budget. */
|
|
28
|
-
export const COLLECTOR_HANDBOOK_MAX_BYTES = 64 * 1024;
|
|
29
|
-
export const collectorHandbookWriteArgsSchema = Type.Object({
|
|
30
|
-
scope: Type.Union([
|
|
31
|
-
Type.Literal("general"),
|
|
32
|
-
Type.Literal("repo"),
|
|
33
|
-
], { description: "general=通用手册;repo=当前仓库差异" }),
|
|
34
|
-
body: Type.String({
|
|
35
|
-
description: `手册全文(opaque 工作记忆;整份替换;UTF-8 至多 ${COLLECTOR_HANDBOOK_MAX_BYTES} 字节)`,
|
|
36
|
-
}),
|
|
37
|
-
}, { additionalProperties: true });
|
|
38
|
-
export const collectorReadArgsSchema = Type.Object({
|
|
39
|
-
evidenceId: Type.String({ minLength: 1, description: "observe 返回的材料证据 id(evidenceId)" }),
|
|
40
|
-
}, { additionalProperties: true });
|
|
41
|
-
export const collectorWaitArgsSchema = Type.Object({
|
|
42
|
-
durationMs: Type.Integer({
|
|
43
|
-
minimum: 1,
|
|
44
|
-
description: "等待毫秒;实际睡眠不超过剩余等待窗(#678;无包内单次任意上限)",
|
|
45
|
-
}),
|
|
46
|
-
}, { additionalProperties: true });
|
|
47
|
-
|
|
48
5
|
/**
|
|
49
|
-
* #
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*/
|
|
53
|
-
// #836 r16 class 3: startedAt.minLength deleted — an empty string is already
|
|
54
|
-
// handled as omission by src/collector-role.ts:586-597, so the provider
|
|
55
|
-
// nonblank gate is not load-bearing for any branch.
|
|
56
|
-
export const collectorOpenWaitWindowArgsSchema = Type.Object({
|
|
57
|
-
startedAt: Type.Optional(Type.String({
|
|
58
|
-
description: "等待窗起点 ISO 时间;新建 PR 用创建成功时刻;省略=现在(触发阶段结束)",
|
|
59
|
-
})),
|
|
60
|
-
}, { additionalProperties: true });
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* #676 A: role-decided target bind. The model judges task materials and submits
|
|
64
|
-
* the chosen PR and/or issue identity; runtime only performs online association
|
|
65
|
-
* for the role-chosen ticket — never scrapes task text to lock a target.
|
|
66
|
-
*/
|
|
67
|
-
export const collectorBindTargetArgsSchema = Type.Object({
|
|
68
|
-
prNumber: Type.Optional(Type.Unknown({
|
|
69
|
-
description: "本仓 PR 号(正整数)。",
|
|
70
|
-
})),
|
|
71
|
-
issueNumber: Type.Optional(Type.Unknown({
|
|
72
|
-
description: "本仓 issue 号(正整数)。",
|
|
73
|
-
})),
|
|
74
|
-
}, { additionalProperties: true });
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* #641 chain① / #676 C / ADR 0057: nested finding item declarations for model
|
|
78
|
-
* guidance. Host must not pure-shape-reject the envelope (第 0 条). Runtime binds
|
|
79
|
-
* resolvable evidence pointers only; unprojected content stays distinguishable.
|
|
80
|
-
*/
|
|
81
|
-
const collectorFindingItemDeclaration = (() => {
|
|
82
|
-
// Nested declarations for model guidance only — open required so host cannot
|
|
83
|
-
// pure-shape-reject missing optional fields (第 0 条 / ADR 0057 / #676 C).
|
|
84
|
-
const item = Type.Object(
|
|
85
|
-
{
|
|
86
|
-
evidenceId: Type.Unknown({
|
|
87
|
-
description: "observe 返回的材料指针(必填语义)",
|
|
88
|
-
}),
|
|
89
|
-
category: Type.Unknown({
|
|
90
|
-
description: "简短归类标签,不是摘要",
|
|
91
|
-
}),
|
|
92
|
-
summary: Type.Unknown({
|
|
93
|
-
description: "哪个 bot、什么问题的摘要;不誊抄正文",
|
|
94
|
-
}),
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
additionalProperties: true,
|
|
98
|
-
description:
|
|
99
|
-
"单条 finding 指针:evidenceId,以及可选 category、summary。",
|
|
100
|
-
},
|
|
101
|
-
);
|
|
102
|
-
(item as unknown as { required: string[] }).required = [];
|
|
103
|
-
return item;
|
|
104
|
-
})();
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Field declarations + descriptions are guidance for the model — host must not
|
|
108
|
-
* pure-shape-reject the envelope (第 0 条 / ADR 0057).
|
|
6
|
+
* #1088: LLM gathers evidence via host CLI and submits the receipt.
|
|
7
|
+
* Field declarations guide the model — host must not pure-shape-reject (第 0 条 / ADR 0057).
|
|
8
|
+
* Presence of `groups` remains the Collector terminal discriminator for settlement/analyst.
|
|
109
9
|
*/
|
|
110
10
|
export const collectorOutputBaseSchema = openToolObject(
|
|
111
11
|
Type.Object({
|
|
112
|
-
|
|
113
|
-
// Nested item declarations ride `items` for registration preservation (ADR 0057).
|
|
114
|
-
findings: Type.Unsafe({
|
|
12
|
+
groups: Type.Unsafe({
|
|
115
13
|
description:
|
|
116
|
-
"
|
|
117
|
-
items: collectorFindingItemDeclaration,
|
|
14
|
+
"按机器身份归组的出席、材料与逐条 findings(LLM 自行从宿主 CLI 取证整理;代码不代收不归并)。",
|
|
118
15
|
}),
|
|
119
16
|
unfinishedReasons: Type.Unknown({
|
|
120
|
-
description:
|
|
121
|
-
"未完成原因。",
|
|
17
|
+
description: "未完成原因;取证受限时如实报告。",
|
|
122
18
|
}),
|
|
123
19
|
}),
|
|
124
20
|
);
|
|
125
21
|
|
|
126
|
-
/** Runtime
|
|
22
|
+
/** Runtime does not enrich findings; the model submits the receipt and signals sole-final. */
|
|
127
23
|
export const collectorOutputArgsSchema = withTerminatingOutputDeclarations(
|
|
128
24
|
collectorOutputBaseSchema,
|
|
129
25
|
);
|
|
130
26
|
|
|
131
|
-
export type CollectorObserveArgs = Static<typeof collectorObserveArgsSchema>;
|
|
132
|
-
export type CollectorRequestArgs = Static<typeof collectorRequestArgsSchema>;
|
|
133
|
-
export type CollectorReadArgs = Static<typeof collectorReadArgsSchema>;
|
|
134
|
-
export type CollectorWaitArgs = Static<typeof collectorWaitArgsSchema>;
|
|
135
|
-
export type CollectorOpenWaitWindowArgs = Static<typeof collectorOpenWaitWindowArgsSchema>;
|
|
136
|
-
export type CollectorBindTargetArgs = Static<typeof collectorBindTargetArgsSchema>;
|
|
137
27
|
export type CollectorOutputArgs = Static<typeof collectorOutputArgsSchema>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export type GhApiRunOptions = {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type GhApiResponse = {
|
|
8
|
+
status: number;
|
|
9
|
+
headers: Record<string, string>;
|
|
10
|
+
bodyText: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type GhApiRunner = (
|
|
14
|
+
args: readonly string[],
|
|
15
|
+
options?: GhApiRunOptions,
|
|
16
|
+
) => Promise<GhApiResponse>;
|
|
17
|
+
|
|
18
|
+
export function createGhApiRunner(
|
|
19
|
+
options: {
|
|
20
|
+
spawnImpl?: typeof spawn;
|
|
21
|
+
env?: NodeJS.ProcessEnv;
|
|
22
|
+
} = {},
|
|
23
|
+
): GhApiRunner {
|
|
24
|
+
const spawnImpl = options.spawnImpl ?? spawn;
|
|
25
|
+
return async (args, runOptions = {}) => {
|
|
26
|
+
return await new Promise<GhApiResponse>((resolve, reject) => {
|
|
27
|
+
const signal = runOptions.signal;
|
|
28
|
+
if (signal?.aborted) {
|
|
29
|
+
reject(signal.reason ?? new Error("aborted"));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const child = spawnImpl("gh", args, {
|
|
33
|
+
env: options.env ?? process.env,
|
|
34
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
35
|
+
});
|
|
36
|
+
let stdout = "";
|
|
37
|
+
let stderr = "";
|
|
38
|
+
let settled = false;
|
|
39
|
+
const settle = (fn: () => void) => {
|
|
40
|
+
if (settled) return;
|
|
41
|
+
settled = true;
|
|
42
|
+
if (signal !== undefined) {
|
|
43
|
+
signal.removeEventListener("abort", onAbort);
|
|
44
|
+
}
|
|
45
|
+
fn();
|
|
46
|
+
};
|
|
47
|
+
const onAbort = () => {
|
|
48
|
+
try {
|
|
49
|
+
child.kill("SIGTERM");
|
|
50
|
+
} catch (error) {
|
|
51
|
+
settle(() => reject(error));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
settle(() => {
|
|
55
|
+
reject(signal?.reason ?? new Error("aborted"));
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
if (signal !== undefined) {
|
|
59
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
60
|
+
}
|
|
61
|
+
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
62
|
+
stdout += chunk;
|
|
63
|
+
});
|
|
64
|
+
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
|
65
|
+
stderr += chunk;
|
|
66
|
+
});
|
|
67
|
+
child.on("error", (error) => {
|
|
68
|
+
settle(() => reject(error));
|
|
69
|
+
});
|
|
70
|
+
child.on("close", (code, signal) => {
|
|
71
|
+
settle(() => {
|
|
72
|
+
// gh api --include prints: HTTP/ headers blank-line body
|
|
73
|
+
const match = stdout.match(/^HTTP\/[\d.]+\s+(\d+)[^\n]*\r?\n([\s\S]*?)\r?\n\r?\n([\s\S]*)$/);
|
|
74
|
+
if (match) {
|
|
75
|
+
const status = Number(match[1]);
|
|
76
|
+
const headerText = match[2] ?? "";
|
|
77
|
+
const bodyText = match[3] ?? "";
|
|
78
|
+
const headers: Record<string, string> = {};
|
|
79
|
+
for (const line of headerText.split(/\r?\n/)) {
|
|
80
|
+
const idx = line.indexOf(":");
|
|
81
|
+
if (idx === -1) continue;
|
|
82
|
+
const name = line.slice(0, idx).trim().toLowerCase();
|
|
83
|
+
const value = line.slice(idx + 1).trim();
|
|
84
|
+
headers[name] = value;
|
|
85
|
+
}
|
|
86
|
+
resolve({ status, headers, bodyText });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (code === 0) {
|
|
90
|
+
resolve({ status: 200, headers: {}, bodyText: stdout });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const failure = new Error(
|
|
94
|
+
`gh api failed without a parseable HTTP response (code=${String(code)}): ${stderr || stdout}`,
|
|
95
|
+
{ cause: { code, signal, stderr, stdout } },
|
|
96
|
+
);
|
|
97
|
+
reject(failure);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Exact current-invocation principal for
|
|
2
|
+
* Exact current-invocation principal for shared role lifecycle (#1087).
|
|
3
3
|
* Shared role lifecycle owns one opaque uuidv7 per Role invocation and persists
|
|
4
4
|
* it on the role session via Pi's guaranteed `pi.appendEntry` boundary.
|
|
5
5
|
*
|
|
@@ -7,21 +7,14 @@
|
|
|
7
7
|
* unfinished exact-session resume reuses the latest valid marker only when its
|
|
8
8
|
* role/phase/subjectKey still match the expected identity; a packaged role
|
|
9
9
|
* terminal completing that marker starts the next invocation (fresh mint).
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* self-shape, markers after the terminal, or stale older markers behind a
|
|
13
|
-
* malformed nearest. Non-UUIDv7 principals are never accepted.
|
|
10
|
+
* Public settlement does not re-correlate attendance against this principal
|
|
11
|
+
* (ADR 0042); lifecycle dispose/grace owns call-boundary isolation (ADR 0084).
|
|
14
12
|
*
|
|
15
13
|
* One shared typed terminal classifier owns durable completion for lifecycle,
|
|
16
14
|
* publicNavigatorSettlement, and every public CLI Receipt extractor:
|
|
17
15
|
* - accepted/human: isError exactly false and no infrastructure-failure fact
|
|
18
16
|
* - infrastructure: isError exactly true plus base kind/source/reasonCode identity
|
|
19
17
|
* - retryable/missing/nonboolean/contradictory/malformed: nonterminal
|
|
20
|
-
*
|
|
21
|
-
* One truth table also owns marker↔terminal cardinality:
|
|
22
|
-
* - a marker binds exactly one durable packaged role terminal
|
|
23
|
-
* - multiple durable terminals after the same marker → ambiguous / fail-closed
|
|
24
|
-
* - marker role/phase/subjectKey must match the terminal + independent expected
|
|
25
18
|
*/
|
|
26
19
|
|
|
27
20
|
import { LEGACY_REVIEW_OUTPUT_ROLES, PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
|
|
@@ -147,7 +140,7 @@ export type InvocationMarkerIdentity = {
|
|
|
147
140
|
readonly subjectKey: string;
|
|
148
141
|
};
|
|
149
142
|
|
|
150
|
-
/** Independently admitted / registry / cwd expected identity for resume
|
|
143
|
+
/** Independently admitted / registry / cwd expected identity for lifecycle resume. */
|
|
151
144
|
export type ExpectedInvocationIdentity = {
|
|
152
145
|
readonly role: string;
|
|
153
146
|
readonly phase?: InvocationPhase;
|
|
@@ -342,83 +335,6 @@ export function findLatestDurablePackagedRoleTerminal(
|
|
|
342
335
|
return undefined;
|
|
343
336
|
}
|
|
344
337
|
|
|
345
|
-
/**
|
|
346
|
-
* Truth-table binding of the current durable packaged role terminal to its
|
|
347
|
-
* owning invocation marker. Singleton cardinality: multiple durable terminals
|
|
348
|
-
* after the same marker are ambiguous and fail closed.
|
|
349
|
-
*/
|
|
350
|
-
export type DurableTerminalMarkerBinding =
|
|
351
|
-
| {
|
|
352
|
-
readonly kind: "bound";
|
|
353
|
-
readonly terminal: DurablePackagedRoleTerminalRef;
|
|
354
|
-
readonly marker: InvocationMarkerIdentity & { readonly index: number };
|
|
355
|
-
}
|
|
356
|
-
| {
|
|
357
|
-
readonly kind: "unbound";
|
|
358
|
-
readonly terminal: DurablePackagedRoleTerminalRef;
|
|
359
|
-
}
|
|
360
|
-
| { readonly kind: "absent" }
|
|
361
|
-
| { readonly kind: "ambiguous" };
|
|
362
|
-
|
|
363
|
-
export function bindCurrentDurableTerminalToMarker(
|
|
364
|
-
entries: readonly NavigatorInvocationEntryLike[],
|
|
365
|
-
): DurableTerminalMarkerBinding {
|
|
366
|
-
const terminal = findLatestDurablePackagedRoleTerminal(entries);
|
|
367
|
-
if (terminal === undefined) return { kind: "absent" };
|
|
368
|
-
|
|
369
|
-
let markerIndex = -1;
|
|
370
|
-
for (let i = terminal.index - 1; i >= 0; i -= 1) {
|
|
371
|
-
if (isInvocationMarkerEntry(entries[i])) {
|
|
372
|
-
markerIndex = i;
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
if (markerIndex < 0) {
|
|
377
|
-
return { kind: "unbound", terminal };
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
|
|
381
|
-
if (marker === undefined) {
|
|
382
|
-
// Malformed nearest marker: no stale fallback; terminal exists but is unbound.
|
|
383
|
-
return { kind: "unbound", terminal };
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
let windowEnd = entries.length;
|
|
387
|
-
for (let i = markerIndex + 1; i < entries.length; i += 1) {
|
|
388
|
-
if (isInvocationMarkerEntry(entries[i])) {
|
|
389
|
-
windowEnd = i;
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
let durableCount = 0;
|
|
395
|
-
for (let i = markerIndex + 1; i < windowEnd; i += 1) {
|
|
396
|
-
if (durableTerminalAt(entries, i) !== undefined) durableCount += 1;
|
|
397
|
-
}
|
|
398
|
-
if (durableCount !== 1) return { kind: "ambiguous" };
|
|
399
|
-
// Current terminal must be the singleton inside this marker window.
|
|
400
|
-
if (terminal.index <= markerIndex || terminal.index >= windowEnd) {
|
|
401
|
-
return { kind: "ambiguous" };
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return {
|
|
405
|
-
kind: "bound",
|
|
406
|
-
terminal,
|
|
407
|
-
marker: { ...marker, index: markerIndex },
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Whether public Receipt settlement may proceed for the current durable terminal.
|
|
413
|
-
* Only ambiguous multi-terminal bindings fail closed; attendance mismatch does not
|
|
414
|
-
* pollute an otherwise legal single Receipt.
|
|
415
|
-
*/
|
|
416
|
-
export function isReceiptSettlementBindingClear(
|
|
417
|
-
entries: readonly NavigatorInvocationEntryLike[],
|
|
418
|
-
): boolean {
|
|
419
|
-
return bindCurrentDurableTerminalToMarker(entries).kind !== "ambiguous";
|
|
420
|
-
}
|
|
421
|
-
|
|
422
338
|
export type LifecycleInvocationPrincipal = {
|
|
423
339
|
readonly invocationId: string;
|
|
424
340
|
/** True when the principal is already on the admitted session (resume; do not re-append). */
|
|
@@ -463,36 +379,3 @@ export function resolveLifecycleInvocationPrincipal(
|
|
|
463
379
|
|
|
464
380
|
return { invocationId: marker.invocationId, resume: true };
|
|
465
381
|
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Exact current invocation principal already present on the role session.
|
|
469
|
-
* `beforeIndex` is the current packaged role terminal index: only the nearest
|
|
470
|
-
* marker strictly before that bound is applicable. A malformed nearest marker
|
|
471
|
-
* fails closed (undefined) — never falls back to a stale older valid marker.
|
|
472
|
-
* Markers at/after the terminal (future prepare/event) are never considered.
|
|
473
|
-
* Non-UUIDv7 values are rejected.
|
|
474
|
-
*/
|
|
475
|
-
export function currentInvocationPrincipalFromSession(
|
|
476
|
-
entries: readonly NavigatorInvocationEntryLike[],
|
|
477
|
-
beforeIndex: number = entries.length,
|
|
478
|
-
): string | undefined {
|
|
479
|
-
return currentInvocationMarkerFromSession(entries, beforeIndex)?.invocationId;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Full marker identity nearest before `beforeIndex` (current durable terminal).
|
|
484
|
-
* Malformed nearest fails closed — no stale older fallback.
|
|
485
|
-
*/
|
|
486
|
-
export function currentInvocationMarkerFromSession(
|
|
487
|
-
entries: readonly NavigatorInvocationEntryLike[],
|
|
488
|
-
beforeIndex: number = entries.length,
|
|
489
|
-
): InvocationMarkerIdentity | undefined {
|
|
490
|
-
const limit = Math.min(Math.max(beforeIndex, 0), entries.length);
|
|
491
|
-
for (let i = limit - 1; i >= 0; i -= 1) {
|
|
492
|
-
const entry = entries[i];
|
|
493
|
-
if (!isInvocationMarkerEntry(entry)) continue;
|
|
494
|
-
// Nearest marker only — parse full identity or fail closed.
|
|
495
|
-
return parseInvocationMarkerIdentity(entry?.data);
|
|
496
|
-
}
|
|
497
|
-
return undefined;
|
|
498
|
-
}
|
|
@@ -1,188 +1,29 @@
|
|
|
1
1
|
/** Package-owned Collector receipt leaf — no role registration surface. */
|
|
2
2
|
|
|
3
3
|
export const COLLECTOR_OUTPUT_TOOL = "ak_collector_output";
|
|
4
|
-
export const COLLECTOR_HOST = "github.com";
|
|
5
4
|
|
|
5
|
+
/**
|
|
6
|
+
* One identity group on the LLM-submitted receipt.
|
|
7
|
+
* #1088: code no longer builds ledger projections; the model submits groups.
|
|
8
|
+
*/
|
|
6
9
|
export type CollectorIdentityGroup = {
|
|
7
10
|
identity: Record<string, unknown> | null;
|
|
8
11
|
displayLogin?: string;
|
|
9
|
-
attendance
|
|
10
|
-
materials
|
|
11
|
-
findings
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type CollectorRequestAttempt = {
|
|
15
|
-
attemptId: string;
|
|
16
|
-
requestId: string;
|
|
17
|
-
observedHead: string;
|
|
18
|
-
snapshotId: string;
|
|
19
|
-
marker: string;
|
|
20
|
-
body: string;
|
|
21
|
-
startedAt: string;
|
|
22
|
-
status: "started" | "succeeded" | "rejected" | "ambiguous_loss" | "recovered";
|
|
23
|
-
responseDiagnostics?: string;
|
|
24
|
-
commentEvidenceId?: string;
|
|
25
|
-
recoverySnapshotId?: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type CollectorSnapshot = {
|
|
29
|
-
snapshotId: string;
|
|
30
|
-
observedAt: string;
|
|
31
|
-
completedAt: string;
|
|
32
|
-
completedMono: number;
|
|
33
|
-
host: "github.com";
|
|
34
|
-
repository: string;
|
|
35
|
-
prNumber: number;
|
|
36
|
-
prState: string;
|
|
37
|
-
headOid: string;
|
|
38
|
-
complete: boolean;
|
|
39
|
-
evidenceIds: string[];
|
|
40
|
-
pageDiagnostics: Array<Record<string, unknown>>;
|
|
41
|
-
normalizedByteLength: number;
|
|
12
|
+
attendance?: true;
|
|
13
|
+
materials?: Array<Record<string, unknown>>;
|
|
14
|
+
findings?: Array<Record<string, unknown>>;
|
|
42
15
|
};
|
|
43
16
|
|
|
44
17
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* never transcribed into the receipt.
|
|
18
|
+
* Collector receipt as submitted. Presence of `groups` is the terminal
|
|
19
|
+
* discriminator for settlement/analyst (#836: original object is the receipt).
|
|
48
20
|
*/
|
|
49
|
-
export type CollectorEvidenceRecord = {
|
|
50
|
-
evidenceId: string;
|
|
51
|
-
kind: string;
|
|
52
|
-
versionId: string;
|
|
53
|
-
contentDigest: string;
|
|
54
|
-
firstObservedAt: string;
|
|
55
|
-
githubId?: number;
|
|
56
|
-
authorLogin?: string;
|
|
57
|
-
htmlUrl?: string;
|
|
58
|
-
authoritativeTime?: string | null;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* #676 C: sole structured projection-fact type (builder + package leaf).
|
|
63
|
-
* Builder always emits the full object; package leaf keeps it optional so old
|
|
64
|
-
* volumes without the field stay readable — missing is unknown, never defaulted.
|
|
65
|
-
*/
|
|
66
|
-
export type CollectorSubmissionProjection = {
|
|
67
|
-
findingsSource: "absent" | "array" | "unreadable";
|
|
68
|
-
findingsProjectedCount: number;
|
|
69
|
-
findingsUnprojected: boolean;
|
|
70
|
-
unfinishedReasonsSource: "absent" | "array" | "unreadable";
|
|
71
|
-
unfinishedReasonsProjectedCount: number;
|
|
72
|
-
unfinishedReasonsUnprojected: boolean;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
21
|
export type CollectorReceipt = {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* Absent/unreadable on old volumes is unknown — never defaulted to OPEN.
|
|
82
|
-
*/
|
|
22
|
+
groups: CollectorIdentityGroup[] | ReadonlyArray<Record<string, unknown>>;
|
|
23
|
+
unfinishedReasons?: unknown;
|
|
24
|
+
host?: string;
|
|
25
|
+
repository?: string;
|
|
26
|
+
prNumber?: number;
|
|
83
27
|
prState?: string;
|
|
84
|
-
manifestDigest
|
|
85
|
-
activationTime: string;
|
|
86
|
-
deadlineTime: string;
|
|
87
|
-
finalObservationTime: string;
|
|
88
|
-
finalSnapshotId: string;
|
|
89
|
-
targetHead: string;
|
|
90
|
-
groups: CollectorIdentityGroup[];
|
|
91
|
-
unfinishedReasons?: string[];
|
|
92
|
-
/**
|
|
93
|
-
* Distinguishes readable-empty from unprojected original submission content (#676 C).
|
|
94
|
-
* Package leaf may carry a partial projection when some sibling keys are unreadable —
|
|
95
|
-
* missing keys stay absent (never defaulted false/0/OPEN).
|
|
96
|
-
*/
|
|
97
|
-
submissionProjection?: Partial<CollectorSubmissionProjection>;
|
|
98
|
-
requestAttempts: CollectorRequestAttempt[];
|
|
99
|
-
snapshots: CollectorSnapshot[];
|
|
100
|
-
evidenceRecords: CollectorEvidenceRecord[];
|
|
28
|
+
manifestDigest?: string;
|
|
101
29
|
};
|
|
102
|
-
|
|
103
|
-
function safeGet(value: unknown, key: string): unknown {
|
|
104
|
-
if ((typeof value !== "object" && typeof value !== "function") || value === null) return undefined;
|
|
105
|
-
try { return (value as Record<string, unknown>)[key]; } catch { return undefined; }
|
|
106
|
-
}
|
|
107
|
-
function records(value: unknown): Record<string, unknown>[] {
|
|
108
|
-
return Array.isArray(value) ? value.filter((item): item is Record<string, unknown> => item !== null && typeof item === "object") : [];
|
|
109
|
-
}
|
|
110
|
-
function strings(value: unknown): string[] {
|
|
111
|
-
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Settlement projection. Presence of the canonical groups array is the one Collector terminal discriminator. */
|
|
115
|
-
export function validateAcceptedCollectorReceipt(value: unknown): CollectorReceipt {
|
|
116
|
-
const rawGroups = safeGet(value, "groups");
|
|
117
|
-
if (!Array.isArray(rawGroups)) throw new Error("Collector receipt has no typed groups terminal discriminator");
|
|
118
|
-
const groups = records(rawGroups).map((group) => ({
|
|
119
|
-
identity: (safeGet(group, "identity") ?? null) as Record<string, unknown> | null,
|
|
120
|
-
...(typeof safeGet(group, "displayLogin") === "string" ? { displayLogin: safeGet(group, "displayLogin") as string } : {}),
|
|
121
|
-
attendance: true as const,
|
|
122
|
-
materials: records(safeGet(group, "materials")),
|
|
123
|
-
findings: records(safeGet(group, "findings")),
|
|
124
|
-
}));
|
|
125
|
-
const unfinishedRaw = safeGet(value, "unfinishedReasons");
|
|
126
|
-
const unfinishedReasons = strings(unfinishedRaw);
|
|
127
|
-
// #676 C: project readable submissionProjection fields independently — do not
|
|
128
|
-
// drop the whole block when one sibling key is the wrong type (left-拾遗1).
|
|
129
|
-
const submissionProjection = projectSubmissionProjection(safeGet(value, "submissionProjection"));
|
|
130
|
-
const prStateRaw = safeGet(value, "prState");
|
|
131
|
-
const prState = typeof prStateRaw === "string" ? prStateRaw : undefined;
|
|
132
|
-
return {
|
|
133
|
-
host: safeGet(value, "host") as "github.com",
|
|
134
|
-
repository: safeGet(value, "repository") as string,
|
|
135
|
-
prNumber: safeGet(value, "prNumber") as number,
|
|
136
|
-
...(prState === undefined ? {} : { prState }),
|
|
137
|
-
manifestDigest: safeGet(value, "manifestDigest") as string,
|
|
138
|
-
activationTime: safeGet(value, "activationTime") as string,
|
|
139
|
-
deadlineTime: safeGet(value, "deadlineTime") as string,
|
|
140
|
-
finalObservationTime: safeGet(value, "finalObservationTime") as string,
|
|
141
|
-
finalSnapshotId: safeGet(value, "finalSnapshotId") as string,
|
|
142
|
-
targetHead: safeGet(value, "targetHead") as string,
|
|
143
|
-
groups,
|
|
144
|
-
...(unfinishedReasons.length > 0 ? { unfinishedReasons } : {}),
|
|
145
|
-
...(submissionProjection === undefined ? {} : { submissionProjection }),
|
|
146
|
-
requestAttempts: records(safeGet(value, "requestAttempts")) as unknown as CollectorRequestAttempt[],
|
|
147
|
-
snapshots: records(safeGet(value, "snapshots")).map((snapshot) => ({
|
|
148
|
-
snapshotId: safeGet(snapshot, "snapshotId"), observedAt: safeGet(snapshot, "observedAt"), completedAt: safeGet(snapshot, "completedAt"), completedMono: safeGet(snapshot, "completedMono"), host: safeGet(snapshot, "host"), repository: safeGet(snapshot, "repository"), prNumber: safeGet(snapshot, "prNumber"), prState: safeGet(snapshot, "prState"), headOid: safeGet(snapshot, "headOid"), complete: safeGet(snapshot, "complete"), evidenceIds: strings(safeGet(snapshot, "evidenceIds")), pageDiagnostics: records(safeGet(snapshot, "pageDiagnostics")), normalizedByteLength: safeGet(snapshot, "normalizedByteLength"),
|
|
149
|
-
} as CollectorSnapshot)),
|
|
150
|
-
evidenceRecords: records(safeGet(value, "evidenceRecords")).map((record) => ({ evidenceId: safeGet(record, "evidenceId"), kind: safeGet(record, "kind"), versionId: safeGet(record, "versionId"), contentDigest: safeGet(record, "contentDigest"), firstObservedAt: safeGet(record, "firstObservedAt"), githubId: safeGet(record, "githubId"), authorLogin: safeGet(record, "authorLogin"), htmlUrl: safeGet(record, "htmlUrl"), authoritativeTime: safeGet(record, "authoritativeTime") } as CollectorEvidenceRecord)),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Readable projection facts only; omit keys that are the wrong type — never fabricate defaults. */
|
|
155
|
-
function projectSubmissionProjection(
|
|
156
|
-
raw: unknown,
|
|
157
|
-
): Partial<CollectorSubmissionProjection> | undefined {
|
|
158
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
159
|
-
const p = raw as Record<string, unknown>;
|
|
160
|
-
const out: Partial<CollectorSubmissionProjection> = {};
|
|
161
|
-
if (
|
|
162
|
-
p["findingsSource"] === "absent" ||
|
|
163
|
-
p["findingsSource"] === "array" ||
|
|
164
|
-
p["findingsSource"] === "unreadable"
|
|
165
|
-
) {
|
|
166
|
-
out.findingsSource = p["findingsSource"];
|
|
167
|
-
}
|
|
168
|
-
if (typeof p["findingsProjectedCount"] === "number") {
|
|
169
|
-
out.findingsProjectedCount = p["findingsProjectedCount"];
|
|
170
|
-
}
|
|
171
|
-
if (typeof p["findingsUnprojected"] === "boolean") {
|
|
172
|
-
out.findingsUnprojected = p["findingsUnprojected"];
|
|
173
|
-
}
|
|
174
|
-
if (
|
|
175
|
-
p["unfinishedReasonsSource"] === "absent" ||
|
|
176
|
-
p["unfinishedReasonsSource"] === "array" ||
|
|
177
|
-
p["unfinishedReasonsSource"] === "unreadable"
|
|
178
|
-
) {
|
|
179
|
-
out.unfinishedReasonsSource = p["unfinishedReasonsSource"];
|
|
180
|
-
}
|
|
181
|
-
if (typeof p["unfinishedReasonsProjectedCount"] === "number") {
|
|
182
|
-
out.unfinishedReasonsProjectedCount = p["unfinishedReasonsProjectedCount"];
|
|
183
|
-
}
|
|
184
|
-
if (typeof p["unfinishedReasonsUnprojected"] === "boolean") {
|
|
185
|
-
out.unfinishedReasonsUnprojected = p["unfinishedReasonsUnprojected"];
|
|
186
|
-
}
|
|
187
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
188
|
-
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
COLLECTOR_OUTPUT_TOOL,
|
|
8
|
-
validateAcceptedCollectorReceipt,
|
|
9
8
|
type CollectorReceipt,
|
|
10
9
|
} from "./collector-output.ts";
|
|
11
10
|
import {
|
|
@@ -44,7 +43,6 @@ export {
|
|
|
44
43
|
JUDGE_OUTPUT_TOOL_NAME,
|
|
45
44
|
REVIEWER_OUTPUT_TOOL_NAME,
|
|
46
45
|
MERGER_OUTPUT_TOOL_NAME,
|
|
47
|
-
validateAcceptedCollectorReceipt,
|
|
48
46
|
validateReviewerIntent,
|
|
49
47
|
validateAcceptedWorkerDetails,
|
|
50
48
|
validateDoctorSubmissionShape,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** Composition-root unique authoritative public-role records (#509 / #524). */
|
|
2
2
|
import { COLLECTOR_OUTPUT_TOOL } from "./package-contracts/collector-output.ts";
|
|
3
|
-
import { COLLECTOR_WAIT_TOOL } from "./collector-ledger.ts";
|
|
4
3
|
import { GATEKEEPER_OUTPUT_TOOL_NAME } from "./package-contracts/gatekeeper-output.ts";
|
|
5
4
|
import { NAVIGATOR_OUTPUT_TOOL_NAME } from "./package-contracts/navigator-output.ts";
|
|
6
5
|
import { JUDGE_OUTPUT_TOOL_NAME } from "./package-contracts/judge-output.ts";
|
|
@@ -232,23 +231,18 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
232
231
|
],
|
|
233
232
|
},
|
|
234
233
|
// ak-collector-repo is GitHub owner/repo identity, not a local material path (#438).
|
|
234
|
+
// #1088: seat + request-manifest kept; code no longer collects — LLM uses host CLI.
|
|
235
235
|
{
|
|
236
236
|
role: "collector",
|
|
237
237
|
inCallAutoResume: false,
|
|
238
238
|
presentSettled: "always",
|
|
239
239
|
summonResume: false,
|
|
240
240
|
admission: "collect-target",
|
|
241
|
-
/** Public no_receipt projects a durable bind-target rejection. */
|
|
242
|
-
projectTargetBindRejection: true,
|
|
243
241
|
sameParent: "none",
|
|
244
242
|
phases: [null],
|
|
245
243
|
bareCommand: false,
|
|
246
244
|
outputTool: COLLECTOR_OUTPUT_TOOL,
|
|
247
|
-
settlement: "
|
|
248
|
-
/** #633: a prior wait-tool residual must not mask this attempt. */
|
|
249
|
-
residualScan: "current-attempt",
|
|
250
|
-
residualTool: COLLECTOR_WAIT_TOOL,
|
|
251
|
-
runnerFailure: "collector-known-first",
|
|
245
|
+
settlement: "sealed",
|
|
252
246
|
artifactFace: {
|
|
253
247
|
evidenceRole: true,
|
|
254
248
|
leaves: [
|
|
@@ -264,10 +258,13 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
264
258
|
{ field: "repo", from: "repository.display", flag: "ak-collector-repo" },
|
|
265
259
|
{ field: "pr", from: "prNumber", text: true, flag: "ak-collector-pr" },
|
|
266
260
|
{ field: "requestManifestPath", flag: "ak-collector-request-manifest" },
|
|
267
|
-
{ field: "waitMs", from: "waitWindowMs", text: true, flag: "ak-collector-wait-ms" },
|
|
268
261
|
],
|
|
269
262
|
activationStage: "load-and-install",
|
|
270
|
-
sessionMaterials: [
|
|
263
|
+
sessionMaterials: [
|
|
264
|
+
"CLAUDE.md",
|
|
265
|
+
"souls/collector.md",
|
|
266
|
+
"resources/collector-bot-handbook.md",
|
|
267
|
+
],
|
|
271
268
|
},
|
|
272
269
|
{
|
|
273
270
|
role: "doctor",
|