feat(api): CL0UD capability probe + a11y/HAR evidence + worker scale toggle
CI (SIAX Cloud) / contracts (pull_request) Successful in 17s
CI (SIAX Cloud) / contracts (push) Successful in 17s
CI (SIAX Cloud) / quality (pull_request) Successful in 54s
CI (SIAX Cloud) / quality (push) Successful in 56s
CI (SIAX Cloud) / security (pull_request) Successful in 1m7s
CI (SIAX Cloud) / security (push) Successful in 1m5s
CI (SIAX Cloud) / contracts (pull_request) Successful in 17s
CI (SIAX Cloud) / contracts (push) Successful in 17s
CI (SIAX Cloud) / quality (pull_request) Successful in 54s
CI (SIAX Cloud) / quality (push) Successful in 56s
CI (SIAX Cloud) / security (pull_request) Successful in 1m7s
CI (SIAX Cloud) / security (push) Successful in 1m5s
- cl0ud client: contract-first probe of documented GET /capabilities/:id, status connected/denied/unavailable/disabled surfaced in /health; CL0UD_REQUIRED=true = fail-closed 503; full policy-decisions gated on CL0UD B-4/ADR-0031 (deliberately not simulated) - capture depth: accessibility tree + condensed HAR as evidence kinds (null = explicit evidence gap) - CAPTURE_WORKER=on/off for separate-scale deployments - 34/34 tests
This commit is contained in:
@@ -15,6 +15,16 @@ export interface CapturedPage {
|
||||
images: string[];
|
||||
computedStyle: Record<string, Record<string, string | null>>;
|
||||
networkRequests: { url: string; method: string; resourceType: string; status: number | null }[];
|
||||
accessibilityTree: {
|
||||
role: string | null;
|
||||
name: string | null;
|
||||
value: string | null;
|
||||
children?: unknown[];
|
||||
} | null;
|
||||
har: {
|
||||
entries: { url: string; method: string; status: number; mimeType: string | null; size: number | null }[];
|
||||
entryCount: number;
|
||||
} | null;
|
||||
screenshotBase64: string | null;
|
||||
}
|
||||
|
||||
@@ -85,6 +95,21 @@ export function buildEvidence(targetId: string, page: CapturedPage): CaptureEvid
|
||||
requests: page.networkRequests,
|
||||
}, tool),
|
||||
];
|
||||
if (page.accessibilityTree) {
|
||||
evidence.push(
|
||||
record(targetId, page.finalUrl, "accessibility", {
|
||||
tree: page.accessibilityTree,
|
||||
}, tool),
|
||||
);
|
||||
}
|
||||
if (page.har) {
|
||||
evidence.push(
|
||||
record(targetId, page.finalUrl, "har", {
|
||||
entries: page.har.entries,
|
||||
entryCount: page.har.entryCount,
|
||||
}, tool),
|
||||
);
|
||||
}
|
||||
if (page.screenshotBase64) {
|
||||
evidence.push(
|
||||
record(targetId, page.finalUrl, "screenshot", {
|
||||
@@ -122,8 +147,12 @@ export async function capturePage(
|
||||
args: ["--no-sandbox", "--disable-dev-shm-usage"],
|
||||
});
|
||||
const maxRequests = opts.maxRequests ?? 100;
|
||||
const harPath = `/tmp/c0py-capture-${randomUUID()}.har`;
|
||||
try {
|
||||
const context = await browser.newContext({ viewport: DEFAULT_VIEWPORT });
|
||||
const context = await browser.newContext({
|
||||
viewport: DEFAULT_VIEWPORT,
|
||||
recordHar: { path: harPath, mode: "minimal" },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const networkRequests: CapturedPage["networkRequests"] = [];
|
||||
page.on("response", (res) => {
|
||||
@@ -205,15 +234,48 @@ export async function capturePage(
|
||||
// Screenshot failure is an evidence gap, not a capture failure.
|
||||
screenshotBase64 = null;
|
||||
}
|
||||
// Accessibility tree (measured; null on failure = explicit evidence gap).
|
||||
let accessibilityTree: CapturedPage["accessibilityTree"] = null;
|
||||
try {
|
||||
const acc = (page as unknown as { accessibility?: { snapshot(): Promise<unknown> } })
|
||||
.accessibility;
|
||||
accessibilityTree = (await acc?.snapshot()) as CapturedPage["accessibilityTree"] ?? null;
|
||||
} catch {
|
||||
accessibilityTree = null;
|
||||
}
|
||||
// HAR (measured; condensed to entry-level metadata).
|
||||
let har: CapturedPage["har"] = null;
|
||||
try {
|
||||
await context.close();
|
||||
const raw = await import("node:fs/promises").then((fs) => fs.readFile(harPath, "utf8"));
|
||||
const parsed = JSON.parse(raw) as {
|
||||
log: { entries: { request: { url: string; method: string }; response: { status: number; content: { mimeType?: string; size?: number } } }[] };
|
||||
};
|
||||
const entries = parsed.log.entries.slice(0, maxRequests).map((e) => ({
|
||||
url: e.request.url,
|
||||
method: e.request.method,
|
||||
status: e.response.status,
|
||||
mimeType: e.response.content?.mimeType ?? null,
|
||||
size: e.response.content?.size ?? null,
|
||||
}));
|
||||
har = { entries, entryCount: parsed.log.entries.length };
|
||||
} catch {
|
||||
har = null;
|
||||
}
|
||||
return {
|
||||
finalUrl: page.url(),
|
||||
status: response?.status() ?? 0,
|
||||
contentType: response?.headers()["content-type"] ?? null,
|
||||
...captured,
|
||||
networkRequests,
|
||||
accessibilityTree,
|
||||
har,
|
||||
screenshotBase64,
|
||||
};
|
||||
} finally {
|
||||
await browser.close().catch(() => {});
|
||||
await import("node:fs/promises")
|
||||
.then((fs) => fs.unlink(harPath).catch(() => {}))
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user