Compare commits

...
Author SHA1 Message Date
morlutoandGitHub 360fc5195e Merge branch 'main' into codex/review-fixes-pr-2457 2026-10-14 02:39:44 +08:00
0bc422d7be fix(wasm): attribute invalid paths to their selected fields (#2796)
* fix(wasm): attribute invalid paths to their selected fields

* test(wasm): assert the exact rejected selection issues

---------

Co-authored-by: maxazure <405711+maxazure@users.noreply.github.com>
2026-10-14 02:39:03 +08:00
morlutoandGitHub 2895a18ca9 fix(ci): skip portable report merge when shards are skipped (#2943)
* fix(ci): skip portable report merge when shards are skipped

The portable consumer can still run after an upstream build failure, even though the test-shard matrix is skipped and no Vitest blob artifacts exist. Guard artifact download and both report merge variants on successful shards; retain the required-shard gate so skipped or failed shards remain failures.

This applies the report-consumer condition from ee0256c740c9be18baae6c56d90285df8069f5b4 to current pinned main without changing action pins or required gates.

* fix(ci): preserve failed shard diagnostics

* docs: clarify current revision CI policy
2026-10-14 02:30:44 +08:00
morlutoandGitHub 7a6defab18 test(reconstruction): declare complete comparison fixture facets (#2964) 2026-10-14 02:17:57 +08:00
morluto 7b33314701 fix(ci): verify retained Windows directory ownership 2026-10-14 02:11:39 +08:00
Morluto d41f7963c8 Merge commit '8fef2aa64ec1ee742e8b70b825938d30a0ea7319' into codex/ci-wave3-2801 2026-10-14 02:10:41 +08:00
morluto f3532d9fb2 chore: merge main into PR #2801 2026-10-14 02:07:23 +08:00
morluto 432543def3 test(evaluation): provide complete fixture target 2026-10-14 00:46:42 +08:00
morluto caadb20563 chore: merge current main into PR #2801 2026-10-13 23:55:24 +08:00
N0zoM1z0andmorluto f5fe0463d4 style(tests): format the Java class fixture signature
The captured main baseline has an unformatted helper signature. Apply the
repository formatter so the required style gate can run to completion.

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
(cherry picked from commit b5a25fbdda)
2026-10-13 23:01:38 +08:00
morluto 3cb84db002 chore: reconcile captured main with PR #2801 2026-10-13 21:43:32 +08:00
morluto 29711d11fd fix(ci): classify Notes evaluation assets as code inputs 2026-10-13 20:32:35 +08:00
AISOP.dev ac82a18424 test(agent): grade notes export facts against produced evidence 2026-10-12 22:59:45 -04:00
14 changed files with 652 additions and 40 deletions
+11 -3
View File
@@ -290,14 +290,19 @@ jobs:
- if: needs.changes.outputs.code == 'true' && success()
run: npm ci
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
if: needs.changes.outputs.code == 'true' && success()
if: >-
!cancelled() && needs.changes.outputs.code == 'true' &&
(needs.test-shard.result == 'success' || needs.test-shard.result == 'failure')
with:
path: .vitest/blob
pattern: vitest-report-*
merge-multiple: true
- name: Merge coverage, JUnit, and timing reports
id: merge
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'full' && success()
if: >-
!cancelled() && needs.changes.outputs.code == 'true' &&
needs.changes.outputs.tier == 'full' &&
(needs.test-shard.result == 'success' || needs.test-shard.result == 'failure')
run: >-
npm run test:ci:merge --
--coverage.reportsDirectory=coverage
@@ -312,7 +317,10 @@ jobs:
--outputFile.json=test-results.json
- name: Merge fast-suite JUnit and timing reports
id: merge-fast
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'fast' && success()
if: >-
!cancelled() && needs.changes.outputs.code == 'true' &&
needs.changes.outputs.tier == 'fast' &&
(needs.test-shard.result == 'success' || needs.test-shard.result == 'failure')
run: >-
npm exec -- vitest --merge-reports
--reporter=default
+1 -2
View File
@@ -182,8 +182,7 @@ The separate GitHub Pages host uses only the manual
## Development feedback and PR verification
For routine merges, wait for the required checks to pass for the current PR
revision against the current `main`. Branch protection requires up-to-date
branches; administrators retain an override for deliberate maintainer recovery.
revision. Administrators retain an override for deliberate maintainer recovery.
If `main` is broken, prioritize a focused repair before updating affected PRs.
Select checks for the behavior changed. For a source edit:
+3 -2
View File
@@ -389,8 +389,9 @@ adds in-place reparse, breakaway, environment, and token observations.
The separate `scripts/verify-windows-native-inspection.mjs` lane uses the
`.cache/windows-native-inspection-fixture` artifact built on Linux with
`node scripts/build-windows-native.mjs --inspection-fixture`. Its controlled
first deletion failure checks retained NTFS ownership, later-query recovery,
successful-admission caching, and environment-teardown cleanup. It neither
first deletion failure checks retained NTFS ownership across a same-volume
rename, later-query handle-based recovery, successful-admission caching, and
environment-teardown cleanup. It neither
establishes the cause of the intermittent #1701 host failure nor replaces
verification with the shipped production artifact.
`npm run verify:ghidra:windows:package` additionally packs and installs REA into
+3
View File
@@ -197,6 +197,9 @@ if (tier === "reused") {
if (GUARDED_WORKFLOWS.has(file)) selected.code = true;
if (file !== ".github/workflows/ci.yml") continue;
}
// The Notes example is also the source-owned agent evaluation fixture.
if (file.startsWith("website/public/examples/notes-electron/"))
selected.code = true;
if (owners.length === 1 && owners[0] === "website") continue;
if (/^src\/(contracts|domain)\//.test(file)) selected.docs = true;
if (file === "package.json") {
+12
View File
@@ -155,6 +155,18 @@ try {
requiredAnswerTermGroups: [["profileapi"], ["profile:read"]],
prompt: `請靜態分析已交付的 Electron 程式 ${targets.javascript},說明 renderer 如何透過 preload 讀取 profile,以及 main handler 的關係。不要執行目標或安裝分析引擎;引用完整的產出 Evidence,保留 runtime 未知事項。`,
},
{
id: "notes-export",
expectedFirstTool: "analyze_javascript_application",
requiresEvidence: true,
requiredAnswerTermGroups: [],
requiredToolSubsequence: [
"analyze_javascript_application",
"trace_application_feature",
"trace_javascript_semantics",
],
prompt: `Use REA on the shipped Electron application at ${targets.notesElectron} to investigate its notes export API, preload exposure and main handler. Do not read target files directly or launch Electron. Use the returned application Evidence to trace the exact notes:export IPC channel in both directions, then query semantic relations for the property invoke with forward influence, excluding ambiguous dynamic edges and without an expected sink. Report the selected static facts, source locations and coverage limits using the closed claim contract.`,
},
{
id: "javascript-module-view",
expectedFirstTool: "analyze_javascript_application",
+5 -2
View File
@@ -62,8 +62,11 @@ assert.equal(first.retryable,true);
assert.match(first.reason,/Delete owned object by handle failed.*Win32 5/s);
const [name,...extra]=readdirSync(temp);assert.ok(name);assert.equal(extra.length,0);
const path=join(temp,name);const identity=statSync(path,{bigint:true});
assert.throws(()=>renameSync(path,path+'-replacement'));
assert.equal(statSync(path,{bigint:true}).ino,identity.ino);
// This private directory permits rename while its retained handle still owns
// cleanup. Recovery must follow the object identity after its path changes.
const replacement=path+'-replacement';renameSync(path,replacement);
assert.equal(statSync(replacement,{bigint:true}).ino,identity.ino);
assert.equal(readdirSync(temp).length,1);
if(process.argv[2]!=='teardown'){
const second=systemWindowsNativeAuthority();assert.equal(second.available,true);
assert.equal(readdirSync(temp).length,0);
+24 -16
View File
@@ -6,6 +6,7 @@ import {
AnalysisCancelledError,
AnalysisInputError,
AnalysisOutputError,
type AnalysisInputIssue,
} from "../../domain/analysisErrorCore.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import {
@@ -41,22 +42,29 @@ export class WasmArtifactService {
cause: input.error,
}),
);
if (
![
input.data.path,
...input.data.glue_paths,
...input.data.candidate_paths,
].every(isAbsolute)
)
return err(
new AnalysisInputError(OPERATION, undefined, [
{
path: ["path"],
reason: "invalid_format",
message: "Expected an absolute filesystem path on this host.",
},
]),
);
const pathIssues = [
{ value: input.data.path, path: ["path"] },
...input.data.glue_paths.map((value, index) => ({
value,
path: ["glue_paths", index],
})),
...input.data.candidate_paths.map((value, index) => ({
value,
path: ["candidate_paths", index],
})),
].flatMap<AnalysisInputIssue>(({ value, path }) =>
isAbsolute(value)
? []
: [
{
path,
reason: "invalid_format",
message: "Expected an absolute filesystem path on this host.",
},
],
);
if (pathIssues.length > 0)
return err(new AnalysisInputError(OPERATION, undefined, pathIssues));
const inspected = await this.provider.inspect(input.data, options);
if (!inspected.ok) return inspected;
if (options?.signal?.aborted)
@@ -44,6 +44,16 @@ const dossier = jsonValueSchema.parse(
referenced_names: [],
native_api: null,
native_value_flow: null,
facet_availability: {
pseudocode: { available: true },
assembly: { available: true },
comments: { available: true },
callers: { available: true },
callees: { available: true },
references: { available: true },
strings_names: { available: true },
cfg: { available: true },
},
basic_blocks: [{ start: "0x1000", end: "0x1001", successors: [] }],
limitations: [],
}),
+144 -12
View File
@@ -8,6 +8,11 @@ import type { JsonValue } from "../domain/jsonValue.js";
import { javascriptApplicationAnalysisResultSchema } from "../domain/javascript/javascriptApplicationAnalysis.js";
import type { ApplicationNode } from "../domain/javascript/javascriptApplicationGraphSchemas.js";
import { javaScriptExportShapeComparisonResultSchema } from "../domain/javascript/javascriptExportShapeComparisonSchemas.js";
import {
applicationFeatureTraceResultSchema,
featureSeedMatchMode,
} from "../domain/javascript/javascriptFeatureTraceSchemas.js";
import { javaScriptSemanticTraceResultSchema } from "../domain/javascript/javascriptSemanticTraceSchemas.js";
import {
managedArtifactInspectionSchema,
managedMemberInspectionSchema,
@@ -28,6 +33,8 @@ export interface AgentEvaluationFixtureTargets {
readonly javascriptSha256: string;
readonly javascriptShapeLeft: string;
readonly javascriptShapeRight: string;
readonly notesElectron: string;
readonly notesElectronSha256: string;
readonly managed?: string;
readonly managedSha256?: string;
}
@@ -50,6 +57,8 @@ export const createAgentEvaluationFixtures = async (
const javascript = join(root, "desktop-app.asar");
const javascriptShapeLeft = join(root, "parser-v1");
const javascriptShapeRight = join(root, "parser-v2");
const notesDirectory = join(root, "notes-electron");
const notesElectron = join(root, "notes-electron.asar");
await Promise.all([
mkdir(join(desktop, "renderer"), { recursive: true }),
mkdir(javascriptShapeLeft, { recursive: true }),
@@ -67,8 +76,14 @@ export const createAgentEvaluationFixtures = async (
join(repositoryRoot, "tests/fixtures/replay/parser-v2.mjs"),
join(javascriptShapeRight, "parser.mjs"),
),
cp(
join(repositoryRoot, "website/public/examples/notes-electron"),
notesDirectory,
{ recursive: true },
),
]);
await createPackage(desktop, javascript);
await createPackage(notesDirectory, notesElectron);
return {
javascript,
javascriptSha256: createHash("sha256")
@@ -76,6 +91,10 @@ export const createAgentEvaluationFixtures = async (
.digest("hex"),
javascriptShapeLeft,
javascriptShapeRight,
notesElectron,
notesElectronSha256: createHash("sha256")
.update(await readFile(notesElectron))
.digest("hex"),
};
};
@@ -189,21 +208,116 @@ const runtimeSemantics = (value: JsonValue): JsonValue | undefined => {
: undefined;
};
const asarClaims = (
const featureModules = (value: JsonValue): JsonValue | undefined => {
const result = applicationFeatureTraceResultSchema.safeParse(value);
if (!result.success || result.data.graph === null) return undefined;
if (
result.data.seed.kind !== "channel" ||
result.data.seed.value !== "notes:export" ||
featureSeedMatchMode(result.data.seed) !== "exact"
)
return undefined;
const paths = result.data.graph.nodes
.filter((node) => node.kind === "javascript-module")
.flatMap((node) => node.observations)
.filter(({ properties }) => properties.semantic_role === "source-module")
.map(({ properties }) => properties.path);
if (paths.length === 0 || !paths.every((path) => typeof path === "string"))
return undefined;
return {
paths: [...new Set(paths)].sort(compareUnicodeCodePoints),
coverage: result.data.coverage.status,
};
};
const semanticBoundary = (value: JsonValue): JsonValue | undefined => {
const result = javaScriptSemanticTraceResultSchema.safeParse(value);
if (!result.success) return undefined;
return {
status: result.data.status,
coverage: result.data.coverage.status,
relevant_unknowns: result.data.summary.relevant_unknowns,
};
};
const notesClaims = (
targets: AgentEvaluationFixtureTargets,
): readonly FixtureClaimExpectation[] => {
const subject = {
path: targets.notesElectron,
sha256: targets.notesElectronSha256,
};
const derived = {
authority: "analyst-inference",
confidence: "inferred",
linkedSubjects: [
{ operation: "analyze_javascript_application", ...subject },
],
} satisfies Pick<
FixtureClaimExpectation["source"],
"authority" | "confidence" | "linkedSubjects"
>;
// Fixture source establishes bridge/module facts; the semantic claim checks
// the bounded static-query contract, not execution or CSV formatter behavior.
return [
...bridgeClaims(subject, "notes", ["exportCsv"], "notes:export"),
{
id: "feature_modules",
expectedValue: {
paths: ["csv.js", "main.js", "preload.js", "renderer.js"],
coverage: "complete-within-source",
},
source: {
...derived,
operation: "trace_application_feature",
parameters: {
direction: "both",
},
select: featureModules,
},
},
{
id: "semantic_boundary",
expectedValue: {
status: "ambiguous",
coverage: "partial",
relevant_unknowns: 1,
},
source: {
...derived,
operation: "trace_javascript_semantics",
parameters: {
query: {
seed: { kind: "property", name: "invoke" },
direction: "forward-influence",
include_ambiguous_dynamic_edges: false,
expected: null,
},
},
select: semanticBoundary,
},
},
];
};
const bridgeClaims = (
subject: { readonly path: string; readonly sha256: string },
key: string,
members: readonly string[],
channel: string,
): readonly FixtureClaimExpectation[] => {
const source = {
operation: "analyze_javascript_application",
authority: "shipped-artifact",
confidence: "derived",
subject: { path: targets.javascript, sha256: targets.javascriptSha256 },
subject,
} satisfies Omit<FixtureClaimExpectation["source"], "select">;
return [
{
id: "renderer_api",
expectedValue: {
key: "profileApi",
members: ["read"],
key,
members: [...members],
world: "main",
source: "preload.js",
},
@@ -212,7 +326,7 @@ const asarClaims = (
{
id: "renderer_ipc",
expectedValue: {
channel: "profile:read",
channel,
operation: "invoke",
source: "preload.js",
},
@@ -221,7 +335,7 @@ const asarClaims = (
{
id: "main_ipc",
expectedValue: {
channel: "profile:read",
channel,
operation: "handle",
source: "main.js",
},
@@ -235,6 +349,16 @@ const asarClaims = (
];
};
const asarClaims = (
targets: AgentEvaluationFixtureTargets,
): readonly FixtureClaimExpectation[] =>
bridgeClaims(
{ path: targets.javascript, sha256: targets.javascriptSha256 },
"profileApi",
["read"],
"profile:read",
);
const parserClaims = (
targets: AgentEvaluationFixtureTargets,
): readonly FixtureClaimExpectation[] => {
@@ -399,25 +523,33 @@ export const agentFixtureClaims = (
if (scenarioId === "asar") return asarClaims(targets);
if (scenarioId === "managed") return managedClaims(targets);
if (scenarioId === "javascript-export-shape") return parserClaims(targets);
if (scenarioId === "notes-export") return notesClaims(targets);
return undefined;
};
const bridgeAnswerDescriptions = [
'renderer_api: {"key": string, "members": string[], "world": string, "source": string}, describing the exposed bridge API, its members, world, and source file.',
'renderer_ipc: {"channel": string, "operation": string, "source": string}, describing the renderer-side IPC operation and its source file.',
'main_ipc: {"channel": string, "operation": string, "source": string}, describing the main-side IPC operation and its source file.',
'preload: {"path": string, "resolution_status": string}, describing the BrowserWindow-declared preload resolved within the shipped artifact.',
];
const answerValueDescriptions: Readonly<Record<string, readonly string[]>> = {
managed: [
"public_types: string[], listing the exact public top-level type full names in Unicode code-point order from unfiltered member Evidence (omit type_names).",
'entry_point: {"declaring_type": string, "name": string}, resolving the CLI metadata entry-point token against the member inventory from the same artifact SHA-256 and module MVID. Cite member Evidence; both artifact and member Evidence must be delivered.',
],
asar: [
'renderer_api: {"key": string, "members": string[], "world": string, "source": string}, describing the exposed bridge API, its members, world, and source file.',
'renderer_ipc: {"channel": string, "operation": string, "source": string}, describing the renderer-side IPC operation and its source file.',
'main_ipc: {"channel": string, "operation": string, "source": string}, describing the main-side IPC operation and its source file.',
'preload: {"path": string, "resolution_status": string}, describing the BrowserWindow-declared preload resolved within the shipped artifact.',
],
asar: bridgeAnswerDescriptions,
"javascript-export-shape": [
'heading_change: {"status": string, "path": string, "discriminant": object or null, "left": object, "right": object}, copying the exact heading-change fields from the comparison result.',
'comparison_coverage: {"status": string, "paired_variants": number, "added": number, "removed": number, "changed": number, "unknown": number}, copying comparison coverage and summary counts.',
'runtime_semantics: "established" or "not_established", stating whether the cited comparison establishes runtime behavior.',
],
"notes-export": [
...bridgeAnswerDescriptions,
'feature_modules: {"paths": string[], "coverage": string}, listing the unique source-module observation paths in the feature trace in Unicode code-point order, and its coverage status.',
'semantic_boundary: {"status": string, "coverage": string, "relevant_unknowns": number}, copying the semantic trace status, coverage status, and summary.relevant_unknowns. Static traces do not establish runtime execution.',
],
};
/** Describe the strict answer contract without revealing any golden fixture values. */
@@ -476,6 +476,10 @@ it.each([
{ code: true, runtime: false, browser: false, package: false },
],
["website/src/index.ts", { code: false, website: true, package: false }],
[
"website/public/examples/notes-electron/preload.js",
{ code: true, website: true, package: false },
],
[
"website/locales/zh-CN/messages.po",
{ code: false, website: true, package: false },
@@ -45,23 +45,34 @@ const workflow = async () =>
),
).jobs;
it("collects failed-shard diagnostics before enforcing the shard result", async () => {
it("merges available failed-shard diagnostics and skips unavailable shard reports", async () => {
const steps = (await workflow()).test!.steps;
const download = steps.find(
(step) =>
step.uses?.includes("actions/download-artifact") &&
step.with?.pattern === "vitest-report-*",
);
const merge = steps.findIndex((step) => step.run?.includes("test:ci:merge"));
const upload = steps.findIndex((step) => step.with?.name === "test-results");
const gate = steps.findIndex(
(step) => step.env?.SHARD_RESULT && step.run?.includes("test "),
);
expect(merge).toBeGreaterThan(0);
expect(download?.if).toContain("!cancelled()");
expect(download?.if).toContain("needs.test-shard.result == 'success'");
expect(download?.if).toContain("needs.test-shard.result == 'failure'");
expect(upload).toBeGreaterThan(merge);
expect(gate).toBeGreaterThan(upload);
expect(steps[merge]?.if).not.toContain("needs.test-shard.result");
const merges = steps.filter((step) => step.run?.includes("--reporter=json"));
expect(merges).toHaveLength(2);
for (const step of merges)
for (const step of merges) {
expect(step.run).toContain(
"--reporter=./scripts/ci/test-timing-reporter.mjs",
);
expect(step.if).toContain("!cancelled()");
expect(step.if).toContain("needs.test-shard.result == 'success'");
expect(step.if).toContain("needs.test-shard.result == 'failure'");
}
expect(steps[upload]?.with?.path).toContain("test-timings.json");
expect(steps[upload]?.with).toMatchObject({ "include-hidden-files": true });
expect(steps[upload]?.with?.path).toContain(
@@ -245,6 +245,334 @@ describe("source-produced parser comparison known answers", () => {
});
});
describe("Notes export known-answer evaluation", () => {
it("grades real analysis and traces while rejecting false values and unsupported citations", async () => {
const root = await createTestTempDirectory("rea-known-answer-notes-");
const targets = await createAgentEvaluationFixtures(root, resolve("."));
const claims = agentFixtureClaims("notes-export", targets);
if (claims === undefined) throw new Error("Missing Notes rubric");
expect(claims.map(({ id }) => id)).toEqual([
"renderer_api",
"renderer_ipc",
"main_ipc",
"preload",
"feature_modules",
"semantic_boundary",
]);
const harness = await createApplicationMcpHarness();
try {
const workflow = await notesWorkflow(harness, targets.notesElectron);
assertNotesAnswerFailures(workflow, claims);
assertNotesUnsupportedClaims(workflow, claims);
await assertNotesSeedBinding(harness, workflow, claims);
} finally {
await harness.close();
}
});
it("binds Notes analysis and derived traces to the requested artifact path and bytes", async () => {
const root = await createTestTempDirectory(
"rea-known-answer-notes-identity-",
);
const targets = await createAgentEvaluationFixtures(root, resolve("."));
const claims = agentFixtureClaims("notes-export", targets);
if (claims === undefined) throw new Error("Missing Notes rubric");
const foreign = join(root, "other-notes.asar");
await cp(targets.notesElectron, foreign);
const harness = await createApplicationMcpHarness();
try {
for (const path of [foreign, targets.notesElectron]) {
if (path === targets.notesElectron) {
await appendFile(
join(root, "notes-electron/renderer.js"),
"\n// changed shipped bytes\n",
);
await createPackage(join(root, "notes-electron"), path);
}
const workflow = await notesWorkflow(harness, path);
const digest = notesEvidence(workflow, "analyze_javascript_application")
.subject?.digest.sha256;
expect(digest === targets.notesElectronSha256).toBe(path === foreign);
const assessment = grade(
workflow.events,
claimManifestByOperation(claims, workflow.evidence),
"analyze_javascript_application",
claims,
).factualAssessment;
expect(assessment.status).toBe("failed");
assertClaimFailure(
assessment,
"renderer_api",
"source_subject_mismatch",
);
for (const id of ["feature_modules", "semantic_boundary"])
assertClaimFailure(assessment, id, "source_links_mismatch");
}
} finally {
await harness.close();
}
});
});
type NotesWorkflow = Awaited<ReturnType<typeof notesWorkflow>>;
const notesEvidence = (
workflow: NotesWorkflow,
operation: string,
): Evidence => {
const evidence = workflow.evidence.get(operation);
if (evidence === undefined)
throw new Error(`Missing Notes ${operation} Evidence`);
return evidence;
};
const assertClaimFailure = (
assessment: ReturnType<typeof grade>["factualAssessment"],
id: string,
failure: string,
) => {
expect(assessment.claims).toContainEqual(
expect.objectContaining({
id,
failures: expect.arrayContaining([failure]),
}),
);
};
const assertNotesAnswerFailures = (
workflow: NotesWorkflow,
claims: readonly FixtureClaimExpectation[],
) => {
const manifest = claimManifestByOperation(claims, workflow.evidence);
const assess = (answer: unknown, events = workflow.events) =>
grade(events, answer, "analyze_javascript_application", claims)
.factualAssessment;
expect(assess(manifest).status).toBe("passed");
const cases = [
{
id: "renderer_ipc",
patch: {
value: {
channel: "notes:delete",
operation: "send",
source: "preload.js",
},
},
failure: "value_mismatch",
},
{
id: "semantic_boundary",
patch: {
value: { status: "found", coverage: "complete", relevant_unknowns: 0 },
},
failure: "value_mismatch",
},
{
id: "renderer_api",
patch: {
evidence_id: notesEvidence(workflow, "trace_application_feature")
.evidence_id,
},
failure: "source_operation_mismatch",
},
];
for (const { id, patch, failure } of cases) {
const answer = {
claims: manifest.claims.map((claim) =>
claim.id === id ? { ...claim, ...patch } : claim,
),
};
assertClaimFailure(assess(answer), id, failure);
}
const missingParent = assess(
manifest,
workflow.events.filter((event) => event.item.id !== "notes-analysis"),
);
for (const id of ["feature_modules", "semantic_boundary"])
assertClaimFailure(missingParent, id, "source_links_mismatch");
};
const assertNotesUnsupportedClaims = (
workflow: NotesWorkflow,
claims: readonly FixtureClaimExpectation[],
) => {
const manifest = claimManifestByOperation(claims, workflow.evidence);
const evidenceId = notesEvidence(
workflow,
"trace_application_feature",
).evidence_id;
const unsupportedRuntime = {
claims: [
...manifest.claims,
{
id: "runtime_export",
value: "A click wrote notes.csv to the actual Downloads directory.",
evidence_id: evidenceId,
authority: "controlled-replay",
confidence: "observed",
},
],
};
expect(
grade(
workflow.events,
unsupportedRuntime,
"analyze_javascript_application",
claims,
).factualAssessment.manifestFailures,
).toContain("unknown_claim:runtime_export");
const prose = evaluateCodexEvents(
[
...workflow.events,
{
type: "item.completed",
item: {
type: "agent_message",
text: `Observed Evidence ${evidenceId}: notes:export, preload.js and main.js do not exist. The export certainly executed and wrote notes.csv; runtime behavior is known without further observation.`,
},
},
],
"analyze_javascript_application",
{
requireEvidence: true,
requiredAnswerTermGroups: [["notes:export"], ["preload.js"], ["main.js"]],
fixtureClaims: claims,
},
);
expect(prose).toMatchObject({
finalCitesEvidence: true,
answerTermCoverageMet: true,
factualCorrectness: "failed",
});
expect(prose.factualAssessment.manifestFailures).toContain(
"invalid_final_claim_manifest",
);
};
const assertNotesSeedBinding = async (
harness: Awaited<ReturnType<typeof createApplicationMcpHarness>>,
workflow: NotesWorkflow,
claims: readonly FixtureClaimExpectation[],
) => {
const manifest = claimManifestByOperation(claims, workflow.evidence);
for (const match of ["contains", undefined] as const) {
const feature = await harness.client.callTool({
name: "trace_application_feature",
arguments: {
application: notesEvidence(workflow, "analyze_javascript_application"),
seed: {
kind: "channel",
value: "notes:export",
...(match === undefined ? {} : { match }),
case_sensitive: false,
},
direction: "both",
},
});
expect(feature.isError).not.toBe(true);
const answer = {
claims: manifest.claims.map((claim) =>
claim.id === "feature_modules"
? { ...claim, evidence_id: resultEvidence(feature).evidence_id }
: claim,
),
};
const assessment = grade(
[
...workflow.events,
toolEvent(
`notes-feature-${match ?? "default"}`,
"trace_application_feature",
feature,
),
],
answer,
"analyze_javascript_application",
claims,
).factualAssessment;
expect(assessment.status).toBe(match === undefined ? "passed" : "failed");
if (match === "contains") {
assertClaimFailure(
assessment,
"feature_modules",
"source_assertion_mismatch",
);
expect(
assessment.claims.find(({ id }) => id === "feature_modules")?.failures,
).not.toContain("source_parameters_mismatch");
}
}
};
const notesWorkflow = async (
harness: Awaited<ReturnType<typeof createApplicationMcpHarness>>,
inputPath: string,
) => {
const analysis = await harness.client.callTool({
name: "analyze_javascript_application",
arguments: { input_path: inputPath },
});
expect(analysis.isError).not.toBe(true);
const application = resultEvidence(analysis);
const feature = await harness.client.callTool({
name: "trace_application_feature",
arguments: {
application,
seed: {
kind: "channel",
value: "notes:export",
match: "exact",
case_sensitive: false,
},
direction: "both",
},
});
expect(feature.isError).not.toBe(true);
const semantic = await harness.client.callTool({
name: "trace_javascript_semantics",
arguments: {
application,
query: {
seed: { kind: "property", name: "invoke" },
direction: "forward-influence",
include_ambiguous_dynamic_edges: false,
expected: null,
},
},
});
expect(semantic.isError).not.toBe(true);
return {
events: [
toolEvent("notes-analysis", "analyze_javascript_application", analysis),
toolEvent("notes-feature", "trace_application_feature", feature),
toolEvent("notes-semantic", "trace_javascript_semantics", semantic),
],
evidence: new Map<string, Evidence>([
["analyze_javascript_application", application],
["trace_application_feature", resultEvidence(feature)],
["trace_javascript_semantics", resultEvidence(semantic)],
]),
};
};
const claimManifestByOperation = (
claims: readonly FixtureClaimExpectation[],
evidenceByOperation: ReadonlyMap<string, Evidence>,
) => ({
claims: claims.map(({ id, expectedValue, source }) => {
const evidence = evidenceByOperation.get(source.operation);
if (evidence === undefined)
throw new Error(`Missing producer for claim ${id}`);
return {
id,
value: expectedValue,
evidence_id: evidence.evidence_id,
authority: evidence.authority,
confidence: evidence.confidence,
};
}),
});
const resultEvidence = (result: {
readonly structuredContent?: unknown;
}): Evidence => parseEvidence(result.structuredContent);
@@ -215,6 +215,97 @@ it("advertises exact valid schemas and records inline WASM interface evidence wi
expect(close).toHaveBeenCalled();
});
it("attributes rejected WASM paths to their selected fields before provider work", async () => {
const value = wasmArtifactFixture();
value.glue = [
{
artifact: { path: "/glue.js", sha256: "b".repeat(64), bytes: 0 },
parse_status: "complete",
references: [],
},
];
value.candidates.push({
path: "/other.wasm",
sha256: "c".repeat(64),
bytes: 8,
});
const validInput = {
path: value.artifact.path,
glue_paths: ["/glue.js"],
candidate_paths: ["/other.wasm"],
};
const inspect = vi.fn(() =>
Promise.resolve(ok(wasmArtifactExecution(value))),
);
const harness = await createApplicationMcpHarness({
serverOptions: {
wasmArtifact: new WasmArtifactService({
close: () => Promise.resolve(),
inspect,
}),
},
});
onTestFinished(harness.close);
for (const [input, paths] of [
[{ path: "relative.wasm" }, [["path"]]],
[
{ path: value.artifact.path, glue_paths: ["relative.js"] },
[["glue_paths", 0]],
],
[
{ path: value.artifact.path, candidate_paths: ["relative.wasm"] },
[["candidate_paths", 0]],
],
[
{
path: value.artifact.path,
glue_paths: ["/glue.js", "relative.js"],
candidate_paths: ["/other.wasm", "relative.wasm"],
},
[
["glue_paths", 1],
["candidate_paths", 1],
],
],
[
{
path: "relative.wasm",
glue_paths: ["relative.js"],
candidate_paths: ["relative-candidate.wasm"],
},
[["path"], ["glue_paths", 0], ["candidate_paths", 0]],
],
] as const) {
const response = await harness.client.callTool({
name: "inspect_wasm_artifact",
arguments: input,
});
const failure = parseMcpToolError(response).error;
expect(failure.code).toBe("invalid_request");
const { issues } = z
.object({ issues: z.array(z.unknown()) })
.parse(failure.details);
expect(issues).toEqual(
paths.map((path) => ({
path,
reason: "invalid_format",
message: "Expected an absolute filesystem path on this host.",
})),
);
}
expect(inspect).not.toHaveBeenCalled();
const valid = await harness.client.callTool({
name: "inspect_wasm_artifact",
arguments: validInput,
});
expect(valid.isError).not.toBe(true);
expect(inspect).toHaveBeenCalledExactlyOnceWith(
validInput,
expect.objectContaining({ signal: expect.any(AbortSignal) }),
);
await harness.client.ping();
});
it("rejects a WASM provider response with a non-file subject before recording Evidence", async () => {
const value = wasmArtifactFixture();
const execution = wasmArtifactExecution(value);
@@ -140,6 +140,8 @@ const managedTranscript = () => {
javascriptSha256: "",
javascriptShapeLeft: "",
javascriptShapeRight: "",
notesElectron: "",
notesElectronSha256: "",
managed: target.path,
managedSha256: target.sha256,
});