fix(web): validate corrected startup HTTP and align generated schemas
This commit is contained in:
+20
-20
@@ -99,25 +99,6 @@
|
|||||||
"maxLength": 128,
|
"maxLength": 128,
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"Diagnostic": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"code": {
|
|
||||||
"type": "string",
|
|
||||||
"maxLength": 128,
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"explanation": {
|
|
||||||
"type": "string",
|
|
||||||
"maxLength": 2048,
|
|
||||||
"minLength": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"code",
|
|
||||||
"explanation"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"FailureContext": {
|
"FailureContext": {
|
||||||
"description": "Common metadata is untrusted after deserialization. No audience label confers trust.",
|
"description": "Common metadata is untrusted after deserialization. No audience label confers trust.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -143,7 +124,7 @@
|
|||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/$defs/Diagnostic"
|
"$ref": "#/$defs/FailureDiagnostic"
|
||||||
},
|
},
|
||||||
"maxItems": 8
|
"maxItems": 8
|
||||||
},
|
},
|
||||||
@@ -165,6 +146,25 @@
|
|||||||
"supplementary_omitted"
|
"supplementary_omitted"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"FailureDiagnostic": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"explanation": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 2048,
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"code",
|
||||||
|
"explanation"
|
||||||
|
]
|
||||||
|
},
|
||||||
"OperationContext": {
|
"OperationContext": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -82,25 +82,6 @@
|
|||||||
"maxLength": 128,
|
"maxLength": 128,
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"Diagnostic": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"code": {
|
|
||||||
"type": "string",
|
|
||||||
"maxLength": 128,
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"explanation": {
|
|
||||||
"type": "string",
|
|
||||||
"maxLength": 2048,
|
|
||||||
"minLength": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"code",
|
|
||||||
"explanation"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"FailureContext": {
|
"FailureContext": {
|
||||||
"description": "Common metadata is untrusted after deserialization. No audience label confers trust.",
|
"description": "Common metadata is untrusted after deserialization. No audience label confers trust.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -126,7 +107,7 @@
|
|||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/$defs/Diagnostic"
|
"$ref": "#/$defs/FailureDiagnostic"
|
||||||
},
|
},
|
||||||
"maxItems": 8
|
"maxItems": 8
|
||||||
},
|
},
|
||||||
@@ -148,6 +129,25 @@
|
|||||||
"supplementary_omitted"
|
"supplementary_omitted"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"FailureDiagnostic": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"explanation": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 2048,
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"code",
|
||||||
|
"explanation"
|
||||||
|
]
|
||||||
|
},
|
||||||
"FailureEnvelope": {
|
"FailureEnvelope": {
|
||||||
"description": "DTO/schema source only. Deserialization does not produce a ProjectedFailure.",
|
"description": "DTO/schema source only. Deserialization does not produce a ProjectedFailure.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type {
|
|||||||
WorkingDirectoryRemovalResponse,
|
WorkingDirectoryRemovalResponse,
|
||||||
WorkingDirectorySource,
|
WorkingDirectorySource,
|
||||||
WorkingDirectorySummary,
|
WorkingDirectorySummary,
|
||||||
} from "../../generated/workdir-api";
|
} from "../../generated/workdir-api.ts";
|
||||||
|
|
||||||
const SUMMARY_KEYS = new Set([
|
const SUMMARY_KEYS = new Set([
|
||||||
"working_directory_id",
|
"working_directory_id",
|
||||||
|
|||||||
@@ -6,12 +6,44 @@ import {
|
|||||||
stopOwnedProcesses,
|
stopOwnedProcesses,
|
||||||
} from "../../../../tools/web-ux/src/processes.ts";
|
} from "../../../../tools/web-ux/src/processes.ts";
|
||||||
import { startupFixture } from "../../src/lib/workspace/workers/startup-failure.test-fixtures.ts";
|
import { startupFixture } from "../../src/lib/workspace/workers/startup-failure.test-fixtures.ts";
|
||||||
|
import { verifyStartupHttpCaptures } from "../worker-startup-backend-contract.ts";
|
||||||
|
|
||||||
// production build + exact T812 HTTP fixtures。派生故障・操作結果はWeb試験用でありServer保証ではない。
|
// production build + exact T812 HTTP fixtures。派生故障・操作結果はWeb試験用でありServer保証ではない。
|
||||||
// beforeは旧production buildで同fixtureを撮影するためだけに明示選択し、afterの成功と混同しない。
|
// beforeは旧production buildで同fixtureを撮影するためだけに明示選択し、afterの成功と混同しない。
|
||||||
Deno.test("Worker failure cause, independent outcomes, keyboard details and safe reconciliation", async () => {
|
Deno.test("Worker failure cause, independent outcomes, keyboard details and safe reconciliation", async () => {
|
||||||
const before = Deno.env.get("T813_PHASE") === "before";
|
const before = Deno.env.get("T813_PHASE") === "before";
|
||||||
const phase = before ? "before-real-contract" : "after";
|
const captureInput = Deno.env.get("T813_HTTP_CAPTURE_LOG");
|
||||||
|
const captureLog = captureInput
|
||||||
|
? await Deno.readTextFile(captureInput)
|
||||||
|
: null;
|
||||||
|
const captureSha256 = captureLog
|
||||||
|
? Array.from(
|
||||||
|
new Uint8Array(
|
||||||
|
await crypto.subtle.digest(
|
||||||
|
"SHA-256",
|
||||||
|
new TextEncoder().encode(captureLog),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).map((b) => b.toString(16).padStart(2, "0")).join("")
|
||||||
|
: null;
|
||||||
|
const captures = captureLog
|
||||||
|
? (await verifyStartupHttpCaptures(captureLog)).captures
|
||||||
|
: null;
|
||||||
|
function fixture(name: Parameters<typeof startupFixture>[0]) {
|
||||||
|
if (captures && (name === "denial" || name === "lost")) {
|
||||||
|
const capture = captures.find((item) =>
|
||||||
|
item.status === (name === "denial" ? 409 : 502)
|
||||||
|
);
|
||||||
|
assert(capture, "Expected both validated real HTTP captures");
|
||||||
|
return JSON.parse(capture.body);
|
||||||
|
}
|
||||||
|
return startupFixture(name);
|
||||||
|
}
|
||||||
|
const phase = before
|
||||||
|
? "before-real-contract"
|
||||||
|
: captureInput
|
||||||
|
? "after-connected"
|
||||||
|
: "after";
|
||||||
const scenarioPath =
|
const scenarioPath =
|
||||||
new URL("./optional-repository.json", import.meta.url).pathname;
|
new URL("./optional-repository.json", import.meta.url).pathname;
|
||||||
const scenario = await loadScenario(scenarioPath);
|
const scenario = await loadScenario(scenarioPath);
|
||||||
@@ -115,7 +147,7 @@ Deno.test("Worker failure cause, independent outcomes, keyboard details and safe
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let body = JSON.parse(JSON.stringify(startupFixture("denial")));
|
let body = JSON.parse(JSON.stringify(fixture("denial")));
|
||||||
await page.route("**/api/w/home-empty/workers", async (route) => {
|
await page.route("**/api/w/home-empty/workers", async (route) => {
|
||||||
if (route.request().method() !== "POST") return route.continue();
|
if (route.request().method() !== "POST") return route.continue();
|
||||||
submissions++;
|
submissions++;
|
||||||
@@ -144,7 +176,7 @@ Deno.test("Worker failure cause, independent outcomes, keyboard details and safe
|
|||||||
]
|
]
|
||||||
) {
|
) {
|
||||||
body = JSON.parse(
|
body = JSON.parse(
|
||||||
JSON.stringify(startupFixture(
|
JSON.stringify(fixture(
|
||||||
name === "lost"
|
name === "lost"
|
||||||
? "lost"
|
? "lost"
|
||||||
: name === "rejected"
|
: name === "rejected"
|
||||||
@@ -294,8 +326,12 @@ Deno.test("Worker failure cause, independent outcomes, keyboard details and safe
|
|||||||
route: "/w/home-empty/workers/new",
|
route: "/w/home-empty/workers/new",
|
||||||
persona: "synthetic owner",
|
persona: "synthetic owner",
|
||||||
name,
|
name,
|
||||||
|
captureInput,
|
||||||
|
captureSha256,
|
||||||
fixture: ["denial", "lost"].includes(name)
|
fixture: ["denial", "lost"].includes(name)
|
||||||
? "T812 exact real HTTP body"
|
? captureInput
|
||||||
|
? "forwarded exact T812 corrected-source HTTP capture"
|
||||||
|
: "T812 exact real HTTP body"
|
||||||
: "synthetic derivative/fault",
|
: "synthetic derivative/fault",
|
||||||
width,
|
width,
|
||||||
theme: colorScheme,
|
theme: colorScheme,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// 実認可・Runtime・保存を通したRust connected試験の公開HTTP捕捉だけを受ける。
|
// 実認可・Runtime・保存を通したRust connected試験の公開HTTP捕捉だけを受ける。
|
||||||
// Web送信/受信/表示modelへの接続であり、このprocess自体は認可や保存を代替証明しない。
|
// Web送信/受信/表示modelへの接続であり、このprocess自体は認可や保存を代替証明しない。
|
||||||
import { buildCreateWorkspaceWorkerRequest } from "../src/lib/workspace/sidebar/worker-launch.ts";
|
import type { CreateWorkspaceWorkerRequest } from "../src/lib/generated/worker-launch-api.ts";
|
||||||
import {
|
import {
|
||||||
submitWorkerLaunch,
|
submitWorkerLaunch,
|
||||||
workerLaunchNeedsReconciliation,
|
workerLaunchNeedsReconciliation,
|
||||||
@@ -14,17 +14,18 @@ export async function verifyStartupHttpCaptures(log: string) {
|
|||||||
if (new TextEncoder().encode(log).length > maxLogBytes) {
|
if (new TextEncoder().encode(log).length > maxLogBytes) {
|
||||||
throw new Error("Startup capture log exceeds the test input limit");
|
throw new Error("Startup capture log exceeds the test input limit");
|
||||||
}
|
}
|
||||||
const request = buildCreateWorkspaceWorkerRequest({
|
const request: CreateWorkspaceWorkerRequest = {
|
||||||
runtime_id: "embedded",
|
runtime_id: "embedded",
|
||||||
display_name: "HTTP contract check",
|
display_name: "HTTP contract check",
|
||||||
profile: "",
|
profile: null,
|
||||||
subjektiv_subject_id: "",
|
ticket_assignment: null,
|
||||||
initial_text: "",
|
initial_submit: [],
|
||||||
workdir_attachments: [],
|
workdir_attachments: [],
|
||||||
working_directory_repository_key: "",
|
feature_connections: {},
|
||||||
working_directory_selector: "HEAD",
|
control_operation_id: null,
|
||||||
});
|
};
|
||||||
const cases = new Set<string>();
|
const cases = new Set<string>();
|
||||||
|
const captures: { status: number; contentType: string; body: string }[] = [];
|
||||||
let records = 0;
|
let records = 0;
|
||||||
for (const line of log.split("\n")) {
|
for (const line of log.split("\n")) {
|
||||||
if (!line.includes(marker)) continue;
|
if (!line.includes(marker)) continue;
|
||||||
@@ -99,6 +100,7 @@ export async function verifyStartupHttpCaptures(log: string) {
|
|||||||
"Web cause presentation or uncertain-result gating differs from the connected HTTP result",
|
"Web cause presentation or uncertain-result gating differs from the connected HTTP result",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
captures.push({ status: Number(status), contentType, body });
|
||||||
records++;
|
records++;
|
||||||
}
|
}
|
||||||
if (!cases.has("denial") || !cases.has("response_lost")) {
|
if (!cases.has("denial") || !cases.has("response_lost")) {
|
||||||
@@ -106,7 +108,7 @@ export async function verifyStartupHttpCaptures(log: string) {
|
|||||||
"Missing real denial or response-loss Startup HTTP capture",
|
"Missing real denial or response-loss Startup HTTP capture",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { records, cases: cases.size };
|
return { records, cases: cases.size, captures };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.meta.main) {
|
if (import.meta.main) {
|
||||||
|
|||||||
Reference in New Issue
Block a user