@colophon-claims/verify 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/admission/application.d.ts +18 -0
- package/dist/admission/application.js +53 -0
- package/dist/admission/contracts.d.ts +506 -0
- package/dist/admission/contracts.js +459 -0
- package/dist/admission/index.d.ts +5 -0
- package/dist/admission/index.js +5 -0
- package/dist/admission/intake.d.ts +75 -0
- package/dist/admission/intake.js +70 -0
- package/dist/admission/result-evaluation.d.ts +18 -0
- package/dist/admission/result-evaluation.js +71 -0
- package/dist/admission/screening-sample.d.ts +56 -0
- package/dist/admission/screening-sample.js +126 -0
- package/dist/admission/verification.d.ts +102 -0
- package/dist/admission/verification.js +844 -0
- package/dist/anchor/check.d.ts +95 -0
- package/dist/anchor/check.js +146 -0
- package/dist/anchor/ports.d.ts +6 -0
- package/dist/anchor/ports.js +354 -0
- package/dist/assets.d.ts +33 -0
- package/dist/assets.js +885 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +20 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +205 -0
- package/dist/comparison.d.ts +64 -0
- package/dist/comparison.js +235 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/manifest.d.ts +54 -0
- package/dist/manifest.js +240 -0
- package/dist/materialize.d.ts +4 -0
- package/dist/materialize.js +13 -0
- package/dist/profile/admission-receipts.d.ts +11 -0
- package/dist/profile/admission-receipts.js +24 -0
- package/dist/profile/anchor-claims.d.ts +157 -0
- package/dist/profile/anchor-claims.js +330 -0
- package/dist/profile/artifacts.d.ts +261 -0
- package/dist/profile/artifacts.js +139 -0
- package/dist/profile/assembly-ports.d.ts +19 -0
- package/dist/profile/assembly-ports.js +42 -0
- package/dist/profile/binary-judge-manifest.d.ts +121 -0
- package/dist/profile/binary-judge-manifest.js +106 -0
- package/dist/profile/binary-qualification.d.ts +9 -0
- package/dist/profile/binary-qualification.js +58 -0
- package/dist/profile/branding-assets.d.ts +5 -0
- package/dist/profile/branding-assets.js +5 -0
- package/dist/profile/branding.d.ts +24 -0
- package/dist/profile/branding.js +14 -0
- package/dist/profile/claim-consistency.d.ts +26 -0
- package/dist/profile/claim-consistency.js +91 -0
- package/dist/profile/claim.d.ts +327 -0
- package/dist/profile/claim.js +742 -0
- package/dist/profile/errors.d.ts +49 -0
- package/dist/profile/errors.js +78 -0
- package/dist/profile/inspect-assurance.d.ts +16 -0
- package/dist/profile/inspect-assurance.js +30 -0
- package/dist/profile/inspect-disclosure.d.ts +28 -0
- package/dist/profile/inspect-disclosure.js +54 -0
- package/dist/profile/inspect-manifest.d.ts +706 -0
- package/dist/profile/inspect-manifest.js +397 -0
- package/dist/profile/isolation.d.ts +21 -0
- package/dist/profile/isolation.js +31 -0
- package/dist/profile/pinning-evidence.d.ts +41 -0
- package/dist/profile/pinning-evidence.js +43 -0
- package/dist/profile/ports.d.ts +3 -0
- package/dist/profile/ports.js +11 -0
- package/dist/profile/preview-log.d.ts +4 -0
- package/dist/profile/preview-log.js +3 -0
- package/dist/profile/run-results.d.ts +17 -0
- package/dist/profile/run-results.js +23 -0
- package/dist/profile/signing.d.ts +10 -0
- package/dist/profile/signing.js +46 -0
- package/dist/profile/trust.d.ts +13 -0
- package/dist/profile/trust.js +35 -0
- package/dist/profile/venue.d.ts +2 -0
- package/dist/profile/venue.js +2 -0
- package/dist/profile/verdict.d.ts +28 -0
- package/dist/profile/verdict.js +51 -0
- package/dist/reader-instructions.d.ts +41 -0
- package/dist/reader-instructions.js +52 -0
- package/dist/schema.d.ts +380 -0
- package/dist/schema.js +498 -0
- package/dist/verify.d.ts +72 -0
- package/dist/verify.js +1573 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +63 -3
- package/schemas/assembly-row.schema.json +194 -0
- package/schemas/bundle-manifest.schema.json +23 -0
- package/schemas/claim-package.schema.json +279 -0
- package/schemas/dsse-envelope.schema.json +25 -0
- package/schemas/evidence-catalog.schema.json +43 -0
- package/schemas/public-trust.schema.json +44 -0
- package/schemas/verdict-catalog.schema.json +27 -0
- package/scripts/external-verify.py +238 -0
package/dist/assets.js
ADDED
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
import { BENCHMARKING_METHOD_IDS } from "@jinn-network/benchmarking-records";
|
|
2
|
+
import { validateBinaryInstrumentQualificationProjection } from "@jinn-network/benchmarking-aggregate";
|
|
3
|
+
import { canonicalJsonBytes } from "@jinn-network/trust-core";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { Buffer } from "node:buffer";
|
|
7
|
+
import { PRODUCT_BRANDING } from "./profile/branding.js";
|
|
8
|
+
import { COLOPHON_MARK_SVG } from "./profile/branding-assets.js";
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
const decoder = new TextDecoder();
|
|
11
|
+
const SHA256_HEX = /^[a-f0-9]{64}$/u;
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
function embeddedFont(packagePath) {
|
|
14
|
+
return readFileSync(require.resolve(packagePath)).toString("base64");
|
|
15
|
+
}
|
|
16
|
+
const NEWSREADER_FONT = embeddedFont("@fontsource-variable/newsreader/files/newsreader-latin-wght-normal.woff2");
|
|
17
|
+
const PUBLIC_SANS_FONT = embeddedFont("@fontsource-variable/public-sans/files/public-sans-latin-wght-normal.woff2");
|
|
18
|
+
const IBM_PLEX_MONO_FONT = embeddedFont("@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-400-normal.woff2");
|
|
19
|
+
const COLOPHON_MARK_DATA_URI = `data:image/svg+xml;base64,${Buffer.from(COLOPHON_MARK_SVG).toString("base64")}`;
|
|
20
|
+
const FONT_LICENSES = [
|
|
21
|
+
["Newsreader", "@fontsource-variable/newsreader/LICENSE"],
|
|
22
|
+
["Public Sans", "@fontsource-variable/public-sans/LICENSE"],
|
|
23
|
+
["IBM Plex Mono", "@fontsource/ibm-plex-mono/LICENSE"],
|
|
24
|
+
];
|
|
25
|
+
function embeddedFontCss() {
|
|
26
|
+
return `@font-face{font-family:"Newsreader";font-style:normal;font-weight:200 800;font-display:swap;src:url(data:font/woff2;base64,${NEWSREADER_FONT}) format("woff2")}@font-face{font-family:"Public Sans";font-style:normal;font-weight:100 900;font-display:swap;src:url(data:font/woff2;base64,${PUBLIC_SANS_FONT}) format("woff2")}@font-face{font-family:"IBM Plex Mono";font-style:normal;font-weight:400;font-display:swap;src:url(data:font/woff2;base64,${IBM_PLEX_MONO_FONT}) format("woff2")}`;
|
|
27
|
+
}
|
|
28
|
+
function visibleControls(value) {
|
|
29
|
+
return value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, (character) => `\\u${character.codePointAt(0).toString(16).padStart(4, "0")}`);
|
|
30
|
+
}
|
|
31
|
+
function escapeMarkup(value) {
|
|
32
|
+
return visibleControls(value)
|
|
33
|
+
.replaceAll("&", "&")
|
|
34
|
+
.replaceAll("<", "<")
|
|
35
|
+
.replaceAll(">", ">")
|
|
36
|
+
.replaceAll('"', """)
|
|
37
|
+
.replaceAll("'", "'");
|
|
38
|
+
}
|
|
39
|
+
function oneLine(value) {
|
|
40
|
+
return visibleControls(value).replace(/\s+/gu, " ").trim();
|
|
41
|
+
}
|
|
42
|
+
function escapeMarkdown(value) {
|
|
43
|
+
return oneLine(value)
|
|
44
|
+
.replaceAll("\\", "\\\\")
|
|
45
|
+
.replaceAll("<", "<")
|
|
46
|
+
.replaceAll(">", ">")
|
|
47
|
+
.replace(/([\[\]()*_`|#!])/gu, "\\$1")
|
|
48
|
+
.replace(/(https?):\/\//giu, "$1\\://");
|
|
49
|
+
}
|
|
50
|
+
function escapeMarkdownCode(value) {
|
|
51
|
+
return visibleControls(value)
|
|
52
|
+
.replaceAll("&", "&")
|
|
53
|
+
.replaceAll("<", "<")
|
|
54
|
+
.replaceAll(">", ">")
|
|
55
|
+
.replace(/(https?):\/\//giu, "$1\\://");
|
|
56
|
+
}
|
|
57
|
+
function plainText(value) {
|
|
58
|
+
return oneLine(value).replace(/(https?):\/\//giu, "$1:\\/\\/");
|
|
59
|
+
}
|
|
60
|
+
function canonicalText(value) {
|
|
61
|
+
return decoder.decode(canonicalJsonBytes(value));
|
|
62
|
+
}
|
|
63
|
+
function boundedVisual(value, maximumCodePoints) {
|
|
64
|
+
const points = Array.from(oneLine(value));
|
|
65
|
+
return points.length <= maximumCodePoints ? points.join("") : `${points.slice(0, maximumCodePoints - 1).join("")}…`;
|
|
66
|
+
}
|
|
67
|
+
function recordPaths(input) {
|
|
68
|
+
const expected = [...input.recordSha256s].sort();
|
|
69
|
+
if (input.recordSha256s.length !== new Set(input.recordSha256s).size
|
|
70
|
+
|| input.recordSha256s.some((sha256) => !SHA256_HEX.test(sha256))
|
|
71
|
+
|| input.recordSha256s.some((sha256, index) => sha256 !== expected[index])) {
|
|
72
|
+
throw new Error("public assets require unique, canonically sorted record identities");
|
|
73
|
+
}
|
|
74
|
+
return input.recordSha256s.map((sha256) => `records/${sha256}.bin`);
|
|
75
|
+
}
|
|
76
|
+
/** Every method this product wires publishes its single Matrix subject's results at this fixed
|
|
77
|
+
* path -- mirrors `report/claim.ts`'s `singleSubjectResults`; the wrapper itself is
|
|
78
|
+
* method-agnostic, only what's inside `results` differs by method. */
|
|
79
|
+
function singleSubjectResults(value, label) {
|
|
80
|
+
const results = value;
|
|
81
|
+
if (results?.perSubject?.length !== 1) {
|
|
82
|
+
throw new Error(`${label}: expected exactly one subject (this product's single-Matrix Report shape)`);
|
|
83
|
+
}
|
|
84
|
+
return results.perSubject[0]?.results;
|
|
85
|
+
}
|
|
86
|
+
/** wilson@1's per-arm facts projection. A narrow, local shape check -- deliberately not a general
|
|
87
|
+
* Method-results parser; a mismatch here means the sealed Report was not produced by wilson@1. */
|
|
88
|
+
function wilsonProjection(subjectResults, label) {
|
|
89
|
+
const shape = subjectResults;
|
|
90
|
+
if (shape?.arms === undefined || shape.conflicted === undefined) {
|
|
91
|
+
throw new Error(`${label}: expected wilson@1's arms/conflicted shape`);
|
|
92
|
+
}
|
|
93
|
+
const arms = Object.entries(shape.arms).map(([armId, arm]) => {
|
|
94
|
+
if (typeof arm.n !== "number"
|
|
95
|
+
|| typeof arm.passRate !== "string"
|
|
96
|
+
|| typeof arm.wilsonInterval?.low !== "string"
|
|
97
|
+
|| typeof arm.wilsonInterval.high !== "string") {
|
|
98
|
+
throw new Error(`${label}: arm ${armId} lacks exact wilson@1 facts`);
|
|
99
|
+
}
|
|
100
|
+
return { armId, n: arm.n, passRate: arm.passRate, low: arm.wilsonInterval.low, high: arm.wilsonInterval.high };
|
|
101
|
+
});
|
|
102
|
+
if (typeof shape.conflicted.count !== "number" || !Array.isArray(shape.conflicted.cellKeys)) {
|
|
103
|
+
throw new Error(`${label}: conflicted facts are invalid`);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
kind: "wilson",
|
|
107
|
+
arms,
|
|
108
|
+
conflicted: {
|
|
109
|
+
count: shape.conflicted.count,
|
|
110
|
+
cellKeys: shape.conflicted.cellKeys.map((value) => String(value)),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** paired-delta@1's comparison facts projection (P4b Task 6), sibling to `wilsonProjection`. Same
|
|
115
|
+
* narrow, local-shape-check posture: a mismatch here means the sealed Report was not produced by
|
|
116
|
+
* paired-delta@1. Mirrors `report/claim.ts`'s `comparisonProjection`, narrowed to only the fields
|
|
117
|
+
* a public bundle asset presents. */
|
|
118
|
+
function comparisonProjection(subjectResults, parameters, label) {
|
|
119
|
+
const shape = subjectResults;
|
|
120
|
+
const parameterShape = parameters;
|
|
121
|
+
if (typeof shape?.pairs !== "number"
|
|
122
|
+
|| !(typeof shape.delta === "string" || shape.delta === null)
|
|
123
|
+
|| !Array.isArray(shape.reasons)
|
|
124
|
+
|| typeof shape.conflicted?.count !== "number"
|
|
125
|
+
|| !Array.isArray(shape.conflicted.cellKeys)
|
|
126
|
+
|| !(shape.interval === null
|
|
127
|
+
|| (typeof shape.interval?.alpha === "string"
|
|
128
|
+
&& typeof shape.interval.low === "string"
|
|
129
|
+
&& typeof shape.interval.high === "string"))
|
|
130
|
+
|| typeof parameterShape?.baseline !== "string"
|
|
131
|
+
|| typeof parameterShape.candidate !== "string"
|
|
132
|
+
|| typeof parameterShape.alpha !== "string"
|
|
133
|
+
|| !["0.10", "0.05", "0.01"].includes(parameterShape.alpha)) {
|
|
134
|
+
throw new Error(`${label}: expected paired-delta@1's pairs/delta/interval/reasons/conflicted shape`);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
kind: "comparison",
|
|
138
|
+
baseline: parameterShape.baseline,
|
|
139
|
+
candidate: parameterShape.candidate,
|
|
140
|
+
alpha: Number(parameterShape.alpha).toFixed(4),
|
|
141
|
+
pairs: shape.pairs,
|
|
142
|
+
delta: shape.delta,
|
|
143
|
+
interval: shape.interval === null ? null : { alpha: shape.interval.alpha, low: shape.interval.low, high: shape.interval.high },
|
|
144
|
+
reasons: shape.reasons.map((value) => String(value)),
|
|
145
|
+
conflicted: {
|
|
146
|
+
count: shape.conflicted.count,
|
|
147
|
+
cellKeys: shape.conflicted.cellKeys.map((value) => String(value)),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function judgeInterval(value, label) {
|
|
152
|
+
if (value === null || value === undefined)
|
|
153
|
+
return null;
|
|
154
|
+
const shape = value;
|
|
155
|
+
if (typeof shape.lower !== "string" || typeof shape.upper !== "string" || typeof shape.alpha !== "string") {
|
|
156
|
+
throw new Error(`${label}: expected a {lower, upper, alpha} interval`);
|
|
157
|
+
}
|
|
158
|
+
return { lower: shape.lower, upper: shape.upper, alpha: shape.alpha };
|
|
159
|
+
}
|
|
160
|
+
/** `pairwise-disagreement@1`'s asset projection (packet #2837), sibling to `comparisonProjection`.
|
|
161
|
+
* Narrow local shape check, same posture as its siblings: a mismatch means the sealed Report was
|
|
162
|
+
* not produced by this method. */
|
|
163
|
+
function pairwiseDisagreementAssetProjection(subjectResults, label) {
|
|
164
|
+
const shape = subjectResults;
|
|
165
|
+
if (!Array.isArray(shape?.pairs)
|
|
166
|
+
|| typeof shape.conflicted?.count !== "number"
|
|
167
|
+
|| !Array.isArray(shape.conflicted.cellKeys)) {
|
|
168
|
+
throw new Error(`${label}: expected pairwise-disagreement@1's pairs/conflicted shape`);
|
|
169
|
+
}
|
|
170
|
+
const pairs = shape.pairs.map((entry) => {
|
|
171
|
+
const pair = entry;
|
|
172
|
+
if (typeof pair.armA !== "string" || typeof pair.armB !== "string"
|
|
173
|
+
|| typeof pair.n !== "number" || typeof pair.disagreements !== "number"
|
|
174
|
+
|| !(typeof pair.rate === "string" || pair.rate === null)) {
|
|
175
|
+
throw new Error(`${label}: expected pairwise-disagreement@1's per-pair armA/armB/n/disagreements/rate shape`);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
armA: pair.armA, armB: pair.armB, n: pair.n, disagreements: pair.disagreements,
|
|
179
|
+
rate: pair.rate, interval: judgeInterval(pair.interval, label),
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
kind: "pairwise-disagreement",
|
|
184
|
+
pairs,
|
|
185
|
+
conflicted: {
|
|
186
|
+
count: shape.conflicted.count,
|
|
187
|
+
cellKeys: shape.conflicted.cellKeys.map((value) => String(value)),
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** `paired-majority-delta@1`'s asset projection (packet #2837), sibling to `comparisonProjection`.
|
|
192
|
+
* `baseline`/`candidate` come from the method's own output here (unlike `paired-delta@1`, which
|
|
193
|
+
* reads them from the sealed parameters) because this method DERIVES its pair rather than
|
|
194
|
+
* accepting one. */
|
|
195
|
+
function pairedMajorityDeltaAssetProjection(subjectResults, label) {
|
|
196
|
+
const shape = subjectResults;
|
|
197
|
+
if (typeof shape?.baseline !== "string" || typeof shape.candidate !== "string"
|
|
198
|
+
|| typeof shape.n !== "number"
|
|
199
|
+
|| !(typeof shape.delta === "string" || shape.delta === null)
|
|
200
|
+
|| !Array.isArray(shape.reasons)
|
|
201
|
+
|| typeof shape.conflicted?.count !== "number"
|
|
202
|
+
|| !Array.isArray(shape.conflicted.cellKeys)) {
|
|
203
|
+
throw new Error(`${label}: expected paired-majority-delta@1's baseline/candidate/n/delta/reasons/conflicted shape`);
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
kind: "paired-majority-delta",
|
|
207
|
+
baseline: shape.baseline,
|
|
208
|
+
candidate: shape.candidate,
|
|
209
|
+
n: shape.n,
|
|
210
|
+
delta: shape.delta,
|
|
211
|
+
interval: judgeInterval(shape.interval, label),
|
|
212
|
+
reasons: shape.reasons.map((value) => String(value)),
|
|
213
|
+
conflicted: {
|
|
214
|
+
count: shape.conflicted.count,
|
|
215
|
+
cellKeys: shape.conflicted.cellKeys.map((value) => String(value)),
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/** Dispatches on the produced method (P4b Task 6) -- mirrors `report/claim.ts`'s
|
|
220
|
+
* `methodProjection`, so the two dispatches read as one pattern. Any method this product has not
|
|
221
|
+
* wired an asset projection for throws rather than silently rendering incomplete facts. */
|
|
222
|
+
function methodProjection(value, method, label) {
|
|
223
|
+
const subjectResults = singleSubjectResults(value, label);
|
|
224
|
+
switch (method.id) {
|
|
225
|
+
case BENCHMARKING_METHOD_IDS.wilson:
|
|
226
|
+
return wilsonProjection(subjectResults, label);
|
|
227
|
+
case BENCHMARKING_METHOD_IDS.pairedDelta:
|
|
228
|
+
return comparisonProjection(subjectResults, method.parameters, label);
|
|
229
|
+
case BENCHMARKING_METHOD_IDS.binaryInstrument: {
|
|
230
|
+
const validation = validateBinaryInstrumentQualificationProjection(subjectResults);
|
|
231
|
+
if (!validation.ok)
|
|
232
|
+
throw new Error(`${label}: invalid binary qualification: ${validation.issues.join("; ")}`);
|
|
233
|
+
const qualification = subjectResults;
|
|
234
|
+
return { kind: "binary", qualification, conflicted: qualification.conflicted };
|
|
235
|
+
}
|
|
236
|
+
case BENCHMARKING_METHOD_IDS.pairwiseDisagreement:
|
|
237
|
+
return pairwiseDisagreementAssetProjection(subjectResults, label);
|
|
238
|
+
case BENCHMARKING_METHOD_IDS.pairedMajorityDelta:
|
|
239
|
+
return pairedMajorityDeltaAssetProjection(subjectResults, label);
|
|
240
|
+
default:
|
|
241
|
+
throw new Error(`${label}: method "${method.id}" has no bundle-asset projection`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function outcomeLabel(outcome) {
|
|
245
|
+
if (outcome === "complete")
|
|
246
|
+
return "Complete comparison";
|
|
247
|
+
if (outcome === "partial")
|
|
248
|
+
return "Partial comparison";
|
|
249
|
+
return "Cancelled comparison";
|
|
250
|
+
}
|
|
251
|
+
function qualificationOutcomeLabel(outcome) {
|
|
252
|
+
if (outcome === "complete")
|
|
253
|
+
return "Complete qualification";
|
|
254
|
+
if (outcome === "partial")
|
|
255
|
+
return "Partial qualification";
|
|
256
|
+
return "Cancelled qualification";
|
|
257
|
+
}
|
|
258
|
+
function scopeLine(input) {
|
|
259
|
+
return `${input.claim.scope.taskCount} tasks · ${input.claim.scope.arms.length} arms · ${input.claim.scope.replicates} replicates · ${input.claim.scope.venue}`;
|
|
260
|
+
}
|
|
261
|
+
function adverseFacts(input, reportFacts) {
|
|
262
|
+
const facts = [];
|
|
263
|
+
if (input.matrix.completeness.runOutcome !== "complete")
|
|
264
|
+
facts.push(`Matrix: ${reportFacts.kind === "binary" ? qualificationOutcomeLabel(input.matrix.completeness.runOutcome) : outcomeLabel(input.matrix.completeness.runOutcome)}; incomplete cells remain accounted below.`);
|
|
265
|
+
if (reportFacts.conflicted.count > 0)
|
|
266
|
+
facts.push(`Report conflicted cells: ${reportFacts.conflicted.count}.`);
|
|
267
|
+
if (input.claim.conflicted.count > 0)
|
|
268
|
+
facts.push(`Claim conflicted cells: ${input.claim.conflicted.count}.`);
|
|
269
|
+
if (input.dissentCellKeys.length > 0)
|
|
270
|
+
facts.push(`Verification assembly dissenting cells: ${input.dissentCellKeys.length}.`);
|
|
271
|
+
if (input.matrix.attrition.asymmetryFlags.length > 0)
|
|
272
|
+
facts.push(`Matrix asymmetry flags: ${input.matrix.attrition.asymmetryFlags.length}.`);
|
|
273
|
+
if ((input.report.limitations ?? []).length > 0)
|
|
274
|
+
facts.push(`Report limitations: ${input.report.limitations.length}; read every limitation below.`);
|
|
275
|
+
if (input.claim.limitations.length > 0)
|
|
276
|
+
facts.push(`Claim limitations: ${input.claim.limitations.length}; read every limitation below.`);
|
|
277
|
+
if (facts.length === 0)
|
|
278
|
+
facts.push("No conflicts, dissent, asymmetry flags, or stated limitations.");
|
|
279
|
+
return facts;
|
|
280
|
+
}
|
|
281
|
+
function list(items, empty) {
|
|
282
|
+
if (items.length === 0)
|
|
283
|
+
return `<p>${escapeMarkup(empty)}</p>`;
|
|
284
|
+
return `<ul>${items.map((item) => `<li>${escapeMarkup(item)}</li>`).join("")}</ul>`;
|
|
285
|
+
}
|
|
286
|
+
function armRows(facts) {
|
|
287
|
+
return facts.arms.map((arm) => `<tr><th scope="row">${escapeMarkup(arm.armId)}</th><td>${arm.n}</td><td>${escapeMarkup(arm.passRate)}</td><td>${escapeMarkup(arm.low)}</td><td>${escapeMarkup(arm.high)}</td></tr>`).join("");
|
|
288
|
+
}
|
|
289
|
+
function armResultTable(facts, caption) {
|
|
290
|
+
return `<div class="table-scroll" tabindex="0" role="region" aria-label="${escapeMarkup(caption)}"><table><caption>${escapeMarkup(caption)}</caption><thead><tr><th scope="col">Arm</th><th scope="col">n</th><th scope="col">Pass rate</th><th scope="col">Interval low</th><th scope="col">Interval high</th></tr></thead><tbody>${armRows(facts)}</tbody></table></div>`;
|
|
291
|
+
}
|
|
292
|
+
/** Operator-approved P4b copy. The order is part of the public reading contract: direction,
|
|
293
|
+
* estimate, interval status, exact alpha, then the number of paired Tasks. */
|
|
294
|
+
function pairedEstimateLine(facts) {
|
|
295
|
+
const estimate = facts.delta === null ? "unavailable" : facts.delta;
|
|
296
|
+
const interval = facts.interval === null
|
|
297
|
+
? "withheld"
|
|
298
|
+
: `${facts.interval.low} to ${facts.interval.high}`;
|
|
299
|
+
return `Candidate minus baseline estimate (${facts.candidate} minus ${facts.baseline}): ${estimate}. Interval: ${interval}. Alpha: ${facts.alpha}. Paired task count: ${facts.pairs}.`;
|
|
300
|
+
}
|
|
301
|
+
/** `paired-majority-delta@1`'s counterpart to `pairedEstimateLine`. Same reading order (direction,
|
|
302
|
+
* estimate, interval status, alpha, paired count) so the two estimators read alike, but written
|
|
303
|
+
* against this method's own field spellings: `lower`/`upper` rather than `low`/`high`, `n` rather
|
|
304
|
+
* than `pairs`, and the alpha carried on the interval rather than alongside it. `candidate` is the
|
|
305
|
+
* evidence-declaring arm and `baseline` its evidence-free twin. */
|
|
306
|
+
function pairedMajorityDeltaEstimateLine(facts) {
|
|
307
|
+
const estimate = facts.delta === null ? "unavailable" : facts.delta;
|
|
308
|
+
const interval = facts.interval === null ? "withheld" : `${facts.interval.lower} to ${facts.interval.upper}`;
|
|
309
|
+
const alpha = facts.interval === null ? "not stated" : facts.interval.alpha;
|
|
310
|
+
return `Candidate minus baseline estimate (${facts.candidate} minus ${facts.baseline}): ${estimate}. Interval: ${interval}. Alpha: ${alpha}. Paired task count: ${facts.n}.`;
|
|
311
|
+
}
|
|
312
|
+
/** Full-report (unbounded) rendering of paired-delta@1 facts: pairs, delta, and either the
|
|
313
|
+
* interval bounds or -- when withheld -- every reason string, in full (no truncation budget
|
|
314
|
+
* applies on this surface, unlike the compact assets below). */
|
|
315
|
+
function comparisonFactsHtml(facts) {
|
|
316
|
+
const intervalHtml = facts.interval === null
|
|
317
|
+
? `<p>Interval withheld.</p>${list(facts.reasons, "No withheld reason recorded.")}`
|
|
318
|
+
: `<dl class="facts"><div><dt>Interval low</dt><dd>${escapeMarkup(facts.interval.low)}</dd></div><div><dt>Interval high</dt><dd>${escapeMarkup(facts.interval.high)}</dd></div><div><dt>Alpha</dt><dd>${escapeMarkup(facts.alpha)}</dd></div></dl>`;
|
|
319
|
+
return `<p class="paired-estimate">${escapeMarkup(pairedEstimateLine(facts))}</p><dl class="facts"><div><dt>Paired task count</dt><dd>${facts.pairs}</dd></div><div><dt>Candidate minus baseline estimate</dt><dd>${facts.delta === null ? "—" : escapeMarkup(facts.delta)}</dd></div></dl>${intervalHtml}`;
|
|
320
|
+
}
|
|
321
|
+
function judgeIntervalHtml(interval, reasons) {
|
|
322
|
+
return interval === null
|
|
323
|
+
? `<p>Interval withheld.</p>${list(reasons, "No withheld reason recorded.")}`
|
|
324
|
+
: `<dl class="facts"><div><dt>Interval lower</dt><dd>${escapeMarkup(interval.lower)}</dd></div><div><dt>Interval upper</dt><dd>${escapeMarkup(interval.upper)}</dd></div><div><dt>Alpha</dt><dd>${escapeMarkup(interval.alpha)}</dd></div></dl>`;
|
|
325
|
+
}
|
|
326
|
+
/** Renders every unordered arm pair the panel readout computed. No comparative winner is stated:
|
|
327
|
+
* the method carries no chosen pair and this asset must not invent one. */
|
|
328
|
+
function pairwiseDisagreementFactsHtml(facts) {
|
|
329
|
+
if (facts.pairs.length === 0)
|
|
330
|
+
return "<p>No arm pairs were computed.</p>";
|
|
331
|
+
const rows = facts.pairs.map((pair) => `<tr><th scope="row">${escapeMarkup(pair.armA)} vs ${escapeMarkup(pair.armB)}</th><td>${pair.n}</td><td>${pair.disagreements}</td><td>${pair.rate === null ? "—" : escapeMarkup(pair.rate)}</td><td>${pair.interval === null ? "withheld" : `${escapeMarkup(pair.interval.lower)} – ${escapeMarkup(pair.interval.upper)}`}</td></tr>`).join("");
|
|
332
|
+
return `<table><caption>Exact pairwise-disagreement@1 values from the sealed Report</caption><thead><tr><th scope="col">Arm pair</th><th scope="col">n</th><th scope="col">Disagreements</th><th scope="col">Rate</th><th scope="col">Interval</th></tr></thead><tbody>${rows}</tbody></table>`;
|
|
333
|
+
}
|
|
334
|
+
function pairedMajorityDeltaFactsHtml(facts) {
|
|
335
|
+
return `<dl class="facts"><div><dt>Candidate (evidence-declaring arm)</dt><dd>${escapeMarkup(facts.candidate)}</dd></div><div><dt>Baseline (evidence-free twin)</dt><dd>${escapeMarkup(facts.baseline)}</dd></div><div><dt>Paired task count</dt><dd>${facts.n}</dd></div><div><dt>Candidate minus baseline estimate</dt><dd>${facts.delta === null ? "—" : escapeMarkup(facts.delta)}</dd></div></dl>${judgeIntervalHtml(facts.interval, facts.reasons)}`;
|
|
336
|
+
}
|
|
337
|
+
/** The projection is already validated at :294 (validateBinaryInstrumentQualificationProjection),
|
|
338
|
+
* so `configuration["strata"]` is a sealed, non-empty, sorted-unique, grammar-conforming array. */
|
|
339
|
+
function stratumCaption(configuration) {
|
|
340
|
+
const strata = Array.isArray(configuration["strata"])
|
|
341
|
+
? configuration["strata"]
|
|
342
|
+
.filter((value) => typeof value === "string")
|
|
343
|
+
: [];
|
|
344
|
+
return `Buckets by stratum (${strata.join(", ")})`;
|
|
345
|
+
}
|
|
346
|
+
function binaryFactsHtml(facts) {
|
|
347
|
+
const caption = stratumCaption(facts.qualification.configuration);
|
|
348
|
+
const arms = Object.entries(facts.qualification.arms).map(([armId, arm]) => {
|
|
349
|
+
const rates = ["agreement", "falseAccept", "falseReject", "instability", "parserInvalid"]
|
|
350
|
+
.map((name) => `<tr><th scope="row">${escapeMarkup(name)}</th><td><pre>${escapeMarkup(canonicalText(arm[name]))}</pre></td></tr>`)
|
|
351
|
+
.join("");
|
|
352
|
+
return `<section class="binary-arm"><h3>${escapeMarkup(armId)}</h3><p>Instrument <span class="digest">${escapeMarkup(String(arm["instrumentSha256"]))}</span></p><h4>Item, call, and confusion denominators</h4><pre>${escapeMarkup(canonicalText({ item: arm["item"], call: arm["call"], confusion: arm["confusion"] }))}</pre><div class="table-scroll" tabindex="0" role="region" aria-label="${escapeMarkup(armId)} qualification rates"><table><caption>Five registered rates with exact denominators and Wilson intervals</caption><thead><tr><th scope="col">Rate</th><th scope="col">Registered result</th></tr></thead><tbody>${rates}</tbody></table></div><h4>Every candidate-class bucket</h4><pre>${escapeMarkup(canonicalText(arm["byCandidateClass"]))}</pre><h4>${escapeMarkup(caption)}</h4><pre>${escapeMarkup(canonicalText(arm["byStratum"]))}</pre></section>`;
|
|
353
|
+
}).join("");
|
|
354
|
+
return `<p class="neutral">Qualification facts are presented per instrument without comparative conclusions.</p><h3>Registered configuration</h3><pre>${escapeMarkup(canonicalText(facts.qualification.configuration))}</pre>${arms}<h3>Per-item decisions and instability</h3><pre>${escapeMarkup(canonicalText(facts.qualification.itemDecisions))}</pre><h3>Parser-invalid, infrastructure, and other exclusions</h3><pre>${escapeMarkup(canonicalText(facts.qualification.excluded))}</pre>`;
|
|
355
|
+
}
|
|
356
|
+
/** Dispatches the arm/comparison facts block on `facts.kind` (P4b Task 6). The wilson branch is
|
|
357
|
+
* byte-identical to before this dispatch existed -- `armResultTable` itself is untouched. */
|
|
358
|
+
function armResultsHtml(facts, wilsonCaption) {
|
|
359
|
+
if (facts.kind === "wilson")
|
|
360
|
+
return armResultTable(facts, wilsonCaption);
|
|
361
|
+
if (facts.kind === "comparison")
|
|
362
|
+
return comparisonFactsHtml(facts);
|
|
363
|
+
if (facts.kind === "pairwise-disagreement")
|
|
364
|
+
return pairwiseDisagreementFactsHtml(facts);
|
|
365
|
+
if (facts.kind === "paired-majority-delta")
|
|
366
|
+
return pairedMajorityDeltaFactsHtml(facts);
|
|
367
|
+
return binaryFactsHtml(facts);
|
|
368
|
+
}
|
|
369
|
+
function attritionRows(input) {
|
|
370
|
+
return Object.entries(input.matrix.attrition.perArm).map(([armId, counts]) => `<tr><th scope="row">${escapeMarkup(armId)}</th><td>${counts.expected}</td><td>${counts.judged}</td><td>${counts.unjudged}</td><td>${counts.unscorable}</td><td>${counts.expired}</td><td>${counts.invalidated}</td><td>${counts.excluded}</td><td>${counts.replacements}</td></tr>`).join("");
|
|
371
|
+
}
|
|
372
|
+
function cellScore(cell) {
|
|
373
|
+
return cell.primaryScore === undefined
|
|
374
|
+
? "No primary score"
|
|
375
|
+
: `${cell.primaryScore.value} ${cell.primaryScore.name} (${cell.primaryScore.direction})`;
|
|
376
|
+
}
|
|
377
|
+
function descriptiveComparisonHtml(comparison) {
|
|
378
|
+
const fact = comparison.descriptiveComparison;
|
|
379
|
+
if (fact === undefined)
|
|
380
|
+
return '<p class="neutral">No paired descriptive measurement is available. No comparative winner is stated.</p>';
|
|
381
|
+
return `<p class="neutral">Across ${fact.pairedCells} paired cells, ${escapeMarkup(fact.firstArm)} had lower ${fact.measurement} in ${fact.lowerByFirst}; ${escapeMarkup(fact.secondArm)} had lower ${fact.measurement} in ${fact.lowerBySecond}; ${fact.ties} tied. Lower is better. This is descriptive evidence, not a registered comparative winner.</p>`;
|
|
382
|
+
}
|
|
383
|
+
function comparisonMatrixHtml(comparison) {
|
|
384
|
+
const header = comparison.arms.map((arm) => `<th scope="col">${escapeMarkup(arm)}</th>`).join("");
|
|
385
|
+
const rows = comparison.tasks.map((task) => {
|
|
386
|
+
const armCells = comparison.arms.map((arm) => {
|
|
387
|
+
const cells = comparison.cells.filter((cell) => cell.taskDigest === task.digest && cell.armId === arm);
|
|
388
|
+
if (cells.length === 0)
|
|
389
|
+
return "<td>Not accounted</td>";
|
|
390
|
+
return `<td>${cells.map((cell) => `<a href="index.html#cell-${escapeMarkup(cell.cellKey)}"><strong>${escapeMarkup(cell.outputSummary)}</strong><br>${escapeMarkup(cellScore(cell))}<br><span class="source-label">${escapeMarkup(cell.outcome)} · replicate ${cell.replicate}</span></a>`).join("<hr>")}</td>`;
|
|
391
|
+
}).join("");
|
|
392
|
+
return `<tr><th scope="row"><strong>${escapeMarkup(task.label)}</strong><br><span class="source-label">${escapeMarkup(task.summary)}</span></th>${armCells}</tr>`;
|
|
393
|
+
}).join("");
|
|
394
|
+
return `<div class="table-scroll" tabindex="0" role="region" aria-label="Authenticated task by arm comparison"><table class="comparison-table"><caption>Each configuration faced the same authenticated Tasks</caption><thead><tr><th scope="col">Task</th>${header}</tr></thead><tbody>${rows}</tbody></table></div>`;
|
|
395
|
+
}
|
|
396
|
+
function comparisonCellDetailsHtml(comparison) {
|
|
397
|
+
return comparison.cells.map((cell) => {
|
|
398
|
+
const outputLinks = cell.outputs.map((output) => `<li><a href="index.html#records-heading">${escapeMarkup(output.name)}</a>: ${escapeMarkup(output.summary)} <span class="digest">${escapeMarkup(output.sha256)}</span></li>`).join("");
|
|
399
|
+
const verdicts = cell.verdicts.map((verdict) => `<li><a href="index.html#records-heading">${escapeMarkup(verdict.evaluator)}</a>: ${escapeMarkup(verdict.verdict)}<pre>${escapeMarkup(canonicalText(verdict.measurements))}</pre></li>`).join("");
|
|
400
|
+
return `<details id="cell-${escapeMarkup(cell.cellKey)}" class="cell-detail"><summary><strong>${escapeMarkup(cell.armId)}</strong> · Task ${escapeMarkup(cell.taskDigest.slice(0, 12))} · replicate ${cell.replicate} · ${escapeMarkup(cellScore(cell))}</summary><p>${escapeMarkup(cell.outputSummary)}</p><h4>Authenticated outputs</h4><ul>${outputLinks || "<li>No solve output.</li>"}</ul><h4>Authenticated verdict evidence</h4><ul>${verdicts || "<li>No verdict evidence.</li>"}</ul></details>`;
|
|
401
|
+
}).join("");
|
|
402
|
+
}
|
|
403
|
+
function comparisonSectionHtml(comparison) {
|
|
404
|
+
if (comparison === undefined)
|
|
405
|
+
return "";
|
|
406
|
+
const sampleDisclosure = comparison.sampleKind === "bundled-prediction"
|
|
407
|
+
? '<p class="sample-note">This bundled sample demonstrates the evidence path, not agent quality. Its outcomes are synthetic and derived from the sample consensus inputs. No comparative winner is stated.</p>'
|
|
408
|
+
: "";
|
|
409
|
+
return `<section id="comparison" aria-labelledby="comparison-heading"><p class="eyebrow">Answer first</p><h2 id="comparison-heading">What happened, task by task</h2>${descriptiveComparisonHtml(comparison)}${sampleDisclosure}${comparisonMatrixHtml(comparison)}<h3>Open a cell to inspect its evidence</h3>${comparisonCellDetailsHtml(comparison)}</section>`;
|
|
410
|
+
}
|
|
411
|
+
function comparisonSectionMarkdown(comparison) {
|
|
412
|
+
if (comparison === undefined)
|
|
413
|
+
return "";
|
|
414
|
+
const fact = comparison.descriptiveComparison;
|
|
415
|
+
const descriptive = fact === undefined
|
|
416
|
+
? "No paired descriptive measurement is available. No comparative winner is stated."
|
|
417
|
+
: `Across ${fact.pairedCells} paired cells, ${escapeMarkdown(fact.firstArm)} had lower ${fact.measurement} in ${fact.lowerByFirst}; ${escapeMarkdown(fact.secondArm)} had lower ${fact.measurement} in ${fact.lowerBySecond}; ${fact.ties} tied. Lower is better. This is descriptive evidence, not a registered comparative winner.`;
|
|
418
|
+
const sample = comparison.sampleKind === "bundled-prediction"
|
|
419
|
+
? "\n\nThis bundled sample demonstrates the evidence path, not agent quality. Its outcomes are synthetic and derived from the sample consensus inputs."
|
|
420
|
+
: "";
|
|
421
|
+
const tasks = comparison.tasks.map((task) => {
|
|
422
|
+
const cells = comparison.cells.filter((cell) => cell.taskDigest === task.digest).map((cell) => ` - **${escapeMarkdown(cell.armId)}**, replicate ${cell.replicate}: ${escapeMarkdown(cell.outputSummary)}; ${escapeMarkdown(cellScore(cell))}; outcome ${escapeMarkdown(cell.outcome)}. Task evidence \`${escapeMarkdownCode(task.evidencePath)}\`; outputs ${cell.outputs.map((output) => `\`${escapeMarkdownCode(output.evidencePath)}\``).join(", ") || "none"}; verdicts ${cell.verdicts.map((verdict) => `\`${escapeMarkdownCode(verdict.evidencePath)}\``).join(", ") || "none"}.`).join("\n");
|
|
423
|
+
return `- **${escapeMarkdown(task.label)}** — ${escapeMarkdown(task.summary)}\n${cells}`;
|
|
424
|
+
}).join("\n");
|
|
425
|
+
return `## What happened, task by task\n\n${descriptive}${sample}\n\n${tasks}\n\n`;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Publication-grade wording, keyed on `truthAdmission` (spec §6.8a Group A; ruling C-5) rather
|
|
429
|
+
* than the `publicationGrade` boolean alone. The boolean-keyed ternary this replaced printed the
|
|
430
|
+
* literal words "Yes — two-human unanimous" for ANY publication-grade admission, and the
|
|
431
|
+
* screened-operator-sampled branch is publication-grade under §6.8 -- so the old ternary would
|
|
432
|
+
* have published a false two-human-unanimous claim for a screened bundle. The two existing
|
|
433
|
+
* strings below keep their exact bytes (half of the byte-compatibility proof, spec §0.4); the
|
|
434
|
+
* screened wording is frozen by ruling C-5 and passes §6.1's overclaim test: no "human", no
|
|
435
|
+
* "unanimous", no "independent". It echoes the mode name so a reader can join it to the "Truth
|
|
436
|
+
* admission" field rendered immediately below, and stays terse because the full disclosure is the
|
|
437
|
+
* `screened-not-independently-labeled` limitation prose rendered in the limitations section.
|
|
438
|
+
*/
|
|
439
|
+
function publicationGradeWording(truthAdmission) {
|
|
440
|
+
switch (truthAdmission) {
|
|
441
|
+
case "two-human-unanimous":
|
|
442
|
+
return { html: "Yes — two-human unanimous", markdown: "yes — two-human unanimous" };
|
|
443
|
+
case "operator-only":
|
|
444
|
+
return { html: "No — operator-only", markdown: "no — operator-only" };
|
|
445
|
+
case "screened-operator-sampled":
|
|
446
|
+
return { html: "Yes — screened and operator-sampled", markdown: "yes — screened and operator-sampled" };
|
|
447
|
+
default: {
|
|
448
|
+
const exhaustive = truthAdmission;
|
|
449
|
+
throw new Error(`unsupported truthAdmission ${String(exhaustive)}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
export function binaryAdmissionHtml(input) {
|
|
454
|
+
const admission = input.binaryQualification;
|
|
455
|
+
if (admission === undefined)
|
|
456
|
+
return "";
|
|
457
|
+
return `<section id="qualification-admission" aria-labelledby="qualification-admission-heading"><h2 id="qualification-admission-heading">Authenticated truth admission and instruments</h2><dl class="facts"><div><dt>Publication grade</dt><dd>${publicationGradeWording(admission.truthAdmission).html}</dd></div><div><dt>Truth admission</dt><dd>${escapeMarkup(admission.truthAdmission)}</dd></div><div><dt>Source manifest</dt><dd class="digest">${escapeMarkup(admission.sourceManifestSha256)}</dd></div><div><dt>Admission manifest</dt><dd class="digest">${escapeMarkup(admission.admissionManifestSha256)}</dd></div><div><dt>Pre-run exclusions</dt><dd>${admission.exclusions.length}</dd></div></dl><h3>Human disagreement and deterministic replacements</h3><pre>${escapeMarkup(canonicalText(admission.exclusions))}</pre><h3>Exact instrument and prompt-template commitments</h3><pre>${escapeMarkup(canonicalText(admission.instruments))}</pre></section>`;
|
|
458
|
+
}
|
|
459
|
+
export function binaryAdmissionMarkdown(input) {
|
|
460
|
+
const admission = input.binaryQualification;
|
|
461
|
+
if (admission === undefined)
|
|
462
|
+
return "";
|
|
463
|
+
return `## Authenticated truth admission and instruments\n\n- Publication grade: ${publicationGradeWording(admission.truthAdmission).markdown}\n- Truth admission: ${escapeMarkdown(admission.truthAdmission)}\n- Source manifest: ${escapeMarkdown(admission.sourceManifestSha256)}\n- Admission manifest: ${escapeMarkdown(admission.admissionManifestSha256)}\n- Pre-run exclusions: ${admission.exclusions.length}\n\n### Human disagreement and deterministic replacements\n\n ${escapeMarkdownCode(canonicalText(admission.exclusions))}\n\n### Exact instrument and prompt-template commitments\n\n ${escapeMarkdownCode(canonicalText(admission.instruments))}\n\n`;
|
|
464
|
+
}
|
|
465
|
+
function factsHeading(facts, source) {
|
|
466
|
+
if (facts.kind === "wilson")
|
|
467
|
+
return source === "report" ? "Sealed Report arm results" : "Stored claim mirror";
|
|
468
|
+
if (facts.kind === "comparison")
|
|
469
|
+
return source === "report" ? "Sealed Report paired comparison" : "Stored paired claim mirror";
|
|
470
|
+
if (facts.kind === "pairwise-disagreement") {
|
|
471
|
+
return source === "report" ? "Sealed Report pairwise disagreement panel" : "Stored pairwise disagreement mirror";
|
|
472
|
+
}
|
|
473
|
+
if (facts.kind === "paired-majority-delta") {
|
|
474
|
+
return source === "report" ? "Sealed Report evidence contrast" : "Stored evidence contrast mirror";
|
|
475
|
+
}
|
|
476
|
+
return source === "report" ? "Sealed binary qualification" : "Stored binary qualification";
|
|
477
|
+
}
|
|
478
|
+
/** The one neutral sentence each method's index page leads with. Every method needs its own: the
|
|
479
|
+
* fallthrough used to be binary-instrument's sentence, so a method added without a branch here
|
|
480
|
+
* would have published "Verified binary-instrument qualification" over a judge readout. */
|
|
481
|
+
function neutralClaimHtml(facts) {
|
|
482
|
+
if (facts.kind === "wilson") {
|
|
483
|
+
return '<p class="neutral">No comparative winner is stated; wilson@1 reports neutral per-arm facts only.</p>';
|
|
484
|
+
}
|
|
485
|
+
if (facts.kind === "comparison") {
|
|
486
|
+
return `<p class="neutral">${escapeMarkup(pairedEstimateLine(facts))}</p>`;
|
|
487
|
+
}
|
|
488
|
+
if (facts.kind === "paired-majority-delta") {
|
|
489
|
+
return `<p class="neutral">${escapeMarkup(pairedMajorityDeltaEstimateLine(facts))}</p>`;
|
|
490
|
+
}
|
|
491
|
+
if (facts.kind === "pairwise-disagreement") {
|
|
492
|
+
return '<p class="neutral">No comparative winner is stated; pairwise-disagreement@1 reports every unordered arm pair as a panel, with no baseline and no candidate.</p>';
|
|
493
|
+
}
|
|
494
|
+
return '<p class="neutral">Verified binary-instrument qualification. Facts are presented per instrument without comparative conclusions.</p>';
|
|
495
|
+
}
|
|
496
|
+
function buildIndex(input, reportFacts, claimFacts) {
|
|
497
|
+
const outcome = input.matrix.completeness.runOutcome;
|
|
498
|
+
const status = reportFacts.kind === "binary" ? qualificationOutcomeLabel(outcome) : outcomeLabel(outcome);
|
|
499
|
+
const adverse = adverseFacts(input, reportFacts);
|
|
500
|
+
const arms = input.claim.scope.arms.map((arm) => `<li><strong>${escapeMarkup(arm.armId)}</strong><pre>${escapeMarkup(canonicalText(arm.pinning))}</pre></li>`).join("");
|
|
501
|
+
const topLevelFiles = [
|
|
502
|
+
["benchmark.json", "Benchmark record"],
|
|
503
|
+
["run.json", "Run record"],
|
|
504
|
+
["matrix.json", "Matrix record"],
|
|
505
|
+
["report.json", "Report payload"],
|
|
506
|
+
["report-envelope.json", "Report signature envelope"],
|
|
507
|
+
["claim-package.json", "Claim package"],
|
|
508
|
+
...(reportFacts.kind === "binary" ? [["qualification.json", "Binary qualification graph"]] : []),
|
|
509
|
+
["static-bundle.json", "Static-bundle projection"],
|
|
510
|
+
["evidence.json", "Evidence catalog"],
|
|
511
|
+
["verdicts.json", "Verdict catalog"],
|
|
512
|
+
["verification/assembly.jsonl", "Verification assembly"],
|
|
513
|
+
["trust/public-keys.json", "Public trust material"],
|
|
514
|
+
];
|
|
515
|
+
const casFiles = recordPaths(input);
|
|
516
|
+
const rehearsal = input.claim.rehearsal;
|
|
517
|
+
const rehearsalHtml = rehearsal === undefined
|
|
518
|
+
? "<p>No product rehearsal is recorded in the stored Claim.</p>"
|
|
519
|
+
: `<p>Preview count: ${rehearsal.previewCount}</p>${list(rehearsal.timestamps, "No timestamps recorded.")}`;
|
|
520
|
+
const html = `<!doctype html>
|
|
521
|
+
<html lang="en">
|
|
522
|
+
<head>
|
|
523
|
+
<meta charset="utf-8">
|
|
524
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
525
|
+
<meta name="color-scheme" content="light">
|
|
526
|
+
<title>${escapeMarkup(status)} — Colophon report</title>
|
|
527
|
+
<style>
|
|
528
|
+
${embeddedFontCss()}
|
|
529
|
+
:root{color-scheme:light;--paper:#f7f4ed;--panel:#fffdf8;--ink:#14120e;--muted:#6b675f;--line:#cfc8bb;--vermilion:#c7402a;--indigo:#27406b;--moss:#496246;--ochre:#9d6b23;font:16px/1.6 "Public Sans",Arial,sans-serif}
|
|
530
|
+
*{box-sizing:border-box}html,body{margin:0;max-width:100%}body{background:var(--paper);color:var(--ink)}a{color:var(--indigo);text-decoration-thickness:1px;text-underline-offset:.22em}a:focus-visible,[tabindex]:focus-visible{outline:.2rem solid var(--vermilion);outline-offset:.2rem}header,main,footer{width:min(68rem,100%);margin-inline:auto;padding:clamp(1rem,4vw,3rem)}header{border-bottom:1px solid var(--ink);padding-block-end:clamp(2rem,7vw,5rem)}header,main,footer,section,ul,li,dl,div{min-width:0}main{display:grid;gap:0}section{background:transparent;border-bottom:1px solid var(--line);padding:clamp(1.4rem,4vw,3rem) 0}.masthead{display:flex;align-items:center;gap:.7rem;margin-bottom:clamp(2.5rem,8vw,6rem);font-family:"Newsreader",Georgia,serif;font-size:1.55rem;font-weight:650;letter-spacing:-.02em}.masthead img{width:1.65rem;height:1.65rem}.eyebrow,.status,.source-label,.facts dt{font:600 .72rem/1.35 "Public Sans",Arial,sans-serif;letter-spacing:.09em;text-transform:uppercase}.eyebrow{color:var(--vermilion);margin:0 0 .65rem}.status{display:inline-block;border:1px solid currentColor;border-radius:.18rem;padding:.3rem .55rem;margin:0 0 1.2rem}.status[data-run-outcome="partial"],.status[data-run-outcome="cancelled"]{color:var(--vermilion)}h1,h2{font-family:"Newsreader",Georgia,serif;font-weight:500;letter-spacing:-.035em}h1,h2,h3{line-height:1.05;margin-block:0 .8rem}h1{font-size:clamp(3.2rem,11vw,7rem);max-width:11ch}h2{font-size:clamp(2rem,6vw,3.8rem);max-width:18ch}h3{font-size:1rem;margin-top:1.6rem}.lede{font-family:"Newsreader",Georgia,serif;font-size:clamp(1.25rem,3vw,1.75rem);line-height:1.35;max-width:52ch}.neutral{font-weight:650;max-width:62ch;border-top:1px solid var(--line);padding-top:1rem}.adverse{border-top:.3rem solid var(--vermilion);border-bottom-color:var(--vermilion)}.adverse h2{color:var(--vermilion)}.facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,12rem),1fr));gap:1.25rem}.facts div{border-top:1px solid var(--ink);padding-top:.55rem}.facts dt{color:var(--muted)}.facts dd{margin:.2rem 0 0;font-weight:600}strong,th,td,li,dd,.source-label{overflow-wrap:anywhere;word-break:break-word}code,pre,.digest{font-family:"IBM Plex Mono",monospace;overflow-wrap:anywhere;word-break:break-word}pre{max-width:100%;white-space:pre-wrap;margin:.7rem 0 0;padding:1rem;background:#eee9df;border:1px solid var(--line);border-radius:.18rem;font-size:.82rem}.table-scroll{max-width:100%;overflow-x:auto;margin-block:.8rem;border-top:1px solid var(--ink)}table{width:100%;min-width:43rem;table-layout:fixed;border-collapse:collapse}caption{text-align:left;font-weight:650;padding-block:.65rem}th,td{text-align:left;vertical-align:top;border-bottom:1px solid var(--line);padding:.65rem}.compact-list{columns:2;column-width:18rem}.compact-list li{break-inside:avoid;margin-block:.3rem}.source-label{color:var(--muted)}.about{font-family:"Newsreader",Georgia,serif;font-size:1.1rem}footer{border-top:1px solid var(--ink)}
|
|
531
|
+
@media(max-width:32rem){header,main,footer{padding:1rem}.masthead{margin-bottom:3rem}.compact-list{columns:1}.facts{grid-template-columns:1fr}section{padding:1.5rem 0}h1{font-size:clamp(3rem,18vw,4.6rem)}}
|
|
532
|
+
@media print{:root{color-scheme:light}body{background:#fff;color:#000}header,main,footer{width:100%;padding:.5rem 0}section{break-inside:avoid;border-color:#777}a{color:#000;text-decoration:underline}.table-scroll{overflow:visible}table{min-width:0;font-size:9pt}}
|
|
533
|
+
</style>
|
|
534
|
+
</head>
|
|
535
|
+
<body>
|
|
536
|
+
<header>
|
|
537
|
+
<div class="masthead"><img src="${COLOPHON_MARK_DATA_URI}" width="32" height="32" alt=""><span>${escapeMarkup(PRODUCT_BRANDING.displayName)}</span></div>
|
|
538
|
+
<p class="eyebrow">${escapeMarkup(PRODUCT_BRANDING.categoryDescriptor)}</p>
|
|
539
|
+
<p class="status" data-run-outcome="${outcome}">${escapeMarkup(status)}</p>
|
|
540
|
+
<h1>Colophon report</h1>
|
|
541
|
+
<p class="lede">${escapeMarkup(scopeLine(input))}</p>
|
|
542
|
+
${neutralClaimHtml(reportFacts)}
|
|
543
|
+
</header>
|
|
544
|
+
<main>
|
|
545
|
+
<section class="adverse" aria-labelledby="adverse-heading"><h2 id="adverse-heading">Prominent adverse facts</h2>${list(adverse, "No adverse facts stated.")}</section>${input.comparison === undefined ? "" : `\n${comparisonSectionHtml(input.comparison)}`}
|
|
546
|
+
<section aria-labelledby="scope-heading"><h2 id="scope-heading">Benchmark and configuration scope</h2><dl class="facts"><div><dt>Benchmark digest</dt><dd class="digest">${input.claim.scope.benchmarkSha256}</dd></div><div><dt>Tasks</dt><dd>${input.claim.scope.taskCount}</dd></div><div><dt>Replicates</dt><dd>${input.claim.scope.replicates}</dd></div><div><dt>Venue</dt><dd>${escapeMarkup(input.claim.scope.venue)}</dd></div></dl><h3>Arms and pinned configuration</h3><ul>${arms}</ul></section>${reportFacts.kind === "binary" ? binaryAdmissionHtml(input) : ""}
|
|
547
|
+
<section aria-labelledby="matrix-heading"><h2 id="matrix-heading">Sealed Matrix accounting</h2><p class="source-label">Source: authenticated <a href="matrix.json">matrix.json</a>; values below are copied without reconciliation.</p><pre>${escapeMarkup(canonicalText({ completeness: input.matrix.completeness, attrition: input.matrix.attrition }))}</pre><h3>Completeness and attrition</h3><dl class="facts"><div><dt>Matrix run outcome</dt><dd>${escapeMarkup(outcome)}</dd></div><div><dt>Matrix expected</dt><dd>${input.matrix.completeness.expected}</dd></div><div><dt>Matrix judged</dt><dd>${input.matrix.completeness.judged}</dd></div><div><dt>Matrix floor</dt><dd>${escapeMarkup(input.matrix.completeness.floor)}</dd></div></dl><div class="table-scroll" tabindex="0" role="region" aria-label="Per-arm Matrix attrition"><table><caption>Exact per-arm attrition stored in the Matrix</caption><thead><tr><th scope="col">Arm</th><th scope="col">Expected</th><th scope="col">Judged</th><th scope="col">Unjudged</th><th scope="col">Unscorable</th><th scope="col">Expired</th><th scope="col">Invalidated</th><th scope="col">Excluded</th><th scope="col">Replacements</th></tr></thead><tbody>${attritionRows(input)}</tbody></table></div><h3>Matrix asymmetry flags</h3>${list(input.matrix.attrition.asymmetryFlags, "None recorded in the Matrix.")}</section>
|
|
548
|
+
<section aria-labelledby="report-heading"><h2 id="report-heading">Sealed Report facts</h2><p class="source-label">Source: authenticated <a href="report.json">report.json</a>; values below are copied without reconciliation.</p><h3>${factsHeading(reportFacts, "report")}</h3>${armResultsHtml(reportFacts, "Exact wilson@1 values from the sealed Report")}<h3>Method and assurance facts stored in the Report</h3><dl class="facts"><div><dt>Report method</dt><dd>${escapeMarkup(input.report.method.id)} @ ${escapeMarkup(input.report.method.version)}</dd></div><div><dt>Report preregistered</dt><dd>${input.report.preregistered === true ? "Yes" : "No"}</dd></div></dl><h3>Report parameters</h3><pre>${escapeMarkup(canonicalText(input.report.method.parameters))}</pre><h3>Report conflicts</h3><pre>${escapeMarkup(canonicalText(reportFacts.conflicted))}</pre><h3>Report disclosures</h3><pre>${escapeMarkup(canonicalText(input.report.disclosures))}</pre></section>
|
|
549
|
+
<section aria-labelledby="claim-heading"><h2 id="claim-heading">Stored Claim facts</h2><p class="source-label">Source: authenticated <a href="claim-package.json">claim-package.json</a>; values below are copied without reconciliation.</p><h3>${factsHeading(claimFacts, "claim")}</h3>${armResultsHtml(claimFacts, "Exact arm values stored in the Claim package")}<h3>Claim method and preregistration</h3><dl class="facts"><div><dt>Claim method</dt><dd>${escapeMarkup(input.claim.method.id)} @ ${escapeMarkup(input.claim.method.version)}</dd></div><div><dt>Claim preregistered</dt><dd>${input.claim.method.preregistered ? "Yes" : "No"}</dd></div><div><dt>Assurance preset</dt><dd>${escapeMarkup(input.claim.assurance.preset)}</dd></div></dl><h3>Claim parameters</h3><pre>${escapeMarkup(canonicalText(input.claim.method.parameters))}</pre><h3>Claim completeness</h3><pre>${escapeMarkup(canonicalText(input.claim.completeness))}</pre><h3>Claim attrition</h3><pre>${escapeMarkup(canonicalText(input.claim.attrition))}</pre><h3>Claim conflicts</h3><pre>${escapeMarkup(canonicalText(input.claim.conflicted))}</pre><h3>Claim disclosures</h3><h4>Unverifiable axes, integrity tiers, and per-subject disclosures</h4><pre>${escapeMarkup(canonicalText(input.claim.disclosures))}</pre><h3>Resolved assurance primitives</h3><pre>${escapeMarkup(canonicalText(input.claim.assurance.resolved))}</pre><p>${escapeMarkup(input.claim.assurance.disclosure)}</p><h3>Rehearsal disclosure</h3>${rehearsalHtml}</section>
|
|
550
|
+
<section aria-labelledby="dissent-heading"><h2 id="dissent-heading">Verification assembly dissent</h2><p class="source-label">Source: authenticated <a href="verification/assembly.jsonl">verification assembly</a>.</p><dl class="facts"><div><dt>Dissenting cells</dt><dd>${input.dissentCellKeys.length}</dd></div></dl>${list(input.dissentCellKeys, "None recorded in the verification assembly.")}</section>
|
|
551
|
+
<section id="limitations" aria-labelledby="limitations-heading"><h2 id="limitations-heading">Limitations by stored source</h2><h3>Sealed Report limitations</h3>${list(input.report.limitations ?? [], "None recorded in the sealed Report.")}<h3>Stored Claim limitations</h3>${list(input.claim.limitations, "None recorded in the stored Claim.")}<h3>Local self-run trust boundary stored in the Claim</h3><pre>${escapeMarkup(canonicalText(input.claim.venueHonesty))}</pre></section>
|
|
552
|
+
<section aria-labelledby="records-heading"><h2 id="records-heading">Records and exact identities</h2><dl class="facts"><div><dt>Report SHA-256</dt><dd class="digest">${input.reportSha256}</dd></div><div><dt>Matrix SHA-256</dt><dd class="digest">${input.matrixSha256}</dd></div><div><dt>Run SHA-256</dt><dd class="digest">${input.claim.records.runSha256}</dd></div><div><dt>Report envelope SHA-256</dt><dd class="digest">${input.claim.records.reportEnvelopeSha256}</dd></div></dl><h3>Top-level records and catalogs</h3><ul class="compact-list">${topLevelFiles.map(([path, label]) => `<li><a href="${path}">${escapeMarkup(label)} <span class="digest">(${path})</span></a></li>`).join("")}</ul><h3>Every manifest-listed content-addressed record</h3><ul class="compact-list">${casFiles.map((path) => `<li><a href="${path}">CAS record <span class="digest">(${path})</span></a></li>`).join("")}</ul></section>
|
|
553
|
+
<section id="verification" aria-labelledby="verification-heading"><h2 id="verification-heading">Portable verification</h2><p>Copy this entire directory. Reproduce publication with the exact verifier:</p><pre><code>${escapeMarkup(input.claim.verification.command)}</code></pre><p>Use the compatible major line to receive fixes that preserve this bundle-format contract:</p><pre><code>${escapeMarkup(input.claim.verification.compatibleCommand)}</code></pre><h3>Named checks</h3>${list(input.claim.verification.checks, "No checks recorded.")}<h3>Trust root</h3><p>${escapeMarkup(input.claim.verification.trustRoot)}</p><p class="about">${escapeMarkup(PRODUCT_BRANDING.attribution)}</p></section>
|
|
554
|
+
</main>
|
|
555
|
+
<footer><nav aria-label="Report references"><a href="index.html#limitations">Read limitations</a> · <a href="index.html#verification">Verify this report</a></nav><p>Report <span class="digest">${input.reportSha256}</span></p><p class="about">${escapeMarkup(PRODUCT_BRANDING.attribution)}</p></footer>
|
|
556
|
+
</body>
|
|
557
|
+
</html>\n`;
|
|
558
|
+
return html;
|
|
559
|
+
}
|
|
560
|
+
function compactStatus(input, reportFacts) {
|
|
561
|
+
return `${outcomeLabel(input.matrix.completeness.runOutcome)} · Report conflicts ${reportFacts.conflicted.count} · Claim conflicts ${input.claim.conflicted.count} · assembly dissent ${input.dissentCellKeys.length} · Matrix asymmetry ${input.matrix.attrition.asymmetryFlags.length} · Report limitations ${(input.report.limitations ?? []).length} · Claim limitations ${input.claim.limitations.length}`;
|
|
562
|
+
}
|
|
563
|
+
/** Compact paired surfaces deliberately carry no estimate, interval bound, alpha, or pair count.
|
|
564
|
+
* They disclose the interval state and point to the relative full-report path instead. */
|
|
565
|
+
function pairedCompactFragment(facts) {
|
|
566
|
+
if (facts.kind === "wilson")
|
|
567
|
+
return "";
|
|
568
|
+
if (facts.kind === "binary")
|
|
569
|
+
return "Verified qualification signpost · full evidence at index.html";
|
|
570
|
+
if (facts.kind === "pairwise-disagreement") {
|
|
571
|
+
return `Pairwise disagreement panel · ${facts.pairs.length} arm pairs · index.html`;
|
|
572
|
+
}
|
|
573
|
+
if (facts.delta === null)
|
|
574
|
+
return "Paired estimate unavailable · read full report at index.html";
|
|
575
|
+
if (facts.interval === null)
|
|
576
|
+
return "Paired estimate reported; interval withheld · index.html";
|
|
577
|
+
return "Paired estimate and interval in full report · index.html";
|
|
578
|
+
}
|
|
579
|
+
function buildBadge(input, reportFacts) {
|
|
580
|
+
const scope = scopeLine(input);
|
|
581
|
+
if (reportFacts.kind === "binary") {
|
|
582
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc" viewBox="0 0 920 150" width="920" height="150"><title id="title">Colophon verified qualification</title><desc id="desc">${escapeMarkup(`Verified. ${scope}. Report SHA-256 ${input.reportSha256}. Full evidence: index.html.`)}</desc><metadata>${escapeMarkup(`verified=true; scope=${scope}; Report SHA-256=${input.reportSha256}`)}</metadata><rect width="920" height="150" fill="#14120e"/><text x="20" y="45" fill="#f7f4ed" font-family="sans-serif" font-size="22">Colophon · verified qualification</text><text x="20" y="82" fill="#d8d1c5" font-family="sans-serif" font-size="14">${escapeMarkup(scope)}</text><a href="index.html"><text x="20" y="118" fill="#9eb9ef" font-family="monospace" font-size="11">Report ${input.reportSha256} · index.html</text></a></svg>\n`;
|
|
583
|
+
}
|
|
584
|
+
const status = compactStatus(input, reportFacts);
|
|
585
|
+
const digest = input.reportSha256.slice(0, 12);
|
|
586
|
+
const exactArms = input.claim.scope.arms.map((arm) => arm.armId).join(" · ");
|
|
587
|
+
const visualStatus = boundedVisual(status, 112);
|
|
588
|
+
const visualConfig = boundedVisual(scope, 112);
|
|
589
|
+
// Extra row only for the paired branch (P4b Task 6) -- empty string for wilson keeps every
|
|
590
|
+
// wilson byte, including the fixed 150-height viewBox, identical to before this dispatch.
|
|
591
|
+
const pairedFragment = pairedCompactFragment(reportFacts);
|
|
592
|
+
const height = pairedFragment === "" ? 150 : 176;
|
|
593
|
+
const pairedRow = pairedFragment === ""
|
|
594
|
+
? ""
|
|
595
|
+
: `<a href="index.html"><text data-field="paired-status" x="20" y="160" fill="#d8d1c5" font-family="Public Sans,sans-serif" font-size="12">${escapeMarkup(pairedFragment)}</text></a>`;
|
|
596
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc" viewBox="0 0 920 ${height}" width="920" height="${height}" style="max-width:100%;height:auto"><title id="title">Colophon · ${escapeMarkup(outcomeLabel(input.matrix.completeness.runOutcome))}; no comparative winner stated</title><desc id="desc">${escapeMarkup(`${scope}. Exact configuration arm IDs: ${exactArms}. ${status}. Full Report SHA-256 ${input.reportSha256}. Read index.html limitations and verification.`)}</desc><metadata>${escapeMarkup(`Report SHA-256: ${input.reportSha256}; exact arm IDs: ${exactArms}`)}</metadata><style>${embeddedFontCss()}</style><rect width="920" height="${height}" fill="#14120e"/><rect x="18" y="18" width="12" height="12" transform="rotate(45 24 24)" fill="#c7402a"/><text x="44" y="30" fill="#f7f4ed" font-family="Newsreader,serif" font-size="21" font-weight="650">Colophon</text><line x1="20" x2="900" y1="42" y2="42" stroke="#6b675f"/><text data-field="neutral-status" x="20" y="66" fill="#f7f4ed" font-family="Public Sans,sans-serif" font-size="16" font-weight="700">${escapeMarkup(outcomeLabel(input.matrix.completeness.runOutcome))} · no comparative winner stated</text><text data-field="adverse-status" x="20" y="90" fill="#f07a62" font-family="Public Sans,sans-serif" font-size="12">${escapeMarkup(visualStatus)}</text><text data-field="config-summary" x="20" y="112" fill="#d8d1c5" font-family="Public Sans,sans-serif" font-size="13">${escapeMarkup(visualConfig)}</text><a href="index.html#limitations"><text x="20" y="136" fill="#9eb9ef" font-family="IBM Plex Mono,monospace" font-size="11">Report ${digest} · index.html#limitations · index.html#verification</text></a>${pairedRow}</svg>\n`;
|
|
597
|
+
}
|
|
598
|
+
function buildSocialCard(input, reportFacts) {
|
|
599
|
+
const scope = scopeLine(input);
|
|
600
|
+
if (reportFacts.kind === "binary") {
|
|
601
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc" viewBox="0 0 1200 630" width="1200" height="630"><title id="title">Colophon verified qualification</title><desc id="desc">${escapeMarkup(`Verified. ${scope}. Report SHA-256 ${input.reportSha256}. Full evidence: index.html.`)}</desc><metadata>${escapeMarkup(`verified=true; scope=${scope}; Report SHA-256=${input.reportSha256}`)}</metadata><rect width="1200" height="630" fill="#f7f4ed"/><text x="78" y="180" fill="#c7402a" font-family="sans-serif" font-size="24">COLOPHON · VERIFIED QUALIFICATION</text><text x="78" y="300" fill="#14120e" font-family="serif" font-size="64">Evidence signpost</text><text x="78" y="390" fill="#14120e" font-family="sans-serif" font-size="24">${escapeMarkup(scope)}</text><a href="index.html"><text x="78" y="500" fill="#27406b" font-family="monospace" font-size="17">Report ${input.reportSha256}</text></a><text x="78" y="545" fill="#27406b" font-family="monospace" font-size="17">Full evidence: index.html</text></svg>\n`;
|
|
602
|
+
}
|
|
603
|
+
const status = compactStatus(input, reportFacts);
|
|
604
|
+
const armIds = input.claim.scope.arms.map((arm) => arm.armId).join(" · ");
|
|
605
|
+
const digest = input.reportSha256.slice(0, 12);
|
|
606
|
+
const visualStatus = boundedVisual(status, 112);
|
|
607
|
+
const visualConfig = boundedVisual(`${input.claim.scope.arms.length} configurations: ${armIds}`, 40);
|
|
608
|
+
// Extra row only for the paired branch (P4b Task 6) -- empty string for wilson, appended after
|
|
609
|
+
// the last existing element, keeps every wilson byte identical to before this dispatch.
|
|
610
|
+
const pairedFragment = pairedCompactFragment(reportFacts);
|
|
611
|
+
const pairedRow = pairedFragment === ""
|
|
612
|
+
? ""
|
|
613
|
+
: `<a href="index.html"><text data-field="paired-status" x="78" y="605" fill="#6b675f" font-family="Public Sans,sans-serif" font-size="16">${escapeMarkup(pairedFragment)}</text></a>`;
|
|
614
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc" viewBox="0 0 1200 630" width="1200" height="630" style="max-width:100%;height:auto"><title id="title">Colophon · ${escapeMarkup(outcomeLabel(input.matrix.completeness.runOutcome))}; neutral benchmark report</title><desc id="desc">${escapeMarkup(`${scope}. Exact configuration arm IDs: ${armIds}. ${status}. Full Report SHA-256 ${input.reportSha256}. Read index.html limitations and verification.`)}</desc><metadata>${escapeMarkup(`Report SHA-256: ${input.reportSha256}; exact arm IDs: ${armIds}`)}</metadata><style>${embeddedFontCss()}</style><rect width="1200" height="630" fill="#f7f4ed"/><line x1="72" x2="1128" y1="72" y2="72" stroke="#14120e" stroke-width="2"/><rect x="78" y="93" width="18" height="18" transform="rotate(45 87 102)" fill="#c7402a"/><text x="118" y="112" fill="#14120e" font-family="Newsreader,serif" font-size="32" font-weight="650">Colophon</text><text data-field="neutral-status" x="78" y="180" fill="#c7402a" font-family="Public Sans,sans-serif" font-size="20" font-weight="700" letter-spacing="1">${escapeMarkup(outcomeLabel(input.matrix.completeness.runOutcome))} · NO COMPARATIVE WINNER STATED</text><text x="78" y="290" fill="#14120e" font-family="Newsreader,serif" font-size="76" font-weight="500">Benchmark report</text><text data-field="adverse-status" x="78" y="350" fill="#c7402a" font-family="Public Sans,sans-serif" font-size="22">${escapeMarkup(visualStatus)}</text><text data-field="config-summary" x="78" y="405" fill="#14120e" font-family="Public Sans,sans-serif" font-size="23">${escapeMarkup(visualConfig)}</text><text x="78" y="452" fill="#14120e" font-family="Public Sans,sans-serif" font-size="22">${escapeMarkup(scope)}</text><line x1="78" x2="1122" y1="492" y2="492" stroke="#cfc8bb"/><a href="index.html#verification"><text x="78" y="540" fill="#27406b" font-family="IBM Plex Mono,monospace" font-size="16">Report ${digest} · index.html#limitations · index.html#verification</text></a><text x="78" y="580" fill="#6b675f" font-family="Public Sans,sans-serif" font-size="16">${escapeMarkup(PRODUCT_BRANDING.attribution)}</text>${pairedRow}</svg>\n`;
|
|
615
|
+
}
|
|
616
|
+
function markdownArmTable(facts) {
|
|
617
|
+
return [
|
|
618
|
+
"| Arm | n | Pass rate | Wilson low | Wilson high |",
|
|
619
|
+
"|---|---:|---:|---:|---:|",
|
|
620
|
+
...facts.arms.map((arm) => `| ${escapeMarkdown(arm.armId)} | ${arm.n} | ${escapeMarkdown(arm.passRate)} | ${escapeMarkdown(arm.low)} | ${escapeMarkdown(arm.high)} |`),
|
|
621
|
+
].join("\n");
|
|
622
|
+
}
|
|
623
|
+
/** Markdown counterpart to `comparisonFactsHtml` (P4b Task 6): pairs, delta, and either the
|
|
624
|
+
* interval bounds or every withheld reason, in full -- no truncation budget on this surface. */
|
|
625
|
+
function comparisonFactsMarkdown(facts) {
|
|
626
|
+
const delta = facts.delta === null ? "—" : escapeMarkdown(facts.delta);
|
|
627
|
+
const intervalLines = facts.interval === null
|
|
628
|
+
? ["- Interval: withheld", ...facts.reasons.map((reason) => ` - ${escapeMarkdown(reason)}`)]
|
|
629
|
+
: [
|
|
630
|
+
`- Interval low: ${escapeMarkdown(facts.interval.low)}`,
|
|
631
|
+
`- Interval high: ${escapeMarkdown(facts.interval.high)}`,
|
|
632
|
+
`- Alpha: ${escapeMarkdown(facts.alpha)}`,
|
|
633
|
+
];
|
|
634
|
+
return [
|
|
635
|
+
escapeMarkdown(pairedEstimateLine(facts)),
|
|
636
|
+
"",
|
|
637
|
+
`- Paired task count: ${facts.pairs}`,
|
|
638
|
+
`- Candidate minus baseline estimate: ${delta}`,
|
|
639
|
+
...intervalLines,
|
|
640
|
+
].join("\n");
|
|
641
|
+
}
|
|
642
|
+
function binaryFactsMarkdown(facts) {
|
|
643
|
+
const caption = stratumCaption(facts.qualification.configuration);
|
|
644
|
+
return [
|
|
645
|
+
"Qualification facts are presented per instrument without comparative conclusions.",
|
|
646
|
+
"",
|
|
647
|
+
"### Registered configuration",
|
|
648
|
+
"",
|
|
649
|
+
` ${escapeMarkdownCode(canonicalText(facts.qualification.configuration))}`,
|
|
650
|
+
"",
|
|
651
|
+
...Object.entries(facts.qualification.arms).flatMap(([armId, arm]) => [
|
|
652
|
+
`### ${escapeMarkdown(armId)}`,
|
|
653
|
+
"",
|
|
654
|
+
`- Instrument: ${escapeMarkdown(String(arm["instrumentSha256"]))}`,
|
|
655
|
+
`- Item, call, and confusion denominators: ${escapeMarkdown(canonicalText({ item: arm["item"], call: arm["call"], confusion: arm["confusion"] }))}`,
|
|
656
|
+
`- Agreement: ${escapeMarkdown(canonicalText(arm["agreement"]))}`,
|
|
657
|
+
`- False accept: ${escapeMarkdown(canonicalText(arm["falseAccept"]))}`,
|
|
658
|
+
`- False reject: ${escapeMarkdown(canonicalText(arm["falseReject"]))}`,
|
|
659
|
+
`- Instability: ${escapeMarkdown(canonicalText(arm["instability"]))}`,
|
|
660
|
+
`- Parser invalid: ${escapeMarkdown(canonicalText(arm["parserInvalid"]))}`,
|
|
661
|
+
`- Every candidate-class bucket: ${escapeMarkdown(canonicalText(arm["byCandidateClass"]))}`,
|
|
662
|
+
`- ${escapeMarkdown(caption)}: ${escapeMarkdown(canonicalText(arm["byStratum"]))}`,
|
|
663
|
+
"",
|
|
664
|
+
]),
|
|
665
|
+
"### Per-item decisions, instability, and exclusions",
|
|
666
|
+
"",
|
|
667
|
+
` ${escapeMarkdownCode(canonicalText({ itemDecisions: facts.qualification.itemDecisions, excluded: facts.qualification.excluded }))}`,
|
|
668
|
+
].join("\n");
|
|
669
|
+
}
|
|
670
|
+
function judgeIntervalMarkdownLines(interval, reasons) {
|
|
671
|
+
return interval === null
|
|
672
|
+
? ["- Interval: withheld", ...reasons.map((reason) => ` - ${escapeMarkdown(reason)}`)]
|
|
673
|
+
: [
|
|
674
|
+
`- Interval lower: ${escapeMarkdown(interval.lower)}`,
|
|
675
|
+
`- Interval upper: ${escapeMarkdown(interval.upper)}`,
|
|
676
|
+
`- Alpha: ${escapeMarkdown(interval.alpha)}`,
|
|
677
|
+
];
|
|
678
|
+
}
|
|
679
|
+
function pairwiseDisagreementFactsMarkdown(facts) {
|
|
680
|
+
if (facts.pairs.length === 0)
|
|
681
|
+
return "No arm pairs were computed.";
|
|
682
|
+
return [
|
|
683
|
+
"| Arm pair | n | Disagreements | Rate | Interval |",
|
|
684
|
+
"| --- | --- | --- | --- | --- |",
|
|
685
|
+
...facts.pairs.map((pair) => {
|
|
686
|
+
const interval = pair.interval === null
|
|
687
|
+
? "withheld"
|
|
688
|
+
: `${escapeMarkdown(pair.interval.lower)} – ${escapeMarkdown(pair.interval.upper)}`;
|
|
689
|
+
return `| ${escapeMarkdown(pair.armA)} vs ${escapeMarkdown(pair.armB)} | ${pair.n} | ${pair.disagreements} | ${pair.rate === null ? "—" : escapeMarkdown(pair.rate)} | ${interval} |`;
|
|
690
|
+
}),
|
|
691
|
+
].join("\n");
|
|
692
|
+
}
|
|
693
|
+
function pairedMajorityDeltaFactsMarkdown(facts) {
|
|
694
|
+
return [
|
|
695
|
+
`- Candidate (evidence-declaring arm): ${escapeMarkdown(facts.candidate)}`,
|
|
696
|
+
`- Baseline (evidence-free twin): ${escapeMarkdown(facts.baseline)}`,
|
|
697
|
+
`- Paired task count: ${facts.n}`,
|
|
698
|
+
`- Candidate minus baseline estimate: ${facts.delta === null ? "—" : escapeMarkdown(facts.delta)}`,
|
|
699
|
+
...judgeIntervalMarkdownLines(facts.interval, facts.reasons),
|
|
700
|
+
].join("\n");
|
|
701
|
+
}
|
|
702
|
+
/** Dispatches the arm/comparison facts block on `facts.kind` (P4b Task 6), markdown counterpart
|
|
703
|
+
* to `armResultsHtml`. The wilson branch is byte-identical to before this dispatch existed. */
|
|
704
|
+
function armResultsMarkdown(facts) {
|
|
705
|
+
if (facts.kind === "wilson")
|
|
706
|
+
return markdownArmTable(facts);
|
|
707
|
+
if (facts.kind === "comparison")
|
|
708
|
+
return comparisonFactsMarkdown(facts);
|
|
709
|
+
if (facts.kind === "pairwise-disagreement")
|
|
710
|
+
return pairwiseDisagreementFactsMarkdown(facts);
|
|
711
|
+
if (facts.kind === "paired-majority-delta")
|
|
712
|
+
return pairedMajorityDeltaFactsMarkdown(facts);
|
|
713
|
+
return binaryFactsMarkdown(facts);
|
|
714
|
+
}
|
|
715
|
+
function buildReadme(input, reportFacts, claimFacts) {
|
|
716
|
+
const adverse = adverseFacts(input, reportFacts);
|
|
717
|
+
const arms = input.claim.scope.arms.map((arm) => `- **${escapeMarkdown(arm.armId)}** — pinning: ${escapeMarkdown(canonicalText(arm.pinning))}`).join("\n");
|
|
718
|
+
const reportLimitations = (input.report.limitations ?? []).map((value) => `- ${escapeMarkdown(value)}`).join("\n") || "- None recorded in the sealed Report.";
|
|
719
|
+
const claimLimitations = input.claim.limitations.map((value) => `- ${escapeMarkdown(value)}`).join("\n") || "- None recorded in the stored Claim.";
|
|
720
|
+
const topLevelFiles = [
|
|
721
|
+
["benchmark.json", "Benchmark record"], ["run.json", "Run record"],
|
|
722
|
+
["matrix.json", "Matrix record"], ["report.json", "Report payload"],
|
|
723
|
+
["report-envelope.json", "Report signature envelope"], ["claim-package.json", "Claim package"],
|
|
724
|
+
...(reportFacts.kind === "binary" ? [["qualification.json", "Binary qualification graph"]] : []),
|
|
725
|
+
["static-bundle.json", "Static-bundle projection"], ["evidence.json", "Evidence catalog"],
|
|
726
|
+
["verdicts.json", "Verdict catalog"], ["verification/assembly.jsonl", "Verification assembly"],
|
|
727
|
+
["trust/public-keys.json", "Public trust material"],
|
|
728
|
+
];
|
|
729
|
+
const topLevelLinks = topLevelFiles.map(([path, label]) => `- [${label} (\`${path}\`)](${path})`).join("\n");
|
|
730
|
+
const casLinks = recordPaths(input).map((path) => `- [CAS record \`${path}\`](${path})`).join("\n");
|
|
731
|
+
const documentStatus = reportFacts.kind === "binary"
|
|
732
|
+
? `${qualificationOutcomeLabel(input.matrix.completeness.runOutcome)}. Verified binary-instrument qualification.`
|
|
733
|
+
: `${outcomeLabel(input.matrix.completeness.runOutcome)}. No comparative winner is stated.`;
|
|
734
|
+
return `# Colophon report
|
|
735
|
+
|
|
736
|
+
**${documentStatus}**
|
|
737
|
+
|
|
738
|
+
Scope: ${input.claim.scope.taskCount} tasks · ${input.claim.scope.arms.length} arms · ${input.claim.scope.replicates} replicates · ${escapeMarkdown(input.claim.scope.venue)}.
|
|
739
|
+
|
|
740
|
+
Report SHA-256: \`${input.reportSha256}\`
|
|
741
|
+
|
|
742
|
+
Matrix SHA-256: \`${input.matrixSha256}\`
|
|
743
|
+
|
|
744
|
+
Read the [full report](index.html), [limitations](index.html#limitations), and [portable verification instructions](index.html#verification).${input.comparison === undefined ? "" : `\n\n${comparisonSectionMarkdown(input.comparison).trimEnd()}`}
|
|
745
|
+
|
|
746
|
+
## Prominent adverse facts
|
|
747
|
+
|
|
748
|
+
${adverse.map((value) => `- ${escapeMarkdown(value)}`).join("\n")}
|
|
749
|
+
|
|
750
|
+
## Configurations
|
|
751
|
+
|
|
752
|
+
${arms}${reportFacts.kind === "binary" ? `\n\n${binaryAdmissionMarkdown(input).trimEnd()}` : ""}
|
|
753
|
+
|
|
754
|
+
## Sealed Matrix accounting
|
|
755
|
+
|
|
756
|
+
Source: authenticated [\`matrix.json\`](matrix.json). These stored values are not reconciled with another source.
|
|
757
|
+
|
|
758
|
+
${escapeMarkdownCode(canonicalText({ completeness: input.matrix.completeness, attrition: input.matrix.attrition }))}
|
|
759
|
+
|
|
760
|
+
## Sealed Report facts
|
|
761
|
+
|
|
762
|
+
Source: authenticated [\`report.json\`](report.json). These stored values are not reconciled with another source.
|
|
763
|
+
|
|
764
|
+
### ${factsHeading(reportFacts, "report")}
|
|
765
|
+
|
|
766
|
+
${armResultsMarkdown(reportFacts)}
|
|
767
|
+
|
|
768
|
+
### Report method and preregistration
|
|
769
|
+
|
|
770
|
+
- Report method: ${escapeMarkdown(input.report.method.id)} @ ${escapeMarkdown(input.report.method.version)}
|
|
771
|
+
- Report preregistered: ${input.report.preregistered === true ? "yes" : "no"}
|
|
772
|
+
- Report parameters: ${escapeMarkdown(canonicalText(input.report.method.parameters))}
|
|
773
|
+
|
|
774
|
+
### Report conflicts
|
|
775
|
+
|
|
776
|
+
${escapeMarkdownCode(canonicalText(reportFacts.conflicted))}
|
|
777
|
+
|
|
778
|
+
### Report disclosures
|
|
779
|
+
|
|
780
|
+
${escapeMarkdownCode(canonicalText(input.report.disclosures))}
|
|
781
|
+
|
|
782
|
+
### Report limitations
|
|
783
|
+
|
|
784
|
+
${reportLimitations}
|
|
785
|
+
|
|
786
|
+
## Stored Claim facts
|
|
787
|
+
|
|
788
|
+
Source: authenticated [\`claim-package.json\`](claim-package.json). These stored values are not reconciled with another source.
|
|
789
|
+
|
|
790
|
+
### ${factsHeading(claimFacts, "claim")}
|
|
791
|
+
|
|
792
|
+
${armResultsMarkdown(claimFacts)}
|
|
793
|
+
|
|
794
|
+
### Claim method and preregistration
|
|
795
|
+
|
|
796
|
+
- Claim method: ${escapeMarkdown(input.claim.method.id)} @ ${escapeMarkdown(input.claim.method.version)}
|
|
797
|
+
- Claim preregistered: ${input.claim.method.preregistered ? "yes" : "no"}
|
|
798
|
+
- Claim parameters: ${escapeMarkdown(canonicalText(input.claim.method.parameters))}
|
|
799
|
+
|
|
800
|
+
### Claim completeness
|
|
801
|
+
|
|
802
|
+
${escapeMarkdownCode(canonicalText(input.claim.completeness))}
|
|
803
|
+
|
|
804
|
+
### Claim attrition
|
|
805
|
+
|
|
806
|
+
${escapeMarkdownCode(canonicalText(input.claim.attrition))}
|
|
807
|
+
|
|
808
|
+
### Claim conflicts
|
|
809
|
+
|
|
810
|
+
${escapeMarkdownCode(canonicalText(input.claim.conflicted))}
|
|
811
|
+
|
|
812
|
+
### Claim disclosures
|
|
813
|
+
|
|
814
|
+
${escapeMarkdownCode(canonicalText(input.claim.disclosures))}
|
|
815
|
+
|
|
816
|
+
### Claim limitations
|
|
817
|
+
|
|
818
|
+
${claimLimitations}
|
|
819
|
+
|
|
820
|
+
### Claim assurance, rehearsal, and self-run trust boundary
|
|
821
|
+
|
|
822
|
+
- Assurance: ${escapeMarkdown(input.claim.assurance.preset)} — ${escapeMarkdown(canonicalText(input.claim.assurance.resolved))}
|
|
823
|
+
- Boundary: ${escapeMarkdown(input.claim.assurance.disclosure)}
|
|
824
|
+
- Rehearsal: ${escapeMarkdown(canonicalText(input.claim.rehearsal ?? null))}
|
|
825
|
+
- Venue honesty: ${escapeMarkdown(canonicalText(input.claim.venueHonesty))}
|
|
826
|
+
|
|
827
|
+
## Verification assembly dissent
|
|
828
|
+
|
|
829
|
+
Source: authenticated [verification assembly](verification/assembly.jsonl).
|
|
830
|
+
|
|
831
|
+
${escapeMarkdownCode(canonicalText({ dissentCellKeys: input.dissentCellKeys }))}
|
|
832
|
+
|
|
833
|
+
## Raw records and catalogs
|
|
834
|
+
|
|
835
|
+
### Top-level records and catalogs
|
|
836
|
+
|
|
837
|
+
${topLevelLinks}
|
|
838
|
+
|
|
839
|
+
### Every manifest-listed content-addressed record
|
|
840
|
+
|
|
841
|
+
${casLinks}
|
|
842
|
+
|
|
843
|
+
## Portable verification
|
|
844
|
+
|
|
845
|
+
Copy the complete bundle directory. Reproduce publication with the exact verifier:
|
|
846
|
+
|
|
847
|
+
${input.claim.verification.command}
|
|
848
|
+
|
|
849
|
+
Use the compatible major line to receive fixes that preserve this bundle-format contract:
|
|
850
|
+
|
|
851
|
+
${input.claim.verification.compatibleCommand}
|
|
852
|
+
|
|
853
|
+
The verifier authenticates the manifest, records, evidence graph, Matrix, Report, claim consistency, and every presentation byte using only bundle-carried public trust material. See [index.html#verification](index.html#verification). ${PRODUCT_BRANDING.attribution}
|
|
854
|
+
|
|
855
|
+
Typography is embedded for offline use from the SIL Open Font License distributions of Newsreader, Public Sans, and IBM Plex Mono. The complete notices follow so the redistributed font software travels with its license.
|
|
856
|
+
|
|
857
|
+
${FONT_LICENSES.map(([name, path]) => `## ${name} font license\n\n\`\`\`text\n${readFileSync(require.resolve(path), "utf8").trim()}\n\`\`\``).join("\n\n")}
|
|
858
|
+
`;
|
|
859
|
+
}
|
|
860
|
+
function buildShareText(input, reportFacts) {
|
|
861
|
+
if (reportFacts.kind === "binary") {
|
|
862
|
+
return `Colophon · verified qualification. ${plainText(scopeLine(input))}. Report ${input.reportSha256}. Full evidence: index.html; verify: index.html#verification with ${plainText(input.claim.verification.command)}.\n`;
|
|
863
|
+
}
|
|
864
|
+
// Paired branch only (P4b Task 6): empty string for wilson keeps this sentence byte-identical
|
|
865
|
+
// to before this dispatch existed.
|
|
866
|
+
const pairedFragment = pairedCompactFragment(reportFacts);
|
|
867
|
+
const pairedClause = pairedFragment === "" ? "" : ` ${plainText(pairedFragment)}.`;
|
|
868
|
+
return `Colophon · ${outcomeLabel(input.matrix.completeness.runOutcome)}; no comparative winner stated. ${input.claim.scope.taskCount} tasks · ${input.claim.scope.arms.length} arms · ${input.claim.scope.replicates} replicates · ${plainText(input.claim.scope.venue)}. ${plainText(compactStatus(input, reportFacts))}. Report ${input.reportSha256}.${pairedClause} Full report: index.html; limitations: index.html#limitations; verify: index.html#verification with ${plainText(input.claim.verification.command)}. ${PRODUCT_BRANDING.attribution}\n`;
|
|
869
|
+
}
|
|
870
|
+
/** Fixed, deterministic public-bundle/2 presentation bytes. The builder only projects already
|
|
871
|
+
* verified stored facts; it never computes a statistic, selects a winner, or reconciles records. */
|
|
872
|
+
export function buildPublicAssets(input) {
|
|
873
|
+
const reportFacts = methodProjection(input.report.results, input.report.method, "sealed Report");
|
|
874
|
+
const claimFacts = methodProjection(input.claim.results, input.claim.method, "stored claim package");
|
|
875
|
+
if ((reportFacts.kind === "binary") !== (input.binaryQualification !== undefined)) {
|
|
876
|
+
throw new Error("binary public assets require exactly one producer-verified admission/instrument projection");
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
"index.html": encoder.encode(buildIndex(input, reportFacts, claimFacts)),
|
|
880
|
+
"badge.svg": encoder.encode(buildBadge(input, reportFacts)),
|
|
881
|
+
"social-card.svg": encoder.encode(buildSocialCard(input, reportFacts)),
|
|
882
|
+
"README.md": encoder.encode(buildReadme(input, reportFacts, claimFacts)),
|
|
883
|
+
"share.txt": encoder.encode(buildShareText(input, reportFacts)),
|
|
884
|
+
};
|
|
885
|
+
}
|