feat: resolve Workspace Plugin config into immutable execution plans

This commit is contained in:
2026-10-29 08:46:39 +09:00
parent 7ad6030498
commit 9b7a58d5c4
42 changed files with 4528 additions and 38 deletions
@@ -1,6 +1,6 @@
// @generated by yoi-companion-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:eca6e2cd9309ecf9fe73dcb044d82a5549950d66e93cd33157b2b2b3fa8ea82f)
// Input contract: 0.1.0 (sha256:e538f535da2bcf56ee07f439f8e29ef58a2fc55fc6f03eac31604ef2b3cc1dbc)
// Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/companion-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-repository-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:eca6e2cd9309ecf9fe73dcb044d82a5549950d66e93cd33157b2b2b3fa8ea82f)
// Input contract: 0.1.0 (sha256:e538f535da2bcf56ee07f439f8e29ef58a2fc55fc6f03eac31604ef2b3cc1dbc)
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
// Output: web/workspace/src/lib/generated/repository-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-runtime-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:eca6e2cd9309ecf9fe73dcb044d82a5549950d66e93cd33157b2b2b3fa8ea82f)
// Input contract: 0.1.0 (sha256:e538f535da2bcf56ee07f439f8e29ef58a2fc55fc6f03eac31604ef2b3cc1dbc)
// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/runtime-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:eca6e2cd9309ecf9fe73dcb044d82a5549950d66e93cd33157b2b2b3fa8ea82f)
// Input contract: 0.1.0 (sha256:e538f535da2bcf56ee07f439f8e29ef58a2fc55fc6f03eac31604ef2b3cc1dbc)
// Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/ticket-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-worker-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:eca6e2cd9309ecf9fe73dcb044d82a5549950d66e93cd33157b2b2b3fa8ea82f)
// Input contract: 0.1.0 (sha256:e538f535da2bcf56ee07f439f8e29ef58a2fc55fc6f03eac31604ef2b3cc1dbc)
// Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/worker-launch-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check
@@ -316,6 +316,21 @@ export async function fetchConfigHistory(
);
}
export async function previewConfigTree(
workspaceId: string,
request: ConfigCommitRequest,
fetcher: typeof fetch = fetch,
): Promise<WorkspaceConfigTreeResponse> {
return await readJson(
await fetcher(`${sourceTreeUrl(workspaceId)}/preview`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(request),
}),
parseWorkspaceConfigTreeResponse,
);
}
export async function commitConfigTree(
workspaceId: string,
request: ConfigCommitRequest,
@@ -7,6 +7,12 @@ export function apply_changes(changes: any): any;
export function changes_between(base: any, candidate: any): any;
/**
* Complete after converting the editor's UTF-16 offset to the shared analyzer's UTF-8 byte offset.
*
* Convert the returned start position back to UTF-16 so non-ASCII characters do not
* misalign editor and analyzer positions. Reject offsets that cannot map to a character boundary.
*/
export function complete_current(entrypoint: string, source: string, utf16_offset: number, explicit: boolean): any;
export function compose_schema_bundle(contributions: any): any;
@@ -21,6 +27,12 @@ export function format_source(source: string): string;
export function set_schema_bundle(schema_bundle: any): void;
/**
* Retain the browser's edit candidate as a snapshot.
*
* Deserialization uses shared digest validation but neither saves to nor authorizes the Backend.
* Keep the schema bundle in separate state to distinguish sources from evaluation conditions.
*/
export function set_snapshot(snapshot: any): void;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
@@ -44,6 +44,10 @@ export function changes_between(base, candidate) {
}
/**
* Complete after converting the editor's UTF-16 offset to the shared analyzer's UTF-8 byte offset.
*
* Convert the returned start position back to UTF-16 so non-ASCII characters do not
* misalign editor and analyzer positions. Reject offsets that cannot map to a character boundary.
* @param {string} entrypoint
* @param {string} source
* @param {number} utf16_offset
@@ -154,6 +158,10 @@ export function set_schema_bundle(schema_bundle) {
}
/**
* Retain the browser's edit candidate as a snapshot.
*
* Deserialization uses shared digest validation but neither saves to nor authorizes the Backend.
* Keep the schema bundle in separate state to distinguish sources from evaluation conditions.
* @param {any} snapshot
*/
export function set_snapshot(snapshot) {
@@ -13,6 +13,7 @@ import {
fetchConfigHistory,
fetchConfigTree,
parseWorkspaceConfigTreeResponse,
previewConfigTree,
} from "../../src/lib/workspace/config-source/api.ts";
function response(body: unknown, status = 200): Response {
@@ -335,3 +336,24 @@ Deno.test("config source API carries bounded optional authoring schemas without
}), ConfigSourceApiError);
}
});
Deno.test("config source API previews a candidate without calling commit", async () => {
const request = {
base_digest: "sha256:base",
changes: [],
entrypoints: ["main.dcdl"],
};
const calls: string[] = [];
const result = await previewConfigTree(
"w/one",
request,
((input: string | URL | Request, init?: RequestInit) => {
calls.push(String(input));
assertEquals(init?.method, "POST");
assertEquals(JSON.parse(String(init?.body)), request);
return Promise.resolve(response(tree));
}) as typeof fetch,
);
assertEquals(calls, ["/api/w/w%2Fone/config/source-tree/preview"]);
assertEquals<unknown>(result, tree);
});
@@ -985,3 +985,61 @@ Deno.test("generated WASM rejects forged content identity at the snapshot bounda
assertThrows(() => set_snapshot(forged));
}
});
Deno.test("Plugin closed preflight shares native projection and diagnostic fixture without catalog authority", async () => {
const fixture = JSON.parse(
await Deno.readTextFile(
new URL(
"../../../../resources/config-schema/fixtures/plugin-preflight.json",
import.meta.url,
),
),
) as {
schema: string;
cases: Array<{ source: string; mode?: string; diagnostic?: string }>;
};
const bundle = compose_schema_bundle([{
provider_id: "plugin-preflight",
namespace: "plugins",
version: "1",
source: fixture.schema,
source_digest: await digestText(fixture.schema),
}]) as WorkspaceConfigSchemaBundle;
const contract: ToolchainContract = {
contract_version: 2,
decodal_version: "0.4.0",
schema_version: 1,
entrypoints: ["main.dcdl"],
import_policy_version: 1,
schema_bundle: bundle,
fingerprint: await toolchainFingerprint(["main.dcdl"], bundle),
};
for (const testCase of fixture.cases) {
const candidate = fixtureSnapshot(schemaSnapshot(testCase.source));
if (testCase.diagnostic) {
let failure: ProjectedDiagnostic[] = [];
try {
evaluate_snapshot(candidate, contract);
} catch (error) {
failure = error as ProjectedDiagnostic[];
}
assertEquals(Array.isArray(failure), true);
assertEquals(failure[0].kind, testCase.diagnostic);
assertEquals(failure[0].path, "main.dcdl");
} else {
const result = evaluate_snapshot(candidate, contract) as {
projections: Array<
{
data_json: {
plugins: { enabled: { echo: { config: { mode: string } } } };
};
}
>;
};
assertEquals(
result.projections[0].data_json.plugins.enabled.echo.config.mode,
testCase.mode,
);
}
}
});