Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
160d821ac9 | ||
|
|
113679c9a6 | ||
|
|
80673b22a4 | ||
|
|
c74d302cc4 | ||
|
|
2c38becc4e | ||
|
|
0a2b7af905 | ||
|
|
cc146042d1 | ||
|
|
fead4fdaa7 | ||
|
|
4973ce7202 | ||
|
|
06a4c338fc | ||
|
|
60ab148afa | ||
|
|
8e6a957886 | ||
|
|
9750486e78 | ||
|
|
4232399079 | ||
|
|
26e155987f | ||
|
|
69a2faa120 | ||
|
|
bffe8a52a1 | ||
|
|
4ed33efb0c | ||
|
|
75e0d620bf | ||
|
|
4c680d099c | ||
|
|
046ec56d16 | ||
|
|
e9b21aadc5 | ||
|
|
f62a2baad8 | ||
|
|
c8269ca497 | ||
|
|
cb26e8ca63 | ||
|
|
d23dc5d188 | ||
|
|
e97f47b0b7 | ||
|
|
f3ed1657f8 | ||
|
|
ab720b4216 | ||
|
|
bb17e90f27 | ||
|
|
627c085cee | ||
|
|
5938581b37 | ||
|
|
fdf5a33fe2 | ||
|
|
be698d4761 | ||
|
|
760d9f6019 | ||
|
|
42c16ba631 | ||
|
|
76afdd35ae | ||
|
|
ed86f9c3bb | ||
|
|
475f09d5ca | ||
|
|
f2fa951c00 | ||
|
|
9140deae63 | ||
|
|
efab65c2f9 | ||
|
|
07eb044f77 | ||
|
|
238914974d | ||
|
|
1ff275fbf7 | ||
|
|
6bcd243ca4 | ||
|
|
aba487eec0 | ||
|
|
ae54b96c6f | ||
|
|
599099f656 | ||
|
|
9548953b47 | ||
|
|
d24ad4f55d | ||
|
|
4b045e8f2f | ||
|
|
10b83a9b62 | ||
|
|
38719a6fa9 | ||
|
|
58fb824105 | ||
|
|
835904e4ee | ||
|
|
5ea2c280b8 | ||
|
|
aced9fa9b4 | ||
|
|
149b43ee61 | ||
|
|
22b88300d0 | ||
|
|
162bd56c9e | ||
|
|
a891bb80ae | ||
|
|
f196ebf806 | ||
|
|
c7ae27807f | ||
|
|
2acdd54130 | ||
|
|
f4f7c75c66 | ||
|
|
a5bfd04401 | ||
|
|
ce4e48ccae | ||
|
|
6a96f4fa4a | ||
|
|
0354759370 | ||
|
|
8dfc6832a4 | ||
|
|
7fe5e5ff5e | ||
|
|
03ed0c63d9 | ||
|
|
a834ed1cab | ||
|
|
425776174c | ||
|
|
31fc9762a3 | ||
|
|
faf219e90b | ||
|
|
3f168af2e8 | ||
|
|
92f32981da | ||
|
|
9b3a633298 | ||
|
|
955c7c80ec | ||
|
|
fcebd79ed8 | ||
|
|
85bc8c8977 |
No files matched your search
@@ -68,9 +68,10 @@ closing-issue reporters. Grouping preserves all those credits in one line.
|
||||
When Highlights, detailed entries or migrations reference the same work,
|
||||
put its thanks in one of those entries, not all of them. Include issue links.
|
||||
Direct-commit bullets carry the commit link and known human credit.
|
||||
Exclude GitHub bots and known agent accounts. REA's observed Anthropic, Cursor
|
||||
and Codex co-author trailers resolve to `@claude`, `@cursoragent` and `@codex`
|
||||
with GitHub type `User`; the credit policy excludes them while retaining their metadata.
|
||||
Exclude GitHub bots and known agent accounts. REA's observed Anthropic, Cursor,
|
||||
Codex, and Copilot co-author trailers resolve to `@claude`, `@cursoragent`,
|
||||
`@codex`, and `@Copilot` with GitHub type `User`; the credit policy excludes
|
||||
them while retaining their metadata.
|
||||
At the REA maintainer's request, omit `@morluto` self-thanks while preserving
|
||||
authorship in the inventory. Other human contributors remain eligible.
|
||||
Use explicit GitHub links for cross-repository references to avoid ambiguous
|
||||
|
||||
@@ -384,8 +384,9 @@ selected provider. Do not treat those scopes as interchangeable.
|
||||
For a Ghidra startup timeout on a large native target, follow
|
||||
[deadline recovery](references/native-and-artifacts.md#ghidra-startup-deadline-recovery)
|
||||
before abandoning deep analysis or repeating the same cold import. The default
|
||||
330 seconds is configurable; changing the server's environment requires a new
|
||||
REA process.
|
||||
330 seconds can be changed for new startup attempts through
|
||||
`update_analysis_settings` using `ghidra.startup_timeout_ms`. Changing the
|
||||
server's launch environment instead requires a new REA process.
|
||||
|
||||
## Work summary-first
|
||||
|
||||
|
||||
@@ -28,8 +28,11 @@ stdio. A full JDK, including `jdk.compiler`, is required on every host; Windows
|
||||
verification coverage. Calls use one worker and provider deadlines; optional
|
||||
`REA_JADX_HEAP_MIB` selects the JVM heap and `REA_JADX_OPERATION_TIMEOUT_MS`
|
||||
the per-operation deadline (integer milliseconds from 1 to 2147483647, default
|
||||
120000; a change applies on the next process start; the MCP client's tool
|
||||
timeout must also allow it). The APK is never launched,
|
||||
120000). To change that deadline on a running MCP server, set
|
||||
`jadx.operation_timeout_ms` through `update_analysis_settings`, then retry after
|
||||
work and cleanup settle. Newly accepted operations use the override; accepted
|
||||
work keeps its budget. Changing the launch environment requires a new process.
|
||||
The MCP client's tool timeout must also allow the selected deadline. The APK is never launched,
|
||||
and no emulator is required.
|
||||
|
||||
Manifest entries and decompiled text are static observations, not execution or
|
||||
|
||||
@@ -128,7 +128,7 @@ rea setup --client codex --dry-run --json
|
||||
Use the current supported client's ID: `claude_code`, `claude_desktop`, `codex`,
|
||||
`cursor`, `gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`,
|
||||
`copilot_cli`, `commandcode`, `qwen_code`, `vscode`, `trae`, `grok_build`, `omp`, `pi`,
|
||||
`hermes`, `qoder`, `droid`, `zcode`, `dsh`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
|
||||
`hermes`, `qoder`, `droid`, `zcode`, `dsh`, `freebuff`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
|
||||
scope. `grok_bot` uses a chat registration step rather than local `mcp.json`.
|
||||
For unsupported clients, use version-pinned manual stdio registration or CLI.
|
||||
|
||||
@@ -175,6 +175,17 @@ writing. Managed policy can constrain client settings. Consult the
|
||||
for the installed client's supported settings. This is an optional recovery
|
||||
choice, not a prerequisite for every REA task.
|
||||
|
||||
## Repair an editable REA limit
|
||||
|
||||
If `details.limit` names an editable setting and its bounds, call
|
||||
`update_analysis_settings` directly with a `changes` entry containing `setting`,
|
||||
`action: "set"` and `value`. `inspect_analysis_settings` is optional when those
|
||||
facts are already present. Retry the same input after work and cleanup settle.
|
||||
Runtime defaults affect new work only; explicit per-call arguments take
|
||||
precedence and must be corrected directly when selected. Reset restores launch
|
||||
values. Updates survive target close/open but disappear on REA restart. They do
|
||||
not edit host deadlines, response buffers, registrations or fixed limits.
|
||||
|
||||
## Continue through an installed CLI
|
||||
|
||||
Check the command environment only if the launcher fails:
|
||||
|
||||
@@ -24,9 +24,13 @@ not an unfiltered request. `detail: "summary"` reduces delivery
|
||||
and does not by itself reduce analysis work.
|
||||
|
||||
ASAR header/index admission defaults to 16 MiB / 16777216 bytes;
|
||||
`REA_ASAR_MAX_HEADER_BYTES` (at most 256 MiB, set where REA starts) changes it.
|
||||
`source_paths`, `detail`, and `max_heap_mb` do not bypass or raise it. Or
|
||||
analyze an extracted directory.
|
||||
an admission error names `asar.max_header_bytes` and its bounds (at most
|
||||
256 MiB). Call `update_analysis_settings` with that setting, `action: "set"`
|
||||
and enough bytes, then retry the same full-archive request. No inspection or
|
||||
REA restart is required. `REA_ASAR_MAX_HEADER_BYTES` seeds future REA processes.
|
||||
`source_paths`, `detail`, and `max_heap_mb` do not bypass or raise header admission.
|
||||
Runtime JavaScript timeout/heap defaults are also editable; explicit per-call
|
||||
`analysis_timeout_ms` and `max_heap_mb` take precedence.
|
||||
|
||||
Exact `#alias` specifiers resolve through the importer's
|
||||
enclosing package `imports` map to package-local files; nested `node_modules`
|
||||
|
||||
@@ -342,14 +342,18 @@ providers. Top-level session capabilities describe the current route, and
|
||||
bridge startup, and readiness. The default 330,000 ms is configurable through
|
||||
`REA_GHIDRA_STARTUP_TIMEOUT_MS`; a smaller query still needs the same cold import.
|
||||
|
||||
Preserve useful Evidence before closing the session or restarting REA. Check
|
||||
`close_binary` and resolve reported cleanup failures. Set a larger startup budget
|
||||
in the environment that launches REA, then restart REA and reconnect the MCP
|
||||
client before reopening the same target. Closing and reopening a binary in the
|
||||
existing server keeps its original configuration and starts import again.
|
||||
Environment changes in an agent's shell do not update an already running MCP
|
||||
server. If its launch configuration cannot be changed, report that constraint
|
||||
and the exact retry setting instead of claiming a fixed technical limit.
|
||||
When the error names `ghidra.startup_timeout_ms`, call
|
||||
`update_analysis_settings` with `action: "set"` and a larger value within the
|
||||
reported bounds. Resolve retained cleanup before the next startup attempt.
|
||||
No REA restart is needed; an already accepted startup keeps its original
|
||||
budget. A new attempt repeats import. Preserve useful Evidence before closing
|
||||
and reopening the target when remediation requires that lifecycle step.
|
||||
|
||||
For a per-function timeout, update `ghidra.decompile_timeout_seconds` and retry
|
||||
the same function on the existing Program. Explicit `null` removes that
|
||||
per-function deadline. Inspection is optional when the error supplies the
|
||||
setting and bounds. Runtime updates disappear when REA exits; environment
|
||||
changes only seed future REA processes.
|
||||
|
||||
The client's tool-call deadline must allow startup plus the requested query;
|
||||
server settings and progress notifications cannot override it. CLI analysis
|
||||
|
||||
@@ -36,6 +36,9 @@ unchecked. Maintainers can help resolve open questions.
|
||||
Use "none" or "not applicable" only when established. -->
|
||||
|
||||
- Semantic owner and earliest changed stage:
|
||||
<!-- Name the producer of parsed facts or resources and its affected consumers.
|
||||
Summarize superseded paths removed and any distinct policy, lifecycle,
|
||||
lazy-loading, or compatibility responsibility retained. -->
|
||||
- Contribution scope fit and linked maintainer decision:
|
||||
- CLI and MCP/tool-catalog contract:
|
||||
- Provider, bridge, target-format, or platform compatibility:
|
||||
@@ -68,6 +71,10 @@ source, link, or rendered-document checks. -->
|
||||
- User-visible CLI/MCP output (if applicable):
|
||||
- Remaining proof gaps:
|
||||
|
||||
For boundary fixes, identify the applicable [boundary regressions](https://github.com/morluto/rea/blob/main/docs/testing.md#boundary-regressions)
|
||||
and [lifecycle regressions](https://github.com/morluto/rea/blob/main/docs/testing.md#lifecycle-regressions)
|
||||
covered by the evidence above, and explain relevant gaps.
|
||||
|
||||
For evidence-bearing changes:
|
||||
|
||||
- [ ] Observed, derived, and inferred claims remain distinguishable.
|
||||
|
||||
@@ -858,6 +858,7 @@ jobs:
|
||||
src/process/capture/FilesystemSnapshot.test.ts
|
||||
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
|
||||
tests/boundary/filesystem/typescriptCompiler.test.ts
|
||||
tests/boundary/filesystem/releaseProposal.test.ts
|
||||
tests/boundary/filesystem/binaryTarget.test.ts
|
||||
src/contracts/sessionPathInputs.test.ts
|
||||
tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts
|
||||
|
||||
@@ -55,6 +55,12 @@ jobs:
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
- name: Install ELF32 cross toolchains for source-owned fixtures
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes --no-install-recommends \
|
||||
gcc-i686-linux-gnu gcc-arm-linux-gnueabi gcc-mips-linux-gnu gcc-mipsel-linux-gnu \
|
||||
libc6-dev-i386-cross libc6-dev-armel-cross libc6-dev-mips-cross libc6-dev-mipsel-cross
|
||||
- name: Acquire exact upstream libraries in an isolated runner environment
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
+95
-3
@@ -1,7 +1,13 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript", "import", "unicorn", "oxc"],
|
||||
"plugins": ["typescript", "import", "unicorn", "oxc", "vitest"],
|
||||
"rules": {
|
||||
"rea/no-buffer-deep-equality": "error",
|
||||
"rea/cancellation-cause": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-escape": ["warn", { "allowRegexCharacters": ["-"] }],
|
||||
"complexity": ["warn", 20],
|
||||
"max-depth": ["warn", 4],
|
||||
"max-lines": [
|
||||
@@ -23,7 +29,38 @@
|
||||
"max-params": ["warn", 4],
|
||||
"no-var": "warn",
|
||||
"no-var-requires": "error",
|
||||
"typescript/no-import-type-side-effects": "error"
|
||||
"typescript/no-import-type-side-effects": "error",
|
||||
"vitest/no-standalone-expect": "off",
|
||||
"vitest/no-conditional-expect": "off",
|
||||
"vitest/expect-expect": "off",
|
||||
"vitest/no-disabled-tests": "off",
|
||||
"vitest/require-mock-type-parameters": "off",
|
||||
"vitest/require-to-throw-message": "off",
|
||||
"import/no-duplicates": [
|
||||
"warn",
|
||||
{
|
||||
"preferInline": false
|
||||
}
|
||||
],
|
||||
"vitest/valid-expect": [
|
||||
"warn",
|
||||
{
|
||||
"maxArgs": 2
|
||||
}
|
||||
],
|
||||
"rea/no-evidence-json-preadmission": "error",
|
||||
"rea/no-positional-tool-contract": "error",
|
||||
"rea/no-fixture-root-cleanup": "error",
|
||||
"typescript/no-floating-promises": "error",
|
||||
"typescript/no-misused-promises": "warn",
|
||||
"typescript/return-await": ["error", "error-handling-correctness-only"],
|
||||
"typescript/switch-exhaustiveness-check": [
|
||||
"error",
|
||||
{
|
||||
"considerDefaultExhaustiveForUnions": true
|
||||
}
|
||||
],
|
||||
"rea/no-shared-module-mock": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@@ -36,6 +73,12 @@
|
||||
"no-var-requires": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/**/*.ts"],
|
||||
"rules": {
|
||||
"typescript/no-misused-promises": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.fixture.ts"],
|
||||
"rules": {
|
||||
@@ -47,7 +90,8 @@
|
||||
"skipBlankLines": true,
|
||||
"skipComments": true
|
||||
}
|
||||
]
|
||||
],
|
||||
"typescript/no-misused-promises": "warn"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -91,6 +135,45 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/**/*.test.ts", "src/**/*.test.ts"],
|
||||
"rules": {
|
||||
"vitest/valid-expect": [
|
||||
"warn",
|
||||
{
|
||||
"maxArgs": 2
|
||||
}
|
||||
],
|
||||
"vitest/valid-expect-in-promise": "error",
|
||||
"vitest/no-focused-tests": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"scripts/lib/http3-capture-assertions.test.mjs",
|
||||
"scripts/lib/http3-verifier-lifecycle.test.mjs"
|
||||
],
|
||||
"rules": {
|
||||
"typescript/no-floating-promises": [
|
||||
"error",
|
||||
{
|
||||
"allowForKnownSafeCalls": [
|
||||
{
|
||||
"from": "package",
|
||||
"package": "@types/node",
|
||||
"name": "test"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["scripts/fixtures/javascript-recovery/code-split/entry.mjs"],
|
||||
"rules": {
|
||||
"typescript/no-floating-promises": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
@@ -109,5 +192,14 @@
|
||||
".devin",
|
||||
".factory",
|
||||
"third_party"
|
||||
],
|
||||
"options": {
|
||||
"reportUnusedDisableDirectives": "error"
|
||||
},
|
||||
"jsPlugins": [
|
||||
{
|
||||
"name": "rea",
|
||||
"specifier": "./scripts/lint/index.mjs"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "6.7.0"
|
||||
".": "6.8.0"
|
||||
}
|
||||
@@ -80,7 +80,7 @@ Coordinate expensive checks across agents. If a test unexpectedly consumes heavy
|
||||
|
||||
For tool design, follow [docs/tool-design.md](docs/tool-design.md): start from the analyst question, reuse existing contracts, preserve caller choices, and return useful evidence inline. Limits must follow real format, protocol, authority, or resource constraints. Account for representation expansion before allocation; derive completeness from examined coverage.
|
||||
|
||||
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
|
||||
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Select applicable [boundary regressions](docs/testing.md#boundary-regressions) for parser, inventory, snapshot, and projection fixes. Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
|
||||
|
||||
Use Conventional Commit subjects and PR titles. Mark breaking changes with `!` or a `BREAKING CHANGE:` footer. Describe behavior changes, relevant validation, and real-provider verification scope.
|
||||
|
||||
@@ -108,10 +108,11 @@ distinguish source-derived conclusions, proposed checks, and unavailable coverag
|
||||
Address actionable automated review findings and explain disagreements.
|
||||
|
||||
Leave the template's **Author readiness** declarations unchecked for the human
|
||||
owner to complete. Never attest to human review on their behalf. Keep the PR in
|
||||
draft until the human declares the PR initially ready. Later pushes do not
|
||||
invalidate that declaration or require another human sign-off. Keep the PR body
|
||||
and affected validation current as work changes. The readiness check validates
|
||||
owner to complete. Never attest to human review on their behalf. Draft status
|
||||
is optional; preserve the PR's current draft status unless the user asks to change
|
||||
it. Later pushes do not invalidate the readiness declaration or require another
|
||||
human sign-off. Keep the PR body and affected validation current as work changes.
|
||||
The readiness check validates
|
||||
the filled declaration; CI and automated review results do not establish that
|
||||
human review occurred, maintainer scope approval, or merge permission.
|
||||
|
||||
|
||||
+102
@@ -1,5 +1,107 @@
|
||||
# Changelog
|
||||
|
||||
## [6.8.0](https://github.com/morluto/rea/compare/rea-agents-6.7.0...rea-agents-6.8.0) (2026-10-25)
|
||||
|
||||
### Highlights
|
||||
|
||||
- Build local agent environments from network captures, browser scenarios, or a prompt, then serve derived reads and evidence-gated writes and compare them with fresh application captures.
|
||||
- Inspect more shipped formats, including GPU code objects, BEAM, Python bytecode, Unity IL2CPP metadata, Godot PCK, PyInstaller, Node.js SEA, devicetree, and additional PE/ELF tables.
|
||||
- Keep large investigations focused with retained summaries, selected fact queries, configurable recovery limits, and row-oriented CLI exports. Complete Evidence remains available.
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
- **Function analysis:** `analyze_function` now defaults to a retained summary. Set `detail: "complete"` for the previous inline dossier; the CLI retains its complete default and requires `--evidence_output` when selecting a summary. [#5660](https://github.com/morluto/rea/pull/5660). Thanks @CryptoJones, @gensokyo-dev, @RishTheFish21.
|
||||
- **Accessibility:** Accept JSON numbers and booleans in node `value`, alongside strings and null. Update consumers that previously assumed text-only values. [cc90771](https://github.com/morluto/rea/commit/cc90771af14f045538651615147d8bf86b576549), [#6759](https://github.com/morluto/rea/pull/6759), [#6467](https://github.com/morluto/rea/issues/6467). Thanks @coygeek, @gensokyo-dev, @mvanhorn.
|
||||
- **JSON Lines:** `--format jsonl` now emits object-collection rows followed by a `$rea: "jsonl/v1"`, `type: "metadata"` record; scalar selections and unsupported reports are refused. Parse control records separately, select a single supported collection when necessary, and use `--format json` for the previous whole-document output. See [JSON Lines framing](https://github.com/morluto/rea/blob/rea-agents-6.8.0/docs/cli.md#json-lines-record-output). [#5629](https://github.com/morluto/rea/pull/5629), [#5487](https://github.com/morluto/rea/issues/5487). Thanks @gensokyo-dev, @jayykernel, @vijaykiran06.
|
||||
- **Interface Builder:** Keyed-archive graph identities use decimal table UIDs instead of authored `objectID`/`object-id`. Treat authored IDs as attributes and update persisted graph lookups; ibtool and native NIBArchive identities keep their source semantics. [6fbe67d](https://github.com/morluto/rea/commit/6fbe67d66e3d716db69929503a3a232a7ada0860).
|
||||
- **Frida:** `instrument-with-frida` JSON returns the canonical Evidence directly. Read `normalized_result.cleanup_error` instead of the former top-level wrapper field. [48f700e](https://github.com/morluto/rea/commit/48f700ee35e0988ed973e5486dc9db33f2a5d944).
|
||||
- **Reconstruction proof:** ID-array proof records no longer establish verified or ready ledgers. Supply implementation/revision bindings and individual claim outcomes; handle inapplicable, unavailable, and failed proof diagnostics. [f8b65e7](https://github.com/morluto/rea/commit/f8b65e78723b1e2169f94dd1226198ea3139707e).
|
||||
- **Mach-O imports:** Handle a null import `name` when identity is ambiguous; use `raw_name` and `name_status` to distinguish observed expressions from exact symbols. [5d0d243](https://github.com/morluto/rea/commit/5d0d243a45c9132e1aff75d454ab53d86a74cc31), [169512a](https://github.com/morluto/rea/commit/169512a35bb015ff209168fb8bc1c9b31a2c678b).
|
||||
- **Node.js:** Use a runtime satisfying `^22.23.4 || ^24.22.1 || >=26.12.1`; Node 23 and 25 are unsupported. Upgrade Node before installing or updating REA; the installer does not upgrade it. [#6315](https://github.com/morluto/rea/pull/6315), [#6283](https://github.com/morluto/rea/issues/6283). Thanks @GamerDubz, @gensokyo-dev, @ShikamaruNaaraa.
|
||||
- **Browser storage:** Historical hashed storage remains readable, but comparisons stay unknown until both captures are recaptured in the new representation. [#6341](https://github.com/morluto/rea/pull/6341), [#6289](https://github.com/morluto/rea/issues/6289). Thanks @coygeek, @gongfpp.
|
||||
- **Environment serving:** `environment-serve` serves derived state by default, including write tools whose effects are confirmed by recorded evidence. Use `--replay-only` for stateless replay of manifest tools. Read stop-summary `state` instead of `state_evidence_id`. [#5351](https://github.com/morluto/rea/pull/5351), [#6158](https://github.com/morluto/rea/pull/6158). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **MCP schemas:** Input advertisements default to compact. Set `REA_MCP_INPUT_SCHEMA_PROFILE=full` for complete advertised guidance and nested constraints. [#5975](https://github.com/morluto/rea/pull/5975). Thanks @gensokyo-dev.
|
||||
- **Browser console arguments:** Accept null `argument_types` and `text_capture.status: "unavailable"` when the protocol argument inventory is omitted or malformed; preserve that uncertainty. [#6016](https://github.com/morluto/rea/pull/6016), [#5934](https://github.com/morluto/rea/issues/5934). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Reconstruction summaries:** `summary.by_evidence_authority` is keyed by Evidence authority; read the former source-state counts from required `summary.by_source_state`. Regenerate ledgers whose closure digests depended on the old projection. [#5875](https://github.com/morluto/rea/pull/5875), [#5838](https://github.com/morluto/rea/issues/5838). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Swift bindings:** Conformance `type_name` and `protocol_name` may be null; consume typed `type_binding`/`protocol_binding` evidence. Replace the former Objective-C-specific import-binding type/schema with `MachoImportBinding`. [d0067ba](https://github.com/morluto/rea/commit/d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f).
|
||||
- **WebSocket frames:** Accept null opcodes and preserve unknown frame facts; numeric opcodes are restricted to the four-bit protocol range. [0bc2b42](https://github.com/morluto/rea/commit/0bc2b42001f684beffcf47b70e507443b1a5d320).
|
||||
- **Resource-only inspection:** Evidence subjects and executable digests may be null. Use `resource_root` and each resource's reported provenance instead of requiring an executable target. [6a6adf7](https://github.com/morluto/rea/commit/6a6adf703fa44100643947d20fbc670f86260b1b).
|
||||
- **Browser timestamps:** Console and session-timeline timestamps may be null when the protocol supplies no finite/comparable time. Keep unknown time explicit in sorting and comparisons. [690fc51](https://github.com/morluto/rea/commit/690fc518540bae982038a74f47ef7781201785aa), [2a0410f](https://github.com/morluto/rea/commit/2a0410f1717b3126e8a95063d77dea66a597010a).
|
||||
- **Reference sources:** Read failed root enumeration from `rootFailure` rather than a synthetic failed child at `"."`; preserve the top-level source-graph limitation. [6296fdf](https://github.com/morluto/rea/commit/6296fdf560f1ac4aa55985558edaa98300aacdbc).
|
||||
- **Filesystem capture:** Accept `entry_unavailable` and `identity_changed` enumeration omission reasons in addition to the previous reason literals. [c85b44e](https://github.com/morluto/rea/commit/c85b44e1b14851c54e65327a4807f79ad5cd5a61). Thanks @rudrakshagarwal4200-ai.
|
||||
- **APK locales:** Replace numeric legacy selectors such as `es-r419` with `es-419` or `b+es+419`. Two-letter regions remain supported. [29910d1](https://github.com/morluto/rea/commit/29910d16b98fe4b607a7aa15928789a73614b0b9). Thanks @mertkayacs.
|
||||
- **XML plist scalars:** Recognize `$plist_type: "unknown"` and read `element`, `literal`, and `reason`; unresolved XML scalars use this wrapper instead of the former real/null unknown marker or malformed scalar prefixes. Supported extended UTC years preserve their timestamp. [3537e58](https://github.com/morluto/rea/commit/3537e58c1ab29309f06eabf50bc52adba9817a0c).
|
||||
|
||||
### Changes
|
||||
|
||||
- **Binary layouts:** Inspect ELF32 i386, little-endian ARM, and little- or big-endian MIPS `EXEC`/`DYN`/`REL` layouts on Linux x64 using a caller-supplied pwntools environment. [#7052](https://github.com/morluto/rea/pull/7052), [#6934](https://github.com/morluto/rea/issues/6934). Thanks @roblennon, @salmanalmutairi1.
|
||||
- **Agent environments:** Project network captures and Evidence files into manifests, preserve template path pairs and positions, export AgentEnv bundles, and serve manifests as replay environments. [#4954](https://github.com/morluto/rea/pull/4954), [#5293](https://github.com/morluto/rea/pull/5293), [#5664](https://github.com/morluto/rea/pull/5664), [#4999](https://github.com/morluto/rea/pull/4999), [#4975](https://github.com/morluto/rea/pull/4975). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **Capture to environment:** Build environments from browser scenarios or a prompt, extract the initial observed state, and compare the environment with application probe captures. [#5182](https://github.com/morluto/rea/pull/5182), [#6946](https://github.com/morluto/rea/pull/6946), [#5319](https://github.com/morluto/rea/pull/5319), [#5699](https://github.com/morluto/rea/pull/5699). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **State-backed tools:** Render read filters, search, sorting and paging from state; apply confirmed write effects with broader state targets and recording-based write admission. [#5352](https://github.com/morluto/rea/pull/5352), [293832b](https://github.com/morluto/rea/commit/293832bafc9a054914ec514fe906dd4936c7a372), [#5800](https://github.com/morluto/rea/pull/5800), [#5787](https://github.com/morluto/rea/pull/5787), [#5600](https://github.com/morluto/rea/pull/5600), [5375ff0](https://github.com/morluto/rea/commit/5375ff0f9aadb4fd0d46333815aa0431e3df1010), [#6169](https://github.com/morluto/rea/pull/6169), [#6385](https://github.com/morluto/rea/pull/6385), [#6897](https://github.com/morluto/rea/pull/6897). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **State learning:** Model application-assigned values, preserve view-specific clock spellings, learn write clocks from scenario timing, derive fields, and learn creates across sorted arrays and referenced entities. [7ec9b6e](https://github.com/morluto/rea/commit/7ec9b6e56e1b3762285dca881db6d900a389ab9c), [#6136](https://github.com/morluto/rea/pull/6136), [957bcfa](https://github.com/morluto/rea/commit/957bcfa149c1a0593bb0aa2466cc56d1d4f62e7d), [#6138](https://github.com/morluto/rea/pull/6138), [#6967](https://github.com/morluto/rea/pull/6967), [#7025](https://github.com/morluto/rea/pull/7025), [#7034](https://github.com/morluto/rea/pull/7034). Thanks @earakely-scale.
|
||||
- **Recorded writes:** Serve creates whose compatible read-backs add omitted fields or express timestamps at a different precision, and whole-object updates that regenerate observed slugs or modification times. Conflicting observations still withhold writes. State views and bundles carrying the new read-back data need REA 6.8.0 or later to serve. [#7080](https://github.com/morluto/rea/pull/7080). Thanks @earakely-scale.
|
||||
- **Native investigations:** Page native inventory summaries, match functions across builds, export pseudocode to resumable directories, and select objdump disassembly by symbol. [#5378](https://github.com/morluto/rea/pull/5378), [#5390](https://github.com/morluto/rea/pull/5390), [#6170](https://github.com/morluto/rea/pull/6170), [#5171](https://github.com/morluto/rea/pull/5171), [#4249](https://github.com/morluto/rea/issues/4249), [#5343](https://github.com/morluto/rea/issues/5343), [#5159](https://github.com/morluto/rea/issues/5159). Thanks @coygeek, @CryptoJones, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **Engine workflows:** Bound a single Ghidra decompilation without closing its session, analyze GameCube/Wii DOL executables, and inspect static CUDA/HIP code objects. [#5662](https://github.com/morluto/rea/pull/5662), [#6240](https://github.com/morluto/rea/pull/6240), [#5813](https://github.com/morluto/rea/pull/5813), [#5340](https://github.com/morluto/rea/issues/5340), [#5345](https://github.com/morluto/rea/issues/5345). Thanks @arielfikru, @coygeek, @gensokyo-dev, @RishTheFish21.
|
||||
- **PE/ELF:** Inspect export tables without an engine, PE imports/delay-loads, Authenticode certificates, TLS callbacks/load configuration, ELF notes, Linux kernel module metadata, and Zstandard section-name tables. [#6278](https://github.com/morluto/rea/pull/6278), [#6509](https://github.com/morluto/rea/pull/6509), [#4830](https://github.com/morluto/rea/pull/4830), [#7008](https://github.com/morluto/rea/pull/7008), [#4837](https://github.com/morluto/rea/pull/4837), [#7010](https://github.com/morluto/rea/pull/7010), [#6987](https://github.com/morluto/rea/pull/6987), [#4057](https://github.com/morluto/rea/issues/4057). Thanks @gensokyo-dev, @kaoru0822-kitauji, @mvanhorn, @Rinkia, @ShuaixinHuang.
|
||||
- **Apple/native metadata:** Inspect IPS crash reports, decode Swift nominal declarations, and select universal Mach-O slices in Go/Rust metadata readers. [#6511](https://github.com/morluto/rea/pull/6511), [#6257](https://github.com/morluto/rea/pull/6257), [#6219](https://github.com/morluto/rea/pull/6219), [#5341](https://github.com/morluto/rea/issues/5341), [#4102](https://github.com/morluto/rea/issues/4102). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @mvanhorn, @Rinkia.
|
||||
- **Packaged applications:** Triage desktop installation directories and inspect/extract Godot PCK, PyInstaller archives, and Node.js single-executable application blobs. [#5207](https://github.com/morluto/rea/pull/5207), [#3634](https://github.com/morluto/rea/pull/3634), [afa5da7](https://github.com/morluto/rea/commit/afa5da745e0b139e68029a96b776ae30e48ea2b2), [#2433](https://github.com/morluto/rea/pull/2433), [#7000](https://github.com/morluto/rea/pull/7000), [#4399](https://github.com/morluto/rea/issues/4399). Thanks @0xthearchitect, @coygeek, @gensokyo-dev, @mvanhorn.
|
||||
- **Unity and managed code:** Inventory IL2CPP images/types/methods and metadata versions 24/27; read member names and literals, link native methods and generic instances, and export Ghidra seeds. Project managed call/field-access edges and page managed member summaries. [#3807](https://github.com/morluto/rea/pull/3807), [#5326](https://github.com/morluto/rea/pull/5326), [52d1a72](https://github.com/morluto/rea/commit/52d1a72af047a73b787b8935b032786a34966036), [#5327](https://github.com/morluto/rea/pull/5327), [#5785](https://github.com/morluto/rea/pull/5785), [#6260](https://github.com/morluto/rea/pull/6260), [#5167](https://github.com/morluto/rea/pull/5167), [#3784](https://github.com/morluto/rea/issues/3784), [#5112](https://github.com/morluto/rea/issues/5112), [#4056](https://github.com/morluto/rea/issues/4056), [#5148](https://github.com/morluto/rea/issues/5148). Thanks @8rulerstar, @alexlanghart, @elricfd, @gensokyo-dev, @jaelliot, @kaoru0822-kitauji, @Rinkia.
|
||||
- **Android:** Inspect APK signing declarations and structured manifest components; report static-final DEX field values. [e93e3b1](https://github.com/morluto/rea/commit/e93e3b1e7d6e7e4f49c2de24b92452ec29e853f8), [#4158](https://github.com/morluto/rea/pull/4158), [#5025](https://github.com/morluto/rea/pull/5025), [#6981](https://github.com/morluto/rea/pull/6981), [#4537](https://github.com/morluto/rea/issues/4537), [#6397](https://github.com/morluto/rea/issues/6397). Thanks @Abelo9996, @BaaRai, @FerroLx, @gensokyo-dev, @sserrgo12.
|
||||
- **Bytecode and firmware:** Inspect and query CPython pyc and BEAM facts, inspect flattened devicetrees, and attribute MMIO peripheral regions in native disassembly. [#4826](https://github.com/morluto/rea/pull/4826), [#6928](https://github.com/morluto/rea/pull/6928), [#4833](https://github.com/morluto/rea/pull/4833), [#6943](https://github.com/morluto/rea/pull/6943), [#7009](https://github.com/morluto/rea/pull/7009), [#5348](https://github.com/morluto/rea/pull/5348), [#6922](https://github.com/morluto/rea/issues/6922), [#6938](https://github.com/morluto/rea/issues/6938). Thanks @gensokyo-dev, @liziye627-design, @mvanhorn.
|
||||
- **JavaScript:** Retain queryable static route inventories, link assignment values to writes, and accept source selections as a JSON array or file. [#6019](https://github.com/morluto/rea/pull/6019), [#6206](https://github.com/morluto/rea/pull/6206), [#5029](https://github.com/morluto/rea/pull/5029), [#4772](https://github.com/morluto/rea/issues/4772), [#6193](https://github.com/morluto/rea/issues/6193). Thanks @gensokyo-dev, @IvenKooLab, @jacobmalibiran, @JoshOsullivan-au, @kaoru0822-kitauji, @KJLavender.
|
||||
- **Data inspection:** Decode selected protobuf/gRPC bodies, inspect GGUF/safetensors declarations, and produce byte-entropy profiles. [#5031](https://github.com/morluto/rea/pull/5031), [#5603](https://github.com/morluto/rea/pull/5603), [#2331](https://github.com/morluto/rea/pull/2331), [#4986](https://github.com/morluto/rea/issues/4986), [#3323](https://github.com/morluto/rea/issues/3323). Thanks @AayushJB03, @gensokyo-dev, @kaoru0822-kitauji, @KJLavender, @michaelkillgta, @mvanhorn.
|
||||
- **CLI input/output:** Accept structured request documents on stdin and expose version/runtime output formats. Row-oriented JSONL exports use the migration above. [#7040](https://github.com/morluto/rea/pull/7040), [#5486](https://github.com/morluto/rea/issues/5486), [#6530](https://github.com/morluto/rea/pull/6530), [169512a](https://github.com/morluto/rea/commit/169512a35bb015ff209168fb8bc1c9b31a2c678b), [#5483](https://github.com/morluto/rea/issues/5483). Thanks @Abelo9996, @ahmed-husssain, @CassieFox23, @DeryFerd, @eco-null, @ezrabrilliant, @GamerDubz, @gensokyo-dev, @gongfpp, @hifzabuildsai, @jayykernel, @mhamzasakhi, @pablodiazjorge, @Plastic-time, @raidnouredden-ux, @SandstromPL, @ShuaixinHuang, @Sriram-PR, @XiaoBinGan.
|
||||
- **MCP and setup:** Add opt-in Streamable HTTP and FreeBuff setup registration. FreeBuff support first appears in 6.8.0; installed native FreeBuff loading and skill discovery remain unverified. [#4955](https://github.com/morluto/rea/pull/4955), [#2736](https://github.com/morluto/rea/pull/2736), [#3243](https://github.com/morluto/rea/issues/3243). Thanks @0xLLVEL, @codebuff-team, @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **Recovery controls:** Adjust per-call WASM/SQLite/firmware deadlines, MCP startup budgets, regex matching deadlines/worker heap, capture decoder heap, ASAR header admission, Apple dispatch input, and the bridge request ceiling. MCP runtime limit recovery reports actionable settings. [#5798](https://github.com/morluto/rea/pull/5798), [#5818](https://github.com/morluto/rea/pull/5818), [aa53c06](https://github.com/morluto/rea/commit/aa53c065387c58b4189d82c47f1f3445fc505e30), [#5863](https://github.com/morluto/rea/pull/5863), [#6304](https://github.com/morluto/rea/pull/6304), [#6267](https://github.com/morluto/rea/pull/6267), [#6301](https://github.com/morluto/rea/pull/6301), [#6299](https://github.com/morluto/rea/pull/6299), [#6209](https://github.com/morluto/rea/pull/6209), [#6233](https://github.com/morluto/rea/pull/6233), [#6376](https://github.com/morluto/rea/pull/6376), [#7049](https://github.com/morluto/rea/pull/7049), [#5797](https://github.com/morluto/rea/issues/5797), [#5812](https://github.com/morluto/rea/issues/5812), [#5851](https://github.com/morluto/rea/issues/5851), [#6176](https://github.com/morluto/rea/issues/6176), [#3917](https://github.com/morluto/rea/issues/3917). Thanks @gensokyo-dev, @jaychou813, @kaoru0822-kitauji.
|
||||
- **APK output:** Set `string_tables_only` or CLI `--string-tables-only` to omit duplicate primary strings and avoid their output-capacity charge. [7c458f0](https://github.com/morluto/rea/commit/7c458f04b9155707c8ba406f8b455b10016f1b99), [#4990](https://github.com/morluto/rea/pull/4990), [#4982](https://github.com/morluto/rea/issues/4982). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Retained analysis:** Reuse successful validation of authenticated immutable native/imported-JavaScript parents and captures. Query retained Go build identity, Rust dependencies, JavaScript source-to-bundle mappings, and native export facts without repeating full output. [8f7d12e](https://github.com/morluto/rea/commit/8f7d12e30d63f3d3b4cfe2a90336362841438f56), [#4962](https://github.com/morluto/rea/pull/4962), [#5349](https://github.com/morluto/rea/pull/5349), [#5524](https://github.com/morluto/rea/pull/5524), [#5815](https://github.com/morluto/rea/pull/5815), [#6128](https://github.com/morluto/rea/pull/6128), [#6907](https://github.com/morluto/rea/pull/6907), [#4810](https://github.com/morluto/rea/issues/4810), [#5499](https://github.com/morluto/rea/issues/5499), [#5802](https://github.com/morluto/rea/issues/5802), [#6112](https://github.com/morluto/rea/issues/6112), [#6900](https://github.com/morluto/rea/issues/6900). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Compact results:** Use retained Frida/GDB/objdump summaries, omit redundant Ghidra raw results, and defer the analysis runtime during MCP catalog discovery. [#5661](https://github.com/morluto/rea/pull/5661), [#5614](https://github.com/morluto/rea/pull/5614), [#5355](https://github.com/morluto/rea/pull/5355), [#4424](https://github.com/morluto/rea/pull/4424), [#5640](https://github.com/morluto/rea/issues/5640), [#5584](https://github.com/morluto/rea/issues/5584), [#5337](https://github.com/morluto/rea/issues/5337). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @leeguooooo.
|
||||
- **Analysis work:** Reuse Hopper literal-string records, JavaScript workers and captured prefix selections, Interface Builder validation and plist scalar schemas; bound stable reads to one snapshot buffer and avoid decoding omitted GGUF strings. [#5017](https://github.com/morluto/rea/pull/5017), [#5819](https://github.com/morluto/rea/pull/5819), [31de177](https://github.com/morluto/rea/commit/31de177404461d122761d8243abe4fd24564f4da), [#5885](https://github.com/morluto/rea/pull/5885), [85e38ac](https://github.com/morluto/rea/commit/85e38ac199dff15e8c43343d9170db7e0e46d284), [#6966](https://github.com/morluto/rea/pull/6966), [#5781](https://github.com/morluto/rea/pull/5781), [#5010](https://github.com/morluto/rea/issues/5010), [#5855](https://github.com/morluto/rea/issues/5855), [#6954](https://github.com/morluto/rea/issues/6954), [#5774](https://github.com/morluto/rea/issues/5774). Thanks @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **Native scheduling:** Batch modern IDA callee lookups, schedule decompilation reads on demand, index NativeAOT type closure, and retain call-path ancestry once per visited node. [0c6fed9](https://github.com/morluto/rea/commit/0c6fed90b74c75a9fd23d1ec1b510a7274f85887), [06fe7ee](https://github.com/morluto/rea/commit/06fe7eecbc8c387414b4c1aa0d88ad6314f54f0c), [ce61680](https://github.com/morluto/rea/commit/ce61680c04c95a9cdfad5335632a99542af26d37), [5e4ec0f](https://github.com/morluto/rea/commit/5e4ec0fa1b678aa3bf20704ec9110224ba8c1676).
|
||||
- **Selected processing:** Prepare CLI positional schemas only for the selected command, separate entropy reads from report resolution, prune typed-array reference escapes, index state-write cutoffs, decode only selected replay query values, and skip payload text during WAT framing. [315b234](https://github.com/morluto/rea/commit/315b23403f0819967878cee418c3f9cb4b1a69d9), [#6950](https://github.com/morluto/rea/pull/6950), [#5139](https://github.com/morluto/rea/pull/5139), [#5350](https://github.com/morluto/rea/pull/5350), [96ea32b](https://github.com/morluto/rea/commit/96ea32b7e4e28d3c9cd79a1de4b8e1ba3b4244e9), [70835d3](https://github.com/morluto/rea/commit/70835d3d5f030deadf606e1c79c70a60883e50c9), [#5766](https://github.com/morluto/rea/pull/5766), [#6947](https://github.com/morluto/rea/issues/6947), [#5760](https://github.com/morluto/rea/issues/5760). Thanks @galleonli, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
|
||||
### Fixes
|
||||
|
||||
- **IPS report fields:** Preserve the recorded terminating process and PID, termination flags, parent PID, and build/platform metadata. [#7079](https://github.com/morluto/rea/pull/7079), [#7078](https://github.com/morluto/rea/issues/7078). Thanks @rodricksz4h5.
|
||||
- **Ghidra language selection:** Accept stock language IDs containing `LEBE` or slash-separated processor names, and report conflicting DOL language overrides as configuration errors naming `REA_GHIDRA_LANGUAGE_ID`. [#7070](https://github.com/morluto/rea/pull/7070), [#7072](https://github.com/morluto/rea/issues/7072), [#7081](https://github.com/morluto/rea/pull/7081), [#7082](https://github.com/morluto/rea/issues/7082). Thanks @Adolanium.
|
||||
- **Setup and Hopper:** Preserve the Linux embedding runtime, supported x86_64 host restriction, readiness-based provider choices, orderly demo-helper shutdown, and installed apt dependency versions. Keep cleanup and skill publication ownership through retries. [#5041](https://github.com/morluto/rea/pull/5041), [#3422](https://github.com/morluto/rea/pull/3422), [f5d1012](https://github.com/morluto/rea/commit/f5d1012fabfe99af82523c3078ce9d93eb3780c1), [#4966](https://github.com/morluto/rea/pull/4966), [#6974](https://github.com/morluto/rea/pull/6974), [211e353](https://github.com/morluto/rea/commit/211e3533b34f81ae0910ad4f53f2a3d3c8587d3e), [#6920](https://github.com/morluto/rea/pull/6920), [#5015](https://github.com/morluto/rea/issues/5015), [#3419](https://github.com/morluto/rea/issues/3419), [#4921](https://github.com/morluto/rea/issues/4921), [#6975](https://github.com/morluto/rea/issues/6975). Thanks @Abel-I-Akhigbe, @Adolanium, @DavidFB-creator, @DeryFerd, @gensokyo-dev, @kaoru0822-kitauji, @sx4im.
|
||||
- **Client setup:** Treat empty APPDATA as unset, distinguish MSIX Claude Desktop process views, preserve aliased Hermes server tables, reuse client dialect/alias bindings, and materialize the pinned package-runner cache before the first start. [#5126](https://github.com/morluto/rea/pull/5126), [#4903](https://github.com/morluto/rea/pull/4903), [#3785](https://github.com/morluto/rea/pull/3785), [b7eeded](https://github.com/morluto/rea/commit/b7eededf5af5061a834f93f57f2c888acf870344), [#7032](https://github.com/morluto/rea/pull/7032), [#5104](https://github.com/morluto/rea/issues/5104), [#4278](https://github.com/morluto/rea/issues/4278), [#3320](https://github.com/morluto/rea/issues/3320), [#7027](https://github.com/morluto/rea/issues/7027). Thanks @CmsChase, @coygeek, @myso-kr, @pablodiazjorge, @vaidehi-snk, @wxj006007.
|
||||
- **Target admission:** Preserve thin arm64_32 Mach-O support, distinguish JVM identity before FAT admission, validate FAT format/CPU identity, preserve original DOS header padding, and reject NULs in local objdump/rizin paths. [#5788](https://github.com/morluto/rea/pull/5788), [6843dbe](https://github.com/morluto/rea/commit/6843dbea9bce4709ee2e8bf46c38f0bbf640716b), [497037e](https://github.com/morluto/rea/commit/497037e6e3975cbb37cfd89ea0ad300faa2a4769), [#7013](https://github.com/morluto/rea/pull/7013), [#3600](https://github.com/morluto/rea/pull/3600), [#5713](https://github.com/morluto/rea/issues/5713), [#6510](https://github.com/morluto/rea/issues/6510). Thanks @coygeek, @dhruvac29, @kaoru0822-kitauji, @Lew1sWong, @qorvr.
|
||||
- **Mach-O and dylibs:** Preserve container framing, facts when dyld rejects a missing UUID, verified-signature snapshot identity, unsigned diagnostics, absolute paths inside the selected root, legacy platform rules, lazy-load payloads, and reasons for rejecting present dylibs. [cf3288d](https://github.com/morluto/rea/commit/cf3288d8cf7a09685acf89103b36c32359be56fa), [2b263a7](https://github.com/morluto/rea/commit/2b263a7fe9e9d4c185c7d6af6b4e67b21a73af30), [eb7acc7](https://github.com/morluto/rea/commit/eb7acc70bf06f119e8639e30a78081d5ba82b0bf), [1502cfb](https://github.com/morluto/rea/commit/1502cfbc8ca0d1d1d86eebf334018dfb418bbac0), [90062c0](https://github.com/morluto/rea/commit/90062c092228dea9d33fadc0d7e1b968105b8b2c), [#5650](https://github.com/morluto/rea/pull/5650), [3b67b36](https://github.com/morluto/rea/commit/3b67b362aa2cd79eac5af57d0965e81a7d0b8c2b), [#5551](https://github.com/morluto/rea/issues/5551). Thanks @coygeek.
|
||||
- **Native facts:** Admit direct dispatch-metadata providers; preserve record-local Objective-C omissions, snapshot profiles, class-import binding identity, stripped class labels, version-like names, and qualified Swift protocol identities. [50b6975](https://github.com/morluto/rea/commit/50b6975d861069e2c6cfa202780621449f863802), [9eec57b](https://github.com/morluto/rea/commit/9eec57b469087fe9bff3d29dcc02f47074159e91), [0ba4d6f](https://github.com/morluto/rea/commit/0ba4d6f43ad610dacc861a1548be1a5c4712cd61), [4d7de00](https://github.com/morluto/rea/commit/4d7de00d4cc9a95ec2b7a3123b04e01db2daafa0), [22c84ce](https://github.com/morluto/rea/commit/22c84ce64aa6c7601aa81af86d4117b5a4c2feac), [4852c83](https://github.com/morluto/rea/commit/4852c830af1e3b9df437235b7a8fa8970a6d1c59), [aa9043f](https://github.com/morluto/rea/commit/aa9043f6b61c453e4735795cfe6a559bf6b6cc16).
|
||||
- **Apple resources:** Preserve independent plist scalars, processing instructions, nonstandard table widths, unsupported NIB connector coverage, and strict IPS JSON surrogate validation; classify Interface Builder objects by their declared superclass chains. [fd728f2](https://github.com/morluto/rea/commit/fd728f271e7de7bcfa63890b573715720d5f948c), [#5806](https://github.com/morluto/rea/pull/5806), [4526862](https://github.com/morluto/rea/commit/4526862728af01f1135714079c0d8a8c754eb364), [14cd78c](https://github.com/morluto/rea/commit/14cd78c3224edcf79fca152915077112f547b9ed), [#6990](https://github.com/morluto/rea/pull/6990), [#5805](https://github.com/morluto/rea/issues/5805), [#5228](https://github.com/morluto/rea/pull/5228), [#5218](https://github.com/morluto/rea/issues/5218). Thanks @coygeek, @DeryFerd, @kaoru0822-kitauji, @Mangolycheematcha, @mhamzasakhi, @Shreyash021104.
|
||||
- **Archives and files:** Complete positional ZIP reads, preserve bounded ASAR signatures, report stable symlink loops as resolution failures, retain target applicability, and report unknown traversal coverage. Retain reported integrity contradictions for members present in only one compared artifact. [#4414](https://github.com/morluto/rea/pull/4414), [e8da7e1](https://github.com/morluto/rea/commit/e8da7e1edd075feec0e6beedec93d5645fed8125), [#5571](https://github.com/morluto/rea/pull/5571), [#5763](https://github.com/morluto/rea/pull/5763), [#5867](https://github.com/morluto/rea/pull/5867), [#5521](https://github.com/morluto/rea/issues/5521), [#5731](https://github.com/morluto/rea/issues/5731), [#5837](https://github.com/morluto/rea/issues/5837), [#7076](https://github.com/morluto/rea/pull/7076). Thanks @Afloat16, @coygeek, @jdotc1, @kaoru0822-kitauji, @WenyuChiou.
|
||||
- **Extraction and file ownership:** Restrict destination-case recovery to owned collisions, retain physical-read failures through late cancellation/cleanup, preserve cleanup capabilities for retry, and retain independent firmware extraction failures. [932fbb7](https://github.com/morluto/rea/commit/932fbb7d84a9a995b9aee3c1be7da05a8c4d7bbb), [9499667](https://github.com/morluto/rea/commit/949966712e68d8857a50a165ecddf04c2d7e6d3b), [56679dd](https://github.com/morluto/rea/commit/56679dd2ae31dda58e181f8bf99775014f7bb82b), [b3c912a](https://github.com/morluto/rea/commit/b3c912ab79ad147a37b6cfbe1552a1a5eecec629), [7298bd0](https://github.com/morluto/rea/commit/7298bd08d134860b84b4073f6703021916a5a4c5).
|
||||
- **Processes and cancellation:** Retain partial samples and shutdown gaps, unresolved descendants and caller abort reasons; bind cleanup errors to their owners, propagate comparison contradictions, and report no normal exit code for signalled targets. [ba866a2](https://github.com/morluto/rea/commit/ba866a26ecbe179a717e746cbc0d1605ae616ad5), [#5270](https://github.com/morluto/rea/pull/5270), [#6144](https://github.com/morluto/rea/pull/6144), [a721c42](https://github.com/morluto/rea/commit/a721c42c07dc674c32ab939627355b8a1bee97a5), [#6298](https://github.com/morluto/rea/pull/6298), [#6486](https://github.com/morluto/rea/pull/6486), [#5935](https://github.com/morluto/rea/issues/5935), [#5944](https://github.com/morluto/rea/issues/5944), [#6100](https://github.com/morluto/rea/issues/6100), [#6101](https://github.com/morluto/rea/issues/6101), [#6110](https://github.com/morluto/rea/issues/6110), [#6113](https://github.com/morluto/rea/issues/6113), [#6121](https://github.com/morluto/rea/issues/6121), [#6131](https://github.com/morluto/rea/issues/6131), [#6132](https://github.com/morluto/rea/issues/6132), [#6218](https://github.com/morluto/rea/issues/6218), [#6445](https://github.com/morluto/rea/issues/6445). Thanks @agorevski, @coygeek, @gongfpp, @kwakhyun, @mhamzasakhi.
|
||||
- **Provider cleanup:** Preserve Windows native handles until close is confirmed, native/Inspector/Electron cleanup ownership, Kuna probe identity and cleanup, IDA registration configuration, and Hopper preparation ownership. [#5772](https://github.com/morluto/rea/pull/5772), [87ab054](https://github.com/morluto/rea/commit/87ab054ea384b3fcbae2998b1f94685edcd5f624), [ff78165](https://github.com/morluto/rea/commit/ff78165978de9dbcf9fa16c2acf74bf85a69dc44), [04ae07a](https://github.com/morluto/rea/commit/04ae07ab8c9ae1a6697b6ad15489bbde515da8b5), [dd0015a](https://github.com/morluto/rea/commit/dd0015aaf156c5701ef9598bcc6c885e6b8da803), [b231c28](https://github.com/morluto/rea/commit/b231c28cf64c2f799ada99343f8f21e0e6acb15e), [#4505](https://github.com/morluto/rea/issues/4505), [#5616](https://github.com/morluto/rea/issues/5616).
|
||||
- **Provider diagnostics:** Give Ghidra restart recovery, stable Homebrew aliases, unsupported-host results for Linux-only integrations, correct IDA regex/readiness handling, and clear diagnostics when Windows refuses a verified native addon. [#5356](https://github.com/morluto/rea/pull/5356), [7c6142e](https://github.com/morluto/rea/commit/7c6142efdc19a42c148d0e8c96b69d18e63c7f87), [#5793](https://github.com/morluto/rea/pull/5793), [#4862](https://github.com/morluto/rea/pull/4862), [99ccfb6](https://github.com/morluto/rea/commit/99ccfb6e709ac4bc0afe176315280abde9ebcb9f), [#6832](https://github.com/morluto/rea/pull/6832), [#5730](https://github.com/morluto/rea/issues/5730), [#4860](https://github.com/morluto/rea/issues/4860), [#5723](https://github.com/morluto/rea/issues/5723). Thanks @coygeek, @dark13b, @gensokyo-dev, @jgt87, @jpmorris41488, @Lew1sWong.
|
||||
- **MCP results:** Preserve protocol envelopes on oversized errors, enforce and advertise absolute Evidence paths, retain typed output-filter errors and selected JSON paths, and give client/provider timeout recovery. Compact advertisements now normalize reported enum and union shapes for strict gateway translation. [562dab0](https://github.com/morluto/rea/commit/562dab00c5cbf76a9b2695bad93792eccdc9a49d), [3cf6206](https://github.com/morluto/rea/commit/3cf6206614e65b5ebb6284a5709090dfb8f9aaee), [0e83d92](https://github.com/morluto/rea/commit/0e83d92c583ae36afe00b2c23dd2a0f1d025cbca), [4714c7e](https://github.com/morluto/rea/commit/4714c7eaa3d1d8b6a5df0aff586e844b9d9ab06d), [1bfa60a](https://github.com/morluto/rea/commit/1bfa60ac20e1a344bcf3132c08d15b6860b05c61), [#5608](https://github.com/morluto/rea/pull/5608), [#3806](https://github.com/morluto/rea/pull/3806), [#5598](https://github.com/morluto/rea/issues/5598), [#3800](https://github.com/morluto/rea/issues/3800). Thanks @gensokyo-dev, @Hahaa909, @wang1xiang.
|
||||
- **Browser lifecycles:** Retain historical outcomes through cleanup, stop late navigation before session release, stop expired work, observe aborted fetch settlement, and mark later scenario actions skipped after failure or cancellation. [2e5171e](https://github.com/morluto/rea/commit/2e5171e391b83f8a467d0861fd452c1f78621fd8), [599fdac](https://github.com/morluto/rea/commit/599fdac029912f96c511f859660483405d2de26f), [b2fad36](https://github.com/morluto/rea/commit/b2fad362f2b37f81dc9a6f784f4521e4ca24d41e), [13a8640](https://github.com/morluto/rea/commit/13a864020414577006dce58d7fdaa877ddd0cb52), [#6023](https://github.com/morluto/rea/pull/6023), [#5943](https://github.com/morluto/rea/issues/5943). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Browser comparisons:** Preserve frame identity, default origin scope, unknown body/shape coverage, correspondence and uncertainty, and independent retained-observation budgets. [#4040](https://github.com/morluto/rea/pull/4040), [#4413](https://github.com/morluto/rea/pull/4413), [#4476](https://github.com/morluto/rea/pull/4476), [a0c22ff](https://github.com/morluto/rea/commit/a0c22ff0125b8af7612db54d3708413e2bfecb25), [a0fcc60](https://github.com/morluto/rea/commit/a0fcc60d89eb0cc09b08181eb9f961acfd07d8e9), [#3878](https://github.com/morluto/rea/issues/3878), [#3910](https://github.com/morluto/rea/issues/3910), [#3941](https://github.com/morluto/rea/issues/3941), [#4385](https://github.com/morluto/rea/issues/4385), [#4477](https://github.com/morluto/rea/issues/4477). Thanks @Adolanium, @coygeek, @eco-null, @gensokyo-dev, @kaoru0822-kitauji, @XY-code1.
|
||||
- **Electron observations:** Preserve listener callback ownership and count registrations by source callsite; block trash calls in the active hook, retain window handles through cancelled selection, and preserve cancellation during finalization. [dfe8378](https://github.com/morluto/rea/commit/dfe8378cf5a727e3d93d47a5986233175c6ef898), [5064007](https://github.com/morluto/rea/commit/506400748eb1d6c674f0cf51330073929c45f939), [816a9dc](https://github.com/morluto/rea/commit/816a9dc98886e6d824cd8ae88b68731b161d5dda), [1707d17](https://github.com/morluto/rea/commit/1707d171d750955bf6387e028e378183b360ffe3), [4e2a566](https://github.com/morluto/rea/commit/4e2a566d6c7e053ecbbee1b684311ed31ea163bf).
|
||||
- **JavaScript source maps:** Keep unmapped and missing mappings unknown, preserve partial indexed-map facts and section paths, propagate preparation capability errors, and retain observed worker failures through cancellation. [a4dbb36](https://github.com/morluto/rea/commit/a4dbb36d8ea83e9f94dd289ac1a2d220d2c72c9b), [2b2e362](https://github.com/morluto/rea/commit/2b2e362a00fc5ba2f93fa6c62e2d8780072d1b4b), [498a667](https://github.com/morluto/rea/commit/498a667234b0140c1cfa43c819b35454ab11d14e), [#6149](https://github.com/morluto/rea/pull/6149), [#6214](https://github.com/morluto/rea/pull/6214), [f01dcca](https://github.com/morluto/rea/commit/f01dccab77f712fb4ac936d0a6e92a70d12f0789), [#6082](https://github.com/morluto/rea/issues/6082), [#6061](https://github.com/morluto/rea/issues/6061). Thanks @coygeek, @diyasharma05, @kaoru0822-kitauji.
|
||||
- **JavaScript semantics:** Preserve overflowing timer uncertainty, unavailable-scope comparisons, directory-form URL/ESM path intent, and artifact confinement. Replaced globals lose built-in effects; erased imports no longer become runtime bindings. Treat a known truthy `fork()` `silent` option as piped stdio unless a string/array `stdio` override takes precedence; unresolved options stay dynamic. [#3456](https://github.com/morluto/rea/pull/3456), [8d198d6](https://github.com/morluto/rea/commit/8d198d6ec707402d75276f516e0090d71767b7e4), [#4646](https://github.com/morluto/rea/pull/4646), [#2700](https://github.com/morluto/rea/pull/2700), [#4565](https://github.com/morluto/rea/pull/4565), [#6994](https://github.com/morluto/rea/pull/6994), [#3400](https://github.com/morluto/rea/issues/3400), [#3882](https://github.com/morluto/rea/issues/3882), [#3367](https://github.com/morluto/rea/issues/3367), [#7083](https://github.com/morluto/rea/pull/7083), [#7084](https://github.com/morluto/rea/issues/7084). Thanks @Adolanium, @amyanger, @coygeek, @gensokyo-dev, @HawkForge, @rohitg00, @viniciusdsandrade.
|
||||
- **JavaScript routes and imports:** Treat path data as route candidates, preserve route flag and lexical RPC identities, package-main trailing-slash selection, catch aliases, and factory parameter ownership. [#6360](https://github.com/morluto/rea/pull/6360), [38ecc02](https://github.com/morluto/rea/commit/38ecc0248e1993d25a2f7dd350024484cce797bb), [#4219](https://github.com/morluto/rea/pull/4219), [21097b1](https://github.com/morluto/rea/commit/21097b192b1ab94647e3446b1a2a4dd00e924d5e), [f774dc4](https://github.com/morluto/rea/commit/f774dc420b3befe2b8e68971b4116da2d62fc800), [#6349](https://github.com/morluto/rea/issues/6349), [#4189](https://github.com/morluto/rea/issues/4189). Thanks @coygeek, @hemv-857, @kaoru0822-kitauji, @Nikhi00718.
|
||||
- **Managed/IL2CPP:** Preserve extension-only scope uncertainty and type discovery after member limits, nested names, ReadyToRun/declaration facts, selected type scope, valid shared-suffix strings, and truncated IL2CPP metadata prefixes. [#5674](https://github.com/morluto/rea/pull/5674), [#5312](https://github.com/morluto/rea/pull/5312), [87fa1b6](https://github.com/morluto/rea/commit/87fa1b613d2b6bda1722855a00f1390f27731b64), [bc2d494](https://github.com/morluto/rea/commit/bc2d494030efd1ccabaeaa0bd49226aa87c378a2), [1f0eb22](https://github.com/morluto/rea/commit/1f0eb228e50908ee2a2aaa72d329178ae1a2cdbd), [4c5fae5](https://github.com/morluto/rea/commit/4c5fae5179dd54c4a0d427cd2a115b9c887e3841), [b3aadaf](https://github.com/morluto/rea/commit/b3aadaf474e59b33e5ff465236008a67eccefb87), [#5467](https://github.com/morluto/rea/issues/5467), [#5146](https://github.com/morluto/rea/issues/5146). Thanks @coygeek, @eco-null, @elricfd, @umi008.
|
||||
- **Format boundaries:** Preserve independent facts across malformed inputs in model, GPU, WASM, Rust, Android, and SQLite inspection; report optional metadata warnings without discarding core results. [eb41f35](https://github.com/morluto/rea/commit/eb41f358cf83d5626f09de68332f9718f9aa6482), [#5789](https://github.com/morluto/rea/pull/5789), [00a9e95](https://github.com/morluto/rea/commit/00a9e95de91a4bd4d2a41f8aee704a53fa575750), [fc63fe8](https://github.com/morluto/rea/commit/fc63fe8682654086261540dcf3c59afc36c7d58f), [39a91d4](https://github.com/morluto/rea/commit/39a91d4db96ca13224c25936330ea84fe78970e5), [6c6e2ad](https://github.com/morluto/rea/commit/6c6e2ad62b20f810b54f5a567e66fc55241514b2), [929a0a7](https://github.com/morluto/rea/commit/929a0a76c870d9991e015dde58308fd9df0280b8), [d137a4c](https://github.com/morluto/rea/commit/d137a4cdde0049de967f1d5cd429ed9e1f7d0a10), [ef475fc](https://github.com/morluto/rea/commit/ef475fcf8b0601c61052b7091568ba574546417d), [#5749](https://github.com/morluto/rea/issues/5749). Thanks @coygeek, @gensokyo-dev.
|
||||
- **Capture decoding:** Preserve malformed protobuf trailer facts and wire/schema identity, HTTP/2 URI and UTF-8 failure facts, WebMCP property coverage, and offline capture availability without a browser provider. Report invalid record ordinals as selection errors with actual bounds where available, and trim surrounding SP/HTAB from gRPC-web trailer values while preserving internal whitespace. [9cf7a7c](https://github.com/morluto/rea/commit/9cf7a7cb0d3c72a1121fde280cb11bf911af8055), [b3c3b21](https://github.com/morluto/rea/commit/b3c3b21e43cab4e71b460d08829d8c6e4e9b9c21), [4cb1f1d](https://github.com/morluto/rea/commit/4cb1f1d820df4498ee464bcf72a5f47d40cf8dab), [51e5938](https://github.com/morluto/rea/commit/51e593877642ab42fb5bf25812f4a4110ee7ac26), [d23be5c](https://github.com/morluto/rea/commit/d23be5c0898c39c9f0c54e2e0b43ee746d92d316), [#6725](https://github.com/morluto/rea/pull/6725), [#3947](https://github.com/morluto/rea/issues/3947), [#7067](https://github.com/morluto/rea/pull/7067), [#7068](https://github.com/morluto/rea/issues/7068), [#7069](https://github.com/morluto/rea/pull/7069), [#7071](https://github.com/morluto/rea/issues/7071). Thanks @Adolanium, @coygeek, @Sriram-PR.
|
||||
- **Evidence and errors:** Authenticate ledger Evidence before committing; preserve parent-schema diagnostics, canonical correction details and owned cleanup outcomes; identify the rejected function-Evidence side/kind. Fixed allowances now name their actual limits. [0407bbf](https://github.com/morluto/rea/commit/0407bbfbe65e8fa475e8bb0c78c30170e2c2f55e), [#6361](https://github.com/morluto/rea/pull/6361), [1629266](https://github.com/morluto/rea/commit/1629266e3c08c90fed6bba5383528af7986a12b7), [#6969](https://github.com/morluto/rea/pull/6969), [#5860](https://github.com/morluto/rea/pull/5860), [#6034](https://github.com/morluto/rea/issues/6034), [#5932](https://github.com/morluto/rea/issues/5932), [#5847](https://github.com/morluto/rea/issues/5847). Thanks @AyoubElleuch, @coygeek, @dhruvac29, @kaoru0822-kitauji.
|
||||
- **ADB and firmware recovery:** Report an unparsed push checksum as unverified, normalize absent installer-package sentinels, describe extraction-relative Unblob links, and avoid recommending a budget increase at its fixed ceiling. [#6010](https://github.com/morluto/rea/pull/6010), [#4073](https://github.com/morluto/rea/pull/4073), [#4573](https://github.com/morluto/rea/pull/4573), [#6971](https://github.com/morluto/rea/pull/6971), [#5924](https://github.com/morluto/rea/issues/5924), [#4175](https://github.com/morluto/rea/issues/4175). Thanks @coygeek, @Ganesh2325, @kaoru0822-kitauji, @Rinkia, @XiaoBinGan, @yankikucuk.
|
||||
- **CLI formatting:** Share Markdown report formatting, exclude inherited row values, and preserve arrays of empty objects as JSON. Render Markdown table headers and cells literally while keeping intraword underscores readable. [b784912](https://github.com/morluto/rea/commit/b784912811fd10ac9dc425696dc0e9af632afa34), [#6984](https://github.com/morluto/rea/pull/6984), [#7058](https://github.com/morluto/rea/pull/7058), [#7057](https://github.com/morluto/rea/issues/7057), [#7073](https://github.com/morluto/rea/pull/7073). Thanks @Plastic-time, @ShuaixinHuang, @viniciusdsandrade.
|
||||
- **Format identity:** Preserve Python marshal scalar/decode limits, empty optional BEAM chunks, PE resource branch identities and certificate alignment, unsupported compressed ELF notes, and codec-framed GPU bundle extents. [0603223](https://github.com/morluto/rea/commit/06032237c6755e3dd5f9a8db732c89958b238cc7), [c46064c](https://github.com/morluto/rea/commit/c46064c3f6eaba121c5de7b17390bfdecdd132ef), [66671e8](https://github.com/morluto/rea/commit/66671e874888e98f7e2b27e68a7c6fb320bbb827), [03077d5](https://github.com/morluto/rea/commit/03077d54afc49613db4e38f70c506bc60f5223aa), [#6721](https://github.com/morluto/rea/pull/6721), [9fb8c11](https://github.com/morluto/rea/commit/9fb8c114a03ad60e57c11586967c6df845a3f97e), [f6a1e2c](https://github.com/morluto/rea/commit/f6a1e2c892dc23c7583c3d03af9edb01632ff5a5), [#6694](https://github.com/morluto/rea/issues/6694). Thanks @coygeek, @Deepak-Kambala.
|
||||
- **WASM and APK metadata:** Preserve ambiguous WASM identity and partial facts, glue URL query/fragment text across line terminators, APK signer identity, and raw issuer identity when comparison rules are unknown. [af8f0ba](https://github.com/morluto/rea/commit/af8f0badf64fd3546392b6aa4625777904b2f051), [#6802](https://github.com/morluto/rea/pull/6802), [f783f0f](https://github.com/morluto/rea/commit/f783f0f1e82472cdddd4de2156d974f729e332a6), [115a78f](https://github.com/morluto/rea/commit/115a78fe241276c34917b496621954c5f29bfc15), [#6639](https://github.com/morluto/rea/issues/6639). Thanks @coygeek, @gensokyo-dev, @gongfpp.
|
||||
- **Source identity:** Preserve Next route spelling, module grammar and loader request identity, directory package-main behavior, accessor effects, exact source targets, and resolver-discovery versus module-loading distinctions. [ddd7ac7](https://github.com/morluto/rea/commit/ddd7ac7b846b48191ead1f7dcd0e3bd735468fa8), [9974512](https://github.com/morluto/rea/commit/99745126009b15060d71c38809418858a07d5afa), [f2e5fd1](https://github.com/morluto/rea/commit/f2e5fd148a85fa737302dd194a33172c4561093e), [baf1678](https://github.com/morluto/rea/commit/baf16784af3592534687052431c145daef55cfd1), [6e324b2](https://github.com/morluto/rea/commit/6e324b224405f2befd953234f6dc210cf72ad50a), [75daf0c](https://github.com/morluto/rea/commit/75daf0c7df108f04ab41f34aec1a31d4a7ceb322), [f91b7f8](https://github.com/morluto/rea/commit/f91b7f8009c9af27a5e0148e3b07d20bfe3b7a2a).
|
||||
- **Graphs and selected facts:** Preserve JavaScript graph omissions/version facts, Electron native-loader facts, web JSON identity/coverage, module-path selection, feature-trace qualifications, selected native paths, endpoint context, and mixed Swift demangler outcomes. [30db499](https://github.com/morluto/rea/commit/30db499db310f073d9bf3d3acfd77985e1cb7e2e), [5a7bddb](https://github.com/morluto/rea/commit/5a7bddb3aeb0345854e76b79d529aed1c40e2054), [79805f2](https://github.com/morluto/rea/commit/79805f244a1ec0850f3109929168a9c8ebb1b908), [f719dc4](https://github.com/morluto/rea/commit/f719dc46c51a55b3926eec9659d06e7dfb95132b), [7784fdb](https://github.com/morluto/rea/commit/7784fdbad78f8b384d513f0e7dae8e983cd48ef8), [76500f1](https://github.com/morluto/rea/commit/76500f12b54e07b1ea700a403b2593890d63b42a), [676d29f](https://github.com/morluto/rea/commit/676d29f9eef794cea668f5f09f0ff510c848a513), [edf7b3d](https://github.com/morluto/rea/commit/edf7b3d32c3248f221e27f7ce5a3d34e8590285b), [93de7bc](https://github.com/morluto/rea/commit/93de7bc7e324a761162a31625b838ba0d4e3824d).
|
||||
- **Managed boundaries:** Honor native platform symbol lookup, consume resolved IL2CPP ELF declarations, and attribute malformed comparison inputs to the supplied argument. [64fa1db](https://github.com/morluto/rea/commit/64fa1db1309218283df19f3ee4dba61f5fbd2a67), [87745af](https://github.com/morluto/rea/commit/87745af52ab0ccd3a172c2534c516d41b0095035), [96ad94f](https://github.com/morluto/rea/commit/96ad94f69eda5d68c7335ff775b6927b1376fd2b).
|
||||
- **Provider and worker recovery:** Preserve output loss after successful command exit, physical refusal diagnostics, bounded worker control framing, JavaScript worker-phase/budget attribution, admitted ambiguity choices, and selected EVM limiter availability. Scope objdump debug-link limitations to DWARF requests and keep process-capture preparation nonexecuting. [e639edf](https://github.com/morluto/rea/commit/e639edf6204c05285d96840478926592daf95876), [171b33b](https://github.com/morluto/rea/commit/171b33b2c4defc6cbea49545bedf4272b72d1ea2), [34563fc](https://github.com/morluto/rea/commit/34563fcc9bd92af30668820dcac5635e27181094), [3f9357b](https://github.com/morluto/rea/commit/3f9357b5990d166a38440f56f439452266650093), [cec8f1e](https://github.com/morluto/rea/commit/cec8f1e8f305d6332272457a6ee419d3c5fb6359), [a74d859](https://github.com/morluto/rea/commit/a74d859a19122fb8f1282b3632d320997b1caae0), [c284168](https://github.com/morluto/rea/commit/c284168c4b1cd214ce7f504d43da28528d82d603), [4fb936f](https://github.com/morluto/rea/commit/4fb936fe86bb7a07f94f819ae8510301f2ef69c9).
|
||||
- **Setup and limits:** Retain unknown environment-budget facts, selected-skill/maintenance uncertainty, Hopper URL admission and bookmark verification, and listener failure recovery that matches the system cause. [a718037](https://github.com/morluto/rea/commit/a7180370332350bc0f998fbd643888221759570e), [d1fbebb](https://github.com/morluto/rea/commit/d1fbebb578538dc94fd5e376e6e1959218efa835), [60accad](https://github.com/morluto/rea/commit/60accad2de8f2862fe91901502fe26cc5390fb36), [fd7da77](https://github.com/morluto/rea/commit/fd7da77fc02dcfaf7d1bc9f2fb240d71cdd27c59), [0c440bf](https://github.com/morluto/rea/commit/0c440bfc07f3cb343e2e836d27cccaed539cfe9c).
|
||||
- **State and Evidence:** Retain export tarball ownership and SQLite primary outcomes through cleanup retry, count emitted SQLite schema names across encodings, scope display refresh to matching snapshot bindings, and report retained-result validation failures consistently. Reconstruction uses one effective completion scope and kind-based labels when names are blank. [ab58edc](https://github.com/morluto/rea/commit/ab58edcdf50b4bd687900ee2dc362ecc5c3dce15), [1006714](https://github.com/morluto/rea/commit/100671427105791a5c5913a918b63a394f6bc83e), [a47788a](https://github.com/morluto/rea/commit/a47788a6a4abb067273f954275a9d0a269adf1e7), [8aa40e9](https://github.com/morluto/rea/commit/8aa40e9a61155cff8b6f3c138c601bf805b20653), [b8c1b45](https://github.com/morluto/rea/commit/b8c1b45316882beeab4a9de49381d94e131fab0f), [a35b933](https://github.com/morluto/rea/commit/a35b9335a0124674ee5e3abe8c075f1fff6b4fa6), [#6137](https://github.com/morluto/rea/pull/6137), [#6126](https://github.com/morluto/rea/issues/6126). Thanks @carlogiovannineri-rgb, @mohithhhh.
|
||||
- **Capture coverage:** Name browser bundle budgets and deadline recovery, separate domain readiness from worker attribution, attribute rendered-frame omissions to their producing stage, share offline-capture host admission with discovery, preserve selected signal-file semantics, and retain short Flutter GNU build IDs. [#6036](https://github.com/morluto/rea/pull/6036), [ba78133](https://github.com/morluto/rea/commit/ba781330d6d28b22d15498e63c144fb40e086156), [5934387](https://github.com/morluto/rea/commit/5934387f4185129efe9dc8b7338d8bc3fa5b25bd), [ad629b2](https://github.com/morluto/rea/commit/ad629b2e31c3a9495b27179360b508fa021fe86a), [928ba76](https://github.com/morluto/rea/commit/928ba76882bfd216dde8ed79118789b2c3aad4f9), [678c4cf](https://github.com/morluto/rea/commit/678c4cfa68e2fee12f620075e12dc87d9b1195b0).
|
||||
- **Additional boundary facts:** Retain parser format/query occurrence identity and independent malformed-input facts, unresolved nested Swift identifiers, and completed signing-capture coverage; describe crash documents without assuming a provider. [5f702dd](https://github.com/morluto/rea/commit/5f702dd6ccd9e8e505e224854b91678439c3b41f), [99d2d7a](https://github.com/morluto/rea/commit/99d2d7ad22bcbd780919760008aba6a88e7d1077), [90e433d](https://github.com/morluto/rea/commit/90e433df06b3f83417176b8db1b0a905f9df1d3a), [#5548](https://github.com/morluto/rea/pull/5548), [ee12d51](https://github.com/morluto/rea/commit/ee12d517a25d34853d40e31fc039c7d1576c60e1), [#5538](https://github.com/morluto/rea/issues/5538). Thanks @coygeek, @ShuaixinHuang.
|
||||
- **Documentation:** Add the Romanian README. [#7026](https://github.com/morluto/rea/pull/7026). Thanks @An3treiu.
|
||||
|
||||
Full contribution record: [1740 commits and 497 merged PRs](https://github.com/morluto/rea/blob/rea-agents-6.8.0/docs/releases/6.8.0.contributions.json).
|
||||
|
||||
## [6.7.0](https://github.com/morluto/rea/compare/rea-agents-6.6.0...rea-agents-6.7.0) (2026-10-18)
|
||||
|
||||
### Highlights
|
||||
|
||||
+30
-4
@@ -25,6 +25,18 @@ acquiring targets; optional adapter failures must preserve successful peers. Run
|
||||
`npm run verify:test-discovery` after adding or moving tests. Resolved import
|
||||
boundaries are checked by `npm run verify:module-boundaries`, included in lint.
|
||||
|
||||
Before retiring an alias or facade, trace runtime registration, dynamic imports,
|
||||
JavaScript verifiers, subprocess scripts, and packaged entrypoints as well as
|
||||
TypeScript references. Tests may be the last consumers of a superseded internal
|
||||
path; migrate their distinct assertions to the current production path before
|
||||
removing it. Preserve validation, lazy loading, caller policy, and resource
|
||||
acquisition and cleanup supplied by the old boundary. Published APIs, persisted
|
||||
formats, and independently versioned bridges require an explicit compatibility
|
||||
decision; an internal representation alone does not establish that obligation.
|
||||
See [interpreting boundary facts once](docs/tool-design.md#interpret-boundary-facts-once)
|
||||
for admission ownership and the distinction between repeated validation and
|
||||
independent checks.
|
||||
|
||||
For analysis workflows and agent interfaces, follow the
|
||||
[performance priorities](AGENTS.md#performance). Explain the selected output
|
||||
scope, reuse/cache decisions, and relevant before/after measurements when these
|
||||
@@ -35,7 +47,9 @@ compare equivalent real tasks before claiming an improvement.
|
||||
|
||||
When adding or changing an MCP tool, follow the [tool design guide](docs/tool-design.md) and preserve the canonical contracts and generated catalog.
|
||||
When adding or changing an admission ceiling, budget, or deadline, follow
|
||||
[admission limits and recovery](docs/design/limits-and-recovery.md).
|
||||
[analysis settings, limits, and recovery](docs/design/limits-and-recovery.md)
|
||||
for ownership, operation snapshots, provider integration, and the steps for
|
||||
adding a runtime setting.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
@@ -89,7 +103,11 @@ Human ownership and review remain required when CI or an automated reviewer
|
||||
reports success. Readiness requests maintainer review; it does not grant scope
|
||||
approval or permission to merge.
|
||||
|
||||
Draft PRs may request early feedback with open questions and incomplete checks.
|
||||
Draft status is optional. Draft PRs may request early feedback with open questions
|
||||
and incomplete checks. Agents preserve the current draft status unless asked to
|
||||
change it; missing readiness declarations or later updates do not require a PR
|
||||
to become a draft.
|
||||
|
||||
Leave the readiness declaration unchecked until the human owner has completed
|
||||
it. The declaration records initial readiness; later pushes do not invalidate
|
||||
it or require another human sign-off. Keep the PR description and affected
|
||||
@@ -368,9 +386,17 @@ Real-provider execution is uncached.
|
||||
### Temporary files and diagnostics
|
||||
|
||||
Tests use `createTestTempDirectory` from `tests/fixtures/temporaryDirectory.ts`
|
||||
for exact-path, awaited cleanup, including failures and timeouts.
|
||||
for exact-path, awaited cleanup, including failures and timeouts. It registers
|
||||
deletion with Vitest's `onTestFinished`; do not also add its result to a roots
|
||||
array or an `afterEach` removal. `createTestWorkspace` from
|
||||
`tests/support/workspace/workspaceFixture.ts` leaves cleanup with its caller,
|
||||
which must await `removeTestWorkspace`. Keep explicit resource teardown before
|
||||
directory deletion, using the case's `finally` or `afterEach`; Vitest runs
|
||||
`afterEach` before `onTestFinished` and awaits finish hooks in reverse
|
||||
registration order.
|
||||
`npm run verify:test-temp-hygiene` runs the suite under a fresh `TMPDIR` and
|
||||
rejects remaining REA-owned paths. Never glob-clean shared `/tmp/rea-*` content.
|
||||
rejects remaining REA-owned paths. It cannot detect duplicate deletion owners or
|
||||
prove resource-close ordering. Never glob-clean shared `/tmp/rea-*` content.
|
||||
|
||||
`REA_LOG_LEVEL` selects `trace`, `debug`, `info`, `warn`, `error`, `fatal`, or
|
||||
`silent`. Logs go to stderr: MCP defaults to `info`, and one-shot CLI logging
|
||||
|
||||
@@ -3,6 +3,8 @@ import com.google.gson.*;
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.*;
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.*;
|
||||
import jadx.api.*;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.ICodeReader;
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.api.plugins.input.insns.InsnIndexType;
|
||||
@@ -40,7 +42,8 @@ class ReaJadxBridge {
|
||||
|
||||
private record MethodSnapshot(String name, String rawName, String shortId, List<String> bodyAbsenceMarkers,
|
||||
Map<String, Object> summary) {}
|
||||
private record ClassSnapshot(JavaClass node, List<MethodSnapshot> methods, Map<String, Object> summary) {}
|
||||
private record ClassSnapshot(JavaClass node, List<MethodSnapshot> methods, Map<String, Object> summary,
|
||||
Map<String, Object> summaryWithValues) {}
|
||||
private record ManifestSnapshot(String text, Map<String, Object> summary) {}
|
||||
|
||||
private static final class InputFailure extends IllegalArgumentException {
|
||||
@@ -142,6 +145,14 @@ class ReaJadxBridge {
|
||||
return value.getAsString();
|
||||
}
|
||||
|
||||
private static boolean flag(JsonObject args, String name) {
|
||||
JsonElement value = args.get(name);
|
||||
if (value == null) return false;
|
||||
if (!value.isJsonPrimitive() || !value.getAsJsonPrimitive().isBoolean())
|
||||
throw new IllegalArgumentException(name + " must be a boolean");
|
||||
return value.getAsBoolean();
|
||||
}
|
||||
|
||||
private static int integer(JsonObject args, String name, int fallback) {
|
||||
JsonElement value = args.get(name);
|
||||
if (value == null) return fallback;
|
||||
@@ -263,17 +274,38 @@ class ReaJadxBridge {
|
||||
"dex_descriptor", descriptor,
|
||||
"is_constructor", method.isConstructor(), "def_pos", 0));
|
||||
}).toList();
|
||||
List<Map<String, Object>> fields = node.getFields().stream()
|
||||
.sorted(Comparator.comparing(FieldNode::getAlias))
|
||||
.map(field -> Map.<String, Object>of("name", field.getAlias(),
|
||||
"type", field.getType().toString(), "def_pos", 0)).toList();
|
||||
List<FieldNode> orderedFields = new ArrayList<>(node.getFields());
|
||||
orderedFields.sort(Comparator.comparing(FieldNode::getAlias));
|
||||
List<Map<String, Object>> fields = new ArrayList<>();
|
||||
List<Map<String, Object>> valuedFields = new ArrayList<>();
|
||||
boolean anyValue = false;
|
||||
for (FieldNode field : orderedFields) {
|
||||
Map<String, Object> entry = Map.of("name", field.getAlias(),
|
||||
"type", field.getType().toString(), "def_pos", 0);
|
||||
fields.add(entry);
|
||||
Map<String, Object> value = dexStaticValue(field);
|
||||
if (value == null) valuedFields.add(entry);
|
||||
else {
|
||||
anyValue = true;
|
||||
Map<String, Object> valued = new LinkedHashMap<>(entry);
|
||||
valued.put("dex_static_value", value);
|
||||
valuedFields.add(Collections.unmodifiableMap(valued));
|
||||
}
|
||||
}
|
||||
List<String> inner = node.getInnerClasses().stream().map(ClassNode::getFullName).toList();
|
||||
Map<String, Object> summary = Map.of(
|
||||
"full_name", cls.getFullName(), "raw_name", cls.getRawName(), "name", cls.getName(),
|
||||
"method_count", methods.size(), "field_count", fields.size(),
|
||||
"inner_class_count", inner.size(), "inner_classes", inner,
|
||||
"methods", methods.stream().map(MethodSnapshot::summary).toList(), "fields", fields);
|
||||
ClassSnapshot snapshot = new ClassSnapshot(cls, methods, summary);
|
||||
"methods", methods.stream().map(MethodSnapshot::summary).toList(), "fields", List.copyOf(fields));
|
||||
// DEX static values are opt-in, so method and reference lookups keep the member-only reply.
|
||||
Map<String, Object> summaryWithValues = summary;
|
||||
if (anyValue) {
|
||||
Map<String, Object> copy = new LinkedHashMap<>(summary);
|
||||
copy.put("fields", List.copyOf(valuedFields));
|
||||
summaryWithValues = Collections.unmodifiableMap(copy);
|
||||
}
|
||||
ClassSnapshot snapshot = new ClassSnapshot(cls, methods, summary, summaryWithValues);
|
||||
index.computeIfAbsent(cls.getFullName(), ignored -> new ArrayList<>()).add(snapshot);
|
||||
// Keep the binary identity supplied by JADX; '$' can also be a literal
|
||||
// name character, so replacing separators would invent lookup aliases.
|
||||
@@ -293,6 +325,28 @@ class ReaJadxBridge {
|
||||
return candidates.get(0);
|
||||
}
|
||||
|
||||
/** A static final field's class-definition static value; a static initializer can still assign it. */
|
||||
private static Map<String, Object> dexStaticValue(FieldNode field) {
|
||||
if (!field.getAccessFlags().isStatic() || !field.getAccessFlags().isFinal()) return null;
|
||||
EncodedValue encoded = field.get(JadxAttrType.CONSTANT_VALUE);
|
||||
if (encoded == null) return null;
|
||||
Object value = encoded.getValue();
|
||||
// JADX also attaches the shared null to static finals without a value, so null is never reported.
|
||||
// Wide and floating values keep their Java text instead of a lossy JSON number.
|
||||
return switch (encoded.getType()) {
|
||||
case ENCODED_BOOLEAN -> Map.of("type", "boolean", "value", value);
|
||||
case ENCODED_BYTE -> Map.of("type", "byte", "value", value);
|
||||
case ENCODED_SHORT -> Map.of("type", "short", "value", value);
|
||||
case ENCODED_CHAR -> Map.of("type", "char", "value", (int) (Character) value);
|
||||
case ENCODED_INT -> Map.of("type", "int", "value", value);
|
||||
case ENCODED_LONG -> Map.of("type", "long", "value", value.toString());
|
||||
case ENCODED_FLOAT -> Map.of("type", "float", "value", value.toString());
|
||||
case ENCODED_DOUBLE -> Map.of("type", "double", "value", value.toString());
|
||||
case ENCODED_STRING -> Map.of("type", "string", "value", value);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private List<MethodSnapshot> methods(ClassSnapshot cls, JsonObject args) {
|
||||
String name = string(args, "method_name");
|
||||
List<MethodSnapshot> result = cls.methods().stream().filter(method -> method.name().equals(name)).toList();
|
||||
@@ -443,7 +497,8 @@ class ReaJadxBridge {
|
||||
"items", items));
|
||||
}
|
||||
ClassSnapshot cls = selected(args);
|
||||
if (name.equals("get_class_summary")) return json(cls.summary());
|
||||
if (name.equals("get_class_summary"))
|
||||
return json(flag(args, "dex_static_values") ? cls.summaryWithValues() : cls.summary());
|
||||
if (name.equals("get_method_body")) {
|
||||
List<MethodSnapshot> overloads = methods(cls, args);
|
||||
int index = integer(args, "overload_index", 0);
|
||||
@@ -526,7 +581,7 @@ class ReaJadxBridge {
|
||||
SessionHolder holder = new SessionHolder(defaults);
|
||||
ServerCapabilities capabilities = new ServerCapabilities(
|
||||
new ServerCapabilities.Tools(null), null, null, null, null, null, null, Map.of());
|
||||
Server server = new Server(new Implementation("rea-jadx-bridge", "6", null, null, null),
|
||||
Server server = new Server(new Implementation("rea-jadx-bridge", "7", null, null, null),
|
||||
new ServerOptions(capabilities), "", ignored -> Unit.INSTANCE);
|
||||
SessionToolsKt.registerSessionTools(server, holder);
|
||||
ClassToolsKt.registerClassTools(server, holder);
|
||||
@@ -568,9 +623,12 @@ class ReaJadxBridge {
|
||||
if (name.equals("list_classes")) properties.put("query",
|
||||
new kotlinx.serialization.json.JsonObject(Map.of(
|
||||
"type", kotlinx.serialization.json.JsonElementKt.JsonPrimitive("string"))));
|
||||
if (name.equals("get_class_summary")) properties.put("dex_static_values",
|
||||
new kotlinx.serialization.json.JsonObject(Map.of(
|
||||
"type", kotlinx.serialization.json.JsonElementKt.JsonPrimitive("boolean"))));
|
||||
String description = switch (name) {
|
||||
case "list_classes" -> "Pages of case-sensitive matching top-level class names; offset and next_offset are inventory positions, limit bounds matching rows. Omitted or empty query inventories all names.";
|
||||
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation.";
|
||||
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation. Set dex_static_values to include encoded static final field values.";
|
||||
case "get_method_body" -> "Source for an exact class and stable same-name overload index; static initializers use smali to preserve folded field initialization.";
|
||||
default -> "Complete static using-node inventory for an exact class or selected method overload; one node may contain multiple invoke instructions, and source lines are unresolved.";
|
||||
};
|
||||
|
||||
@@ -404,6 +404,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
writeSuccess(writer, request.id, result);
|
||||
return;
|
||||
}
|
||||
decompileTimeoutSeconds = request.decompileTimeoutSeconds() == null
|
||||
? descriptor.decompileTimeoutSeconds() : request.decompileTimeoutSeconds();
|
||||
writeSuccess(writer, request.id, handleRequest(request, descriptor));
|
||||
}
|
||||
catch (RequestFailure failure) {
|
||||
@@ -3381,7 +3383,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
"decompile_timed_out",
|
||||
"Ghidra decompilation of " + canonicalAddress(function.getEntryPoint()) +
|
||||
" exceeded the " + decompileTimeoutSeconds +
|
||||
"-second limit set by REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS"
|
||||
"-second configured decompilation limit (ghidra.decompile_timeout_seconds; launch seed REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS)"
|
||||
);
|
||||
}
|
||||
if (!results.decompileCompleted()) {
|
||||
@@ -4771,7 +4773,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
}
|
||||
|
||||
private static Request parseRequest(String line, String expectedToken) {
|
||||
JsonObject object = requireObject(JsonParser.parseString(line), REQUEST_KEYS);
|
||||
JsonElement parsed = JsonParser.parseString(line);
|
||||
Set<String> keys = new HashSet<>(REQUEST_KEYS);
|
||||
if (parsed.isJsonObject() && parsed.getAsJsonObject().has("decompile_timeout_seconds"))
|
||||
keys.add("decompile_timeout_seconds");
|
||||
JsonObject object = requireObject(parsed, keys);
|
||||
Integer timeout = null;
|
||||
if (object.has("decompile_timeout_seconds")) {
|
||||
JsonElement value = object.get("decompile_timeout_seconds");
|
||||
timeout = value.isJsonNull() ? 0 : requireInteger(object, "decompile_timeout_seconds");
|
||||
if (!value.isJsonNull() && (timeout < 1 || timeout > MAX_DECOMPILE_TIMEOUT_SECONDS))
|
||||
throw new IllegalArgumentException("Invalid per-request decompile deadline");
|
||||
}
|
||||
int id = requireInteger(object, "id");
|
||||
if (id <= 0 || !constantTimeEquals(requireString(object, "token"), expectedToken)) {
|
||||
throw new IllegalArgumentException("Bridge request authentication failed");
|
||||
@@ -4780,7 +4793,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
if (params == null || !params.isJsonObject()) {
|
||||
throw new IllegalArgumentException("Bridge request parameters are invalid");
|
||||
}
|
||||
return new Request(id, requireString(object, "method"), params.getAsJsonObject());
|
||||
return new Request(id, requireString(object, "method"), params.getAsJsonObject(), timeout);
|
||||
}
|
||||
|
||||
private static JsonObject requireObject(JsonElement element, Set<String> keys) {
|
||||
@@ -4974,7 +4987,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
JsonArray analysisExtensions,
|
||||
int decompileTimeoutSeconds
|
||||
) {}
|
||||
private record Request(int id, String method, JsonObject params) {}
|
||||
private record Request(int id, String method, JsonObject params, Integer decompileTimeoutSeconds) {}
|
||||
|
||||
private static final class RequestFailure extends RuntimeException {
|
||||
private final String code;
|
||||
|
||||
@@ -15,6 +15,11 @@ import sys
|
||||
PROFILE = "pwntools@4.15.0;pyelftools@0.33;unicorn@2.1.2"
|
||||
PACKAGES = {"pwntools": "4.15.0", "pyelftools": "0.33", "unicorn": "2.1.2"}
|
||||
OUTPUT_BYTES = 64 * 1024 * 1024
|
||||
# (e_machine, ELF class bits, byte order) -> architecture pwntools must also report.
|
||||
PROFILES = {(62, 64, "little"): "amd64", (3, 32, "little"): "i386", (40, 32, "little"): "arm",
|
||||
(8, 32, "little"): "mips", (8, 32, "big"): "mips"}
|
||||
PROFILE_SCOPE = ("Layout profile requires ELF64 x86-64, ELF32 i386, ELF32 ARM little-endian "
|
||||
"or ELF32 MIPS little- or big-endian objects.")
|
||||
|
||||
|
||||
class LayoutFailure(Exception):
|
||||
@@ -116,13 +121,16 @@ def inspect_elf(path, cache):
|
||||
raise LayoutFailure("format", "Selected object does not contain an ELF header.")
|
||||
if len(content) < 16:
|
||||
raise LayoutFailure("format", "Selected ELF identification is truncated.")
|
||||
if content[4] != 2 or content[5] != 1:
|
||||
raise LayoutFailure("unsupported-target", "Initial layout profile requires ELF64 little-endian objects.")
|
||||
if len(content) < 64:
|
||||
raise LayoutFailure("format", "Selected ELF64 header is truncated.")
|
||||
if int.from_bytes(content[18:20], "little") != 62:
|
||||
raise LayoutFailure("unsupported-target", "Initial layout profile requires x86-64 ELF objects.")
|
||||
if int.from_bytes(content[16:18], "little") not in (1, 2, 3):
|
||||
if content[4] not in (1, 2) or content[5] not in (1, 2):
|
||||
raise LayoutFailure("unsupported-target", "Selected ELF declares an unknown class or byte order.")
|
||||
bits, header_size = (32, 52) if content[4] == 1 else (64, 64)
|
||||
order = "little" if content[5] == 1 else "big"
|
||||
if len(content) < header_size:
|
||||
raise LayoutFailure("format", f"Selected ELF{bits} header is truncated.")
|
||||
profile = (int.from_bytes(content[18:20], order), bits, order)
|
||||
if profile not in PROFILES:
|
||||
raise LayoutFailure("unsupported-target", PROFILE_SCOPE)
|
||||
if int.from_bytes(content[16:18], order) not in (1, 2, 3):
|
||||
raise LayoutFailure("unsupported-target", "Selected ELF type is outside the EXEC/DYN/REL layout profile; recorded core analysis is separate.")
|
||||
from pwnlib.context import context
|
||||
with context.local(cache_dir=str(cache), log_level="warning"):
|
||||
@@ -185,8 +193,8 @@ def inspect_elf(path, cache):
|
||||
if type is None or section.header.sh_type == type)
|
||||
try:
|
||||
image = LayoutELF(str(path), checksec=False)
|
||||
if image.arch != "amd64" or image.bits != 64 or image.endian != "little":
|
||||
raise LayoutFailure("unsupported-target", "Initial layout profile requires x86-64 ELF64 little-endian objects.")
|
||||
if (image.arch, image.bits, image.endian) != (PROFILES[profile], bits, order):
|
||||
raise LayoutFailure("unsupported-target", PROFILE_SCOPE)
|
||||
if image.header.e_type not in ("ET_EXEC", "ET_DYN", "ET_REL"):
|
||||
raise LayoutFailure("unsupported-target", "Selected ELF type is outside the EXEC/DYN/REL layout profile; recorded core analysis is separate.")
|
||||
length = len(content)
|
||||
@@ -208,7 +216,7 @@ def inspect_elf(path, cache):
|
||||
})
|
||||
if h.sh_type in ("SHT_SYMTAB", "SHT_DYNSYM"):
|
||||
if h.sh_entsize < image.structs.Elf_Sym.sizeof():
|
||||
raise LayoutFailure("format", "ELF symbol entry is smaller than the decoded Elf64_Sym structure.")
|
||||
raise LayoutFailure("format", f"ELF symbol entry is smaller than the decoded Elf{bits}_Sym structure.")
|
||||
if h.sh_link >= len(all_sections) or all_sections[h.sh_link].header.sh_type != "SHT_STRTAB":
|
||||
raise LayoutFailure("format", f"ELF symbol table {index} links to invalid string table {h.sh_link}.")
|
||||
strings = all_sections[h.sh_link]
|
||||
|
||||
@@ -683,7 +683,12 @@ A view that names a field in `sorted_arrays` lists that string array sorted, as
|
||||
a read-back showed the application does, while the state keeps it in the order
|
||||
it was written, so a write's own answer echoes it. If a later counted read
|
||||
contradicts that ordering, the view stays recorded and its earlier reads no
|
||||
longer confirm served writes. Members a view names in
|
||||
longer confirm served writes. A view that names a field in `nested_fields`
|
||||
always showed that field's objects with the same keys, in any order, and fewer
|
||||
than an entity stores, such
|
||||
as a list showing a parent without the parent's own nested type; a stored
|
||||
object it never recorded, such as a created entity's parent, is rendered with
|
||||
those keys. Members a view names in
|
||||
`per_response_members`, such as a request id each response mints, keep the
|
||||
value its recording carried. A list's `counters` and `query.totals` are
|
||||
top-level members or members one level down in a paging object
|
||||
@@ -721,7 +726,7 @@ every MCP session and `data/get` then see:
|
||||
| Effect | Call |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `create` | Adds an entity to the collection the path names, built from the observed entity with each ruled field taken from the call or generated, at the plan's `insert` position. A plan tied to its exemplar's collection writes only there. |
|
||||
| `update` | Sets the sent members on the entity the path names, constants for members accepted only with their recorded value, and the clock, nested clock, increment, slug, stamp, derived, unique and renewed changes the plan models. |
|
||||
| `update` | Sets the sent members on the entity the path names, constants for members accepted only with their recorded value, and the clock, nested clock, increment, slug, stamp, derived, composed, reference, unique and renewed changes the plan models. |
|
||||
| `update` (action) | A `POST`, `PUT` or `PATCH` to an item path plus an action segment (`/{type}/{id}/archive`) sets, on the entity before the segment, each scalar the recorded calls showed the action set (`set`), with any sent members; it is served only when a recorded call found those fields already set, so a toggle never is. |
|
||||
| `delete` | Removes the entity the path names. |
|
||||
| bulk `update` or `delete` | A `DELETE`, `PUT` or `PATCH` at a collection, or a `POST` to it plus `delete`, `remove` or `destroy`, applies its effect to every entity the body names, as recorded: an array of ids or of objects carrying the id field, at the root or as the body's one array member (`{"IDs": […], "Read": true}`). |
|
||||
@@ -923,6 +928,9 @@ Known limits of served writes:
|
||||
updates require `updated_at` to match the entity (a `held-clock`).
|
||||
- A slug derived from a title can repeat across served creates with the same
|
||||
title, where the application would make it unique.
|
||||
- A `composed` string copies sent text as sent: an application that escapes or
|
||||
renders it (HTML entities, Markdown) differs for text that needs it, as
|
||||
BookStack's `description_html` would for a description holding `&`.
|
||||
- A served create's sequence id is the state's next value, not the gaps an
|
||||
application's own sequence would leave.
|
||||
- Nested clocks are served in the spelling of the write's own response, which
|
||||
@@ -1057,9 +1065,11 @@ Numeric arrays retain that containing field's clock context through nested
|
||||
arrays; an object's own fields establish their own context. Numeric values at
|
||||
the body root, in root arrays, or under measurement fields remain literal.
|
||||
|
||||
Generated strings normalize only where this confirmed, served write's entity
|
||||
response rules identify generation. A `unique` rule or UUID identity rule must
|
||||
still compare two UUIDs; a `renewed` rule applies only at its recorded pointers,
|
||||
Generated values normalize only where this confirmed, served write's entity
|
||||
response rules identify generation. A UUID identity rule must still compare two
|
||||
UUIDs, and a `unique` rule two UUIDs or two generated integers (10 to 20
|
||||
digits) of one JSON kind and length; a `renewed` rule applies only at its
|
||||
recorded pointers,
|
||||
with `*` matching actual array items. Token/sequence strings retain the existing
|
||||
format check: equal length of at least eight, the same character classes, and
|
||||
a digit. Numeric sequences continue to compare literally unless an ID mapping
|
||||
@@ -1071,6 +1081,12 @@ Admitted generated values must also preserve aliases and distinct identities
|
||||
within that response. UUID identity uses the generator's case-insensitive
|
||||
matching; token and sequence strings retain their literal identity. This
|
||||
correspondence ends with the answer and is not inferred across calls.
|
||||
A `composed` response field must spell the value its parts build from that
|
||||
response's own fields. Its parts then compare by their rendered text or their
|
||||
source fields' clock and ID rules. A coherent archive URL can therefore differ
|
||||
only in normalized clock digits without reporting a value difference; an
|
||||
incoherent URL still differs even when both sides repeat the same stale text.
|
||||
Normalized compositions count under `generated`; source fields are counted once.
|
||||
The report counts each kind of normalised
|
||||
value; the generation rules remain model-derived facts, rather than new
|
||||
observations of the application.
|
||||
|
||||
@@ -194,6 +194,21 @@ queries accept the same `overload_index`; the bridge resolves the selected
|
||||
method node and the result reports its index and displayed signature. These
|
||||
indices are tied to the current artifact and engine, not stable across builds.
|
||||
|
||||
`inspect_android_class` also reports `dex_static_value` for each `static final`
|
||||
field whose class definition encodes a value in its DEX static values, so
|
||||
compile-time constants are readable even when the class has no `<clinit>`.
|
||||
Each value is typed: `boolean`, `byte`, `short`, `char` (UTF-16 code unit) and
|
||||
`int` values are JSON booleans or numbers, while `long`, `float` and `double`
|
||||
values are strings holding Java's text for the value, so 64-bit values stay
|
||||
exact. The field reports `null` when no value is encoded, the encoded value is
|
||||
null or of another kind, or the field is not `static final`. The value is read from parsed
|
||||
metadata without generating source, and a static initializer can still assign
|
||||
the field; use `inspect_android_method` on `<clinit>` for that evidence.
|
||||
Only `inspect_android_class` asks the bridge for these values.
|
||||
`inspect_android_method` and `trace_android_references` read the member-only
|
||||
class summary, so large encoded strings cannot push their member lookup past
|
||||
the 8 MiB protocol frame limit.
|
||||
|
||||
Smali fallbacks select one complete block using the original method name and DEX
|
||||
descriptor, before applying the source byte limit. This preserves the selected
|
||||
overload even when Java code generation renames a method. If the selected Java
|
||||
@@ -400,11 +415,17 @@ export REA_JADX_OPERATION_TIMEOUT_MS=600000
|
||||
|
||||
The value must be a decimal millisecond count from 1 to 2147483647; anything
|
||||
else is refused before Java starts. REA reads it from the environment captured
|
||||
at process start. A change applies on the next process start; an already
|
||||
running server keeps the deadline it started with. The same deadline bounds
|
||||
each upstream call. A timeout names `REA_JADX_OPERATION_TIMEOUT_MS`, the
|
||||
effective milliseconds, and that range. A passing JADX configuration check in
|
||||
`rea doctor` reports the effective `operation_timeout_ms`. Explicit
|
||||
at process start. Changing that environment requires a new REA process. On a
|
||||
running MCP server, set `jadx.operation_timeout_ms` through
|
||||
`update_analysis_settings` to change the default for newly accepted operations;
|
||||
queued or running operations retain their accepted budget. The same deadline
|
||||
bounds each upstream call. Timeout recovery reports the effective milliseconds
|
||||
and range, with `details.limit` identifying the runtime setting. An invalid
|
||||
provider-local launch value can also be repaired by a runtime override; reset
|
||||
exposes that invalid launch value again. See the
|
||||
[settings design](design/limits-and-recovery.md#invalid-launch-configuration).
|
||||
A passing JADX configuration check in `rea doctor` reports its launch-derived
|
||||
`operation_timeout_ms`; it does not inspect another running server's overrides. Explicit
|
||||
cancellation still ends the operation immediately, and partial observations
|
||||
and owned-process cleanup are unchanged. Raising the deadline does not extend
|
||||
the bridge's 30-second startup handshake or the upstream **90-second**
|
||||
|
||||
+20
-2
@@ -29,7 +29,12 @@ body is parsed only when that string is `"309"`. Other types, including `"288"`
|
||||
stackshots, return no crash result.
|
||||
|
||||
`process`, `exception`, `termination`, `faulting_thread`, `threads`, and
|
||||
`images` reproduce documented body fields when present. Integers keep their
|
||||
`images` reproduce documented body fields when present, including each image's
|
||||
`name` and each frame's `symbolLocation` (as `symbol_location`, the byte offset
|
||||
from the start of `recorded_symbol`). `termination` keeps the terminating
|
||||
process as `by_proc` and `by_pid` and its `flags`; `process.parent_pid` keeps
|
||||
`parentPid` next to `parent`; and `metadata` keeps `build_version` and
|
||||
`platform`. Integers keep their
|
||||
decimal literals and a hexadecimal form, including arm64e registers above
|
||||
2^53. Frame roles follow the recorded frame index: row zero of the resolved
|
||||
faulting thread is `faulting_instruction`, row zero of other threads is
|
||||
@@ -38,9 +43,22 @@ address uses that row's own image base and offset. The recorded `inline` flag
|
||||
does not supply a missing offset or authorize borrowing another row's address.
|
||||
When `faultingThread` is missing or does not resolve to a recorded thread
|
||||
object, row-zero roles are `unknown`; the recorded lookup remains available.
|
||||
A translated process records Rosetta's registers in a nested
|
||||
`threadState.rosetta` object; each one is reported as `rosetta.<name>` and
|
||||
counts toward the register budget. A [public ReportCrash `.ips` log](https://github.com/Mortennn/Dozer/issues/189)
|
||||
shows `rosetta.tmp2`, `tmp1`, and `tmp0` beside ordinary x86 registers. The
|
||||
test fixture quotes that register excerpt inside an authored report body; it
|
||||
does not generate a new Rosetta crash during the test.
|
||||
Observed JSON strings retain `""`; `null` means the field was missing or JSON
|
||||
`null`. This includes image paths, frame symbols, process text, and the older
|
||||
string `osVersion` form. Unlisted JSON keys and generic register names also
|
||||
retain empty spellings. An empty image architecture remains recorded as `""`
|
||||
while `uuid_unknown` stays true, since that spelling does not establish an
|
||||
architecture. An empty UUID is malformed, so its exact spelling appears in
|
||||
`coverage.type_mismatches` and its canonical UUID remains unknown.
|
||||
`process.os_version` keeps the producer's dictionary (`train`, `build`,
|
||||
`release_type`, `is_embedded`) or the older string form. Image UUIDs are uppercase 8-4-4-4-12; an all-zero UUID
|
||||
or a missing `arch` is `uuid_unknown`. Image `source` letters keep their
|
||||
or a missing or empty `arch` is `uuid_unknown`. Image `source` letters keep their
|
||||
documented meaning (`P` process, `S` shared cache, `C` shared cache library,
|
||||
`K` kernel, `U` kernel cache, `T` kernel text exec, `A` absolute). Keys the
|
||||
documentation does not list remain in `unlisted_keys` or `unlisted_body_keys`.
|
||||
|
||||
@@ -23,7 +23,7 @@ graph TD
|
||||
SESSION["SessionProviderRouter + BinarySession<br/>one immutable deep binding per target<br/>disjoint auxiliary routing"]
|
||||
REGISTRY["AnalysisProviderRegistry<br/>sorted deep candidates<br/>deterministic selection, no fallback"]
|
||||
RECORDS["src/application/investigation/<br/>composed InvestigationRecords + narrow ports<br/>EvidenceLedger and Unknown ownership"]
|
||||
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots<br/>compatibility facade and post-commit notifications"]
|
||||
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots and session file ownership<br/>record coordination and post-commit notifications"]
|
||||
JS_RECON["src/application/javascript/ + src/artifacts/javascript/<br/>JavaScript artifact reconstruction<br/>directory/ASAR reads<br/>AST-only bundle projection"]
|
||||
end
|
||||
|
||||
@@ -75,7 +75,7 @@ graph TD
|
||||
end
|
||||
|
||||
subgraph "Domain Layer"
|
||||
DOMAIN["src/domain/<br/>Pure side-effect-free modules<br/>evidence, comparisons, application graphs"]
|
||||
DOMAIN["src/domain/<br/>Pure side-effect-free modules<br/>format declarations, Evidence, comparisons, application graphs"]
|
||||
end
|
||||
|
||||
subgraph "Configuration"
|
||||
|
||||
@@ -8,9 +8,12 @@ ordinary binary inspection use the same modular capability.
|
||||
|
||||
## Bring your own engine
|
||||
|
||||
Initial real verification covers **Linux x64, ELF64 x86-64 little-endian
|
||||
EXEC/DYN/REL**. Provide an absolute Python executable whose environment already
|
||||
contains unchanged pwntools 4.15.0, pyelftools 0.33 and Unicorn 2.1.2:
|
||||
Real verification covers **Linux x64** hosts and **EXEC/DYN/REL** objects in
|
||||
these profiles: ELF64 x86-64, ELF32 i386, ELF32 little-endian ARM and ELF32
|
||||
little- or big-endian MIPS. Other machines, classes and byte orders, including
|
||||
AArch64 and big-endian ARM, return `unsupported_target`. Provide an absolute
|
||||
Python executable whose environment already contains unchanged pwntools 4.15.0,
|
||||
pyelftools 0.33 and Unicorn 2.1.2:
|
||||
|
||||
```sh
|
||||
REA_PWNTOOLS_PYTHON=/absolute/isolated-env/bin/python \
|
||||
|
||||
@@ -434,6 +434,17 @@ complete Structured Field Item, including parameters such as `report-to`.
|
||||
These are declarations, not an assessment of the document's effective policy;
|
||||
absent, malformed or non-token items project as `null`.
|
||||
|
||||
Console entries, WebSocket frame summaries, and selected request/response JSON
|
||||
shape outputs share one 8 MiB retained-observation allowance per capture. Shape
|
||||
inference admits expanded path bytes before allocating those paths, including
|
||||
escaped and Unicode property names. Replacing or removing a request's shapes
|
||||
refunds their retained charges. A refused body shape preserves network metadata
|
||||
and any captured peer shape with `resource_budget_exhausted` coverage; a refused
|
||||
WebSocket frame is counted as dropped rather than reclassified as non-JSON text.
|
||||
The allowance has no runtime override. Protocol messages and JSON input parsing
|
||||
remain subject to their separate transport admission; this output allowance does
|
||||
not bound the whole process heap.
|
||||
|
||||
REA retains local observation data and removes only URL userinfo credentials, explicitly declared scenario secrets, and values from structured credential/storage fields:
|
||||
|
||||
- URL observations preserve the complete local URL, including query values, fragments, duplicate parameters, and their order. Only username/password userinfo is removed; malformed original URL strings remain available with `origin: null`.
|
||||
@@ -598,6 +609,22 @@ Network and console coverage starts only after REA attaches and enables the rele
|
||||
|
||||
Source maps are not fetched by inspection. Bundle analysis fetches them only when `fetch_source_maps` is selected, with exact-origin redirect checks and no credentials, cookies, or referrer. Each fetch operation uses `source_map_fetch_timeout_ms` (default 30000) and a 64 MiB aggregate streamed-response budget across redirects. The byte budget has no runtime override. A fetch overrun is an item limitation, not a thrown analysis timeout. A deadline or byte-limit failure remains explicitly unavailable and makes bundle completeness partial. Source-map decoding also bounds raw resolved identities to 64 MiB and aggregate inline evidence to 32 MiB. Failed maps preserve their failure context when it fits; if another context cannot fit, collection stops with the full requested count, the returned processed count, and an explicit unknown-results limitation. Retained mappings and original sources are returned inline. See [source-location limits](web-source-location.md) for the structural decoder bounds. Original source artifacts with `.ts`, `.tsx`, `.mts`, or `.cts` suffixes carry `text/typescript`; other source names retain `text/javascript`.
|
||||
|
||||
A fully read response that fails UTF-8, JSON, mapping, or structural validation
|
||||
retains `artifact: null`, empty original-source, module-edge, and mapping
|
||||
arrays, and an explicit failure limitation. Invalid content is `invalid`;
|
||||
decoder or structural capacity refusal is `fetch_failed`. Its optional
|
||||
`response_identity` records the complete body's SHA-256 digest, byte count,
|
||||
and `fetch-response-body-bytes` representation without retaining malformed
|
||||
text or charging that text to the 32 MiB output budget. These are bytes
|
||||
delivered by Fetch after HTTP content decoding and before UTF-8 decoding, not
|
||||
HTTP wire bytes. The identity is absent when the body was not fully read, and
|
||||
older observations may omit it. A response whose text cannot fit the existing
|
||||
output budget is likewise `fetch_failed` with text omitted and completed-body
|
||||
identity; JSON parsing and source-map decoding are not attempted. Successful and partial maps keep their
|
||||
full text artifacts. Their original text, including an initial BOM or XSSI
|
||||
line, remains distinct from the parsing view that removes that framing.
|
||||
Collection reads the body once and does not raise its response or output limits.
|
||||
|
||||
When fetching is selected, source-map `excluded` facts preserve the capture
|
||||
ledger's reasons and counts for declarations rejected before download. No
|
||||
disallowed URL details are retained or fetched. `requested` and `processed`
|
||||
@@ -605,6 +632,17 @@ count the admitted fetch requests; exclusions are counted separately. Excluded
|
||||
declarations make the collection unavailable, or partial when maps were retained.
|
||||
An empty collection with no declarations remains `not_requested`.
|
||||
|
||||
If the caller cancels while later source maps are being fetched, the failure's
|
||||
partial Evidence retains the captured page and every completed map item. Its
|
||||
`normalized_result.kind` is `web-bundle-acquisition` and
|
||||
`analysis_status` is `not_performed`: no script-analysis result is claimed.
|
||||
`source_maps.requested` remains the admitted request count,
|
||||
`source_maps.processed` counts completed items, and any unprocessed requests
|
||||
have an explicit unknown-results limitation. Capture exclusions remain in the
|
||||
same collection. The cancellation error keeps the caller's exact reason;
|
||||
an already observed response resource or cleanup failure keeps its own error
|
||||
category and cleanup owner.
|
||||
|
||||
Source-map items retain the script's `declared_url` separately from the observed
|
||||
`effective_url`. Successful maps use the latter as their source-resolution base.
|
||||
Failed fetches retain the last response URL observed within the approved origins;
|
||||
|
||||
+12
-3
@@ -465,7 +465,14 @@ an exact result when the target bytes, operation, parameters, provider and
|
||||
settings match. Mutations and cursor-dependent calls are excluded from the
|
||||
cache. Snapshot files are local and use owner-only permissions.
|
||||
|
||||
Importing an Evidence bundle refreshes retained subject display names and paths.
|
||||
One-shot binary CLI Evidence reports the exact path argument in `caller_input_path`,
|
||||
including relative spelling and symlink aliases. Its subject identifies the
|
||||
inspected artifact; `caller_input_path` does not change the Evidence ID or
|
||||
snapshot query. A replay reports the current invocation's path while leaving
|
||||
the saved Evidence unchanged. Provider inspections continue to include the
|
||||
established `parameters.input_path` field.
|
||||
|
||||
Importing an Evidence bundle refreshes retained request and subject display metadata.
|
||||
It preserves cached analysis results, including observations for other targets.
|
||||
Snapshot bindings that use a replaced record retain its updated display path.
|
||||
|
||||
@@ -917,8 +924,10 @@ redirected, which keeps large results smaller for agents and scripts; pipe
|
||||
through `jq .` for indented files. Output selection and formatting do not change
|
||||
operation status.
|
||||
|
||||
`--format md` escapes backslashes and pipes in table headers and cells, and
|
||||
renders cell line breaks as `<br>`. These are presentation changes; retained
|
||||
`--format md` escapes backslashes, pipes, and inline Markdown syntax in table
|
||||
headers and cells so source text is displayed literally, and renders cell line
|
||||
breaks as `<br>`. Underscores between letters or digits, as in `source_index`,
|
||||
cannot form emphasis and stay unescaped. These are presentation changes; retained
|
||||
Evidence and JSON output keep the original values. Nested objects and arrays
|
||||
use JSON inside their owning Markdown cells, retaining the field names and
|
||||
values instead of replacing them with an object label. Table widths are measured
|
||||
|
||||
@@ -6,6 +6,17 @@ for the reviewed setup transaction and [client IDs](installation.md#supported-ag
|
||||
The versioned verification notes below describe their stated runs; live service
|
||||
acceptance is verified only where explicitly recorded.
|
||||
|
||||
## Runtime settings and client deadlines
|
||||
|
||||
`update_analysis_settings` changes curated defaults inside the running REA
|
||||
server. It does not edit the client registration, extend the client's tool-call
|
||||
deadline, or recompute timeout values previously derived by `rea setup`. When
|
||||
raising a REA startup or operation deadline, separately ensure the client allows
|
||||
enough time for the full request. Use `inspect_analysis_settings` for the running
|
||||
server's defaults; `rea doctor` checks launch configuration and cannot observe
|
||||
another process's runtime overrides. See the
|
||||
[settings design](design/limits-and-recovery.md) for ownership and lifetime.
|
||||
|
||||
## Client tool names and discovery
|
||||
|
||||
REA advertises canonical names such as `binary_session`. The MCP host decides
|
||||
@@ -493,6 +504,28 @@ as stale, and rerunning setup removes the flag to repair it. Native configuratio
|
||||
uses strict JSON; setup refuses comments, trailing commas and a BOM rather than
|
||||
writing a registration ZCode cannot load.
|
||||
|
||||
## FreeBuff
|
||||
|
||||
For FreeBuff, the proposed integration writes `mcpServers.rea` into the user-scope
|
||||
`~/.agents/mcp.json` shared-agent file. Entries carry `command`, `args`, and
|
||||
`env`; stdio is the default transport. The proposed personal skill location is
|
||||
`~/.agents/skills`, so setup installs its skill copy there. Because the same
|
||||
file doubles as ZCode's user-scope fallback (see above), a `freebuff`
|
||||
registration is also in the file ZCode uses until ZCode defines its own native server.
|
||||
Setup and doctor detect FreeBuff from its own profile directory
|
||||
(`~/.config/freebuff-desktop`), not from the shared `.agents` folder that
|
||||
REA's skill installs create for every client.
|
||||
|
||||
The configuration write and personal skill locations are proposed integration
|
||||
boundaries. FreeBuff MCP loading and skill discovery have not been verified
|
||||
with an installed native FreeBuff client/version; filesystem tests cover REA's
|
||||
editing, discovery, and ownership behavior only. Automatic applicability
|
||||
requires the FreeBuff profile directory even when the shared MCP file exists.
|
||||
|
||||
FreeBuff is main-only and is not in a published release yet; npm 6.7.0 is the
|
||||
latest. `rea setup --client freebuff` against `@latest` reports an unsupported
|
||||
client until a release containing it is published.
|
||||
|
||||
## DeepSeek Harness
|
||||
|
||||
DeepSeek Harness registers MCP clients through a Cordis YAML patch sequence
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Client and agent verification
|
||||
|
||||
FreeBuff MCP loading and personal skill discovery are currently unverified with
|
||||
an installed native client/version. Its temporary-filesystem coverage verifies
|
||||
REA's configuration and applicability boundaries only; it does not establish
|
||||
that FreeBuff consumes the written registration or skill.
|
||||
|
||||
Use these optional lanes to verify an installed client or evaluate an agent's
|
||||
REA workflow. [Testing REA](testing.md) provides the normal development commands
|
||||
and provider lanes. Each record below states its host, client version, model
|
||||
|
||||
@@ -1,35 +1,121 @@
|
||||
# Admission limits and recovery
|
||||
# Analysis settings, limits, and recovery
|
||||
|
||||
How to add a REA-owned admission ceiling, budget, or deadline, and what a caller
|
||||
does when one is hit. This is a design rule, not an inventory. Ceilings that
|
||||
should become settings are tracked in
|
||||
This guide explains the runtime settings design for maintainers and developers:
|
||||
who owns each value, when an update takes effect, how providers enforce it, and
|
||||
how to extend the system. The public tool contract is in
|
||||
[MCP contracts](../mcp-contracts.md#editable-analysis-settings); recorded runtime
|
||||
checks and their limitations are in the
|
||||
[verification report](../verification/runtime-analysis-settings.md).
|
||||
|
||||
## Problem and design boundary
|
||||
|
||||
A valid workload can exceed a conservative default. An oversized ASAR header
|
||||
may prevent analysis of the entire archive; a slow Ghidra function may exceed
|
||||
its decompile deadline. Requiring a new REA process to change either budget
|
||||
interrupts the investigation and can discard reusable provider state.
|
||||
|
||||
REA exposes a curated set of analysis defaults through MCP so callers can
|
||||
adjust a reported limit and retry the same workload in the running runtime.
|
||||
Each setting has a typed value, resource bounds, an enforcement owner, and a
|
||||
defined application point. The design applies across archive admission,
|
||||
provider startup, operation deadlines, and worker memory requests.
|
||||
|
||||
The setter changes in-memory defaults. It does not edit arbitrary environment
|
||||
variables, configuration files, MCP registrations, or provider installations.
|
||||
Those mechanisms have different owners and lifetimes. It also cannot change a
|
||||
client deadline, extend accepted work, release retained resources, or remove a
|
||||
fixed representation limit. Exposing a setting requires a working enforcement
|
||||
path, not just an entry in the tool schema.
|
||||
|
||||
Further ceilings that should become settings are tracked in
|
||||
[#6176](https://github.com/morluto/rea/issues/6176). Naming honesty for limit
|
||||
errors is [#5910](https://github.com/morluto/rea/issues/5910).
|
||||
|
||||
## Ownership and data flow
|
||||
|
||||
| Owner | Responsibility |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Domain definitions](https://github.com/morluto/rea/blob/main/src/domain/analysisSettingDefinitions.ts) and [types](https://github.com/morluto/rea/blob/main/src/domain/analysisSettings.ts) | Exact setting IDs, value types, shared bounds/defaults, units, provider/operation associations, application points, and structured limit metadata. Numeric constants stay with their domain owner. |
|
||||
| [Launch parser](https://github.com/morluto/rea/blob/main/src/config/analysisSettings.ts) | Parse selected launch values once, preserving an invalid value as a typed configuration failure rather than substituting a default. |
|
||||
| [AnalysisSettings](https://github.com/morluto/rea/blob/main/src/application/AnalysisSettings.ts) | Own one runtime's launch baseline, immutable override revisions, atomic updates, inspection, and accepted-operation snapshots. It does not own provider lifecycle or cleanup. |
|
||||
| [Composition](https://github.com/morluto/rea/blob/main/src/main/eagerRuntime.ts) and [typed configuration views](https://github.com/morluto/rea/blob/main/src/config/runtimeAnalysisSettings.ts) | Inject the same settings reader into providers and workflows; translate overrides into the configuration each consumer understands. |
|
||||
| Provider or resource owner | Resolve the effective budget, enforce it at the applicable boundary, and attach the actual value and known source to failures. |
|
||||
| [MCP registration boundary](https://github.com/morluto/rea/blob/main/src/server/EvidenceMcpServerCore.ts) | Enter the operation's settings scope before invoking its handler. |
|
||||
| [Settings contracts](https://github.com/morluto/rea/blob/main/src/contracts/analysisSettingsContracts.ts) and [tool registration](https://github.com/morluto/rea/blob/main/src/server/registerAnalysisSettingsTools.ts) | Validate named requests and deliver inspection/update results without requiring an open target or healthy analysis provider. |
|
||||
|
||||
The normal path is: composition creates the store, an MCP handler captures its
|
||||
current revision, and providers read that snapshot when resolving budgets.
|
||||
If an owner rejects work, it reports the effective limit through the shared
|
||||
error projection. A subsequent settings update publishes a new revision for
|
||||
later handlers. It does not reach into the failed operation or retry it.
|
||||
|
||||
### Accepted-operation snapshots
|
||||
|
||||
`AnalysisSettings.run()` installs the current immutable snapshot in
|
||||
`AsyncLocalStorage`. Its `read` function returns that snapshot inside the
|
||||
operation and the latest revision outside it. Providers depend on the reader
|
||||
contract rather than on the mutable store or an ambient environment lookup.
|
||||
|
||||
Acceptance here is entry into the registered handler's settings scope, not
|
||||
arrival of bytes at the MCP transport. Work queued or awaited within that scope
|
||||
keeps its snapshot. Deferred server initialization must still dispatch through
|
||||
the canonical scoped handler.
|
||||
|
||||
| Event | Revision used |
|
||||
| --------------------------------------------------------- | ----------------------------------- |
|
||||
| Operation A enters its handler, then waits for a provider | Current revision 0 |
|
||||
| A settings update publishes revision 1 | Future handlers can read revision 1 |
|
||||
| Operation A resumes and starts its worker | Its accepted revision 0 |
|
||||
| Operation B enters its handler | Revision 1 |
|
||||
|
||||
This gives each operation stable budget semantics even if another connection
|
||||
updates settings while it waits. `next_worker` and `next_startup` describe where
|
||||
an accepted value is applied; they do not cause workers or startups belonging
|
||||
to an older operation to adopt a newer revision.
|
||||
|
||||
Inspection deliberately reads the store's current defaults, rather than the
|
||||
calling operation's snapshot. It reports `active_values.status: "not_observed"`:
|
||||
an inspection result cannot establish the budget or remaining time of an
|
||||
already-running worker.
|
||||
|
||||
### Composition and embedding
|
||||
|
||||
The MCP runtime creates one store and supplies its reader to provider factories.
|
||||
`createServer` uses a supplied store or creates one, and wires its default
|
||||
providers to that store. An embedder supplying a custom binary session or other
|
||||
provider must also inject the same reader into that provider's composition and
|
||||
pass the store to `createServer`. Merely exposing the settings tools cannot
|
||||
make a custom provider consume updates.
|
||||
|
||||
Keep default factories, explicitly supplied providers, transport composition,
|
||||
and deferred handler dispatch covered separately where their wiring differs.
|
||||
Resolve settings at the operation boundary; copying a runtime value into a
|
||||
provider constructor would freeze it at launch. For entrypoints outside the
|
||||
MCP wrapper, an embedder needing the same snapshot guarantee must establish
|
||||
the operation scope with `AnalysisSettings.run()`.
|
||||
|
||||
## Name a default, then a setting when operators need headroom
|
||||
|
||||
Put the number in a named constant with a unit. Do not leave it as a magic
|
||||
number at the call site.
|
||||
|
||||
When a workload may need more room, add a setting whose unset value is that
|
||||
constant. One module under
|
||||
[`src/config/`](https://github.com/morluto/rea/blob/main/src/config) owns the
|
||||
default, any floor, the maximum, the schema, the parser, and the recovery
|
||||
text. Register that schema on `environmentSchema` in
|
||||
[`environment.ts`](https://github.com/morluto/rea/blob/main/src/config/environment.ts).
|
||||
`parseConfig` calls `parseEnvironment`. Doctor uses that same parser through
|
||||
`nativeAnalysisConfigurationError`, so startup and `rea doctor` accept the
|
||||
same values. Add the name to `SELECTED_REGISTRATION_SETTINGS` so `rea setup`
|
||||
copies it into the client registration. Unset keeps today's behavior. An
|
||||
invalid value is a `ConfigurationError` and projects as `configuration_invalid`.
|
||||
Do not fall back to the default.
|
||||
constant. Pure numeric contracts live in `src/domain/analysisResourceLimits.ts`
|
||||
or their existing domain owner. `analysisSettingDefinitions.ts` binds the
|
||||
curated runtime IDs to those same bounds, defaults and application points.
|
||||
Environment parsers and recovery text stay under `src/config/`; register launch
|
||||
schemas on `environmentSchema` so startup and doctor agree. Add launch settings
|
||||
to `SELECTED_REGISTRATION_SETTINGS` when setup should forward them. Unset keeps
|
||||
today's behavior. An invalid launch value remains a configuration failure,
|
||||
never a silent fallback; its validation boundary determines whether MCP can
|
||||
start (see [invalid launch configuration](#invalid-launch-configuration)). An
|
||||
invalid runtime write is `invalid_request` and changes nothing.
|
||||
|
||||
[`REA_REGEX_MATCHING_DEADLINE_MS`](https://github.com/morluto/rea/blob/main/src/config/regexMatchingDeadline.ts)
|
||||
([#6267](https://github.com/morluto/rea/pull/6267)) is the reference. Default
|
||||
5000 ms, floor 1 ms, maximum 2147483647 ms, because Node schedules a longer
|
||||
`setTimeout` delay as 1 ms. Setup copies the setting. A hit says:
|
||||
|
||||
> The effective REA_REGEX_MATCHING_DEADLINE_MS value is 5000 ms (source default; default 5000 ms, floor 1 ms, maximum 2147483647 ms). Set REA_REGEX_MATCHING_DEADLINE_MS in the environment that starts REA to a positive decimal integer from 1 to 2147483647, then restart REA. The worker old-generation heap does not raise this deadline.
|
||||
For example, `regex.matching_deadline_ms` and its launch seed
|
||||
`REA_REGEX_MATCHING_DEADLINE_MS` share a 5000 ms default, 1 ms floor and
|
||||
2147483647 ms maximum. Node schedules a longer `setTimeout` delay as 1 ms.
|
||||
Changing the runtime setting affects the next accepted search; changing the
|
||||
launch environment affects the next REA process.
|
||||
|
||||
[`REA_MCP_MAX_RESPONSE_BYTES`](https://github.com/morluto/rea/blob/main/src/config/mcpResponseBudget.ts)
|
||||
is the byte-budget shape: a decimal safe integer with a 10 MiB floor
|
||||
@@ -39,7 +125,8 @@ raise REA's reserved string limit or Node's string ceiling. The setup
|
||||
allowlist does not copy this setting. A new headroom setting should, following
|
||||
the regex deadline.
|
||||
|
||||
A per-call budget is not an environment setting. JavaScript `max_heap_mb`
|
||||
A per-call budget can have a runtime default without having an environment
|
||||
setting. JavaScript `max_heap_mb`
|
||||
(CLI `--max-heap-mb`) is an integer from 128 to 16384 MiB, default 1024. It
|
||||
applies on the next analysis, which starts a new worker, and does not restart
|
||||
REA. It does not raise the parent result budget, transfer ceilings, or fixed
|
||||
@@ -51,8 +138,122 @@ does: its recovery says to set `NODE_OPTIONS` (for example
|
||||
`--max-old-space-size=<MiB>`) on the process that starts REA, then restart
|
||||
that process. `max_heap_mb` does not raise it.
|
||||
|
||||
## Runtime settings through MCP
|
||||
|
||||
`inspect_analysis_settings` lists the curated settings, their current and launch
|
||||
values, source, bounds, application point and relevant operation names. Filters
|
||||
by exact setting IDs, provider or operation intersect. Active worker values are explicitly `not_observed`; a configured default is
|
||||
not a measurement of active work. Inspection also separates known server limits from unknown client deadlines and receive buffers.
|
||||
|
||||
`update_analysis_settings` accepts an atomic `changes` array. Each entry names
|
||||
an exact `setting` and either `action: "set"` with a schema-validated `value`, or
|
||||
`action: "reset"`. Reset restores the launch value. `expected_revision` is an
|
||||
optional compare-and-set guard; conflicts and duplicate keys change nothing.
|
||||
An unchanged write does not advance the revision.
|
||||
|
||||
The revision tracks override state, not only effective numeric values. Setting
|
||||
an explicit override equal to the launch value still changes its source to
|
||||
`runtime` and advances the revision. Reset removes that override, even if the
|
||||
effective number stays the same. Validation, revision checks, and duplicate-key
|
||||
checks all happen before publishing the replacement snapshot; a rejected batch
|
||||
cannot partially apply. Clients coordinating writes can supply
|
||||
`expected_revision`; without it, valid updates apply to the current state.
|
||||
|
||||
| Runtime setting | Application point |
|
||||
| ------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `asar.max_header_bytes` | Next archive operation, including full JavaScript application analysis |
|
||||
| `ghidra.startup_timeout_ms`, `hopper.startup_timeout_ms` | Next provider startup attempt |
|
||||
| `ghidra.decompile_timeout_seconds` | Next operation on the existing Ghidra Program; `null` means unbounded |
|
||||
| `jadx.operation_timeout_ms` | Next JADX operation |
|
||||
| `regex.matching_deadline_ms`, `regex.worker_heap_request_mib` | Next search / new regex worker |
|
||||
| `capture.decoder_old_generation_mib` | Next owned capture decoder |
|
||||
| `javascript.analysis_timeout_ms`, `javascript.max_heap_mb` | Next source analysis / new JavaScript worker |
|
||||
| `wasm.command_timeout_ms` | Next WABT operation |
|
||||
|
||||
### Precedence and lifetime
|
||||
|
||||
Where a setting supports an explicit per-call argument, precedence is:
|
||||
|
||||
1. The caller's explicit argument.
|
||||
2. The accepted operation's runtime override.
|
||||
3. Valid launch configuration, where that setting has an environment seed.
|
||||
4. The built-in default when launch configuration is absent.
|
||||
|
||||
An invalid launch value does not fall through to the built-in default. Preserve
|
||||
omitted caller arguments in Evidence: resolving an internal default must not
|
||||
rewrite the request as though the caller supplied it. `null` is the explicit
|
||||
unbounded value for `ghidra.decompile_timeout_seconds`; it is not a substitute
|
||||
for an unknown or omitted value elsewhere.
|
||||
|
||||
A write does not extend active work, relaunch a provider, clear retained
|
||||
cleanup, or invalidate successful Evidence.
|
||||
|
||||
Overrides belong to one REA runtime, survive target close/open, and disappear
|
||||
when REA exits. Connections sharing that runtime share its overrides. No files,
|
||||
ambient environment, registrations or upstream software are edited. Overrides
|
||||
are runtime-wide, not scoped to a target or an individual connection.
|
||||
|
||||
### Failure and retry
|
||||
|
||||
An ASAR admission error supplies `details.limit` with the exact setting,
|
||||
effective value, units, bounds, scope, application point, restart requirement
|
||||
and, when known, required bytes and value source. The agent can immediately call:
|
||||
|
||||
```json
|
||||
{
|
||||
"changes": [
|
||||
{ "setting": "asar.max_header_bytes", "action": "set", "value": 33554432 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then retry the same full-archive request once work and cleanup have settled.
|
||||
No preliminary inspection or source subset is required. The setter does not
|
||||
retry automatically. If an explicit per-call value caused the failure, correct
|
||||
that argument; raising a runtime default cannot override it.
|
||||
|
||||
The resource owner constructs `details.limit` from the registry plus the budget
|
||||
it actually enforced. Metadata includes `agent_editable: true`,
|
||||
`scope: "rea-runtime"`, and `rea_restart_required: false`. Optional
|
||||
`required_value`, `source`, and `parameter` fields are supplied only where known
|
||||
or applicable. Do not infer a source because a value happens to equal a default.
|
||||
Error copying, attribution, redaction, and delivery must preserve these facts.
|
||||
Human-readable remediation explains the same failure; clients should not need
|
||||
to parse that sentence to identify an editable setting.
|
||||
|
||||
Cache policy is part of enforcement. Successful reusable analysis can survive
|
||||
a budget update. Transient Ghidra decompile failures remain uncached so a raised
|
||||
deadline can recover on the same Program. Admission checks still apply to reused
|
||||
data: lowering an ASAR limit must reject a cached oversized header. Neither
|
||||
raising a limit nor clearing a transient analysis failure authorizes skipping
|
||||
cleanup or changing the identity of the provider session.
|
||||
|
||||
### Invalid launch configuration
|
||||
|
||||
Launch validation has two relevant boundaries. Settings validated by global
|
||||
configuration parsing can prevent REA from starting; an MCP setter cannot
|
||||
repair a server that is not running. Correct the launch environment or
|
||||
registration, use `rea doctor` for its configuration checks, and start REA again.
|
||||
|
||||
Provider-local validation can leave MCP available. For example, an invalid
|
||||
`REA_JADX_OPERATION_TIMEOUT_MS` is inspectable with `source: "invalid"`, a
|
||||
`configuration_error`, and omitted `value` and `launch_value`. A valid runtime
|
||||
override repairs the effective JADX timeout without editing that launch seed.
|
||||
Inspection then reports the runtime value while retaining the launch error.
|
||||
Reset exposes the invalid launch state again; it does not repair or replace it
|
||||
with the built-in default. Provider availability and execution must resolve the
|
||||
same effective setting so readiness changes along with repair and reset.
|
||||
|
||||
## Keep a fixed ceiling honest
|
||||
|
||||
Inspection's separate
|
||||
[constraints](https://github.com/morluto/rea/blob/main/src/server/analysisSettingsConstraints.ts)
|
||||
describe operator-owned, external, and fixed controls. REA can report its
|
||||
configured response budget and parent heap limit; client deadlines and receive
|
||||
buffers remain explicitly unknown. These are not entries accepted by the
|
||||
runtime setter. Raising a worker heap request cannot raise the parent heap,
|
||||
and raising a provider deadline cannot make the client wait longer.
|
||||
|
||||
If the ceiling must stay fixed, say so in the failure. Name the limit and the
|
||||
effective value, say this release has no runtime override, and name what does
|
||||
not raise it. Do not invent a setting.
|
||||
@@ -68,20 +269,22 @@ The string the caller sees includes:
|
||||
|
||||
- the limit or setting name
|
||||
- the effective value and unit
|
||||
- the source (`default` or `setting`)
|
||||
- the source when known (`default`, `setting`, `runtime`, or `request`)
|
||||
- the default, the maximum, and the floor when the setting has one
|
||||
- the next action, including where to set it ("in the environment that starts REA")
|
||||
- whether a restart is required
|
||||
- what the change does not raise
|
||||
|
||||
Invalid settings never take this path. They are `configuration_invalid`.
|
||||
Doctor reports `runtime-configuration:<setting>` and says to correct it in the
|
||||
shell or the MCP registration, then rerun.
|
||||
Distinguish an invalid configuration from a valid budget exhausted by work.
|
||||
Preserve the configuration failure and its setting identity; apply the
|
||||
[launch validation boundary](#invalid-launch-configuration) when describing
|
||||
whether live repair is possible.
|
||||
|
||||
## What agents do
|
||||
|
||||
Follow `remediation.action`. Change a request field or a `REA_*` setting only
|
||||
when that text recommends the change. Restart only when it says the value is
|
||||
Use `details.limit` for a direct runtime update when it names an editable
|
||||
setting. Follow `remediation.action` for per-call, fixed and external controls.
|
||||
Inspect settings only when bounds or application details are missing. Restart only when it says the value is
|
||||
read at startup. A sentence that a control does not raise the limit is not a
|
||||
request to change it.
|
||||
|
||||
@@ -103,22 +306,72 @@ request to change it.
|
||||
- `resource_constraint` for capacity, when `retryable` is true: wait for the
|
||||
active admission to finish, then retry within the reported limits. Do not
|
||||
restart REA to free the slot.
|
||||
- `configuration_invalid`: correct the named setting in the environment that
|
||||
starts REA, then start REA again. Run `rea doctor` for the
|
||||
- `configuration_invalid`: when MCP remains available and the failure belongs
|
||||
to a curated provider-local setting, inspect and set a valid runtime override.
|
||||
Otherwise correct the named setting in the environment that starts REA, then
|
||||
start REA again. Run `rea doctor` for the
|
||||
`runtime-configuration:` check. Re-run `rea setup` for that client when the
|
||||
setting is on the registration allowlist, then reconnect so the new process
|
||||
sees it.
|
||||
- `invalid_request`: correct the reported fields from the current schema or
|
||||
CLI help. Do not repeat the unchanged call.
|
||||
|
||||
A restart is required for an environment setting and for a client still
|
||||
running the previous registration. It is not the first step for a transient
|
||||
A restart is required to adopt a changed launch environment or registration.
|
||||
The curated runtime settings above require no REA restart. It is not the first step for a transient
|
||||
MCP drop, a capacity wait, or retained cleanup.
|
||||
|
||||
## Tests
|
||||
## Adding a runtime setting
|
||||
|
||||
Lock the unset default, one accepted override, rejected floor, maximum, and
|
||||
syntax, and the recovery sentence through `parseEnvironment` and `parseConfig`.
|
||||
1. Identify the enforcing owner and the real resource constraint. Decide whether
|
||||
callers need a per-call argument, a runtime default, a launch-only control,
|
||||
or a fixed limit. A registry entry must not advertise authority REA lacks.
|
||||
2. Define the value type and named numeric bounds with the domain owner. Add the
|
||||
ID and metadata to `analysisSettings.ts` and `analysisSettingDefinitions.ts`.
|
||||
Reuse those bounds in runtime validation, launch parsing, and error metadata.
|
||||
Choose the application point and provider/operation associations from actual
|
||||
consumers; a shared control need not apply to every provider.
|
||||
3. If there is a launch seed, add its parser and appropriate configuration/doctor
|
||||
validation. Decide whether an invalid value blocks the runtime or only the
|
||||
provider. Add it to setup's `SELECTED_REGISTRATION_SETTINGS` when it should
|
||||
be forwarded; runtime editability does not automatically propagate it.
|
||||
4. Thread the shared reader through every applicable composition path. Resolve
|
||||
the effective value within the accepted operation, honor explicit arguments,
|
||||
and preserve their omission in Evidence. Keep availability checks consistent
|
||||
with execution. A worker heap request must not be described as a measured
|
||||
process memory limit.
|
||||
5. Enforce the value at its resource boundary and construct structured limit
|
||||
facts there. Preserve those facts through shared error projection and copies.
|
||||
Check cache reuse, lowered admission, transient failures, and retained cleanup
|
||||
against the same ownership rules.
|
||||
6. Update the public contracts and affected guides, then regenerate catalogs
|
||||
and packaged skill metadata using the repository commands. Keep externally
|
||||
owned and fixed controls in the separate inspection constraints, not in the
|
||||
editable registry. Validate both store semantics and a public recovery path.
|
||||
|
||||
## Tests and verification
|
||||
|
||||
Cover the unset default, accepted overrides, rejected floor/maximum values,
|
||||
and invalid syntax through the owning parser and configuration boundary.
|
||||
[`regexMatchingDeadline.test.ts`](https://github.com/morluto/rea/blob/main/src/config/regexMatchingDeadline.test.ts)
|
||||
is the pattern. Project the failure and assert `configuration_invalid` for a
|
||||
bad value and the recovery wording for a hit.
|
||||
is one example. Assert error codes, setting IDs, values, units, provenance,
|
||||
application points, and resulting behavior. Check that adapters deliver the
|
||||
canonical recovery guidance, with independent assertions for its underlying
|
||||
facts. Avoid freezing prose or deriving all expected values from the same
|
||||
registry used by the implementation; either can hide the wrong failure.
|
||||
See [test design](../testing.md#test-design-and-pruning) for the assertion policy.
|
||||
|
||||
Runtime checks cover atomic validation, stale writes, reset, runtime isolation,
|
||||
accepted-operation snapshots, and explicit argument precedence. Public MCP
|
||||
acceptance must reject an oversized ASAR, raise admission, and successfully
|
||||
retry the same complete archive on the same connection. Lowering admission must
|
||||
also refuse previously cached headers. The real Ghidra timeout verifier checks
|
||||
recovery after a live update without replacing its session. Also cover invalid
|
||||
provider-local launch configuration, repair/reset availability, default and
|
||||
custom composition paths, and preservation of structured limits through error
|
||||
copies. Gates used to overlap work and updates need bounded acknowledgements
|
||||
and release/join in `finally`.
|
||||
|
||||
The [verification report](../verification/runtime-analysis-settings.md) separates
|
||||
public MCP checks, simulated provider coverage, real Ghidra execution, and
|
||||
measured task costs. It does not establish real-provider behavior for every
|
||||
editable setting or active worker telemetry.
|
||||
@@ -305,8 +305,13 @@ capture provides them.
|
||||
|
||||
ASAR observation reads the header through one open file handle and validates its
|
||||
declared lengths against the archive size. Header/index admission is the same
|
||||
budget used by static ASAR reads: 16 MiB / 16777216 bytes by default, or
|
||||
`REA_ASAR_MAX_HEADER_BYTES` (at most 256 MiB) when set. An oversized header is
|
||||
budget used by static ASAR reads: 16 MiB / 16777216 bytes by default, seeded by
|
||||
`REA_ASAR_MAX_HEADER_BYTES` (at most 256 MiB). In MCP,
|
||||
`update_analysis_settings` can override `asar.max_header_bytes` for new
|
||||
operations without restarting REA. Cached headers are checked against each
|
||||
operation's admission, including after a decrease. See the
|
||||
[settings design](design/limits-and-recovery.md#failure-and-retry).
|
||||
An oversized header is
|
||||
not parsed; selecting a member path does not bypass admission, and the page
|
||||
stays unauthorized. The header cache separately retains at most that many bytes
|
||||
of already-admitted encoded headers across eight archives. Evicting that cache does not raise admission.
|
||||
+4
-2
@@ -24,8 +24,8 @@ requires an absolute filesystem path and preserves that exact selection:
|
||||
|
||||
The inline Evidence result contains the artifact path, SHA-256, and byte length.
|
||||
`tables` lists each declared note program header or section, including the
|
||||
section name when a string table is present. Zlib-compressed section-name
|
||||
tables are decompressed within the selected file-size budget. An unsupported
|
||||
section name when a string table is present. Zlib- and Zstandard-compressed
|
||||
section-name tables are decompressed within the selected file-size budget. An unsupported
|
||||
compression type leaves section names unknown and adds a limitation; note bytes
|
||||
remain independently examined. Each `entries[]` item reports owner, type,
|
||||
descriptor SHA-256, and a hex preview for descriptors of 64 bytes or less.
|
||||
@@ -63,6 +63,8 @@ numbering (`PN_XNUM`, including a section count stored when `e_shnum` is 0) is
|
||||
unsupported and returns an unsupported-target outcome rather than invalid-input
|
||||
recovery.
|
||||
|
||||
Zstandard section-name tables also use a 128 MiB decoder window limit.
|
||||
|
||||
`SHF_COMPRESSED` note sections return `unsupported_target` before their payload
|
||||
is interpreted or decompressed. Their compression header is storage metadata,
|
||||
not a note header. The current result requires physical file offsets for each
|
||||
|
||||
@@ -15,7 +15,7 @@ snapshot parser and a separate optional annotation extension. Check the
|
||||
[release boundary](installation.md#released-package-and-main) when using npm.
|
||||
|
||||
See [import language and analysis seeds](installation.md#ghidra-import-language-and-analysis-seeds)
|
||||
to override the header-detected language or seed analysis. An override that conflicts with a fixed raw, DOS, PSP, MIPS or RISC-V import
|
||||
to override the header-detected language or seed analysis. An override that conflicts with a fixed raw, DOS, PSP, MIPS, RISC-V or GameCube DOL import
|
||||
is a configuration error naming the conflicting settings. It does not make an
|
||||
otherwise admitted target unsupported; REA preserves the requested settings.
|
||||
An inaccessible language
|
||||
@@ -187,7 +187,7 @@ profile stages before retrying their retained descriptors.
|
||||
|
||||
| Concern | Ghidra contract |
|
||||
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Decompiler lifetime | One persistent `DecompInterface` is opened for the imported Program and disposed during bridge shutdown. Native decompilation has no fixed per-function deadline unless `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` sets one; a decompile that reaches it fails only that request with `remote_code` `decompile_timed_out`, the Program and session stay open, and the next decompile uses a fresh decompiler process. External functions or functions without bodies return `null`; cancellation and native failure remain distinct. |
|
||||
| Decompiler lifetime | One persistent `DecompInterface` is opened for the imported Program and disposed during bridge shutdown. Native decompilation has no fixed per-function deadline unless the launch setting `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` or runtime override `ghidra.decompile_timeout_seconds` sets one; a decompile that reaches it fails only that request with `remote_code` `decompile_timed_out`, the Program and session stay open, and the next decompile uses a fresh decompiler process. External functions or functions without bodies return `null`; cancellation and native failure remain distinct. |
|
||||
| Serialization | A FIFO sends one Program request at a time. Caller cancellation removes queued work promptly and is passed to active socket waits. This is an adapter safety commitment, not a claim that every Ghidra API is thread-safe. |
|
||||
| Function identity | Every function result carries the entry address and Ghidra FunctionManager classification for external, thunk, and resolved thunk target. These are observations, not proof that unresolved targetless calls have been recovered. |
|
||||
| Assembly and pseudocode | Assembly is complete Ghidra Listing text; pseudocode is Ghidra decompiler output. Neither is original source, and cross-provider comparison never treats Hopper and Ghidra text as equal or unequal semantic facts. |
|
||||
@@ -197,6 +197,13 @@ profile stages before retrying their retained descriptors.
|
||||
| P-code value flow | Function dossiers include up to 3,000 high-p-code operations, at most 64 inputs per operation, and 12,000 def-use edges. Results identify memory reads/writes, branches, and calls by p-code opcode, and mark decompiler-dead operations. Large results report truncation, an omitted-operation lower bound, and known omitted-input/edge counts. The dossier is an intra-function graph; `trace_native_values` composes derived parameter/argument and return/output dependencies across resolved calls. It does not resolve memory aliasing, call side effects, runtime behavior, persistent state, or business meaning. |
|
||||
| Result extent | Function instruction scans and native API boundary observations remain complete. P-code flow is the explicit bounded exception; its count fields distinguish exact retained counts from lower-bound or known-omitted counts. Caller cancellation and provider failures remain distinct. |
|
||||
|
||||
Runtime updates apply to newly accepted operations on the existing Program.
|
||||
They preserve accepted budgets and do not clear cleanup blockers or change the
|
||||
client deadline. Transient decompile timeout failures remain uncached so the
|
||||
same function can be retried after raising the limit. See the
|
||||
[settings design](design/limits-and-recovery.md) for snapshots, precedence,
|
||||
and provider integration.
|
||||
|
||||
Function extents and instruction completeness refer to Ghidra's analyzed body.
|
||||
An inferred no-return call can terminate that body before subsequent bytes.
|
||||
Pseudocode, assembly, procedure info, instruction windows, incoming and outgoing
|
||||
|
||||
@@ -7,6 +7,9 @@ releases have not been verified. Format 1 is unsupported.
|
||||
|
||||
Inspection retains the original header and directory facts, including engine
|
||||
version, flags, declared file count, source paths, offsets and declared MD5.
|
||||
For a member declared as `res://scenes/main.gd`, the occurrence's
|
||||
`source_path` retains that exact declaration while `logical_path` is
|
||||
`scenes/main.gd`, the relative path used for lookup and extraction.
|
||||
Observed plaintext bytes receive content-addressed SHA-256 identities. MD5 is a
|
||||
container integrity claim, separate from the observed SHA-256. A mismatch fails
|
||||
by default; `--integrity-policy record-and-continue` retains the observed bytes
|
||||
|
||||
+29
-4
@@ -241,7 +241,7 @@ record addresses remain distinct candidates, and conflicting file mappings
|
||||
remain malformed.
|
||||
|
||||
Module records establish names through their declared NUL-terminated string pointers. NUL/0xFF padding, spaces, Unicode and suffix-shared storage are supported. A pointer to a longer string does not match just its suffix; a pointer directly into a shared suffix may name it. Exact reversed UTF-8 matching avoids rescanning the file for every name and retains duplicate-name owners until module records resolve them. A contributor investigated a Linux
|
||||
`GameAssembly.so` from Unity 2023.2.13f1 whose static registration was not located. When no static pair is resolved on x86-64, the reader searches executable ranges for bytes consistent with RIP-relative LEA/MOV references to module-array candidates. Exactly one referenced array candidate must remain. `code_gen_modules_field` is then `null`, and Evidence is **inferred**: instruction boundaries, the absence of a static registration elsewhere, and runtime use were not verified. The module records and slots themselves remain file observations. Static-table associations are **derived**. Otherwise the result is `code-registration-not-found`.
|
||||
`GameAssembly.so` from Unity 2023.2.13f1 whose static registration was not located. When no static pair is resolved on x86-64 or arm64, the reader searches executable ranges for bytes consistent with a reference to a module-array candidate: RIP-relative LEA/MOV on x86-64, an adjacent `adrp`/`add` pair on arm64. Exactly one referenced array candidate must remain. `code_gen_modules_field` is then `null`, and Evidence is **inferred**: instruction boundaries, the absence of a static registration elsewhere, and runtime use were not verified. The module records and slots themselves remain file observations. Static-table associations are **derived**. Otherwise the result is `code-registration-not-found`. Cpp2IL's StrippedCodeRegSupport plugin also recovers the module list of an inlined CodeRegistration from the module names; this fallback additionally requires the exact located module set and a single code reference. A Unity 2022.3.62f3 macOS Master build checked by a contributor keeps neither registration symbol; its type table is then inferred as described below.
|
||||
|
||||
The binary/metadata pair is caller-selected. Name/count/token agreement is structural evidence, not a cryptographic same-build proof. File-offset locations refer to the primary metadata artifact; linked virtual addresses refer to `binary.artifact`. Both SHA-256 identities remain available in compact native pages as `artifact` and `binary_source`.
|
||||
|
||||
@@ -299,6 +299,30 @@ The result adds:
|
||||
`Il2CppMetadataRegistration.typesCount` and its value. That record is found as
|
||||
the single place whose `fieldOffsetsCount` and `typeDefinitionsSizesCount` both
|
||||
equal the metadata type-definition count and whose type table is file mapped.
|
||||
Linking reads only that table. When no record on x86-64 or arm64 has both of
|
||||
those counts, the reader collects maximal runs of pointers to Il2CppType-shaped
|
||||
records (an assigned Il2CppTypeEnum kind from 0x01 to 0x1e; class and
|
||||
value-type data below the type-definition count) that are long enough for every
|
||||
type index the metadata references. Exactly one such run may exist in the
|
||||
image, it must start and end inside its mapping (a run touching either end may
|
||||
continue in a neighbouring mapping), and its start must be loaded by the same
|
||||
instruction forms as the module-array fallback. `types_field` is then `null`,
|
||||
`types_count` is that run's length rather than an observed `typesCount`, and
|
||||
Evidence is **inferred**. The run length is at least every referenced type
|
||||
index but can differ from `typesCount`: a type-shaped pointer after the table
|
||||
would lengthen it, and an unreadable slot (such as an unresolved relocation)
|
||||
would end it early. Only the type indices the metadata references are read. A
|
||||
second long-enough run anywhere, a run that a type-shaped neighbour extends
|
||||
before the loaded address, a run touching a mapping end or a slot the loader
|
||||
binds or leaves unresolved (its stored word is only a placeholder), and a covered
|
||||
128-byte record whose two counts match but whose other fields are unusable all
|
||||
leave `metadata-registration-not-found` rather than a guess; the search stops
|
||||
at the second run. The length requirement only separates the type table from
|
||||
other pointer runs when the metadata references most of it: on the one Master
|
||||
build checked, the highest referenced index is 6,031 of 6,576 entries. A
|
||||
metadata referencing few type indices usually
|
||||
leaves several short runs and fails closed.
|
||||
|
||||
Counts use their declared 32-bit width independently of alignment padding.
|
||||
The complete 128-byte registration must have unambiguous loader coverage. Its
|
||||
used fields through byte 112 must be file-backed; a trailing declared zero-fill
|
||||
@@ -354,7 +378,8 @@ the result has no rows, and `generic_method_pointers_field`,
|
||||
`generic_method_pointers_count` and `generic_method_table_stride` are `null`.
|
||||
A table of a few rows can still leave two layouts agreeing, which fails as
|
||||
ambiguous rather than picking one. Metadata versions 24 and 39 and builds
|
||||
without a static CodeRegistration (`code_gen_modules_field` `null`) fail as
|
||||
without a static CodeRegistration (`code_gen_modules_field` `null`) or
|
||||
MetadataRegistration (`types_field` `null`) fail as
|
||||
`generic-methods-unsupported`: for version 24 the type-definition generic
|
||||
container offset and the generic class layout have not been checked against a
|
||||
real build, so it is refused rather than guessed.
|
||||
@@ -419,10 +444,10 @@ The builds checked are listed after the module search rules above.
|
||||
| `code-registration-not-found` | input error | No unique module table or executable-byte array candidate was resolved. |
|
||||
| `invalid-method-token` | input error | A token is outside its module's method-pointer rows, or a MethodDef row is reused within one image. |
|
||||
| `invalid-method-pointer` | input error | A slot is unavailable or its nonzero address lacks unambiguous executable file coverage. |
|
||||
| `metadata-registration-not-found` | input error | No single MetadataRegistration matches the type-definition count. |
|
||||
| `metadata-registration-not-found` | input error | No single MetadataRegistration or code-loaded type-table candidate was resolved. |
|
||||
| `invalid-type` | input error | A type index or `Il2CppType` record is out of range, unmapped or malformed. |
|
||||
| `unsupported-type-kind` | unsupported | A valid type kind has no implemented rendering. |
|
||||
| `generic-methods-unsupported` | unsupported | Generic instances were requested for metadata version 24 or 39 or a build without a static CodeRegistration. |
|
||||
| `generic-methods-unsupported` | unsupported | Generic instances were requested for metadata version 24 or 39, or a build lacks either static registration. |
|
||||
| `generic-method-registration-not-found` | input error | No CodeRegistration layout bounds every genericMethodTable row, or the generic tables are unreadable. |
|
||||
| `ambiguous-generic-method-registration` | input error | More than one CodeRegistration layout bounds every genericMethodTable row. |
|
||||
|
||||
|
||||
+44
-14
@@ -319,14 +319,14 @@ Register with the variable unset:
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
```bash
|
||||
claude mcp add --scope local rea -- npx -y rea-agents@6.7.0 mcp
|
||||
claude mcp add --scope local rea -- npx -y rea-agents@6.8.0 mcp
|
||||
```
|
||||
|
||||
On native Windows, use the same project directory and wrap the package-runner
|
||||
shim so the stdio client can launch it:
|
||||
|
||||
```powershell
|
||||
claude mcp add --scope local rea -- cmd.exe /d /c npx -y rea-agents@6.7.0 mcp
|
||||
claude mcp add --scope local rea -- cmd.exe /d /c npx -y rea-agents@6.8.0 mcp
|
||||
```
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
@@ -616,9 +616,12 @@ hosted connector; see [its configuration](client-configuration.md#other-client-s
|
||||
| WorkBuddy | `workbuddy` |
|
||||
| ZCode | `zcode` |
|
||||
| DeepSeek Harness | `dsh` |
|
||||
| FreeBuff | `freebuff` |
|
||||
|
||||
`droid`, `dsh`, `kimi`, `qoder`, `trae`, `workbuddy`, and `zcode` require
|
||||
`rea-agents@6.5.0` or later. The 6.4.0 registry check above does not cover them.
|
||||
`freebuff` requires `rea-agents@6.8.0` or later. Older releases reject
|
||||
`rea setup --client freebuff` as an unsupported client.
|
||||
|
||||
See [client configuration and troubleshooting](client-configuration.md) for
|
||||
configuration paths, profiles, trust policies, and schema-limit workarounds.
|
||||
@@ -681,6 +684,12 @@ search permissions (`0700`) on a directory you own before retrying.
|
||||
|
||||
On supported Linux distributions, approved setup verifies Hopper's official
|
||||
`.deb`, `.rpm`, or Arch package before invoking the native package manager.
|
||||
On apt systems, Hopper's runtime dependencies are installed with
|
||||
`--no-upgrade` in their own transaction, so setup no longer names an
|
||||
already-installed dependency as an upgrade target. apt can still upgrade a
|
||||
package that the Hopper package or the Python library repair requires at a
|
||||
newer version. Arch's `pacman -S --needed` still updates an outdated
|
||||
dependency.
|
||||
The official release feed selects the current public demo without a version
|
||||
pin. REA verifies the package's detached PGP signature against Hopper's stable
|
||||
primary fingerprint `7C2D73BAFAF5B9A710A69DA1A26DF815099B1BCD`, then checks
|
||||
@@ -803,7 +812,10 @@ Hopper startup allows 120,000 ms for launch, initial analysis, and authenticated
|
||||
bridge readiness. For large targets, set `REA_HOPPER_STARTUP_TIMEOUT_MS` to a
|
||||
decimal integer from 1 to 2,147,483,647 milliseconds in the environment that
|
||||
starts REA. CLI and MCP use the same setting; invalid values fail configuration
|
||||
validation. An MCP client's own request timeout must also allow enough time.
|
||||
validation. In MCP, `update_analysis_settings` can set
|
||||
`hopper.startup_timeout_ms` for the next startup attempt without restarting REA;
|
||||
accepted work keeps its budget and retained cleanup must still complete. An
|
||||
MCP client's own request timeout must also allow enough time.
|
||||
`open_binary` validates and selects a target without waiting for native provider
|
||||
readiness. During Hopper startup, `binary_session.provider_operation_health`
|
||||
reports `busy` at stage `launch`; `analysis_activity` describes bridge requests
|
||||
@@ -1173,19 +1185,25 @@ can time out earlier even when Ghidra would complete within its startup deadline
|
||||
See [Ghidra first-query deadlines and recovery](mcp-contracts.md#ghidra-first-query-deadlines-and-recovery)
|
||||
for client options and the close/reopen recovery flow.
|
||||
|
||||
The 330-second default is configurable. Configuration is read when REA starts:
|
||||
changing an environment variable in another shell, or closing and reopening a
|
||||
binary in the same MCP server, does not change that server's startup allowance.
|
||||
The 330-second default can be changed on a running MCP server by setting
|
||||
`ghidra.startup_timeout_ms` through `update_analysis_settings`. The next accepted
|
||||
operation that starts a provider uses that value; an accepted startup keeps its
|
||||
original budget. Changing an environment variable in another shell, or closing
|
||||
and reopening a binary, does not change the launch configuration already read
|
||||
by that server.
|
||||
For a CLI retry with an example 30-minute allowance:
|
||||
|
||||
```sh
|
||||
REA_GHIDRA_STARTUP_TIMEOUT_MS=1800000 rea analyze /absolute/path/to/app.exe --provider ghidra --json
|
||||
```
|
||||
|
||||
For MCP, export useful Evidence before restarting, close the binary and resolve
|
||||
any cleanup failure, update the environment in the client's REA server launch
|
||||
configuration, then restart REA and reconnect before reopening the target. Allow
|
||||
startup plus the query in the client's own tool-call deadline. Every retry uses
|
||||
For MCP, update the runtime setting and resolve any retained cleanup before the
|
||||
next startup attempt. Follow the error's close/reopen guidance where needed;
|
||||
the settings update does not clear cleanup blockers. To change the launch
|
||||
environment instead, export useful Evidence, close the binary and resolve any
|
||||
cleanup failure, update the client's REA server launch configuration, then
|
||||
restart REA and reconnect. Allow startup plus the query in the client's own
|
||||
tool-call deadline; runtime updates do not change that deadline. Each new startup attempt uses
|
||||
a fresh ephemeral import; smaller result requests do not reduce the prerequisite
|
||||
auto-analysis. A larger time allowance does not raise the JVM heap limit or
|
||||
guarantee completion. Keep [heap and host CPU controls](#ghidra-heap-and-cpu-controls)
|
||||
@@ -1212,9 +1230,14 @@ fails with `provider_timeout` and that message; other Ghidra tools stay
|
||||
available. If the caller also cancels while the timeout settles, the session
|
||||
still closes and the message says so. Session reuse concerns an active MCP or
|
||||
library session; a standalone CLI command still closes its own session when it
|
||||
finishes. The limit is read when a Ghidra session
|
||||
starts, and Codex/Kimi registrations derived by `rea setup` add it to the tool
|
||||
deadline as a single-decompile estimate. A bulk call can need that limit for
|
||||
finishes. In MCP, `ghidra.decompile_timeout_seconds` can override the launch
|
||||
value for newly accepted operations on the existing Program; explicit `null`
|
||||
removes the deadline. A transient timeout remains uncached so the same function
|
||||
can be retried after an update. See the
|
||||
[settings design](design/limits-and-recovery.md#runtime-settings-through-mcp).
|
||||
Codex/Kimi registrations derived by `rea setup` add the selected launch value
|
||||
to the tool deadline as a single-decompile estimate; a runtime update does not
|
||||
change that registration. A bulk call can need that limit for
|
||||
every attempted function plus listing and file work; select the client deadline
|
||||
for the workload, split batches, use `decompile_all` limit/resume, or reduce
|
||||
`trace_native_values` `max_functions` to select fewer serial decompiles. Ghidra's own interactive default is 30 seconds. Unresolved computed calls
|
||||
@@ -1448,7 +1471,7 @@ For a client that requires manual configuration, use:
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@6.7.0", "mcp"]
|
||||
"args": ["-y", "rea-agents@6.8.0", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1462,6 +1485,13 @@ setup runs through `bunx`, and pins it to the exact version that performed
|
||||
setup. Run current setup to refresh an older registration, then restart the
|
||||
client.
|
||||
|
||||
Package runners key their package cache by the requested spec, so the entry
|
||||
installed for the `@latest` invocation that ran setup cannot serve the pinned
|
||||
registration. Setup therefore materializes the pinned entry once, best-effort
|
||||
with a bounded timeout, before finishing. When that step cannot run, for
|
||||
example offline, the first client start performs the one-time install inside
|
||||
the client's MCP startup deadline and may need one more restart.
|
||||
|
||||
## Package runners and global installation
|
||||
|
||||
A package-runner invocation can use a REA version installed in the current project. To
|
||||
|
||||
@@ -140,17 +140,24 @@ returns an unavailable error with the retained inventory, rather than claiming
|
||||
the selected source does not exist. Observed exact file candidates keep their
|
||||
identity; unknown traversal cannot establish a unique application root.
|
||||
|
||||
ASAR header/index admission defaults to 16 MiB / 16777216 bytes. Set
|
||||
`REA_ASAR_MAX_HEADER_BYTES` in the environment that starts REA to a decimal
|
||||
byte count of at most 268435456 (256 MiB) to raise or lower it; restart REA
|
||||
after changing it. Invalid values are configuration errors, never a silent
|
||||
fallback. The archive index is read before any member can be selected, so
|
||||
ASAR header/index admission defaults to 16 MiB / 16777216 bytes. In MCP, use
|
||||
`update_analysis_settings` to set `asar.max_header_bytes` to an integer byte
|
||||
count from 1 to 268435456 (256 MiB), then retry the same full-archive request.
|
||||
The update applies to new operations without restarting REA; cached headers
|
||||
must also satisfy the new admission. For CLI or future MCP processes, set
|
||||
`REA_ASAR_MAX_HEADER_BYTES` in the launch environment. Changing that environment
|
||||
requires a new REA process. Invalid launch values are configuration errors,
|
||||
never a silent fallback. See the
|
||||
[settings design](design/limits-and-recovery.md#failure-and-retry) for recovery
|
||||
metadata, precedence, and reset behavior.
|
||||
|
||||
The archive index is read before any member can be selected, so
|
||||
`source_paths`, `--source-path`, `detail: "summary"`, `max_heap_mb`, and
|
||||
available host memory do not bypass or raise it. When the header exceeds the
|
||||
effective allowance, the failure names it, whether it came from the default or
|
||||
the setting, and the setting's limits. Alternatively, analyze an extracted
|
||||
effective allowance, the failure reports the value, its known source, and the
|
||||
setting's limits. Alternatively, analyze an extracted
|
||||
application directory, which does not admit an ASAR header. Repeating the unchanged
|
||||
request fails the same way.
|
||||
request without changing admission fails the same way.
|
||||
|
||||
## Read the analysis
|
||||
|
||||
@@ -195,6 +202,27 @@ For supplied passive browser or Electron observations, use
|
||||
[runtime reconciliation](javascript-runtime-reconciliation.md). It compares
|
||||
existing Evidence without rerunning static analysis or contacting CDP.
|
||||
|
||||
Each newly analyzed JavaScript file retains `analysis_coverage` in its graph
|
||||
observation. `syntax` distinguishes complete parsing, recovered syntax errors,
|
||||
failed parsing and unexamined source. `semantics` records the number of observed
|
||||
unknown findings and typed uncertainty, including returned-reference widening
|
||||
and unavailable type-only compiler options. `examination` reports static semantic
|
||||
resource limits and Electron wrapper expansion limits independently. A completed
|
||||
pass does not establish complete runtime semantics. Route examination and result
|
||||
capacity remain in the separate route inventory.
|
||||
|
||||
Electron applicability is `observed` when a static Electron module reference is
|
||||
retained, and otherwise `unknown`: a source without such a reference does not
|
||||
prove that the application is inapplicable. Sources excluded by `source_paths`
|
||||
retain `not-examined` with a reason and null semantic counts. Historical Evidence
|
||||
without this facet remains unmeasured. The existing `parse_status` remains its
|
||||
conservative overall summary; finer facts do not relabel earlier partial results.
|
||||
|
||||
Summary delivery counts these facts under `summary.coverage.static_sources`.
|
||||
Read exact per-file measurements with `inspect_analysis_view`, selecting
|
||||
`kind: "javascript-facts"` and `property_keys: ["analysis_coverage"]`, or inspect
|
||||
the module item. Both views use retained Evidence and preserve source provenance.
|
||||
|
||||
## ASAR integrity
|
||||
|
||||
ASAR inventory checks Electron integrity metadata for embedded archive entries
|
||||
@@ -716,6 +744,10 @@ deadline per source, including startup and fact transfer. Select
|
||||
`max_heap_mb` and `analysis_timeout_ms` through MCP, or `--max-heap-mb` and
|
||||
`--analysis-timeout-ms` on the dedicated CLI command. These choices control the
|
||||
analyzer; the caller's `NODE_OPTIONS` still controls the CLI/MCP process heap.
|
||||
For defaults across new MCP operations, use `update_analysis_settings` with
|
||||
`javascript.max_heap_mb` or `javascript.analysis_timeout_ms`. Explicit per-call
|
||||
arguments take precedence, and accepted work keeps its original settings. See
|
||||
the [settings design](design/limits-and-recovery.md#precedence-and-lifetime).
|
||||
V8 old-space limits do not impose a process RSS limit. When REA runs on Bun,
|
||||
the analyzer ignores the V8 heap flags; resource failures then report
|
||||
`worker_heap_limit_bytes` as null instead of a requested or V8-reported limit.
|
||||
|
||||
+47
-5
@@ -932,6 +932,25 @@ of a later null value. Inspect the slot's `xrefs` and the writer's assembly or
|
||||
pseudocode, retaining uncertainty about execution order and runtime values.
|
||||
These operations inspect static evidence and do not execute the target.
|
||||
|
||||
## Editable analysis settings
|
||||
|
||||
Agents can call `update_analysis_settings` to raise a curated admission,
|
||||
timeout or worker heap default on a connected server, then retry the same
|
||||
request. Errors carry `details.limit` with the setting and bounds when the
|
||||
failing owner supports runtime recovery; `inspect_analysis_settings` is optional
|
||||
when those facts are supplied. Updates are atomic, apply to new work, survive
|
||||
target close/open, and vanish when REA exits. Explicit per-call arguments take
|
||||
precedence. Neither tool changes host deadlines or resolves retained cleanup.
|
||||
See [runtime settings and recovery](design/limits-and-recovery.md#runtime-settings-through-mcp)
|
||||
for the full setting list, reset/revision semantics and ASAR retry example.
|
||||
For implementation ownership, accepted-operation snapshots, provider wiring,
|
||||
and extension guidance, see the
|
||||
[maintainer design](design/limits-and-recovery.md#ownership-and-data-flow).
|
||||
|
||||
Ghidra and Hopper startup updates apply to the next startup attempt without
|
||||
restarting REA. Ghidra decompile timeout updates apply on the existing Program.
|
||||
An operation already accepted retains its original budgets.
|
||||
|
||||
## Ghidra first-query deadlines and recovery
|
||||
|
||||
A successful MCP initialize handshake establishes the REA connection.
|
||||
@@ -1033,11 +1052,13 @@ the current client, so it is not a fresh-start recovery. On the same connection:
|
||||
`provider_id: "ghidra"`, then retry the first query with an appropriate client
|
||||
deadline. The connection and selected provider do not need to change.
|
||||
|
||||
If REA's provider startup allowance also needs raising, preserve Evidence and
|
||||
close as above, then update `REA_GHIDRA_STARTUP_TIMEOUT_MS` in REA's launch
|
||||
environment and restart the server. Reconnect the MCP client before reopening
|
||||
the target. Configuration is read at process startup; close/reopen on the same
|
||||
connection cannot apply a changed environment. See the
|
||||
If REA's provider startup allowance also needs raising, set
|
||||
`ghidra.startup_timeout_ms` through `update_analysis_settings` before the next
|
||||
startup attempt. This needs no REA restart and does not change accepted work,
|
||||
clear cleanup blockers, or extend the client's deadline. Alternatively, change
|
||||
`REA_GHIDRA_STARTUP_TIMEOUT_MS` in REA's launch environment and restart the
|
||||
server, preserving Evidence and closing as above. Close/reopen on the same
|
||||
connection cannot apply a changed launch environment. See the
|
||||
[CLI retry example](installation.md#ghidra).
|
||||
|
||||
This close/reopen flow was exercised on one real Linux stdio connection after a
|
||||
@@ -1232,6 +1253,22 @@ Kimi-K3 backend, not the WorkBuddy MCP client. Set the profile on REA's
|
||||
registration in the actual MCP host; the host's name alone does not identify
|
||||
the downstream model or its schema constraints.
|
||||
|
||||
The compact profile also protects strict Anthropic-compatible gateways from
|
||||
known schema-translation failures reported in [#3800](https://github.com/morluto/rea/issues/3800):
|
||||
primitive `enum` properties without a sibling `type`, and `oneOf`/`allOf`
|
||||
inside a property listed in `required`. Affected schemas use the same reduced
|
||||
first-level projection described below, so the gateway receives a plain object
|
||||
shape while REA continues to validate the complete canonical contract locally.
|
||||
This is a compatibility projection, not a claim that the gateway reproduces
|
||||
the full union semantics.
|
||||
|
||||
For array items expressed as an untyped `oneOf`/`anyOf`, the compact profile
|
||||
adds a type when every branch has the same type. Mixed-type item unions use the
|
||||
reduced projection; selecting one branch's type would exclude valid calls.
|
||||
This addresses the emitted shape reported through a Gemini-backed gateway in
|
||||
[#5201](https://github.com/morluto/rea/issues/5201). Gateway conversion and
|
||||
live provider acceptance have not been verified for this projection.
|
||||
|
||||
The compact profile renders the same tool set, output schemas, annotations,
|
||||
and canonical server-side validation. It changes only the advertised input
|
||||
JSON Schema:
|
||||
@@ -1263,6 +1300,11 @@ Boundary tests pin every advertised compact schema to the budget, keep them
|
||||
valid JSON Schema that still accepts every canonical example. Client verification
|
||||
also checks provider-facing sizes and canonical examples after conversion by
|
||||
the actual Kimi Code CLI; local captures do not establish Moonshot acceptance.
|
||||
The SDK catalog test checks that full-profile schemas retain nested ADB extra,
|
||||
managed selection, and WASM timeout and detail restrictions. Path-contract tests
|
||||
check full-profile local-path rules. Compact MCP workflow tests verify canonical
|
||||
rejection of invalid inputs before provider work, independent of how much detail
|
||||
the selected advertisement retains.
|
||||
|
||||
### Output schemas
|
||||
|
||||
|
||||
@@ -284,17 +284,22 @@ stops matching while leaving the Hopper API available. The deadline is
|
||||
2147483647 ms. Unset uses 5000 ms. A larger value is refused because Node
|
||||
schedules a delay past that signed 32-bit bound as 1 ms. Invalid values are
|
||||
configuration errors and do not fall back to the default. The setting is read
|
||||
from the environment that starts REA, so a change needs a restart. A deadline
|
||||
failure names the effective milliseconds, whether they came from the default
|
||||
or the setting, the default, and the maximum. The worker old-generation heap
|
||||
does not raise it. Kuna regex search uses the same deadline. The worker
|
||||
from the environment that starts REA; changing that environment needs a restart.
|
||||
In MCP, `regex.matching_deadline_ms` overrides the deadline for newly accepted
|
||||
searches without restarting REA. A deadline failure names the effective
|
||||
milliseconds, known source, default, and maximum. The worker old-generation heap
|
||||
does not raise it. IDA and Kuna use these same regex controls; Ghidra's Java
|
||||
matcher does not. The worker
|
||||
heap request is `REA_REGEX_WORKER_HEAP_REQUEST_MIB`, a decimal integer from
|
||||
7 to 16384 MiB. Unset uses 128 MiB. A request below 7 MiB aborts the worker
|
||||
during startup, and a larger value is refused. Invalid values are
|
||||
configuration errors and do not fall back to 128. The setting is read from
|
||||
the environment that starts REA, so a change needs a restart. A heap failure
|
||||
names the effective request, whether it came from the default or the setting,
|
||||
the default, the floor, and the maximum. A positive Node.js
|
||||
the environment that starts REA, while MCP `regex.worker_heap_request_mib`
|
||||
can override it for workers belonging to newly accepted searches. Use
|
||||
`update_analysis_settings` for either runtime control; accepted work keeps its
|
||||
budget. See the [settings design](design/limits-and-recovery.md).
|
||||
A heap failure names the effective request, known source, default, floor, and
|
||||
maximum. A positive Node.js
|
||||
`--max-old-space-size` on the process that starts
|
||||
REA, including `NODE_OPTIONS`, overrides that request and sets the effective
|
||||
heap. `--max-old-space-size-percentage` takes precedence over that flag in
|
||||
|
||||
+18
-8
@@ -17,7 +17,7 @@ Summary delivery retains the complete Evidence: use `parent_evidence_id` with
|
||||
Extraction returns a fresh `output_root`; analyze that extracted directory with
|
||||
`analyze_javascript_application`. Snapshot-only applications have no recovered
|
||||
main script and receive no JavaScript next probe. Neither does a main script
|
||||
whose code path lacks a JavaScript source suffix, such as `main`, because the
|
||||
whose logical path lacks a JavaScript source suffix, such as `main`, because the
|
||||
analyzer selects extracted files by suffix.
|
||||
|
||||
## Recognition and evidence
|
||||
@@ -35,11 +35,18 @@ inventoried once. Native carrier bytes keep their content hash and native
|
||||
classification while the root occurrence represents the SEA container.
|
||||
|
||||
Root `format_metadata` preserves raw flags, decoded flags, code path, blob
|
||||
location, exec arguments and fuse observations. Children retain their role and
|
||||
original `source_path`. The main script uses its declared code path; snapshots
|
||||
and code caches use `__node_sea__/snapshot` and `__node_sea__/code-cache`.
|
||||
Assets retain their names. Extraction applies the shared path-containment and
|
||||
collision rules, rejects unsafe names and never overwrites existing files.
|
||||
location, exec arguments and fuse observations. Main and asset children retain
|
||||
their role and exact declared name in `source_path`. Safe, noncolliding names
|
||||
become logical paths after the shared NFC path normalization. Names that are
|
||||
unsafe as relative paths, have an exact or file-prefix collision with another
|
||||
entry, or occupy the reserved `__node_sea__`
|
||||
namespace receive deterministic logical paths: `__node_sea__/main.cjs` for the
|
||||
main CommonJS source and `__node_sea__/assets/<ordinal>` for assets, using their
|
||||
zero-based order in the blob. The original declared name remains
|
||||
in `source_path`, including an empty asset key. Snapshots and code caches use
|
||||
`__node_sea__/snapshot` and `__node_sea__/code-cache`. Extraction materializes
|
||||
the logical paths through the shared path-containment and collision rules and
|
||||
never overwrites existing files.
|
||||
|
||||
The fuse scan independently records `set`, `unset`, `absent`, or `conflicting`.
|
||||
An unset fuse with a present blob is reported as a limitation. These are static
|
||||
@@ -56,7 +63,7 @@ including the exec-argument extension byte. Older layouts without that byte,
|
||||
Carrier byte order is independent of blob byte order.
|
||||
|
||||
Malformed magic, truncation, unknown flags/extensions, invalid UTF-8, duplicate
|
||||
asset names and conflicting blob declarations fail closed with named reasons.
|
||||
raw asset keys and conflicting blob declarations fail closed with named reasons.
|
||||
Zero padding after a complete blob is allowed. The blob read ceiling is 256 MiB;
|
||||
PE carriers also use the shared 512 MiB input, 65,536 directory-entry and 8 MiB
|
||||
resource-metadata ceilings. The decoder admits at most 100,000 assets whose
|
||||
@@ -74,7 +81,10 @@ code-cache bytes remain opaque.
|
||||
|
||||
Synthetic fixtures cover Mach-O, ELF and PE inventory/extraction, malformed
|
||||
blobs, collisions and fuse contradictions. CLI and MCP tests exercise the same
|
||||
artifact provider and advertised schemas.
|
||||
artifact provider and advertised schemas. A CLI regression also generates a blob
|
||||
with the pinned local Node version, an absolute main path and an empty asset
|
||||
key, then inventories it inside a synthetic ELF carrier. This verifies the real
|
||||
blob producer, not a real injected executable.
|
||||
|
||||
The optional real ELF lane uses Docker's official Node v24.10.0 image and pinned
|
||||
postject to build a fixture, then verifies inventory and extracted script/asset
|
||||
|
||||
@@ -25,8 +25,12 @@ metadata retains the original name, table index, type, compression flag,
|
||||
relative offset and known directory record extent. Logical paths follow the
|
||||
carrier's path dialect: backslashes separate components for PE carriers, while
|
||||
they remain literal filename characters for POSIX carriers. The source name
|
||||
always retains its stored spelling. If the carrier dialect is unconfirmed, REA
|
||||
preserves backslashes verbatim instead of inferring POSIX semantics.
|
||||
always retains its stored spelling in occurrence `source_path` and
|
||||
`format_metadata.source_name`. For PYZ rows, that source is the exact module or
|
||||
data key in the PYZ table, not a reconstructed filesystem name;
|
||||
`format_metadata.container_name` identifies the containing PYZ image. If the
|
||||
carrier dialect is unconfirmed, REA preserves backslashes verbatim instead of
|
||||
inferring POSIX semantics.
|
||||
`relative_offset` is relative to the CArchive start for CArchive rows, or to
|
||||
the decoded PYZ image for PYZ rows. A CArchive directory record starts at
|
||||
`archive_start + toc_offset + directory_record_offset`; its size is
|
||||
|
||||
@@ -325,9 +325,7 @@ REA の利用、バグ報告、機能の要望、ビルドのテスト、修正
|
||||
|
||||
REA は、法令に従った研究、教育、相互運用性の確保、ソフトウェアの保存、および所有するソフトウェアや調査を許可されたソフトウェアの解析を目的としています。
|
||||
|
||||
The project's position on illegal or unauthorized use covers all REA
|
||||
versions, distribution channels, and platforms; each copy retains its
|
||||
applicable licenses.
|
||||
違法または無許可の利用に関するプロジェクトの立場は、REA のすべてのバージョン、配布チャネル、およびプラットフォームに適用されます。各コピーには、それぞれに適用されるライセンスが引き続き適用されます。
|
||||
|
||||
## 貢献する
|
||||
|
||||
@@ -339,14 +337,6 @@ REA への貢献を歓迎します
|
||||
|
||||
REA-authored code is licensed under MIT. Third-party material retains its own
|
||||
licenses and copyright notices; see [Third-party notices](../../THIRD_PARTY_NOTICES.md)
|
||||
for the dependency inventory, preserved license texts, and external-tool terms.
|
||||
REA が作成したコードは MIT ライセンスの下で提供されます。サードパーティの素材には、それぞれのライセンスと著作権表示が引き続き適用されます。依存関係の一覧、保存されたライセンス本文、および外部ツールの利用条件については、[サードパーティ通知](../../THIRD_PARTY_NOTICES.md)を参照してください。
|
||||
|
||||
**You must legally obtain and be authorized to use commercial tools**, including
|
||||
IDA Pro / Hex-Rays decompilers, Hopper, and JEB, under their applicable licenses.
|
||||
REA grants no tool license, key, or activation entitlement. Trials and demos
|
||||
remain subject to vendor terms, including user, feature, automation, and output
|
||||
sharing restrictions. Cracked copies, stolen keys, unauthorized key sharing,
|
||||
and bypassing license restrictions are not authorized. This guidance does not
|
||||
amend MIT or third-party licenses.
|
||||
**IDA Pro / Hex-Rays デコンパイラ、Hopper、JEB を含む商用ツールは、適用されるライセンスに従って合法的に入手し、使用する権限を得ている必要があります。** REA はツールのライセンス、キー、またはアクティベーションの権利を一切付与しません。試用版やデモ版も、ユーザー、機能、自動化、および出力の共有に関する制限を含め、ベンダーの利用条件に従います。クラックされたコピー、盗まれたキー、無許可のキー共有、およびライセンス制限の回避は認められていません。このガイダンスは、MIT ライセンスやサードパーティのライセンスを変更するものではありません。
|
||||
File diff suppressed because it is too large.
Load diff
@@ -83,7 +83,8 @@ empty dependency set. Retained views describe the captured artifact digest;
|
||||
they do not assert that the current file or running application is unchanged.
|
||||
|
||||
ELF section names are resolved through the section-header string table.
|
||||
Zlib-compressed name tables are decoded within the native structural budget.
|
||||
Zlib- and Zstandard-compressed name tables are decoded within the native structural
|
||||
budget. Zstandard decoding also uses a 128 MiB window limit.
|
||||
If a name table uses unsupported compression, the result reports that
|
||||
limitation and leaves `.dep-v0` presence unknown rather than reporting the
|
||||
section absent.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Implementation plan
|
||||
|
||||
1. **Defaults.** `WriteBody.defaults` and its schema; a create's echo of a member a call leaves out takes its default.
|
||||
2. **Merging.** Explain a later create on its own and merge it: agreeing rules, references one call shows alone, joined
|
||||
bodies and defaults, the plan's rule order.
|
||||
3. **Sorted echoes.** Learn `sorted` for creates and updates, store sorted strings when serving, and agree plain and
|
||||
sorted echoes in update and create plans.
|
||||
4. **References.** A references module that learns and resolves them; created and updated fields resolve them against
|
||||
the replayed or live state; the members they read are accepted.
|
||||
5. **Nested view keys.** The gate's view spelling learner, moved to its own module, notes object keys; rendering
|
||||
applies them.
|
||||
6. **Schema, tests and docs.**
|
||||
@@ -0,0 +1,48 @@
|
||||
# Learn creates from every call, sorted arrays and referenced entities
|
||||
|
||||
Issue: https://github.com/morluto/rea/issues/4933
|
||||
|
||||
## Outcome
|
||||
|
||||
After the derived-values change, the creates prompt-built environments still withheld failed for four reasons the
|
||||
write model did not cover:
|
||||
|
||||
- A create plan was learned from the tool's first call alone, so a later call that sent a member the first did not
|
||||
(Linkding's `shared`), or left one out, was refused even when it agreed with the plan everywhere else.
|
||||
- Linkding stores `tag_names` sorted, so a call that sent its tags in another order did not echo them, and its create
|
||||
was withheld or contradicted.
|
||||
- Homebox's item create answers its parent and type as whole objects (`parent`, `location`, `entityType`) beside the
|
||||
`parentId` and `entityTypeId` it sent. Kept as constants, they pinned those members to their recorded values.
|
||||
- Lists show some objects with fewer keys than an entity holds: Homebox lists an item's parent without the parent's
|
||||
own type, and Keycloak lists a user's `access` with one key. A created entity's object rendered whole, so the list
|
||||
check failed.
|
||||
|
||||
## Behaviour
|
||||
|
||||
- **Every call.** A later create is explained on its own and merged into the plan when both calls show the same fields,
|
||||
but for a reference whose member a call without it did not send (one an earlier create added stays), every field
|
||||
both rule they rule alike, and a field neither rules holds one value. Members either call sent are accepted, only
|
||||
those every call sent are required, and an echoed member a call left out takes the value that call stored
|
||||
(`body.defaults`). A clock widens to `trimmed` only when no held entity or earlier call showed it at a fixed width. A
|
||||
later create that disagrees leaves the plan as it was and is judged as before; one that left out a member the plan
|
||||
fills while showing that field otherwise is `unexplained-change`. Merged rules keep the plan's order.
|
||||
- **Sorted arrays.** An echo is `sorted` when the application stored the sent strings in code-point order rather than
|
||||
as sent; serving stores them sorted. A plain echo and a sorted one agree, and the plan keeps the sorted one, since a
|
||||
call whose array was already in order cannot show it; once a call stored strings out of order as sent, they disagree.
|
||||
- **References.** A created or updated object field that holds fields of the entity a sent member names, as the state
|
||||
holds it, is a `reference` (member, type, fields). Several members naming such entities resolve to the one named like
|
||||
the field. The member is accepted with any value. Replay and serving look the entity up in the state they hold;
|
||||
serving refuses a call naming one the environment does not hold or holds in more than one collection, and the field
|
||||
is absent, from the entity and the create's answer, when the call does not send the member. An update that sends the
|
||||
member naming the entity the field already repeats keeps a reference an earlier call showed.
|
||||
- **Nested view keys.** A view that always shows a field's objects with the same keys, in any order, and fewer than
|
||||
an entity stores, names them in `nested_fields` in the first recording's order, and a stored object it never
|
||||
recorded renders with those keys.
|
||||
|
||||
Plans and views that need none of this are unchanged.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Linkding's prompt-recorded bookmark create whose read-back lists tags sorted is served, and Homebox's item create is
|
||||
explained without pinning its parent or type.
|
||||
- No write tool of the 69 recorded captures goes from served to withheld.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Tasks
|
||||
|
||||
- [x] Merge later create calls into the plan, with defaults for members a call leaves out.
|
||||
- [x] Learn and serve sorted echoes.
|
||||
- [x] Learn, replay and serve references to held entities.
|
||||
- [x] Learn the keys views show of nested objects and render written objects with them.
|
||||
- [x] Add view, serving and unit tests.
|
||||
- [x] Document the rules and view member.
|
||||
- [x] Measure the recorded captures and served write tasks.
|
||||
- [x] Run static, formatting, documentation and test gates.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Verification
|
||||
|
||||
Executed on macOS with Node v24.22.1 at `043053d1` (main `79845063`, the derived-values branch `8ce1127b` and this
|
||||
branch). Recorded captures and served write tasks were measured with a local, unpushed build of this branch merged with
|
||||
the scenario-timing branch (#6967), as the derived-values branch was; its baseline is the derived-values branch with the
|
||||
same merge.
|
||||
|
||||
## Gates
|
||||
|
||||
- `npm run -s typecheck`, `lint`, `format:check`, `docs:check`, `mcp-catalog:check`, `verify:test-discovery`,
|
||||
`scan:todos` and `npx knip`: passed.
|
||||
- `npx vitest run --project domain --project services --project adapters`: 17,258 passed, 25 skipped, 1 failed ("stops
|
||||
Inspector target authorization after the current stat settles"), which fails the same way on main in this environment.
|
||||
- `npm run test:focused --` the environment build, export and serve CLI boundaries, the analysis-view CLI and MCP
|
||||
boundaries, the environment serve, served-writes and bundle MCP boundaries, MCP schema validation and output schema
|
||||
constraints: 10 files, 82 passed.
|
||||
|
||||
## Tests
|
||||
|
||||
- View: a later create sending a member the first did not, or leaving one out, widens the plan with the member's default;
|
||||
a later create storing a member otherwise than sent stays unexplained; sent tags stored sorted give a sorted echo; a
|
||||
create embedding the shelf its `shelf_id` names gives a reference, and the list that shows the shelf by id and name
|
||||
learns `nested_fields`.
|
||||
- Serving: a create leaving the member out stores its default and one sending it stores it; a create embeds the named
|
||||
shelf as the environment holds it, lists it with the listed keys, and is refused for a shelf it does not hold; sent
|
||||
tags are stored sorted.
|
||||
- Unit: references (the member named like the field among several, too few keys, a disagreeing value, an unheld entity)
|
||||
and merges (a constant that differs, a member one fixes and the other accepts, a reference one call shows alone).
|
||||
|
||||
## Write plans over recorded captures
|
||||
|
||||
The 69 captures the derived-values branch measured: 50 published, 18 Claude Code recorded from a prompt, and one timed
|
||||
Grafana recording. After merging main `ed79bf93` (which bounds create learning), the census was run again against main
|
||||
with the scenario-timing branch: 124 served there, 132 with both branches, no write going from served to withheld; the
|
||||
columns below are the measurements before that merge, whose totals it reproduces.
|
||||
|
||||
| Captures | Write plans | Served before both branches | Derived values | This branch |
|
||||
| ------------------------- | ----------- | --------------------------- | -------------- | ----------- |
|
||||
| 50 published | 187 | 68 | 68 | 70 |
|
||||
| 19 recorded from a prompt | 120 | 56 | 61 | 62 |
|
||||
|
||||
- No write goes from served to withheld.
|
||||
- Gains: Keycloak's published user create and update, whose list shows each user's `access` with one key, so a created
|
||||
user now lists as recorded and its plan's sorted insert checks; and the round-4 Linkding bookmark create, whose tags
|
||||
the application stores sorted.
|
||||
- Explained but still withheld: Homebox's item create now merges with the location create, with references for
|
||||
`parent`, `location` and `entityType` and `parentId` and `entityTypeId` accepted with any value. Its read-back list
|
||||
answers items only, sorted by name, from a line with no parameter saying so, which the list model does not cover.
|
||||
- Linkding's create whose later call sends `shared` merges too, and is withheld as `side-effect`: the recording also
|
||||
added a bookmark through the UI that no recorded write explains.
|
||||
|
||||
## Served write tasks
|
||||
|
||||
The same captures rebuilt by the baseline and by this branch, with `fireworks_ai/gpt-oss-120b` and
|
||||
`fireworks_ai/glm-5p3-flash`. The tasks are new; each checks the result from the environment's state, including that
|
||||
nothing else changed.
|
||||
|
||||
| Capture | Tasks | Baseline (gpt-oss, GLM) | This branch (gpt-oss, GLM) |
|
||||
| --------------------------- | ------------------------------------------ | ----------------------- | -------------------------- |
|
||||
| Linkding, recorded round 4 | 2 bookmark creates, one with two tags | 0/2, 0/2 | 2/2, 2/2 |
|
||||
| Keycloak, published capture | create a user, change another user's email | 0/2, 0/2 | 2/2, 2/2 |
|
||||
@@ -0,0 +1,12 @@
|
||||
# Implementation plan
|
||||
|
||||
1. **Clock formats.** A `trimmed` ISO format, `fixedWidth` on a clock reading, fitting a format to another value, and
|
||||
calendar digits, in a clock formats module.
|
||||
2. **Composed strings.** A module that indexes a create's candidate parts once, segments a string, checks the type's
|
||||
other entities and an update's before value, and spells the string from written fields.
|
||||
3. **Explaining.** Create explanation composes unexplained strings after templates, learns trimmed clocks from the
|
||||
type and generated integers; update explanation composes unsent changes after the existing rules.
|
||||
4. **Later calls.** A later create or update widens the plan's clocks to a trimmed spelling it shows; a later create's
|
||||
unique value may be a generated integer.
|
||||
5. **Serving.** Derived fields are rebuilt after other rules; unique integers are seeded by length and kind.
|
||||
6. **Schema, tests and docs.** The plan schema, view, serving and unit tests, and the write-plan docs.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Explain values an application derives from other fields
|
||||
|
||||
Issue: https://github.com/morluto/rea/issues/4933
|
||||
|
||||
## Outcome
|
||||
|
||||
Environments an agent built from a prompt withheld creates in three of five applications, each for a value the write
|
||||
model could not explain, though the application derives it deterministically:
|
||||
|
||||
- Linkding's `web_archive_snapshot_url` is `https://web.archive.org/web/<creation time as YYYYMMDDhhmmss>/<sent url>`.
|
||||
The plan kept it as a constant, which pinned `url` to its recorded value, so a later create with another URL was
|
||||
refused.
|
||||
- Grafana's folder `url` is `/dashboards/f/<uid>/<slug of the title>`, which pinned `uid` the same way, and its numeric
|
||||
`id` is a large time-based integer the plan copied from the first create, which the second create's read-back
|
||||
contradicted.
|
||||
- Homebox, written in Go, spells nanosecond clocks with trailing fraction zeros dropped (`…34.38812218Z`), so a later
|
||||
create's clock did not follow the format learned from the first.
|
||||
|
||||
## Behaviour
|
||||
|
||||
- **Composed strings.** A created field no other rule explains may be a `composed` string: literal text and parts read
|
||||
from the fields other rules write, each a field's value, its slug, or a clock's calendar digits (`YYYYMMDDhhmmss`,
|
||||
`YYYYMMDD`, `YYYY-MM-DD`, `YYYY/MM/DD`, in the clock's zone). Parts are found left to right, longest first, delimited
|
||||
by non-alphanumerics; generated values may be short, other values need eight characters, as a copy does. A create
|
||||
composes a string only when at least one other entity of the type spells it with the same parts and none spells it
|
||||
otherwise, and never when one text comes from two sources serving could tell apart (two clocks the write stamps are
|
||||
one source when they spell the same zone), and never when another entity holds the field as something other than the
|
||||
parts spell, a non-string value included. An update's unsent change may be composed from members it stored as sent and
|
||||
fields it left as they were, when the same parts also spell the value before the call and no other entity spells it
|
||||
otherwise. Serving rebuilds the string from the written fields after every other rule and refuses a call when a part's
|
||||
field holds nothing to spell, and a composed field no longer pins the members it reads. A composition learned on a
|
||||
later call is checked against every earlier call, and a later create whose response spells a composed field otherwise
|
||||
is withheld.
|
||||
- **Trimmed clocks.** A clock format may be `trimmed`: its values differ only in how many fraction digits they show and
|
||||
none ends a fraction in a zero. It is learned from the type's held values of the field, from a later create, and from
|
||||
a later update; other clocks the plan stamps take the spelling too, and no clock takes it once a held entity, the
|
||||
exemplar or any call the plan accepted showed the field at a fixed width; a later call that then shows it shorter is
|
||||
withheld. `fraction` is the most digits shown, and serving drops trailing zeros and the point with them.
|
||||
- **Generated integers.** A created integer of 10 to 20 digits that no entity of the type holds, where every held entity
|
||||
holds a distinct one of the same length and JSON kind and none is a clock, is `unique`. Serving draws a seeded integer
|
||||
of that length and kind that no held entity has. One that another field of the created entity also holds, as a value,
|
||||
a number at any depth or a delimited part of a nested string, is not `unique`, and the create is withheld as
|
||||
`unexplained-change`, since seeding cannot keep the two equal. A create is also withheld when another string holds its
|
||||
digits and no `template` or `composed` rule rebuilds that string from it, or when a later create answers the field
|
||||
with anything but an integer of the same length and kind. Compare treats two such integers of one JSON kind and length
|
||||
under a `unique` rule as generated values.
|
||||
|
||||
Plans that need none of this are unchanged.
|
||||
|
||||
Later creates must retain the exemplar's unique-value family (UUID, integer with
|
||||
the same digit count and JSON kind, or opaque token). Their recorded composed
|
||||
fields must equal the values the plan rebuilds; simulation cannot replace a
|
||||
contradictory captured value and then count that replacement as confirmation.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- The prompt-built Linkding, Homebox and Grafana captures serve the creates and renames they withheld, and write
|
||||
tasks that use them pass.
|
||||
- No write tool of the 50 published captures or the earlier prompt-built captures goes from served to withheld.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Tasks
|
||||
|
||||
- [x] Read and fit trimmed clock formats, and spell calendar digits.
|
||||
- [x] Compose created strings and unsent update changes from other fields.
|
||||
- [x] Learn generated integers as unique values and serve them by length and kind.
|
||||
- [x] Widen plan clocks on later creates and updates, and spread a trimmed spelling to the plan's other clocks.
|
||||
- [x] Add view, serving and unit tests.
|
||||
- [x] Document the rules.
|
||||
- [x] Measure the prompt-built captures, the 50 published captures and the served write tasks.
|
||||
- [x] Run static, formatting, documentation and test gates.
|
||||
@@ -0,0 +1,91 @@
|
||||
# Verification
|
||||
|
||||
Executed on macOS with Node v24.22.1 at `a1edeafd` (main `79845063` plus this branch). The prompt-built recordings and
|
||||
evaluations used a local, unpushed build of this branch merged with the scenario-timing branch (#6967), whose request
|
||||
times the clock rules need from browser scenarios, on Ubuntu 24.04 VMs with snap Chromium 155. The baseline is the same
|
||||
merge without this branch.
|
||||
|
||||
## Gates
|
||||
|
||||
Follow-up on 2026-10-25, after merging main `a5bfd0440`, with Node 24.22.1 and npm 11.16.0 on macOS:
|
||||
|
||||
- Environment write/compare source tests: 26 files, 839 passed.
|
||||
- Analysis-view CLI/MCP, environment served-write MCP, environment-compare CLI, managed bundle read guards, and setup
|
||||
registration prewarm boundaries: 6 distinct files, 77 passed. The managed low-headroom failure is fixed by main's
|
||||
Evidence ownership change (#7036); the setup tests share host preparation to satisfy lint without removing cases.
|
||||
- `check:fast`, `format:check`, and `docs:check`: passed.
|
||||
- New capture-to-plan regressions reproduce missing composition parts on earlier updates (null and structured sources)
|
||||
and fixed-width clock evidence from either the selected entity or another held entity. Rule tests exercise several
|
||||
separator/zone groups in both orders, choosing each group's widest trimmed peer while preserving fixed-width fields.
|
||||
- Compare uses the domain clock matcher on parsed readings. Tests cover shorter fractions, absent fractions, maximum
|
||||
width, trailing zeros, separators, zones, and response-entity scope; the compiled CLI accepts a Go-style create whose
|
||||
live clocks have eight digits and served clocks have none, preserving all input Evidence files.
|
||||
- The original application's recordings and model evaluations below were not rerun in this follow-up.
|
||||
|
||||
Original author validation:
|
||||
|
||||
- `npm run -s typecheck`, `lint`, `format:check`, `docs:check`, `mcp-catalog:check`, `verify:test-discovery`,
|
||||
`scan:todos` and `npx knip`: passed.
|
||||
- `npx vitest run --project domain --project services --project adapters`: 17,246 passed, 25 skipped, 1 failed ("stops
|
||||
Inspector target authorization after the current stat settles"), which fails the same way on main in this environment.
|
||||
- `npm run test:prepare`, then `npm run test:focused --` the environment build, export and serve CLI boundaries, the
|
||||
analysis-view CLI and MCP boundaries, the environment serve, served-writes and bundle MCP boundaries, MCP schema
|
||||
validation and output schema constraints: 10 files, 82 passed.
|
||||
|
||||
## Tests
|
||||
|
||||
- View: Go-spelled tag creates and renames are served with a trimmed clock; bookmark creates are served with a composed
|
||||
snapshot address and accept any URL; folder creates and renames are served with a composed address and a generated
|
||||
integer id.
|
||||
- Serving: a served tag create spells its clocks without trailing zeros; a served bookmark create composes its snapshot
|
||||
address from the served clock and the sent URL; a served folder create gets a fresh sixteen-digit id and an address
|
||||
that follows its uid and title, and a rename moves the address.
|
||||
- Unit: fitting clock formats (trimmed, fixed width, separator and zone), calendar digits, seeded integers, generated
|
||||
integer detection, composition and its refusals (one call alone, a held entity spelling otherwise, two sources with
|
||||
one text, a member stored otherwise than sent, a value the parts do not spell before the call), and a wide body
|
||||
composed within the existing time limit.
|
||||
|
||||
## Write plans over recorded captures
|
||||
|
||||
Every write plan of 69 captures, built by the baseline and by this branch: the 50 published captures, 18 captures
|
||||
Claude Code recorded from a prompt for the prompt branch's checks, and one new timed Grafana recording.
|
||||
|
||||
| Captures | Write plans | Served on the baseline | Served on this branch |
|
||||
| ------------------------- | ----------- | ---------------------- | --------------------- |
|
||||
| 50 published | 187 | 68 | 68 |
|
||||
| 19 recorded from a prompt | 120 | 56 | 61 |
|
||||
|
||||
- No write goes from served to withheld. The five gains are Linkding's bookmark create, Homebox's tag create, and
|
||||
Grafana's folder create in two recordings and folder rename in the timed one.
|
||||
- Published plans change only where a value is now composed: Linkding's bookmark create and BookStack's book create and
|
||||
update (`description_html` as `<p>` around the description) now accept the member they read.
|
||||
- Unchanged by design: Grafana's published folder, whose uid and title slug are the same text, so its address names two
|
||||
sources; Ghost's post update, whose `lexical` keeps the `derived` rule that explained it before; BookStack's page HTML
|
||||
and Jellyfin's playlist path, which other pages and playlists of the capture spell otherwise.
|
||||
- What still withholds a create the composed value now explains: a later Linkding create that sends a member the first
|
||||
did not (`shared`), Homebox item creates that differ from the first location create, and a Linkding create whose
|
||||
read-back lists `tag_names` sorted.
|
||||
|
||||
## Served write tasks
|
||||
|
||||
The same timed captures, rebuilt by the baseline and by this branch, with the models `fireworks_ai/gpt-oss-120b` and
|
||||
`fireworks_ai/glm-5p3-flash`. Create tasks are new; recorded write tasks are the applications' existing ones with entity
|
||||
counts taken relative to each bundle's state.
|
||||
|
||||
| Application | Tasks | Baseline (gpt-oss, GLM) | This branch (gpt-oss, GLM) |
|
||||
| ----------- | ---------------------------------- | ----------------------- | -------------------------- |
|
||||
| Linkding | 2 bookmark creates | 0/2, 0/2 | 2/2, 2/2 |
|
||||
| Linkding | 3 recorded (update, notes, delete) | 3/3, 3/3 | 3/3, 3/3 |
|
||||
| Homebox | 2 tag creates | 0/2, 0/2 | 2/2, 2/2 |
|
||||
| Homebox | 2 recorded deletes | 0/2, 0/2 | 0/2, 0/2 |
|
||||
| Grafana | folder create and rename | not served | 1/2, 2/2 |
|
||||
| Grafana | 2 recorded deletes | | 2/2, 2/2 |
|
||||
|
||||
- Smoke and retrieval match on both builds (Linkding 9/9 and 8/9, Homebox 3/3 and 3/3); Grafana's new recording passes
|
||||
its smoke task and 9/9 retrieval with both models.
|
||||
- gpt-oss missed the Grafana rename by sending the update without the title it was asked to change, ten times; the
|
||||
served environment refused each call with the members it accepts.
|
||||
- Homebox's recorded deletes fail on both builds: this recording's served entity list holds its locations only, so the
|
||||
models never find the item to delete.
|
||||
- The created bookmarks' snapshot addresses and the created and renamed folders' addresses are checked from the
|
||||
environment's state.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Implementation plan
|
||||
|
||||
1. **Read-backs.** A create's observation looks for the first read of its entity before the next write; an item read's
|
||||
fields and clock spellings are taken when it agrees otherwise, and the read observes; a list read adds fields only. The plan's observation records the
|
||||
read and its shape, and serving merges the same objects.
|
||||
2. **Sent members.** An update's sent member stored as a fresh clock after it sent back the held one is a `clock`; one
|
||||
stored as the slug of another member stored as sent is a `slug`.
|
||||
3. **Schema, tests and docs.**
|
||||
@@ -0,0 +1,47 @@
|
||||
# Writes whose answers show the entity otherwise than reads, and whole-object updates
|
||||
|
||||
Issue: https://github.com/morluto/rea/issues/4933
|
||||
|
||||
## Outcome
|
||||
|
||||
Agents built environments from a prompt for five applications the prompt flow had never seen (Vikunja, Ghost, EspoCRM,
|
||||
Paperless-ngx and Miniflux). Two write-model gaps withheld most of their creates and edits:
|
||||
|
||||
- **A create's answer is another view of the entity than its reads.** Paperless answers a tag create without the
|
||||
`document_count` its item read shows. EspoCRM answers a contact create without nine fields its item read shows.
|
||||
Vikunja answers a label create with a partial `created_by` and clocks to the nanosecond, where reads show the full
|
||||
user and whole seconds. Ghost answers a tag create without the `count` its list shows. The state built the entity from
|
||||
the answer alone, so the read-back contradicted the create.
|
||||
- **A whole-object update sends back what the application derives.** Paperless's UI sends the tag's old `slug` with a
|
||||
new name, and the application slugs the name anew. Ghost's UI sends the `updated_at` it read, and the application
|
||||
stamps a new one. The update was withheld as `unexplained-change`, or its first call's value was pinned so that later
|
||||
calls conflicted.
|
||||
|
||||
## Behaviour
|
||||
|
||||
- **Read-backs.** When the first read of a created entity before the next write shows fields the create's answer lacks,
|
||||
or spells its clocks to another precision, the observed entity takes them, and the plan records the read
|
||||
(`observation.read`, with its shape).
|
||||
- An item read that agrees with the answer otherwise lends its fields and its clock spellings (whole seconds of a
|
||||
nanosecond clock, or microseconds of one), and no longer confirms the create. One that disagrees on another value,
|
||||
or omits an answer field its item view renders, still checks the create, which it contradicts.
|
||||
- A list read, which may show fewer keys of an object, only adds fields the answer lacks and still checks the create.
|
||||
- A read that shows nothing new leaves the observation as it was and still confirms the create.
|
||||
- Serving merges the same two recorded objects, so a served create answers in its answer's shape and stores the
|
||||
fields of both. Generated clocks in the create answer retain that answer's recorded precision independently of
|
||||
the stored read-back precision.
|
||||
- **Restamped clocks.** An update member sent back as the clock the entity held, and stored as a fresh clock near the
|
||||
call, is a `clock` rule, accepted with any value.
|
||||
- **Re-derived slugs.** An update member stored as the slug of another member the call stored as sent is a `slug` rule
|
||||
from that member, accepted with any value.
|
||||
- **Compatible saves.** A call that echoes an already-derived slug or holds a clock in its own recorded second does
|
||||
not conflict with a demonstrated derivation, in either call order. A slug derivation must reproduce earlier saves
|
||||
as well; an explicit custom slug that disagrees still withholds the update.
|
||||
|
||||
Plans and views that need none of this are unchanged.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- The held-out recordings serve EspoCRM's contact create and Paperless's tag edit.
|
||||
- A create whose item read-back disagrees with its answer on another value is still contradicted.
|
||||
- No write tool of the 69 recorded captures goes from served to withheld.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Tasks
|
||||
|
||||
- [x] Take a create's read-back fields into its observation, and serve the same merge.
|
||||
- [x] Learn restamped clocks and re-derived slugs for sent update members.
|
||||
- [x] Add view and serving tests that fail without the change.
|
||||
- [x] Document the observation member and the rules.
|
||||
- [x] Measure the held-out and recorded captures.
|
||||
- [x] Run static, formatting, documentation and test gates.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Verification
|
||||
|
||||
Executed on macOS with Node v24.22.1 at `2f807efb` (main `8e6a9578` and this branch).
|
||||
|
||||
## Gates
|
||||
|
||||
- `npm run check` (build, typecheck, format, lint, typed lint, module boundaries, architecture guards) and
|
||||
`npm run docs:check`: passed.
|
||||
- `npx vitest run --project domain --project services --project adapters`: 17,624 passed, 25 skipped, 1 failed ("stops
|
||||
Inspector target authorization after the current stat settles"), which fails the same way on main in this environment.
|
||||
- `npm run test:focused --` the analysis-view, environment build, compare, export and serve CLI boundaries, the
|
||||
analysis-view, environment bundle, serve and served-writes MCP boundaries, tool schema validity, output schema profile
|
||||
and MCP schema validation: 12 files, 106 passed.
|
||||
- The eight new view and serving tests fail on main and pass here.
|
||||
|
||||
## Recorded captures
|
||||
|
||||
The 69 recorded captures and the five held-out agent recordings, built as environment-state views on main and on this
|
||||
branch:
|
||||
|
||||
| Captures | Served on main | Served here | Served to withheld |
|
||||
| ----------------------------------------- | -------------- | ----------- | ------------------ |
|
||||
| 69 recorded | 132 | 132 | none |
|
||||
| 5 held-out (20 write tools with a target) | 8 | 10 | none |
|
||||
|
||||
- **Now served:** EspoCRM's contact create (its read-back adds nine fields the answer lacks) and Paperless's tag edit
|
||||
(the stale slug is slugged anew from the name).
|
||||
- **Past the read-back, withheld by other gaps:**
|
||||
- Paperless's tag create: its list's `all` member lists every id, which state rendering does not model.
|
||||
- Ghost's tag create and edit: the state does not list the created tags in Ghost's tag lists.
|
||||
- **Still contradicted:** Vikunja's label create, whose item read-back shows a fully loaded `created_by` where the answer
|
||||
showed a partial one.
|
||||
- Three plans of the recorded captures (Airbyte, BookStack, PhotoPrism) now record a read-back; their served state is
|
||||
unchanged.
|
||||
|
||||
## Served write tasks
|
||||
|
||||
The held-out round's write tasks, rebuilt from the same agent recordings with this branch, with
|
||||
`fireworks_ai/gpt-oss-120b` and `fireworks_ai/glm-5p3-flash`:
|
||||
|
||||
| Application | Main (gpt-oss, GLM) | This branch (gpt-oss, GLM) |
|
||||
| ---------------------------------------------- | ------------------- | -------------------------- |
|
||||
| EspoCRM contacts: create, update email, delete | 0/3, 0/3 | 1/3, 1/3 (create) |
|
||||
| Paperless tags: create, rename, delete | 1/3, 1/3 | 1/3, 2/3 (rename with GLM) |
|
||||
|
||||
- gpt-oss's rename did not apply. Its trajectory was not kept, so the cause is unconfirmed. A served `PUT` requires
|
||||
every member the recorded call sent (about 14 here), and GLM sent them all.
|
||||
- EspoCRM's served update accepts only the members its recording edited, so the email change is refused, as on main.
|
||||
+169
-12
@@ -140,6 +140,72 @@ static tasks. Both lint caches include the website, documentation configuration,
|
||||
and experiment code scanned by the checker; a regression compares the actual
|
||||
lint inventory with Turbo's resolved inputs.
|
||||
|
||||
Static checks locate candidates; they do not establish semantic ownership.
|
||||
`jscpd` finds token clones, and Knip counts test references as uses. Module
|
||||
boundaries and architecture guards enforce their declared import and source
|
||||
rules. Passing them does not prove that two parsers agree, a wrapper is still
|
||||
needed at runtime, or cleanup has one owner.
|
||||
|
||||
The lint gate treats `no-async-promise-executor`, `no-unreachable`, and
|
||||
`no-unsafe-optional-chaining` as errors. These catch lost async executor failures,
|
||||
dead code, and unsafe uses of optional-chain results. `no-unsafe-finally` remains
|
||||
a warning because cleanup paths can deliberately throw aggregated failures;
|
||||
review whether they preserve the primary failure. Two local rules
|
||||
cover narrower observed failures: `rea/no-buffer-deep-equality` flags Vitest
|
||||
deep comparisons of Buffers from recognized constructors and file reads,
|
||||
including simple `const` aliases; `rea/cancellation-cause` requires an explicit
|
||||
cause when a directly checked caller `AbortSignal` is converted to an
|
||||
`AnalysisCancelledError`. The rules do not infer arbitrary Buffer types,
|
||||
follow indirect cancellation helpers, or prove that an explicit cause equals
|
||||
the caller's reason. Keep focused boundary and lifecycle tests for those facts.
|
||||
|
||||
`rea/no-evidence-json-preadmission` rejects direct generic JSON parses in
|
||||
`createEvidence` payload expressions; it does not analyze intermediate values.
|
||||
`rea/no-positional-tool-contract` rejects catalog indices used to select
|
||||
operation names, schemas or types; display-only selection remains allowed.
|
||||
`rea/no-fixture-root-cleanup` follows `const` fixture-root aliases into teardown
|
||||
deletion; mutable bindings and mixed root arrays remain outside its scope.
|
||||
|
||||
`rea/no-shared-module-mock` resolves Vitest import bindings before rejecting
|
||||
module mocks in projects without per-file isolation. The test-project definitions
|
||||
own isolation for Vitest and lint; the executable gate checks the resolved policy
|
||||
and runs the same collector. Aliases and namespace imports count; comments,
|
||||
shadowed locals, ordinary methods and `spyOn` do not. Negative CLI probes use an
|
||||
external temporary project so concurrent repository checks cannot discover them.
|
||||
|
||||
Focused `.only` declarations, broken assertion promise chains, and unused disable
|
||||
directives are errors. `vitest/valid-expect` warns (`maxArgs: 2`) because valid stored and
|
||||
collected assertions can be awaited later. `import/no-duplicates` warns with
|
||||
`preferInline: false`.
|
||||
|
||||
`no-useless-escape` keeps intentional `\-` regex escapes because advertised
|
||||
patterns must also compile in `v` mode; the catalog regression checks every
|
||||
advertised pattern.
|
||||
|
||||
Fast lint stays syntax-based. `npm run lint:typed` runs serially with pinned
|
||||
`oxlint-tsgolint` 7.0.2005 and records native plus typed diagnostics in
|
||||
`.cache/static-checks/typed-oxlint.json`. Production `src` treats
|
||||
`no-misused-promises` as an error; tests/fixtures warn. `no-floating-promises` is
|
||||
an error throughout the lint inventory. `return-await` is an error in
|
||||
error-handling scopes so asynchronous completion stays inside its catch or
|
||||
cleanup boundary. Exhaustiveness is an error when a union member has neither a
|
||||
case nor a default. Intentional defaults count as handling the remaining union;
|
||||
new members covered by those defaults therefore require behavioral review.
|
||||
|
||||
Two HTTP/3 source-test files allow the runner-owned `node:test` registration API;
|
||||
ordinary unowned promises in those files still fail. The authored code-split
|
||||
recovery input is exempt from floating-promise policy because it is analysis
|
||||
input, rather than application orchestration. Preserve framework-owned callbacks
|
||||
such as an awaited `Promise.finally`; inspect the actual completion owner before
|
||||
changing a callback solely to satisfy its declared `void` return type.
|
||||
|
||||
`npm run knip:production` writes advisory candidates to
|
||||
`.cache/static-checks/production-reachability.json`; execution/report errors fail.
|
||||
Ordinary Knip remains. `check`, `check:pr`, and `check:ci:deep` run reachability
|
||||
then typed lint sequentially after cached work. Typed lint builds the project
|
||||
type graph and stays separate from fast feedback. Its default Go settings are
|
||||
`GOMAXPROCS=2` and `GOMEMLIMIT=1024MiB`; caller-selected settings take precedence.
|
||||
|
||||
Routine iterations and rebases need focused regressions and relevant checks.
|
||||
Before handing off a PR, run `npm run check` and generated-document checks when
|
||||
applicable; CI owns the full suite and coverage. Use the full local gate for
|
||||
@@ -169,6 +235,14 @@ with the consumer preloaded as well as alone. For simulated protocol traces,
|
||||
use an acknowledged event barrier before asserting complete observations;
|
||||
a zero-duration capture does not guarantee queued events have arrived.
|
||||
|
||||
Place faults at the semantic boundary under test: the selected read, EOF, hash
|
||||
completion, or cleanup attempt. Global call ordinals can shift during setup or
|
||||
refactoring without reaching that phase. For controlled asynchronous gates, use
|
||||
`awaitAcknowledgement` from `tests/fixtures/lifecycleAcknowledgement.ts` to race
|
||||
the acknowledgement against triggering-operation settlement and a bounded
|
||||
deadline. Register release gates before launch, then release and join launched
|
||||
work in `finally` before restoring mocks, even when the barrier fails.
|
||||
|
||||
Focused contract tests should import the named contract family they exercise;
|
||||
reserve the full tool catalog for catalog-wide assertions. Resolve contracts by
|
||||
operation name rather than array position.
|
||||
@@ -200,6 +274,10 @@ work from fixture generation, schema compilation, subprocess startup and cleanup
|
||||
Choose elapsed, CPU and process-family memory limits before the run; a worker
|
||||
count or test timeout alone does not enforce those limits.
|
||||
|
||||
Keep capacity fixtures intact when diagnosing a test failure. For byte equality
|
||||
of large buffers, use `Buffer.equals` or `Buffer.compare`; a generic structural
|
||||
diff can allocate far more than the production operation.
|
||||
|
||||
Remove demonstrated repeated work while preserving distinct assertions and
|
||||
resource ownership. Share immutable fixture preparation where safe; keep fresh
|
||||
sessions when lifecycle or concurrency is the behavior under test. Compare the
|
||||
@@ -308,6 +386,23 @@ Trivial helper assertions need no replacement. Remove abandoned production
|
||||
scaffolding only after checking runtime and verifier consumers, including
|
||||
compiled imports.
|
||||
|
||||
Assert error codes, structured limit facts, source provenance, and observed
|
||||
recovery behavior instead of freezing explanatory sentences or banning obsolete
|
||||
phrases. Keep exact checks for protocol values, setting and parameter names,
|
||||
caller data, and diagnostic provenance. To verify guidance delivery, compare the
|
||||
consumer with the owning formatter or constant, alongside independent assertions
|
||||
for the behavior and facts; shared text alone is not a correctness oracle.
|
||||
Documentation wording belongs in review, not substring tests of source files.
|
||||
When replacing prose assertions, verify that a harmless wording change passes
|
||||
and an incorrect budget or lost fact still fails.
|
||||
|
||||
After an owner changes, move fault injection and event spies to the path the
|
||||
production consumer actually uses, and assert that the intended boundary was
|
||||
reached. Keep independent expectations from authored input, producer output, or
|
||||
an external oracle; comparing two paths through the same replacement algorithm
|
||||
does not preserve the old oracle. A negative probe inside a conditional or loop
|
||||
must assert that it ran.
|
||||
|
||||
Use representative scenarios rather than Cartesian matrices of equivalent
|
||||
cases. Share setup without hiding inputs or expected evidence. For successful
|
||||
`Result` values, throw the typed error before asserting the value; a preceding
|
||||
@@ -335,6 +430,34 @@ that a real loader accepts the artifact. If multiple producer revisions share a
|
||||
format identifier but differ in behavior, record that ambiguity rather than
|
||||
using one installed version as an oracle for all of them.
|
||||
|
||||
### Boundary regressions
|
||||
|
||||
For parser, inventory, snapshot, or projection fixes, select the applicable
|
||||
cases below. Assert the producing boundary's facts and their meaning in affected
|
||||
consumers. Add [lifecycle regressions](#lifecycle-regressions) when resource
|
||||
ownership or finalization changes; a single malformed fixture cannot establish
|
||||
the whole contract.
|
||||
|
||||
| Failure class | Regression that catches it |
|
||||
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Format assumptions | Pair valid padding, offsets, or names containing delimiters with malformed input. Identify the producer revision when behavior varies by version; an unrelated runtime API's range is not an on-disk format limit. |
|
||||
| Raw identity and normalization | Exercise allowed empty or unusual source names/metadata keys and values that normalize to one lookup or display value. Preserve admitted raw spelling under the existing redactions for transport credentials and explicitly marked sensitive values. Distinguish local paths, file URLs and HTTP paths; assert source identity through consumers. |
|
||||
| Missing, empty, and unknown values | Distinguish omitted, null, empty, zero, and false where the format admits them. Pair a malformed optional field with usable sibling facts; assert uncertainty without inventing absence or discarding the row. |
|
||||
| Completed work followed by failure | Complete one input, then fail or cancel a later input. Assert earlier digests, metadata, and parsed facts survive with the later failure reason and accurate processed/unprocessed coverage. For cancellation, assert the exact caller abort reason; where a primary failure was already produced, assert it survives. |
|
||||
| Independent limits and failures | Trigger a sample/output limit alongside a read or parse failure. Assert both dimensions survive; reaching a limit does not erase sampling failures or prove complete coverage. |
|
||||
| Representation expansion and capacity | Keep a valid set near the limit and a large malformed input followed by a valid input. Cover JSON escaping, expanded paths or decoded rows as applicable. Assert serialized bounds, retained identities, later valid results, and unknown omissions without charging discarded interpretations as retained output. |
|
||||
| Snapshots, caches, and comparison | Vary metadata that affects the result while keeping payload bytes equal. Exercise historical records with newly introduced fields omitted, current caller inputs on replay, and reuse across differing selections. Assert correct invalidation or unknown coverage, current request provenance, and unchanged saved Evidence and IDs. |
|
||||
| Public success and error delivery | Exercise affected public CLI/MCP paths, including partial Evidence on failure. Assert retained facts and named contracts; retrieve partial Evidence when MCP returns a reference. If SDK cancellation suppresses the reply, check server settlement and any retained Evidence. An error-code assertion alone misses lost observations. |
|
||||
|
||||
For partial Evidence delivery, verify that same-session readback preserves
|
||||
identity and provenance without rerunning the provider. Include a no-writer
|
||||
control that keeps completed Evidence inline and preserves the original failure.
|
||||
|
||||
When producer order is unspecified, synchronize fixtures on the observed request
|
||||
or completion event. A filename, generated key, or fixed delay does not prove
|
||||
which item completed before cancellation. Preserve real producer ordering and
|
||||
use a controlled seam for the phase that needs deterministic failure.
|
||||
|
||||
Packed bridge validation uses Python's standard-library AST and tar readers
|
||||
with `-I -S`: caller-local modules, `PYTHONPATH`, and site startup hooks must
|
||||
not replace those readers or execute during validation. Controlled `ast.py`
|
||||
@@ -352,6 +475,19 @@ pass that definition through the SDK's `toolDefinition` call option. This keeps
|
||||
the selected output-schema validation without eagerly compiling unrelated
|
||||
catalog schemas. Re-read definitions if the server advertises a changed list.
|
||||
|
||||
For exact nested input constraints, select the full profile explicitly or use
|
||||
the full-SDK conformance suite that owns the field. Compact workflow tests
|
||||
should assert canonical failure categories and provider-call controls where
|
||||
rejection before provider work matters. Do not make today's omission of a
|
||||
constraint a compact-profile invariant.
|
||||
|
||||
When a file repeatedly validates the same MCP schemas, use
|
||||
`createMcpSchemaValidation` for file-owned preparation and clear it in
|
||||
`afterAll`. Its content keys include the complete schema, and the SDK provider
|
||||
keeps dialect and format validation. Keep sessions and connections scoped to
|
||||
each test; share immutable schema preparation through the fixture's validator
|
||||
dependency.
|
||||
|
||||
Artifact verification uses `artifactMcpCall` for both lifecycle and analysis
|
||||
calls. A fresh SDK client has no held tool listing, so a bare `callTool` can
|
||||
skip output-schema validation. The helper discovers actual advertised
|
||||
@@ -384,15 +520,16 @@ the affected owners and applicable scenarios below. Include temporary snapshots
|
||||
and one-shot commands. Use public CLI/MCP workflows where practical and a
|
||||
controlled production seam for interleavings that cannot be reproduced reliably.
|
||||
|
||||
| Scenario | Assertion and control |
|
||||
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Target admitted; startup still pending | Status preserves the readiness distinction and observed startup activity; readiness requires the provider's documented proof. |
|
||||
| First handshake is late, absent, invalid, or recovered during close | Initial and recovery paths retain the same authenticated identity facts. Foreign run/token controls cannot authorize cleanup; authenticated idle/native work survives the unauthenticated listener deadline. |
|
||||
| Launch returns an error with a valid process handle | Cover already-exited and still-live owned processes. Verify observed exit separately from creation; failed termination or unavailable identity remains unresolved. |
|
||||
| Cancellation arrives after workload exit, during result reads or temporary-root removal | Return typed cancellation with available partial observations and actual cleanup status. The non-aborted control succeeds; existing primary/cleanup failures keep their precedence. |
|
||||
| Children have mixed health or cleanup outcomes | Missing peer health does not erase an observed failure or activity. Idle plus unknown stays unknown. Retry a failed child without closing a successful peer again, including after a close-option change. |
|
||||
| Cleanup fails after success or primary failure | Public output retains the completed observation or primary diagnostic, remaining resources, and owner recovery action. Restore the denied resource and verify the owning cleanup retry. |
|
||||
| Target exits without an acknowledgement, or transport disappears while it stays alive | Positive owned exit proof resolves target lifetime; other resources still need cleanup. Lost transport alone does not prove exit. Verify unrelated/attached targets survive and same-connection recovery works when supported. |
|
||||
| Scenario | Assertion and control |
|
||||
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Target admitted; startup still pending | Status preserves the readiness distinction and observed startup activity; readiness requires the provider's documented proof. |
|
||||
| First handshake is late, absent, invalid, or recovered during close | Initial and recovery paths retain the same authenticated identity facts. Foreign run/token controls cannot authorize cleanup; authenticated idle/native work survives the unauthenticated listener deadline. |
|
||||
| Launch returns an error with a valid process handle | Cover already-exited and still-live owned processes. Verify observed exit separately from creation; failed termination or unavailable identity remains unresolved. |
|
||||
| Cancellation arrives after workload exit, during result reads or temporary-root removal | Return typed cancellation with available partial observations and actual cleanup status. The non-aborted control succeeds; existing primary/cleanup failures keep their precedence. |
|
||||
| Children have mixed health or cleanup outcomes | Missing peer health does not erase an observed failure or activity. Idle plus unknown stays unknown. Retry a failed child without closing a successful peer again, including after a close-option change. |
|
||||
| Cleanup fails after success or primary failure | Public output retains the completed observation or primary diagnostic, remaining resources, and owner recovery action. Restore the denied resource and verify the owning cleanup retry. |
|
||||
| Cleanup owners recover after an error was returned | Retain the original child and aggregate errors and project them after each owner releases. A successful peer stays released; the aggregate remains incomplete until all owners release. Preserve the first cause even when rejection is `undefined`. |
|
||||
| Target exits without an acknowledgement, or transport disappears while it stays alive | Positive owned exit proof resolves target lifetime; other resources still need cleanup. Lost transport alone does not prove exit. Verify unrelated/attached targets survive and same-connection recovery works when supported. |
|
||||
|
||||
Shared process-supervision controls separate leader exit from output closure.
|
||||
Cover output during held or failed cleanup, bounded retention and drain timeout,
|
||||
@@ -406,6 +543,11 @@ owned cleanup seams while preserving real cleanup where feasible. Check server
|
||||
settlement and retained resources even if the MCP SDK rejects the client promise
|
||||
before the handler finishes. A cancellation during active execution does not
|
||||
cover cancellation during final cleanup.
|
||||
For ZIP extraction, replace the pathname after inventory reader close while the
|
||||
admitted root descriptor remains owned. Verify that identical member bytes in a
|
||||
different carrier, a directory, or a missing pathname cannot switch the
|
||||
materialized source. Exercise descriptor close failure and retry before output
|
||||
publication.
|
||||
Use explicit abort reasons and assert their identity at admission and after
|
||||
provider completion, alongside retained observations and cleanup state.
|
||||
|
||||
@@ -554,7 +696,11 @@ creation outside the workspace seam and its narrowly documented boundary/package
|
||||
exceptions.
|
||||
|
||||
The shared temporary-directory and workspace helpers preserve caller labels
|
||||
under an owned `rea-` prefix when needed. `npm run verify:test-temp-hygiene`
|
||||
under an owned `rea-` prefix when needed. Their
|
||||
[cleanup ownership differs](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#temporary-files-and-diagnostics):
|
||||
`createTestTempDirectory` registers `onTestFinished` deletion, while
|
||||
`createTestWorkspace` requires caller cleanup. Close resources before removing
|
||||
their directory. `npm run verify:test-temp-hygiene`
|
||||
uses the same ownership rule to find retained fixtures under a private temporary
|
||||
root. Third-party caches remain outside that ownership rule.
|
||||
|
||||
@@ -1862,12 +2008,16 @@ coverage limits.
|
||||
|
||||
### Offline binary layout
|
||||
|
||||
`npm run verify:binary:layout` requires Linux x64, GCC/binutils, absolute
|
||||
`npm run verify:binary:layout` requires Linux x64, GCC/binutils, the
|
||||
`i686-linux-gnu`, `arm-linux-gnueabi`, `mips-linux-gnu` and `mipsel-linux-gnu`
|
||||
cross `gcc`/`readelf` toolchains, absolute
|
||||
`REA_PWNTOOLS_PYTHON` with pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2 and
|
||||
absolute `REA_VERIFY_STRACE_COMMAND`. It compiles ephemeral source-owned ELF
|
||||
fixtures and checks public CLI/MCP, lossless addresses/names, file ranges,
|
||||
mitigation inferences, malformed/unsupported input, original file hashes and
|
||||
released process ownership. Exec syscall tracing must identify only the declared
|
||||
released process ownership. Cross-compiled ELF32 executables and relocatable
|
||||
objects check the reported class, machine and byte order, and every section
|
||||
name, address, offset and size against GNU `readelf`. Exec syscall tracing must identify only the declared
|
||||
Node/Python launchers; no target binary is executed. Core/debugger claims need
|
||||
separate verification lanes. Pass an installed package entrypoint as the script's
|
||||
first argument to verify packaging independently of the checkout.
|
||||
@@ -1975,6 +2125,13 @@ Codex CLI. The lane runs sequentially; select scenarios with
|
||||
[agent evaluation reference](client-verification.md#agent-evaluation-and-conformance-records)
|
||||
contains isolation, authentication cleanup, grading, and conformance boundaries.
|
||||
|
||||
The Notes evaluation packages `website/public/examples/notes-electron/`. When
|
||||
changing those example bytes, run `tests/boundary/mcp/agentKnownAnswerMcp.test.ts`
|
||||
and check its rubric in `tests/evaluation/model-evals/AgentEvaluationFixtures.ts`.
|
||||
Preserve exact source facts and the coverage reported by the analysis. If a
|
||||
source change crosses an unresolved static-analysis boundary, keep that
|
||||
uncertainty and a negative control rejecting claims of complete coverage.
|
||||
|
||||
## Frida instrumentation
|
||||
|
||||
`npm run verify:frida` builds REA, spawns a short-lived local Node.js fixture,
|
||||
|
||||
+70
-12
@@ -53,11 +53,11 @@ satisfy. A plan without execution does not establish runtime behavior.
|
||||
- Use strict inputs with explicit target identity and required fields. Preserve
|
||||
meaningful target, action, capture, and output choices; remove ignored options
|
||||
and repeated permission declarations.
|
||||
One-shot CLI provider inspections retain the caller's exact target spelling
|
||||
in Evidence `parameters.input_path`; the subject keeps the canonical inspected
|
||||
file identity. Keep request provenance separate from provider arguments.
|
||||
Snapshot entries bind Evidence parameters to provider queries, so changes to
|
||||
snapshot-backed request provenance must preserve that cache contract.
|
||||
One-shot binary CLI analysis retains the caller's verbatim target spelling in
|
||||
Evidence `caller_input_path`, including a result loaded from a snapshot. The
|
||||
subject keeps the inspected file identity. Provider inspections also retain
|
||||
the established `parameters.input_path` field for existing consumers. Keep
|
||||
request provenance separate from provider arguments and snapshot queries.
|
||||
- Return the requested facts inline with artifact/provider identity, source
|
||||
locations, Evidence references, coverage, and relevant confidence and
|
||||
limitations. Use supported selection, summaries or views when they answer
|
||||
@@ -85,6 +85,21 @@ satisfy. A plan without execution does not establish runtime behavior.
|
||||
|
||||
### Interpret boundary facts once
|
||||
|
||||
The format or protocol reader owns interpretation. Pass its parsed facts and
|
||||
explicit uncertainty to consumers; do not make adapters reparse display text,
|
||||
reconstruct omitted values, or infer success from missing diagnostics.
|
||||
Client registration inspections, for example, carry command identity and
|
||||
activation independently of readiness. An owned registration may be disabled
|
||||
or stale; unresolved ownership remains unknown. Consumers use those facts
|
||||
instead of reclassifying the raw command.
|
||||
|
||||
A completed read can establish byte identity even when decoding or format
|
||||
validation fails. State which representation a digest identifies, such as file
|
||||
bytes, Fetch response-body bytes, or UTF-8 text. Preserve admitted source values
|
||||
separately from normalized lookup and display values. When the retention
|
||||
contract keeps only a digest and size, state that the bytes or text are
|
||||
unavailable; the digest does not establish format validity.
|
||||
|
||||
Comparison needs separate proofs for lookup, correspondence, content, and
|
||||
coverage. A hash cannot supply the proof that two retained values belong to
|
||||
corresponding objects.
|
||||
@@ -103,10 +118,6 @@ Complete traversal or inventory does not make unavailable values equal. A
|
||||
partial aggregate fingerprint difference cannot establish modification when
|
||||
omissions alone explain it; independently proven differences still survive.
|
||||
|
||||
The format or protocol reader owns interpretation. Pass its parsed facts and
|
||||
explicit uncertainty to consumers; do not make adapters reparse display text,
|
||||
reconstruct omitted values, or infer success from missing diagnostics.
|
||||
|
||||
Keep format recognition separate from provider admission. A recognizable header
|
||||
can supply useful observations while later structure remains unresolved or a
|
||||
provider cannot accept the artifact. Report the reason at the affected boundary
|
||||
@@ -133,6 +144,12 @@ establish each transition. Include temporary files and one-shot commands, as
|
||||
well as persistent engine sessions. Use the existing provider contracts;
|
||||
providers need not expose identical state machines.
|
||||
|
||||
In multi-stage work, retain each completed, admitted observation before awaiting
|
||||
the next stage. Later reads, validation, cancellation, or finalization can fail
|
||||
without erasing those facts. Carry them through typed failure outcomes with
|
||||
explicit coverage of unperformed work; do not construct a successful result for
|
||||
stages that never ran.
|
||||
|
||||
- Keep target admission, provider readiness, active work, observed process exit,
|
||||
and resource release separate. A valid process handle, successful launcher,
|
||||
or selected target establishes only its own fact. Publish available identity
|
||||
@@ -207,6 +224,12 @@ providers need not expose identical state machines.
|
||||
applies rather than duplicate close/finally handling.
|
||||
- Give descriptor-backed reads an owner before opening the file. A rejected
|
||||
close must retain the exact handle and preserve any earlier failure.
|
||||
Carry [`openRegularFile`](https://github.com/morluto/rea/blob/main/src/filesystem/RegularFile.ts)'s
|
||||
admitted handle and initial metadata rather than restatting only to rebuild
|
||||
those facts. Keep fresh descriptor and selected-path identity checks after
|
||||
work, comparing against the original baseline instead of replacing it with
|
||||
a later stat. Preserve explicit bigint reads when ordinary admission metadata
|
||||
cannot supply the required size or timestamp precision.
|
||||
Stable file reads expose completed values separately from cancellation so
|
||||
consumers retain acquired identity facts after successful close or cleanup retry.
|
||||
If an abortable caller stops waiting, keep the underlying work in that owner's
|
||||
@@ -237,9 +260,38 @@ through the producing owner and affected CLI/MCP consumers.
|
||||
|
||||
## Implement and evaluate
|
||||
|
||||
Put provider protocols/parsing in adapters and shared CLI/MCP workflows in the
|
||||
application layer. Share Evidence provenance, unknown projection, and session
|
||||
retention semantics; matching payloads alone do not establish parity.
|
||||
Put provider wire-protocol parsing in adapters and shared CLI/MCP workflows in
|
||||
the application layer. Reusable format declarations may live in pure domain
|
||||
owners: [`peOptionalHeader.ts`](https://github.com/morluto/rea/blob/main/src/domain/peOptionalHeader.ts)
|
||||
decodes PE structure, while consumers retain machine admission, directory-address
|
||||
interpretation, and file/mapping bounds. Share Evidence provenance, unknown
|
||||
projection, and session retention semantics; matching payloads alone do not
|
||||
establish parity.
|
||||
|
||||
Let `createEvidence` own JSON validation and snapshotting of ordinary observation
|
||||
payloads. `EvidenceObservationInput` accepts semantically parsed parameters,
|
||||
results, and raw results whose JSON validity still needs admission. Keep format,
|
||||
selection, and identity validation at their producing boundary. Invalid JSON
|
||||
issues are rooted at `parameters`, `normalized_result`, or `raw_result` in the
|
||||
Evidence envelope.
|
||||
|
||||
`EvidenceObservation` retains the stronger JSON contract used by producer
|
||||
helpers and `createImmutableEvidenceSteps`. The cooperative constructor also
|
||||
requires an authenticated immutable result; preserve that ownership proof.
|
||||
For objects, proof comes from completed sealing by
|
||||
[`immutableJson.ts`](https://github.com/morluto/rea/blob/main/src/domain/immutableJson.ts);
|
||||
`readonly` or `Object.freeze` alone does not establish it. JSON primitives are
|
||||
directly eligible. Seal owned data and complete cooperative sealing before
|
||||
reusing its result.
|
||||
|
||||
When a failed or cancelled operation retains completed, valid partial Evidence,
|
||||
use the shared [`ToolResultDelivery`](https://github.com/morluto/rea/blob/main/src/server/ToolResultDelivery.ts)
|
||||
path with the session's Evidence writer. Keep the original failure and the
|
||||
named contract's target-content marking; replace the inline partial with a
|
||||
retained reference only after recording succeeds. An absent writer or a writer
|
||||
returning a failure `Result` leaves the partial inline, as does a partial that
|
||||
is not valid Evidence. Unexpected recorder exceptions propagate. An `evidence_id`
|
||||
member alone does not establish Evidence validity or retention.
|
||||
|
||||
Measure the resource before choosing a budget and enforce it before the costly
|
||||
step. Account for value alternatives, trace-frame products, retained records,
|
||||
@@ -248,6 +300,12 @@ completeness from examined records and exhaustiveness, rather than diagnostic
|
||||
prose or an empty failure list. Preserve observations independently of cleanup
|
||||
success.
|
||||
|
||||
Price mutually exclusive success and failure representations separately.
|
||||
Replace a reserved failure envelope when pricing success, and release discarded
|
||||
decoding state from retained-output accounting. Retaining additional failed-body
|
||||
text changes the capacity available to later results; treat that as a contract
|
||||
change and cover its effect on later valid items.
|
||||
|
||||
For example, if ten applicable records are discovered, eight are examined, and
|
||||
five are returned, the five returned records do not establish complete coverage
|
||||
of the ten. Keep discovered, applicable, examined, and retained scope distinct.
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Runtime analysis settings verification
|
||||
|
||||
Verified on macOS with Node 24.22.1, Java 21.0.12.1 and Ghidra 12.1.4.
|
||||
This change adds two MCP tools and 11 runtime defaults without a new dependency.
|
||||
|
||||
## Behavioral checks
|
||||
|
||||
- Built stdio MCP: a valid ASAR with an index above 16 MiB is refused. Updating
|
||||
`asar.max_header_bytes` to 32 MiB allows the unchanged full-archive inventory
|
||||
and JavaScript analysis on the same connection. Both source files are parsed.
|
||||
Reset refuses the same archive again; target close preserves the revision.
|
||||
- An Electron header cached under a higher admission remains denied after a
|
||||
decrease. No bulk extraction or source selection bypasses admission.
|
||||
- Store and deferred MCP handler tests retain the accepted snapshot across an
|
||||
overlapping update. Atomic rejection, duplicate keys, revision conflicts,
|
||||
idempotence, reset, isolation and explicit WABT argument precedence are covered.
|
||||
- The real Ghidra verifier reaches a natural one-second decompile timeout, raises
|
||||
the runtime deadline to 60 seconds, and obtains pseudocode for the same slow
|
||||
function without replacing the Program. Initial and final session run IDs
|
||||
match; subsequent analysis succeeds and cleanup completes. Its generated
|
||||
6,000-branch fixture is compiled but never executed.
|
||||
- The focused suite passed 459 tests across 22 files, with 10 platform-specific
|
||||
skips. Typecheck/lint, bridge source compilation, runtime build and generated
|
||||
documentation checks passed.
|
||||
|
||||
## Review follow-up
|
||||
|
||||
Invalid provider-local launch settings remain typed configuration failures rather
|
||||
than preventing MCP startup. Inspection reports `source: "invalid"`, omits
|
||||
`value` and `launch_value`, and includes `configuration_error`; a runtime
|
||||
replacement supplies a usable value, while reset restores the invalid launch
|
||||
state. JADX availability and execution resolve the same admitted timeout, so
|
||||
repair and reset also update capability guidance. The availability regression
|
||||
uses a fixture JAR and simulated JDK; it does not establish real JADX behavior.
|
||||
Update history omits an unknown old or new value instead of using null
|
||||
(the explicit unbounded Ghidra value).
|
||||
|
||||
Regex controls apply to IDA, Hopper and Kuna's supervised ECMAScript matcher.
|
||||
Ghidra uses its own Java matcher and is excluded from these controls. IDA's bound
|
||||
client and capability descriptions consume the same accepted configuration.
|
||||
WASM passes its effective deadline separately from caller parameters, preserving
|
||||
omitted arguments in both successful and partial Evidence. Shared error copying
|
||||
retains structured limit recovery through attribution and explicit redaction.
|
||||
|
||||
## Representative task costs
|
||||
|
||||
Measurements below precede the review follow-up; task costs were not remeasured.
|
||||
One measurement per scenario, using the built stdio server and a valid ASAR with
|
||||
an index just above 16 MiB and one small JavaScript module. Each scenario ends
|
||||
with successful full application analysis delivered as a summary. Discovery,
|
||||
failed calls and recovery are included. Fixture creation, build and tokenizer
|
||||
work are excluded. Filesystem caches were uncontrolled.
|
||||
|
||||
| Recovery workflow | Elapsed | Sampled server-family CPU | Sampled peak server-family RSS |
|
||||
| ---------------------------------------------------------------------- | ------: | ------------------------: | -----------------------------: |
|
||||
| Fresh server: fail, runtime update, retry | 3.68 s | 1.81 s | 680 MiB |
|
||||
| Warm server: fail, runtime update, retry | 0.25 s | 0.49 s | 625 MiB |
|
||||
| Launch-environment recovery: fail, close, restart with override, retry | 6.02 s | 2.85 s | 674 MiB |
|
||||
|
||||
The restart comparison uses the same current build to model the previous
|
||||
recovery workflow; it is not a benchmark of the previous commit. CPU and RSS
|
||||
are 100 ms `ps` samples of the server and descendants. Short-lived processes and
|
||||
unsampled peaks can be missed; CPU estimates exclude the client and sampler.
|
||||
These observations do not establish a general speedup or performance guarantee.
|
||||
|
||||
Tokenized with `cl100k_base` / tiktoken 0.12.0, not billed model usage. Catalog
|
||||
projections include tool name, description and input schema. The two new tools
|
||||
add 1,181 tokens; the full 229-tool projection is 59,734 tokens. The runtime
|
||||
recovery task uses 5,880 tokens for all requests and complete tool replies, plus
|
||||
1,710 for its selected tools (7,590 total), or 65,614 including full discovery.
|
||||
Warm recovery reuses discovery. The restart workflow uses 5,492 request/reply
|
||||
tokens plus two full discoveries (124,960 total). A host that caches discovery
|
||||
or loads only selected tools has a different context cost. System instructions,
|
||||
reasoning and client-specific presentation overhead are unmeasured.
|
||||
|
||||
## Coverage limits
|
||||
|
||||
The archive fixture isolates header admission; it is not the original Loom
|
||||
archive. Real engine coverage here is Ghidra on macOS. Hopper, JADX, capture
|
||||
and regex setting integration have source/boundary coverage, not a new real
|
||||
provider run for every setting. Linux/Windows engines and Bun are unverified
|
||||
for this change. Client deadlines and receive buffers remain externally owned;
|
||||
active worker values are explicitly unobserved in the settings inspection.
|
||||
@@ -71,6 +71,19 @@ selected byte SHA-256 and size, section payload ranges, full header/detail outpu
|
||||
and its UTF-8 SHA-256. Producer format drift and invalid or truncated modules
|
||||
are failures, with validation diagnostics and partial Evidence retaining
|
||||
selected byte identity and tool provenance.
|
||||
If inspection stops after selected files were read, partial Evidence also keeps
|
||||
the completed candidate file identities and glue parse observations. A stable
|
||||
read remains observed when descriptor finalization later fails; cleanup still
|
||||
requires retry. Partial Evidence for a glue read whose finalization fails
|
||||
before decoding retains its digest with `not-performed` parse status. Invalid
|
||||
UTF-8 retains the digest with failed parse status. Neither case invents
|
||||
references, and a failed file read contributes no invented facts.
|
||||
These independent observations do not establish that the selected WASM module is
|
||||
valid: partial results state whether WABT validation is unknown, rejected the
|
||||
module, or succeeded before a later inspection failure.
|
||||
CLI errors include this partial Evidence inline. MCP errors can return its
|
||||
`retained-evidence` reference in `details.partial_observation`; use
|
||||
`get_evidence_bundle` with `detail: "complete"` to retrieve the matching record.
|
||||
|
||||
Core validation remains authoritative when an optional custom section cannot
|
||||
be interpreted. WABT warning output is preserved in `custom_section_warnings`;
|
||||
@@ -102,6 +115,12 @@ actual selected deadline and these recovery controls; increasing only the client
|
||||
deadline does not change the provider budget. No server restart is required to
|
||||
change this per-call setting. Resolve incomplete cleanup before another attempt.
|
||||
|
||||
To change the default across new MCP operations, set `wasm.command_timeout_ms`
|
||||
through `update_analysis_settings`. An explicit `command_timeout_ms` still wins;
|
||||
correct that argument if it caused the timeout. Runtime defaults do not insert
|
||||
omitted arguments into Evidence parameters. See the
|
||||
[settings design](design/limits-and-recovery.md#precedence-and-lifetime).
|
||||
|
||||
For example, `rea inspect-wasm-artifact /artifacts/module.wasm
|
||||
--command-timeout-ms 60000 --format json` uses a 60-second per-command deadline;
|
||||
the equivalent MCP input is `{"path":"/artifacts/module.wasm",
|
||||
|
||||
@@ -737,21 +737,72 @@ request body must be a usable JSON object (as response bodies are usable), and
|
||||
the written entity must show in that call's response or in a later read before
|
||||
the next write. Each field of a created entity gets the first rule that
|
||||
explains it: generated `identity` (a sequence, UUID or token, by the recorded
|
||||
value's shape), `echo` of a body member, `copy` of a body string of 8 or more
|
||||
characters, a `path` value, a `slug` of a body member, a `clock` value within 10
|
||||
value's shape), `echo` of a body member (`sorted` when the application stored
|
||||
its strings in code-point order rather than as sent), `copy` of a body string of
|
||||
8 or more characters, a `path` value, a `slug` of a body member, a `clock` value within 10
|
||||
minutes of the request in its recorded format, a `sequence` one past the
|
||||
largest value of its collection or type, a `unique` UUID or opaque token, a
|
||||
`nested-clock` object whose clocks the request stamped, or a `template` around
|
||||
a generated value; other fields keep the observed value. A body that wraps one
|
||||
largest value of its collection or type, a `unique` UUID, opaque token or
|
||||
integer of 10 or more digits that no entity of the type holds (where every one
|
||||
holds a distinct integer of the same length, as time-based ids do), a
|
||||
`nested-clock` object whose clocks the request stamped, a `template` around
|
||||
a generated value, a `reference` to an entity, or a `composed` string; other
|
||||
fields keep the observed value. When the first read of a created entity before
|
||||
the next write shows fields the create's answer lacks, or its clocks to another
|
||||
precision, the observed entity takes them (`observation.read`). An item read
|
||||
that agrees with the answer otherwise lends its fields and clock spellings and
|
||||
no longer confirms the create; one that disagrees or omits an answer field its
|
||||
item view renders still checks it. Clock agreement uses the coarser recorded
|
||||
precision, including microseconds. A list read
|
||||
only adds fields the answer lacks and still checks it. Serving answers a create
|
||||
in its answer's shape and clock precision and stores the fields of both with
|
||||
the item read-back's clock precision. A `reference` is an object holding fields of the
|
||||
entity a sent member names, as the state holds it (a parent beside `parentId`):
|
||||
the member is accepted with any value, serving looks the entity up and refuses a
|
||||
call naming one the environment does not hold or holds in more than one
|
||||
collection, and the field is absent, from the entity and the create's answer,
|
||||
when the call does not send the member. An id naming entities of one type in
|
||||
several collections teaches no reference. Each later create widens its plan
|
||||
when, explained on its own, it agrees with it: both calls show the same fields
|
||||
but for a reference whose member a call without it did not send (one an earlier
|
||||
create added stays), every field both rule they rule alike (an echo one showed
|
||||
sorted stays sorted, unless a call stored its strings out of order as sent),
|
||||
and a field neither rules holds one value. Members either call sent are accepted, only those every call sent are
|
||||
required, and an echoed member a call left out takes the value that call stored
|
||||
(`body.defaults`). A clock rule widens to `trimmed` only when no held entity or
|
||||
earlier call showed that clock at a fixed width. A later create that disagrees
|
||||
leaves the plan as it was. A create whose observed reference differs from the
|
||||
entity the plan rebuilds, whether or not it sent the reference member, is
|
||||
`unexplained-change`. So is a create that left out an echoed member yet shows a
|
||||
value different from the learned default.
|
||||
A `composed` string is literal text and parts read from the fields other rules
|
||||
write: a field's value, its slug, or a clock's calendar digits
|
||||
(`YYYYMMDDhhmmss`, `YYYYMMDD`, `YYYY-MM-DD` or `YYYY/MM/DD`, in the clock's
|
||||
zone). A create composes a string only when at least one other entity of the
|
||||
type spells it with the same parts and none spells it otherwise, and never when
|
||||
one text could come from two sources serving could tell apart. A clock format
|
||||
is `trimmed` when the field's values differ only in how many fraction digits
|
||||
they show and none ends a fraction in a zero, as Go spells nanoseconds: its
|
||||
`fraction` is the most it shows, and serving drops trailing zeros and the point
|
||||
with them. Other clocks the plan stamps take that spelling too unless their own
|
||||
value shows a fixed width. A body that wraps one
|
||||
object in a one-element array is that object, wrapped so on every call
|
||||
(`body.envelope`). An update's members
|
||||
echo when stored as sent; a member stored differently is accepted only with its
|
||||
echo when stored as sent. A clock sent back as the entity held it and stored as
|
||||
a fresh clock near the call is a `clock`, and a member stored as the slug of
|
||||
another member stored as sent is a `slug`, both accepted with any value, as a
|
||||
whole-object write that sends what it read shows. A save that echoes an
|
||||
already-derived slug or holds a clock in its own second remains compatible
|
||||
with that derivation in either call order; earlier saves must also reproduce
|
||||
a newly learned slug. Any other member stored
|
||||
differently is accepted only with its
|
||||
recorded value (`body.fixed`) and stores the value the call that showed it
|
||||
recorded (the `value` of its `constant` rule), and a field it changed without
|
||||
sending must be a clock, a nested clock, an increment, a slug, a fresh `unique`
|
||||
value, a `stamp` of the acting user, a value `derived` from the one member it
|
||||
stores nowhere, or for an item action a `set` of a scalar to the value it
|
||||
showed, or the plan is `unexplained-change`; an increment, slug, stamp, derived
|
||||
stores nowhere, a `reference` to the entity a sent member names (kept by a call that names the entity the field already repeats), a `composed` string whose parts read members it stored as sent
|
||||
or fields it left as they were and spell the value before the call too, or for
|
||||
an item action a `set` of a scalar to the value it showed, or the plan is
|
||||
`unexplained-change`; an increment, slug, stamp, derived
|
||||
value or set must hold for every call of the tool, and an action is served
|
||||
only once a call found what it sets already set. A write is served only
|
||||
with the query its first call sent, which serving applies to a call that
|
||||
@@ -841,9 +892,11 @@ HAR, SAZ and qlog run in an owned Node old-generation heap with one V8 worker.
|
||||
`REA_CAPTURE_DECODER_OLD_GENERATION_MIB` is a decimal integer of mebibytes from
|
||||
8 to 16384. Unset uses 192, the previous constant. Invalid values fail
|
||||
configuration and name the setting; they do not fall back to 192. The setting
|
||||
is read from the environment that starts REA, so a change needs a restart.
|
||||
A memory failure names the effective mebibytes, whether they came from the
|
||||
default or the setting, the default, the floor, and the maximum. The decoder
|
||||
seeds the launch value. In MCP, set `capture.decoder_old_generation_mib`
|
||||
through `update_analysis_settings` for decoders belonging to newly accepted
|
||||
operations, without restarting REA. Changing the launch environment still
|
||||
requires a new process. A memory failure names the effective mebibytes, known
|
||||
source, default, floor, and maximum. The decoder
|
||||
passes that value as `--max-old-space-size` and removes inherited
|
||||
`NODE_OPTIONS`, so additional host memory and `NODE_OPTIONS` do not raise the
|
||||
heap. The 8 MiB semi-space stays fixed; a smaller old generation aborts Node
|
||||
@@ -865,8 +918,11 @@ hosts enforce no decoder heap limit, so raising the setting does not bound them.
|
||||
|
||||
Observed decoder memory exhaustion returns `resource_constraint`, with the
|
||||
selected capture, applicable limits and retained process diagnostics. Raising
|
||||
host memory or `NODE_OPTIONS` does not raise the old-generation heap. Set
|
||||
`REA_CAPTURE_DECODER_OLD_GENERATION_MIB` and restart REA.
|
||||
host memory or `NODE_OPTIONS` does not raise the old-generation heap. Update
|
||||
`capture.decoder_old_generation_mib` through MCP, or set
|
||||
`REA_CAPTURE_DECODER_OLD_GENERATION_MIB` before starting a new REA process.
|
||||
Retry after owned cleanup completes; the update does not release resources.
|
||||
See the [settings design](design/limits-and-recovery.md#failure-and-retry).
|
||||
Record selection happens after complete decoding and does not reduce that
|
||||
workload. Use a smaller capture exported by its producer or another decoder
|
||||
with sufficient capacity, retaining the original for provenance. Subsets do
|
||||
|
||||
@@ -111,6 +111,10 @@ are made.
|
||||
## Verification
|
||||
|
||||
Browser bundle collection uses absolute section offsets for nested inline maps.
|
||||
If caller cancellation stops browser map fetching after some items complete,
|
||||
the failed bundle request retains those items with the captured page in partial
|
||||
Evidence and marks bundle analysis `not_performed`. This is distinct from the
|
||||
local source-map decoder's failure semantics above.
|
||||
An enclosing column offset contributes only when the nested section starts on
|
||||
that enclosing section's first generated line. Parent boundaries still clip
|
||||
preceding browser mappings when a nested section starts with a gap or has no
|
||||
@@ -136,6 +140,17 @@ requests are left unprocessed and reported as unknown through the top-level
|
||||
items.
|
||||
Malformed UTF-8 map responses are reported as invalid rather than repaired with
|
||||
replacement characters.
|
||||
For browser fetching, an `invalid` map or structural or decoder capacity
|
||||
failure has `artifact: null`, empty decoded arrays, and an explicit limitation.
|
||||
When its bounded response body was read in full, optional `response_identity`
|
||||
records that body's SHA-256 digest and byte count after HTTP content decoding
|
||||
and before UTF-8 decoding. It is not a digest of partial reads or a claim that
|
||||
the map was valid; historical items may omit it. Malformed text is omitted so
|
||||
it does not consume the 32 MiB inline budget needed by later valid maps. A
|
||||
response whose text cannot fit that budget is refused before JSON parsing and
|
||||
source-map decoding, and retains the same
|
||||
completed-body identity. Successful and partial maps keep their full original
|
||||
text, including BOM and XSSI framing; only their parsing view removes it.
|
||||
|
||||
Focused tests cover duplicate mappings, indexed offsets/overlap/local indexes,
|
||||
32-bit VLQ overflow, nullable content, UTF-16/CRLF positions, immutable artifact
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "./node_modules/knip/schema.json",
|
||||
"entry": [
|
||||
"src/main.ts!",
|
||||
"src/cli.ts!",
|
||||
"src/cliOutput.ts!",
|
||||
"src/cliBuiltinArguments.ts!",
|
||||
"src/cli/cliResultOutput.ts!",
|
||||
"src/mcpDoctor.ts!",
|
||||
"src/composition/javascriptAnalysisWorkerProcess.ts!",
|
||||
"src/composition/webBundleAnalysisWorkerProcess.ts!",
|
||||
"src/sqlite/SqliteDatabaseWorker.ts!",
|
||||
"src/evm/EvmoleInterfaceWorker.ts!",
|
||||
"src/browser/history/NodeCaptureProcess.ts!",
|
||||
"src/javascript/sourceMaps/SourceMapCodecProcess.ts!",
|
||||
"scripts/rea.mjs!",
|
||||
"scripts/rea-android-workspace-guardian.mjs!",
|
||||
"scripts/electron-active-hook.cjs!",
|
||||
"scripts/prepack.mjs!",
|
||||
"scripts/prepare.mjs!",
|
||||
"scripts/verify-windows-native-artifact.mjs!"
|
||||
],
|
||||
"project": ["src/**/*.ts!", "!src/**/*.test.ts", "!src/**/*.fixture.ts"],
|
||||
"ignoreExportsUsedInFile": true,
|
||||
"include": [
|
||||
"files",
|
||||
"exports",
|
||||
"nsExports",
|
||||
"types",
|
||||
"nsTypes",
|
||||
"enumMembers",
|
||||
"namespaceMembers",
|
||||
"duplicates"
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,7 @@ REA npm dependency inventory
|
||||
Prepared from the root package-lock.json; refresh this inventory when locked dependencies change.
|
||||
License identifiers are upstream lockfile declarations, not a legal assessment.
|
||||
Runtime includes transitive and optional platform packages; development includes build, test and website tooling.
|
||||
Locked package entries: 956; runtime: 180.
|
||||
Locked package entries: 963; runtime: 180.
|
||||
Full runtime copyright/license/NOTICE texts: npm-runtime-notices.txt.
|
||||
|
||||
@algolia/abtesting@1.25.0 | development | MIT
|
||||
@@ -1100,6 +1100,36 @@ commander@13.1.0 | runtime | MIT
|
||||
Package: https://www.npmjs.com/package/@oxfmt/binding-win32-x64-msvc/v/0.72.0
|
||||
Archive: https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.72.0.tgz
|
||||
Integrity: sha512-uhooIN+DCzWX5uH7mQkSVCsODqsGluGJ4n3ch4+mITtWKpESMDjUJUSDBAVFuYQDwqY2McPDKdKJbM0oZVW/Qw==
|
||||
@oxlint-tsgolint/darwin-arm64@7.0.2005 | development, optional | MIT
|
||||
Lockfile path: node_modules/@oxlint-tsgolint/darwin-arm64
|
||||
Package: https://www.npmjs.com/package/@oxlint-tsgolint/darwin-arm64/v/7.0.2005
|
||||
Archive: https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2005.tgz
|
||||
Integrity: sha512-VqDZxcZVjlonNLuuvraVNgDQt+1ndwa4yJo8OqsK4HYh/61ba9IlMStCVfi/aaIqRoDhNbAKC8gdcTrXqnb+Ug==
|
||||
@oxlint-tsgolint/darwin-x64@7.0.2005 | development, optional | MIT
|
||||
Lockfile path: node_modules/@oxlint-tsgolint/darwin-x64
|
||||
Package: https://www.npmjs.com/package/@oxlint-tsgolint/darwin-x64/v/7.0.2005
|
||||
Archive: https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2005.tgz
|
||||
Integrity: sha512-4kUfLRNYoVpv0Tm3jPYrvxyfOc/LmdvFC3pKZ4tL6K7lA7K/VS81SAtmxRzbYXYSyb3SBWhwsuX+df4neqzxxA==
|
||||
@oxlint-tsgolint/linux-arm64@7.0.2005 | development, optional | MIT
|
||||
Lockfile path: node_modules/@oxlint-tsgolint/linux-arm64
|
||||
Package: https://www.npmjs.com/package/@oxlint-tsgolint/linux-arm64/v/7.0.2005
|
||||
Archive: https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2005.tgz
|
||||
Integrity: sha512-rhUNcPsBlKvhXtrbhbBUidtRBRdJ8fbEHN9H5Q5N59gby18ruGuDA7KUxl7oRnUzp797uykYQhkBDm+0KpfK/Q==
|
||||
@oxlint-tsgolint/linux-x64@7.0.2005 | development, optional | MIT
|
||||
Lockfile path: node_modules/@oxlint-tsgolint/linux-x64
|
||||
Package: https://www.npmjs.com/package/@oxlint-tsgolint/linux-x64/v/7.0.2005
|
||||
Archive: https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2005.tgz
|
||||
Integrity: sha512-tKs9yinDQZOHygM0e8EJnjTU3mzJtdfkOZP6Udu82iLMePUSHOhTJ9zzU/mSd1hQxURI4LTRRiFhfzC+PzwdNA==
|
||||
@oxlint-tsgolint/win32-arm64@7.0.2005 | development, optional | MIT
|
||||
Lockfile path: node_modules/@oxlint-tsgolint/win32-arm64
|
||||
Package: https://www.npmjs.com/package/@oxlint-tsgolint/win32-arm64/v/7.0.2005
|
||||
Archive: https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2005.tgz
|
||||
Integrity: sha512-VYPnu1gjfbSv+ROhuyNfMs382D4zQU2ZRc8Uc3nuEZo1k2kf+1KfTrg4hrlWh73h9GI+dUWyo+DYCQzUBlPwcQ==
|
||||
@oxlint-tsgolint/win32-x64@7.0.2005 | development, optional | MIT
|
||||
Lockfile path: node_modules/@oxlint-tsgolint/win32-x64
|
||||
Package: https://www.npmjs.com/package/@oxlint-tsgolint/win32-x64/v/7.0.2005
|
||||
Archive: https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2005.tgz
|
||||
Integrity: sha512-ekU89cTGy9fpQzuvxIsFHrnoP8XoFjGIRgPEW6eqgY2NYjGy9pwpjF79o04WXRMJFKRrcaVqkZXUUBdLsqpQDw==
|
||||
@oxlint/binding-android-arm-eabi@1.87.0 | development, optional | MIT
|
||||
Lockfile path: node_modules/@oxlint/binding-android-arm-eabi
|
||||
Package: https://www.npmjs.com/package/@oxlint/binding-android-arm-eabi/v/1.87.0
|
||||
@@ -3540,6 +3570,11 @@ oxlint@1.87.0 | development | MIT
|
||||
Package: https://www.npmjs.com/package/oxlint/v/1.87.0
|
||||
Archive: https://registry.npmjs.org/oxlint/-/oxlint-1.87.0.tgz
|
||||
Integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg==
|
||||
oxlint-tsgolint@7.0.2005 | development | MIT
|
||||
Lockfile path: node_modules/oxlint-tsgolint
|
||||
Package: https://www.npmjs.com/package/oxlint-tsgolint/v/7.0.2005
|
||||
Archive: https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2005.tgz
|
||||
Integrity: sha512-6ccPGu4u4J9bwYPHXBFPee3RZrX3BboRdU7k14zRDDB1lvN3O7J/vPRFdaEfa1+CWx4xnQQOp4ctoaF66oO0uw==
|
||||
p-limit@2.3.0 | development | MIT
|
||||
Lockfile path: node_modules/p-limit
|
||||
Package: https://www.npmjs.com/package/p-limit/v/2.3.0
|
||||
|
||||
Generated
+105
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "rea-agents",
|
||||
"version": "6.7.0",
|
||||
"version": "6.8.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "rea-agents",
|
||||
"version": "6.7.0",
|
||||
"version": "6.8.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "8.0.4",
|
||||
@@ -73,6 +73,7 @@
|
||||
"lint-staged": "16.4.0",
|
||||
"oxfmt": "0.72.0",
|
||||
"oxlint": "1.87.0",
|
||||
"oxlint-tsgolint": "7.0.2005",
|
||||
"release-please": "17.3.0",
|
||||
"rolldown": "1.2.12",
|
||||
"tsrs": "0.18.0-ts7.1.0-dev.20260929",
|
||||
@@ -3491,6 +3492,90 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint-tsgolint/darwin-arm64": {
|
||||
"version": "7.0.2005",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2005.tgz",
|
||||
"integrity": "sha512-VqDZxcZVjlonNLuuvraVNgDQt+1ndwa4yJo8OqsK4HYh/61ba9IlMStCVfi/aaIqRoDhNbAKC8gdcTrXqnb+Ug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxlint-tsgolint/darwin-x64": {
|
||||
"version": "7.0.2005",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2005.tgz",
|
||||
"integrity": "sha512-4kUfLRNYoVpv0Tm3jPYrvxyfOc/LmdvFC3pKZ4tL6K7lA7K/VS81SAtmxRzbYXYSyb3SBWhwsuX+df4neqzxxA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxlint-tsgolint/linux-arm64": {
|
||||
"version": "7.0.2005",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2005.tgz",
|
||||
"integrity": "sha512-rhUNcPsBlKvhXtrbhbBUidtRBRdJ8fbEHN9H5Q5N59gby18ruGuDA7KUxl7oRnUzp797uykYQhkBDm+0KpfK/Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxlint-tsgolint/linux-x64": {
|
||||
"version": "7.0.2005",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2005.tgz",
|
||||
"integrity": "sha512-tKs9yinDQZOHygM0e8EJnjTU3mzJtdfkOZP6Udu82iLMePUSHOhTJ9zzU/mSd1hQxURI4LTRRiFhfzC+PzwdNA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxlint-tsgolint/win32-arm64": {
|
||||
"version": "7.0.2005",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2005.tgz",
|
||||
"integrity": "sha512-VYPnu1gjfbSv+ROhuyNfMs382D4zQU2ZRc8Uc3nuEZo1k2kf+1KfTrg4hrlWh73h9GI+dUWyo+DYCQzUBlPwcQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxlint-tsgolint/win32-x64": {
|
||||
"version": "7.0.2005",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2005.tgz",
|
||||
"integrity": "sha512-ekU89cTGy9fpQzuvxIsFHrnoP8XoFjGIRgPEW6eqgY2NYjGy9pwpjF79o04WXRMJFKRrcaVqkZXUUBdLsqpQDw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxlint/binding-android-arm-eabi": {
|
||||
"version": "1.87.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.87.0.tgz",
|
||||
@@ -10137,6 +10222,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/oxlint-tsgolint": {
|
||||
"version": "7.0.2005",
|
||||
"resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2005.tgz",
|
||||
"integrity": "sha512-6ccPGu4u4J9bwYPHXBFPee3RZrX3BboRdU7k14zRDDB1lvN3O7J/vPRFdaEfa1+CWx4xnQQOp4ctoaF66oO0uw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"tsgolint": "bin/tsgolint.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@oxlint-tsgolint/darwin-arm64": "7.0.2005",
|
||||
"@oxlint-tsgolint/darwin-x64": "7.0.2005",
|
||||
"@oxlint-tsgolint/linux-arm64": "7.0.2005",
|
||||
"@oxlint-tsgolint/linux-x64": "7.0.2005",
|
||||
"@oxlint-tsgolint/win32-arm64": "7.0.2005",
|
||||
"@oxlint-tsgolint/win32-x64": "7.0.2005"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
|
||||
+8
-5
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "rea-agents",
|
||||
"mcpName": "io.github.morluto/rea",
|
||||
"version": "6.7.0",
|
||||
"version": "6.8.0",
|
||||
"description": "Reverse engineer anything from your terminal or agent with one CLI and MCP server.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -112,7 +112,7 @@
|
||||
"prebuild": "npm run deps:check",
|
||||
"build": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:skills --force",
|
||||
"precheck": "npm run deps:check",
|
||||
"check": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check && npm run verify:architecture-guards",
|
||||
"check": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check && npm run verify:architecture-guards && npm run knip:production && npm run lint:typed",
|
||||
"precheck:fast": "npm run deps:check",
|
||||
"check:fast": "node scripts/run-turbo.mjs run typecheck lint --output-logs=errors-only",
|
||||
"precheck:changed": "npm run deps:check",
|
||||
@@ -120,14 +120,14 @@
|
||||
"precheck:test": "npm run deps:check",
|
||||
"check:test": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run",
|
||||
"precheck:pr": "npm run deps:check",
|
||||
"check:pr": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run && npm run test:python && npm run docs:check && npm run jscpd && npm run scan:todos",
|
||||
"check:pr": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run && npm run test:python && npm run docs:check && npm run jscpd && npm run scan:todos && npm run knip:production && npm run lint:typed",
|
||||
"precheck:ci": "npm run deps:check",
|
||||
"precheck:ci:fast": "npm run deps:check",
|
||||
"precheck:ci:deep": "npm run deps:check",
|
||||
"check:ci": "npm run check:ci:fast && npm run check:ci:deep",
|
||||
"check:ci:fast": "node scripts/run-exclusive.mjs artifacts npm run check:ci:fast:tasks",
|
||||
"check:ci:fast:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check",
|
||||
"check:ci:deep": "node scripts/run-exclusive.mjs artifacts npm run check:ci:deep:tasks && npm run verify:architecture-guards && npm run verify:test-discovery && npm run test:python",
|
||||
"check:ci:deep": "node scripts/run-exclusive.mjs artifacts npm run check:ci:deep:tasks && npm run verify:architecture-guards && npm run verify:test-discovery && npm run test:python && npm run knip:production && npm run lint:typed",
|
||||
"check:ci:deep:tasks": "node scripts/run-turbo.mjs run knip metadata:check jscpd scan:todos",
|
||||
"check:ci:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check jscpd scan:todos",
|
||||
"ci:static-context": "node scripts/ci/static-task-context.mjs",
|
||||
@@ -326,7 +326,9 @@
|
||||
"docs:prepare": "node scripts/generate-package-metadata.mjs && node scripts/run-turbo.mjs run artifacts:product-catalog artifacts:managed-evidence artifacts:error-schema",
|
||||
"verify:web:qlog": "npm run build:cached && node scripts/verify-qlog-captures.mjs",
|
||||
"licenses:generate": "node scripts/generate-third-party-inventory.mjs",
|
||||
"licenses:check": "node scripts/generate-third-party-inventory.mjs --check"
|
||||
"licenses:check": "node scripts/generate-third-party-inventory.mjs --check",
|
||||
"lint:typed": "node scripts/ci/static-check-report.mjs typed-lint",
|
||||
"knip:production": "node scripts/ci/production-reachability-report.mjs"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}": [
|
||||
@@ -404,6 +406,7 @@
|
||||
"lint-staged": "16.4.0",
|
||||
"oxfmt": "0.72.0",
|
||||
"oxlint": "1.87.0",
|
||||
"oxlint-tsgolint": "7.0.2005",
|
||||
"release-please": "17.3.0",
|
||||
"rolldown": "1.2.12",
|
||||
"tsrs": "0.18.0-ts7.1.0-dev.20260929",
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ function classifyPackage(base, head) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
/^(test(?::|$)|check(?::|$)|lint(?::|$)|format(?::|$)|knip$|jscpd$)/.test(
|
||||
/^(test(?::|$)|check(?::|$)|lint(?::|$)|format(?::|$)|knip(?::|$)|jscpd$)/.test(
|
||||
script,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
export interface ProductionReachabilityFinding {
|
||||
readonly name: string;
|
||||
readonly namespace?: string;
|
||||
readonly kind?: string;
|
||||
readonly specifier?: string;
|
||||
readonly line?: number;
|
||||
readonly col?: number;
|
||||
readonly pos?: number;
|
||||
}
|
||||
|
||||
export interface ProductionReachabilityIssue {
|
||||
readonly file: string;
|
||||
readonly owners?: readonly { readonly name: string }[];
|
||||
readonly files?: readonly ProductionReachabilityFinding[];
|
||||
readonly exports?: readonly ProductionReachabilityFinding[];
|
||||
readonly nsExports?: readonly ProductionReachabilityFinding[];
|
||||
readonly types?: readonly ProductionReachabilityFinding[];
|
||||
readonly nsTypes?: readonly ProductionReachabilityFinding[];
|
||||
readonly enumMembers?: readonly ProductionReachabilityFinding[];
|
||||
readonly namespaceMembers?: readonly ProductionReachabilityFinding[];
|
||||
readonly duplicates?: readonly (readonly ProductionReachabilityFinding[])[];
|
||||
}
|
||||
|
||||
export interface ProductionReachabilityReport {
|
||||
readonly issues: readonly ProductionReachabilityIssue[];
|
||||
}
|
||||
|
||||
export function parseProductionReachabilityReport(
|
||||
text: string,
|
||||
): ProductionReachabilityReport;
|
||||
export function recordProductionReachabilityReport(
|
||||
root?: string,
|
||||
): Promise<ProductionReachabilityReport>;
|
||||
@@ -0,0 +1,86 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdir, rm, writeFile } from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { z } from "zod";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const finding = z.object({
|
||||
name: z.string(),
|
||||
namespace: z.string().optional(),
|
||||
kind: z.string().optional(),
|
||||
specifier: z.string().optional(),
|
||||
line: z.number().optional(),
|
||||
col: z.number().optional(),
|
||||
pos: z.number().optional(),
|
||||
});
|
||||
const issue = z.strictObject({
|
||||
file: z.string(),
|
||||
owners: z.array(z.object({ name: z.string() })).optional(),
|
||||
files: z.array(finding).optional(),
|
||||
exports: z.array(finding).optional(),
|
||||
nsExports: z.array(finding).optional(),
|
||||
types: z.array(finding).optional(),
|
||||
nsTypes: z.array(finding).optional(),
|
||||
enumMembers: z.array(finding).optional(),
|
||||
namespaceMembers: z.array(finding).optional(),
|
||||
duplicates: z.array(z.array(finding)).optional(),
|
||||
});
|
||||
const reportSchema = z.strictObject({ issues: z.array(issue) });
|
||||
|
||||
/** Admit the installed Knip JSON reporter's candidate records. */
|
||||
export const parseProductionReachabilityReport = (text) =>
|
||||
reportSchema.parse(JSON.parse(text));
|
||||
|
||||
/** Run production reachability without turning candidates into failures. */
|
||||
export const recordProductionReachabilityReport = async (
|
||||
root = process.cwd(),
|
||||
) => {
|
||||
const reportDirectory = join(root, ".cache", "static-checks");
|
||||
const reportPath = join(reportDirectory, "production-reachability.json");
|
||||
|
||||
// Findings are advisory. Execution, configuration and report-admission failures
|
||||
// still fail, and must never leave an older successful report at this path.
|
||||
await mkdir(reportDirectory, { recursive: true });
|
||||
await rm(reportPath, { force: true });
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
join(dirname(require.resolve("knip")), "..", "bin", "knip.js"),
|
||||
"--config",
|
||||
"knip.production.json",
|
||||
"--production",
|
||||
"--reporter",
|
||||
"json",
|
||||
"--no-exit-code",
|
||||
],
|
||||
{ cwd: root, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 },
|
||||
);
|
||||
if (result.stderr) process.stderr.write(result.stderr);
|
||||
if (result.error !== undefined) throw result.error;
|
||||
if (result.status !== 0) {
|
||||
if (result.stdout) process.stderr.write(result.stdout);
|
||||
throw new Error(
|
||||
result.status === null
|
||||
? `Production reachability analysis terminated by ${result.signal}`
|
||||
: `Production reachability analysis failed with status ${result.status}`,
|
||||
);
|
||||
}
|
||||
const report = parseProductionReachabilityReport(result.stdout);
|
||||
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
||||
console.log(
|
||||
`Production reachability: ${report.issues.length} files with advisory candidates.`,
|
||||
);
|
||||
console.log(`Full report: ${reportPath}`);
|
||||
console.log(
|
||||
"Candidates require runtime, verifier and published-contract review before removal; ordinary Knip remains separate.",
|
||||
);
|
||||
return report;
|
||||
};
|
||||
|
||||
if (
|
||||
process.argv[1] !== undefined &&
|
||||
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||
)
|
||||
await recordProductionReachabilityReport();
|
||||
@@ -144,6 +144,9 @@ export const SCOPES = {
|
||||
"scripts/windows-capabilities.mjs",
|
||||
"scripts/prepack.mjs",
|
||||
"scripts/prepare.mjs",
|
||||
"scripts/lib/release-proposal.mjs",
|
||||
"patches/release-please+17.3.0.patch",
|
||||
"tests/boundary/filesystem/releaseProposal.test.ts",
|
||||
"scripts/rea.mjs",
|
||||
"scripts/lib/cli-process-environment.mjs",
|
||||
"install.sh",
|
||||
|
||||
@@ -48,7 +48,12 @@ const duplicateReport = z
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const run = (packageName, entrypoint, arguments_) => {
|
||||
const run = (
|
||||
packageName,
|
||||
entrypoint,
|
||||
arguments_,
|
||||
environment = process.env,
|
||||
) => {
|
||||
const executable = join(
|
||||
dirname(require.resolve(`${packageName}/package.json`)),
|
||||
entrypoint,
|
||||
@@ -56,6 +61,7 @@ const run = (packageName, entrypoint, arguments_) => {
|
||||
const result = spawnSync(process.execPath, [executable, ...arguments_], {
|
||||
encoding: "utf8",
|
||||
maxBuffer: 32 * 1024 * 1024,
|
||||
env: environment,
|
||||
});
|
||||
if (result.stderr) process.stderr.write(result.stderr);
|
||||
if (result.error !== undefined) throw result.error;
|
||||
@@ -66,14 +72,30 @@ const run = (packageName, entrypoint, arguments_) => {
|
||||
|
||||
try {
|
||||
await mkdir(reportRoot, { recursive: true });
|
||||
if (mode === "lint") {
|
||||
const reportPath = join(reportRoot, "oxlint.json");
|
||||
if (mode === "lint" || mode === "typed-lint") {
|
||||
const typed = mode === "typed-lint";
|
||||
const reportPath = join(
|
||||
reportRoot,
|
||||
typed ? "typed-oxlint.json" : "oxlint.json",
|
||||
);
|
||||
await rm(reportPath, { force: true });
|
||||
const result = run("oxlint", "bin/oxlint", [
|
||||
...extraArguments,
|
||||
"--format",
|
||||
"json",
|
||||
]);
|
||||
const result = run(
|
||||
"oxlint",
|
||||
"bin/oxlint",
|
||||
[
|
||||
...(typed ? ["--type-aware", "--threads", "1"] : []),
|
||||
...extraArguments,
|
||||
"--format",
|
||||
"json",
|
||||
],
|
||||
typed
|
||||
? {
|
||||
...process.env,
|
||||
GOMAXPROCS: process.env.GOMAXPROCS ?? "2",
|
||||
GOMEMLIMIT: process.env.GOMEMLIMIT ?? "1024MiB",
|
||||
}
|
||||
: process.env,
|
||||
);
|
||||
let report;
|
||||
try {
|
||||
report = lintReport.parse(JSON.parse(result.stdout));
|
||||
@@ -89,7 +111,7 @@ try {
|
||||
({ severity }) => severity === "error",
|
||||
);
|
||||
console.log(
|
||||
`Lint: ${report.number_of_files} files, ${errors.length} errors, ${warnings.length} warnings.`,
|
||||
`${typed ? "Typed lint" : "Lint"}: ${report.number_of_files} files, ${errors.length} errors, ${warnings.length} warnings.`,
|
||||
);
|
||||
for (const diagnostic of errors) {
|
||||
const location = diagnostic.labels[0]?.span;
|
||||
@@ -106,7 +128,9 @@ try {
|
||||
)
|
||||
.slice(0, 8))
|
||||
console.log(` ${rule}: ${count} warnings`);
|
||||
console.log(`Full diagnostics: ${reportPath}; npm run lint:details`);
|
||||
console.log(
|
||||
`Full diagnostics: ${reportPath}${typed ? "" : "; npm run lint:details"}`,
|
||||
);
|
||||
process.exitCode = result.status;
|
||||
} else if (mode === "duplicates") {
|
||||
const output = join(reportRoot, "jscpd");
|
||||
@@ -143,7 +167,7 @@ try {
|
||||
console.log(`Full diagnostics: ${reportPath}; npm run jscpd:details`);
|
||||
process.exitCode = result.status;
|
||||
} else {
|
||||
throw new Error("Expected lint or duplicates");
|
||||
throw new Error("Expected lint, typed-lint or duplicates");
|
||||
}
|
||||
} catch (cause) {
|
||||
console.error(cause instanceof Error ? cause.message : String(cause));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createVerifierHttpServer } from "../lib/verifier-http-server.mjs";
|
||||
import { once } from "node:events";
|
||||
import { createServer } from "node:http";
|
||||
import { gzipSync } from "node:zlib";
|
||||
|
||||
export const networkFixtureSecret = "私密-network-fixture";
|
||||
@@ -23,7 +23,7 @@ const html = `<!doctype html><meta charset="utf-8"><body><button id="start">Run<
|
||||
export async function startBrowserNetworkSite() {
|
||||
const counts = new Map();
|
||||
const timers = new Set();
|
||||
const server = createServer(async (request, response) => {
|
||||
const handleRequest = async (request, response) => {
|
||||
const path = new URL(request.url, "http://fixture.test").pathname;
|
||||
counts.set(path, (counts.get(path) ?? 0) + 1);
|
||||
if (path === "/same") {
|
||||
@@ -89,7 +89,8 @@ export async function startBrowserNetworkSite() {
|
||||
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
||||
response.end(html);
|
||||
}
|
||||
});
|
||||
};
|
||||
const { server, close } = createVerifierHttpServer(handleRequest);
|
||||
server.listen(0, "127.0.0.1");
|
||||
await once(server, "listening");
|
||||
const address = server.address();
|
||||
@@ -100,10 +101,7 @@ export async function startBrowserNetworkSite() {
|
||||
counts,
|
||||
async close() {
|
||||
for (const timer of timers) clearTimeout(timer);
|
||||
server.closeAllConnections();
|
||||
await new Promise((resolve, reject) =>
|
||||
server.close((error) => (error ? reject(error) : resolve())),
|
||||
);
|
||||
await close();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createVerifierHttpServer } from "../lib/verifier-http-server.mjs";
|
||||
import { once } from "node:events";
|
||||
import { createServer } from "node:http";
|
||||
|
||||
/** The CSRF cookie and the stored bearer token the page holds; request steps must send both and the capture must not keep them. */
|
||||
export const requestFixtureCsrf = "csrf-fixture-7c1d2e";
|
||||
@@ -52,7 +52,7 @@ export async function startBrowserRequestSite() {
|
||||
};
|
||||
const writes = [];
|
||||
const hosts = [];
|
||||
const server = createServer(async (request, response) => {
|
||||
const handleRequest = async (request, response) => {
|
||||
const url = new URL(request.url, "http://fixture.test");
|
||||
hosts.push(`${request.headers.host} ${url.pathname}`);
|
||||
const chunks = [];
|
||||
@@ -108,7 +108,8 @@ export async function startBrowserRequestSite() {
|
||||
}
|
||||
const [status, value] = answerItems(store, request.method, id, body);
|
||||
json(response, status, value);
|
||||
});
|
||||
};
|
||||
const { server, close } = createVerifierHttpServer(handleRequest);
|
||||
server.listen(0, "127.0.0.1");
|
||||
await once(server, "listening");
|
||||
const address = server.address();
|
||||
@@ -120,11 +121,6 @@ export async function startBrowserRequestSite() {
|
||||
/** Host header and path of every request the server received. */
|
||||
hosts,
|
||||
items: store.items,
|
||||
async close() {
|
||||
server.closeAllConnections();
|
||||
await new Promise((resolve, reject) =>
|
||||
server.close((error) => (error ? reject(error) : resolve())),
|
||||
);
|
||||
},
|
||||
close,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { tsImport } from "tsx/esm/api";
|
||||
|
||||
const { agentEvaluationPassed } = await tsImport(
|
||||
new URL(
|
||||
"../../tests/evaluation/model-evals/AgentEvaluationReport.ts",
|
||||
import.meta.url,
|
||||
).href,
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
/** Fail the runner after publishing a report with any rejected scenario. */
|
||||
export const assertAgentEvaluationReportPassed = (results) => {
|
||||
const failed = results.filter((result) => !agentEvaluationPassed(result));
|
||||
if (failed.length > 0)
|
||||
throw new Error(
|
||||
`Agent routing/workflow/answer checks failed: ${failed.map(({ id }) => id).join(", ")}`,
|
||||
);
|
||||
};
|
||||
@@ -123,6 +123,7 @@ export const toolFamilyCatalog = (sources) => {
|
||||
surface: "application-workflow",
|
||||
contracts: [
|
||||
...sources.applicationContracts.APPLICATION_TOOL_CONTRACTS,
|
||||
...sources.analysisSettingsContracts.ANALYSIS_SETTINGS_TOOL_CONTRACTS,
|
||||
...sources.analysisViewContracts.ANALYSIS_VIEW_TOOL_CONTRACTS,
|
||||
...sources.webScriptContracts.WEB_SCRIPT_TOOL_CONTRACTS,
|
||||
...sources.javascriptRecoveryContracts
|
||||
|
||||
@@ -41,6 +41,7 @@ const SOURCE_PATHS = {
|
||||
officialContracts: "dist/contracts/officialToolContracts.js",
|
||||
enhancedContracts: "dist/contracts/enhancedToolContracts.js",
|
||||
sessionContracts: "dist/contracts/sessionToolContracts.js",
|
||||
analysisSettingsContracts: "dist/contracts/analysisSettingsContracts.js",
|
||||
binaryDiagnosticsContracts:
|
||||
"dist/contracts/native/binaryDiagnosticsToolContracts.js",
|
||||
cutterContracts: "dist/contracts/cutterToolContracts.js",
|
||||
|
||||
@@ -529,9 +529,11 @@ export async function verifyGhidraBoundaries(
|
||||
regexError.remediation.action,
|
||||
/literal mode or simplify the regex/u,
|
||||
);
|
||||
// The retry advice lives in the remediation; the reason states that the
|
||||
// session survived the overflow.
|
||||
assert.match(
|
||||
regexError.details.reason,
|
||||
/exhausted its stack.*Use literal mode/u,
|
||||
/exhausted its stack.*session and annotations remain available/u,
|
||||
);
|
||||
rejectedCalls++;
|
||||
const nestedPattern = "(".repeat(12000) + "a" + ")".repeat(12000);
|
||||
|
||||
@@ -104,7 +104,7 @@ export async function readReleaseHistory(run, base, target) {
|
||||
|
||||
// GitHub maps these coding-agent trailers to ordinary User actors.
|
||||
// Keep those source identities in the inventory while excluding automation credit.
|
||||
const AGENT_ACCOUNTS = new Set(["claude", "cursoragent", "codex"]);
|
||||
const AGENT_ACCOUNTS = new Set(["claude", "cursoragent", "codex", "copilot"]);
|
||||
// REA's maintainer requested external-contributor thanks without self-credit.
|
||||
const MAINTAINER_ACCOUNTS = new Set(["morluto"]);
|
||||
const human = (value) =>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, relative } from "node:path";
|
||||
import { dirname, relative, sep } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { requireCompleteReleaseHistory } from "./release-notes.mjs";
|
||||
@@ -24,7 +24,13 @@ export async function applyReleasePleasePatch(
|
||||
"The release proposal patch requires release-please 17.3.0",
|
||||
);
|
||||
}
|
||||
const args = ["--directory", relative(project, directory), patch];
|
||||
// Git prefixes patch filenames with a slash-separated path, even on Windows.
|
||||
// Split only native separators so POSIX backslashes remain filename bytes.
|
||||
const args = [
|
||||
"--directory",
|
||||
relative(project, directory).split(sep).join("/"),
|
||||
patch,
|
||||
];
|
||||
const check = async (reverse) => {
|
||||
try {
|
||||
await execute(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface TestProjectDefinition {
|
||||
readonly name: string;
|
||||
readonly group: "source" | "built";
|
||||
readonly isolate?: boolean;
|
||||
readonly include: readonly string[];
|
||||
readonly exclude?: readonly string[];
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@ export const SOURCE_TEST_PROJECTS = [
|
||||
{
|
||||
name: "domain",
|
||||
group: "source",
|
||||
isolate: false,
|
||||
include: ["src/{contracts,domain}/**/*.test.ts"],
|
||||
},
|
||||
{
|
||||
name: "services",
|
||||
group: "source",
|
||||
isolate: false,
|
||||
include: ["src/application/**/*.test.ts"],
|
||||
},
|
||||
{
|
||||
@@ -23,17 +25,20 @@ export const SOURCE_TEST_PROJECTS = [
|
||||
{
|
||||
name: "composition",
|
||||
group: "source",
|
||||
isolate: false,
|
||||
include: ["tests/composition/**/*.test.ts"],
|
||||
},
|
||||
{
|
||||
name: "conformance",
|
||||
group: "source",
|
||||
isolate: false,
|
||||
include: ["tests/conformance/**/*.test.ts"],
|
||||
exclude: ["tests/conformance/providers/hopper/realHopperSearch.test.ts"],
|
||||
},
|
||||
{
|
||||
name: "evaluation",
|
||||
group: "source",
|
||||
isolate: false,
|
||||
include: ["tests/evaluation/**/*.test.ts"],
|
||||
},
|
||||
];
|
||||
@@ -55,6 +60,7 @@ export const BUILT_TEST_PROJECTS = [
|
||||
{
|
||||
name: "cli-boundary",
|
||||
group: "built",
|
||||
isolate: false,
|
||||
include: ["tests/boundary/cli/**/*.test.ts"],
|
||||
},
|
||||
{
|
||||
@@ -65,6 +71,7 @@ export const BUILT_TEST_PROJECTS = [
|
||||
{
|
||||
name: "mcp-boundary",
|
||||
group: "built",
|
||||
isolate: false,
|
||||
include: ["tests/boundary/mcp/**/*.test.ts"],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { IncomingMessage, Server, ServerResponse } from "node:http";
|
||||
|
||||
/** Own admitted fixture requests through transport closure and report unexpected failures. */
|
||||
export function createVerifierHttpServer(
|
||||
handleRequest: (
|
||||
request: IncomingMessage,
|
||||
response: ServerResponse,
|
||||
) => Promise<void>,
|
||||
): {
|
||||
readonly server: Server;
|
||||
close(): Promise<void>;
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { createServer } from "node:http";
|
||||
import { withVerifierCleanup } from "./verifier-cleanup.mjs";
|
||||
|
||||
/** Own admitted fixture requests through transport closure and report unexpected failures. */
|
||||
export function createVerifierHttpServer(handleRequest) {
|
||||
const pending = new Set();
|
||||
let failure;
|
||||
const server = createServer((request, response) => {
|
||||
const work = handleRequest(request, response)
|
||||
.catch((cause) => {
|
||||
failure ??= { cause };
|
||||
response.destroy();
|
||||
})
|
||||
.finally(() => {
|
||||
pending.delete(work);
|
||||
});
|
||||
pending.add(work);
|
||||
});
|
||||
return {
|
||||
server,
|
||||
close() {
|
||||
return withVerifierCleanup(
|
||||
() =>
|
||||
new Promise((resolve, reject) => {
|
||||
server.close((cause) =>
|
||||
cause === undefined ? resolve() : reject(cause),
|
||||
);
|
||||
server.closeAllConnections();
|
||||
}),
|
||||
[
|
||||
async () => {
|
||||
await Promise.all(pending);
|
||||
if (failure !== undefined) throw failure.cause;
|
||||
},
|
||||
],
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/** Resolve the lexical binding instead of treating a matching name as an API. */
|
||||
export const findBinding = (sourceCode, identifier) => {
|
||||
for (
|
||||
let scope = sourceCode.getScope(identifier);
|
||||
scope !== null;
|
||||
scope = scope.upper
|
||||
) {
|
||||
const variable = scope.set.get(identifier.name);
|
||||
if (variable !== undefined) return variable;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { RuleTester } from "oxlint/plugins-dev";
|
||||
|
||||
declare const cancellationCauseRule: Parameters<RuleTester["run"]>[1];
|
||||
export default cancellationCauseRule;
|
||||
@@ -0,0 +1,200 @@
|
||||
import { findBinding } from "./bindings.mjs";
|
||||
|
||||
/**
|
||||
* Require an explicit cancellation reason when a directly checked caller signal
|
||||
* is converted to an AnalysisCancelledError. This is deliberately syntactic:
|
||||
* internal shutdown and cancellation forwarded through other owners are outside
|
||||
* the rule.
|
||||
*/
|
||||
|
||||
const coreModule = /(?:^|\/)analysisErrorCore(?:\.js|\.ts)?$/;
|
||||
|
||||
const unwrapChain = (node) =>
|
||||
node?.type === "ChainExpression" ? node.expression : node;
|
||||
|
||||
const propertyName = (property) =>
|
||||
property.computed
|
||||
? property.property.type === "Literal"
|
||||
? property.property.value
|
||||
: undefined
|
||||
: property.property.name;
|
||||
|
||||
const annotationType = (node) => {
|
||||
const annotation = node?.typeAnnotation?.typeAnnotation;
|
||||
return annotation?.type === "TSTypeReference" &&
|
||||
annotation.typeName.type === "Identifier"
|
||||
? annotation.typeName
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const soleDefinition = (variable) =>
|
||||
variable?.defs.length === 1 ? variable.defs[0] : undefined;
|
||||
|
||||
const isKnownType = (node, kind, context) => {
|
||||
const type = annotationType(node);
|
||||
if (type === undefined) return false;
|
||||
const definition = soleDefinition(findBinding(context.sourceCode, type));
|
||||
if (kind === "AbortSignal")
|
||||
return type.name === "AbortSignal" && definition === undefined;
|
||||
return (
|
||||
definition?.type === "ImportBinding" &&
|
||||
definition.parent?.type === "ImportDeclaration" &&
|
||||
/(?:^|\/)AnalysisProvider(?:\.js|\.ts)?$/.test(
|
||||
definition.parent.source.value,
|
||||
) &&
|
||||
definition.node.type === "ImportSpecifier" &&
|
||||
definition.node.imported.name === "ExecutionOptions"
|
||||
);
|
||||
};
|
||||
|
||||
const unmodified = (variable) =>
|
||||
variable !== undefined &&
|
||||
variable.references.every(
|
||||
(reference) => !reference.isWrite() || reference.init,
|
||||
);
|
||||
|
||||
const isOptionsSignal = (node, context, seen) => {
|
||||
node = unwrapChain(node);
|
||||
if (node?.type !== "MemberExpression" || propertyName(node) !== "signal")
|
||||
return false;
|
||||
return isKnownBinding(node.object, "ExecutionOptions", context, seen);
|
||||
};
|
||||
|
||||
const isKnownBinding = (node, kind, context, seen) => {
|
||||
if (node?.type !== "Identifier") return false;
|
||||
const variable = findBinding(context.sourceCode, node);
|
||||
if (!unmodified(variable) || seen.has(variable)) return false;
|
||||
seen.add(variable);
|
||||
const definition = soleDefinition(variable);
|
||||
if (definition?.type === "Parameter")
|
||||
return isKnownType(definition.name, kind, context);
|
||||
if (definition?.type !== "Variable" || definition.parent?.kind !== "const")
|
||||
return false;
|
||||
const declaration = definition.node;
|
||||
if (isKnownType(declaration.id, kind, context)) return true;
|
||||
return kind === "AbortSignal"
|
||||
? isKnownSignal(declaration.init, context, seen)
|
||||
: false;
|
||||
};
|
||||
|
||||
const isKnownSignal = (node, context, seen = new Set()) =>
|
||||
isKnownBinding(unwrapChain(node), "AbortSignal", context, seen) ||
|
||||
isOptionsSignal(node, context, seen);
|
||||
|
||||
const isAbortRead = (node, context) => {
|
||||
node = unwrapChain(node);
|
||||
return (
|
||||
node?.type === "MemberExpression" &&
|
||||
propertyName(node) === "aborted" &&
|
||||
isKnownSignal(node.object, context)
|
||||
);
|
||||
};
|
||||
|
||||
const isTrueLiteral = (node) => node?.type === "Literal" && node.value === true;
|
||||
|
||||
const positiveAbortGuard = (test, context) => {
|
||||
if (isAbortRead(test, context)) return true;
|
||||
if (
|
||||
test?.type === "BinaryExpression" &&
|
||||
(test.operator === "===" || test.operator === "==")
|
||||
)
|
||||
return (
|
||||
(isAbortRead(test.left, context) && isTrueLiteral(test.right)) ||
|
||||
(isTrueLiteral(test.left) && isAbortRead(test.right, context))
|
||||
);
|
||||
if (test?.type === "LogicalExpression" && test.operator === "&&")
|
||||
return (
|
||||
positiveAbortGuard(test.left, context) ||
|
||||
positiveAbortGuard(test.right, context)
|
||||
);
|
||||
return false;
|
||||
};
|
||||
|
||||
const isCanonicalConstructor = (node, context) => {
|
||||
if (node?.type !== "Identifier") return false;
|
||||
const definition = soleDefinition(findBinding(context.sourceCode, node));
|
||||
return (
|
||||
definition?.type === "ImportBinding" &&
|
||||
definition.parent?.type === "ImportDeclaration" &&
|
||||
coreModule.test(definition.parent.source.value) &&
|
||||
definition.node.type === "ImportSpecifier" &&
|
||||
definition.node.imported.name === "AnalysisCancelledError"
|
||||
);
|
||||
};
|
||||
|
||||
const hasExplicitCause = (node) => {
|
||||
const options = node.arguments[1];
|
||||
if (options !== undefined && options.type !== "ObjectExpression") return true; // Forwarded context is owned elsewhere.
|
||||
if (options === undefined) return false;
|
||||
return options.properties.some(
|
||||
(property) =>
|
||||
property.type === "SpreadElement" ||
|
||||
(property.type === "Property" &&
|
||||
(property.computed
|
||||
? property.key.type !== "Literal" || property.key.value === "cause"
|
||||
: (property.key.type === "Identifier"
|
||||
? property.key.name
|
||||
: property.key.value) === "cause")),
|
||||
);
|
||||
};
|
||||
|
||||
const inspectExpression = (expression, context) => {
|
||||
if (expression?.type === "NewExpression") {
|
||||
if (
|
||||
isCanonicalConstructor(expression.callee, context) &&
|
||||
!hasExplicitCause(expression)
|
||||
)
|
||||
context.report({
|
||||
node: expression,
|
||||
messageId: "missingCause",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (expression?.type === "CallExpression") {
|
||||
// Common Result adapter: return err(new AnalysisCancelledError(...)).
|
||||
for (const argument of expression.arguments)
|
||||
inspectExpression(argument, context);
|
||||
} else if (expression?.type === "AssignmentExpression") {
|
||||
inspectExpression(expression.right, context);
|
||||
}
|
||||
};
|
||||
|
||||
const inspectBranch = (branch, context) => {
|
||||
if (branch.type === "BlockStatement") {
|
||||
for (const statement of branch.body) inspectBranch(statement, context);
|
||||
} else if (branch.type === "VariableDeclaration") {
|
||||
for (const declaration of branch.declarations)
|
||||
inspectExpression(declaration.init, context);
|
||||
} else if (
|
||||
branch.type === "ThrowStatement" ||
|
||||
branch.type === "ReturnStatement"
|
||||
) {
|
||||
inspectExpression(branch.argument, context);
|
||||
} else if (branch.type === "ExpressionStatement") {
|
||||
inspectExpression(branch.expression, context);
|
||||
}
|
||||
};
|
||||
|
||||
const cancellationCauseRule = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Preserve a directly checked caller AbortSignal reason in AnalysisCancelledError",
|
||||
},
|
||||
messages: {
|
||||
missingCause:
|
||||
"Pass the checked AbortSignal's reason as an explicit cancellation cause.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
IfStatement(node) {
|
||||
if (positiveAbortGuard(node.test, context))
|
||||
inspectBranch(node.consequent, context);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default cancellationCauseRule;
|
||||
@@ -0,0 +1,19 @@
|
||||
import noBufferDeepEquality from "./no-buffer-deep-equality.mjs";
|
||||
import cancellationCause from "./cancellation-cause.mjs";
|
||||
|
||||
import noEvidenceJsonPreadmission from "./rules/no-evidence-json-preadmission.mjs";
|
||||
import noFixtureRootCleanup from "./rules/no-fixture-root-cleanup.mjs";
|
||||
import noPositionalToolContract from "./rules/no-positional-tool-contract.mjs";
|
||||
import noSharedModuleMock from "./rules/no-shared-module-mock.mjs";
|
||||
|
||||
export default {
|
||||
meta: { name: "rea" },
|
||||
rules: {
|
||||
"no-buffer-deep-equality": noBufferDeepEquality,
|
||||
"cancellation-cause": cancellationCause,
|
||||
"no-evidence-json-preadmission": noEvidenceJsonPreadmission,
|
||||
"no-fixture-root-cleanup": noFixtureRootCleanup,
|
||||
"no-positional-tool-contract": noPositionalToolContract,
|
||||
"no-shared-module-mock": noSharedModuleMock,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { RuleTester } from "oxlint/plugins-dev";
|
||||
|
||||
declare const noBufferDeepEquality: Parameters<RuleTester["run"]>[1];
|
||||
export default noBufferDeepEquality;
|
||||
@@ -0,0 +1,166 @@
|
||||
import { findBinding } from "./bindings.mjs";
|
||||
|
||||
const memberName = (node) =>
|
||||
node?.type === "MemberExpression" &&
|
||||
!node.computed &&
|
||||
node.property.type === "Identifier"
|
||||
? node.property.name
|
||||
: null;
|
||||
|
||||
const unwrap = (node) => {
|
||||
while (
|
||||
node?.type === "TSAsExpression" ||
|
||||
node?.type === "TSSatisfiesExpression" ||
|
||||
node?.type === "TSNonNullExpression" ||
|
||||
node?.type === "TSTypeAssertion" ||
|
||||
node?.type === "ParenthesizedExpression"
|
||||
)
|
||||
node = node.expression;
|
||||
return node;
|
||||
};
|
||||
|
||||
const importedKind = (variable) => {
|
||||
const definition = variable?.defs[0];
|
||||
if (definition?.type !== "ImportBinding") return null;
|
||||
const source = definition.parent?.source?.value;
|
||||
const node = definition.node;
|
||||
if (node?.type === "ImportNamespaceSpecifier") {
|
||||
if (source === "node:fs/promises") return "fsPromises";
|
||||
if (source === "node:fs") return "fs";
|
||||
return null;
|
||||
}
|
||||
if (node?.type !== "ImportSpecifier") return null;
|
||||
const imported = node.imported?.name ?? node.imported?.value;
|
||||
if (source === "vitest" && imported === "expect") return "expect";
|
||||
if (source === "node:buffer" && imported === "Buffer") return "Buffer";
|
||||
if (source === "node:fs/promises" && imported === "readFile")
|
||||
return "readFile";
|
||||
if (source === "node:fs" && imported === "readFileSync")
|
||||
return "readFileSync";
|
||||
return null;
|
||||
};
|
||||
|
||||
const constInitializer = (variable) => {
|
||||
if (variable?.defs.length !== 1) return null;
|
||||
const definition = variable.defs[0];
|
||||
if (
|
||||
definition?.type !== "Variable" ||
|
||||
definition.node?.type !== "VariableDeclarator" ||
|
||||
definition.parent?.kind !== "const" ||
|
||||
variable.references.some(
|
||||
(reference) => reference.isWrite() && !reference.init,
|
||||
)
|
||||
)
|
||||
return null;
|
||||
return definition.node.init;
|
||||
};
|
||||
|
||||
/** Flag byte comparisons that make Vitest enumerate a Node Buffer. */
|
||||
const noBufferDeepEquality = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
messages: {
|
||||
nativeBytes:
|
||||
"Compare Node Buffers with Buffer.equals or Buffer.compare; deep equality can enumerate every byte and exhaust memory.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const { sourceCode } = context;
|
||||
const symbolKind = (expression, seen = new Set()) => {
|
||||
const node = unwrap(expression);
|
||||
if (node?.type === "Identifier") {
|
||||
const variable = findBinding(sourceCode, node);
|
||||
if (
|
||||
(variable === undefined || variable.defs.length === 0) &&
|
||||
sourceCode.isGlobalReference(node)
|
||||
)
|
||||
return node.name === "Buffer" ? "Buffer" : null;
|
||||
const imported = importedKind(variable);
|
||||
if (imported !== null) return imported;
|
||||
if (variable === undefined || seen.has(variable)) return null;
|
||||
const initializer = constInitializer(variable);
|
||||
if (initializer === null) return null;
|
||||
seen.add(variable);
|
||||
return symbolKind(initializer, seen);
|
||||
}
|
||||
const property = memberName(node);
|
||||
if (property === null) return null;
|
||||
const owner = symbolKind(node.object, seen);
|
||||
if (owner === "fsPromises" && property === "readFile") return "readFile";
|
||||
if (owner === "fs" && property === "readFileSync") return "readFileSync";
|
||||
if (
|
||||
owner === "Buffer" &&
|
||||
["from", "alloc", "allocUnsafe", "allocUnsafeSlow", "concat"].includes(
|
||||
property,
|
||||
)
|
||||
)
|
||||
return "bufferFactory";
|
||||
return null;
|
||||
};
|
||||
const isBuffer = (expression, seen = new Set(), awaited = false) => {
|
||||
const node = unwrap(expression);
|
||||
if (node?.type === "AwaitExpression")
|
||||
return isBuffer(node.argument, seen, true);
|
||||
if (node?.type === "Identifier") {
|
||||
const variable = findBinding(sourceCode, node);
|
||||
if (variable === undefined || seen.has(variable)) return false;
|
||||
const initializer = constInitializer(variable);
|
||||
if (initializer === null) return false;
|
||||
seen.add(variable);
|
||||
return isBuffer(initializer, seen, awaited);
|
||||
}
|
||||
if (node?.type !== "CallExpression" || node.optional) return false;
|
||||
const kind = symbolKind(node.callee);
|
||||
return (
|
||||
kind === "bufferFactory" ||
|
||||
((kind === "readFileSync" || (kind === "readFile" && awaited)) &&
|
||||
node.arguments.length === 1 &&
|
||||
node.arguments[0].type !== "SpreadElement")
|
||||
);
|
||||
};
|
||||
const expectedCall = (object, settlement = null) => {
|
||||
const node = unwrap(object);
|
||||
const property = memberName(node);
|
||||
if (property === "not") return expectedCall(node.object, settlement);
|
||||
if (property === "resolves" || property === "rejects")
|
||||
return expectedCall(node.object, property);
|
||||
return node?.type === "CallExpression" &&
|
||||
!node.optional &&
|
||||
symbolKind(node.callee) === "expect"
|
||||
? { call: node, settlement }
|
||||
: null;
|
||||
};
|
||||
return {
|
||||
CallExpression(node) {
|
||||
if (node.optional) return;
|
||||
const matcher = memberName(node.callee);
|
||||
if (matcher !== "toEqual" && matcher !== "toStrictEqual") return;
|
||||
const assertion = expectedCall(node.callee.object);
|
||||
if (
|
||||
assertion === null ||
|
||||
![1, 2].includes(assertion.call.arguments.length) ||
|
||||
assertion.call.arguments.some(
|
||||
(argument) => argument.type === "SpreadElement",
|
||||
) ||
|
||||
node.arguments.length !== 1
|
||||
)
|
||||
return;
|
||||
if (
|
||||
(assertion.settlement !== "rejects" &&
|
||||
isBuffer(
|
||||
assertion.call.arguments[0],
|
||||
new Set(),
|
||||
assertion.settlement === "resolves",
|
||||
)) ||
|
||||
isBuffer(node.arguments[0])
|
||||
)
|
||||
context.report({
|
||||
node: node.callee.property,
|
||||
messageId: "nativeBytes",
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default noBufferDeepEquality;
|
||||
@@ -0,0 +1,4 @@
|
||||
export declare const resolveImportSource: (
|
||||
filename: string,
|
||||
specifier: unknown,
|
||||
) => string | undefined;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { dirname, isAbsolute, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/** Resolve local import spellings without assigning local meaning to bare or HTTP imports. */
|
||||
export const resolveImportSource = (filename, specifier) => {
|
||||
if (typeof specifier !== "string") return undefined;
|
||||
if (isAbsolute(specifier)) return resolve(specifier);
|
||||
if (specifier.startsWith("file:")) {
|
||||
try {
|
||||
const url = new URL(specifier);
|
||||
if (url.search !== "" || url.hash !== "") return undefined;
|
||||
return fileURLToPath(url);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (specifier.startsWith("./") || specifier.startsWith("../"))
|
||||
return resolve(dirname(filename), specifier);
|
||||
return undefined;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { RuleTester } from "oxlint/plugins-dev";
|
||||
|
||||
declare const rule: Parameters<RuleTester["run"]>[1];
|
||||
export default rule;
|
||||
@@ -0,0 +1,137 @@
|
||||
import { findBinding } from "../bindings.mjs";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveImportSource } from "./import-source.mjs";
|
||||
|
||||
const domain = fileURLToPath(new URL("../../../src/domain/", import.meta.url));
|
||||
const payloadFields = new Set(["parameters", "result", "rawResult"]);
|
||||
|
||||
const propertyName = (node) => {
|
||||
if (!node.computed && node.property?.type === "Identifier")
|
||||
return node.property.name;
|
||||
if (
|
||||
node.property?.type === "Literal" &&
|
||||
typeof node.property.value === "string"
|
||||
)
|
||||
return node.property.value;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const fieldName = (node) => {
|
||||
if (node.type !== "Property") return undefined;
|
||||
if (!node.computed && node.key.type === "Identifier") return node.key.name;
|
||||
if (node.key.type === "Literal" && typeof node.key.value === "string")
|
||||
return node.key.value;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const importedBinding = (sourceCode, node) => {
|
||||
if (node.type !== "Identifier") return undefined;
|
||||
const variable = findBinding(sourceCode, node);
|
||||
if (!variable || variable.defs.length !== 1) return undefined;
|
||||
const definition = variable.defs[0];
|
||||
return definition.type === "ImportBinding" ? definition : undefined;
|
||||
};
|
||||
|
||||
const isImported = (context, node, module, names) => {
|
||||
const member = node.type === "MemberExpression" && !node.optional;
|
||||
const binding = importedBinding(
|
||||
context.sourceCode,
|
||||
member ? node.object : node,
|
||||
);
|
||||
if (!binding || binding.parent?.type !== "ImportDeclaration") return false;
|
||||
const declaration = binding.parent;
|
||||
if (declaration.importKind === "type" || binding.node.importKind === "type")
|
||||
return false;
|
||||
const specifier = declaration.source.value;
|
||||
const target = resolveImportSource(
|
||||
context.physicalFilename ?? context.filename,
|
||||
specifier,
|
||||
);
|
||||
if (
|
||||
target !== resolve(domain, `${module}.js`) &&
|
||||
target !== resolve(domain, `${module}.ts`)
|
||||
)
|
||||
return false;
|
||||
if (member)
|
||||
return (
|
||||
binding.node.type === "ImportNamespaceSpecifier" &&
|
||||
names.has(propertyName(node))
|
||||
);
|
||||
if (binding.node.type !== "ImportSpecifier") return false;
|
||||
return names.has(binding.node.imported.name ?? binding.node.imported.value);
|
||||
};
|
||||
|
||||
const evidenceCreator = new Set(["createEvidence"]);
|
||||
const genericJsonSchemas = new Set(["jsonValueSchema", "jsonObjectSchema"]);
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: {
|
||||
description:
|
||||
"Leave ordinary Evidence JSON payload admission with createEvidence.",
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
redundantJsonAdmission:
|
||||
"Pass the {{field}} payload directly to createEvidence; it owns generic JSON admission. Keep semantic validation at its own boundary.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
CallExpression(node) {
|
||||
if (
|
||||
node.optional ||
|
||||
!isImported(context, node.callee, "evidence", evidenceCreator)
|
||||
)
|
||||
return;
|
||||
const observation = node.arguments[2];
|
||||
if (observation?.type !== "ObjectExpression") return;
|
||||
for (const [index, property] of observation.properties.entries()) {
|
||||
const field = fieldName(property);
|
||||
if (
|
||||
!payloadFields.has(field) ||
|
||||
property.kind !== "init" ||
|
||||
property.method
|
||||
)
|
||||
continue;
|
||||
// A later spread or unknown key may replace this field before admission.
|
||||
if (
|
||||
observation.properties
|
||||
.slice(index + 1)
|
||||
.some(
|
||||
(later) =>
|
||||
later.type !== "Property" ||
|
||||
fieldName(later) === undefined ||
|
||||
fieldName(later) === field,
|
||||
)
|
||||
)
|
||||
continue;
|
||||
const parsed = property.value;
|
||||
if (
|
||||
parsed.type !== "CallExpression" ||
|
||||
parsed.optional ||
|
||||
parsed.arguments.length !== 1
|
||||
)
|
||||
continue;
|
||||
const parse = parsed.callee;
|
||||
if (
|
||||
parse.type !== "MemberExpression" ||
|
||||
parse.optional ||
|
||||
propertyName(parse) !== "parse"
|
||||
)
|
||||
continue;
|
||||
if (
|
||||
isImported(context, parse.object, "jsonValue", genericJsonSchemas)
|
||||
)
|
||||
context.report({
|
||||
node: parsed,
|
||||
messageId: "redundantJsonAdmission",
|
||||
data: { field },
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { RuleTester } from "oxlint/plugins-dev";
|
||||
declare const rule: Parameters<RuleTester["run"]>[1];
|
||||
export default rule;
|
||||
@@ -0,0 +1,149 @@
|
||||
import { findBinding } from "../bindings.mjs";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveImportSource } from "./import-source.mjs";
|
||||
|
||||
const root = fileURLToPath(new URL("../../../", import.meta.url));
|
||||
const fixtureModule = resolve(root, "tests/fixtures/temporaryDirectory");
|
||||
const workspaceModule = resolve(
|
||||
root,
|
||||
"tests/support/workspace/workspaceFixture",
|
||||
);
|
||||
const hooks = new Set(["afterEach", "afterAll", "onTestFinished"]);
|
||||
const unwrap = (node) => {
|
||||
while (
|
||||
node &&
|
||||
[
|
||||
"AwaitExpression",
|
||||
"TSAsExpression",
|
||||
"TSNonNullExpression",
|
||||
"ChainExpression",
|
||||
].includes(node.type)
|
||||
)
|
||||
node = node.argument ?? node.expression;
|
||||
return node;
|
||||
};
|
||||
const isFunction = (node) =>
|
||||
node &&
|
||||
[
|
||||
"FunctionDeclaration",
|
||||
"FunctionExpression",
|
||||
"ArrowFunctionExpression",
|
||||
].includes(node.type);
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description: "Let createTestTempDirectory own removal of its root.",
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
duplicate:
|
||||
"createTestTempDirectory already owns root cleanup; remove the duplicate teardown deletion, retaining cleanup of other resources.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
const filename = context.physicalFilename ?? context.filename;
|
||||
const calls = [];
|
||||
const variable = (node) => findBinding(sourceCode, node);
|
||||
const imported = (node) => {
|
||||
const member = node?.type === "MemberExpression" ? node : undefined;
|
||||
const binding = member ? member.object : node;
|
||||
if (binding?.type !== "Identifier") return undefined;
|
||||
const definition = variable(binding)?.defs[0];
|
||||
if (
|
||||
definition?.type !== "ImportBinding" ||
|
||||
(member
|
||||
? definition.node.type !== "ImportNamespaceSpecifier"
|
||||
: definition.node.type !== "ImportSpecifier")
|
||||
)
|
||||
return undefined;
|
||||
const declaration = definition.parent ?? definition.node.parent;
|
||||
const specifier = declaration.source?.value;
|
||||
if (typeof specifier !== "string") return undefined;
|
||||
return {
|
||||
name: member
|
||||
? member.computed
|
||||
? member.property.value
|
||||
: member.property.name
|
||||
: (definition.node.imported.name ?? definition.node.imported.value),
|
||||
module:
|
||||
resolveImportSource(filename, specifier)?.replace(
|
||||
/\.(?:[cm]?[jt]s)$/u,
|
||||
"",
|
||||
) ?? specifier,
|
||||
};
|
||||
};
|
||||
const fixtureRoot = (raw, seen = new Set()) => {
|
||||
const node = unwrap(raw);
|
||||
if (node?.type === "CallExpression") {
|
||||
const identity = imported(node.callee);
|
||||
return (
|
||||
identity?.module === fixtureModule &&
|
||||
identity.name === "createTestTempDirectory"
|
||||
);
|
||||
}
|
||||
if (node?.type !== "Identifier") return false;
|
||||
const binding = variable(node);
|
||||
if (!binding || seen.has(binding)) return false;
|
||||
seen.add(binding);
|
||||
const definition = binding.defs[0];
|
||||
// Mutable ownership and mixed root registries require flow analysis, not
|
||||
// a guess based on the variable name or one assignment.
|
||||
return (
|
||||
definition?.type === "Variable" &&
|
||||
definition.parent.kind === "const" &&
|
||||
definition.node.id.type === "Identifier" &&
|
||||
fixtureRoot(definition.node.init, seen)
|
||||
);
|
||||
};
|
||||
const callback = (raw) => {
|
||||
const node = unwrap(raw);
|
||||
if (isFunction(node)) return node;
|
||||
if (node?.type !== "Identifier") return undefined;
|
||||
const definition = variable(node)?.defs[0];
|
||||
if (definition?.type === "FunctionName") return definition.node;
|
||||
if (
|
||||
definition?.type === "Variable" &&
|
||||
definition.parent.kind === "const"
|
||||
) {
|
||||
const value = unwrap(definition.node.init);
|
||||
if (isFunction(value)) return value;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
return {
|
||||
CallExpression(node) {
|
||||
calls.push(node);
|
||||
},
|
||||
"Program:exit"() {
|
||||
const teardowns = new Set();
|
||||
for (const call of calls) {
|
||||
const identity = imported(call.callee);
|
||||
if (identity?.module === "vitest" && hooks.has(identity.name)) {
|
||||
const body = callback(call.arguments[0]);
|
||||
if (body) teardowns.add(body);
|
||||
}
|
||||
}
|
||||
for (const call of calls) {
|
||||
const identity = imported(call.callee);
|
||||
if (!identity) continue;
|
||||
const removesRoot =
|
||||
(identity.module === "node:fs/promises" &&
|
||||
["rm", "rmdir"].includes(identity.name)) ||
|
||||
(identity.module === "node:fs" &&
|
||||
["rm", "rmSync", "rmdir", "rmdirSync"].includes(identity.name)) ||
|
||||
(identity.module === workspaceModule &&
|
||||
identity.name === "removeTestWorkspace");
|
||||
if (!removesRoot || !fixtureRoot(call.arguments[0])) continue;
|
||||
let parent = call.parent;
|
||||
while (parent && !isFunction(parent)) parent = parent.parent;
|
||||
if (teardowns.has(parent))
|
||||
context.report({ node: call, messageId: "duplicate" });
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { RuleTester } from "oxlint/plugins-dev";
|
||||
declare const rule: Parameters<RuleTester["run"]>[1];
|
||||
export default rule;
|
||||
@@ -0,0 +1,218 @@
|
||||
import { findBinding } from "../bindings.mjs";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { collectToolCatalogSources } from "./tool-catalog-sources.mjs";
|
||||
import { resolveImportSource } from "./import-source.mjs";
|
||||
|
||||
const root = fileURLToPath(new URL("../../../", import.meta.url));
|
||||
const catalogs = collectToolCatalogSources(
|
||||
resolve(root, "src/contracts/toolContracts.ts"),
|
||||
);
|
||||
|
||||
const unwrap = (node) => {
|
||||
while (
|
||||
node &&
|
||||
[
|
||||
"TSAsExpression",
|
||||
"TSSatisfiesExpression",
|
||||
"TSNonNullExpression",
|
||||
"ChainExpression",
|
||||
].includes(node.type)
|
||||
)
|
||||
node = node.expression;
|
||||
return node;
|
||||
};
|
||||
const numeric = (node) =>
|
||||
(node?.type === "Literal" &&
|
||||
(typeof node.value === "number" ||
|
||||
(typeof node.value === "string" &&
|
||||
/^(?:0|[1-9][0-9]*)$/u.test(node.value)))) ||
|
||||
(node?.type === "UnaryExpression" &&
|
||||
["+", "-"].includes(node.operator) &&
|
||||
node.argument.type === "Literal" &&
|
||||
typeof node.argument.value === "number");
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Select canonical tool contracts by name, never catalog position.",
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
positional:
|
||||
"Select a named tool contract; catalog ordering must not select an operation or schema.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
const filename = context.physicalFilename ?? context.filename;
|
||||
const variable = (node) => findBinding(sourceCode, node);
|
||||
const imported = (node) => {
|
||||
if (node?.type !== "Identifier") return undefined;
|
||||
const definition = variable(node)?.defs[0];
|
||||
if (definition?.type !== "ImportBinding") return undefined;
|
||||
const declaration = definition.parent ?? definition.node.parent;
|
||||
const specifier = declaration.source?.value;
|
||||
const path = resolveImportSource(filename, specifier)?.replace(
|
||||
/\.(?:[cm]?[jt]s)$/u,
|
||||
"",
|
||||
);
|
||||
const expected = catalogs.get(path);
|
||||
if (expected === undefined) return undefined;
|
||||
return { definition, expected };
|
||||
};
|
||||
const isCatalog = (raw, seen = new Set()) => {
|
||||
const node = unwrap(raw);
|
||||
if (!node) return false;
|
||||
if (node.type === "Identifier") {
|
||||
const binding = variable(node);
|
||||
if (!binding || seen.has(binding)) return false;
|
||||
seen.add(binding);
|
||||
const identity = imported(node);
|
||||
if (identity) {
|
||||
const specifier = identity.definition.node;
|
||||
return (
|
||||
specifier.type === "ImportSpecifier" &&
|
||||
identity.expected.has(
|
||||
specifier.imported.name ?? specifier.imported.value,
|
||||
)
|
||||
);
|
||||
}
|
||||
const definition = binding.defs[0];
|
||||
return (
|
||||
definition?.type === "Variable" &&
|
||||
definition.parent.kind === "const" &&
|
||||
definition.node.id.type === "Identifier" &&
|
||||
isCatalog(definition.node.init, seen)
|
||||
);
|
||||
}
|
||||
if (node.type === "MemberExpression" || node.type === "TSQualifiedName") {
|
||||
const object =
|
||||
node.type === "MemberExpression" ? node.object : node.left;
|
||||
const identity = imported(object);
|
||||
if (identity?.definition.node.type !== "ImportNamespaceSpecifier")
|
||||
return false;
|
||||
const property =
|
||||
node.type === "MemberExpression" ? node.property : node.right;
|
||||
const name = node.computed ? property.value : property.name;
|
||||
return identity.expected.has(name);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const selectedContract = (raw, seen = new Set()) => {
|
||||
const node = unwrap(raw);
|
||||
if (!node) return undefined;
|
||||
if (
|
||||
node.type === "MemberExpression" &&
|
||||
node.computed &&
|
||||
numeric(node.property) &&
|
||||
isCatalog(node.object)
|
||||
)
|
||||
return node;
|
||||
if (node.type === "CallExpression") {
|
||||
const callee = unwrap(node.callee);
|
||||
if (
|
||||
callee?.type === "MemberExpression" &&
|
||||
(callee.computed ? callee.property.value : callee.property.name) ===
|
||||
"at" &&
|
||||
numeric(node.arguments[0]) &&
|
||||
isCatalog(callee.object)
|
||||
)
|
||||
return node;
|
||||
}
|
||||
if (node.type !== "Identifier") return undefined;
|
||||
const binding = variable(node);
|
||||
if (!binding || seen.has(binding)) return undefined;
|
||||
seen.add(binding);
|
||||
const definition = binding.defs[0];
|
||||
if (definition?.type !== "Variable" || definition.parent.kind !== "const")
|
||||
return undefined;
|
||||
if (definition.node.id.type === "Identifier")
|
||||
return selectedContract(definition.node.init, seen);
|
||||
if (
|
||||
definition.node.id.type === "ArrayPattern" &&
|
||||
definition.node.id.elements.some(
|
||||
(element) =>
|
||||
element?.type === "Identifier" && element.name === node.name,
|
||||
) &&
|
||||
isCatalog(definition.node.init)
|
||||
)
|
||||
return definition.node;
|
||||
return undefined;
|
||||
};
|
||||
const bindingFields = new Set(["name", "inputSchema", "outputSchema"]);
|
||||
const isRegistrationOptions = (node) => {
|
||||
if (node?.type !== "Identifier") return false;
|
||||
const definition = variable(node)?.defs[0];
|
||||
if (
|
||||
definition?.type !== "ImportBinding" ||
|
||||
definition.node.type !== "ImportSpecifier"
|
||||
)
|
||||
return false;
|
||||
const declaration = definition.parent ?? definition.node.parent;
|
||||
const specifier = declaration.source?.value;
|
||||
const path = resolveImportSource(filename, specifier)?.replace(
|
||||
/\.(?:[cm]?[jt]s)$/u,
|
||||
"",
|
||||
);
|
||||
const name =
|
||||
definition.node.imported.name ?? definition.node.imported.value;
|
||||
return (
|
||||
path === resolve(root, "src/server/toolRegistrationOptions") &&
|
||||
[
|
||||
"toolRegistrationOptions",
|
||||
"applicationToolRegistrationOptions",
|
||||
].includes(name)
|
||||
);
|
||||
};
|
||||
const reported = new Set();
|
||||
const reportSelection = (raw) => {
|
||||
const node = selectedContract(raw);
|
||||
if (node && !reported.has(node)) {
|
||||
reported.add(node);
|
||||
context.report({ node, messageId: "positional" });
|
||||
}
|
||||
};
|
||||
return {
|
||||
MemberExpression(node) {
|
||||
const name = node.computed ? node.property.value : node.property.name;
|
||||
const parent = node.parent;
|
||||
const writesProperty =
|
||||
(parent?.type === "AssignmentExpression" && parent.left === node) ||
|
||||
parent?.type === "UpdateExpression" ||
|
||||
(parent?.type === "UnaryExpression" && parent.operator === "delete");
|
||||
if (bindingFields.has(name) && !writesProperty)
|
||||
reportSelection(node.object);
|
||||
},
|
||||
CallExpression(node) {
|
||||
if (isRegistrationOptions(unwrap(node.callee)))
|
||||
reportSelection(node.arguments[0]);
|
||||
},
|
||||
VariableDeclarator(node) {
|
||||
if (
|
||||
node.id.type === "ObjectPattern" &&
|
||||
node.id.properties.some(
|
||||
(property) =>
|
||||
property.type === "Property" &&
|
||||
bindingFields.has(
|
||||
property.computed ? property.key.value : property.key.name,
|
||||
),
|
||||
)
|
||||
)
|
||||
reportSelection(node.init);
|
||||
},
|
||||
TSIndexedAccessType(node) {
|
||||
if (
|
||||
node.objectType.type === "TSTypeQuery" &&
|
||||
node.indexType.type === "TSLiteralType" &&
|
||||
numeric(node.indexType.literal) &&
|
||||
isCatalog(node.objectType.exprName)
|
||||
)
|
||||
context.report({ node, messageId: "positional" });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { RuleTester } from "oxlint/plugins-dev";
|
||||
|
||||
declare const rule: Parameters<RuleTester["run"]>[1];
|
||||
export default rule;
|
||||
@@ -0,0 +1,116 @@
|
||||
import { relative, isAbsolute } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { findBinding } from "../bindings.mjs";
|
||||
import { projectForTest } from "../../lib/test-projects.mjs";
|
||||
|
||||
const repository = fileURLToPath(new URL("../../../", import.meta.url));
|
||||
const mockMethods = new Set(["mock", "doMock"]);
|
||||
|
||||
const unwrap = (node) =>
|
||||
node?.type === "ChainExpression" ? node.expression : node;
|
||||
|
||||
const propertyName = (member) => {
|
||||
if (!member?.computed && member?.property?.type === "Identifier")
|
||||
return member.property.name;
|
||||
if (member?.computed && member.property?.type === "Literal")
|
||||
return member.property.value;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const importedFromVitest = (sourceCode, identifier) => {
|
||||
const binding = findBinding(sourceCode, identifier);
|
||||
const definition = binding?.defs[0];
|
||||
if (definition?.type !== "ImportBinding") return undefined;
|
||||
const declaration = definition.parent ?? definition.node.parent;
|
||||
if (declaration?.source?.value !== "vitest") return undefined;
|
||||
return definition.node;
|
||||
};
|
||||
|
||||
const isViImport = (sourceCode, identifier) => {
|
||||
const specifier = importedFromVitest(sourceCode, identifier);
|
||||
return (
|
||||
specifier?.type === "ImportSpecifier" &&
|
||||
(specifier.imported.name ?? specifier.imported.value) === "vi"
|
||||
);
|
||||
};
|
||||
|
||||
const isVitestNamespace = (sourceCode, identifier) =>
|
||||
importedFromVitest(sourceCode, identifier)?.type ===
|
||||
"ImportNamespaceSpecifier";
|
||||
|
||||
const isVitestViMember = (sourceCode, node) => {
|
||||
const member = unwrap(node);
|
||||
if (
|
||||
member?.type !== "MemberExpression" ||
|
||||
propertyName(member) !== "vi" ||
|
||||
member.object?.type !== "Identifier"
|
||||
)
|
||||
return false;
|
||||
return isVitestNamespace(sourceCode, member.object);
|
||||
};
|
||||
|
||||
const isModuleMockCall = (sourceCode, callee) => {
|
||||
const method = unwrap(callee);
|
||||
if (
|
||||
method?.type !== "MemberExpression" ||
|
||||
!mockMethods.has(propertyName(method))
|
||||
)
|
||||
return false;
|
||||
const receiver = unwrap(method.object);
|
||||
return (
|
||||
(receiver?.type === "Identifier" && isViImport(sourceCode, receiver)) ||
|
||||
isVitestViMember(sourceCode, receiver)
|
||||
);
|
||||
};
|
||||
|
||||
const isolationForFile = (filename, cwd) => {
|
||||
if (typeof filename !== "string" || !filename.endsWith(".test.ts"))
|
||||
return undefined;
|
||||
const testPath = relative(cwd, filename).split("\\").join("/");
|
||||
if (
|
||||
testPath === "" ||
|
||||
isAbsolute(testPath) ||
|
||||
testPath === ".." ||
|
||||
testPath.startsWith("../")
|
||||
)
|
||||
return undefined;
|
||||
try {
|
||||
return projectForTest(testPath).isolate !== false;
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message.startsWith("No test project owns ")
|
||||
)
|
||||
return undefined;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Keep Vitest module mocks in projects with per-file module isolation.",
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
shared:
|
||||
"Vitest module mocks need an isolated test project; move the test to an isolated project or replace the module mock with a scoped spy or injected dependency.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const filename = context.physicalFilename ?? context.filename;
|
||||
const sourceCode = context.sourceCode;
|
||||
const cwd =
|
||||
context.cwd ?? context.getCwd?.() ?? sourceCode.getCwd?.() ?? repository;
|
||||
if (isolationForFile(filename, cwd) !== false) return {};
|
||||
return {
|
||||
CallExpression(node) {
|
||||
if (isModuleMockCall(sourceCode, node.callee))
|
||||
context.report({ node, messageId: "shared" });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export declare const collectToolCatalogSources: (
|
||||
entryPath: string,
|
||||
readSource?: (path: string) => string,
|
||||
) => ReadonlyMap<string, ReadonlySet<string>>;
|
||||
@@ -0,0 +1,118 @@
|
||||
import { parse } from "@babel/parser";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
const withoutExtension = (path) => path.replace(/\.(?:[cm]?[jt]s)$/u, "");
|
||||
const arrayExpression = (raw) => {
|
||||
let node = raw;
|
||||
while (
|
||||
node &&
|
||||
["TSAsExpression", "TSSatisfiesExpression"].includes(node.type)
|
||||
)
|
||||
node = node.expression;
|
||||
return node;
|
||||
};
|
||||
|
||||
/** Read only source composition, never execute the runtime contract catalog. */
|
||||
export const collectToolCatalogSources = (
|
||||
entryPath,
|
||||
readSource = (path) => readFileSync(path, "utf8"),
|
||||
) => {
|
||||
const catalogs = new Map();
|
||||
const modules = new Map();
|
||||
const active = new Set();
|
||||
const complete = new Set();
|
||||
const moduleFacts = (path) => {
|
||||
let facts = modules.get(path);
|
||||
if (facts) return facts;
|
||||
// Read/parse errors intentionally escape: a broken inventory cannot disable
|
||||
// the rule by presenting an empty set of catalog bindings.
|
||||
const ast = parse(readSource(path), {
|
||||
sourceType: "module",
|
||||
plugins: ["typescript"],
|
||||
});
|
||||
const imports = new Map();
|
||||
const exports = new Map();
|
||||
const locals = new Map();
|
||||
for (const statement of ast.program.body) {
|
||||
if (statement.type === "ImportDeclaration") {
|
||||
for (const specifier of statement.specifiers) {
|
||||
if (specifier.type === "ImportSpecifier")
|
||||
imports.set(specifier.local.name, {
|
||||
source: statement.source.value,
|
||||
name: specifier.imported.name ?? specifier.imported.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
const declarations =
|
||||
statement.type === "ExportNamedDeclaration"
|
||||
? statement.declaration
|
||||
: statement;
|
||||
if (declarations?.type === "VariableDeclaration")
|
||||
for (const declaration of declarations.declarations)
|
||||
if (declaration.id.type === "Identifier") {
|
||||
locals.set(declaration.id.name, declaration.init);
|
||||
if (statement.type === "ExportNamedDeclaration")
|
||||
exports.set(declaration.id.name, declaration.init);
|
||||
}
|
||||
}
|
||||
facts = { imports, exports, locals };
|
||||
modules.set(path, facts);
|
||||
return facts;
|
||||
};
|
||||
const visit = (path, name) => {
|
||||
const key = JSON.stringify([path, name]);
|
||||
if (complete.has(key)) return;
|
||||
if (active.has(key))
|
||||
throw new Error(`Cyclic tool catalog composition: ${path}#${name}`);
|
||||
active.add(key);
|
||||
const facts = moduleFacts(path);
|
||||
const array = arrayExpression(facts.exports.get(name));
|
||||
if (array?.type !== "ArrayExpression")
|
||||
throw new Error(`Expected exported tool catalog array: ${path}#${name}`);
|
||||
const source = withoutExtension(path);
|
||||
const names = catalogs.get(source) ?? new Set();
|
||||
names.add(name);
|
||||
catalogs.set(source, names);
|
||||
const composition = (raw, localPath = new Set()) => {
|
||||
const expression = arrayExpression(raw);
|
||||
if (expression?.type === "ArrayExpression") {
|
||||
for (const element of expression.elements)
|
||||
if (element?.type === "SpreadElement")
|
||||
composition(element.argument, localPath);
|
||||
return;
|
||||
}
|
||||
// Calls such as HOPPER_MEMORY_TOOL_DEFINITIONS.map(official) construct
|
||||
// entries locally; they are not imported catalog arrays. Do not execute
|
||||
// or infer catalog ownership from arbitrary computed expressions.
|
||||
if (expression?.type !== "Identifier") return;
|
||||
const binding = facts.imports.get(expression.name);
|
||||
if (binding) {
|
||||
if (!binding.source.startsWith("."))
|
||||
throw new Error(`Expected local catalog module: ${path}#${name}`);
|
||||
visit(
|
||||
`${withoutExtension(resolve(dirname(path), binding.source))}.ts`,
|
||||
binding.name,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (localPath.has(expression.name))
|
||||
throw new Error(
|
||||
`Cyclic local catalog composition: ${path}#${expression.name}`,
|
||||
);
|
||||
if (!facts.locals.has(expression.name))
|
||||
throw new Error(
|
||||
`Unresolved local catalog spread: ${path}#${expression.name}`,
|
||||
);
|
||||
composition(
|
||||
facts.locals.get(expression.name),
|
||||
new Set([...localPath, expression.name]),
|
||||
);
|
||||
};
|
||||
composition(array);
|
||||
active.delete(key);
|
||||
complete.add(key);
|
||||
};
|
||||
visit(entryPath, "TOOL_CONTRACTS");
|
||||
return catalogs;
|
||||
};
|
||||
@@ -15,6 +15,7 @@ import { tsImport } from "tsx/esm/api";
|
||||
|
||||
import { PRODUCT_IDENTITY } from "../dist/identity.js";
|
||||
import { createAgentEvaluationCommands } from "./lib/agent-evaluation-commands.mjs";
|
||||
import { assertAgentEvaluationReportPassed } from "./lib/agent-evaluation-report-gate.mjs";
|
||||
import {
|
||||
closeVerifierResources,
|
||||
withVerifierCleanup,
|
||||
@@ -44,7 +45,7 @@ const {
|
||||
).href,
|
||||
import.meta.url,
|
||||
);
|
||||
const { agentEvaluationPassed, summarizeFactualCorrectness } = await tsImport(
|
||||
const { summarizeFactualCorrectness } = await tsImport(
|
||||
new URL(
|
||||
"../tests/evaluation/model-evals/AgentEvaluationReport.ts",
|
||||
import.meta.url,
|
||||
@@ -413,11 +414,7 @@ await withVerifierCleanup(
|
||||
await writeFile(resolve(reportPath), encoded);
|
||||
}
|
||||
|
||||
const failed = results.filter((result) => !agentEvaluationPassed(result));
|
||||
if (failed.length > 0)
|
||||
throw new Error(
|
||||
`Agent routing/workflow/answer checks failed: ${failed.map(({ id }) => id).join(", ")}`,
|
||||
);
|
||||
assertAgentEvaluationReportPassed(results);
|
||||
},
|
||||
// Even a deliberately retained fixture directory must never retain auth.
|
||||
[
|
||||
|
||||
@@ -35,11 +35,27 @@ if (
|
||||
!isAbsolute(strace ?? "")
|
||||
)
|
||||
throw new Error(
|
||||
"verify:binary:layout requires Linux x64, absolute REA_PWNTOOLS_PYTHON (pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2) and REA_VERIFY_STRACE_COMMAND. gcc, ld and strip must be available. No target is executed.",
|
||||
"verify:binary:layout requires Linux x64, absolute REA_PWNTOOLS_PYTHON (pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2) and REA_VERIFY_STRACE_COMMAND. gcc, ld, strip and the i686/ARM/MIPS/MIPSel linux-gnu cross gcc and readelf must be available. No target is executed.",
|
||||
);
|
||||
const run = createVerifierRun();
|
||||
const execute = promisify(execFile);
|
||||
for (const command of ["gcc", "ld", "strip", "/usr/bin/prlimit", strace]) {
|
||||
const ELF32_TOOLCHAINS = [
|
||||
["i686-linux-gnu", "EM_386", "little"],
|
||||
["arm-linux-gnueabi", "EM_ARM", "little"],
|
||||
["mips-linux-gnu", "EM_MIPS", "big"],
|
||||
["mipsel-linux-gnu", "EM_MIPS", "little"],
|
||||
];
|
||||
for (const command of [
|
||||
"gcc",
|
||||
"ld",
|
||||
"strip",
|
||||
"/usr/bin/prlimit",
|
||||
strace,
|
||||
...ELF32_TOOLCHAINS.flatMap(([prefix]) => [
|
||||
`${prefix}-gcc`,
|
||||
`${prefix}-readelf`,
|
||||
]),
|
||||
]) {
|
||||
try {
|
||||
await execute(command, [command === strace ? "-V" : "--version"], {
|
||||
timeout: 10_000,
|
||||
@@ -328,6 +344,163 @@ try {
|
||||
cases++;
|
||||
}
|
||||
}
|
||||
// ELF32 profiles: cross-compiled, never executed, checked against GNU readelf.
|
||||
for (const [
|
||||
prefix,
|
||||
machine,
|
||||
byteOrder,
|
||||
kind,
|
||||
flags,
|
||||
] of ELF32_TOOLCHAINS.flatMap((toolchain) => [
|
||||
[...toolchain, "executable", ["-fPIE", "-pie"]],
|
||||
[...toolchain, "relocatable", ["-c", "-fPIC"]],
|
||||
])) {
|
||||
const path = join(root.path, `${prefix}-${kind}`);
|
||||
await execute(`${prefix}-gcc`, ["-O1", ...flags, source, "-o", path], {
|
||||
timeout: 30_000,
|
||||
maxBuffer: 1024 * 1024,
|
||||
});
|
||||
const { stdout } = await execute(`${prefix}-readelf`, ["-SWsr", path], {
|
||||
timeout: 10_000,
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
env: { ...process.env, LC_ALL: "C" },
|
||||
});
|
||||
const expectedSections = [
|
||||
...stdout.matchAll(
|
||||
/^\s*\[\s*(\d+)\]\s+(\S+)\s+\S+\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+[0-9a-f]+\s+[A-Za-z]*\s+(\d+)\s+\d+\s+\d+\s*$/gm,
|
||||
),
|
||||
]
|
||||
.filter(([, index]) => index !== "0")
|
||||
.map(([, index, name, address, offset, size, link]) => ({
|
||||
index: Number(index),
|
||||
name,
|
||||
address: BigInt(`0x${address}`),
|
||||
offset: BigInt(`0x${offset}`),
|
||||
size: BigInt(`0x${size}`),
|
||||
link: Number(link),
|
||||
}));
|
||||
assert.ok(
|
||||
expectedSections.length > 0,
|
||||
`readelf listed no sections for ${path}`,
|
||||
);
|
||||
const expectedSymbols = [];
|
||||
for (const [, name, count, body] of stdout.matchAll(
|
||||
/^Symbol table '([^']+)' contains (\d+) entr(?:y|ies):\n((?:[^\n]+\n?)*)/gm,
|
||||
)) {
|
||||
const table = expectedSections.find((section) => section.name === name);
|
||||
assert.notEqual(table, undefined, `readelf symbol table absent: ${name}`);
|
||||
const rows = [...body.matchAll(/^\s*(\d+):\s+([0-9a-f]+)\s+(\d+)\s/gm)];
|
||||
assert.equal(rows.length, Number(count), `readelf symbol count: ${name}`);
|
||||
for (const [, index, value, size] of rows)
|
||||
expectedSymbols.push({
|
||||
table_index: table.index,
|
||||
entry_index: Number(index),
|
||||
value: BigInt(`0x${value}`),
|
||||
size: BigInt(size),
|
||||
});
|
||||
}
|
||||
assert.ok(
|
||||
expectedSymbols.length > 0,
|
||||
`readelf listed no symbols for ${path}`,
|
||||
);
|
||||
const expectedRelocations = [];
|
||||
for (const [, name, count, body] of stdout.matchAll(
|
||||
/^Relocation section '([^']+)' at offset 0x[0-9a-f]+ contains (\d+) entr(?:y|ies):\n((?:[^\n]+\n?)*)/gm,
|
||||
)) {
|
||||
const section = expectedSections.find((section) => section.name === name);
|
||||
assert.notEqual(
|
||||
section,
|
||||
undefined,
|
||||
`readelf relocation section absent: ${name}`,
|
||||
);
|
||||
const rows = [...body.matchAll(/^\s*([0-9a-f]+)\s+([0-9a-f]+)\s+\S+/gm)];
|
||||
assert.equal(
|
||||
rows.length,
|
||||
Number(count),
|
||||
`readelf relocation count: ${name}`,
|
||||
);
|
||||
for (const [entryIndex, [, offset, info]] of rows.entries()) {
|
||||
// ELF32 r_info uses the low byte for type and the upper 24 bits for
|
||||
// the symbol index. readelf has already decoded the file's byte order.
|
||||
const decodedInfo = BigInt(`0x${info}`);
|
||||
expectedRelocations.push({
|
||||
section_index: section.index,
|
||||
entry_index: entryIndex,
|
||||
reported_offset: BigInt(`0x${offset}`),
|
||||
type: Number(decodedInfo & 0xffn),
|
||||
symbol_table_index: section.link,
|
||||
symbol_index: Number(decodedInfo >> 8n),
|
||||
});
|
||||
}
|
||||
}
|
||||
assert.ok(
|
||||
expectedRelocations.length > 0,
|
||||
`readelf listed no relocations for ${path}`,
|
||||
);
|
||||
const bytes = await readFile(path);
|
||||
for (const mode of ["cli", "mcp"]) {
|
||||
const value = await inspect(mode, path);
|
||||
assert.deepEqual(value.architecture, {
|
||||
machine,
|
||||
bits: 32,
|
||||
byte_order: byteOrder,
|
||||
});
|
||||
assert.equal(
|
||||
value.image_type,
|
||||
kind === "executable" ? "ET_DYN" : "ET_REL",
|
||||
);
|
||||
for (const section of expectedSections) {
|
||||
const reported = value.sections[section.index];
|
||||
assert.equal(reported.name.display, section.name);
|
||||
assert.equal(BigInt(reported.address), section.address);
|
||||
assert.equal(BigInt(reported.offset), section.offset);
|
||||
assert.equal(BigInt(reported.size), section.size);
|
||||
}
|
||||
assert.equal(value.sections.length, expectedSections.length + 1);
|
||||
assert.deepEqual(
|
||||
value.symbols.map(({ table_index, entry_index, value, size }) => ({
|
||||
table_index,
|
||||
entry_index,
|
||||
value: BigInt(value),
|
||||
size: BigInt(size),
|
||||
})),
|
||||
expectedSymbols,
|
||||
`${mode} symbol values differ from readelf for ${path}`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
value.relocations.map(
|
||||
({
|
||||
section_index,
|
||||
entry_index,
|
||||
reported_offset,
|
||||
type,
|
||||
symbol_table_index,
|
||||
symbol_index,
|
||||
}) => ({
|
||||
section_index,
|
||||
entry_index,
|
||||
reported_offset: BigInt(reported_offset),
|
||||
type,
|
||||
symbol_table_index,
|
||||
symbol_index,
|
||||
}),
|
||||
),
|
||||
expectedRelocations,
|
||||
`${mode} relocations differ from readelf for ${path}`,
|
||||
);
|
||||
if (kind === "executable") {
|
||||
assert.ok(value.segments.length > 0);
|
||||
assert.equal(value.linkage.interpreters.length, 1);
|
||||
assert.ok(
|
||||
value.linkage.needed_libraries.some(
|
||||
(library) => library.display === "libc.so.6",
|
||||
),
|
||||
);
|
||||
}
|
||||
assert.deepEqual(await readFile(path), bytes);
|
||||
cases++;
|
||||
}
|
||||
}
|
||||
const protectedReport = reports.get("protected");
|
||||
// Ordinary data markers are not a request to discover/decompress a kernel config.
|
||||
const markerBytes = Buffer.from(await readFile(join(root.path, "protected")));
|
||||
|
||||
Loaded 100 of 998 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user