Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11d295eeab | ||
|
|
a9195c3a98 | ||
|
|
4758ba30b6 | ||
|
|
0d31902b7f | ||
|
|
ccc009412e | ||
|
|
0d2bb5fb09 | ||
|
|
47dd84ea69 | ||
|
|
4e829cea2f | ||
|
|
bf30b081db | ||
|
|
15efa61149 | ||
|
|
70435de2c6 | ||
|
|
1b3371ad7f | ||
|
|
c7fdd7e153 | ||
|
|
3c794a5cc0 | ||
|
|
16caa5ca9b | ||
|
|
2a6bef5589 | ||
|
|
cace19caad | ||
|
|
87b7bf8db1 | ||
|
|
3b66532e26 | ||
|
|
3ad063b454 | ||
|
|
69485e27f7 |
@@ -209,9 +209,11 @@ native/runtime questions retain their own authority and coverage.
|
||||
Names and semantic labels do not establish durable member identity. Comparison
|
||||
preserves observed differences, structural inferences, and unknowns separately.
|
||||
|
||||
Cross-version matching first prefers exact CIL/signature identity. When that
|
||||
does not match, it pairs an exact declared type, method name, and raw signature
|
||||
before trying structural body shape. The exact-signature key uses names only
|
||||
Cross-version matching first prefers exact raw CIL/signature identity, with
|
||||
equal decoded fingerprints. Equal decoded fingerprints alone cannot establish
|
||||
an exact match: they omit switch targets and collapse distinct NaN encodings.
|
||||
When that does not match, it pairs an exact declared type, method name, and raw
|
||||
signature before trying structural body shape. The exact-signature key uses names only
|
||||
as part of that full tuple; names alone never select a pair. Duplicate tuples
|
||||
remain ambiguous rather than being paired by token order. Fields follow the
|
||||
same exact tiers. The raw signature is exact whether or not REA decoded it, so
|
||||
@@ -222,8 +224,11 @@ unpaired member, one-sided or ambiguous, with the same declared type and name
|
||||
whose signature was not decoded.
|
||||
|
||||
For a matched method, an unavailable or partial body makes body-shape facets
|
||||
unknown while preserving observed signature differences. Structural identity
|
||||
compares normalized signatures, the limited decoded-CIL tuple fingerprint,
|
||||
unknown while preserving observed signature differences. For complete bodies,
|
||||
a difference in either the raw CIL digest or the decoded fingerprint is reported
|
||||
in the `cil` dimension, including reordered switch targets with equal decoded
|
||||
fingerprints. Structural identity compares normalized signatures, the limited
|
||||
decoded-CIL tuple fingerprint,
|
||||
constants, and bounded shape context. It reports all candidates at the winning
|
||||
score and remains ambiguous when the evidence does not distinguish them. The
|
||||
digest does not itself remap metadata tokens.
|
||||
@@ -360,8 +365,8 @@ Opcode names retain short/long forms and prefix opcodes. This makes the value a
|
||||
reproducible decoded-tuple fingerprint, not a canonical semantic CIL identity:
|
||||
it does not resolve tokens across MVIDs, does not commit full control flow or
|
||||
exception semantics, and does not replace the raw CIL digest. Cross-build
|
||||
comparison uses the value only as one unique-only structural tier and reports
|
||||
ambiguity when that evidence is insufficient.
|
||||
comparison requires equal raw CIL digests as well for exact CIL/signature
|
||||
matches and reports ambiguity when structural evidence is insufficient.
|
||||
|
||||
For example, a decoded `nop; ret` body serializes exactly as
|
||||
`[["nop","none",null],["ret","none",null]]` and has digest
|
||||
|
||||
@@ -8,4 +8,10 @@ export function assertLinuxPackageProviderFailure(execution: {
|
||||
export function verifyPackagePlatform(input: {
|
||||
readonly cli: string;
|
||||
readonly environment: Readonly<Record<string, string | undefined>>;
|
||||
readonly platform?: string;
|
||||
readonly runCommand?: (
|
||||
command: string,
|
||||
arguments_: readonly string[],
|
||||
environment: Readonly<Record<string, string | undefined>>,
|
||||
) => Promise<string>;
|
||||
}): Promise<void>;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { json, run, runWithStatus } from "./lib/verify-package-core.mjs";
|
||||
|
||||
/** Run platform-specific deep-analysis smoke tests. */
|
||||
export async function verifyPackagePlatform({ cli, environment }) {
|
||||
if (process.platform === "linux") {
|
||||
export async function verifyPackagePlatform({
|
||||
cli,
|
||||
environment,
|
||||
platform = process.platform,
|
||||
runCommand = run,
|
||||
}) {
|
||||
if (platform === "linux") {
|
||||
const unsupportedExecution = await runWithStatus(
|
||||
cli,
|
||||
["analyze", process.execPath, "--json"],
|
||||
@@ -11,25 +16,27 @@ export async function verifyPackagePlatform({ cli, environment }) {
|
||||
assertLinuxPackageProviderFailure(unsupportedExecution);
|
||||
} else {
|
||||
const overview = json(
|
||||
await run(cli, ["analyze", process.execPath, "--json"], environment),
|
||||
await runCommand(
|
||||
cli,
|
||||
["analyze", process.execPath, "--json"],
|
||||
environment,
|
||||
),
|
||||
);
|
||||
if (
|
||||
overview.operation !== "binary_overview" ||
|
||||
overview.normalized_result?.procedure_count < 1
|
||||
)
|
||||
if (!hasPositiveProcedureCount(overview))
|
||||
throw new Error(
|
||||
`packaged Hopper-backed analyze CLI failed: ${JSON.stringify(overview)}`,
|
||||
);
|
||||
const inspected = json(
|
||||
await run(cli, ["inspect", process.execPath, "--json"], environment),
|
||||
await runCommand(
|
||||
cli,
|
||||
["inspect", process.execPath, "--json"],
|
||||
environment,
|
||||
),
|
||||
);
|
||||
if (
|
||||
inspected.operation !== "binary_overview" ||
|
||||
inspected.normalized_result?.procedure_count < 1
|
||||
)
|
||||
if (!hasPositiveProcedureCount(inspected))
|
||||
throw new Error("packaged inspect CLI failed");
|
||||
const functionResult = json(
|
||||
await run(
|
||||
await runCommand(
|
||||
cli,
|
||||
["function", process.execPath, "0x1000", "--json"],
|
||||
environment,
|
||||
@@ -44,7 +51,7 @@ export async function verifyPackagePlatform({ cli, environment }) {
|
||||
`packaged function CLI failed: ${JSON.stringify(functionResult)}`,
|
||||
);
|
||||
const xrefs = json(
|
||||
await run(
|
||||
await runCommand(
|
||||
cli,
|
||||
["xrefs", process.execPath, "0x1000", "--json"],
|
||||
environment,
|
||||
@@ -56,7 +63,7 @@ export async function verifyPackagePlatform({ cli, environment }) {
|
||||
)
|
||||
throw new Error(`packaged xrefs CLI failed: ${JSON.stringify(xrefs)}`);
|
||||
const trace = json(
|
||||
await run(
|
||||
await runCommand(
|
||||
cli,
|
||||
["trace", process.execPath, "fixture", "--json"],
|
||||
environment,
|
||||
@@ -93,3 +100,10 @@ export const assertLinuxPackageProviderFailure = (execution) => {
|
||||
`packaged Linux Hopper verification did not fail closed: ${JSON.stringify(failure)}`,
|
||||
);
|
||||
};
|
||||
|
||||
const hasPositiveProcedureCount = (result) =>
|
||||
typeof result === "object" &&
|
||||
result !== null &&
|
||||
result.operation === "binary_overview" &&
|
||||
Number.isInteger(result.normalized_result?.procedure_count) &&
|
||||
result.normalized_result.procedure_count > 0;
|
||||
|
||||
@@ -162,14 +162,21 @@ const resolveSourceHead = async (
|
||||
// The library probes readFile() without a path to detect promise APIs.
|
||||
// Reject that invocation without attributing it to a source read.
|
||||
if (path === undefined)
|
||||
throw new TypeError("Git filesystem read requires a path");
|
||||
throw new TypeError("Git metadata path is required");
|
||||
try {
|
||||
const bytes = await readMetadataBytes(path);
|
||||
const encoding =
|
||||
typeof options === "string" ? options : options?.encoding;
|
||||
return encoding === undefined || encoding === null
|
||||
? bytes
|
||||
: bytes.toString(encoding);
|
||||
typeof options === "string"
|
||||
? options
|
||||
: typeof options === "object" &&
|
||||
options !== null &&
|
||||
"encoding" in options
|
||||
? options.encoding
|
||||
: undefined;
|
||||
if (encoding === undefined || encoding === null) return bytes;
|
||||
if (typeof encoding !== "string" || !Buffer.isEncoding(encoding))
|
||||
throw new TypeError("Unsupported Git metadata text encoding");
|
||||
return bytes.toString(encoding);
|
||||
} catch (cause: unknown) {
|
||||
if (cause instanceof ArtifactReaderFailure) {
|
||||
cleanupFailures.push(cause);
|
||||
|
||||
@@ -43,10 +43,7 @@ import {
|
||||
type WebScreenshotDiff,
|
||||
} from "../domain/webScreenshot.js";
|
||||
import { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
||||
import { BrowserObservationError } from "../domain/browserObservationError.js";
|
||||
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
||||
import type { BrowserObservationOperation } from "../domain/browserObservationErrors.js";
|
||||
@@ -167,8 +164,6 @@ export class CdpBrowserProvider implements BrowserObservationPort {
|
||||
),
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
if (options.signal?.aborted === true)
|
||||
return err(new AnalysisCancelledError("analyze_web_bundle", { cause }));
|
||||
return err(providerError(cause, "analyze_web_bundle"));
|
||||
}
|
||||
}
|
||||
@@ -340,10 +335,6 @@ const withTargetSession = async <
|
||||
);
|
||||
// An unadopted browser socket is closed here; closing is idempotent.
|
||||
await endpoint.connection?.close();
|
||||
// Cleanup can observe an interrupt after work returns. Do not publish its
|
||||
// result once cancellation arrived while the target session was closing.
|
||||
if (options.signal?.aborted === true)
|
||||
throw new AnalysisCancelledError(operation);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -73,7 +73,11 @@ const methodDimensions = (
|
||||
if (bothAbsent) return { dimensions, bodyUnknown: false };
|
||||
if (!leftBodyComplete || !rightBodyComplete)
|
||||
return { dimensions, bodyUnknown: true };
|
||||
if (left.body.normalized_il_sha256 !== right.body.normalized_il_sha256)
|
||||
// The decoded fingerprint omits switch targets and is not exact CIL identity.
|
||||
if (
|
||||
left.body.il_sha256 !== right.body.il_sha256 ||
|
||||
left.body.normalized_il_sha256 !== right.body.normalized_il_sha256
|
||||
)
|
||||
dimensions.push("cil");
|
||||
if (
|
||||
canonicalize(left.body.opcode_counts) !==
|
||||
|
||||
@@ -49,10 +49,13 @@ interface Ambiguous<Item> {
|
||||
const keyMethod = (item: Method): Keyed<Method> => ({
|
||||
item,
|
||||
exactKey:
|
||||
item.body.status === "present" && item.body.normalized_il_sha256 !== null
|
||||
item.body.status === "present" &&
|
||||
item.body.il_sha256 !== null &&
|
||||
item.body.normalized_il_sha256 !== null
|
||||
? sha256([
|
||||
"method-exact",
|
||||
item.signature.raw_sha256,
|
||||
item.body.il_sha256,
|
||||
item.body.normalized_il_sha256,
|
||||
])
|
||||
: null,
|
||||
|
||||
@@ -7,205 +7,12 @@ import {
|
||||
} from "../../../src/domain/browserObservation.js";
|
||||
import { analyzeWebBundleInputSchema } from "../../../src/domain/webBundleAnalysis.js";
|
||||
import { observeWebSessionInputSchema } from "../../../src/domain/browserSession.js";
|
||||
import { discoverWebMcpToolsInputSchema } from "../../../src/domain/webMcpDiscovery.js";
|
||||
import { captureWebScreenshotInputSchema } from "../../../src/domain/webScreenshot.js";
|
||||
import {
|
||||
startFakeCdpBrowser,
|
||||
type FakeCdpBrowser,
|
||||
} from "../../fixtures/fakeCdpBrowser.js";
|
||||
import { trackBrowser } from "./cdpBrowserProvider.support.js";
|
||||
|
||||
const waitFor = async (ready: () => boolean, description: string) => {
|
||||
const deadline = Date.now() + 5_000;
|
||||
while (!ready()) {
|
||||
if (Date.now() > deadline) throw new Error(description);
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
};
|
||||
|
||||
describe("CdpBrowserProvider: cancellation during cleanup", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "WebMCP discovery",
|
||||
operation: "discover_webmcp_tools",
|
||||
invoke: (
|
||||
provider: CdpBrowserProvider,
|
||||
browser: FakeCdpBrowser,
|
||||
signal: AbortSignal,
|
||||
) =>
|
||||
provider.discoverWebMcpTools(
|
||||
discoverWebMcpToolsInputSchema.parse({
|
||||
cdp_endpoint: browser.endpoint,
|
||||
allowed_origins: [browser.allowedOrigin],
|
||||
target_id: "allowed-page",
|
||||
observation_ms: 0,
|
||||
}),
|
||||
{ signal },
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "screenshot capture",
|
||||
operation: "capture_web_screenshot",
|
||||
invoke: (
|
||||
provider: CdpBrowserProvider,
|
||||
browser: FakeCdpBrowser,
|
||||
signal: AbortSignal,
|
||||
) =>
|
||||
provider.captureScreenshot(
|
||||
captureWebScreenshotInputSchema.parse({
|
||||
cdp_endpoint: browser.endpoint,
|
||||
allowed_origins: [browser.allowedOrigin],
|
||||
target_id: "allowed-page",
|
||||
}),
|
||||
{ signal },
|
||||
),
|
||||
},
|
||||
])(
|
||||
"reports cancellation during $name cleanup",
|
||||
async ({ operation, invoke }) => {
|
||||
const browser = await startFakeCdpBrowser({
|
||||
hangOnMethod: "Page.disable",
|
||||
webMcpTools: true,
|
||||
});
|
||||
trackBrowser(browser);
|
||||
const controller = new AbortController();
|
||||
const pending = invoke(
|
||||
new CdpBrowserProvider(),
|
||||
browser,
|
||||
controller.signal,
|
||||
);
|
||||
await waitFor(
|
||||
() => browser.commands.some(({ method }) => method === "Page.disable"),
|
||||
"page cleanup did not start",
|
||||
);
|
||||
controller.abort();
|
||||
await expect(pending).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisCancelledError", operation },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("reports cancellation when an interrupt arrives during page cleanup", async () => {
|
||||
const browser = await startFakeCdpBrowser({
|
||||
hangOnMethod: "Network.disable",
|
||||
});
|
||||
trackBrowser(browser);
|
||||
const controller = new AbortController();
|
||||
const pending = new CdpBrowserProvider().inspectPage(
|
||||
inspectWebPageInputSchema.parse({
|
||||
cdp_endpoint: browser.endpoint,
|
||||
allowed_origins: [browser.allowedOrigin],
|
||||
target_id: "allowed-page",
|
||||
observation_ms: 0,
|
||||
}),
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
await waitFor(
|
||||
() => browser.commands.some(({ method }) => method === "Network.disable"),
|
||||
"page cleanup did not start",
|
||||
);
|
||||
controller.abort();
|
||||
|
||||
await expect(pending).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisCancelledError",
|
||||
operation: "inspect_web_page",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("reports cancellation when an interrupt arrives during session cleanup", async () => {
|
||||
const browser = await startFakeCdpBrowser({
|
||||
hangOnMethod: "Network.disable",
|
||||
});
|
||||
trackBrowser(browser);
|
||||
const controller = new AbortController();
|
||||
const pending = new CdpBrowserProvider().observeSession(
|
||||
observeWebSessionInputSchema.parse({
|
||||
cdp_endpoint: browser.endpoint,
|
||||
allowed_origins: [browser.allowedOrigin],
|
||||
target_id: "allowed-page",
|
||||
observation_ms: 1,
|
||||
}),
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
await waitFor(
|
||||
() => browser.commands.some(({ method }) => method === "Network.disable"),
|
||||
"session cleanup did not start",
|
||||
);
|
||||
controller.abort();
|
||||
|
||||
await expect(pending).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisCancelledError",
|
||||
operation: "observe_web_session",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("reports cancellation when an interrupt arrives during source-map download", async () => {
|
||||
const browser = await startFakeCdpBrowser({ hangSourceMap: true });
|
||||
trackBrowser(browser);
|
||||
const controller = new AbortController();
|
||||
const pending = new CdpBrowserProvider().analyzeBundle(
|
||||
analyzeWebBundleInputSchema.parse({
|
||||
cdp_endpoint: browser.endpoint,
|
||||
allowed_origins: [browser.allowedOrigin],
|
||||
target_id: "allowed-page",
|
||||
observation_ms: 0,
|
||||
fetch_source_maps: true,
|
||||
}),
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
await waitFor(
|
||||
() =>
|
||||
browser.httpRequests.some(({ url }) => url.startsWith("/app.js.map")),
|
||||
"source-map download did not start",
|
||||
);
|
||||
controller.abort();
|
||||
|
||||
await expect(pending).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisCancelledError",
|
||||
operation: "analyze_web_bundle",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("classifies custom source-map abort reasons as cancellation", async () => {
|
||||
const browser = await startFakeCdpBrowser({ hangSourceMap: true });
|
||||
trackBrowser(browser);
|
||||
const controller = new AbortController();
|
||||
const pending = new CdpBrowserProvider().analyzeBundle(
|
||||
analyzeWebBundleInputSchema.parse({
|
||||
cdp_endpoint: browser.endpoint,
|
||||
allowed_origins: [browser.allowedOrigin],
|
||||
target_id: "allowed-page",
|
||||
observation_ms: 0,
|
||||
fetch_source_maps: true,
|
||||
}),
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
await waitFor(
|
||||
() =>
|
||||
browser.httpRequests.some(({ url }) => url.startsWith("/app.js.map")),
|
||||
"source-map download did not start",
|
||||
);
|
||||
controller.abort(new Error("stopped"));
|
||||
await expect(pending).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisCancelledError",
|
||||
operation: "analyze_web_bundle",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("CdpBrowserProvider: cancellation and session events", () => {
|
||||
it("honors cancellation during the final progress callback before publishing inspection", async () => {
|
||||
const browser = await startFakeCdpBrowser();
|
||||
|
||||
@@ -73,13 +73,7 @@ it("releases cleanup when cancellation arrives after capture completes", async (
|
||||
});
|
||||
controller.abort();
|
||||
const result = await pending;
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisCancelledError",
|
||||
operation: "observe_web_session",
|
||||
},
|
||||
});
|
||||
expect(result.ok).toBe(true);
|
||||
expect(browser.commands.map(({ method }) => method)).toContain(
|
||||
"Target.detachFromTarget",
|
||||
);
|
||||
|
||||
@@ -8,14 +8,14 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"rejects malformed oversized JSON through both CLI and Evidence readers",
|
||||
"rejects malformed oversized input through CLI and Evidence readers",
|
||||
async ({ cli, processes }) => {
|
||||
const root = await createTestTempDirectory("rea-json-string-limit-");
|
||||
const input = join(root, "oversized.json");
|
||||
const file = await open(input, "wx");
|
||||
try {
|
||||
// Sparse zero bytes are valid UTF-8 but invalid JSON. Both readers
|
||||
// stream the input and reject the syntax before assembling a value.
|
||||
// Sparse zero bytes are valid UTF-8. Both streaming readers reject
|
||||
// their invalid JSON syntax without assembling a whole-document string.
|
||||
await file.truncate(constants.MAX_STRING_LENGTH + 1);
|
||||
} finally {
|
||||
await file.close();
|
||||
@@ -175,7 +175,7 @@ cliTest(
|
||||
|
||||
cliTest(
|
||||
"returns a typed string assembly constraint under a bounded heap",
|
||||
async ({ cli }) => {
|
||||
async ({ cli, processes }) => {
|
||||
const root = await createTestTempDirectory("rea-json-string-headroom-");
|
||||
const input = join(root, "long-string.json");
|
||||
const file = await open(input, "wx");
|
||||
@@ -207,5 +207,21 @@ cliTest(
|
||||
action: expect.stringContaining("re-analyze a smaller selection"),
|
||||
},
|
||||
});
|
||||
const evidence = await processes.run(process.execPath, [
|
||||
"--max-old-space-size=768",
|
||||
"--input-type=module",
|
||||
"-e",
|
||||
`import { readJsonFile } from "./dist/application/JsonFiles.js";
|
||||
import { projectAnalysisError } from "./dist/domain/analysisErrorProjection.js";
|
||||
const result = await readJsonFile(process.argv[1]);
|
||||
console.log(JSON.stringify(result.ok ? result : projectAnalysisError(result.error)));`,
|
||||
input,
|
||||
]);
|
||||
expect(JSON.parse(evidence.stdout)).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
details: { operation: "read_evidence_file", resource: "memory" },
|
||||
});
|
||||
expect(evidence.stderr).toBe("");
|
||||
expect(evidence.exitCode).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
+16
-12
@@ -72,17 +72,8 @@ describe("static Electron application analysis", () => {
|
||||
|
||||
const result = await reconstructJavaScriptArtifact({ input_path: root });
|
||||
const graph = parseJavaScriptApplicationGraph(result.graph);
|
||||
// Computed access keeps no known members; the unknown access is an
|
||||
// explicit dynamic flag rather than a "*" sentinel inside members.
|
||||
const accesses = graph.nodes.flatMap((node) =>
|
||||
node.kind === "native-export"
|
||||
? node.observations.map(({ properties }) => ({
|
||||
members: Array.isArray(properties.requested_members)
|
||||
? properties.requested_members
|
||||
: [],
|
||||
dynamicMemberAccess: properties.dynamic_member_access ?? false,
|
||||
}))
|
||||
: [],
|
||||
const nativeBindings = graph.nodes.filter(
|
||||
(node) => node.kind === "native-export",
|
||||
);
|
||||
|
||||
expect(result.electron_summary).toMatchObject({
|
||||
@@ -90,7 +81,20 @@ describe("static Electron application analysis", () => {
|
||||
native_addon_bindings: 1,
|
||||
resolved_native_addon_bindings: 1,
|
||||
});
|
||||
expect(accesses).toEqual([{ members: [], dynamicMemberAccess: true }]);
|
||||
expect(nativeBindings).toMatchObject([
|
||||
{
|
||||
observations: [
|
||||
{
|
||||
properties: {
|
||||
requested_members: [],
|
||||
namespace_access: false,
|
||||
dynamic_member_access: true,
|
||||
native_export_verification: "not-performed",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("maps Electron boundaries through bundler-renamed bindings", async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
@@ -108,3 +108,33 @@ it("reads a linked worktree's own branch and detached HEAD through shared Git re
|
||||
dirty: null,
|
||||
});
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"keeps unreadable loose refs unknown instead of falling back to packed refs",
|
||||
async () => {
|
||||
const root = await createTestTempDirectory("rea-reference-loose-ref-");
|
||||
const gitdir = join(root, ".git");
|
||||
const looseRef = join(gitdir, "refs", "heads", "main");
|
||||
await mkdir(join(gitdir, "refs", "heads"), { recursive: true });
|
||||
const head = "1234567890abcdef1234567890abcdef12345678";
|
||||
await writeFile(join(gitdir, "HEAD"), "ref: refs/heads/main\n");
|
||||
await writeFile(join(gitdir, "packed-refs"), `${head} refs/heads/main\n`);
|
||||
expect(await readVcs(root)).toEqual({
|
||||
kind: "git",
|
||||
head,
|
||||
dirty: null,
|
||||
});
|
||||
if (process.getuid?.() === 0) return;
|
||||
await writeFile(looseRef, `${head}\n`);
|
||||
await chmod(looseRef, 0);
|
||||
try {
|
||||
expect(await readVcs(root)).toEqual({
|
||||
kind: "unknown",
|
||||
head: null,
|
||||
dirty: null,
|
||||
});
|
||||
} finally {
|
||||
await chmod(looseRef, 0o600);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -116,6 +116,63 @@ describe("managed member comparison", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed member comparison with equal decoded fingerprints", () => {
|
||||
it.each([
|
||||
{ name: "Main", basis: "exact-signature" },
|
||||
{ name: "Renamed", basis: "structural-method-shape" },
|
||||
])("detects reordered switch targets in $name", ({ name, basis }) => {
|
||||
const observe = (targets: readonly number[], methodName: string) => {
|
||||
// ldarg.0; switch (case 0, case 1); ldc.i4.0; ret; ldc.i4.1; ret.
|
||||
// Both cases target instruction starts, relative to the end of the table.
|
||||
const il = Buffer.alloc(18);
|
||||
il[0] = 0x02;
|
||||
il[1] = 0x45;
|
||||
il.writeUInt32LE(2, 2);
|
||||
targets.forEach((target, index) =>
|
||||
il.writeInt32LE(target, 6 + index * 4),
|
||||
);
|
||||
Buffer.from([0x16, 0x2a, 0x17, 0x2a]).copy(il, 14);
|
||||
return inspect(
|
||||
buildManagedPeFixture({
|
||||
methodName,
|
||||
methodSignature: Buffer.from([0x00, 0x01, 0x08, 0x08]),
|
||||
ilBody: Buffer.concat([Buffer.from([(il.length << 2) | 2]), il]),
|
||||
}),
|
||||
`/tmp/${methodName}-${targets.join("-")}.dll`,
|
||||
);
|
||||
};
|
||||
const left = observe([0, 2], "Main");
|
||||
const right = observe([2, 0], name);
|
||||
const leftBody = left.result.methods[0]?.body;
|
||||
const rightBody = right.result.methods[0]?.body;
|
||||
expect(leftBody?.status).toBe("present");
|
||||
expect(rightBody?.status).toBe("present");
|
||||
expect(leftBody?.normalized_il_sha256).toBe(
|
||||
rightBody?.normalized_il_sha256,
|
||||
);
|
||||
expect(leftBody?.il_sha256).not.toBe(rightBody?.il_sha256);
|
||||
|
||||
const result = compareManagedMembers(left, right);
|
||||
expect(result.matching.exact_il_signature).toBe(0);
|
||||
expect(result.methods[0]).toMatchObject({
|
||||
status: "changed",
|
||||
match: { status: "matched", basis },
|
||||
dimensions: ["cil"],
|
||||
});
|
||||
expect(
|
||||
compareManagedMembers(right, observe([2, 0], name)).methods[0],
|
||||
).toMatchObject({
|
||||
status: "unchanged",
|
||||
match: {
|
||||
status: "matched",
|
||||
basis: "exact-il-signature",
|
||||
confidence: "exact",
|
||||
},
|
||||
dimensions: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed member comparison floating-point constants", () => {
|
||||
it.each([
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { assertLinuxPackageProviderFailure } from "../../../scripts/verify-package-platform.mjs";
|
||||
import {
|
||||
assertLinuxPackageProviderFailure,
|
||||
verifyPackagePlatform,
|
||||
} from "../../../scripts/verify-package-platform.mjs";
|
||||
|
||||
const missingDisplay = {
|
||||
code: "provider_unavailable",
|
||||
@@ -22,6 +25,37 @@ const execution = (failure: unknown, status = 1) => ({
|
||||
stdout: JSON.stringify(failure),
|
||||
});
|
||||
|
||||
const overview = (procedure_count?: unknown, operation = "binary_overview") => ({
|
||||
operation,
|
||||
...(procedure_count === undefined
|
||||
? {}
|
||||
: { normalized_result: { procedure_count } }),
|
||||
});
|
||||
|
||||
const verifyMacOS = async (
|
||||
analyze: unknown,
|
||||
inspect: unknown,
|
||||
): Promise<void> => {
|
||||
const outputs: Readonly<Record<string, unknown>> = {
|
||||
analyze,
|
||||
inspect,
|
||||
function: {
|
||||
operation: "analyze_function",
|
||||
provider: { id: "hopper" },
|
||||
normalized_result: { procedure: { address: "0x1000" } },
|
||||
},
|
||||
xrefs: { operation: "xrefs", normalized_result: ["0x1000"] },
|
||||
trace: { operation: "trace_feature" },
|
||||
};
|
||||
await verifyPackagePlatform({
|
||||
cli: "fixture-cli",
|
||||
environment: {},
|
||||
platform: "darwin",
|
||||
runCommand: async (_cli, arguments_) =>
|
||||
JSON.stringify(outputs[arguments_[0]!]) ?? "",
|
||||
});
|
||||
};
|
||||
|
||||
describe("packaged Linux failure verification", () => {
|
||||
it("accepts the unsupported fixture build or an exact missing display dependency", () => {
|
||||
expect(() =>
|
||||
@@ -71,3 +105,29 @@ describe("packaged Linux failure verification", () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("packaged analyze and inspect result guards", () => {
|
||||
it("accepts positive integer procedure counts for both commands", async () => {
|
||||
await verifyMacOS(overview(1), overview(2));
|
||||
});
|
||||
|
||||
const malformed = [
|
||||
["missing result", undefined],
|
||||
["null result", null],
|
||||
["missing normalized count", overview()],
|
||||
["zero count", overview(0)],
|
||||
["non-integer count", overview(1.5)],
|
||||
["wrong operation", overview(2, "other_operation")],
|
||||
] as const;
|
||||
|
||||
it.each(["analyze", "inspect"] as const)(
|
||||
"rejects malformed %s results before trusting the package",
|
||||
async (command) => {
|
||||
for (const [label, invalid] of malformed) {
|
||||
const analyze = command === "analyze" ? invalid : overview(4);
|
||||
const inspect = command === "inspect" ? invalid : overview(4);
|
||||
await expect(verifyMacOS(analyze, inspect), label).rejects.toThrow();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Vendored
-5
@@ -229,11 +229,6 @@ const serveSourceMap = (
|
||||
response: ServerResponse,
|
||||
options: FakeOptions,
|
||||
): boolean => {
|
||||
if (
|
||||
options.hangSourceMap === true &&
|
||||
request.url?.startsWith("/app.js.map") === true
|
||||
)
|
||||
return true;
|
||||
if (options.sourceMapBody === undefined) return false;
|
||||
if (request.url?.startsWith("/app.js.map") === true) {
|
||||
if (options.sourceMapRedirect !== undefined)
|
||||
|
||||
-2
@@ -95,8 +95,6 @@ export interface FakeOptions {
|
||||
readonly webSocketWithoutFrames?: boolean;
|
||||
readonly invalidBinaryWebSocketEvent?: boolean;
|
||||
readonly sourceMapBody?: string;
|
||||
/** Accept a source-map request and leave the response open. */
|
||||
readonly hangSourceMap?: boolean;
|
||||
readonly sourceMapRedirect?: string;
|
||||
readonly sessionTimeline?:
|
||||
| "same_origin"
|
||||
|
||||
@@ -319,6 +319,8 @@ describe("updated executable maintenance planning", () => {
|
||||
it("runs real setup as a noninteractive dry run and creates no new integrations or backups", async () => {
|
||||
const configPath = await writeClient("codex", staleCodex);
|
||||
const untouched = await writeClient("claude_code", '{"mcpServers":{}}');
|
||||
const skillContent = `---\nname: ${PRODUCT_IDENTITY.skillName}\nmetadata:\n version: "1"\n---\n# REA\n`;
|
||||
const skillPath = await writeSkill(skillContent);
|
||||
vi.stubEnv("npm_command", "exec");
|
||||
const result = await planIntegrationMaintenance(
|
||||
home,
|
||||
@@ -328,18 +330,35 @@ describe("updated executable maintenance planning", () => {
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
status: "planned",
|
||||
scope: { clients: ["codex"], skill: false },
|
||||
plannedActions: [
|
||||
{ id: "configure_client:codex", kind: "configure_client" },
|
||||
],
|
||||
scope: {
|
||||
clients: ["codex"],
|
||||
skill: true,
|
||||
skillDestinations: [{ client: "shared", path: dirname(skillPath) }],
|
||||
},
|
||||
});
|
||||
if (result.status !== "planned") throw new Error(JSON.stringify(result));
|
||||
expect(result.plannedActions).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "configure_client:codex",
|
||||
kind: "configure_client",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "install_skill",
|
||||
kind: "install_skill",
|
||||
target: dirname(skillPath),
|
||||
}),
|
||||
]);
|
||||
expect(await readFile(configPath, "utf8")).toBe(staleCodex);
|
||||
expect(await readFile(untouched, "utf8")).toBe('{"mcpServers":{}}');
|
||||
expect(await readFile(skillPath, "utf8")).toBe(skillContent);
|
||||
await expect(access(`${configPath}.rea.backup`)).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
await expect(access(`${skillPath}.rea.backup`)).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
await expect(
|
||||
access(join(home, ".agents", "skills", PRODUCT_IDENTITY.skillName)),
|
||||
access(join(home, ".claude", "skills", PRODUCT_IDENTITY.skillName)),
|
||||
).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user