[PR #7112] [CLOSED] feat(native): make the function-matching allowance a runtime setting #7648

Closed
opened 2026-11-03 13:39:23 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/7112
Author: @kaoru0822-kitauji
Created: 10/25/2026
Status: ❌ Closed

Base: main ← Head: feat/native-matching-allowance-setting


📝 Commits (2)

  • 87d3d60 feat(native): make the function-matching allowance a runtime setting
  • 0600c7d refactor(native): keep matching allowance recovery with its parser

📊 Changes

18 files changed (+477 additions, -22 deletions)

View changed files

📝 docs/design/limits-and-recovery.md (+3 -0)
📝 docs/native-function-matching.md (+13 -2)
📝 src/application/SetupRegistrationEnvironment.ts (+1 -0)
📝 src/application/native/NativeFunctionMatchingService.test.ts (+64 -0)
📝 src/application/native/NativeFunctionMatchingService.ts (+18 -4)
📝 src/application/native/NativeMatchingAdmission.ts (+32 -12)
📝 src/cli.ts (+1 -1)
📝 src/cli/nativeFunctionMatchingCommands.ts (+5 -1)
📝 src/config/analysisSettings.ts (+3 -0)
➕ src/config/nativeMatchingAllowance.test.ts (+78 -0)
➕ src/config/nativeMatchingAllowance.ts (+78 -0)
📝 src/config/runtimeAnalysisSettings.ts (+22 -0)
📝 src/domain/analysisResourceLimits.ts (+11 -0)
📝 src/domain/analysisSettingDefinitions.ts (+12 -0)
📝 src/domain/analysisSettings.ts (+1 -0)
📝 src/server/registerNativeFunctionMatchingTool.ts (+14 -1)
📝 src/server/registerSessionTools.ts (+6 -1)
📝 tests/acceptance/analysisSettingsMcp.test.ts (+115 -0)

📄 Description

What broke: match_native_functions refused every request above its fixed 96 MiB representation allowance with "This release has no runtime override". Ordinary 20k–50k function builds could not be matched at all, over either MCP or CLI.
Fix: The allowance is now the runtime setting native.matching_representation_bytes: default 96 MiB, 1 byte to 1 GiB, applied to the next operation. Its launch seed is REA_NATIVE_MATCHING_REPRESENTATION_BYTES, which rea setup copies. Refusals carry details.limit and recovery text that names the setting.
Risk: Low. The default and charging are unchanged, so existing requests behave the same. Raising the setting lets REA retain more memory. The 1 GiB maximum is an REA policy ceiling, not a host-capacity claim.

Summary

Fixes #6406 (criteria 1, 3 and 5; criterion 2 is the accounting change in #7108; criterion 4 is open, see gaps)

With the setting raised, 50,000 Ghidra-shaped functions per side now match through both MCP and CLI. Before, any request above 96 MiB was refused with no override. This follows docs/design/limits-and-recovery.md: the default equals today's constant, the value has a floor and a maximum, the launch seed is copied by setup, and the refusal names the setting. This PR is independent of #7108, and either can merge first.

Problem and expected behavior

  • Trigger: two list_procedures inventories of more than about 10,000 Ghidra-shaped rows per side (reproduced below with synthetic bundles at 11k, 20k and 50k).
  • Before: the request was refused as resource_constraint, with "This release has no runtime override" and retryable: false.
  • After: the same refusal, now with details.limit (setting, effective_value, source, unit, minimum, maximum, applies_at, agent_editable) and recovery that names update_analysis_settings and REA_NATIVE_MATCHING_REPRESENTATION_BYTES. After raising the setting, the unchanged request succeeds.

Change and scope

  • src/domain/analysisResourceLimits.ts: adds DEFAULT_NATIVE_MATCHING_REPRESENTATION_BYTES (96 MiB) and MAXIMUM_NATIVE_MATCHING_REPRESENTATION_BYTES (1 GiB, an REA policy ceiling). The setting definition is in analysisSettingDefinitions.ts, typed in analysisSettings.ts.
  • src/config/nativeMatchingAllowance.ts: launch parser, plus the recovery text and display formatting (kept with the parser under src/config/, per the limits guide). A positive decimal integer is accepted; anything else is a ConfigurationError that names the variable and is never replaced by the default.
  • runtimeNativeMatchingAllowance: resolves the accepted operation's runtime override over the launch value, once per call. This is operation-local, following the JADX precedent in "Invalid launch configuration": an invalid seed does not stop MCP startup. match_native_functions alone returns configuration_invalid, and a runtime value repairs it.
  • NativeMatchingAdmission takes the resolved allowance, reads it once per request, and binds limit through analysisSettingLimit. The fixed-allowance recovery is removed.
  • Adapters: the MCP handler and the CLI command both pass the allowance into the shared matchNativeFunctions / matchNativeFunctionsFromBundles service. The CLI takes the snapshotted command environment, like the other commands.
  • SetupRegistrationEnvironment forwards the seed. inspect_analysis_settings lists the setting through the shared definitions.
  • Not included: accounting changes (#7108), and any change to what a match asserts.

Contribution scope: not applicable. This adds a curated budget setting to an existing read-only tool and changes no authority.

Contract and boundary impact

  • Semantic owner and earliest changed stage: the setting definition is in the domain. Matching admission is the only enforcement owner.

  • Contribution scope fit and linked maintainer decision: not applicable.

  • CLI and MCP/tool-catalog contract: inspect_analysis_settings and update_analysis_settings accept one more setting ID in their enum. The refusal adds details.limit, and its message and recovery text change; the error code, category and retryable: false are unchanged. The match_native_functions schema is unchanged.

  • Provider, bridge, target-format, or platform compatibility: none.

  • Evidence, artifact, provenance, or reconstruction contract: unchanged. The allowance is not recorded in Evidence parameters because it does not affect the result.

  • Target selection, authority, upstream behavior, and cleanup details: not applicable. The setting changes in-memory defaults only; nothing is written.

  • Generated metadata, package, or installation impact: rea setup copies one more environment key when it is set. npm run metadata:check passes.

  • Read existing files or captured data.

Evidence and regression coverage

  • Tests added:

    • src/config/nativeMatchingAllowance.test.ts:
      • The unset default.
      • 1 byte, 256 MiB and 1 GiB accepted.
      • 0, -1, 01, 1.5, 96MiB, an empty string and 1 GiB + 1 refused as ConfigurationError, without falling back.
      • Global parseEnvironment unaffected by an invalid seed (operation-local), with a runtime override repairing it.
      • rea setup forwarding.
    • NativeFunctionMatchingService.test.ts:
      • A 64 KiB runtime allowance refuses 100-row inventories with the complete limit facts and recovery text.
      • The default admits the same request.
      • 15,000 rows per side are refused at the 96 MiB default (source: "default") and match unchanged under 256 MiB.
    • tests/acceptance/analysisSettingsMcp.test.ts, against the built rea mcp over stdio:
      • Runtime: set 4096 → refusal with limit.source: "runtime"; reset → success.
      • Launch seed: 4096 → inspection shows launch_value, source: "setting" and the environment name, and matching refuses.
      • Invalid seed 0: binary_session still works, matching returns configuration_invalid naming the variable, and a runtime value repairs it.
  • End-to-end measurements (built CLI and MCP, synthetic Ghidra-shaped list_procedures bundles, macOS arm64, Node 24.22.1; every run is a cold process; peak RSS from /usr/bin/time -l for CLI and from 20 ms sampling of the process family for MCP):

    Rows per side Path Default (96 MiB) native.matching_representation_bytes = 512 MiB
    11,000 CLI refused, 1.01 s matched, 2.11 s wall, 2.98 s CPU, 616 MB peak
    20,000 CLI refused, 1.43 s matched, 3.50 s wall, 4.59 s CPU, 795 MB peak
    50,000 CLI refused, 2.57 s matched, 8.74 s wall, 10.97 s CPU, 1,338 MB peak
    20,000 MCP (retained refs) refused, 196 ms matched, 1.13 s, server CPU +1.4 s, family RSS 549 → 793 MB
    50,000 MCP (retained refs) refused, 239 ms matched, 2.67 s, server CPU +3.4 s, family RSS 1,044 → 1,163 MB

    The MCP detail: "summary" reply is 3,308 bytes. The refusal grows from 773 to 1,536 bytes, because it now carries the limit facts and recovery a client needs to retry. Token counts were not measured. The MCP family's starting RSS includes both imported bundles.

  • Remaining proof gaps:

    • #6406 criterion 4 (one real pair of large stripped Ghidra inventories) is not run, because Ghidra is not installed on this host.
    • The 1 GiB ceiling is a policy value and was not driven to the limit.
    • Peak RSS above is process-wide and includes bundle loading.

Boundary regressions: configuration parsing (valid, invalid and bounds), precedence (runtime over launch over default), operation-local invalid launch with repair and reset, and capacity refusal and recovery through the real MCP transport. There is no lifecycle stage.

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run test:local -- src/application/native/NativeFunctionMatchingService.test.ts src/application/AnalysisSettings.test.ts src/server/DeferredEvidenceMcpServer.test.ts src/mcpStartupPolicy.test.ts src/setupEnvironment.test.ts src/application/Doctor.test.ts src/config/regexWorkerHeapRequest.test.ts: 150 passed.
  • npm run test:focused -- tests/acceptance/analysisSettingsMcp.test.ts src/config/nativeMatchingAllowance.test.ts src/application/native/NativeFunctionMatchingService.test.ts: 30 passed.
  • npm run test:focused -- tests/acceptance/analysisSettingsMcp.test.ts tests/acceptance/tools/nativeFunctionMatching.test.ts tests/boundary/filesystem/clientConfigurationEnvironment.test.ts tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts tests/boundary/filesystem/analysisSettingsAdmission.test.ts: 77 passed, 11 skipped (platform).
  • npm run check:fast: passed.
  • npm run format:check: passed.
  • npm run docs:check: passed.
  • npm run metadata:check: passed.
  • CLI and MCP measurement runs: see the table above.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Without the setting, every request behaves exactly as before. Only the refusal's message and recovery text change, plus the new details.limit.
  • Real Hopper/Ghidra, browser, or OS coverage: synthetic Ghidra-shaped Evidence on macOS arm64. No real provider.
  • Package or release metadata impact: none beyond the new setting.
  • Security, privacy, process, or containment review: a larger allowance only admits more in-memory work for caller-supplied Evidence. The value is bounded at 1 GiB, and the documentation states that it is not a process RSS bound and that peak heap can exceed it.

Author readiness

Human owner: @kaoru0822-kitauji

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/7112 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/25/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `feat/native-matching-allowance-setting` --- ### 📝 Commits (2) - [`87d3d60`](https://github.com/morluto/rea/commit/87d3d6022c370350ae5484a25ea8e5c7168239fa) feat(native): make the function-matching allowance a runtime setting - [`0600c7d`](https://github.com/morluto/rea/commit/0600c7d11a907b9d62f9e0ff6f19ae33b70d7795) refactor(native): keep matching allowance recovery with its parser ### 📊 Changes **18 files changed** (+477 additions, -22 deletions) <details> <summary>View changed files</summary> 📝 `docs/design/limits-and-recovery.md` (+3 -0) 📝 `docs/native-function-matching.md` (+13 -2) 📝 `src/application/SetupRegistrationEnvironment.ts` (+1 -0) 📝 `src/application/native/NativeFunctionMatchingService.test.ts` (+64 -0) 📝 `src/application/native/NativeFunctionMatchingService.ts` (+18 -4) 📝 `src/application/native/NativeMatchingAdmission.ts` (+32 -12) 📝 `src/cli.ts` (+1 -1) 📝 `src/cli/nativeFunctionMatchingCommands.ts` (+5 -1) 📝 `src/config/analysisSettings.ts` (+3 -0) ➕ `src/config/nativeMatchingAllowance.test.ts` (+78 -0) ➕ `src/config/nativeMatchingAllowance.ts` (+78 -0) 📝 `src/config/runtimeAnalysisSettings.ts` (+22 -0) 📝 `src/domain/analysisResourceLimits.ts` (+11 -0) 📝 `src/domain/analysisSettingDefinitions.ts` (+12 -0) 📝 `src/domain/analysisSettings.ts` (+1 -0) 📝 `src/server/registerNativeFunctionMatchingTool.ts` (+14 -1) 📝 `src/server/registerSessionTools.ts` (+6 -1) 📝 `tests/acceptance/analysisSettingsMcp.test.ts` (+115 -0) </details> ### 📄 Description **What broke:** `match_native_functions` refused every request above its fixed 96 MiB representation allowance with "This release has no runtime override". Ordinary 20k–50k function builds could not be matched at all, over either MCP or CLI. **Fix:** The allowance is now the runtime setting `native.matching_representation_bytes`: default 96 MiB, 1 byte to 1 GiB, applied to the next operation. Its launch seed is `REA_NATIVE_MATCHING_REPRESENTATION_BYTES`, which `rea setup` copies. Refusals carry `details.limit` and recovery text that names the setting. **Risk:** Low. The default and charging are unchanged, so existing requests behave the same. Raising the setting lets REA retain more memory. The 1 GiB maximum is an REA policy ceiling, not a host-capacity claim. ## Summary Fixes #6406 (criteria 1, 3 and 5; criterion 2 is the accounting change in #7108; criterion 4 is open, see gaps) With the setting raised, 50,000 Ghidra-shaped functions per side now match through both MCP and CLI. Before, any request above 96 MiB was refused with no override. This follows `docs/design/limits-and-recovery.md`: the default equals today's constant, the value has a floor and a maximum, the launch seed is copied by setup, and the refusal names the setting. This PR is independent of #7108, and either can merge first. ## Problem and expected behavior - Trigger: two `list_procedures` inventories of more than about 10,000 Ghidra-shaped rows per side (reproduced below with synthetic bundles at 11k, 20k and 50k). - Before: the request was refused as `resource_constraint`, with "This release has no runtime override" and `retryable: false`. - After: the same refusal, now with `details.limit` (`setting`, `effective_value`, `source`, `unit`, `minimum`, `maximum`, `applies_at`, `agent_editable`) and recovery that names `update_analysis_settings` and `REA_NATIVE_MATCHING_REPRESENTATION_BYTES`. After raising the setting, the unchanged request succeeds. ## Change and scope - `src/domain/analysisResourceLimits.ts`: adds `DEFAULT_NATIVE_MATCHING_REPRESENTATION_BYTES` (96 MiB) and `MAXIMUM_NATIVE_MATCHING_REPRESENTATION_BYTES` (1 GiB, an REA policy ceiling). The setting definition is in `analysisSettingDefinitions.ts`, typed in `analysisSettings.ts`. - `src/config/nativeMatchingAllowance.ts`: launch parser, plus the recovery text and display formatting (kept with the parser under `src/config/`, per the limits guide). A positive decimal integer is accepted; anything else is a `ConfigurationError` that names the variable and is never replaced by the default. - `runtimeNativeMatchingAllowance`: resolves the accepted operation's runtime override over the launch value, once per call. This is **operation-local**, following the JADX precedent in "Invalid launch configuration": an invalid seed does not stop MCP startup. `match_native_functions` alone returns `configuration_invalid`, and a runtime value repairs it. - `NativeMatchingAdmission` takes the resolved allowance, reads it once per request, and binds `limit` through `analysisSettingLimit`. The fixed-allowance recovery is removed. - Adapters: the MCP handler and the CLI command both pass the allowance into the shared `matchNativeFunctions` / `matchNativeFunctionsFromBundles` service. The CLI takes the snapshotted command environment, like the other commands. - `SetupRegistrationEnvironment` forwards the seed. `inspect_analysis_settings` lists the setting through the shared definitions. - Not included: accounting changes (#7108), and any change to what a match asserts. Contribution scope: not applicable. This adds a curated budget setting to an existing read-only tool and changes no authority. ## Contract and boundary impact - Semantic owner and earliest changed stage: the setting definition is in the domain. Matching admission is the only enforcement owner. - Contribution scope fit and linked maintainer decision: not applicable. - CLI and MCP/tool-catalog contract: `inspect_analysis_settings` and `update_analysis_settings` accept one more setting ID in their enum. The refusal adds `details.limit`, and its message and recovery text change; the error code, category and `retryable: false` are unchanged. The `match_native_functions` schema is unchanged. - Provider, bridge, target-format, or platform compatibility: none. - Evidence, artifact, provenance, or reconstruction contract: unchanged. The allowance is not recorded in Evidence parameters because it does not affect the result. - Target selection, authority, upstream behavior, and cleanup details: not applicable. The setting changes in-memory defaults only; nothing is written. - Generated metadata, package, or installation impact: `rea setup` copies one more environment key when it is set. `npm run metadata:check` passes. - [x] Read existing files or captured data. ## Evidence and regression coverage - Tests added: - `src/config/nativeMatchingAllowance.test.ts`: - The unset default. - 1 byte, 256 MiB and 1 GiB accepted. - `0`, `-1`, `01`, `1.5`, `96MiB`, an empty string and 1 GiB + 1 refused as `ConfigurationError`, without falling back. - Global `parseEnvironment` unaffected by an invalid seed (operation-local), with a runtime override repairing it. - `rea setup` forwarding. - `NativeFunctionMatchingService.test.ts`: - A 64 KiB runtime allowance refuses 100-row inventories with the complete `limit` facts and recovery text. - The default admits the same request. - 15,000 rows per side are refused at the 96 MiB default (`source: "default"`) and match unchanged under 256 MiB. - `tests/acceptance/analysisSettingsMcp.test.ts`, against the built `rea mcp` over stdio: - Runtime: set 4096 → refusal with `limit.source: "runtime"`; reset → success. - Launch seed: `4096` → inspection shows `launch_value`, `source: "setting"` and the environment name, and matching refuses. - Invalid seed `0`: `binary_session` still works, matching returns `configuration_invalid` naming the variable, and a runtime value repairs it. - End-to-end measurements (built CLI and MCP, synthetic Ghidra-shaped `list_procedures` bundles, macOS arm64, Node 24.22.1; every run is a cold process; peak RSS from `/usr/bin/time -l` for CLI and from 20 ms sampling of the process family for MCP): | Rows per side | Path | Default (96 MiB) | `native.matching_representation_bytes` = 512 MiB | | --- | --- | --- | --- | | 11,000 | CLI | refused, 1.01 s | matched, 2.11 s wall, 2.98 s CPU, 616 MB peak | | 20,000 | CLI | refused, 1.43 s | matched, 3.50 s wall, 4.59 s CPU, 795 MB peak | | 50,000 | CLI | refused, 2.57 s | matched, 8.74 s wall, 10.97 s CPU, 1,338 MB peak | | 20,000 | MCP (retained refs) | refused, 196 ms | matched, 1.13 s, server CPU +1.4 s, family RSS 549 → 793 MB | | 50,000 | MCP (retained refs) | refused, 239 ms | matched, 2.67 s, server CPU +3.4 s, family RSS 1,044 → 1,163 MB | The MCP `detail: "summary"` reply is 3,308 bytes. The refusal grows from 773 to 1,536 bytes, because it now carries the `limit` facts and recovery a client needs to retry. Token counts were not measured. The MCP family's starting RSS includes both imported bundles. - Remaining proof gaps: - #6406 criterion 4 (one real pair of large stripped Ghidra inventories) is not run, because Ghidra is not installed on this host. - The 1 GiB ceiling is a policy value and was not driven to the limit. - Peak RSS above is process-wide and includes bundle loading. Boundary regressions: configuration parsing (valid, invalid and bounds), precedence (runtime over launch over default), operation-local invalid launch with repair and reset, and capacity refusal and recovery through the real MCP transport. There is no lifecycle stage. - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run test:local -- src/application/native/NativeFunctionMatchingService.test.ts src/application/AnalysisSettings.test.ts src/server/DeferredEvidenceMcpServer.test.ts src/mcpStartupPolicy.test.ts src/setupEnvironment.test.ts src/application/Doctor.test.ts src/config/regexWorkerHeapRequest.test.ts`: 150 passed. - `npm run test:focused -- tests/acceptance/analysisSettingsMcp.test.ts src/config/nativeMatchingAllowance.test.ts src/application/native/NativeFunctionMatchingService.test.ts`: 30 passed. - `npm run test:focused -- tests/acceptance/analysisSettingsMcp.test.ts tests/acceptance/tools/nativeFunctionMatching.test.ts tests/boundary/filesystem/clientConfigurationEnvironment.test.ts tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts tests/boundary/filesystem/analysisSettingsAdmission.test.ts`: 77 passed, 11 skipped (platform). - `npm run check:fast`: passed. - `npm run format:check`: passed. - `npm run docs:check`: passed. - `npm run metadata:check`: passed. - CLI and MCP measurement runs: see the table above. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Without the setting, every request behaves exactly as before. Only the refusal's message and recovery text change, plus the new `details.limit`. - Real Hopper/Ghidra, browser, or OS coverage: synthetic Ghidra-shaped Evidence on macOS arm64. No real provider. - Package or release metadata impact: none beyond the new setting. - Security, privacy, process, or containment review: a larger allowance only admits more in-memory work for caller-supplied Evidence. The value is bounded at 1 GiB, and the documentation states that it is not a process RSS bound and that peak heap can exceed it. ## Author readiness Human owner: @kaoru0822-kitauji - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-03 13:39:23 +00:00
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#7648
No description provided.