@reqvire-org/ads 0.3.15 → 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/check.mjs +13 -8
- package/dist/evidence.mjs +18 -0
- package/dist/investigations.mjs +70 -0
- package/dist/mcp/context.mjs +1 -1
- package/dist/mcp/mutations.mjs +34 -17
- package/dist/mcp/preview-widget.bundle.js +12 -12
- package/dist/mcp/resource-loader.js +3 -3
- package/dist/mcp/server.mjs +30 -8
- package/dist/scenario-selectors.mjs +140 -0
- package/dist/selector-match.mjs +94 -0
- package/dist/showcase/client.bundle.js +18 -18
- package/dist/showcase/resource-reader.js +37 -0
- package/dist/skills/ads-break-ui/SKILL.md +3 -0
- package/dist/skills/ads-break-ui/references/evidence.md +73 -0
- package/dist/skills/ads-break-ui/references/guarded-editing.md +12 -0
- package/dist/skills/ads-break-ui/references/scenarios.md +32 -0
- package/dist/skills/ads-designer/SKILL.md +3 -0
- package/dist/skills/ads-designer/references/evidence.md +73 -0
- package/dist/skills/ads-designer/references/guarded-editing.md +12 -0
- package/dist/skills/ads-designer/references/scenarios.md +32 -0
- package/dist/skills/ads-import/SKILL.md +3 -0
- package/dist/skills/ads-import/references/evidence.md +73 -0
- package/dist/skills/ads-import/references/guarded-editing.md +12 -0
- package/dist/skills/ads-import/references/scenarios.md +32 -0
- package/dist/skills/ads-migrate/SKILL.md +3 -0
- package/dist/skills/ads-migrate/references/evidence.md +73 -0
- package/dist/skills/ads-migrate/references/guarded-editing.md +12 -0
- package/dist/skills/ads-migrate/references/scenarios.md +32 -0
- package/dist/skills/ads-motion/SKILL.md +3 -0
- package/dist/skills/ads-motion/references/evidence.md +73 -0
- package/dist/skills/ads-motion/references/guarded-editing.md +12 -0
- package/dist/skills/ads-motion/references/scenarios.md +32 -0
- package/dist/verification/browser.mjs +27 -8
- package/dist/verification/playwright-reporter.cjs +39 -1
- package/dist/verification.mjs +32 -9
- package/docs/ARCHITECTURE.md +29 -3
- package/docs/BEHAVIORS.md +35 -2
- package/docs/CHANGELOG.md +21 -0
- package/docs/MCP.md +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Investigate the failing boundary
|
|
2
|
+
|
|
3
|
+
Use this reference after a rejected patch, failed check or preview loading error.
|
|
4
|
+
MCP serves it as `ads://guides/evidence`. Inspect the compact result first; fetch
|
|
5
|
+
only the failing check's bundle or relevant artifact, rather than repeating a suite.
|
|
6
|
+
|
|
7
|
+
## Read the result correctly
|
|
8
|
+
|
|
9
|
+
`failure.category` identifies a boundary, not an inferred root cause:
|
|
10
|
+
|
|
11
|
+
| Category | Established observation | Next inspection |
|
|
12
|
+
|----------|-------------------------|-----------------|
|
|
13
|
+
| `architecture` | A source/ownership/configuration gate rejected the candidate. | Exact rule, source location and `certainty`. An inconclusive ownership proof does not establish that a valid API is wrong. |
|
|
14
|
+
| `build` | A registered project pipeline step or compiled-resource integrity check failed. | Named command/phase, exit code, bounded output and build revision. A custom project script may contain several kinds of checks. |
|
|
15
|
+
| `transport` | An MCP/browser resource delivery boundary failed. | Resource URI, relevant failed startup requests and observed browser errors. Do not infer a network-interface or component cause without evidence. |
|
|
16
|
+
| `runner` | The harness, process, readiness or reporting boundary failed, or a test threw without assertion evidence. | Active/pending checks, timeout, failing step and artifacts. `certainty: "unclassified"` means ADS cannot establish the cause. |
|
|
17
|
+
| `assertion` | A reported assertion step or token binding check failed. | Expected behavior, source location, state and trace. This can still be a test defect; compare the intended contract before editing the component. |
|
|
18
|
+
|
|
19
|
+
A complete failed run can contain independent passing cases. Suite interruption,
|
|
20
|
+
missing/skipped cases, stale inputs and an unexplained process exit cannot certify
|
|
21
|
+
a pass. Keep the reported case outcome separate from whole-run acceptance.
|
|
22
|
+
|
|
23
|
+
## Get a compact investigation
|
|
24
|
+
|
|
25
|
+
Discover exact owner/check IDs with `ads_behaviors`, then:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{ "unit": "components/Example", "check": "keyboard-navigation" }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Send this to `ads_investigate`. It reads the latest bundle without running code.
|
|
32
|
+
`ads://project/investigations` lists available entries; the returned `resourceUri`
|
|
33
|
+
reads the same bundle. It records:
|
|
34
|
+
|
|
35
|
+
- Project versus isolated candidate, configured framework/profile and scenario.
|
|
36
|
+
- Exact input revision and the preview revision actually observed by the helper.
|
|
37
|
+
- Failing step/source, observed startup viewport and relevant failed requests.
|
|
38
|
+
- Available registered trace, screenshot or context artifact resource URIs.
|
|
39
|
+
- Explicit `acceptance.established` and `acceptance.unverified` scope.
|
|
40
|
+
|
|
41
|
+
Absent observations are `null` or empty; ADS does not invent a viewport, trace or
|
|
42
|
+
source line. Viewport evidence is labeled `preview-startup`; a test that resizes
|
|
43
|
+
later must collect its own failure-state evidence. Failed-request collection
|
|
44
|
+
covers managed startup, not a complete network trace. Bodies and headers are not
|
|
45
|
+
collected. Project-configured Playwright trace/screenshot policy controls which
|
|
46
|
+
artifacts exist; ADS does not turn on expensive recording or rerun a failed case.
|
|
47
|
+
Protocol runners may provide structured failure/step/location/artifact fields;
|
|
48
|
+
older reports remain valid and their unclassified errors stay explicit.
|
|
49
|
+
|
|
50
|
+
Only the latest bundle per registered check is exposed, including dry runs.
|
|
51
|
+
Another verification replaces it and removes old artifacts from resource access;
|
|
52
|
+
old runner output directories follow project cleanup policy. Artifact resources
|
|
53
|
+
are restricted to registered ordinary files under that run's output directory,
|
|
54
|
+
bounded to 20 MiB each and checked against their recorded digest on read.
|
|
55
|
+
Metadata is bounded to 2 MiB. Removed registrations are pruned on verification.
|
|
56
|
+
|
|
57
|
+
Candidate evidence never becomes passing live-source evidence. Compare the
|
|
58
|
+
recorded input revision with current `ads_behaviors` evidence; a bundle describes
|
|
59
|
+
its run, and does not claim current source merely because it remains available.
|
|
60
|
+
For a build/architecture rejection before behavior runs, inspect the mutation
|
|
61
|
+
result's `investigation` (also retained in `ads_status.mutations.lastResult`).
|
|
62
|
+
|
|
63
|
+
## Preserve the design while repairing the failure
|
|
64
|
+
|
|
65
|
+
Fix the owner of the demonstrated defect. Do not privatize a valid API, introduce
|
|
66
|
+
demo-only props, substitute a styled link, change the reference baseline or weaken
|
|
67
|
+
verification policy to pass a tooling limitation. Preserve the draft and report
|
|
68
|
+
the smallest source/browser reproduction when ownership remains unprovable.
|
|
69
|
+
|
|
70
|
+
`acceptance` states what that result established. Core checks establish source
|
|
71
|
+
guard acceptance; registered tests establish only their actual cases/profiles.
|
|
72
|
+
Neither establishes untested states, visual parity, an accessibility audit or
|
|
73
|
+
another MCP host's compatibility. Captures show an appearance, not a transition.
|
|
@@ -51,6 +51,18 @@ blanket sleeps. Fake clocks suit timer-only guarantees; keep real time for actua
|
|
|
51
51
|
gesture/motion/layout guarantees. Project settings belong in guarded configuration
|
|
52
52
|
changes; ADS does not silently raise budgets or override the worker count.
|
|
53
53
|
|
|
54
|
+
If a check cannot load its MCP preview, preserve the draft and inspect the exact
|
|
55
|
+
resource URI and failed browser requests before changing component code. The ADS
|
|
56
|
+
showcase retries a fetch transport failure on `resources/read` at most twice
|
|
57
|
+
(200/800 ms backoff); recovered startup reads remain visible in
|
|
58
|
+
`startupDiagnostics`. It never retries tool calls, assertions or complete checks,
|
|
59
|
+
and persistent failures still block application. HTTP/protocol/content errors,
|
|
60
|
+
integrity failures and cancellation do not retry. External hosts own their
|
|
61
|
+
transport behavior. Chrome's `ERR_NETWORK_CHANGED` does not prove an ADS restart
|
|
62
|
+
or identify Docker/VPN churn: correlate failure timestamps with live interface
|
|
63
|
+
events, Chrome network logs and server availability. Do not weaken checks or
|
|
64
|
+
rewrite a valid API to hide infrastructure failures.
|
|
65
|
+
|
|
54
66
|
Named barrel imports can narrow affected checks when the project accurately
|
|
55
67
|
declares package `sideEffects`; retain CSS/global effects in that policy. Missing
|
|
56
68
|
or unsupported policies and dynamic/wildcard dependencies stay conservative.
|
|
@@ -101,6 +101,38 @@ do not export the helper solely for a test. Public/private ownership is a produc
|
|
|
101
101
|
decision, not a coverage workaround. Browser checks verify transitions but do not
|
|
102
102
|
replace the current static `covers` requirement; `exercises` does not waive it.
|
|
103
103
|
|
|
104
|
+
## Native semantics through runtime-owned selectors
|
|
105
|
+
|
|
106
|
+
An anchor or other native semantic element can also occupy a position styled by
|
|
107
|
+
public runtime UI, without cloning or an `asChild` API:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// Runtime owner: the class is attached to Line's actual rendered span.
|
|
111
|
+
const line = css`:is(a, button) > & { color: var(--text-body); }`;
|
|
112
|
+
export function Line(props) { return <span className={line} {...props} />; }
|
|
113
|
+
|
|
114
|
+
// Scenario: semantics belong to the consumer, appearance to the runtime owner.
|
|
115
|
+
<a href={fixture.destination}><Line>{fixture.label}</Line></a>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
ADS traces the actual returned DOM, the attached Linaria template and matching
|
|
119
|
+
selector relationships. A public parent's `& > a` is evidence too. A tag-agnostic
|
|
120
|
+
parent selector such as `& > :has(> [data-part="line"])` must match a marker
|
|
121
|
+
declared by the actual runtime-owned child; a scenario-authored lookalike does not
|
|
122
|
+
qualify. No component names or marker spellings are reserved.
|
|
123
|
+
|
|
124
|
+
Unused styles, unrelated CSS, broad `& > *` selectors, negative selectors alone,
|
|
125
|
+
fabricated markers, unknown render alternatives and opaque class replacement
|
|
126
|
+
cannot grant permission. A selector cannot establish a scenario-built custom
|
|
127
|
+
widget's keyboard behavior. Inline styles and arbitrary classes remain rejected;
|
|
128
|
+
existing source-verified public class helpers keep their own contract.
|
|
129
|
+
|
|
130
|
+
Do not add a public link part or change the DOM to satisfy a cloning-only model.
|
|
131
|
+
Preserve semantic attributes and the intended public API. Unsupported source
|
|
132
|
+
grammar produces an inconclusive ownership diagnostic, not proof of a bad API.
|
|
133
|
+
Browser verification must establish the real focus treatment, hover/state
|
|
134
|
+
precedence, navigation and other promised behavior in the compiled App iframe.
|
|
135
|
+
|
|
104
136
|
## Native semantics through a public runtime slot
|
|
105
137
|
|
|
106
138
|
A native element can be the direct child of a public runtime component that
|
|
@@ -16,26 +16,45 @@ export async function openBehaviorScenario(page, checkId, { timeout = 30000 } =
|
|
|
16
16
|
|
|
17
17
|
/** Shared readiness for registered verification and on-demand captures. */
|
|
18
18
|
export async function openPreviewScenario(page, url, scenario, { timeout = 30000 } = {}) {
|
|
19
|
-
const failures = [];
|
|
19
|
+
const failures = [], requests = [], errors = [];
|
|
20
|
+
let boundary;
|
|
21
|
+
const record = (request, detail) => {
|
|
22
|
+
if (requests.length >= 8) return;
|
|
23
|
+
let resourceUri;
|
|
24
|
+
try { const payload=request.postDataJSON(); if(payload?.method==="resources/read"&&typeof payload.params?.uri==="string"&&payload.params.uri.startsWith("ads://"))resourceUri=payload.params.uri.slice(0,500); } catch { /* Bodies are never retained. */ }
|
|
25
|
+
requests.push({method:request.method(),url:request.url().slice(0,300),...detail,...(resourceUri?{resourceUri}:{})});
|
|
26
|
+
};
|
|
20
27
|
const failed = request => {
|
|
21
28
|
if (failures.length < 5) failures.push(`${request.method()} ${request.url().slice(0, 300)}: ${request.failure()?.errorText ?? "request failed"}`);
|
|
29
|
+
record(request,{error:request.failure()?.errorText??"request failed"});
|
|
22
30
|
};
|
|
31
|
+
const response = response => { if(response.status()>=400)record(response.request(),{status:response.status()}); };
|
|
32
|
+
const pageError = error => { if(errors.length<3)errors.push(error.message.slice(0,500)); };
|
|
33
|
+
const diagnostic = (outcome, detail={}) => console.warn(JSON.stringify({adsPreviewDiagnostic:{kind:"preview-startup",scenario,outcome,viewport:page.viewportSize(),requests,errors,...detail}}));
|
|
23
34
|
page.on("requestfailed", failed);
|
|
35
|
+
page.on("response", response);page.on("pageerror", pageError);
|
|
24
36
|
try {
|
|
25
37
|
await page.goto(url, { waitUntil: "load", timeout });
|
|
26
38
|
const end = Date.now() + timeout;
|
|
27
39
|
while (Date.now() < end) {
|
|
28
40
|
for (const frame of page.frames().filter(frame => frame !== page.mainFrame())) {
|
|
29
|
-
const state = await frame.evaluate(() =>
|
|
30
|
-
if (state.id === scenario && state.state === "ready")
|
|
31
|
-
|
|
41
|
+
const state = await frame.evaluate(() => {const data=document.documentElement.dataset;return {state:data.adsPreviewState,id:data.adsScenario,revision:data.adsPreviewRevision,category:data.adsPreviewErrorCategory,code:data.adsPreviewErrorCode,resourceUri:data.adsPreviewResourceUri,message:document.getElementById("ads-preview-status")?.textContent};}).catch(() => ({}));
|
|
42
|
+
if (state.id === scenario && state.state === "ready") {
|
|
43
|
+
const retries = await page.evaluate(() => Number(document.documentElement.dataset.adsResourceRetries ?? 0)).catch(() => 0);
|
|
44
|
+
if (retries > 0) console.warn(JSON.stringify({ adsPreviewDiagnostic: { kind: "resource-read-recovery", scenario, retries, failedRequests: failures } }));
|
|
45
|
+
diagnostic("ready",{previewRevision:state.revision??null});
|
|
46
|
+
return frame;
|
|
47
|
+
}
|
|
48
|
+
if (state.id === scenario && state.state === "error") {boundary={category:state.category??"runner",phase:"preview-startup",code:state.code??"ADS_PREVIEW_STARTUP",...(state.resourceUri?{resourceUri:state.resourceUri}:{})};throw new Error(state.message || "The MCP App failed before verification.");}
|
|
32
49
|
}
|
|
33
|
-
const error = await page.evaluate(() => document.querySelector('#status[data-error="true"]')
|
|
34
|
-
if (error) throw new Error(error);
|
|
50
|
+
const error = await page.evaluate(() => {const node=document.querySelector('#status[data-error="true"]');return node?{message:node.textContent,category:node.dataset.errorCategory,code:node.dataset.errorCode}:null;}).catch(() => null);
|
|
51
|
+
if (error) {boundary={category:error.category??(requests.length?"transport":"runner"),phase:"host-startup",code:error.code??(requests.length?"ADS_BROWSER_REQUEST":"ADS_HOST_STARTUP")};throw new Error(error.message);}
|
|
35
52
|
await page.waitForTimeout(50);
|
|
36
53
|
}
|
|
37
54
|
throw new Error("Timed out waiting for the selected MCP App scenario to become current and ready. Inspect ads_status and ads://project/build.");
|
|
38
55
|
} catch (error) {
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
boundary??={category:requests.length?"transport":"runner",phase:"preview-startup",code:requests.length?"ADS_BROWSER_REQUEST":"ADS_PREVIEW_NOT_READY",certainty:requests.length?"observed-boundary":"unclassified"};
|
|
57
|
+
diagnostic("failed",{failure:boundary});
|
|
58
|
+
throw Object.assign(new Error(`Scenario ${scenario}: ${error.message}${failures.length ? `\nFailed browser requests:\n${failures.join("\n")}` : ""}`, { cause: error }),{failure:boundary});
|
|
59
|
+
} finally { page.off("requestfailed", failed);page.off("response",response);page.off("pageerror",pageError); }
|
|
41
60
|
}
|
|
@@ -8,6 +8,9 @@ module.exports = class AdsBehaviorReporter {
|
|
|
8
8
|
this.cases = [];
|
|
9
9
|
this.errors = [];
|
|
10
10
|
this.active = new Map();
|
|
11
|
+
this.diagnostics = new Map();
|
|
12
|
+
this.diagnosticBuffers = new Map();
|
|
13
|
+
this.steps = new Map();
|
|
11
14
|
this.startedAt = Date.now();
|
|
12
15
|
this.totalTests = 0;
|
|
13
16
|
}
|
|
@@ -20,11 +23,46 @@ module.exports = class AdsBehaviorReporter {
|
|
|
20
23
|
this.active.set(test.id, this.entries(test).map(entry => ({ ...entry, startedAt: (result.startTime ?? new Date()).toISOString(), workerIndex: result.workerIndex, retry: result.retry })));
|
|
21
24
|
this.publish();
|
|
22
25
|
}
|
|
26
|
+
location(value) {
|
|
27
|
+
if (!value?.file) return undefined;
|
|
28
|
+
const file=path.relative(this.request.projectRoot,value.file).replaceAll("\\","/");
|
|
29
|
+
return {file,line:value.line,column:value.column};
|
|
30
|
+
}
|
|
31
|
+
onStepEnd(test, _result, step) {
|
|
32
|
+
if (!step.error || this.steps.has(test.id)) return;
|
|
33
|
+
this.steps.set(test.id,{title:String(step.title).slice(0,300),category:step.category,location:this.location(step.error.location??step.location),durationMs:step.duration});
|
|
34
|
+
}
|
|
23
35
|
onTestEnd(test, result) {
|
|
24
36
|
this.active.delete(test.id);
|
|
25
|
-
|
|
37
|
+
const diagnostics = this.diagnostics.get(test.id);
|
|
38
|
+
const step=this.steps.get(test.id),startup=diagnostics?.findLast(item=>item.kind==="preview-startup"),failed=result.status!=="passed"||test.expectedStatus!=="passed";
|
|
39
|
+
const failure=failed?(startup?.failure??{category:step?.category==="expect"?"assertion":"runner",phase:step?.category==="expect"?"test-assertion":"test-execution",code:result.status==="timedOut"?"ADS_CASE_TIMEOUT":"ADS_CASE_FAILURE",certainty:step?.category==="expect"?"observed-boundary":"unclassified"}):undefined;
|
|
40
|
+
const artifacts=(result.attachments??[]).filter(item=>item.path).slice(0,5).map(item=>({name:String(item.name).slice(0,100),mimeType:String(item.contentType).slice(0,100),path:path.relative(path.dirname(process.env.ADS_VERIFY_REPORT),item.path).replaceAll("\\","/")}));
|
|
41
|
+
for (const entry of this.entries(test)) this.cases.push({ ...entry, status: !failed ? "passed" : ["skipped", "interrupted"].includes(result.status) ? "skipped" : "failed", durationMs: result.duration, workerIndex: result.workerIndex, retry: result.retry, completedAt: new Date().toISOString(), source:step?.location??this.location(result.errors?.[0]?.location??test.location), ...(step?{failingStep:step}:{}),...(failure?{failure}:{}),...(startup?.viewport?{viewport:startup.viewport,viewportSource:"preview-startup"}:{}),...(artifacts.length?{artifacts}:{}), ...(diagnostics?.length ? { diagnostics } : {}), message: result.errors?.map(error => error.message || error.stack || error.value || "The browser runner reported an error without details.").join("\n").slice(0, 1000) });
|
|
42
|
+
this.diagnostics.delete(test.id);
|
|
43
|
+
this.diagnosticBuffers.delete(test.id);
|
|
44
|
+
this.steps.delete(test.id);
|
|
26
45
|
this.publish();
|
|
27
46
|
}
|
|
47
|
+
onStdErr(chunk, test) {
|
|
48
|
+
if (!test) return;
|
|
49
|
+
const lines = ((this.diagnosticBuffers.get(test.id) ?? "") + String(chunk)).split("\n");
|
|
50
|
+
this.diagnosticBuffers.set(test.id, lines.pop().slice(-8192));
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
if (line.length > 8192) continue;
|
|
53
|
+
try {
|
|
54
|
+
const diagnostic = JSON.parse(line).adsPreviewDiagnostic;
|
|
55
|
+
if (typeof diagnostic?.scenario !== "string") continue;
|
|
56
|
+
let entry;
|
|
57
|
+
if(diagnostic.kind==="resource-read-recovery"&&Number.isSafeInteger(diagnostic.retries)&&diagnostic.retries>0)entry={ kind: diagnostic.kind, scenario: diagnostic.scenario.slice(0, 300), retries: diagnostic.retries, failedRequests: (Array.isArray(diagnostic.failedRequests) ? diagnostic.failedRequests : []).slice(0, 5).map(value => String(value).slice(0, 500)) };
|
|
58
|
+
else if(diagnostic.kind==="preview-startup"&&["ready","failed"].includes(diagnostic.outcome))entry={kind:diagnostic.kind,scenario:diagnostic.scenario.slice(0,300),outcome:diagnostic.outcome,viewport:diagnostic.viewport,previewRevision:diagnostic.previewRevision,requests:diagnostic.requests,errors:diagnostic.errors,...(diagnostic.failure&&["architecture","build","transport","runner","assertion"].includes(diagnostic.failure.category)?{failure:diagnostic.failure}:{})};
|
|
59
|
+
if(!entry)continue;
|
|
60
|
+
const collected = this.diagnostics.get(test.id) ?? [];
|
|
61
|
+
if (collected.length < 5) collected.push(entry);
|
|
62
|
+
this.diagnostics.set(test.id, collected);
|
|
63
|
+
} catch { /* Ordinary test output is not an ADS startup diagnostic. */ }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
28
66
|
onError(error) {
|
|
29
67
|
if (this.errors.length < 5) this.errors.push(String(error.message || error.stack || error.value || "The browser runner reported a suite error without details.").slice(0, 4000));
|
|
30
68
|
this.publish();
|
package/dist/verification.mjs
CHANGED
|
@@ -12,6 +12,8 @@ import { inspectUnits, configurationFiles } from "./units.mjs";
|
|
|
12
12
|
import { inspectBehaviors } from "./behaviors.mjs";
|
|
13
13
|
import { inspectScenarios } from "./scenarios.mjs";
|
|
14
14
|
import { readPreviewManifest } from "./preview-resources.mjs";
|
|
15
|
+
import { acceptance, failure, failureCategories } from "./evidence.mjs";
|
|
16
|
+
import { investigationArtifacts, investigationUri, saveInvestigations } from "./investigations.mjs";
|
|
15
17
|
|
|
16
18
|
export const BEHAVIOR_URI = "ads://project/behaviors";
|
|
17
19
|
const hash = bytes => createHash("sha256").update(bytes).digest("hex");
|
|
@@ -123,7 +125,7 @@ export function behaviorInputs(options, contract, check, context = inputContext(
|
|
|
123
125
|
const rows = [...files].sort().map(file => [slash(path.relative(options.root, file)), context.digest(file)]);
|
|
124
126
|
for (const file of ["package-lock.json", "bun.lock", "bun.lockb", "pnpm-lock.yaml", "yarn.lock", "node_modules/.package-lock.json"]) rows.push([file, existsSync(path.join(options.root, file)) ? context.digest(path.join(options.root, file)) : null]);
|
|
125
127
|
rows.push(["profile", JSON.stringify(options.config.mcp.behaviorProfiles[check.profile])], ["node", process.version]);
|
|
126
|
-
for (const file of ["./verification.mjs", "./verification/browser.mjs", "./verification/playwright-reporter.cjs"]) rows.push([`tool:${file}`, hash(readFileSync(new URL(file, import.meta.url)))]);
|
|
128
|
+
for (const file of ["./verification.mjs", "./verification/browser.mjs", "./verification/playwright-reporter.cjs", "./evidence.mjs", "./investigations.mjs"]) rows.push([`tool:${file}`, hash(readFileSync(new URL(file, import.meta.url)))]);
|
|
127
129
|
return { revision: hash(JSON.stringify(rows)), files: rows.map(([file]) => file) };
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -136,6 +138,11 @@ function saved(root) {
|
|
|
136
138
|
} catch { return {}; }
|
|
137
139
|
}
|
|
138
140
|
const identity = (unit, check) => `${unit}#${check}`;
|
|
141
|
+
function verificationResult(diagnostics, results) {
|
|
142
|
+
const ok=diagnostics.length===0&&results.length>0&&results.every(result=>result.status==="passed"&&result.current);
|
|
143
|
+
return {ok,diagnostics:diagnostics.map(diagnostic=>({...diagnostic,failure:diagnostic.failure??failure("runner","verification",diagnostic.rule)})),results,
|
|
144
|
+
acceptance:acceptance("selected-behavior-checks",ok,results.filter(result=>result.status==="passed"&&result.current).map(result=>`${result.unit}#${result.check}`),["unselected-checks","unasserted-behavior","visual-parity","accessibility-audit"])};
|
|
145
|
+
}
|
|
139
146
|
|
|
140
147
|
export function behaviorStatus(options) {
|
|
141
148
|
const inventory = inspectBehaviors(options), evidence = saved(options.root);
|
|
@@ -223,7 +230,7 @@ export function createVerificationController(root, { reportRoot = root, onProgre
|
|
|
223
230
|
status: () => ({ running: !!active, ...(active && progress ? { progress: { ...progress, elapsedMs: Date.now() - progress.startedAt } } : {}) }),
|
|
224
231
|
async close() { active?.abort(); await active?.completion; },
|
|
225
232
|
async run({ unit, units, checks, persist = true } = {}, signal, notifyProgress) {
|
|
226
|
-
if (active) return
|
|
233
|
+
if (active) return verificationResult([{ rule: "behavior-busy", message: "A behavior verification is already running." }],[]);
|
|
227
234
|
active = new AbortController();
|
|
228
235
|
const controller = active;
|
|
229
236
|
signal = signal ? AbortSignal.any([signal, active.signal]) : active.signal;
|
|
@@ -232,13 +239,13 @@ export function createVerificationController(root, { reportRoot = root, onProgre
|
|
|
232
239
|
const results = [], diagnostics = [];
|
|
233
240
|
try {
|
|
234
241
|
const options = loadConfig(root), inventory = inspectBehaviors(options);
|
|
235
|
-
if (inventory.diagnostics.length) return {
|
|
242
|
+
if (inventory.diagnostics.length) return verificationResult(inventory.diagnostics.map(diagnostic=>({...diagnostic,failure:failure("architecture","behavior-contract",diagnostic.rule)})),results);
|
|
236
243
|
const selectedUnits = new Set(units ?? (unit ? [unit] : inventory.contracts.map(item => item.unit)));
|
|
237
244
|
const contracts = inventory.contracts.filter(item => selectedUnits.has(item.unit));
|
|
238
|
-
if (!contracts.length || [...selectedUnits].some(id => !contracts.some(contract => contract.unit === id))) return
|
|
245
|
+
if (!contracts.length || [...selectedUnits].some(id => !contracts.some(contract => contract.unit === id))) return verificationResult([{ rule: "behavior-selection", message: "No behavior contract matches the complete selection. Discover exact owner/check IDs with ads_behaviors." }],results);
|
|
239
246
|
const context = inputContext(options);
|
|
240
247
|
const selection = contracts.flatMap(contract => Object.entries(contract.checks).filter(([id]) => !checks || checks.includes(id)).map(([id, check]) => ({ unit: contract.unit, id, ...check, inputRevision: behaviorInputs(options, contract, check, context).revision, contract })));
|
|
241
|
-
if (!selection.length || checks?.some(id => !selection.some(check => check.id === id))) return
|
|
248
|
+
if (!selection.length || checks?.some(id => !selection.some(check => check.id === id))) return verificationResult([{ rule: "behavior-selection", message: "Unknown check selection. Use check IDs returned by ads_behaviors." }],results);
|
|
242
249
|
for (const profileName of new Set(selection.map(check => check.profile))) {
|
|
243
250
|
const profile = options.config.mcp.behaviorProfiles[profileName];
|
|
244
251
|
const group = selection.filter(check => check.profile === profileName);
|
|
@@ -295,7 +302,11 @@ export function createVerificationController(root, { reportRoot = root, onProgre
|
|
|
295
302
|
// Unexplained exits, suite errors and interrupted reports grant no pass.
|
|
296
303
|
const caseFailureExit = run?.code === 1 && !runnerError && !suiteErrors.length && !incompleteReport && reportValue?.cases.some(item => item?.status === "failed");
|
|
297
304
|
const runnerFailure = run && (run.code !== 0 || suiteErrors.length || incompleteReport) ? `Registered runner ${profileName} ${run.code !== 0 ? `exited with code ${run.code ?? "unknown"}` : incompleteReport ? "left an incomplete report" : "reported a suite error"}. ${suiteErrors.join("; ") || reportValue?.cases.filter(item => item?.status === "failed").slice(0, 3).map(item => `${item.id}: ${item.message || "failed without a case diagnostic"}`).join("; ") || "Inspect the runner output and verification report."}`.slice(0, 3000) : undefined;
|
|
298
|
-
if (runnerFailure)
|
|
305
|
+
if (runnerFailure) {
|
|
306
|
+
const caseFailures=reportValue?.cases.filter(item=>item.status==="failed"&&failureCategories.has(item.failure?.category)).map(item=>item.failure)??[];
|
|
307
|
+
diagnostics.push({ rule: "behavior-runner", message: runnerFailure, output: run.output?.slice(-4000), profile: profileName,
|
|
308
|
+
failure:caseFailureExit&&caseFailures.length?{...caseFailures[0],phase:"case-results",relatedCategories:[...new Set(caseFailures.map(item=>item.category))]}:failure("runner","profile-execution","ADS_RUNNER_EXIT",{certainty:caseFailureExit?"unclassified":"observed-boundary"}) });
|
|
309
|
+
}
|
|
299
310
|
const afterOptions = loadConfig(root), afterContext = inputContext(afterOptions);
|
|
300
311
|
for (const check of group) {
|
|
301
312
|
const file = slash(path.relative(root, path.resolve(root, path.dirname(check.contract.file), check.file)));
|
|
@@ -307,15 +318,27 @@ export function createVerificationController(root, { reportRoot = root, onProgre
|
|
|
307
318
|
const current = behaviorInputs(afterOptions, check.contract, check, afterContext).revision === check.inputRevision;
|
|
308
319
|
if (!current) status = "stale";
|
|
309
320
|
if (signal.aborted) status = "cancelled";
|
|
310
|
-
|
|
321
|
+
const caseDetail=matches.find(item=>item.status!=="passed")??matches.at(-1);
|
|
322
|
+
const reportedFailure=caseDetail?.failure;
|
|
323
|
+
const checkFailure=status==="passed"?undefined:status==="stale"||status==="cancelled"?failure("runner","verification",`ADS_${status.toUpperCase()}`):caseDetail?.status==="failed"&&failureCategories.has(reportedFailure?.category)?reportedFailure:blockedRun?failure("runner","profile-execution",runnerError?.reason??"ADS_RUNNER_INCOMPLETE",{certainty:"observed-boundary"}):failure("runner","test-execution","ADS_CASE_UNCLASSIFIED",{certainty:"unclassified"});
|
|
324
|
+
const accepted=acceptance("registered-case",status==="passed"&¤t,status==="passed"&¤t?["registered-case-completed"]:[],["unasserted-behavior","visual-parity","accessibility-audit"]);
|
|
325
|
+
const investigation={unit:check.unit,check:check.id,status,runId,resourceUri:investigationUri({unit:check.unit,check:check.id}),target:root===reportRoot?"project":"candidate",framework:options.config.framework,profile:profileName,mode:profile.mode??"unit",scenario:check.scenario??null,inputRevision:check.inputRevision,inputsCurrentAtCompletion:current,
|
|
326
|
+
source:caseDetail?.source??{file},failingStep:caseDetail?.failingStep??null,viewport:caseDetail?.viewport??null,viewportSource:caseDetail?.viewportSource??null,
|
|
327
|
+
observedPreviewRevision:matches.flatMap(item=>item.diagnostics??[]).findLast(item=>item.kind==="preview-startup")?.previewRevision??null,
|
|
328
|
+
requests:matches.flatMap(item=>item.diagnostics??[]).flatMap(item=>item.requests??[]).slice(0,8),browserErrors:matches.flatMap(item=>item.diagnostics??[]).flatMap(item=>item.errors??[]).slice(0,3),
|
|
329
|
+
artifacts:investigationArtifacts(reportRoot,report,matches.flatMap(item=>item.artifacts??[])),acceptance:accepted,...(checkFailure?{failure:checkFailure}:{}),completedAt:new Date().toISOString()};
|
|
330
|
+
results.push({ unit: check.unit, check: check.id, file: check.file, case: check.case, profile: profileName, status, current, revision: check.inputRevision, runId, completedAt: investigation.completedAt,
|
|
331
|
+
...(checkFailure?{failure:checkFailure}:{}),acceptance:accepted,investigation,
|
|
311
332
|
previewRevision: profile.mode === "browser" ? host?.previewRevision() ?? readPreviewManifest(options)?.revision ?? null : null,
|
|
312
333
|
...(matches.length ? { reportedStatus, durationMs: matches.reduce((sum, item) => sum + (Number.isFinite(item.durationMs) ? Math.max(0, item.durationMs) : 0), 0) } : {}),
|
|
334
|
+
...(matches.some(item => item.diagnostics?.length) ? { startupDiagnostics: matches.flatMap(item => Array.isArray(item.diagnostics) ? item.diagnostics.slice(0, 5) : []).slice(0, 10) } : {}),
|
|
313
335
|
...(runnerFailure ? { runnerExitCode: run.code } : {}),
|
|
314
336
|
execution: progress.cases.find(item => item.unit === check.unit && item.check === check.id)?.state,
|
|
315
337
|
message: [matches.filter(item => item.status !== "passed").map(item => String(item.message || `Case reported ${item.status} without a diagnostic.`).slice(0, 2000)).join("\n"), runnerError ? `${runnerError.message} This check ${matches.length ? `reported ${reportedStatus}` : `was ${progress.cases.find(item => item.unit === check.unit && item.check === check.id)?.state ?? "not reported"}`}; the complete run did not pass.` : !matches.length ? "The selected case did not execute/report. Missing and skipped cases do not pass." : runnerFailure ? `This case reported ${reportedStatus}, but the complete run did not pass. ${runnerFailure}` : ""].filter(Boolean).join("\n"), report: slash(path.relative(reportRoot, report)) });
|
|
316
338
|
}
|
|
317
339
|
if (signal.aborted) break;
|
|
318
340
|
}
|
|
341
|
+
saveInvestigations(reportRoot,results.map(result=>result.investigation),inventory.contracts.flatMap(contract=>Object.keys(contract.checks).map(id=>identity(contract.unit,id))));
|
|
319
342
|
if (persist) {
|
|
320
343
|
const evidence = saved(root);
|
|
321
344
|
for (const result of results) evidence[identity(result.unit, result.check)] = result;
|
|
@@ -324,8 +347,8 @@ export function createVerificationController(root, { reportRoot = root, onProgre
|
|
|
324
347
|
if (Buffer.byteLength(content) > 2 * 1024 * 1024) throw new Error("Verification evidence exceeds 2 MiB. Remove obsolete generated evidence and retry.");
|
|
325
348
|
try { writeFileSync(temporary, content); renameSync(temporary, file); } finally { rmSync(temporary, { force: true }); }
|
|
326
349
|
}
|
|
327
|
-
return
|
|
328
|
-
} catch (error) { return
|
|
350
|
+
return verificationResult(diagnostics,results);
|
|
351
|
+
} catch (error) { return verificationResult([{ rule: "behavior-verification", message: error.message }],results); }
|
|
329
352
|
finally { try { await host?.close(); } finally { if (active === controller) { active = undefined; progress = undefined; } finish(); } }
|
|
330
353
|
},
|
|
331
354
|
};
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -229,6 +229,15 @@ scenarios unmounts the previous view while keeping unchanged dependencies cached
|
|
|
229
229
|
Persistence is a host lifecycle choice; a new external widget loads the same
|
|
230
230
|
resources independently. Resource sharing is scoped to an App instance.
|
|
231
231
|
|
|
232
|
+
The showcase's resource bridge can recover a transient fetch failure with at most
|
|
233
|
+
three read attempts and bounded backoff. This applies only to `resources/read`;
|
|
234
|
+
tool calls, assertions and whole checks are never replayed. Cancellation,
|
|
235
|
+
HTTP/protocol/content errors and integrity failures remain failures. Browser
|
|
236
|
+
diagnostics record retries, and verification retains recovered startup reads in
|
|
237
|
+
its case results. Persistent transport failures still block a mutation. External
|
|
238
|
+
hosts own their transport recovery; the reusable App's integrity checks remain
|
|
239
|
+
the same.
|
|
240
|
+
|
|
232
241
|
Managed graph builders publish into a private session directory beside the
|
|
233
242
|
configured manifest. Logical scenario IDs and resource URIs stay unchanged;
|
|
234
243
|
standalone builds retain the configured output and cannot overwrite a live
|
|
@@ -313,7 +322,12 @@ styles. This preserves ownership rather than granting scenario styling freedom.
|
|
|
313
322
|
A native element may provide semantics as the single direct child of a public
|
|
314
323
|
runtime slot when source tracing proves it is cloned with that owner's props.
|
|
315
324
|
This preserves polymorphic composition without allowing standalone scenario
|
|
316
|
-
controls, scenario styling or name-based exceptions.
|
|
325
|
+
controls, scenario styling or name-based exceptions. Selector-based composition
|
|
326
|
+
can establish the same ownership through actual returned DOM and attached runtime
|
|
327
|
+
Linaria: a positive native-tag relationship, or a `:has` relationship to a marker
|
|
328
|
+
declared by a real runtime-owned child. Broad selectors and scenario-authored
|
|
329
|
+
lookalikes grant no permission. This source proof does not establish computed
|
|
330
|
+
appearance, keyboard behavior or visual parity; browser checks remain necessary.
|
|
317
331
|
The skill-local [scenario guide](../skills/ads-designer/references/scenarios.md)
|
|
318
332
|
explains evidence, limits and diagnostics.
|
|
319
333
|
|
|
@@ -418,8 +432,20 @@ ADS owns affected-check selection, candidate hosting, progress, evidence freshne
|
|
|
418
432
|
and process cleanup. One profile timeout bounds its entire runner process. Parallel
|
|
419
433
|
workers can share that candidate host with independent App state. Partial outcomes
|
|
420
434
|
remain diagnostic when execution is interrupted; they cannot approve a mutation.
|
|
421
|
-
Passing checks describe their registered assertions, not every natural-language claim.
|
|
422
|
-
|
|
435
|
+
Passing checks describe their registered assertions, not every natural-language claim.
|
|
436
|
+
Results distinguish architecture, project-build, transport, runner and assertion
|
|
437
|
+
boundaries with structured phase/code evidence; an unclassified test exception
|
|
438
|
+
does not become an assertion or a component defect. Acceptance explicitly lists
|
|
439
|
+
established and unverified scope. Verification retains one latest investigation
|
|
440
|
+
per registered check, including isolated candidates: exact input revision,
|
|
441
|
+
observed preview revision, failing step/source, observed startup viewport,
|
|
442
|
+
failed requests and available project-recorded artifacts. MCP exposes bounded
|
|
443
|
+
bundle/index resources and only registered, digest-checked artifact files.
|
|
444
|
+
Another run replaces that check's resource access; candidate evidence never
|
|
445
|
+
certifies live source. Detailed usage lives in the portable skill-local
|
|
446
|
+
[evidence guide](../skills/ads-designer/references/evidence.md).
|
|
447
|
+
|
|
448
|
+
Edits stay focused without requiring entire file bodies in model context. Peer units and
|
|
423
449
|
unrelated owners remain outside the selected unit's edit boundary. Shared foundation,
|
|
424
450
|
branding and guides have their own units. Shared credits and text license notices
|
|
425
451
|
belong to the documentation unit, including conventional root notices and shared
|
package/docs/BEHAVIORS.md
CHANGED
|
@@ -169,8 +169,23 @@ pass, even if all reported cases claim success. Results retain `reportedStatus`
|
|
|
169
169
|
and `runnerExitCode` and explain the overall failure. Suite-level browser errors and
|
|
170
170
|
bounded runner output appear in diagnostics. `openBehaviorScenario` includes the
|
|
171
171
|
scenario ID and failed browser requests in startup failures; App resource errors
|
|
172
|
-
identify the resource URI.
|
|
173
|
-
|
|
172
|
+
identify the resource URI. The ADS showcase retries only `resources/read` fetch
|
|
173
|
+
transport failures, with at most three attempts and 200/800 ms backoff. Recovered
|
|
174
|
+
startup reads appear in `startupDiagnostics` with retry counts and failed browser
|
|
175
|
+
requests; each retry is also logged in the browser console. This does not retry a
|
|
176
|
+
tool call, assertion, check or complete startup. HTTP/protocol/content errors,
|
|
177
|
+
integrity failures and cancellations do not retry. Persistent loading failures
|
|
178
|
+
still fail the check and block mutations. External hosts control their own
|
|
179
|
+
transport recovery.
|
|
180
|
+
|
|
181
|
+
`net::ERR_NETWORK_CHANGED` reports Chrome's network-change error, not proof that
|
|
182
|
+
the MCP endpoint restarted or that a particular interface changed. To investigate
|
|
183
|
+
an intermittent failure, correlate the saved case timestamps with Chrome's
|
|
184
|
+
network log, server availability and live interface monitoring (for example,
|
|
185
|
+
`ip -ts monitor link address route` on Linux). Interfaces being present, or stable
|
|
186
|
+
after a failure, does not establish the cause. Preserve the draft and original
|
|
187
|
+
diagnostic; do not change a component API, reference example or verification
|
|
188
|
+
policy to compensate for a resource-loading failure.
|
|
174
189
|
|
|
175
190
|
The reporter atomically publishes partial snapshots at test start/end. While a
|
|
176
191
|
run is active, `ads_status.behaviors.progress` exposes the profile, runner budget,
|
|
@@ -257,6 +272,13 @@ publish `complete:true` at completion. Older final reports without `complete`
|
|
|
257
272
|
remain supported after normal process completion. An explicitly incomplete report
|
|
258
273
|
cannot pass even with exit code zero. Partial reports retain the same schema/run
|
|
259
274
|
identity and size limits as final reports.
|
|
275
|
+
Cases may optionally include `failure: {category,phase,code,certainty?}`,
|
|
276
|
+
`source: {file,line,column}`, `failingStep`, `viewport`/`viewportSource`, and
|
|
277
|
+
`artifacts: [{name,mimeType,path}]`. Artifact paths are relative to the report
|
|
278
|
+
directory and must remain under that run's `artifacts/` directory. The built-in
|
|
279
|
+
Playwright reporter supplies these from actual step/startup/attachment evidence.
|
|
280
|
+
Older protocol runners can omit them; ADS preserves that lack of classification
|
|
281
|
+
instead of treating an arbitrary thrown message as an assertion.
|
|
260
282
|
`file` is project-relative. Nonzero process exit, missing cases, skipped cases,
|
|
261
283
|
wrong run IDs and unavailable reports do not pass. Test commands are trusted
|
|
262
284
|
project code; an adapter cannot prove the quality or honesty of their assertions.
|
|
@@ -285,6 +307,17 @@ changes mark results stale; changes during execution prevent a current pass.
|
|
|
285
307
|
Guarantees report passing only when all their checks have current passing evidence.
|
|
286
308
|
Passing applies to those cases and profiles, not every application state.
|
|
287
309
|
|
|
310
|
+
Results expose structured `failure` boundaries and explicit `acceptance` scope.
|
|
311
|
+
Use `ads_investigate({unit,check})` for the latest compact investigation, including
|
|
312
|
+
isolated dry runs; `ads://project/investigations` lists available entries. Exact
|
|
313
|
+
input and observed preview revisions, failing step/source, startup viewport,
|
|
314
|
+
failed startup requests and available project-recorded artifacts keep diagnosis
|
|
315
|
+
separate from an inferred component defect. No artifact is invented or test
|
|
316
|
+
retried. Each check retains one latest bundle; old artifact resource access is
|
|
317
|
+
superseded. Candidate bundles remain distinct from passing live-source evidence.
|
|
318
|
+
Read the portable [evidence reference](../skills/ads-designer/references/evidence.md)
|
|
319
|
+
for category meanings, collection limits and artifact policy.
|
|
320
|
+
|
|
288
321
|
Revisions include owned files, literal import dependencies, local CSS imports and
|
|
289
322
|
asset URLs, token foundation, project/runner configuration, lockfiles and tooling.
|
|
290
323
|
Computed module selection conservatively includes all ADS owners. Environment,
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.17
|
|
4
|
+
|
|
5
|
+
- Recognize native semantic composition through source-verified runtime Linaria
|
|
6
|
+
selectors, including runtime-owned part bindings. Keep unrelated controls,
|
|
7
|
+
broad selectors, fabricated markers and custom scenario styling rejected.
|
|
8
|
+
- Distinguish architecture, build, transport, runner and assertion boundaries;
|
|
9
|
+
expose explicit acceptance scope and latest-per-check investigation bundles
|
|
10
|
+
with revisions, source/step, startup viewport, requests and available artifacts.
|
|
11
|
+
Preserve candidate/live-source evidence and independent case outcomes.
|
|
12
|
+
- Add generic React/Preact conformance checks for selector composition, extracted
|
|
13
|
+
CSS, MCP resource recovery and native iframe interaction. Document diagnosis
|
|
14
|
+
through portable skill references without project-specific implementation rules.
|
|
15
|
+
|
|
16
|
+
## 0.3.16
|
|
17
|
+
|
|
18
|
+
- Recover transient showcase MCP resource fetch failures with bounded read-only
|
|
19
|
+
retries. Keep tool calls, assertions, checks and non-transport failures outside
|
|
20
|
+
recovery; expose recovered startup reads in verification diagnostics and keep
|
|
21
|
+
persistent loading failures blocking. Verify interrupted resource delivery and
|
|
22
|
+
failed delivery against a dummy App, without changing project verification policy.
|
|
23
|
+
|
|
3
24
|
## 0.3.15
|
|
4
25
|
|
|
5
26
|
- Keep fragment links inside inline preview documents by supplying a local
|
package/docs/MCP.md
CHANGED
|
@@ -177,6 +177,7 @@ The server supplies agent guidance through its instructions, tool descriptions,
|
|
|
177
177
|
| `ads_show_scenario` | Render an exact discovered scenario with its own fixtures |
|
|
178
178
|
| `ads_behaviors` | Bounded guarantee/evidence discovery; pass an exact unit for contract details |
|
|
179
179
|
| `ads_verify` | Execute an owner's registered checks and record revision-bound evidence |
|
|
180
|
+
| `ads_investigate` | Read one check's latest compact failure/evidence bundle without rerunning it |
|
|
180
181
|
| `ads_capture` | On-demand PNG of a discovered scenario or named element; replace current and clear candidate |
|
|
181
182
|
| `ads_capture_clear` | Remove generated captures or discard only the candidate |
|
|
182
183
|
| `ads_show_<id>` | Optional explicitly configured grouped App |
|
|
@@ -275,6 +276,18 @@ single failed assertion does not turn the other checks into failures. Overall
|
|
|
275
276
|
verification and guarded application still fail. Suite errors, unexplained exits,
|
|
276
277
|
missing results and interrupted reports cannot certify passing checks.
|
|
277
278
|
|
|
279
|
+
For diagnosis, call `ads_investigate({ "unit": "components/Example", "check": "keyboard-navigation" })`
|
|
280
|
+
with discovered IDs. It reads the latest check bundle without rerunning anything,
|
|
281
|
+
including rejected candidates. `ads://project/investigations` lists available
|
|
282
|
+
bundles; returned resource URIs expose the bundle and available registered
|
|
283
|
+
artifacts. Exact input/observed preview revisions, source/failing step, startup
|
|
284
|
+
viewport and failed requests separate evidence from a suspected cause.
|
|
285
|
+
`failure.category` distinguishes architecture, build, transport, runner and
|
|
286
|
+
assertion boundaries; `acceptance` names established and unverified scope.
|
|
287
|
+
Only the latest bundle per check remains resource-accessible. Read the portable
|
|
288
|
+
[evidence guide](../skills/ads-designer/references/evidence.md) for collection
|
|
289
|
+
limits and project-controlled trace/screenshot recording.
|
|
290
|
+
|
|
278
291
|
Browser profiles start an isolated showcase and verify its sandboxed App using
|
|
279
292
|
the selected candidate's sources. StrictMode profiles need a project React
|
|
280
293
|
development harness. Profiles describe the execution environment, not a proof
|