@cassiomc1/forgeloop 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +1 -1
- package/AGENTS.md +1 -1
- package/AGENT_COMPATIBILITY.md +8 -0
- package/CLAUDE.md +1 -1
- package/CONTRIBUTING.md +90 -0
- package/DOCS_INDEX.md +46 -12
- package/ENG/c-development-eng.md +112 -0
- package/ENG/cpp-development-eng.md +109 -0
- package/ENG/dotnet-aspnetcore-development-eng.md +401 -0
- package/ENG/go-development-eng.md +103 -0
- package/ENG/java-development-eng.md +125 -0
- package/ENG/nodejs-backend-development-eng.md +605 -0
- package/ENG/php-development-eng.md +104 -0
- package/ENG/rust-development-eng.md +422 -0
- package/ENG/sec-code-eng.md +7 -7
- package/ENG/sql-development-eng.md +108 -0
- package/ENG/swift-development-eng.md +111 -0
- package/ENG/typescript-development-eng.md +108 -0
- package/EXECUTION_STATE.md +12 -0
- package/GUIDE_ROUTER.md +418 -9
- package/LOOP_ENGINEERING.md +28 -2
- package/ORCHESTRATOR_INTEGRATION.md +9 -5
- package/PROTOCOL_INTEGRATION.md +55 -2
- package/QUALITY_SCORECARD.md +1 -0
- package/README.md +78 -52
- package/TERMINOLOGY.md +2 -0
- package/THIRD_PARTY_NOTICES.md +19 -7
- package/THREAT_MODEL.md +140 -1
- package/completions/_forgeloop +22 -4
- package/completions/forgeloop.bash +40 -4
- package/completions/forgeloop.fish +130 -1
- package/docs/ADVISORY_CONTEXT.md +25 -0
- package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
- package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +81 -3
- package/docs/AGENT_SKILL.md +66 -0
- package/docs/ARTIFACT_REFERENCE.md +123 -0
- package/docs/AUDIT_UX.md +46 -0
- package/docs/BROWSER_VERIFICATION.md +136 -0
- package/docs/CLI_REFERENCE.md +392 -10
- package/docs/CODE_ATTESTATION.md +2 -2
- package/docs/DOCUMENTATION_GUIDE.md +34 -12
- package/docs/GETTING_STARTED.md +59 -0
- package/docs/JEV_BENCHMARKS.md +31 -0
- package/docs/MODEL_ROUTING.md +37 -0
- package/docs/OPENSRC_ADAPTER.md +241 -0
- package/docs/PACKAGE_CONTENTS.md +60 -19
- package/docs/PROVIDERS.md +126 -0
- package/docs/PROVIDER_ARCHITECTURE.md +199 -0
- package/docs/RECIPES.md +32 -0
- package/docs/RELEASE_CHECKLIST.md +66 -5
- package/docs/SECURITY_REVIEW.md +71 -0
- package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
- package/docs/TEST_INTELLIGENCE.md +29 -0
- package/docs/TEST_PRUNING.md +14 -0
- package/docs/TROUBLESHOOTING.md +298 -3
- package/docs/UNIVERSAL_INTEGRATION.md +31 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
- package/docs/diagrams/README.md +13 -9
- package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
- package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
- package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
- package/docs/documentation-manifest.json +1397 -0
- package/docs/protocol-requirements.json +101 -0
- package/package.json +46 -4
- package/schemas/config.schema.json +14 -0
- package/schemas/context-plan.schema.json +18 -0
- package/schemas/routing-input.schema.json +1 -1
- package/schemas/semantic-decision.schema.json +46 -0
- package/schemas/test-utility.schema.json +44 -0
- package/scripts/CI_VALIDATORS.md +84 -11
- package/scripts/benchmark-jev.mjs +5 -0
- package/scripts/benchmark-test-intelligence.mjs +4 -0
- package/scripts/generate-agent-protocol-summary.mjs +40 -1
- package/scripts/generate-forgeloop-skill.mjs +133 -0
- package/scripts/jev-smoke.mjs +19 -0
- package/skills/forgeloop/README.md +9 -0
- package/skills/forgeloop/SKILL.md +77 -0
- package/skills/forgeloop/references/lifecycle.md +9 -0
- package/skills/forgeloop/references/recovery.md +7 -0
- package/skills/forgeloop/references/verification.md +7 -0
- package/src/adapters/agent-browser/assertions.js +47 -0
- package/src/adapters/agent-browser/commands.js +54 -0
- package/src/adapters/agent-browser/index.js +3 -0
- package/src/adapters/agent-browser/locator.js +40 -0
- package/src/adapters/agent-browser/process.js +215 -0
- package/src/adapters/agent-browser/provider.js +313 -0
- package/src/adapters/emulated-services/constants.js +24 -0
- package/src/adapters/emulated-services/index.js +7 -0
- package/src/adapters/emulated-services/process.js +162 -0
- package/src/adapters/emulated-services/provider.js +282 -0
- package/src/adapters/opensrc/normalize.js +90 -0
- package/src/adapters/opensrc/process.js +248 -0
- package/src/adapters/opensrc/provider.js +338 -0
- package/src/adapters/opensrc/search.js +264 -0
- package/src/adapters/typesafe/client.js +28 -0
- package/src/adapters/typesafe/engine.js +63 -0
- package/src/adapters/typesafe/normalize.js +41 -0
- package/src/cli.js +108 -0
- package/src/commands/checkpoint-revalidate.js +176 -0
- package/src/commands/context-plan.js +38 -0
- package/src/commands/contract-create.js +264 -0
- package/src/commands/contract-revise.js +236 -0
- package/src/commands/decision-show.js +14 -0
- package/src/commands/decision-status.js +22 -0
- package/src/commands/discover.js +41 -0
- package/src/commands/doctor.js +15 -0
- package/src/commands/gate-record.js +205 -0
- package/src/commands/gate-revalidate.js +137 -0
- package/src/commands/model-route.js +32 -0
- package/src/commands/next.js +19 -7
- package/src/commands/route.js +146 -18
- package/src/commands/semantic-plan.js +17 -0
- package/src/commands/task-abandon.js +224 -0
- package/src/commands/task-create.js +84 -25
- package/src/commands/task-list.js +22 -2
- package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
- package/src/commands/task-repair-contract-bootstrap.js +263 -0
- package/src/commands/test-inventory.js +5 -0
- package/src/commands/test-prune-plan.js +5 -0
- package/src/commands/test-prune-probe.js +5 -0
- package/src/commands/test-utility.js +5 -0
- package/src/commands/validate-protocol.js +10 -1
- package/src/config/guides.json +44 -0
- package/src/core/artifact-registry.js +24 -0
- package/src/core/audit-ux.js +514 -0
- package/src/core/browser-verification/constants.js +149 -0
- package/src/core/browser-verification/normalize.js +254 -0
- package/src/core/browser-verification/provider.js +519 -0
- package/src/core/browser-verification/service.js +115 -0
- package/src/core/build-script.js +151 -0
- package/src/core/c-cpp-project.js +143 -0
- package/src/core/checkpoint-revalidation.js +319 -0
- package/src/core/cli-command-definitions.js +249 -1
- package/src/core/command-executors.js +115 -3
- package/src/core/command-input.js +212 -102
- package/src/core/completion-artifacts.js +14 -5
- package/src/core/completion.js +4 -6
- package/src/core/config.js +3 -0
- package/src/core/context-compiler/budget.js +9 -0
- package/src/core/context-compiler/candidates.js +39 -0
- package/src/core/context-compiler/compiler.js +63 -0
- package/src/core/context-compiler/fingerprint.js +11 -0
- package/src/core/context-compiler/policy.js +13 -0
- package/src/core/context-compiler/result.js +23 -0
- package/src/core/contract-bootstrap-recovery.js +655 -0
- package/src/core/contract-presets.js +82 -0
- package/src/core/contract-revision.js +210 -0
- package/src/core/decision/artifact.js +69 -0
- package/src/core/decision/benchmarks.js +103 -0
- package/src/core/decision/cache.js +27 -0
- package/src/core/decision/constants.js +58 -0
- package/src/core/decision/cutover.js +34 -0
- package/src/core/decision/engine.js +22 -0
- package/src/core/decision/errors.js +68 -0
- package/src/core/decision/events.js +101 -0
- package/src/core/decision/freshness.js +19 -0
- package/src/core/decision/normalizers/index.js +115 -0
- package/src/core/decision/policy.js +18 -0
- package/src/core/decision/projection.js +16 -0
- package/src/core/decision/question-registry.js +201 -0
- package/src/core/decision/request.js +26 -0
- package/src/core/decision/resolver.js +130 -0
- package/src/core/decision/result.js +58 -0
- package/src/core/decision/service.js +156 -0
- package/src/core/decision/state-builder.js +65 -0
- package/src/core/decision/task-bindings.js +30 -0
- package/src/core/decision/test-provider.js +32 -0
- package/src/core/decision/thresholds.js +15 -0
- package/src/core/error-codes.js +281 -3
- package/src/core/events.js +226 -57
- package/src/core/evidence-readiness.js +9 -0
- package/src/core/execution-prerequisites.js +14 -0
- package/src/core/execution-profile.js +63 -38
- package/src/core/filesystem.js +1 -10
- package/src/core/gate-provenance.js +124 -0
- package/src/core/go-project.js +206 -0
- package/src/core/integration-invocation-policy.js +27 -4
- package/src/core/integration-resources.js +86 -61
- package/src/core/java-project.js +403 -0
- package/src/core/model-router/constants.js +10 -0
- package/src/core/model-router/policy.js +103 -0
- package/src/core/model-router/router.js +37 -0
- package/src/core/multi-language-project.js +117 -0
- package/src/core/next-action-model.js +58 -0
- package/src/core/next-action-phases.js +130 -42
- package/src/core/next-action-refresh.js +43 -9
- package/src/core/next-action-review-phase.js +7 -2
- package/src/core/next-action.js +35 -7
- package/src/core/next-explanation.js +63 -0
- package/src/core/phase.js +128 -10
- package/src/core/php-project.js +85 -0
- package/src/core/preflight-consistency.js +23 -9
- package/src/core/preflight-loaders.js +37 -5
- package/src/core/project-detection.js +1760 -52
- package/src/core/protocol-info.js +65 -0
- package/src/core/protocol.js +20 -0
- package/src/core/reconcile-closure.js +132 -53
- package/src/core/recovery-history.js +1 -0
- package/src/core/resumability.js +154 -44
- package/src/core/route-artifact.js +15 -1
- package/src/core/router.js +223 -4
- package/src/core/runtime-context.js +118 -61
- package/src/core/rust-project.js +400 -0
- package/src/core/schema-validation.js +3 -0
- package/src/core/security-review/constants.js +64 -0
- package/src/core/security-review/normalize.js +245 -0
- package/src/core/security-review/provider.js +204 -0
- package/src/core/security-review/service.js +134 -0
- package/src/core/semantic-planning/constants.js +19 -0
- package/src/core/semantic-planning/projection.js +94 -0
- package/src/core/semantic-planning/service.js +15 -0
- package/src/core/sources.js +37 -0
- package/src/core/sql-project.js +141 -0
- package/src/core/swift-project.js +200 -0
- package/src/core/task-claim-state.js +201 -1
- package/src/core/task-conflict-inspection.js +31 -5
- package/src/core/task-paths.js +13 -0
- package/src/core/task-recovery.js +1 -0
- package/src/core/templates.js +3 -0
- package/src/core/test-intelligence/benchmarks.js +68 -0
- package/src/core/test-intelligence/inventory.js +73 -0
- package/src/core/test-intelligence/prune.js +90 -0
- package/src/core/test-intelligence/semantic-state.js +15 -0
- package/src/core/test-intelligence/service.js +40 -0
- package/src/core/test-intelligence/utility.js +50 -0
- package/src/core/trace.js +11 -7
- package/src/core/transaction.js +1 -0
- package/src/core/typescript-project.js +349 -0
- package/src/core/xml-structure.js +123 -0
- package/src/integration.d.ts +492 -0
- package/src/integration.js +54 -0
- package/src/providers/README.md +47 -0
- package/src/providers/capabilities.js +46 -0
- package/src/providers/errors.js +15 -0
- package/src/providers/index.js +29 -0
- package/src/providers/json-snapshot.js +105 -0
- package/src/providers/registry.js +152 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
function quotedValue(text, start) {
|
|
2
|
+
const quote = text[start];
|
|
3
|
+
let value = "";
|
|
4
|
+
let escaped = false;
|
|
5
|
+
for (let index = start + 1; index < text.length; index += 1) {
|
|
6
|
+
const character = text[index];
|
|
7
|
+
if (escaped) {
|
|
8
|
+
value += character;
|
|
9
|
+
escaped = false;
|
|
10
|
+
} else if (character === "\\") {
|
|
11
|
+
escaped = true;
|
|
12
|
+
} else if (character === quote) {
|
|
13
|
+
return { value, next: index + 1 };
|
|
14
|
+
} else {
|
|
15
|
+
value += character;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function bracketEnd(text, start) {
|
|
22
|
+
const marker = text.slice(start).match(/^\[(=*)\[/u);
|
|
23
|
+
if (!marker) return null;
|
|
24
|
+
const closing = `]${marker[1]}]`;
|
|
25
|
+
const end = text.indexOf(closing, start + marker[0].length);
|
|
26
|
+
return end < 0 ? null : { next: end + closing.length };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function maskRange(text, start, end) {
|
|
30
|
+
return text.slice(start, end).replace(/[^\n\r]/gu, " ");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function skipHashComment(text, start) {
|
|
34
|
+
const bracket = bracketEnd(text, start + 1);
|
|
35
|
+
if (text[start + 1] === "[" && bracket) return bracket.next;
|
|
36
|
+
const lineEnd = text.slice(start).search(/[\n\r]/u);
|
|
37
|
+
return lineEnd < 0 ? text.length : start + lineEnd;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function skipBuildToken(text, start) {
|
|
41
|
+
if (text[start] === "#") return { next: skipHashComment(text, start) };
|
|
42
|
+
if (text[start] === "\"" || text[start] === "'") {
|
|
43
|
+
const quoted = quotedValue(text, start);
|
|
44
|
+
return quoted ? { next: quoted.next } : null;
|
|
45
|
+
}
|
|
46
|
+
const bracket = bracketEnd(text, start);
|
|
47
|
+
return bracket ? { next: bracket.next } : { next: start + 1 };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function maskBuildScript(text) {
|
|
51
|
+
if (typeof text !== "string") return null;
|
|
52
|
+
let result = "";
|
|
53
|
+
for (let index = 0; index < text.length;) {
|
|
54
|
+
const step = skipBuildToken(text, index);
|
|
55
|
+
if (!step) return null;
|
|
56
|
+
result += step.next === index + 1
|
|
57
|
+
? text[index]
|
|
58
|
+
: maskRange(text, index, step.next);
|
|
59
|
+
index = step.next;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function callEnd(text, open) {
|
|
65
|
+
let depth = 1;
|
|
66
|
+
for (let index = open + 1; index < text.length; index += 1) {
|
|
67
|
+
const character = text[index];
|
|
68
|
+
const step = skipBuildToken(text, index);
|
|
69
|
+
if (!step) return null;
|
|
70
|
+
if (step.next !== index + 1) {
|
|
71
|
+
index = step.next - 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (character === "(") depth += 1;
|
|
75
|
+
else if (character === ")") {
|
|
76
|
+
depth -= 1;
|
|
77
|
+
if (depth === 0) return index;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function extractBuildCalls(text, names, { caseInsensitive = false } = {}) {
|
|
84
|
+
if (typeof text !== "string" || !Array.isArray(names)) return null;
|
|
85
|
+
const wanted = new Set(names.map((name) => caseInsensitive ? name.toLowerCase() : name));
|
|
86
|
+
const calls = [];
|
|
87
|
+
for (let index = 0; index < text.length;) {
|
|
88
|
+
const step = skipBuildToken(text, index);
|
|
89
|
+
if (!step) return null;
|
|
90
|
+
if (step.next !== index + 1) {
|
|
91
|
+
index = step.next;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const name = text.slice(index).match(/^[A-Za-z_][A-Za-z0-9_]*/u)?.[0];
|
|
95
|
+
if (!name || !wanted.has(caseInsensitive ? name.toLowerCase() : name)) {
|
|
96
|
+
index += name?.length ?? 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
let open = index + name.length;
|
|
100
|
+
while (/\s/u.test(text[open] ?? "")) open += 1;
|
|
101
|
+
if (text[open] !== "(") {
|
|
102
|
+
index += name.length;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const close = callEnd(text, open);
|
|
106
|
+
if (close === null) return null;
|
|
107
|
+
calls.push({ name, body: text.slice(open + 1, close) });
|
|
108
|
+
index = close + 1;
|
|
109
|
+
}
|
|
110
|
+
return calls;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function splitBuildArguments(text) {
|
|
114
|
+
const parts = [];
|
|
115
|
+
let start = 0;
|
|
116
|
+
const depths = { "(": 0, "[": 0, "{": 0 };
|
|
117
|
+
for (let index = 0; index < text.length;) {
|
|
118
|
+
const step = skipBuildToken(text, index);
|
|
119
|
+
if (!step) return null;
|
|
120
|
+
if (step.next !== index + 1) {
|
|
121
|
+
index = step.next;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const character = text[index];
|
|
125
|
+
if (character === "(" || character === "[" || character === "{") depths[character] += 1;
|
|
126
|
+
if (character === ")" || character === "]" || character === "}") {
|
|
127
|
+
const opening = character === ")" ? "(" : character === "]" ? "[" : "{";
|
|
128
|
+
depths[opening] -= 1;
|
|
129
|
+
if (depths[opening] < 0) return null;
|
|
130
|
+
}
|
|
131
|
+
if (character === "," && Object.values(depths).every((depth) => depth === 0)) {
|
|
132
|
+
parts.push(text.slice(start, index).trim());
|
|
133
|
+
start = index + 1;
|
|
134
|
+
}
|
|
135
|
+
index += 1;
|
|
136
|
+
}
|
|
137
|
+
if (Object.values(depths).some((depth) => depth !== 0)) return null;
|
|
138
|
+
parts.push(text.slice(start).trim());
|
|
139
|
+
return parts.filter(Boolean);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function quotedBuildArguments(text) {
|
|
143
|
+
const parts = splitBuildArguments(text);
|
|
144
|
+
if (!parts) return null;
|
|
145
|
+
return parts.flatMap((part) => {
|
|
146
|
+
const value = part.length >= 2 && (part[0] === "\"" || part[0] === "'")
|
|
147
|
+
? quotedValue(part, 0)
|
|
148
|
+
: null;
|
|
149
|
+
return value && value.next === part.length ? [value.value] : [];
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createLanguageProject, C_SOURCE_EXTENSIONS, CPP_SOURCE_EXTENSIONS, pathBelongsToRoot, portablePath } from "./multi-language-project.js";
|
|
2
|
+
import { extractBuildCalls, maskBuildScript, quotedBuildArguments } from "./build-script.js";
|
|
3
|
+
|
|
4
|
+
function invalidNative() {
|
|
5
|
+
return {
|
|
6
|
+
valid: false,
|
|
7
|
+
c: false,
|
|
8
|
+
cpp: false,
|
|
9
|
+
make: false,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function nativeLanguageTokens(text) {
|
|
14
|
+
const tokens = text.toUpperCase().match(/\b(?:CXX|CPP|C)\b/gu) ?? [];
|
|
15
|
+
return {
|
|
16
|
+
c: tokens.includes("C"),
|
|
17
|
+
cpp: tokens.some((language) => language === "CXX" || language === "CPP"),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function nativeLanguagesFromCMake(text) {
|
|
22
|
+
const result = { c: false, cpp: false };
|
|
23
|
+
const calls = extractBuildCalls(text, ["project", "enable_language"], { caseInsensitive: true });
|
|
24
|
+
if (!calls) return result;
|
|
25
|
+
for (const call of calls) {
|
|
26
|
+
const body = maskBuildScript(call.body);
|
|
27
|
+
if (body === null) continue;
|
|
28
|
+
if (call.name.toLowerCase() === "enable_language") {
|
|
29
|
+
const languages = nativeLanguageTokens(body);
|
|
30
|
+
result.c ||= languages.c;
|
|
31
|
+
result.cpp ||= languages.cpp;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const explicit = body.match(/\blanguages?\b([\s\S]*)/iu)?.[1]
|
|
35
|
+
?? body.match(/^\s*[^\s]+\s+((?:C|CXX|CPP)\b[\s\S]*)/iu)?.[1]
|
|
36
|
+
?? "";
|
|
37
|
+
const languages = nativeLanguageTokens(explicit);
|
|
38
|
+
result.c ||= languages.c;
|
|
39
|
+
result.cpp ||= languages.cpp;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseCMake(text) {
|
|
45
|
+
if (typeof text !== "string" || text.length > 1024 * 1024 || /\r(?!\n)/u.test(text)) return invalidNative();
|
|
46
|
+
const source = maskBuildScript(text);
|
|
47
|
+
if (source === null) return invalidNative();
|
|
48
|
+
const languages = nativeLanguagesFromCMake(text);
|
|
49
|
+
const hasProject = /\bproject\s*\(/iu.test(source);
|
|
50
|
+
const hasLanguage = /\benable_language\s*\(/iu.test(source);
|
|
51
|
+
return { valid: hasProject || hasLanguage, ...languages, make: false };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function parseMeson(text) {
|
|
55
|
+
if (typeof text !== "string" || text.length > 1024 * 1024 || /\r(?!\n)/u.test(text)) return invalidNative();
|
|
56
|
+
const calls = extractBuildCalls(text, ["project", "add_languages"]);
|
|
57
|
+
if (!calls || calls.length === 0) return invalidNative();
|
|
58
|
+
const languageValues = calls.flatMap((call) => {
|
|
59
|
+
const values = quotedBuildArguments(call.body) ?? [];
|
|
60
|
+
return call.name === "project" ? values.slice(1) : values;
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
valid: true,
|
|
64
|
+
c: languageValues.some((value) => value.toLowerCase() === "c"),
|
|
65
|
+
cpp: languageValues.some((value) => ["cpp", "cxx"].includes(value.toLowerCase())),
|
|
66
|
+
make: false,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function parseBazelNative(text) {
|
|
71
|
+
if (typeof text !== "string" || text.length > 1024 * 1024 || /\r(?!\n)/u.test(text)) return invalidNative();
|
|
72
|
+
const source = maskBuildScript(text);
|
|
73
|
+
const hasNativeRule = source !== null && /\b(?:cc_|c_)(?:library|binary|test|shared_library|static_library)\s*\(/u.test(source);
|
|
74
|
+
return { valid: hasNativeRule, c: false, cpp: false, make: false };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function parseMakefile(text) {
|
|
78
|
+
if (typeof text !== "string" || text.length > 1024 * 1024 || /\r(?!\n)/u.test(text)) return invalidNative();
|
|
79
|
+
return { valid: true, c: false, cpp: false, make: true };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sourceEvidence(sourceFiles, targetRoot, projectRoot, projectRoots) {
|
|
83
|
+
const c = sourceFiles.some((file) => C_SOURCE_EXTENSIONS.has(file.extension)
|
|
84
|
+
&& pathBelongsToRoot(portablePath(targetRoot, file.path), projectRoot, projectRoots));
|
|
85
|
+
const cpp = sourceFiles.some((file) => CPP_SOURCE_EXTENSIONS.has(file.extension)
|
|
86
|
+
&& pathBelongsToRoot(portablePath(targetRoot, file.path), projectRoot, projectRoots));
|
|
87
|
+
return { c, cpp };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseNativeManifest(manifestName, manifestText) {
|
|
91
|
+
const normalizedName = manifestName.toLowerCase();
|
|
92
|
+
if (normalizedName.includes("cmake")) return parseCMake(manifestText);
|
|
93
|
+
if (normalizedName.includes("meson")) return parseMeson(manifestText);
|
|
94
|
+
if (["makefile", "gnumakefile"].includes(normalizedName)) return parseMakefile(manifestText);
|
|
95
|
+
return parseBazelNative(manifestText);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function nativeFrameworks(parsed, source) {
|
|
99
|
+
return {
|
|
100
|
+
c: parsed.c || (parsed.valid && source.c),
|
|
101
|
+
cpp: parsed.cpp || (parsed.valid && source.cpp),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function nativePrimarySignals({ manifestRelative, projectRoot, parsed, source, c, cpp }) {
|
|
106
|
+
if (!c && !cpp) return [];
|
|
107
|
+
return [
|
|
108
|
+
...(c && parsed.c ? [`${manifestRelative}:c`] : []),
|
|
109
|
+
...(cpp && parsed.cpp ? [`${manifestRelative}:cpp`] : []),
|
|
110
|
+
...(c && source.c && !parsed.c ? [`${projectRoot}/source:c`] : []),
|
|
111
|
+
...(cpp && source.cpp && !parsed.cpp ? [`${projectRoot}/source:cpp`] : []),
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function inspectNativeProject({ projectRoot, manifestRelative, manifestText, manifestName, targetRoot, sourceFiles, projectRoots }) {
|
|
116
|
+
const parsed = parseNativeManifest(manifestName, manifestText);
|
|
117
|
+
const source = sourceEvidence(sourceFiles, targetRoot, projectRoot, projectRoots);
|
|
118
|
+
const { c, cpp } = nativeFrameworks(parsed, source);
|
|
119
|
+
const primary = c || cpp;
|
|
120
|
+
const frameworks = [];
|
|
121
|
+
if (c) frameworks.push("c");
|
|
122
|
+
if (cpp) frameworks.push("cpp");
|
|
123
|
+
const primarySignals = nativePrimarySignals({ manifestRelative, projectRoot, parsed, source, c, cpp });
|
|
124
|
+
const supportingSignals = parsed.make ? [`${manifestRelative}:make`] : [];
|
|
125
|
+
return createLanguageProject({
|
|
126
|
+
kind: "native",
|
|
127
|
+
root: projectRoot,
|
|
128
|
+
manifest: manifestRelative,
|
|
129
|
+
frameworks,
|
|
130
|
+
primary,
|
|
131
|
+
primarySignals,
|
|
132
|
+
supportingSignals,
|
|
133
|
+
internal: { ...parsed, valid: parsed.valid },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function nativeFrameworkForSource(fileName) {
|
|
138
|
+
const rawExtension = fileName.match(/\.[^.]+$/u)?.[0] ?? "";
|
|
139
|
+
const extension = rawExtension === ".C" ? ".C" : rawExtension.toLowerCase();
|
|
140
|
+
if (C_SOURCE_EXTENSIONS.has(extension)) return "c";
|
|
141
|
+
if (CPP_SOURCE_EXTENSIONS.has(extension)) return "cpp";
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { canonicalFingerprint } from "./artifacts.js";
|
|
2
|
+
import {
|
|
3
|
+
resolveCanonicalRouteEvolution,
|
|
4
|
+
resolveCanonicalPostRepairRouteBinding,
|
|
5
|
+
resolveEffectiveContractBootstrapRepairAnchor,
|
|
6
|
+
} from "./contract-bootstrap-recovery.js";
|
|
7
|
+
import { resolveCanonicalContractEvolution } from "./contract-revision.js";
|
|
8
|
+
|
|
9
|
+
export const CHECKPOINT_REVALIDATED_EVENT = "CHECKPOINT_REVALIDATED";
|
|
10
|
+
export const CHECKPOINT_REVALIDATION_PHASES = Object.freeze(["ROUTED"]);
|
|
11
|
+
|
|
12
|
+
const REPOSITORY_FINGERPRINT_KEYS = Object.freeze(["branch", "head"]);
|
|
13
|
+
const REVALIDATION_DETAIL_KEYS = Object.freeze([
|
|
14
|
+
"phase",
|
|
15
|
+
"previousRepositoryFingerprint",
|
|
16
|
+
"repositoryFingerprint",
|
|
17
|
+
"contractFingerprint",
|
|
18
|
+
"routeFingerprint",
|
|
19
|
+
"previousStateRevision",
|
|
20
|
+
"revalidatedStateRevision",
|
|
21
|
+
"previousStateFingerprint",
|
|
22
|
+
"revalidatedStateFingerprint",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function invalid(message) {
|
|
26
|
+
const error = new Error(message);
|
|
27
|
+
error.code = "E_CHECKPOINT_REVALIDATION_UNSAFE";
|
|
28
|
+
return error;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isFingerprint(value) {
|
|
32
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function assertRepositoryFingerprint(value, label = "repository fingerprint", { requireHead = false } = {}) {
|
|
36
|
+
if (!value || typeof value !== "object" || Array.isArray(value)
|
|
37
|
+
|| Object.keys(value).length !== REPOSITORY_FINGERPRINT_KEYS.length
|
|
38
|
+
|| REPOSITORY_FINGERPRINT_KEYS.some((key) => !Object.prototype.hasOwnProperty.call(value, key))) {
|
|
39
|
+
throw invalid(`${label} must contain exactly branch and head`);
|
|
40
|
+
}
|
|
41
|
+
for (const key of REPOSITORY_FINGERPRINT_KEYS) {
|
|
42
|
+
if (value[key] !== null && (typeof value[key] !== "string" || !value[key].trim())) {
|
|
43
|
+
throw invalid(`${label}.${key} must be a non-empty string or null`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (requireHead && typeof value.head !== "string") {
|
|
47
|
+
throw invalid(`${label}.head must be available`);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function assertCheckpointRevalidatedDetails(details) {
|
|
53
|
+
if (!details || typeof details !== "object" || Array.isArray(details)
|
|
54
|
+
|| Object.keys(details).length !== REVALIDATION_DETAIL_KEYS.length
|
|
55
|
+
|| REVALIDATION_DETAIL_KEYS.some((key) => !Object.prototype.hasOwnProperty.call(details, key))) {
|
|
56
|
+
throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} requires its exact state-transition detail set`);
|
|
57
|
+
}
|
|
58
|
+
if (!CHECKPOINT_REVALIDATION_PHASES.includes(details.phase)) {
|
|
59
|
+
throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} details.phase is unsupported`);
|
|
60
|
+
}
|
|
61
|
+
assertRepositoryFingerprint(details.previousRepositoryFingerprint, `${CHECKPOINT_REVALIDATED_EVENT} details.previousRepositoryFingerprint`);
|
|
62
|
+
assertRepositoryFingerprint(details.repositoryFingerprint, `${CHECKPOINT_REVALIDATED_EVENT} details.repositoryFingerprint`, { requireHead: true });
|
|
63
|
+
if (JSON.stringify(details.previousRepositoryFingerprint) === JSON.stringify(details.repositoryFingerprint)) {
|
|
64
|
+
throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} must record repository drift`);
|
|
65
|
+
}
|
|
66
|
+
for (const key of ["contractFingerprint", "routeFingerprint", "previousStateFingerprint", "revalidatedStateFingerprint"]) {
|
|
67
|
+
if (!isFingerprint(details[key])) throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} details.${key} must be a lowercase SHA-256 fingerprint`);
|
|
68
|
+
}
|
|
69
|
+
for (const key of ["previousStateRevision", "revalidatedStateRevision"]) {
|
|
70
|
+
if (!Number.isInteger(details[key]) || details[key] < 0) {
|
|
71
|
+
throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} details.${key} must be a non-negative integer`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (details.revalidatedStateRevision !== details.previousStateRevision + 1) {
|
|
75
|
+
throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} state revisions must advance exactly once`);
|
|
76
|
+
}
|
|
77
|
+
return details;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function sameRepositoryFingerprint(left, right) {
|
|
81
|
+
return left?.branch === right?.branch && left?.head === right?.head;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function bindingError(message) {
|
|
85
|
+
return { code: "E_CHECKPOINT_REVALIDATION_UNSAFE", message };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function eventHash(event) {
|
|
89
|
+
const { hash, ...body } = event ?? {};
|
|
90
|
+
return canonicalFingerprint(body);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function routeEvolutionAfter(
|
|
94
|
+
events,
|
|
95
|
+
sourceEvent,
|
|
96
|
+
sourceRouteFingerprint,
|
|
97
|
+
targetRouteFingerprint,
|
|
98
|
+
targetContractFingerprint = sourceEvent.details?.contractFingerprint,
|
|
99
|
+
) {
|
|
100
|
+
const sourceIndex = events.indexOf(sourceEvent);
|
|
101
|
+
if (sourceIndex < 0 || !isFingerprint(sourceRouteFingerprint) || !isFingerprint(targetRouteFingerprint)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const contractFingerprint = sourceEvent.details?.contractFingerprint;
|
|
106
|
+
if (targetContractFingerprint !== contractFingerprint) {
|
|
107
|
+
const contractEvolution = resolveCanonicalContractEvolution(events, {
|
|
108
|
+
taskId: sourceEvent.taskId,
|
|
109
|
+
sourceSeq: sourceEvent.seq,
|
|
110
|
+
sourceContractFingerprint: contractFingerprint,
|
|
111
|
+
targetContractFingerprint,
|
|
112
|
+
});
|
|
113
|
+
if (!contractEvolution?.revisionEvents?.length) return false;
|
|
114
|
+
|
|
115
|
+
let currentRouteFingerprint = sourceRouteFingerprint;
|
|
116
|
+
let currentContractFingerprint = contractFingerprint;
|
|
117
|
+
let currentSeq = sourceEvent.seq;
|
|
118
|
+
for (const revisionEvent of contractEvolution.revisionEvents) {
|
|
119
|
+
const boundaryRouteFingerprint = revisionEvent.details.previousRouteFingerprint;
|
|
120
|
+
if (!isFingerprint(boundaryRouteFingerprint)) return false;
|
|
121
|
+
const preRevisionEvents = events.filter((event) => event.seq <= revisionEvent.seq);
|
|
122
|
+
const preRevisionRoute = resolveCanonicalRouteEvolution(preRevisionEvents, {
|
|
123
|
+
taskId: sourceEvent.taskId,
|
|
124
|
+
sourceSeq: currentSeq,
|
|
125
|
+
sourceRouteFingerprint: currentRouteFingerprint,
|
|
126
|
+
targetRouteFingerprint: boundaryRouteFingerprint,
|
|
127
|
+
contractFingerprint: currentContractFingerprint,
|
|
128
|
+
});
|
|
129
|
+
if (!preRevisionRoute) return false;
|
|
130
|
+
currentRouteFingerprint = boundaryRouteFingerprint;
|
|
131
|
+
currentContractFingerprint = revisionEvent.details.contractFingerprint;
|
|
132
|
+
currentSeq = revisionEvent.seq;
|
|
133
|
+
}
|
|
134
|
+
const postRevisionRoute = resolveCanonicalRouteEvolution(events, {
|
|
135
|
+
taskId: sourceEvent.taskId,
|
|
136
|
+
sourceSeq: currentSeq,
|
|
137
|
+
sourceRouteFingerprint: currentRouteFingerprint,
|
|
138
|
+
targetRouteFingerprint,
|
|
139
|
+
contractFingerprint: currentContractFingerprint,
|
|
140
|
+
});
|
|
141
|
+
return Boolean(postRevisionRoute);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const canonical = resolveCanonicalRouteEvolution(events, {
|
|
145
|
+
taskId: sourceEvent.taskId,
|
|
146
|
+
sourceSeq: sourceEvent.seq,
|
|
147
|
+
sourceRouteFingerprint,
|
|
148
|
+
targetRouteFingerprint,
|
|
149
|
+
contractFingerprint,
|
|
150
|
+
});
|
|
151
|
+
if (!canonical) return false;
|
|
152
|
+
|
|
153
|
+
const anchor = resolveEffectiveContractBootstrapRepairAnchor(events);
|
|
154
|
+
if (anchor?.details?.contractFingerprint === contractFingerprint) {
|
|
155
|
+
const binding = resolveCanonicalPostRepairRouteBinding(
|
|
156
|
+
events,
|
|
157
|
+
anchor.sourceMarker,
|
|
158
|
+
targetRouteFingerprint,
|
|
159
|
+
);
|
|
160
|
+
return Boolean(binding?.reboundEvents?.some(({ reboundEvent }) =>
|
|
161
|
+
reboundEvent.seq > sourceEvent.seq
|
|
162
|
+
&& reboundEvent.details.routeFingerprint === targetRouteFingerprint));
|
|
163
|
+
}
|
|
164
|
+
return canonical.valid;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolves the only valid transaction witness for a checkpoint revalidation.
|
|
169
|
+
* The witness is intentionally positional: a later transaction commit cannot
|
|
170
|
+
* retroactively authorize an earlier revalidation event.
|
|
171
|
+
*/
|
|
172
|
+
export function resolveCheckpointRevalidationBoundary(events = [], revalidationEvent) {
|
|
173
|
+
const index = events.findIndex((candidate) => candidate === revalidationEvent
|
|
174
|
+
|| (candidate?.seq === revalidationEvent?.seq
|
|
175
|
+
&& candidate?.taskId === revalidationEvent?.taskId
|
|
176
|
+
&& candidate?.event === CHECKPOINT_REVALIDATED_EVENT));
|
|
177
|
+
if (index < 0) return null;
|
|
178
|
+
const commitEvent = events[index + 1];
|
|
179
|
+
if (!commitEvent
|
|
180
|
+
|| revalidationEvent?.event !== CHECKPOINT_REVALIDATED_EVENT
|
|
181
|
+
|| commitEvent.event !== "TRANSACTION_COMMITTED"
|
|
182
|
+
|| commitEvent.seq !== revalidationEvent.seq + 1
|
|
183
|
+
|| commitEvent.previousHash !== revalidationEvent.hash
|
|
184
|
+
|| revalidationEvent.hash !== eventHash(revalidationEvent)
|
|
185
|
+
|| commitEvent.hash !== eventHash(commitEvent)
|
|
186
|
+
|| commitEvent.taskId !== revalidationEvent.taskId
|
|
187
|
+
|| typeof commitEvent.details?.transactionId !== "string"
|
|
188
|
+
|| !commitEvent.details.transactionId
|
|
189
|
+
|| commitEvent.details.operation !== "checkpoint-revalidate") {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return { revalidationEvent, transactionCommitEvent: commitEvent };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function validateCheckpointRevalidationEventBindings(events = []) {
|
|
196
|
+
const errors = [];
|
|
197
|
+
const revalidations = events.filter((event) => event.event === CHECKPOINT_REVALIDATED_EVENT);
|
|
198
|
+
for (let index = 0; index < revalidations.length; index += 1) {
|
|
199
|
+
const event = revalidations[index];
|
|
200
|
+
let details;
|
|
201
|
+
try {
|
|
202
|
+
details = assertCheckpointRevalidatedDetails(event.details);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
errors.push(bindingError(`event ${event.seq}: ${error.message}`));
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (!resolveCheckpointRevalidationBoundary(events, event)) {
|
|
208
|
+
errors.push(bindingError(`event ${event.seq} must be immediately followed by its checkpoint-revalidate transaction commit`));
|
|
209
|
+
}
|
|
210
|
+
const previous = revalidations[index - 1]?.details;
|
|
211
|
+
if (previous) {
|
|
212
|
+
if (!sameRepositoryFingerprint(details.previousRepositoryFingerprint, previous.repositoryFingerprint)) {
|
|
213
|
+
errors.push(bindingError(`event ${event.seq} is disconnected from the previous checkpoint revalidation repository`));
|
|
214
|
+
}
|
|
215
|
+
if (details.previousStateRevision < previous.revalidatedStateRevision) {
|
|
216
|
+
errors.push(bindingError(`event ${event.seq} rolls back the previous checkpoint revalidation revision`));
|
|
217
|
+
}
|
|
218
|
+
if (details.previousStateRevision === previous.revalidatedStateRevision
|
|
219
|
+
&& details.previousStateFingerprint !== previous.revalidatedStateFingerprint) {
|
|
220
|
+
errors.push(bindingError(`event ${event.seq} does not continue the previous checkpoint revalidation state fingerprint`));
|
|
221
|
+
}
|
|
222
|
+
const contractEvolved = details.contractFingerprint !== previous.contractFingerprint
|
|
223
|
+
&& resolveCanonicalContractEvolution(events, {
|
|
224
|
+
taskId: event.taskId,
|
|
225
|
+
sourceSeq: revalidations[index - 1].seq,
|
|
226
|
+
sourceContractFingerprint: previous.contractFingerprint,
|
|
227
|
+
targetContractFingerprint: details.contractFingerprint,
|
|
228
|
+
});
|
|
229
|
+
if (details.contractFingerprint !== previous.contractFingerprint && !contractEvolved) {
|
|
230
|
+
errors.push(bindingError(`event ${event.seq} changes the checkpoint revalidation contract identity without canonical contract provenance`));
|
|
231
|
+
}
|
|
232
|
+
if (details.routeFingerprint !== previous.routeFingerprint) {
|
|
233
|
+
const evolved = details.previousStateRevision > previous.revalidatedStateRevision
|
|
234
|
+
&& routeEvolutionAfter(
|
|
235
|
+
events,
|
|
236
|
+
revalidations[index - 1],
|
|
237
|
+
previous.routeFingerprint,
|
|
238
|
+
details.routeFingerprint,
|
|
239
|
+
details.contractFingerprint,
|
|
240
|
+
);
|
|
241
|
+
if (!evolved) {
|
|
242
|
+
errors.push(bindingError(`event ${event.seq} changes the checkpoint revalidation route identity without canonical route provenance`));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
const anchor = resolveEffectiveContractBootstrapRepairAnchor(events);
|
|
247
|
+
if (anchor?.details?.reconstructedStateRevision === details.previousStateRevision
|
|
248
|
+
&& anchor.details.reconstructedStateFingerprint !== details.previousStateFingerprint) {
|
|
249
|
+
errors.push(bindingError(`event ${event.seq} does not bind the repaired checkpoint state fingerprint`));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return errors;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function validateCurrentContractBinding(state, events, event) {
|
|
257
|
+
const details = event.details;
|
|
258
|
+
if (!state?.contractFingerprint || details?.contractFingerprint === state.contractFingerprint) return null;
|
|
259
|
+
const evolved = Number.isInteger(state.revision)
|
|
260
|
+
&& Number.isInteger(details?.revalidatedStateRevision)
|
|
261
|
+
&& state.revision > details.revalidatedStateRevision
|
|
262
|
+
&& resolveCanonicalContractEvolution(events, {
|
|
263
|
+
taskId: event.taskId,
|
|
264
|
+
sourceSeq: event.seq,
|
|
265
|
+
sourceContractFingerprint: details.contractFingerprint,
|
|
266
|
+
targetContractFingerprint: state.contractFingerprint,
|
|
267
|
+
});
|
|
268
|
+
return evolved ? null : bindingError(`event ${event.seq} does not bind the current contract identity through canonical contract provenance`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function validateCurrentRouteBinding(state, events, event) {
|
|
272
|
+
const details = event.details;
|
|
273
|
+
if (!state?.routeFingerprint || details?.routeFingerprint === state.routeFingerprint) return null;
|
|
274
|
+
const routeChangedAfterCheckpoint = Number.isInteger(state.revision)
|
|
275
|
+
&& Number.isInteger(details?.revalidatedStateRevision)
|
|
276
|
+
&& state.revision > details.revalidatedStateRevision;
|
|
277
|
+
const evolved = routeChangedAfterCheckpoint
|
|
278
|
+
&& routeEvolutionAfter(events, event, details.routeFingerprint, state.routeFingerprint, state.contractFingerprint);
|
|
279
|
+
return evolved ? null : bindingError(`event ${event.seq} does not bind the current route identity through canonical route provenance`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function validateLatestCheckpointBinding(state, latest) {
|
|
283
|
+
if (!state || !latest || state.revision !== latest.details.revalidatedStateRevision) return [];
|
|
284
|
+
const details = latest.details;
|
|
285
|
+
const errors = [];
|
|
286
|
+
if (!sameRepositoryFingerprint(state.repositoryFingerprint, details.repositoryFingerprint)) {
|
|
287
|
+
errors.push(bindingError(`event ${latest.seq} does not bind the current repository fingerprint`));
|
|
288
|
+
}
|
|
289
|
+
if (state.phase !== details.phase || canonicalFingerprint(state) !== details.revalidatedStateFingerprint) {
|
|
290
|
+
errors.push(bindingError(`event ${latest.seq} does not bind the current checkpoint state`));
|
|
291
|
+
}
|
|
292
|
+
return errors;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function validateCheckpointRevalidationCurrentBinding(state, events = []) {
|
|
296
|
+
const errors = [];
|
|
297
|
+
const revalidations = events.filter((event) => event.event === CHECKPOINT_REVALIDATED_EVENT);
|
|
298
|
+
const latest = revalidations.at(-1);
|
|
299
|
+
for (const event of revalidations) {
|
|
300
|
+
const contractError = validateCurrentContractBinding(state, events, event);
|
|
301
|
+
if (contractError) errors.push(contractError);
|
|
302
|
+
const routeError = validateCurrentRouteBinding(state, events, event);
|
|
303
|
+
if (routeError) errors.push(routeError);
|
|
304
|
+
}
|
|
305
|
+
errors.push(...validateLatestCheckpointBinding(state, latest));
|
|
306
|
+
return errors;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Validates the semantic chain of checkpoint-revalidation events. The event
|
|
311
|
+
* hash proves bytes were not changed accidentally; these checks prove that a
|
|
312
|
+
* rehashed event still describes a valid state transition.
|
|
313
|
+
*/
|
|
314
|
+
export function validateCheckpointRevalidationBindings(state, events = []) {
|
|
315
|
+
return [
|
|
316
|
+
...validateCheckpointRevalidationEventBindings(events),
|
|
317
|
+
...validateCheckpointRevalidationCurrentBinding(state, events),
|
|
318
|
+
];
|
|
319
|
+
}
|