Files
reverse-engineering-anything/src/application/native/NativeMatchingAdmission.ts
T
Kaoru0822andClaude Opus 5.5 87d3d6022c feat(native): make the function-matching allowance a runtime setting
match_native_functions refused inventories above its fixed 96 MiB
representation allowance with "This release has no runtime override", so
ordinary 20k-50k function builds could not be matched at all.

Add native.matching_representation_bytes (1 byte to 1 GiB, default 96 MiB,
next operation) with the REA_NATIVE_MATCHING_REPRESENTATION_BYTES launch seed,
copied by rea setup. MCP resolves the accepted operation's override over the
launch value on each call; the CLI uses its launch environment. A refusal
binds details.limit with the effective value, source and bounds, and its
recovery names the setting and the seed. An invalid seed is refused by
matching alone, so MCP stays available and a runtime value repairs it.

Fixes #6406

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:48:25 +00:00

108 lines
4.4 KiB
TypeScript

import { setImmediate } from "node:timers/promises";
import { AnalysisResourceConstraintError } from "../../domain/analysisErrorCore.js";
import { analysisSettingLimit } from "../../domain/analysisSettingDefinitions.js";
import {
DEFAULT_NATIVE_MATCHING_REPRESENTATION_BYTES,
MAXIMUM_NATIVE_MATCHING_REPRESENTATION_BYTES,
} from "../../domain/analysisResourceLimits.js";
import {
DEFAULT_NATIVE_MATCHING_ALLOWANCE,
NATIVE_MATCHING_ALLOWANCE_SETTING,
type NativeMatchingAllowance,
} from "../../config/nativeMatchingAllowance.js";
const MEBIBYTE = 1024 * 1024;
const byteAllowance = (bytes: number): string =>
Number.isInteger(bytes / MEBIBYTE)
? `${String(bytes / MEBIBYTE)} MiB / ${String(bytes)} bytes`
: `${String(bytes)} bytes`;
/** User-visible recovery for the effective matching representation allowance. */
const nativeMatchingAllowanceRecovery = (
allowance: NativeMatchingAllowance,
): string =>
[
`Native function matching is limited to ${byteAllowance(allowance.maximumBytes)} of representation${allowance.source === "setting" ? ` by ${NATIVE_MATCHING_ALLOWANCE_SETTING}` : ""}.`,
`On MCP, call update_analysis_settings with setting native.matching_representation_bytes, action set, and a value in bytes up to ${byteAllowance(MAXIMUM_NATIVE_MATCHING_REPRESENTATION_BYTES)} (default ${byteAllowance(DEFAULT_NATIVE_MATCHING_REPRESENTATION_BYTES)}), then retry the original request. It applies to the next operation without restarting REA. For CLI or future REA processes, set ${NATIVE_MATCHING_ALLOWANCE_SETTING} in the launch environment.`,
"This budget is not a bound on total process RSS; raising it lets matching retain more representation in the REA process.",
"Splitting inventories into separate matches changes global name and body uniqueness, so it is not an equivalent workaround.",
"Repeating the unchanged request fails the same way.",
].join(" ");
/** Budget the actual selected observations, before cloning or index allocation. */
export class NativeMatchingAdmission {
readonly #maximum: number;
readonly #source: NativeMatchingAllowance["source"];
#remaining: number;
#work = 0;
readonly #seen = new WeakSet<object>();
constructor(
readonly operation: string,
readonly signal?: AbortSignal,
allowance: NativeMatchingAllowance = DEFAULT_NATIVE_MATCHING_ALLOWANCE,
) {
// Read the accepted operation's value once, so the refusal reports the
// budget this request actually ran under.
this.#maximum = allowance.maximumBytes;
this.#source = allowance.source;
this.#remaining = this.#maximum;
}
readonly checkpoint = async (bytes = 0): Promise<void> => {
this.#remaining -= bytes;
if (this.#remaining < 0)
throw new AnalysisResourceConstraintError(
this.operation,
"capacity",
`Native function matching exceeds its ${byteAllowance(this.#maximum)} representation allowance (native.matching_representation_bytes).`,
{ maximum_bytes: this.#maximum },
{
retryable: false,
remediationAction: nativeMatchingAllowanceRecovery({
maximumBytes: this.#maximum,
source: this.#source,
}),
limit: analysisSettingLimit(
"native.matching_representation_bytes",
this.#maximum,
this.#source,
),
},
);
this.signal?.throwIfAborted();
if (++this.#work % 256 === 0)
await setImmediate(
undefined,
this.signal === undefined ? {} : { signal: this.signal },
);
};
async admit(root: unknown): Promise<void> {
const pending: Iterator<unknown>[] = [[root][Symbol.iterator]()];
while (pending.length) {
const next = pending[pending.length - 1]!.next();
if (next.done) {
pending.pop();
continue;
}
const value = next.value;
if (typeof value === "object" && value !== null) {
if (this.#seen.has(value)) continue;
this.#seen.add(value);
await this.checkpoint(128);
// Iterator traversal avoids a second width-sized array of child values.
pending.push(
(function* () {
for (const key in value)
if (Object.hasOwn(value, key)) {
yield key;
yield (value as Record<string, unknown>)[key];
}
})(),
);
} else
await this.checkpoint(
typeof value === "string" ? 64 + value.length * 4 : 128,
);
}
}
}