# Apple IPS static inspection verification — 2026-10-24

## Scope

PR #6511 adds portable `inspect_apple_ips` / `inspect-apple-ips` over one
caller-selected local file. The metadata must identify string `bug_type`
`"309"`; stackshots and other report kinds remain unsupported. No target is
executed, no deep engine starts, and no atos/dSYM lookup is performed. These
checks do not qualify live macOS state, symbolication or Mach-O UUID joins.

The source contract preserves integer literals beyond IEEE-754 precision,
observed type mismatches and partial coverage. The frame address is derived
from its image's base plus its recorded offset, as described in
[Apple's crash JSON documentation](https://developer.apple.com/documentation/xcode/interpreting-the-json-format-of-a-crash-report).
That documentation does not provide a 64-bit bound for every numeric field;
the parser therefore bounds representation cost without truncating values.

## Review corrections and ownership

The contributor's `80ad2a02e9c6cbe248d58d78a7a79753bcc09f79` already projects
documented JSON members and checks thread/frame/image/register allowances
before materializing each row. Unknown members are syntax-checked without
retaining their values. Producer controls include a budget error before an
invalid next item, wrong-type collections, frame-role cases, and the DEAD10CC
decimal near miss. Inline-role assumptions are corrected below. Source inspection alone does not
establish the runtime checks below.

Further negative controls expose three remaining boundaries:

- Two contradictory thread/image coverage reports were accepted by the
  service. Coverage now agrees with the retained arrays before Evidence is
  issued. Already cancelled requests also preserve the exact caller reason.
- A 20,001-digit image base expanded into 512 derived frame addresses despite
  a small input file. The field reader now owns an 8 MiB default / 64 MiB
  maximum UTF-16 allowance for recorded decimal/hex fields. Conservative
  reservations precede conversion and arithmetic. Source BigInts are reused;
  derived sums are not retained in a cache. Repeated appearances count
  separately, including a service control using the same object twice.
- A 20,013-character image architecture appeared 513 times in a 512-frame
  report. Frames now retain their recorded `image_index`; architecture, UUID
  and `uuid_unknown` belong to the image once. The old per-frame copies and
  superseded field-access helpers are removed.

Each defect has a failing control before its correction. Raising the integer
allowance to 64 MiB preserves the 512-frame decimal facts exactly. The
allowance is per call, requires no restart, and is separate from file-size,
row, wire and client-buffer limits. Refusal is a typed capacity error without
partial success. A refused task is not treated as a completed-task speedup.

## Public corpus provenance

Two pinned upstream reports are valid two-object JSON and are used without
editing their bytes. Their original generation history has not been verified;
these are public repository corpus files, not a claim of fresh OS capture.

| Report                                                                                                                                                                                                             | Repository revision                        |  Bytes | SHA-256                                                            |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | -----: | ------------------------------------------------------------------ |
| [LLDB multithread-test.ips](https://github.com/llvm/llvm-project/blob/16dde0db3ce0b9c5eae14178780cc6db1eec7ac8/lldb/test/Shell/ScriptInterpreter/Python/Crashlog/Inputs/interactive_crashlog/multithread-test.ips) | `16dde0db3ce0b9c5eae14178780cc6db1eec7ac8` | 16,093 | `567cadb22af19be798390ab2021ed929fb782c69b2d3b18ff5bb3f39a3741d8c` |
| [pycrashreport Monterey report](https://github.com/doronz88/pycrashreport/blob/634dff127fa6d4d3df948a64a3e254fe208a99dc/tests/user_mode_crash_report_monterey_non_symbolicated.ips)                                | `634dff127fa6d4d3df948a64a3e254fe208a99dc` |  7,047 | `7de3d3c3515e2ac1bb6ed4c00b70cb12c1ff6ad4317617d258d58346e00264b7` |

Three other LLDB inputs (`a.out.ips`, `a.out.inline.ips`, `no_threadState.ips`)
contain invalid JSON at their pinned revision. They were not repaired or
counted as successful report qualifications. Files remain temporary local
verification inputs and are not committed to this repository.

## Completed-task comparison

Compare `d4930ed57c0b175a632fd8572732b1e88a73cb2f` (original IPS behavior
with main `cc18e9ad` provider/API integration repairs) against
`135cab1234c3663e839f5fbea6bf833737cd140a`. Three alternating pairs of five
public CLI tasks complete, **30 tasks total**. Every task returns complete
coverage. An independent Python JSON/integer oracle checks all ordered images,
threads, registers, frame offsets, derived addresses and the then-selected
physical-depth roles, as well as process facts and artifact digests. The later
source-backed correction below replaces that role assumption; neither public
report contains an inline row, so its role expectations remain unchanged. The two public reports contain
3/1 threads, 8/4 images, 30/4 frames and 36/22 retained registers respectively.

Every Evidence field agrees after excluding the changed Evidence ID, the new
default allowance parameter and the removed per-frame copies. Before excluding
those copies, the oracle verifies each against its owning image. Image facts,
uncertainty, other report fields, provenance, coverage and limitations remain
equal. Artifact digests are unchanged across all runs.

| Complete CLI task                    | Estimated stdout + invocation tokens, before → after | Elapsed median s |  CPU median s | Sampled family RSS median MiB |
| ------------------------------------ | ---------------------------------------------------: | ---------------: | ------------: | ----------------------------: |
| Small synthetic report               |                                            934 → 923 |    2.069 → 1.969 | 2.309 → 2.212 |                 368.8 → 369.6 |
| LLDB multithread report              |                                        6,500 → 5,851 |    3.180 → 2.878 | 2.292 → 2.340 |                 370.4 → 373.5 |
| Monterey report                      |                                        2,377 → 2,309 |    3.330 → 3.029 | 2.230 → 2.208 |                 369.6 → 368.8 |
| 20,001-digit base / 64 frames        |                                    402,476 → 401,783 |    2.725 → 2.879 | 2.354 → 2.319 |                 375.1 → 371.8 |
| Long image architecture / 512 frames |                                   1,760,820 → 41,023 |    3.231 → 2.928 | 2.468 → 2.261 |                 387.3 → 372.7 |

The long-image task reduces estimated whole-task context by **97.67%** while
preserving joined facts. Ordinary reports save less. No latency speedup is
claimed: elapsed ranges overlap, including the long-image ranges 2.070–3.381 s
before and 2.928–3.633 s after. Small-report ranges reach about 9.2 s in both
lanes during shared-host contention. The wide-integer elapsed median rises
about 0.154 s, while its CPU and sampled RSS medians decrease slightly. Three
pairs do not establish universal performance or maximum-capacity behavior.

Each task uses a fresh Node 24.22.1 CLI/provider process, with CPU affinity 0,1.
OS caches were not flushed; these are fresh-process runs with uncontrolled
warm filesystem caches, not measured cold-disk runs. No result cache or
persistent provider session participates. Warm long-lived process behavior is
unmeasured. Tokenx 1.3.0 estimates the entire Evidence stdout and invocation;
these are estimates, not billed/model token counts. The analyst already knows
the single CLI command, so no discovery, paging or extra lookup call is
required. Image metadata is inline in the same result. MCP catalog costs,
prompts, framing, rendering and client JSON parsing are unmeasured.

CPU is waited-child user plus system time. RSS is a 50 ms recursive
process-family sample; it can double-count shared pages and miss short-lived
or unobserved descendants. Token estimation and the oracle run outside the
timed command. Later main integrations are outside these pinned timings.

Synthetic recipes: `small.ips` has one frame/register; `wide-integer.ips` uses
base `10^20000`, 64 frames with image offset 1; `wide-image-text.ips` uses
architecture `unknown_arch_` plus 20,000 `x` characters and 512 frames. Their
SHA-256 values are respectively
`1178a013b86f8a6c11abef633fdee5fefd23e010c4af92710321cb0f369ed857`,
`b455598694d9953031e52450f1a4a6655e16abd683a2cec59e393899883b6994`, and
`4d3d2f3e4946cbbeed7e14e1f12cd97bd2f119af1b3a33496dd3e8d079c3f999`.

## Final integration checks

Main `cd10ee1f8a8e124be4864e77750a16e76c561809` integration passes **167**
selected domain/service/parser/filesystem/CLI/MCP/shared lifecycle checks in
74.312 s (80.355 s waited-child CPU; 1,902.0 MiB sampled family peak). Full
`npm run check`, `npm run docs:check` and test discovery pass. All 2,324 test
files are discovered exactly once. Earlier source selections overlap these
checks. Two initial public-error assertions placed remediation at the wrong
level; the tests now check the existing top-level error contract. No production
error shape was changed to accommodate them.

Both pinned public reports additionally pass through the actual stdio MCP
server and SDK client, with the advertised input/output JSON Schemas validated
in AJV 2020. The same independent oracle checks their returned facts. The
client explicitly admits a 64 MiB receive buffer for the complete tool catalog;
this does not qualify every client's default buffer. Target execution and
symbolication remain unverified. The 30-task cost table remains pinned to its
recorded revisions, without reusing these integration costs as speed evidence.

## Integration after PE imports

The following integration includes main `4ef916da03d93729041f79cec0ee243cd2d3097d`
and the newly merged PE imports tool. The documentation index retains links to
both static formats. Full static/docs/discovery gates pass, together with
**191** selected checks, including the upstream native-plist cleanup test.
The selection takes 49.939 s, 52.461 s waited-child CPU and 2,079.8 MiB sampled
family RSS. It overlaps earlier selections and is not a performance comparison.

Previous-head CI run `40058145277` had one functional failure in
`nativePlistFileCleanup.test.ts`, repeated by the aggregate lane; seven other
shards were cancelled. Exact main `d662fa54a67bf24bd4800d1867231662019d91c6`
and actual CI merge `440029745397d78103a623fd1fd91cfa09fc6df5` each reproduce
11 passing / 1 failing cases with identical statuses and normalized failure
messages. The failing test and selected producer sources are byte-identical.
The subsequent main integration fixes that closing race, and all 12 cleanup
cases pass within the 191 checks. Cancelled CI coverage was not executed.

## Follow-up string capacity and identity review

Baseline `45a570656cb3b9a7a8bf41302e1f01f8662bc884` and candidate
`6fa8083fc6974bc18c7d227ded33db28eb590f03` isolate three further corrections:

- Retained JSON strings are syntax-checked by the same scanner used for skipped
  strings, then escaped literals are decoded by the native JSON string parser.
  Unescaped strings retain the slice fast path. The superseded per-escape
  fragment array is gone; decoded length is bounded by the selected input's
  literal length. Malformed escapes and controls still fail before projection.
- UUID interpretation owns format validation before canonicalization. Both
  32 hexadecimal digits and canonical hyphenation are accepted. Malformed
  nonempty spellings remain in `coverage.type_mismatches[].recorded_value`,
  canonical identity stays unknown and coverage becomes partial. Absent UUIDs
  and the documented all-zero unknown sentinel remain distinct from that
  malformed-value outcome. The field uses the existing exact-optional contract
  convention; it does not admit an explicit JavaScript `undefined` value.
- `metadata.bug_type` is literal `"309"` in the domain and advertised schema.
  The service rejects unsupported reports from defective/custom ports too.

Seven new cases failed against the earlier production code: four malformed
UUID spellings, two unsupported port kinds and a public CLI capacity case.
The latter selects a file below the 16 MiB allowance containing 7,500,000
newline escapes. Earlier decoding exhausts a 256 MiB Node old-space limit;
the corrected CLI completes with every character verified by length and
SHA-256. This is an old-space setting, not a 256 MiB total RSS claim.

The first expanded run passed 182 of 183 cases; the advertised-schema MCP case
hit its 30-second test deadline. Its isolated two-case file then passed, and
the expanded schema assertions passed again. After switching to the final
exact-optional representation, both MCP cases passed once more: actual SDK
schemas reject a forged `bug_type: "288"` and admit the partial result with its
exact malformed UUID spelling. `check:fast` passed. The two public crash
reports also passed again through an actual stdio MCP client and Ajv2020.

Twelve further completed CLI tasks used three alternating pairs for the same
public LLVM report and a generated 4,000,063-byte report containing 2,000,000
newline escapes (SHA-256
`5c4a9282da37091973774bf98c6822caeecbc1d74d7c44cd4eda1937ad05e350`).
Every complete Evidence field, including IDs, is identical within each case.
The escape case explicitly selects an 8 MiB read allowance; both revisions
complete under the same default 2 GiB old-space task environment.

| Completed task     | Estimated tokens, both revisions | Elapsed seconds, before → after | CPU seconds, before → after | Peak family RSS MiB, before → after |
| ------------------ | -------------------------------- | ------------------------------- | --------------------------- | ----------------------------------- |
| LLVM public report | 5,851                            | 2.071 → 1.970                   | 2.378 → 2.328               | 365.3 → 366.0                       |
| 2,000,000 escapes  | 4,000,703                        | 2.425 → 2.374                   | 2.457 → 2.289               | 459.3 → 372.1                       |

Values are medians. Escape-case elapsed ranges overlap: 2.120–2.930 seconds
before and 2.324–2.424 after, so no elapsed-time improvement is established.
Its CPU ranges are 2.318–2.521 versus 2.245–2.297 seconds; sampled peak RSS
ranges are 457.4–462.9 versus 371.5–372.9 MiB. The measured dense-escape task
reduces median CPU by 6.8% and sampled memory by 19.0%; those observations are
specific to this case. LLVM ranges overlap and show no general speed gain.
Complete output and estimated tokens are unchanged.

Tokens use tokenx 1.3.0 estimates for the invocation plus all stdout, not model
billing. These are known CLI tasks with existing inputs, fresh processes and
warm OS caches. Input creation, cold disk, discovery, client parsing, prompts,
schemas, MCP envelopes and retries are outside that comparison. The same
50 ms descendant RSS sampling and waited-child CPU limitations above apply.
Later main integration is not part of these cost measurements.

## Final public MCP workflow and main integration

Main `21a20306b4887ff8e6b614b6ae34a387cfbdf1d8` was integrated in
`6d77f265027d34154145e807b1a5c8fd26a1e567`. Its static/documentation checks
and discovery of all 2,339 test files passed. The combined 183-case selection
again reached the in-memory MCP test's 30-second deadline while its other
182 cases passed. Isolated passes did not establish that the setup was robust
under the combined run.

The MCP test now exercises the compiled production stdio entrypoint through
the existing local-tools fixture, which owns client/transport cleanup and
validates the SDK-advertised schemas. It no longer reconstructs the full source
server/catalog inside the shared test worker. One public workflow retains all
the prior schema, annotation, capacity, register, malformed-UUID and relative-path
assertions; the redundant second connection case was consolidated. No test
deadline was increased. The same combined selection then passed all 182 cases,
including the large escaped-text CLI case and all producer/service regressions.
This is stronger public-path verification, not a claimed runtime speedup from
a before/after test-suite timing comparison.

## Reference and inline review follow-up

Commit `174c7496a2f007a1ef0ab5da0a341b2f801277c3` separates a recorded
uint32 lookup from whether it resolves to an object row. An out-of-range or
malformed `faultingThread` reference preserves the lookup, marks coverage
partial and leaves row-zero roles unknown. Image lookups use the same owner;
unresolved image references preserve `image_index` and leave the derived
address unknown. Negative or oversized integer lookups retain their exact
literal in `coverage.type_mismatches[].recorded_integer`. Reservation happens
before retention, and the service counts these literals against the same
integer-text allowance even for custom ports. No BigInt conversion is needed
for rejected indexes.

The previous comment and guide incorrectly asserted that inline rows share a
subsequent physical row's address. Apple's per-frame `imageOffset` description
and [the pinned LLDB parser](https://github.com/llvm/llvm-project/blob/16dde0db3ce0b9c5eae14178780cc6db1eec7ac8/lldb/examples/python/crashlog.py#L716)
support each row's own base-plus-offset; that parser increments its frame index
for every recorded row. REA therefore keeps its existing address derivation,
leaves a missing offset unknown and corrects roles to use the recorded row
index. The inline flag remains recorded evidence and does not reconstruct a
physical stack. This is source-derived validation; LLDB was not executed.

Nine source controls fail before this correction: two in-range-but-unresolved
faulting indexes, a reference to a malformed thread row, three rejected integer
spellings, the 19-byte rejected-literal allowance, unresolved image coverage and
the corrected row-role oracle. A separate differing/missing-inline-offset case
already passes before the correction and protects the existing address rule;
it is not evidence of a fixed address bug. All **193 selected checks pass**
afterward, including CLI, filesystem, custom-provider allowance checks and
production stdio MCP. The actual SDK validates retained lookup literals against
the advertised schema. `check:fast`, complete formatting, documentation and
cached build also pass. Both pinned public reports pass the real stdio SDK/Ajv
workflow again (13.510 s elapsed, 14.909 s waited CPU, 907.4 MiB sampled family
RSS); the independent integer/JSON oracle checks the CLI comparison below.

Six completed alternating CLI tasks compare the prior actual CI tree
`acb837583d8346016fd8b46d4fcf1fdf09fb0b06` with the corrected commit on the
pinned LLDB multithread report. All full Evidence, including IDs, agrees.
Estimated invocation plus full stdout tokens remain **5,851** (tokenx 1.3.0).
Elapsed medians are 2.071 -> 1.969 s (ranges 2.071–2.271 / 1.919–2.069), CPU
2.295 -> 2.278 s (2.140–2.471 / 2.231–2.283), and sampled process-family RSS
366.8 -> 367.0 MiB (348.2–370.9 / 348.9–368.4). CPU/memory ranges overlap;
this small warm-cache sample does not establish a general speed or memory
improvement. Processes are fresh; the OS file cache is warm. Caller input
creation, schemas/discovery, prompts, client parsing and MCP transport are not
included. The same 50 ms family-RSS limitations apply.

## JSON admission and output consistency follow-up

Three later review findings were reproduced with 19 failing controls before the
correction (49 other selected checks pass). Broad JavaScript trimming admitted
form-feed, vertical-tab, NBSP and BOM around JSON objects; those are not JSON
whitespace. The IPS adapter now passes the original body to the lossless parser,
and the complete-value boundary permits only space, tab, CR and LF after the
value. Twelve controls cover metadata suffixes and body prefixes/suffixes; a
positive control retains JSON whitespace around both objects. Caller bytes and
artifact identity stay unchanged.

The shared service no longer accepts a reported faulting-thread index without
an established retained owner or its corresponding reference mismatch. Unknown
or out-of-range recorded indexes remain available with partial coverage; an
unrelated mismatch cannot justify the unresolved lookup. The old empty service
fixture incorrectly asserted faulting thread zero and now records null. This is
output admission, not reinterpretation of provider source JSON.

Classification is now a named discriminated schema: `classified` requires at
least one match and `unclassified` permits none. The source schema rejects both
contradictions. Production SDK/Ajv checks also test both contradictions and both
valid alternatives against the advertised JSON Schema; an actual stdio call
checks that non-JSON trailing whitespace returns a typed caller-input failure.
These are correctness changes. Prior CLI/performance results retain their exact
pins and are not a fresh measurement of the expanded classification schema or
agent catalog costs. No speed or token reduction is claimed for this follow-up.

Final executed source pin `38b5a3c44824282b55041389b7cbb4d9c261cc8c`
incorporates main `98ec00f4068bab894639d0d1d83ac0bfc428bcbf`. All **216**
selected checks pass after the runtime rebuild: 52.922 s elapsed, 51.098 CPU s,
1,347.9 MiB sampled peak family RSS. Type/lint, complete formatting and generated
documentation checks pass. Both pinned public LLVM/pycrashreport inputs were
repeated through the actual stdio SDK/Ajv: 20.231 s elapsed, 20.602 CPU s,
934.0 MiB sampled family RSS, with complete coverage. Full Evidence and IDs agree
with the saved pre-correction public-corpus outputs for both valid inputs.
This repeat is a fresh client/server process with primed OS/file caches; it is
not a cold-system measurement. Client/schema work is included in runtime costs;
model/catalog token usage was not measured in this repeat. The family sampler's
shared-page and short-lived-child limitations described above still apply.
