Compare commits

..
Author SHA1 Message Date
morlutoandGitHub 11d295eeab Merge branch 'main' into fix/main-maintenance-ci-20261010 2026-10-14 03:09:00 +08:00
Devulapalli Naga Sri VaishnaviandGitHub a9195c3a98 fix(managed): retain raw CIL changes in member comparison (#2912) 2026-10-14 03:07:12 +08:00
4758ba30b6 fix(package-verifier): reject overview responses without a procedure count (#2936)
* fix(package-verifier): reject overview responses without a procedure count

The analyze and inspect checks compared procedure_count with `< 1`, and an absent
or null normalized_result makes that comparison false, so a binary_overview
response without its required result passed the platform smoke check. Require an
integer procedure count before the positive-count comparison.

* test(package-verifier): cover malformed overview receipts

---------

Co-authored-by: REA Maintenance <76467478+morluto@users.noreply.github.com>
2026-10-14 03:06:53 +08:00
morluto 0d31902b7f fix(reference-source): refresh maintenance fixes against main 2026-10-14 02:29:20 +08:00
吴佳浩Albenandmorluto ccc009412e fix: repair the static-check failures on the default branch (#2895)
* fix: repair the static-check failures on the default branch

The default branch fails its own required `Static checks` job three ways, so
every pull request reports that job as failed regardless of its own change.

- `npm run format:check` reports `src/domain/functionComparison.test.ts` and
  `src/ida/IdaOperationRunner.ts`.
- `npm run lint` reports a duplicate `import { join } from "node:path"` in
  `tests/boundary/mcp/ghidraInventoryMcp.test.ts` (lines 4 and 17).
- `npm run typecheck` reports `format: "devin"` missing from the client format
  union in `src/application/SupportedClients.ts`, and an unchecked index in
  `tests/boundary/cli/clientProfileLifecycle.test.ts`, whose pair array is
  inferred as `string[][]`.

Apply the pinned formatter, drop the duplicate import, list the `"devin"` format
that its own definition already uses, and mark the pair array `as const`.

Validation: `npm run check:fast` and `npm run format:check` pass.

* fix(setup): keep Devin on the JSON configuration dialect

---------

Co-authored-by: 吴佳浩 <wujiahao@supremind.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
(cherry picked from commit df3d96da80)
2026-10-14 01:15:17 +08:00
morluto 0d2bb5fb09 fix(android): make DEX descriptor pattern portable
(cherry picked from commit 1335bcddc569a1cfb34b6efedcb6f2e3aaaa3357)
2026-10-14 01:15:04 +08:00
REA Maintenance 47dd84ea69 fix(ci): isolate Linux Hopper process fixture inventory
(cherry picked from commit 8cac51e12184d56a9ba80a4427b3e6082ddac1ec)
2026-10-14 01:15:04 +08:00
Lucienandmorluto 4e829cea2f fix(comparison): recheck historical calls-only changes (#2223)
* fix(comparison): preserve incomplete function call evidence

Keep missing, unknown, partial and unresolved call relationships unknown in function comparisons and reconstruction claims. Aggregate IDA caller and callee coverage without overwriting uncertainty.

Validate with 144 focused tests, typecheck/lint, docs checks and test discovery. Real providers were not verified; the full headless IDA session suite requires unavailable Windows native controls.

* fix(comparison): recheck historical calls-only changes

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
(cherry picked from commit 688defca0f)
2026-10-14 01:15:04 +08:00
morluto bf30b081db fix: reconcile captured main and owned findings for PR #1603 2026-10-13 23:02:19 +08:00
morluto 15efa61149 style: format reviewed source changes 2026-10-13 20:36:55 +08:00
morluto 70435de2c6 chore: merge latest main after source review 2026-10-13 20:29:41 +08:00
morluto 1b3371ad7f fix(ci): reconcile Git ownership and preserve portable checks 2026-10-13 20:11:02 +08:00
N0zoM1z0 c7fdd7e153 test(artifacts): share ASAR member fixture setup 2026-10-11 03:54:57 +08:00
N0zoM1z0 3c794a5cc0 Merge commit '5100a1a1a56b810c8246b8c7ed19ff42c4d9dbe1' into fix/ci-duplicate-boundaries-1603 2026-10-11 03:50:21 +08:00
N0zoM1z0 16caa5ca9b chore(ci): refresh maintenance checks against current main 2026-10-11 03:44:06 +08:00
N0zoM1z0 2a6bef5589 chore(ci): include current snapshot and runtime repairs 2026-10-11 01:50:32 +08:00
N0zoM1z0 cace19caad fix(ci): reconcile lifecycle checks with current main
Preserve failed event finalization across retries and current capture ownership checks. Retain Git unreadable-ref and bounded-heap Evidence coverage, pinned maintenance destinations, and explicit native binding facts.
2026-10-11 01:08:09 +08:00
N0zoM1z0 87b7bf8db1 fix(reference-source): distinguish Git API probes from failed reads 2026-10-10 16:59:30 +00:00
N0zoM1z0 3b66532e26 Merge commit '603d04ee3ac6ed496f84478164f832a9b5ce3b91' into fix/main-maintenance-ci-20261010
# Conflicts:
#	tests/process-global/updateMaintenance.test.ts
2026-10-10 16:36:42 +00:00
N0zoM1z0 3ad063b454 Merge remote-tracking branch 'origin/main' into fix/main-maintenance-ci-20261010 2026-10-10 16:34:18 +00:00
N0zoM1z0 69485e27f7 fix(ci): restore capture and maintenance regression checks 2026-10-10 16:34:17 +00:00
17 changed files with 279 additions and 269 deletions
+12 -7
View File
@@ -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
+6
View File
@@ -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>;
+29 -15
View File
@@ -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;
+12 -5
View File
@@ -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);
+1 -10
View File
@@ -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",
);
+20 -4
View File
@@ -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);
},
);
@@ -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();
}
},
);
});
-5
View File
@@ -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
View File
@@ -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"
+24 -5
View File
@@ -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" });
});
});