@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
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import Value from "typebox/value";
|
|
2
|
+
import { applyEvidenceVersionHistory, assignWindowRelations, COLLECTOR_ELIGIBILITY_MS, measureNormalizedBytes, normalizeAuthenticatedUserEvidence, normalizeIssueCommentEvidence, normalizePullRequestEvidence, normalizePullRequestReactionEvidence, normalizeReviewCommentEvidence, normalizeReviewEvidence, sha256Text, } from "./collector-evidence.js";
|
|
3
|
+
import { buildCollectorRequestBody, } from "./collector-github.js";
|
|
4
|
+
import { collectorObserveArgsSchema, collectorOutputArgsSchema, collectorReadArgsSchema, collectorRequestArgsSchema, collectorWaitArgsSchema, } from "./collector-tool-schemas.js";
|
|
5
|
+
import { COLLECTOR_OUTPUT_TOOL } from "./package-contracts/collector-output.js";
|
|
6
|
+
export const COLLECTOR_OBSERVE_TOOL = "ak_collector_observe";
|
|
7
|
+
export const COLLECTOR_READ_TOOL = "ak_collector_read";
|
|
8
|
+
export const COLLECTOR_REQUEST_TOOL = "ak_collector_request";
|
|
9
|
+
export const COLLECTOR_WAIT_TOOL = "ak_collector_wait";
|
|
10
|
+
export { COLLECTOR_OUTPUT_TOOL };
|
|
11
|
+
export const COLLECTOR_OPERATIONAL_TOOLS = [
|
|
12
|
+
COLLECTOR_OBSERVE_TOOL,
|
|
13
|
+
COLLECTOR_READ_TOOL,
|
|
14
|
+
COLLECTOR_REQUEST_TOOL,
|
|
15
|
+
COLLECTOR_WAIT_TOOL,
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* #641 chain①: observe context projects at most this many bytes of each
|
|
19
|
+
* material body; the full body stays reachable via the record pointer
|
|
20
|
+
* (evidenceId + htmlUrl) and is preserved in the volume, never unconditionally
|
|
21
|
+
* transcribed into model context or receipt.
|
|
22
|
+
*/
|
|
23
|
+
export const COLLECTOR_OBSERVE_BODY_HEAD_BYTES = 512;
|
|
24
|
+
function utf8SafePrefix(text, maxBytes) {
|
|
25
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes)
|
|
26
|
+
return text;
|
|
27
|
+
const bytes = Buffer.from(text, "utf8");
|
|
28
|
+
let end = maxBytes;
|
|
29
|
+
while (end > 0 && (bytes[end] & 0xc0) === 0x80)
|
|
30
|
+
end -= 1;
|
|
31
|
+
return `${bytes.subarray(0, end).toString("utf8")}…`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* #641 chain① model-context projection: identical shape to the volume view,
|
|
35
|
+
* with every material body bounded to a head preview. Full bodies remain
|
|
36
|
+
* pointer-reachable (evidenceId/htmlUrl) and stay in the ledger volume
|
|
37
|
+
* (collector-side; provider-visible observe details carry only this bounded
|
|
38
|
+
* projection), never unconditionally transcribed into model context.
|
|
39
|
+
*/
|
|
40
|
+
export function projectObserveContextView(modelView) {
|
|
41
|
+
return {
|
|
42
|
+
...modelView,
|
|
43
|
+
evidence: modelView.evidence.map((entry) => entry.body === undefined
|
|
44
|
+
? entry
|
|
45
|
+
: { ...entry, body: utf8SafePrefix(entry.body, COLLECTOR_OBSERVE_BODY_HEAD_BYTES) }),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export const COLLECTOR_ACTIVATION_ENTRY_TYPE = "ak-collector-activation";
|
|
49
|
+
export const COLLECTOR_SNAPSHOT_ENTRY_TYPE = "ak-collector-snapshot";
|
|
50
|
+
export const COLLECTOR_REQUEST_ENTRY_TYPE = "ak-collector-request";
|
|
51
|
+
export const COLLECTOR_WAIT_ENTRY_TYPE = "ak-collector-wait";
|
|
52
|
+
function isOperationalTool(name) {
|
|
53
|
+
return COLLECTOR_OPERATIONAL_TOOLS.includes(name);
|
|
54
|
+
}
|
|
55
|
+
/** Shared Check owner for Collector tool argument shapes (schema seam, not batch law). */
|
|
56
|
+
export function collectorToolArgumentsValid(name, args) {
|
|
57
|
+
// Residual envelope: reject missing/null args before schema check.
|
|
58
|
+
if (args === undefined || args === null)
|
|
59
|
+
return false;
|
|
60
|
+
switch (name) {
|
|
61
|
+
case COLLECTOR_OBSERVE_TOOL:
|
|
62
|
+
return Value.Check(collectorObserveArgsSchema, args);
|
|
63
|
+
case COLLECTOR_READ_TOOL:
|
|
64
|
+
return Value.Check(collectorReadArgsSchema, args);
|
|
65
|
+
case COLLECTOR_REQUEST_TOOL:
|
|
66
|
+
return Value.Check(collectorRequestArgsSchema, args);
|
|
67
|
+
case COLLECTOR_WAIT_TOOL:
|
|
68
|
+
return Value.Check(collectorWaitArgsSchema, args);
|
|
69
|
+
case COLLECTOR_OUTPUT_TOOL:
|
|
70
|
+
return Value.Check(collectorOutputArgsSchema, args);
|
|
71
|
+
default:
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function createCollectorLedger(config, options) {
|
|
76
|
+
const clock = options?.clock;
|
|
77
|
+
const journal = options?.journal;
|
|
78
|
+
let fatal = false;
|
|
79
|
+
let fatalReason;
|
|
80
|
+
let outputCandidate = false;
|
|
81
|
+
let pendingOutputCallId;
|
|
82
|
+
let activationTime;
|
|
83
|
+
let deadlineTime;
|
|
84
|
+
let activationMono;
|
|
85
|
+
let deadlineMono;
|
|
86
|
+
let requesterLogin;
|
|
87
|
+
let latestCompleteSnapshotId;
|
|
88
|
+
let finalObservationRequired = false;
|
|
89
|
+
let finalObservationCompleted = false;
|
|
90
|
+
let activeOperationalCallId;
|
|
91
|
+
let mutationGeneration = 0;
|
|
92
|
+
let observedGeneration = 0;
|
|
93
|
+
const evidenceById = new Map();
|
|
94
|
+
const evidenceByVersion = new Map();
|
|
95
|
+
const snapshots = [];
|
|
96
|
+
const attempts = [];
|
|
97
|
+
const attemptKeys = new Set();
|
|
98
|
+
const waits = [];
|
|
99
|
+
const transportFailures = [];
|
|
100
|
+
const latchFatal = (reason, cause) => {
|
|
101
|
+
fatal = true;
|
|
102
|
+
fatalReason = reason;
|
|
103
|
+
const error = new Error(reason, cause === undefined ? undefined : { cause });
|
|
104
|
+
Object.assign(error, { collectorFatal: true });
|
|
105
|
+
return error;
|
|
106
|
+
};
|
|
107
|
+
const assertNotFatal = () => {
|
|
108
|
+
if (fatal)
|
|
109
|
+
throw new Error(fatalReason ?? "通进司致命状态");
|
|
110
|
+
};
|
|
111
|
+
const storeEvidence = (record) => {
|
|
112
|
+
const existingId = evidenceByVersion.get(record.versionId);
|
|
113
|
+
if (existingId !== undefined) {
|
|
114
|
+
return evidenceById.get(existingId) ?? record;
|
|
115
|
+
}
|
|
116
|
+
evidenceByVersion.set(record.versionId, record.evidenceId);
|
|
117
|
+
evidenceById.set(record.evidenceId, record);
|
|
118
|
+
return record;
|
|
119
|
+
};
|
|
120
|
+
const monoNowOrThrow = (clock) => clock.monoNow();
|
|
121
|
+
const pastCutoff = (clock) => {
|
|
122
|
+
if (deadlineMono === undefined)
|
|
123
|
+
return false;
|
|
124
|
+
return monoNowOrThrow(clock) >= deadlineMono;
|
|
125
|
+
};
|
|
126
|
+
const remainingMs = (clock) => {
|
|
127
|
+
if (deadlineMono === undefined)
|
|
128
|
+
return COLLECTOR_ELIGIBILITY_MS;
|
|
129
|
+
return Math.max(0, deadlineMono - monoNowOrThrow(clock));
|
|
130
|
+
};
|
|
131
|
+
const prIdentity = (pr) => `${pr.state}|${pr.headOid}|${pr.updatedAt ?? ""}`;
|
|
132
|
+
const fetchObserveSurfaces = async (transport, observedAt, signal) => {
|
|
133
|
+
const owner = config.repository.owner;
|
|
134
|
+
const repo = config.repository.repo;
|
|
135
|
+
const prNumber = config.prNumber;
|
|
136
|
+
const signalOpt = signal === undefined ? {} : { signal };
|
|
137
|
+
const user = await transport.getAuthenticatedUser(signalOpt);
|
|
138
|
+
const prInitial = await transport.getPullRequest({
|
|
139
|
+
owner,
|
|
140
|
+
repo,
|
|
141
|
+
prNumber,
|
|
142
|
+
...signalOpt,
|
|
143
|
+
});
|
|
144
|
+
const reviews = await transport.listPullRequestReviews({
|
|
145
|
+
owner,
|
|
146
|
+
repo,
|
|
147
|
+
prNumber,
|
|
148
|
+
...signalOpt,
|
|
149
|
+
});
|
|
150
|
+
const reactions = transport.listPullRequestReactions === undefined
|
|
151
|
+
? { items: [], pages: [] }
|
|
152
|
+
: await transport.listPullRequestReactions({ owner, repo, prNumber, ...signalOpt });
|
|
153
|
+
const issueComments = await transport.listIssueComments({
|
|
154
|
+
owner,
|
|
155
|
+
repo,
|
|
156
|
+
prNumber,
|
|
157
|
+
...signalOpt,
|
|
158
|
+
});
|
|
159
|
+
const reviewComments = await transport.listReviewComments({
|
|
160
|
+
owner,
|
|
161
|
+
repo,
|
|
162
|
+
prNumber,
|
|
163
|
+
...signalOpt,
|
|
164
|
+
});
|
|
165
|
+
const prTerminal = await transport.getPullRequest({
|
|
166
|
+
owner,
|
|
167
|
+
repo,
|
|
168
|
+
prNumber,
|
|
169
|
+
...signalOpt,
|
|
170
|
+
});
|
|
171
|
+
return { user, prInitial, reviews, reactions, issueComments, reviewComments, prTerminal };
|
|
172
|
+
};
|
|
173
|
+
const appendJournal = (customType, data) => {
|
|
174
|
+
journal?.append(customType, data);
|
|
175
|
+
};
|
|
176
|
+
const bindDeadlineMonoFromWall = () => {
|
|
177
|
+
if (clock === undefined || activationTime === undefined || deadlineTime === undefined)
|
|
178
|
+
return;
|
|
179
|
+
const wallNow = clock.wallNow().getTime();
|
|
180
|
+
const monoNow = clock.monoNow();
|
|
181
|
+
activationMono = monoNow - (wallNow - activationTime.getTime());
|
|
182
|
+
deadlineMono = monoNow + (deadlineTime.getTime() - wallNow);
|
|
183
|
+
};
|
|
184
|
+
const rebindSnapshotCompletedMono = (snapshot) => {
|
|
185
|
+
const copy = { ...snapshot, evidenceIds: [...snapshot.evidenceIds] };
|
|
186
|
+
if (deadlineTime !== undefined && deadlineMono !== undefined && copy.completedAt !== undefined) {
|
|
187
|
+
const completedWall = new Date(copy.completedAt).getTime();
|
|
188
|
+
copy.completedMono = deadlineMono + (completedWall - deadlineTime.getTime());
|
|
189
|
+
}
|
|
190
|
+
return copy;
|
|
191
|
+
};
|
|
192
|
+
const commitActivationWindow = (nextActivation, nextDeadline) => {
|
|
193
|
+
if (activationTime !== undefined)
|
|
194
|
+
return;
|
|
195
|
+
activationTime = nextActivation;
|
|
196
|
+
deadlineTime = nextDeadline;
|
|
197
|
+
bindDeadlineMonoFromWall();
|
|
198
|
+
};
|
|
199
|
+
const recoverAmbiguousRequestLosses = (snapshotId, observedRecords) => {
|
|
200
|
+
for (const failure of transportFailures) {
|
|
201
|
+
if (failure.recovered || failure.kind !== "ambiguous_request_loss")
|
|
202
|
+
continue;
|
|
203
|
+
if (failure.marker === undefined || failure.requestId === undefined)
|
|
204
|
+
continue;
|
|
205
|
+
const found = observedRecords.find((record) => record.kind === "issue_comment" &&
|
|
206
|
+
record.authorLogin === requesterLogin &&
|
|
207
|
+
typeof record.body === "string" &&
|
|
208
|
+
record.body.includes(failure.marker));
|
|
209
|
+
if (found === undefined)
|
|
210
|
+
continue;
|
|
211
|
+
failure.recovered = true;
|
|
212
|
+
const attempt = attempts.find((item) => item.status === "ambiguous_loss" &&
|
|
213
|
+
item.requestId === failure.requestId &&
|
|
214
|
+
item.marker === failure.marker);
|
|
215
|
+
if (attempt !== undefined) {
|
|
216
|
+
attempt.status = "recovered";
|
|
217
|
+
attempt.commentEvidenceId = found.evidenceId;
|
|
218
|
+
attempt.recoverySnapshotId = snapshotId;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const finalizeObservedSnapshot = (snapshot) => {
|
|
223
|
+
if (requesterLogin === undefined) {
|
|
224
|
+
for (const id of snapshot.evidenceIds) {
|
|
225
|
+
const record = evidenceById.get(id);
|
|
226
|
+
if (record?.kind === "authenticated_user" && record.authorLogin !== undefined) {
|
|
227
|
+
requesterLogin = record.authorLogin.toLowerCase();
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const stored = rebindSnapshotCompletedMono(snapshot);
|
|
233
|
+
snapshots.push(stored);
|
|
234
|
+
latestCompleteSnapshotId = stored.snapshotId;
|
|
235
|
+
const observedRecords = stored.evidenceIds.flatMap((id) => {
|
|
236
|
+
const record = evidenceById.get(id);
|
|
237
|
+
return record === undefined ? [] : [record];
|
|
238
|
+
});
|
|
239
|
+
recoverAmbiguousRequestLosses(stored.snapshotId, observedRecords);
|
|
240
|
+
observedGeneration = mutationGeneration;
|
|
241
|
+
if (finalObservationRequired &&
|
|
242
|
+
stored.completedMono !== undefined &&
|
|
243
|
+
deadlineMono !== undefined &&
|
|
244
|
+
stored.completedMono >= deadlineMono) {
|
|
245
|
+
finalObservationCompleted = true;
|
|
246
|
+
}
|
|
247
|
+
else if (clock !== undefined &&
|
|
248
|
+
deadlineMono !== undefined &&
|
|
249
|
+
clock.monoNow() >= deadlineMono &&
|
|
250
|
+
stored.completedMono !== undefined &&
|
|
251
|
+
stored.completedMono >= deadlineMono) {
|
|
252
|
+
finalObservationRequired = true;
|
|
253
|
+
finalObservationCompleted = true;
|
|
254
|
+
}
|
|
255
|
+
return stored;
|
|
256
|
+
};
|
|
257
|
+
const commitObservedSnapshot = (snapshot, evidenceRecords) => {
|
|
258
|
+
for (const record of evidenceRecords) {
|
|
259
|
+
storeEvidence(record);
|
|
260
|
+
}
|
|
261
|
+
return finalizeObservedSnapshot(snapshot);
|
|
262
|
+
};
|
|
263
|
+
const commitRequestAttempt = (attempt, attemptKey, commentEvidence) => {
|
|
264
|
+
if (commentEvidence !== undefined) {
|
|
265
|
+
storeEvidence(commentEvidence);
|
|
266
|
+
}
|
|
267
|
+
const existing = attempts.find((item) => item.attemptId === attempt.attemptId);
|
|
268
|
+
if (existing === undefined) {
|
|
269
|
+
attempts.push({ ...attempt });
|
|
270
|
+
attemptKeys.add(attemptKey);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
Object.assign(existing, attempt);
|
|
274
|
+
}
|
|
275
|
+
if (attempt.status === "ambiguous_loss" && !transportFailures.some((failure) => failure.requestId === attempt.requestId && failure.observedHead === attempt.observedHead)) {
|
|
276
|
+
transportFailures.push({
|
|
277
|
+
failureId: sha256Text(`loss:${attempt.attemptId}`).slice(0, 16),
|
|
278
|
+
kind: "ambiguous_request_loss",
|
|
279
|
+
message: attempt.responseDiagnostics ?? "ambiguous request loss",
|
|
280
|
+
requestId: attempt.requestId,
|
|
281
|
+
observedHead: attempt.observedHead,
|
|
282
|
+
marker: attempt.marker,
|
|
283
|
+
recovered: false,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (attempt.status === "succeeded" || attempt.status === "ambiguous_loss") {
|
|
287
|
+
mutationGeneration += 1;
|
|
288
|
+
finalObservationCompleted = false;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
const commitWaitRecord = (wait) => {
|
|
292
|
+
waits.push({ ...wait });
|
|
293
|
+
if (wait.cutoffReached)
|
|
294
|
+
finalObservationRequired = true;
|
|
295
|
+
mutationGeneration += 1;
|
|
296
|
+
finalObservationCompleted = false;
|
|
297
|
+
};
|
|
298
|
+
const replayDossierEntries = (entries) => {
|
|
299
|
+
const orderedEntries = Array.from(entries);
|
|
300
|
+
const finalRequestStatus = new Map();
|
|
301
|
+
for (const raw of orderedEntries) {
|
|
302
|
+
if (typeof raw !== "object" || raw === null)
|
|
303
|
+
continue;
|
|
304
|
+
const entry = raw;
|
|
305
|
+
if (entry.type !== "custom" || entry.customType !== COLLECTOR_REQUEST_ENTRY_TYPE || typeof entry.data !== "object" || entry.data === null)
|
|
306
|
+
continue;
|
|
307
|
+
const attempt = entry.data.attempt;
|
|
308
|
+
if (attempt?.attemptId !== undefined)
|
|
309
|
+
finalRequestStatus.set(attempt.attemptId, attempt.status);
|
|
310
|
+
}
|
|
311
|
+
for (const raw of orderedEntries) {
|
|
312
|
+
if (typeof raw !== "object" || raw === null)
|
|
313
|
+
continue;
|
|
314
|
+
const entry = raw;
|
|
315
|
+
if (entry.type !== "custom" || typeof entry.data !== "object" || entry.data === null)
|
|
316
|
+
continue;
|
|
317
|
+
const data = entry.data;
|
|
318
|
+
if (entry.customType === COLLECTOR_ACTIVATION_ENTRY_TYPE) {
|
|
319
|
+
if (typeof data.activationTime === "string" && typeof data.deadlineTime === "string") {
|
|
320
|
+
commitActivationWindow(new Date(data.activationTime), new Date(data.deadlineTime));
|
|
321
|
+
}
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (entry.customType === COLLECTOR_SNAPSHOT_ENTRY_TYPE) {
|
|
325
|
+
const snapshot = data.snapshot;
|
|
326
|
+
if (snapshot?.snapshotId === undefined || !Array.isArray(data.evidence))
|
|
327
|
+
continue;
|
|
328
|
+
// Generations advance only via request/wait commits and observation sync — never from collection sizes or payload counters.
|
|
329
|
+
commitObservedSnapshot(snapshot, data.evidence);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (entry.customType === COLLECTOR_REQUEST_ENTRY_TYPE) {
|
|
333
|
+
const attempt = data.attempt;
|
|
334
|
+
if (attempt?.attemptId === undefined)
|
|
335
|
+
continue;
|
|
336
|
+
const attemptKey = typeof data.attemptKey === "string"
|
|
337
|
+
? data.attemptKey
|
|
338
|
+
: [config.repository.canonical, String(config.prNumber), attempt.observedHead, attempt.requestId].join("|");
|
|
339
|
+
const commentEvidence = data.commentEvidence;
|
|
340
|
+
const replayAttempt = attempt.status === "started" && finalRequestStatus.get(attempt.attemptId) === "started"
|
|
341
|
+
? {
|
|
342
|
+
...attempt,
|
|
343
|
+
status: "ambiguous_loss",
|
|
344
|
+
responseDiagnostics: "request interrupted after dispatch before completion was recorded",
|
|
345
|
+
}
|
|
346
|
+
: attempt;
|
|
347
|
+
commitRequestAttempt(replayAttempt, attemptKey, commentEvidence);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (entry.customType === COLLECTOR_WAIT_ENTRY_TYPE) {
|
|
351
|
+
const wait = data.waitRecord;
|
|
352
|
+
if (wait?.waitId === undefined)
|
|
353
|
+
continue;
|
|
354
|
+
commitWaitRecord(wait);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
if (options?.dossierEntries !== undefined) {
|
|
359
|
+
replayDossierEntries(options.dossierEntries);
|
|
360
|
+
}
|
|
361
|
+
if (clock !== undefined && deadlineMono !== undefined && clock.monoNow() >= deadlineMono) {
|
|
362
|
+
finalObservationRequired = true;
|
|
363
|
+
const lastSnap = snapshots.find((item) => item.snapshotId === latestCompleteSnapshotId);
|
|
364
|
+
if (lastSnap?.completedMono !== undefined && lastSnap.completedMono >= deadlineMono) {
|
|
365
|
+
finalObservationCompleted = true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const ledger = {
|
|
369
|
+
get config() {
|
|
370
|
+
return config;
|
|
371
|
+
},
|
|
372
|
+
get fatal() {
|
|
373
|
+
return fatal;
|
|
374
|
+
},
|
|
375
|
+
get fatalReason() {
|
|
376
|
+
return fatalReason;
|
|
377
|
+
},
|
|
378
|
+
get outputCandidate() {
|
|
379
|
+
return outputCandidate || pendingOutputCallId !== undefined;
|
|
380
|
+
},
|
|
381
|
+
get activationRecorded() {
|
|
382
|
+
return activationTime !== undefined;
|
|
383
|
+
},
|
|
384
|
+
get activationTime() {
|
|
385
|
+
return activationTime;
|
|
386
|
+
},
|
|
387
|
+
get deadlineTime() {
|
|
388
|
+
return deadlineTime;
|
|
389
|
+
},
|
|
390
|
+
get activationMono() {
|
|
391
|
+
return activationMono;
|
|
392
|
+
},
|
|
393
|
+
get deadlineMono() {
|
|
394
|
+
return deadlineMono;
|
|
395
|
+
},
|
|
396
|
+
get requesterLogin() {
|
|
397
|
+
return requesterLogin;
|
|
398
|
+
},
|
|
399
|
+
get latestCompleteSnapshotId() {
|
|
400
|
+
return latestCompleteSnapshotId;
|
|
401
|
+
},
|
|
402
|
+
get finalObservationRequired() {
|
|
403
|
+
return finalObservationRequired;
|
|
404
|
+
},
|
|
405
|
+
get finalObservationCompleted() {
|
|
406
|
+
return finalObservationCompleted;
|
|
407
|
+
},
|
|
408
|
+
get unresolvedTransportFailure() {
|
|
409
|
+
return transportFailures.some((failure) => !failure.recovered);
|
|
410
|
+
},
|
|
411
|
+
get mutationGeneration() {
|
|
412
|
+
return mutationGeneration;
|
|
413
|
+
},
|
|
414
|
+
get observedGeneration() {
|
|
415
|
+
return observedGeneration;
|
|
416
|
+
},
|
|
417
|
+
latchFatal,
|
|
418
|
+
assertNotFatal,
|
|
419
|
+
recordActivation(clock) {
|
|
420
|
+
assertNotFatal();
|
|
421
|
+
if (activationTime !== undefined)
|
|
422
|
+
return;
|
|
423
|
+
activationTime = clock.wallNow();
|
|
424
|
+
activationMono = clock.monoNow();
|
|
425
|
+
deadlineTime = new Date(activationTime.getTime() + COLLECTOR_ELIGIBILITY_MS);
|
|
426
|
+
deadlineMono = activationMono + COLLECTOR_ELIGIBILITY_MS;
|
|
427
|
+
appendJournal(COLLECTOR_ACTIVATION_ENTRY_TYPE, {
|
|
428
|
+
activationTime: activationTime.toISOString(),
|
|
429
|
+
deadlineTime: deadlineTime.toISOString(),
|
|
430
|
+
});
|
|
431
|
+
},
|
|
432
|
+
recordOutputCandidate() {
|
|
433
|
+
assertNotFatal();
|
|
434
|
+
outputCandidate = true;
|
|
435
|
+
},
|
|
436
|
+
beginOperational(toolName, toolCallId) {
|
|
437
|
+
assertNotFatal();
|
|
438
|
+
if (toolName !== COLLECTOR_OUTPUT_TOOL &&
|
|
439
|
+
(outputCandidate || pendingOutputCallId !== undefined)) {
|
|
440
|
+
throw new Error("通进司已产出输出候选,本局不再受理操作");
|
|
441
|
+
}
|
|
442
|
+
if (toolName === COLLECTOR_OUTPUT_TOOL) {
|
|
443
|
+
pendingOutputCallId = toolCallId;
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
// Idempotent for the same call across tool_call preflight and execute.
|
|
447
|
+
if (activeOperationalCallId === toolCallId) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (activeOperationalCallId !== undefined) {
|
|
451
|
+
throw latchFatal("通进司操作调用已在进行");
|
|
452
|
+
}
|
|
453
|
+
if (!isOperationalTool(toolName)) {
|
|
454
|
+
throw latchFatal(`未知通进司工具 ${toolName}`);
|
|
455
|
+
}
|
|
456
|
+
activeOperationalCallId = toolCallId;
|
|
457
|
+
},
|
|
458
|
+
completeOperational(toolCallId) {
|
|
459
|
+
if (pendingOutputCallId === toolCallId) {
|
|
460
|
+
pendingOutputCallId = undefined;
|
|
461
|
+
}
|
|
462
|
+
if (activeOperationalCallId === toolCallId) {
|
|
463
|
+
activeOperationalCallId = undefined;
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
noteCutoffObserved() {
|
|
467
|
+
finalObservationRequired = true;
|
|
468
|
+
},
|
|
469
|
+
assertOutputObservationLaw(clock) {
|
|
470
|
+
assertNotFatal();
|
|
471
|
+
if (activationTime === undefined || deadlineTime === undefined || deadlineMono === undefined) {
|
|
472
|
+
throw new Error("通进司回执需要激活时间线");
|
|
473
|
+
}
|
|
474
|
+
if (latestCompleteSnapshotId === undefined) {
|
|
475
|
+
throw new Error("通进司回执需要完整终局快照");
|
|
476
|
+
}
|
|
477
|
+
if (observedGeneration !== mutationGeneration) {
|
|
478
|
+
throw new Error("通进司回执要求在最近 request/wait 变更后完成一次 observe");
|
|
479
|
+
}
|
|
480
|
+
const snapshot = snapshots.find((item) => item.snapshotId === latestCompleteSnapshotId);
|
|
481
|
+
if (snapshot === undefined || !snapshot.complete) {
|
|
482
|
+
throw new Error("通进司终局快照缺失或不完整");
|
|
483
|
+
}
|
|
484
|
+
const mono = monoNowOrThrow(clock);
|
|
485
|
+
const atOrAfterCutoff = mono >= deadlineMono;
|
|
486
|
+
if (atOrAfterCutoff) {
|
|
487
|
+
finalObservationRequired = true;
|
|
488
|
+
if (snapshot.completedMono === undefined ||
|
|
489
|
+
snapshot.completedMono < deadlineMono) {
|
|
490
|
+
throw new Error("通进司截止时/后回执要求不早于截止完成的完整观察");
|
|
491
|
+
}
|
|
492
|
+
finalObservationCompleted = true;
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
async observe(transport, clock, signal) {
|
|
496
|
+
assertNotFatal();
|
|
497
|
+
if (activationTime === undefined) {
|
|
498
|
+
throw latchFatal("通进司观察需要激活");
|
|
499
|
+
}
|
|
500
|
+
if (signal?.aborted) {
|
|
501
|
+
const abortMessage = signal.reason instanceof Error
|
|
502
|
+
? signal.reason.message
|
|
503
|
+
: String(signal.reason ?? "已中止");
|
|
504
|
+
throw latchFatal(`通进司观察失败:${abortMessage}`, signal.reason);
|
|
505
|
+
}
|
|
506
|
+
const observedAt = clock.wallNow().toISOString();
|
|
507
|
+
const cutoff = pastCutoff(clock);
|
|
508
|
+
if (cutoff) {
|
|
509
|
+
finalObservationRequired = true;
|
|
510
|
+
}
|
|
511
|
+
let surfaces;
|
|
512
|
+
try {
|
|
513
|
+
surfaces = await fetchObserveSurfaces(transport, observedAt, signal);
|
|
514
|
+
if (prIdentity(surfaces.prInitial) !== prIdentity(surfaces.prTerminal)) {
|
|
515
|
+
// Retry full surfaces once; bind only a consistent terminal read.
|
|
516
|
+
surfaces = await fetchObserveSurfaces(transport, observedAt, signal);
|
|
517
|
+
if (prIdentity(surfaces.prInitial) !== prIdentity(surfaces.prTerminal)) {
|
|
518
|
+
throw new Error(`PR 身份在 observe 括弧重试后仍漂移(${prIdentity(surfaces.prInitial)} → ${prIdentity(surfaces.prTerminal)})`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
const message = error instanceof Error ? error.message : "未知失败";
|
|
524
|
+
throw latchFatal(`通进司观察失败:${message}`, error);
|
|
525
|
+
}
|
|
526
|
+
// First-sighting trust must not predate actual surface observation.
|
|
527
|
+
// Observe-start may be before cutoff while surfaces arrive after; stamp
|
|
528
|
+
// evidence wall metadata and mono trust only once surfaces are in hand
|
|
529
|
+
// (budget retain during fetch may keep the temporary start stamp).
|
|
530
|
+
const firstObservedAt = clock.wallNow().toISOString(); // metadata
|
|
531
|
+
const firstObservedMono = monoNowOrThrow(clock); // trust
|
|
532
|
+
// Always bind terminal PR identity fields.
|
|
533
|
+
const pr = surfaces.prTerminal;
|
|
534
|
+
const { user, reviews, reactions, issueComments, reviewComments } = surfaces;
|
|
535
|
+
requesterLogin = user.login.toLowerCase();
|
|
536
|
+
const pageDiagnostics = [
|
|
537
|
+
...reviews.pages,
|
|
538
|
+
...reactions.pages,
|
|
539
|
+
...issueComments.pages,
|
|
540
|
+
...reviewComments.pages,
|
|
541
|
+
];
|
|
542
|
+
const pendingRecords = [];
|
|
543
|
+
pendingRecords.push(normalizeAuthenticatedUserEvidence(user, firstObservedAt));
|
|
544
|
+
pendingRecords.push(normalizePullRequestEvidence(pr, firstObservedAt));
|
|
545
|
+
for (const review of reviews.items) {
|
|
546
|
+
pendingRecords.push(normalizeReviewEvidence(review, firstObservedAt));
|
|
547
|
+
}
|
|
548
|
+
for (const reaction of reactions.items) {
|
|
549
|
+
pendingRecords.push(normalizePullRequestReactionEvidence(reaction, firstObservedAt));
|
|
550
|
+
}
|
|
551
|
+
for (const comment of issueComments.items) {
|
|
552
|
+
pendingRecords.push(normalizeIssueCommentEvidence(comment, firstObservedAt));
|
|
553
|
+
}
|
|
554
|
+
for (const comment of reviewComments.items) {
|
|
555
|
+
pendingRecords.push(normalizeReviewCommentEvidence(comment, firstObservedAt));
|
|
556
|
+
}
|
|
557
|
+
applyEvidenceVersionHistory(pendingRecords, [...evidenceById.values()], { deadlineMono: deadlineMono, firstObservedMono });
|
|
558
|
+
assignWindowRelations(pendingRecords, activationTime, deadlineTime);
|
|
559
|
+
const normalizedByteLength = measureNormalizedBytes(pendingRecords);
|
|
560
|
+
// Commit only after complete surfaces validated.
|
|
561
|
+
const storedIds = [];
|
|
562
|
+
for (const record of pendingRecords) {
|
|
563
|
+
const stored = storeEvidence(record);
|
|
564
|
+
storedIds.push(stored.evidenceId);
|
|
565
|
+
}
|
|
566
|
+
// Project modelView from canonical stored snapshot records (not mutable pending).
|
|
567
|
+
const storedRecords = storedIds.map((id) => {
|
|
568
|
+
const stored = evidenceById.get(id);
|
|
569
|
+
if (stored === undefined) {
|
|
570
|
+
throw latchFatal(`通进司观察丢失已存证据 ${id}`);
|
|
571
|
+
}
|
|
572
|
+
return stored;
|
|
573
|
+
});
|
|
574
|
+
const completedAt = clock.wallNow().toISOString();
|
|
575
|
+
const completedMono = clock.monoNow();
|
|
576
|
+
const snapshotId = sha256Text(`${completedAt}:${pr.headOid}:${storedIds.join(",")}`).slice(0, 16);
|
|
577
|
+
const snapshot = {
|
|
578
|
+
snapshotId,
|
|
579
|
+
observedAt,
|
|
580
|
+
completedAt,
|
|
581
|
+
completedMono,
|
|
582
|
+
host: "github.com",
|
|
583
|
+
repository: config.repository.canonical,
|
|
584
|
+
prNumber: config.prNumber,
|
|
585
|
+
prState: pr.state,
|
|
586
|
+
headOid: pr.headOid,
|
|
587
|
+
complete: true,
|
|
588
|
+
evidenceIds: storedIds,
|
|
589
|
+
pageDiagnostics,
|
|
590
|
+
normalizedByteLength,
|
|
591
|
+
};
|
|
592
|
+
if (cutoff) {
|
|
593
|
+
finalObservationRequired = true;
|
|
594
|
+
}
|
|
595
|
+
const committed = finalizeObservedSnapshot(snapshot);
|
|
596
|
+
appendJournal(COLLECTOR_SNAPSHOT_ENTRY_TYPE, {
|
|
597
|
+
snapshot: committed,
|
|
598
|
+
evidence: committed.evidenceIds.flatMap((id) => {
|
|
599
|
+
const record = evidenceById.get(id);
|
|
600
|
+
return record === undefined ? [] : [record];
|
|
601
|
+
}),
|
|
602
|
+
});
|
|
603
|
+
const modelView = buildObserveModelView({
|
|
604
|
+
snapshot: committed,
|
|
605
|
+
records: committed.evidenceIds.flatMap((id) => {
|
|
606
|
+
const record = evidenceById.get(id);
|
|
607
|
+
return record === undefined ? [] : [record];
|
|
608
|
+
}),
|
|
609
|
+
requesterLogin,
|
|
610
|
+
attempts,
|
|
611
|
+
});
|
|
612
|
+
return {
|
|
613
|
+
snapshot: committed,
|
|
614
|
+
modelView,
|
|
615
|
+
contextView: projectObserveContextView(modelView),
|
|
616
|
+
};
|
|
617
|
+
},
|
|
618
|
+
async request(input, transport, clock, signal) {
|
|
619
|
+
assertNotFatal();
|
|
620
|
+
if (activationTime === undefined || deadlineTime === undefined) {
|
|
621
|
+
throw latchFatal("通进司请求需要激活");
|
|
622
|
+
}
|
|
623
|
+
if (pastCutoff(clock)) {
|
|
624
|
+
finalObservationRequired = true;
|
|
625
|
+
throw latchFatal("通进司请求不在资格截止前");
|
|
626
|
+
}
|
|
627
|
+
if (ledger.unresolvedTransportFailure) {
|
|
628
|
+
throw latchFatal("通进司请求时存在未恢复的传输失败");
|
|
629
|
+
}
|
|
630
|
+
const request = config.manifest.requests.find((item) => item.id === input.requestId);
|
|
631
|
+
if (request === undefined) {
|
|
632
|
+
throw new Error(`未知通进司 requestId "${input.requestId}"`);
|
|
633
|
+
}
|
|
634
|
+
const snapshot = snapshots.find((item) => item.snapshotId === input.snapshotId);
|
|
635
|
+
if (snapshot === undefined) {
|
|
636
|
+
throw new Error(`未知通进司 snapshotId "${input.snapshotId}"`);
|
|
637
|
+
}
|
|
638
|
+
if (snapshot.snapshotId !== latestCompleteSnapshotId) {
|
|
639
|
+
throw new Error("通进司请求要求最新完整快照");
|
|
640
|
+
}
|
|
641
|
+
if (snapshot.prState !== "OPEN") {
|
|
642
|
+
throw latchFatal("通进司请求要求 OPEN 状态的 PR 快照");
|
|
643
|
+
}
|
|
644
|
+
const { body, marker } = buildCollectorRequestBody({
|
|
645
|
+
configuredBody: request.requestBody,
|
|
646
|
+
manifestDigest: config.manifest.digest,
|
|
647
|
+
requestId: request.id,
|
|
648
|
+
headOid: snapshot.headOid,
|
|
649
|
+
});
|
|
650
|
+
const existingMarker = snapshot.evidenceIds.some((id) => {
|
|
651
|
+
const record = evidenceById.get(id);
|
|
652
|
+
return record?.kind === "issue_comment" &&
|
|
653
|
+
record.authorLogin === requesterLogin &&
|
|
654
|
+
typeof record.body === "string" &&
|
|
655
|
+
record.body.includes(marker);
|
|
656
|
+
});
|
|
657
|
+
if (existingMarker) {
|
|
658
|
+
throw new Error(`通进司在此 HEAD 已有同 marker 的已认证请求 "${input.requestId}"`);
|
|
659
|
+
}
|
|
660
|
+
const attemptKey = [
|
|
661
|
+
config.repository.canonical,
|
|
662
|
+
String(config.prNumber),
|
|
663
|
+
snapshot.headOid,
|
|
664
|
+
request.id,
|
|
665
|
+
].join("|");
|
|
666
|
+
if (attemptKeys.has(attemptKey)) {
|
|
667
|
+
throw new Error(`通进司进程内请求 "${request.id}" 在 HEAD ${snapshot.headOid} 的 attempt 已用`);
|
|
668
|
+
}
|
|
669
|
+
const startedAt = clock.wallNow().toISOString();
|
|
670
|
+
const attemptId = sha256Text(`${attemptKey}:${startedAt}`).slice(0, 16);
|
|
671
|
+
const attempt = {
|
|
672
|
+
attemptId,
|
|
673
|
+
requestId: request.id,
|
|
674
|
+
observedHead: snapshot.headOid,
|
|
675
|
+
snapshotId: snapshot.snapshotId,
|
|
676
|
+
marker,
|
|
677
|
+
body,
|
|
678
|
+
startedAt,
|
|
679
|
+
status: "started",
|
|
680
|
+
};
|
|
681
|
+
commitRequestAttempt(attempt, attemptKey, undefined);
|
|
682
|
+
appendJournal(COLLECTOR_REQUEST_ENTRY_TYPE, {
|
|
683
|
+
attempt: { ...attempt },
|
|
684
|
+
attemptKey,
|
|
685
|
+
});
|
|
686
|
+
const result = await transport.createIssueComment({
|
|
687
|
+
owner: config.repository.owner,
|
|
688
|
+
repo: config.repository.repo,
|
|
689
|
+
prNumber: config.prNumber,
|
|
690
|
+
body,
|
|
691
|
+
...(signal === undefined ? {} : { signal }),
|
|
692
|
+
});
|
|
693
|
+
const journalRequest = (commentEvidence) => {
|
|
694
|
+
const attemptKey = [
|
|
695
|
+
config.repository.canonical,
|
|
696
|
+
String(config.prNumber),
|
|
697
|
+
attempt.observedHead,
|
|
698
|
+
attempt.requestId,
|
|
699
|
+
].join("|");
|
|
700
|
+
appendJournal(COLLECTOR_REQUEST_ENTRY_TYPE, {
|
|
701
|
+
attempt: { ...attempt },
|
|
702
|
+
attemptKey,
|
|
703
|
+
commentEvidence,
|
|
704
|
+
});
|
|
705
|
+
};
|
|
706
|
+
if (result.kind === "success") {
|
|
707
|
+
const record = normalizeIssueCommentEvidence(result.comment, startedAt);
|
|
708
|
+
attempt.status = "succeeded";
|
|
709
|
+
attempt.commentEvidenceId = record.evidenceId;
|
|
710
|
+
commitRequestAttempt(attempt, attemptKey, record);
|
|
711
|
+
journalRequest(record);
|
|
712
|
+
return {
|
|
713
|
+
status: "succeeded",
|
|
714
|
+
attemptId,
|
|
715
|
+
requestId: request.id,
|
|
716
|
+
observedHead: snapshot.headOid,
|
|
717
|
+
marker,
|
|
718
|
+
commentEvidenceId: record.evidenceId,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
if (result.kind === "ambiguous_loss") {
|
|
722
|
+
attempt.status = "ambiguous_loss";
|
|
723
|
+
attempt.responseDiagnostics = result.diagnostics;
|
|
724
|
+
commitRequestAttempt(attempt, attemptKey, undefined);
|
|
725
|
+
journalRequest(undefined);
|
|
726
|
+
return {
|
|
727
|
+
status: "ambiguous_loss",
|
|
728
|
+
attemptId,
|
|
729
|
+
requestId: request.id,
|
|
730
|
+
observedHead: snapshot.headOid,
|
|
731
|
+
marker,
|
|
732
|
+
diagnostics: result.diagnostics,
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
attempt.status = "rejected";
|
|
736
|
+
attempt.responseDiagnostics = result.diagnostics;
|
|
737
|
+
commitRequestAttempt(attempt, attemptKey, undefined);
|
|
738
|
+
journalRequest(undefined);
|
|
739
|
+
throw latchFatal(`通进司请求被拒:${result.diagnostics}`);
|
|
740
|
+
},
|
|
741
|
+
async wait(input, clock, signal) {
|
|
742
|
+
assertNotFatal();
|
|
743
|
+
if (activationTime === undefined) {
|
|
744
|
+
throw latchFatal("通进司等待需要激活");
|
|
745
|
+
}
|
|
746
|
+
if (!Number.isSafeInteger(input.durationMs) || input.durationMs < 1) {
|
|
747
|
+
throw new Error("通进司等待 durationMs 须为正安全整数");
|
|
748
|
+
}
|
|
749
|
+
if (input.durationMs > COLLECTOR_ELIGIBILITY_MS) {
|
|
750
|
+
throw new Error(`通进司等待 durationMs 至多为 ${COLLECTOR_ELIGIBILITY_MS}`);
|
|
751
|
+
}
|
|
752
|
+
if (pastCutoff(clock)) {
|
|
753
|
+
finalObservationRequired = true;
|
|
754
|
+
throw latchFatal("通进司等待不在资格截止前");
|
|
755
|
+
}
|
|
756
|
+
const remaining = remainingMs(clock);
|
|
757
|
+
// Single-wait runtime cadence cap (v2 §6 / §9); schema max stays 15m.
|
|
758
|
+
const COLLECTOR_SINGLE_WAIT_MAX_MS = 300_000;
|
|
759
|
+
const effectiveMs = Math.min(input.durationMs, remaining, COLLECTOR_SINGLE_WAIT_MAX_MS);
|
|
760
|
+
const startedAt = clock.wallNow().toISOString();
|
|
761
|
+
const waitId = sha256Text(`wait:${startedAt}:${effectiveMs}`).slice(0, 16);
|
|
762
|
+
await clock.sleep(effectiveMs, signal);
|
|
763
|
+
const endedAt = clock.wallNow().toISOString();
|
|
764
|
+
const cutoffReached = pastCutoff(clock);
|
|
765
|
+
const record = {
|
|
766
|
+
waitId,
|
|
767
|
+
requestedMs: input.durationMs,
|
|
768
|
+
effectiveMs,
|
|
769
|
+
startedAt,
|
|
770
|
+
endedAt,
|
|
771
|
+
cutoffReached,
|
|
772
|
+
};
|
|
773
|
+
commitWaitRecord(record);
|
|
774
|
+
appendJournal(COLLECTOR_WAIT_ENTRY_TYPE, {
|
|
775
|
+
waitRecord: record,
|
|
776
|
+
});
|
|
777
|
+
return {
|
|
778
|
+
waitId,
|
|
779
|
+
requestedMs: input.durationMs,
|
|
780
|
+
effectiveMs,
|
|
781
|
+
cutoffReached,
|
|
782
|
+
remainingMsAfter: remainingMs(clock),
|
|
783
|
+
};
|
|
784
|
+
},
|
|
785
|
+
getSnapshot(snapshotId) {
|
|
786
|
+
return snapshots.find((item) => item.snapshotId === snapshotId);
|
|
787
|
+
},
|
|
788
|
+
getEvidence(evidenceId) {
|
|
789
|
+
return evidenceById.get(evidenceId);
|
|
790
|
+
},
|
|
791
|
+
allEvidence() {
|
|
792
|
+
return [...evidenceById.values()];
|
|
793
|
+
},
|
|
794
|
+
allSnapshots() {
|
|
795
|
+
return [...snapshots];
|
|
796
|
+
},
|
|
797
|
+
requestAttempts() {
|
|
798
|
+
return [...attempts];
|
|
799
|
+
},
|
|
800
|
+
waits() {
|
|
801
|
+
return [...waits];
|
|
802
|
+
},
|
|
803
|
+
transportFailures() {
|
|
804
|
+
return [...transportFailures];
|
|
805
|
+
},
|
|
806
|
+
requestById(requestId) {
|
|
807
|
+
return config.manifest.requests.find((request) => request.id === requestId);
|
|
808
|
+
},
|
|
809
|
+
};
|
|
810
|
+
return ledger;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* #641 chain①: single authoritative evidence-record projection shared by the
|
|
814
|
+
* observe model view and the pointer-open read tool.
|
|
815
|
+
*/
|
|
816
|
+
export function projectEvidenceEntryView(record) {
|
|
817
|
+
return {
|
|
818
|
+
evidenceId: record.evidenceId,
|
|
819
|
+
kind: record.kind,
|
|
820
|
+
authorLogin: record.authorLogin,
|
|
821
|
+
state: record.state,
|
|
822
|
+
body: record.body,
|
|
823
|
+
commitOid: record.commitOid,
|
|
824
|
+
htmlUrl: record.htmlUrl,
|
|
825
|
+
path: record.path,
|
|
826
|
+
// Single display fallback: current line, else originalLine.
|
|
827
|
+
line: record.line ?? record.originalLine,
|
|
828
|
+
side: record.side,
|
|
829
|
+
authoritativeTime: record.authoritativeTime,
|
|
830
|
+
windowRelation: record.windowRelation,
|
|
831
|
+
pullRequestReviewId: record.pullRequestReviewId,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function buildObserveModelView(input) {
|
|
835
|
+
const relevant = input.records;
|
|
836
|
+
return {
|
|
837
|
+
snapshotId: input.snapshot.snapshotId,
|
|
838
|
+
observedAt: input.snapshot.observedAt,
|
|
839
|
+
completedAt: input.snapshot.completedAt,
|
|
840
|
+
prState: input.snapshot.prState,
|
|
841
|
+
headOid: input.snapshot.headOid,
|
|
842
|
+
complete: input.snapshot.complete,
|
|
843
|
+
evidence: relevant.map((record) => projectEvidenceEntryView(record)),
|
|
844
|
+
requestAttempts: input.attempts.map((attempt) => ({
|
|
845
|
+
attemptId: attempt.attemptId,
|
|
846
|
+
requestId: attempt.requestId,
|
|
847
|
+
observedHead: attempt.observedHead,
|
|
848
|
+
status: attempt.status,
|
|
849
|
+
marker: attempt.marker,
|
|
850
|
+
recoverySnapshotId: attempt.recoverySnapshotId,
|
|
851
|
+
})),
|
|
852
|
+
};
|
|
853
|
+
}
|