config: require schema assertion on main
This commit is contained in:
@@ -14,8 +14,6 @@
|
||||
} from "./types.ts";
|
||||
|
||||
const MAIN_ENTRYPOINT = "main.dcdl";
|
||||
const WORKSPACE_SCHEMA_ASSERTION = "WorkspaceConfigSchema";
|
||||
const WORKSPACE_SCHEMA_SUFFIX = `} as ${WORKSPACE_SCHEMA_ASSERTION}`;
|
||||
|
||||
let { workspaceId }: { workspaceId: string } = $props();
|
||||
let treeState = $state<WorkspaceConfigTreeResponse | null>(null);
|
||||
@@ -42,7 +40,6 @@
|
||||
treeState && selectedPath ? treeState.snapshot.entries[selectedPath] : undefined,
|
||||
);
|
||||
const mainSelected = $derived(selectedPath === MAIN_ENTRYPOINT);
|
||||
const mainSchemaWrapped = $derived(mainSelected && hasWorkspaceSchemaWrapper(source));
|
||||
const dirty = $derived(draftChanges.length > 0 || (selected ? source !== selected.content : source.length > 0));
|
||||
const commitReady = $derived(dirty && preflightDigest === treeState?.snapshot.digest);
|
||||
|
||||
@@ -117,35 +114,6 @@
|
||||
return [MAIN_ENTRYPOINT];
|
||||
}
|
||||
|
||||
function hasWorkspaceSchemaWrapper(value: string): boolean {
|
||||
const sourceWithoutTrailingWhitespace = value.trimEnd();
|
||||
return value.startsWith("{") && sourceWithoutTrailingWhitespace.endsWith(WORKSPACE_SCHEMA_SUFFIX);
|
||||
}
|
||||
|
||||
async function wrapMainWithWorkspaceSchema() {
|
||||
if (!toolchain || !mainSelected || mainSchemaWrapped || busy) return;
|
||||
const candidate = source.trim();
|
||||
if (!candidate.startsWith("{") || !candidate.endsWith("}")) {
|
||||
status = "WorkspaceConfigSchema can only wrap a top-level object source.";
|
||||
return;
|
||||
}
|
||||
busy = true;
|
||||
try {
|
||||
source = await toolchain.format(`${candidate} as ${WORKSPACE_SCHEMA_ASSERTION}`);
|
||||
diagnostics = await toolchain.analyze(selectedPath, source);
|
||||
preflightDigest = "";
|
||||
candidateContract = null;
|
||||
conflict = false;
|
||||
status = diagnostics.length === 0
|
||||
? "WorkspaceConfigSchema wrapper staged. Preview and Commit to persist it."
|
||||
: `${diagnostics.length} diagnostic(s) after adding WorkspaceConfigSchema.`;
|
||||
} catch (error) {
|
||||
status = String(error);
|
||||
} finally {
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function analyze() {
|
||||
if (!toolchain || !treeState || !selectedPath) return;
|
||||
diagnostics = await toolchain.analyze(selectedPath, source);
|
||||
@@ -344,9 +312,6 @@
|
||||
<div class="config-source-actions">
|
||||
<input aria-label="Rename path" bind:value={renamePath} disabled={!selected || mainSelected || busy} />
|
||||
<button type="button" onclick={renameEntry} disabled={!selected || mainSelected || renamePath === selectedPath || busy}>Rename</button>
|
||||
{#if mainSelected && !mainSchemaWrapped}
|
||||
<button type="button" onclick={wrapMainWithWorkspaceSchema} disabled={!selected || busy}>Wrap with WorkspaceConfigSchema</button>
|
||||
{/if}
|
||||
<button type="button" onclick={format} disabled={!selectedPath || busy}>Format</button>
|
||||
<button type="button" onclick={analyze} disabled={!selectedPath || busy}>Analyze</button>
|
||||
<button type="button" onclick={preview} disabled={!dirty || busy}>Preview</button>
|
||||
@@ -357,7 +322,7 @@
|
||||
<DecodalSourceEditor
|
||||
value={source}
|
||||
readonly={!selectedPath || busy}
|
||||
fixedSchemaWrapper={mainSchemaWrapped}
|
||||
fixedSchemaWrapper={mainSelected}
|
||||
onChange={(value) => source = value}
|
||||
onComplete={(value, offset, explicit) => toolchain?.complete(selectedPath, value, offset, explicit) ?? Promise.resolve(null)}
|
||||
/>
|
||||
|
||||
Binary file not shown.
@@ -80,7 +80,7 @@ Deno.test("Decodal editor follows readonly prop changes after mount", async () =
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("main config wrapper is an explicit canonical draft conversion", async () => {
|
||||
Deno.test("main entrypoint always enables the fixed schema wrapper", async () => {
|
||||
const source = await Deno.readTextFile(
|
||||
new URL(
|
||||
"../../src/lib/workspace/config-source/ConfigSourceEditor.svelte",
|
||||
@@ -89,15 +89,12 @@ Deno.test("main config wrapper is an explicit canonical draft conversion", async
|
||||
);
|
||||
|
||||
assert(
|
||||
source.includes("Wrap with WorkspaceConfigSchema") &&
|
||||
source.includes("wrapMainWithWorkspaceSchema") &&
|
||||
source.includes("fixedSchemaWrapper={mainSchemaWrapped}"),
|
||||
"legacy main sources should offer an explicit conversion before fixed wrapper mode is enabled",
|
||||
source.includes("fixedSchemaWrapper={mainSelected}"),
|
||||
"main.dcdl should always enable fixed wrapper behavior",
|
||||
);
|
||||
assert(
|
||||
source.includes("source.trim()") &&
|
||||
source.includes("toolchain.format") &&
|
||||
source.includes("wrapper staged. Preview and Commit"),
|
||||
"conversion should remain a formatted local draft until the normal commit flow",
|
||||
!source.includes("Wrap with WorkspaceConfigSchema") &&
|
||||
!source.includes("wrapMainWithWorkspaceSchema"),
|
||||
"the authoritative main source should not require an optional client-side conversion",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -81,6 +81,12 @@ const contract: ToolchainContract = {
|
||||
),
|
||||
};
|
||||
|
||||
const mainEntrypointContract: ToolchainContract = {
|
||||
...contract,
|
||||
entrypoints: ["main.dcdl"],
|
||||
fingerprint: await toolchainFingerprint(["main.dcdl"], emptySchemaBundle),
|
||||
};
|
||||
|
||||
Deno.test("generated WASM evaluates the same virtual import contract", () => {
|
||||
const result = evaluate_snapshot(snapshot, contract) as {
|
||||
projections: Array<{ data_json: { answer: number } }>;
|
||||
@@ -88,6 +94,27 @@ Deno.test("generated WASM evaluates the same virtual import contract", () => {
|
||||
assertEquals(result.projections[0].data_json, { answer: 42 });
|
||||
});
|
||||
|
||||
Deno.test("generated WASM accepts the mandatory main schema assertion", () => {
|
||||
const assertedSnapshot: ConfigTreeSnapshot = {
|
||||
revision: 1,
|
||||
digest: "sha256:asserted-main",
|
||||
entries: {
|
||||
"main.dcdl": {
|
||||
path: "main.dcdl",
|
||||
content_type: "decodal",
|
||||
content: "{ answer = 42; } as WorkspaceConfigSchema\n",
|
||||
content_digest: "sha256:asserted-main-entry",
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = evaluate_snapshot(
|
||||
assertedSnapshot,
|
||||
mainEntrypointContract,
|
||||
) as { projections: Array<{ data_json: { answer: number } }> };
|
||||
|
||||
assertEquals(result.projections[0].data_json, { answer: 42 });
|
||||
});
|
||||
|
||||
Deno.test("generated WASM diagnostics carry snapshot provenance", () => {
|
||||
const diagnostics = analyze_snapshot(
|
||||
snapshot,
|
||||
|
||||
Reference in New Issue
Block a user