Skip to content

Commit dc710ce

Browse files
committed
merge: integrate setting update_mode from morluto#9885
Depends on morluto#9885. That branch replaces rea_restart_required with update_mode and pending_restart and forbids the legacy fields.
2 parents 67cc63d + f2b7c3b commit dc710ce

40 files changed

Lines changed: 782 additions & 42 deletions

‎docs/.vitepress/documentation.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,11 @@ export const documentationSections = [
358358
source: "design/limits-and-recovery.md",
359359
route: "/design/limits-and-recovery",
360360
},
361+
{
362+
text: "REA limit inventory",
363+
source: "design/rea-limit-inventory.md",
364+
route: "/design/rea-limit-inventory",
365+
},
361366
{
362367
text: "MCP tool design",
363368
source: "development/tool-design.md",

‎docs/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ product pages. The VitePress documentation source is this `docs/` tree.
111111
## Development and verification
112112

113113
- [Settings and recovery](design/limits-and-recovery.md)
114+
- [REA limit inventory](design/rea-limit-inventory.md)
114115
- [Flutter budget calibration](verification/flutter-budget-calibration.md)
115116
- [Source-map budget calibration](verification/source-map-budget-calibration.md)
116117
- [Source-location delivery qualification](verification/source-location-delivery.md)

‎docs/design/limits-and-recovery.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ who owns each value, when an update takes effect, how providers enforce it, and
55
how to extend the system. The public tool contract is in
66
[MCP contracts](../reference/mcp-contracts.md#editable-analysis-settings); recorded runtime
77
checks and their limitations are in the
8-
[verification report](../verification/runtime-analysis-settings.md).
8+
[verification report](../verification/runtime-analysis-settings.md). The
9+
[limit inventory](rea-limit-inventory.md) lists every numeric `REA_` limit, which
10+
of them are runtime settings, and which stay restart-only.
911

1012
## Problem and design boundary
1113

@@ -287,7 +289,10 @@ that argument; raising a runtime default cannot override it.
287289

288290
The resource owner constructs `details.limit` from the registry plus the budget
289291
it actually enforced. Metadata includes `agent_editable: true`,
290-
`scope: "rea-runtime"`, and `rea_restart_required: false`. Optional
292+
`scope: "rea-runtime"`, `update_mode`, and `pending_restart`. `update_mode:
293+
"runtime"` means `update_analysis_settings` can change the active value.
294+
`pending_restart` is false when that reported value is the one this process is
295+
using. Optional
291296
`required_value`, `source`, and `parameter` fields are supplied only where known
292297
or applicable. Do not infer a source because a value happens to equal a default.
293298
Error copying, attribution, redaction, and delivery must preserve these facts.
@@ -302,8 +307,9 @@ so its adapter projects with an explicit `"invocation"` caller lifetime.
302307
`scope: "rea-invocation"`, `applies_at: "next_invocation"` and the launch
303308
`environment` seed when one exists. `agent_editable: true` still holds, because
304309
the caller chooses that environment value or per-call `parameter` for the new
305-
invocation; `rea_restart_required: false` means no running REA process needs a
306-
restart. A producer keeps guidance that holds only while its caller retains the
310+
invocation; `update_mode: "runtime"` and `pending_restart: false` mean the
311+
reported value is active in this process. `update_mode: "restart"` means
312+
`update_analysis_settings` does not change it. A producer keeps guidance that holds only while its caller retains the
307313
session, such as "the Ghidra session remains usable" or a same-session runtime
308314
update, in `continuingCallerGuidance`; the shared presentation adds it only for
309315
a continuing caller. The producer's own message and remediation must stay true

‎docs/design/rea-limit-inventory.md‎

Lines changed: 226 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/verification/runtime-analysis-settings-ghidra-2026-11-09.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@
192192
"applies_at": "next_operation",
193193
"agent_editable": true,
194194
"scope": "rea-runtime",
195-
"rea_restart_required": false
195+
"update_mode": "runtime",
196+
"pending_restart": false
196197
},
197198
"remediation": {
198199
"action": "Use analyze_function (CLI: `rea function`) for listing facts. Call update_analysis_settings with setting ghidra.decompile_timeout_seconds and action set to raise the per-function deadline for the next operation on this Program. For CLI or future REA processes, set REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Set the MCP tool-call deadline to cover startup and the full call; earlier cancellation closes the session. `rea setup` estimates deadlines for new Codex/Kimi registrations and preserves existing positive values; edit existing deadlines yourself."

‎docs/verification/runtime-analysis-settings-ghidra-integration-2026-11-09.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@
196196
"applies_at": "next_operation",
197197
"agent_editable": true,
198198
"scope": "rea-runtime",
199-
"rea_restart_required": false
199+
"update_mode": "runtime",
200+
"pending_restart": false
200201
},
201202
"remediation": {
202203
"action": "Use analyze_function (CLI: `rea function`) for listing facts. Call update_analysis_settings with setting ghidra.decompile_timeout_seconds and action set to raise the per-function deadline for the next operation on this Program. For CLI or future REA processes, set REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Set the MCP tool-call deadline to cover startup and the full call; earlier cancellation closes the session. `rea setup` estimates deadlines for new Codex/Kimi registrations and preserves existing positive values; edit existing deadlines yourself."

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@
148148
"build:termux:run": "npm run compile:termux && npm run artifacts:skills",
149149
"format": "oxfmt .",
150150
"format:check": "oxfmt --check .",
151-
"lint": "node scripts/ci/static-check-report.mjs lint && npm run verify:module-boundaries",
151+
"lint": "node scripts/ci/static-check-report.mjs lint && npm run verify:module-boundaries && node scripts/verify/rea-numeric-limits.mjs",
152152
"lint:details": "oxlint && npm run verify:module-boundaries",
153153
"lint:fix": "oxlint --fix && npm run verify:module-boundaries",
154154
"knip": "knip --cache",

‎scripts/verify/architecture-guards.mjs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { spawnSync } from "node:child_process";
12
import { readFile, readdir } from "node:fs/promises";
23
import { join, relative, sep } from "node:path";
34
import { fileURLToPath } from "node:url";
@@ -193,4 +194,10 @@ if (failures.length > 0) {
193194
console.error(`Architecture guards failed:\n\n${failures.join("\n\n")}\n`);
194195
process.exit(1);
195196
}
197+
const numericLimits = spawnSync(
198+
process.execPath,
199+
[fileURLToPath(new URL("./rea-numeric-limits.mjs", import.meta.url))],
200+
{ stdio: "inherit" },
201+
);
202+
if (numericLimits.status !== 0) process.exit(numericLimits.status ?? 1);
196203
console.log(`Architecture guards passed across ${files.length} source files.`);
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { readFile, readdir } from "node:fs/promises";
2+
import { join, relative, sep } from "node:path";
3+
import { fileURLToPath } from "node:url";
4+
5+
// Every numeric REA_ limit under src must be an analysis setting or a
6+
// documented exception with a reason. restart-only is durable. per-call is a
7+
// launch-captured limit whose runtime migration deletes the registry entry.
8+
// The three limits moving on #9868, #9865, and #9836 are not in this tree;
9+
// once their names appear here they must join ANALYSIS_SETTINGS.
10+
11+
const root = fileURLToPath(new URL("../../", import.meta.url));
12+
const definitionsPath = join(root, "src/domain/analysisSettingDefinitions.ts");
13+
const registryPath = join(root, "src/domain/numericLimitRegistry.ts");
14+
15+
const listFiles = async (dir) => {
16+
const output = [];
17+
for (const entry of await readdir(dir, { withFileTypes: true })) {
18+
const path = join(dir, entry.name);
19+
if (entry.isDirectory()) {
20+
if (entry.name === "node_modules" || entry.name === "dist") continue;
21+
output.push(...(await listFiles(path)));
22+
} else if (
23+
entry.name.endsWith(".ts") &&
24+
!entry.name.endsWith(".test.ts") &&
25+
!entry.name.endsWith(".fixture.ts")
26+
)
27+
output.push(path);
28+
}
29+
return output;
30+
};
31+
32+
const isNumericLimit = (name) =>
33+
/(?:_BYTES|_MS|_MIB|_SECONDS|_POLLS|_JOBS|_ELEMENTS|_COUNT|_SESSIONS|_COMMANDS)$/u.test(
34+
name,
35+
) ||
36+
/_MAX_/u.test(name) ||
37+
/_(?:TIMEOUT|DEADLINE|BUDGET)(?:_|$)/u.test(name) ||
38+
/_HEAP(?:_|$)/u.test(name);
39+
40+
const namesIn = (source, pattern) =>
41+
new Set([...source.matchAll(pattern)].map((match) => match[1]));
42+
43+
const failures = [];
44+
const definitions = await readFile(definitionsPath, "utf8");
45+
const registry = await readFile(registryPath, "utf8");
46+
const runtime = namesIn(definitions, /environment:\s*"(REA_[A-Z0-9_]+)"/gu);
47+
const declaredNames = [
48+
...registry.matchAll(/name:\s*"(REA_[A-Z0-9_]+)"/gu),
49+
].map((match) => match[1]);
50+
const entries = [
51+
...registry.matchAll(
52+
/name:\s*"(REA_[A-Z0-9_]+)"\s*,\s*update_mode:\s*"(runtime|restart)"\s*,\s*policy:\s*"(restart-only|per-call)"\s*,\s*read:\s*"(startup|per-call)"\s*,\s*reason:\s*"((?:\\.|[^"\\])*)"/gu,
53+
),
54+
];
55+
const listed = new Map();
56+
for (const match of entries) {
57+
const [, name, updateMode, policy, read, reason] = match;
58+
if (listed.has(name)) failures.push(`${name} is listed twice`);
59+
listed.set(name, { updateMode, policy, read, reason });
60+
if (reason.trim().length < 40)
61+
failures.push(`${name} needs a reason that states why`);
62+
if (updateMode !== "restart")
63+
failures.push(
64+
`${name} is outside ANALYSIS_SETTINGS, so its update_mode must be restart`,
65+
);
66+
if (runtime.has(name))
67+
failures.push(
68+
`${name} is both a runtime setting and on the exception list`,
69+
);
70+
if (policy === "per-call" && read !== "per-call")
71+
failures.push(`${name} is per-call policy but is not read per call`);
72+
if (policy === "restart-only" && read !== "startup")
73+
failures.push(`${name} is restart-only but is not read at startup`);
74+
}
75+
for (const name of declaredNames) {
76+
if (!listed.has(name))
77+
failures.push(`${name} is registered without update_mode`);
78+
}
79+
const settingBodies = [
80+
...definitions.matchAll(/\n {2}"[^"]+": \{([\s\S]*?)\n {2}\},/gu),
81+
];
82+
if (settingBodies.length === 0)
83+
failures.push("ANALYSIS_SETTINGS did not parse into setting bodies");
84+
for (const [, body] of settingBodies) {
85+
if (!/updateMode:\s*"(?:runtime|restart)"/u.test(body))
86+
failures.push("an analysis setting is missing updateMode");
87+
}
88+
89+
const discovered = new Map();
90+
for (const path of await listFiles(join(root, "src"))) {
91+
const source = await readFile(path, "utf8");
92+
for (const match of source.matchAll(/(?<![A-Z0-9_])REA_[A-Z0-9_]+/gu)) {
93+
const name = match[0];
94+
if (!isNumericLimit(name)) continue;
95+
const relativePath = relative(root, path).split(sep).join("/");
96+
discovered.set(name, relativePath);
97+
}
98+
}
99+
100+
for (const [name, file] of [...discovered].sort(([left], [right]) =>
101+
left < right ? -1 : left > right ? 1 : 0,
102+
)) {
103+
if (!runtime.has(name) && !listed.has(name))
104+
failures.push(
105+
`${name} in ${file} is neither an analysis setting nor docs/design/rea-limit-inventory.md via src/domain/numericLimitRegistry.ts`,
106+
);
107+
}
108+
for (const name of listed.keys()) {
109+
if (!discovered.has(name))
110+
failures.push(`${name} is registered but no longer appears in src`);
111+
}
112+
113+
const forbidden =
114+
/(?<![A-Za-z0-9_])(?:rea_restart_required|restartRequired|restart_required)(?![A-Za-z0-9_])|effect:\s*"(?:immediate|next_process)"|SettingChangeEffect/gu;
115+
const scanRoots = ["src", "tests", "docs", ".agents", "scripts"].map((dir) =>
116+
join(root, dir),
117+
);
118+
const skipDir = new Set([
119+
"node_modules",
120+
"dist",
121+
".turbo",
122+
".cache",
123+
"rea-numeric-limits.mjs",
124+
]);
125+
const listText = async (dir) => {
126+
const output = [];
127+
let entries;
128+
try {
129+
entries = await readdir(dir, { withFileTypes: true });
130+
} catch {
131+
return output;
132+
}
133+
for (const entry of entries) {
134+
if (skipDir.has(entry.name)) continue;
135+
const path = join(dir, entry.name);
136+
if (entry.isDirectory()) output.push(...(await listText(path)));
137+
else if (/\.(?:ts|tsx|mjs|js|md|json|py|yml|yaml)$/u.test(entry.name))
138+
output.push(path);
139+
}
140+
return output;
141+
};
142+
for (const dir of scanRoots) {
143+
for (const path of await listText(dir)) {
144+
if (path.endsWith(`${sep}rea-numeric-limits.mjs`)) continue;
145+
const source = await readFile(path, "utf8");
146+
const hit = source.match(forbidden);
147+
if (hit)
148+
failures.push(
149+
`${relative(root, path).split(sep).join("/")} still uses ${[...new Set(hit)].join(", ")}`,
150+
);
151+
}
152+
}
153+
154+
if (failures.length > 0) {
155+
console.error(`Numeric REA_ limit check failed:\n${failures.join("\n")}`);
156+
process.exit(1);
157+
}
158+
console.log(
159+
`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.`,
160+
);

‎src/android/JadxOperationTimeout.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ it("names REA_JADX_OPERATION_TIMEOUT_MS when the operation deadline elapses", ()
3636
applies_at: "next_operation",
3737
agent_editable: true,
3838
scope: "rea-runtime",
39-
rea_restart_required: false,
39+
update_mode: "runtime",
40+
pending_restart: false,
4041
},
4142
});
4243
expect(projected).toMatchObject({

0 commit comments

Comments
 (0)