Skip to content

feat(merge): identity grouping — nest supersessions, split distinct tests (0046) - #13

Merged
siddhant573 merged 5 commits into
LambdaTest:mainfrom
siddhant573:feat/merge-identity-grouping
Jul 20, 2026
Merged

feat(merge): identity grouping — nest supersessions, split distinct tests (0046)#13
siddhant573 merged 5 commits into
LambdaTest:mainfrom
siddhant573:feat/merge-identity-grouping

Conversation

@siddhant573

Copy link
Copy Markdown
Collaborator

What

An optional tests.identity block in merge-rules turns a test-id collision from "pick one winner" into "group by identity".

tests:
  on_collision: error                 # unchanged fallback
  identity:
    keys: [external_id.commit_id, external_id.test_id]
    on_same: nest
    on_different: split
  • Same identity → the latest run holds the canonical tests/<id>/ exactly as an uncontested test does; superseded runs are archived whole beneath it as 1/, 2/ … oldest first.
  • Different identity → the copy gets its own sibling tests/<id>-1/, -2/.

Purely additive. Absent the block, output is unchanged — there is a regression test asserting exactly that.

merged/tests/
  login-flow/              result.yaml (latest) + steps/ logs/
    1/                     oldest superseded run, whole tree
    2/
  login-flow-1/            distinct test — result.yaml `test:` rewritten

Why

Producers derive test ids (a slug plus a path hash), so merging two runs of the same suite collides on every test. prefer_latest answers that by discarding artifacts and discard by dropping the test outright — neither preserves history, and neither can tell "the same test, run again" apart from "two different tests that happen to share a derived name". This realises the slice 0045 deferred as "cross-environment matrix identity".

Design notes

  • Why a block, not a split action on the rule list. Grouping must ask "does this challenger belong to group 0, group 1, or neither?", which needs the whole key set as an up-front fact. Scraping it from "every rule whose action is split" would make identity an emergent property of an unrelated ordering — adding a guard rule would silently redefine sameness.
  • Why not new on_collision values. error/prefer_first/prefer_latest/discard all answer which copy survives and never change the pack's shape. nest/split answer what shape the output takes.
  • Guard rules still resolve first, so a must: same + error rule can never be downgraded into a split. discard tombstones the base id — split siblings included.
  • Suffix reservation runs against every eligible pack's test ids, so a split never steals a name a pack legitimately owns (<id>-1 taken → allocates -2).
  • Nested copies are inert by construction, not special-casing: totals walk top-level tests/*, the failure index reads tests/<id>/steps/, L1 checks enumerate <test>/steps/.
  • Vendor-neutral per 0002keys are caller-supplied dot-paths; no producer field name enters src/ or the schemas.

Implementation

collide.ts generalises the union walk from one incumbent per test id to an ordered list of groups. A single-member group with folder === baseId is 0045's behaviour, so there is one code path rather than two. assemble.ts writes canonical + nested copies, rewrites test on split folders only (0031's directory equality), and pushes divergent environment down into every copy from its own source pack.

Also in this PR

  • src/design-links.test.ts — 0046 initially shipped without governs/feature, which made it invisible to the design viewer everywhere except the decisions log; it was the only one of 46 decisions missing them. The new test catches the class. Note it revealed that a governs entry may be a conceptual area (repo in 0001/0025) rather than a path, so only path-shaped entries are filesystem-checked.
  • CHANGELOG backfill for 0.1.6 — published to npm but never documented, leaving a hole between 0.1.5 and this release.

Verification

  • npx vitest run194 passed / 20 files
  • npm run build → clean
  • Docs site rebuilt; 0046 now resolves against Contract.tsx's design/contract/03-commands.md token

Note for consumers

on_same: nest retains artifacts that prefer_latest discards today, so merged packs of re-runs grow with the number of runs kept. That is the intent of the feature, but it is a real change in output size for anyone who opts in.

🤖 Generated with Claude Code

siddhant573 and others added 5 commits July 20, 2026 14:59
…split distinct tests)

An optional tests.identity block in merge-rules.yaml turns a test-id collision
from "pick one winner" into "group by identity". Same declared identity keeps
the latest run in the canonical tests/<id>/ slot and nests superseded runs
beneath it as 1/, 2/ …; different identity splits into a suffixed sibling
folder. Realises the cross-environment matrix identity slice 0045 deferred.

Purely additive — absent the block, merge behaves exactly as 0045 specifies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ests (0046)

An optional tests.identity block turns a test-id collision from "pick one
winner" into "group by identity". Copies whose declared keys all match keep the
latest run in the canonical tests/<id>/ slot and archive superseded runs
beneath it as 1/, 2/ … oldest first; copies that differ split into a suffixed
sibling tests/<id>-1/.

- collide: the union walk generalises from one incumbent per test id to an
  ordered list of groups, so a 3+-way collision is matched against every
  existing group before a new folder is minted. Single-member groups with
  folder === baseId are exactly 0045's behaviour — one code path, not two.
- assemble: writes canonical + nested copies, rewrites `test` on split folders
  only (0031's directory equality), and pushes divergent environment down into
  every copy from its own source pack.
- Guard rules still run first, so a must:same + error rule can never be
  downgraded into a split; discard tombstones the base id, siblings included.
- Suffixes are reserved against every eligible pack's test ids, so a split
  never steals a name a pack legitimately owns.

Nested copies are inert to totals, the failure index and validation, all of
which scope themselves one level. Purely additive: absent the block, output is
unchanged.

192 tests passing, build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"TMS test uuid" is product jargon in a decision whose point is that
evidence-cli never interprets an identity key. Per 0002, nothing
producer-specific belongs in the contract's vocabulary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The design viewer is generated from decision metadata, not prose: `governs`
is reverse-indexed into the "shaped by" chips on the spec page
(design/web/src/components/Contract.tsx builds tokens from it) and `feature`
attaches a decision to its spec-area node. 0046 shipped without either, so it
rendered in the decisions log but was invisible everywhere else — it was the
only one of 46 decisions missing them.

Adds src/design-links.test.ts to catch the class: every decision must declare
governs + feature, and every path-shaped governs entry must still exist. Note
a governs entry may be a conceptual AREA ("repo" in 0001/0025) rather than a
path, so only path-shaped entries are checked against the filesystem.

194 tests passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also backfills the missing 0.1.6 CHANGELOG entry: that version is published
on npm but was never documented here, which left a hole between 0.1.5 and
this release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@siddhant573
siddhant573 merged commit fc9e91d into LambdaTest:main Jul 20, 2026
1 check 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.

1 participant