omk-protocol 0.98.2 → 0.98.4
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/CHANGELOG.md +21 -0
- package/README.md +36 -0
- package/dist/claims/claim-blocking-cut.d.ts +10 -0
- package/dist/claims/claim-blocking-cut.d.ts.map +1 -0
- package/dist/claims/claim-blocking-cut.js +188 -0
- package/dist/claims/claim-blocking-cut.js.map +1 -0
- package/dist/claims/claim-closure.d.ts +30 -0
- package/dist/claims/claim-closure.d.ts.map +1 -0
- package/dist/claims/claim-closure.js +228 -0
- package/dist/claims/claim-closure.js.map +1 -0
- package/dist/claims/claim-graph.d.ts +30 -0
- package/dist/claims/claim-graph.d.ts.map +1 -0
- package/dist/claims/claim-graph.js +149 -0
- package/dist/claims/claim-graph.js.map +1 -0
- package/dist/claims/claim-types.d.ts +120 -0
- package/dist/claims/claim-types.d.ts.map +1 -0
- package/dist/claims/claim-types.js +34 -0
- package/dist/claims/claim-types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.98.4] - 2026-09-10
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added bounded `explainBlockingCut()` metadata and opt-in `witnessIndependence: "explicit-groups"`. The compatibility default remains observation-ID grouping.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Shared DAG branches can share a repair instead of choosing incompatible local minima. Composite-local obligations remain visible. A bounded-search greedy fallback explicitly does not claim minimum cardinality.
|
|
14
|
+
|
|
15
|
+
## [0.98.3] - 2026-09-06
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Claim Closure Graph v1 types, validation, pure proof-closure evaluation and minimal blocking-cut analysis. Required/advisory claims, source/environment bindings, stale observations, trust floors, witness groups, workspace scope and open effects remain distinct. Callers must supply trustworthy evidence; a result is not a runner attestation or release approval.
|
|
20
|
+
|
|
21
|
+
Earlier lockstep releases predate this package-specific changelog; see the repository release notes.
|
package/README.md
CHANGED
|
@@ -13,6 +13,42 @@ TaskSpec -> ExecutionAttempt -> Observation -> EvaluationResult -> RuntimeDecisi
|
|
|
13
13
|
- `ClaimEvaluation`, `EvaluationResult`, `RuntimeDecision`, `WaiverRecord`
|
|
14
14
|
- Runtime parsers for every top-level record
|
|
15
15
|
- `evaluateTask()` and `reduceRuntimeDecision()`
|
|
16
|
+
- Claim Closure Graph v1: `evaluateProofClosure()`, `validateClaimGraph()`,
|
|
17
|
+
`minimalBlockingCut()` and the readonly claim/observation/waiver vocabulary
|
|
18
|
+
|
|
19
|
+
The claim-closure API is explicit. It checks supplied source/environment bindings,
|
|
20
|
+
trust floors, witness groups, expiry, workspace completeness and unresolved-effect
|
|
21
|
+
boundaries; those supplied facts remain caller trust boundaries. It does not attest a
|
|
22
|
+
runner or automatically decide an ordinary chat turn.
|
|
23
|
+
|
|
24
|
+
## Claim closure and blocking explanations
|
|
25
|
+
|
|
26
|
+
`evaluateProofClosure()` evaluates qualified witnesses on an all/any claim DAG.
|
|
27
|
+
Set `witnessIndependence: "explicit-groups"` for a strict profile: multi-witness
|
|
28
|
+
claims (`requiredWitnesses > 1`) then require explicit, nonempty `independenceGroup`
|
|
29
|
+
values. Merely changing an observation ID does not create another independent
|
|
30
|
+
witness in that profile. Single-witness claims keep their existing behavior.
|
|
31
|
+
|
|
32
|
+
The compatibility default remains `legacy-observation-id`; the result reports the
|
|
33
|
+
resolved policy. Existing callers must opt into the strict profile deliberately.
|
|
34
|
+
Group identities still need binding to real evidence origins by a trusted caller;
|
|
35
|
+
the reducer cannot authenticate an arbitrary caller-supplied label.
|
|
36
|
+
|
|
37
|
+
`explainBlockingCut()` returns a bounded antichain repair explanation. Shared DAG
|
|
38
|
+
branches can share one repair. Composite-local counterexamples and scope obligations
|
|
39
|
+
are retained alongside child obligations rather than being hidden by them.
|
|
40
|
+
|
|
41
|
+
- `blockingCut.optimality: "minimum"` means minimum cardinality in this snapshot's
|
|
42
|
+
repair model, not semantic correctness after a real change.
|
|
43
|
+
- At the candidate/operation limit, `algorithm: "greedy"`, `truncated: true`, and
|
|
44
|
+
`optimality: "not-proven"` identify a fallback with no minimality guarantee.
|
|
45
|
+
- The legacy `minimalBlockingCut` array remains as a compatibility projection.
|
|
46
|
+
Consumers needing optimality must read the explanation metadata. Older results
|
|
47
|
+
without that metadata do not establish a minimum.
|
|
48
|
+
|
|
49
|
+
Repair explanations never close claims, reconcile effects, issue waivers, or authorize
|
|
50
|
+
merges. Re-evaluate evidence after a change. Set operations depend on candidate and
|
|
51
|
+
set sizes; the shared-DAG optimization is not a linear-time minimum-cut algorithm.
|
|
16
52
|
|
|
17
53
|
The package does not execute tools, persist records, schedule work, choose providers, or infer topology. Retry and failover counts are derived from attempt records rather than stored counters.
|
|
18
54
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type BlockingCutExplanation, type ClaimClosureEvaluation, type ClaimNode, type ClaimVerdict } from "./claim-types.ts";
|
|
2
|
+
export declare const MAX_BLOCKING_CUT_CANDIDATES = 128;
|
|
3
|
+
type Cut = readonly string[];
|
|
4
|
+
export declare function isBlockingVerdict(verdict: ClaimVerdict): boolean;
|
|
5
|
+
/** Prefer this API over the legacy array-only projection when optimality matters. */
|
|
6
|
+
export declare function explainBlockingCut(claims: ReadonlyMap<string, ClaimNode>, evaluations: ReadonlyMap<string, ClaimClosureEvaluation>, rootIds: readonly string[]): BlockingCutExplanation;
|
|
7
|
+
/** Compatibility name: on a bounded-search fallback this array is not guaranteed minimal. */
|
|
8
|
+
export declare function minimalBlockingCut(claims: ReadonlyMap<string, ClaimNode>, evaluations: ReadonlyMap<string, ClaimClosureEvaluation>, rootIds: readonly string[]): Cut;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=claim-blocking-cut.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-blocking-cut.d.ts","sourceRoot":"","sources":["../../src/claims/claim-blocking-cut.ts"],"names":[],"mappings":"AAQA,OAAO,EACN,KAAK,sBAAsB,EAE3B,KAAK,sBAAsB,EAC3B,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,MAAM,kBAAkB,CAAC;AAS1B,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAG/C,KAAK,GAAG,GAAG,SAAS,MAAM,EAAE,CAAC;AAc7B,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAEhE;AAyID,qFAAqF;AACrF,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,EACtC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,EACxD,OAAO,EAAE,SAAS,MAAM,EAAE,GACxB,sBAAsB,CA2BxB;AAED,6FAA6F;AAC7F,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,EACtC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,EACxD,OAAO,EAAE,SAAS,MAAM,EAAE,GACxB,GAAG,CAEL","sourcesContent":["/**\n * Bounded blocking explanations for a validated all/any claim DAG.\n * Shared subgraphs require alternative repair sets, not a local cheapest branch.\n * Exact antichains can grow exponentially; bounds fall back to a deterministic,\n * not-proven explanation. Set-operation cost also depends on output-set size.\n * A repair is an explanatory obligation, never permission to satisfy a claim.\n */\nimport { ClaimGraphError, topologicalClaimOrder } from \"./claim-graph.ts\";\nimport {\n\ttype BlockingCutExplanation,\n\tCLAIM_GRAPH_SCHEMA_VERSION,\n\ttype ClaimClosureEvaluation,\n\ttype ClaimNode,\n\ttype ClaimVerdict,\n} from \"./claim-types.ts\";\n\nconst BLOCKING_VERDICTS: readonly ClaimVerdict[] = [\n\t\"violated\",\n\t\"stale\",\n\t\"incomplete_scope\",\n\t\"insufficient_trust\",\n\t\"missing\",\n];\nexport const MAX_BLOCKING_CUT_CANDIDATES = 128;\nconst MAX_CUT_OPERATIONS = 65536;\nclass CutSearchLimit extends Error {}\ntype Cut = readonly string[];\ntype Family = readonly Cut[];\ninterface CutNode {\n\treadonly id: string;\n\treadonly rule: \"all\" | \"any\";\n\treadonly inputs: readonly string[];\n\treadonly closed: boolean;\n\treadonly local: boolean;\n}\n/** Search-owned mutable accounting, including work before a limit is reached. */\ninterface CutSearchBudget {\n\toperations: number;\n}\n\nexport function isBlockingVerdict(verdict: ClaimVerdict): boolean {\n\treturn BLOCKING_VERDICTS.includes(verdict);\n}\nfunction compareIds(left: string, right: string): number {\n\tif (left === right) return 0;\n\treturn left < right ? -1 : 1;\n}\nfunction compareCuts(left: Cut, right: Cut): number {\n\tif (left.length !== right.length) return left.length - right.length;\n\tfor (let i = 0; i < left.length; i++) {\n\t\tconst order = compareIds(left[i], right[i]);\n\t\tif (order) return order;\n\t}\n\treturn 0;\n}\nfunction union(sets: readonly Cut[]): Cut {\n\treturn [...new Set(sets.flat())].sort(compareIds);\n}\nfunction* unions(left: Family, right: Family): Generator<Cut> {\n\tfor (const a of left) for (const b of right) yield union([a, b]);\n}\nfunction* alternatives(families: readonly Family[]): Generator<Cut> {\n\tfor (const family of families) yield* family;\n}\n\nfunction cutNodes(\n\tclaims: ReadonlyMap<string, ClaimNode>,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n): CutNode[] {\n\tconst ordered = topologicalClaimOrder(\n\t\t{ schemaVersion: CLAIM_GRAPH_SCHEMA_VERSION, claims: [...claims.values()] },\n\t\tclaims,\n\t);\n\treturn ordered.map((claim) => {\n\t\tconst evaluation = evaluations.get(claim.claimId);\n\t\tif (\n\t\t\t!evaluation ||\n\t\t\t(!isBlockingVerdict(evaluation.verdict) &&\n\t\t\t\tevaluation.verdict !== \"satisfied\" &&\n\t\t\t\tevaluation.verdict !== \"waived\")\n\t\t) {\n\t\t\tthrow new ClaimGraphError(\"invalid_input\", \"Blocking explanation requires a valid evaluation for every claim\");\n\t\t}\n\t\tconst inputs = claim.satisfaction.inputs.filter((id) => claims.get(id)?.severity === \"required\").sort(compareIds);\n\t\tconst inferredLocal =\n\t\t\t(evaluation.verdict === \"violated\" && evaluation.observationIds.length > 0) ||\n\t\t\t(evaluation.verdict === \"incomplete_scope\" && claim.scopeSensitive === true);\n\t\treturn {\n\t\t\tid: claim.claimId,\n\t\t\trule: claim.satisfaction.rule,\n\t\t\tinputs,\n\t\t\tclosed: !isBlockingVerdict(evaluation.verdict),\n\t\t\tlocal:\n\t\t\t\tinputs.length === 0 ||\n\t\t\t\tevaluation.blockingOrigin === \"local\" ||\n\t\t\t\t(evaluation.blockingOrigin === undefined && inferredLocal),\n\t\t};\n\t});\n}\nfunction reachableNodes(ordered: CutNode[], roots: readonly string[]): CutNode[] {\n\tconst byId = new Map(ordered.map((node) => [node.id, node]));\n\tconst reachable = new Set<string>();\n\tconst pending = [...roots];\n\twhile (pending.length) {\n\t\tconst id = pending.pop();\n\t\tif (id === undefined || reachable.has(id)) continue;\n\t\treachable.add(id);\n\t\tconst node = byId.get(id);\n\t\tif (node && !node.closed) pending.push(...node.inputs);\n\t}\n\treturn ordered.filter((node) => reachable.has(node.id));\n}\nfunction step(budget: CutSearchBudget): void {\n\tif (budget.operations >= MAX_CUT_OPERATIONS) throw new CutSearchLimit();\n\tbudget.operations++;\n}\nfunction prune(candidates: Iterable<Cut>, budget: CutSearchBudget): Family {\n\tlet kept: Cut[] = [];\n\tfor (const candidate of candidates) {\n\t\tstep(budget);\n\t\tconst set = new Set(candidate);\n\t\tif (\n\t\t\tkept.some((other) => {\n\t\t\t\tstep(budget);\n\t\t\t\treturn other.every((id) => set.has(id));\n\t\t\t})\n\t\t)\n\t\t\tcontinue;\n\t\tkept = kept.filter((other) => {\n\t\t\tstep(budget);\n\t\t\tconst existing = new Set(other);\n\t\t\treturn !candidate.every((id) => existing.has(id));\n\t\t});\n\t\tkept.push(candidate);\n\t\tif (kept.length > MAX_BLOCKING_CUT_CANDIDATES) throw new CutSearchLimit();\n\t}\n\treturn kept.sort(compareCuts);\n}\nfunction combineAll(families: readonly Family[], budget: CutSearchBudget): Family {\n\tlet result: Family = [[]];\n\tfor (const family of families) result = prune(unions(result, family), budget);\n\treturn result;\n}\nfunction exactCut(nodes: readonly CutNode[], roots: readonly string[], budget: CutSearchBudget): Cut {\n\tconst families = new Map<string, Family>();\n\tfor (const node of nodes) {\n\t\tif (node.closed) {\n\t\t\tfamilies.set(node.id, [[]]);\n\t\t\tcontinue;\n\t\t}\n\t\tconst children = node.inputs.map((id) => families.get(id) ?? []);\n\t\tlet family: Family = [[]];\n\t\tif (children.length) {\n\t\t\tfamily = node.rule === \"all\" ? combineAll(children, budget) : prune(alternatives(children), budget);\n\t\t}\n\t\tif (node.local) family = prune(unions(family, [[node.id]]), budget);\n\t\tfamilies.set(node.id, family);\n\t}\n\treturn (\n\t\tcombineAll(\n\t\t\troots.map((id) => families.get(id) ?? []),\n\t\t\tbudget,\n\t\t)[0] ?? []\n\t);\n}\nfunction greedyCut(nodes: readonly CutNode[], roots: readonly string[]): Cut {\n\tconst cuts = new Map<string, Cut>();\n\tfor (const node of nodes) {\n\t\tif (node.closed) {\n\t\t\tcuts.set(node.id, []);\n\t\t\tcontinue;\n\t\t}\n\t\tconst children = node.inputs.map((id) => cuts.get(id) ?? []);\n\t\tconst childCut = node.rule === \"all\" ? union(children) : ([...children].sort(compareCuts)[0] ?? []);\n\t\tcuts.set(node.id, node.local ? union([childCut, [node.id]]) : childCut);\n\t}\n\treturn union(roots.map((id) => cuts.get(id) ?? []));\n}\n\n/** Prefer this API over the legacy array-only projection when optimality matters. */\nexport function explainBlockingCut(\n\tclaims: ReadonlyMap<string, ClaimNode>,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n\trootIds: readonly string[],\n): BlockingCutExplanation {\n\tconst roots = [...new Set(rootIds)].sort(compareIds);\n\tfor (const id of roots) if (!claims.has(id)) throw new ClaimGraphError(\"unknown_input\", \"Unknown blocking root\");\n\tconst nodes = reachableNodes(cutNodes(claims, evaluations), roots);\n\tconst budget: CutSearchBudget = { operations: 0 };\n\tlet claimIds: Cut;\n\tlet truncated = false;\n\ttry {\n\t\tclaimIds = exactCut(nodes, roots, budget);\n\t} catch (error) {\n\t\tif (!(error instanceof CutSearchLimit)) throw error;\n\t\ttruncated = true;\n\t\tclaimIds = greedyCut(nodes, roots);\n\t}\n\tconst selected = new Set(claimIds);\n\treturn Object.freeze({\n\t\tclaimIds: Object.freeze([...claimIds]),\n\t\talgorithm: truncated ? \"greedy\" : \"exact-antichain\",\n\t\toptimality: truncated ? \"not-proven\" : \"minimum\",\n\t\ttruncated,\n\t\texploredStates: budget.operations,\n\t\tlocalClaimIds: Object.freeze(\n\t\t\tnodes\n\t\t\t\t.flatMap((node) => (node.local && node.inputs.length > 0 && selected.has(node.id) ? [node.id] : []))\n\t\t\t\t.sort(compareIds),\n\t\t),\n\t});\n}\n\n/** Compatibility name: on a bounded-search fallback this array is not guaranteed minimal. */\nexport function minimalBlockingCut(\n\tclaims: ReadonlyMap<string, ClaimNode>,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n\trootIds: readonly string[],\n): Cut {\n\treturn explainBlockingCut(claims, evaluations, rootIds).claimIds;\n}\n"]}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded blocking explanations for a validated all/any claim DAG.
|
|
3
|
+
* Shared subgraphs require alternative repair sets, not a local cheapest branch.
|
|
4
|
+
* Exact antichains can grow exponentially; bounds fall back to a deterministic,
|
|
5
|
+
* not-proven explanation. Set-operation cost also depends on output-set size.
|
|
6
|
+
* A repair is an explanatory obligation, never permission to satisfy a claim.
|
|
7
|
+
*/
|
|
8
|
+
import { ClaimGraphError, topologicalClaimOrder } from "./claim-graph.js";
|
|
9
|
+
import { CLAIM_GRAPH_SCHEMA_VERSION, } from "./claim-types.js";
|
|
10
|
+
const BLOCKING_VERDICTS = [
|
|
11
|
+
"violated",
|
|
12
|
+
"stale",
|
|
13
|
+
"incomplete_scope",
|
|
14
|
+
"insufficient_trust",
|
|
15
|
+
"missing",
|
|
16
|
+
];
|
|
17
|
+
export const MAX_BLOCKING_CUT_CANDIDATES = 128;
|
|
18
|
+
const MAX_CUT_OPERATIONS = 65536;
|
|
19
|
+
class CutSearchLimit extends Error {
|
|
20
|
+
}
|
|
21
|
+
export function isBlockingVerdict(verdict) {
|
|
22
|
+
return BLOCKING_VERDICTS.includes(verdict);
|
|
23
|
+
}
|
|
24
|
+
function compareIds(left, right) {
|
|
25
|
+
if (left === right)
|
|
26
|
+
return 0;
|
|
27
|
+
return left < right ? -1 : 1;
|
|
28
|
+
}
|
|
29
|
+
function compareCuts(left, right) {
|
|
30
|
+
if (left.length !== right.length)
|
|
31
|
+
return left.length - right.length;
|
|
32
|
+
for (let i = 0; i < left.length; i++) {
|
|
33
|
+
const order = compareIds(left[i], right[i]);
|
|
34
|
+
if (order)
|
|
35
|
+
return order;
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
function union(sets) {
|
|
40
|
+
return [...new Set(sets.flat())].sort(compareIds);
|
|
41
|
+
}
|
|
42
|
+
function* unions(left, right) {
|
|
43
|
+
for (const a of left)
|
|
44
|
+
for (const b of right)
|
|
45
|
+
yield union([a, b]);
|
|
46
|
+
}
|
|
47
|
+
function* alternatives(families) {
|
|
48
|
+
for (const family of families)
|
|
49
|
+
yield* family;
|
|
50
|
+
}
|
|
51
|
+
function cutNodes(claims, evaluations) {
|
|
52
|
+
const ordered = topologicalClaimOrder({ schemaVersion: CLAIM_GRAPH_SCHEMA_VERSION, claims: [...claims.values()] }, claims);
|
|
53
|
+
return ordered.map((claim) => {
|
|
54
|
+
const evaluation = evaluations.get(claim.claimId);
|
|
55
|
+
if (!evaluation ||
|
|
56
|
+
(!isBlockingVerdict(evaluation.verdict) &&
|
|
57
|
+
evaluation.verdict !== "satisfied" &&
|
|
58
|
+
evaluation.verdict !== "waived")) {
|
|
59
|
+
throw new ClaimGraphError("invalid_input", "Blocking explanation requires a valid evaluation for every claim");
|
|
60
|
+
}
|
|
61
|
+
const inputs = claim.satisfaction.inputs.filter((id) => claims.get(id)?.severity === "required").sort(compareIds);
|
|
62
|
+
const inferredLocal = (evaluation.verdict === "violated" && evaluation.observationIds.length > 0) ||
|
|
63
|
+
(evaluation.verdict === "incomplete_scope" && claim.scopeSensitive === true);
|
|
64
|
+
return {
|
|
65
|
+
id: claim.claimId,
|
|
66
|
+
rule: claim.satisfaction.rule,
|
|
67
|
+
inputs,
|
|
68
|
+
closed: !isBlockingVerdict(evaluation.verdict),
|
|
69
|
+
local: inputs.length === 0 ||
|
|
70
|
+
evaluation.blockingOrigin === "local" ||
|
|
71
|
+
(evaluation.blockingOrigin === undefined && inferredLocal),
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function reachableNodes(ordered, roots) {
|
|
76
|
+
const byId = new Map(ordered.map((node) => [node.id, node]));
|
|
77
|
+
const reachable = new Set();
|
|
78
|
+
const pending = [...roots];
|
|
79
|
+
while (pending.length) {
|
|
80
|
+
const id = pending.pop();
|
|
81
|
+
if (id === undefined || reachable.has(id))
|
|
82
|
+
continue;
|
|
83
|
+
reachable.add(id);
|
|
84
|
+
const node = byId.get(id);
|
|
85
|
+
if (node && !node.closed)
|
|
86
|
+
pending.push(...node.inputs);
|
|
87
|
+
}
|
|
88
|
+
return ordered.filter((node) => reachable.has(node.id));
|
|
89
|
+
}
|
|
90
|
+
function step(budget) {
|
|
91
|
+
if (budget.operations >= MAX_CUT_OPERATIONS)
|
|
92
|
+
throw new CutSearchLimit();
|
|
93
|
+
budget.operations++;
|
|
94
|
+
}
|
|
95
|
+
function prune(candidates, budget) {
|
|
96
|
+
let kept = [];
|
|
97
|
+
for (const candidate of candidates) {
|
|
98
|
+
step(budget);
|
|
99
|
+
const set = new Set(candidate);
|
|
100
|
+
if (kept.some((other) => {
|
|
101
|
+
step(budget);
|
|
102
|
+
return other.every((id) => set.has(id));
|
|
103
|
+
}))
|
|
104
|
+
continue;
|
|
105
|
+
kept = kept.filter((other) => {
|
|
106
|
+
step(budget);
|
|
107
|
+
const existing = new Set(other);
|
|
108
|
+
return !candidate.every((id) => existing.has(id));
|
|
109
|
+
});
|
|
110
|
+
kept.push(candidate);
|
|
111
|
+
if (kept.length > MAX_BLOCKING_CUT_CANDIDATES)
|
|
112
|
+
throw new CutSearchLimit();
|
|
113
|
+
}
|
|
114
|
+
return kept.sort(compareCuts);
|
|
115
|
+
}
|
|
116
|
+
function combineAll(families, budget) {
|
|
117
|
+
let result = [[]];
|
|
118
|
+
for (const family of families)
|
|
119
|
+
result = prune(unions(result, family), budget);
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
function exactCut(nodes, roots, budget) {
|
|
123
|
+
const families = new Map();
|
|
124
|
+
for (const node of nodes) {
|
|
125
|
+
if (node.closed) {
|
|
126
|
+
families.set(node.id, [[]]);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const children = node.inputs.map((id) => families.get(id) ?? []);
|
|
130
|
+
let family = [[]];
|
|
131
|
+
if (children.length) {
|
|
132
|
+
family = node.rule === "all" ? combineAll(children, budget) : prune(alternatives(children), budget);
|
|
133
|
+
}
|
|
134
|
+
if (node.local)
|
|
135
|
+
family = prune(unions(family, [[node.id]]), budget);
|
|
136
|
+
families.set(node.id, family);
|
|
137
|
+
}
|
|
138
|
+
return (combineAll(roots.map((id) => families.get(id) ?? []), budget)[0] ?? []);
|
|
139
|
+
}
|
|
140
|
+
function greedyCut(nodes, roots) {
|
|
141
|
+
const cuts = new Map();
|
|
142
|
+
for (const node of nodes) {
|
|
143
|
+
if (node.closed) {
|
|
144
|
+
cuts.set(node.id, []);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const children = node.inputs.map((id) => cuts.get(id) ?? []);
|
|
148
|
+
const childCut = node.rule === "all" ? union(children) : ([...children].sort(compareCuts)[0] ?? []);
|
|
149
|
+
cuts.set(node.id, node.local ? union([childCut, [node.id]]) : childCut);
|
|
150
|
+
}
|
|
151
|
+
return union(roots.map((id) => cuts.get(id) ?? []));
|
|
152
|
+
}
|
|
153
|
+
/** Prefer this API over the legacy array-only projection when optimality matters. */
|
|
154
|
+
export function explainBlockingCut(claims, evaluations, rootIds) {
|
|
155
|
+
const roots = [...new Set(rootIds)].sort(compareIds);
|
|
156
|
+
for (const id of roots)
|
|
157
|
+
if (!claims.has(id))
|
|
158
|
+
throw new ClaimGraphError("unknown_input", "Unknown blocking root");
|
|
159
|
+
const nodes = reachableNodes(cutNodes(claims, evaluations), roots);
|
|
160
|
+
const budget = { operations: 0 };
|
|
161
|
+
let claimIds;
|
|
162
|
+
let truncated = false;
|
|
163
|
+
try {
|
|
164
|
+
claimIds = exactCut(nodes, roots, budget);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
if (!(error instanceof CutSearchLimit))
|
|
168
|
+
throw error;
|
|
169
|
+
truncated = true;
|
|
170
|
+
claimIds = greedyCut(nodes, roots);
|
|
171
|
+
}
|
|
172
|
+
const selected = new Set(claimIds);
|
|
173
|
+
return Object.freeze({
|
|
174
|
+
claimIds: Object.freeze([...claimIds]),
|
|
175
|
+
algorithm: truncated ? "greedy" : "exact-antichain",
|
|
176
|
+
optimality: truncated ? "not-proven" : "minimum",
|
|
177
|
+
truncated,
|
|
178
|
+
exploredStates: budget.operations,
|
|
179
|
+
localClaimIds: Object.freeze(nodes
|
|
180
|
+
.flatMap((node) => (node.local && node.inputs.length > 0 && selected.has(node.id) ? [node.id] : []))
|
|
181
|
+
.sort(compareIds)),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/** Compatibility name: on a bounded-search fallback this array is not guaranteed minimal. */
|
|
185
|
+
export function minimalBlockingCut(claims, evaluations, rootIds) {
|
|
186
|
+
return explainBlockingCut(claims, evaluations, rootIds).claimIds;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=claim-blocking-cut.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-blocking-cut.js","sourceRoot":"","sources":["../../src/claims/claim-blocking-cut.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bounded blocking explanations for a validated all/any claim DAG.\n * Shared subgraphs require alternative repair sets, not a local cheapest branch.\n * Exact antichains can grow exponentially; bounds fall back to a deterministic,\n * not-proven explanation. Set-operation cost also depends on output-set size.\n * A repair is an explanatory obligation, never permission to satisfy a claim.\n */\nimport { ClaimGraphError, topologicalClaimOrder } from \"./claim-graph.ts\";\nimport {\n\ttype BlockingCutExplanation,\n\tCLAIM_GRAPH_SCHEMA_VERSION,\n\ttype ClaimClosureEvaluation,\n\ttype ClaimNode,\n\ttype ClaimVerdict,\n} from \"./claim-types.ts\";\n\nconst BLOCKING_VERDICTS: readonly ClaimVerdict[] = [\n\t\"violated\",\n\t\"stale\",\n\t\"incomplete_scope\",\n\t\"insufficient_trust\",\n\t\"missing\",\n];\nexport const MAX_BLOCKING_CUT_CANDIDATES = 128;\nconst MAX_CUT_OPERATIONS = 65536;\nclass CutSearchLimit extends Error {}\ntype Cut = readonly string[];\ntype Family = readonly Cut[];\ninterface CutNode {\n\treadonly id: string;\n\treadonly rule: \"all\" | \"any\";\n\treadonly inputs: readonly string[];\n\treadonly closed: boolean;\n\treadonly local: boolean;\n}\n/** Search-owned mutable accounting, including work before a limit is reached. */\ninterface CutSearchBudget {\n\toperations: number;\n}\n\nexport function isBlockingVerdict(verdict: ClaimVerdict): boolean {\n\treturn BLOCKING_VERDICTS.includes(verdict);\n}\nfunction compareIds(left: string, right: string): number {\n\tif (left === right) return 0;\n\treturn left < right ? -1 : 1;\n}\nfunction compareCuts(left: Cut, right: Cut): number {\n\tif (left.length !== right.length) return left.length - right.length;\n\tfor (let i = 0; i < left.length; i++) {\n\t\tconst order = compareIds(left[i], right[i]);\n\t\tif (order) return order;\n\t}\n\treturn 0;\n}\nfunction union(sets: readonly Cut[]): Cut {\n\treturn [...new Set(sets.flat())].sort(compareIds);\n}\nfunction* unions(left: Family, right: Family): Generator<Cut> {\n\tfor (const a of left) for (const b of right) yield union([a, b]);\n}\nfunction* alternatives(families: readonly Family[]): Generator<Cut> {\n\tfor (const family of families) yield* family;\n}\n\nfunction cutNodes(\n\tclaims: ReadonlyMap<string, ClaimNode>,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n): CutNode[] {\n\tconst ordered = topologicalClaimOrder(\n\t\t{ schemaVersion: CLAIM_GRAPH_SCHEMA_VERSION, claims: [...claims.values()] },\n\t\tclaims,\n\t);\n\treturn ordered.map((claim) => {\n\t\tconst evaluation = evaluations.get(claim.claimId);\n\t\tif (\n\t\t\t!evaluation ||\n\t\t\t(!isBlockingVerdict(evaluation.verdict) &&\n\t\t\t\tevaluation.verdict !== \"satisfied\" &&\n\t\t\t\tevaluation.verdict !== \"waived\")\n\t\t) {\n\t\t\tthrow new ClaimGraphError(\"invalid_input\", \"Blocking explanation requires a valid evaluation for every claim\");\n\t\t}\n\t\tconst inputs = claim.satisfaction.inputs.filter((id) => claims.get(id)?.severity === \"required\").sort(compareIds);\n\t\tconst inferredLocal =\n\t\t\t(evaluation.verdict === \"violated\" && evaluation.observationIds.length > 0) ||\n\t\t\t(evaluation.verdict === \"incomplete_scope\" && claim.scopeSensitive === true);\n\t\treturn {\n\t\t\tid: claim.claimId,\n\t\t\trule: claim.satisfaction.rule,\n\t\t\tinputs,\n\t\t\tclosed: !isBlockingVerdict(evaluation.verdict),\n\t\t\tlocal:\n\t\t\t\tinputs.length === 0 ||\n\t\t\t\tevaluation.blockingOrigin === \"local\" ||\n\t\t\t\t(evaluation.blockingOrigin === undefined && inferredLocal),\n\t\t};\n\t});\n}\nfunction reachableNodes(ordered: CutNode[], roots: readonly string[]): CutNode[] {\n\tconst byId = new Map(ordered.map((node) => [node.id, node]));\n\tconst reachable = new Set<string>();\n\tconst pending = [...roots];\n\twhile (pending.length) {\n\t\tconst id = pending.pop();\n\t\tif (id === undefined || reachable.has(id)) continue;\n\t\treachable.add(id);\n\t\tconst node = byId.get(id);\n\t\tif (node && !node.closed) pending.push(...node.inputs);\n\t}\n\treturn ordered.filter((node) => reachable.has(node.id));\n}\nfunction step(budget: CutSearchBudget): void {\n\tif (budget.operations >= MAX_CUT_OPERATIONS) throw new CutSearchLimit();\n\tbudget.operations++;\n}\nfunction prune(candidates: Iterable<Cut>, budget: CutSearchBudget): Family {\n\tlet kept: Cut[] = [];\n\tfor (const candidate of candidates) {\n\t\tstep(budget);\n\t\tconst set = new Set(candidate);\n\t\tif (\n\t\t\tkept.some((other) => {\n\t\t\t\tstep(budget);\n\t\t\t\treturn other.every((id) => set.has(id));\n\t\t\t})\n\t\t)\n\t\t\tcontinue;\n\t\tkept = kept.filter((other) => {\n\t\t\tstep(budget);\n\t\t\tconst existing = new Set(other);\n\t\t\treturn !candidate.every((id) => existing.has(id));\n\t\t});\n\t\tkept.push(candidate);\n\t\tif (kept.length > MAX_BLOCKING_CUT_CANDIDATES) throw new CutSearchLimit();\n\t}\n\treturn kept.sort(compareCuts);\n}\nfunction combineAll(families: readonly Family[], budget: CutSearchBudget): Family {\n\tlet result: Family = [[]];\n\tfor (const family of families) result = prune(unions(result, family), budget);\n\treturn result;\n}\nfunction exactCut(nodes: readonly CutNode[], roots: readonly string[], budget: CutSearchBudget): Cut {\n\tconst families = new Map<string, Family>();\n\tfor (const node of nodes) {\n\t\tif (node.closed) {\n\t\t\tfamilies.set(node.id, [[]]);\n\t\t\tcontinue;\n\t\t}\n\t\tconst children = node.inputs.map((id) => families.get(id) ?? []);\n\t\tlet family: Family = [[]];\n\t\tif (children.length) {\n\t\t\tfamily = node.rule === \"all\" ? combineAll(children, budget) : prune(alternatives(children), budget);\n\t\t}\n\t\tif (node.local) family = prune(unions(family, [[node.id]]), budget);\n\t\tfamilies.set(node.id, family);\n\t}\n\treturn (\n\t\tcombineAll(\n\t\t\troots.map((id) => families.get(id) ?? []),\n\t\t\tbudget,\n\t\t)[0] ?? []\n\t);\n}\nfunction greedyCut(nodes: readonly CutNode[], roots: readonly string[]): Cut {\n\tconst cuts = new Map<string, Cut>();\n\tfor (const node of nodes) {\n\t\tif (node.closed) {\n\t\t\tcuts.set(node.id, []);\n\t\t\tcontinue;\n\t\t}\n\t\tconst children = node.inputs.map((id) => cuts.get(id) ?? []);\n\t\tconst childCut = node.rule === \"all\" ? union(children) : ([...children].sort(compareCuts)[0] ?? []);\n\t\tcuts.set(node.id, node.local ? union([childCut, [node.id]]) : childCut);\n\t}\n\treturn union(roots.map((id) => cuts.get(id) ?? []));\n}\n\n/** Prefer this API over the legacy array-only projection when optimality matters. */\nexport function explainBlockingCut(\n\tclaims: ReadonlyMap<string, ClaimNode>,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n\trootIds: readonly string[],\n): BlockingCutExplanation {\n\tconst roots = [...new Set(rootIds)].sort(compareIds);\n\tfor (const id of roots) if (!claims.has(id)) throw new ClaimGraphError(\"unknown_input\", \"Unknown blocking root\");\n\tconst nodes = reachableNodes(cutNodes(claims, evaluations), roots);\n\tconst budget: CutSearchBudget = { operations: 0 };\n\tlet claimIds: Cut;\n\tlet truncated = false;\n\ttry {\n\t\tclaimIds = exactCut(nodes, roots, budget);\n\t} catch (error) {\n\t\tif (!(error instanceof CutSearchLimit)) throw error;\n\t\ttruncated = true;\n\t\tclaimIds = greedyCut(nodes, roots);\n\t}\n\tconst selected = new Set(claimIds);\n\treturn Object.freeze({\n\t\tclaimIds: Object.freeze([...claimIds]),\n\t\talgorithm: truncated ? \"greedy\" : \"exact-antichain\",\n\t\toptimality: truncated ? \"not-proven\" : \"minimum\",\n\t\ttruncated,\n\t\texploredStates: budget.operations,\n\t\tlocalClaimIds: Object.freeze(\n\t\t\tnodes\n\t\t\t\t.flatMap((node) => (node.local && node.inputs.length > 0 && selected.has(node.id) ? [node.id] : []))\n\t\t\t\t.sort(compareIds),\n\t\t),\n\t});\n}\n\n/** Compatibility name: on a bounded-search fallback this array is not guaranteed minimal. */\nexport function minimalBlockingCut(\n\tclaims: ReadonlyMap<string, ClaimNode>,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n\trootIds: readonly string[],\n): Cut {\n\treturn explainBlockingCut(claims, evaluations, rootIds).claimIds;\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof closure evaluation over a claim graph (plan §5.5).
|
|
3
|
+
*
|
|
4
|
+
* Order of operations, each step pure and deterministic:
|
|
5
|
+
*
|
|
6
|
+
* 1. Drop observations bound to another source root or environment.
|
|
7
|
+
* 2. Classify expired observations and observations missing one of a claim's
|
|
8
|
+
* invalidation keys as stale for that claim.
|
|
9
|
+
* 3. A qualified counterexample decides `violated` before any witness counts.
|
|
10
|
+
* 4. Leaf witnesses below the claim's trust floor never count; distinct
|
|
11
|
+
* independence groups must reach `requiredWitnesses`.
|
|
12
|
+
* 5. Composite claims fold their *required* children (`all` = worst child,
|
|
13
|
+
* `any` = best child) in topological order, so a parent is never read
|
|
14
|
+
* before its inputs. Advisory children are reported but never fold into a
|
|
15
|
+
* parent, and a composite with no required children is witnessed like a leaf.
|
|
16
|
+
* 6. A scope-sensitive claim is blocked while the workspace witness is not
|
|
17
|
+
* complete; an unresolved effect blocks the whole closure.
|
|
18
|
+
* 7. A waiver removes an unclosed claim from the required set. It never
|
|
19
|
+
* removes a violated one: a counterexample beats a waiver.
|
|
20
|
+
*
|
|
21
|
+
* The public verdict is decided by the required *roots* (claims nobody lists
|
|
22
|
+
* as an input): `verified` only when every required root is closed, no effect
|
|
23
|
+
* is unresolved, and the workspace witness is complete. A required leaf under
|
|
24
|
+
* an `any` parent therefore blocks nothing once a sibling branch closes — that
|
|
25
|
+
* is what `any` means — while `blockingClaimIds` still lists every required
|
|
26
|
+
* claim on a blocking path so the cause is explainable.
|
|
27
|
+
*/
|
|
28
|
+
import { type ProofClosureInput, type ProofClosureResult } from "./claim-types.ts";
|
|
29
|
+
export declare function evaluateProofClosure(input: ProofClosureInput): ProofClosureResult;
|
|
30
|
+
//# sourceMappingURL=claim-closure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-closure.d.ts","sourceRoot":"","sources":["../../src/claims/claim-closure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,EAQN,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EAIvB,MAAM,kBAAkB,CAAC;AAmK1B,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,kBAAkB,CAmEjF","sourcesContent":["/**\n * Proof closure evaluation over a claim graph (plan §5.5).\n *\n * Order of operations, each step pure and deterministic:\n *\n * 1. Drop observations bound to another source root or environment.\n * 2. Classify expired observations and observations missing one of a claim's\n * invalidation keys as stale for that claim.\n * 3. A qualified counterexample decides `violated` before any witness counts.\n * 4. Leaf witnesses below the claim's trust floor never count; distinct\n * independence groups must reach `requiredWitnesses`.\n * 5. Composite claims fold their *required* children (`all` = worst child,\n * `any` = best child) in topological order, so a parent is never read\n * before its inputs. Advisory children are reported but never fold into a\n * parent, and a composite with no required children is witnessed like a leaf.\n * 6. A scope-sensitive claim is blocked while the workspace witness is not\n * complete; an unresolved effect blocks the whole closure.\n * 7. A waiver removes an unclosed claim from the required set. It never\n * removes a violated one: a counterexample beats a waiver.\n *\n * The public verdict is decided by the required *roots* (claims nobody lists\n * as an input): `verified` only when every required root is closed, no effect\n * is unresolved, and the workspace witness is complete. A required leaf under\n * an `any` parent therefore blocks nothing once a sibling branch closes — that\n * is what `any` means — while `blockingClaimIds` still lists every required\n * claim on a blocking path so the cause is explainable.\n */\n\nimport { explainBlockingCut, isBlockingVerdict } from \"./claim-blocking-cut.ts\";\nimport { ClaimGraphError, rootClaimIds, topologicalClaimOrder, validateClaimGraph } from \"./claim-graph.ts\";\nimport {\n\tCLAIM_VERDICT_PRECEDENCE,\n\ttype ClaimClosureEvaluation,\n\ttype ClaimNode,\n\ttype ClaimReasonCode,\n\ttype ClaimVerdict,\n\tOBSERVATION_TRUST_RANK,\n\ttype ObservationNode,\n\ttype ProofClosureInput,\n\ttype ProofClosureResult,\n\ttype VerificationVerdict,\n\ttype WaiverNode,\n\ttype WitnessIndependencePolicy,\n} from \"./claim-types.ts\";\n\nconst ISO_TIMESTAMP = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/;\n\ninterface Witnesses {\n\treadonly supporting: ObservationNode[];\n\treadonly violating: ObservationNode[];\n\treadonly stale: ObservationNode[];\n}\n\nfunction compareCodeUnits(left: string, right: string): number {\n\tif (left === right) return 0;\n\treturn left < right ? -1 : 1;\n}\n\nfunction requireTimestamp(value: string, label: string): string {\n\tif (!ISO_TIMESTAMP.test(value))\n\t\tthrow new ClaimGraphError(\"invalid_input\", `${label} must be an ISO-8601 UTC instant`);\n\treturn value;\n}\n\nfunction reasonFor(verdict: ClaimVerdict): ClaimReasonCode {\n\treturn `claim.${verdict}` as ClaimReasonCode;\n}\n\nfunction worst(verdicts: readonly ClaimVerdict[]): ClaimVerdict {\n\treturn [...verdicts].sort((a, b) => CLAIM_VERDICT_PRECEDENCE.indexOf(a) - CLAIM_VERDICT_PRECEDENCE.indexOf(b))[0];\n}\n\nfunction best(verdicts: readonly ClaimVerdict[]): ClaimVerdict {\n\treturn [...verdicts].sort((a, b) => CLAIM_VERDICT_PRECEDENCE.indexOf(b) - CLAIM_VERDICT_PRECEDENCE.indexOf(a))[0];\n}\n\n/** Steps 1–2: bind observations to claims, dropping foreign ones and marking stale ones. */\nfunction indexWitnesses(input: ProofClosureInput, claims: ReadonlyMap<string, ClaimNode>): Map<string, Witnesses> {\n\tconst index = new Map<string, Witnesses>();\n\tfor (const claim of claims.values()) index.set(claim.claimId, { supporting: [], violating: [], stale: [] });\n\tfor (const observation of input.observations) {\n\t\tif (observation.sourceRoot !== input.sourceRoot || observation.environmentDigest !== input.environmentDigest)\n\t\t\tcontinue;\n\t\tconst expired =\n\t\t\tobservation.validUntil !== undefined && requireTimestamp(observation.validUntil, \"validUntil\") <= input.now;\n\t\tconst attested = observation.invalidationKeys ?? [];\n\t\tfor (const claimId of observation.claimIds) {\n\t\t\tconst claim = claims.get(claimId);\n\t\t\tconst witnesses = index.get(claimId);\n\t\t\tif (claim === undefined || witnesses === undefined) continue;\n\t\t\tconst missingKey = claim.invalidationKeys.some((key) => !attested.includes(key));\n\t\t\tif (expired || missingKey) witnesses.stale.push(observation);\n\t\t\telse if (observation.polarity === \"violates\") witnesses.violating.push(observation);\n\t\t\telse witnesses.supporting.push(observation);\n\t\t}\n\t}\n\treturn index;\n}\n\nfunction ids(observations: readonly ObservationNode[]): readonly string[] {\n\treturn observations.map((observation) => observation.observationId).sort(compareCodeUnits);\n}\n\n/** Steps 3–4 for a leaf claim. */\nfunction evaluateLeaf(\n\tclaim: ClaimNode,\n\twitnesses: Witnesses,\n\tindependence: WitnessIndependencePolicy,\n): { verdict: ClaimVerdict; observationIds: readonly string[] } {\n\tif (witnesses.violating.length > 0) return { verdict: \"violated\", observationIds: ids(witnesses.violating) };\n\tconst floor = OBSERVATION_TRUST_RANK[claim.trustFloor];\n\tconst trusted = witnesses.supporting.filter((observation) => OBSERVATION_TRUST_RANK[observation.source] >= floor);\n\tconst requiredWitnesses = claim.requiredWitnesses ?? 1;\n\tconst attributable = trusted.filter(\n\t\t(observation) =>\n\t\t\tindependence === \"legacy-observation-id\" ||\n\t\t\trequiredWitnesses === 1 ||\n\t\t\tBoolean(observation.independenceGroup?.trim()),\n\t);\n\tconst groups = new Set(\n\t\tattributable.map(\n\t\t\t(observation) =>\n\t\t\t\tobservation.independenceGroup ??\n\t\t\t\t(independence === \"legacy-observation-id\" ? observation.observationId : undefined),\n\t\t),\n\t);\n\tif (groups.size >= requiredWitnesses) return { verdict: \"satisfied\", observationIds: ids(trusted) };\n\tif (trusted.length > 0) return { verdict: \"missing\", observationIds: ids(trusted) };\n\tif (witnesses.supporting.length > 0)\n\t\treturn { verdict: \"insufficient_trust\", observationIds: ids(witnesses.supporting) };\n\tif (witnesses.stale.length > 0) return { verdict: \"stale\", observationIds: ids(witnesses.stale) };\n\treturn { verdict: \"missing\", observationIds: [] };\n}\n\n/** Inputs that take part in a parent's fold: advisory children are informational only. */\nfunction requiredInputs(claim: ClaimNode, claims: ReadonlyMap<string, ClaimNode>): readonly string[] {\n\treturn claim.satisfaction.inputs.filter((input) => claims.get(input)?.severity === \"required\");\n}\n\nfunction isClosed(verdict: ClaimVerdict): boolean {\n\treturn verdict === \"satisfied\" || verdict === \"waived\";\n}\n\n/** Step 5 for a composite claim; its required children are already evaluated. */\nfunction evaluateComposite(\n\tclaim: ClaimNode,\n\tinputs: readonly string[],\n\twitnesses: Witnesses,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n): { verdict: ClaimVerdict; observationIds: readonly string[] } {\n\tif (witnesses.violating.length > 0) return { verdict: \"violated\", observationIds: ids(witnesses.violating) };\n\tconst childVerdicts = inputs.map((input) => evaluations.get(input)?.verdict ?? \"missing\");\n\tif (claim.satisfaction.rule === \"all\") {\n\t\tconst open = childVerdicts.filter((verdict) => !isClosed(verdict));\n\t\treturn { verdict: open.length === 0 ? \"satisfied\" : worst(open), observationIds: [] };\n\t}\n\tif (childVerdicts.some(isClosed)) return { verdict: \"satisfied\", observationIds: [] };\n\treturn { verdict: best(childVerdicts), observationIds: [] };\n}\n\n/** Step 7: the lexically first valid waiver for a claim, or none. */\nfunction indexWaivers(input: ProofClosureInput, claims: ReadonlyMap<string, ClaimNode>): Map<string, WaiverNode> {\n\tconst byClaim = new Map<string, WaiverNode>();\n\tfor (const waiver of [...input.waivers].sort((a, b) => compareCodeUnits(a.waiverId, b.waiverId))) {\n\t\tif (!claims.has(waiver.claimId))\n\t\t\tthrow new ClaimGraphError(\"unknown_input\", `${waiver.waiverId} waives unknown ${waiver.claimId}`);\n\t\tif (waiver.sourceRoot !== input.sourceRoot) continue;\n\t\tif (requireTimestamp(waiver.expiresAt, \"expiresAt\") <= input.now) continue;\n\t\tif (!byClaim.has(waiver.claimId)) byClaim.set(waiver.claimId, waiver);\n\t}\n\treturn byClaim;\n}\n\n/** Required claims on a blocking path from a blocking required root, in graph order. */\nfunction blockingPathClaimIds(\n\tclaims: ReadonlyMap<string, ClaimNode>,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n\tblockingRoots: readonly string[],\n\tgraphOrder: readonly string[],\n): readonly string[] {\n\tconst onPath = new Set<string>();\n\tconst visit = (claimId: string): void => {\n\t\tif (onPath.has(claimId)) return;\n\t\tconst claim = claims.get(claimId);\n\t\tconst verdict = evaluations.get(claimId)?.verdict;\n\t\tif (claim === undefined || verdict === undefined || !isBlockingVerdict(verdict)) return;\n\t\tonPath.add(claimId);\n\t\tfor (const input of requiredInputs(claim, claims)) visit(input);\n\t};\n\tfor (const root of blockingRoots) visit(root);\n\treturn graphOrder.filter((claimId) => onPath.has(claimId));\n}\n\nfunction globalVerdict(\n\trequiredRoots: readonly ClaimClosureEvaluation[],\n\tinput: ProofClosureInput,\n): VerificationVerdict {\n\tif (requiredRoots.length === 0) return \"unverified\";\n\tif (requiredRoots.some((root) => root.verdict === \"violated\")) return \"violated\";\n\tconst closure =\n\t\trequiredRoots.every((root) => isClosed(root.verdict)) &&\n\t\tinput.unresolvedEffectIds.length === 0 &&\n\t\tinput.workspaceCompleteness === \"complete\";\n\treturn closure ? \"verified\" : \"inconclusive\";\n}\n\nexport function evaluateProofClosure(input: ProofClosureInput): ProofClosureResult {\n\tconst claims = validateClaimGraph(input.graph);\n\tconst witnessIndependence =\n\t\tinput.witnessIndependence === undefined ? \"legacy-observation-id\" : input.witnessIndependence;\n\tif (witnessIndependence !== \"legacy-observation-id\" && witnessIndependence !== \"explicit-groups\") {\n\t\tthrow new ClaimGraphError(\"invalid_input\", \"Unknown witness independence policy\");\n\t}\n\trequireTimestamp(input.now, \"now\");\n\tconst witnesses = indexWitnesses(input, claims);\n\tconst waivers = indexWaivers(input, claims);\n\tconst evaluations = new Map<string, ClaimClosureEvaluation>();\n\tfor (const claim of topologicalClaimOrder(input.graph, claims)) {\n\t\tconst claimWitnesses = witnesses.get(claim.claimId) as Witnesses;\n\t\tconst inputs = requiredInputs(claim, claims);\n\t\tconst local =\n\t\t\tinputs.length === 0\n\t\t\t\t? evaluateLeaf(claim, claimWitnesses, witnessIndependence)\n\t\t\t\t: evaluateComposite(claim, inputs, claimWitnesses, evaluations);\n\t\tlet verdict = local.verdict;\n\t\tif (verdict !== \"violated\" && claim.scopeSensitive === true && input.workspaceCompleteness !== \"complete\") {\n\t\t\tverdict = \"incomplete_scope\";\n\t\t}\n\t\tconst waiver = waivers.get(claim.claimId);\n\t\tconst waived = waiver !== undefined && verdict !== \"violated\" && verdict !== \"satisfied\";\n\t\tif (waived) verdict = \"waived\";\n\t\tevaluations.set(\n\t\t\tclaim.claimId,\n\t\t\tObject.freeze({\n\t\t\t\tclaimId: claim.claimId,\n\t\t\t\tseverity: claim.severity,\n\t\t\t\tverdict,\n\t\t\t\treasonCode: reasonFor(verdict),\n\t\t\t\tobservationIds: Object.freeze([...local.observationIds]),\n\t\t\t\t...(waived ? { waiverId: waiver.waiverId } : {}),\n\t\t\t\t...(isBlockingVerdict(verdict)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tblockingOrigin:\n\t\t\t\t\t\t\t\tinputs.length === 0 ||\n\t\t\t\t\t\t\t\tclaimWitnesses.violating.length > 0 ||\n\t\t\t\t\t\t\t\t(claim.scopeSensitive === true && input.workspaceCompleteness !== \"complete\")\n\t\t\t\t\t\t\t\t\t? (\"local\" as const)\n\t\t\t\t\t\t\t\t\t: (\"children\" as const),\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t}),\n\t\t);\n\t}\n\tconst graphOrder = input.graph.claims.map((claim) => claim.claimId);\n\tconst ordered = graphOrder.map((claimId) => evaluations.get(claimId) as ClaimClosureEvaluation);\n\tconst requiredRoots = rootClaimIds(input.graph)\n\t\t.map((claimId) => evaluations.get(claimId) as ClaimClosureEvaluation)\n\t\t.filter((evaluation) => evaluation.severity === \"required\");\n\tconst blockingRoots = requiredRoots\n\t\t.filter((evaluation) => isBlockingVerdict(evaluation.verdict))\n\t\t.map((evaluation) => evaluation.claimId);\n\tconst blockingClaimIds = blockingPathClaimIds(claims, evaluations, blockingRoots, graphOrder);\n\tconst blockingCut = explainBlockingCut(claims, evaluations, blockingRoots);\n\treturn Object.freeze({\n\t\tverdict: globalVerdict(requiredRoots, input),\n\t\twitnessIndependence,\n\t\tclaimEvaluations: Object.freeze(ordered),\n\t\tblockingClaimIds: Object.freeze(blockingClaimIds),\n\t\tminimalBlockingCut: blockingCut.claimIds,\n\t\tblockingCut,\n\t\tunresolvedEffectIds: Object.freeze([...input.unresolvedEffectIds].sort(compareCodeUnits)),\n\t\tworkspaceCompleteness: input.workspaceCompleteness,\n\t});\n}\n"]}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof closure evaluation over a claim graph (plan §5.5).
|
|
3
|
+
*
|
|
4
|
+
* Order of operations, each step pure and deterministic:
|
|
5
|
+
*
|
|
6
|
+
* 1. Drop observations bound to another source root or environment.
|
|
7
|
+
* 2. Classify expired observations and observations missing one of a claim's
|
|
8
|
+
* invalidation keys as stale for that claim.
|
|
9
|
+
* 3. A qualified counterexample decides `violated` before any witness counts.
|
|
10
|
+
* 4. Leaf witnesses below the claim's trust floor never count; distinct
|
|
11
|
+
* independence groups must reach `requiredWitnesses`.
|
|
12
|
+
* 5. Composite claims fold their *required* children (`all` = worst child,
|
|
13
|
+
* `any` = best child) in topological order, so a parent is never read
|
|
14
|
+
* before its inputs. Advisory children are reported but never fold into a
|
|
15
|
+
* parent, and a composite with no required children is witnessed like a leaf.
|
|
16
|
+
* 6. A scope-sensitive claim is blocked while the workspace witness is not
|
|
17
|
+
* complete; an unresolved effect blocks the whole closure.
|
|
18
|
+
* 7. A waiver removes an unclosed claim from the required set. It never
|
|
19
|
+
* removes a violated one: a counterexample beats a waiver.
|
|
20
|
+
*
|
|
21
|
+
* The public verdict is decided by the required *roots* (claims nobody lists
|
|
22
|
+
* as an input): `verified` only when every required root is closed, no effect
|
|
23
|
+
* is unresolved, and the workspace witness is complete. A required leaf under
|
|
24
|
+
* an `any` parent therefore blocks nothing once a sibling branch closes — that
|
|
25
|
+
* is what `any` means — while `blockingClaimIds` still lists every required
|
|
26
|
+
* claim on a blocking path so the cause is explainable.
|
|
27
|
+
*/
|
|
28
|
+
import { explainBlockingCut, isBlockingVerdict } from "./claim-blocking-cut.js";
|
|
29
|
+
import { ClaimGraphError, rootClaimIds, topologicalClaimOrder, validateClaimGraph } from "./claim-graph.js";
|
|
30
|
+
import { CLAIM_VERDICT_PRECEDENCE, OBSERVATION_TRUST_RANK, } from "./claim-types.js";
|
|
31
|
+
const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
32
|
+
function compareCodeUnits(left, right) {
|
|
33
|
+
if (left === right)
|
|
34
|
+
return 0;
|
|
35
|
+
return left < right ? -1 : 1;
|
|
36
|
+
}
|
|
37
|
+
function requireTimestamp(value, label) {
|
|
38
|
+
if (!ISO_TIMESTAMP.test(value))
|
|
39
|
+
throw new ClaimGraphError("invalid_input", `${label} must be an ISO-8601 UTC instant`);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function reasonFor(verdict) {
|
|
43
|
+
return `claim.${verdict}`;
|
|
44
|
+
}
|
|
45
|
+
function worst(verdicts) {
|
|
46
|
+
return [...verdicts].sort((a, b) => CLAIM_VERDICT_PRECEDENCE.indexOf(a) - CLAIM_VERDICT_PRECEDENCE.indexOf(b))[0];
|
|
47
|
+
}
|
|
48
|
+
function best(verdicts) {
|
|
49
|
+
return [...verdicts].sort((a, b) => CLAIM_VERDICT_PRECEDENCE.indexOf(b) - CLAIM_VERDICT_PRECEDENCE.indexOf(a))[0];
|
|
50
|
+
}
|
|
51
|
+
/** Steps 1–2: bind observations to claims, dropping foreign ones and marking stale ones. */
|
|
52
|
+
function indexWitnesses(input, claims) {
|
|
53
|
+
const index = new Map();
|
|
54
|
+
for (const claim of claims.values())
|
|
55
|
+
index.set(claim.claimId, { supporting: [], violating: [], stale: [] });
|
|
56
|
+
for (const observation of input.observations) {
|
|
57
|
+
if (observation.sourceRoot !== input.sourceRoot || observation.environmentDigest !== input.environmentDigest)
|
|
58
|
+
continue;
|
|
59
|
+
const expired = observation.validUntil !== undefined && requireTimestamp(observation.validUntil, "validUntil") <= input.now;
|
|
60
|
+
const attested = observation.invalidationKeys ?? [];
|
|
61
|
+
for (const claimId of observation.claimIds) {
|
|
62
|
+
const claim = claims.get(claimId);
|
|
63
|
+
const witnesses = index.get(claimId);
|
|
64
|
+
if (claim === undefined || witnesses === undefined)
|
|
65
|
+
continue;
|
|
66
|
+
const missingKey = claim.invalidationKeys.some((key) => !attested.includes(key));
|
|
67
|
+
if (expired || missingKey)
|
|
68
|
+
witnesses.stale.push(observation);
|
|
69
|
+
else if (observation.polarity === "violates")
|
|
70
|
+
witnesses.violating.push(observation);
|
|
71
|
+
else
|
|
72
|
+
witnesses.supporting.push(observation);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return index;
|
|
76
|
+
}
|
|
77
|
+
function ids(observations) {
|
|
78
|
+
return observations.map((observation) => observation.observationId).sort(compareCodeUnits);
|
|
79
|
+
}
|
|
80
|
+
/** Steps 3–4 for a leaf claim. */
|
|
81
|
+
function evaluateLeaf(claim, witnesses, independence) {
|
|
82
|
+
if (witnesses.violating.length > 0)
|
|
83
|
+
return { verdict: "violated", observationIds: ids(witnesses.violating) };
|
|
84
|
+
const floor = OBSERVATION_TRUST_RANK[claim.trustFloor];
|
|
85
|
+
const trusted = witnesses.supporting.filter((observation) => OBSERVATION_TRUST_RANK[observation.source] >= floor);
|
|
86
|
+
const requiredWitnesses = claim.requiredWitnesses ?? 1;
|
|
87
|
+
const attributable = trusted.filter((observation) => independence === "legacy-observation-id" ||
|
|
88
|
+
requiredWitnesses === 1 ||
|
|
89
|
+
Boolean(observation.independenceGroup?.trim()));
|
|
90
|
+
const groups = new Set(attributable.map((observation) => observation.independenceGroup ??
|
|
91
|
+
(independence === "legacy-observation-id" ? observation.observationId : undefined)));
|
|
92
|
+
if (groups.size >= requiredWitnesses)
|
|
93
|
+
return { verdict: "satisfied", observationIds: ids(trusted) };
|
|
94
|
+
if (trusted.length > 0)
|
|
95
|
+
return { verdict: "missing", observationIds: ids(trusted) };
|
|
96
|
+
if (witnesses.supporting.length > 0)
|
|
97
|
+
return { verdict: "insufficient_trust", observationIds: ids(witnesses.supporting) };
|
|
98
|
+
if (witnesses.stale.length > 0)
|
|
99
|
+
return { verdict: "stale", observationIds: ids(witnesses.stale) };
|
|
100
|
+
return { verdict: "missing", observationIds: [] };
|
|
101
|
+
}
|
|
102
|
+
/** Inputs that take part in a parent's fold: advisory children are informational only. */
|
|
103
|
+
function requiredInputs(claim, claims) {
|
|
104
|
+
return claim.satisfaction.inputs.filter((input) => claims.get(input)?.severity === "required");
|
|
105
|
+
}
|
|
106
|
+
function isClosed(verdict) {
|
|
107
|
+
return verdict === "satisfied" || verdict === "waived";
|
|
108
|
+
}
|
|
109
|
+
/** Step 5 for a composite claim; its required children are already evaluated. */
|
|
110
|
+
function evaluateComposite(claim, inputs, witnesses, evaluations) {
|
|
111
|
+
if (witnesses.violating.length > 0)
|
|
112
|
+
return { verdict: "violated", observationIds: ids(witnesses.violating) };
|
|
113
|
+
const childVerdicts = inputs.map((input) => evaluations.get(input)?.verdict ?? "missing");
|
|
114
|
+
if (claim.satisfaction.rule === "all") {
|
|
115
|
+
const open = childVerdicts.filter((verdict) => !isClosed(verdict));
|
|
116
|
+
return { verdict: open.length === 0 ? "satisfied" : worst(open), observationIds: [] };
|
|
117
|
+
}
|
|
118
|
+
if (childVerdicts.some(isClosed))
|
|
119
|
+
return { verdict: "satisfied", observationIds: [] };
|
|
120
|
+
return { verdict: best(childVerdicts), observationIds: [] };
|
|
121
|
+
}
|
|
122
|
+
/** Step 7: the lexically first valid waiver for a claim, or none. */
|
|
123
|
+
function indexWaivers(input, claims) {
|
|
124
|
+
const byClaim = new Map();
|
|
125
|
+
for (const waiver of [...input.waivers].sort((a, b) => compareCodeUnits(a.waiverId, b.waiverId))) {
|
|
126
|
+
if (!claims.has(waiver.claimId))
|
|
127
|
+
throw new ClaimGraphError("unknown_input", `${waiver.waiverId} waives unknown ${waiver.claimId}`);
|
|
128
|
+
if (waiver.sourceRoot !== input.sourceRoot)
|
|
129
|
+
continue;
|
|
130
|
+
if (requireTimestamp(waiver.expiresAt, "expiresAt") <= input.now)
|
|
131
|
+
continue;
|
|
132
|
+
if (!byClaim.has(waiver.claimId))
|
|
133
|
+
byClaim.set(waiver.claimId, waiver);
|
|
134
|
+
}
|
|
135
|
+
return byClaim;
|
|
136
|
+
}
|
|
137
|
+
/** Required claims on a blocking path from a blocking required root, in graph order. */
|
|
138
|
+
function blockingPathClaimIds(claims, evaluations, blockingRoots, graphOrder) {
|
|
139
|
+
const onPath = new Set();
|
|
140
|
+
const visit = (claimId) => {
|
|
141
|
+
if (onPath.has(claimId))
|
|
142
|
+
return;
|
|
143
|
+
const claim = claims.get(claimId);
|
|
144
|
+
const verdict = evaluations.get(claimId)?.verdict;
|
|
145
|
+
if (claim === undefined || verdict === undefined || !isBlockingVerdict(verdict))
|
|
146
|
+
return;
|
|
147
|
+
onPath.add(claimId);
|
|
148
|
+
for (const input of requiredInputs(claim, claims))
|
|
149
|
+
visit(input);
|
|
150
|
+
};
|
|
151
|
+
for (const root of blockingRoots)
|
|
152
|
+
visit(root);
|
|
153
|
+
return graphOrder.filter((claimId) => onPath.has(claimId));
|
|
154
|
+
}
|
|
155
|
+
function globalVerdict(requiredRoots, input) {
|
|
156
|
+
if (requiredRoots.length === 0)
|
|
157
|
+
return "unverified";
|
|
158
|
+
if (requiredRoots.some((root) => root.verdict === "violated"))
|
|
159
|
+
return "violated";
|
|
160
|
+
const closure = requiredRoots.every((root) => isClosed(root.verdict)) &&
|
|
161
|
+
input.unresolvedEffectIds.length === 0 &&
|
|
162
|
+
input.workspaceCompleteness === "complete";
|
|
163
|
+
return closure ? "verified" : "inconclusive";
|
|
164
|
+
}
|
|
165
|
+
export function evaluateProofClosure(input) {
|
|
166
|
+
const claims = validateClaimGraph(input.graph);
|
|
167
|
+
const witnessIndependence = input.witnessIndependence === undefined ? "legacy-observation-id" : input.witnessIndependence;
|
|
168
|
+
if (witnessIndependence !== "legacy-observation-id" && witnessIndependence !== "explicit-groups") {
|
|
169
|
+
throw new ClaimGraphError("invalid_input", "Unknown witness independence policy");
|
|
170
|
+
}
|
|
171
|
+
requireTimestamp(input.now, "now");
|
|
172
|
+
const witnesses = indexWitnesses(input, claims);
|
|
173
|
+
const waivers = indexWaivers(input, claims);
|
|
174
|
+
const evaluations = new Map();
|
|
175
|
+
for (const claim of topologicalClaimOrder(input.graph, claims)) {
|
|
176
|
+
const claimWitnesses = witnesses.get(claim.claimId);
|
|
177
|
+
const inputs = requiredInputs(claim, claims);
|
|
178
|
+
const local = inputs.length === 0
|
|
179
|
+
? evaluateLeaf(claim, claimWitnesses, witnessIndependence)
|
|
180
|
+
: evaluateComposite(claim, inputs, claimWitnesses, evaluations);
|
|
181
|
+
let verdict = local.verdict;
|
|
182
|
+
if (verdict !== "violated" && claim.scopeSensitive === true && input.workspaceCompleteness !== "complete") {
|
|
183
|
+
verdict = "incomplete_scope";
|
|
184
|
+
}
|
|
185
|
+
const waiver = waivers.get(claim.claimId);
|
|
186
|
+
const waived = waiver !== undefined && verdict !== "violated" && verdict !== "satisfied";
|
|
187
|
+
if (waived)
|
|
188
|
+
verdict = "waived";
|
|
189
|
+
evaluations.set(claim.claimId, Object.freeze({
|
|
190
|
+
claimId: claim.claimId,
|
|
191
|
+
severity: claim.severity,
|
|
192
|
+
verdict,
|
|
193
|
+
reasonCode: reasonFor(verdict),
|
|
194
|
+
observationIds: Object.freeze([...local.observationIds]),
|
|
195
|
+
...(waived ? { waiverId: waiver.waiverId } : {}),
|
|
196
|
+
...(isBlockingVerdict(verdict)
|
|
197
|
+
? {
|
|
198
|
+
blockingOrigin: inputs.length === 0 ||
|
|
199
|
+
claimWitnesses.violating.length > 0 ||
|
|
200
|
+
(claim.scopeSensitive === true && input.workspaceCompleteness !== "complete")
|
|
201
|
+
? "local"
|
|
202
|
+
: "children",
|
|
203
|
+
}
|
|
204
|
+
: {}),
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
const graphOrder = input.graph.claims.map((claim) => claim.claimId);
|
|
208
|
+
const ordered = graphOrder.map((claimId) => evaluations.get(claimId));
|
|
209
|
+
const requiredRoots = rootClaimIds(input.graph)
|
|
210
|
+
.map((claimId) => evaluations.get(claimId))
|
|
211
|
+
.filter((evaluation) => evaluation.severity === "required");
|
|
212
|
+
const blockingRoots = requiredRoots
|
|
213
|
+
.filter((evaluation) => isBlockingVerdict(evaluation.verdict))
|
|
214
|
+
.map((evaluation) => evaluation.claimId);
|
|
215
|
+
const blockingClaimIds = blockingPathClaimIds(claims, evaluations, blockingRoots, graphOrder);
|
|
216
|
+
const blockingCut = explainBlockingCut(claims, evaluations, blockingRoots);
|
|
217
|
+
return Object.freeze({
|
|
218
|
+
verdict: globalVerdict(requiredRoots, input),
|
|
219
|
+
witnessIndependence,
|
|
220
|
+
claimEvaluations: Object.freeze(ordered),
|
|
221
|
+
blockingClaimIds: Object.freeze(blockingClaimIds),
|
|
222
|
+
minimalBlockingCut: blockingCut.claimIds,
|
|
223
|
+
blockingCut,
|
|
224
|
+
unresolvedEffectIds: Object.freeze([...input.unresolvedEffectIds].sort(compareCodeUnits)),
|
|
225
|
+
workspaceCompleteness: input.workspaceCompleteness,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=claim-closure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-closure.js","sourceRoot":"","sources":["../../src/claims/claim-closure.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Proof closure evaluation over a claim graph (plan §5.5).\n *\n * Order of operations, each step pure and deterministic:\n *\n * 1. Drop observations bound to another source root or environment.\n * 2. Classify expired observations and observations missing one of a claim's\n * invalidation keys as stale for that claim.\n * 3. A qualified counterexample decides `violated` before any witness counts.\n * 4. Leaf witnesses below the claim's trust floor never count; distinct\n * independence groups must reach `requiredWitnesses`.\n * 5. Composite claims fold their *required* children (`all` = worst child,\n * `any` = best child) in topological order, so a parent is never read\n * before its inputs. Advisory children are reported but never fold into a\n * parent, and a composite with no required children is witnessed like a leaf.\n * 6. A scope-sensitive claim is blocked while the workspace witness is not\n * complete; an unresolved effect blocks the whole closure.\n * 7. A waiver removes an unclosed claim from the required set. It never\n * removes a violated one: a counterexample beats a waiver.\n *\n * The public verdict is decided by the required *roots* (claims nobody lists\n * as an input): `verified` only when every required root is closed, no effect\n * is unresolved, and the workspace witness is complete. A required leaf under\n * an `any` parent therefore blocks nothing once a sibling branch closes — that\n * is what `any` means — while `blockingClaimIds` still lists every required\n * claim on a blocking path so the cause is explainable.\n */\n\nimport { explainBlockingCut, isBlockingVerdict } from \"./claim-blocking-cut.ts\";\nimport { ClaimGraphError, rootClaimIds, topologicalClaimOrder, validateClaimGraph } from \"./claim-graph.ts\";\nimport {\n\tCLAIM_VERDICT_PRECEDENCE,\n\ttype ClaimClosureEvaluation,\n\ttype ClaimNode,\n\ttype ClaimReasonCode,\n\ttype ClaimVerdict,\n\tOBSERVATION_TRUST_RANK,\n\ttype ObservationNode,\n\ttype ProofClosureInput,\n\ttype ProofClosureResult,\n\ttype VerificationVerdict,\n\ttype WaiverNode,\n\ttype WitnessIndependencePolicy,\n} from \"./claim-types.ts\";\n\nconst ISO_TIMESTAMP = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/;\n\ninterface Witnesses {\n\treadonly supporting: ObservationNode[];\n\treadonly violating: ObservationNode[];\n\treadonly stale: ObservationNode[];\n}\n\nfunction compareCodeUnits(left: string, right: string): number {\n\tif (left === right) return 0;\n\treturn left < right ? -1 : 1;\n}\n\nfunction requireTimestamp(value: string, label: string): string {\n\tif (!ISO_TIMESTAMP.test(value))\n\t\tthrow new ClaimGraphError(\"invalid_input\", `${label} must be an ISO-8601 UTC instant`);\n\treturn value;\n}\n\nfunction reasonFor(verdict: ClaimVerdict): ClaimReasonCode {\n\treturn `claim.${verdict}` as ClaimReasonCode;\n}\n\nfunction worst(verdicts: readonly ClaimVerdict[]): ClaimVerdict {\n\treturn [...verdicts].sort((a, b) => CLAIM_VERDICT_PRECEDENCE.indexOf(a) - CLAIM_VERDICT_PRECEDENCE.indexOf(b))[0];\n}\n\nfunction best(verdicts: readonly ClaimVerdict[]): ClaimVerdict {\n\treturn [...verdicts].sort((a, b) => CLAIM_VERDICT_PRECEDENCE.indexOf(b) - CLAIM_VERDICT_PRECEDENCE.indexOf(a))[0];\n}\n\n/** Steps 1–2: bind observations to claims, dropping foreign ones and marking stale ones. */\nfunction indexWitnesses(input: ProofClosureInput, claims: ReadonlyMap<string, ClaimNode>): Map<string, Witnesses> {\n\tconst index = new Map<string, Witnesses>();\n\tfor (const claim of claims.values()) index.set(claim.claimId, { supporting: [], violating: [], stale: [] });\n\tfor (const observation of input.observations) {\n\t\tif (observation.sourceRoot !== input.sourceRoot || observation.environmentDigest !== input.environmentDigest)\n\t\t\tcontinue;\n\t\tconst expired =\n\t\t\tobservation.validUntil !== undefined && requireTimestamp(observation.validUntil, \"validUntil\") <= input.now;\n\t\tconst attested = observation.invalidationKeys ?? [];\n\t\tfor (const claimId of observation.claimIds) {\n\t\t\tconst claim = claims.get(claimId);\n\t\t\tconst witnesses = index.get(claimId);\n\t\t\tif (claim === undefined || witnesses === undefined) continue;\n\t\t\tconst missingKey = claim.invalidationKeys.some((key) => !attested.includes(key));\n\t\t\tif (expired || missingKey) witnesses.stale.push(observation);\n\t\t\telse if (observation.polarity === \"violates\") witnesses.violating.push(observation);\n\t\t\telse witnesses.supporting.push(observation);\n\t\t}\n\t}\n\treturn index;\n}\n\nfunction ids(observations: readonly ObservationNode[]): readonly string[] {\n\treturn observations.map((observation) => observation.observationId).sort(compareCodeUnits);\n}\n\n/** Steps 3–4 for a leaf claim. */\nfunction evaluateLeaf(\n\tclaim: ClaimNode,\n\twitnesses: Witnesses,\n\tindependence: WitnessIndependencePolicy,\n): { verdict: ClaimVerdict; observationIds: readonly string[] } {\n\tif (witnesses.violating.length > 0) return { verdict: \"violated\", observationIds: ids(witnesses.violating) };\n\tconst floor = OBSERVATION_TRUST_RANK[claim.trustFloor];\n\tconst trusted = witnesses.supporting.filter((observation) => OBSERVATION_TRUST_RANK[observation.source] >= floor);\n\tconst requiredWitnesses = claim.requiredWitnesses ?? 1;\n\tconst attributable = trusted.filter(\n\t\t(observation) =>\n\t\t\tindependence === \"legacy-observation-id\" ||\n\t\t\trequiredWitnesses === 1 ||\n\t\t\tBoolean(observation.independenceGroup?.trim()),\n\t);\n\tconst groups = new Set(\n\t\tattributable.map(\n\t\t\t(observation) =>\n\t\t\t\tobservation.independenceGroup ??\n\t\t\t\t(independence === \"legacy-observation-id\" ? observation.observationId : undefined),\n\t\t),\n\t);\n\tif (groups.size >= requiredWitnesses) return { verdict: \"satisfied\", observationIds: ids(trusted) };\n\tif (trusted.length > 0) return { verdict: \"missing\", observationIds: ids(trusted) };\n\tif (witnesses.supporting.length > 0)\n\t\treturn { verdict: \"insufficient_trust\", observationIds: ids(witnesses.supporting) };\n\tif (witnesses.stale.length > 0) return { verdict: \"stale\", observationIds: ids(witnesses.stale) };\n\treturn { verdict: \"missing\", observationIds: [] };\n}\n\n/** Inputs that take part in a parent's fold: advisory children are informational only. */\nfunction requiredInputs(claim: ClaimNode, claims: ReadonlyMap<string, ClaimNode>): readonly string[] {\n\treturn claim.satisfaction.inputs.filter((input) => claims.get(input)?.severity === \"required\");\n}\n\nfunction isClosed(verdict: ClaimVerdict): boolean {\n\treturn verdict === \"satisfied\" || verdict === \"waived\";\n}\n\n/** Step 5 for a composite claim; its required children are already evaluated. */\nfunction evaluateComposite(\n\tclaim: ClaimNode,\n\tinputs: readonly string[],\n\twitnesses: Witnesses,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n): { verdict: ClaimVerdict; observationIds: readonly string[] } {\n\tif (witnesses.violating.length > 0) return { verdict: \"violated\", observationIds: ids(witnesses.violating) };\n\tconst childVerdicts = inputs.map((input) => evaluations.get(input)?.verdict ?? \"missing\");\n\tif (claim.satisfaction.rule === \"all\") {\n\t\tconst open = childVerdicts.filter((verdict) => !isClosed(verdict));\n\t\treturn { verdict: open.length === 0 ? \"satisfied\" : worst(open), observationIds: [] };\n\t}\n\tif (childVerdicts.some(isClosed)) return { verdict: \"satisfied\", observationIds: [] };\n\treturn { verdict: best(childVerdicts), observationIds: [] };\n}\n\n/** Step 7: the lexically first valid waiver for a claim, or none. */\nfunction indexWaivers(input: ProofClosureInput, claims: ReadonlyMap<string, ClaimNode>): Map<string, WaiverNode> {\n\tconst byClaim = new Map<string, WaiverNode>();\n\tfor (const waiver of [...input.waivers].sort((a, b) => compareCodeUnits(a.waiverId, b.waiverId))) {\n\t\tif (!claims.has(waiver.claimId))\n\t\t\tthrow new ClaimGraphError(\"unknown_input\", `${waiver.waiverId} waives unknown ${waiver.claimId}`);\n\t\tif (waiver.sourceRoot !== input.sourceRoot) continue;\n\t\tif (requireTimestamp(waiver.expiresAt, \"expiresAt\") <= input.now) continue;\n\t\tif (!byClaim.has(waiver.claimId)) byClaim.set(waiver.claimId, waiver);\n\t}\n\treturn byClaim;\n}\n\n/** Required claims on a blocking path from a blocking required root, in graph order. */\nfunction blockingPathClaimIds(\n\tclaims: ReadonlyMap<string, ClaimNode>,\n\tevaluations: ReadonlyMap<string, ClaimClosureEvaluation>,\n\tblockingRoots: readonly string[],\n\tgraphOrder: readonly string[],\n): readonly string[] {\n\tconst onPath = new Set<string>();\n\tconst visit = (claimId: string): void => {\n\t\tif (onPath.has(claimId)) return;\n\t\tconst claim = claims.get(claimId);\n\t\tconst verdict = evaluations.get(claimId)?.verdict;\n\t\tif (claim === undefined || verdict === undefined || !isBlockingVerdict(verdict)) return;\n\t\tonPath.add(claimId);\n\t\tfor (const input of requiredInputs(claim, claims)) visit(input);\n\t};\n\tfor (const root of blockingRoots) visit(root);\n\treturn graphOrder.filter((claimId) => onPath.has(claimId));\n}\n\nfunction globalVerdict(\n\trequiredRoots: readonly ClaimClosureEvaluation[],\n\tinput: ProofClosureInput,\n): VerificationVerdict {\n\tif (requiredRoots.length === 0) return \"unverified\";\n\tif (requiredRoots.some((root) => root.verdict === \"violated\")) return \"violated\";\n\tconst closure =\n\t\trequiredRoots.every((root) => isClosed(root.verdict)) &&\n\t\tinput.unresolvedEffectIds.length === 0 &&\n\t\tinput.workspaceCompleteness === \"complete\";\n\treturn closure ? \"verified\" : \"inconclusive\";\n}\n\nexport function evaluateProofClosure(input: ProofClosureInput): ProofClosureResult {\n\tconst claims = validateClaimGraph(input.graph);\n\tconst witnessIndependence =\n\t\tinput.witnessIndependence === undefined ? \"legacy-observation-id\" : input.witnessIndependence;\n\tif (witnessIndependence !== \"legacy-observation-id\" && witnessIndependence !== \"explicit-groups\") {\n\t\tthrow new ClaimGraphError(\"invalid_input\", \"Unknown witness independence policy\");\n\t}\n\trequireTimestamp(input.now, \"now\");\n\tconst witnesses = indexWitnesses(input, claims);\n\tconst waivers = indexWaivers(input, claims);\n\tconst evaluations = new Map<string, ClaimClosureEvaluation>();\n\tfor (const claim of topologicalClaimOrder(input.graph, claims)) {\n\t\tconst claimWitnesses = witnesses.get(claim.claimId) as Witnesses;\n\t\tconst inputs = requiredInputs(claim, claims);\n\t\tconst local =\n\t\t\tinputs.length === 0\n\t\t\t\t? evaluateLeaf(claim, claimWitnesses, witnessIndependence)\n\t\t\t\t: evaluateComposite(claim, inputs, claimWitnesses, evaluations);\n\t\tlet verdict = local.verdict;\n\t\tif (verdict !== \"violated\" && claim.scopeSensitive === true && input.workspaceCompleteness !== \"complete\") {\n\t\t\tverdict = \"incomplete_scope\";\n\t\t}\n\t\tconst waiver = waivers.get(claim.claimId);\n\t\tconst waived = waiver !== undefined && verdict !== \"violated\" && verdict !== \"satisfied\";\n\t\tif (waived) verdict = \"waived\";\n\t\tevaluations.set(\n\t\t\tclaim.claimId,\n\t\t\tObject.freeze({\n\t\t\t\tclaimId: claim.claimId,\n\t\t\t\tseverity: claim.severity,\n\t\t\t\tverdict,\n\t\t\t\treasonCode: reasonFor(verdict),\n\t\t\t\tobservationIds: Object.freeze([...local.observationIds]),\n\t\t\t\t...(waived ? { waiverId: waiver.waiverId } : {}),\n\t\t\t\t...(isBlockingVerdict(verdict)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tblockingOrigin:\n\t\t\t\t\t\t\t\tinputs.length === 0 ||\n\t\t\t\t\t\t\t\tclaimWitnesses.violating.length > 0 ||\n\t\t\t\t\t\t\t\t(claim.scopeSensitive === true && input.workspaceCompleteness !== \"complete\")\n\t\t\t\t\t\t\t\t\t? (\"local\" as const)\n\t\t\t\t\t\t\t\t\t: (\"children\" as const),\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t}),\n\t\t);\n\t}\n\tconst graphOrder = input.graph.claims.map((claim) => claim.claimId);\n\tconst ordered = graphOrder.map((claimId) => evaluations.get(claimId) as ClaimClosureEvaluation);\n\tconst requiredRoots = rootClaimIds(input.graph)\n\t\t.map((claimId) => evaluations.get(claimId) as ClaimClosureEvaluation)\n\t\t.filter((evaluation) => evaluation.severity === \"required\");\n\tconst blockingRoots = requiredRoots\n\t\t.filter((evaluation) => isBlockingVerdict(evaluation.verdict))\n\t\t.map((evaluation) => evaluation.claimId);\n\tconst blockingClaimIds = blockingPathClaimIds(claims, evaluations, blockingRoots, graphOrder);\n\tconst blockingCut = explainBlockingCut(claims, evaluations, blockingRoots);\n\treturn Object.freeze({\n\t\tverdict: globalVerdict(requiredRoots, input),\n\t\twitnessIndependence,\n\t\tclaimEvaluations: Object.freeze(ordered),\n\t\tblockingClaimIds: Object.freeze(blockingClaimIds),\n\t\tminimalBlockingCut: blockingCut.claimIds,\n\t\tblockingCut,\n\t\tunresolvedEffectIds: Object.freeze([...input.unresolvedEffectIds].sort(compareCodeUnits)),\n\t\tworkspaceCompleteness: input.workspaceCompleteness,\n\t});\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural rules for a claim graph: identity, references, acyclicity, and
|
|
3
|
+
* a deterministic evaluation order.
|
|
4
|
+
*
|
|
5
|
+
* Validation throws `ClaimGraphError` because a malformed graph is a
|
|
6
|
+
* programming or configuration defect, not an evaluation outcome; a proof
|
|
7
|
+
* must never report `inconclusive` for a graph it could not even read.
|
|
8
|
+
*
|
|
9
|
+
* `canonicalClaimGraph` returns the graph in a normalized shape (claims by
|
|
10
|
+
* id, inputs and keys sorted) so that any layer with a hasher can derive the
|
|
11
|
+
* graph digest the plan binds into operations, receipts, and snapshots.
|
|
12
|
+
* This package deliberately owns the normalization and not the hash.
|
|
13
|
+
*/
|
|
14
|
+
import { type ClaimGraph, type ClaimNode } from "./claim-types.ts";
|
|
15
|
+
export declare class ClaimGraphError extends Error {
|
|
16
|
+
readonly code: "invalid_schema" | "invalid_claim" | "duplicate_claim" | "unknown_input" | "cycle" | "invalid_input";
|
|
17
|
+
constructor(code: ClaimGraphError["code"], message: string);
|
|
18
|
+
}
|
|
19
|
+
/** Throws on the first structural defect; returns the claims indexed by id otherwise. */
|
|
20
|
+
export declare function validateClaimGraph(graph: ClaimGraph): ReadonlyMap<string, ClaimNode>;
|
|
21
|
+
/**
|
|
22
|
+
* Children before parents, ties broken by claim id, so evaluation order is a
|
|
23
|
+
* function of the graph alone. Kahn's algorithm; leftover nodes form a cycle.
|
|
24
|
+
*/
|
|
25
|
+
export declare function topologicalClaimOrder(graph: ClaimGraph, index?: ReadonlyMap<string, ClaimNode>): readonly ClaimNode[];
|
|
26
|
+
/** Claims that are nobody's input: the roots a public verdict is reported against. */
|
|
27
|
+
export declare function rootClaimIds(graph: ClaimGraph): readonly string[];
|
|
28
|
+
/** Normalized graph for digesting: deterministic member order regardless of input order. */
|
|
29
|
+
export declare function canonicalClaimGraph(graph: ClaimGraph): ClaimGraph;
|
|
30
|
+
//# sourceMappingURL=claim-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-graph.d.ts","sourceRoot":"","sources":["../../src/claims/claim-graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAA8B,KAAK,UAAU,EAAE,KAAK,SAAS,EAA0B,MAAM,kBAAkB,CAAC;AAEvH,qBAAa,eAAgB,SAAQ,KAAK;IACzC,SAAgB,IAAI,EACjB,gBAAgB,GAChB,eAAe,GACf,iBAAiB,GACjB,eAAe,GACf,OAAO,GACP,eAAe,CAAC;IAEnB,YAAY,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,EAIzD;CACD;AAkCD,yFAAyF;AACzF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAkBpF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,SAAS,SAAS,EAAE,CAqCrH;AAQD,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,MAAM,EAAE,CAMjE;AAED,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAejE","sourcesContent":["/**\n * Structural rules for a claim graph: identity, references, acyclicity, and\n * a deterministic evaluation order.\n *\n * Validation throws `ClaimGraphError` because a malformed graph is a\n * programming or configuration defect, not an evaluation outcome; a proof\n * must never report `inconclusive` for a graph it could not even read.\n *\n * `canonicalClaimGraph` returns the graph in a normalized shape (claims by\n * id, inputs and keys sorted) so that any layer with a hasher can derive the\n * graph digest the plan binds into operations, receipts, and snapshots.\n * This package deliberately owns the normalization and not the hash.\n */\n\nimport { CLAIM_GRAPH_SCHEMA_VERSION, type ClaimGraph, type ClaimNode, OBSERVATION_TRUST_RANK } from \"./claim-types.ts\";\n\nexport class ClaimGraphError extends Error {\n\tpublic readonly code:\n\t\t| \"invalid_schema\"\n\t\t| \"invalid_claim\"\n\t\t| \"duplicate_claim\"\n\t\t| \"unknown_input\"\n\t\t| \"cycle\"\n\t\t| \"invalid_input\";\n\n\tconstructor(code: ClaimGraphError[\"code\"], message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"ClaimGraphError\";\n\t\tthis.code = code;\n\t}\n}\n\nfunction compareCodeUnits(left: string, right: string): number {\n\tif (left === right) return 0;\n\treturn left < right ? -1 : 1;\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.trim().length > 0;\n}\n\nfunction validateClaim(claim: ClaimNode): void {\n\tconst id = claim.claimId;\n\tif (!isNonEmptyString(id)) throw new ClaimGraphError(\"invalid_claim\", \"claimId must be a non-empty string\");\n\tif (!isNonEmptyString(claim.statement)) throw new ClaimGraphError(\"invalid_claim\", `${id}: statement is empty`);\n\tif (claim.severity !== \"required\" && claim.severity !== \"advisory\") {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: severity must be required or advisory`);\n\t}\n\tif (claim.satisfaction.rule !== \"all\" && claim.satisfaction.rule !== \"any\") {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: satisfaction rule must be all or any`);\n\t}\n\tif (typeof OBSERVATION_TRUST_RANK[claim.trustFloor] !== \"number\") {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: unknown trustFloor ${String(claim.trustFloor)}`);\n\t}\n\tconst witnesses = claim.requiredWitnesses ?? 1;\n\tif (!Number.isInteger(witnesses) || witnesses < 1) {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: requiredWitnesses must be a positive integer`);\n\t}\n\tif (new Set(claim.satisfaction.inputs).size !== claim.satisfaction.inputs.length) {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: inputs contain duplicates`);\n\t}\n\tif (claim.satisfaction.inputs.includes(id)) throw new ClaimGraphError(\"cycle\", `${id} lists itself as an input`);\n}\n\n/** Throws on the first structural defect; returns the claims indexed by id otherwise. */\nexport function validateClaimGraph(graph: ClaimGraph): ReadonlyMap<string, ClaimNode> {\n\tif (graph.schemaVersion !== CLAIM_GRAPH_SCHEMA_VERSION) {\n\t\tthrow new ClaimGraphError(\"invalid_schema\", `Unsupported claim graph schema ${String(graph.schemaVersion)}`);\n\t}\n\tconst byId = new Map<string, ClaimNode>();\n\tfor (const claim of graph.claims) {\n\t\tvalidateClaim(claim);\n\t\tif (byId.has(claim.claimId)) throw new ClaimGraphError(\"duplicate_claim\", `Duplicate claim ${claim.claimId}`);\n\t\tbyId.set(claim.claimId, claim);\n\t}\n\tfor (const claim of graph.claims) {\n\t\tfor (const input of claim.satisfaction.inputs) {\n\t\t\tif (!byId.has(input))\n\t\t\t\tthrow new ClaimGraphError(\"unknown_input\", `${claim.claimId} references unknown ${input}`);\n\t\t}\n\t}\n\ttopologicalClaimOrder(graph, byId);\n\treturn byId;\n}\n\n/**\n * Children before parents, ties broken by claim id, so evaluation order is a\n * function of the graph alone. Kahn's algorithm; leftover nodes form a cycle.\n */\nexport function topologicalClaimOrder(graph: ClaimGraph, index?: ReadonlyMap<string, ClaimNode>): readonly ClaimNode[] {\n\tconst byId = index ?? new Map(graph.claims.map((claim) => [claim.claimId, claim]));\n\tconst remainingInputs = new Map<string, number>();\n\tconst dependents = new Map<string, string[]>();\n\tfor (const claim of graph.claims) {\n\t\tremainingInputs.set(claim.claimId, claim.satisfaction.inputs.length);\n\t\tfor (const input of claim.satisfaction.inputs) {\n\t\t\tconst list = dependents.get(input) ?? [];\n\t\t\tlist.push(claim.claimId);\n\t\t\tdependents.set(input, list);\n\t\t}\n\t}\n\tconst ready = graph.claims\n\t\t.filter((claim) => claim.satisfaction.inputs.length === 0)\n\t\t.map((claim) => claim.claimId)\n\t\t.sort(compareCodeUnits);\n\tconst ordered: ClaimNode[] = [];\n\twhile (ready.length > 0) {\n\t\tconst id = ready.shift() as string;\n\t\tconst claim = byId.get(id);\n\t\tif (claim !== undefined) ordered.push(claim);\n\t\tfor (const dependent of (dependents.get(id) ?? []).sort(compareCodeUnits)) {\n\t\t\tconst remaining = (remainingInputs.get(dependent) ?? 0) - 1;\n\t\t\tremainingInputs.set(dependent, remaining);\n\t\t\tif (remaining === 0) insertSorted(ready, dependent);\n\t\t}\n\t}\n\tif (ordered.length !== graph.claims.length) {\n\t\tconst stuck = graph.claims\n\t\t\t.map((claim) => claim.claimId)\n\t\t\t.filter((id) => !ordered.some((claim) => claim.claimId === id));\n\t\tthrow new ClaimGraphError(\n\t\t\t\"cycle\",\n\t\t\t`Claim graph contains a cycle through ${stuck.sort(compareCodeUnits).join(\", \")}`,\n\t\t);\n\t}\n\treturn ordered;\n}\n\nfunction insertSorted(list: string[], value: string): void {\n\tlet index = 0;\n\twhile (index < list.length && compareCodeUnits(list[index], value) < 0) index++;\n\tlist.splice(index, 0, value);\n}\n\n/** Claims that are nobody's input: the roots a public verdict is reported against. */\nexport function rootClaimIds(graph: ClaimGraph): readonly string[] {\n\tconst referenced = new Set(graph.claims.flatMap((claim) => claim.satisfaction.inputs));\n\treturn graph.claims\n\t\t.map((claim) => claim.claimId)\n\t\t.filter((id) => !referenced.has(id))\n\t\t.sort(compareCodeUnits);\n}\n\n/** Normalized graph for digesting: deterministic member order regardless of input order. */\nexport function canonicalClaimGraph(graph: ClaimGraph): ClaimGraph {\n\tconst claims = [...graph.claims]\n\t\t.sort((left, right) => compareCodeUnits(left.claimId, right.claimId))\n\t\t.map((claim) => ({\n\t\t\tclaimId: claim.claimId,\n\t\t\tkind: claim.kind,\n\t\t\tstatement: claim.statement,\n\t\t\tseverity: claim.severity,\n\t\t\tsatisfaction: { rule: claim.satisfaction.rule, inputs: [...claim.satisfaction.inputs].sort(compareCodeUnits) },\n\t\t\ttrustFloor: claim.trustFloor,\n\t\t\trequiredWitnesses: claim.requiredWitnesses ?? 1,\n\t\t\tscopeSensitive: claim.scopeSensitive ?? false,\n\t\t\tinvalidationKeys: [...claim.invalidationKeys].sort(compareCodeUnits),\n\t\t}));\n\treturn { schemaVersion: CLAIM_GRAPH_SCHEMA_VERSION, claims };\n}\n"]}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural rules for a claim graph: identity, references, acyclicity, and
|
|
3
|
+
* a deterministic evaluation order.
|
|
4
|
+
*
|
|
5
|
+
* Validation throws `ClaimGraphError` because a malformed graph is a
|
|
6
|
+
* programming or configuration defect, not an evaluation outcome; a proof
|
|
7
|
+
* must never report `inconclusive` for a graph it could not even read.
|
|
8
|
+
*
|
|
9
|
+
* `canonicalClaimGraph` returns the graph in a normalized shape (claims by
|
|
10
|
+
* id, inputs and keys sorted) so that any layer with a hasher can derive the
|
|
11
|
+
* graph digest the plan binds into operations, receipts, and snapshots.
|
|
12
|
+
* This package deliberately owns the normalization and not the hash.
|
|
13
|
+
*/
|
|
14
|
+
import { CLAIM_GRAPH_SCHEMA_VERSION, OBSERVATION_TRUST_RANK } from "./claim-types.js";
|
|
15
|
+
export class ClaimGraphError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
constructor(code, message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "ClaimGraphError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function compareCodeUnits(left, right) {
|
|
24
|
+
if (left === right)
|
|
25
|
+
return 0;
|
|
26
|
+
return left < right ? -1 : 1;
|
|
27
|
+
}
|
|
28
|
+
function isNonEmptyString(value) {
|
|
29
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
30
|
+
}
|
|
31
|
+
function validateClaim(claim) {
|
|
32
|
+
const id = claim.claimId;
|
|
33
|
+
if (!isNonEmptyString(id))
|
|
34
|
+
throw new ClaimGraphError("invalid_claim", "claimId must be a non-empty string");
|
|
35
|
+
if (!isNonEmptyString(claim.statement))
|
|
36
|
+
throw new ClaimGraphError("invalid_claim", `${id}: statement is empty`);
|
|
37
|
+
if (claim.severity !== "required" && claim.severity !== "advisory") {
|
|
38
|
+
throw new ClaimGraphError("invalid_claim", `${id}: severity must be required or advisory`);
|
|
39
|
+
}
|
|
40
|
+
if (claim.satisfaction.rule !== "all" && claim.satisfaction.rule !== "any") {
|
|
41
|
+
throw new ClaimGraphError("invalid_claim", `${id}: satisfaction rule must be all or any`);
|
|
42
|
+
}
|
|
43
|
+
if (typeof OBSERVATION_TRUST_RANK[claim.trustFloor] !== "number") {
|
|
44
|
+
throw new ClaimGraphError("invalid_claim", `${id}: unknown trustFloor ${String(claim.trustFloor)}`);
|
|
45
|
+
}
|
|
46
|
+
const witnesses = claim.requiredWitnesses ?? 1;
|
|
47
|
+
if (!Number.isInteger(witnesses) || witnesses < 1) {
|
|
48
|
+
throw new ClaimGraphError("invalid_claim", `${id}: requiredWitnesses must be a positive integer`);
|
|
49
|
+
}
|
|
50
|
+
if (new Set(claim.satisfaction.inputs).size !== claim.satisfaction.inputs.length) {
|
|
51
|
+
throw new ClaimGraphError("invalid_claim", `${id}: inputs contain duplicates`);
|
|
52
|
+
}
|
|
53
|
+
if (claim.satisfaction.inputs.includes(id))
|
|
54
|
+
throw new ClaimGraphError("cycle", `${id} lists itself as an input`);
|
|
55
|
+
}
|
|
56
|
+
/** Throws on the first structural defect; returns the claims indexed by id otherwise. */
|
|
57
|
+
export function validateClaimGraph(graph) {
|
|
58
|
+
if (graph.schemaVersion !== CLAIM_GRAPH_SCHEMA_VERSION) {
|
|
59
|
+
throw new ClaimGraphError("invalid_schema", `Unsupported claim graph schema ${String(graph.schemaVersion)}`);
|
|
60
|
+
}
|
|
61
|
+
const byId = new Map();
|
|
62
|
+
for (const claim of graph.claims) {
|
|
63
|
+
validateClaim(claim);
|
|
64
|
+
if (byId.has(claim.claimId))
|
|
65
|
+
throw new ClaimGraphError("duplicate_claim", `Duplicate claim ${claim.claimId}`);
|
|
66
|
+
byId.set(claim.claimId, claim);
|
|
67
|
+
}
|
|
68
|
+
for (const claim of graph.claims) {
|
|
69
|
+
for (const input of claim.satisfaction.inputs) {
|
|
70
|
+
if (!byId.has(input))
|
|
71
|
+
throw new ClaimGraphError("unknown_input", `${claim.claimId} references unknown ${input}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
topologicalClaimOrder(graph, byId);
|
|
75
|
+
return byId;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Children before parents, ties broken by claim id, so evaluation order is a
|
|
79
|
+
* function of the graph alone. Kahn's algorithm; leftover nodes form a cycle.
|
|
80
|
+
*/
|
|
81
|
+
export function topologicalClaimOrder(graph, index) {
|
|
82
|
+
const byId = index ?? new Map(graph.claims.map((claim) => [claim.claimId, claim]));
|
|
83
|
+
const remainingInputs = new Map();
|
|
84
|
+
const dependents = new Map();
|
|
85
|
+
for (const claim of graph.claims) {
|
|
86
|
+
remainingInputs.set(claim.claimId, claim.satisfaction.inputs.length);
|
|
87
|
+
for (const input of claim.satisfaction.inputs) {
|
|
88
|
+
const list = dependents.get(input) ?? [];
|
|
89
|
+
list.push(claim.claimId);
|
|
90
|
+
dependents.set(input, list);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const ready = graph.claims
|
|
94
|
+
.filter((claim) => claim.satisfaction.inputs.length === 0)
|
|
95
|
+
.map((claim) => claim.claimId)
|
|
96
|
+
.sort(compareCodeUnits);
|
|
97
|
+
const ordered = [];
|
|
98
|
+
while (ready.length > 0) {
|
|
99
|
+
const id = ready.shift();
|
|
100
|
+
const claim = byId.get(id);
|
|
101
|
+
if (claim !== undefined)
|
|
102
|
+
ordered.push(claim);
|
|
103
|
+
for (const dependent of (dependents.get(id) ?? []).sort(compareCodeUnits)) {
|
|
104
|
+
const remaining = (remainingInputs.get(dependent) ?? 0) - 1;
|
|
105
|
+
remainingInputs.set(dependent, remaining);
|
|
106
|
+
if (remaining === 0)
|
|
107
|
+
insertSorted(ready, dependent);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (ordered.length !== graph.claims.length) {
|
|
111
|
+
const stuck = graph.claims
|
|
112
|
+
.map((claim) => claim.claimId)
|
|
113
|
+
.filter((id) => !ordered.some((claim) => claim.claimId === id));
|
|
114
|
+
throw new ClaimGraphError("cycle", `Claim graph contains a cycle through ${stuck.sort(compareCodeUnits).join(", ")}`);
|
|
115
|
+
}
|
|
116
|
+
return ordered;
|
|
117
|
+
}
|
|
118
|
+
function insertSorted(list, value) {
|
|
119
|
+
let index = 0;
|
|
120
|
+
while (index < list.length && compareCodeUnits(list[index], value) < 0)
|
|
121
|
+
index++;
|
|
122
|
+
list.splice(index, 0, value);
|
|
123
|
+
}
|
|
124
|
+
/** Claims that are nobody's input: the roots a public verdict is reported against. */
|
|
125
|
+
export function rootClaimIds(graph) {
|
|
126
|
+
const referenced = new Set(graph.claims.flatMap((claim) => claim.satisfaction.inputs));
|
|
127
|
+
return graph.claims
|
|
128
|
+
.map((claim) => claim.claimId)
|
|
129
|
+
.filter((id) => !referenced.has(id))
|
|
130
|
+
.sort(compareCodeUnits);
|
|
131
|
+
}
|
|
132
|
+
/** Normalized graph for digesting: deterministic member order regardless of input order. */
|
|
133
|
+
export function canonicalClaimGraph(graph) {
|
|
134
|
+
const claims = [...graph.claims]
|
|
135
|
+
.sort((left, right) => compareCodeUnits(left.claimId, right.claimId))
|
|
136
|
+
.map((claim) => ({
|
|
137
|
+
claimId: claim.claimId,
|
|
138
|
+
kind: claim.kind,
|
|
139
|
+
statement: claim.statement,
|
|
140
|
+
severity: claim.severity,
|
|
141
|
+
satisfaction: { rule: claim.satisfaction.rule, inputs: [...claim.satisfaction.inputs].sort(compareCodeUnits) },
|
|
142
|
+
trustFloor: claim.trustFloor,
|
|
143
|
+
requiredWitnesses: claim.requiredWitnesses ?? 1,
|
|
144
|
+
scopeSensitive: claim.scopeSensitive ?? false,
|
|
145
|
+
invalidationKeys: [...claim.invalidationKeys].sort(compareCodeUnits),
|
|
146
|
+
}));
|
|
147
|
+
return { schemaVersion: CLAIM_GRAPH_SCHEMA_VERSION, claims };
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=claim-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-graph.js","sourceRoot":"","sources":["../../src/claims/claim-graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Structural rules for a claim graph: identity, references, acyclicity, and\n * a deterministic evaluation order.\n *\n * Validation throws `ClaimGraphError` because a malformed graph is a\n * programming or configuration defect, not an evaluation outcome; a proof\n * must never report `inconclusive` for a graph it could not even read.\n *\n * `canonicalClaimGraph` returns the graph in a normalized shape (claims by\n * id, inputs and keys sorted) so that any layer with a hasher can derive the\n * graph digest the plan binds into operations, receipts, and snapshots.\n * This package deliberately owns the normalization and not the hash.\n */\n\nimport { CLAIM_GRAPH_SCHEMA_VERSION, type ClaimGraph, type ClaimNode, OBSERVATION_TRUST_RANK } from \"./claim-types.ts\";\n\nexport class ClaimGraphError extends Error {\n\tpublic readonly code:\n\t\t| \"invalid_schema\"\n\t\t| \"invalid_claim\"\n\t\t| \"duplicate_claim\"\n\t\t| \"unknown_input\"\n\t\t| \"cycle\"\n\t\t| \"invalid_input\";\n\n\tconstructor(code: ClaimGraphError[\"code\"], message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"ClaimGraphError\";\n\t\tthis.code = code;\n\t}\n}\n\nfunction compareCodeUnits(left: string, right: string): number {\n\tif (left === right) return 0;\n\treturn left < right ? -1 : 1;\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.trim().length > 0;\n}\n\nfunction validateClaim(claim: ClaimNode): void {\n\tconst id = claim.claimId;\n\tif (!isNonEmptyString(id)) throw new ClaimGraphError(\"invalid_claim\", \"claimId must be a non-empty string\");\n\tif (!isNonEmptyString(claim.statement)) throw new ClaimGraphError(\"invalid_claim\", `${id}: statement is empty`);\n\tif (claim.severity !== \"required\" && claim.severity !== \"advisory\") {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: severity must be required or advisory`);\n\t}\n\tif (claim.satisfaction.rule !== \"all\" && claim.satisfaction.rule !== \"any\") {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: satisfaction rule must be all or any`);\n\t}\n\tif (typeof OBSERVATION_TRUST_RANK[claim.trustFloor] !== \"number\") {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: unknown trustFloor ${String(claim.trustFloor)}`);\n\t}\n\tconst witnesses = claim.requiredWitnesses ?? 1;\n\tif (!Number.isInteger(witnesses) || witnesses < 1) {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: requiredWitnesses must be a positive integer`);\n\t}\n\tif (new Set(claim.satisfaction.inputs).size !== claim.satisfaction.inputs.length) {\n\t\tthrow new ClaimGraphError(\"invalid_claim\", `${id}: inputs contain duplicates`);\n\t}\n\tif (claim.satisfaction.inputs.includes(id)) throw new ClaimGraphError(\"cycle\", `${id} lists itself as an input`);\n}\n\n/** Throws on the first structural defect; returns the claims indexed by id otherwise. */\nexport function validateClaimGraph(graph: ClaimGraph): ReadonlyMap<string, ClaimNode> {\n\tif (graph.schemaVersion !== CLAIM_GRAPH_SCHEMA_VERSION) {\n\t\tthrow new ClaimGraphError(\"invalid_schema\", `Unsupported claim graph schema ${String(graph.schemaVersion)}`);\n\t}\n\tconst byId = new Map<string, ClaimNode>();\n\tfor (const claim of graph.claims) {\n\t\tvalidateClaim(claim);\n\t\tif (byId.has(claim.claimId)) throw new ClaimGraphError(\"duplicate_claim\", `Duplicate claim ${claim.claimId}`);\n\t\tbyId.set(claim.claimId, claim);\n\t}\n\tfor (const claim of graph.claims) {\n\t\tfor (const input of claim.satisfaction.inputs) {\n\t\t\tif (!byId.has(input))\n\t\t\t\tthrow new ClaimGraphError(\"unknown_input\", `${claim.claimId} references unknown ${input}`);\n\t\t}\n\t}\n\ttopologicalClaimOrder(graph, byId);\n\treturn byId;\n}\n\n/**\n * Children before parents, ties broken by claim id, so evaluation order is a\n * function of the graph alone. Kahn's algorithm; leftover nodes form a cycle.\n */\nexport function topologicalClaimOrder(graph: ClaimGraph, index?: ReadonlyMap<string, ClaimNode>): readonly ClaimNode[] {\n\tconst byId = index ?? new Map(graph.claims.map((claim) => [claim.claimId, claim]));\n\tconst remainingInputs = new Map<string, number>();\n\tconst dependents = new Map<string, string[]>();\n\tfor (const claim of graph.claims) {\n\t\tremainingInputs.set(claim.claimId, claim.satisfaction.inputs.length);\n\t\tfor (const input of claim.satisfaction.inputs) {\n\t\t\tconst list = dependents.get(input) ?? [];\n\t\t\tlist.push(claim.claimId);\n\t\t\tdependents.set(input, list);\n\t\t}\n\t}\n\tconst ready = graph.claims\n\t\t.filter((claim) => claim.satisfaction.inputs.length === 0)\n\t\t.map((claim) => claim.claimId)\n\t\t.sort(compareCodeUnits);\n\tconst ordered: ClaimNode[] = [];\n\twhile (ready.length > 0) {\n\t\tconst id = ready.shift() as string;\n\t\tconst claim = byId.get(id);\n\t\tif (claim !== undefined) ordered.push(claim);\n\t\tfor (const dependent of (dependents.get(id) ?? []).sort(compareCodeUnits)) {\n\t\t\tconst remaining = (remainingInputs.get(dependent) ?? 0) - 1;\n\t\t\tremainingInputs.set(dependent, remaining);\n\t\t\tif (remaining === 0) insertSorted(ready, dependent);\n\t\t}\n\t}\n\tif (ordered.length !== graph.claims.length) {\n\t\tconst stuck = graph.claims\n\t\t\t.map((claim) => claim.claimId)\n\t\t\t.filter((id) => !ordered.some((claim) => claim.claimId === id));\n\t\tthrow new ClaimGraphError(\n\t\t\t\"cycle\",\n\t\t\t`Claim graph contains a cycle through ${stuck.sort(compareCodeUnits).join(\", \")}`,\n\t\t);\n\t}\n\treturn ordered;\n}\n\nfunction insertSorted(list: string[], value: string): void {\n\tlet index = 0;\n\twhile (index < list.length && compareCodeUnits(list[index], value) < 0) index++;\n\tlist.splice(index, 0, value);\n}\n\n/** Claims that are nobody's input: the roots a public verdict is reported against. */\nexport function rootClaimIds(graph: ClaimGraph): readonly string[] {\n\tconst referenced = new Set(graph.claims.flatMap((claim) => claim.satisfaction.inputs));\n\treturn graph.claims\n\t\t.map((claim) => claim.claimId)\n\t\t.filter((id) => !referenced.has(id))\n\t\t.sort(compareCodeUnits);\n}\n\n/** Normalized graph for digesting: deterministic member order regardless of input order. */\nexport function canonicalClaimGraph(graph: ClaimGraph): ClaimGraph {\n\tconst claims = [...graph.claims]\n\t\t.sort((left, right) => compareCodeUnits(left.claimId, right.claimId))\n\t\t.map((claim) => ({\n\t\t\tclaimId: claim.claimId,\n\t\t\tkind: claim.kind,\n\t\t\tstatement: claim.statement,\n\t\t\tseverity: claim.severity,\n\t\t\tsatisfaction: { rule: claim.satisfaction.rule, inputs: [...claim.satisfaction.inputs].sort(compareCodeUnits) },\n\t\t\ttrustFloor: claim.trustFloor,\n\t\t\trequiredWitnesses: claim.requiredWitnesses ?? 1,\n\t\t\tscopeSensitive: claim.scopeSensitive ?? false,\n\t\t\tinvalidationKeys: [...claim.invalidationKeys].sort(compareCodeUnits),\n\t\t}));\n\treturn { schemaVersion: CLAIM_GRAPH_SCHEMA_VERSION, claims };\n}\n"]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claim Closure Graph V1 vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* The flat `TaskSpec.claims` model answers "did this attempt satisfy its
|
|
5
|
+
* predicates?". The closure graph answers the stricter question a verified
|
|
6
|
+
* verdict needs: are all required claims closed by *qualified* witnesses —
|
|
7
|
+
* bound to this source root and environment, not expired, from a source the
|
|
8
|
+
* claim's policy trusts, independent enough to count separately — with no
|
|
9
|
+
* counterexample, no unresolved side effect, and a complete workspace scope?
|
|
10
|
+
*
|
|
11
|
+
* Pure vocabulary: no behaviour, no I/O, no clock. Evaluation lives in
|
|
12
|
+
* `claim-closure.ts`, structure checks in `claim-graph.ts`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CLAIM_GRAPH_SCHEMA_VERSION: "omk.claim-graph.v1";
|
|
15
|
+
export type ClaimNodeKind = "requirement" | "safety" | "scope" | "quality" | "performance" | "release";
|
|
16
|
+
export type ClaimSeverity = "required" | "advisory";
|
|
17
|
+
export type ClaimRule = "all" | "any";
|
|
18
|
+
/** Trust lattice, highest first. A claim's `trustFloor` names the weakest source that may witness it. */
|
|
19
|
+
export type ObservationSource = "deterministic_validator" | "trusted_attestation" | "workspace_witness" | "effect_reconciliation" | "independent_review" | "self_review" | "model_narrative";
|
|
20
|
+
export declare const OBSERVATION_TRUST_RANK: Readonly<Record<ObservationSource, number>>;
|
|
21
|
+
export type ObservationPolarity = "supports" | "violates";
|
|
22
|
+
export type ClaimVerdict = "satisfied" | "violated" | "missing" | "stale" | "incomplete_scope" | "insufficient_trust" | "waived";
|
|
23
|
+
/** Precedence when several verdicts compete; lower index wins. */
|
|
24
|
+
export declare const CLAIM_VERDICT_PRECEDENCE: readonly ClaimVerdict[];
|
|
25
|
+
export type WorkspaceCompleteness = "complete" | "partial_excluded" | "partial_truncated" | "unknown";
|
|
26
|
+
export type VerificationVerdict = "verified" | "unverified" | "inconclusive" | "violated";
|
|
27
|
+
export interface ClaimNode {
|
|
28
|
+
readonly claimId: string;
|
|
29
|
+
readonly kind: ClaimNodeKind;
|
|
30
|
+
readonly statement: string;
|
|
31
|
+
readonly severity: ClaimSeverity;
|
|
32
|
+
/** `inputs` are child claim ids. An empty list makes this a leaf witnessed by observations. */
|
|
33
|
+
readonly satisfaction: {
|
|
34
|
+
readonly rule: ClaimRule;
|
|
35
|
+
readonly inputs: readonly string[];
|
|
36
|
+
};
|
|
37
|
+
readonly trustFloor: ObservationSource;
|
|
38
|
+
/** Distinct independence groups a leaf needs among its qualified supporting witnesses. Default 1. */
|
|
39
|
+
readonly requiredWitnesses?: number;
|
|
40
|
+
/** Blocked with `incomplete_scope` whenever the workspace witness is not complete. */
|
|
41
|
+
readonly scopeSensitive?: boolean;
|
|
42
|
+
/** Keys a witness must have attested to; any missing key makes the witness stale for this claim. */
|
|
43
|
+
readonly invalidationKeys: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
export interface ObservationNode {
|
|
46
|
+
readonly observationId: string;
|
|
47
|
+
readonly claimIds: readonly string[];
|
|
48
|
+
readonly polarity: ObservationPolarity;
|
|
49
|
+
readonly source: ObservationSource;
|
|
50
|
+
readonly receiptId?: string;
|
|
51
|
+
readonly sourceRoot: string;
|
|
52
|
+
readonly environmentDigest: string;
|
|
53
|
+
/** ISO-8601 instant after which the observation no longer qualifies. */
|
|
54
|
+
readonly validUntil?: string;
|
|
55
|
+
readonly invalidationKeys?: readonly string[];
|
|
56
|
+
/** Witnesses sharing a group count once. The explicit-groups policy requires this for multi-witness claims. */
|
|
57
|
+
readonly independenceGroup?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface WaiverNode {
|
|
60
|
+
readonly waiverId: string;
|
|
61
|
+
readonly claimId: string;
|
|
62
|
+
readonly issuer: string;
|
|
63
|
+
readonly reason: string;
|
|
64
|
+
readonly sourceRoot: string;
|
|
65
|
+
readonly expiresAt: string;
|
|
66
|
+
}
|
|
67
|
+
export interface ClaimGraph {
|
|
68
|
+
readonly schemaVersion: typeof CLAIM_GRAPH_SCHEMA_VERSION;
|
|
69
|
+
readonly claims: readonly ClaimNode[];
|
|
70
|
+
}
|
|
71
|
+
export type WitnessIndependencePolicy = "legacy-observation-id" | "explicit-groups";
|
|
72
|
+
export interface ProofClosureInput {
|
|
73
|
+
readonly graph: ClaimGraph;
|
|
74
|
+
readonly observations: readonly ObservationNode[];
|
|
75
|
+
/** Compatibility default: legacy-observation-id. Strong profiles must opt into explicit-groups. */
|
|
76
|
+
readonly witnessIndependence?: WitnessIndependencePolicy;
|
|
77
|
+
readonly waivers: readonly WaiverNode[];
|
|
78
|
+
readonly sourceRoot: string;
|
|
79
|
+
readonly environmentDigest: string;
|
|
80
|
+
readonly workspaceCompleteness: WorkspaceCompleteness;
|
|
81
|
+
readonly unresolvedEffectIds: readonly string[];
|
|
82
|
+
/** ISO-8601 evaluation instant; injected so evaluation never reads a clock. */
|
|
83
|
+
readonly now: string;
|
|
84
|
+
}
|
|
85
|
+
export type ClaimReasonCode = "claim.satisfied" | "claim.violated" | "claim.missing" | "claim.stale" | "claim.incomplete_scope" | "claim.insufficient_trust" | "claim.waived";
|
|
86
|
+
export interface ClaimClosureEvaluation {
|
|
87
|
+
readonly claimId: string;
|
|
88
|
+
readonly severity: ClaimSeverity;
|
|
89
|
+
readonly verdict: ClaimVerdict;
|
|
90
|
+
readonly reasonCode: ClaimReasonCode;
|
|
91
|
+
/** Qualified observations that decided the verdict (violations, or counted witnesses). */
|
|
92
|
+
readonly observationIds: readonly string[];
|
|
93
|
+
readonly waiverId?: string;
|
|
94
|
+
/** Distinguish local obligations from a verdict derived only from children. */
|
|
95
|
+
readonly blockingOrigin?: "local" | "children";
|
|
96
|
+
}
|
|
97
|
+
export interface BlockingCutExplanation {
|
|
98
|
+
readonly claimIds: readonly string[];
|
|
99
|
+
readonly algorithm: "exact-antichain" | "greedy";
|
|
100
|
+
/** Minimum cardinality in this repair model only; fallback has no minimality guarantee. */
|
|
101
|
+
readonly optimality: "minimum" | "not-proven";
|
|
102
|
+
readonly truncated: boolean;
|
|
103
|
+
readonly exploredStates: number;
|
|
104
|
+
/** Selected composite-local obligations that child repairs alone cannot resolve. */
|
|
105
|
+
readonly localClaimIds: readonly string[];
|
|
106
|
+
}
|
|
107
|
+
export interface ProofClosureResult {
|
|
108
|
+
readonly verdict: VerificationVerdict;
|
|
109
|
+
readonly witnessIndependence?: WitnessIndependencePolicy;
|
|
110
|
+
readonly claimEvaluations: readonly ClaimClosureEvaluation[];
|
|
111
|
+
/** Required claims that are not closed, in evaluation order. */
|
|
112
|
+
readonly blockingClaimIds: readonly string[];
|
|
113
|
+
/** Compatibility projection; consult blockingCut for bounded-search optimality and local causes. */
|
|
114
|
+
readonly minimalBlockingCut: readonly string[];
|
|
115
|
+
/** Explanation only; never overrides the closure verdict or unresolved global effects. */
|
|
116
|
+
readonly blockingCut?: BlockingCutExplanation;
|
|
117
|
+
readonly unresolvedEffectIds: readonly string[];
|
|
118
|
+
readonly workspaceCompleteness: WorkspaceCompleteness;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=claim-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-types.d.ts","sourceRoot":"","sources":["../../src/claims/claim-types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Claim Closure Graph V1 vocabulary.\n *\n * The flat `TaskSpec.claims` model answers \"did this attempt satisfy its\n * predicates?\". The closure graph answers the stricter question a verified\n * verdict needs: are all required claims closed by *qualified* witnesses —\n * bound to this source root and environment, not expired, from a source the\n * claim's policy trusts, independent enough to count separately — with no\n * counterexample, no unresolved side effect, and a complete workspace scope?\n *\n * Pure vocabulary: no behaviour, no I/O, no clock. Evaluation lives in\n * `claim-closure.ts`, structure checks in `claim-graph.ts`.\n */\n\nexport const CLAIM_GRAPH_SCHEMA_VERSION = \"omk.claim-graph.v1\" as const;\n\nexport type ClaimNodeKind = \"requirement\" | \"safety\" | \"scope\" | \"quality\" | \"performance\" | \"release\";\nexport type ClaimSeverity = \"required\" | \"advisory\";\nexport type ClaimRule = \"all\" | \"any\";\n\n/** Trust lattice, highest first. A claim's `trustFloor` names the weakest source that may witness it. */\nexport type ObservationSource =\n\t| \"deterministic_validator\"\n\t| \"trusted_attestation\"\n\t| \"workspace_witness\"\n\t| \"effect_reconciliation\"\n\t| \"independent_review\"\n\t| \"self_review\"\n\t| \"model_narrative\";\n\nexport const OBSERVATION_TRUST_RANK: Readonly<Record<ObservationSource, number>> = {\n\tdeterministic_validator: 6,\n\ttrusted_attestation: 5,\n\tworkspace_witness: 4,\n\teffect_reconciliation: 4,\n\tindependent_review: 3,\n\tself_review: 2,\n\tmodel_narrative: 1,\n};\n\nexport type ObservationPolarity = \"supports\" | \"violates\";\n\nexport type ClaimVerdict =\n\t| \"satisfied\"\n\t| \"violated\"\n\t| \"missing\"\n\t| \"stale\"\n\t| \"incomplete_scope\"\n\t| \"insufficient_trust\"\n\t| \"waived\";\n\n/** Precedence when several verdicts compete; lower index wins. */\nexport const CLAIM_VERDICT_PRECEDENCE: readonly ClaimVerdict[] = [\n\t\"violated\",\n\t\"stale\",\n\t\"incomplete_scope\",\n\t\"insufficient_trust\",\n\t\"missing\",\n\t\"waived\",\n\t\"satisfied\",\n];\n\nexport type WorkspaceCompleteness = \"complete\" | \"partial_excluded\" | \"partial_truncated\" | \"unknown\";\n\nexport type VerificationVerdict = \"verified\" | \"unverified\" | \"inconclusive\" | \"violated\";\n\nexport interface ClaimNode {\n\treadonly claimId: string;\n\treadonly kind: ClaimNodeKind;\n\treadonly statement: string;\n\treadonly severity: ClaimSeverity;\n\t/** `inputs` are child claim ids. An empty list makes this a leaf witnessed by observations. */\n\treadonly satisfaction: { readonly rule: ClaimRule; readonly inputs: readonly string[] };\n\treadonly trustFloor: ObservationSource;\n\t/** Distinct independence groups a leaf needs among its qualified supporting witnesses. Default 1. */\n\treadonly requiredWitnesses?: number;\n\t/** Blocked with `incomplete_scope` whenever the workspace witness is not complete. */\n\treadonly scopeSensitive?: boolean;\n\t/** Keys a witness must have attested to; any missing key makes the witness stale for this claim. */\n\treadonly invalidationKeys: readonly string[];\n}\n\nexport interface ObservationNode {\n\treadonly observationId: string;\n\treadonly claimIds: readonly string[];\n\treadonly polarity: ObservationPolarity;\n\treadonly source: ObservationSource;\n\treadonly receiptId?: string;\n\treadonly sourceRoot: string;\n\treadonly environmentDigest: string;\n\t/** ISO-8601 instant after which the observation no longer qualifies. */\n\treadonly validUntil?: string;\n\treadonly invalidationKeys?: readonly string[];\n\t/** Witnesses sharing a group count once. The explicit-groups policy requires this for multi-witness claims. */\n\treadonly independenceGroup?: string;\n}\n\nexport interface WaiverNode {\n\treadonly waiverId: string;\n\treadonly claimId: string;\n\treadonly issuer: string;\n\treadonly reason: string;\n\treadonly sourceRoot: string;\n\treadonly expiresAt: string;\n}\n\nexport interface ClaimGraph {\n\treadonly schemaVersion: typeof CLAIM_GRAPH_SCHEMA_VERSION;\n\treadonly claims: readonly ClaimNode[];\n}\n\nexport type WitnessIndependencePolicy = \"legacy-observation-id\" | \"explicit-groups\";\n\nexport interface ProofClosureInput {\n\treadonly graph: ClaimGraph;\n\treadonly observations: readonly ObservationNode[];\n\t/** Compatibility default: legacy-observation-id. Strong profiles must opt into explicit-groups. */\n\treadonly witnessIndependence?: WitnessIndependencePolicy;\n\treadonly waivers: readonly WaiverNode[];\n\treadonly sourceRoot: string;\n\treadonly environmentDigest: string;\n\treadonly workspaceCompleteness: WorkspaceCompleteness;\n\treadonly unresolvedEffectIds: readonly string[];\n\t/** ISO-8601 evaluation instant; injected so evaluation never reads a clock. */\n\treadonly now: string;\n}\n\nexport type ClaimReasonCode =\n\t| \"claim.satisfied\"\n\t| \"claim.violated\"\n\t| \"claim.missing\"\n\t| \"claim.stale\"\n\t| \"claim.incomplete_scope\"\n\t| \"claim.insufficient_trust\"\n\t| \"claim.waived\";\n\nexport interface ClaimClosureEvaluation {\n\treadonly claimId: string;\n\treadonly severity: ClaimSeverity;\n\treadonly verdict: ClaimVerdict;\n\treadonly reasonCode: ClaimReasonCode;\n\t/** Qualified observations that decided the verdict (violations, or counted witnesses). */\n\treadonly observationIds: readonly string[];\n\treadonly waiverId?: string;\n\t/** Distinguish local obligations from a verdict derived only from children. */\n\treadonly blockingOrigin?: \"local\" | \"children\";\n}\n\nexport interface BlockingCutExplanation {\n\treadonly claimIds: readonly string[];\n\treadonly algorithm: \"exact-antichain\" | \"greedy\";\n\t/** Minimum cardinality in this repair model only; fallback has no minimality guarantee. */\n\treadonly optimality: \"minimum\" | \"not-proven\";\n\treadonly truncated: boolean;\n\treadonly exploredStates: number;\n\t/** Selected composite-local obligations that child repairs alone cannot resolve. */\n\treadonly localClaimIds: readonly string[];\n}\n\nexport interface ProofClosureResult {\n\treadonly verdict: VerificationVerdict;\n\treadonly witnessIndependence?: WitnessIndependencePolicy;\n\treadonly claimEvaluations: readonly ClaimClosureEvaluation[];\n\t/** Required claims that are not closed, in evaluation order. */\n\treadonly blockingClaimIds: readonly string[];\n\t/** Compatibility projection; consult blockingCut for bounded-search optimality and local causes. */\n\treadonly minimalBlockingCut: readonly string[];\n\t/** Explanation only; never overrides the closure verdict or unresolved global effects. */\n\treadonly blockingCut?: BlockingCutExplanation;\n\treadonly unresolvedEffectIds: readonly string[];\n\treadonly workspaceCompleteness: WorkspaceCompleteness;\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claim Closure Graph V1 vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* The flat `TaskSpec.claims` model answers "did this attempt satisfy its
|
|
5
|
+
* predicates?". The closure graph answers the stricter question a verified
|
|
6
|
+
* verdict needs: are all required claims closed by *qualified* witnesses —
|
|
7
|
+
* bound to this source root and environment, not expired, from a source the
|
|
8
|
+
* claim's policy trusts, independent enough to count separately — with no
|
|
9
|
+
* counterexample, no unresolved side effect, and a complete workspace scope?
|
|
10
|
+
*
|
|
11
|
+
* Pure vocabulary: no behaviour, no I/O, no clock. Evaluation lives in
|
|
12
|
+
* `claim-closure.ts`, structure checks in `claim-graph.ts`.
|
|
13
|
+
*/
|
|
14
|
+
export const CLAIM_GRAPH_SCHEMA_VERSION = "omk.claim-graph.v1";
|
|
15
|
+
export const OBSERVATION_TRUST_RANK = {
|
|
16
|
+
deterministic_validator: 6,
|
|
17
|
+
trusted_attestation: 5,
|
|
18
|
+
workspace_witness: 4,
|
|
19
|
+
effect_reconciliation: 4,
|
|
20
|
+
independent_review: 3,
|
|
21
|
+
self_review: 2,
|
|
22
|
+
model_narrative: 1,
|
|
23
|
+
};
|
|
24
|
+
/** Precedence when several verdicts compete; lower index wins. */
|
|
25
|
+
export const CLAIM_VERDICT_PRECEDENCE = [
|
|
26
|
+
"violated",
|
|
27
|
+
"stale",
|
|
28
|
+
"incomplete_scope",
|
|
29
|
+
"insufficient_trust",
|
|
30
|
+
"missing",
|
|
31
|
+
"waived",
|
|
32
|
+
"satisfied",
|
|
33
|
+
];
|
|
34
|
+
//# sourceMappingURL=claim-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-types.js","sourceRoot":"","sources":["../../src/claims/claim-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG,oBAA6B,CAAC;AAgBxE,MAAM,CAAC,MAAM,sBAAsB,GAAgD;IAClF,uBAAuB,EAAE,CAAC;IAC1B,mBAAmB,EAAE,CAAC;IACtB,iBAAiB,EAAE,CAAC;IACpB,qBAAqB,EAAE,CAAC;IACxB,kBAAkB,EAAE,CAAC;IACrB,WAAW,EAAE,CAAC;IACd,eAAe,EAAE,CAAC;CAClB,CAAC;AAaF,kEAAkE;AAClE,MAAM,CAAC,MAAM,wBAAwB,GAA4B;IAChE,UAAU;IACV,OAAO;IACP,kBAAkB;IAClB,oBAAoB;IACpB,SAAS;IACT,QAAQ;IACR,WAAW;CACX,CAAC","sourcesContent":["/**\n * Claim Closure Graph V1 vocabulary.\n *\n * The flat `TaskSpec.claims` model answers \"did this attempt satisfy its\n * predicates?\". The closure graph answers the stricter question a verified\n * verdict needs: are all required claims closed by *qualified* witnesses —\n * bound to this source root and environment, not expired, from a source the\n * claim's policy trusts, independent enough to count separately — with no\n * counterexample, no unresolved side effect, and a complete workspace scope?\n *\n * Pure vocabulary: no behaviour, no I/O, no clock. Evaluation lives in\n * `claim-closure.ts`, structure checks in `claim-graph.ts`.\n */\n\nexport const CLAIM_GRAPH_SCHEMA_VERSION = \"omk.claim-graph.v1\" as const;\n\nexport type ClaimNodeKind = \"requirement\" | \"safety\" | \"scope\" | \"quality\" | \"performance\" | \"release\";\nexport type ClaimSeverity = \"required\" | \"advisory\";\nexport type ClaimRule = \"all\" | \"any\";\n\n/** Trust lattice, highest first. A claim's `trustFloor` names the weakest source that may witness it. */\nexport type ObservationSource =\n\t| \"deterministic_validator\"\n\t| \"trusted_attestation\"\n\t| \"workspace_witness\"\n\t| \"effect_reconciliation\"\n\t| \"independent_review\"\n\t| \"self_review\"\n\t| \"model_narrative\";\n\nexport const OBSERVATION_TRUST_RANK: Readonly<Record<ObservationSource, number>> = {\n\tdeterministic_validator: 6,\n\ttrusted_attestation: 5,\n\tworkspace_witness: 4,\n\teffect_reconciliation: 4,\n\tindependent_review: 3,\n\tself_review: 2,\n\tmodel_narrative: 1,\n};\n\nexport type ObservationPolarity = \"supports\" | \"violates\";\n\nexport type ClaimVerdict =\n\t| \"satisfied\"\n\t| \"violated\"\n\t| \"missing\"\n\t| \"stale\"\n\t| \"incomplete_scope\"\n\t| \"insufficient_trust\"\n\t| \"waived\";\n\n/** Precedence when several verdicts compete; lower index wins. */\nexport const CLAIM_VERDICT_PRECEDENCE: readonly ClaimVerdict[] = [\n\t\"violated\",\n\t\"stale\",\n\t\"incomplete_scope\",\n\t\"insufficient_trust\",\n\t\"missing\",\n\t\"waived\",\n\t\"satisfied\",\n];\n\nexport type WorkspaceCompleteness = \"complete\" | \"partial_excluded\" | \"partial_truncated\" | \"unknown\";\n\nexport type VerificationVerdict = \"verified\" | \"unverified\" | \"inconclusive\" | \"violated\";\n\nexport interface ClaimNode {\n\treadonly claimId: string;\n\treadonly kind: ClaimNodeKind;\n\treadonly statement: string;\n\treadonly severity: ClaimSeverity;\n\t/** `inputs` are child claim ids. An empty list makes this a leaf witnessed by observations. */\n\treadonly satisfaction: { readonly rule: ClaimRule; readonly inputs: readonly string[] };\n\treadonly trustFloor: ObservationSource;\n\t/** Distinct independence groups a leaf needs among its qualified supporting witnesses. Default 1. */\n\treadonly requiredWitnesses?: number;\n\t/** Blocked with `incomplete_scope` whenever the workspace witness is not complete. */\n\treadonly scopeSensitive?: boolean;\n\t/** Keys a witness must have attested to; any missing key makes the witness stale for this claim. */\n\treadonly invalidationKeys: readonly string[];\n}\n\nexport interface ObservationNode {\n\treadonly observationId: string;\n\treadonly claimIds: readonly string[];\n\treadonly polarity: ObservationPolarity;\n\treadonly source: ObservationSource;\n\treadonly receiptId?: string;\n\treadonly sourceRoot: string;\n\treadonly environmentDigest: string;\n\t/** ISO-8601 instant after which the observation no longer qualifies. */\n\treadonly validUntil?: string;\n\treadonly invalidationKeys?: readonly string[];\n\t/** Witnesses sharing a group count once. The explicit-groups policy requires this for multi-witness claims. */\n\treadonly independenceGroup?: string;\n}\n\nexport interface WaiverNode {\n\treadonly waiverId: string;\n\treadonly claimId: string;\n\treadonly issuer: string;\n\treadonly reason: string;\n\treadonly sourceRoot: string;\n\treadonly expiresAt: string;\n}\n\nexport interface ClaimGraph {\n\treadonly schemaVersion: typeof CLAIM_GRAPH_SCHEMA_VERSION;\n\treadonly claims: readonly ClaimNode[];\n}\n\nexport type WitnessIndependencePolicy = \"legacy-observation-id\" | \"explicit-groups\";\n\nexport interface ProofClosureInput {\n\treadonly graph: ClaimGraph;\n\treadonly observations: readonly ObservationNode[];\n\t/** Compatibility default: legacy-observation-id. Strong profiles must opt into explicit-groups. */\n\treadonly witnessIndependence?: WitnessIndependencePolicy;\n\treadonly waivers: readonly WaiverNode[];\n\treadonly sourceRoot: string;\n\treadonly environmentDigest: string;\n\treadonly workspaceCompleteness: WorkspaceCompleteness;\n\treadonly unresolvedEffectIds: readonly string[];\n\t/** ISO-8601 evaluation instant; injected so evaluation never reads a clock. */\n\treadonly now: string;\n}\n\nexport type ClaimReasonCode =\n\t| \"claim.satisfied\"\n\t| \"claim.violated\"\n\t| \"claim.missing\"\n\t| \"claim.stale\"\n\t| \"claim.incomplete_scope\"\n\t| \"claim.insufficient_trust\"\n\t| \"claim.waived\";\n\nexport interface ClaimClosureEvaluation {\n\treadonly claimId: string;\n\treadonly severity: ClaimSeverity;\n\treadonly verdict: ClaimVerdict;\n\treadonly reasonCode: ClaimReasonCode;\n\t/** Qualified observations that decided the verdict (violations, or counted witnesses). */\n\treadonly observationIds: readonly string[];\n\treadonly waiverId?: string;\n\t/** Distinguish local obligations from a verdict derived only from children. */\n\treadonly blockingOrigin?: \"local\" | \"children\";\n}\n\nexport interface BlockingCutExplanation {\n\treadonly claimIds: readonly string[];\n\treadonly algorithm: \"exact-antichain\" | \"greedy\";\n\t/** Minimum cardinality in this repair model only; fallback has no minimality guarantee. */\n\treadonly optimality: \"minimum\" | \"not-proven\";\n\treadonly truncated: boolean;\n\treadonly exploredStates: number;\n\t/** Selected composite-local obligations that child repairs alone cannot resolve. */\n\treadonly localClaimIds: readonly string[];\n}\n\nexport interface ProofClosureResult {\n\treadonly verdict: VerificationVerdict;\n\treadonly witnessIndependence?: WitnessIndependencePolicy;\n\treadonly claimEvaluations: readonly ClaimClosureEvaluation[];\n\t/** Required claims that are not closed, in evaluation order. */\n\treadonly blockingClaimIds: readonly string[];\n\t/** Compatibility projection; consult blockingCut for bounded-search optimality and local causes. */\n\treadonly minimalBlockingCut: readonly string[];\n\t/** Explanation only; never overrides the closure verdict or unresolved global effects. */\n\treadonly blockingCut?: BlockingCutExplanation;\n\treadonly unresolvedEffectIds: readonly string[];\n\treadonly workspaceCompleteness: WorkspaceCompleteness;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export { explainBlockingCut, isBlockingVerdict, MAX_BLOCKING_CUT_CANDIDATES, minimalBlockingCut, } from "./claims/claim-blocking-cut.ts";
|
|
2
|
+
export { evaluateProofClosure } from "./claims/claim-closure.ts";
|
|
3
|
+
export { ClaimGraphError, canonicalClaimGraph, rootClaimIds, topologicalClaimOrder, validateClaimGraph, } from "./claims/claim-graph.ts";
|
|
4
|
+
export { type BlockingCutExplanation, CLAIM_GRAPH_SCHEMA_VERSION, CLAIM_VERDICT_PRECEDENCE, type ClaimClosureEvaluation, type ClaimGraph, type ClaimNode, type ClaimNodeKind, type ClaimRule, type ClaimSeverity, type ClaimVerdict, OBSERVATION_TRUST_RANK, type ObservationNode, type ObservationPolarity, type ObservationSource, type ProofClosureInput, type ProofClosureResult, type VerificationVerdict, type WaiverNode, type WitnessIndependencePolicy, type WorkspaceCompleteness, } from "./claims/claim-types.ts";
|
|
1
5
|
export { reduceRuntimeDecision } from "./decision.ts";
|
|
2
6
|
export { evaluateTask, ProtocolInvariantError } from "./evaluation.ts";
|
|
3
7
|
export type { AllCondition, AnyCondition, AttemptExecutor, AttemptOutcome, AttemptTrigger, ClaimCondition, ClaimEvaluation, ClaimPredicate, ClaimReasonCode, ClaimResult, EvaluationInput, EvaluationResult, ExecutionAttempt, JsonObject, JsonPrimitive, JsonValue, NotCondition, Observation, ObservationCondition, ProtocolVersion, RequirementLevel, RuntimeAction, RuntimeDecision, RuntimeDecisionInput, RuntimeDecisionPolicy, RuntimeDecisionReason, SemanticVerdict, TaskSpec, WaiverRecord, } from "./types.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,SAAS,EACT,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,QAAQ,EACR,YAAY,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EACN,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,iBAAiB,GACjB,MAAM,iBAAiB,CAAC","sourcesContent":["export { reduceRuntimeDecision } from \"./decision.ts\";\nexport { evaluateTask, ProtocolInvariantError } from \"./evaluation.ts\";\nexport type {\n\tAllCondition,\n\tAnyCondition,\n\tAttemptExecutor,\n\tAttemptOutcome,\n\tAttemptTrigger,\n\tClaimCondition,\n\tClaimEvaluation,\n\tClaimPredicate,\n\tClaimReasonCode,\n\tClaimResult,\n\tEvaluationInput,\n\tEvaluationResult,\n\tExecutionAttempt,\n\tJsonObject,\n\tJsonPrimitive,\n\tJsonValue,\n\tNotCondition,\n\tObservation,\n\tObservationCondition,\n\tProtocolVersion,\n\tRequirementLevel,\n\tRuntimeAction,\n\tRuntimeDecision,\n\tRuntimeDecisionInput,\n\tRuntimeDecisionPolicy,\n\tRuntimeDecisionReason,\n\tSemanticVerdict,\n\tTaskSpec,\n\tWaiverRecord,\n} from \"./types.ts\";\nexport { PROTOCOL_VERSION } from \"./types.ts\";\nexport {\n\tProtocolValidationError,\n\tparseEvaluationResult,\n\tparseExecutionAttempt,\n\tparseObservation,\n\tparseRuntimeDecision,\n\tparseTaskSpec,\n\tparseWaiverRecord,\n} from \"./validation.ts\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,kBAAkB,EAClB,iBAAiB,EACjB,2BAA2B,EAC3B,kBAAkB,GAClB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EACN,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,GAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,KAAK,sBAAsB,EAC3B,0BAA0B,EAC1B,wBAAwB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,sBAAsB,EACtB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EACf,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,GAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,SAAS,EACT,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,QAAQ,EACR,YAAY,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EACN,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,iBAAiB,GACjB,MAAM,iBAAiB,CAAC","sourcesContent":["export {\n\texplainBlockingCut,\n\tisBlockingVerdict,\n\tMAX_BLOCKING_CUT_CANDIDATES,\n\tminimalBlockingCut,\n} from \"./claims/claim-blocking-cut.ts\";\nexport { evaluateProofClosure } from \"./claims/claim-closure.ts\";\nexport {\n\tClaimGraphError,\n\tcanonicalClaimGraph,\n\trootClaimIds,\n\ttopologicalClaimOrder,\n\tvalidateClaimGraph,\n} from \"./claims/claim-graph.ts\";\nexport {\n\ttype BlockingCutExplanation,\n\tCLAIM_GRAPH_SCHEMA_VERSION,\n\tCLAIM_VERDICT_PRECEDENCE,\n\ttype ClaimClosureEvaluation,\n\ttype ClaimGraph,\n\ttype ClaimNode,\n\ttype ClaimNodeKind,\n\ttype ClaimRule,\n\ttype ClaimSeverity,\n\ttype ClaimVerdict,\n\tOBSERVATION_TRUST_RANK,\n\ttype ObservationNode,\n\ttype ObservationPolarity,\n\ttype ObservationSource,\n\ttype ProofClosureInput,\n\ttype ProofClosureResult,\n\ttype VerificationVerdict,\n\ttype WaiverNode,\n\ttype WitnessIndependencePolicy,\n\ttype WorkspaceCompleteness,\n} from \"./claims/claim-types.ts\";\nexport { reduceRuntimeDecision } from \"./decision.ts\";\nexport { evaluateTask, ProtocolInvariantError } from \"./evaluation.ts\";\nexport type {\n\tAllCondition,\n\tAnyCondition,\n\tAttemptExecutor,\n\tAttemptOutcome,\n\tAttemptTrigger,\n\tClaimCondition,\n\tClaimEvaluation,\n\tClaimPredicate,\n\tClaimReasonCode,\n\tClaimResult,\n\tEvaluationInput,\n\tEvaluationResult,\n\tExecutionAttempt,\n\tJsonObject,\n\tJsonPrimitive,\n\tJsonValue,\n\tNotCondition,\n\tObservation,\n\tObservationCondition,\n\tProtocolVersion,\n\tRequirementLevel,\n\tRuntimeAction,\n\tRuntimeDecision,\n\tRuntimeDecisionInput,\n\tRuntimeDecisionPolicy,\n\tRuntimeDecisionReason,\n\tSemanticVerdict,\n\tTaskSpec,\n\tWaiverRecord,\n} from \"./types.ts\";\nexport { PROTOCOL_VERSION } from \"./types.ts\";\nexport {\n\tProtocolValidationError,\n\tparseEvaluationResult,\n\tparseExecutionAttempt,\n\tparseObservation,\n\tparseRuntimeDecision,\n\tparseTaskSpec,\n\tparseWaiverRecord,\n} from \"./validation.ts\";\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export { explainBlockingCut, isBlockingVerdict, MAX_BLOCKING_CUT_CANDIDATES, minimalBlockingCut, } from "./claims/claim-blocking-cut.js";
|
|
2
|
+
export { evaluateProofClosure } from "./claims/claim-closure.js";
|
|
3
|
+
export { ClaimGraphError, canonicalClaimGraph, rootClaimIds, topologicalClaimOrder, validateClaimGraph, } from "./claims/claim-graph.js";
|
|
4
|
+
export { CLAIM_GRAPH_SCHEMA_VERSION, CLAIM_VERDICT_PRECEDENCE, OBSERVATION_TRUST_RANK, } from "./claims/claim-types.js";
|
|
1
5
|
export { reduceRuntimeDecision } from "./decision.js";
|
|
2
6
|
export { evaluateTask, ProtocolInvariantError } from "./evaluation.js";
|
|
3
7
|
export { PROTOCOL_VERSION } from "./types.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAgCvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EACN,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,iBAAiB,GACjB,MAAM,iBAAiB,CAAC","sourcesContent":["export { reduceRuntimeDecision } from \"./decision.ts\";\nexport { evaluateTask, ProtocolInvariantError } from \"./evaluation.ts\";\nexport type {\n\tAllCondition,\n\tAnyCondition,\n\tAttemptExecutor,\n\tAttemptOutcome,\n\tAttemptTrigger,\n\tClaimCondition,\n\tClaimEvaluation,\n\tClaimPredicate,\n\tClaimReasonCode,\n\tClaimResult,\n\tEvaluationInput,\n\tEvaluationResult,\n\tExecutionAttempt,\n\tJsonObject,\n\tJsonPrimitive,\n\tJsonValue,\n\tNotCondition,\n\tObservation,\n\tObservationCondition,\n\tProtocolVersion,\n\tRequirementLevel,\n\tRuntimeAction,\n\tRuntimeDecision,\n\tRuntimeDecisionInput,\n\tRuntimeDecisionPolicy,\n\tRuntimeDecisionReason,\n\tSemanticVerdict,\n\tTaskSpec,\n\tWaiverRecord,\n} from \"./types.ts\";\nexport { PROTOCOL_VERSION } from \"./types.ts\";\nexport {\n\tProtocolValidationError,\n\tparseEvaluationResult,\n\tparseExecutionAttempt,\n\tparseObservation,\n\tparseRuntimeDecision,\n\tparseTaskSpec,\n\tparseWaiverRecord,\n} from \"./validation.ts\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,kBAAkB,EAClB,iBAAiB,EACjB,2BAA2B,EAC3B,kBAAkB,GAClB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EACN,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,GAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEN,0BAA0B,EAC1B,wBAAwB,EAQxB,sBAAsB,GAUtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAgCvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EACN,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,iBAAiB,GACjB,MAAM,iBAAiB,CAAC","sourcesContent":["export {\n\texplainBlockingCut,\n\tisBlockingVerdict,\n\tMAX_BLOCKING_CUT_CANDIDATES,\n\tminimalBlockingCut,\n} from \"./claims/claim-blocking-cut.ts\";\nexport { evaluateProofClosure } from \"./claims/claim-closure.ts\";\nexport {\n\tClaimGraphError,\n\tcanonicalClaimGraph,\n\trootClaimIds,\n\ttopologicalClaimOrder,\n\tvalidateClaimGraph,\n} from \"./claims/claim-graph.ts\";\nexport {\n\ttype BlockingCutExplanation,\n\tCLAIM_GRAPH_SCHEMA_VERSION,\n\tCLAIM_VERDICT_PRECEDENCE,\n\ttype ClaimClosureEvaluation,\n\ttype ClaimGraph,\n\ttype ClaimNode,\n\ttype ClaimNodeKind,\n\ttype ClaimRule,\n\ttype ClaimSeverity,\n\ttype ClaimVerdict,\n\tOBSERVATION_TRUST_RANK,\n\ttype ObservationNode,\n\ttype ObservationPolarity,\n\ttype ObservationSource,\n\ttype ProofClosureInput,\n\ttype ProofClosureResult,\n\ttype VerificationVerdict,\n\ttype WaiverNode,\n\ttype WitnessIndependencePolicy,\n\ttype WorkspaceCompleteness,\n} from \"./claims/claim-types.ts\";\nexport { reduceRuntimeDecision } from \"./decision.ts\";\nexport { evaluateTask, ProtocolInvariantError } from \"./evaluation.ts\";\nexport type {\n\tAllCondition,\n\tAnyCondition,\n\tAttemptExecutor,\n\tAttemptOutcome,\n\tAttemptTrigger,\n\tClaimCondition,\n\tClaimEvaluation,\n\tClaimPredicate,\n\tClaimReasonCode,\n\tClaimResult,\n\tEvaluationInput,\n\tEvaluationResult,\n\tExecutionAttempt,\n\tJsonObject,\n\tJsonPrimitive,\n\tJsonValue,\n\tNotCondition,\n\tObservation,\n\tObservationCondition,\n\tProtocolVersion,\n\tRequirementLevel,\n\tRuntimeAction,\n\tRuntimeDecision,\n\tRuntimeDecisionInput,\n\tRuntimeDecisionPolicy,\n\tRuntimeDecisionReason,\n\tSemanticVerdict,\n\tTaskSpec,\n\tWaiverRecord,\n} from \"./types.ts\";\nexport { PROTOCOL_VERSION } from \"./types.ts\";\nexport {\n\tProtocolValidationError,\n\tparseEvaluationResult,\n\tparseExecutionAttempt,\n\tparseObservation,\n\tparseRuntimeDecision,\n\tparseTaskSpec,\n\tparseWaiverRecord,\n} from \"./validation.ts\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omk-protocol",
|
|
3
|
-
"version": "0.98.
|
|
3
|
+
"version": "0.98.4",
|
|
4
4
|
"description": "Canonical OMK Run Protocol contracts and pure semantic reducers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
|
-
"README.md"
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md"
|
|
18
19
|
],
|
|
19
20
|
"scripts": {
|
|
20
21
|
"clean": "shx rm -rf dist",
|