mirror of
https://github.com/morluto/rea.git
synced 2026-11-08 15:56:35 +00:00
Merge remote-tracking branch 'origin/main' into eb
The environment-coverage sentence joins main's BEAM additions to the view description. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
53a03c06a5
272 files changed
+21570
-3711
No files matched your search
@@ -28,7 +28,6 @@ const FIXTURE_SOURCES = [
|
||||
"tests/conformance/swift/**",
|
||||
"tests/conformance/versions/**",
|
||||
];
|
||||
const READINESS_SOURCES = ["tests/conformance/readiness/**"];
|
||||
// These reporters affect CI output only; they do not select a host or provider
|
||||
// verification lane when changed on their own.
|
||||
const PORTABLE_CI_REPORTERS = new Set([
|
||||
@@ -281,7 +280,6 @@ if (tier === "reused") {
|
||||
selected.fixtures = true;
|
||||
selected.readiness = true;
|
||||
}
|
||||
if (matchesAny(file, READINESS_SOURCES)) selected.readiness = true;
|
||||
if (file.startsWith("tests/")) continue;
|
||||
if (
|
||||
/^(src\/process\/|src\/filesystem\/|src\/contracts\/tool|src\/server\/createServer|src\/cli\.ts|scripts\/ci\/)/.test(
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// Shared build inputs are classified once in plan.mjs rather than repeated in
|
||||
// every workflow.
|
||||
export const SCOPES = {
|
||||
readiness: [
|
||||
"tests/conformance/readiness/**",
|
||||
"scripts/verify-reconstruction-readiness.mjs",
|
||||
],
|
||||
reverse_tools: [
|
||||
".github/workflows/mutation.yml",
|
||||
"src/application/reverse/**",
|
||||
@@ -141,6 +145,7 @@ export const SCOPES = {
|
||||
"scripts/prepack.mjs",
|
||||
"scripts/prepare.mjs",
|
||||
"scripts/rea.mjs",
|
||||
"scripts/lib/cli-process-environment.mjs",
|
||||
"install.sh",
|
||||
"src/config/**",
|
||||
"src/application/Setup*.ts",
|
||||
@@ -373,6 +378,7 @@ export const SCOPES = {
|
||||
"src/contracts/toolContracts.ts",
|
||||
"src/contracts/toolEffects.ts",
|
||||
"scripts/rea.mjs",
|
||||
"scripts/lib/cli-process-environment.mjs",
|
||||
"src/process/**",
|
||||
"src/artifacts/SafeOutputTree.ts",
|
||||
"scripts/verify/javascript/recovery.mjs",
|
||||
|
||||
@@ -260,6 +260,13 @@ export const providerCatalog = (sources) => {
|
||||
({ name }) => name === "inspect_pe_certificates",
|
||||
),
|
||||
},
|
||||
{
|
||||
identity: sources.artifactProviders.PE_IMPORTS_PROVIDER,
|
||||
contracts:
|
||||
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
|
||||
({ name }) => name === "inspect_pe_imports",
|
||||
),
|
||||
},
|
||||
{
|
||||
identity: sources.artifactProviders.ELF_NOTES_PROVIDER,
|
||||
contracts:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { join } from "node:path";
|
||||
|
||||
/** Prepare the selected process environment before importing CLI or MCP dependencies. */
|
||||
export const prepareCliProcessEnvironment = (
|
||||
environment,
|
||||
platform,
|
||||
packageVersion,
|
||||
) => {
|
||||
environment.REA_PACKAGE_VERSION = packageVersion;
|
||||
// Windows home lookup throws for an empty USERPROFILE. Dependencies resolve
|
||||
// home during import, before REA's own resolver can apply its account fallback.
|
||||
if (platform === "win32" && environment.USERPROFILE === "") {
|
||||
delete environment.USERPROFILE;
|
||||
}
|
||||
|
||||
// Playwright's registry assumes a desktop host while choosing its cache.
|
||||
// Termux needs an explicit path before the registry initializes.
|
||||
if (
|
||||
platform === "android" &&
|
||||
environment.PLAYWRIGHT_BROWSERS_PATH === undefined
|
||||
) {
|
||||
const cacheRoot =
|
||||
environment.XDG_CACHE_HOME ?? join(environment.HOME ?? ".", ".cache");
|
||||
environment.PLAYWRIGHT_BROWSERS_PATH = join(cacheRoot, "ms-playwright");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/** Extract one real procedure address from the complete inline inventory. */
|
||||
export function firstProcedureAddress(input: unknown): string;
|
||||
|
||||
/** Reject empty and success-shaped embedded decompilation failures. */
|
||||
export function requirePseudocode(input: unknown, operation: string): string;
|
||||
|
||||
/** Validate a complete Hopper function dossier semantically. */
|
||||
export function requireFunctionDossier(
|
||||
input: unknown,
|
||||
expectedAddress: string,
|
||||
): Record<string, unknown>;
|
||||
|
||||
/** Positive semantic facts required from one source-owned Hopper fixture. */
|
||||
export interface FunctionDossierOracle {
|
||||
readonly procedure_address: string;
|
||||
readonly callee_address?: string;
|
||||
readonly caller_address?: string;
|
||||
readonly referenced_string?: string;
|
||||
readonly referenced_name?: string;
|
||||
readonly comment?: string;
|
||||
readonly require_cfg_successor?: boolean;
|
||||
readonly require_assembly?: boolean;
|
||||
}
|
||||
|
||||
/** Reject a structurally valid dossier that omits fixture-proven semantics. */
|
||||
export function requireFunctionDossierOracle(
|
||||
input: unknown,
|
||||
oracle: FunctionDossierOracle,
|
||||
): Record<string, unknown>;
|
||||
|
||||
/** Require every relationship to be an actual hexadecimal address. */
|
||||
export function requireAddressArray(
|
||||
input: unknown,
|
||||
operation: string,
|
||||
): string[];
|
||||
@@ -0,0 +1,191 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const addressSchema = z.string().regex(/^0x[0-9a-f]+$/iu);
|
||||
const identitySchema = z.object({ address: addressSchema, name: z.string() });
|
||||
/** Extract one real procedure address from the complete inline inventory. */
|
||||
export const firstProcedureAddress = (input) => {
|
||||
const first = z
|
||||
.array(z.object({ address: addressSchema }))
|
||||
.min(1)
|
||||
.parse(input)[0];
|
||||
if (first === undefined) throw new TypeError("Procedure inventory was empty");
|
||||
return first.address;
|
||||
};
|
||||
|
||||
/** Reject empty and success-shaped embedded decompilation failures. */
|
||||
export const requirePseudocode = (input, operation) => {
|
||||
if (typeof input !== "string" || input.trim().length === 0)
|
||||
throw new TypeError(`${operation} returned empty pseudocode`);
|
||||
if (input === "No output" || input.startsWith("Error:"))
|
||||
throw new TypeError(`${operation} returned an embedded failure: ${input}`);
|
||||
return input;
|
||||
};
|
||||
|
||||
/** Validate a complete Hopper function dossier semantically. */
|
||||
export const requireFunctionDossier = (input, expectedAddress) => {
|
||||
const value = objectValue(
|
||||
input,
|
||||
"analyze_function returned no function dossier",
|
||||
);
|
||||
const procedure = identitySchema.parse(value.procedure);
|
||||
if (procedure.address !== expectedAddress)
|
||||
throw new TypeError("analyze_function returned the wrong procedure");
|
||||
if (procedure.name.trim().length === 0)
|
||||
throw new TypeError("analyze_function omitted the procedure name");
|
||||
requirePseudocode(value.pseudocode, "analyze_function");
|
||||
for (const field of COLLECTION_FIELDS) list(value, field);
|
||||
for (const field of ["callers", "callees"])
|
||||
z.array(identitySchema).parse(list(value, field));
|
||||
validateBlocks(list(value, "basic_blocks"));
|
||||
return value;
|
||||
};
|
||||
|
||||
/** Reject a structurally valid dossier that omits fixture-proven semantics. */
|
||||
export const requireFunctionDossierOracle = (input, oracle) => {
|
||||
const value = requireFunctionDossier(input, oracle.procedure_address);
|
||||
if (oracle.callee_address !== undefined) {
|
||||
const callees = z.array(identitySchema).parse(list(value, "callees"));
|
||||
if (!callees.some(({ address }) => address === oracle.callee_address))
|
||||
throw new TypeError(
|
||||
"analyze_function omitted the expected fixture callee",
|
||||
);
|
||||
|
||||
const references = z
|
||||
.array(
|
||||
z.object({
|
||||
source_address: addressSchema,
|
||||
target_address: addressSchema,
|
||||
}),
|
||||
)
|
||||
.parse(list(value, "outgoing_references"));
|
||||
if (
|
||||
!references.some(
|
||||
({ target_address }) => target_address === oracle.callee_address,
|
||||
)
|
||||
)
|
||||
throw new TypeError(
|
||||
"analyze_function omitted the expected fixture reference",
|
||||
);
|
||||
}
|
||||
|
||||
if (oracle.caller_address !== undefined) {
|
||||
const callers = z.array(identitySchema).parse(list(value, "callers"));
|
||||
if (!callers.some(({ address }) => address === oracle.caller_address))
|
||||
throw new TypeError(
|
||||
"analyze_function omitted the expected fixture caller",
|
||||
);
|
||||
}
|
||||
|
||||
if (oracle.referenced_string !== undefined)
|
||||
requireReferencedValue(
|
||||
value,
|
||||
"referenced_strings",
|
||||
oracle.referenced_string,
|
||||
(candidate) => candidate,
|
||||
);
|
||||
|
||||
if (oracle.referenced_name !== undefined)
|
||||
requireReferencedValue(
|
||||
value,
|
||||
"referenced_names",
|
||||
oracle.referenced_name,
|
||||
(candidate) => candidate.replace(/^_+/u, ""),
|
||||
);
|
||||
|
||||
if (oracle.comment !== undefined) {
|
||||
const comments = z
|
||||
.array(
|
||||
z.object({
|
||||
address: addressSchema,
|
||||
kind: z.enum(["comment", "inline"]),
|
||||
text: z.string(),
|
||||
}),
|
||||
)
|
||||
.parse(list(value, "comments"));
|
||||
if (
|
||||
!comments.some(
|
||||
({ address, kind, text }) =>
|
||||
address === oracle.procedure_address &&
|
||||
kind === "comment" &&
|
||||
text === oracle.comment,
|
||||
)
|
||||
)
|
||||
throw new TypeError("analyze_function omitted the verifier comment");
|
||||
}
|
||||
|
||||
if (oracle.require_assembly === true && list(value, "assembly").length === 0)
|
||||
throw new TypeError("analyze_function omitted fixture assembly");
|
||||
|
||||
if (oracle.require_cfg_successor) {
|
||||
const blocks = z
|
||||
.array(z.object({ successors: z.array(addressSchema) }))
|
||||
.parse(list(value, "basic_blocks"));
|
||||
if (!blocks.some(({ successors }) => successors.length > 0))
|
||||
throw new TypeError("analyze_function omitted a real CFG successor");
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const referencedValueSchema = z.object({
|
||||
address: addressSchema,
|
||||
value: z.string(),
|
||||
source_address: addressSchema,
|
||||
});
|
||||
|
||||
const referenceEdgeSchema = z.object({
|
||||
source_address: addressSchema,
|
||||
target_address: addressSchema,
|
||||
});
|
||||
|
||||
const requireReferencedValue = (dossier, field, expected, normalize) => {
|
||||
const values = z.array(referencedValueSchema).parse(list(dossier, field));
|
||||
const references = z
|
||||
.array(referenceEdgeSchema)
|
||||
.parse(list(dossier, "outgoing_references"));
|
||||
const found = values.some(
|
||||
({ address, value, source_address: sourceAddress }) =>
|
||||
normalize(value) === expected &&
|
||||
references.some(
|
||||
({ source_address, target_address }) =>
|
||||
source_address === sourceAddress && target_address === address,
|
||||
),
|
||||
);
|
||||
if (!found)
|
||||
throw new TypeError(
|
||||
`analyze_function omitted the expected fixture ${field === "referenced_strings" ? "string" : "name"}`,
|
||||
);
|
||||
};
|
||||
|
||||
/** Require every relationship to be an actual hexadecimal address. */
|
||||
export const requireAddressArray = (input, operation) => {
|
||||
const parsed = z.array(addressSchema).parse(input);
|
||||
if (parsed.some((address) => address.length === 0))
|
||||
throw new TypeError(`${operation} returned an invalid address list`);
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const COLLECTION_FIELDS = [
|
||||
"assembly",
|
||||
"comments",
|
||||
"callers",
|
||||
"callees",
|
||||
"incoming_references",
|
||||
"outgoing_references",
|
||||
"referenced_strings",
|
||||
"referenced_names",
|
||||
"basic_blocks",
|
||||
];
|
||||
|
||||
const list = (value, field) => z.array(z.unknown()).parse(value[field]);
|
||||
|
||||
const validateBlocks = (items) => {
|
||||
for (const block of items)
|
||||
if (!Array.isArray(objectValue(block, "Invalid basic block").successors))
|
||||
throw new TypeError("analyze_function omitted CFG successor evidence");
|
||||
};
|
||||
|
||||
const objectValue = (input, message) => {
|
||||
const parsed = z.record(z.string(), z.unknown()).safeParse(input);
|
||||
if (!parsed.success) throw new TypeError(message);
|
||||
return parsed.data;
|
||||
};
|
||||
@@ -1,17 +1,6 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
/**
|
||||
* `requireFunctionDossierOracle` is only used inside `verifyRealHopperFixture`,
|
||||
* which runs against the real Hopper bridge after `build:cached`. Loading it
|
||||
* lazily keeps this module importable from `test:fast` (which docs promise to
|
||||
* run "without build" — see docs/testing.md § test scripts) so that mock-only
|
||||
* callers such as `requireCurrentDocument` and `resolveFixtureProcedure` do
|
||||
* not require `dist/`.
|
||||
*/
|
||||
const loadFunctionDossierOracle = () =>
|
||||
import("../../dist/application/RealHopperAssertions.js").then(
|
||||
(module) => module.requireFunctionDossierOracle,
|
||||
);
|
||||
import { requireFunctionDossierOracle } from "./real-hopper-assertions.mjs";
|
||||
|
||||
/** Require truthful tri-state permissions from real Hopper memory regions. */
|
||||
export const requireTruthfulMemoryRegions = (segments) => {
|
||||
@@ -99,7 +88,6 @@ export async function verifyRealHopperFixture({
|
||||
oracle,
|
||||
normalizedResult,
|
||||
}) {
|
||||
const requireFunctionDossierOracle = await loadFunctionDossierOracle();
|
||||
const main = await resolveFixtureProcedure(
|
||||
client,
|
||||
options,
|
||||
|
||||
@@ -53,7 +53,7 @@ export const releaseInventorySchema = z.object({
|
||||
pullRequests: z.array(pullRequest),
|
||||
});
|
||||
|
||||
export async function readReleaseHistory(run, base, target) {
|
||||
export async function requireCompleteReleaseHistory(run) {
|
||||
if (
|
||||
(await run("git", ["rev-parse", "--is-shallow-repository"])).trim() !==
|
||||
"false"
|
||||
@@ -61,6 +61,10 @@ export async function readReleaseHistory(run, base, target) {
|
||||
throw new Error(
|
||||
"Release inventory requires complete Git history; fetch --unshallow first",
|
||||
);
|
||||
}
|
||||
|
||||
export async function readReleaseHistory(run, base, target) {
|
||||
await requireCompleteReleaseHistory(run);
|
||||
const resolve = async (ref) =>
|
||||
sha.parse(
|
||||
(
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export function applyReleasePleasePatch(directory?: string): Promise<void>;
|
||||
|
||||
export function releaseProposalSearchDepth(
|
||||
run: (command: string, args: string[]) => Promise<string>,
|
||||
sourceSha: string,
|
||||
): Promise<number>;
|
||||
@@ -0,0 +1,61 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, relative } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { requireCompleteReleaseHistory } from "./release-notes.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const require = createRequire(import.meta.url);
|
||||
const project = fileURLToPath(new URL("../../", import.meta.url));
|
||||
const patch = fileURLToPath(
|
||||
new URL("../../patches/release-please+17.3.0.patch", import.meta.url),
|
||||
);
|
||||
|
||||
export async function applyReleasePleasePatch(
|
||||
directory = dirname(require.resolve("release-please/package.json")),
|
||||
) {
|
||||
const metadata = JSON.parse(
|
||||
await readFile(`${directory}/package.json`, "utf8"),
|
||||
);
|
||||
if (metadata.version !== "17.3.0") {
|
||||
throw new Error(
|
||||
"The release proposal patch requires release-please 17.3.0",
|
||||
);
|
||||
}
|
||||
const args = ["--directory", relative(project, directory), patch];
|
||||
const check = async (reverse) => {
|
||||
try {
|
||||
await execute(
|
||||
"git",
|
||||
["apply", "--check", ...(reverse ? ["--reverse"] : []), ...args],
|
||||
{ cwd: project },
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error.code === 1) return false;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
if (await check(false)) {
|
||||
await execute("git", ["apply", ...args], { cwd: project });
|
||||
} else if (!(await check(true))) {
|
||||
throw new Error(
|
||||
"The release proposal dependency patch does not match the installed source",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function releaseProposalSearchDepth(run, sourceSha) {
|
||||
if (!/^[a-f0-9]{40}$/u.test(sourceSha ?? "")) {
|
||||
throw new Error("Expected an exact release proposal source SHA");
|
||||
}
|
||||
await requireCompleteReleaseHistory(run);
|
||||
const text = (await run("git", ["rev-list", "--count", sourceSha])).trim();
|
||||
const count = Number(text);
|
||||
if (!/^[1-9][0-9]*$/u.test(text) || !Number.isSafeInteger(count)) {
|
||||
throw new Error("Git returned an invalid release history size");
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -45,6 +45,7 @@ if (await pathExists(join(root, "src"))) {
|
||||
run(process.execPath, ["scripts/generate-skill-metadata.mjs", "--check"]);
|
||||
} else {
|
||||
for (const path of [
|
||||
"scripts/lib/cli-process-environment.mjs",
|
||||
"dist/main.js",
|
||||
"dist/mcp/main.js",
|
||||
"dist/cli-runtime/cli.js",
|
||||
|
||||
+8
-22
@@ -1,9 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { access } from "node:fs/promises";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { prepareCliProcessEnvironment } from "./lib/cli-process-environment.mjs";
|
||||
|
||||
// Route production MCP before importing Incur. Incur owns registration helpers
|
||||
// such as `mcp add`, while the MCP runtime serves the tool catalog over stdio
|
||||
// or, with `--http`, over streamable HTTP.
|
||||
@@ -11,7 +13,11 @@ const args = process.argv.slice(2);
|
||||
const { default: packageJson } = await import("../package.json", {
|
||||
with: { type: "json" },
|
||||
});
|
||||
process.env.REA_PACKAGE_VERSION = packageJson.version;
|
||||
prepareCliProcessEnvironment(
|
||||
process.env,
|
||||
process.platform,
|
||||
packageJson.version,
|
||||
);
|
||||
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const builtinArgumentRuntime = "dist/cli-runtime/cliBuiltinArguments.js";
|
||||
const builtinArguments = (await compiledRuntimeExists([builtinArgumentRuntime]))
|
||||
@@ -36,26 +42,6 @@ const isMcpHttpMode =
|
||||
const SIGPIPE_EXIT_STATUS = 141;
|
||||
// Set once the reader of CLI output has closed the pipe.
|
||||
let stdoutClosed = false;
|
||||
// Windows home lookup throws for an empty USERPROFILE. Dependencies resolve
|
||||
// home during import, before REA's own resolver can apply its account fallback.
|
||||
if (process.platform === "win32" && process.env.USERPROFILE === "") {
|
||||
delete process.env.USERPROFILE;
|
||||
}
|
||||
|
||||
// Playwright's registry assumes one of its three desktop host platforms when
|
||||
// it chooses the browser cache directory. Termux reports `android`, although
|
||||
// its Node runtime and filesystem are POSIX-compatible. Keep the cache path
|
||||
// explicit so REA can start on Termux. Browser launch still requires a
|
||||
// caller-supplied Android-compatible Chromium executable.
|
||||
if (
|
||||
process.platform === "android" &&
|
||||
process.env.PLAYWRIGHT_BROWSERS_PATH === undefined
|
||||
) {
|
||||
const cacheRoot =
|
||||
process.env.XDG_CACHE_HOME ?? join(process.env.HOME ?? ".", ".cache");
|
||||
process.env.PLAYWRIGHT_BROWSERS_PATH = join(cacheRoot, "ms-playwright");
|
||||
}
|
||||
|
||||
const runtimeFiles = [
|
||||
builtinArgumentRuntime,
|
||||
...(isMcpMode || isMcpHttpMode
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import semver from "semver";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export function releaseNpmTag(version) {
|
||||
if (version === undefined || semver.valid(version) !== version)
|
||||
throw new Error("Expected an exact SemVer version.");
|
||||
return semver.prerelease(version) === null ? "latest" : "next";
|
||||
}
|
||||
|
||||
if (
|
||||
process.argv[1] !== undefined &&
|
||||
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||
)
|
||||
process.stdout.write(releaseNpmTag(process.argv[2]));
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env node
|
||||
import { execFile } from "node:child_process";
|
||||
import { appendFile } from "node:fs/promises";
|
||||
import { parseArgs, promisify } from "node:util";
|
||||
import {
|
||||
applyReleasePleasePatch,
|
||||
releaseProposalSearchDepth,
|
||||
} from "./lib/release-proposal.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
|
||||
async function main() {
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
"target-branch": { type: "string" },
|
||||
"source-sha": { type: "string" },
|
||||
},
|
||||
});
|
||||
const repository = process.env.GITHUB_REPOSITORY;
|
||||
const token = process.env.GH_TOKEN;
|
||||
if (
|
||||
!repository ||
|
||||
!/^[\w.-]+\/[\w.-]+$/u.test(repository) ||
|
||||
!token ||
|
||||
!values["target-branch"]
|
||||
) {
|
||||
throw new Error("Expected GITHUB_REPOSITORY, GH_TOKEN and --target-branch");
|
||||
}
|
||||
const depth = await releaseProposalSearchDepth(
|
||||
async (command, args) =>
|
||||
(await execute(command, args, { encoding: "utf8" })).stdout,
|
||||
values["source-sha"],
|
||||
);
|
||||
// Patch before import: only proposal generation uses this maintained upstream fix.
|
||||
// Publication continues through the pinned release-please action.
|
||||
await applyReleasePleasePatch();
|
||||
const { GitHub, Manifest } = await import("release-please");
|
||||
const [owner, repo] = repository.split("/");
|
||||
const branch = values["target-branch"];
|
||||
const github = await GitHub.create({
|
||||
owner,
|
||||
repo,
|
||||
token,
|
||||
defaultBranch: branch,
|
||||
});
|
||||
const manifest = await Manifest.fromManifest(
|
||||
github,
|
||||
branch,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
commitSearchDepth: depth,
|
||||
},
|
||||
);
|
||||
const prs = (await manifest.createPullRequests()).filter(Boolean);
|
||||
if (process.env.GITHUB_OUTPUT) {
|
||||
await appendFile(
|
||||
process.env.GITHUB_OUTPUT,
|
||||
`prs_created=${prs.length > 0}\n${prs.length ? `pr=${JSON.stringify(prs[0])}\n` : ""}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await main().catch((error) => {
|
||||
process.stderr.write(
|
||||
`Release proposal failed: ${error instanceof Error ? error.message : String(error)}\n`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -11,26 +11,44 @@ import {
|
||||
import { homedir } from "node:os";
|
||||
import { delimiter, dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { tsImport } from "tsx/esm/api";
|
||||
|
||||
import { evaluateCodexEvents } from "../dist/evaluation/CodexAgentEval.js";
|
||||
import {
|
||||
MISSING_TARGET_SCENARIO,
|
||||
nativeProviderScenario,
|
||||
} from "../dist/evaluation/AgentEvaluationScenarios.js";
|
||||
import {
|
||||
createAgentEvaluationFixtures,
|
||||
agentFixtureClaims,
|
||||
agentFixtureAnswerInstructions,
|
||||
} from "../dist/evaluation/AgentEvaluationFixtures.js";
|
||||
import {
|
||||
agentEvaluationPassed,
|
||||
summarizeFactualCorrectness,
|
||||
} from "../dist/evaluation/AgentEvaluationReport.js";
|
||||
import { PRODUCT_IDENTITY } from "../dist/identity.js";
|
||||
import { createAgentEvaluationCommands } from "./lib/agent-evaluation-commands.mjs";
|
||||
import { closeVerifierResources } from "./lib/verifier-cleanup.mjs";
|
||||
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
const { evaluateCodexEvents } = await tsImport(
|
||||
new URL("../tests/evaluation/model-evals/CodexAgentEval.ts", import.meta.url)
|
||||
.href,
|
||||
import.meta.url,
|
||||
);
|
||||
const { MISSING_TARGET_SCENARIO, nativeProviderScenario } = await tsImport(
|
||||
new URL(
|
||||
"../tests/evaluation/model-evals/AgentEvaluationScenarios.ts",
|
||||
import.meta.url,
|
||||
).href,
|
||||
import.meta.url,
|
||||
);
|
||||
const {
|
||||
createAgentEvaluationFixtures,
|
||||
agentFixtureClaims,
|
||||
agentFixtureAnswerInstructions,
|
||||
} = await tsImport(
|
||||
new URL(
|
||||
"../tests/evaluation/model-evals/AgentEvaluationFixtures.ts",
|
||||
import.meta.url,
|
||||
).href,
|
||||
import.meta.url,
|
||||
);
|
||||
const { agentEvaluationPassed, summarizeFactualCorrectness } = await tsImport(
|
||||
new URL(
|
||||
"../tests/evaluation/model-evals/AgentEvaluationReport.ts",
|
||||
import.meta.url,
|
||||
).href,
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const verifierRun = createVerifierRun();
|
||||
const timeoutMs = Number(process.env.REA_AGENT_EVAL_TIMEOUT_MS ?? 480_000);
|
||||
|
||||
@@ -37,6 +37,8 @@ export async function verifyPackagePack({ root, workspace }) {
|
||||
throw new Error("package did not expose both rea command entry points");
|
||||
if (packedManifest.mcpName !== "io.github.morluto/rea")
|
||||
throw new Error("package did not retain its MCP Registry ownership marker");
|
||||
if (!packedFiles.includes("package/scripts/lib/cli-process-environment.mjs"))
|
||||
throw new Error("package omitted the CLI process environment owner");
|
||||
if (packedFiles.some((path) => path.startsWith("package/.agents/")))
|
||||
throw new Error(
|
||||
"package included authored skill sources instead of only the generated bundle",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { firstProcedureAddress } from "../dist/application/RealHopperAssertions.js";
|
||||
import { firstProcedureAddress } from "./lib/real-hopper-assertions.mjs";
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { ArtifactResourceScope } from "../dist/artifacts/ArtifactResourceScope.js";
|
||||
import { AnalysisCleanupLease } from "../dist/artifacts/AnalysisCleanupLease.js";
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
requireAddressArray,
|
||||
requireFunctionDossier,
|
||||
requirePseudocode,
|
||||
} from "../dist/application/RealHopperAssertions.js";
|
||||
} from "./lib/real-hopper-assertions.mjs";
|
||||
import { HOPPER_PROVIDER_IDENTITY } from "../dist/hopper/HopperProviderCapabilities.js";
|
||||
import { HOPPER_TARGET_LEASE_DIRECTORY } from "../dist/process/HopperLease.js";
|
||||
import { REA_WORKFLOW_PROVIDER } from "../dist/domain/investigationProviders.js";
|
||||
|
||||
@@ -6,14 +6,32 @@ import { promisify } from "node:util";
|
||||
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { tsImport } from "tsx/esm/api";
|
||||
|
||||
import { toolContract } from "../dist/contracts/toolContracts.js";
|
||||
import { RECONSTRUCTION_READINESS_EXAMPLE } from "../dist/contracts/reconstructionReadinessExample.js";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { createEvidenceBundle } from "../dist/domain/evidenceBundle.js";
|
||||
import { createReconstructionReadinessReport } from "../dist/domain/reconstructionReadiness.js";
|
||||
import { reconstructionReadinessReportSchema } from "../dist/domain/reconstructionReadinessSchemas.js";
|
||||
import { PACKAGE_METADATA } from "../dist/generatedPackageMetadata.js";
|
||||
import { closeVerifierResources } from "./lib/verifier-cleanup.mjs";
|
||||
|
||||
const [
|
||||
{ RECONSTRUCTION_READINESS_EXAMPLE },
|
||||
{ createReconstructionReadinessReport },
|
||||
{ reconstructionReadinessReportSchema },
|
||||
] = await Promise.all(
|
||||
[
|
||||
"reconstructionReadinessExample",
|
||||
"reconstructionReadiness",
|
||||
"reconstructionReadinessSchemas",
|
||||
].map((name) =>
|
||||
tsImport(
|
||||
new URL(
|
||||
`../tests/conformance/readiness/report/${name}.ts`,
|
||||
import.meta.url,
|
||||
).href,
|
||||
import.meta.url,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const root = process.cwd();
|
||||
@@ -50,6 +68,8 @@ const client = new Client({
|
||||
capabilities: { elicitation: {} },
|
||||
});
|
||||
|
||||
let verificationReport;
|
||||
let primaryError;
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const catalog = await client.listTools();
|
||||
@@ -61,6 +81,7 @@ try {
|
||||
{ name: "binary_session", arguments: {} },
|
||||
{ toolDefinition: sessionTool },
|
||||
);
|
||||
assert.notEqual(sessionReply.isError, true);
|
||||
const sessionStatus = toolContract("binary_session").outputSchema.parse(
|
||||
sessionReply.structuredContent,
|
||||
).result;
|
||||
@@ -77,76 +98,93 @@ try {
|
||||
"CLI/MCP Electron analysis Evidence identity drifted",
|
||||
);
|
||||
|
||||
const input = structuredClone(RECONSTRUCTION_READINESS_EXAMPLE);
|
||||
input.identity.cli_version = PACKAGE_METADATA.version;
|
||||
input.identity.server_version = PACKAGE_METADATA.version;
|
||||
input.identity.catalog_digest =
|
||||
sessionStatus.server_identity.catalog.digests.combined_sha256;
|
||||
const records = [
|
||||
...input.evidence_bundle.records,
|
||||
nativeEvidence,
|
||||
javascriptCliEvidence,
|
||||
electronCliEvidence,
|
||||
].map(parseEvidence);
|
||||
input.evidence_bundle = createEvidenceBundle(records);
|
||||
setFixtureDigest(input, "native", nativeEvidence);
|
||||
setFixtureDigest(input, "javascript-cli", javascriptCliEvidence);
|
||||
setFixtureDigest(input, "electron", electronCliEvidence);
|
||||
linkCheck(input, "discover-classify", "artifact-inventory", nativeEvidence);
|
||||
linkCheck(input, "static-analysis", "native-routed", nativeEvidence);
|
||||
linkCheck(
|
||||
input,
|
||||
"static-analysis",
|
||||
"javascript-routed",
|
||||
javascriptCliEvidence,
|
||||
);
|
||||
linkCheck(
|
||||
input,
|
||||
"reactive-scenarios",
|
||||
"electron-correlated",
|
||||
electronCliEvidence,
|
||||
);
|
||||
|
||||
const readinessReport = reconstructionReadinessReportSchema.parse(
|
||||
createReconstructionReadinessReport(input),
|
||||
);
|
||||
assert.equal(readinessReport.status, "pass");
|
||||
if (process.env.REA_READINESS_REPORT_PATH !== undefined)
|
||||
await writeFile(
|
||||
resolve(root, process.env.REA_READINESS_REPORT_PATH),
|
||||
`${JSON.stringify(readinessReport, null, 2)}\n`,
|
||||
);
|
||||
|
||||
const tampered = structuredClone(input);
|
||||
tampered.replay.expected_source_digest = readinessReport.source_digest;
|
||||
tampered.identity.server_version = "tampered";
|
||||
const tamperedReport = reconstructionReadinessReportSchema.parse(
|
||||
createReconstructionReadinessReport(tampered),
|
||||
);
|
||||
assert.equal(tamperedReport.status, "fail");
|
||||
assert.ok(
|
||||
tamperedReport.findings.some(
|
||||
({ code }) => code === "replay-source-digest-mismatch",
|
||||
),
|
||||
);
|
||||
|
||||
process.stdout.write(
|
||||
`${JSON.stringify({
|
||||
status: readinessReport.status,
|
||||
report_id: readinessReport.report_id,
|
||||
report_digest: readinessReport.report_digest,
|
||||
stages: readinessReport.summary.stages,
|
||||
findings: readinessReport.summary.findings,
|
||||
native_evidence_id: nativeEvidence.evidence_id,
|
||||
javascript_evidence_id: javascriptCliEvidence.evidence_id,
|
||||
electron_evidence_id: electronCliEvidence.evidence_id,
|
||||
provider: "local-stdio",
|
||||
})}\n`,
|
||||
);
|
||||
verificationReport = {
|
||||
schema: "ReconstructionReadinessVerificationReport",
|
||||
schema_version: 1,
|
||||
status: "pass",
|
||||
observed_journeys: {
|
||||
evidence_kind: "observed",
|
||||
scope: "static-cli-mcp",
|
||||
status: "pass",
|
||||
server_identity: sessionStatus.server_identity,
|
||||
evidence_bundles: {
|
||||
cli: createEvidenceBundle([
|
||||
nativeEvidence,
|
||||
javascriptCliEvidence,
|
||||
electronCliEvidence,
|
||||
]),
|
||||
mcp: createEvidenceBundle([javascriptMcp, electronMcp]),
|
||||
},
|
||||
journeys: [
|
||||
{
|
||||
journey_id: "native-artifact-cli",
|
||||
operation: "inspect-artifact",
|
||||
surfaces: ["cli"],
|
||||
status: "pass",
|
||||
evidence_ids: [nativeEvidence.evidence_id],
|
||||
checks: ["cli-completed", "evidence-authenticated"],
|
||||
},
|
||||
...[
|
||||
["javascript-static-cli-mcp", javascriptCliEvidence],
|
||||
["electron-static-cli-mcp", electronCliEvidence],
|
||||
].map(([journeyId, evidence]) => ({
|
||||
journey_id: journeyId,
|
||||
operation: "analyze-javascript-application",
|
||||
surfaces: ["cli", "mcp"],
|
||||
status: "pass",
|
||||
evidence_ids: [evidence.evidence_id],
|
||||
checks: [
|
||||
"cli-completed",
|
||||
"mcp-completed",
|
||||
"evidence-authenticated",
|
||||
"evidence-identity-parity",
|
||||
],
|
||||
})),
|
||||
],
|
||||
limitations: [
|
||||
"Native MCP parity was not exercised.",
|
||||
"Fixture application runtime behavior was not executed.",
|
||||
"Authority recovery, cancellation cleanup, behavioral comparison, and reconstruction closure were not exercised.",
|
||||
],
|
||||
},
|
||||
evaluator_conformance: verifySyntheticEvaluator(),
|
||||
};
|
||||
} catch (cause) {
|
||||
primaryError = cause;
|
||||
throw cause;
|
||||
} finally {
|
||||
await client.close();
|
||||
await closeVerifierResources(
|
||||
[() => client.close(), () => transport.close()],
|
||||
primaryError,
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.REA_READINESS_REPORT_PATH !== undefined)
|
||||
await writeFile(
|
||||
resolve(root, process.env.REA_READINESS_REPORT_PATH),
|
||||
`${JSON.stringify(verificationReport, null, 2)}\n`,
|
||||
);
|
||||
process.stdout.write(
|
||||
`${JSON.stringify({
|
||||
schema: verificationReport.schema,
|
||||
schema_version: verificationReport.schema_version,
|
||||
status: verificationReport.status,
|
||||
observed_journeys: {
|
||||
status: verificationReport.observed_journeys.status,
|
||||
scope: verificationReport.observed_journeys.scope,
|
||||
journeys: verificationReport.observed_journeys.journeys,
|
||||
limitations: verificationReport.observed_journeys.limitations,
|
||||
},
|
||||
evaluator_conformance: {
|
||||
status: verificationReport.evaluator_conformance.status,
|
||||
evidence_kind: verificationReport.evaluator_conformance.evidence_kind,
|
||||
checks: verificationReport.evaluator_conformance.checks,
|
||||
report_digest:
|
||||
verificationReport.evaluator_conformance.baseline_report.report_digest,
|
||||
},
|
||||
})}\n`,
|
||||
);
|
||||
|
||||
async function runCli(arguments_) {
|
||||
const { stdout } = await execute(
|
||||
process.execPath,
|
||||
@@ -166,32 +204,50 @@ async function analyzeMcp(client_, inputPath) {
|
||||
arguments: { input_path: inputPath },
|
||||
});
|
||||
assert.notEqual(response.isError, true);
|
||||
const projected = requireObject(response.structuredContent);
|
||||
return { evidence_id: projected.evidence_id };
|
||||
}
|
||||
|
||||
function requireObject(value) {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value))
|
||||
throw new TypeError("Expected an object");
|
||||
return value;
|
||||
}
|
||||
|
||||
function setFixtureDigest(input, kind, evidence) {
|
||||
const fixture = input.fixtures.find(
|
||||
({ kind: fixtureKind }) => fixtureKind === kind,
|
||||
toolContract("analyze_javascript_application").outputSchema.parse(
|
||||
response.structuredContent,
|
||||
);
|
||||
if (fixture === undefined || evidence.subject === null)
|
||||
throw new TypeError(`Missing ${kind} fixture identity`);
|
||||
fixture.artifact_sha256 = evidence.subject.digest.sha256;
|
||||
return parseEvidence(response.structuredContent);
|
||||
}
|
||||
|
||||
function linkCheck(input, stageId, checkId, evidence) {
|
||||
const stage = input.stages.find(({ stage_id: id }) => id === stageId);
|
||||
const check = stage?.checks.find(({ check_id: id }) => id === checkId);
|
||||
if (stage === undefined || check === undefined)
|
||||
throw new TypeError(`Missing readiness check ${stageId}/${checkId}`);
|
||||
stage.evidence_ids = [
|
||||
...new Set([...stage.evidence_ids, evidence.evidence_id]),
|
||||
].sort();
|
||||
check.evidence_ids = [evidence.evidence_id];
|
||||
function verifySyntheticEvaluator() {
|
||||
const input = structuredClone(RECONSTRUCTION_READINESS_EXAMPLE);
|
||||
const evaluate = (value) =>
|
||||
reconstructionReadinessReportSchema.parse(
|
||||
createReconstructionReadinessReport(value),
|
||||
);
|
||||
const baseline = evaluate(input);
|
||||
assert.equal(baseline.status, "pass");
|
||||
assert.deepEqual(evaluate(structuredClone(input)), baseline);
|
||||
|
||||
const tampered = structuredClone(input);
|
||||
const record = tampered.evidence_bundle.records[0];
|
||||
assert.ok(record, "Synthetic conformance requires Evidence");
|
||||
record.operation += "_tampered";
|
||||
assert.throws(
|
||||
() => evaluate(tampered),
|
||||
/Evidence semantic identifier does not match its record/u,
|
||||
);
|
||||
|
||||
const stale = structuredClone(input);
|
||||
stale.replay.expected_source_digest = baseline.source_digest;
|
||||
stale.identity.server_version += "-changed";
|
||||
const staleReport = evaluate(stale);
|
||||
assert.equal(staleReport.status, "fail");
|
||||
assert.ok(
|
||||
staleReport.findings.some(
|
||||
({ code }) => code === "replay-source-digest-mismatch",
|
||||
),
|
||||
);
|
||||
return {
|
||||
evidence_kind: "synthetic",
|
||||
status: "pass",
|
||||
checks: [
|
||||
"baseline-accepted",
|
||||
"deterministic-evaluation",
|
||||
"tampered-evidence-rejected",
|
||||
"stale-source-rejected",
|
||||
].map((checkId) => ({ check_id: checkId, status: "pass" })),
|
||||
baseline_report: baseline,
|
||||
};
|
||||
}
|
||||
@@ -100,6 +100,12 @@ const checkObjc = (metadata, mode) => {
|
||||
const protocol = metadata.objc_protocols.find(
|
||||
({ name }) => name === "ReaDispatchProtocol",
|
||||
);
|
||||
const protocolProperty = protocol?.properties.find(
|
||||
({ name }) => name === "fixtureName",
|
||||
);
|
||||
assert.equal(protocolProperty?.name, "fixtureName");
|
||||
assert.equal(protocolProperty?.type_encoding, '@"NSString"');
|
||||
assert.equal(protocolProperty?.atomicity, "nonatomic");
|
||||
assert.equal(protocol?.methods[0]?.is_required, true);
|
||||
assert.equal(protocol?.methods[0]?.is_optional, false);
|
||||
assert.equal(protocol?.optional_methods[0]?.is_required, false);
|
||||
@@ -161,6 +167,31 @@ const checkObjc = (metadata, mode) => {
|
||||
|
||||
const checkSwift = (metadata, mode) => {
|
||||
assert.equal(fixupCoverage(metadata)?.status, "complete");
|
||||
for (const context of ["ReaNestedA", "ReaNestedB"]) {
|
||||
const type = `${context}.Implementation`;
|
||||
const protocol = `${context}.Requirement`;
|
||||
assert.ok(
|
||||
metadata.swift_conformances.some(
|
||||
(item) =>
|
||||
item.type_name === type &&
|
||||
item.protocol_name === protocol &&
|
||||
item.module === "ReaWitnessFixture" &&
|
||||
item.decode.status === "decoded",
|
||||
),
|
||||
`Nested Swift conformance ${type}: ${protocol} missing (${mode.mode})`,
|
||||
);
|
||||
assert.ok(
|
||||
metadata.swift_dispatch_slots.some(
|
||||
(item) =>
|
||||
item.table_kind === "witness_table" &&
|
||||
item.owner === `${type}: ${protocol}` &&
|
||||
item.implementation_address !== null &&
|
||||
item.decode.status === "decoded",
|
||||
),
|
||||
`Nested Swift witness ${type}: ${protocol} missing (${mode.mode})`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!metadata.swift_dispatch_slots.some(
|
||||
(slot) =>
|
||||
@@ -204,6 +235,10 @@ const EXPECTED_SWIFT_DECLS = [
|
||||
["struct", "ReaDeclFixtureOuter.Inner", false],
|
||||
["struct", "ReaDeclFixtureGeneric", true],
|
||||
["actor", "ReaDeclFixtureActor", false],
|
||||
["enum", "ReaNestedA", false],
|
||||
["struct", "ReaNestedA.Implementation", false],
|
||||
["enum", "ReaNestedB", false],
|
||||
["struct", "ReaNestedB.Implementation", false],
|
||||
];
|
||||
|
||||
/** Stable declaration facts compared across the symbols and stripped builds. */
|
||||
@@ -336,6 +371,26 @@ try {
|
||||
await checkSwiftSymbolAddresses(swiftPath, declarations);
|
||||
unstrippedDeclarations = declarations;
|
||||
} else assert.deepEqual(declarations, unstrippedDeclarations);
|
||||
if (mode.mode === "chained" && variant === "stripped") {
|
||||
const cli = await artifactCliEvidence(
|
||||
"inspect-native-dispatch-metadata",
|
||||
swiftPath,
|
||||
);
|
||||
const cliResult = nativeDispatchMetadataResultSchema.parse(
|
||||
cli.normalized_result,
|
||||
);
|
||||
assert.equal(cliResult.target_sha256, target.sha256);
|
||||
checkSwift(cliResult.result, mode);
|
||||
await withArtifactMcp(swiftPath, async (client) => {
|
||||
const mcpResult = nativeDispatchMetadataResultSchema.parse(
|
||||
await artifactMcpResult(client, "inspect_native_dispatch_metadata"),
|
||||
);
|
||||
assert.equal(mcpResult.target_sha256, target.sha256);
|
||||
checkSwift(mcpResult.result, mode);
|
||||
assert.deepEqual(mcpResult.result, cliResult.result);
|
||||
});
|
||||
}
|
||||
|
||||
results.push({
|
||||
variant: `swift-${mode.mode}-${variant}`,
|
||||
target_sha256: target.sha256,
|
||||
|
||||
Reference in new issue
Block a user