Skip to content

feat(parity): MOS summaries dump harness — Python↔Rust byte-exact (roadmap stage 1) - #308

Merged
PhysShell merged 5 commits into
mainfrom
claude/complex-project-tasks-viyycs
Jul 26, 2026
Merged

feat(parity): MOS summaries dump harness — Python↔Rust byte-exact (roadmap stage 1)#308
PhysShell merged 5 commits into
mainfrom
claude/complex-project-tasks-viyycs

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Дамп MOS-сводок (python -m ownlang summaries) становится двусторонним паритетным артефактом — тем самым «нормализованным диффом сводок Python↔Rust» из роадмапа (этап 1, §2), который проверяет порт инференции на уровне сводок, а не только конечных диагностик. Rust-сторона (own_bridge::dump_summaries) воспроизводит байт-в-байт stdout эталонного CLI на 35 кейсах: замороженный Layer-2 корпус фактов (26, заметается автоматически) + 9 синтетических MOS-кейсов (SCC-циклы, цепочки/циклы forward-return, extern-лог, тай-брейки слияния перегрузок, sig-словарь ключей, достижимая из фактов ветка degraded, sink-каналы, явные эффекты, non-ASCII экранирование).

Ключевые части:

  • Python (авторитетная сторона): tests/test_summaries_fixtures.py — семейство фикстур в стиле lowered-семейства; голдены пинят точные байты stdout CLI (json.dumps(indent=2, sort_keys=True), дефолтный ensure_ascii, trailing newline); INF-R1 проверяется на каждом кейсе (передёргивание functions[] → те же байты). Исключение одно — tolerant_unknown_kind (дверь load(), остаток Bridge contract OD-1/2/3: pin the tolerant door — direct check_facts() diverges from load() (unknown kind fallback, line coercion) #294 OD-2): аудируемая запись в манифесте, которая краснеет, если дверь начнёт его принимать.
  • Rust: own-bridge/src/mos.rs несёт полную дамп-поверхность (name/disposable/file/line/source + solve_with_log с отсортированным extern-логом); lower.rs — тай-брейки слияния (lexicographic-min имя параметра, min (file, line)); новый dump.rs — эмиттер, байт-совместимый с Python json.dumps (sorted keys, \uXXXX-эскейпы с суррогатными парами); degraded мапится в эталонный текст "ValueError: …".
  • Харнесс: own-bridge/tests/summaries.rs — леджерная сверка во все стороны (пропавший/протухший/осиротевший голден, фантомное исключение — красный билд), детерминизм, пол ≥35 кейсов, пиновка ветки degraded.

Тип изменения

  • feat — новая возможность
  • fix — исправление бага
  • docs — документация
  • refactor / chore / test / ci — без изменения поведения

Как проверено

  • python tests/run_tests.py — все сьюты, включая новое семейство (35 кейсов, 1 degraded)
  • ruff check . и mypy
  • селфтесты затронутых скриптов (python scripts/<...>.py --selftest) — скрипты не затронуты
  • cargo fmt --check, cargo clippy --all-targets (без новых предупреждений), cargo test — 29 сьютов / 116 тестов
  • mutation-проверка: порченый голден валит Rust-харнесс с байт-диффом; CLI stdout ≡ голден побайтово (diff)
  • CI зелёный на ветке (d5f3b69)

Связанные issue

Refs #262 (P-022 cutover: паритет уровня сводок — предпосылка разморозки), #294 (размещение дверей load/lower — зафиксировано леджером исключений), #304 (интерпроцедуральный трекер).

Чеклист

  • изменение покрыто тестом/селфтестом (или объяснено, почему нет)
  • README/docs обновлены при необходимости (spec/Inference.md §8, роадмап, шапки own-bridge)
  • коммиты в conventional-commit стиле (feat:, fix:, docs: …)

Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added Rust support for generating ownership summary reports matching the Python output byte-for-byte.
    • Summary reports now include method metadata, parameter transfer details, unresolved boundaries, and degraded-state information.
    • Improved deterministic handling of overloaded methods and non-ASCII text.
  • Tests

    • Added broad parity coverage using synthetic and corpus-based cases, including overloads, recursion, unresolved calls, and degraded results.
    • Added golden fixtures and validation for deterministic output and fixture consistency.
  • Documentation

    • Updated the inference specification and roadmap with summary parity requirements.

…admap stage 1)

The summaries dump becomes a two-sided parity artifact, the summary-level
diff the interprocedural roadmap names as stage 1's second role (and a P-022
cutover prerequisite for the inference layer):

Python (authoritative):
- tests/test_summaries_fixtures.py — new fixture family in the lowered-family
  style. Goldens pin the EXACT `python -m ownlang summaries` stdout bytes
  (json.dumps indent=2 sort_keys, default ensure_ascii, trailing newline).
  Two case sources, one golden tree: the frozen Layer 2 facts corpus swept
  automatically (minus the audited `excluded_lowered` ledger — an exclusion
  must still FAIL load() or the build goes red demanding promotion), plus
  9 synthetic MOS cases (SCC cycles, return-forward chains/cycles, the
  extern-boundary log, overload-merge tie-breaks, sig-key vocabulary, the
  degraded branch, sink channels, explicit effects, non-ASCII escaping).
  INF-R1 is enforced per case: every dump re-runs with functions[] reversed.

Rust:
- own-bridge/src/mos.rs — the dump surface is now carried in full: param
  name/disposable, method file/line/source, and solve_with_log with the
  sorted unresolved-boundary log (extern param + return edges); solve() stays
  as the lowering-path wrapper. lookup gains the reference's non-disposable
  short-circuit; SCC members filter on disposable like the reference.
- own-bridge/src/lower.rs — build_skeletons/merge_skeletons carry the
  INF-R1 tie-breaks: lexicographic-min merged param name, min (file, line)
  merged location.
- own-bridge/src/dump.rs (new) — dump_summaries renders the document
  byte-identically to the CLI, with a Python-json.dumps-compatible emitter
  (sorted keys at every level, 2-space indent, \uXXXX ensure_ascii escaping
  with surrogate pairs — serde_json cannot produce that shape); a solver
  failure degrades to the reference's "ValueError: …" text (INF-F6).
- own-bridge/tests/summaries.rs (new) — the harness: ledger/tree equality in
  every direction, byte-exact replay of all 35 goldens, determinism re-run,
  ≥35 case floor, the degraded branch pinned. tolerant_unknown_kind is the
  only exclusion (load()-door rejection, #294 OD-2 door placement — its
  rejection text stays pinned by the lowered family).

Docs: spec/Inference.md §8 points at the harness; the roadmap's stage-1
parity bullet is marked shipped; stale "#294 stays open" note in own-bridge
lib.rs corrected.

Verified: python tests/run_tests.py (all suites incl. the new family: 35
cases, 1 degraded), ruff, mypy, cargo fmt --check, clippy (no new
warnings), cargo test (29 suites / 116 tests green). Red-checked: a mutated
golden fails the Rust harness with the byte-diff message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 19e170fe-0d4c-45fd-83e9-16f827e0bd57

📥 Commits

Reviewing files that changed from the base of the PR and between d5f3b69 and 59af5f4.

📒 Files selected for processing (3)
  • rust/crates/own-bridge/src/dump.rs
  • rust/crates/own-bridge/src/lib.rs
  • tests/test_summaries_fixtures.py
📝 Walkthrough

Walkthrough

The PR adds a Rust MOS summary serializer with Python-compatible formatting, carries metadata and unresolved boundaries through resolution, and introduces Python/Rust golden parity harnesses with synthetic and lowered fixtures.

Changes

MOS summary parity

Layer / File(s) Summary
MOS metadata and unresolved resolution
rust/crates/own-bridge/src/lower.rs, rust/crates/own-bridge/src/mos.rs
Skeletons and summaries now carry parameter names, disposability, source locations, and inferred source tiers; MOS resolution records sorted unresolved boundaries.
Byte-exact summary serialization
rust/crates/own-bridge/src/dump.rs, rust/crates/own-bridge/src/lib.rs
Adds dump_summaries, custom Python-compatible escaping and formatting, deterministic key ordering, degraded output, and newline termination.
Cross-language parity fixtures
tests/test_summaries_fixtures.py, rust/crates/own-bridge/tests/summaries.rs, tests/fixtures/summaries/*, rust/crates/own-bridge/Cargo.toml
Adds manifest validation, golden generation and verification, permutation determinism checks, degraded/unresolved cases, and summary fixture coverage.
Parity contract documentation
docs/notes/interprocedural-roadmap.md, spec/Inference.md
Documents the Python golden writer and Rust byte-for-byte replay contract.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PythonFixtures
  participant GoldenFiles
  participant RustParityTest
  participant OwnIr
  participant SummaryDump
  PythonFixtures->>GoldenFiles: write summary goldens
  RustParityTest->>OwnIr: load facts.json
  OwnIr->>SummaryDump: provide typed facts
  SummaryDump-->>RustParityTest: emit summary bytes
  RustParityTest->>GoldenFiles: compare exact bytes
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the MOS summaries dump harness and byte-exact Python↔Rust parity, matching the main change.
Description check ✅ Passed The description fills all required sections with substantive details, checks, related issues, and checklist items.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/complex-project-tasks-viyycs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5f3b6933c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let root = root.as_object().cloned().unwrap_or_default();
// Python: `str(facts.get("module", "?"))` / `facts.get("functions", [])`
// (a present non-list reads as empty).
let module = root.get("module").map_or_else(|| "?".to_owned(), py_str);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match Python stringification for container-valued modules

When an accepted OwnIR document has a container-valued module (the Python loader does not validate this field, and Rust preserves it in the flattened root metadata), this call breaks the advertised byte-exact parity because py_str uses JSON formatting for containers rather than Python's str. For example, "module": ["M"] is rendered as "['M']" by Python but as "[\"M\"]" by Rust. Either reproduce Python container formatting here or reject the same values at both input doors.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified — the divergence is real (load() accepts any module type; Python str(["M"])"['M']" vs JSON text here), but neither of the two proposed resolutions is sound to take inside this PR:

  • Reproduce Python str(): unattainable for dicts under the current representation. serde_json's map (no preserve_order) re-sorts keys at parse time, while Python str() preserves insertion order — {"b":1,"a":2} diverges no matter how faithful the repr emulator is. Enabling preserve_order is a workspace-wide serialization-semantics change, disproportionate for a shape with no producer.
  • Reject at both doors: the class is wider than modulefunctions[].name, file, and call callee/sig are all str()-ified from unvalidated JSON, on the Layer 2 surface too (this predates the PR; it is the long-documented py_str caveat in lower.rs). Tightening load() piecemeal for one field is cosmetic; aligning the whole door is an authoritative-contract change of the Bridge contract OD-1/2/3: pin the tolerant door — direct check_facts() diverges from load() (unknown kind fallback, line coercion) #294 kind and deserves its own tracked decision, not a rider on this harness.

What this PR now does instead (2a34c7a): the scope is stated explicitly at the dump door — the parity contract covers scalar metadata, matching the crate's "restricted to the behavior the shared fixtures exercise" charter — and the property that keeps it honest is fail-loud: the Python generator would happily pin "['M']" into a golden, so any future fixture that smuggles a container in turns the Rust harness red on the byte diff rather than diverging silently.


Generated by Claude Code

claude added 2 commits July 26, 2026 09:20
The reference str()-ifies raw metadata (module, functions[].name/file)
without type validation on either door; a container there renders as
Python repr vs JSON text here — a shape with no producer that cannot be
emulated faithfully for dicts (serde_json re-sorts map keys, Python str()
keeps insertion order). Keep it out of the parity contract explicitly:
the harness pins scalar-metadata corpora only and goes red on the byte
diff if such a fixture ever lands, rather than diverging silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/test_summaries_fixtures.py (2)

216-223: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid recomputing _project just to read degraded.

_project already ran once per case in the main loop (line 189), each call doing two solver+render passes (expected + permuted). This second pass reruns it again per case purely to check degraded, doubling the total solve/render work for every case. Since this code only executes once every case already succeeded, degraded can be derived from expected inline in the main loop instead.

♻️ Proposed fix
+    n_degraded = 0
     for case, facts_path in sorted(plan.items()):
         golden_path = os.path.join(FIXDIR, f"{case}.summaries.json")
         try:
             expected, permuted = _project(facts_path)
         except OwnIRError as e:
             fails.append(f"{case}: load() rejected its facts ({e}) — a "
                          f"rejected case must be in excluded_lowered, not "
                          f"silently skipped")
             continue
         if permuted != expected:
             fails.append(f"{case}: dump is not byte-identical under "
                          f"functions[] permutation (INF-R1)")
             continue
         if not os.path.exists(golden_path):
             fails.append(f"{case}: golden missing; regenerate with "
                          f"'python tests/test_summaries_fixtures.py --write'")
             continue
         with open(golden_path, encoding="utf-8") as f:
             actual = f.read()
         if actual != expected:
             fails.append(f"{case}: golden is stale (the solver or the dump "
                          f"changed); regenerate with "
                          f"'python tests/test_summaries_fixtures.py --write'")
+            continue
+        if json.loads(expected).get("degraded") is not None:
+            n_degraded += 1
     for orphan in sorted(_goldens() - set(plan)):
         fails.append(f"{orphan}: orphaned golden (not a planned case); remove "
                      f"it or restore the case (manifest/facts)")
     if fails:
         for f_ in fails:
             print(f"FAIL: summaries fixture {f_}")
         return 1
-    n_degraded = 0
-    for facts_path in plan.values():
-        if json.loads(_project(facts_path)[0]).get("degraded") is not None:
-            n_degraded += 1
     print(f"summaries (MOS parity) fixtures OK: {len(plan)} cases "
           f"({n_degraded} degraded, {len(plan) - n_degraded} solved) "
           f"verified in sync")
     return 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_summaries_fixtures.py` around lines 216 - 223, Remove the second
per-case _project call used to count degraded fixtures. In the main loop around
the existing _project invocation, derive and accumulate the degraded count
directly from that case’s expected result, then use the accumulated value in the
final summary while preserving the existing verification behavior.

149-158: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reuse the already-computed lowered set instead of re-listing the directory.

lowered was computed once at line 122 via _disk_cases(LOWDIR, ".facts.json"); recomputing it here per synthetic case re-hits the filesystem for no benefit.

♻️ Proposed fix
-        elif name in _disk_cases(LOWDIR, ".facts.json"):
+        elif name in lowered:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_summaries_fixtures.py` around lines 149 - 158, In the
synthetic-case validation loop, replace the per-case _disk_cases(LOWDIR,
".facts.json") call with the already-computed lowered set from the surrounding
test setup. Keep the existing shadow-detection condition and error message
unchanged while avoiding repeated filesystem scans.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/test_summaries_fixtures.py`:
- Around line 216-223: Remove the second per-case _project call used to count
degraded fixtures. In the main loop around the existing _project invocation,
derive and accumulate the degraded count directly from that case’s expected
result, then use the accumulated value in the final summary while preserving the
existing verification behavior.
- Around line 149-158: In the synthetic-case validation loop, replace the
per-case _disk_cases(LOWDIR, ".facts.json") call with the already-computed
lowered set from the surrounding test setup. Keep the existing shadow-detection
condition and error message unchanged while avoiding repeated filesystem scans.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 61a10316-e281-4cae-b89b-1fca1c5f4abb

📥 Commits

Reviewing files that changed from the base of the PR and between fedd01a and d5f3b69.

⛔ Files ignored due to path filters (1)
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (54)
  • docs/notes/interprocedural-roadmap.md
  • rust/crates/own-bridge/Cargo.toml
  • rust/crates/own-bridge/src/dump.rs
  • rust/crates/own-bridge/src/lib.rs
  • rust/crates/own-bridge/src/lower.rs
  • rust/crates/own-bridge/src/mos.rs
  • rust/crates/own-bridge/tests/summaries.rs
  • spec/Inference.md
  • tests/fixtures/summaries/alias_join_cases.summaries.json
  • tests/fixtures/summaries/flow_kill_on_rebind.summaries.json
  • tests/fixtures/summaries/flow_unmapped_refs.summaries.json
  • tests/fixtures/summaries/fn_params_ordering.summaries.json
  • tests/fixtures/summaries/handles_global_counters.summaries.json
  • tests/fixtures/summaries/handles_null_metadata.summaries.json
  • tests/fixtures/summaries/hoist_neg_early_return.summaries.json
  • tests/fixtures/summaries/hoist_neg_nested_depth.summaries.json
  • tests/fixtures/summaries/hoist_neg_while_body.summaries.json
  • tests/fixtures/summaries/hoist_pool_kind.summaries.json
  • tests/fixtures/summaries/hoist_positive_release.summaries.json
  • tests/fixtures/summaries/hoist_positive_use_only.summaries.json
  • tests/fixtures/summaries/lines_preserved.summaries.json
  • tests/fixtures/summaries/manifest.json
  • tests/fixtures/summaries/mos_call_channel_overload_sig.summaries.json
  • tests/fixtures/summaries/mos_call_direct_consume.summaries.json
  • tests/fixtures/summaries/mos_call_unknown_drop.summaries.json
  • tests/fixtures/summaries/mos_fresh_mint.summaries.json
  • tests/fixtures/summaries/mos_killsite_toplevel.summaries.json
  • tests/fixtures/summaries/mos_untrack_inbranch.summaries.json
  • tests/fixtures/summaries/mosdump_degraded_duplicate_key.facts.json
  • tests/fixtures/summaries/mosdump_degraded_duplicate_key.summaries.json
  • tests/fixtures/summaries/mosdump_explicit_effects.facts.json
  • tests/fixtures/summaries/mosdump_explicit_effects.summaries.json
  • tests/fixtures/summaries/mosdump_nonascii_escaping.facts.json
  • tests/fixtures/summaries/mosdump_nonascii_escaping.summaries.json
  • tests/fixtures/summaries/mosdump_overload_merge_tiebreaks.facts.json
  • tests/fixtures/summaries/mosdump_overload_merge_tiebreaks.summaries.json
  • tests/fixtures/summaries/mosdump_partial_and_guarded.facts.json
  • tests/fixtures/summaries/mosdump_partial_and_guarded.summaries.json
  • tests/fixtures/summaries/mosdump_return_chains.facts.json
  • tests/fixtures/summaries/mosdump_return_chains.summaries.json
  • tests/fixtures/summaries/mosdump_scc_cycles.facts.json
  • tests/fixtures/summaries/mosdump_scc_cycles.summaries.json
  • tests/fixtures/summaries/mosdump_sig_vocabulary.facts.json
  • tests/fixtures/summaries/mosdump_sig_vocabulary.summaries.json
  • tests/fixtures/summaries/mosdump_sink_channels.facts.json
  • tests/fixtures/summaries/mosdump_sink_channels.summaries.json
  • tests/fixtures/summaries/routing_r1_unresolved.summaries.json
  • tests/fixtures/summaries/routing_r2_subscribe_self.summaries.json
  • tests/fixtures/summaries/routing_r3_capture_static.summaries.json
  • tests/fixtures/summaries/routing_r4_returned_fresh.summaries.json
  • tests/fixtures/summaries/routing_r5_di_capture.summaries.json
  • tests/fixtures/summaries/routing_r6_token_kinds.summaries.json
  • tests/fixtures/summaries/vocab_unknown_op.summaries.json
  • tests/test_summaries_fixtures.py

claude added 2 commits July 26, 2026 09:26
…lowered set

Both CodeRabbit nitpicks on the summaries fixture harness: the degraded
counter re-ran _project (two solver+render passes) per case after the
main loop had already produced the same bytes — derive it from the
verified dump instead; and the synthetic shadow check re-listed the
lowered directory per case where the already-computed set serves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
Arbiter P2 follow-up on the Codex finding: the crate-level doc and the
public dump_summaries promised unqualified byte-parity 'on the same
facts' while the domain boundary lived only on the private
implementation — the public reader had to excavate a private module to
learn what 'same' excludes. The public contract now states the shared
parity domain itself (metadata consumed through Python str() must be
JSON scalars; container-valued metadata diverges without a runtime
error and stays a #294-class door decision), and the dump.rs header's
BYTE-EXACT claim carries the same qualifier. No implementation change:
rejecting containers Rust-side would open a new door asymmetry (Python's
dump accepts and reprs them), the opposite of what #294 closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
@PhysShell
PhysShell merged commit 0b2d970 into main Jul 26, 2026
39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants