@riddledc/riddle-proof 0.5.40 → 0.5.42
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/checkpoint.cjs +28 -3
- package/dist/checkpoint.d.cts +2 -1
- package/dist/checkpoint.d.ts +2 -1
- package/dist/checkpoint.js +3 -1
- package/dist/{chunk-5LVCGDSD.js → chunk-RI25RGQP.js} +27 -3
- package/dist/{chunk-4YCWZVBN.js → chunk-U4VNN2CT.js} +2 -1
- package/dist/{chunk-COFOLRUW.js → chunk-VSLU6XNI.js} +31 -15
- package/dist/engine-harness.cjs +56 -17
- package/dist/engine-harness.js +3 -3
- package/dist/index.cjs +63 -22
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -3
- package/dist/proof-run-core.cjs +2 -1
- package/dist/proof-run-core.js +1 -1
- package/dist/proof-run-engine.cjs +2 -1
- package/dist/proof-run-engine.js +1 -1
- package/dist/types.d.cts +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/runtime/lib/recon.py +63 -6
- package/runtime/lib/ship.py +3 -1
- package/runtime/lib/verify.py +84 -9
- package/runtime/tests/recon_verify_smoke.py +100 -1
package/dist/checkpoint.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(checkpoint_exports, {
|
|
|
34
34
|
RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION: () => RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
|
|
35
35
|
authorPacketPayloadFromCheckpointResponse: () => authorPacketPayloadFromCheckpointResponse,
|
|
36
36
|
buildAuthorCheckpointPacket: () => buildAuthorCheckpointPacket,
|
|
37
|
+
checkpointResponseIdentity: () => checkpointResponseIdentity,
|
|
37
38
|
checkpointSummaryFromState: () => checkpointSummaryFromState,
|
|
38
39
|
isDuplicateCheckpointResponse: () => isDuplicateCheckpointResponse,
|
|
39
40
|
normalizeCheckpointResponse: () => normalizeCheckpointResponse,
|
|
@@ -77,6 +78,14 @@ function jsonCloneValue(value) {
|
|
|
77
78
|
return value;
|
|
78
79
|
}
|
|
79
80
|
}
|
|
81
|
+
function stableJson(value) {
|
|
82
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
83
|
+
const record = recordValue(value);
|
|
84
|
+
if (record) {
|
|
85
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
|
|
86
|
+
}
|
|
87
|
+
return JSON.stringify(value);
|
|
88
|
+
}
|
|
80
89
|
function compactText(value, limit = 1600) {
|
|
81
90
|
const text = nonEmptyString(value);
|
|
82
91
|
if (!text) return void 0;
|
|
@@ -234,6 +243,7 @@ function checkpointSummaryFromState(state, engineStatePath) {
|
|
|
234
243
|
const history = state.checkpoint_history || [];
|
|
235
244
|
const packets = history.filter((entry) => entry.packet);
|
|
236
245
|
const responses = history.filter((entry) => entry.response);
|
|
246
|
+
const duplicateResponses = (state.events || []).filter((event) => event.kind === "checkpoint.response.duplicate");
|
|
237
247
|
const latestPacketEntry = [...history].reverse().find((entry) => entry.packet);
|
|
238
248
|
const latestResponseEntry = [...history].reverse().find((entry) => entry.response);
|
|
239
249
|
const latestPacket = state.checkpoint_packet || latestPacketEntry?.packet;
|
|
@@ -245,6 +255,7 @@ function checkpointSummaryFromState(state, engineStatePath) {
|
|
|
245
255
|
pending: Boolean(state.checkpoint_packet),
|
|
246
256
|
packet_count: packets.length,
|
|
247
257
|
response_count: responses.length,
|
|
258
|
+
duplicate_response_count: duplicateResponses.length,
|
|
248
259
|
latest_checkpoint: state.checkpoint_packet?.checkpoint || latestResponse?.checkpoint || state.last_checkpoint || null,
|
|
249
260
|
latest_stage: state.checkpoint_packet?.stage || latestResponse?.continue_with_stage || state.current_stage || null,
|
|
250
261
|
latest_kind: state.checkpoint_packet?.kind || latestPacket?.kind || null,
|
|
@@ -260,9 +271,22 @@ function checkpointSummaryFromState(state, engineStatePath) {
|
|
|
260
271
|
});
|
|
261
272
|
}
|
|
262
273
|
function isDuplicateCheckpointResponse(state, response) {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
274
|
+
const identity = checkpointResponseIdentity(response);
|
|
275
|
+
return (state.checkpoint_history || []).some((entry) => entry.response ? checkpointResponseIdentity(entry.response) === identity : false);
|
|
276
|
+
}
|
|
277
|
+
function checkpointResponseIdentity(response) {
|
|
278
|
+
const logicalResponse = compactRecord({
|
|
279
|
+
run_id: response.run_id,
|
|
280
|
+
checkpoint: response.checkpoint,
|
|
281
|
+
resume_token: response.resume_token,
|
|
282
|
+
decision: response.decision,
|
|
283
|
+
summary: response.summary,
|
|
284
|
+
payload: response.payload,
|
|
285
|
+
reasons: response.reasons,
|
|
286
|
+
continue_with_stage: response.continue_with_stage,
|
|
287
|
+
source: response.source
|
|
288
|
+
});
|
|
289
|
+
return import_node_crypto.default.createHash("sha256").update(stableJson(logicalResponse)).digest("hex").slice(0, 24);
|
|
266
290
|
}
|
|
267
291
|
function authorPacketPayloadFromCheckpointResponse(response) {
|
|
268
292
|
if (response.decision !== "author_packet") return null;
|
|
@@ -310,6 +334,7 @@ function proofContractFromAuthorCheckpointResponse(response, packet, payload) {
|
|
|
310
334
|
RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
|
|
311
335
|
authorPacketPayloadFromCheckpointResponse,
|
|
312
336
|
buildAuthorCheckpointPacket,
|
|
337
|
+
checkpointResponseIdentity,
|
|
313
338
|
checkpointSummaryFromState,
|
|
314
339
|
isDuplicateCheckpointResponse,
|
|
315
340
|
normalizeCheckpointResponse,
|
package/dist/checkpoint.d.cts
CHANGED
|
@@ -20,7 +20,8 @@ declare function buildAuthorCheckpointPacket(input: {
|
|
|
20
20
|
declare function normalizeCheckpointResponse(value: unknown): RiddleProofCheckpointResponse | null;
|
|
21
21
|
declare function checkpointSummaryFromState(state: RiddleProofRunState, engineStatePath?: string | null): RiddleProofCheckpointSummary;
|
|
22
22
|
declare function isDuplicateCheckpointResponse(state: RiddleProofRunState, response: RiddleProofCheckpointResponse): boolean;
|
|
23
|
+
declare function checkpointResponseIdentity(response: RiddleProofCheckpointResponse): string;
|
|
23
24
|
declare function authorPacketPayloadFromCheckpointResponse(response: RiddleProofCheckpointResponse): Record<string, unknown> | null;
|
|
24
25
|
declare function proofContractFromAuthorCheckpointResponse(response: RiddleProofCheckpointResponse, packet: RiddleProofCheckpointPacket, payload: Record<string, unknown>): RiddleProofProofContract;
|
|
25
26
|
|
|
26
|
-
export { RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION, RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION, authorPacketPayloadFromCheckpointResponse, buildAuthorCheckpointPacket, checkpointSummaryFromState, isDuplicateCheckpointResponse, normalizeCheckpointResponse, proofContractFromAuthorCheckpointResponse, statePathsForRunState };
|
|
27
|
+
export { RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION, RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION, authorPacketPayloadFromCheckpointResponse, buildAuthorCheckpointPacket, checkpointResponseIdentity, checkpointSummaryFromState, isDuplicateCheckpointResponse, normalizeCheckpointResponse, proofContractFromAuthorCheckpointResponse, statePathsForRunState };
|
package/dist/checkpoint.d.ts
CHANGED
|
@@ -20,7 +20,8 @@ declare function buildAuthorCheckpointPacket(input: {
|
|
|
20
20
|
declare function normalizeCheckpointResponse(value: unknown): RiddleProofCheckpointResponse | null;
|
|
21
21
|
declare function checkpointSummaryFromState(state: RiddleProofRunState, engineStatePath?: string | null): RiddleProofCheckpointSummary;
|
|
22
22
|
declare function isDuplicateCheckpointResponse(state: RiddleProofRunState, response: RiddleProofCheckpointResponse): boolean;
|
|
23
|
+
declare function checkpointResponseIdentity(response: RiddleProofCheckpointResponse): string;
|
|
23
24
|
declare function authorPacketPayloadFromCheckpointResponse(response: RiddleProofCheckpointResponse): Record<string, unknown> | null;
|
|
24
25
|
declare function proofContractFromAuthorCheckpointResponse(response: RiddleProofCheckpointResponse, packet: RiddleProofCheckpointPacket, payload: Record<string, unknown>): RiddleProofProofContract;
|
|
25
26
|
|
|
26
|
-
export { RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION, RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION, authorPacketPayloadFromCheckpointResponse, buildAuthorCheckpointPacket, checkpointSummaryFromState, isDuplicateCheckpointResponse, normalizeCheckpointResponse, proofContractFromAuthorCheckpointResponse, statePathsForRunState };
|
|
27
|
+
export { RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION, RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION, authorPacketPayloadFromCheckpointResponse, buildAuthorCheckpointPacket, checkpointResponseIdentity, checkpointSummaryFromState, isDuplicateCheckpointResponse, normalizeCheckpointResponse, proofContractFromAuthorCheckpointResponse, statePathsForRunState };
|
package/dist/checkpoint.js
CHANGED
|
@@ -3,18 +3,20 @@ import {
|
|
|
3
3
|
RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
|
|
4
4
|
authorPacketPayloadFromCheckpointResponse,
|
|
5
5
|
buildAuthorCheckpointPacket,
|
|
6
|
+
checkpointResponseIdentity,
|
|
6
7
|
checkpointSummaryFromState,
|
|
7
8
|
isDuplicateCheckpointResponse,
|
|
8
9
|
normalizeCheckpointResponse,
|
|
9
10
|
proofContractFromAuthorCheckpointResponse,
|
|
10
11
|
statePathsForRunState
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-RI25RGQP.js";
|
|
12
13
|
import "./chunk-W7VTDN4T.js";
|
|
13
14
|
export {
|
|
14
15
|
RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION,
|
|
15
16
|
RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
|
|
16
17
|
authorPacketPayloadFromCheckpointResponse,
|
|
17
18
|
buildAuthorCheckpointPacket,
|
|
19
|
+
checkpointResponseIdentity,
|
|
18
20
|
checkpointSummaryFromState,
|
|
19
21
|
isDuplicateCheckpointResponse,
|
|
20
22
|
normalizeCheckpointResponse,
|
|
@@ -28,6 +28,14 @@ function jsonCloneValue(value) {
|
|
|
28
28
|
return value;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
function stableJson(value) {
|
|
32
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
33
|
+
const record = recordValue(value);
|
|
34
|
+
if (record) {
|
|
35
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
|
|
36
|
+
}
|
|
37
|
+
return JSON.stringify(value);
|
|
38
|
+
}
|
|
31
39
|
function compactText(value, limit = 1600) {
|
|
32
40
|
const text = nonEmptyString(value);
|
|
33
41
|
if (!text) return void 0;
|
|
@@ -185,6 +193,7 @@ function checkpointSummaryFromState(state, engineStatePath) {
|
|
|
185
193
|
const history = state.checkpoint_history || [];
|
|
186
194
|
const packets = history.filter((entry) => entry.packet);
|
|
187
195
|
const responses = history.filter((entry) => entry.response);
|
|
196
|
+
const duplicateResponses = (state.events || []).filter((event) => event.kind === "checkpoint.response.duplicate");
|
|
188
197
|
const latestPacketEntry = [...history].reverse().find((entry) => entry.packet);
|
|
189
198
|
const latestResponseEntry = [...history].reverse().find((entry) => entry.response);
|
|
190
199
|
const latestPacket = state.checkpoint_packet || latestPacketEntry?.packet;
|
|
@@ -196,6 +205,7 @@ function checkpointSummaryFromState(state, engineStatePath) {
|
|
|
196
205
|
pending: Boolean(state.checkpoint_packet),
|
|
197
206
|
packet_count: packets.length,
|
|
198
207
|
response_count: responses.length,
|
|
208
|
+
duplicate_response_count: duplicateResponses.length,
|
|
199
209
|
latest_checkpoint: state.checkpoint_packet?.checkpoint || latestResponse?.checkpoint || state.last_checkpoint || null,
|
|
200
210
|
latest_stage: state.checkpoint_packet?.stage || latestResponse?.continue_with_stage || state.current_stage || null,
|
|
201
211
|
latest_kind: state.checkpoint_packet?.kind || latestPacket?.kind || null,
|
|
@@ -211,9 +221,22 @@ function checkpointSummaryFromState(state, engineStatePath) {
|
|
|
211
221
|
});
|
|
212
222
|
}
|
|
213
223
|
function isDuplicateCheckpointResponse(state, response) {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
const identity = checkpointResponseIdentity(response);
|
|
225
|
+
return (state.checkpoint_history || []).some((entry) => entry.response ? checkpointResponseIdentity(entry.response) === identity : false);
|
|
226
|
+
}
|
|
227
|
+
function checkpointResponseIdentity(response) {
|
|
228
|
+
const logicalResponse = compactRecord({
|
|
229
|
+
run_id: response.run_id,
|
|
230
|
+
checkpoint: response.checkpoint,
|
|
231
|
+
resume_token: response.resume_token,
|
|
232
|
+
decision: response.decision,
|
|
233
|
+
summary: response.summary,
|
|
234
|
+
payload: response.payload,
|
|
235
|
+
reasons: response.reasons,
|
|
236
|
+
continue_with_stage: response.continue_with_stage,
|
|
237
|
+
source: response.source
|
|
238
|
+
});
|
|
239
|
+
return crypto.createHash("sha256").update(stableJson(logicalResponse)).digest("hex").slice(0, 24);
|
|
217
240
|
}
|
|
218
241
|
function authorPacketPayloadFromCheckpointResponse(response) {
|
|
219
242
|
if (response.decision !== "author_packet") return null;
|
|
@@ -264,6 +287,7 @@ export {
|
|
|
264
287
|
normalizeCheckpointResponse,
|
|
265
288
|
checkpointSummaryFromState,
|
|
266
289
|
isDuplicateCheckpointResponse,
|
|
290
|
+
checkpointResponseIdentity,
|
|
267
291
|
authorPacketPayloadFromCheckpointResponse,
|
|
268
292
|
proofContractFromAuthorCheckpointResponse
|
|
269
293
|
};
|
|
@@ -371,7 +371,8 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
371
371
|
if (visualDelta.status === "measured" && visualDelta.passed === true) return null;
|
|
372
372
|
const status = String(visualDelta.status || "missing");
|
|
373
373
|
if (status === "unmeasured") {
|
|
374
|
-
|
|
374
|
+
const reason2 = String(visualDelta.reason || "").trim();
|
|
375
|
+
return reason2 ? `visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof: ${reason2}` : "visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof";
|
|
375
376
|
}
|
|
376
377
|
if (status === "measured" && visualDelta.passed === false) {
|
|
377
378
|
return "visual_delta.status=measured but visual_delta.passed=false blocks ready_to_ship for visual/UI proof";
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
authorPacketPayloadFromCheckpointResponse,
|
|
3
3
|
buildAuthorCheckpointPacket,
|
|
4
|
+
checkpointResponseIdentity,
|
|
4
5
|
checkpointSummaryFromState,
|
|
5
6
|
isDuplicateCheckpointResponse,
|
|
6
7
|
normalizeCheckpointResponse,
|
|
7
8
|
proofContractFromAuthorCheckpointResponse,
|
|
8
9
|
statePathsForRunState
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-RI25RGQP.js";
|
|
10
11
|
import {
|
|
11
12
|
appendRunEvent,
|
|
12
13
|
appendStageHeartbeat,
|
|
@@ -25,7 +26,7 @@ import {
|
|
|
25
26
|
} from "./chunk-W7VTDN4T.js";
|
|
26
27
|
import {
|
|
27
28
|
visualDeltaShipGateReason
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-U4VNN2CT.js";
|
|
29
30
|
|
|
30
31
|
// src/engine-harness.ts
|
|
31
32
|
import { execFileSync } from "child_process";
|
|
@@ -497,20 +498,35 @@ function checkpointResponseContinuation(state, value) {
|
|
|
497
498
|
}
|
|
498
499
|
};
|
|
499
500
|
}
|
|
500
|
-
if (
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
501
|
+
if (isDuplicateCheckpointResponse(state, response)) {
|
|
502
|
+
const stage = packet?.stage || state.current_stage || "author";
|
|
503
|
+
recordEvent(state, {
|
|
504
|
+
kind: "checkpoint.response.duplicate",
|
|
505
|
+
checkpoint: response.checkpoint,
|
|
506
|
+
stage,
|
|
507
|
+
summary: "Duplicate checkpoint response ignored.",
|
|
508
|
+
details: compactRecord({
|
|
509
|
+
decision: response.decision,
|
|
510
|
+
resume_token: response.resume_token,
|
|
511
|
+
response_identity: checkpointResponseIdentity(response)
|
|
512
|
+
})
|
|
513
|
+
});
|
|
514
|
+
return {
|
|
515
|
+
blocker: {
|
|
516
|
+
code: "checkpoint_response_duplicate",
|
|
517
|
+
checkpoint: response.checkpoint,
|
|
518
|
+
message: "Checkpoint response was already accepted for this run/checkpoint/resume token and was not applied again.",
|
|
519
|
+
details: {
|
|
520
|
+
stage,
|
|
521
|
+
duplicate: true,
|
|
522
|
+
response,
|
|
523
|
+
response_identity: checkpointResponseIdentity(response),
|
|
524
|
+
checkpoint_summary: checkpointSummaryFromState(state)
|
|
511
525
|
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
if (!packet) {
|
|
514
530
|
return {
|
|
515
531
|
blocker: {
|
|
516
532
|
code: "checkpoint_response_without_packet",
|
package/dist/engine-harness.cjs
CHANGED
|
@@ -379,7 +379,8 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
379
379
|
if (visualDelta.status === "measured" && visualDelta.passed === true) return null;
|
|
380
380
|
const status = String(visualDelta.status || "missing");
|
|
381
381
|
if (status === "unmeasured") {
|
|
382
|
-
|
|
382
|
+
const reason2 = String(visualDelta.reason || "").trim();
|
|
383
|
+
return reason2 ? `visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof: ${reason2}` : "visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof";
|
|
383
384
|
}
|
|
384
385
|
if (status === "measured" && visualDelta.passed === false) {
|
|
385
386
|
return "visual_delta.status=measured but visual_delta.passed=false blocks ready_to_ship for visual/UI proof";
|
|
@@ -3163,6 +3164,14 @@ function jsonCloneValue(value) {
|
|
|
3163
3164
|
return value;
|
|
3164
3165
|
}
|
|
3165
3166
|
}
|
|
3167
|
+
function stableJson(value) {
|
|
3168
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
3169
|
+
const record = recordValue(value);
|
|
3170
|
+
if (record) {
|
|
3171
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
|
|
3172
|
+
}
|
|
3173
|
+
return JSON.stringify(value);
|
|
3174
|
+
}
|
|
3166
3175
|
function compactText(value, limit = 1600) {
|
|
3167
3176
|
const text = nonEmptyString(value);
|
|
3168
3177
|
if (!text) return void 0;
|
|
@@ -3320,6 +3329,7 @@ function checkpointSummaryFromState(state, engineStatePath2) {
|
|
|
3320
3329
|
const history = state.checkpoint_history || [];
|
|
3321
3330
|
const packets = history.filter((entry) => entry.packet);
|
|
3322
3331
|
const responses = history.filter((entry) => entry.response);
|
|
3332
|
+
const duplicateResponses = (state.events || []).filter((event) => event.kind === "checkpoint.response.duplicate");
|
|
3323
3333
|
const latestPacketEntry = [...history].reverse().find((entry) => entry.packet);
|
|
3324
3334
|
const latestResponseEntry = [...history].reverse().find((entry) => entry.response);
|
|
3325
3335
|
const latestPacket = state.checkpoint_packet || latestPacketEntry?.packet;
|
|
@@ -3331,6 +3341,7 @@ function checkpointSummaryFromState(state, engineStatePath2) {
|
|
|
3331
3341
|
pending: Boolean(state.checkpoint_packet),
|
|
3332
3342
|
packet_count: packets.length,
|
|
3333
3343
|
response_count: responses.length,
|
|
3344
|
+
duplicate_response_count: duplicateResponses.length,
|
|
3334
3345
|
latest_checkpoint: state.checkpoint_packet?.checkpoint || latestResponse?.checkpoint || state.last_checkpoint || null,
|
|
3335
3346
|
latest_stage: state.checkpoint_packet?.stage || latestResponse?.continue_with_stage || state.current_stage || null,
|
|
3336
3347
|
latest_kind: state.checkpoint_packet?.kind || latestPacket?.kind || null,
|
|
@@ -3346,9 +3357,22 @@ function checkpointSummaryFromState(state, engineStatePath2) {
|
|
|
3346
3357
|
});
|
|
3347
3358
|
}
|
|
3348
3359
|
function isDuplicateCheckpointResponse(state, response) {
|
|
3349
|
-
const
|
|
3350
|
-
|
|
3351
|
-
|
|
3360
|
+
const identity = checkpointResponseIdentity(response);
|
|
3361
|
+
return (state.checkpoint_history || []).some((entry) => entry.response ? checkpointResponseIdentity(entry.response) === identity : false);
|
|
3362
|
+
}
|
|
3363
|
+
function checkpointResponseIdentity(response) {
|
|
3364
|
+
const logicalResponse = compactRecord({
|
|
3365
|
+
run_id: response.run_id,
|
|
3366
|
+
checkpoint: response.checkpoint,
|
|
3367
|
+
resume_token: response.resume_token,
|
|
3368
|
+
decision: response.decision,
|
|
3369
|
+
summary: response.summary,
|
|
3370
|
+
payload: response.payload,
|
|
3371
|
+
reasons: response.reasons,
|
|
3372
|
+
continue_with_stage: response.continue_with_stage,
|
|
3373
|
+
source: response.source
|
|
3374
|
+
});
|
|
3375
|
+
return import_node_crypto.default.createHash("sha256").update(stableJson(logicalResponse)).digest("hex").slice(0, 24);
|
|
3352
3376
|
}
|
|
3353
3377
|
function authorPacketPayloadFromCheckpointResponse(response) {
|
|
3354
3378
|
if (response.decision !== "author_packet") return null;
|
|
@@ -3851,20 +3875,35 @@ function checkpointResponseContinuation(state, value) {
|
|
|
3851
3875
|
}
|
|
3852
3876
|
};
|
|
3853
3877
|
}
|
|
3854
|
-
if (
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3878
|
+
if (isDuplicateCheckpointResponse(state, response)) {
|
|
3879
|
+
const stage = packet?.stage || state.current_stage || "author";
|
|
3880
|
+
recordEvent(state, {
|
|
3881
|
+
kind: "checkpoint.response.duplicate",
|
|
3882
|
+
checkpoint: response.checkpoint,
|
|
3883
|
+
stage,
|
|
3884
|
+
summary: "Duplicate checkpoint response ignored.",
|
|
3885
|
+
details: compactRecord({
|
|
3886
|
+
decision: response.decision,
|
|
3887
|
+
resume_token: response.resume_token,
|
|
3888
|
+
response_identity: checkpointResponseIdentity(response)
|
|
3889
|
+
})
|
|
3890
|
+
});
|
|
3891
|
+
return {
|
|
3892
|
+
blocker: {
|
|
3893
|
+
code: "checkpoint_response_duplicate",
|
|
3894
|
+
checkpoint: response.checkpoint,
|
|
3895
|
+
message: "Checkpoint response was already accepted for this run/checkpoint/resume token and was not applied again.",
|
|
3896
|
+
details: {
|
|
3897
|
+
stage,
|
|
3898
|
+
duplicate: true,
|
|
3899
|
+
response,
|
|
3900
|
+
response_identity: checkpointResponseIdentity(response),
|
|
3901
|
+
checkpoint_summary: checkpointSummaryFromState(state)
|
|
3865
3902
|
}
|
|
3866
|
-
}
|
|
3867
|
-
}
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
if (!packet) {
|
|
3868
3907
|
return {
|
|
3869
3908
|
blocker: {
|
|
3870
3909
|
code: "checkpoint_response_without_packet",
|
package/dist/engine-harness.js
CHANGED
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
createDisabledRiddleProofAgentAdapter,
|
|
3
3
|
readRiddleProofRunStatus,
|
|
4
4
|
runRiddleProofEngineHarness
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-VSLU6XNI.js";
|
|
6
|
+
import "./chunk-RI25RGQP.js";
|
|
7
7
|
import "./chunk-7S7O3NKF.js";
|
|
8
8
|
import "./chunk-W7VTDN4T.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-U4VNN2CT.js";
|
|
10
10
|
export {
|
|
11
11
|
createDisabledRiddleProofAgentAdapter,
|
|
12
12
|
readRiddleProofRunStatus,
|
package/dist/index.cjs
CHANGED
|
@@ -379,7 +379,8 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
379
379
|
if (visualDelta.status === "measured" && visualDelta.passed === true) return null;
|
|
380
380
|
const status = String(visualDelta.status || "missing");
|
|
381
381
|
if (status === "unmeasured") {
|
|
382
|
-
|
|
382
|
+
const reason2 = String(visualDelta.reason || "").trim();
|
|
383
|
+
return reason2 ? `visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof: ${reason2}` : "visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof";
|
|
383
384
|
}
|
|
384
385
|
if (status === "measured" && visualDelta.passed === false) {
|
|
385
386
|
return "visual_delta.status=measured but visual_delta.passed=false blocks ready_to_ship for visual/UI proof";
|
|
@@ -2751,6 +2752,7 @@ __export(index_exports, {
|
|
|
2751
2752
|
authorPacketPayloadFromCheckpointResponse: () => authorPacketPayloadFromCheckpointResponse,
|
|
2752
2753
|
buildAuthorCheckpointPacket: () => buildAuthorCheckpointPacket,
|
|
2753
2754
|
buildVisualProofSession: () => buildVisualProofSession,
|
|
2755
|
+
checkpointResponseIdentity: () => checkpointResponseIdentity,
|
|
2754
2756
|
checkpointSummaryFromState: () => checkpointSummaryFromState,
|
|
2755
2757
|
compactRecord: () => compactRecord,
|
|
2756
2758
|
compareVisualProofSessionFingerprint: () => compareVisualProofSessionFingerprint,
|
|
@@ -3254,6 +3256,14 @@ function jsonCloneValue(value) {
|
|
|
3254
3256
|
return value;
|
|
3255
3257
|
}
|
|
3256
3258
|
}
|
|
3259
|
+
function stableJson(value) {
|
|
3260
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
3261
|
+
const record = recordValue(value);
|
|
3262
|
+
if (record) {
|
|
3263
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
|
|
3264
|
+
}
|
|
3265
|
+
return JSON.stringify(value);
|
|
3266
|
+
}
|
|
3257
3267
|
function compactText(value, limit = 1600) {
|
|
3258
3268
|
const text = nonEmptyString(value);
|
|
3259
3269
|
if (!text) return void 0;
|
|
@@ -3411,6 +3421,7 @@ function checkpointSummaryFromState(state, engineStatePath2) {
|
|
|
3411
3421
|
const history = state.checkpoint_history || [];
|
|
3412
3422
|
const packets = history.filter((entry) => entry.packet);
|
|
3413
3423
|
const responses = history.filter((entry) => entry.response);
|
|
3424
|
+
const duplicateResponses = (state.events || []).filter((event) => event.kind === "checkpoint.response.duplicate");
|
|
3414
3425
|
const latestPacketEntry = [...history].reverse().find((entry) => entry.packet);
|
|
3415
3426
|
const latestResponseEntry = [...history].reverse().find((entry) => entry.response);
|
|
3416
3427
|
const latestPacket = state.checkpoint_packet || latestPacketEntry?.packet;
|
|
@@ -3422,6 +3433,7 @@ function checkpointSummaryFromState(state, engineStatePath2) {
|
|
|
3422
3433
|
pending: Boolean(state.checkpoint_packet),
|
|
3423
3434
|
packet_count: packets.length,
|
|
3424
3435
|
response_count: responses.length,
|
|
3436
|
+
duplicate_response_count: duplicateResponses.length,
|
|
3425
3437
|
latest_checkpoint: state.checkpoint_packet?.checkpoint || latestResponse?.checkpoint || state.last_checkpoint || null,
|
|
3426
3438
|
latest_stage: state.checkpoint_packet?.stage || latestResponse?.continue_with_stage || state.current_stage || null,
|
|
3427
3439
|
latest_kind: state.checkpoint_packet?.kind || latestPacket?.kind || null,
|
|
@@ -3437,9 +3449,22 @@ function checkpointSummaryFromState(state, engineStatePath2) {
|
|
|
3437
3449
|
});
|
|
3438
3450
|
}
|
|
3439
3451
|
function isDuplicateCheckpointResponse(state, response) {
|
|
3440
|
-
const
|
|
3441
|
-
|
|
3442
|
-
|
|
3452
|
+
const identity = checkpointResponseIdentity(response);
|
|
3453
|
+
return (state.checkpoint_history || []).some((entry) => entry.response ? checkpointResponseIdentity(entry.response) === identity : false);
|
|
3454
|
+
}
|
|
3455
|
+
function checkpointResponseIdentity(response) {
|
|
3456
|
+
const logicalResponse = compactRecord({
|
|
3457
|
+
run_id: response.run_id,
|
|
3458
|
+
checkpoint: response.checkpoint,
|
|
3459
|
+
resume_token: response.resume_token,
|
|
3460
|
+
decision: response.decision,
|
|
3461
|
+
summary: response.summary,
|
|
3462
|
+
payload: response.payload,
|
|
3463
|
+
reasons: response.reasons,
|
|
3464
|
+
continue_with_stage: response.continue_with_stage,
|
|
3465
|
+
source: response.source
|
|
3466
|
+
});
|
|
3467
|
+
return import_node_crypto.default.createHash("sha256").update(stableJson(logicalResponse)).digest("hex").slice(0, 24);
|
|
3443
3468
|
}
|
|
3444
3469
|
function authorPacketPayloadFromCheckpointResponse(response) {
|
|
3445
3470
|
if (response.decision !== "author_packet") return null;
|
|
@@ -4428,20 +4453,35 @@ function checkpointResponseContinuation(state, value) {
|
|
|
4428
4453
|
}
|
|
4429
4454
|
};
|
|
4430
4455
|
}
|
|
4431
|
-
if (
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4456
|
+
if (isDuplicateCheckpointResponse(state, response)) {
|
|
4457
|
+
const stage = packet?.stage || state.current_stage || "author";
|
|
4458
|
+
recordEvent(state, {
|
|
4459
|
+
kind: "checkpoint.response.duplicate",
|
|
4460
|
+
checkpoint: response.checkpoint,
|
|
4461
|
+
stage,
|
|
4462
|
+
summary: "Duplicate checkpoint response ignored.",
|
|
4463
|
+
details: compactRecord({
|
|
4464
|
+
decision: response.decision,
|
|
4465
|
+
resume_token: response.resume_token,
|
|
4466
|
+
response_identity: checkpointResponseIdentity(response)
|
|
4467
|
+
})
|
|
4468
|
+
});
|
|
4469
|
+
return {
|
|
4470
|
+
blocker: {
|
|
4471
|
+
code: "checkpoint_response_duplicate",
|
|
4472
|
+
checkpoint: response.checkpoint,
|
|
4473
|
+
message: "Checkpoint response was already accepted for this run/checkpoint/resume token and was not applied again.",
|
|
4474
|
+
details: {
|
|
4475
|
+
stage,
|
|
4476
|
+
duplicate: true,
|
|
4477
|
+
response,
|
|
4478
|
+
response_identity: checkpointResponseIdentity(response),
|
|
4479
|
+
checkpoint_summary: checkpointSummaryFromState(state)
|
|
4442
4480
|
}
|
|
4443
|
-
}
|
|
4444
|
-
}
|
|
4481
|
+
}
|
|
4482
|
+
};
|
|
4483
|
+
}
|
|
4484
|
+
if (!packet) {
|
|
4445
4485
|
return {
|
|
4446
4486
|
blocker: {
|
|
4447
4487
|
code: "checkpoint_response_without_packet",
|
|
@@ -5187,12 +5227,12 @@ function hashString(value) {
|
|
|
5187
5227
|
if (!text) return void 0;
|
|
5188
5228
|
return (0, import_node_crypto4.createHash)("sha256").update(text).digest("hex");
|
|
5189
5229
|
}
|
|
5190
|
-
function
|
|
5230
|
+
function stableJson2(value) {
|
|
5191
5231
|
if (value === void 0) return "null";
|
|
5192
5232
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
5193
|
-
if (Array.isArray(value)) return `[${value.map((item) =>
|
|
5233
|
+
if (Array.isArray(value)) return `[${value.map((item) => stableJson2(item)).join(",")}]`;
|
|
5194
5234
|
const record = value;
|
|
5195
|
-
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${
|
|
5235
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson2(record[key])}`).join(",")}}`;
|
|
5196
5236
|
}
|
|
5197
5237
|
function withoutUndefined(record) {
|
|
5198
5238
|
for (const key of Object.keys(record)) {
|
|
@@ -5218,7 +5258,7 @@ function visualSessionFingerprintBasis(input) {
|
|
|
5218
5258
|
}
|
|
5219
5259
|
function visualSessionFingerprint(input) {
|
|
5220
5260
|
const basis = input.version === RIDDLE_PROOF_VISUAL_SESSION_FINGERPRINT_VERSION ? input : visualSessionFingerprintBasis(input);
|
|
5221
|
-
return (0, import_node_crypto4.createHash)("sha256").update(
|
|
5261
|
+
return (0, import_node_crypto4.createHash)("sha256").update(stableJson2(basis)).digest("hex");
|
|
5222
5262
|
}
|
|
5223
5263
|
function buildVisualProofSession(input) {
|
|
5224
5264
|
const basis = visualSessionFingerprintBasis(input);
|
|
@@ -5279,7 +5319,7 @@ function compareVisualProofSessionFingerprint(parent, input) {
|
|
|
5279
5319
|
for (const key of keys) {
|
|
5280
5320
|
const expectedValue = expectedRecord[key];
|
|
5281
5321
|
const actualValue = actualRecord[key];
|
|
5282
|
-
if (
|
|
5322
|
+
if (stableJson2(expectedValue) !== stableJson2(actualValue)) {
|
|
5283
5323
|
mismatches.push({ key, expected: expectedValue, actual: actualValue });
|
|
5284
5324
|
}
|
|
5285
5325
|
}
|
|
@@ -5661,6 +5701,7 @@ function parseJson(value) {
|
|
|
5661
5701
|
authorPacketPayloadFromCheckpointResponse,
|
|
5662
5702
|
buildAuthorCheckpointPacket,
|
|
5663
5703
|
buildVisualProofSession,
|
|
5704
|
+
checkpointResponseIdentity,
|
|
5664
5705
|
checkpointSummaryFromState,
|
|
5665
5706
|
compactRecord,
|
|
5666
5707
|
compareVisualProofSessionFingerprint,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { EvidenceArtifact, EvidenceReference, ImplementationAdapter, ImplementationAdapterInput, ImplementationAdapterResult, IntegrationContext, JsonObject, JsonPrimitive, JsonValue, JudgeAdapter, NotificationAdapter, PreflightAdapter, PreflightAdapterInput, PreflightAdapterResult, ProofAdapter, ProofAdapterInput, ProofAdapterResult, RiddleProofArtifactRole, RiddleProofAssessment, RiddleProofBlocker, RiddleProofCheckpointArtifact, RiddleProofCheckpointPacket, RiddleProofCheckpointResponse, RiddleProofCheckpointRole, RiddleProofCheckpointRoutingHint, RiddleProofCheckpointSummary, RiddleProofCheckpointVisibility, RiddleProofDecision, RiddleProofEvent, RiddleProofEvidenceBundle, RiddleProofPrLifecycleState, RiddleProofPrLifecycleStatus, RiddleProofProofContract, RiddleProofRunParams, RiddleProofRunResult, RiddleProofRunState, RiddleProofRunStatusSnapshot, RiddleProofStage, RiddleProofStatePaths, RiddleProofStatus, RiddleProofTerminalMetadata, RiddleProofVerificationMode, RiddleProofVisualSession, RiddleProofVisualSessionFingerprintBasis, SetupAdapter, SetupAdapterInput, SetupAdapterResult, ShipAdapter } from './types.cjs';
|
|
2
2
|
export { TerminalMetadataInput, applyTerminalMetadata, compactRecord, createRunResult, isSuccessfulStatus, isTerminalStatus, nonEmptyString, normalizeTerminalMetadata, recordValue } from './result.cjs';
|
|
3
3
|
export { CreateRunStateInput, RIDDLE_PROOF_RUN_STATE_VERSION, RunEventInput, appendRunEvent, appendStageHeartbeat, applyPrLifecycleState, createRunState, createRunStatusSnapshot, normalizeIntegrationContext, normalizePrLifecycleState, normalizeRunParams, setRunStatus } from './state.cjs';
|
|
4
|
-
export { RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION, RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION, authorPacketPayloadFromCheckpointResponse, buildAuthorCheckpointPacket, checkpointSummaryFromState, isDuplicateCheckpointResponse, normalizeCheckpointResponse, proofContractFromAuthorCheckpointResponse, statePathsForRunState } from './checkpoint.cjs';
|
|
4
|
+
export { RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION, RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION, authorPacketPayloadFromCheckpointResponse, buildAuthorCheckpointPacket, checkpointResponseIdentity, checkpointSummaryFromState, isDuplicateCheckpointResponse, normalizeCheckpointResponse, proofContractFromAuthorCheckpointResponse, statePathsForRunState } from './checkpoint.cjs';
|
|
5
5
|
export { RiddleProofRunnerAdapters, RunRiddleProofInput, runRiddleProof } from './runner.cjs';
|
|
6
6
|
export { RiddleProofAgentAdapter, RiddleProofAgentPayload, RiddleProofCheckpointMode, RiddleProofEngine, RiddleProofEngineHarnessConfig, RiddleProofEngineHarnessContext, RiddleProofEngineResult, RiddleProofShipMode, RiddleProofWorkflowParams, RunRiddleProofEngineHarnessInput, createDisabledRiddleProofAgentAdapter, readRiddleProofRunStatus, runRiddleProofEngineHarness } from './engine-harness.cjs';
|
|
7
7
|
export { CreateCaptureDiagnosticInput, DEFAULT_DIAGNOSTIC_ARRAY_LIMIT, DEFAULT_DIAGNOSTIC_HISTORY_LIMIT, DEFAULT_DIAGNOSTIC_STRING_LIMIT, RIDDLE_PROOF_CAPTURE_DIAGNOSTIC_VERSION, RiddleProofArtifactSummary, RiddleProofCaptureArtifactSummary, RiddleProofCaptureDiagnostic, RiddleProofDiagnosticRedactionOptions, appendCaptureDiagnostic, createCaptureDiagnostic, redactForProofDiagnostics, summarizeCaptureArtifacts } from './diagnostics.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { EvidenceArtifact, EvidenceReference, ImplementationAdapter, ImplementationAdapterInput, ImplementationAdapterResult, IntegrationContext, JsonObject, JsonPrimitive, JsonValue, JudgeAdapter, NotificationAdapter, PreflightAdapter, PreflightAdapterInput, PreflightAdapterResult, ProofAdapter, ProofAdapterInput, ProofAdapterResult, RiddleProofArtifactRole, RiddleProofAssessment, RiddleProofBlocker, RiddleProofCheckpointArtifact, RiddleProofCheckpointPacket, RiddleProofCheckpointResponse, RiddleProofCheckpointRole, RiddleProofCheckpointRoutingHint, RiddleProofCheckpointSummary, RiddleProofCheckpointVisibility, RiddleProofDecision, RiddleProofEvent, RiddleProofEvidenceBundle, RiddleProofPrLifecycleState, RiddleProofPrLifecycleStatus, RiddleProofProofContract, RiddleProofRunParams, RiddleProofRunResult, RiddleProofRunState, RiddleProofRunStatusSnapshot, RiddleProofStage, RiddleProofStatePaths, RiddleProofStatus, RiddleProofTerminalMetadata, RiddleProofVerificationMode, RiddleProofVisualSession, RiddleProofVisualSessionFingerprintBasis, SetupAdapter, SetupAdapterInput, SetupAdapterResult, ShipAdapter } from './types.js';
|
|
2
2
|
export { TerminalMetadataInput, applyTerminalMetadata, compactRecord, createRunResult, isSuccessfulStatus, isTerminalStatus, nonEmptyString, normalizeTerminalMetadata, recordValue } from './result.js';
|
|
3
3
|
export { CreateRunStateInput, RIDDLE_PROOF_RUN_STATE_VERSION, RunEventInput, appendRunEvent, appendStageHeartbeat, applyPrLifecycleState, createRunState, createRunStatusSnapshot, normalizeIntegrationContext, normalizePrLifecycleState, normalizeRunParams, setRunStatus } from './state.js';
|
|
4
|
-
export { RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION, RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION, authorPacketPayloadFromCheckpointResponse, buildAuthorCheckpointPacket, checkpointSummaryFromState, isDuplicateCheckpointResponse, normalizeCheckpointResponse, proofContractFromAuthorCheckpointResponse, statePathsForRunState } from './checkpoint.js';
|
|
4
|
+
export { RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION, RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION, authorPacketPayloadFromCheckpointResponse, buildAuthorCheckpointPacket, checkpointResponseIdentity, checkpointSummaryFromState, isDuplicateCheckpointResponse, normalizeCheckpointResponse, proofContractFromAuthorCheckpointResponse, statePathsForRunState } from './checkpoint.js';
|
|
5
5
|
export { RiddleProofRunnerAdapters, RunRiddleProofInput, runRiddleProof } from './runner.js';
|
|
6
6
|
export { RiddleProofAgentAdapter, RiddleProofAgentPayload, RiddleProofCheckpointMode, RiddleProofEngine, RiddleProofEngineHarnessConfig, RiddleProofEngineHarnessContext, RiddleProofEngineResult, RiddleProofShipMode, RiddleProofWorkflowParams, RunRiddleProofEngineHarnessInput, createDisabledRiddleProofAgentAdapter, readRiddleProofRunStatus, runRiddleProofEngineHarness } from './engine-harness.js';
|
|
7
7
|
export { CreateCaptureDiagnosticInput, DEFAULT_DIAGNOSTIC_ARRAY_LIMIT, DEFAULT_DIAGNOSTIC_HISTORY_LIMIT, DEFAULT_DIAGNOSTIC_STRING_LIMIT, RIDDLE_PROOF_CAPTURE_DIAGNOSTIC_VERSION, RiddleProofArtifactSummary, RiddleProofCaptureArtifactSummary, RiddleProofCaptureDiagnostic, RiddleProofDiagnosticRedactionOptions, appendCaptureDiagnostic, createCaptureDiagnostic, redactForProofDiagnostics, summarizeCaptureArtifacts } from './diagnostics.js';
|
package/dist/index.js
CHANGED
|
@@ -25,18 +25,19 @@ import {
|
|
|
25
25
|
createDisabledRiddleProofAgentAdapter,
|
|
26
26
|
readRiddleProofRunStatus,
|
|
27
27
|
runRiddleProofEngineHarness
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-VSLU6XNI.js";
|
|
29
29
|
import {
|
|
30
30
|
RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION,
|
|
31
31
|
RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
|
|
32
32
|
authorPacketPayloadFromCheckpointResponse,
|
|
33
33
|
buildAuthorCheckpointPacket,
|
|
34
|
+
checkpointResponseIdentity,
|
|
34
35
|
checkpointSummaryFromState,
|
|
35
36
|
isDuplicateCheckpointResponse,
|
|
36
37
|
normalizeCheckpointResponse,
|
|
37
38
|
proofContractFromAuthorCheckpointResponse,
|
|
38
39
|
statePathsForRunState
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-RI25RGQP.js";
|
|
40
41
|
import {
|
|
41
42
|
RIDDLE_PROOF_RUN_STATE_VERSION,
|
|
42
43
|
appendRunEvent,
|
|
@@ -66,7 +67,7 @@ import {
|
|
|
66
67
|
extractPlayabilityEvidence,
|
|
67
68
|
isRiddleProofPlayabilityMode
|
|
68
69
|
} from "./chunk-NSWT3VSV.js";
|
|
69
|
-
import "./chunk-
|
|
70
|
+
import "./chunk-U4VNN2CT.js";
|
|
70
71
|
export {
|
|
71
72
|
DEFAULT_DIAGNOSTIC_ARRAY_LIMIT,
|
|
72
73
|
DEFAULT_DIAGNOSTIC_HISTORY_LIMIT,
|
|
@@ -88,6 +89,7 @@ export {
|
|
|
88
89
|
authorPacketPayloadFromCheckpointResponse,
|
|
89
90
|
buildAuthorCheckpointPacket,
|
|
90
91
|
buildVisualProofSession,
|
|
92
|
+
checkpointResponseIdentity,
|
|
91
93
|
checkpointSummaryFromState,
|
|
92
94
|
compactRecord,
|
|
93
95
|
compareVisualProofSessionFingerprint,
|
package/dist/proof-run-core.cjs
CHANGED
|
@@ -430,7 +430,8 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
430
430
|
if (visualDelta.status === "measured" && visualDelta.passed === true) return null;
|
|
431
431
|
const status = String(visualDelta.status || "missing");
|
|
432
432
|
if (status === "unmeasured") {
|
|
433
|
-
|
|
433
|
+
const reason2 = String(visualDelta.reason || "").trim();
|
|
434
|
+
return reason2 ? `visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof: ${reason2}` : "visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof";
|
|
434
435
|
}
|
|
435
436
|
if (status === "measured" && visualDelta.passed === false) {
|
|
436
437
|
return "visual_delta.status=measured but visual_delta.passed=false blocks ready_to_ship for visual/UI proof";
|
package/dist/proof-run-core.js
CHANGED
|
@@ -412,7 +412,8 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
412
412
|
if (visualDelta.status === "measured" && visualDelta.passed === true) return null;
|
|
413
413
|
const status = String(visualDelta.status || "missing");
|
|
414
414
|
if (status === "unmeasured") {
|
|
415
|
-
|
|
415
|
+
const reason2 = String(visualDelta.reason || "").trim();
|
|
416
|
+
return reason2 ? `visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof: ${reason2}` : "visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof";
|
|
416
417
|
}
|
|
417
418
|
if (status === "measured" && visualDelta.passed === false) {
|
|
418
419
|
return "visual_delta.status=measured but visual_delta.passed=false blocks ready_to_ship for visual/UI proof";
|
package/dist/proof-run-engine.js
CHANGED
package/dist/types.d.cts
CHANGED
|
@@ -114,6 +114,7 @@ interface RiddleProofCheckpointSummary {
|
|
|
114
114
|
pending: boolean;
|
|
115
115
|
packet_count: number;
|
|
116
116
|
response_count: number;
|
|
117
|
+
duplicate_response_count?: number;
|
|
117
118
|
latest_checkpoint?: string | null;
|
|
118
119
|
latest_stage?: RiddleProofStage | null;
|
|
119
120
|
latest_kind?: string | null;
|
package/dist/types.d.ts
CHANGED
|
@@ -114,6 +114,7 @@ interface RiddleProofCheckpointSummary {
|
|
|
114
114
|
pending: boolean;
|
|
115
115
|
packet_count: number;
|
|
116
116
|
response_count: number;
|
|
117
|
+
duplicate_response_count?: number;
|
|
117
118
|
latest_checkpoint?: string | null;
|
|
118
119
|
latest_stage?: RiddleProofStage | null;
|
|
119
120
|
latest_kind?: string | null;
|
package/package.json
CHANGED
package/runtime/lib/recon.py
CHANGED
|
@@ -33,6 +33,7 @@ import subprocess as sp
|
|
|
33
33
|
MAX_RECON_ATTEMPTS = 4
|
|
34
34
|
MIN_BODY_TEXT_LENGTH = 50
|
|
35
35
|
MIN_INTERACTIVE_ELEMENTS = 1
|
|
36
|
+
MIN_CANVAS_AREA = 50000
|
|
36
37
|
HYDRATION_WAIT_MS = 1500
|
|
37
38
|
PAGE_STATE_PREFIX = 'RIDDLE_PROOF_STATE:'
|
|
38
39
|
PROOF_EVIDENCE_PREFIX = 'RIDDLE_PROOF_EVIDENCE:'
|
|
@@ -423,6 +424,45 @@ def has_enriched_page_state(page_state):
|
|
|
423
424
|
)
|
|
424
425
|
|
|
425
426
|
|
|
427
|
+
def numeric_value(value):
|
|
428
|
+
if isinstance(value, bool) or value is None:
|
|
429
|
+
return None
|
|
430
|
+
if isinstance(value, (int, float)):
|
|
431
|
+
return float(value)
|
|
432
|
+
if isinstance(value, str):
|
|
433
|
+
text = value.strip().rstrip('%').replace(',', '')
|
|
434
|
+
if not text:
|
|
435
|
+
return None
|
|
436
|
+
try:
|
|
437
|
+
return float(text)
|
|
438
|
+
except Exception:
|
|
439
|
+
return None
|
|
440
|
+
return None
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def canvas_capture_signal(page_state):
|
|
444
|
+
if not isinstance(page_state, dict):
|
|
445
|
+
return {
|
|
446
|
+
'canvas_ready': False,
|
|
447
|
+
'canvas_count': 0,
|
|
448
|
+
'large_canvas_area': 0,
|
|
449
|
+
}
|
|
450
|
+
large_canvas_area = 0
|
|
451
|
+
for item in list_value(page_state.get('largeVisibleElements')):
|
|
452
|
+
if not isinstance(item, dict) or item.get('tag') != 'canvas':
|
|
453
|
+
continue
|
|
454
|
+
area = numeric_value(item.get('area')) or 0
|
|
455
|
+
if area > large_canvas_area:
|
|
456
|
+
large_canvas_area = area
|
|
457
|
+
canvas_count = int(numeric_value(page_state.get('canvasCount')) or 0)
|
|
458
|
+
return {
|
|
459
|
+
'canvas_ready': bool(canvas_count > 0 and large_canvas_area >= MIN_CANVAS_AREA),
|
|
460
|
+
'canvas_count': canvas_count,
|
|
461
|
+
'large_canvas_area': int(large_canvas_area),
|
|
462
|
+
'min_canvas_area': MIN_CANVAS_AREA,
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
|
|
426
466
|
def normalize_observed_path(value):
|
|
427
467
|
raw = str(value or '').strip()
|
|
428
468
|
if not raw:
|
|
@@ -538,6 +578,9 @@ def evaluate_capture_quality(payload, expected_path):
|
|
|
538
578
|
'buttons': [],
|
|
539
579
|
'links': [],
|
|
540
580
|
'canvas_count': 0,
|
|
581
|
+
'canvas_capture_ready': False,
|
|
582
|
+
'large_canvas_area': 0,
|
|
583
|
+
'min_canvas_area': MIN_CANVAS_AREA,
|
|
541
584
|
'large_visible_elements': [],
|
|
542
585
|
'semantic_anchor_count': 0,
|
|
543
586
|
'capture_error_messages': [],
|
|
@@ -572,6 +615,12 @@ def evaluate_capture_quality(payload, expected_path):
|
|
|
572
615
|
'large_visible_elements': list_value(page_state.get('largeVisibleElements'))[:10],
|
|
573
616
|
'semantic_anchor_count': semantic_anchor_count(page_state),
|
|
574
617
|
})
|
|
618
|
+
canvas_signal = canvas_capture_signal(page_state)
|
|
619
|
+
details.update({
|
|
620
|
+
'canvas_capture_ready': canvas_signal['canvas_ready'],
|
|
621
|
+
'large_canvas_area': canvas_signal['large_canvas_area'],
|
|
622
|
+
'min_canvas_area': canvas_signal['min_canvas_area'],
|
|
623
|
+
})
|
|
575
624
|
else:
|
|
576
625
|
details.update({
|
|
577
626
|
'body_text_length': MIN_BODY_TEXT_LENGTH + 100,
|
|
@@ -596,11 +645,20 @@ def evaluate_capture_quality(payload, expected_path):
|
|
|
596
645
|
details['capture_error_messages'].append(str(proof_json.get('script_error'))[:500])
|
|
597
646
|
|
|
598
647
|
reasons = []
|
|
599
|
-
|
|
648
|
+
canvas_ready = bool(details.get('canvas_capture_ready'))
|
|
649
|
+
body_text_ready = details['body_text_length'] >= MIN_BODY_TEXT_LENGTH or canvas_ready
|
|
650
|
+
interactive_ready = details['interactive_elements'] >= MIN_INTERACTIVE_ELEMENTS or canvas_ready
|
|
651
|
+
semantic_ready = (not has_enriched_page_state(page_state)) or details['semantic_anchor_count'] >= 1 or canvas_ready
|
|
652
|
+
details['body_text_ready'] = body_text_ready
|
|
653
|
+
details['interactive_ready'] = interactive_ready
|
|
654
|
+
details['semantic_ready'] = semantic_ready
|
|
655
|
+
details['canvas_capture_override'] = canvas_ready
|
|
656
|
+
|
|
657
|
+
if not body_text_ready:
|
|
600
658
|
reasons.append(f'blank/near-blank page (text length: {details["body_text_length"]})')
|
|
601
|
-
if
|
|
659
|
+
if not interactive_ready:
|
|
602
660
|
reasons.append(f'not interactive enough ({details["interactive_elements"]} interactive elements)')
|
|
603
|
-
if has_enriched_page_state(page_state) and
|
|
661
|
+
if has_enriched_page_state(page_state) and not semantic_ready:
|
|
604
662
|
reasons.append('no visible semantic UI anchors in page capture')
|
|
605
663
|
if details['has_errors']:
|
|
606
664
|
reasons.append('page has console/runtime errors')
|
|
@@ -610,11 +668,10 @@ def evaluate_capture_quality(payload, expected_path):
|
|
|
610
668
|
raw_observed = details.get('observed_path_raw') or details.get('observed_path') or observed_path
|
|
611
669
|
reasons.append(f'wrong route: expected {expected_path}, got {raw_observed}')
|
|
612
670
|
|
|
613
|
-
semantic_ready = (not has_enriched_page_state(page_state)) or details['semantic_anchor_count'] >= 1
|
|
614
671
|
telemetry_ready = (
|
|
615
672
|
details['has_screenshot']
|
|
616
|
-
and
|
|
617
|
-
and
|
|
673
|
+
and body_text_ready
|
|
674
|
+
and interactive_ready
|
|
618
675
|
and semantic_ready
|
|
619
676
|
and not details['has_errors']
|
|
620
677
|
)
|
package/runtime/lib/ship.py
CHANGED
|
@@ -352,7 +352,9 @@ def visual_delta_ship_blocker(state):
|
|
|
352
352
|
return ''
|
|
353
353
|
status = str(visual_delta.get('status') or 'missing')
|
|
354
354
|
if status == 'unmeasured':
|
|
355
|
-
|
|
355
|
+
reason = str(visual_delta.get('reason') or '').strip()
|
|
356
|
+
detail = f': {reason}' if reason else ''
|
|
357
|
+
return 'visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof' + detail
|
|
356
358
|
if status == 'measured' and visual_delta.get('passed') is False:
|
|
357
359
|
return 'visual_delta.status=measured but visual_delta.passed=false blocks ready_to_ship for visual/UI proof'
|
|
358
360
|
reason = str(visual_delta.get('reason') or '').strip()
|
package/runtime/lib/verify.py
CHANGED
|
@@ -33,6 +33,7 @@ import subprocess as sp
|
|
|
33
33
|
|
|
34
34
|
MIN_BODY_TEXT_LENGTH = 50
|
|
35
35
|
MIN_INTERACTIVE_ELEMENTS = 1
|
|
36
|
+
MIN_CANVAS_AREA = 50000
|
|
36
37
|
HYDRATION_WAIT_MS = 1500
|
|
37
38
|
PAGE_STATE_PREFIX = 'RIDDLE_PROOF_STATE:'
|
|
38
39
|
PROOF_EVIDENCE_PREFIX = 'RIDDLE_PROOF_EVIDENCE:'
|
|
@@ -800,6 +801,8 @@ def extract_visual_delta(payload):
|
|
|
800
801
|
result = payload.get('result') if isinstance(payload, dict) else {}
|
|
801
802
|
proof_json = payload.get('_proof_json') if isinstance(payload, dict) else {}
|
|
802
803
|
proof_evidence = extract_proof_evidence(payload)
|
|
804
|
+
screenshot_url = extract_screenshot_url(payload)
|
|
805
|
+
artifact_summary = summarize_capture_artifacts(payload)
|
|
803
806
|
candidates = [
|
|
804
807
|
payload if isinstance(payload, dict) else {},
|
|
805
808
|
result if isinstance(result, dict) else {},
|
|
@@ -837,6 +840,23 @@ def extract_visual_delta(payload):
|
|
|
837
840
|
percent = (changed_pixels / total_pixels) * 100
|
|
838
841
|
|
|
839
842
|
if percent is None and changed_pixels is None:
|
|
843
|
+
has_artifacts = bool(
|
|
844
|
+
screenshot_url
|
|
845
|
+
or artifact_summary.get('outputs')
|
|
846
|
+
or artifact_summary.get('screenshots')
|
|
847
|
+
or artifact_summary.get('artifact_json')
|
|
848
|
+
)
|
|
849
|
+
reason = 'No measured before/after visual delta was found in proof evidence.'
|
|
850
|
+
if screenshot_url:
|
|
851
|
+
reason = (
|
|
852
|
+
'After screenshot artifact is present, but no measured before/after visual delta metrics were emitted; '
|
|
853
|
+
'the comparator did not run or did not publish change_percent/changed_pixels.'
|
|
854
|
+
)
|
|
855
|
+
elif has_artifacts:
|
|
856
|
+
reason = (
|
|
857
|
+
'Capture artifacts are present, but no measured before/after visual delta metrics were emitted; '
|
|
858
|
+
'the comparator did not run or did not publish change_percent/changed_pixels.'
|
|
859
|
+
)
|
|
840
860
|
return {
|
|
841
861
|
'status': 'unmeasured',
|
|
842
862
|
'passed': None,
|
|
@@ -845,7 +865,19 @@ def extract_visual_delta(payload):
|
|
|
845
865
|
'total_pixels': int(total_pixels) if total_pixels else None,
|
|
846
866
|
'min_change_percent': MIN_VISUAL_DELTA_PERCENT,
|
|
847
867
|
'min_changed_pixels': MIN_VISUAL_CHANGED_PIXELS,
|
|
848
|
-
'reason':
|
|
868
|
+
'reason': reason,
|
|
869
|
+
'diagnostic': {
|
|
870
|
+
'after_screenshot_present': bool(screenshot_url),
|
|
871
|
+
'proof_evidence_present': proof_evidence is not None,
|
|
872
|
+
'artifact_output_count': len(artifact_summary.get('outputs') or []),
|
|
873
|
+
'artifact_screenshot_count': len(artifact_summary.get('screenshots') or []),
|
|
874
|
+
'artifact_json': list(artifact_summary.get('artifact_json') or []),
|
|
875
|
+
'expected_metric_keys': {
|
|
876
|
+
'percent': sorted(VISUAL_DELTA_PERCENT_KEYS),
|
|
877
|
+
'changed_pixels': sorted(VISUAL_CHANGED_PIXEL_KEYS),
|
|
878
|
+
'total_pixels': sorted(VISUAL_TOTAL_PIXEL_KEYS),
|
|
879
|
+
},
|
|
880
|
+
},
|
|
849
881
|
}
|
|
850
882
|
|
|
851
883
|
percent_pass = percent is not None and percent >= MIN_VISUAL_DELTA_PERCENT
|
|
@@ -891,7 +923,8 @@ def visual_delta_blocker_for_mode(verification_mode, visual_delta):
|
|
|
891
923
|
status = str(visual_delta.get('status') or 'missing')
|
|
892
924
|
reason = str(visual_delta.get('reason') or '').strip()
|
|
893
925
|
if status == 'unmeasured':
|
|
894
|
-
|
|
926
|
+
detail = f' Reason: {reason}' if reason else ''
|
|
927
|
+
return 'visual_delta.status=unmeasured blocks ready_to_ship for visual/UI proof; capture a measured before/after visual delta or choose needs_richer_proof.' + detail
|
|
895
928
|
if status == 'measured' and isinstance(visual_delta, dict) and visual_delta.get('passed') is False:
|
|
896
929
|
return 'visual_delta.status=measured but visual_delta.passed=false blocks ready_to_ship for visual/UI proof; the measured change did not clear the threshold.'
|
|
897
930
|
if reason:
|
|
@@ -926,6 +959,29 @@ def has_enriched_page_state(page_state):
|
|
|
926
959
|
)
|
|
927
960
|
|
|
928
961
|
|
|
962
|
+
def canvas_capture_signal(page_state):
|
|
963
|
+
if not isinstance(page_state, dict):
|
|
964
|
+
return {
|
|
965
|
+
'canvas_ready': False,
|
|
966
|
+
'canvas_count': 0,
|
|
967
|
+
'large_canvas_area': 0,
|
|
968
|
+
}
|
|
969
|
+
large_canvas_area = 0
|
|
970
|
+
for item in list_value(page_state.get('largeVisibleElements')):
|
|
971
|
+
if not isinstance(item, dict) or item.get('tag') != 'canvas':
|
|
972
|
+
continue
|
|
973
|
+
area = metric_number(item.get('area')) or 0
|
|
974
|
+
if area > large_canvas_area:
|
|
975
|
+
large_canvas_area = area
|
|
976
|
+
canvas_count = int(metric_number(page_state.get('canvasCount')) or 0)
|
|
977
|
+
return {
|
|
978
|
+
'canvas_ready': bool(canvas_count > 0 and large_canvas_area >= MIN_CANVAS_AREA),
|
|
979
|
+
'canvas_count': canvas_count,
|
|
980
|
+
'large_canvas_area': int(large_canvas_area),
|
|
981
|
+
'min_canvas_area': MIN_CANVAS_AREA,
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
|
|
929
985
|
def normalize_observed_path(value):
|
|
930
986
|
raw = str(value or '').strip()
|
|
931
987
|
if not raw:
|
|
@@ -1135,6 +1191,7 @@ def evaluate_capture_quality(payload, expected_path, verification_mode='proof'):
|
|
|
1135
1191
|
mode = normalized_verification_mode(verification_mode)
|
|
1136
1192
|
supporting = collect_supporting_artifacts(payload)
|
|
1137
1193
|
structured_ready = bool(supporting.get('has_structured_payload'))
|
|
1194
|
+
playability_ready = mode in PLAYABILITY_MODES and bool(supporting.get('playability_ready'))
|
|
1138
1195
|
screenshot_required = screenshot_required_for_mode(mode)
|
|
1139
1196
|
details = {
|
|
1140
1197
|
'verification_mode': mode,
|
|
@@ -1143,6 +1200,10 @@ def evaluate_capture_quality(payload, expected_path, verification_mode='proof'):
|
|
|
1143
1200
|
'screenshot_required': screenshot_required,
|
|
1144
1201
|
'structured_evidence_present': structured_ready,
|
|
1145
1202
|
'proof_evidence_present': bool(supporting.get('proof_evidence_present')),
|
|
1203
|
+
'playability_ready': playability_ready,
|
|
1204
|
+
'canvas_capture_ready': False,
|
|
1205
|
+
'large_canvas_area': 0,
|
|
1206
|
+
'min_canvas_area': MIN_CANVAS_AREA,
|
|
1146
1207
|
'proof_evidence_sample': supporting.get('proof_evidence_sample', ''),
|
|
1147
1208
|
'body_text_length': 0,
|
|
1148
1209
|
'interactive_elements': 0,
|
|
@@ -1183,6 +1244,12 @@ def evaluate_capture_quality(payload, expected_path, verification_mode='proof'):
|
|
|
1183
1244
|
'large_visible_elements': list_value(page_state.get('largeVisibleElements'))[:10],
|
|
1184
1245
|
'semantic_anchor_count': semantic_anchor_count(page_state),
|
|
1185
1246
|
})
|
|
1247
|
+
canvas_signal = canvas_capture_signal(page_state)
|
|
1248
|
+
details.update({
|
|
1249
|
+
'canvas_capture_ready': canvas_signal['canvas_ready'],
|
|
1250
|
+
'large_canvas_area': canvas_signal['large_canvas_area'],
|
|
1251
|
+
'min_canvas_area': canvas_signal['min_canvas_area'],
|
|
1252
|
+
})
|
|
1186
1253
|
elif screenshot_url:
|
|
1187
1254
|
details.update({
|
|
1188
1255
|
'body_text_length': MIN_BODY_TEXT_LENGTH + 100,
|
|
@@ -1220,11 +1287,20 @@ def evaluate_capture_quality(payload, expected_path, verification_mode='proof'):
|
|
|
1220
1287
|
reasons.append('no screenshot or structured proof evidence in capture')
|
|
1221
1288
|
|
|
1222
1289
|
should_enforce_visual_readiness = screenshot_required or (details['has_screenshot'] and not structured_ready)
|
|
1223
|
-
|
|
1290
|
+
canvas_ready = bool(details.get('canvas_capture_ready'))
|
|
1291
|
+
body_text_ready = details['body_text_length'] >= MIN_BODY_TEXT_LENGTH or canvas_ready or playability_ready
|
|
1292
|
+
interactive_ready = details['interactive_elements'] >= MIN_INTERACTIVE_ELEMENTS or canvas_ready or playability_ready
|
|
1293
|
+
semantic_ready = (not has_enriched_page_state(page_state)) or details['semantic_anchor_count'] >= 1 or canvas_ready or playability_ready
|
|
1294
|
+
details['body_text_ready'] = body_text_ready
|
|
1295
|
+
details['interactive_ready'] = interactive_ready
|
|
1296
|
+
details['semantic_ready'] = semantic_ready
|
|
1297
|
+
details['canvas_or_playability_override'] = bool(should_enforce_visual_readiness and (canvas_ready or playability_ready))
|
|
1298
|
+
|
|
1299
|
+
if should_enforce_visual_readiness and not body_text_ready:
|
|
1224
1300
|
reasons.append(f'blank/near-blank page (text length: {details["body_text_length"]})')
|
|
1225
|
-
if should_enforce_visual_readiness and
|
|
1301
|
+
if should_enforce_visual_readiness and not interactive_ready:
|
|
1226
1302
|
reasons.append(f'not interactive enough ({details["interactive_elements"]} interactive elements)')
|
|
1227
|
-
if should_enforce_visual_readiness and has_enriched_page_state(page_state) and
|
|
1303
|
+
if should_enforce_visual_readiness and has_enriched_page_state(page_state) and not semantic_ready:
|
|
1228
1304
|
reasons.append('no visible semantic UI anchors in page capture')
|
|
1229
1305
|
if details['has_errors']:
|
|
1230
1306
|
reasons.append('page has console/runtime errors')
|
|
@@ -1234,15 +1310,14 @@ def evaluate_capture_quality(payload, expected_path, verification_mode='proof'):
|
|
|
1234
1310
|
raw_observed = details.get('observed_path_raw') or details.get('observed_path') or observed_path
|
|
1235
1311
|
reasons.append(f'wrong route: expected {expected_path}, got {raw_observed}')
|
|
1236
1312
|
|
|
1237
|
-
semantic_ready = (not has_enriched_page_state(page_state)) or details['semantic_anchor_count'] >= 1
|
|
1238
1313
|
visual_ready = (
|
|
1239
1314
|
details['has_screenshot']
|
|
1240
|
-
and
|
|
1241
|
-
and
|
|
1315
|
+
and body_text_ready
|
|
1316
|
+
and interactive_ready
|
|
1242
1317
|
and semantic_ready
|
|
1243
1318
|
and not details['has_errors']
|
|
1244
1319
|
)
|
|
1245
|
-
telemetry_ready = (visual_ready or structured_ready) and not details['has_errors']
|
|
1320
|
+
telemetry_ready = (visual_ready or structured_ready or playability_ready) and not details['has_errors']
|
|
1246
1321
|
|
|
1247
1322
|
return {
|
|
1248
1323
|
'valid': len(reasons) == 0 and telemetry_ready,
|
|
@@ -566,8 +566,106 @@ def run_verify_quality_ignores_proof_telemetry_console_text():
|
|
|
566
566
|
})
|
|
567
567
|
assert unmeasured_delta['status'] == 'unmeasured'
|
|
568
568
|
assert unmeasured_delta['passed'] is None
|
|
569
|
+
assert unmeasured_delta['diagnostic']['after_screenshot_present'] is True
|
|
570
|
+
assert 'After screenshot artifact is present' in unmeasured_delta['reason']
|
|
571
|
+
|
|
572
|
+
canvas_payload = {
|
|
573
|
+
'bodyTextLength': 7,
|
|
574
|
+
'visibleTextSample': 'Luge',
|
|
575
|
+
'interactiveElements': 1,
|
|
576
|
+
'visibleInteractiveElements': 1,
|
|
577
|
+
'pathname': '/games/luge-run',
|
|
578
|
+
'title': 'Luge Run',
|
|
579
|
+
'headings': [],
|
|
580
|
+
'buttons': [],
|
|
581
|
+
'links': [],
|
|
582
|
+
'canvasCount': 1,
|
|
583
|
+
'largeVisibleElements': [{'tag': 'canvas', 'text': '', 'area': 420000}],
|
|
584
|
+
}
|
|
585
|
+
canvas_quality = namespace['evaluate_capture_quality']({
|
|
586
|
+
'ok': True,
|
|
587
|
+
'screenshots': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after-proof.png'}],
|
|
588
|
+
'outputs': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after-proof.png'}],
|
|
589
|
+
'console': ['RIDDLE_PROOF_STATE:' + json.dumps(canvas_payload)],
|
|
590
|
+
}, '/games/luge-run', 'visual')
|
|
591
|
+
assert canvas_quality['valid'] is True, canvas_quality
|
|
592
|
+
assert canvas_quality['details']['canvas_capture_ready'] is True
|
|
593
|
+
assert canvas_quality['details']['body_text_ready'] is True
|
|
594
|
+
assert 'blank/near-blank' not in canvas_quality['reason']
|
|
595
|
+
|
|
596
|
+
playability_payload = {
|
|
597
|
+
**canvas_payload,
|
|
598
|
+
'interactiveElements': 0,
|
|
599
|
+
'visibleInteractiveElements': 0,
|
|
600
|
+
'largeVisibleElements': [],
|
|
601
|
+
}
|
|
602
|
+
playability_evidence = {
|
|
603
|
+
'input_events': [{'type': 'pointerdown'}],
|
|
604
|
+
'state_delta': {'changed': True, 'changed_keys': ['distance']},
|
|
605
|
+
'canvas_delta': {'changed_pixels': 18000},
|
|
606
|
+
'time_delta_ms': 1300,
|
|
607
|
+
}
|
|
608
|
+
playable_quality = namespace['evaluate_capture_quality']({
|
|
609
|
+
'ok': True,
|
|
610
|
+
'screenshots': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after-proof.png'}],
|
|
611
|
+
'outputs': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after-proof.png'}],
|
|
612
|
+
'console': [
|
|
613
|
+
'RIDDLE_PROOF_STATE:' + json.dumps(playability_payload),
|
|
614
|
+
'RIDDLE_PROOF_EVIDENCE:' + json.dumps({'playability': playability_evidence}),
|
|
615
|
+
],
|
|
616
|
+
}, '/games/luge-run', 'playable')
|
|
617
|
+
assert playable_quality['valid'] is True, playable_quality
|
|
618
|
+
assert playable_quality['details']['playability_ready'] is True
|
|
619
|
+
assert playable_quality['details']['interactive_ready'] is True
|
|
569
620
|
|
|
570
|
-
return {
|
|
621
|
+
return {
|
|
622
|
+
'ok': True,
|
|
623
|
+
'telemetry_valid': quality['valid'],
|
|
624
|
+
'weak_delta_passed': weak_delta['passed'],
|
|
625
|
+
'canvas_valid': canvas_quality['valid'],
|
|
626
|
+
'playable_valid': playable_quality['valid'],
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def run_recon_quality_accepts_canvas_first_routes():
|
|
631
|
+
tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-recon-canvas-quality-'))
|
|
632
|
+
state_path = tempdir / 'state.json'
|
|
633
|
+
try:
|
|
634
|
+
write_state(state_path, {
|
|
635
|
+
'after_worktree': str(tempdir),
|
|
636
|
+
'before_worktree': str(tempdir),
|
|
637
|
+
})
|
|
638
|
+
with temporary_env(RIDDLE_PROOF_STATE_FILE=str(state_path)):
|
|
639
|
+
sys.modules.pop('util', None)
|
|
640
|
+
source = RECON_PATH.read_text()
|
|
641
|
+
helpers_source = source.split('\ndef clean_next_cache', 1)[0]
|
|
642
|
+
namespace = {'__file__': str(RECON_PATH)}
|
|
643
|
+
exec(compile(helpers_source, str(RECON_PATH), 'exec'), namespace)
|
|
644
|
+
canvas_payload = {
|
|
645
|
+
'bodyTextLength': 4,
|
|
646
|
+
'visibleTextSample': 'Game',
|
|
647
|
+
'interactiveElements': 1,
|
|
648
|
+
'visibleInteractiveElements': 1,
|
|
649
|
+
'pathname': '/games/luge-run',
|
|
650
|
+
'title': 'Luge Run',
|
|
651
|
+
'headings': [],
|
|
652
|
+
'buttons': [],
|
|
653
|
+
'links': [],
|
|
654
|
+
'canvasCount': 1,
|
|
655
|
+
'largeVisibleElements': [{'tag': 'canvas', 'text': '', 'area': 420000}],
|
|
656
|
+
}
|
|
657
|
+
quality = namespace['evaluate_capture_quality']({
|
|
658
|
+
'ok': True,
|
|
659
|
+
'screenshots': [{'name': 'before.png', 'url': 'https://cdn.example.com/before.png'}],
|
|
660
|
+
'outputs': [{'name': 'before.png', 'url': 'https://cdn.example.com/before.png'}],
|
|
661
|
+
'console': ['RIDDLE_PROOF_STATE:' + json.dumps(canvas_payload)],
|
|
662
|
+
}, '/games/luge-run')
|
|
663
|
+
assert quality['valid'] is True, quality
|
|
664
|
+
assert quality['details']['canvas_capture_ready'] is True
|
|
665
|
+
assert 'blank/near-blank' not in quality['reason']
|
|
666
|
+
return {'ok': True, 'valid': quality['valid']}
|
|
667
|
+
finally:
|
|
668
|
+
shutil.rmtree(tempdir, ignore_errors=True)
|
|
571
669
|
|
|
572
670
|
|
|
573
671
|
def load_util_with_fake(fake: FakeRiddle):
|
|
@@ -1992,6 +2090,7 @@ if __name__ == '__main__':
|
|
|
1992
2090
|
'implement_records_detection_when_changes_missing': run_implement_records_detection_when_changes_missing(),
|
|
1993
2091
|
'implement_ignores_tool_noise_when_detecting_changes': run_implement_ignores_tool_noise_when_detecting_changes(),
|
|
1994
2092
|
'verify_quality_ignores_proof_telemetry_console_text': run_verify_quality_ignores_proof_telemetry_console_text(),
|
|
2093
|
+
'recon_quality_accepts_canvas_first_routes': run_recon_quality_accepts_canvas_first_routes(),
|
|
1995
2094
|
'recon_then_author_request': run_recon_then_author_request(),
|
|
1996
2095
|
'recon_preserves_query_route': run_recon_preserves_query_route(),
|
|
1997
2096
|
'recon_route_literal_preference': run_recon_prefers_route_literals_over_import_paths(),
|