Skip to content

PROD-2446: make companion-field reference scanning schema-driven - #208

Merged
5PK merged 1 commit into
mainfrom
fix/PROD-2446
Aug 27, 2026
Merged

PROD-2446: make companion-field reference scanning schema-driven#208
5PK merged 1 commit into
mainfrom
fix/PROD-2446

Conversation

@5PK

@5PK 5PK commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes PROD-2446 — a follow-up audit after PROD-2431/2435/2442 found that the schema-driven companion-field fix landed in ContentFieldMapper only, while two sibling utilities that scan the exact same content-item field data for other purposes were never updated to match. Both still assumed a linked-content reference is only ever shaped as a contentid/contentID/sortids object key, with no knowledge of a Content-typed field's LinkeContentDropdownValueField/SortIDFieldName companion setting.

Depends on #207 (PROD-2431/2435/2442) — this branches off PROD-2431, which is not yet merged, so this diff includes that PR's commits until it lands. The actual new work here is the final commit (PROD-2446: ...); the diff will shrink to just that once #207 merges to main.

1 — PROD-2309 guard is blind to companion fields

has-unresolved-content-references.tscollectUnresolvedContentReferences() only recognized contentid/contentID/sortids keys. Called from content-batch-processor.ts (~line 416) before the field mapper runs, as the guard whose whole purpose is to throw before shipping a payload guaranteed to fail server-side with a NullReferenceException. sourceModel was already in scope at that call site but was never passed to the guard.

Worse: ContentFieldMapper.mapContentFields() (called right after) does find the companion field via the schema-driven pass, but when the reference can't be resolved it only increments a warning, not an error — and content-batch-processor.ts only logs validationErrors, never warnings. So an item with an unresolved companion-field reference sailed past the guard entirely and shipped to target with a dangling source ID — reproducing the exact opaque NullReferenceException PROD-2309 was built to prevent, via the companion-field path instead of the plain contentid/sortids path.

2 — Push-order dependency detection is blind to companion fields

collect-content-id-references.ts feeds get-content-item-types.ts's push-order logic (an item another item depends on is promoted to push first, so its source→target mapping exists in time). Same structural-only sniffing — collectContentIDReferences() didn't even take a model parameter, so it couldn't consult companion settings without a signature change. An item that references another item only through a companion field was never promoted to push-first, which can produce exactly the unresolved-reference scenario in #1.

Fix

  • Extracted the companion-field-pair lookup (previously a private method inside ContentFieldMapper) into a shared src/lib/content/linked-content-companion-fields.ts (getLinkedContentCompanionFields/findFieldKey), so there's one source of truth instead of three near-duplicates.
  • collectUnresolvedContentReferences() now accepts the source model and also checks each Content field's companion column (only at the top level — companions are always siblings of the main field, never nested).
  • collectContentIDReferences() now accepts the source model too; get-content-item-types.ts resolves each item's own source model via modelMapper (mirroring the existing hasValidMappings pattern) to supply it.
  • content-batch-processor.ts now passes sourceModel into the PROD-2309 guard call.

Testing

  • New regression tests in collect-content-id-references.test.ts and has-unresolved-content-references.test.ts: arbitrarily-named companion, SortIDFieldName companion, sentinel/no-model back-compat, and (for the unresolved-reference guard) case-insensitive match and top-level-only scanning.
  • New test in get-content-item-types.test.ts proving an item referenced only through a companion field is promoted to linkedContentItems (push-first).
  • Full suite: npm test — 105 suites / 1924 tests passing, no regressions.
  • npm run build — compiles clean.

🤖 Generated with Claude Code

PROD-2431/2435/2442 fixed ContentFieldMapper's remap of a linked-content
field's companion column (LinkeContentDropdownValueField/SortIDFieldName),
but two sibling utilities that scan the same content-item field data for
different purposes were never updated to match — both still assumed a
reference is only ever shaped as a contentid/contentID/sortids object key,
with no knowledge of an arbitrarily-named companion field.

1. has-unresolved-content-references.ts (the PROD-2309 pre-push guard):
   collectUnresolvedContentReferences() now takes the source model and also
   checks each Content field's companion column. Previously an unresolved
   reference living only in a companion field shipped to the target
   undetected — the field mapper leaves it as the SOURCE id and only warns
   (not errors), which content-batch-processor.ts doesn't log or block on —
   reproducing the exact opaque server NullReferenceException PROD-2309
   was built to prevent.

2. collect-content-id-references.ts (push-order dependency detection):
   collectContentIDReferences() now takes the source model too, so an item
   referenced only through a companion field is recognized as a dependency
   and promoted to push first (get-content-item-types.ts resolves each
   item's own source model via modelMapper to supply it).

Extracted the companion-field-pair lookup (previously private inside
ContentFieldMapper) into a shared src/lib/content/linked-content-companion-fields.ts
(getLinkedContentCompanionFields/findFieldKey), used by all three now
instead of duplicating the logic a third time.

Added regression tests for both utilities (arbitrarily-named companion,
SortIDFieldName companion, case-insensitive match, sentinel/no-model
back-compat) plus a get-content-item-types push-order test proving an
item referenced only via a companion field gets promoted to linked.

Full suite: 105 suites / 1924 tests passing. npm run build compiles clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@5PK
5PK merged commit d128b78 into main Aug 27, 2026
3 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