assertledger 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.fr.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/build-info.json +1 -1
  4. package/dist/core/index.d.ts.map +1 -1
  5. package/dist/core/index.js +11 -4
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/proof-planner/carry-over.d.ts +21 -0
  8. package/dist/proof-planner/carry-over.d.ts.map +1 -0
  9. package/dist/proof-planner/carry-over.js +212 -0
  10. package/dist/proof-planner/carry-over.js.map +1 -0
  11. package/dist/proof-planner/index.d.ts +11 -0
  12. package/dist/proof-planner/index.d.ts.map +1 -0
  13. package/dist/proof-planner/index.js +11 -0
  14. package/dist/proof-planner/index.js.map +1 -0
  15. package/dist/proof-planner/model.d.ts +272 -0
  16. package/dist/proof-planner/model.d.ts.map +1 -0
  17. package/dist/proof-planner/model.js +215 -0
  18. package/dist/proof-planner/model.js.map +1 -0
  19. package/dist/proof-planner/plan.d.ts +140 -0
  20. package/dist/proof-planner/plan.d.ts.map +1 -0
  21. package/dist/proof-planner/plan.js +1124 -0
  22. package/dist/proof-planner/plan.js.map +1 -0
  23. package/dist/proof-planner/policy.d.ts +100 -0
  24. package/dist/proof-planner/policy.d.ts.map +1 -0
  25. package/dist/proof-planner/policy.js +407 -0
  26. package/dist/proof-planner/policy.js.map +1 -0
  27. package/dist/proof-planner/render.d.ts +4 -0
  28. package/dist/proof-planner/render.d.ts.map +1 -0
  29. package/dist/proof-planner/render.js +73 -0
  30. package/dist/proof-planner/render.js.map +1 -0
  31. package/docs/architecture.md +7 -0
  32. package/docs/migration-timeout-discovery-inconclusive.md +126 -0
  33. package/docs/proof-model.md +20 -2
  34. package/docs/proof-planner.md +368 -0
  35. package/docs/reference.md +3 -1
  36. package/package.json +4 -4
@@ -0,0 +1,1124 @@
1
+ import * as z from "zod";
2
+ import { sha256Canonical } from "../core/index.js";
3
+ import { AssuranceClaimSchema, BOUNDARIES, ChangeImpactSchema, compareOrdinal, EVIDENCE_SUBJECTS, INFRASTRUCTURE_SIGNALS, levelAt, levelIndex, PRODUCT_SIGNALS, PROOF_PLANNER_SCHEMA_VERSION, ProofPlannerError, ProofSignalSchema, sortedUnique, subjectOfRole, } from "./model.js";
4
+ import { assurancePolicyDigest, DEFAULT_ASSURANCE_POLICY, evidenceKindSemanticDigest, parseAssurancePolicy, } from "./policy.js";
5
+ const PRODUCT_RUNTIME_ROLES = new Set([
6
+ "product",
7
+ "execution-context",
8
+ "evaluation",
9
+ ]);
10
+ const EVALUATION_BOUNDARIES = new Set(["benchmark", "holdout"]);
11
+ /** Boundaries that keep their weight when declared on a test or a proof-infrastructure surface. */
12
+ const SENSITIVE_BOUNDARIES = new Set(["security", "admission", "decision"]);
13
+ const PRE_EXISTING_CAPABLE = new Set([
14
+ "UNEXPECTED_BEHAVIOR",
15
+ "CORPUS_DIVERGENCE",
16
+ "REGRESSION",
17
+ ]);
18
+ const SIGNAL_FACTS = {
19
+ UNEXPECTED_BEHAVIOR: "observed:unexpected-behavior",
20
+ UNPLANNED_IMPACT: "observed:unplanned-impact",
21
+ UNKNOWN_DEPENDENCY: "observed:unknown-dependency",
22
+ LIVE_RUNTIME_TOUCHED: "observed:live-runtime",
23
+ CORPUS_DIVERGENCE: "observed:corpus-divergence",
24
+ WITNESS_NOT_CAUSAL: "observed:witness-not-causal",
25
+ REGRESSION: "observed:regression",
26
+ };
27
+ const HYPOTHETICAL_PREFIX = "hypothetical.";
28
+ /**
29
+ * Decide which evidence is proportionate for a change. Pure and deterministic: no clock, no
30
+ * randomness, no I/O. The plan states requirements; it never claims that evidence exists.
31
+ */
32
+ export function planAssurance(input) {
33
+ const normalized = normalizeInput(input);
34
+ const core = planCore(normalized);
35
+ const catalog = normalized.policy.evidence;
36
+ const required = materialize(core.required, catalog);
37
+ const recommended = materialize(core.recommended, catalog);
38
+ const partition = partitionCatalog(normalized, core);
39
+ const escalations = computeEscalations(normalized, core);
40
+ const residualUncertainty = computeUncertainty(normalized, core, partition.notRequired);
41
+ const plan = {
42
+ schemaVersion: PROOF_PLANNER_SCHEMA_VERSION,
43
+ status: core.status,
44
+ level: core.level,
45
+ levelBySubject: EVIDENCE_SUBJECTS.filter((subject) => core.levelBySubject.has(subject)).map((subject) => ({ subject, level: levelAt(core.levelBySubject.get(subject) ?? 0) })),
46
+ impactBound: core.bound,
47
+ subject: {
48
+ revision: normalized.impact.revision.id,
49
+ baseline: normalized.impact.revision.baseline,
50
+ runtimeTreeDigest: normalized.impact.revision.runtimeTreeDigest ?? null,
51
+ surfaces: normalized.impact.surfaces.map((surface) => ({
52
+ id: surface.id,
53
+ role: surface.role,
54
+ reach: surface.reach,
55
+ contentDigest: surface.contentDigest ?? null,
56
+ exercises: surface.exercises ?? null,
57
+ })),
58
+ },
59
+ requiredEvidence: required,
60
+ recommendedEvidence: [
61
+ ...recommended,
62
+ ...materialize(partition.worstCaseRecommended, catalog),
63
+ ].sort((left, right) => compareOrdinal(left.kind, right.kind)),
64
+ explicitlyNotRequired: partition.notRequired,
65
+ undeterminedEvidence: partition.undetermined,
66
+ escalations,
67
+ rationale: core.rationale,
68
+ facts: core.facts,
69
+ signals: core.signals,
70
+ unaffectedClaims: core.unaffectedClaims,
71
+ residualUncertainty,
72
+ policy: {
73
+ id: normalized.policy.id,
74
+ version: normalized.policy.version,
75
+ digest: normalized.policyDigest,
76
+ },
77
+ inputDigest: sha256Canonical({
78
+ impact: normalized.impact,
79
+ claims: normalized.claims,
80
+ signals: normalized.signals,
81
+ policyDigest: normalized.policyDigest,
82
+ }),
83
+ };
84
+ return { ...plan, planDigest: sha256Canonical(plan) };
85
+ }
86
+ function normalizeInput(input) {
87
+ const impactResult = ChangeImpactSchema.safeParse(input.impact);
88
+ if (!impactResult.success) {
89
+ throw new ProofPlannerError("PROOF_PLANNER_INPUT_INVALID", z.prettifyError(impactResult.error));
90
+ }
91
+ const claimsResult = z
92
+ .array(AssuranceClaimSchema)
93
+ .max(1_024)
94
+ .safeParse(input.claims ?? []);
95
+ if (!claimsResult.success) {
96
+ throw new ProofPlannerError("PROOF_PLANNER_INPUT_INVALID", z.prettifyError(claimsResult.error));
97
+ }
98
+ const signalsResult = z
99
+ .array(ProofSignalSchema)
100
+ .max(256)
101
+ .safeParse(input.signals ?? []);
102
+ if (!signalsResult.success) {
103
+ throw new ProofPlannerError("PROOF_PLANNER_INPUT_INVALID", z.prettifyError(signalsResult.error));
104
+ }
105
+ const policy = parseAssurancePolicy(input.policy ?? DEFAULT_ASSURANCE_POLICY);
106
+ const raw = impactResult.data;
107
+ const impact = {
108
+ schemaVersion: raw.schemaVersion,
109
+ revision: {
110
+ id: raw.revision.id,
111
+ baseline: raw.revision.baseline,
112
+ ...(raw.revision.runtimeTreeDigest === undefined
113
+ ? {}
114
+ : { runtimeTreeDigest: raw.revision.runtimeTreeDigest }),
115
+ },
116
+ surfaces: raw.surfaces
117
+ .map((surface) => ({
118
+ id: surface.id,
119
+ role: surface.role,
120
+ reach: surface.reach,
121
+ runtime: surface.runtime,
122
+ boundaries: sortedUnique(surface.boundaries),
123
+ behaviorChange: surface.behaviorChange,
124
+ coverage: surface.coverage,
125
+ environmentSensitive: surface.environmentSensitive,
126
+ ...(surface.contentDigest === undefined ? {} : { contentDigest: surface.contentDigest }),
127
+ ...(surface.exercises === undefined ? {} : { exercises: sortedUnique(surface.exercises) }),
128
+ }))
129
+ .sort((left, right) => compareOrdinal(left.id, right.id)),
130
+ analysis: {
131
+ method: raw.analysis.method,
132
+ completeness: raw.analysis.completeness,
133
+ uncertainty: raw.analysis.uncertainty,
134
+ unknowns: raw.analysis.unknowns
135
+ .map((unknown) => ({
136
+ description: unknown.description,
137
+ surfaces: sortedUnique(unknown.surfaces),
138
+ }))
139
+ .sort((left, right) => compareOrdinal(left.description, right.description) ||
140
+ compareOrdinal(left.surfaces.join("\u0000"), right.surfaces.join("\u0000"))),
141
+ omittedSurfaceCount: raw.analysis.omittedSurfaceCount,
142
+ },
143
+ reversibility: raw.reversibility,
144
+ causalWitness: raw.causalWitness,
145
+ };
146
+ assertUniqueIds(impact.surfaces, "surface");
147
+ const claims = claimsResult.data
148
+ .map((claim) => ({ ...claim, surfaces: sortedUnique(claim.surfaces) }))
149
+ .sort((left, right) => compareOrdinal(left.id, right.id));
150
+ assertUniqueIds(claims, "claim");
151
+ const signals = signalsResult.data
152
+ .map((signal) => ({
153
+ ...signal,
154
+ surfaces: sortedUnique(signal.surfaces),
155
+ attribution: sortedUnique(signal.attribution),
156
+ }))
157
+ .sort((left, right) => compareOrdinal(left.id, right.id));
158
+ assertUniqueIds(signals, "signal");
159
+ for (const [label, items] of [
160
+ ["surface", impact.surfaces],
161
+ ["claim", claims],
162
+ ["signal", signals],
163
+ ]) {
164
+ for (const item of items) {
165
+ if (item.id.startsWith(HYPOTHETICAL_PREFIX)) {
166
+ throw new ProofPlannerError("PROOF_PLANNER_INPUT_INCONSISTENT", `${label} id prefix ${HYPOTHETICAL_PREFIX} is reserved: ${item.id}`);
167
+ }
168
+ }
169
+ }
170
+ return { impact, claims, signals, policy, policyDigest: assurancePolicyDigest(policy) };
171
+ }
172
+ function assertUniqueIds(items, label) {
173
+ const seen = new Set();
174
+ for (const item of items) {
175
+ if (seen.has(item.id)) {
176
+ throw new ProofPlannerError("PROOF_PLANNER_INPUT_INCONSISTENT", `duplicate ${label} ${item.id}`);
177
+ }
178
+ seen.add(item.id);
179
+ }
180
+ }
181
+ function planCore(input) {
182
+ const { impact, policy } = input;
183
+ const facts = new FactSet();
184
+ const rationale = [];
185
+ const productRuntimeTouched = impact.surfaces.some((surface) => PRODUCT_RUNTIME_ROLES.has(surface.role));
186
+ deriveSurfaceFacts(impact, facts);
187
+ // 1. Product signals first: they can reopen the impact bound. A signal observed on another
188
+ // revision still counts while it is unresolved and the product it observed is byte-identical.
189
+ const classifications = [];
190
+ const infrastructureSignals = [];
191
+ const record = ({ classification, facts: observed, source }) => {
192
+ const carried = source.revision !== impact.revision.id;
193
+ if (carried && !isUnresolvedSignal(classification)) {
194
+ classifications.push(otherRevision(source, `observed on revision ${source.revision} and resolved there`));
195
+ return;
196
+ }
197
+ for (const fact of observed) {
198
+ facts.add(fact.id, fact.subject, fact.surfaces, fact.refs, fact.detail);
199
+ }
200
+ classifications.push(carried
201
+ ? {
202
+ ...classification,
203
+ revision: source.revision,
204
+ reason: `carried from revision ${source.revision}, same baseline and runtime tree: ${classification.reason}`,
205
+ }
206
+ : { ...classification, revision: source.revision });
207
+ };
208
+ for (const signal of input.signals) {
209
+ if (signal.revision !== impact.revision.id && !observedSameProduct(signal, impact)) {
210
+ classifications.push(otherRevision(signal, `observed on revision ${signal.revision}, not on the planned revision`));
211
+ continue;
212
+ }
213
+ const name = signal.signal;
214
+ if (!isProductSignal(name)) {
215
+ infrastructureSignals.push(signal);
216
+ continue;
217
+ }
218
+ record(classifyProductSignal(signal, name, impact));
219
+ }
220
+ // 2. Impact bound, after product signals.
221
+ const boundResult = computeBound(input, productRuntimeTouched, facts);
222
+ rationale.push({
223
+ step: "impact",
224
+ subject: null,
225
+ fact: null,
226
+ level: null,
227
+ detail: `${boundResult.bound}${boundResult.reasons.length > 0 ? `: ${boundResult.reasons.join("; ")}` : ""}`,
228
+ });
229
+ // 3. Claims, which need the bound to decide whether an untouched claim is unaffected.
230
+ const claimOutcome = deriveClaimFacts(input, boundResult.bound, facts);
231
+ for (const note of [...claimOutcome.unaffected, ...claimOutcome.throughProof]) {
232
+ rationale.push({
233
+ step: "claim",
234
+ subject: null,
235
+ fact: null,
236
+ level: null,
237
+ detail: `${note.id}: ${note.reason}`,
238
+ });
239
+ }
240
+ // 4. Infrastructure signals last, with the final bound.
241
+ for (const signal of infrastructureSignals) {
242
+ record(classifyInfrastructureSignal(signal, input, boundResult.bound));
243
+ }
244
+ classifications.sort((left, right) => compareOrdinal(left.id, right.id));
245
+ for (const classification of classifications) {
246
+ rationale.push({
247
+ step: "signal",
248
+ subject: null,
249
+ fact: null,
250
+ level: null,
251
+ detail: `${classification.id} (${classification.signal}) -> ${classification.classification}: ${classification.reason}`,
252
+ });
253
+ }
254
+ // 5. Levels per subject.
255
+ const floors = new Map();
256
+ for (const fact of facts.list()) {
257
+ for (const floor of policy.floors.filter((entry) => entry.fact === fact.id)) {
258
+ const index = levelIndex(floor.level);
259
+ floors.set(fact.subject, Math.max(floors.get(fact.subject) ?? 0, index));
260
+ rationale.push({
261
+ step: "floor",
262
+ subject: fact.subject,
263
+ fact: fact.id,
264
+ level: floor.level,
265
+ detail: fact.detail,
266
+ });
267
+ }
268
+ if (!floors.has(fact.subject))
269
+ floors.set(fact.subject, 0);
270
+ }
271
+ const raised = new Set();
272
+ for (const fact of facts.list()) {
273
+ if (policy.raises.facts.includes(fact.id)) {
274
+ raised.add(fact.subject);
275
+ rationale.push({
276
+ step: "raise",
277
+ subject: fact.subject,
278
+ fact: fact.id,
279
+ level: null,
280
+ detail: `${fact.detail}; raises ${fact.subject} by one step, capped at ${policy.raises.cap}`,
281
+ });
282
+ }
283
+ }
284
+ const levelBySubject = new Map();
285
+ for (const subject of EVIDENCE_SUBJECTS) {
286
+ const floor = floors.get(subject);
287
+ if (floor === undefined)
288
+ continue;
289
+ const level = raised.has(subject)
290
+ ? Math.max(floor, Math.min(levelIndex(policy.raises.cap), floor + 1))
291
+ : floor;
292
+ levelBySubject.set(subject, level);
293
+ rationale.push({
294
+ step: "level",
295
+ subject,
296
+ fact: null,
297
+ level: levelAt(level),
298
+ detail: `${subject}: highest floor ${levelAt(floor)}${raised.has(subject) ? ", raised" : ""}`,
299
+ });
300
+ }
301
+ const overall = Math.max(0, ...levelBySubject.values());
302
+ // 6. Evidence from level baselines and fact triggers.
303
+ const required = new Map();
304
+ const recommended = new Map();
305
+ const catalog = new Map(policy.evidence.map((spec) => [spec.id, spec]));
306
+ // Evidence bound to the exact revision is revision-wide: it carries no surface scope.
307
+ const scoped = (kind, surfaces) => catalog.get(kind)?.binding === "exact-revision" ? [] : surfaces;
308
+ for (const baseline of policy.baselines) {
309
+ const spec = catalog.get(baseline.kind);
310
+ if (!spec)
311
+ continue;
312
+ const subjects = spec.subjects.filter((subject) => (levelBySubject.get(subject) ?? -1) >= levelIndex(baseline.level));
313
+ for (const subject of subjects) {
314
+ accumulate(required, baseline.kind, scoped(baseline.kind, subjectSurfaces(impact, facts, subject)), `baseline:${baseline.level}:${subject}`);
315
+ }
316
+ }
317
+ for (const fact of facts.list()) {
318
+ for (const entry of policy.triggers.filter((candidate) => candidate.fact === fact.id)) {
319
+ for (const kind of entry.require) {
320
+ accumulate(required, kind, scoped(kind, fact.surfaces), fact.id);
321
+ }
322
+ for (const kind of entry.recommend) {
323
+ accumulate(recommended, kind, scoped(kind, fact.surfaces), fact.id);
324
+ }
325
+ }
326
+ }
327
+ for (const kind of required.keys())
328
+ recommended.delete(kind);
329
+ // 7. Status.
330
+ const blocking = sortedUnique(facts.list().flatMap((fact) => (policy.status.block.includes(fact.id) ? [fact.id] : [])));
331
+ const holding = sortedUnique(facts.list().flatMap((fact) => (policy.status.hold.includes(fact.id) ? [fact.id] : [])));
332
+ const status = blocking.length > 0 ? "BLOCKED" : holding.length > 0 ? "HOLD" : "PROVE";
333
+ rationale.push({
334
+ step: "status",
335
+ subject: null,
336
+ fact: null,
337
+ level: null,
338
+ detail: status === "PROVE"
339
+ ? "no blocking or holding fact"
340
+ : `${status}: ${[...blocking, ...holding].join(", ")}`,
341
+ });
342
+ return {
343
+ status,
344
+ level: levelAt(overall),
345
+ levelBySubject,
346
+ bound: boundResult.bound,
347
+ boundReasons: boundResult.reasons,
348
+ facts: facts.list(),
349
+ factIds: new Set(facts.list().map((fact) => fact.id)),
350
+ signals: classifications,
351
+ unaffectedClaims: claimOutcome.unaffected,
352
+ claimsOutsideImpact: claimOutcome.outsideImpact,
353
+ required,
354
+ recommended,
355
+ rationale,
356
+ };
357
+ }
358
+ /** Facts keyed by id and subject: one observation can concern several subjects. */
359
+ class FactSet {
360
+ facts = new Map();
361
+ add(id, subject, surfaces, refs, detail) {
362
+ const key = `${id}|${subject}`;
363
+ const existing = this.facts.get(key);
364
+ if (existing) {
365
+ existing.surfaces = sortedUnique([...existing.surfaces, ...surfaces]);
366
+ existing.refs = sortedUnique([...existing.refs, ...refs]);
367
+ return;
368
+ }
369
+ this.facts.set(key, {
370
+ id,
371
+ subject,
372
+ surfaces: sortedUnique(surfaces),
373
+ refs: sortedUnique(refs),
374
+ detail,
375
+ });
376
+ }
377
+ has(id) {
378
+ return [...this.facts.values()].some((fact) => fact.id === id);
379
+ }
380
+ list() {
381
+ return [...this.facts.values()].sort((left, right) => compareOrdinal(left.id, right.id) || compareOrdinal(left.subject, right.subject));
382
+ }
383
+ }
384
+ function ids(surfaces) {
385
+ return surfaces.map((surface) => surface.id);
386
+ }
387
+ function describe(surfaces, what) {
388
+ return `${ids(surfaces).join(", ")}: ${what}`;
389
+ }
390
+ function deriveSurfaceFacts(impact, facts) {
391
+ const direct = impact.surfaces.filter((surface) => surface.reach === "direct");
392
+ const directOf = (...roles) => direct.filter((surface) => roles.includes(surface.role));
393
+ const add = (id, subject, surfaces, what) => {
394
+ if (surfaces.length > 0)
395
+ facts.add(id, subject, ids(surfaces), [], describe(surfaces, what));
396
+ };
397
+ const runtime = impact.surfaces.filter((surface) => PRODUCT_RUNTIME_ROLES.has(surface.role));
398
+ const directRuntime = runtime.filter((surface) => surface.reach === "direct");
399
+ const directRuntimeIds = new Set(ids(directRuntime));
400
+ add("documentation:changed", "documentation", directOf("documentation"), "documentation changed");
401
+ add("tests:changed", "proof-infrastructure", directOf("test").filter((surface) => !(surface.exercises?.some((id) => directRuntimeIds.has(id)) ?? false)), "tests changed that the product evidence does not run");
402
+ add("oracle:changed", "proof-infrastructure", directOf("test").filter((surface) => surface.behaviorChange !== "none"), "an existing test oracle, budget or selection changed");
403
+ add("proof-infrastructure:changed", "proof-infrastructure", directOf("proof-infrastructure"), "proof infrastructure changed");
404
+ add("execution-context:changed", "product", directOf("execution-context"), "execution context (dependencies, build or runtime configuration) changed");
405
+ add("evaluation:touched", "evaluation", directOf("evaluation"), "evaluation surface changed");
406
+ const product = directOf("product");
407
+ if (product.length > 0 && product.every((surface) => surface.behaviorChange === "none")) {
408
+ add("product:refactor", "product", product, "product changed without declared behavior change");
409
+ }
410
+ const behavioral = product.filter((surface) => surface.behaviorChange !== "none");
411
+ add("product:behavior-change", "product", behavioral, "product behavior may change");
412
+ add("product:behavior-suspected", "product", product.filter((surface) => surface.behaviorChange === "suspected"), "behavior may change without intent");
413
+ const fixes = product.filter((surface) => surface.behaviorChange === "fix");
414
+ if (impact.causalWitness === "infeasible") {
415
+ add("witness:infeasible", "product", fixes, "fix declared without a reproducible witness");
416
+ }
417
+ else {
418
+ add("product:fix", "product", fixes, "behavioral fix");
419
+ }
420
+ add("product:new-behavior", "product", product.filter((surface) => surface.behaviorChange === "feature"), "new behavior");
421
+ const liveCapable = impact.surfaces.filter((surface) => (surface.role === "product" || surface.role === "execution-context") &&
422
+ surface.runtime === "live");
423
+ add("runtime:live:touched", "product", liveCapable.filter((surface) => surface.reach === "direct"), "live runtime surface changed");
424
+ add("runtime:live:behavior", "product", liveCapable.filter((surface) => surface.behaviorChange !== "none"), "live runtime behavior may change");
425
+ for (const boundary of BOUNDARIES) {
426
+ const evaluationBoundary = EVALUATION_BOUNDARIES.has(boundary);
427
+ // Security, admission and decision boundaries keep their weight on a changed gate or oracle.
428
+ const withBoundary = impact.surfaces.filter((surface) => surface.boundaries.includes(boundary) &&
429
+ (PRODUCT_RUNTIME_ROLES.has(surface.role) ||
430
+ (SENSITIVE_BOUNDARIES.has(boundary) &&
431
+ (surface.role === "proof-infrastructure" ||
432
+ (surface.role === "test" && surface.behaviorChange !== "none")))));
433
+ // Evaluation boundaries call for evaluation evidence, whatever role carries them.
434
+ const bySubject = (id, surfaces, what) => {
435
+ for (const subject of EVIDENCE_SUBJECTS) {
436
+ add(id, subject, surfaces.filter((surface) => (evaluationBoundary ? "evaluation" : subjectOfRole(surface.role)) === subject), what);
437
+ }
438
+ };
439
+ bySubject(`boundary:${boundary}:touched`, withBoundary.filter((surface) => surface.reach === "direct"), `${boundary} boundary changed`);
440
+ bySubject(`boundary:${boundary}:behavior`, withBoundary.filter((surface) => surface.behaviorChange !== "none" && (surface.reach === "direct" || !evaluationBoundary)), `${boundary} behavior may change`);
441
+ bySubject(`boundary:${boundary}:transitive`, withBoundary.filter((surface) => surface.reach === "transitive" &&
442
+ (surface.behaviorChange === "none" || evaluationBoundary)), `${boundary} boundary reached transitively`);
443
+ }
444
+ add("coverage:missing", "product", product.filter((surface) => surface.coverage === "untested" ||
445
+ (surface.coverage === "unknown" && surface.behaviorChange !== "none")), "no test covers a changed product surface");
446
+ add("environment:sensitive", "product", product.filter((surface) => surface.environmentSensitive &&
447
+ surface.behaviorChange !== "none" &&
448
+ (surface.runtime === "offline-analysis" || surface.runtime === "live")), "environment-sensitive behavior may change");
449
+ // Scoped to the runtime surfaces, so that a later proof-only commit keeps the rollback plan.
450
+ const reversibilityScope = directRuntime.length > 0 ? directRuntime : runtime;
451
+ if (impact.reversibility === "costly") {
452
+ add("reversibility:costly", "product", reversibilityScope, "the change is costly to reverse");
453
+ }
454
+ if (impact.reversibility === "irreversible") {
455
+ add("reversibility:irreversible", "product", reversibilityScope, "the change is irreversible");
456
+ }
457
+ }
458
+ function isProductSignal(signal) {
459
+ return PRODUCT_SIGNALS.includes(signal);
460
+ }
461
+ function observedSameProduct(signal, impact) {
462
+ return (signal.baseline === impact.revision.baseline &&
463
+ signal.runtimeTreeDigest !== undefined &&
464
+ signal.runtimeTreeDigest === impact.revision.runtimeTreeDigest);
465
+ }
466
+ /** Evidence about the product, or a failure that may still be about the change. */
467
+ export function isUnresolvedSignal(classification) {
468
+ return (classification.classification === "product" ||
469
+ (classification.classification === "unattributed" && classification.exercises !== "no"));
470
+ }
471
+ function otherRevision(signal, reason) {
472
+ return {
473
+ id: signal.id,
474
+ revision: signal.revision,
475
+ signal: signal.signal,
476
+ classification: "other-revision",
477
+ exercises: signal.exercisesImpactedSurfaces,
478
+ basis: [],
479
+ surfaces: signal.surfaces,
480
+ reason,
481
+ };
482
+ }
483
+ function observedFact(id, subject, signal, detail) {
484
+ return { id, subject, surfaces: signal.surfaces, refs: [signal.id], detail };
485
+ }
486
+ function classifyProductSignal(signal, name, impact) {
487
+ const base = {
488
+ id: signal.id,
489
+ signal: name,
490
+ exercises: signal.exercisesImpactedSurfaces,
491
+ basis: [],
492
+ surfaces: signal.surfaces,
493
+ };
494
+ if (PRE_EXISTING_CAPABLE.has(name) && signal.attribution.includes("REPRODUCES_ON_BASELINE")) {
495
+ return {
496
+ source: signal,
497
+ classification: {
498
+ ...base,
499
+ classification: "pre-existing",
500
+ basis: ["REPRODUCES_ON_BASELINE"],
501
+ reason: "reproduces on the baseline: a product defect this change did not introduce",
502
+ },
503
+ // The reproduction is itself evidence that must exist.
504
+ facts: [
505
+ observedFact("observed:baseline-reproduction", "product", signal, `${name} attributed to the baseline: ${signal.detail}`),
506
+ ],
507
+ };
508
+ }
509
+ const byId = new Map(impact.surfaces.map((surface) => [surface.id, surface]));
510
+ const declared = signal.surfaces.map((id) => byId.get(id));
511
+ const allDeclared = declared.length > 0 && declared.every((surface) => surface !== undefined);
512
+ if (name === "UNPLANNED_IMPACT" && allDeclared) {
513
+ return {
514
+ source: signal,
515
+ classification: {
516
+ ...base,
517
+ classification: "absorbed",
518
+ reason: "the impact already contains every observed surface",
519
+ },
520
+ facts: [],
521
+ };
522
+ }
523
+ if (name === "LIVE_RUNTIME_TOUCHED" &&
524
+ allDeclared &&
525
+ declared.every((surface) => surface?.runtime === "live" &&
526
+ (surface.role === "product" || surface.role === "execution-context"))) {
527
+ return {
528
+ source: signal,
529
+ classification: {
530
+ ...base,
531
+ classification: "absorbed",
532
+ reason: "the impact already declares every observed surface as live runtime",
533
+ },
534
+ facts: [],
535
+ };
536
+ }
537
+ return {
538
+ source: signal,
539
+ classification: {
540
+ ...base,
541
+ classification: "product",
542
+ reason: "new evidence about the product",
543
+ },
544
+ facts: [
545
+ observedFact(SIGNAL_FACTS[name], "product", signal, `${name} observed: ${signal.detail}`),
546
+ ],
547
+ };
548
+ }
549
+ function classifyInfrastructureSignal(signal, input, bound) {
550
+ const { impact } = input;
551
+ // Only a changed runtime surface, or a proof surface that exercises one, can make a job fail
552
+ // because of the change. A changed test that exercises nothing changed stays proof evidence.
553
+ const runtimeIds = new Set(impact.surfaces
554
+ .filter((surface) => PRODUCT_RUNTIME_ROLES.has(surface.role))
555
+ .map((surface) => surface.id));
556
+ const exercisingChange = new Set([
557
+ ...runtimeIds,
558
+ ...impact.surfaces
559
+ .filter((surface) => (surface.role === "test" || surface.role === "proof-infrastructure") &&
560
+ (surface.exercises === undefined
561
+ ? surface.role === "test" && runtimeIds.size > 0
562
+ : surface.exercises.some((id) => runtimeIds.has(id))))
563
+ .map((surface) => surface.id),
564
+ ]);
565
+ const overlapping = signal.surfaces.filter((surface) => exercisingChange.has(surface));
566
+ const trustsImpact = bound === "bounded-confident" || bound === "no-product-runtime";
567
+ let exercises;
568
+ let note = "";
569
+ if (overlapping.length > 0) {
570
+ exercises = "yes";
571
+ if (signal.exercisesImpactedSurfaces === "no") {
572
+ note = "; declared outside the impact but its surfaces exercise the change";
573
+ }
574
+ }
575
+ else if (signal.exercisesImpactedSurfaces === "no") {
576
+ if (signal.surfaces.length === 0) {
577
+ exercises = "unknown";
578
+ note = "; the failure names no surface to check against the impact";
579
+ }
580
+ else {
581
+ exercises = trustsImpact ? "no" : "unknown";
582
+ if (!trustsImpact)
583
+ note = "; the impact is not bounded with confidence";
584
+ }
585
+ }
586
+ else {
587
+ exercises = signal.exercisesImpactedSurfaces;
588
+ }
589
+ let basis = [];
590
+ if (exercises === "no") {
591
+ basis = signal.attribution.filter((entry) => input.policy.attribution.admissibleOutsideImpact.includes(entry));
592
+ }
593
+ else if (signal.attribution.includes("REPRODUCES_ON_BASELINE")) {
594
+ basis = ["REPRODUCES_ON_BASELINE"];
595
+ }
596
+ const base = {
597
+ id: signal.id,
598
+ signal: signal.signal,
599
+ exercises,
600
+ surfaces: signal.surfaces,
601
+ };
602
+ if (basis.length > 0) {
603
+ const facts = [
604
+ observedFact("observed:infrastructure-failure", "proof-infrastructure", signal, `${signal.signal} attributed to the proof infrastructure: ${signal.detail}`),
605
+ ];
606
+ if (basis.every((entry) => entry === "REPRODUCES_ON_BASELINE")) {
607
+ facts.push(observedFact("observed:baseline-reproduction", "proof-infrastructure", signal, `${signal.signal} attributed only by a reproduction on the baseline: ${signal.detail}`));
608
+ }
609
+ return {
610
+ source: signal,
611
+ classification: {
612
+ ...base,
613
+ classification: "proof-infrastructure",
614
+ basis,
615
+ reason: `new evidence about the proof infrastructure (${basis.join(", ")})${note}`,
616
+ },
617
+ facts,
618
+ };
619
+ }
620
+ return {
621
+ source: signal,
622
+ classification: {
623
+ ...base,
624
+ classification: "unattributed",
625
+ basis: [],
626
+ reason: exercises === "no"
627
+ ? `no admissible attribution basis${note}`
628
+ : `the failing job may exercise the change; only REPRODUCES_ON_BASELINE can attribute it${note}`,
629
+ },
630
+ facts: [
631
+ observedFact("observed:unattributed-failure", exercises === "no" ? "proof-infrastructure" : "product", signal, `${signal.signal} not attributed: ${signal.detail}`),
632
+ ],
633
+ };
634
+ }
635
+ function computeBound(input, productRuntimeTouched, facts) {
636
+ const { impact, claims } = input;
637
+ const { analysis } = impact;
638
+ // Facts about the analysis concern the runtime subjects it reaches, otherwise what changed.
639
+ const analysisSubjects = sortedUnique(impact.surfaces
640
+ .filter((surface) => productRuntimeTouched
641
+ ? PRODUCT_RUNTIME_ROLES.has(surface.role)
642
+ : surface.reach === "direct")
643
+ .map((surface) => subjectOfRole(surface.role)));
644
+ const addAnalysisFact = (id, surfaces, detail) => {
645
+ for (const subject of analysisSubjects)
646
+ facts.add(id, subject, surfaces, [], detail);
647
+ };
648
+ const highUncertainty = "the impact analysis reports high uncertainty";
649
+ // An incomplete analysis is unbounded whatever it enumerates: the omitted part may be product.
650
+ const unbounded = [];
651
+ if (facts.has("observed:unknown-dependency"))
652
+ unbounded.push("an unknown dependency was observed");
653
+ if (facts.has("observed:unplanned-impact")) {
654
+ unbounded.push("an impact outside the analysis was observed");
655
+ }
656
+ if (analysis.completeness === "unknown")
657
+ unbounded.push("analysis completeness is unknown");
658
+ if (analysis.omittedSurfaceCount > 0) {
659
+ unbounded.push(`${analysis.omittedSurfaceCount} impacted surfaces were omitted`);
660
+ }
661
+ if (analysis.unknowns.some((unknown) => unknown.surfaces.length === 0)) {
662
+ unbounded.push("an unknown is not localized to named surfaces");
663
+ }
664
+ if (analysis.completeness === "partial" && analysis.unknowns.length === 0) {
665
+ unbounded.push("the analysis is partial without naming what it misses");
666
+ }
667
+ if (facts.has("execution-context:changed")) {
668
+ unbounded.push("the execution context changed, which static analysis cannot bound");
669
+ }
670
+ const localized = new Set(analysis.unknowns.flatMap((unknown) => unknown.surfaces));
671
+ const sensitive = new Set([
672
+ ...impact.surfaces.filter((surface) => surface.runtime === "live").map((surface) => surface.id),
673
+ ...claims
674
+ .filter((claim) => claim.criticality === "high" || claim.criticality === "critical")
675
+ .flatMap((claim) => claim.surfaces),
676
+ ]);
677
+ if ([...localized].some((surface) => sensitive.has(surface))) {
678
+ unbounded.push("an unknown touches a live surface or a high or critical claim");
679
+ }
680
+ if (unbounded.length > 0) {
681
+ // An unbounded impact may reach the product, whatever was enumerated.
682
+ facts.add("impact:unbounded", "product", [], [], `impact not bounded: ${unbounded.join("; ")}`);
683
+ if (analysis.uncertainty === "high")
684
+ facts.add("uncertainty:high", "product", [], [], highUncertainty);
685
+ return { bound: "unbounded", reasons: unbounded };
686
+ }
687
+ const uncertain = [];
688
+ if (analysis.method === "declared")
689
+ uncertain.push("the impact is declared, not derived");
690
+ if (analysis.completeness === "partial")
691
+ uncertain.push("the analysis is partial");
692
+ if (analysis.uncertainty !== "low")
693
+ uncertain.push(`analysis uncertainty is ${analysis.uncertainty}`);
694
+ if (localized.size > 0)
695
+ uncertain.push("localized unknowns remain");
696
+ if (uncertain.length === 0) {
697
+ return productRuntimeTouched
698
+ ? { bound: "bounded-confident", reasons: ["static analysis, complete, low uncertainty"] }
699
+ : {
700
+ bound: "no-product-runtime",
701
+ reasons: [
702
+ "no product, execution-context or evaluation surface changed, per a complete static analysis",
703
+ ],
704
+ };
705
+ }
706
+ if (analysis.method === "declared") {
707
+ addAnalysisFact("impact:declared", [], "the surface list is declared by the caller; surfaces it omits are not bounded");
708
+ }
709
+ if (localized.size > 0) {
710
+ addAnalysisFact("impact:localized-unknowns", localized, `unknowns localized to ${[...localized].sort(compareOrdinal).join(", ")}`);
711
+ }
712
+ if (analysis.uncertainty === "high")
713
+ addAnalysisFact("uncertainty:high", [], highUncertainty);
714
+ addAnalysisFact("impact:bounded-uncertain", [], `impact bounded with uncertainty: ${uncertain.join("; ")}`);
715
+ return { bound: "bounded-uncertain", reasons: uncertain };
716
+ }
717
+ function deriveClaimFacts(input, bound, facts) {
718
+ const { impact } = input;
719
+ const runtimeById = new Map(impact.surfaces
720
+ .filter((surface) => PRODUCT_RUNTIME_ROLES.has(surface.role))
721
+ .map((surface) => [surface.id, surface]));
722
+ // A modified oracle, budget, selection or gate can stop protecting the claims it checks.
723
+ const changedProof = impact.surfaces.filter((surface) => surface.reach === "direct" &&
724
+ (surface.role === "test" || surface.role === "proof-infrastructure") &&
725
+ surface.behaviorChange !== "none");
726
+ const unaffected = [];
727
+ const throughProof = [];
728
+ const outsideImpact = [];
729
+ const trustsImpact = bound === "bounded-confident" || bound === "no-product-runtime";
730
+ for (const claim of input.claims) {
731
+ const critical = claim.criticality === "high" || claim.criticality === "critical";
732
+ if (claim.kind === "documentation") {
733
+ const stale = claim.surfaces.flatMap((id) => {
734
+ const surface = runtimeById.get(id);
735
+ return surface && surface.behaviorChange !== "none" ? [id] : [];
736
+ });
737
+ if (stale.length > 0) {
738
+ facts.add("claim:documentation", "documentation", stale, [claim.id], `documentation claim ${claim.id} describes ${stale.join(", ")}, whose behavior may change`);
739
+ }
740
+ else {
741
+ unaffected.push({
742
+ id: claim.id,
743
+ reason: "no surface it documents changes behavior within the impact",
744
+ });
745
+ }
746
+ continue;
747
+ }
748
+ const protectors = changedProof.filter((surface) => claim.surfaces.includes(surface.id) ||
749
+ (surface.exercises?.some((id) => claim.surfaces.includes(id)) ?? false));
750
+ if (protectors.length > 0 && critical) {
751
+ facts.add(`claim:${claim.criticality === "critical" ? "critical" : "high"}:oracle`, "proof-infrastructure", ids(protectors), [claim.id], `claim ${claim.id} (${claim.criticality}) is checked by changed proof surfaces ${ids(protectors).join(", ")}`);
752
+ }
753
+ const inImpact = claim.surfaces.filter((id) => runtimeById.has(id));
754
+ let reach;
755
+ let surfaces;
756
+ if (inImpact.length > 0) {
757
+ reach = inImpact.some((id) => runtimeById.get(id)?.reach === "direct")
758
+ ? "direct"
759
+ : "transitive";
760
+ surfaces = inImpact;
761
+ }
762
+ else if (trustsImpact) {
763
+ if (protectors.length > 0) {
764
+ throughProof.push({
765
+ id: claim.id,
766
+ reason: `its runtime surfaces are unchanged; only its proof changed (${ids(protectors).join(", ")})`,
767
+ });
768
+ }
769
+ else {
770
+ unaffected.push({
771
+ id: claim.id,
772
+ reason: "none of its runtime surfaces is in an impact bounded with confidence",
773
+ });
774
+ }
775
+ continue;
776
+ }
777
+ else {
778
+ outsideImpact.push(claim.id);
779
+ if (bound === "bounded-uncertain" && claim.kind !== "empirical") {
780
+ // Criticality deepens the proof of what the change reaches; under a bounded but
781
+ // uncertain impact, a claim outside it earns a recommendation, not a floor.
782
+ if (critical) {
783
+ facts.add("claim:outside-impact", "product", claim.surfaces, [claim.id], `claim ${claim.id} (${claim.criticality}) lies outside an impact bounded with uncertainty`);
784
+ }
785
+ continue;
786
+ }
787
+ reach = "transitive";
788
+ surfaces = claim.surfaces;
789
+ }
790
+ const detail = `claim ${claim.id} (${claim.criticality}, ${claim.scope}, ${claim.kind}) reached ${reach}ly`;
791
+ // An empirical claim is proved by evaluation evidence; its criticality and scope do not
792
+ // turn into product requirements.
793
+ if (claim.kind === "empirical") {
794
+ facts.add(reach === "direct" ? "claim:empirical" : "claim:empirical:transitive", "evaluation", surfaces, [claim.id], detail);
795
+ continue;
796
+ }
797
+ for (const subject of ["product", "evaluation"]) {
798
+ const scoped = surfaces.filter((id) => {
799
+ const surface = runtimeById.get(id);
800
+ return (surface ? subjectOfRole(surface.role) : "product") === subject;
801
+ });
802
+ if (scoped.length === 0)
803
+ continue;
804
+ if (claim.criticality === "high" || claim.criticality === "critical") {
805
+ facts.add(`claim:${claim.criticality}:${reach}`, subject, scoped, [claim.id], detail);
806
+ }
807
+ if (reach === "direct" && claim.scope !== "local") {
808
+ facts.add(`claim:scope:${claim.scope}`, subject, scoped, [claim.id], detail);
809
+ }
810
+ }
811
+ }
812
+ return { unaffected, throughProof, outsideImpact };
813
+ }
814
+ /** Impact surfaces a baseline of `subject` is about: its direct ones and those its facts name. */
815
+ function subjectSurfaces(impact, facts, subject) {
816
+ const named = new Set(facts
817
+ .list()
818
+ .filter((fact) => fact.subject === subject)
819
+ .flatMap((fact) => fact.surfaces));
820
+ return impact.surfaces
821
+ .filter((surface) => subjectOfRole(surface.role) === subject &&
822
+ (surface.reach === "direct" || named.has(surface.id)))
823
+ .map((surface) => surface.id);
824
+ }
825
+ function accumulate(target, kind, surfaces, because) {
826
+ const entry = target.get(kind) ?? { surfaces: new Set(), because: new Set() };
827
+ for (const surface of surfaces)
828
+ entry.surfaces.add(surface);
829
+ entry.because.add(because);
830
+ target.set(kind, entry);
831
+ }
832
+ function requirementOf(spec, surfaces, because) {
833
+ return {
834
+ kind: spec.id,
835
+ title: spec.title,
836
+ weight: spec.weight,
837
+ binding: spec.binding,
838
+ verification: spec.verification,
839
+ subjects: [...spec.subjects],
840
+ semanticDigest: evidenceKindSemanticDigest(spec),
841
+ surfaces: sortedUnique(surfaces),
842
+ because: sortedUnique(because),
843
+ };
844
+ }
845
+ function materialize(entries, catalog) {
846
+ return catalog
847
+ .filter((spec) => entries.has(spec.id))
848
+ .map((spec) => {
849
+ const entry = entries.get(spec.id);
850
+ return requirementOf(spec, entry?.surfaces ?? [], entry?.because ?? []);
851
+ })
852
+ .sort((left, right) => compareOrdinal(left.kind, right.kind));
853
+ }
854
+ /**
855
+ * Worst case of the declared uncertainty over enumerated surfaces: every transitive runtime surface
856
+ * becomes direct, every "no change" becomes "suspected", every unknown coverage becomes untested.
857
+ * The analysis itself is kept, so the worst case is never more trusting than the actual plan.
858
+ */
859
+ function worstCaseInput(input) {
860
+ return {
861
+ ...input,
862
+ impact: {
863
+ ...input.impact,
864
+ surfaces: input.impact.surfaces.map((surface) => {
865
+ if (!PRODUCT_RUNTIME_ROLES.has(surface.role))
866
+ return surface;
867
+ return {
868
+ ...surface,
869
+ reach: "direct",
870
+ behaviorChange: surface.behaviorChange === "none" ? "suspected" : surface.behaviorChange,
871
+ coverage: surface.coverage === "unknown" ? "untested" : surface.coverage,
872
+ };
873
+ }),
874
+ },
875
+ };
876
+ }
877
+ function partitionCatalog(input, core) {
878
+ const selected = new Set([...core.required.keys(), ...core.recommended.keys()]);
879
+ const worstCaseRecommended = new Map();
880
+ let worst = null;
881
+ if (core.bound === "bounded-uncertain") {
882
+ worst = planCore(worstCaseInput(input));
883
+ for (const source of [worst.required, worst.recommended]) {
884
+ for (const [kind, entry] of source) {
885
+ if (selected.has(kind))
886
+ continue;
887
+ for (const reason of entry.because) {
888
+ accumulate(worstCaseRecommended, kind, entry.surfaces, `worst-case:${reason}`);
889
+ }
890
+ }
891
+ }
892
+ for (const kind of worstCaseRecommended.keys())
893
+ selected.add(kind);
894
+ }
895
+ const evaluatedAgainst = worst
896
+ ? "worst-case"
897
+ : "actual-change";
898
+ // Activation values report the stricter of the actual plan and its worst case.
899
+ const levels = new Map();
900
+ for (const subject of EVIDENCE_SUBJECTS) {
901
+ const values = [core.levelBySubject.get(subject), worst?.levelBySubject.get(subject)].filter((value) => value !== undefined);
902
+ if (values.length > 0)
903
+ levels.set(subject, Math.max(...values));
904
+ }
905
+ const unbounded = core.bound === "unbounded"
906
+ ? `the impact is not bounded (${core.boundReasons.join("; ")})`
907
+ : worst?.bound === "unbounded"
908
+ ? `the worst case of the declared uncertainty is not bounded (${worst.boundReasons.join("; ")})`
909
+ : null;
910
+ const notRequired = [];
911
+ const undetermined = [];
912
+ for (const spec of input.policy.evidence) {
913
+ if (selected.has(spec.id))
914
+ continue;
915
+ const requirement = requirementOf(spec, [], []);
916
+ if (unbounded !== null) {
917
+ undetermined.push({
918
+ requirement,
919
+ rationale: `Undetermined: ${unbounded}, so its absence cannot be justified.`,
920
+ });
921
+ continue;
922
+ }
923
+ const activation = activationOf(spec, input.policy, levels);
924
+ notRequired.push({
925
+ requirement,
926
+ rationale: notRequiredRationale(spec, activation, core, evaluatedAgainst),
927
+ activation,
928
+ evaluatedAgainst,
929
+ });
930
+ }
931
+ const byKind = (left, right) => compareOrdinal(left.requirement.kind, right.requirement.kind);
932
+ return {
933
+ notRequired: notRequired.sort(byKind),
934
+ undetermined: undetermined.sort(byKind),
935
+ worstCaseRecommended,
936
+ };
937
+ }
938
+ function activationOf(spec, policy, levels) {
939
+ const triggers = policy.triggers
940
+ .flatMap((entry) => [
941
+ ...(entry.require.includes(spec.id)
942
+ ? [{ fact: entry.fact, effect: "require" }]
943
+ : []),
944
+ ...(entry.recommend.includes(spec.id)
945
+ ? [{ fact: entry.fact, effect: "recommend" }]
946
+ : []),
947
+ ])
948
+ .sort((left, right) => compareOrdinal(`${left.fact}|${left.effect}`, `${right.fact}|${right.effect}`));
949
+ const baselines = policy.baselines
950
+ .filter((baseline) => baseline.kind === spec.id)
951
+ .map((baseline) => ({
952
+ level: baseline.level,
953
+ subjects: spec.subjects.map((subject) => {
954
+ const level = levels.get(subject);
955
+ return { subject, level: level === undefined ? null : levelAt(level) };
956
+ }),
957
+ }));
958
+ return { triggers, baselines };
959
+ }
960
+ function notRequiredRationale(spec, activation, core, evaluatedAgainst) {
961
+ const conditions = [];
962
+ if (activation.triggers.length > 0) {
963
+ conditions.push(`one of [${activation.triggers.map((entry) => entry.fact).join(", ")}] holds`);
964
+ }
965
+ for (const baseline of activation.baselines) {
966
+ const current = baseline.subjects
967
+ .map((entry) => `${entry.subject} ${entry.level ?? "absent"}`)
968
+ .join(", ");
969
+ conditions.push(`a subject among [${baseline.subjects.map((entry) => entry.subject).join(", ")}] reaches ${baseline.level} (now ${current})`);
970
+ }
971
+ if (conditions.length === 0)
972
+ return `Not required: no rule of this policy asks for ${spec.id}.`;
973
+ const scope = evaluatedAgainst === "worst-case"
974
+ ? "even in the worst case of the declared uncertainty"
975
+ : core.bound === "no-product-runtime"
976
+ ? "for this change, which touches no product, execution-context or evaluation surface"
977
+ : "for this change, whose impact is bounded with confidence";
978
+ return `Not required: the policy asks for ${spec.id} when ${conditions.join(" or ")}; none holds ${scope}.`;
979
+ }
980
+ function computeEscalations(input, core) {
981
+ const directProduct = input.impact.surfaces
982
+ .filter((surface) => surface.reach === "direct" && subjectOfRole(surface.role) === "product")
983
+ .map((surface) => surface.id);
984
+ const directAll = input.impact.surfaces
985
+ .filter((surface) => surface.reach === "direct")
986
+ .map((surface) => surface.id);
987
+ const productSurfaces = directProduct.length > 0 ? directProduct : directAll;
988
+ const rows = [];
989
+ const hypothetical = (signal, variant, surfaces, attribution) => {
990
+ const synthetic = {
991
+ id: `${HYPOTHETICAL_PREFIX}${signal}`,
992
+ revision: input.impact.revision.id,
993
+ signal,
994
+ surfaces,
995
+ exercisesImpactedSurfaces: "yes",
996
+ attribution,
997
+ detail: "hypothetical signal used to precompute the escalation",
998
+ };
999
+ const next = planCore({ ...input, signals: [...input.signals, synthetic] });
1000
+ const classification = next.signals.find((entry) => entry.id === synthetic.id)?.classification ?? "product";
1001
+ const label = {
1002
+ product: "Evidence about the product",
1003
+ "proof-infrastructure": "Evidence about the proof infrastructure",
1004
+ unattributed: "Unattributed failure",
1005
+ "pre-existing": "Attributed to the baseline",
1006
+ absorbed: "Absorbed by the declared impact",
1007
+ "other-revision": "Observed on another revision",
1008
+ };
1009
+ const addsRequired = [...next.required.keys()]
1010
+ .filter((kind) => !core.required.has(kind))
1011
+ .sort(compareOrdinal);
1012
+ const levelChange = next.level === core.level ? `stays ${core.level}` : `moves ${core.level} -> ${next.level}`;
1013
+ return {
1014
+ signal,
1015
+ variant,
1016
+ classification,
1017
+ resultingLevel: next.level,
1018
+ resultingStatus: next.status,
1019
+ addsRequired,
1020
+ rationale: `${label[classification]}: level ${levelChange}, status ${next.status}${addsRequired.length > 0 ? `, adds ${addsRequired.join(", ")}` : ", adds nothing"}.`,
1021
+ };
1022
+ };
1023
+ for (const signal of PRODUCT_SIGNALS) {
1024
+ const surfaces = signal === "UNPLANNED_IMPACT"
1025
+ ? [`${HYPOTHETICAL_PREFIX}unplanned-surface`]
1026
+ : signal === "UNKNOWN_DEPENDENCY"
1027
+ ? []
1028
+ : productSurfaces;
1029
+ rows.push(hypothetical(signal, "product", surfaces, []));
1030
+ }
1031
+ const infrastructure = INFRASTRUCTURE_SIGNALS[0];
1032
+ rows.push(hypothetical(infrastructure, "infrastructure-attributed", [], ["REPRODUCES_ON_BASELINE"]));
1033
+ rows.push(hypothetical(infrastructure, "infrastructure-unattributed", [], []));
1034
+ return rows;
1035
+ }
1036
+ function computeUncertainty(input, core, notRequired) {
1037
+ const entries = [];
1038
+ const { impact } = input;
1039
+ if (impact.analysis.method === "declared") {
1040
+ entries.push({
1041
+ id: "impact.declared",
1042
+ statement: "Surfaces, roles and boundaries are declared by the caller, not derived; exemptions are limited to what holds in the worst case of that declaration.",
1043
+ mitigatedBy: [],
1044
+ });
1045
+ }
1046
+ if (core.bound === "unbounded" || core.bound === "bounded-uncertain") {
1047
+ entries.push({
1048
+ id: `impact.${core.bound}`,
1049
+ statement: `Impact ${core.bound}: ${core.boundReasons.join("; ")}.`,
1050
+ mitigatedBy: core.bound === "unbounded" ? ["FULL_TEST_SUITE"] : [],
1051
+ });
1052
+ }
1053
+ impact.analysis.unknowns.forEach((unknown, index) => {
1054
+ entries.push({
1055
+ id: `impact.unknown.${index + 1}`,
1056
+ statement: `${unknown.description}${unknown.surfaces.length > 0 ? ` (${unknown.surfaces.join(", ")})` : ""}.`,
1057
+ mitigatedBy: unknown.surfaces.length > 0 ? ["TARGETED_REGRESSION"] : ["FULL_TEST_SUITE"],
1058
+ });
1059
+ });
1060
+ const unknownCoverage = impact.surfaces.filter((surface) => surface.role === "product" &&
1061
+ surface.reach === "direct" &&
1062
+ surface.coverage === "unknown" &&
1063
+ surface.behaviorChange === "none");
1064
+ if (unknownCoverage.length > 0) {
1065
+ entries.push({
1066
+ id: "coverage.unknown",
1067
+ statement: `Existing coverage of ${ids(unknownCoverage).join(", ")} is unknown; a refactor there is only as safe as the tests that happen to exist.`,
1068
+ mitigatedBy: ["AFFECTED_TESTS"],
1069
+ });
1070
+ }
1071
+ if (core.factIds.has("witness:infeasible")) {
1072
+ entries.push({
1073
+ id: "witness.infeasible",
1074
+ statement: "The fix has no reproducible witness: the defect is not causally demonstrated, only observed through substitutes.",
1075
+ mitigatedBy: ["PRODUCTION_PATH_TEST", "TARGETED_CORPUS"],
1076
+ });
1077
+ }
1078
+ for (const claim of core.claimsOutsideImpact) {
1079
+ entries.push({
1080
+ id: `claim.${claim}.outside-impact`,
1081
+ statement: core.bound === "unbounded"
1082
+ ? `Claim ${claim} references surfaces outside an unbounded impact; it is treated as transitively reached.`
1083
+ : `Claim ${claim} references surfaces outside an impact bounded with uncertainty; a high or critical one earns a recommended invariant check.`,
1084
+ mitigatedBy: core.bound === "unbounded" ? ["INVARIANT_CHECK"] : [],
1085
+ });
1086
+ }
1087
+ const unnamed = impact.surfaces.filter((surface) => surface.reach === "direct" &&
1088
+ (surface.role === "test" || surface.role === "proof-infrastructure") &&
1089
+ surface.behaviorChange !== "none" &&
1090
+ surface.exercises === undefined);
1091
+ if (unnamed.length > 0) {
1092
+ entries.push({
1093
+ id: "proof.exercises-unknown",
1094
+ statement: `Changed proof surfaces ${ids(unnamed).join(", ")} name no exercised surface; only the claims that list them are known to depend on them.`,
1095
+ mitigatedBy: ["GATE_DELTA_REVIEW"],
1096
+ });
1097
+ }
1098
+ if (core.factIds.has("claim:empirical:transitive")) {
1099
+ entries.push({
1100
+ id: "claim.empirical.transitive",
1101
+ statement: "An empirical claim depends on changed code; results established before this revision may not hold for it.",
1102
+ mitigatedBy: ["BENCHMARK_PROTOCOL"],
1103
+ });
1104
+ }
1105
+ for (const signal of core.signals) {
1106
+ if (signal.classification === "unattributed" || signal.classification === "pre-existing") {
1107
+ entries.push({
1108
+ id: `signal.${signal.id}.${signal.classification}`,
1109
+ statement: `${signal.signal} ${signal.id}: ${signal.reason}.`,
1110
+ mitigatedBy: ["FAILURE_ATTRIBUTION"],
1111
+ });
1112
+ }
1113
+ }
1114
+ const broadExempted = notRequired.filter((entry) => entry.requirement.weight !== "targeted");
1115
+ if (core.bound === "bounded-confident" && broadExempted.length > 0) {
1116
+ entries.push({
1117
+ id: "exemptions.rely-on-impact",
1118
+ statement: `${broadExempted.length} broad or ceremonial kinds are not required because the impact analysis bounds the change; an UNPLANNED_IMPACT or UNKNOWN_DEPENDENCY signal reopens them.`,
1119
+ mitigatedBy: [],
1120
+ });
1121
+ }
1122
+ return entries.sort((left, right) => compareOrdinal(left.id, right.id));
1123
+ }
1124
+ //# sourceMappingURL=plan.js.map