Repository navigation
test(seed): guard that seeds stay inside the app's own object graph (#1258) - #1260
Merged
Merged
Conversation
…1258) `Seed.object` is a plain string, so a seed naming a platform `sys_*` table type-checks, builds, validates and loads — and cannot produce a usable row. Measured on 17.1.0 by seeding three `sys_activity` rows for a demo lead: the rows land (boot counts 345 rows, up from 342) with every readonly column written, because seeds write under `{ isSystem: true }`. But `record_id` is `Field.text()`, and the loader resolves natural keys only for lookup / master_detail fields, so it stores the literal string while the lead's id is a runtime nanoid. The query `record:activity` actually issues — `{ object_name: 'crm_lead', record_id: <id> }` — returns 0 rows for the real id and 3 for the literal key. The tab stays empty; the seed book looks populated. Same rule as the `owner_id` note in src/data/index.ts, one column over: a seed cannot name an id that does not exist yet, and reference resolution is the only exemption. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This was referenced Aug 23, 2026
os-zhuang
marked this pull request as ready for review
August 23, 2026 13:40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1258 — and it deliberately does NOT populate the Activity tab, because measurement says that cannot be done from this repo. The card's premise is falsified; this PR ships only the guard that keeps the next author from paying for the same measurement.
What was measured
The card asked for "a handful of
sys_activityrows for the demo leads/accounts". I authored exactly that — threecompletedinteraction rows for the seeded lead Lisa Thompson (CloudFirst Inc) — built it, wiped.objectstack/data, booted, and read the result back out of the running server.The rows load.
pnpm buildis silent about a seed naming a platform table, the boot banner counts them (Seeds: app.objectstack.hotcrm 345 rows, up from 342), and every column arrives — readonly ones included. That answers the open question in the card:sys_activityis seedable in the narrow sense. Specifically:{ isSystem: true }, which exempts the static readonly strip — andsys_activitydeclares every fieldreadonly;type: 'completed'is accepted (the value the renderer maps to feed kindtask);celvalue such asdaysAgo(n)resolves normally;mode: 'upsert'is accepted even though the object ismanagedBy: 'append-only'.The rows attach to nothing.
sys_activity.record_idisField.text(), not a lookup, and the seed loader resolves a natural key only forlookup/master_detailfields — every other field is stored verbatim. Sorecord_id: 'Lisa Thompson'is stored as that literal string, while the seeded lead's id is a runtime nanoid (tgIjpNhjlfmWU8YF) that does not exist until first boot.The consequence, using the query
record:activityactually issues — read out of the shipped console bundle, not inferred (sys_activity, { $filter: { object_name, record_id }, $orderby: { timestamp: 'desc' } }):The tab stays empty and the seed book looks populated. This is the same rule the ownership note in
src/data/index.tsalready records forowner_id: 'Dev Admin', one column over: a seed cannot name an id that does not exist yet, and reference resolution is the only exemption from that.Two further facts, in case the id problem ever looks solvable locally:
sys_activityislifecycle.class: 'telemetry'withretention: { maxAge: '14d' }and day-shard rotation — rows live insys_activity__r<YYYYMMDD>behind a view — so a demo narrative older than a fortnight is reaped by design. ThedaysAgo(40)probe row is outside the retention window on the day it lands.crm_forecastprecedent the card pointed at:sys_activitydeclares noowner_idat all, and the seeded rows read back fine over REST as the dev admin.Why no fourth writer
Interaction rows have exactly two writer sites in
src/— thelog_call/log_meetingfactory insrc/actions/global.actions.tsandsend_emailinsrc/actions/contact.actions.ts. Nothing else writessys_activity. A hook oncrm_eventthat also wrote the pointer row would give every action-created event two rows: the two-producer conflict #702 ruled against, one object over. The card explicitly ruled that out in advance, and the measurement agrees with it.What this PR contains
One guard block in
test/seed-consistency.test.ts: every seed dataset must target an object this app declares.Seed.objectis a plainz.string(), so naming a platformsys_*table type-checks, builds, validates and loads — the failure is invisible at every gate and only shows up as an empty tab. The guard closes that whole class rather than the singlesys_activitycase, and the measurement above is written into the block comment so the next author inherits it instead of re-deriving it.If a future platform release makes an ActivityPointer target seedable, this test should be deleted deliberately — not widened.
Verification
Run at
86fd1e49(the final commit on this branch):pnpm typecheck— exit 0,tsc --noEmitcleanpnpm validate— exit 0 (pre-existingcolSpan/ props warnings on views this PR does not touch)pnpm build—✓ Build complete (952ms)pnpm test—Test Files 120 passed (120)/Tests 2841 passed | 2 skipped (2843)Reverse verification of the new guard, predicted direction red, observed red: re-registering the
sys_activityseed made exactly one test fail —expected [ 'sys_activity' ] to deeply equal []— with the other 36 in the file still green, so the guard is specific rather than a blanket break. The mutation was confirmed on disk by grepping the injected import, the array entry and the seed file's ownsys_activityline (all non-zero) before the run, and the restore leg was confirmed the same way (file absent,0mentions inindex.ts); the script carried atrap … EXIT INT TERM. No rebuild leg applies: this test imports../src/data/indexand../objectstack.configas source through vitest's transform, not across a packageexports→dist/boundary.Note on tooling:
scripts/pm/os-verify-lock.shrefuses on this macOS host —flockis absent, and the entry point reportsVERDICT lock-unusableitself — so the heavy commands above ran unserialised. Declaring it rather than hand-rolling a substitute lock.skip-changeset: this PR ships nothing to users — one test file, no metadata, no runtime behaviour.Generated by Claude Code