功能修改

This commit is contained in:
npc0-hue
2026-07-20 16:42:33 +08:00
parent 48b8ad8d6c
commit a0e69417db
224 changed files with 22015 additions and 884 deletions
+350 -33
View File
@@ -17,7 +17,12 @@ const forbiddenFragments = [
{ name: "tcp socket", pattern: /tcp:\/\// },
{ name: "bearer token", pattern: /Bearer\s+/ },
{ name: "raw api key", pattern: /sk-[A-Za-z0-9_-]+/ },
{ name: "password query", pattern: /password=/ },
{ name: "password query", pattern: /password\s*=/i },
{ name: "credential value", pattern: /credential\s*[:=]\s*\S+/i },
{ name: "DSN value", pattern: /\b(?:postgres(?:ql)?|mysql|redis|mongodb(?:\+srv)?):\/\//i },
{ name: "RCON value", pattern: /\brcon(?:[_-]?(?:password|token))?\s*[:=]\s*\S+/i },
{ name: "host path", pattern: /\/(?:home|var|etc|opt|root|tmp)\/|[A-Za-z]:\\/ },
{ name: "PID value", pattern: /\bpid\s*[:=#]?\s*\d+/i },
{ name: "raw api key ref field", pattern: /apiKeyRef/ },
{ name: "raw api key field", pattern: /rawApiKey/ },
{ name: "session token", pattern: /sessionToken|run session token/i },
@@ -67,8 +72,17 @@ async function main() {
assertIncludes(plugin.requiredRunCapabilities, "process.start", "plugin requires process.start");
assertIncludes(plugin.bridgeActions, "logs.query", "plugin exposes logs.query bridge action");
assertIncludes(plugin.bridgeActions, "artifacts.open", "plugin exposes artifacts.open bridge action");
assertIncludes(plugin.bridgeActions, "plugin-lifecycle.request", "plugin exposes Platform lifecycle bridge action");
assertIncludes(plugin.bridgeActions, "ai.invoke", "plugin exposes Platform AI bridge action");
assertIncludes(plugin.productionLifecycle?.operations, "rollback", "plugin declares rollback governance");
assertIncludes(marketplacePlugin.capabilities, "process.start", "marketplace exposes lifecycle capability");
assertSafeRedactedRef(aiProvider.apiKeyRef, "AI provider key reference");
assertEqual(aiProvider.apiKeyConfigured, true, "AI provider key presence projection");
assertEqual(aiProvider.baseUrlConfigured, true, "AI provider base URL presence projection");
if (Object.hasOwn(aiProvider, "baseUrl") || Object.hasOwn(aiProvider, "apiKeyRef")) {
throw new Error("AI provider response exposed Platform-owned endpoint or secret reference");
}
const productionSeed = await prepareProductionOperations(authHeaders, server, plugin);
const chrome = await startChrome();
const evidence = {
@@ -90,11 +104,12 @@ async function main() {
plugin: pick(plugin, ["id", "name", "version", "status", "manifestRef", "requiredRunCapabilities", "bridgeActions", "declaredPermissions"]),
marketplacePlugin: pick(marketplacePlugin, ["id", "name", "version", "status", "manifestRef", "capabilities", "bridgeActions", "declaredPermissions"]),
operator: pick(operator, ["id", "displayName", "email", "status", "roles"]),
aiProvider: pick(aiProvider, ["id", "name", "kind", "relayMode", "status", "apiKeyRef"]),
aiProvider: pick(aiProvider, ["id", "name", "kind", "relayMode", "status", "baseUrlConfigured", "apiKeyConfigured"]),
jobs: jobs.items.map((job) => pick(job, ["id", "serverInstanceId", "runEndpointId", "capability", "state", "resultRef"])),
logStreams: logStreams.items.map((stream) => pick(stream, ["id", "serverInstanceId", "streamKey", "source"])),
artifacts: artifacts.items.map((artifact) => pick(artifact, ["id", "ownerKind", "ownerId", "state", "checksum"])),
usage: pick(usage, ["cpuPercent", "memoryPercent", "diskPercent", "source"])
usage: pick(usage, ["cpuPercent", "memoryPercent", "diskPercent", "source"]),
production: productionSeed.apiProof
},
routes: [],
safety: {
@@ -112,7 +127,7 @@ async function main() {
{
name: "首页",
hash: "#/home",
markers: ["平台概览", "数据已加载", "game.example", "运行节点", "CPU"]
markers: ["平台概览", "运营数据已同步", "game.example", "运行节点", "CPU", "生产容量与告警", "运行槽位"]
},
{
name: "服务器管理",
@@ -145,7 +160,12 @@ async function main() {
{
name: "AI 提供商管理",
hash: "#/aiProviders",
markers: ["AI 提供商管理", "平台 API", aiProvider.name, aiProvider.apiKeyRef, "密钥引用"]
markers: ["AI 提供商管理", "平台 API", aiProvider.name, "密钥状态", "已配置", "AI 配置审查", productionSeed.diff.diffSummary, server.id]
},
{
name: "系统维护",
hash: "#/maintenance",
markers: ["系统维护", "容量治理与告警闭环", "运行槽位", productionSeed.alert.title]
},
{
name: "服务器详情",
@@ -181,9 +201,15 @@ async function main() {
}
}
const pluginControls = await clickAndVerify(chrome, "插件控制", ["Logs 桥接执行", "server.logs.read", "server.artifacts.read", "读取"]);
const pluginControls = await clickAndVerify(chrome, "插件控制", ["生产生命周期", "Logs 桥接执行", "server.logs.read", "server.artifacts.read", "读取"]);
evidence.routes.push({ name: "服务器详情 / 插件控制", url: await chrome.url(), ...pluginControls });
evidence.productionInteractions = {
alert: await verifyAlertInteraction(chrome, authHeaders, productionSeed.alert),
pluginLifecycle: await verifyPluginLifecycleInteraction(chrome, authHeaders, plugin, server),
aiDiffApproval: await verifyAIConfigDiffInteraction(chrome, authHeaders, productionSeed.diff)
};
evidence.walkthroughs = await verifyResponsiveThemeWalkthroughs(chrome, routeChecks, server);
const operationProof = await verifyLifecycleOperation(authHeaders, server, chrome);
@@ -229,12 +255,12 @@ async function loginInBrowser(chrome) {
setInputValue(password, "operator-local");
submit.click();
});
await chrome.waitForText(["平台概览", "数据已加载"], "post-login home");
await chrome.waitForText(["平台概览", "运营数据已同步"], "post-login home");
const visibleText = await chrome.visibleText();
scanText(visibleText, "登录后首页");
return {
url: await chrome.url(),
requiredMarkers: ["平台概览", "数据已加载"],
requiredMarkers: ["平台概览", "运营数据已同步"],
fallbackScan: "passed",
forbiddenFragmentScan: "passed"
};
@@ -294,9 +320,21 @@ async function verifyResponsiveThemeWalkthroughs(chrome, routeChecks, server) {
for (const scenario of scenarios) {
await chrome.setViewport(scenario.viewport);
await applyBrowserTheme(chrome, scenario.paletteId, scenario.backgroundId);
const appliedPalette = await chrome.evaluate(() => document.documentElement.dataset.themePalette || "");
assertEqual(appliedPalette, scenario.paletteId, `${scenario.name} root theme marker`);
const routeEvidence = [];
if (scenario.viewport.mobile) {
const mobileNavigation = await verifyMobileNavigation(chrome, scenario.name, scenario.themeMarkers);
routeEvidence.push({
name: "移动导航抽屉",
url: await chrome.url(),
...mobileNavigation,
layout: await chrome.layoutSnapshot()
});
}
for (const route of routeChecks) {
const state = await verifyBrowserRoute(chrome, route.hash, [...route.markers, ...scenario.themeMarkers], `${scenario.name} / ${route.name}`);
const routeThemeMarkers = scenario.viewport.mobile ? [] : scenario.themeMarkers;
const state = await verifyBrowserRoute(chrome, route.hash, [...route.markers, ...routeThemeMarkers], `${scenario.name} / ${route.name}`);
const layout = await chrome.layoutSnapshot();
assertNoVisibleLayoutIssues(layout, `${scenario.name} / ${route.name}`);
routeEvidence.push({
@@ -328,7 +366,7 @@ async function verifyResponsiveThemeWalkthroughs(chrome, routeChecks, server) {
window.location.hash = "#/servers/server-local-debug";
});
await chrome.waitForText([server.name, "插件控制"], `${scenario.name} / server detail tabs`);
const pluginControls = await clickAndVerify(chrome, "插件控制", ["Logs 桥接执行", "server.logs.read", "server.artifacts.read", "读取"]);
const pluginControls = await clickAndVerify(chrome, "插件控制", ["生产生命周期", "Logs 桥接执行", "server.logs.read", "server.artifacts.read", "读取"]);
const pluginLayout = await chrome.layoutSnapshot();
assertNoVisibleLayoutIssues(pluginLayout, `${scenario.name} / plugin controls`);
routeEvidence.push({
@@ -402,15 +440,11 @@ async function clickAndVerify(chrome, buttonText, markers) {
async function verifyServerQuickRuntimeMenu(chrome, label) {
const markers = ["生成 run", "下载 run", "推送更新", "生成客户端", "依赖检查", "依赖安装", "实时日志", "历史日志"];
await chrome.evaluate(() => {
const summary = Array.from(document.querySelectorAll("summary")).find((item) => item.textContent?.includes("运行操作"));
if (!(summary instanceof HTMLElement)) {
throw new Error("runtime action menu summary not found");
const trigger = Array.from(document.querySelectorAll("button")).find((item) => item.textContent?.includes("运行操作"));
if (!(trigger instanceof HTMLButtonElement)) {
throw new Error("runtime action menu trigger not found");
}
const details = summary.closest("details");
if (!(details instanceof HTMLDetailsElement)) {
throw new Error("runtime action menu container not found");
}
details.open = true;
trigger.click();
});
await chrome.waitForText(markers, `${label} runtime action menu`);
const visibleText = await chrome.visibleText();
@@ -424,6 +458,63 @@ async function verifyServerQuickRuntimeMenu(chrome, label) {
};
}
async function verifyMobileNavigation(chrome, label, themeMarkers) {
const menuLabels = ["平台概览", "服务器管理", "插件市场", "用户管理", "AI 提供商", "系统工具"];
const closed = await chrome.evaluate(() => {
const handle = document.querySelector(".mobile-sidebar-handle");
const sidebar = document.querySelector("#primary-sidebar");
return {
handle: handle instanceof HTMLButtonElement,
expanded: handle?.getAttribute("aria-expanded") === "true",
sidebarOpen: sidebar?.classList.contains("app-sidebar-mobile-open") ?? false,
mainTop: document.querySelector("main")?.getBoundingClientRect().top ?? null
};
});
if (!closed.handle || closed.expanded || closed.sidebarOpen || closed.mainTop !== 0) {
throw new Error(`${label} mobile drawer was not closed and out of page flow: ${JSON.stringify(closed)}`);
}
await chrome.evaluate(() => {
const handle = document.querySelector(".mobile-sidebar-handle");
if (!(handle instanceof HTMLButtonElement)) throw new Error("mobile drawer handle not found");
handle.click();
});
await chrome.waitForText([...menuLabels, ...themeMarkers], `${label} mobile drawer open`);
const opened = await chrome.evaluate(() => ({
expanded: document.querySelector(".mobile-sidebar-handle")?.getAttribute("aria-expanded") === "true",
sidebarOpen: document.querySelector("#primary-sidebar")?.classList.contains("app-sidebar-mobile-open") ?? false,
width: document.querySelector("#primary-sidebar")?.getBoundingClientRect().width ?? 0
}));
if (!opened.expanded || !opened.sidebarOpen || opened.width <= 0 || opened.width > 320) {
throw new Error(`${label} mobile drawer did not open as a bounded text sidebar: ${JSON.stringify(opened)}`);
}
const visibleText = await chrome.visibleText();
assertMarkers(visibleText, [...menuLabels, ...themeMarkers], `${label} mobile drawer`);
scanText(visibleText, `${label} mobile drawer`);
await chrome.evaluate(() => {
const backdrop = document.querySelector(".mobile-sidebar-backdrop");
if (!(backdrop instanceof HTMLButtonElement)) throw new Error("mobile drawer backdrop not found");
backdrop.click();
});
await delay(220);
const closedAgain = await chrome.evaluate(() => {
const backdrop = document.querySelector(".mobile-sidebar-backdrop");
const style = backdrop ? window.getComputedStyle(backdrop) : null;
return document.querySelector(".mobile-sidebar-handle")?.getAttribute("aria-expanded") !== "true"
&& style?.visibility === "hidden"
&& style?.pointerEvents === "none";
});
if (!closedAgain) throw new Error(`${label} mobile drawer did not close from backdrop`);
return {
requiredMarkers: [...menuLabels, ...themeMarkers],
fallbackScan: "passed",
forbiddenFragmentScan: "passed",
closed,
opened,
closedAgain,
textSample: visibleText.slice(0, 1200)
};
}
async function ensureAiProvider(headers) {
const providers = await getJson("/ai-providers", headers);
if (providers.items.some((item) => item.id === "ai.openai")) {
@@ -447,6 +538,218 @@ async function ensureAiProvider(headers) {
);
}
async function prepareProductionOperations(headers, server, plugin) {
const stamp = Date.now();
const lifecycle = await postJson(
`/plugin-lifecycles/${encodeURIComponent(plugin.id)}/actions`,
{
serverInstanceId: server.id,
operation: "install",
targetVersion: plugin.version,
idempotencyKey: `browser-acceptance-plugin-install-${stamp}`,
confirmed: false
},
headers
);
if (lifecycle.status !== "queued" || !lifecycle.job?.id || !lifecycle.installation?.id) {
throw new Error(`plugin lifecycle seed did not create one durable job: ${JSON.stringify(lifecycle)}`);
}
const aiInvocation = await postJson(
"/ai/invocations",
{
requestId: `browser-acceptance-ai-config-${stamp}`,
serverInstanceId: server.id,
purpose: "config.suggest",
prompt: "Keep existing settings and add a reviewed max players recommendation."
},
headers
);
if (aiInvocation.status !== "ok" || !aiInvocation.configRecommendation?.diffId) {
throw new Error(`AI invocation did not persist a reviewable diff: ${JSON.stringify(aiInvocation)}`);
}
const admission = await postJson(
"/production/capacity/admission",
{
serverInstanceId: server.id,
capability: "process.restart",
idempotencyKey: `browser-acceptance-capacity-gap-${stamp}`
},
headers
);
if (admission.accepted || admission.state !== "denied" || !admission.alertId || !admission.auditEventId) {
throw new Error(`capacity admission did not create durable denied evidence: ${JSON.stringify(admission)}`);
}
const [capacity, alerts, lifecycles, diffs] = await Promise.all([
getJson("/production/capacity", headers),
getJson("/alerts", headers),
getJson(`/plugin-lifecycles?pluginId=${encodeURIComponent(plugin.id)}&serverInstanceId=${encodeURIComponent(server.id)}`, headers),
getJson(`/ai/config-diffs?serverInstanceId=${encodeURIComponent(server.id)}`, headers)
]);
const alert = findRequired(alerts.items, (item) => item.id === admission.alertId && item.state === "active", "active capacity alert");
const installation = findRequired(lifecycles.items, (item) => item.id === lifecycle.installation.id && item.jobId === lifecycle.job.id, "durable plugin lifecycle installation");
const diff = findRequired(diffs.items, (item) => item.id === aiInvocation.configRecommendation.diffId && item.state === "pending", "pending AI config diff");
if (capacity.activeAlerts < 1 || !capacity.endpoints.some((item) => item.runEndpointId === server.runEndpointId)) {
throw new Error(`production capacity summary did not include seeded state: ${JSON.stringify(capacity)}`);
}
for (const [label, value] of Object.entries({ admission, capacity, alert, installation, diff, aiInvocation })) {
assertNoForbiddenProjection(value, `production seed ${label}`);
}
return {
alert,
diff,
apiProof: {
admission: pick(admission, ["accepted", "state", "reason", "pressureCodes", "alertId", "auditEventId"]),
capacity: {
...pick(capacity, ["totalMaxJobs", "totalRunningJobs", "totalQueuedJobs", "activeAlerts", "generatedAt"]),
endpoints: capacity.endpoints.map((item) => pick(item, ["runEndpointId", "status", "maxJobs", "runningJobs", "queuedJobs", "logBacklogBatches", "artifactBacklogChunks", "pressureCodes"]))
},
alert: pick(alert, ["id", "sourceKind", "sourceId", "ruleKey", "severity", "state", "occurrenceCount", "lastAuditEventId"]),
pluginLifecycle: pick(installation, ["id", "pluginId", "serverInstanceId", "currentVersion", "targetVersion", "desiredState", "currentState", "lastOperation", "compatibility", "dependencyState", "jobId", "auditEventId"]),
aiConfigDiff: pick(diff, ["id", "requestId", "serverInstanceId", "pluginId", "providerId", "model", "key", "configVersion", "diffSummary", "state", "expiresAt"])
}
};
}
async function verifyAlertInteraction(chrome, headers, seededAlert) {
await chrome.navigate(`${webUrl}/#/home`);
await chrome.waitForText(["生产容量与告警", seededAlert.title, "确认"], "production alert interaction");
await chrome.evaluate((title) => {
const item = Array.from(document.querySelectorAll(".production-alert-list .operation-item")).find((candidate) => candidate.textContent?.includes(title));
const button = Array.from(item?.querySelectorAll("button") || []).find((candidate) => candidate.textContent?.trim() === "确认");
if (!(button instanceof HTMLButtonElement)) throw new Error("capacity alert acknowledge button not found");
button.click();
}, seededAlert.title);
await chrome.waitForText(["确认告警", seededAlert.id, "取消"], "alert confirmation dialog");
await chrome.evaluate(() => {
const cancel = document.querySelector(".confirm-panel .confirm-actions button");
if (!(cancel instanceof HTMLButtonElement)) throw new Error("alert confirmation cancel button not found");
cancel.click();
});
await delay(100);
if (await chrome.evaluate(() => Boolean(document.querySelector(".confirm-panel")))) {
throw new Error("alert confirmation dialog did not close after cancel");
}
const afterCancel = await getJson("/alerts", headers);
const stillActive = findRequired(afterCancel.items, (item) => item.id === seededAlert.id, "alert after confirmation cancel");
assertEqual(stillActive.state, "active", "cancel keeps durable alert active");
await chrome.evaluate((title) => {
const item = Array.from(document.querySelectorAll(".production-alert-list .operation-item")).find((candidate) => candidate.textContent?.includes(title));
const button = Array.from(item?.querySelectorAll("button") || []).find((candidate) => candidate.textContent?.trim() === "确认");
if (!(button instanceof HTMLButtonElement)) throw new Error("capacity alert acknowledge button not found after cancel");
button.click();
}, seededAlert.title);
await chrome.waitForText(["确认告警", seededAlert.id], "alert confirmation reopen");
await chrome.evaluate(() => {
const confirm = document.querySelector(".confirm-panel .confirm-primary");
if (!(confirm instanceof HTMLButtonElement)) throw new Error("alert confirmation submit button not found");
confirm.click();
});
await chrome.waitForText(["确认已由 Platform 持久化", "acknowledged"], "durable alert acknowledgement");
const alerts = await getJson("/alerts", headers);
const acknowledged = findRequired(alerts.items, (item) => item.id === seededAlert.id, "acknowledged capacity alert");
assertEqual(acknowledged.state, "acknowledged", "browser alert acknowledgement persisted");
assertNoForbiddenProjection(acknowledged, "acknowledged alert response");
return {
cancelPreservedState: stillActive.state,
persisted: pick(acknowledged, ["id", "state", "acknowledgedBy", "acknowledgedAt", "lastAuditEventId"]),
forbiddenFragmentScan: "passed",
textSample: (await chrome.visibleText()).slice(0, 1200)
};
}
async function verifyPluginLifecycleInteraction(chrome, headers, plugin, server) {
await chrome.navigate(`${webUrl}/#/plugins`);
await chrome.waitForText(["插件市场", plugin.id, "查看详情"], "plugin lifecycle marketplace");
await chrome.evaluate((pluginId) => {
const card = Array.from(document.querySelectorAll(".catalog-card")).find((candidate) => candidate.textContent?.includes(pluginId));
const button = card?.querySelector("button[title='查看插件详情']");
if (!(button instanceof HTMLButtonElement)) throw new Error("plugin detail button not found");
button.click();
}, plugin.id);
await chrome.waitForText([plugin.name, "生产生命周期", server.id, "compatible"], "plugin lifecycle detail");
await chrome.evaluate(() => {
const select = document.querySelector("select[aria-label='生命周期操作']");
if (!(select instanceof HTMLSelectElement)) throw new Error("plugin lifecycle operation select not found");
const setter = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, "value")?.set;
setter.call(select, "enable");
select.dispatchEvent(new Event("change", { bubbles: true }));
});
await chrome.evaluate(() => {
const button = document.querySelector(".plugin-lifecycle-controls .primary-command");
if (!(button instanceof HTMLButtonElement)) throw new Error("plugin lifecycle execute button not found");
button.click();
});
await chrome.waitForText(["确认启用", plugin.name, server.id], "plugin lifecycle confirmation");
await chrome.evaluate(() => {
const confirm = Array.from(document.querySelectorAll(".confirm-panel .confirm-actions button")).find((item) => item.textContent?.trim() === "启用");
if (!(confirm instanceof HTMLButtonElement)) throw new Error("plugin lifecycle confirmation submit not found");
confirm.click();
});
await chrome.waitForText(["启用:queued", "任务"], "plugin lifecycle durable dispatch");
const response = await getJson(`/plugin-lifecycles?pluginId=${encodeURIComponent(plugin.id)}&serverInstanceId=${encodeURIComponent(server.id)}`, headers);
const installation = findRequired(response.items, (item) => item.pluginId === plugin.id && item.serverInstanceId === server.id, "plugin lifecycle after browser dispatch");
assertEqual(installation.lastOperation, "enable", "browser lifecycle operation persisted");
if (!installation.jobId || !installation.auditEventId) {
throw new Error(`browser lifecycle dispatch missed job/audit linkage: ${JSON.stringify(installation)}`);
}
assertNoForbiddenProjection(installation, "browser plugin lifecycle response");
return {
persisted: pick(installation, ["id", "pluginId", "serverInstanceId", "currentState", "desiredState", "lastOperation", "dependencyState", "jobId", "auditEventId", "alertId"]),
forbiddenFragmentScan: "passed",
textSample: (await chrome.visibleText()).slice(0, 1200)
};
}
async function verifyAIConfigDiffInteraction(chrome, headers, seededDiff) {
await chrome.navigate(`${webUrl}/#/aiProviders`);
await chrome.waitForText(["AI 配置审查", seededDiff.serverInstanceId, seededDiff.diffSummary, "审查并批准"], "AI config diff review");
await chrome.evaluate(() => {
const button = Array.from(document.querySelectorAll(".ai-diff-review-panel button")).find((item) => item.textContent?.includes("审查并批准"));
if (!(button instanceof HTMLButtonElement)) throw new Error("AI diff review button not found");
button.click();
});
await chrome.waitForText(["批准 AI 配置差异", seededDiff.id, "取消"], "AI diff approval confirmation");
await chrome.evaluate(() => {
const cancel = document.querySelector(".confirm-panel .confirm-actions button");
if (!(cancel instanceof HTMLButtonElement)) throw new Error("AI diff approval cancel button not found");
cancel.click();
});
const pendingResponse = await getJson(`/ai/config-diffs?serverInstanceId=${encodeURIComponent(seededDiff.serverInstanceId)}`, headers);
const pending = findRequired(pendingResponse.items, (item) => item.id === seededDiff.id, "AI diff after approval cancel");
assertEqual(pending.state, "pending", "cancel keeps AI diff pending");
await chrome.evaluate(() => {
const button = Array.from(document.querySelectorAll(".ai-diff-review-panel button")).find((item) => item.textContent?.includes("审查并批准"));
if (!(button instanceof HTMLButtonElement)) throw new Error("AI diff review button not found after cancel");
button.click();
});
await chrome.waitForText(["批准 AI 配置差异", seededDiff.id], "AI diff approval confirmation reopen");
await chrome.evaluate(() => {
const confirm = document.querySelector(".confirm-panel .confirm-primary");
if (!(confirm instanceof HTMLButtonElement)) throw new Error("AI diff approval submit button not found");
confirm.click();
});
await chrome.waitForText(["已审批", "写入任务"], "AI diff durable approval");
const approvedResponse = await getJson(`/ai/config-diffs?serverInstanceId=${encodeURIComponent(seededDiff.serverInstanceId)}`, headers);
const approved = findRequired(approvedResponse.items, (item) => item.id === seededDiff.id, "approved AI config diff");
assertEqual(approved.state, "approved", "browser AI diff approval persisted");
if (!approved.jobId || !approved.approvedBy || !approved.approvedAt) {
throw new Error(`approved AI diff missed durable approval linkage: ${JSON.stringify(approved)}`);
}
assertNoForbiddenProjection(approved, "approved AI diff response");
return {
cancelPreservedState: pending.state,
persisted: pick(approved, ["id", "serverInstanceId", "state", "approvedBy", "approvedAt", "jobId", "configVersion", "currentConfigChecksum"]),
forbiddenFragmentScan: "passed",
textSample: (await chrome.visibleText()).slice(0, 1200)
};
}
async function verifyFrontendEnvironment() {
const [html, packageJsonText, viteConfigText] = await Promise.all([
fetchText(webUrl),
@@ -470,34 +773,42 @@ async function verifyFrontendEnvironment() {
}
async function verifyLifecycleOperation(headers, server, chrome) {
const request = { expectedConfigVersion: server.configVersion, idempotencyKey: `browser-acceptance-start-${Date.now()}` };
const result = await postJson(`/server-instances/${encodeURIComponent(server.id)}/start`, request, headers);
const currentServer = await getJson(`/server-instances/${encodeURIComponent(server.id)}`, headers);
if (currentServer.configVersion <= server.configVersion) {
throw new Error(`AI config write did not advance the durable server config version: before=${server.configVersion} after=${currentServer.configVersion}`);
}
const action = currentServer.state === "running" ? "stop" : "start";
const expectedCapability = action === "stop" ? "process.stop" : "process.start";
const request = { expectedConfigVersion: currentServer.configVersion, idempotencyKey: `browser-acceptance-${action}-${Date.now()}` };
const result = await postJson(`/server-instances/${encodeURIComponent(currentServer.id)}/${action}`, request, headers);
if (!result.accepted || !result.job?.id) {
throw new Error("lifecycle start operation did not return accepted job evidence");
}
if (result.job.capability !== "process.start") {
if (result.job.capability !== expectedCapability) {
throw new Error(`lifecycle job used unexpected capability ${result.job.capability}`);
}
if (result.job.runEndpointId !== "run-local-debug") {
throw new Error(`lifecycle job used unexpected run endpoint ${result.job.runEndpointId}`);
}
const job = await waitForJob(headers, server.id, result.job.id);
const job = await waitForJob(headers, currentServer.id, result.job.id);
await chrome.navigate(`${webUrl}/#/servers/server-local-debug`);
await chrome.waitForText([server.name, "操作历史"], "server detail after lifecycle operation");
await chrome.waitForText([currentServer.name, "操作历史"], "server detail after lifecycle operation");
const historyState = await clickAndVerify(chrome, "操作历史", ["操作历史", "平台任务记录", "server-lifecycle", "process."]);
return {
action: "start",
action,
accepted: result.accepted,
request: {
expectedConfigVersion: request.expectedConfigVersion,
idempotencyKey: request.idempotencyKey
idempotencyKey: request.idempotencyKey,
configVersionBeforeAIWrite: server.configVersion,
configVersionAfterAIWrite: currentServer.configVersion
},
acceptedJob: pick(result.job, ["id", "serverInstanceId", "runEndpointId", "capability", "state", "resultRef"]),
job: pick(job, ["id", "serverInstanceId", "runEndpointId", "capability", "state", "resultRef"]),
proof: "platform API accepted process.start and platform-owned jobs endpoint returned the same job; browser verified operation history entry point without direct run access",
proof: `platform API accepted ${expectedCapability} and platform-owned jobs endpoint returned the same job; browser verified operation history entry point without direct run access`,
browserEvidence: {
...historyState,
proofMode: "operation-history-entry-point"
@@ -894,6 +1205,18 @@ function scanText(text, routeName) {
}
}
function assertNoForbiddenProjection(value, label) {
const serialized = JSON.stringify(value);
const forbidden = forbiddenFragments.find((item) => item.pattern.test(serialized));
if (forbidden) {
throw new Error(`${label} contains forbidden API fragment: ${forbidden.name}`);
}
const forbiddenField = /"(?:apiKey|apiKeyRef|rawApiKey|baseUrl|hostPath|pid|socket|dsn|rcon|credential|directRunEndpoint)"\s*:/i.exec(serialized);
if (forbiddenField) {
throw new Error(`${label} contains forbidden API field ${forbiddenField[0]}`);
}
}
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label}: expected ${expected}, got ${actual}`);
@@ -906,12 +1229,6 @@ function assertIncludes(values, expected, label) {
}
}
function assertSafeRedactedRef(value, label) {
if (typeof value !== "string" || (!value.startsWith("secret://") && !value.startsWith("env://"))) {
throw new Error(`${label} must be a safe secret/env reference`);
}
}
function pick(value, keys) {
return Object.fromEntries(keys.map((key) => [key, value?.[key]]));
}