@akagilnc/pi-workflow-roles 0.1.1751

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +104 -0
  3. package/README.zh-CN.md +133 -0
  4. package/THIRD_PARTY_NOTICES.md +60 -0
  5. package/dist/activation-ledger-git.js +68 -0
  6. package/dist/activation-ledger-session.js +120 -0
  7. package/dist/activation-ledger-topology.js +239 -0
  8. package/dist/activation-reconciliation.js +61 -0
  9. package/dist/audit-escalation.js +108 -0
  10. package/dist/auditor-dossier-tool.js +35 -0
  11. package/dist/canonical-json.js +78 -0
  12. package/dist/compliance-transport.js +77 -0
  13. package/dist/doctor-contracts.js +172 -0
  14. package/dist/dossier-resolution.js +103 -0
  15. package/dist/evidence-child-executor.js +661 -0
  16. package/dist/exact-utf8.js +12 -0
  17. package/dist/git-object-id.js +7 -0
  18. package/dist/in-process-session.js +50 -0
  19. package/dist/merger-contracts.js +76 -0
  20. package/dist/navigator-attendance.js +995 -0
  21. package/dist/navigator-invocation-identity.js +220 -0
  22. package/dist/open-tool-schema.js +39 -0
  23. package/dist/package-contracts/collector-output.js +50 -0
  24. package/dist/package-contracts/fixer-output.js +72 -0
  25. package/dist/package-contracts/fixer-packet.js +77 -0
  26. package/dist/package-contracts/judge-output.js +17 -0
  27. package/dist/package-contracts/reviewer-output.js +82 -0
  28. package/dist/package-contracts/terminating-tools.js +173 -0
  29. package/dist/package-contracts/worker-output.js +13 -0
  30. package/dist/package-owned-tool-idle.js +104 -0
  31. package/dist/packaged-role-registry.js +34 -0
  32. package/dist/public-cli/main.js +23867 -0
  33. package/dist/public-command-renderer.js +20 -0
  34. package/dist/reviewer-agent.js +93 -0
  35. package/dist/reviewer-child-executor.js +23 -0
  36. package/dist/reviewer-construction.js +95 -0
  37. package/dist/reviewer-dispatch.js +77 -0
  38. package/dist/reviewer-execution-ledger.js +160 -0
  39. package/dist/reviewer-failure-diagnostic.js +17 -0
  40. package/dist/reviewer-git-snapshot.js +38 -0
  41. package/dist/reviewer-pinned-git.js +146 -0
  42. package/dist/reviewer-preflight-error.js +15 -0
  43. package/dist/reviewer-prompt-identity.js +10 -0
  44. package/dist/reviewer-scope-prompt.js +21 -0
  45. package/dist/reviewer-workspace.js +151 -0
  46. package/dist/sha256.js +5 -0
  47. package/dist/sitian-record-entry.js +33 -0
  48. package/dist/stderr-jsonl.js +26 -0
  49. package/dist/stream-idle-guard.js +75 -0
  50. package/dist/tool-execution-observation.js +141 -0
  51. package/dist/uuidv7.js +21 -0
  52. package/dist/work-subject-identity.js +53 -0
  53. package/extensions/role-runtime.ts +303 -0
  54. package/package.json +69 -0
  55. package/packets/fixer-prerequisites.json +6 -0
  56. package/packets/fixer-repair.md +5 -0
  57. package/packets/judge-apply.md +77 -0
  58. package/packets/judge-authority.md +64 -0
  59. package/packets/judge-plan.md +55 -0
  60. package/packets/judge-review.md +49 -0
  61. package/packets/judge-submission.md +34 -0
  62. package/resources/methods/code-review/SKILL.md +92 -0
  63. package/resources/methods/code-review/agents/openai.yaml +3 -0
  64. package/resources/methods/code-review/provenance.json +26 -0
  65. package/resources/methods/diagnosing-bugs/SKILL.md +134 -0
  66. package/resources/methods/diagnosing-bugs/agents/openai.yaml +3 -0
  67. package/resources/methods/diagnosing-bugs/provenance.json +31 -0
  68. package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  69. package/resources/methods/resolving-merge-conflicts/SKILL.md +14 -0
  70. package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +3 -0
  71. package/resources/methods/resolving-merge-conflicts/provenance.json +26 -0
  72. package/resources/methods/tdd/SKILL.md +38 -0
  73. package/resources/methods/tdd/agents/openai.yaml +3 -0
  74. package/resources/methods/tdd/mocking.md +59 -0
  75. package/resources/methods/tdd/provenance.json +36 -0
  76. package/resources/methods/tdd/tests.md +77 -0
  77. package/resources/navigator-route-playbook.md +32 -0
  78. package/schemas/tool-execution-observation.schema.json +107 -0
  79. package/scripts/build-package.mjs +65 -0
  80. package/scripts/generate-tool-execution-observation-schema.ts +7 -0
  81. package/souls/coder.md +10 -0
  82. package/souls/collector.md +11 -0
  83. package/souls/doctor-auditor.md +23 -0
  84. package/souls/doctor.md +8 -0
  85. package/souls/fixer-auditor.md +33 -0
  86. package/souls/fixer.md +13 -0
  87. package/souls/judge-auditor.md +33 -0
  88. package/souls/judge.md +74 -0
  89. package/souls/merger.md +5 -0
  90. package/souls/navigator.md +5 -0
  91. package/souls/reviewer-auditor.md +25 -0
  92. package/souls/reviewer.md +11 -0
  93. package/src/activation-ledger-git.ts +96 -0
  94. package/src/activation-ledger-session.ts +188 -0
  95. package/src/activation-ledger-topology.ts +301 -0
  96. package/src/activation-ledger.ts +240 -0
  97. package/src/activation-reconciliation.ts +163 -0
  98. package/src/activation-trace.ts +38 -0
  99. package/src/audit-escalation.ts +177 -0
  100. package/src/auditor-dossier-tool.ts +48 -0
  101. package/src/auditor-soul.ts +28 -0
  102. package/src/canonical-json.ts +74 -0
  103. package/src/canonical-skill-binding.ts +107 -0
  104. package/src/collector-config.ts +89 -0
  105. package/src/collector-evidence.ts +461 -0
  106. package/src/collector-github.ts +656 -0
  107. package/src/collector-identity.ts +161 -0
  108. package/src/collector-ledger.ts +827 -0
  109. package/src/collector-receipt.ts +87 -0
  110. package/src/collector-role.ts +592 -0
  111. package/src/collector-tool-schemas.ts +19 -0
  112. package/src/compliance-transport.ts +130 -0
  113. package/src/doctor-auditor.ts +53 -0
  114. package/src/doctor-contracts.ts +166 -0
  115. package/src/doctor-evidence.ts +47 -0
  116. package/src/doctor-role.ts +18 -0
  117. package/src/dossier-resolution.ts +137 -0
  118. package/src/evidence-child-executor.ts +775 -0
  119. package/src/exact-utf8.ts +9 -0
  120. package/src/factory-board.ts +1822 -0
  121. package/src/git-object-id.ts +11 -0
  122. package/src/human-format.ts +65 -0
  123. package/src/in-process-session.ts +78 -0
  124. package/src/judge-auditor.ts +55 -0
  125. package/src/judge-recording-anti-forge.ts +53 -0
  126. package/src/judge-role.ts +160 -0
  127. package/src/merger-contracts.ts +71 -0
  128. package/src/merger-git-state.ts +76 -0
  129. package/src/merger-role.ts +60 -0
  130. package/src/navigator-attendance.ts +1254 -0
  131. package/src/navigator-invocation-identity.ts +446 -0
  132. package/src/open-tool-schema.ts +46 -0
  133. package/src/package-contracts/collector-output.ts +109 -0
  134. package/src/package-contracts/fixer-output.ts +81 -0
  135. package/src/package-contracts/fixer-packet.ts +93 -0
  136. package/src/package-contracts/judge-output.ts +39 -0
  137. package/src/package-contracts/reviewer-output.ts +115 -0
  138. package/src/package-contracts/terminating-tools.ts +259 -0
  139. package/src/package-contracts/worker-output.ts +36 -0
  140. package/src/package-owned-tool-idle.ts +134 -0
  141. package/src/package-resources/method-skill-binding.ts +87 -0
  142. package/src/package-resources/method-skill.ts +358 -0
  143. package/src/packaged-role-registry.ts +36 -0
  144. package/src/public-cli/cli-errors.ts +11 -0
  145. package/src/public-cli/cli-io.ts +4 -0
  146. package/src/public-cli/cli.ts +912 -0
  147. package/src/public-cli/coder-run.ts +575 -0
  148. package/src/public-cli/collector-run.ts +375 -0
  149. package/src/public-cli/command-renderer.ts +8 -0
  150. package/src/public-cli/config.ts +346 -0
  151. package/src/public-cli/doctor-run.ts +355 -0
  152. package/src/public-cli/explicit-internal.ts +274 -0
  153. package/src/public-cli/fixer-run.ts +587 -0
  154. package/src/public-cli/host-pi-runtime.ts +112 -0
  155. package/src/public-cli/invocation.ts +1958 -0
  156. package/src/public-cli/judge-run.ts +507 -0
  157. package/src/public-cli/main.ts +15 -0
  158. package/src/public-cli/merger-run.ts +681 -0
  159. package/src/public-cli/public-run-credentials.ts +71 -0
  160. package/src/public-cli/registry.ts +153 -0
  161. package/src/public-cli/reviewer-run.ts +561 -0
  162. package/src/public-cli/run-lifecycle.ts +884 -0
  163. package/src/public-cli/settlement.ts +3765 -0
  164. package/src/public-cli/terminal.ts +325 -0
  165. package/src/public-command-renderer.ts +43 -0
  166. package/src/reviewer-agent.ts +94 -0
  167. package/src/reviewer-auditor.ts +53 -0
  168. package/src/reviewer-child-executor.ts +31 -0
  169. package/src/reviewer-construction.ts +137 -0
  170. package/src/reviewer-dispatch.ts +94 -0
  171. package/src/reviewer-execution-ledger.ts +206 -0
  172. package/src/reviewer-failure-diagnostic.ts +18 -0
  173. package/src/reviewer-git-snapshot.ts +53 -0
  174. package/src/reviewer-pinned-git.ts +144 -0
  175. package/src/reviewer-preflight-error.ts +14 -0
  176. package/src/reviewer-prompt-identity.ts +17 -0
  177. package/src/reviewer-role.ts +193 -0
  178. package/src/reviewer-scope-prompt.ts +24 -0
  179. package/src/reviewer-settlement.ts +63 -0
  180. package/src/reviewer-workspace.ts +111 -0
  181. package/src/role-runtime.ts +884 -0
  182. package/src/sha256.ts +6 -0
  183. package/src/sitian-record-entry.ts +57 -0
  184. package/src/stderr-jsonl.ts +28 -0
  185. package/src/stream-idle-guard.ts +98 -0
  186. package/src/ticket-snapshot.ts +662 -0
  187. package/src/ticket-trajectory.ts +1000 -0
  188. package/src/tool-execution-observation.ts +168 -0
  189. package/src/uuidv7.ts +1 -0
  190. package/src/work-subject-identity.ts +94 -0
  191. package/src/worker-role.ts +434 -0
  192. package/src/worker-submission-gates.ts +225 -0
@@ -0,0 +1,461 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import type {
4
+ GitHubIssueComment,
5
+ GitHubMachineIdentity,
6
+ GitHubPageDiagnostics,
7
+ GitHubPullRequest,
8
+ GitHubPullRequestReaction,
9
+ GitHubReview,
10
+ GitHubReviewComment,
11
+ GitHubUser,
12
+ } from "./collector-github.ts";
13
+
14
+ export const COLLECTOR_ELIGIBILITY_MS = 15 * 60 * 1000;
15
+
16
+ export type WindowRelation = "before" | "within" | "after" | "uncertain";
17
+ export type HeadRelation = "current" | "prior";
18
+
19
+ export type CollectorEvidenceKind =
20
+ | "pull_request"
21
+ | "review"
22
+ | "reaction"
23
+ | "issue_comment"
24
+ | "review_comment"
25
+ | "authenticated_user"
26
+ | "request_attempt"
27
+ | "wait"
28
+ | "transport";
29
+
30
+ export type CollectorEvidenceRecord = {
31
+ evidenceId: string;
32
+ kind: CollectorEvidenceKind;
33
+ stableGitHubId?: string;
34
+ /** Typed GitHub facts retained from the transport's single normalization pass. */
35
+ githubId?: number;
36
+ machineIdentity?: GitHubMachineIdentity | null;
37
+ versionId: string;
38
+ contentDigest: string;
39
+ authorLogin?: string;
40
+ state?: string;
41
+ body?: string;
42
+ commitOid?: string | null;
43
+ htmlUrl?: string;
44
+ path?: string;
45
+ line?: number | null;
46
+ /** Outdated inline location when current line is null. */
47
+ originalLine?: number | null;
48
+ side?: string | null;
49
+ position?: number | null;
50
+ pullRequestReviewId?: number | null;
51
+ /** GitHub-supplied submission clock for reviews (metadata only). */
52
+ submittedAt?: string | null;
53
+ authoritativeTime?: string | null;
54
+ firstObservedAt: string;
55
+ windowRelation?: WindowRelation;
56
+ raw: unknown;
57
+ pagination?: {
58
+ surface: string;
59
+ complete: boolean;
60
+ pages: GitHubPageDiagnostics[];
61
+ };
62
+ };
63
+
64
+ export type CollectorSnapshot = {
65
+ snapshotId: string;
66
+ observedAt: string;
67
+ /** Wall clock when observe finished successfully. */
68
+ completedAt: string;
69
+ /** Monotonic ms when observe finished successfully. */
70
+ completedMono: number;
71
+ host: "github.com";
72
+ repository: string;
73
+ prNumber: number;
74
+ prState: string;
75
+ headOid: string;
76
+ complete: boolean;
77
+ evidenceIds: string[];
78
+ pageDiagnostics: GitHubPageDiagnostics[];
79
+ normalizedByteLength: number;
80
+ };
81
+
82
+ export type CollectorClock = {
83
+ wallNow(): Date;
84
+ monoNow(): number;
85
+ sleep(ms: number, signal?: AbortSignal): Promise<void>;
86
+ };
87
+
88
+ export function createSystemCollectorClock(): CollectorClock {
89
+ const start = process.hrtime.bigint();
90
+ return {
91
+ wallNow: () => new Date(),
92
+ monoNow: () => Number(process.hrtime.bigint() - start) / 1e6,
93
+ sleep: (ms, signal) =>
94
+ new Promise((resolve, reject) => {
95
+ if (signal?.aborted) {
96
+ reject(signal.reason ?? new Error("aborted"));
97
+ return;
98
+ }
99
+ const timer = setTimeout(resolve, ms);
100
+ const onAbort = () => {
101
+ clearTimeout(timer);
102
+ reject(signal?.reason ?? new Error("aborted"));
103
+ };
104
+ signal?.addEventListener("abort", onAbort, { once: true });
105
+ }),
106
+ };
107
+ }
108
+
109
+ export function sha256Text(text: string): string {
110
+ return createHash("sha256").update(text, "utf8").digest("hex");
111
+ }
112
+
113
+ export function computeWindowRelation(
114
+ authoritativeTime: string | null | undefined,
115
+ activationTime: Date,
116
+ deadlineTime: Date,
117
+ ): WindowRelation {
118
+ if (authoritativeTime === undefined || authoritativeTime === null || authoritativeTime.length === 0) {
119
+ return "uncertain";
120
+ }
121
+ const ms = Date.parse(authoritativeTime);
122
+ if (!Number.isFinite(ms)) return "uncertain";
123
+ const activationMs = activationTime.getTime();
124
+ const deadlineMs = deadlineTime.getTime();
125
+ if (ms < activationMs) return "before";
126
+ if (ms <= deadlineMs) return "within";
127
+ return "after";
128
+ }
129
+
130
+ function stableId(kind: string, githubId: string | number): string {
131
+ return `${kind}:${githubId}`;
132
+ }
133
+
134
+ function versionDigest(parts: Record<string, unknown>): string {
135
+ return sha256Text(JSON.stringify(parts));
136
+ }
137
+
138
+ export function evidenceIdFor(kind: string, versionId: string): string {
139
+ return sha256Text(`${kind}:${versionId}`).slice(0, 16);
140
+ }
141
+
142
+ export function normalizeAuthenticatedUserEvidence(
143
+ user: GitHubUser,
144
+ observedAt: string,
145
+ ): CollectorEvidenceRecord {
146
+ const login = user.login.toLowerCase();
147
+ const rawId = (user.raw as { id?: unknown } | undefined)?.id;
148
+ const stableNumericOrStringId =
149
+ typeof rawId === "number" || typeof rawId === "string" ? rawId : undefined;
150
+ // Retain only correlation identity — never the full /user profile.
151
+ const retainedRaw: { login: string; id?: number | string } = { login };
152
+ if (stableNumericOrStringId !== undefined) {
153
+ retainedRaw.id = stableNumericOrStringId;
154
+ }
155
+ const contentDigest = versionDigest({
156
+ login,
157
+ ...(stableNumericOrStringId !== undefined ? { id: stableNumericOrStringId } : {}),
158
+ });
159
+ const versionId = `user:${login}:${contentDigest.slice(0, 12)}`;
160
+ return {
161
+ evidenceId: evidenceIdFor("authenticated_user", versionId),
162
+ kind: "authenticated_user",
163
+ stableGitHubId: stableId("user", login),
164
+ versionId,
165
+ contentDigest,
166
+ authorLogin: login,
167
+ firstObservedAt: observedAt,
168
+ raw: retainedRaw,
169
+ };
170
+ }
171
+
172
+ export function normalizePullRequestEvidence(
173
+ pr: GitHubPullRequest,
174
+ observedAt: string,
175
+ ): CollectorEvidenceRecord {
176
+ const contentDigest = versionDigest({
177
+ number: pr.number,
178
+ state: pr.state,
179
+ headOid: pr.headOid,
180
+ updatedAt: pr.updatedAt ?? null,
181
+ htmlUrl: pr.url,
182
+ });
183
+ const versionId = `pr:${pr.number}:${contentDigest.slice(0, 12)}`;
184
+ return {
185
+ evidenceId: evidenceIdFor("pull_request", versionId),
186
+ kind: "pull_request",
187
+ stableGitHubId: stableId("pr", pr.number),
188
+ versionId,
189
+ contentDigest,
190
+ state: pr.state,
191
+ commitOid: pr.headOid,
192
+ htmlUrl: pr.url,
193
+ authoritativeTime: pr.updatedAt ?? null,
194
+ firstObservedAt: observedAt,
195
+ raw: pr.raw,
196
+ };
197
+ }
198
+
199
+ export function normalizeReviewEvidence(
200
+ review: GitHubReview,
201
+ observedAt: string,
202
+ ): CollectorEvidenceRecord {
203
+ const authorLogin =
204
+ review.userLogin === null || review.userLogin === undefined
205
+ ? undefined
206
+ : review.userLogin.toLowerCase();
207
+ const contentDigest = versionDigest({
208
+ id: review.id,
209
+ state: review.state,
210
+ body: review.body,
211
+ commitId: review.commitId,
212
+ submittedAt: review.submittedAt,
213
+ htmlUrl: review.htmlUrl,
214
+ userLogin: authorLogin ?? null,
215
+ machineIdentity: review.machineIdentity ?? null,
216
+ });
217
+ const versionId = `review:${review.id}:${contentDigest.slice(0, 12)}`;
218
+ return {
219
+ evidenceId: evidenceIdFor("review", versionId),
220
+ kind: "review",
221
+ stableGitHubId: stableId("review", review.id),
222
+ githubId: review.id,
223
+ machineIdentity: review.machineIdentity ?? null,
224
+ versionId,
225
+ contentDigest,
226
+ ...(authorLogin === undefined ? {} : { authorLogin }),
227
+ state: review.state,
228
+ body: review.body,
229
+ commitOid: review.commitId,
230
+ htmlUrl: review.htmlUrl,
231
+ // Submission metadata only; ledger history assigns authoritativeTime.
232
+ submittedAt: review.submittedAt,
233
+ authoritativeTime: review.submittedAt,
234
+ firstObservedAt: observedAt,
235
+ raw: review.raw,
236
+ };
237
+ }
238
+
239
+ export function normalizePullRequestReactionEvidence(
240
+ reaction: GitHubPullRequestReaction,
241
+ observedAt: string,
242
+ ): CollectorEvidenceRecord {
243
+ const authorLogin = reaction.userLogin?.toLowerCase();
244
+ const contentDigest = versionDigest({
245
+ id: reaction.id,
246
+ content: reaction.content,
247
+ createdAt: reaction.createdAt,
248
+ machineIdentity: reaction.machineIdentity ?? null,
249
+ });
250
+ const versionId = `reaction:${reaction.id}:${contentDigest.slice(0, 12)}`;
251
+ return {
252
+ evidenceId: evidenceIdFor("reaction", versionId),
253
+ kind: "reaction",
254
+ stableGitHubId: stableId("reaction", reaction.id),
255
+ githubId: reaction.id,
256
+ machineIdentity: reaction.machineIdentity ?? null,
257
+ versionId,
258
+ contentDigest,
259
+ ...(authorLogin === undefined ? {} : { authorLogin }),
260
+ body: reaction.content,
261
+ authoritativeTime: reaction.createdAt,
262
+ firstObservedAt: observedAt,
263
+ raw: reaction.raw,
264
+ };
265
+ }
266
+
267
+ export function normalizeIssueCommentEvidence(
268
+ comment: GitHubIssueComment,
269
+ observedAt: string,
270
+ ): CollectorEvidenceRecord {
271
+ const authorLogin =
272
+ comment.userLogin === null || comment.userLogin === undefined
273
+ ? undefined
274
+ : comment.userLogin.toLowerCase();
275
+ const contentDigest = versionDigest({
276
+ id: comment.id,
277
+ body: comment.body,
278
+ updatedAt: comment.updatedAt,
279
+ userLogin: authorLogin ?? null,
280
+ machineIdentity: comment.machineIdentity ?? null,
281
+ htmlUrl: comment.htmlUrl,
282
+ });
283
+ const versionId = `issue_comment:${comment.id}:${contentDigest.slice(0, 12)}`;
284
+ return {
285
+ evidenceId: evidenceIdFor("issue_comment", versionId),
286
+ kind: "issue_comment",
287
+ stableGitHubId: stableId("issue_comment", comment.id),
288
+ githubId: comment.id,
289
+ machineIdentity: comment.machineIdentity ?? null,
290
+ versionId,
291
+ contentDigest,
292
+ ...(authorLogin === undefined ? {} : { authorLogin }),
293
+ body: comment.body,
294
+ htmlUrl: comment.htmlUrl,
295
+ authoritativeTime: comment.updatedAt ?? null,
296
+ firstObservedAt: observedAt,
297
+ raw: comment.raw,
298
+ };
299
+ }
300
+
301
+ export function normalizeReviewCommentEvidence(
302
+ comment: GitHubReviewComment,
303
+ observedAt: string,
304
+ ): CollectorEvidenceRecord {
305
+ const authorLogin =
306
+ comment.userLogin === null || comment.userLogin === undefined
307
+ ? undefined
308
+ : comment.userLogin.toLowerCase();
309
+ const contentDigest = versionDigest({
310
+ id: comment.id,
311
+ body: comment.body,
312
+ path: comment.path,
313
+ line: comment.line,
314
+ originalLine: comment.originalLine,
315
+ side: comment.side,
316
+ position: comment.position,
317
+ updatedAt: comment.updatedAt,
318
+ commitId: comment.commitId,
319
+ pullRequestReviewId: comment.pullRequestReviewId,
320
+ userLogin: authorLogin ?? null,
321
+ machineIdentity: comment.machineIdentity ?? null,
322
+ htmlUrl: comment.htmlUrl,
323
+ });
324
+ const versionId = `review_comment:${comment.id}:${contentDigest.slice(0, 12)}`;
325
+ return {
326
+ evidenceId: evidenceIdFor("review_comment", versionId),
327
+ kind: "review_comment",
328
+ stableGitHubId: stableId("review_comment", comment.id),
329
+ githubId: comment.id,
330
+ machineIdentity: comment.machineIdentity ?? null,
331
+ versionId,
332
+ contentDigest,
333
+ ...(authorLogin === undefined ? {} : { authorLogin }),
334
+ body: comment.body,
335
+ commitOid: comment.commitId,
336
+ htmlUrl: comment.htmlUrl,
337
+ path: comment.path,
338
+ line: comment.line,
339
+ originalLine: comment.originalLine,
340
+ side: comment.side,
341
+ position: comment.position,
342
+ pullRequestReviewId: comment.pullRequestReviewId,
343
+ authoritativeTime: comment.updatedAt ?? null,
344
+ firstObservedAt: observedAt,
345
+ raw: comment.raw,
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Ledger history step: first distinct review version may keep submitted_at
351
+ * only when surface-completion mono is at or before deadlineMono; after-deadline
352
+ * first sightings and non-finite mono fail closed to null (no submitted_at
353
+ * backdate). Wall firstObservedAt is receipt/metadata only. Every later distinct
354
+ * review version without a GitHub version timestamp is uncertain. Known
355
+ * versionIds reuse the stored authoritative clock including null. Comments keep
356
+ * their own updated_at per version (null if missing).
357
+ */
358
+ export function applyEvidenceVersionHistory(
359
+ pending: CollectorEvidenceRecord[],
360
+ priorEvidence: readonly CollectorEvidenceRecord[],
361
+ cutoff: {
362
+ deadlineMono: number;
363
+ firstObservedMono: number;
364
+ },
365
+ ): void {
366
+ const versionsByStable = new Map<string, Set<string>>();
367
+ const add = (stableIdValue: string, versionId: string) => {
368
+ let set = versionsByStable.get(stableIdValue);
369
+ if (set === undefined) {
370
+ set = new Set<string>();
371
+ versionsByStable.set(stableIdValue, set);
372
+ }
373
+ set.add(versionId);
374
+ };
375
+ for (const record of priorEvidence) {
376
+ if (record.stableGitHubId !== undefined) {
377
+ add(record.stableGitHubId, record.versionId);
378
+ }
379
+ }
380
+ const priorByVersionId = new Map<string, CollectorEvidenceRecord>();
381
+ for (const record of priorEvidence) {
382
+ priorByVersionId.set(record.versionId, record);
383
+ }
384
+
385
+ for (const record of pending) {
386
+ if (record.stableGitHubId === undefined) continue;
387
+ const priorVersions = versionsByStable.get(record.stableGitHubId) ?? new Set<string>();
388
+ const isNewVersion = !priorVersions.has(record.versionId);
389
+ const hadEarlierDistinctVersion = [...priorVersions].some(
390
+ (versionId) => versionId !== record.versionId,
391
+ );
392
+
393
+ if (record.kind === "review") {
394
+ if (!isNewVersion) {
395
+ // Known versionId must keep the previously stored authoritative clock.
396
+ const prior = priorByVersionId.get(record.versionId);
397
+ if (prior !== undefined) {
398
+ record.authoritativeTime = prior.authoritativeTime ?? null;
399
+ }
400
+ } else if (hadEarlierDistinctVersion) {
401
+ // Reviews have no GitHub-supplied per-edit timestamp.
402
+ record.authoritativeTime = null;
403
+ } else {
404
+ // First submission version may use submitted_at only when surface
405
+ // completion mono is at/before deadlineMono; after-cutoff first
406
+ // sighting must not backdate via submitted_at. Wall firstObservedAt
407
+ // is metadata only and must not gate trust.
408
+ const { deadlineMono, firstObservedMono } = cutoff;
409
+ if (
410
+ !Number.isFinite(deadlineMono) ||
411
+ !Number.isFinite(firstObservedMono) ||
412
+ firstObservedMono > deadlineMono
413
+ ) {
414
+ record.authoritativeTime = null;
415
+ } else {
416
+ record.authoritativeTime = record.submittedAt ?? null;
417
+ }
418
+ }
419
+ } else if (
420
+ record.kind === "issue_comment" ||
421
+ record.kind === "review_comment"
422
+ ) {
423
+ if (
424
+ record.authoritativeTime === undefined ||
425
+ record.authoritativeTime === ""
426
+ ) {
427
+ record.authoritativeTime = null;
428
+ }
429
+ }
430
+
431
+ if (isNewVersion) {
432
+ add(record.stableGitHubId, record.versionId);
433
+ }
434
+ }
435
+ }
436
+
437
+ export function measureNormalizedBytes(records: readonly CollectorEvidenceRecord[]): number {
438
+ return Buffer.byteLength(JSON.stringify(records), "utf8");
439
+ }
440
+
441
+ export function assignWindowRelations(
442
+ records: CollectorEvidenceRecord[],
443
+ activationTime: Date | undefined,
444
+ deadlineTime: Date | undefined,
445
+ ): void {
446
+ if (activationTime === undefined || deadlineTime === undefined) return;
447
+ for (const record of records) {
448
+ if (
449
+ record.kind === "review" ||
450
+ record.kind === "issue_comment" ||
451
+ record.kind === "review_comment" ||
452
+ record.kind === "pull_request"
453
+ ) {
454
+ record.windowRelation = computeWindowRelation(
455
+ record.authoritativeTime,
456
+ activationTime,
457
+ deadlineTime,
458
+ );
459
+ }
460
+ }
461
+ }