@akagilnc/pi-workflow-roles 0.1.3236 → 0.1.3256
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/grok/production-host.js +231 -48
- package/dist/package-contracts/collector-output.js +1 -1
- package/dist/package-contracts/terminating-infrastructure.js +12 -1
- package/dist/packaged-role-registry.js +2 -0
- package/dist/public-cli/main.js +27 -5
- package/package.json +1 -1
- package/souls/audit-law.md +10 -0
- package/souls/collector.md +1 -1
- package/souls/quality-law.md +7 -3
- package/src/collector-identity.ts +128 -81
- package/src/collector-ledger.ts +101 -19
- package/src/collector-receipt.ts +42 -7
- package/src/collector-role.ts +89 -9
- package/src/collector-tool-schemas.ts +25 -2
- package/src/host-contracts.ts +7 -1
- package/src/package-contracts/collector-output.ts +10 -2
- package/src/package-contracts/terminating-infrastructure.ts +12 -0
- package/src/packaged-role-registry.ts +2 -0
- package/src/public-cli/settlement.ts +16 -0
- package/src/role-runtime.ts +2 -0
- package/src/submission-ledger.ts +16 -10
package/souls/quality-law.md
CHANGED
|
@@ -8,17 +8,21 @@
|
|
|
8
8
|
|
|
9
9
|
取舍位次:过测试 → 显意图 → 无重复 → 最少元素。
|
|
10
10
|
|
|
11
|
+
尺子锚定仓内已接受的现状:与紧邻已并入代码同款的写法,不单独判新增行为缺陷;要判就按类判全仓。
|
|
12
|
+
|
|
11
13
|
## 测试对行为负责
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
测试只保**调用输入正确**的最短确定性案:从真实入口进入,在外部可见的结构化结果上断言,秒级完成。涉真实宿主、真实 LLM、真实并发、真实网络的行为,不写测试;以真跑为证。
|
|
16
|
+
|
|
17
|
+
修复的证明是**变异真跑**:装回旧逻辑,真实入口秒级报红;恢复后绿。命令与输出原文记入回执。不为证明修复造测试;拆掉的证明性测试不得换形再造。
|
|
14
18
|
|
|
15
|
-
测试向 spec
|
|
19
|
+
测试向 spec 负责,不向实现妥协:不得靠放松断言、删除失败路径或 mock 被测行为使测试变绿。
|
|
16
20
|
|
|
17
21
|
盯生成物一律禁止:不得对自由文本、LLM 产出内容、图片像素等任何生成产物建立机械依赖——锁措辞、锁句式、锁模板、数条数、埋哨兵、比像素,都算。契约断言只落在结构化字段上;生成物只可特征化观察,不可约束。
|
|
18
22
|
|
|
19
23
|
已有测试能表达真实行为的,修改或复用,不另建平行测试。只锁定内部结构、非契约文本、重复结论、失效行为或单纯覆盖率的测试,应当删除。
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
生产代码和测试代码都计算复杂度。新增任何测试,都必须证明它提供了现有体系没有的必要价值。
|
|
22
26
|
|
|
23
27
|
## 测试分级
|
|
24
28
|
|
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type GitHubMachineIdentity,
|
|
4
|
-
type GitHubPullRequestReaction,
|
|
5
|
-
type GitHubReview,
|
|
6
|
-
type GitHubReviewComment,
|
|
1
|
+
import type {
|
|
2
|
+
GitHubMachineIdentity,
|
|
7
3
|
} from "./collector-github.ts";
|
|
8
4
|
import type { CollectorEvidenceRecord, HeadRelation } from "./collector-evidence.ts";
|
|
9
|
-
|
|
10
|
-
export type GitHubIdentityMaterial = GitHubReview | GitHubIssueComment | GitHubReviewComment | GitHubPullRequestReaction;
|
|
5
|
+
import { CorrectableSubmissionError } from "./submission-correctable-error.ts";
|
|
11
6
|
|
|
12
7
|
export type CollectorMaterialRef = {
|
|
13
8
|
kind: "review" | "issue_comment" | "review_comment" | "reaction";
|
|
14
9
|
id: number;
|
|
15
|
-
/** Original review body, retained as uninterpreted material. */
|
|
16
|
-
body?: string;
|
|
17
10
|
/** Receipt-local immutable source reference. */
|
|
18
11
|
evidenceId?: string;
|
|
19
12
|
headRelation?: HeadRelation | "unbound";
|
|
20
13
|
};
|
|
21
14
|
|
|
15
|
+
/**
|
|
16
|
+
* #641 chain①: a receipt finding is a model-classified unit (splitting stays
|
|
17
|
+
* with the collector LLM) whose source pointer resolves against the stored
|
|
18
|
+
* evidence and whose machine locator (repo/PR/comment id/url/author/kind/时间)
|
|
19
|
+
* is enriched by the runtime from the same record — never transcribed bodies.
|
|
20
|
+
*/
|
|
22
21
|
export type CollectorFinding = {
|
|
23
|
-
identity: GitHubMachineIdentity;
|
|
22
|
+
identity: GitHubMachineIdentity | null;
|
|
24
23
|
source: CollectorMaterialRef;
|
|
25
|
-
category
|
|
26
|
-
|
|
24
|
+
category?: string;
|
|
25
|
+
pointer: {
|
|
26
|
+
repository: string;
|
|
27
|
+
prNumber: number;
|
|
28
|
+
commentId: number;
|
|
29
|
+
htmlUrl?: string;
|
|
30
|
+
authorLogin?: string;
|
|
31
|
+
kind: CollectorMaterialRef["kind"];
|
|
32
|
+
authoritativeTime?: string | null;
|
|
33
|
+
};
|
|
27
34
|
};
|
|
28
35
|
|
|
29
36
|
export type CollectorIdentityGroup = {
|
|
@@ -35,12 +42,10 @@ export type CollectorIdentityGroup = {
|
|
|
35
42
|
materials: CollectorMaterialRef[];
|
|
36
43
|
};
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return "issue_comment";
|
|
43
|
-
}
|
|
45
|
+
export type ExtractedCollectorIdentityGroup = CollectorIdentityGroup & {
|
|
46
|
+
attendance: true;
|
|
47
|
+
findings: CollectorFinding[];
|
|
48
|
+
};
|
|
44
49
|
|
|
45
50
|
function identityKey(identity: GitHubMachineIdentity | null): string {
|
|
46
51
|
if (identity === null) return "unassigned";
|
|
@@ -61,60 +66,10 @@ function mergeMachineIdentity(
|
|
|
61
66
|
return observed.userType < current.userType ? observed : current;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const groups = new Map<string, CollectorIdentityGroup>();
|
|
69
|
-
for (const material of materials) {
|
|
70
|
-
const identity = material.machineIdentity ?? null;
|
|
71
|
-
const key = identityKey(identity);
|
|
72
|
-
let group = groups.get(key);
|
|
73
|
-
if (group === undefined) {
|
|
74
|
-
group = {
|
|
75
|
-
identity,
|
|
76
|
-
...(material.userLogin === null ? {} : { displayLogin: material.userLogin }),
|
|
77
|
-
materials: [],
|
|
78
|
-
};
|
|
79
|
-
groups.set(key, group);
|
|
80
|
-
} else {
|
|
81
|
-
group.identity = mergeMachineIdentity(group.identity, identity);
|
|
82
|
-
}
|
|
83
|
-
const kind = materialKind(material);
|
|
84
|
-
group.materials.push({
|
|
85
|
-
kind,
|
|
86
|
-
id: material.id,
|
|
87
|
-
...(kind === "review" && "body" in material ? { body: material.body } : {}),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return [...groups.values()];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const CODEX_USER_ID = 199175422;
|
|
94
|
-
const CODERABBIT_USER_ID = 136622811;
|
|
95
|
-
export type ExtractedCollectorIdentityGroup = CollectorIdentityGroup & {
|
|
96
|
-
attendance: true;
|
|
97
|
-
findings: CollectorFinding[];
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/** Extract attendance and provider findings without using login/display text as identity. */
|
|
101
|
-
export function extractGitHubIdentityGroups(materials: readonly GitHubIdentityMaterial[]): ExtractedCollectorIdentityGroup[] {
|
|
102
|
-
const groups = groupGitHubMaterialsByIdentity(materials);
|
|
103
|
-
const byKey = new Map(groups.map((group) => [identityKey(group.identity), group]));
|
|
104
|
-
for (const group of groups) {
|
|
105
|
-
group.attendance = true;
|
|
106
|
-
group.findings = [];
|
|
107
|
-
}
|
|
108
|
-
for (const material of materials) {
|
|
109
|
-
const identity = material.machineIdentity ?? null;
|
|
110
|
-
const group = byKey.get(identityKey(identity))!;
|
|
111
|
-
if (identity === null) continue;
|
|
112
|
-
const source = { kind: materialKind(material), id: material.id };
|
|
113
|
-
if (source.kind === "review_comment" && "body" in material && (identity.userId === CODEX_USER_ID || identity.userId === CODERABBIT_USER_ID)) {
|
|
114
|
-
group.findings!.push({ identity, source, category: "inline", body: material.body });
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return groups as ExtractedCollectorIdentityGroup[];
|
|
69
|
+
function headRelationFor(record: CollectorEvidenceRecord, targetHead: string): HeadRelation | "unbound" {
|
|
70
|
+
return record.commitOid === undefined || record.commitOid === null
|
|
71
|
+
? "unbound"
|
|
72
|
+
: record.commitOid === targetHead ? "current" : "prior";
|
|
118
73
|
}
|
|
119
74
|
|
|
120
75
|
/** Receipt adapter consuming the typed facts retained by transport normalization. */
|
|
@@ -131,11 +86,8 @@ export function extractCollectorEvidenceIdentityGroups(
|
|
|
131
86
|
const source: CollectorMaterialRef = {
|
|
132
87
|
kind,
|
|
133
88
|
id: record.githubId,
|
|
134
|
-
...(kind === "review" && record.body !== undefined ? { body: record.body } : {}),
|
|
135
89
|
evidenceId: record.evidenceId,
|
|
136
|
-
headRelation: record
|
|
137
|
-
? "unbound"
|
|
138
|
-
: record.commitOid === targetHead ? "current" : "prior",
|
|
90
|
+
headRelation: headRelationFor(record, targetHead),
|
|
139
91
|
};
|
|
140
92
|
const key = identityKey(identity);
|
|
141
93
|
let group = groups.get(key);
|
|
@@ -152,10 +104,105 @@ export function extractCollectorEvidenceIdentityGroups(
|
|
|
152
104
|
group.identity = mergeMachineIdentity(group.identity, identity);
|
|
153
105
|
}
|
|
154
106
|
group.materials.push(source);
|
|
155
|
-
if (identity === null || record.body === undefined) continue;
|
|
156
|
-
if (kind === "review_comment" && (identity.userId === CODEX_USER_ID || identity.userId === CODERABBIT_USER_ID)) {
|
|
157
|
-
group.findings.push({ identity, source: { ...source }, category: "inline", body: record.body });
|
|
158
|
-
}
|
|
159
107
|
}
|
|
160
108
|
return [...groups.values()];
|
|
161
109
|
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* #641 chain①: pointer-open failures are model misuse, not host failures. The
|
|
113
|
+
* seat rejects them as correctable so the model can retry with a stored
|
|
114
|
+
* evidenceId — on Pi and Grok/ACP alike (第 0 条: 模型提交方式可纠正).
|
|
115
|
+
*/
|
|
116
|
+
export class CollectorUnknownEvidenceError extends CorrectableSubmissionError {
|
|
117
|
+
constructor(evidenceId: string) {
|
|
118
|
+
super(`未在本局已观测材料中找到 evidenceId ${evidenceId};请用 observe 返回的指针重试。`);
|
|
119
|
+
this.name = "CollectorUnknownEvidenceError";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* #641 chain①: any malformed findings submission is a model misuse the model
|
|
125
|
+
* can correct and resubmit — a branded correctable rejection on every supported
|
|
126
|
+
* engine, never a round infrastructure failure.
|
|
127
|
+
*/
|
|
128
|
+
export class CollectorFindingsValidationError extends CorrectableSubmissionError {
|
|
129
|
+
constructor(message: string) {
|
|
130
|
+
super(message);
|
|
131
|
+
this.name = "CollectorFindingsValidationError";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* #641 chain①: turn model-submitted finding pointer refs into receipt findings.
|
|
137
|
+
* Each pointer must resolve to a stored text-bearing evidence record; the
|
|
138
|
+
* machine locator is enriched from the same record so receipt and volume agree
|
|
139
|
+
* (指针可解析、开卷相符) by construction. Throws branded correctable
|
|
140
|
+
* (non-fatal, model-visible) errors for unresolvable or mis-typed findings.
|
|
141
|
+
*/
|
|
142
|
+
export function enrichCollectorFindings(input: {
|
|
143
|
+
candidate: unknown;
|
|
144
|
+
records: readonly CollectorEvidenceRecord[];
|
|
145
|
+
groups: readonly ExtractedCollectorIdentityGroup[];
|
|
146
|
+
targetHead: string;
|
|
147
|
+
repository: string;
|
|
148
|
+
prNumber: number;
|
|
149
|
+
}): void {
|
|
150
|
+
const candidate = input.candidate;
|
|
151
|
+
if (candidate === undefined || candidate === null) return;
|
|
152
|
+
if (typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
153
|
+
throw new CollectorFindingsValidationError("通进司交件参数必须为对象");
|
|
154
|
+
}
|
|
155
|
+
const rawFindings = (candidate as { findings?: unknown }).findings;
|
|
156
|
+
if (rawFindings === undefined) return;
|
|
157
|
+
if (!Array.isArray(rawFindings)) {
|
|
158
|
+
throw new CollectorFindingsValidationError("通进司 findings 必须为数组");
|
|
159
|
+
}
|
|
160
|
+
const byEvidenceId = new Map(input.records.map((record) => [record.evidenceId, record]));
|
|
161
|
+
for (const raw of rawFindings) {
|
|
162
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
163
|
+
throw new CollectorFindingsValidationError("通进司 finding 必须为对象");
|
|
164
|
+
}
|
|
165
|
+
const evidenceId = (raw as { evidenceId?: unknown }).evidenceId;
|
|
166
|
+
if (typeof evidenceId !== "string" || evidenceId.length === 0) {
|
|
167
|
+
throw new CollectorFindingsValidationError("通进司 finding 缺少可解析的 evidenceId 指针");
|
|
168
|
+
}
|
|
169
|
+
const record = byEvidenceId.get(evidenceId);
|
|
170
|
+
if (record === undefined) {
|
|
171
|
+
throw new CollectorUnknownEvidenceError(evidenceId);
|
|
172
|
+
}
|
|
173
|
+
if (record.kind !== "review" && record.kind !== "issue_comment" && record.kind !== "review_comment") {
|
|
174
|
+
throw new CollectorFindingsValidationError(`通进司 finding 指针指向不可承 finding 的证据种类 ${record.kind}`);
|
|
175
|
+
}
|
|
176
|
+
if (record.githubId === undefined) {
|
|
177
|
+
throw new CollectorFindingsValidationError(`通进司 finding 指针证据 ${evidenceId} 缺少 GitHub id`);
|
|
178
|
+
}
|
|
179
|
+
const category = (raw as { category?: unknown }).category;
|
|
180
|
+
if (category !== undefined && (typeof category !== "string" || category.trim().length === 0)) {
|
|
181
|
+
throw new CollectorFindingsValidationError("通进司 finding category 必须为非空字符串");
|
|
182
|
+
}
|
|
183
|
+
const identity = record.machineIdentity ?? null;
|
|
184
|
+
const group = input.groups.find((candidateGroup) => identityKey(candidateGroup.identity) === identityKey(identity));
|
|
185
|
+
if (group === undefined) {
|
|
186
|
+
throw new CollectorFindingsValidationError(`通进司 finding 指针证据 ${evidenceId} 无归属身份组`);
|
|
187
|
+
}
|
|
188
|
+
group.findings.push({
|
|
189
|
+
identity,
|
|
190
|
+
source: {
|
|
191
|
+
kind: record.kind,
|
|
192
|
+
id: record.githubId,
|
|
193
|
+
evidenceId: record.evidenceId,
|
|
194
|
+
headRelation: headRelationFor(record, input.targetHead),
|
|
195
|
+
},
|
|
196
|
+
...(category === undefined ? {} : { category: category.trim() }),
|
|
197
|
+
pointer: {
|
|
198
|
+
repository: input.repository,
|
|
199
|
+
prNumber: input.prNumber,
|
|
200
|
+
commentId: record.githubId,
|
|
201
|
+
...(record.htmlUrl === undefined ? {} : { htmlUrl: record.htmlUrl }),
|
|
202
|
+
...(record.authorLogin === undefined ? {} : { authorLogin: record.authorLogin }),
|
|
203
|
+
kind: record.kind,
|
|
204
|
+
authoritativeTime: record.authoritativeTime ?? null,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
package/src/collector-ledger.ts
CHANGED
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
type CollectorClock,
|
|
17
17
|
type CollectorEvidenceRecord,
|
|
18
18
|
type CollectorSnapshot,
|
|
19
|
+
type HeadRelation,
|
|
20
|
+
type WindowRelation,
|
|
19
21
|
} from "./collector-evidence.ts";
|
|
20
22
|
import {
|
|
21
23
|
buildCollectorRequestBody,
|
|
@@ -26,22 +28,92 @@ import {
|
|
|
26
28
|
import {
|
|
27
29
|
collectorObserveArgsSchema,
|
|
28
30
|
collectorOutputArgsSchema,
|
|
31
|
+
collectorReadArgsSchema,
|
|
29
32
|
collectorRequestArgsSchema,
|
|
30
33
|
collectorWaitArgsSchema,
|
|
31
34
|
} from "./collector-tool-schemas.ts";
|
|
32
35
|
import { COLLECTOR_OUTPUT_TOOL } from "./package-contracts/collector-output.ts";
|
|
33
36
|
|
|
34
37
|
export const COLLECTOR_OBSERVE_TOOL = "ak_collector_observe";
|
|
38
|
+
export const COLLECTOR_READ_TOOL = "ak_collector_read";
|
|
35
39
|
export const COLLECTOR_REQUEST_TOOL = "ak_collector_request";
|
|
36
40
|
export const COLLECTOR_WAIT_TOOL = "ak_collector_wait";
|
|
37
41
|
export { COLLECTOR_OUTPUT_TOOL };
|
|
38
42
|
|
|
39
43
|
export const COLLECTOR_OPERATIONAL_TOOLS = [
|
|
40
44
|
COLLECTOR_OBSERVE_TOOL,
|
|
45
|
+
COLLECTOR_READ_TOOL,
|
|
41
46
|
COLLECTOR_REQUEST_TOOL,
|
|
42
47
|
COLLECTOR_WAIT_TOOL,
|
|
43
48
|
] as const;
|
|
44
49
|
|
|
50
|
+
/**
|
|
51
|
+
* #641 chain①: observe context projects at most this many bytes of each
|
|
52
|
+
* material body; the full body stays reachable via the record pointer
|
|
53
|
+
* (evidenceId + htmlUrl) and is preserved in the volume, never unconditionally
|
|
54
|
+
* transcribed into model context or receipt.
|
|
55
|
+
*/
|
|
56
|
+
export const COLLECTOR_OBSERVE_BODY_HEAD_BYTES = 512;
|
|
57
|
+
|
|
58
|
+
function utf8SafePrefix(text: string, maxBytes: number): string {
|
|
59
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
60
|
+
const bytes = Buffer.from(text, "utf8");
|
|
61
|
+
let end = maxBytes;
|
|
62
|
+
while (end > 0 && (bytes[end]! & 0xc0) === 0x80) end -= 1;
|
|
63
|
+
return `${bytes.subarray(0, end).toString("utf8")}…`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type ObserveEvidenceEntry = {
|
|
67
|
+
evidenceId: string;
|
|
68
|
+
kind: string;
|
|
69
|
+
authorLogin?: string | undefined;
|
|
70
|
+
state?: string | undefined;
|
|
71
|
+
body?: string | undefined;
|
|
72
|
+
commitOid?: string | null | undefined;
|
|
73
|
+
htmlUrl?: string | undefined;
|
|
74
|
+
path?: string | undefined;
|
|
75
|
+
line?: number | null | undefined;
|
|
76
|
+
side?: string | null | undefined;
|
|
77
|
+
authoritativeTime?: string | null | undefined;
|
|
78
|
+
windowRelation?: WindowRelation | undefined;
|
|
79
|
+
pullRequestReviewId?: number | null | undefined;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type ObserveModelView = {
|
|
83
|
+
snapshotId: string;
|
|
84
|
+
observedAt: string;
|
|
85
|
+
completedAt: string;
|
|
86
|
+
prState: string;
|
|
87
|
+
headOid: string;
|
|
88
|
+
complete: boolean;
|
|
89
|
+
evidence: ObserveEvidenceEntry[];
|
|
90
|
+
requestAttempts: Array<{
|
|
91
|
+
attemptId: string;
|
|
92
|
+
requestId: string;
|
|
93
|
+
observedHead: string;
|
|
94
|
+
status: string;
|
|
95
|
+
marker: string;
|
|
96
|
+
recoverySnapshotId?: string | undefined;
|
|
97
|
+
}>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* #641 chain① model-context projection: identical shape to the volume view,
|
|
102
|
+
* with every material body bounded to a head preview. Full bodies remain
|
|
103
|
+
* pointer-reachable (evidenceId/htmlUrl) and stay in the ledger volume
|
|
104
|
+
* (collector-side; provider-visible observe details carry only this bounded
|
|
105
|
+
* projection), never unconditionally transcribed into model context.
|
|
106
|
+
*/
|
|
107
|
+
export function projectObserveContextView(modelView: ObserveModelView): ObserveModelView {
|
|
108
|
+
return {
|
|
109
|
+
...modelView,
|
|
110
|
+
evidence: modelView.evidence.map((entry) =>
|
|
111
|
+
entry.body === undefined
|
|
112
|
+
? entry
|
|
113
|
+
: { ...entry, body: utf8SafePrefix(entry.body, COLLECTOR_OBSERVE_BODY_HEAD_BYTES) }),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
45
117
|
export type CollectorOperationalTool = (typeof COLLECTOR_OPERATIONAL_TOOLS)[number];
|
|
46
118
|
|
|
47
119
|
export type CollectorRequestAttempt = {
|
|
@@ -117,7 +189,7 @@ export type CollectorLedger = {
|
|
|
117
189
|
signal?: AbortSignal,
|
|
118
190
|
): Promise<{
|
|
119
191
|
snapshot: CollectorSnapshot;
|
|
120
|
-
|
|
192
|
+
contextView: ObserveModelView;
|
|
121
193
|
}>;
|
|
122
194
|
|
|
123
195
|
request(
|
|
@@ -157,6 +229,8 @@ export function collectorToolArgumentsValid(
|
|
|
157
229
|
switch (name) {
|
|
158
230
|
case COLLECTOR_OBSERVE_TOOL:
|
|
159
231
|
return Value.Check(collectorObserveArgsSchema, args);
|
|
232
|
+
case COLLECTOR_READ_TOOL:
|
|
233
|
+
return Value.Check(collectorReadArgsSchema, args);
|
|
160
234
|
case COLLECTOR_REQUEST_TOOL:
|
|
161
235
|
return Value.Check(collectorRequestArgsSchema, args);
|
|
162
236
|
case COLLECTOR_WAIT_TOOL:
|
|
@@ -566,7 +640,7 @@ export function createCollectorLedger(config: CollectorConfigState): CollectorLe
|
|
|
566
640
|
attempts,
|
|
567
641
|
});
|
|
568
642
|
|
|
569
|
-
return { snapshot, modelView };
|
|
643
|
+
return { snapshot, contextView: projectObserveContextView(modelView) };
|
|
570
644
|
},
|
|
571
645
|
|
|
572
646
|
async request(input, transport, clock, signal) {
|
|
@@ -780,12 +854,35 @@ export function createCollectorLedger(config: CollectorConfigState): CollectorLe
|
|
|
780
854
|
return ledger;
|
|
781
855
|
}
|
|
782
856
|
|
|
857
|
+
/**
|
|
858
|
+
* #641 chain①: single authoritative evidence-record projection shared by the
|
|
859
|
+
* observe model view and the pointer-open read tool.
|
|
860
|
+
*/
|
|
861
|
+
export function projectEvidenceEntryView(record: CollectorEvidenceRecord): ObserveEvidenceEntry {
|
|
862
|
+
return {
|
|
863
|
+
evidenceId: record.evidenceId,
|
|
864
|
+
kind: record.kind,
|
|
865
|
+
authorLogin: record.authorLogin,
|
|
866
|
+
state: record.state,
|
|
867
|
+
body: record.body,
|
|
868
|
+
commitOid: record.commitOid,
|
|
869
|
+
htmlUrl: record.htmlUrl,
|
|
870
|
+
path: record.path,
|
|
871
|
+
// Single display fallback: current line, else originalLine.
|
|
872
|
+
line: record.line ?? record.originalLine,
|
|
873
|
+
side: record.side,
|
|
874
|
+
authoritativeTime: record.authoritativeTime,
|
|
875
|
+
windowRelation: record.windowRelation,
|
|
876
|
+
pullRequestReviewId: record.pullRequestReviewId,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
783
880
|
function buildObserveModelView(input: {
|
|
784
881
|
snapshot: CollectorSnapshot;
|
|
785
882
|
records: readonly CollectorEvidenceRecord[];
|
|
786
883
|
requesterLogin: string | undefined;
|
|
787
884
|
attempts: readonly CollectorRequestAttempt[];
|
|
788
|
-
}):
|
|
885
|
+
}): ObserveModelView {
|
|
789
886
|
const relevant = input.records;
|
|
790
887
|
return {
|
|
791
888
|
snapshotId: input.snapshot.snapshotId,
|
|
@@ -794,22 +891,7 @@ function buildObserveModelView(input: {
|
|
|
794
891
|
prState: input.snapshot.prState,
|
|
795
892
|
headOid: input.snapshot.headOid,
|
|
796
893
|
complete: input.snapshot.complete,
|
|
797
|
-
evidence: relevant.map((record) => (
|
|
798
|
-
evidenceId: record.evidenceId,
|
|
799
|
-
kind: record.kind,
|
|
800
|
-
authorLogin: record.authorLogin,
|
|
801
|
-
state: record.state,
|
|
802
|
-
body: record.body,
|
|
803
|
-
commitOid: record.commitOid,
|
|
804
|
-
htmlUrl: record.htmlUrl,
|
|
805
|
-
path: record.path,
|
|
806
|
-
// Single display fallback: current line, else originalLine.
|
|
807
|
-
line: record.line ?? record.originalLine,
|
|
808
|
-
side: record.side,
|
|
809
|
-
authoritativeTime: record.authoritativeTime,
|
|
810
|
-
windowRelation: record.windowRelation,
|
|
811
|
-
pullRequestReviewId: record.pullRequestReviewId,
|
|
812
|
-
})),
|
|
894
|
+
evidence: relevant.map((record): ObserveEvidenceEntry => projectEvidenceEntryView(record)),
|
|
813
895
|
requestAttempts: input.attempts.map((attempt) => ({
|
|
814
896
|
attemptId: attempt.attemptId,
|
|
815
897
|
requestId: attempt.requestId,
|
package/src/collector-receipt.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { COLLECTOR_HOST } from "./collector-config.ts";
|
|
2
2
|
import type { CollectorClock, CollectorEvidenceRecord, CollectorSnapshot } from "./collector-evidence.ts";
|
|
3
3
|
import type { CollectorLedger, CollectorRequestAttempt } from "./collector-ledger.ts";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
enrichCollectorFindings,
|
|
6
|
+
extractCollectorEvidenceIdentityGroups,
|
|
7
|
+
type ExtractedCollectorIdentityGroup,
|
|
8
|
+
} from "./collector-identity.ts";
|
|
9
|
+
import { validateAcceptedCollectorReceipt, type CollectorEvidenceRecord as ReceiptEvidenceRecord } from "./package-contracts/collector-output.ts";
|
|
6
10
|
|
|
7
11
|
export { validateAcceptedCollectorReceipt };
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* #641 chain① receipt-local evidence projection: machine identity, integrity
|
|
15
|
+
* digest and resolvable pointer fields only. Bodies/raw stay in the volume
|
|
16
|
+
* (observe details + GitHub pointer); they are never transcribed into the receipt.
|
|
17
|
+
*/
|
|
18
|
+
export type CollectorReceiptEvidenceRecord = ReceiptEvidenceRecord;
|
|
19
|
+
|
|
9
20
|
export type CollectorReceipt = {
|
|
10
21
|
host: "github.com";
|
|
11
22
|
repository: string;
|
|
@@ -19,15 +30,15 @@ export type CollectorReceipt = {
|
|
|
19
30
|
groups: ExtractedCollectorIdentityGroup[];
|
|
20
31
|
requestAttempts: CollectorRequestAttempt[];
|
|
21
32
|
snapshots: CollectorSnapshot[];
|
|
22
|
-
evidenceRecords:
|
|
33
|
+
evidenceRecords: CollectorReceiptEvidenceRecord[];
|
|
23
34
|
};
|
|
24
35
|
|
|
25
36
|
function fail(message: string): never { throw new Error(message); }
|
|
26
37
|
|
|
27
|
-
/** Runtime output
|
|
38
|
+
/** Runtime output assembles findings pointers from the model submission into the typed receipt. */
|
|
28
39
|
export function buildCollectorReceipt(
|
|
29
40
|
ledger: CollectorLedger,
|
|
30
|
-
|
|
41
|
+
candidateRaw: unknown,
|
|
31
42
|
clock?: CollectorClock,
|
|
32
43
|
): CollectorReceipt {
|
|
33
44
|
ledger.assertNotFatal();
|
|
@@ -46,7 +57,7 @@ export function buildCollectorReceipt(
|
|
|
46
57
|
if (finalSnapshot === undefined || !finalSnapshot.complete) fail("Collector final snapshot is incomplete");
|
|
47
58
|
if (finalSnapshot.prState !== "OPEN") fail("Collector final snapshot PR state is not OPEN");
|
|
48
59
|
|
|
49
|
-
const evidenceRecords = [...ledger.allEvidence()];
|
|
60
|
+
const evidenceRecords: CollectorEvidenceRecord[] = [...ledger.allEvidence()];
|
|
50
61
|
const snapshots = [...ledger.allSnapshots()];
|
|
51
62
|
const evidenceIndex = new Map(evidenceRecords.map((record) => [record.evidenceId, record]));
|
|
52
63
|
const snapshotIndex = new Map(snapshots.map((snapshot) => [snapshot.snapshotId, snapshot]));
|
|
@@ -59,6 +70,16 @@ export function buildCollectorReceipt(
|
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
const groups = extractCollectorEvidenceIdentityGroups(evidenceRecords, finalSnapshot.headOid);
|
|
73
|
+
// #641 chain①: the collector LLM submits findings as pointer refs; the runtime
|
|
74
|
+
// enriches each with the machine locator from the same stored record.
|
|
75
|
+
enrichCollectorFindings({
|
|
76
|
+
candidate: candidateRaw,
|
|
77
|
+
records: evidenceRecords,
|
|
78
|
+
groups,
|
|
79
|
+
targetHead: finalSnapshot.headOid,
|
|
80
|
+
repository: ledger.config.repository.canonical,
|
|
81
|
+
prNumber: ledger.config.prNumber,
|
|
82
|
+
});
|
|
62
83
|
for (const group of groups) {
|
|
63
84
|
if (group.attendance !== true) fail("Collector group lacks attendance");
|
|
64
85
|
for (const material of group.materials) {
|
|
@@ -82,6 +103,20 @@ export function buildCollectorReceipt(
|
|
|
82
103
|
groups,
|
|
83
104
|
requestAttempts: [...ledger.requestAttempts()],
|
|
84
105
|
snapshots,
|
|
85
|
-
evidenceRecords,
|
|
106
|
+
evidenceRecords: evidenceRecords.map(toReceiptEvidenceRecord),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function toReceiptEvidenceRecord(record: CollectorEvidenceRecord): CollectorReceiptEvidenceRecord {
|
|
111
|
+
return {
|
|
112
|
+
evidenceId: record.evidenceId,
|
|
113
|
+
kind: record.kind,
|
|
114
|
+
versionId: record.versionId,
|
|
115
|
+
contentDigest: record.contentDigest,
|
|
116
|
+
firstObservedAt: record.firstObservedAt,
|
|
117
|
+
...(record.githubId === undefined ? {} : { githubId: record.githubId }),
|
|
118
|
+
...(record.authorLogin === undefined ? {} : { authorLogin: record.authorLogin }),
|
|
119
|
+
...(record.htmlUrl === undefined ? {} : { htmlUrl: record.htmlUrl }),
|
|
120
|
+
...(record.authoritativeTime === undefined ? {} : { authoritativeTime: record.authoritativeTime }),
|
|
86
121
|
};
|
|
87
122
|
}
|