Initialize Eve conformance owner
This commit is contained in:
commit
4cdfa9c6eb
10 changed files with 9073 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
artifacts/
|
||||
node_modules/
|
||||
|
||||
20
README.md
Normal file
20
README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# EveConformance
|
||||
|
||||
EveConformance owns the cross-repository evidence that an Eve provider, plugin,
|
||||
or runtime obeys the contracts published by the Eve kernel.
|
||||
|
||||
Eve owns schemas and the browser reference lowerer. Provider repos own live
|
||||
state and scenarios. Plugin repos own semantic sidecars. Runtime repos own
|
||||
native projection and captures. This repo reads those public surfaces and emits
|
||||
portable parity reports and conformance packs; it does not import their private
|
||||
implementation bodies.
|
||||
|
||||
## Run
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\scripts\run-conformance.ps1
|
||||
```
|
||||
|
||||
The default workspace assumes sibling repositories under `E:\Projects`. Set
|
||||
`EVE_KERNEL_ROOT` when the Eve checkout lives elsewhere.
|
||||
|
||||
9
package.json
Normal file
9
package.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "@gamecult/eve-conformance",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "powershell -ExecutionPolicy Bypass -File ./scripts/run-conformance.ps1"
|
||||
}
|
||||
}
|
||||
|
||||
22
scripts/run-conformance.ps1
Normal file
22
scripts/run-conformance.ps1
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
param(
|
||||
[string] $KernelRoot = $(if ($env:EVE_KERNEL_ROOT) { $env:EVE_KERNEL_ROOT } else { "E:\Projects\Eve" }),
|
||||
[string] $OutputDirectory = "artifacts\parity"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$env:EVE_KERNEL_ROOT = (Resolve-Path $KernelRoot).Path
|
||||
$env:EVE_PARITY_OUTPUT = if ([IO.Path]::IsPathRooted($OutputDirectory)) {
|
||||
$OutputDirectory
|
||||
} else {
|
||||
Join-Path $repoRoot $OutputDirectory
|
||||
}
|
||||
$env:EVE_CONFORMANCE_OUTPUT = Join-Path $repoRoot "artifacts\conformance"
|
||||
|
||||
Push-Location $repoRoot
|
||||
try {
|
||||
node .\tools\parity\run-parity.mjs
|
||||
node .\tools\conformance\consume-export.mjs .\artifacts\conformance\latest
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
44
tools/conformance/attach-plugin-witness.mjs
Normal file
44
tools/conformance/attach-plugin-witness.mjs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs";
|
||||
|
||||
const [witnessArgument, exportArgument] = process.argv.slice(2);
|
||||
if (!witnessArgument || !exportArgument) {
|
||||
console.error("Usage: node tools/conformance/attach-plugin-witness.mjs <plugin-witness.json> <conformance-export-directory>");
|
||||
process.exit(2);
|
||||
}
|
||||
const witnessPath = path.resolve(witnessArgument);
|
||||
const exportDirectory = path.resolve(exportArgument);
|
||||
const indexPath = path.join(exportDirectory, "index.json");
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const witness = parseJson(await readFile(witnessPath, "utf8"));
|
||||
const schema = parseJson(await readFile(path.join(repoRoot, "schemas/gamecult.eve.plugin_witness.v1.schema.json"), "utf8"));
|
||||
const schemaErrors = validateSchemaSubset(schema, witness);
|
||||
if (schemaErrors.length) throw new Error(`Plugin witness schema errors: ${schemaErrors.join("; ")}`);
|
||||
|
||||
const attachmentRoot = path.join(exportDirectory, "attachments", "plugin", safeSegment(witness.witnessId));
|
||||
await mkdir(attachmentRoot, { recursive: true });
|
||||
const exportedWitnessPath = path.join(attachmentRoot, "witness.json");
|
||||
await writeFile(exportedWitnessPath, `${JSON.stringify(witness, null, 2)}\n`);
|
||||
|
||||
const index = parseJson(await readFile(indexPath, "utf8"));
|
||||
const record = {
|
||||
witnessId: witness.witnessId,
|
||||
pluginId: witness.pluginId,
|
||||
ownerRepo: witness.ownerRepo,
|
||||
status: witness.status,
|
||||
transport: witness.transport,
|
||||
operationCount: witness.operations.length,
|
||||
witnessExportPath: slash(path.relative(exportDirectory, exportedWitnessPath)),
|
||||
};
|
||||
index.pluginWitnesses = [
|
||||
...(Array.isArray(index.pluginWitnesses) ? index.pluginWitnesses : []).filter(candidate => candidate.witnessId !== record.witnessId),
|
||||
record,
|
||||
].sort((left, right) => left.witnessId.localeCompare(right.witnessId));
|
||||
await writeFile(indexPath, `${JSON.stringify(index, null, 2)}\n`);
|
||||
console.log(exportedWitnessPath);
|
||||
|
||||
function safeSegment(value) { return String(value).replace(/[^a-zA-Z0-9._-]+/g, "-"); }
|
||||
function slash(value) { return value.replaceAll("\\", "/"); }
|
||||
function parseJson(value) { return JSON.parse(value.replace(/^\uFEFF/, "")); }
|
||||
78
tools/conformance/attach-runtime-witness.mjs
Normal file
78
tools/conformance/attach-runtime-witness.mjs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs";
|
||||
|
||||
const [witnessArgument, exportArgument] = process.argv.slice(2);
|
||||
if (!witnessArgument || !exportArgument) {
|
||||
console.error("Usage: node tools/conformance/attach-runtime-witness.mjs <runtime-witness.json> <conformance-export-directory>");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const witnessPath = path.resolve(witnessArgument);
|
||||
const exportDirectory = path.resolve(exportArgument);
|
||||
const indexPath = path.join(exportDirectory, "index.json");
|
||||
const witness = parseJson(await readFile(witnessPath, "utf8"));
|
||||
validateWitness(witness);
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const witnessSchema = parseJson(await readFile(path.join(repoRoot, "schemas/gamecult.eve.runtime_witness.v1.schema.json"), "utf8"));
|
||||
const schemaErrors = validateSchemaSubset(witnessSchema, witness);
|
||||
if (schemaErrors.length) throw new Error(`Runtime witness schema errors: ${schemaErrors.join("; ")}`);
|
||||
|
||||
const attachmentRoot = path.join(exportDirectory, "attachments", "runtime", safeSegment(witness.witnessId));
|
||||
await mkdir(attachmentRoot, { recursive: true });
|
||||
const artifacts = [];
|
||||
for (const artifact of witness.artifacts) {
|
||||
const sourcePath = path.resolve(path.dirname(witnessPath), artifact.path);
|
||||
const fileName = path.basename(sourcePath);
|
||||
const destinationPath = path.join(attachmentRoot, fileName);
|
||||
await copyFile(sourcePath, destinationPath);
|
||||
artifacts.push({
|
||||
...artifact,
|
||||
exportPath: slash(path.relative(exportDirectory, destinationPath)),
|
||||
});
|
||||
}
|
||||
|
||||
const exportedWitness = { ...witness, artifacts };
|
||||
const exportedWitnessPath = path.join(attachmentRoot, "witness.json");
|
||||
await writeFile(exportedWitnessPath, `${JSON.stringify(exportedWitness, null, 2)}\n`);
|
||||
|
||||
const index = parseJson(await readFile(indexPath, "utf8"));
|
||||
const record = {
|
||||
witnessId: witness.witnessId,
|
||||
runtimeId: witness.runtimeId,
|
||||
runtimeOwnerRepo: witness.runtimeOwnerRepo,
|
||||
providerId: witness.providerId,
|
||||
surfaceId: witness.surfaceId,
|
||||
projectionKind: witness.projectionKind,
|
||||
status: witness.status,
|
||||
cacheState: witness.execution.cacheState,
|
||||
witnessExportPath: slash(path.relative(exportDirectory, exportedWitnessPath)),
|
||||
artifactCount: artifacts.length,
|
||||
};
|
||||
index.runtimeWitnesses = [
|
||||
...(Array.isArray(index.runtimeWitnesses) ? index.runtimeWitnesses : []).filter(candidate => candidate.witnessId !== record.witnessId),
|
||||
record,
|
||||
].sort((left, right) => left.witnessId.localeCompare(right.witnessId));
|
||||
await writeFile(indexPath, `${JSON.stringify(index, null, 2)}\n`);
|
||||
console.log(exportedWitnessPath);
|
||||
|
||||
function validateWitness(value) {
|
||||
for (const field of ["witnessId", "runtimeId", "runtimeOwnerRepo", "providerId", "surfaceId", "projectionKind", "status", "generatedAtUtc", "authority"]) {
|
||||
if (!value?.[field]) throw new Error(`Runtime witness missing ${field}.`);
|
||||
}
|
||||
if (value.schema !== "gamecult.eve.runtime_witness.v1") throw new Error(`Unexpected runtime witness schema: ${value.schema || ""}`);
|
||||
if (!value.execution?.cacheState || !Number.isFinite(value.execution?.durationMs)) throw new Error("Runtime witness execution is incomplete.");
|
||||
if (!value.assertions || typeof value.assertions !== "object") throw new Error("Runtime witness assertions are missing.");
|
||||
if (value.receipts !== undefined && !Array.isArray(value.receipts)) throw new Error("Runtime witness receipts must be an array when present.");
|
||||
if (!Array.isArray(value.artifacts) || !value.artifacts.length) throw new Error("Runtime witness artifacts are missing.");
|
||||
for (const artifact of value.artifacts) {
|
||||
if (!artifact.path || !/^[a-f0-9]{64}$/i.test(artifact.sha256 || "") || !(artifact.sizeBytes > 0)) {
|
||||
throw new Error(`Runtime witness artifact is invalid: ${artifact.path || "unknown"}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function safeSegment(value) { return String(value).replace(/[^a-zA-Z0-9._-]+/g, "-"); }
|
||||
function slash(value) { return value.replaceAll("\\", "/"); }
|
||||
function parseJson(value) { return JSON.parse(value.replace(/^\uFEFF/, "")); }
|
||||
2100
tools/conformance/consume-export.mjs
Normal file
2100
tools/conformance/consume-export.mjs
Normal file
File diff suppressed because it is too large
Load diff
36
tools/parity/export-fixture.mjs
Normal file
36
tools/parity/export-fixture.mjs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { compileEveDsl } from "../../web/eve-dsl.js";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const manifest = JSON.parse(await readFile(path.join(repoRoot, "tools/parity/parity-manifest.json"), "utf8"));
|
||||
|
||||
const fixtureId = process.argv[2] || "cultui-inspector";
|
||||
const outputPath = process.argv[3]
|
||||
? path.resolve(process.argv[3])
|
||||
: path.join(repoRoot, "artifacts/parity/fixtures", `${fixtureId}.surface.json`);
|
||||
|
||||
const fixture = manifest.fixtures.find(candidate =>
|
||||
candidate.id === fixtureId || candidate.expect?.providerId === fixtureId);
|
||||
|
||||
if (!fixture) {
|
||||
throw new Error(`Unknown parity fixture: ${fixtureId}`);
|
||||
}
|
||||
|
||||
const surface = await loadSurface(fixture.surface);
|
||||
surface.fixtureId = fixture.id;
|
||||
surface.values ||= surface.mesh?.snapshot?.() || {};
|
||||
delete surface.mesh;
|
||||
|
||||
await mkdir(path.dirname(outputPath), { recursive: true });
|
||||
await writeFile(outputPath, `${JSON.stringify(surface, null, 2)}\n`);
|
||||
console.log(outputPath);
|
||||
|
||||
async function loadSurface(surface) {
|
||||
const absolutePath = path.join(repoRoot, surface.path);
|
||||
const source = await readFile(absolutePath, "utf8");
|
||||
if (surface.transport === "local-eve-dsl") return compileEveDsl(source);
|
||||
if (surface.transport === "local-json") return JSON.parse(source);
|
||||
throw new Error(`Unsupported parity surface transport: ${surface.transport}`);
|
||||
}
|
||||
3104
tools/parity/parity-manifest.json
Normal file
3104
tools/parity/parity-manifest.json
Normal file
File diff suppressed because it is too large
Load diff
3657
tools/parity/run-parity.mjs
Normal file
3657
tools/parity/run-parity.mjs
Normal file
File diff suppressed because it is too large
Load diff
Reference in a new issue