[GH-ISSUE #5556] [Bug] Fixed inspection-budget refusals recommend worker and host memory changes #3384

Closed
opened 2026-10-31 00:39:46 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 20, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5556

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

CLI

Observed behavior

Seven fixed-budget refusals across six local inspection commands return code: "resource_constraint", details.resource: "memory", and correct retryable: false, but recommend changing worker or host memory. Those changes cannot admit the unchanged input above the fixed allowance. The PE resource refusal also omits its exceeded 8 MiB metadata allowance from details.reported_limits, although the message states that bound.

An independent verifier executed every refusal twice, then replayed the exact procedure below. All refusals exited 1 with empty stderr and byte-identical repeated stdout within each replay. All six producer controls exited 0 with empty stderr. The defect concerns recovery diagnostics; the intentional refusals and non-retryable flags are correct.

Reproduction

From the source revision below, with locked dependencies and the built CLI already prepared, run this verifier-executed script at the checkout root. It requires Python 3 and the repository's installed tsx development dependency. The optional VERIFY_WORK_ROOT selects an existing scratch directory. It generates local synthetic fixtures and isolates HOME and XDG directories. No target code or external analysis engine executes.

Executed fixture and CLI procedure for all seven rows
python3 - <<'PYTHON'
import json, os, pathlib, struct, subprocess, tempfile, zipfile

ROOT = pathlib.Path.cwd()
WORK = pathlib.Path(tempfile.mkdtemp(prefix="rea-fixed-budget-",
                                     dir=os.environ.get("VERIFY_WORK_ROOT")))
ENV = {k: v for k, v in os.environ.items()
       if not k.startswith(("REA_", "HOPPER_", "GHIDRA_")) and k != "NODE_OPTIONS"}
for key, name in [("HOME", "home"), ("XDG_CONFIG_HOME", "config"),
                  ("XDG_DATA_HOME", "data"), ("XDG_CACHE_HOME", "cache"),
                  ("XDG_STATE_HOME", "state"), ("TMPDIR", "temp")]:
    (WORK / name).mkdir()
    ENV[key] = str(WORK / name)

def run(arguments, timeout=300):
    return subprocess.run(["node", "scripts/rea.mjs", *arguments, "--json"], cwd=ROOT,
                          env=ENV, capture_output=True, text=True, timeout=timeout)

def write(name, content):
    path = WORK / name
    if isinstance(content, bytes):
        path.write_bytes(content)
    else:
        path.write_text(content)
    return path

# Fixtures that use the source-owned builders.
write("go.mjs", f"""
import {{ writeFileSync }} from "node:fs";
import {{ createGoBinaryFixture }} from "{ROOT}/tests/fixtures/go/image.ts";
const out = process.argv[2];
const base = createGoBinaryFixture({{ format: "elf", bits: 64, encoding: "pointer" }});
const overflow = Buffer.from(base.bytes);
overflow.writeBigUInt64LE(1024n * 1024n + 1n, 1536 + 8);
writeFileSync(out + "/go-buildinfo.bin", overflow);
const table = Buffer.alloc(4096 + 18_000_000);
table.writeUInt32LE(0x464c457f, 0); table[4] = 2; table[5] = 1; table[6] = 1;
table.writeUInt16LE(2, 16); table.writeUInt16LE(62, 18); table.writeUInt32LE(1, 20);
table.writeBigUInt64LE(4096n, 32); table.writeBigUInt64LE(0n, 40);
table.writeUInt16LE(64, 52); table.writeUInt16LE(60000, 54); table.writeUInt16LE(300, 56);
table.writeUInt16LE(0, 58); table.writeUInt16LE(0, 60); table.writeUInt16LE(0, 62);
writeFileSync(out + "/go-structure.bin", table);
writeFileSync(out + "/go-control.bin", createGoBinaryFixture().bytes);
""")
write("rust.mjs", f"""
import {{ writeFileSync }} from "node:fs";
import {{ createRustImage, rustDependencyPayload }} from "{ROOT}/tests/fixtures/rust/image.ts";
const out = process.argv[2];
const LIMIT = 1024 * 1024;
const section = Buffer.alloc(1024 + LIMIT + 1);
createRustImage({{ format: "elf", payload: rustDependencyPayload() }}).copy(section, 0, 0, 4096);
section.writeBigUInt64LE(BigInt(LIMIT + 1), 384 + 32);
writeFileSync(out + "/rust-section.elf", section);
writeFileSync(out + "/rust-control.elf", createRustImage({{ format: "elf" }}));
""")
write("pe.mjs", f"""
import {{ writeFileSync }} from "node:fs";
import {{ peResourceFixture }} from "{ROOT}/src/native/pe/PeResources.fixture.ts";
const out = process.argv[2];
const name = "n".repeat(200);
const many = Array.from({{ length: 6000 }}, (_, index) => ({{
  type: 10, name: `${{name}}${{String(index).padStart(4, "0")}}`, language: 1033,
  payload: Buffer.alloc(0) }}));
writeFileSync(out + "/pe-many.bin", peResourceFixture(false, many, 10 * 1024 * 1024).bytes);
writeFileSync(out + "/pe-control.bin", peResourceFixture().bytes);
""")
for script in ("go.mjs", "rust.mjs", "pe.mjs"):
    subprocess.run([str(ROOT / "node_modules/.bin/tsx"), str(WORK / script), str(WORK)],
                   cwd=ROOT, env=ENV, check=True, capture_output=True, text=True)

# PE images: one small CLI-absent control and one above the 128 MiB admission bound.
def pe_image(path, size):
    head = bytearray(0x400)
    head[0:2] = b"MZ"
    struct.pack_into("<H", head, 2, size % 512)
    struct.pack_into("<H", head, 4, max(1, min(65535, (size + 511) // 512)))
    struct.pack_into("<H", head, 8, 4)
    struct.pack_into("<I", head, 60, 0x80)
    head[0x80:0x84] = b"PE\0\0"
    struct.pack_into("<H", head, 0x84, 0x8664)
    struct.pack_into("<H", head, 0x86, 1)
    struct.pack_into("<H", head, 0x94, 240)
    struct.pack_into("<H", head, 0x98, 0x20b)
    struct.pack_into("<I", head, 0x98 + 60, 0x1000)
    at = 0x98 + 240
    head[at:at + 8] = b".text\0\0\0"
    struct.pack_into("<I", head, at + 8, 0x100)
    struct.pack_into("<I", head, at + 12, 0x1000)
    struct.pack_into("<I", head, at + 16, 0x200)
    struct.pack_into("<I", head, at + 20, 0x400)
    struct.pack_into("<I", head, at + 36, 0x60000020)
    with path.open("wb") as handle:
        handle.write(head)
        handle.truncate(size)
pe_image(WORK / "managed-small.bin", 8192)
pe_image(WORK / "managed-large.bin", 128 * 1024 * 1024 + 1)

# APKs: one ABI control and one beyond the 32-ABI inventory bound.
with zipfile.ZipFile(WORK / "flutter-small.apk", "w", zipfile.ZIP_STORED) as archive:
    archive.writestr("lib/arm64-v8a/libapp.so", b"\x7fELF" + bytes(64))
with zipfile.ZipFile(WORK / "flutter-abis.apk", "w", zipfile.ZIP_STORED) as archive:
    for index in range(33):
        archive.writestr(f"lib/abi{index:02d}/libapp.so", b"\x7fELF" + bytes(64))

# Thin 64-bit Mach-O images for the dispatch-metadata snapshot bound.
def macho(path, size):
    head = bytearray(4096)
    struct.pack_into("<I", head, 0, 0xfeedfacf)
    struct.pack_into("<I", head, 4, 0x0100000c)
    struct.pack_into("<I", head, 8, 2)
    with path.open("wb") as handle:
        handle.write(head)
        handle.truncate(size)
macho(WORK / "dispatch-small.bin", 16 * 1024 * 1024)
macho(WORK / "dispatch-large.bin", 64 * 1024 * 1024 + 1)

CASES = [
    ("go-build-info", ["inspect-go-binary", str(WORK / "go-buildinfo.bin")],
     ["inspect-go-binary", str(WORK / "go-control.bin")]),
    ("go-structure", ["inspect-go-binary", str(WORK / "go-structure.bin")], None),
    ("rust-section", ["inspect-rust-binary", str(WORK / "rust-section.elf")],
     ["inspect-rust-binary", str(WORK / "rust-control.elf")]),
    ("pe-metadata", ["inspect-pe-resources", str(WORK / "pe-many.bin")],
     ["inspect-pe-resources", str(WORK / "pe-control.bin")]),
    ("managed-snapshot", ["inspect-managed-artifact", str(WORK / "managed-large.bin")],
     ["inspect-managed-artifact", str(WORK / "managed-small.bin")]),
    ("flutter-abi", ["identify-flutter-build", str(WORK / "flutter-abis.apk")],
     ["identify-flutter-build", str(WORK / "flutter-small.apk")]),
    ("dispatch-snapshot", ["inspect-native-dispatch-metadata", str(WORK / "dispatch-large.bin")],
     ["inspect-native-dispatch-metadata", str(WORK / "dispatch-small.bin")]),
]
for label, refusal, control in CASES:
    results = [run(refusal) for _ in range(2)]
    assert results[0].stdout == results[1].stdout, label
    for attempt, result in enumerate(results, 1):
        (WORK / f"{label}-{attempt}.stdout.json").write_text(result.stdout)
        (WORK / f"{label}-{attempt}.stderr.txt").write_text(result.stderr)
        assert result.returncode == 1, (label, result.stderr)
        assert not result.stderr, (label, result.stderr)
    value = json.loads(results[0].stdout)
    details = value.get("details") or {}
    print(json.dumps({"case": label, "exit": results[0].returncode,
                      "code": value.get("code"), "retryable": value.get("retryable"),
                      "resource": details.get("resource"),
                      "reported_limits": details.get("reported_limits"),
                      "remediation": (value.get("remediation") or {}).get("action"),
                      "reason": (value.get("message") or "").split(": ", 1)[-1]},
                     sort_keys=True))
    if control is not None:
        result = run(control)
        (WORK / f"{label}-control.stdout.json").write_text(result.stdout)
        (WORK / f"{label}-control.stderr.txt").write_text(result.stderr)
        assert result.returncode == 0, (label + " control", result.stdout, result.stderr)
        assert not result.stderr, (label, result.stderr)
        print(json.dumps({"control": label, "exit": result.returncode}))
print("all controls exited 0")
# Discriminating control: existing Go input-admission advice is input-specific.
large_go = WORK / "go-input-large.bin"
with large_go.open("wb") as handle:
    handle.truncate(256 * 1024 * 1024 + 1)
result = run(["inspect-go-binary", str(large_go)])
value = json.loads(result.stdout)
(WORK / "go-input-admission.stdout.json").write_text(result.stdout)
assert result.returncode == 1 and not result.stderr
assert value["details"]["resource"] == "file-size"
assert value["details"]["reported_limits"]["maximum_input_bytes"] == 268435456
print(json.dumps({"control": "go-input-admission", "exit": result.returncode,
                  "resource": value["details"]["resource"],
                  "action": value["remediation"]["action"]}))
print("evidence directory:", WORK)
PYTHON

The exact public CLI calls are node scripts/rea.mjs COMMAND ARTIFACT --json, with each command and generated artifact listed in CASES. The Go structural fixture declares a fully file-backed 18,000,000-byte table with an unusual entry width. The Go pointer-string length and Rust section size are refused before later payload validation, so these establish budget-guard behavior rather than fully valid large applications. The single-ABI Flutter control succeeds with an invalid ELF-header observation; it establishes inventory behavior rather than valid executable decoding.

Expected behavior

Keep the intentional refusals, fixed allowances and retryable: false. Recovery should identify the applicable parser, metadata, count or snapshot budget and explain that worker limits or available host memory do not raise it. Expose the exceeded PE metadata allowance in machine-readable limits. Selecting another complete input within the allowance can avoid a refusal when it preserves the needed observation; truncating required bytes or dropping required coverage does not recover the original request.

This expectation is source-derived from docs/go-binaries.md:94-108, docs/rust-binaries.md:47-54, docs/binary-diagnostics.md:210-213, docs/flutter-analysis.md:146-150, and docs/managed-code-analysis.md:138-145, which document fixed allowances and their scope. src/native/AppleDispatchMetadata.ts:199-217 fixes the dispatch snapshot allowance; docs/error-contract.schema.json:292-332 requires a recovery action for resource constraints. The existing Go input-admission response provides a working example of input-specific recovery. No above-budget successful inspection is promised.

Impact and scope

P3. The refusals and their non-retryable flags remain correct. The observed problem is misleading recovery wording and incomplete PE structured limits; no consequential behavior caused by the advice was observed. Callers can use the fixed allowance in the message or reported limits and disregard worker-memory advice. Smaller controls do not establish recovery of complete inspection of the original above-budget inputs.

The observations cover deterministic static CLI refusals for the seven rows below. Live MCP, an installed package, real large applications, and other platforms were not replayed. No automated retry loop, incorrect successful analysis, security-boundary violation, or data loss was established. Real-world frequency is unknown.

REA version

6.7.0, prepared built source CLI at commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8 with locked dependencies.

Runtime and platform

Node.js v26.12.0; Darwin arm64. Static built-in inspection with isolated HOME and XDG directories; no external analysis engine.

Target and artifact context

Synthetic Go ELF pointer build-info and structural-table fixtures; an ELF Rust dependency-section fixture; a PE resource fixture with 6,000 long named entries and empty payloads; a sparse PE above the managed snapshot allowance; a ZIP/APK inventory with 33 ABI library paths; and a sparse thin arm64 Mach-O above the dispatch snapshot allowance. The procedure includes smaller controls for all six producers and a separate Go selected-input-size control.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

Every affected response supplied this recovery action:

Review the reported worker memory limits and available host memory. Retry with sufficient memory or a smaller artifact; REA retains tighter inherited limits.

All seven returned resource_constraint, details.resource: "memory", retryable: false, exit 1, and empty stderr:

Command and exercised bound Observed details.reported_limits
inspect-go-binary: declared pointer-string length above 1 MiB boundary: "build-info", maximum_bytes: 1048576
inspect-go-binary: declared structural table above 16 MiB boundary: "structure", maximum_bytes: 16777216
inspect-rust-binary: dependency section above 1 MiB compressed maximum_bytes: 1048576
inspect-pe-resources: metadata accounting above 8 MiB max_entries: 16384, max_file_bytes: 67108864; exceeded metadata bound absent
inspect-managed-artifact: input snapshot above 128 MiB max_snapshot_bytes: 134217728
identify-flutter-build: inventory with 33 ABIs max_flutter_abis: 32, max_library_path_bytes: 1024
inspect-native-dispatch-metadata: input snapshot above 64 MiB input_bytes: 67108864

The PE message explicitly identifies the exceeded 8 MiB complete-result allocation budget; its structured limits identify two different allowances. The successful controls cover ordinary Go and Rust fixture reports, the default PE resource fixture, a small PE classified as not managed, a single-ABI APK, and a 16 MiB Mach-O.

The separate Go size-admission control correctly returns details.resource: "file-size", maximum_input_bytes: 268435456, and advice to select a stable input within the fixed allowance without truncating required bytes. This distinguishes applicable input recovery from the memory fallback above.

Typed outcome or recovery expected

Preserve resource_constraint, retryable: false, the enforced numeric allowances, and successful below-budget controls. Each action should explain its fixed budget without implying that worker-memory limits, inherited limits or additional host memory can admit the unchanged input. Include the PE 8 MiB metadata allowance in reported_limits. Preserve genuine operating-system memory diagnostics. These are proposed restoration checks; no correction or restoration was tested.

Analysis or suspected cause

Source-derived reasoning at the tested revision: the fixed-bound failures come from src/go/GoBinaryImage.ts:315-335, src/go/GoBinaryContainer.ts:113-120, src/go/GoBinaryProvider.ts:59-66, src/rust/RustAuditableReader.ts:88-95, src/rust/RustBinaryProvider.ts:182-190, src/native/pe/PeResourceParser.ts:22,54-58, src/native/pe/PeResourcesProvider.ts:118-145, src/dotnet/ManagedSnapshot.ts:27,36-61, src/flutter/FlutterBuildProvider.ts:288-300, and src/native/AppleDispatchMetadata.ts:210-217. They reach src/domain/analysisErrorPresentation.ts:67-78 without refusal-specific recovery. src/domain/analysisErrorProjection.ts:109-117 already returns correct false retryability for these memory cases.

The error type supports refusal-specific recovery and retryability overrides. The reproduction does not establish a need for a new resource enum, a global capacity retry change, or a universal architectural cause.

Submission fetched upstream main at d2a3269d0c1007e16a2f84d44cc0b1503debb2ae. The seven budget guards and memory fallback remain unchanged. Intervening commit e50a271c72325880b5ff944973b0b42d81b4b158 changes input-validation and timeout recovery; the Go container change adds optional metadata fields. Current-main applicability was source-checked, not runtime-reproduced there.

Additional context

Prepared with AI assistance and independently reproduced through the supported built CLI. Related producer-specific sibling reports already filed by this account:

  • #5405: Rust/IL2CPP selected-input-size admission and evidence-output advice.
  • #5456: SQLite worker-reply budget and recovery of Evidence that was never produced.
  • #5459: HAR analysis-view representation accounting and worker-memory advice.
  • #5460: IL2CPP complete-report accounting and worker/host-memory advice.
  • #5504: Rust compiler-hash count and incorrect retryability.
  • #5505: Rust complete-report size and retained-Evidence advice.
  • #5506: WASM comparison representation and incorrect retryability.
  • #5537: Electron fuse sentinel/input bounds and recovery advice.

None covers a row in this report. The compressed Rust section is distinct from its input-admission, compiler-hash and complete-report guards. #3538 and PR #3550 cover JavaScript parent-result capacity after static checkpoints. Closed #4192 and merged PR #4290 concern PE input-byte classification; they do not reproduce this metadata-accounting refusal or restore its missing structured allowance. Closed #4622 concerns Go selected-input admission, which now supplies the successful diagnostic control.

NOT_ENOUGH_INFO: no last-known-good version or first-bad revision was established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5556 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area CLI ### Observed behavior Seven fixed-budget refusals across six local inspection commands return `code: "resource_constraint"`, `details.resource: "memory"`, and correct `retryable: false`, but recommend changing worker or host memory. Those changes cannot admit the unchanged input above the fixed allowance. The PE resource refusal also omits its exceeded 8 MiB metadata allowance from `details.reported_limits`, although the message states that bound. An independent verifier executed every refusal twice, then replayed the exact procedure below. All refusals exited 1 with empty stderr and byte-identical repeated stdout within each replay. All six producer controls exited 0 with empty stderr. The defect concerns recovery diagnostics; the intentional refusals and non-retryable flags are correct. ### Reproduction From the source revision below, with locked dependencies and the built CLI already prepared, run this verifier-executed script at the checkout root. It requires Python 3 and the repository's installed `tsx` development dependency. The optional `VERIFY_WORK_ROOT` selects an existing scratch directory. It generates local synthetic fixtures and isolates HOME and XDG directories. No target code or external analysis engine executes. <details> <summary>Executed fixture and CLI procedure for all seven rows</summary> ```sh python3 - <<'PYTHON' import json, os, pathlib, struct, subprocess, tempfile, zipfile ROOT = pathlib.Path.cwd() WORK = pathlib.Path(tempfile.mkdtemp(prefix="rea-fixed-budget-", dir=os.environ.get("VERIFY_WORK_ROOT"))) ENV = {k: v for k, v in os.environ.items() if not k.startswith(("REA_", "HOPPER_", "GHIDRA_")) and k != "NODE_OPTIONS"} for key, name in [("HOME", "home"), ("XDG_CONFIG_HOME", "config"), ("XDG_DATA_HOME", "data"), ("XDG_CACHE_HOME", "cache"), ("XDG_STATE_HOME", "state"), ("TMPDIR", "temp")]: (WORK / name).mkdir() ENV[key] = str(WORK / name) def run(arguments, timeout=300): return subprocess.run(["node", "scripts/rea.mjs", *arguments, "--json"], cwd=ROOT, env=ENV, capture_output=True, text=True, timeout=timeout) def write(name, content): path = WORK / name if isinstance(content, bytes): path.write_bytes(content) else: path.write_text(content) return path # Fixtures that use the source-owned builders. write("go.mjs", f""" import {{ writeFileSync }} from "node:fs"; import {{ createGoBinaryFixture }} from "{ROOT}/tests/fixtures/go/image.ts"; const out = process.argv[2]; const base = createGoBinaryFixture({{ format: "elf", bits: 64, encoding: "pointer" }}); const overflow = Buffer.from(base.bytes); overflow.writeBigUInt64LE(1024n * 1024n + 1n, 1536 + 8); writeFileSync(out + "/go-buildinfo.bin", overflow); const table = Buffer.alloc(4096 + 18_000_000); table.writeUInt32LE(0x464c457f, 0); table[4] = 2; table[5] = 1; table[6] = 1; table.writeUInt16LE(2, 16); table.writeUInt16LE(62, 18); table.writeUInt32LE(1, 20); table.writeBigUInt64LE(4096n, 32); table.writeBigUInt64LE(0n, 40); table.writeUInt16LE(64, 52); table.writeUInt16LE(60000, 54); table.writeUInt16LE(300, 56); table.writeUInt16LE(0, 58); table.writeUInt16LE(0, 60); table.writeUInt16LE(0, 62); writeFileSync(out + "/go-structure.bin", table); writeFileSync(out + "/go-control.bin", createGoBinaryFixture().bytes); """) write("rust.mjs", f""" import {{ writeFileSync }} from "node:fs"; import {{ createRustImage, rustDependencyPayload }} from "{ROOT}/tests/fixtures/rust/image.ts"; const out = process.argv[2]; const LIMIT = 1024 * 1024; const section = Buffer.alloc(1024 + LIMIT + 1); createRustImage({{ format: "elf", payload: rustDependencyPayload() }}).copy(section, 0, 0, 4096); section.writeBigUInt64LE(BigInt(LIMIT + 1), 384 + 32); writeFileSync(out + "/rust-section.elf", section); writeFileSync(out + "/rust-control.elf", createRustImage({{ format: "elf" }})); """) write("pe.mjs", f""" import {{ writeFileSync }} from "node:fs"; import {{ peResourceFixture }} from "{ROOT}/src/native/pe/PeResources.fixture.ts"; const out = process.argv[2]; const name = "n".repeat(200); const many = Array.from({{ length: 6000 }}, (_, index) => ({{ type: 10, name: `${{name}}${{String(index).padStart(4, "0")}}`, language: 1033, payload: Buffer.alloc(0) }})); writeFileSync(out + "/pe-many.bin", peResourceFixture(false, many, 10 * 1024 * 1024).bytes); writeFileSync(out + "/pe-control.bin", peResourceFixture().bytes); """) for script in ("go.mjs", "rust.mjs", "pe.mjs"): subprocess.run([str(ROOT / "node_modules/.bin/tsx"), str(WORK / script), str(WORK)], cwd=ROOT, env=ENV, check=True, capture_output=True, text=True) # PE images: one small CLI-absent control and one above the 128 MiB admission bound. def pe_image(path, size): head = bytearray(0x400) head[0:2] = b"MZ" struct.pack_into("<H", head, 2, size % 512) struct.pack_into("<H", head, 4, max(1, min(65535, (size + 511) // 512))) struct.pack_into("<H", head, 8, 4) struct.pack_into("<I", head, 60, 0x80) head[0x80:0x84] = b"PE\0\0" struct.pack_into("<H", head, 0x84, 0x8664) struct.pack_into("<H", head, 0x86, 1) struct.pack_into("<H", head, 0x94, 240) struct.pack_into("<H", head, 0x98, 0x20b) struct.pack_into("<I", head, 0x98 + 60, 0x1000) at = 0x98 + 240 head[at:at + 8] = b".text\0\0\0" struct.pack_into("<I", head, at + 8, 0x100) struct.pack_into("<I", head, at + 12, 0x1000) struct.pack_into("<I", head, at + 16, 0x200) struct.pack_into("<I", head, at + 20, 0x400) struct.pack_into("<I", head, at + 36, 0x60000020) with path.open("wb") as handle: handle.write(head) handle.truncate(size) pe_image(WORK / "managed-small.bin", 8192) pe_image(WORK / "managed-large.bin", 128 * 1024 * 1024 + 1) # APKs: one ABI control and one beyond the 32-ABI inventory bound. with zipfile.ZipFile(WORK / "flutter-small.apk", "w", zipfile.ZIP_STORED) as archive: archive.writestr("lib/arm64-v8a/libapp.so", b"\x7fELF" + bytes(64)) with zipfile.ZipFile(WORK / "flutter-abis.apk", "w", zipfile.ZIP_STORED) as archive: for index in range(33): archive.writestr(f"lib/abi{index:02d}/libapp.so", b"\x7fELF" + bytes(64)) # Thin 64-bit Mach-O images for the dispatch-metadata snapshot bound. def macho(path, size): head = bytearray(4096) struct.pack_into("<I", head, 0, 0xfeedfacf) struct.pack_into("<I", head, 4, 0x0100000c) struct.pack_into("<I", head, 8, 2) with path.open("wb") as handle: handle.write(head) handle.truncate(size) macho(WORK / "dispatch-small.bin", 16 * 1024 * 1024) macho(WORK / "dispatch-large.bin", 64 * 1024 * 1024 + 1) CASES = [ ("go-build-info", ["inspect-go-binary", str(WORK / "go-buildinfo.bin")], ["inspect-go-binary", str(WORK / "go-control.bin")]), ("go-structure", ["inspect-go-binary", str(WORK / "go-structure.bin")], None), ("rust-section", ["inspect-rust-binary", str(WORK / "rust-section.elf")], ["inspect-rust-binary", str(WORK / "rust-control.elf")]), ("pe-metadata", ["inspect-pe-resources", str(WORK / "pe-many.bin")], ["inspect-pe-resources", str(WORK / "pe-control.bin")]), ("managed-snapshot", ["inspect-managed-artifact", str(WORK / "managed-large.bin")], ["inspect-managed-artifact", str(WORK / "managed-small.bin")]), ("flutter-abi", ["identify-flutter-build", str(WORK / "flutter-abis.apk")], ["identify-flutter-build", str(WORK / "flutter-small.apk")]), ("dispatch-snapshot", ["inspect-native-dispatch-metadata", str(WORK / "dispatch-large.bin")], ["inspect-native-dispatch-metadata", str(WORK / "dispatch-small.bin")]), ] for label, refusal, control in CASES: results = [run(refusal) for _ in range(2)] assert results[0].stdout == results[1].stdout, label for attempt, result in enumerate(results, 1): (WORK / f"{label}-{attempt}.stdout.json").write_text(result.stdout) (WORK / f"{label}-{attempt}.stderr.txt").write_text(result.stderr) assert result.returncode == 1, (label, result.stderr) assert not result.stderr, (label, result.stderr) value = json.loads(results[0].stdout) details = value.get("details") or {} print(json.dumps({"case": label, "exit": results[0].returncode, "code": value.get("code"), "retryable": value.get("retryable"), "resource": details.get("resource"), "reported_limits": details.get("reported_limits"), "remediation": (value.get("remediation") or {}).get("action"), "reason": (value.get("message") or "").split(": ", 1)[-1]}, sort_keys=True)) if control is not None: result = run(control) (WORK / f"{label}-control.stdout.json").write_text(result.stdout) (WORK / f"{label}-control.stderr.txt").write_text(result.stderr) assert result.returncode == 0, (label + " control", result.stdout, result.stderr) assert not result.stderr, (label, result.stderr) print(json.dumps({"control": label, "exit": result.returncode})) print("all controls exited 0") # Discriminating control: existing Go input-admission advice is input-specific. large_go = WORK / "go-input-large.bin" with large_go.open("wb") as handle: handle.truncate(256 * 1024 * 1024 + 1) result = run(["inspect-go-binary", str(large_go)]) value = json.loads(result.stdout) (WORK / "go-input-admission.stdout.json").write_text(result.stdout) assert result.returncode == 1 and not result.stderr assert value["details"]["resource"] == "file-size" assert value["details"]["reported_limits"]["maximum_input_bytes"] == 268435456 print(json.dumps({"control": "go-input-admission", "exit": result.returncode, "resource": value["details"]["resource"], "action": value["remediation"]["action"]})) print("evidence directory:", WORK) PYTHON ``` </details> The exact public CLI calls are `node scripts/rea.mjs COMMAND ARTIFACT --json`, with each command and generated artifact listed in `CASES`. The Go structural fixture declares a fully file-backed 18,000,000-byte table with an unusual entry width. The Go pointer-string length and Rust section size are refused before later payload validation, so these establish budget-guard behavior rather than fully valid large applications. The single-ABI Flutter control succeeds with an invalid ELF-header observation; it establishes inventory behavior rather than valid executable decoding. ### Expected behavior Keep the intentional refusals, fixed allowances and `retryable: false`. Recovery should identify the applicable parser, metadata, count or snapshot budget and explain that worker limits or available host memory do not raise it. Expose the exceeded PE metadata allowance in machine-readable limits. Selecting another complete input within the allowance can avoid a refusal when it preserves the needed observation; truncating required bytes or dropping required coverage does not recover the original request. This expectation is source-derived from `docs/go-binaries.md:94-108`, `docs/rust-binaries.md:47-54`, `docs/binary-diagnostics.md:210-213`, `docs/flutter-analysis.md:146-150`, and `docs/managed-code-analysis.md:138-145`, which document fixed allowances and their scope. `src/native/AppleDispatchMetadata.ts:199-217` fixes the dispatch snapshot allowance; `docs/error-contract.schema.json:292-332` requires a recovery action for resource constraints. The existing Go input-admission response provides a working example of input-specific recovery. No above-budget successful inspection is promised. ### Impact and scope P3. The refusals and their non-retryable flags remain correct. The observed problem is misleading recovery wording and incomplete PE structured limits; no consequential behavior caused by the advice was observed. Callers can use the fixed allowance in the message or reported limits and disregard worker-memory advice. Smaller controls do not establish recovery of complete inspection of the original above-budget inputs. The observations cover deterministic static CLI refusals for the seven rows below. Live MCP, an installed package, real large applications, and other platforms were not replayed. No automated retry loop, incorrect successful analysis, security-boundary violation, or data loss was established. Real-world frequency is unknown. ### REA version 6.7.0, prepared built source CLI at commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8` with locked dependencies. ### Runtime and platform Node.js v26.12.0; Darwin arm64. Static built-in inspection with isolated HOME and XDG directories; no external analysis engine. ### Target and artifact context Synthetic Go ELF pointer build-info and structural-table fixtures; an ELF Rust dependency-section fixture; a PE resource fixture with 6,000 long named entries and empty payloads; a sparse PE above the managed snapshot allowance; a ZIP/APK inventory with 33 ABI library paths; and a sparse thin arm64 Mach-O above the dispatch snapshot allowance. The procedure includes smaller controls for all six producers and a separate Go selected-input-size control. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence Every affected response supplied this recovery action: ```text Review the reported worker memory limits and available host memory. Retry with sufficient memory or a smaller artifact; REA retains tighter inherited limits. ``` All seven returned `resource_constraint`, `details.resource: "memory"`, `retryable: false`, exit 1, and empty stderr: | Command and exercised bound | Observed `details.reported_limits` | | --- | --- | | `inspect-go-binary`: declared pointer-string length above 1 MiB | `boundary: "build-info"`, `maximum_bytes: 1048576` | | `inspect-go-binary`: declared structural table above 16 MiB | `boundary: "structure"`, `maximum_bytes: 16777216` | | `inspect-rust-binary`: dependency section above 1 MiB compressed | `maximum_bytes: 1048576` | | `inspect-pe-resources`: metadata accounting above 8 MiB | `max_entries: 16384`, `max_file_bytes: 67108864`; exceeded metadata bound absent | | `inspect-managed-artifact`: input snapshot above 128 MiB | `max_snapshot_bytes: 134217728` | | `identify-flutter-build`: inventory with 33 ABIs | `max_flutter_abis: 32`, `max_library_path_bytes: 1024` | | `inspect-native-dispatch-metadata`: input snapshot above 64 MiB | `input_bytes: 67108864` | The PE message explicitly identifies the exceeded 8 MiB complete-result allocation budget; its structured limits identify two different allowances. The successful controls cover ordinary Go and Rust fixture reports, the default PE resource fixture, a small PE classified as not managed, a single-ABI APK, and a 16 MiB Mach-O. The separate Go size-admission control correctly returns `details.resource: "file-size"`, `maximum_input_bytes: 268435456`, and advice to select a stable input within the fixed allowance without truncating required bytes. This distinguishes applicable input recovery from the memory fallback above. ### Typed outcome or recovery expected Preserve `resource_constraint`, `retryable: false`, the enforced numeric allowances, and successful below-budget controls. Each action should explain its fixed budget without implying that worker-memory limits, inherited limits or additional host memory can admit the unchanged input. Include the PE 8 MiB metadata allowance in `reported_limits`. Preserve genuine operating-system memory diagnostics. These are proposed restoration checks; no correction or restoration was tested. ### Analysis or suspected cause Source-derived reasoning at the tested revision: the fixed-bound failures come from `src/go/GoBinaryImage.ts:315-335`, `src/go/GoBinaryContainer.ts:113-120`, `src/go/GoBinaryProvider.ts:59-66`, `src/rust/RustAuditableReader.ts:88-95`, `src/rust/RustBinaryProvider.ts:182-190`, `src/native/pe/PeResourceParser.ts:22,54-58`, `src/native/pe/PeResourcesProvider.ts:118-145`, `src/dotnet/ManagedSnapshot.ts:27,36-61`, `src/flutter/FlutterBuildProvider.ts:288-300`, and `src/native/AppleDispatchMetadata.ts:210-217`. They reach `src/domain/analysisErrorPresentation.ts:67-78` without refusal-specific recovery. `src/domain/analysisErrorProjection.ts:109-117` already returns correct false retryability for these memory cases. The error type supports refusal-specific recovery and retryability overrides. The reproduction does not establish a need for a new resource enum, a global capacity retry change, or a universal architectural cause. Submission fetched upstream main at `d2a3269d0c1007e16a2f84d44cc0b1503debb2ae`. The seven budget guards and memory fallback remain unchanged. Intervening commit `e50a271c72325880b5ff944973b0b42d81b4b158` changes input-validation and timeout recovery; the Go container change adds optional metadata fields. Current-main applicability was source-checked, not runtime-reproduced there. ### Additional context Prepared with AI assistance and independently reproduced through the supported built CLI. Related producer-specific sibling reports already filed by this account: - #5405: Rust/IL2CPP selected-input-size admission and evidence-output advice. - #5456: SQLite worker-reply budget and recovery of Evidence that was never produced. - #5459: HAR analysis-view representation accounting and worker-memory advice. - #5460: IL2CPP complete-report accounting and worker/host-memory advice. - #5504: Rust compiler-hash count and incorrect retryability. - #5505: Rust complete-report size and retained-Evidence advice. - #5506: WASM comparison representation and incorrect retryability. - #5537: Electron fuse sentinel/input bounds and recovery advice. None covers a row in this report. The compressed Rust section is distinct from its input-admission, compiler-hash and complete-report guards. #3538 and PR #3550 cover JavaScript parent-result capacity after static checkpoints. Closed #4192 and merged PR #4290 concern PE input-byte classification; they do not reproduce this metadata-accounting refusal or restore its missing structured allowance. Closed #4622 concerns Go selected-input admission, which now supplies the successful diagnostic control. NOT_ENOUGH_INFO: no last-known-good version or first-bad revision was established.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with canonical issue #5504 ([Bug] Rust compiler-hash capacity refusals incorrectly tell callers to retry) and siblings #5456, #5459, #5460, #5505, #5506, #5537. Both report misleading worker and host memory recovery guidance for fixed reader/inspection bounds. Cataloged under Cluster AJ in consolidation tracker #4330.

<!-- gh-comment-id:6295983393 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #5504 (`[Bug] Rust compiler-hash capacity refusals incorrectly tell callers to retry`) and siblings #5456, #5459, #5460, #5505, #5506, #5537. Both report misleading worker and host memory recovery guidance for fixed reader/inspection bounds. Cataloged under Cluster AJ in consolidation tracker #4330.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3384
No description provided.