* refactor: prune internal seams and preserve configuration ownership
* fix(setup): keep YAML aliases and inherited registration settings on refresh
- Reuse an unchanged YAML collection only while every alias in it still
resolves after the edit; otherwise rebuild it so the alias becomes its
value. A sibling anchor replaced or moved in the same refresh no longer
leaves an unresolved alias in Hermes or Goose output.
- Keep a connection selector whose existing value already chooses stdio
(for example an inherited Hermes transport: stdio) instead of superseding
it; remote selectors are still replaced. Explicit stdio selectors are
therefore no longer rewritten (Kimi expectation updated).
- When the whole Hermes registration is inherited through a merge,
override it with that merge and write only the changed fields.
* fix(setup): edit registration fields whose YAML key is an alias
A key written as an alias to a string scalar (for example *field with
field: &field env) was treated as a source-only key, so any change to
its value was refused in Hermes and Goose. Resolve such keys to their
string, keep the aliased spelling while its anchor survives the edit,
and write the plain key when the same edit rebuilds the anchor.
* test(process): bound cleanup-boundary waits and always release gates
Both cancellation tests awaited a bare started promise and released
their gates only on success, so a missed boundary waited for the outer
deadline and could leave cleanup parked. Use awaitAcknowledgement and
release and join the cleanup in finally.
* fix(setup): repair only aliases an edit leaves unresolved
Replace the pre-edit alias survival guess, which expanded aliases to
sibling anchors that survive unchanged, with an exact repair: record
what each alias in the replaced value resolves to, write the edit, then
replace only aliases left without a preceding anchor by that value.
- Keep an aliased key's source position, so its anchor stays ahead of
later aliases.
- Treat an aliased key as its string only when no other key in the map
resolves to the same string; an ambiguous one stays guarded.
- Override an inherited Hermes registration only when the requested
entry keeps every inherited field, so an explicit replacement can drop
remote selectors.
* fix(setup): repair aliases whose binding an edit changes, keeping comments
An alias whose own anchor was removed could resolve to an earlier anchor
with the same name and silently change value. Record the node each alias
resolved to and repair any alias that no longer resolves to that node.
Carry the alias's comments and spacing onto the repaired value.
* test(setup): split the Hermes anchor and merge refresh cases
* fix(setup): refuse removing an overridden key its merge would restore
The merge-retention check skipped keys the mapping also overrides, so
dropping such a key (for example a remote url during an explicit stdio
replacement) let the retained merge restore it. Check removal before
skipping overridden keys, so the edit is refused with the existing
message instead of producing a registration that cannot be applied.
* fix(pe): 保留证书名称的 RDN 分组
* fix(pe): distinguish multi-valued RDNs in certificate name text
Join attributes that share an RDN with "+" and keep ", " between RDNs,
in encoded order. RFC 4514's RDN reversal is not applied, so names made
only of single-valued RDNs keep their existing spelling.
OID 2.5.4.10 is organizationName, so one RDN renders as
commonName=A+organizationName=B and two RDNs as
commonName=A, organizationName=B.
---------
Co-authored-by: AixSxT <245164663+AixSxT@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Keep opportunistic resource probes outside unadmitted PE layouts; move repeated malformed controls to the public adapter matrix.
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
* feat(artifacts): inventory and extract Deno compile executables
Adds Deno standalone artifact support for Mach-O, ELF, and PE carriers,
exposing virtual files, entry points, recorded options, and file digests
without running the executable. Extraction uses the existing safe output
tree, preserves symlink targets as metadata, and assigns indexed paths
to unsafe or oversized names. Environment-file values and certificate
bytes remain in local Evidence; npm snapshots are reported by length
without decoding. The documentation describes supported layouts, parsing
limits, and refusal behavior.
* fix(artifacts): resolve nested Intel Deno carriers to the enclosing app
The backward trailer search stopped at the first confirmed sentinel, so an
Intel app storing another Intel Deno executable as a virtual file reported
the inner app. Search to the slice start, drop sentinels inside a confirmed
payload, and fail closed on side-by-side trailers that differ.
Correct the docs on retained environment values and generated regions.
* fix(artifacts): keep Deno coverage, remote paths and PE resources truthful
Report unknown coverage when the virtual filesystem cannot be listed, keep
resolved specifiers when another remote ID is missing, and percent-encode
characters in remote paths that a Windows destination would refuse. Reject a
directory at the PE resource language level, and correct the guide's
description of colliding generated regions.
* fix(artifacts): normalize Deno remote escapes so hex case cannot collide
Uppercase every percent escape in a generated remote path, so specifiers that
differ only in escape case reach the path registry as one name and the second
gets an indexed path instead of colliding on a case-insensitive disk.
* fix(artifacts): fail closed on truncated enclosing Deno trailers and encode reserved remote path characters
The Intel trailer scan rejected an overlong sentinel before reading its
marker, so a truncated enclosing app vanished and the nested app was
reported. Read the marker first and fail closed when a confirmed overlong
trailer encloses another trailer.
encodeURIComponent leaves `*` alone and the sanitizer covered only part of
the Windows reserved set. remoteDestination now encodes the whole set once
as uppercase percent hex; source_path keeps the original.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuWHGvzJkrhCqjtEHUwqyH
* fix(artifacts): defer truncated Intel Deno trailer failure until enclosing trailers resolve
A damaged Intel asset stored before a valid one inside a valid app made the
backward trailer search fail before it reached the outer trailer. Hold that
failure until the search ends and drop it when a confirmed enclosing trailer
holds the sentinel as data.
* fix(pe): fail closed when a resource type branch is unreadable beside a found name
namedResourceTargets skipped a type branch whose names directory, or a
name entry whose string, could not be read. A duplicate RT_RCDATA entry
pointing out of bounds therefore vanished once another branch matched,
and locatePeResourcePayloads returned only the readable ranges while
parsePeResources rejected the same tree.
The walk now records unresolved branches. hasPeResourceName still
reports false for a tree that never reaches the name, but once
locatePeResourcePayloads finds the name it raises a format failure if
any branch was left unresolved.
* fix(artifacts): skip Intel Deno sentinels that a native segment maps
A non-Deno x86_64 Mach-O that stores an Intel Deno executable in __DATA
had the asset's trailer promoted to the root application, because the
backward sentinel search never checked who owned the bytes it scanned.
locateMachOSlice now records the file ranges of every segment other than
__SUI and __LINKEDIT (libsui extends __LINKEDIT over the trailer), and
the Intel search skips any sentinel inside one of them. A slice whose
load commands do not frame stays with native inventory, since ownership
cannot be shown there.
* fix(artifacts): reject a universal Deno image with an unreadable sibling slice
The universal Mach-O locator skipped any slice whose load commands could
not be read or framed, and any slice with a FAT range issue. When another
slice confirmed Deno, that slice was reported as the whole image, so a
sibling declaring a different Deno application went unexamined.
locateMachOSlice now reports why part of a slice was not examined
(unreadable or unframed load commands, a truncated slice, or the Intel
sentinel allowance). locateMachO collects those reasons for every
declared and hidden slice and fails closed once any slice confirms a
Deno section. With none confirmed, the image stays with native inventory
as before.
* fix(scripts): supervise the Deno verifier version check
Run `deno --version` through the verifier command owner with a deadline
instead of raw execFile, and close the owner when the version is refused.
* fix(artifacts): keep unconfirmed Deno carriers native and fail truncated Intel trailers
An ELF SUI note whose descriptor is too short for the full d3n0l4nd name
now stays unconfirmed instead of failing closed; failures after the name
matches still fail closed.
Every marker-confirmed Intel trailer whose payload runs past the slice is
now kept until the backward search ends and fails unless an enclosing
payload contains it, so an Intel Deno app missing its last byte reports a
Deno failure instead of native inventory.
The Deno MCP boundary test now calls open_binary and inspect_artifact
through artifactMcpCall, as docs/testing.md requires.
* test(deno): drop source-text checks from verifier pin test
Keep the wrong-version subprocess check, which covers the pinned
release through behavior, and stop asserting on the verifier script's
source spelling.
* test(deno): close readers in finally in symlink and cancellation tests
A failed assertion or read error in either test skipped reader.close(),
leaving the file open while temp-directory cleanup ran. Both tests now
close the reader in finally, as CONTRIBUTING.md requires. The
closed-reader assertion in the symlink test stays after the finally.
* docs(specs): add deno-standalone spec, plan and tasks
Add docs/specs/artifacts/deno-standalone/ with spec.md, plan.md and
tasks.md for the Deno compile executable reader, as AGENTS.md requires
for a feature, and list them in the specifications index.
* docs: list the Deno compile guide in the documentation sidebar
* fix(deno): confirm the d3n0l4nd section before claiming Mach-O damage
A __SUI segment alone no longer makes later load-command damage Deno's.
A Mach-O whose __SUI segment holds only a differently named section now
stays absent and keeps native inventory, while damage seen before or
beside a confirmed d3n0l4nd section still fails closed.
* fix(deno): keep a slice with an unparsed segment command unread
A Mach-O segment command that frames but cannot be parsed may be the
__SUI segment, so the slice is no longer treated as fully examined. A
universal image whose other slice confirms Deno now fails closed instead
of letting that sibling supply the whole inventory, and with no confirmed
sibling the image still keeps native inventory.
* fix(deno): confirm an ELF SUI note before rejecting its length
A SUI note whose descsz runs past its note table was recorded as plain
damage before its descriptor name was read, so a malformed Deno carrier
fell back to native inventory. The owner and the d3n0l4nd name prefix are
now read within the table apart from the claimed extent, and a confirmed
note with an oversized extent fails closed.
* fix(deno): confirm a truncated __SUI section record by its name
A section record cut short after its 16-byte name was skipped, so a
damaged Deno carrier fell back to native inventory. The name is now read
apart from the rest of the record, and a truncated record that names
d3n0l4nd fails closed. The range checks for a confirmed record move into
their own helper.
* fix(deno): reject nonzero bytes after the last ELF note
When fewer than 12 bytes remained in a note table after its last note,
the walk stopped without looking at them, so a Deno note was selected
with complete coverage beside unaccounted data. The remainder is now
read: zero padding is accepted, and nonzero bytes are recorded as damage,
which fails closed once a SUI note is confirmed and otherwise leaves the
ELF to native inventory, matching the native note parser.
* fix(deno): validate FAT slice identity before choosing the search
The locator took a universal slice's CPU type from its FAT row, so a row
that called an x86_64 slice arm64 skipped that slice's Intel trailer and
let the arm64 payload stand for the whole image. Each slice's header is
now checked against its row with validateFatSliceIdentity. A mismatch
leaves the slice unread, so a universal image whose other slice confirms
Deno fails closed. The Intel fixture header now states
CPU_SUBTYPE_X86_64_ALL so its FAT rows can match it exactly.
* fix(deno): check the Intel trailer marker before judging its length
A trailer length word of 2^53 or more was dropped before its d3n0l4nd
marker was read, so an enclosing app with that length was skipped and a
nested app's range stood for the whole image. The marker is now read
first, and a marker-confirmed trailer whose length runs past the slice,
safe or not, joins the deferred truncated candidates and fails closed
unless an enclosing trailer holds it as data.
* fix(deno): count short ELF note tables against the note allowance
A note table shorter than one note header made a read of its trailing
bytes but counted no note, so many distinct tiny tables could force one
sequential read each past the 256-note allowance. Every nonempty table
now counts at least one entry, both in the up-front estimate and while
scanning, so an image past the allowance records the limit instead of
reading every table. The trailing-byte check moves into its own helper.
* fix(deno): fail closed on ELF extended section numbering beside a SUI note
A zero e_shnum with a nonzero e_shoff moves the section count into
section zero, so the section table was skipped and its SHT_NOTE tables
were never compared. Record it as damage, as PN_XNUM already is, so a
confirmed SUI note fails closed instead of reporting full coverage.
* fix(deno): keep undersized Intel trailers until enclosing ownership is known
A trailer length word below 8 was dropped before its d3n0l4nd marker was
read, so an enclosing app with a length of 0 to 7 was skipped and a nested
app's trailer was selected as the root. The marker is now read first, and a
marker-confirmed trailer too short to hold it is kept as malformed and fails
closed unless an enclosing trailer holds it as data.
* fix(pe): treat non-directory RT_RCDATA branches as unresolved
A duplicate typeId root entry whose target is a data entry was skipped
without marking the walk unresolved, so locatePeResourcePayloads returned
the readable named resource as complete. Route non-directory type
branches through the same unresolved path as unreadable names
directories so the Deno reader fails closed.
* fix(deno): fail closed on a zero-fill d3n0l4nd Mach-O section
denoSectionRange admitted the declared offset and size of a confirmed
__SUI,d3n0l4nd record without checking its section type, so a
S_ZEROFILL, S_GB_ZEROFILL or S_THREAD_LOCAL_ZEROFILL section could be
read as a file payload while MachoImage reports it as not file backed.
Reject zero-fill types with the shared isMachoZeroFillSection predicate
before assigning a physical range.
* fix(deno): bound the d3n0l4nd Mach-O section by its segment file extent
denoSectionRange checked a confirmed __SUI,d3n0l4nd record only against
the slice and file bounds, so a section reaching past its segment's
fileoff/filesize was still read as a complete payload while MachoImage
reports the same section as not file backed. Reject a section outside
its enclosing segment's file extent before admitting the physical range.
* style(deno): apply oxfmt to the DenoStandaloneLocation embedding union
* fix(artifacts): preserve Deno carrier ownership
* fix(artifacts): admit Deno parent segments
* fix(deno): preserve native segment ownership
* test(deno): cover mismatched Mach-O segment widths
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
* feat(v8): inspect V8 code cache containers and their retained byte patterns
Bytenode-compiled `.jsc` files — the V8 code cache container that `vm.Script`,
`vm.compileFunction` and bytenode 2.x write, and that Node single-executable applications
and Electron apps ship — had no format-native reader. `inspect_binary_layout` shows the
surrounding ELF/PE and `inspect_artifact` inventories the file, but neither decodes the V8
structures, so an agent could not tell which V8 line produced the file, could not learn the
container is metadata rather than source, and had nothing to point at a version-pinned
decompiler. Issue #8543.
`inspect_v8_code_cache` joins the format-native family with `inspect_python_pyc`,
`inspect_erlang_beam` and `inspect_kernel_module`: one explicit local file, bounded parsing,
no execution, no installation, no child process.
**The container is version-dependent, so the reader reports roles with confidence instead
of assuming an ABI.** All eight header words are reported with byte offset, observed value,
the role this reader believes the word has, and whether that role is `confirmed`,
`inferred`, or `unconfirmed`. Only three facts gate admission: the header size this reader
pins, the V8 code cache magic high half `0xc0de` (the low half is
`ExternalReferenceTable::kSize` and varies per build, so it is a build fingerprint rather
than a constant), and a declared payload length that reaches exactly the end of the file.
I derived the field semantics from the producer rather than assuming them, and two findings
changed the design:
- **The word V8 calls the source hash holds the source length on the measured producers.**
A 54-code-unit source stored `54`, and sources with the same code-unit length but
different content stored the same value, including a source whose UTF-8 length was 154.
The reader reports it as `source_length` and the docs say that this name is what the
measurement supports, not a claim about what another build stores there.
- **`vm.compileFunction` sets a high bit in that word.** The reader masks it out and reports
the producer family as `script` or `compile_function`, so the source length stays correct
for both producers.
Embedded strings are reported as **byte-pattern observations with absolute byte ranges**,
labelled as such in the field name and in the limitations. A reported sequence is not proof
V8 retained it as a string, and a missing literal is not proof it is absent.
**Fail-closed boundaries, all covered by tests:** an unknown magic is a typed
`unsupported_target` failure because the file is not a V8 code cache; a short header or a
payload-length disagreement is a typed `invalid_request` failure. Neither yields a partial
parse and neither records Evidence.
Non-goals are explicit in the tool description and the docs: the payload is serialized V8
heap data, not source; recovering logic needs a version-pinned View8-style decompiler that
this tool neither provides nor depends on.
Fixtures are generated from the running V8 instead of committing a version-pinned binary, so
the tests exercise the real producer and stay valid across Node upgrades.
- Semantic owner and earliest changed stage: format-native V8 code cache container decoding.
- CLI and MCP/tool-catalog contract: one new `artifact-provider` tool and one new
`inspect-v8-code-cache` CLI command, registered in `TOOL_CONTRACTS`, the `artifact`
language family, `TOOL_EFFECTS`, the capability declarations and services, and the
capability inventory. `docs:check` passes, so the generated catalog agrees.
- Provider, bridge, target-format, or platform compatibility: no provider runs and no
external tool is required, so the tool is available on every supported Node host.
- Evidence, artifact, provenance, or reconstruction contract: complete Evidence with the
payload byte range as its location; no artifact format, reader, or reconstruction change.
- Generated metadata, package, or installation impact: catalog and document facts
regenerated by the existing checks; no new dependency.
Expected effects:
- [x] Target content is returned as bounded structured facts.
- `src/v8/V8CodeCacheParser.test.ts` (13 cases): real-producer header facts, per-field role
and confidence, source length for both producers, an observation's absolute offset
verified against the file bytes, the limitation text, foreign magic, payload-length
mismatch, short header, missing payload bytes, a different build constant still admitted,
and the observation budget.
- `tests/boundary/mcp/v8CodeCacheMcp.test.ts` (4 cases): advertisement and annotations,
header facts over MCP, relative-path rejection, unsupported magic, malformed container.
- `tests/acceptance/v8CodeCacheCli.test.ts` (4 cases): the public CLI, relative-path
resolution, byte-range observations, and the typed failure.
- `npx vitest run src/contracts src/v8 src/application/v8CodeCache src/domain/v8` — 242 passed
- `npx vitest run tests/boundary/mcp/v8CodeCacheMcp.test.ts tests/acceptance/v8CodeCacheCli.test.ts tests/boundary/filesystem/productCatalog.test.ts tests/acceptance/tools/localToolContracts.test.ts` — 20 passed, 4 skipped
- `npx tsc --noEmit -p .` — exit 0
- `npm run build` — exit 0
- `npm run lint` — 0 errors
- `npm run knip` — exit 0
- `npm run docs:check` — exit 0
- `npx oxfmt --check src tests docs` — exit 0
Gap: the header table is measured on node v24.22.1 / V8 13.6.233.17-node.58 only. No other
V8 build is available here, so a different build's field order is untested; that is exactly
why only three facts gate admission and the remaining roles carry a confidence level rather
than being asserted.
Additive. No existing tool, schema, or behaviour changes. The reader never executes the
selected file, never launches a process, and never accesses the network; its audited effects
are `mutatesSession: true` only, matching the other static format readers.
Human owner: @XiaoBinGan
- [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
- [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
- [x] I declare this PR ready for initial maintainer review.
- [x] This PR addresses a concrete problem or an agreed enhancement.
- [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
- [x] The PR has one focused outcome and the title follows `type(scope): outcome`.
- [x] Related issue: #8543.
- [x] Tests cover changed observable behavior and meaningful failure paths.
- [x] Owning docs, contracts, and generated metadata are updated where needed.
- [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims.
* chore: retrigger title and readiness checks after the title was corrected
* fix(v8): bound the payload scan and use the repository path conventions
Review feedback from @greptile-apps on four points.
**Bound the synchronous payload scan.** The scan is one synchronous pass over the
payload, so the 512 MiB ceiling allowed a single request to hold the process for
several seconds. The ceiling is now 64 MiB, matching the pinned CPython pyc reader,
and the default is 32 MiB. The scan stays synchronous, like the BEAM and ELF note
readers in this family, but the budget that bounds it is no longer large enough to
matter. The new `max_file_bytes` ceiling and default are documented.
**Use the absolute-path schema.** `path` now uses `absoluteLocalPathStringSchema`
instead of `localPathStringSchema`, matching `inspect_erlang_beam`. A relative name
previously passed validation and was resolved against the server's working
directory, so `module.jsc` could select a different file than the caller meant. The
rejection is now at the schema boundary.
**State the extraction step.** The handoff in `docs/v8-code-cache.md` now says a
cache embedded in a single-executable application or an Electron app must be
extracted first, because this reader requires the magic at byte zero and returns
`unsupported_target` for the surrounding ELF/PE executable.
**Load only the owning contract group in tests.** Both tests now import
`V8_CODE_CACHE_TOOL_CONTRACTS` directly instead of resolving the tool through the
full catalog, so a focused test no longer constructs every other tool's schema.
21 cases pass across the parser, MCP boundary and CLI acceptance files; `tsc` 0;
`build` 0; `knip` 0; `docs:check` 0; `oxfmt --check` 0.
* fix(v8): select the tool contract by name in the focused tests
Reverts the catalog-position selection I added for a review suggestion. This
repository has a lint rule (`rea(no-positional-tool-contract)`) that forbids
selecting a tool contract by catalog order, and `toolContract("inspect_v8_code_cache")`
is how every neighbouring focused test selects one. The positional form I tried
introduced two lint errors, so the named form stays.
The review concern was that resolving through the full catalog constructs every
other tool's schema. That cost is real but it is the repository's chosen trade:
catalog ordering must never select an operation or schema, and `toolContract` is
the named accessor that enforces it.
* test(v8): cover the new tool in the absolute-path boundary test
Main added `tests/boundary/mcp/absoluteLocalPathTools.test.ts` after this branch
was opened. It enumerates every tool whose MCP `tools/list` schema must reject a
relative path, and `inspect_v8_code_cache` uses `absoluteLocalPathStringSchema`,
so the tool belongs in that list. Adding it keeps the enumerated field set complete
instead of leaving one absolute-path tool untested.
`tests/boundary/mcp` + v8 + contracts: 256 passing. `tsc` 0; `build` 0; `knip` 0;
`docs:check` 0; `npm run lint` 0 errors.
* perf(v8): read the payload by index instead of allocating per byte
`for (const [index, byte] of payload.entries())` allocates a two-element array for
every byte of the cache payload. On a 1 MiB payload that allocation dominated the
scan.
Measured with a real cache built from this parser, alternated so the two forms run
against the same input:
| payload | entries() | indexed reads |
| --- | --- | --- |
| 1 MiB | 15.5ms | 4.4ms |
That is 3.49x on the scan, and the gap widens on smaller payloads where the
allocation cost is a larger share (7.3x at 64 KiB in a synthetic run of the same
shape). The scan is the whole cost of `inspect_v8_code_cache` once the file is read,
so this is the tool's own hot loop.
Output is unchanged: the parser returns one retained string for the 64 KiB fixture,
and `src/v8` plus the CLI and MCP boundary files stay at 21 passing. `tsc` 0;
`build` 0; `npm run lint` 0 errors.
* fix(v8): admit only decimal tokens on the numeric CLI options
Main added `tests/boundary/cli/cliDecimalArguments.test.ts` after this branch was
opened. It enumerates every numeric CLI option and requires `0x10` and `1e3` to be
rejected, because plain `Number` coercion would read them as 16 and 1000. This
command's three options forwarded the domain fields unchanged, so the test failed on
them:
inspect-v8-code-cache options.maxFileBytes: Parser.parse accepts 1e3
inspect-v8-code-cache options.maxStrings: Parser.parse accepts 0x10
inspect-v8-code-cache options.maxStrings: Parser.parse accepts 1e3
inspect-v8-code-cache options.minStringBytes: Parser.parse accepts 0x10
`cliDecimalField` publishes the input view that parses the decimal token first and
then lets the domain field own bounds, defaults and help, which is what the other
numeric commands use.
`tests/boundary/cli/cliDecimalArguments.test.ts` now passes. `tsc` 0; `build` 0;
`npm run lint` 0 errors; `knip` 0; `docs:check` 0.
* fix(v8): recognize module caches, and advise from remaining headroom
Two review findings, each measured on Node 24.22.1 before changing anything.
**A module cache was reported as a script.** `vm.SourceTextModule.createCachedData()`
sets bit 30 of the source-length word, which the source-length mask `0x1fffffff` leaves
out, and the producer check only tested the function bit at bit 29:
| producer | source word |
| --- | --- |
| `vm.Script` | `0x00000013` for a 19-unit source |
| `vm.compileFunction` | `0x20000009` for a 9-unit source |
| `vm.SourceTextModule` | `0x40000013` for a 19-unit source |
So a real module cache was labelled `script`, and `vm.Script` rejects that container. The
producer enum gains `module` and the module bit is tested first; the source length is
unchanged because both flags sit outside the mask.
**The budget advice recommended a retry that could not work.** Both limits have
ceilings — `max_strings` at 65536 and `min_string_bytes` at 256 — so an admitted file
with more than 65536 runs of 256 bytes reached the branch while every suggested retry
was a rejected input. `observationBudgetRemediation` now reads the remaining headroom
and only recommends a change that is still admissible, falling back to selecting a
smaller artifact when neither can move. The parser message states the fact and leaves
recovery to the provider, which is the only place that can see both budgets.
`MAX_INSPECT_V8_CODE_CACHE_MIN_STRING_BYTES` names the `min_string_bytes` ceiling that
the schema previously spelled inline, so the advice and the schema cannot drift.
`docs/v8-code-cache.md` describes both flag bits. `src/v8` plus the CLI and MCP boundary
files: 28 passing; 7 new cases fail on the base commit. `tsc` 0; `build` 0;
`npm run lint` 0 errors; `knip` 0; `docs:check` 0.
* refactor(v8): decide the budget recovery as data, and test the decision
The remediation tests matched fragments of the advice sentence and banned old phrases.
`docs/testing.md` asks for error codes, recorded limits and recovery behaviour instead of
frozen prose, so a harmless reword could fail those tests while wrong advice passed them.
`observationBudgetRecovery` now returns the decision — which retries remain within the
schema's admitted range, and whether a smaller artifact is the only option — and
`observationBudgetRemediation` renders one sentence from it. The tests assert the
decision, so the wording is free to change and an incorrect choice cannot pass.
This also fixes a bug in the previous shape: it took the `min_string_bytes` branch to mean
"lower it", but that branch is reachable with the value already at the schema minimum,
where lowering is not a legal retry. The decision now carries `canLowerMinStringBytes`
and a `requiresSmallerArtifact` flag for that case.
5 cases, all failing on the base commit, covering both headroom states, either ceiling
alone, and the run-length floor. `src/v8` plus the CLI and MCP boundary files: 28
passing. `tsc` 0; `build` 0; `npm run lint` 0 errors; `knip` 0; `docs:check` 0.
* ci: rerun after the wakaru release download recovered
The only failing job on this branch was `javascript_recovery / real-javascript-recovery`,
which failed on `curl: (22) The requested URL returned error: 504` while downloading
`wakaru-linux-x64.tar.gz`. The release is reachable again (HTTP 200), so this empty
commit re-triggers the lane.
* docs(v8): place the guide in the tree and record the feature
The guide sat at `docs/v8-code-cache.md`, outside the `docs/guides/` tree every other
guide uses, and neither index linked it, so it could not be reached from
`docs/README.md` or the VitePress sidebar.
Moved to `docs/guides/javascript/`, linked from both indexes, and recorded the feature
under `docs/specs/v8-code-cache-inspection/` with `spec.md`, `plan.md` and `tasks.md`.
The spec keeps only the premises that were measured on Node 24.22.1 — the three producer
words and the retained-string layout — rather than restating the implementation.
`npm run docs:check` 0; `npm run format:check` 0; `tsc` 0; `build` 0; `lint` 0 errors;
`knip` 0. V8 reader, CLI and MCP files: 28 passing.
* docs(v8): use the documented spec path and test the advice against its struct
Two review findings.
**The records were at `docs/specs/v8-code-cache-inspection/`.** `AGENTS.md:73` asks for
`docs/specs/<domain>/<feature-slug>/`, so they move to
`docs/specs/v8/code-cache-inspection/`.
**The verification section claimed more than the tests do.** It said the producer cases
were built from real `vm.Script`, `vm.compileFunction` and `vm.SourceTextModule` output,
and that the budget advice was compared with its owning formatter. In fact only
`vm.Script` and `vm.compileFunction` are real producers — the module case sets the
measured bit on a `vm.Script` cache — and only `observationBudgetRecovery` was asserted.
Corrected the claim, and closed the second gap rather than only describing it:
`observationBudgetRemediation` now has four cases holding the sentence to the struct it
renders from. The sentence must not advise a knob the struct closed, and must mention a
smaller artifact exactly when the struct requires one. Verified by making the sentence
advise `max_strings` after the ceiling was reached — the test fails. Wording stays free;
wrong advice does not.
`npm run docs:check` 0; `npm run format:check` 0; `tsc` 0; `build` 0; `lint` 0 errors;
`knip` 0. V8 reader, CLI, MCP, absolute-path and decimal-argument files: 34 passing.
* fix(v8): restore the absolute-path contract entry after the rebase
The rebase onto `main` conflicted in `tests/boundary/mcp/absoluteLocalPathTools.test.ts`:
`main` gained `inspect_api_contract`, `inspect_devicetree`, `inspect_playwright_trace` and
`inspect_python_pyc` in both the contract list and the path-field map while this branch
added `inspect_v8_code_cache`. Both sides are additive, so both are kept.
Without the resolution the branch carried conflict markers, which made `mergeable_state`
`dirty` and every check on it meaningless. Re-verified after the rebase: `tsc` 0, `build` 0,
`format:check` 0, `lint` 0 errors, `knip` 0, `docs:check` 0, and the V8 reader plus its CLI,
MCP, absolute-path and decimal-argument boundary files at 34 passing.
* test(v8): pin the producer from the runtime's own header word
An independent reviewer ran these files on Node 22.23.4, 24.22.1 and 26.12.1. Only 22.23.4
failed, on `separates the compileFunction producer bit from the source length`, expecting
`compile_function` and reading `script`.
The cause is the fixture, not the parser: `vm.compileFunction(...).cachedData` carries
`0x20000009` in the source-length word on Node 24.22.1, with bit 29 set, while Node 22.23.4
does not set that bit at all. Whether a given V8 build marks the word is a build detail, so
asserting one build's spelling made the check version-dependent.
The case now reads the word the runtime produced and asserts the parser maps it correctly,
and keeps the part that must hold everywhere: the source length is the low bits, so it
equals both `word & 0x1fffffff` and the source's length. The flag meanings themselves stay
pinned by the synthetic `withHeaderWord` cases above, which do not depend on any build.
Reverting the parser's `compile_function` arm fails this case and
`keeps the function and module flags distinct`. `src/v8` plus the CLI, MCP and absolute-path
boundary files: 24 and 34 passing. `tsc` 0; `build` 0; `npm run format:check` 0;
`npm run lint` 0 errors; `knip` 0.
* fix(v8): report an unflagged producer word as unknown, not as a script
`vm.compileFunction` marks bit 29 of the source-length word on Node 24.22.1 (`0x20000004`
for a 4-unit source) and does not mark it on Node 22.23.4 (`0x00000004` for the same call).
Whether a V8 build marks a function cache is therefore a build detail, and an unflagged
word is either a `vm.Script` cache or an unmarked function cache. The container carries
nothing that separates the two.
The reader reported `script` for that case, which is a confident claim the bytes do not
support. My previous commit made the test accept whichever value the runtime produced,
which recorded the ambiguity instead of resolving it in the contract. `unknown` is now a
producer value, the unflagged word maps to it, and the observed word stays in
`header.fields` for a caller that trusts a specific build fingerprint. A limitation line
says the same in the report itself.
`header.producer` widens to `["script", "compile_function", "module", "unknown"]`; `script`
is no longer produced, because no unflagged word can be shown to be one.
The spec's producer rule and its verification notes are updated to match. Reverting the
mapping to `script` fails 2 of the 24 `src/v8` cases. `src/v8` with the CLI, MCP and
absolute-path boundary files: 33 passing. `tsc` 0; `build` 0; `docs:check` 0;
`npm run format:check` 0; `npm run lint` 0 errors. `knip` reports one unused export
(`builtinDataEffectMethod`) on `main` as well, so it is not from this change.
* fix(artifacts): retain completed identity across native parser failures
* docs(v8): preserve unknown unflagged cache provenance
---------
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
* test: focus assertions on behavioral evidence
Prune redundant and wording-coupled checks while retaining diagnostic, parser, recovery, and cleanup coverage.
* refactor: remove test-only wrappers and duplicate checks
Exercise production operations through test fixtures, remove unused adapters, and keep managed-member uncertainty guidance under its existing owner.
* test: focus process validation on diagnostic facts
* refactor: remove test-only runtime helpers
Move semantic evidence fixture setup out of runtime and remove unused Windows settle recovery helpers while checking configured defaults independently.
* test: focus artifact diagnostics on format facts
* test: verify retained cleanup guidance delivery
RFC 5280 4.1 defines an Extension as extnID, an optional critical
BOOLEAN and an extnValue OCTET STRING. Section 4.2 says a certificate
MUST NOT include more than one instance of an extension.
The decoder only checked that extensions were a SEQUENCE. Any entry,
and any repeat, was accepted.
* fix(pe): report exhausted certificate decoder allowances as resource limits
A PKCS#7 payload that exhausted the fixed 64 KiB decoded-text or 16,384-value ASN.1 allowance was refused as malformed input with advice to correct request fields. Keep the allowance and limit on the decoder failure and report it as a nonretryable resource_constraint with fixed-allowance recovery.
Fixes#7531
* test(pe): cover certificate allowances through MCP
---------
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
Migrate schema, installer, ASN.1, and tooling consumers to their owning definitions. Remove the redundant CI task entry and the deprecated Cutter command-local JSON flag.
BREAKING CHANGE: Replace Cutter --json=true or --json=false with --json-output=true or --json-output=false. Bare --json still controls CLI result formatting.
Centralize completed file observations and cleanup error projection across stable-file providers while preserving each provider’s failure classification and exact cleanup leases. Cover BEAM finalization cancellation and devicetree cleanup retries.
Validated 223 focused lifecycle and MCP tests and check:fast.
Normalize completed stable-file path, digest and length in one shared observation owner. Migrate thirteen identical provider projections while preserving their selected paths, schema checks and cleanup composition.
Keep specialized metadata and worker reply observations at their existing owners. Validation: shared real-descriptor coverage and provider lifecycle tests passed; build, typecheck, lint and formatting passed.
Centralize ELF section and Godot trailer declarations while preserving caller admission policies. Route six PE entrypoints through canonical DOS ownership so relocation data cannot establish a PE header, and update NativeAOT interpretation identity and verifier dependency coverage.
Decode optional-header declarations once and pass detached directory words, locations and SizeOfHeaders to native consumers. Preserve executable machine admission, raw GPU/resource machine reporting, RVA mapping and certificate file-offset semantics.
Validation: fresh build/catalog, full static checks, 466 core/provider fixture tests, 122 workflow tests and 14 selected file-backed PE admission tests passed. Public CLI and actual stdio MCP used authored artifacts; installed external providers were not qualified. Added 17 structural cases and two GPU machine-policy controls. Eager directory decoding costs remain unmeasured.
* feat(artifacts): inventory and extract Node.js single executable application blobs
Adds a node-sea artifact reader that locates the NODE_SEA_BLOB payload in
Mach-O sections, ELF notes and PE RCDATA resources, decodes the blob
header, main script, optional snapshot and code cache, and named assets,
and extracts their stored bytes through the existing safe output tree.
The executable is never run. Resource-tree errors in a PE without a
NODE_SEA_BLOB name stay with native classification; truncated, unknown
or over-limit SEA declarations fail closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe
* fix(artifacts): reuse PE resource facts and bound SEA asset tables
Move the bounded NODE_SEA_BLOB name probe into the shared PE resource
layout module so SEA admission uses the same header and section mapping
as the full parser; the layout reader now accepts a header prefix with
the file size. Bound SEA asset count and decoded name bytes before
records are created and report resource bounds as limit failures. Offer
the JavaScript next probe only for main scripts with a source suffix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe
* fix(artifacts): make SEA metadata construction cancellable
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
Explain unreadable ELF note section names without discarding complete note coverage. Keep export and PE import rows when only version or DLL-name metadata is unresolved, and stop claiming those retained rows were unreported.
Use native byte equality for the 4 MiB ELF extended-numbering source-preservation checks; generic deep equality enumerated every buffer byte and exhausted the test worker heap. The full fixture and byte coverage remain.
Validation: focused ELF note, native export, and PE import regressions plus complete CLI/MCP adapter files passed under the existing 512 MiB heap limit. Build, typecheck/lint, formatting, and documentation checks passed.
Forward the file owner's completed stable-read hash and byte count through descriptor-close failure or cancellation in PE resources, certificates and imports, ELF notes and kernel modules, native exports, Apple IPS, and APK signature inspection. Preserve the primary failure, exact cancellation reason, and failed-close cleanup lease.
Validation: 32 acquisition-finalization regressions across eight providers and 30 existing APK provider, lifecycle, and identity tests passed. Existing native filesystem regressions and typecheck/lint also passed. No external analysis engines or Android device were used.
Keep malformed canonical devicetree phandles unresolved instead of falling back to legacy identities. Report the PE dynamic relocation offset and section at their exact source widths with Size-gated absence and zero preservation. Clarify classic kernel-module version coverage and byte-preserving string encoding.
Validation: 158 focused parser, service, filesystem, and finalization regressions; 14 CLI/MCP tests; build, typecheck/lint, formatting, and docs checks passed. Fixtures establish parser behavior; no external analysis engines were exercised.
* feat(pe): inspect TLS callbacks and load configuration
Adds inspect-pe-load-config and inspect_pe_load_config for PE32 and
PE32+ images, with artifact hashes and file ranges in the returned
evidence. Reports callback addresses and mappings, plus size-covered
security-cookie, SafeSEH, CFG, EH-continuation and XFG fields;
flag-derived mitigation claims are explicitly labeled inferred. Missing
directories remain null, and malformed storage or exhausted read budgets
return errors without partial success. Adds documentation and tests for
service validation, CLI/MCP access, malformed inputs and stable-file
handling.
x86 images that declare the legacy 64-byte load-config directory are
read to the structure's own Size when those bytes are file-backed, as
the Windows loader does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe
* fix(pe): retain snapshot facts and bound load-config section lookup
Keep the completed stable-read path, SHA-256 and byte count as a
file-snapshot-hash partial observation when descriptor close fails or is
cancelled. Report the checked Size span as structure_location and add it
to Evidence locations beside the declared directory range. Resolve
callback sections through a sorted segment index built once per image,
keeping the ambiguity failure. Pin every load-config field to offsets
transcribed from the PE/COFF spec and llvm COFF.h at its exact Size
boundary for PE32 and PE32+.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>