feat(profiles): seal builtin import provenance and expose read-only editor imports
Retain the evaluated JSON launch entrypoint, seal observed builtin source bytes and digests as verified provenance, and fail on missing/corrupt dependencies without Runtime builtin reevaluation. Cover real HTTP commits and launch resolution, including worker mode and Workspace Config feature composition. Expose read-only builtin import completions/diagnostics in the editor and rebuild wasm32 release assets with wasm-bindgen 0.2.117. Document public paths, ordinary composition, limits, resave semantics, and the lazy-cycle incident. Validation: worker-runtime 344 library + 34 integration tests; workspace-server 609 library + 11 binary tests plus final builtin HTTP/launch tests (2); archive tests (16); native WASM tests (2); real-WASM/web tests (37), editor component tests (10); root cargo check, cargo fmt --all -- --check and git diff --check HEAD pass.
This commit is contained in:
@@ -216,6 +216,51 @@ test("unmount during schema setup closes the toolchain", async () => {
|
||||
expect(toolchain.close).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test("builtin diagnostics show read-only paths without adding editable workspace files", async () => {
|
||||
toolchain.analyze.mockResolvedValue([{
|
||||
path: "$builtin/profiles/companion.dcdl",
|
||||
revision: 7,
|
||||
tree_digest: "sha256:tree",
|
||||
kind: "constraint_violation",
|
||||
span: { start_byte: 1, end_byte: 4 },
|
||||
message: "Imported value does not match the requested schema",
|
||||
labels: [],
|
||||
notes: [],
|
||||
}]);
|
||||
const view = renderEditor();
|
||||
await expectReady(view);
|
||||
await waitFor(() => {
|
||||
const diagnostic = view.container.querySelector(".config-source-diagnostics");
|
||||
expect(diagnostic?.textContent).toContain("$builtin/profiles/companion.dcdl (read-only builtin source)");
|
||||
expect(diagnostic?.textContent).toContain("bytes 1–4");
|
||||
});
|
||||
const paths = view.getByRole("navigation", { name: "Virtual configuration paths" });
|
||||
expect(paths.textContent).not.toContain("$builtin/");
|
||||
expect(paths.querySelectorAll("button")).toHaveLength(1);
|
||||
expect(view.container.querySelector('.cm-content[contenteditable="true"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
test("unknown builtin diagnostics retain the workspace importer path", async () => {
|
||||
toolchain.analyze.mockResolvedValue([{
|
||||
path: "main.dcdl",
|
||||
revision: 7,
|
||||
tree_digest: "sha256:tree",
|
||||
kind: "import",
|
||||
span: { start_byte: 8, end_byte: 40 },
|
||||
message: "unknown or non-public read-only builtin source: $builtin/profiles/missing.dcdl",
|
||||
labels: [],
|
||||
notes: [],
|
||||
}]);
|
||||
const view = renderEditor();
|
||||
await expectReady(view);
|
||||
await waitFor(() => {
|
||||
const diagnostic = view.container.querySelector(".config-source-diagnostics");
|
||||
expect(diagnostic?.textContent).toContain("main.dcdl · bytes 8–40");
|
||||
expect(diagnostic?.textContent).toContain("unknown or non-public read-only builtin source");
|
||||
});
|
||||
expect(view.getByRole("navigation", { name: "Virtual configuration paths" }).textContent).not.toContain("$builtin/");
|
||||
});
|
||||
|
||||
test("unmount during fetch does not start a toolchain", async () => {
|
||||
let finishFetch!: (response: Response) => void;
|
||||
fetcher.mockReturnValueOnce(
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
<li>
|
||||
<strong>{diagnostic.kind}</strong>
|
||||
<span>{diagnostic.message}</span>
|
||||
<small>bytes {diagnostic.span.start_byte}–{diagnostic.span.end_byte}</small>
|
||||
<small>{diagnostic.path}{diagnostic.path.startsWith("$builtin/") ? " (read-only builtin source)" : ""} · bytes {diagnostic.span.start_byte}–{diagnostic.span.end_byte}</small>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
|
||||
Binary file not shown.
@@ -226,6 +226,62 @@ Deno.test("config source API rejects unsafe revisions and oversized entry conten
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("config source API does not materialize imported builtins in the workspace file list", async () => {
|
||||
const body = {
|
||||
...tree,
|
||||
snapshot: {
|
||||
...snapshot,
|
||||
entries: {
|
||||
"profiles/main.dcdl": {
|
||||
...entry,
|
||||
content: 'import "$builtin/profiles/companion.dcdl"',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const fetcher = (() => Promise.resolve(response(body))) as typeof fetch;
|
||||
const result = await fetchConfigTree("w", fetcher);
|
||||
assertEquals(Object.keys(result.snapshot.entries), ["profiles/main.dcdl"]);
|
||||
assertEquals(
|
||||
result.snapshot.entries["profiles/main.dcdl"].content,
|
||||
body.snapshot.entries["profiles/main.dcdl"].content,
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("config source API preserves unknown read-only builtin diagnostics without fallback requests", async () => {
|
||||
const diagnostic = {
|
||||
path: "main.dcdl",
|
||||
revision: 7,
|
||||
tree_digest: "sha256:tree",
|
||||
kind: "import",
|
||||
span: { start_byte: 8, end_byte: 40 },
|
||||
message:
|
||||
"unknown or non-public read-only builtin source: $builtin/profiles/missing.dcdl",
|
||||
labels: [],
|
||||
notes: [],
|
||||
};
|
||||
const calls: string[] = [];
|
||||
const fetcher = ((input: string | URL | Request) => {
|
||||
calls.push(String(input));
|
||||
return Promise.resolve(
|
||||
response({ error: JSON.stringify([diagnostic]) }, 400),
|
||||
);
|
||||
}) as typeof fetch;
|
||||
const error = await assertRejects(
|
||||
() =>
|
||||
commitConfigTree("w", {
|
||||
base_revision: 7,
|
||||
base_digest: "sha256:tree",
|
||||
changes: [],
|
||||
entrypoints: ["main.dcdl"],
|
||||
}, fetcher),
|
||||
ConfigSourceApiError,
|
||||
diagnostic.message,
|
||||
);
|
||||
assert(String(error).includes("main.dcdl"));
|
||||
assertEquals(calls, ["/api/w/w/config/source-tree/commit"]);
|
||||
});
|
||||
|
||||
Deno.test("config source API surfaces bounded failed evaluation details", async () => {
|
||||
const fetcher = (() =>
|
||||
Promise.resolve(
|
||||
|
||||
@@ -89,6 +89,31 @@ Deno.test("completion starts only after non-whitespace typing", () => {
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("toolchain preserves read-only builtin import completion detail", () => {
|
||||
const result = toCodeMirrorCompletion({
|
||||
from: 8,
|
||||
items: [{
|
||||
label: "$builtin/profiles/companion.dcdl",
|
||||
kind: "file",
|
||||
detail: "read-only builtin Decodal source",
|
||||
priority: 0,
|
||||
}],
|
||||
});
|
||||
assert(result !== null, "builtin completion should be available");
|
||||
assert(
|
||||
result.options[0].label === "$builtin/profiles/companion.dcdl",
|
||||
"builtin namespace must not become a workspace path",
|
||||
);
|
||||
assert(
|
||||
result.options[0].detail === "read-only builtin Decodal source",
|
||||
"CodeMirror must display the read-only completion detail",
|
||||
);
|
||||
assert(
|
||||
result.options[0].type === "file",
|
||||
"builtin imports remain file completions",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("toolchain preserves WASM UTF-16 completion ranges for CodeMirror", () => {
|
||||
const result = toCodeMirrorCompletion({
|
||||
from: "let 名 = ".length,
|
||||
|
||||
@@ -299,6 +299,150 @@ Deno.test("generated WASM returns completion items for the editor adapter", () =
|
||||
assertEquals(result.items[0].kind, "file");
|
||||
});
|
||||
|
||||
Deno.test("generated WASM exposes read-only builtin completions without editable builtin entries", () => {
|
||||
const tree = schemaSnapshot("{}");
|
||||
set_snapshot(tree);
|
||||
set_schema_bundle(emptySchemaBundle);
|
||||
const source = 'let 名 = 1; import "$builtin/profiles/comp"';
|
||||
const result = complete_current(
|
||||
"main.dcdl",
|
||||
source,
|
||||
source.length - 1,
|
||||
true,
|
||||
) as {
|
||||
from: number;
|
||||
items: Array<{ label: string; kind: string; detail: string }>;
|
||||
};
|
||||
const companion = result.items.find((item) =>
|
||||
item.label === "$builtin/profiles/companion.dcdl"
|
||||
);
|
||||
assertEquals(result.from, 'let 名 = 1; import "'.length);
|
||||
assertEquals(companion?.kind, "file");
|
||||
assertEquals(companion?.detail, "read-only builtin Decodal source");
|
||||
assertEquals(Object.keys(tree.entries), ["main.dcdl"]);
|
||||
const evaluated = evaluate_snapshot(
|
||||
schemaSnapshot('import "$builtin/profiles/companion.dcdl"'),
|
||||
mainEntrypointContract,
|
||||
) as {
|
||||
projections: Array<{ data_json: { slug: string } }>;
|
||||
};
|
||||
assertEquals(evaluated.projections[0].data_json.slug, "companion");
|
||||
});
|
||||
|
||||
Deno.test("generated WASM rejects unknown builtin imports without same-suffix workspace fallback", () => {
|
||||
const tree = schemaSnapshot('import "$builtin/profiles/missing.dcdl"');
|
||||
tree.entries["profiles/missing.dcdl"] = {
|
||||
path: "profiles/missing.dcdl",
|
||||
content_type: "decodal",
|
||||
content: "{}",
|
||||
content_digest: "sha256:workspace-fallback",
|
||||
};
|
||||
set_schema_bundle(emptySchemaBundle);
|
||||
const diagnostics = analyze_snapshot(tree, "main.dcdl", undefined) as Array<{
|
||||
path: string;
|
||||
revision: number;
|
||||
tree_digest: string;
|
||||
message: string;
|
||||
}>;
|
||||
assertEquals(diagnostics.length > 0, true);
|
||||
assertEquals(diagnostics[0].path, "main.dcdl");
|
||||
assertEquals(diagnostics[0].revision, tree.revision);
|
||||
assertEquals(diagnostics[0].tree_digest, tree.digest);
|
||||
assertEquals(
|
||||
diagnostics[0].message.includes(
|
||||
"unknown or non-public read-only builtin source",
|
||||
),
|
||||
true,
|
||||
);
|
||||
assertEquals(
|
||||
diagnostics[0].message.includes("$builtin/profiles/missing.dcdl"),
|
||||
true,
|
||||
);
|
||||
let failed = false;
|
||||
try {
|
||||
evaluate_snapshot(tree, mainEntrypointContract);
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
assertEquals(
|
||||
(error as Array<{ message: string }>)[0].message.includes(
|
||||
"unknown or non-public read-only builtin source",
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
assertEquals(failed, true);
|
||||
assertEquals(
|
||||
analyze_snapshot(tree, "main.dcdl", 'import "./profiles/missing.dcdl"'),
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
function importFailure(tree: ConfigTreeSnapshot): ProjectedDiagnostic[] {
|
||||
try {
|
||||
evaluate_snapshot(tree, mainEntrypointContract);
|
||||
} catch (error) {
|
||||
// A WASM trap/stack overflow must not count as a structured import failure.
|
||||
assertEquals(Array.isArray(error), true);
|
||||
const diagnostics = error as Array<
|
||||
ProjectedDiagnostic & {
|
||||
revision: number;
|
||||
tree_digest: string;
|
||||
}
|
||||
>;
|
||||
assertEquals(diagnostics.length > 0, true);
|
||||
assertEquals(diagnostics[0].revision, tree.revision);
|
||||
assertEquals(diagnostics[0].tree_digest, tree.digest);
|
||||
return diagnostics;
|
||||
}
|
||||
throw new Error("expected a structured import diagnostic");
|
||||
}
|
||||
|
||||
Deno.test("generated WASM rejects nested lazy-object import cycles without a stack overflow", () => {
|
||||
const tree = schemaSnapshot('{ nested = import "./b.dcdl"; }');
|
||||
tree.entries["b.dcdl"] = {
|
||||
path: "b.dcdl",
|
||||
content_type: "decodal",
|
||||
content: '{ nested = import "./main.dcdl"; }',
|
||||
content_digest: "sha256:nested-cycle-b",
|
||||
};
|
||||
const diagnostics = importFailure(tree);
|
||||
assertEquals(diagnostics[0].kind, "cycle");
|
||||
assertEquals(diagnostics[0].message.includes("import cycle"), true);
|
||||
});
|
||||
|
||||
Deno.test("generated WASM enforces the import depth budget on a real 33-deep chain", () => {
|
||||
function chain(depth: number): ConfigTreeSnapshot {
|
||||
const tree = schemaSnapshot('import "./chain/1.dcdl"');
|
||||
for (let index = 1; index <= depth; index++) {
|
||||
const path = `chain/${index}.dcdl`;
|
||||
tree.entries[path] = {
|
||||
path,
|
||||
content_type: "decodal",
|
||||
content: index === depth
|
||||
? "{ answer = 42; }"
|
||||
: `import "./${index + 1}.dcdl"`,
|
||||
content_digest: `sha256:chain-${depth}-${index}`,
|
||||
};
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
// The synthetic evaluation-wrapper import consumes one of the 32 edges.
|
||||
const accepted = evaluate_snapshot(chain(31), mainEntrypointContract) as {
|
||||
projections: Array<{ data_json: { answer: number } }>;
|
||||
};
|
||||
assertEquals(accepted.projections[0].data_json, { answer: 42 });
|
||||
for (const depth of [32, 33]) {
|
||||
const diagnostics = importFailure(chain(depth));
|
||||
assertEquals(diagnostics[0].kind, "import");
|
||||
assertEquals(
|
||||
diagnostics.some((diagnostic) =>
|
||||
diagnostic.message.includes("import depth")
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("generated WASM completes blank nested schema positions after Unicode", () => {
|
||||
const source =
|
||||
'{ description = "日本語"; profile = { }\n} as WorkspaceConfigSchema';
|
||||
|
||||
Reference in New Issue
Block a user