config: migrate workspace evaluation to Decodal 0.4

This commit is contained in:
2026-08-14 10:53:22 +09:00
parent 5c57e10de9
commit 1fad5fc8ed
12 changed files with 498 additions and 37 deletions
@@ -0,0 +1,23 @@
import { decodalLanguage } from "decodal-codemirror";
Deno.test("editor grammar accepts Decodal 0.4 schema syntax", () => {
const source = `
import "main.dcdl" as WorkspaceConfigSchema
{
features = {...{ enabled = Bool; }};
web = { enabled = Bool; ...Unknown };
}
`;
const tree = decodalLanguage.parser.parse(source);
const errors: string[] = [];
tree.iterate({
enter(node) {
if (node.type.isError) {
errors.push(`${node.from}..${node.to}`);
}
},
});
if (errors.length > 0) {
throw new Error(`Decodal 0.4 grammar produced parse errors at ${errors.join(", ")}`);
}
});
@@ -1,19 +1,42 @@
/// <reference lib="deno.ns" />
import { assertEquals } from "jsr:@std/assert";
// The generated wasm-bindgen loader is JavaScript with an adjacent declaration file.
// @ts-expect-error Deno checks the generated JS implementation rather than its .d.ts.
import init, {
analyze_snapshot,
compose_schema_bundle,
evaluate_snapshot,
} from "../../src/lib/workspace/config-source/generated/config_source_wasm.js";
import type { ConfigTreeSnapshot, ToolchainContract } from "../../src/lib/workspace/config-source/types.ts";
import type {
ConfigTreeSnapshot,
ToolchainContract,
WorkspaceConfigSchemaBundle,
} from "../../src/lib/workspace/config-source/types.ts";
const bytes = await Deno.readFile(
new URL("../../src/lib/workspace/config-source/generated/config_source_wasm_bg.wasm", import.meta.url),
);
await init({ module_or_path: bytes });
async function digestText(text: string): Promise<string> {
const bytes = new TextEncoder().encode(text);
const digest = await crypto.subtle.digest("SHA-256", bytes);
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
}
async function toolchainFingerprint(
entrypoints: string[],
schemaBundle: WorkspaceConfigSchemaBundle,
): Promise<string> {
return await digestText(JSON.stringify([
2,
"0.4.0",
1,
entrypoints,
1,
schemaBundle.fingerprint,
]));
}
const snapshot: ConfigTreeSnapshot = {
revision: 4,
digest: "sha256:test-tree",
@@ -33,13 +56,15 @@ const snapshot: ConfigTreeSnapshot = {
},
};
const emptySchemaBundle = compose_schema_bundle([]) as WorkspaceConfigSchemaBundle;
const contract: ToolchainContract = {
contract_version: 1,
decodal_version: "0.2.0",
contract_version: 2,
decodal_version: "0.4.0",
schema_version: 1,
entrypoints: ["workspace.dcdl"],
import_policy_version: 1,
fingerprint: "sha256:test-contract",
schema_bundle: emptySchemaBundle,
fingerprint: await toolchainFingerprint(["workspace.dcdl"], emptySchemaBundle),
};
Deno.test("generated WASM evaluates the same virtual import contract", () => {
@@ -65,3 +90,73 @@ Deno.test("generated WASM diagnostics carry snapshot provenance", () => {
assertEquals(diagnostics[0].tree_digest, "sha256:test-tree");
assertEquals(diagnostics[0].kind, "syntax");
});
const featuresSchema = "{ features = {...{ enabled = Bool; }}; }";
const webSchema = "{ web = { enabled = Bool; ...Unknown }; }";
const schemaBundle = compose_schema_bundle([
{
provider_id: "builtin:features",
namespace: "features",
version: "1",
source: featuresSchema,
source_digest: await digestText(featuresSchema),
},
{
provider_id: "builtin:web",
namespace: "web",
version: "1",
source: webSchema,
source_digest: await digestText(webSchema),
},
]) as WorkspaceConfigSchemaBundle;
function schemaSnapshot(source: string): ConfigTreeSnapshot {
return {
revision: 7,
digest: "sha256:schema-tree",
entries: {
"main.dcdl": {
path: "main.dcdl",
content_type: "decodal",
content: source,
content_digest: "sha256:main",
},
},
};
}
const schemaContract: ToolchainContract = {
contract_version: 2,
decodal_version: "0.4.0",
schema_version: 1,
entrypoints: ["main.dcdl"],
import_policy_version: 1,
schema_bundle: schemaBundle,
fingerprint: await toolchainFingerprint(["main.dcdl"], schemaBundle),
};
Deno.test("generated WASM applies Decodal 0.4 typed maps and explicit object rest", () => {
const result = evaluate_snapshot(
schemaSnapshot(
"{ features = { console = { enabled = true; }; }; web = { enabled = true; extension_value = 42; }; }",
),
schemaContract,
) as { projections: Array<{ data_json: Record<string, unknown> }> };
assertEquals(result.projections[0].data_json, {
features: { console: { enabled: true } },
web: { enabled: true, extension_value: 42 },
});
});
Deno.test("generated WASM rejects unknown root fields with source provenance", () => {
let thrown: unknown;
try {
evaluate_snapshot(schemaSnapshot("{ features = {}; custom = 42; }"), schemaContract);
} catch (error) {
thrown = error;
}
const diagnostics = thrown as Array<{ path: string; kind: string }>;
assertEquals(Array.isArray(diagnostics), true);
assertEquals(diagnostics[0].path, "main.dcdl");
assertEquals(diagnostics[0].kind, "constraintviolation");
});