fix(platform-objects): sys_email.highlightFields names to_addresses, not to - #16076
Conversation
…not to
`highlightFields` read ['subject', 'to', 'status', 'sent_at']. `sys_email`
has no field `to` — its recipient column is `to_addresses` — so the entry
was a dangling field reference on a shipped platform object.
Every consumer of `highlightFields` silently skips an entry it cannot
resolve, so the default list columns, record cards, previews and the detail
highlight strip each rendered one field short, with no error anywhere. And
since `object-field-ref-unknown` crossed onto the object write door, the
body could not be republished through `PUT /api/v1/meta/object` or a
package publish (422 INVALID_METADATA). It reaches the runtime as a
code-shipped registry object, so boot was unaffected.
The new pin drives the real door — `runRuntimeAuthoringRules({ type:
'object' })` with the audit module's siblings as context — plus a control
that restores the old entry and requires the same call to refuse it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
The package's tsconfig excludes `**/*.test.ts`, so `pnpm typecheck` never compiled this file; an ad-hoc program that includes tests found TS2677 on the sibling filter's type predicate. Replaced with a plain predicate over `unknown[]`, which is what `context.objects` takes anyway. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
…s-email-highlightfields-to-addresses
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e7ae4c07736f908eceb5bfa2e692337feb0884d1 && git checkout e7ae4c07736f908eceb5bfa2e692337feb0884d1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c2520416cff04575dd3def789dd04a5745e3912b b5ffe86e04b927e04d9a7773373f292f4e023cb8 && git checkout -B drift-repro c2520416cff04575dd3def789dd04a5745e3912b && git merge --no-ff b5ffe86e04b927e04d9a7773373f292f4e023cb8
node scripts/docs-audit/affected-docs.mjs --json c2520416cff04575dd3def789dd04a5745e3912b
|
⭐ Ruling on the open question — A, paired with C. And
|
Fixes #15629
packages/platform-objects/src/audit/sys-email.object.tsdeclaredsys_emailhas no field namedto. Its recipient column isto_addresses, sohighlightFields[1]was a dangling field reference on a shipped platform object. The entry now readsto_addresses; nothing else about the object moved — no reorder, no added column, no tidying.Reproduced through the real door, before and after
Not by reading the array. The object was pushed through the object write door —
runRuntimeAuthoringRules({ type: 'object' })from@objectstack/lint, with the audit module's nine sibling objects as resolution context — at the branch base and again after the one-line change.Before (base
5f4f1f6e2), exactly one error, and it is the card's:After:
errors: [],advisories: [], withvalidateReferenceIntegrityamong the eight rules the door ran.Why it mattered, and why it was p2 rather than p1
Two consequences of different kinds:
highlightFieldsis the object's ordered "most important fields" pointer —packages/spec/src/data/object.zod.tsstates its own reach: "Drives default columns, cards, previews, detail highlight strip." Nothing throws or logs when an entry does not resolve, so the platform's own outbound-email log lost its recipient column with no error anywhere. The renderers live in theobjectuisibling repo and this PR did not drive them; reading them shows the two paths degrade differently, which is worth knowing:RelatedList.tsxprunes the unresolvable name and renders one column short, whilederiveHighlightFieldspasses it straight through torecord:highlights— and sincesys_emailsetsnameField: 'subject', the title is dropped first, sotobecame the first chip on the detail highlight strip.highlightFieldsreference — the authoring gate never sees what the app builder produces #15254 crossedobject-field-ref-unknownonto the object write door, this body could not be republished throughPUT /api/v1/meta/objector a package publish:packages/metadata-protocol/src/runtime-authoring-gate.tsturns anyerrorfinding intocode: 'INVALID_METADATA',status: 422.sys_emailreaches the runtime as a code-shipped registry object instead —EmailServicePluginhands it to the manifest service, a path that runs no authoring gate — so boot was never affected and nothing was failing in production. A trap laid for whoever next edited the object through a door rather than the file.The one test increment
packages/platform-objects/src/audit/sys-email.highlight-fields-resolve.test.ts— deliberately scoped tosys_emailalone. Wideningobject-field-ref-unknowninto a corpus-wide gate over the shipped objects is the durable repair, and that is a gate-surface expansion belonging on its own card, not here.Two cases, and the second is not decoration: a gate assertion yielding zero findings is indistinguishable from a gate that never ran, so the control restores the pre-fix entry and requires the same call to refuse it at the same path. Green therefore means "the door read this object and accepted it", never "nothing looked".
Verification
Union run on the final commit —
git rev-parse --short HEAD=b5ffe86e0.pnpm --filter @objectstack/platform-objects testpnpm --filter @objectstack/platform-objects typecheckpnpm --filter @objectstack/plugin-email test(the only package importingSysEmail)pnpm --filter @objectstack/plugin-email typechecknode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)pnpm check:type-check-debt(--re-measure)PREREQUISITE NOT MET— not a pass and not a findingA note on this package's typecheck, because it is a trap worth naming.
packages/platform-objects/tsconfig.jsonexcludes**/*.test.ts, sopnpm --filter @objectstack/platform-objects typecheckcompiles no test file at all — its green says nothing about the file this PR adds. That is a ledgered, shrink-only TEST_DEBT entry inscripts/check-type-check-coverage.mjs({ errors: 3, note: 'TS2339 x2, TS7006 x1' }), not an oversight. So the new test was measured against an ad-hoc test-inclusive tsc program instead. That measurement caught a real TS2677 in the first draft of this test, which the package's owntypecheckreported green through; it is fixed in the second commit. The package's error count stays exactly 3 — all three pre-existing, infeature-gate-guard.test.ts, none from this PR — the same shape the ledger's@objectstack/service-smsnote already records for a type-clean test file added to a hidden-test package.Three of the 54 answered
PREREQUISITE NOT MET(exit 3), which their own text insists is neither a pass nor a finding:check:dual-build-cjs-loads,check:i18nandcheck:type-check-debteach read built output across the whole workspace, and this branch built only the closures it needed. CI builds the farm and runs all three. For the one of them this change kind is documented to move —check:type-check-debt— the direct substitute is in the note above: the package's TEST_DEBT number was re-measured with its closure built, and it is still exactly 3.One reading in this run was invalid and re-taken, recorded because the shape is worth knowing:
check:dts-closurefirst came back exit 1 naming missing.d.tsfiles in@objectstack/coreand@objectstack/lint. It was not a finding — it had been run concurrently with a rebuild of those very packages in the same worktree, andtsupclearsdist/before it writes. Re-run on a settled tree it is green: "82/82 declared declaration file(s) present across 17 package(s)". A gate read against a tree being rebuilt underneath it can go wrong in either direction.Local scope was narrowed deliberately: the repo-wide
pnpm lintsweep is CI's run, not this branch's.Bump
patch, on@objectstack/platform-objects. Clause-② reading:no, with both limbs re-derived rather than assumed, and the tension stated rather than smoothed over:origin/main→ rebuild → diff the built.d.ts→ restore and prove the restore byte-exact (blob equals the HEAD blob,git diff HEADempty).dist/index.d.tsis byte-identical — but that is not evidence, it is an 11-line re-export barrel that cannot change for a data edit.dist/audit/index.d.tsdoes change, one line:readonly highlightFields: ["subject", "to", …]becomes["subject", "to_addresses", …], becauseObjectSchema.createpreserves literal types. No exported symbol is added, removed or renamed; no function signature moves; the type's shape is unchanged. What moved is one literal value inside an inferred data type — the same thing that moves when any shipped object'slabelis edited. That is a change, not a widening.Generated by Claude Code