Skip to content

Commit 20fcfb3

Browse files
committed
audit: Rust<->TS transform parity round 3 — 25 findings, geometry/threshold transport CRITICALs, fresh-batch verdicts
2 parents bd17e1c + 8a93b69 commit 20fcfb3

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

.alfonso/rust-ts-parity-round3.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Rust ↔ TypeScript transform parity — round 3
2+
3+
Audit base: `b81357458326574b91875002aaeaa42135dd1311` (2026-08-14).
4+
5+
## Scope and severity
6+
7+
This audit followed the OpenCode TypeScript path from `transform.ts` through postprocess, compartment injection, frozen strip/replay, scheduler and limit resolution, then followed the native path through `rust-mode-transform.ts`, `transform.rs`, `selection.rs`, `boundary.rs`, `injection.rs`, `m0_compose.rs`, and `historian.rs`. It also checked the Claude Code profile and config path used by Thalamus. Corpus/data-plane parity (I-03), Pi↔OpenCode drift, and memory-holder ownership are excluded as requested.
8+
9+
**CRITICAL** means overflow/400/security risk; **HIGH** means deterministic provider-wire or major feature drift; **MEDIUM** means scheduling/reclaim drift; **LOW** means an edge-case byte difference. `STALE-PORT` means a port exists but later work changed one side.
10+
11+
## Findings
12+
13+
| ID | Class | Severity | TypeScript site | Rust/native site | Consequence |
14+
|---|---|---:|---|---|---|
15+
| R3-01 | MISSING | CRITICAL | `shared/window-geometry.ts:458-595`; `event-resolvers.ts:18-85` | `transform.rs:4666-4678,2833-2850` | CC consumes Thalamus `context_limit` raw: no S1 output reserve, overlay, provider geometry, usableSoft/usableHard split, or hard wall. |
16+
| R3-02 | STALE-PORT | CRITICAL | `event-resolvers.ts:166-245`; `rust-mode-transform.ts:1543-1549,1705` | `transform.rs:541-655`; `lib.rs:7007-7025`; `config.rs:87-120` | OC computes/sends the model/tokens-aware threshold, but Rust drops it and uses the bind-time scalar; all bands and protected-tail math use the wrong ceiling. |
17+
| R3-03 | STALE-PORT | HIGH | `config/schema/magic-context.ts:573-603,978-984,1060`; `hook.ts:1358-1362` | `config.rs:23-31,105-120,298-304`; `lib.rs:7018-7023` | TS defaults `memory.injection_budget_tokens` to 4k; Rust reads nonexistent `memory.budget_tokens` and defaults to 8k, changing ordinary m0/m1 bytes. |
18+
| R3-04 | MISSING | CRITICAL | `config/schema/magic-context.ts:70-104,573-613`; `config/project-security.ts:187-227` | `config.rs:298-333` | A project can set undocumented Rust-only memory/profile/historian budget keys that TS strips as unknown, enlarging provider-visible profile data and historian spend. |
19+
| R3-05 | STALE-PORT | HIGH | `transform-postprocess-phase.ts:1841-1863`; `hook.ts:1350-1362` | `rust-mode-transform.ts:1689-1722`; `transform.rs:576-587` | OC Rust never forwards auto-search enabled/score/minimum controls, so native defaults override the user's resolved config. |
20+
| R3-06 | DIVERGENT | HIGH | `auto-search-runner.ts:250-305` | `transform.rs:3420-3432,7165-7200` | TS decides on a new live user tail immediately; Rust decides only while another action already busts, delaying/suppressing low-pressure hints. |
21+
| R3-07 | STALE-PORT | MEDIUM | `auto-search-runner.ts:296-309`; `auto-search-hint.ts:55-130` | `transform.rs:6863-6932,7167-7210,7468-7520` | ASCII hint bytes match, but Rust lacks stacked-augmentation suppression, checks minimum length after sanitize/cap, and counts Unicode scalars instead of UTF-16 units. |
22+
| R3-08 | DIVERGENT | CRITICAL | `emergency-drop.ts:106-200` | `selection.rs:981-1010,1166-1180` | TS derives the emergency fixed floor from every active live tag; Rust subtracts tool arcs only and can remain above the safe ceiling. |
23+
| R3-09 | STALE-PORT | CRITICAL | `tool-drop-target.ts:383-408`; `emergency-drop.ts:76-100` | `selection.rs:239-274,733-771`; `transform.rs:9556-9649` | I-22 prevents adjacency collapse, but Rust retains reasoning TS clears while counting those bytes as reclaimed; wire differs and emergency selection can stop early. |
24+
| R3-10 | STALE-PORT | MEDIUM | `transform-postprocess-phase.ts:906-943,972-1038`; `heuristic-cleanup.ts:202-258` | `transform.rs:3095-3108,3213-3242`; `selection.rs:1160-1165` | I-08's duplicate selector runs only through the Rust ride/two-pass gate; TS runs it on every ordinary scheduler execute. |
25+
| R3-11 | DIVERGENT | MEDIUM | `transform-postprocess-phase.ts:1247-1305`; `apply-operations.ts:80-141` | `selection.rs:89-92,440-531,1125-1158` | Rust adds an owner-message K=20 supersession floor; TS protects the newest active tag window, so Rust retains arcs TS supersedes. |
26+
| R3-12 | STALE-PORT | CRITICAL | `transform-postprocess-phase.ts:751-773,1933-1943` | `transform.rs:2358-2360,9840-9863` | TS always exempts the latest assistant; Rust does so only for an OC mid-turn request, exposing completed signed reasoning to rewrite/400 risk. |
27+
| R3-13 | MISSING | MEDIUM | `transform-postprocess-phase.ts:1880-1943` | `transform.rs:9884-9914` | Historical reasoning age-clearing remains OC-only in Rust; CC retains bytes TS would clear (I-09 remains open). |
28+
| R3-14 | DIVERGENT | MEDIUM | `system-injection-stripper.ts:1-48`; `heuristic-cleanup.ts:162-199` | `transform.rs:8065-8117,8250-8265,8331-8337` | TS surgically removes injected blocks from mixed messages and recognizes two more markers; Rust only neutralizes whole injected messages. |
29+
| R3-15 | DIVERGENT | LOW | `strip-content.ts:620-680` | `transform.rs:8206-8209,8289-8315` | With no tag age, TS strips an untagged processed image on a bust; Rust requires a positive tag/cutoff and preserves it. |
30+
| R3-16 | MISSING | HIGH | `inject-compartments.ts:2188-2234`; `features/magic-context/mural/` | `m0_compose.rs:97-133,265-322` | TS injects the mural in HARD m0; Rust has no mural input or rendered block. |
31+
| R3-17 | MISSING | HIGH | `transform.ts:749-815` | `rust-mode-transform.ts:1398-1408`; `transform.rs:541-655` | Compaction-off splits three ways: TS still injects memory/docs, OC Rust returns raw, and CC Rust has no mode field and continues full compaction. |
32+
| R3-18 | MISSING | HIGH | `transform-postprocess-phase.ts:391-474` | `module-wire.ts:153-168`; `transform-postprocess-phase.ts:327-380` | OC Rust ingress removes the compaction summary, but native postprocess never rebuilds the canonical persisted summary after the synthetic head. |
33+
| R3-19 | DIVERGENT | MEDIUM | `transform-postprocess-phase.ts:201-250`; `ctx-reduce-availability.ts:405-413` | `transform.rs:615,3140-3150`; `injection.rs:199-269`; `lib.rs:145-160` | TS freezes tool-map plus live permission denial; OC Rust sends map presence only and CC no verdict, so native todo synthesis fails open. |
34+
| R3-20 | MISSING | HIGH | `prompt-surface-runtime.ts:136-178,216-221`; `system-prompt-hash.ts:346-365` | `prompt_surface.rs:13-28`; no override field in `config.rs`/`lib.rs` | CC guidance cannot honor the trusted user `guidance_override_path`. |
35+
| R3-21 | DIVERGENT | MEDIUM | `prompt-surface-runtime.ts:181-213` | `lib.rs:6402-6460` | TS warns and ignores an unknown tool-description key; Rust rejects the entire prompt-surface selection/transform. |
36+
| R3-22 | MISSING | MEDIUM | `transform.ts:2565-2604`; `channel2-delivery.ts:1-118` | `transform.rs:4428-4444,7720-7805`; CC profile suppressed | Channel-2 ceiling delivery exists for TS/OC and Rust/OC but is absent on CC. |
37+
| R3-23 | DIVERGENT | MEDIUM | `transform.ts:1754-1815` | `transform.rs:2492-2519` | TS may tag the first eligible pass; Rust deliberately denies bootstrap tags to CC and activates them later. |
38+
| R3-24 | DIVERGENT | LOW | `tool-drop-target.ts:209-239` | `selection.rs:562-615` | Edit-marker region caps use UTF-16 units in TS but Unicode scalars in Rust, producing different astral-text payload bytes. |
39+
| R3-25 | DIVERGENT | HIGH | bust/materialization gates at `transform-postprocess-phase.ts:906-943`; `inject-compartments.ts:2147-2240` | `transform.rs:2838-2848,5116-5146,5180-5222` | #313 is valid: legacy rows tokenize on every pass, and estimator changes can flip boundary-recut/HARD and full-prefix cache bust on a nominal defer. |
40+
41+
## Detail
42+
43+
### R3-01 — S1 geometry is host-only and CC is uncarved
44+
45+
`deriveWindowGeometry` resolves provider/auth and overlay facts, filters placeholder output values, applies configured output reserve, distinguishes considered-unknown geometry, and returns `usableSoft` and `usableHard`. The OC adapter is correct: it passes `usableSoft` as `usage.context_limit_tokens` and uses `usableHard` only for raw-fallback/provider-wall checks (`rust-mode-transform.ts:1453-1479,1527-1549`). Rust recomputes usage percentage from that same soft denominator.
46+
47+
CC has no equivalent. `effective_context_limit_tokens` returns supplied `ModuleUsage.context_limit_tokens` (only a generic sane-range check), and scheduler/boundary math consumes it. Rust contains no S1 constants/table/overlay. A 200k combined shared-output window can therefore schedule as 200k where TS schedules near 168k and enforces a different hard wall.
48+
49+
### R3-02 — resolved threshold is sent but not consumed
50+
51+
The adapter resolves `execute_threshold_tokens`/per-model percentage against S1 soft geometry and serializes `effective_execute_threshold`. `TransformRequestWire` has no such field, so serde ignores it. `ProducerContext.execute_threshold_percentage` always comes from bind-time scalar config. Force bands, Channel 2, protected-tail sizing, emergency ceiling, and CC all inherit the stale scalar.
52+
53+
### R3-03/R3-04 — budget schema and trust mismatch
54+
55+
TS's standard key is `memory.injection_budget_tokens`, default 4,000, and the hook forwards it. Rust reads `/memory/budget_tokens` and otherwise keeps 8,000. With no user setting, enough memories yield different selected sets and m0 hashes.
56+
57+
Rust also accepts project `/memory/budget_tokens`, `/memory/user_profile_budget_tokens`, and `/historian/context_limit_tokens`. None exists in the TS schema. The profile budget is unbounded and can enlarge provider-visible global user-profile data; the historian value can raise a derived chunk to 50k. Existing protection for historian model/fallbacks and scalar-threshold lowering is sound; these undocumented leaves are the escalation path.
58+
59+
### R3-05 through R3-07 — auto-search
60+
61+
I-02/I-04 closed CC control transport and common ASCII rendering, but OC `buildTransformBody` sends none of the three controls. Scheduling also differs: TS safely decides on an unserved last user message on any pass; Rust's `maybe_decide_user_hint_on_bust` waits for a natural bust.
62+
63+
For an ASCII memory fixture, both sides render the wrapper, pluralized header, 80-character fragments, and 800-character total cap identically. Residual counterexamples remain: TS suppresses stacked augmentation while Rust can add another hint; TS checks raw prompt length before sanitizing while Rust checks the sanitized/capped query; astral Unicode crosses JS and Rust caps differently.
64+
65+
### R3-08/R3-09 — emergency population and actual reclaim
66+
67+
TS explicitly passes every active tag type as `floorTags`, subtracting live text/file/reasoning/tool bytes from observed input to recover the irreducible prefix. Rust derives `all_active_reclaim_tokens` from tool arcs only, folds other live tail into its fixed floor, and under-computes reclaim.
68+
69+
I-22's adjacency detector closes the Anthropic merged-assistant 400 shape, but it does not close byte/accounting parity. TS full tool drop calls `clearThinkingParts`; Rust emits call/result targets only. Yet `ToolArc::reclaim_bytes` includes `reasoning_bytes`, so Rust can declare a target met while those bytes remain.
70+
71+
### R3-10/R3-11 — duplicate and supersession opportunities
72+
73+
I-08's owner fingerprint, safe list, keep-newest rule, and full-drop mechanics match. The opportunity does not: TS runs dedup on every ordinary execute; Rust needs a ride or two-pass batch. The newer K=20 owner-message supersession floor is also Rust-only. The level-vs-edge fix itself is present: an armed but idle emergency latch does not trickle supersession.
74+
75+
### R3-12/R3-13 — reasoning exemptions
76+
77+
TS identifies the latest assistant and always passes it as `reasoningMutationExemptMessage`. Rust returns an exemption only for `OpencodeAiSdk && mid_turn`, exposing a completed latest assistant to residual stripping. Separately, historical reasoning cutoff is gated to OC, so CC lacks I-09. Signature safety may justify representation differences, but it does not make the bytes equivalent.
78+
79+
### R3-14/R3-15 — frozen strips
80+
81+
TS can remove an embedded reminder/directive while preserving authored text and recognizes the idle-background/subagent-claim warnings. Rust mints `system_injected` only when the whole non-user message matches. Processed-image replay otherwise aligns; the edge is that TS defaults missing tag age to zero while Rust requires a positive tag/cutoff.
82+
83+
### R3-16 — mural is confirmed absent
84+
85+
TS resolves and appends the persisted mural during HARD m0. `M0Inputs` has memory, profile, docs, and compartments but no mural field, renderer, or content epoch. This is the known gap, not a differently named implementation.
86+
87+
### R3-17 — compaction-off is not one contract
88+
89+
TS off mode skips tags, strips, heuristics, historian folds, and markers but still runs additive memory/docs injection. OC rust-mode returns before the module and serves raw, losing additive injection. CC receives no off field and `McModuleConfig` does not parse `compaction.enabled`, so it continues the whole pipeline.
90+
91+
### R3-18/R3-19 — summary and todo host decisions
92+
93+
`module-wire.ts` filters the raw OpenCode summary and records normalization. TS canonicalization removes stale summaries, tags the persisted one, and inserts it after the synthetic head. `runRustModePostprocess` only replays note/search anchors, so native output lacks the summary and shifts array/cache bytes.
94+
95+
TS todo synthesis combines a frozen tool-map verdict with live permission denial. OC native transports only map presence; CC transports nothing, and `None` deliberately fails open. A denied/absent tool can leave a synthetic call/result on wire.
96+
97+
### R3-20/R3-21 — prompt-surface residuals
98+
99+
Built-in full/light selection and known tool overrides are live. TS reads a trusted user guidance file and falls back with a warning; CC has no field/reader. TS also warns and ignores unknown description keys, while Rust rejects the complete request, turning a typo into native failure/LKG rather than warning.
100+
101+
### R3-22/R3-23 — CC profile deltas
102+
103+
TS arms and asynchronously delivers Channel 2; Rust computes an equivalent host directive only for OC and tests assert CC has none. CC also cannot activate tags on bootstrap because `bootstrap_tagging_active` is hard-gated to `OpencodeAiSdk`.
104+
105+
### R3-24 — edit-marker UTF-16 versus scalar units
106+
107+
TypeScript's edit-marker `safeSlice` caps JavaScript UTF-16 units and backs off a split surrogate pair. Rust caps Unicode scalar values. BMP text matches; astral-heavy diff text near the region boundary produces different payload bytes. Caveman does **not** share this defect: both sides compare UTF-8 byte size (`tag.byteSize` / `row.source_bytes.len()`).
108+
109+
### R3-25 — #313 confirmed
110+
111+
Before pass classification, any initialized legacy row without `publication_floor_ordinal` calls `protected_tail_floor_allowance`, tokenizing every non-system live block. Its ordinal span controls `coverage_gap <= live_tail_allowance`. An estimator/version change can therefore flip whether a boundary-divergence recut forces HARD and busts the full m0 prefix. Scope is limited to legacy rows missing that floor, but the cache consequence is real.
112+
113+
## Fresh-batch verdicts and checked non-findings
114+
115+
| Area | Verdict |
116+
|---|---|
117+
| I-02 | **CC closed; OC still open** as R3-05. |
118+
| I-04 | **ASCII fixture closed; residual gate/Unicode drift** R3-07. |
119+
| I-06 `ctx_expand` | **Closed**: verbose range previews precede full single-message expansion. |
120+
| I-07 `ctx_reduce` | **Closed**: unknown tags named; zero-target refused without commit; acknowledgements distinguish states. |
121+
| I-08 | **Mechanics closed; opportunity incomplete** R3-10. |
122+
| I-12 | **Closed**: ×1.15, max three retries, strict `>1.05 × budget`. |
123+
| I-14 | **Closed**: CC transport, UTF-8 minimum-size basis, and frozen pass-start max-tag age basis align. |
124+
| I-22 | **Adjacency 400 fix closed; actual-reclaim/bytes incomplete** R3-09. |
125+
| Threshold/escalation bands | Formulas/constants match; input threshold and emergency population do not (R3-02/R3-08). |
126+
| `cache_ttl` three-state | **Closed for CC**: absent/default emits no marker override, explicit empty means provider default/no paid TTL, explicit finite maps to `5m|1h`. |
127+
| LKG | **No new delta**: native output plus host note/search overlays is captured and replayed with live tail. It preserves other divergences rather than introducing one. |
128+
| smart-drops gate | **Live on both sides**; K=20 representation differs (R3-11). |
129+
| prompt presets | Built-in full/light and known overrides are live; residual policy is R3-20/R3-21. |
130+
| #315 item 1 | **Rust has `forced_ids`** at `m1_compose.rs:359-416`, capped, removed from numeric additions, and appended outside quarter-budget trim. Pi parity is out of scope. |
131+
132+
## Proposed fix batches
133+
134+
1. **Geometry/threshold transport (R3-01, R3-02):** first-class soft/hard geometry and effective threshold contract across TS adapter, Thalamus, `lib.rs`, `transform.rs`, scheduler/boundary.
135+
2. **Config schema/trust (R3-03, R3-04, R3-17 config half):** align `injection_budget_tokens` and defaults, reject undocumented project leaves, transport `compaction.enabled`.
136+
3. **Auto-search control plane (R3-05–R3-07):** forward OC controls, separate live-tail decision from bust replay, stacked suppression, shared UTF-16 cap fixtures.
137+
4. **Reduction correctness (R3-08–R3-12, R3-14/R3-15/R3-24):** one sequential worktree because all overlap `selection.rs`/`transform.rs`; fix actual-reclaim accounting before opportunity/byte edges while retaining I-22 safety.
138+
5. **Composer/prompt bytes (R3-16, R3-20, R3-21):** mural input/content epoch, trusted guidance transport, normalized invalid-description policy.
139+
6. **Host representation/mode (R3-17 output half, R3-18, R3-19):** reuse TS additive off-mode, marker, and permission decisions around native serving.
140+
7. **CC transport/profile (R3-13, R3-22, R3-23):** Thalamus-coordinated reasoning safety, Channel-2 delivery, and bootstrap-tag behavior.
141+
8. **Estimator fence (#313/R3-25):** isolated Rust patch to persist/backfill the floor or delay legacy estimation until already-HARD, with a non-vacuity test proving SOFT does no estimator work and cannot flip recut state.

0 commit comments

Comments
 (0)