Skip to content

Round-trip linksTo error/not-found sentinels through the not-loaded wire shape#4978

Merged
habdelra merged 2 commits into
mainfrom
cs-11223-sentinel-serialization-round-trip-save-reload-state
May 27, 2026
Merged

Round-trip linksTo error/not-found sentinels through the not-loaded wire shape#4978
habdelra merged 2 commits into
mainfrom
cs-11223-sentinel-serialization-round-trip-save-reload-state

Conversation

@habdelra
Copy link
Copy Markdown
Contributor

Summary

When a card is saved while a linksTo / linksToMany field is in error or not-found state, the wire format is identical to the not-loaded shape — { links: { self: <reference> }, data: { type, id: <reference> } }, with no errorDoc and no discriminator. On reload the bucket gets a NotLoadedValue and the lazy-load fires; if the target is still broken, the failure path reproduces the typed sentinel. The relationship state is always reconstructed from the live target rather than from persisted failure data, and the JSON:API wire shape stays stable.

The serialize behavior already lands in the parent PR (the bucket consumers tolerate a sentinel). This change:

  • Factors the non-present-link → relationship-link mapping into one helper (serializeNonPresentLink) shared by the singular and plural serializers, so the two paths cannot drift on the wire shape.
  • Adds the round-trip test suite proving save → reload preserves state, with fixtures that plant state through the lazilyLoadLink failure path — never by direct bucket writes.

The lid→id correlation for unsaved links is untouched: a sentinel only ever carries a URL reference, so its serialize branch is fully separate from the unsaved-card data: { lid } branches.

Stacked on #4975. Base will retarget to main once the parent merges.

Ticket: CS-11223

Test plan

New tests/integration/components/linksto-sentinel-roundtrip-test.gts (6/6 passing locally):

  • a card carrying a link-not-found sentinel serializes to the not-loaded wire shape (no errorDoc, no discriminator)
  • broken-link wire is byte-for-byte identical to a not-yet-loaded link
  • round-trip: the serialized broken link deserializes to a not-loaded value and reproduces not-found on read
  • round-trip: a present link reproduces present
  • plural: a slot holding a sentinel serializes identically to a not-loaded slot
  • contained FieldDef: a broken nested linksTo round-trips through the outer card (serialize/deserialize nest through Contains; lazy-load fires for the nested reference)

Regression suites green locally: serialization (114), linksTo error sentinel producer (6), linksTo sentinel predicates (5), getRelationship (21), CardDef-FieldDef relationships (7). pnpm lint:types and pnpm lint:js clean.

🤖 Generated with Claude Code

habdelra and others added 2 commits May 27, 2026 05:01
…ializers

Factor the not-loaded/error/not-found → relationship-link fragment used by both
the singular and plural linksTo serializers into one helper so the two paths
cannot drift on the wire shape.

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

Cover that a card saved with a linksTo field in error/not-found state writes the
same wire shape as a not-loaded link, and that reload reconstructs the state from
the live target:

- a card carrying a link-not-found sentinel serializes to the not-loaded shape
- broken-link wire is byte-for-byte identical to a not-yet-loaded link
- round-trip: the serialized broken link deserializes to a not-loaded value and
  reproduces not-found on read
- round-trip: a present link reproduces present
- plural: a slot holding a sentinel serializes identically to a not-loaded slot
- contained FieldDef: a broken nested linksTo round-trips through the outer card

Fixtures plant state through the lazilyLoadLink failure path, never by direct
bucket writes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

Preview deployments

Host Test Results

    1 files      1 suites   1h 39m 9s ⏱️
2 787 tests 2 772 ✅ 15 💤 0 ❌
2 806 runs  2 791 ✅ 15 💤 0 ❌

Results for commit efa8710.

Realm Server Test Results

    1 files      1 suites   6m 50s ⏱️
1 491 tests 1 491 ✅ 0 💤 0 ❌
1 582 runs  1 582 ✅ 0 💤 0 ❌

Results for commit efa8710.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR ensures linksTo / linksToMany terminal sentinel states (link-error / link-not-found) serialize exactly like the existing “not-loaded” relationship wire shape, so a save → reload cycle reconstructs relationship state from the live target rather than persisting failure metadata. It also adds an integration test suite that exercises the full round-trip via the real lazy-load failure path (404), including plural and nested/contained relationships.

Changes:

  • Factor the “non-present link” relationship serialization into a shared helper (serializeNonPresentLink) used by both singular and plural serializers.
  • Add a new integration test suite covering serialization equivalence and round-trip behavior for singular, plural, and contained/nested linksTo.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
packages/host/tests/integration/components/linksto-sentinel-roundtrip-test.gts Adds round-trip integration coverage proving sentinel/non-loaded wire equivalence and state reconstruction on reload.
packages/base/card-api.gts Introduces serializeNonPresentLink helper and uses it in LinksTo/LinksToMany to prevent wire-shape drift.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@habdelra habdelra requested a review from a team May 27, 2026 11:21
@habdelra habdelra changed the base branch from cs-11218-write-errornotfound-sentinels-into-the-linksto-data-bucket to main May 27, 2026 15:33
@habdelra habdelra merged commit ba6a7e7 into main May 27, 2026
73 of 74 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.

3 participants