fix(settings): preserve syntax guard contracts across stack integration

Retain existing merge resolutions for update modes, source-scan failures and external-reader fixtures while replaying the settings layer on the merged inventory. Preserve parent provenance, candidate counts and presentation-test resource bounds.

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 19:48:33 +08:00
1 parent 1626461ec1
commit 91b11f62c9
5 files changed
+106 -7

No files matched your search

+1 -1
View File
@@ -133,7 +133,7 @@ These stay out of `update_analysis_settings`.
## Restart-only non-numeric settings
No floor or maximum. They are read from the launch environment (startup). None of them set `effect`. Recovery text tells the caller to change the launch
No floor or maximum. They are read from the launch environment (startup). None of them report a structured update mode. Recovery text tells the caller to change the launch
environment, which takes effect on the next process. They stay
restart-only because they choose a command, a binary, a filesystem root, the
browser sandbox, secret patterns, the provider, or the shape of the MCP server
+1
View File
@@ -23,6 +23,7 @@ export function perCallLimitFailures(
/** Parse static registry declarations, including external-reader provenance. */
export function parseNumericLimitRegistry(source: string): Array<{
name: string;
update_mode: "restart";
policy: "restart-only" | "per-call";
read: "startup" | "per-call" | "construction";
reason: string;
+72 -2
View File
@@ -701,17 +701,20 @@ export const parseNumericLimitRegistry = (source) => {
return array.elements.map((element) => {
const value = fields(element);
const name = text(value, "name");
const update_mode = text(value, "update_mode");
const policy = text(value, "policy");
const read = text(value, "read");
const reason = text(value, "reason");
if (
exactLimit(name) === undefined ||
update_mode !== "restart" ||
!["restart-only", "per-call"].includes(policy) ||
!["startup", "per-call", "construction"].includes(read)
)
throw new Error(`Invalid numeric limit registry entry ${name}`);
const source = value.get("provenance");
if (source === undefined) return { name, policy, read, reason };
if (source === undefined)
return { name, update_mode, policy, read, reason };
const provenance = fields(unwrapExpression(source));
const kind = text(provenance, "kind");
const producer = text(provenance, "source");
@@ -728,6 +731,7 @@ export const parseNumericLimitRegistry = (source) => {
throw new Error(`Invalid external provenance for ${name}`);
return {
name,
update_mode,
policy,
read,
reason,
@@ -814,13 +818,26 @@ export const parseRuntimeSettingEnvironments = (source) => {
!ts.isPropertyAssignment(property) ||
(!ts.isIdentifier(property.name) &&
!ts.isStringLiteral(property.name)) ||
property.name.text === "environment",
["environment", "updateMode"].includes(property.name.text),
);
})
)
throw new Error(
"Analysis setting fields must be static named assignments",
);
const modes = definition.properties.filter(
(field) =>
ts.isPropertyAssignment(field) &&
(ts.isIdentifier(field.name) || ts.isStringLiteral(field.name)) &&
field.name.text === "updateMode",
);
if (
modes.length !== 1 ||
!["runtime", "restart"].includes(
literalText(unwrapExpression(modes[0].initializer)),
)
)
throw new Error("Analysis setting must declare a static updateMode");
const fields = definition.properties.filter(
(field) =>
ts.isPropertyAssignment(field) &&
@@ -980,6 +997,59 @@ if (direct) {
if (failure !== undefined) failures.push(failure);
}
const parameterOwners = [
"src/artifacts/ArtifactReaderLimits.ts",
"src/domain/artifactReaderLimits.ts",
"src/domain/webNetworkCapture.ts",
];
for (const relativePath of parameterOwners) {
const source = await readFile(join(root, relativePath), "utf8");
if (/update_mode:\s*"runtime"/u.test(source))
failures.push(
`${relativePath} is a per-call parameter, so update_mode cannot be runtime`,
);
if (!/update_mode:\s*"parameter"/u.test(source))
failures.push(`${relativePath} must report update_mode parameter`);
}
const forbidden =
/(?<![A-Za-z0-9_])(?:rea_restart_required|restartRequired|restart_required)(?![A-Za-z0-9_])|effect:\s*"(?:immediate|next_process)"|SettingChangeEffect/gu;
// Docs and verification receipts keep the field names they recorded.
// Source, tests, skills, and scripts cannot reintroduce those names.
const scanRoots = ["src", "tests", ".agents", "scripts"].map((dir) =>
join(root, dir),
);
const skipDir = new Set([
"node_modules",
"dist",
".turbo",
".cache",
"rea-numeric-limits.mjs",
]);
const listText = async (dir) => {
const output = [];
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (skipDir.has(entry.name)) continue;
const path = join(dir, entry.name);
if (entry.isDirectory()) output.push(...(await listText(path)));
else if (/\.(?:ts|tsx|mjs|js|md|json|py|yml|yaml)$/u.test(entry.name))
output.push(path);
}
return output;
};
for (const dir of scanRoots) {
for (const path of await listText(dir)) {
if (path.endsWith(`${sep}rea-numeric-limits.mjs`)) continue;
const source = await readFile(path, "utf8");
const hit = source.match(forbidden);
if (hit)
failures.push(
`${relative(root, path).split(sep).join("/")} still uses ${[...new Set(hit)].join(", ")}`,
);
}
}
if (failures.length > 0) {
console.error(`Numeric REA_ limit check failed:\n${failures.join("\n")}`);
process.exit(1);
@@ -14,8 +14,17 @@ processTest.skipIf(
const root = await createTestTempDirectory("rea-limit-permission-");
await mkdir(join(root, "scripts/verify"), { recursive: true });
await mkdir(join(root, "tests/nested"), { recursive: true });
for (const name of ["src", ".agents", "node_modules"])
for (const name of ["src", ".agents", "node_modules", "bridge"])
await symlink(resolve(name), join(root, name), "dir");
await symlink(
resolve("scripts/hopper-demo-x11.py"),
join(root, "scripts/hopper-demo-x11.py"),
);
await symlink(
resolve("tests/boundary"),
join(root, "tests/boundary"),
"dir",
);
await copyFile(
"scripts/verify/rea-numeric-limits.mjs",
join(root, "scripts/verify/rea-numeric-limits.mjs"),
+22 -3
View File
@@ -17,11 +17,21 @@ it("reads only actual settings environment properties", () => {
// environment: "REA_COMMENT_MAX_BYTES"
const text = 'environment: "REA_STRING_MAX_BYTES"';
export const ANALYSIS_SETTINGS = {
first: { environment: "REA_REAL_MAX_BYTES" },
parameter: { defaultValue: 1 },
first: { updateMode: "runtime", environment: "REA_REAL_MAX_BYTES" },
parameter: { updateMode: "runtime", defaultValue: 1 },
} as const;
`),
]).toEqual(["REA_REAL_MAX_BYTES"]);
expect(() =>
parseRuntimeSettingEnvironments(
'export const ANALYSIS_SETTINGS = { a: { environment: "REA_REAL_MAX_BYTES" } };',
),
).toThrow(/updateMode/);
expect(() =>
parseRuntimeSettingEnvironments(
'export const ANALYSIS_SETTINGS = { a: { updateMode: "parameter" } };',
),
).toThrow(/updateMode/);
expect(() =>
parseRuntimeSettingEnvironments(
"export const ANALYSIS_SETTINGS = { a: { environment: value } };",
@@ -49,12 +59,13 @@ it("reads only actual settings environment properties", () => {
it("parses declared external provenance rather than inferring it from prose", () => {
const source = `export const NUMERIC_LIMIT_REGISTRY = [{
read: "startup", name: "REA_HOPPER_TIMEOUT_MS", reason: "external reader",
policy: "restart-only",
update_mode: "restart", policy: "restart-only",
provenance: { kind: "external-reader", source: "scripts/helper.py", witness: "tests/helper.test.ts" },
}] as const;`;
expect(parseNumericLimitRegistry(source)).toEqual([
{
name: "REA_HOPPER_TIMEOUT_MS",
update_mode: "restart",
policy: "restart-only",
read: "startup",
reason: "external reader",
@@ -65,6 +76,14 @@ it("parses declared external provenance rather than inferring it from prose", ()
},
},
]);
expect(() =>
parseNumericLimitRegistry(source.replace('update_mode: "restart", ', "")),
).toThrow(/update_mode/);
expect(() =>
parseNumericLimitRegistry(
source.replace('update_mode: "restart"', 'update_mode: "runtime"'),
),
).toThrow(/Invalid numeric limit registry entry/);
expect(() =>
parseNumericLimitRegistry(
source.replace('source: "scripts/helper.py"', "source: dynamicPath"),