feat: add configurable operator appearance and layout

This commit is contained in:
2026-07-23 11:14:10 +09:00
parent cf117de144
commit f43483533a
34 changed files with 5942 additions and 740 deletions

View File

@@ -0,0 +1,998 @@
import fs from "node:fs";
import path from "node:path";
import { createHash, randomBytes } from "node:crypto";
const exactTargetUrl = "https://legacy-parity.mbn.local/index.html";
const allowedOutboundTypes = new Set([
"ready",
"select-tab",
"set-operator-appearance"
]);
const forbiddenCdpMethods = new Set([
"Browser.close",
"Page.close",
"Runtime.terminateExecution",
"Target.closeTarget",
"Input.dispatchMouseEvent",
"Input.dispatchKeyEvent",
"Input.insertText"
]);
const appearanceValues = Object.freeze({
colorTheme: Object.freeze(["system", "light", "dark"]),
viewMode: Object.freeze(["automatic", "compact", "cards"]),
startWorkspace: Object.freeze(["stockCut", "lastWorkspace"])
});
const controls = Object.freeze({
settings: Object.freeze({
id: "workspace-settings-tab",
expression: 'document.getElementById("workspace-settings-tab")'
}),
colorTheme: Object.freeze({
id: "operator-color-theme",
expression: 'document.getElementById("operator-color-theme")'
}),
viewMode: Object.freeze({
id: "operator-view-mode",
expression: 'document.getElementById("operator-view-mode")'
}),
startWorkspace: Object.freeze({
id: "operator-start-workspace",
expression: 'document.getElementById("operator-start-workspace")'
})
});
class AppearanceFailure extends Error {
constructor(category, message) {
super(`${category}: ${message}`);
this.name = "AppearanceFailure";
this.category = category;
}
}
function failKnown(message) {
throw new AppearanceFailure("KNOWN_FAILURE", message);
}
function failUnknown(message) {
throw new AppearanceFailure("OUTCOME_UNKNOWN", message);
}
function parseClosedValue(values, name, allowed) {
const value = values.get(name);
if (!allowed.includes(value)) failKnown(`--${name} is outside the closed value set.`);
return value;
}
function parseArguments(argv) {
const accepted = new Set([
"phase",
"port",
"output",
"screenshot",
"exchange-directory",
"expected-color-theme",
"expected-view-mode",
"expected-start-workspace",
"final-color-theme",
"final-view-mode",
"final-start-workspace",
"timeout-ms"
]);
if (argv.length === 0 || argv.length % 2 !== 0) {
failKnown(
"Usage: node scripts/Test-LegacyPackageAppearanceSmoke.mjs " +
"--phase mutate|verify-restore --port <port> --output <absolute.json> " +
"--screenshot <absolute.png> --exchange-directory <absolute-directory> " +
"--expected-color-theme <value> --expected-view-mode <value> " +
"--expected-start-workspace <value> --final-color-theme <value> " +
"--final-view-mode <value> --final-start-workspace <value> " +
"[--timeout-ms <milliseconds>]");
}
const values = new Map();
for (let index = 0; index < argv.length; index += 2) {
const rawName = argv[index];
const value = argv[index + 1];
if (!rawName?.startsWith("--") || !value) failKnown("Arguments must be --name value pairs.");
const name = rawName.slice(2);
if (!accepted.has(name) || values.has(name)) failKnown(`Invalid argument --${name}.`);
values.set(name, value);
}
for (const required of [
"phase", "port", "output", "screenshot", "exchange-directory",
"expected-color-theme", "expected-view-mode", "expected-start-workspace",
"final-color-theme", "final-view-mode", "final-start-workspace"
]) {
if (!values.has(required)) failKnown(`--${required} is required.`);
}
const phase = values.get("phase");
if (!new Set(["mutate", "verify-restore"]).has(phase)) {
failKnown("--phase must be mutate or verify-restore.");
}
const port = Number(values.get("port"));
const timeoutMilliseconds = Number(values.get("timeout-ms") || "120000");
if (!Number.isInteger(port) || port < 1024 || port > 65_535) {
failKnown("--port must be from 1024 through 65535.");
}
if (!Number.isInteger(timeoutMilliseconds) ||
timeoutMilliseconds < 30_000 || timeoutMilliseconds > 900_000) {
failKnown("--timeout-ms must be from 30000 through 900000.");
}
const outputPath = path.resolve(values.get("output"));
const screenshotPath = path.resolve(values.get("screenshot"));
const exchangeDirectory = path.resolve(values.get("exchange-directory"));
if (!path.isAbsolute(values.get("output")) ||
path.extname(outputPath).toLowerCase() !== ".json" ||
!path.isAbsolute(values.get("screenshot")) ||
path.extname(screenshotPath).toLowerCase() !== ".png" ||
!path.isAbsolute(values.get("exchange-directory"))) {
failKnown("Output, screenshot and exchange paths must be absolute and typed correctly.");
}
if (new Set([
outputPath.toLowerCase(), screenshotPath.toLowerCase(), exchangeDirectory.toLowerCase()
]).size !== 3) {
failKnown("Evidence paths must be distinct.");
}
if (fs.existsSync(outputPath) || fs.existsSync(screenshotPath) ||
!fs.existsSync(exchangeDirectory) ||
!fs.statSync(exchangeDirectory).isDirectory() ||
fs.readdirSync(exchangeDirectory).length !== 0) {
failKnown("Evidence is never overwritten and the exchange directory must be empty.");
}
const expected = Object.freeze({
colorTheme: parseClosedValue(
values, "expected-color-theme", appearanceValues.colorTheme),
viewMode: parseClosedValue(values, "expected-view-mode", appearanceValues.viewMode),
startWorkspace: parseClosedValue(
values, "expected-start-workspace", appearanceValues.startWorkspace)
});
const final = Object.freeze({
colorTheme: parseClosedValue(values, "final-color-theme", appearanceValues.colorTheme),
viewMode: parseClosedValue(values, "final-view-mode", appearanceValues.viewMode),
startWorkspace: parseClosedValue(
values, "final-start-workspace", appearanceValues.startWorkspace)
});
if (Object.keys(expected).some(key => expected[key] === final[key])) {
failKnown("Every phase must physically change all three appearance values.");
}
return {
phase,
port,
timeoutMilliseconds,
outputPath,
screenshotPath,
exchangeDirectory,
expected,
final
};
}
const configuration = parseArguments(process.argv.slice(2));
const hardDeadline = Date.now() + configuration.timeoutMilliseconds;
let socket = null;
let nextRpcId = 1;
let nextExchangeSequence = 1;
let probeInstalled = false;
let cancellationRequested = false;
const pendingRpc = new Map();
const evidence = {
schemaVersion: 1,
profile: "operator-appearance-persistence",
phase: configuration.phase,
result: "RUNNING",
startedAt: new Date().toISOString(),
completedAt: null,
target: {
expectedUrl: exactTargetUrl,
port: configuration.port,
id: null,
url: null
},
expectedInitial: configuration.expected,
expectedFinal: configuration.final,
safety: {
requiredMode: "dryRun",
requiredPhase: "idle",
inputRetryCount: 0,
appCloseRequested: false,
playoutIntentIssued: false,
allowedOutboundTypes: [...allowedOutboundTypes],
observedOutboundTypes: [],
blockedOutboundMessages: [],
stateViolations: []
},
persistenceVerifiedBeforeInput: false,
startWorkspaceBehaviorVerifiedBeforeInput: false,
selections: [],
exchanges: [],
checkpoints: [],
finalSnapshot: null,
screenshot: null,
error: null
};
process.stdin.setEncoding("utf8");
process.stdin.on("data", chunk => {
if (String(chunk).split(/\r?\n/u).includes("CANCEL")) cancellationRequested = true;
});
if (typeof process.stdin.unref === "function") process.stdin.unref();
function assertDeadline(label) {
if (cancellationRequested) failUnknown(`The wrapper cancelled ${label}; no input was retried.`);
if (Date.now() >= hardDeadline) {
failUnknown(`The global deadline expired ${label}; no input was retried.`);
}
}
async function sleep(milliseconds) {
assertDeadline("before waiting");
await new Promise(resolve => setTimeout(
resolve,
Math.max(0, Math.min(milliseconds, hardDeadline - Date.now()))));
assertDeadline("after waiting");
}
function sha256(bytes) {
return createHash("sha256").update(bytes).digest("hex").toUpperCase();
}
function exactAppearance(value) {
return value ? {
colorTheme: value.colorTheme,
viewMode: value.viewMode,
startWorkspace: value.startWorkspace
} : null;
}
function appearanceEquals(left, right) {
return Boolean(left && right &&
left.colorTheme === right.colorTheme &&
left.viewMode === right.viewMode &&
left.startWorkspace === right.startWorkspace);
}
async function discoverTarget() {
const discoveryUrl = `http://127.0.0.1:${configuration.port}/json/list`;
const deadline = Math.min(hardDeadline, Date.now() + 15_000);
while (Date.now() < deadline) {
try {
const response = await fetch(discoveryUrl, { cache: "no-store" });
if (response.ok) {
const targets = await response.json();
const exact = targets.filter(target =>
target?.type === "page" && target.url === exactTargetUrl);
if (exact.length > 1) failKnown("More than one exact package WebView target exists.");
if (exact.length === 1) return exact[0];
}
} catch (error) {
if (error instanceof AppearanceFailure) throw error;
}
await sleep(100);
}
failKnown("The exact package target was not found.");
}
function validateEndpoint(target) {
if (!target?.id || !target.webSocketDebuggerUrl) failKnown("The target has no debugger URL.");
const endpoint = new URL(target.webSocketDebuggerUrl);
if (endpoint.protocol !== "ws:" || endpoint.hostname !== "127.0.0.1" ||
Number(endpoint.port) !== configuration.port ||
endpoint.pathname !== `/devtools/page/${target.id}`) {
failKnown("The debugger endpoint is not the exact loopback page endpoint.");
}
return endpoint;
}
async function connect(endpoint) {
if (typeof WebSocket !== "function") failKnown("This Node runtime has no WebSocket API.");
socket = new WebSocket(endpoint.href);
await new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new AppearanceFailure(
"OUTCOME_UNKNOWN", "CDP socket open timed out.")), 5_000);
socket.addEventListener("open", () => { clearTimeout(timer); resolve(); }, { once: true });
socket.addEventListener("error", () => { clearTimeout(timer); reject(new AppearanceFailure(
"OUTCOME_UNKNOWN", "CDP socket failed to open.")); }, { once: true });
});
socket.addEventListener("message", event => {
const message = JSON.parse(String(event.data));
if (!Object.hasOwn(message, "id")) return;
const pending = pendingRpc.get(message.id);
if (!pending) return;
pendingRpc.delete(message.id);
clearTimeout(pending.timer);
if (message.error) pending.reject(new AppearanceFailure(
"KNOWN_FAILURE", `${pending.method} failed: ${message.error.message || "CDP error"}`));
else pending.resolve(message.result);
});
}
async function rpc(method, params = {}, timeoutMilliseconds = 10_000) {
assertDeadline(`before ${method}`);
if (forbiddenCdpMethods.has(method)) failKnown(`CDP method ${method} is forbidden.`);
if (!socket || socket.readyState !== WebSocket.OPEN) failUnknown("CDP socket is not open.");
const id = nextRpcId++;
return await new Promise((resolve, reject) => {
const timer = setTimeout(() => {
pendingRpc.delete(id);
reject(new AppearanceFailure(
"OUTCOME_UNKNOWN", `${method} timed out; no input was retried.`));
}, Math.min(timeoutMilliseconds, Math.max(1, hardDeadline - Date.now())));
pendingRpc.set(id, { resolve, reject, timer, method });
socket.send(JSON.stringify({ id, method, params }));
});
}
async function evaluate(expression) {
const result = await rpc("Runtime.evaluate", {
expression,
awaitPromise: true,
returnByValue: true
});
if (result?.exceptionDetails) failKnown("A read-only page inspection failed.");
return result?.result?.value;
}
async function installProbe() {
const token = `appearance-${Date.now()}-${randomBytes(8).toString("hex")}`;
const installed = await evaluate(`(() => {
if (!window.chrome?.webview) throw new Error("WebView2 bridge is unavailable.");
if (globalThis.__legacyAppearanceProbe) throw new Error("Appearance probe already exists.");
const probe = {
token: ${JSON.stringify(token)},
states: [],
outboundMessages: [],
blockedOutboundMessages: [],
stateViolations: [],
inputEvents: [],
originalPostMessage: window.chrome.webview.postMessage,
postMessageWrapper: null,
stateListener: null,
inputListener: null
};
const allowed = new Set(${JSON.stringify([...allowedOutboundTypes])});
probe.stateListener = event => {
if (event.data?.type !== "state" || !event.data.payload) return;
const state = event.data.payload;
probe.states.push(structuredClone(state));
if (probe.states.length > 100) probe.states.shift();
const playout = state.playout;
const reasons = [];
if (!playout) reasons.push("missing-playout");
else {
if (playout.mode !== "dryRun") reasons.push("mode");
if (playout.phase !== "idle") reasons.push("phase");
if (playout.isConnected === true) reasons.push("connected");
if (playout.preparedCode != null) reasons.push("prepared-code");
if (playout.onAirCode != null) reasons.push("on-air-code");
if (playout.outcomeUnknown === true) reasons.push("outcome-unknown");
if (playout.isPlayCompletionPending === true) reasons.push("play-pending");
if (playout.isTakeOutCompletionPending === true) reasons.push("takeout-pending");
if (playout.isBusy === true) reasons.push("playout-busy");
if (playout.refreshActive === true) reasons.push("refresh-active");
}
if (reasons.length > 0) probe.stateViolations.push({
at: new Date().toISOString(),
revision: state.revision ?? null,
reasons
});
};
probe.inputListener = event => {
const element = event.target instanceof Element ? event.target : null;
const controlled = element?.closest?.(
"#workspace-settings-tab, #operator-color-theme, " +
"#operator-view-mode, #operator-start-workspace");
if (!controlled) return;
probe.inputEvents.push({
sequence: probe.inputEvents.length + 1,
type: event.type,
targetId: controlled.id || null,
isTrusted: event.isTrusted === true,
pointerType: event.pointerType || null,
button: Number.isInteger(event.button) ? event.button : null,
buttons: Number.isInteger(event.buttons) ? event.buttons : null,
key: typeof event.key === "string" ? event.key : null,
value: controlled instanceof HTMLSelectElement ? controlled.value : null,
at: new Date().toISOString()
});
if (probe.inputEvents.length > 150) probe.inputEvents.shift();
};
probe.postMessageWrapper = message => {
const type = typeof message?.type === "string" ? message.type : null;
const record = {
sequence: probe.outboundMessages.length + 1,
type,
appearance: type === "set-operator-appearance" ? {
colorTheme: message?.payload?.colorTheme ?? null,
viewMode: message?.payload?.viewMode ?? null,
startWorkspace: message?.payload?.startWorkspace ?? null
} : null,
at: new Date().toISOString()
};
probe.outboundMessages.push(record);
if (!type || !allowed.has(type)) {
probe.blockedOutboundMessages.push(record);
return;
}
return probe.originalPostMessage.call(window.chrome.webview, message);
};
try {
window.chrome.webview.postMessage = probe.postMessageWrapper;
if (window.chrome.webview.postMessage !== probe.postMessageWrapper) {
throw new Error("The outbound safety gate could not be installed.");
}
globalThis.__legacyAppearanceProbe = probe;
window.chrome.webview.addEventListener("message", probe.stateListener);
["pointerdown", "pointerup", "keydown", "keyup", "input", "change"].forEach(type =>
document.addEventListener(type, probe.inputListener, true));
window.chrome.webview.postMessage({ type: "ready", payload: {} });
} catch (error) {
window.chrome.webview.postMessage = probe.originalPostMessage;
delete globalThis.__legacyAppearanceProbe;
throw error;
}
return probe.token;
})()`);
if (installed !== token) failKnown("The safety probe was not installed exactly once.");
probeInstalled = true;
}
async function removeProbe() {
if (!probeInstalled || !socket || socket.readyState !== WebSocket.OPEN) return;
const removed = await evaluate(`(() => {
const probe = globalThis.__legacyAppearanceProbe;
if (!probe) return false;
window.chrome.webview.removeEventListener("message", probe.stateListener);
["pointerdown", "pointerup", "keydown", "keyup", "input", "change"].forEach(type =>
document.removeEventListener(type, probe.inputListener, true));
if (window.chrome.webview.postMessage !== probe.postMessageWrapper) {
throw new Error("The outbound safety gate identity changed.");
}
window.chrome.webview.postMessage = probe.originalPostMessage;
delete globalThis.__legacyAppearanceProbe;
return true;
})()`);
if (removed !== true) failUnknown("The safety probe was not restored exactly once.");
probeInstalled = false;
}
async function readSnapshot() {
return await evaluate(`(() => {
const probe = globalThis.__legacyAppearanceProbe;
const state = probe?.states?.at(-1) || null;
const html = document.documentElement;
const body = document.body;
const active = document.activeElement;
const appearance = state?.operatorSettings ? {
colorTheme: state.operatorSettings.colorTheme,
viewMode: state.operatorSettings.viewMode,
startWorkspace: state.operatorSettings.startWorkspace
} : null;
return {
state: state ? {
revision: state.revision,
isBusy: state.isBusy === true,
playout: structuredClone(state.playout),
operatorSettings: state.operatorSettings ? {
revision: state.operatorSettings.revision,
colorTheme: state.operatorSettings.colorTheme,
viewMode: state.operatorSettings.viewMode,
startWorkspace: state.operatorSettings.startWorkspace,
restartRequired: state.operatorSettings.restartRequired === true,
message: state.operatorSettings.message || "",
messageKind: state.operatorSettings.messageKind || null
} : null
} : null,
appearance,
safety: {
wrapped: probe?.postMessageWrapper != null &&
window.chrome?.webview?.postMessage === probe.postMessageWrapper,
outboundMessages: (probe?.outboundMessages || []).map(value => ({ ...value })),
blockedOutboundMessages: (probe?.blockedOutboundMessages || []).map(value => ({ ...value })),
stateViolations: (probe?.stateViolations || []).map(value => ({
...value,
reasons: [...value.reasons]
})),
inputEvents: (probe?.inputEvents || []).map(value => ({ ...value }))
},
dom: {
url: location.href,
readyState: document.readyState,
bodyBusy: body.getAttribute("aria-busy"),
connectionText: (document.getElementById("playout-connection-state")?.textContent || "").trim(),
settingsVisible: document.getElementById("settings-workspace")?.hidden === false &&
document.getElementById("settings-workspace")?.inert !== true,
settingsCurrent: document.getElementById("workspace-settings-tab")
?.getAttribute("aria-current") || null,
activeWorkspace: document.getElementById("workspace-region")
?.dataset?.activeWorkspace || null,
activeElementId: active?.id || null,
selects: {
colorTheme: document.getElementById("operator-color-theme")?.value ?? null,
viewMode: document.getElementById("operator-view-mode")?.value ?? null,
startWorkspace: document.getElementById("operator-start-workspace")?.value ?? null
},
htmlAppearance: {
colorTheme: html.dataset.colorTheme || null,
viewMode: html.dataset.viewMode || null,
startWorkspace: html.dataset.startWorkspace || null
},
bodyAppearance: {
colorTheme: body.dataset.colorTheme || null,
viewMode: body.dataset.viewMode || null,
startWorkspace: body.dataset.startWorkspace || null
},
computed: {
colorScheme: getComputedStyle(html).colorScheme || null,
bodyBackground: getComputedStyle(body).backgroundColor || null
},
legacyDialogHidden: document.getElementById("legacy-dialog")?.hidden === true,
namedModalHidden: document.getElementById("named-playlist-modal")?.hidden === true,
namedConfirmationHidden:
document.getElementById("named-playlist-confirmation")?.hidden === true
}
};
})()`);
}
function updateSafety(snapshot) {
evidence.safety.observedOutboundTypes =
snapshot?.safety?.outboundMessages?.map(row => row.type) || [];
evidence.safety.blockedOutboundMessages =
snapshot?.safety?.blockedOutboundMessages || [];
evidence.safety.stateViolations = snapshot?.safety?.stateViolations || [];
evidence.safety.playoutIntentIssued = evidence.safety.observedOutboundTypes.some(type =>
["prepare-playout", "take-in", "next-playout", "take-out",
"choose-background", "toggle-background"].includes(type));
}
function assertSafety(snapshot, label, allowBusy = false) {
updateSafety(snapshot);
if (!snapshot?.state?.playout || snapshot.safety.wrapped !== true) {
failUnknown(`The native DryRun state or safety gate is missing at ${label}.`);
}
if (snapshot.safety.blockedOutboundMessages.length > 0) {
failKnown(`An unapproved outbound message was blocked at ${label}.`);
}
if (snapshot.safety.stateViolations.length > 0) {
failUnknown(`DryRun left its safe state at ${label}.`);
}
const playout = snapshot.state.playout;
if (playout.mode !== "dryRun" || playout.phase !== "idle" ||
playout.isConnected === true || playout.preparedCode != null ||
playout.onAirCode != null || playout.outcomeUnknown === true ||
playout.isPlayCompletionPending === true ||
playout.isTakeOutCompletionPending === true || playout.isBusy === true ||
playout.refreshActive === true) {
failUnknown(`Playout is not exact DryRun IDLE at ${label}.`);
}
if (!allowBusy && snapshot.state.isBusy === true) {
failKnown(`The operator UI is busy at ${label}.`);
}
if (snapshot.dom.url !== exactTargetUrl || snapshot.dom.readyState !== "complete" ||
snapshot.dom.connectionText !== "DRY RUN" ||
snapshot.dom.legacyDialogHidden !== true ||
snapshot.dom.namedModalHidden !== true ||
snapshot.dom.namedConfirmationHidden !== true) {
failKnown(`The exact package document is not ready at ${label}.`);
}
}
async function waitFor(label, predicate, { allowBusy = false, allowMissingState = false } = {}) {
if (allowMissingState && label !== "DryRun IDLE preflight") {
failKnown("Only the input-free initial preflight may await the first native state.");
}
const deadline = Math.min(hardDeadline, Date.now() + 45_000);
let last = null;
while (Date.now() < deadline) {
last = await readSnapshot();
if (!last?.state?.playout && allowMissingState) {
if (last?.safety?.wrapped !== true || last?.dom?.url !== exactTargetUrl ||
last?.dom?.readyState !== "complete" || last?.dom?.connectionText !== "DRY RUN") {
failUnknown("The exact DryRun document changed before the first state.");
}
updateSafety(last);
if (evidence.safety.blockedOutboundMessages.length > 0 ||
evidence.safety.stateViolations.length > 0) {
failUnknown("The safety gate observed a violation before initial state.");
}
await sleep(100);
continue;
}
assertSafety(last, label, allowBusy);
if (predicate(last)) return last;
await sleep(100);
}
failUnknown(`Timed out waiting for ${label}; no input was retried.`);
}
function assertAppearanceSnapshot(snapshot, expected, label, requireNeutralLoad = false) {
if (!appearanceEquals(snapshot.appearance, expected) ||
!appearanceEquals(snapshot.dom.selects, expected) ||
!appearanceEquals(snapshot.dom.htmlAppearance, expected) ||
!appearanceEquals(snapshot.dom.bodyAppearance, expected) ||
snapshot.state.operatorSettings?.restartRequired !== false) {
failKnown(`The native and rendered appearance do not agree at ${label}.`);
}
if (requireNeutralLoad && new Set(["warning", "error"])
.has(snapshot.state.operatorSettings?.messageKind)) {
failKnown("The existing operator settings could not be loaded cleanly before input.");
}
}
async function geometryFor(control) {
const value = await evaluate(`(() => {
const element = ${control.expression};
if (!element) return null;
element.scrollIntoView({ block: "nearest", inline: "nearest" });
const rect = element.getBoundingClientRect();
return {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2,
width: rect.width,
height: rect.height,
viewport: {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
},
devicePixelRatio: window.devicePixelRatio,
disabled: element.disabled === true,
hidden: element.hidden === true || element.closest("[hidden]") != null,
optionValues: element instanceof HTMLSelectElement
? [...element.options].map(option => option.value) : null
};
})()`);
if (!value || value.disabled || value.hidden || value.width <= 0 || value.height <= 0 ||
!Number.isFinite(value.x) || !Number.isFinite(value.y) ||
value.x < 0 || value.y < 0 ||
value.x >= value.viewport.width || value.y >= value.viewport.height ||
value.devicePixelRatio < 0.25 || value.devicePixelRatio > 5) {
failKnown(`#${control.id} is not a unique visible physical-input target.`);
}
return value;
}
function exchangePath(sequence, suffix) {
return path.join(configuration.exchangeDirectory,
`${String(sequence).padStart(2, "0")}.${suffix}.json`);
}
function readSmallJson(filePath, label) {
const stat = fs.statSync(filePath);
if (!stat.isFile() || stat.size <= 0 || stat.size > 64 * 1024) {
failKnown(`${label} is empty or too large.`);
}
const bytes = fs.readFileSync(filePath);
let value;
try { value = JSON.parse(bytes.toString("utf8")); }
catch { failKnown(`${label} is not strict JSON.`); }
return { value, bytes, sha256: sha256(bytes) };
}
async function waitForTrustedInput(control, startIndex, targetValue) {
const deadline = Math.min(hardDeadline, Date.now() + 10_000);
while (Date.now() < deadline) {
const events = await evaluate(`(() =>
(globalThis.__legacyAppearanceProbe?.inputEvents || [])
.slice(${Number(startIndex)}).map(value => ({ ...value })))()`);
const relevant = events.filter(event => event.targetId === control.id);
const down = relevant.find(event => event.type === "pointerdown");
const up = relevant.find(event => event.type === "pointerup");
if (down && up) {
if (down.sequence >= up.sequence || down.isTrusted !== true || up.isTrusted !== true ||
down.pointerType !== "mouse" || up.pointerType !== "mouse" ||
down.button !== 0 || up.button !== 0 || down.buttons !== 1 || up.buttons !== 0) {
failKnown(`#${control.id} did not receive one trusted mouse down/up pair.`);
}
if (targetValue === null) return { pointerDown: down, pointerUp: up, change: null };
const change = relevant.find(event =>
event.type === "change" && event.isTrusted === true && event.value === targetValue);
const trustedKeyDowns = relevant.filter(event =>
event.type === "keydown" && event.isTrusted === true);
// A native HTML <select> popup consumes Home/ArrowDown/Enter before the
// page can observe DOM keydown events. The wrapper's sealed one-shot ack
// proves those physical key counts; the page proves the trusted change.
if (change) {
return {
pointerDown: down,
pointerUp: up,
change,
observedTrustedKeyDownCount: trustedKeyDowns.length,
observedKeys: trustedKeyDowns.map(event => event.key)
};
}
}
await sleep(25);
}
failUnknown(`#${control.id} trusted input evidence did not appear; no input was retried.`);
}
async function requestPhysicalInput({
operation,
control,
appearanceKey = null,
targetValue = null,
expectedBefore,
expectedAfter
}) {
const sequence = nextExchangeSequence++;
const token = randomBytes(16).toString("hex").toUpperCase();
const geometry = await geometryFor(control);
let optionIndex = null;
if (appearanceKey) {
if (!Array.isArray(geometry.optionValues) ||
JSON.stringify(geometry.optionValues) !==
JSON.stringify(appearanceValues[appearanceKey])) {
failKnown(`#${control.id} option order is outside the closed contract.`);
}
optionIndex = geometry.optionValues.indexOf(targetValue);
if (optionIndex < 0) failKnown(`#${control.id} lacks the requested option.`);
}
const eventStart = await evaluate(
"globalThis.__legacyAppearanceProbe?.inputEvents?.length ?? null");
const outboundStart = await evaluate(
"globalThis.__legacyAppearanceProbe?.outboundMessages?.length ?? null");
if (!Number.isInteger(eventStart) || !Number.isInteger(outboundStart)) {
failUnknown("The trusted input/outbound evidence baseline is unavailable.");
}
const request = {
schemaVersion: 1,
phase: configuration.phase,
sequence,
token,
targetUrl: exactTargetUrl,
operation,
targetId: control.id,
appearanceKey,
targetValue,
optionIndex,
expectedBefore,
expectedAfter,
point: { x: geometry.x, y: geometry.y },
viewport: geometry.viewport,
devicePixelRatio: geometry.devicePixelRatio,
mouseDownCalls: 1,
mouseUpCalls: 1,
homeKeyCalls: appearanceKey ? 1 : 0,
arrowDownKeyCalls: appearanceKey ? optionIndex : 0,
enterKeyCalls: appearanceKey ? 1 : 0,
inputRetryCount: 0
};
const requestPath = exchangePath(sequence, "request");
const acknowledgementPath = exchangePath(sequence, "ack");
const requestBytes = Buffer.from(`${JSON.stringify(request)}\n`, "utf8");
fs.writeFileSync(requestPath, requestBytes, { flag: "wx" });
const deadline = Math.min(hardDeadline, Date.now() + 45_000);
while (!fs.existsSync(acknowledgementPath)) {
if (Date.now() >= deadline) {
failUnknown(`Physical input exchange ${sequence} timed out; no input was retried.`);
}
await sleep(50);
}
const acknowledgement = readSmallJson(
acknowledgementPath,
`Physical input acknowledgement ${sequence}`);
const ack = acknowledgement.value;
if (!ack || ack.schemaVersion !== 1 || ack.phase !== configuration.phase ||
ack.sequence !== sequence || ack.token !== token || ack.result !== "PASS" ||
ack.operation !== operation || ack.targetId !== control.id ||
ack.appearanceKey !== appearanceKey || ack.targetValue !== targetValue ||
ack.optionIndex !== optionIndex || ack.packageIdentityValidated !== true ||
ack.dryRunValidated !== true || ack.tornadoConnectionCount !== 0 ||
ack.inputRetryCount !== 0 || ack.mouseDownCalls !== 1 || ack.mouseUpCalls !== 1 ||
ack.homeKeyCalls !== request.homeKeyCalls ||
ack.arrowDownKeyCalls !== request.arrowDownKeyCalls ||
ack.enterKeyCalls !== request.enterKeyCalls ||
!appearanceEquals(ack.settings?.appearance, expectedAfter)) {
failKnown(`Physical input acknowledgement ${sequence} is outside its one-shot contract.`);
}
const physicalInput = await waitForTrustedInput(
control,
eventStart,
targetValue);
if (appearanceKey) {
const current = await waitFor(`#${control.id} native save`, snapshot =>
appearanceEquals(snapshot.appearance, expectedAfter) &&
appearanceEquals(snapshot.dom.selects, expectedAfter) &&
appearanceEquals(snapshot.dom.htmlAppearance, expectedAfter) &&
appearanceEquals(snapshot.dom.bodyAppearance, expectedAfter) &&
snapshot.state.isBusy !== true,
{ allowBusy: true });
const outbound = current.safety.outboundMessages.slice(outboundStart);
if (outbound.length !== 1 || outbound[0].type !== "set-operator-appearance" ||
!appearanceEquals(outbound[0].appearance, expectedAfter)) {
failKnown(`#${control.id} did not issue exactly one closed appearance intent.`);
}
evidence.selections.push({
appearanceKey,
targetValue,
expectedAfter,
trustedChange: true,
physicalKeyCounts: {
home: ack.homeKeyCalls,
arrowDown: ack.arrowDownKeyCalls,
enter: ack.enterKeyCalls,
total: ack.homeKeyCalls + ack.arrowDownKeyCalls + ack.enterKeyCalls
},
observedDomKeyDownCount: physicalInput.observedTrustedKeyDownCount,
outboundIntentCount: 1,
nativeRevision: current.state.operatorSettings.revision,
restartRequired: current.state.operatorSettings.restartRequired
});
}
evidence.exchanges.push({
sequence,
operation,
targetId: control.id,
appearanceKey,
targetValue,
optionIndex,
requestPath,
requestSha256: sha256(requestBytes),
acknowledgementPath,
acknowledgementSha256: acknowledgement.sha256,
physicalKeyCounts: {
home: ack.homeKeyCalls,
arrowDown: ack.arrowDownKeyCalls,
enter: ack.enterKeyCalls
},
physicalInput,
settings: ack.settings
});
}
async function checkpoint(name, details = null) {
const snapshot = await readSnapshot();
assertSafety(snapshot, name, false);
evidence.checkpoints.push({ name, details, snapshot });
return snapshot;
}
async function captureScreenshot() {
const response = await rpc("Page.captureScreenshot", {
format: "png",
fromSurface: true,
captureBeyondViewport: false
}, 20_000);
if (!response?.data) failKnown("CDP did not return screenshot data.");
const bytes = Buffer.from(response.data, "base64");
fs.writeFileSync(configuration.screenshotPath, bytes, { flag: "wx" });
evidence.screenshot = {
path: configuration.screenshotPath,
bytes: bytes.length,
sha256: sha256(bytes)
};
}
async function runSequence() {
await rpc("Runtime.enable");
await rpc("Page.enable");
await installProbe();
let current = await waitFor("DryRun IDLE preflight", snapshot =>
snapshot.state?.playout?.mode === "dryRun" &&
snapshot.state.isBusy !== true &&
snapshot.state.operatorSettings != null,
{ allowMissingState: true });
assertAppearanceSnapshot(
current,
configuration.expected,
"input-free initial appearance",
true);
evidence.persistenceVerifiedBeforeInput = configuration.phase === "verify-restore";
if (configuration.phase === "verify-restore") {
const expectedStartupWorkspace = configuration.expected.startWorkspace === "lastWorkspace"
? "settings"
: "stock";
current = await waitFor("persisted start workspace behavior", snapshot =>
appearanceEquals(snapshot.appearance, configuration.expected) &&
snapshot.dom.activeWorkspace === expectedStartupWorkspace,
{ allowBusy: false });
evidence.startWorkspaceBehaviorVerifiedBeforeInput = true;
}
evidence.checkpoints.push({
name: configuration.phase === "verify-restore"
? "persisted-appearance-after-relaunch"
: "baseline-appearance-before-mutation",
expectedStartupWorkspace: configuration.phase === "verify-restore"
? (configuration.expected.startWorkspace === "lastWorkspace" ? "settings" : "stock")
: null,
snapshot: current
});
await requestPhysicalInput({
operation: "application-click",
control: controls.settings,
expectedBefore: configuration.expected,
expectedAfter: configuration.expected
});
current = await waitFor("settings workspace", snapshot =>
snapshot.dom.settingsVisible === true &&
snapshot.dom.settingsCurrent === "page" &&
snapshot.dom.activeWorkspace === "settings",
{ allowBusy: false });
assertAppearanceSnapshot(current, configuration.expected, "settings workspace preflight");
let before = configuration.expected;
for (const appearanceKey of ["colorTheme", "viewMode", "startWorkspace"]) {
const after = Object.freeze({
...before,
[appearanceKey]: configuration.final[appearanceKey]
});
await requestPhysicalInput({
operation: "select-appearance-option",
control: controls[appearanceKey],
appearanceKey,
targetValue: configuration.final[appearanceKey],
expectedBefore: before,
expectedAfter: after
});
before = after;
}
current = await checkpoint(
configuration.phase === "verify-restore"
? "baseline-restored-by-physical-input"
: "alternate-appearance-persisted",
{ appearance: configuration.final });
assertAppearanceSnapshot(current, configuration.final, "final appearance");
if (evidence.exchanges.length !== 4 || evidence.selections.length !== 3 ||
nextExchangeSequence !== 5 ||
evidence.selections.some(selection => selection.trustedChange !== true ||
selection.physicalKeyCounts.home !== 1 ||
selection.physicalKeyCounts.enter !== 1 ||
selection.physicalKeyCounts.total !==
2 + selection.physicalKeyCounts.arrowDown ||
selection.outboundIntentCount !== 1 || selection.restartRequired !== false)) {
failKnown("The closed four-step appearance input plan did not complete exactly once.");
}
await captureScreenshot();
evidence.finalSnapshot = current;
evidence.result = "PASS";
evidence.completedAt = new Date().toISOString();
}
async function writeEvidence() {
const bytes = Buffer.from(`${JSON.stringify(evidence, null, 2)}\n`, "utf8");
fs.writeFileSync(configuration.outputPath, bytes, { flag: "wx" });
}
async function main() {
let exitCode = 0;
try {
const target = await discoverTarget();
evidence.target.id = target.id;
evidence.target.url = target.url;
await connect(validateEndpoint(target));
await runSequence();
} catch (error) {
exitCode = 1;
evidence.result = "FAIL";
evidence.completedAt = new Date().toISOString();
evidence.error = {
category: error instanceof AppearanceFailure ? error.category : "OUTCOME_UNKNOWN",
message: error instanceof AppearanceFailure
? error.message
: "OUTCOME_UNKNOWN: Unexpected appearance harness failure."
};
} finally {
try {
await removeProbe();
} catch (cleanupError) {
exitCode = 1;
evidence.result = "FAIL";
evidence.completedAt = new Date().toISOString();
evidence.error = {
category: "OUTCOME_UNKNOWN",
message: cleanupError instanceof Error
? cleanupError.message
: "OUTCOME_UNKNOWN: Appearance probe cleanup failed."
};
}
if (socket && socket.readyState === WebSocket.OPEN) socket.close();
if (!fs.existsSync(configuration.outputPath)) {
try { await writeEvidence(); }
catch { exitCode = 1; }
}
}
process.exitCode = exitCode;
}
await main();