fix(settings): preserve runtime contracts while restacking operation limits

Retain the original stack merge resolutions for runtime update modes, migrated guard exceptions, deadline ownership and boundary expectations on the merged settings base.

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
N0zoM1z0andgensokyo-dev authored and morluto committed 2026-11-11 20:16:24 +08:00
1 parent 6b102e61b3
commit 11bc738198
10 files changed
+31 -47

No files matched your search

+7 -24
View File
@@ -857,20 +857,11 @@ export const parseRuntimeSettingEnvironments = (source) => {
return names;
};
/** Existing launch-seed migrations. A new per-call policy is rejected. */
const TEMPORARY_PER_CALL_LIMITS = new Set([
"REA_BRIDGE_MAX_REQUEST_BYTES",
"REA_DEBUGGER_COMMAND_DEADLINE_MS",
"REA_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES",
"REA_HOPPER_CALLER_SCAN_BUDGET_MS",
"REA_JEB_MCP_TIMEOUT_MS",
"REA_KUNA_PROJECT_TIMEOUT_MS",
"REA_KUNA_TIMEOUT_MS",
"REA_PWNTOOLS_MAX_INPUT_BYTES",
"REA_WINDOWS_CHANGE_TIME_DRIFT_MS",
"REA_WINDOWS_CHANGE_TIME_SETTLE_MAX_POLLS",
"REA_WINDOWS_CHANGE_TIME_SETTLE_POLL_MS",
]);
/**
* Names still registered as per-call while they move into ANALYSIS_SETTINGS.
* This branch has moved those limits, so the allowlist is empty.
*/
const TEMPORARY_PER_CALL_LIMITS = new Set([]);
/** Temporary exceptions must be removed from both the registry and allowlist. */
export const perCallLimitFailures = (listed, temporary) => {
@@ -908,15 +899,7 @@ if (direct) {
failures.push(
`${name} is both a runtime setting and on the exception list`,
);
if (policy === "restart-only" && read === "per-call")
failures.push(
`${name} is restart-only but is recorded as a per-call read`,
);
if (
policy === "restart-only" &&
read !== "startup" &&
read !== "construction"
)
if (read !== "startup" && read !== "construction")
failures.push(`${name} is restart-only but is not read at startup`);
}
failures.push(
@@ -1055,7 +1038,7 @@ if (direct) {
process.exit(1);
}
console.log(
`Numeric REA_ limits: ${runtime.size} runtime, ${[...listed.values()].filter((entry) => entry.policy === "restart-only").length} restart-only, ${[...listed.values()].filter((entry) => entry.policy === "per-call").length} per-call pending migration, ${discovered.size} discovered.`,
`Numeric REA_ limits: ${runtime.size} runtime, ${listed.size} restart-only, ${discovered.size} discovered.`,
);
console.log(
"Read-site kinds describe syntax only; Node lint does not verify reachability, invocation frequency or complete reader coverage.",
+1 -2
View File
@@ -291,8 +291,7 @@ it("rejects an out-of-range per-call limit and reads an accepted value from the
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
expect(accepted.value).toMatchObject({
effect: "immediate",
rea_restart_required: false,
pending_restart: false,
});
const parsed = parseConfig({});
if (!parsed.ok) throw parsed.error;
+2 -2
View File
@@ -85,8 +85,8 @@ it("explains the time budget and that fixed byte allowances have no override", (
source: "setting",
minimum: MINIMUM_HOPPER_CALLER_SCAN_BUDGET_MS,
maximum: MAX_NODE_TIMER_DELAY_MS,
effect: "immediate",
rea_restart_required: false,
update_mode: "runtime",
pending_restart: false,
});
expect(DEFAULT_HOPPER_CALLER_SCAN_BUDGET_MS).toBe(2000);
const page = hopperCallerScanRecovery(
+1 -1
View File
@@ -97,7 +97,7 @@ it.each([
const facts = {
setting: bound.setting,
constraint: message,
update_mode: "restart" as const,
update_mode: "runtime" as const,
};
expect(resolved.error.settings).toEqual([facts]);
expect(resolved.error.settings[0]).not.toHaveProperty("pending_restart");
+3 -3
View File
@@ -64,7 +64,7 @@ export interface WindowsChangeTimeSettingFailure {
readonly floor: number;
readonly maximum: number;
readonly unit: "ms" | "polls";
readonly update_mode: "restart";
readonly update_mode: "runtime";
}
/** Effective settle budget and whether each part came from the default or a setting. */
@@ -88,14 +88,14 @@ const windowsChangeTimeSettingFailure = (
floor: bound.floor,
maximum: bound.maximum,
unit: bound.unit,
update_mode: "restart",
update_mode: "runtime",
});
/** Name the rejected value, its source, and the allowed range. */
const windowsChangeTimeSettingMessage = (
failure: WindowsChangeTimeSettingFailure,
): string =>
`The supplied ${failure.setting} value is ${failure.currentValue} ${failure.unit} (source ${failure.source}; default ${String(failure.defaultValue)} ${failure.unit}, floor ${String(failure.floor)} ${failure.unit}, maximum ${String(failure.maximum)} ${failure.unit}). Set ${failure.setting} in the environment that starts REA to a positive decimal integer from ${String(failure.floor)} to ${String(failure.maximum)}, then start a new REA invocation.`;
`The supplied ${failure.setting} value is ${failure.currentValue} ${failure.unit} (source ${failure.source}; default ${String(failure.defaultValue)} ${failure.unit}, floor ${String(failure.floor)} ${failure.unit}, maximum ${String(failure.maximum)} ${failure.unit}). windows change-time settings can be changed at runtime with update_analysis_settings. The launch seed ${failure.setting} must be a positive decimal integer from ${String(failure.floor)} to ${String(failure.maximum)}. The rejected value is not active.`;
const inRange = (bound: WindowsChangeTimeBound, value: string): boolean => {
if (!decimalInteger.test(value)) return false;
+11
View File
@@ -336,6 +336,7 @@ export const ANALYSIS_SETTINGS = {
operations: ["match_native_functions"],
},
"bridge.max_request_bytes": {
updateMode: "runtime",
environment: "REA_BRIDGE_MAX_REQUEST_BYTES",
defaultValue: DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
minimum: MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
@@ -396,6 +397,7 @@ export const ANALYSIS_SETTINGS = {
],
},
"debugger.command_deadline_ms": {
updateMode: "runtime",
...timer,
environment: "REA_DEBUGGER_COMMAND_DEADLINE_MS",
defaultValue: DEFAULT_DEBUGGER_COMMAND_DEADLINE_MS,
@@ -413,6 +415,7 @@ export const ANALYSIS_SETTINGS = {
],
},
"debugger.max_command_output_bytes": {
updateMode: "runtime",
environment: "REA_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES",
defaultValue: DEFAULT_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
minimum: MINIMUM_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
@@ -424,6 +427,7 @@ export const ANALYSIS_SETTINGS = {
operations: ["gdb_console", "rizin_debug_command"],
},
"hopper.caller_scan_budget_ms": {
updateMode: "runtime",
...timer,
environment: "REA_HOPPER_CALLER_SCAN_BUDGET_MS",
defaultValue: DEFAULT_HOPPER_CALLER_SCAN_BUDGET_MS,
@@ -436,6 +440,7 @@ export const ANALYSIS_SETTINGS = {
],
},
"jeb.mcp_timeout_ms": {
updateMode: "runtime",
...timer,
environment: "REA_JEB_MCP_TIMEOUT_MS",
defaultValue: DEFAULT_JEB_MCP_TIMEOUT_MS,
@@ -448,6 +453,7 @@ export const ANALYSIS_SETTINGS = {
],
},
"kuna.project_timeout_ms": {
updateMode: "runtime",
...timer,
environment: "REA_KUNA_PROJECT_TIMEOUT_MS",
defaultValue: DEFAULT_KUNA_PROJECT_TIMEOUT_MS,
@@ -466,6 +472,7 @@ export const ANALYSIS_SETTINGS = {
],
},
"kuna.function_timeout_ms": {
updateMode: "runtime",
...timer,
environment: "REA_KUNA_TIMEOUT_MS",
defaultValue: DEFAULT_KUNA_FUNCTION_TIMEOUT_MS,
@@ -478,6 +485,7 @@ export const ANALYSIS_SETTINGS = {
],
},
"pwntools.max_input_bytes": {
updateMode: "runtime",
environment: "REA_PWNTOOLS_MAX_INPUT_BYTES",
defaultValue: DEFAULT_PWNTOOLS_MAX_INPUT_BYTES,
minimum: 1,
@@ -489,6 +497,7 @@ export const ANALYSIS_SETTINGS = {
parameter: "producer_limits.input_bytes",
},
"windows.change_time_drift_ms": {
updateMode: "runtime",
environment: "REA_WINDOWS_CHANGE_TIME_DRIFT_MS",
defaultValue: DEFAULT_WINDOWS_CHANGE_TIME_DRIFT_MS,
minimum: 1,
@@ -505,6 +514,7 @@ export const ANALYSIS_SETTINGS = {
],
},
"windows.change_time_settle_poll_ms": {
updateMode: "runtime",
environment: "REA_WINDOWS_CHANGE_TIME_SETTLE_POLL_MS",
defaultValue: DEFAULT_WINDOWS_CHANGE_TIME_SETTLE_POLL_MS,
minimum: 1,
@@ -521,6 +531,7 @@ export const ANALYSIS_SETTINGS = {
],
},
"windows.change_time_settle_max_polls": {
updateMode: "runtime",
environment: "REA_WINDOWS_CHANGE_TIME_SETTLE_MAX_POLLS",
defaultValue: DEFAULT_WINDOWS_CHANGE_TIME_SETTLE_MAX_POLLS,
minimum: 1,
-9
View File
@@ -405,8 +405,6 @@ export class KunaSessionClient implements AnalysisClient {
#installation:
| SharedStartupAttempt<Result<KunaInstallation, AnalysisError>>
| undefined;
/** Launch configuration captured when the export starts, for its deadline source. */
#kunaConfiguration: KunaConfiguration | undefined;
/** Deadline the project export accepted. Not recomputed from later settings. */
#acceptedProjectDeadline: KunaDeadline | undefined;
#inventory:
@@ -2101,14 +2099,7 @@ export class KunaSessionClient implements AnalysisClient {
return this.#starting;
}
#projectDeadline(): KunaDeadline | undefined {
// The export already accepted this deadline. Re-resolving after a reset
// would throw away the value the failed export used.
return this.#acceptedProjectDeadline;
}
async #lookupOrBuild(installation: KunaInstallation): Promise<void> {
this.#kunaConfiguration = installation.configuration;
this.#acceptedProjectDeadline ??= kunaProjectDeadline(
installation.configuration,
);
@@ -84,8 +84,8 @@ it("runs a cold independent request without starting a project export", async ()
limit: {
setting: "kuna.function_timeout_ms",
effective_value: 300,
effect: "immediate",
rea_restart_required: false,
update_mode: "runtime",
pending_restart: false,
minimum: 1,
maximum: 2147483647,
},
@@ -372,8 +372,8 @@ it("names the setting that owns a single-function deadline", async () => {
limit: {
setting: "kuna.function_timeout_ms",
effective_value: 300,
effect: "immediate",
rea_restart_required: false,
update_mode: "runtime",
pending_restart: false,
},
},
});
@@ -438,8 +438,8 @@ it.each([
limit: {
setting: "kuna.project_timeout_ms",
effective_value: 500,
effect: "immediate",
rea_restart_required: false,
update_mode: "runtime",
pending_restart: false,
},
},
});