Skip to content

test(parity): pin key order across every way an object reaches a layout (#10868 stage 0) - #10919

Closed
proggeramlug wants to merge 1 commit into
mainfrom
test/shape-identity-order
Closed

proggeramlug wants to merge 1 commit into
mainfrom
test/shape-identity-order

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Stage 0 of #10868 step 2.5 (canonical shape identity). One new parity file, no runtime change.

Why identity tests land before any identity change: step 2.5 changes shape identity for every object in every program, and key order is observable in JS — so a canonicalisation that merged two layouts with the same key set but different order is a silent wrong answer everywhere, not a slow one. These tests exist first so every later slice is checked against them rather than written alongside them.

What the eleven scenarios pin

75 output rows, each scenario printed through Object.keys, for-in, Reflect.ownKeys and JSON.stringify:

scenario what it pins
born3 / grown3 a literal and the same keys added by name enumerate identically
ab / ba same key set, two orders — must never be merged
tomb / direct delete then re-add moves the key to the end
hole / holeGrown a mid-list delete leaves the survivors in order, born or grown
bornWide / grownWide born with 8 keys vs grown to 8 by name — the grown one spills past INLINE_SLOT_FLOOR; same layout, different physical placement (the pair L8.3.7's birth-sizing precondition exists for)
mixed / mixedBorn integer-like keys first, ascending, then strings in insertion order
dp / dpGrown defineProperty with enumerable:false is in ownKeys, not in keys
acc an accessor keeps its insertion position
sym symbols come after strings in ownKeys
np a null-prototype object
siteA / siteB two literal sites with equal key lists — distinct static arrays today, one layout under a content key

Proven able to fail

A row that cannot redden is documentation. The four views reach the key list by three different paths, so one sabotage was not enough; each was applied to the runtime, run, and reverted, and the file's header records which proves which so a reviewer can re-run it:

view path sabotage rows red
keys= / forin= js_object_keys reverse its result 37 lines
own= js_object_get_own_property_names (via Reflect.ownKeys) reverse its result 19, own= only
json= object_keys_array read directly by the json/stringify_* serialisers reverse the keys array at publication 9 json= rows

The row that matters most is ab / ba: under the first sabotage both come back in the same order — i.e. the sabotage merges the exact two layouts this file exists to keep apart. Under the third, hole prints {"d":1,"b":3,"a":4} — keys reversed, values left in their slots — which is the silent-wrong-value failure a wrong canonicalisation would produce.

The json= rows needed their own sabotage because JSON.stringify bypasses both enumeration functions; the first two sabotages left every json= row green. Reported rather than left for review: until the third sabotage existed those rows were documentation, not tests.

Verification

Byte-identical to node on pristine main @ v0.5.1632 (75 rows), built from its own tree — not only on the stacked branches where step 2.5 is being developed.

Summary by CodeRabbit

  • Tests
    • Added coverage to verify that object property identity and ordering match Node.js across a broad range of object shapes and property types.
    • Added checks for enumeration, own-property, and JSON views, including symbols, accessors, non-enumerable properties, null-prototype objects, and integer-like keys.

…ut (#10868 stage 0)

Step 2.5 of #10868 makes shape identity content-canonical, which changes
identity for every object in every program. Key ORDER is observable in JS, so a
canonicalisation that merged two layouts with the same key SET but different
order -- or a deleted-then-re-added key with a never-deleted one -- is a silent
wrong answer everywhere, not a slow one. These tests land BEFORE any identity
change so that every later slice is checked against them rather than written
alongside them.

Eleven scenarios, 75 output rows, each printed through Object.keys, for-in,
Reflect.ownKeys and JSON.stringify: born vs grown; same set in two orders;
tombstone then re-add; mid-list delete; born-wide vs grown-into-spill (the pair
the design's birth-sizing precondition exists for); integer-like keys before
string keys; defineProperty with enumerable:false; accessor position; symbols
after strings; a null-prototype object; and two literal sites with equal key
lists.

Proven able to fail. The four views reach the key list by three different
paths, so one sabotage was not enough; each was applied to the runtime and
reverted, and the file's header records which proves which:

  keys= / forin=  reverse js_object_keys' result          37 lines red
  own=            reverse js_object_get_own_property_names 19 rows red, own= only
  json=           reverse the keys array at publication    9 json= rows red
                  (json/stringify_* read object_keys_array
                   directly and bypass both functions above)

The row that matters most is ab/ba: under the first sabotage both come back in
the same order, i.e. it merges the two layouts this file exists to keep apart.
@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Understand this PR’s impact

Explore downstream dependencies and potential security impact with Blast Radius.

View blast radius →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: e8e6d696-20ed-4d0d-902a-e28be81f7b07

📥 Commits

Reviewing files that changed from the base of the PR and between 0fa3915 and 7bfeafb.

📒 Files selected for processing (1)
  • test-files/test_parity_shape_identity_order.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

Adds a parity test file for canonical object shape identity and property order. The tests compare literal, grown, deleted, wide, and property-definition scenarios across multiple observable object views.

Changes

Shape Identity Order

Layer / File(s) Summary
Observable order views
test-files/test_parity_shape_identity_order.ts
Adds a shared helper that prints Object.keys, forin, Reflect.ownKeys, and JSON.stringify results.
Shape construction and deletion cases
test-files/test_parity_shape_identity_order.ts
Compares literal and grown objects, insertion orders, delete-and-readd behavior, holes, and wide objects.
Property order edge cases
test-files/test_parity_shape_identity_order.ts
Tests integer-like keys, non-enumerable keys, accessors, symbols, null-prototype objects, and separate literal sites.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Other

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: parity tests that pin key order across object layout paths for stage 0 of issue #10868.
Description check ✅ Passed The description gives a clear purpose, detailed scenario coverage, related issue, and verification result. It does not use the template headings or include the requested command-based test plan and ch…
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

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.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Landed on main in merge train 254 (#10930, a022cf2e41, released as v0.5.1634) — your commits are on main verbatim; the train cherry-picked them rather than merging this branch, so GitHub cannot mark it merged. Closing as landed, not as rejected.

A changelog fragment was written for it in the train (changelog.d/10919-shape-identity-key-order.md); the PR carried none.

The train was validated as one tree: all ratchets, cargo check --workspace --all-targets under -D warnings, cargo audit (0 vulnerabilities), the 83-gate run_lint_gates.sh (only the known-red public baseline failing), 6,679 unit tests + 1,150 CLI tests + 8 acceptance tests with zero failures, both compiler-output regressions, the repsel census, and a 174-test gap sweep with no unexplained regressions. Artifacts were pinned by sha256 before the test phase and still matched after it.

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.

1 participant