From e9c424e7978f21a91fe52bba98a1db97d5a01410 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 07:01:15 +0000 Subject: [PATCH 1/2] fix(examples): require the master on showcase field-zoo's f_master_detail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `showcase_field_zoo.f_master_detail` was the only `master_detail` declaration across the loadable authored corpora that did not set `required: true` — a detail record cannot exist without its master, and `objectstack validate` was warning about exactly this one field (`relationship/master-detail-required`). Both seeded zoo specimens already supply the value, so no data changes and nothing turns red; this is example hygiene, and it zeroes the migration list a future promotion of that rule from `warning` to `error` would produce. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21 --- examples/app-showcase/src/data/objects/field-zoo.object.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/app-showcase/src/data/objects/field-zoo.object.ts b/examples/app-showcase/src/data/objects/field-zoo.object.ts index 106da8fa70..76120013cd 100644 --- a/examples/app-showcase/src/data/objects/field-zoo.object.ts +++ b/examples/app-showcase/src/data/objects/field-zoo.object.ts @@ -104,7 +104,7 @@ export const FieldZoo = ObjectSchema.create({ // this is the seedable half of the `multiple: true` reference surface — // see `f_users` below for the half that a fresh boot cannot seed. f_lookups: Field.lookup('showcase_account', { label: 'Lookup → Accounts (multiple)', multiple: true }), - f_master_detail: Field.masterDetail('showcase_project', { label: 'Master-Detail → Project' }), + f_master_detail: Field.masterDetail('showcase_project', { label: 'Master-Detail → Project', required: true }), f_tree: { type: 'tree', label: 'Tree (self/category)', reference: 'showcase_category' }, // ── User (lookup specialized to sys_user) ──────────────────────────── From b0e788ee311a9d5db28b4599f2fcb968182ab13d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 07:46:37 +0000 Subject: [PATCH 2/2] test(dogfood): give the temporal-storage zoo fixtures a real master MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `showcase_field_zoo.f_master_detail` is now required, and this suite creates its zoo rows through the real REST write path supplying only `name` plus a temporal field. Every insert answered 400 "Master-Detail → Project is required", failing at `write f_time hm: expected 400 to be 201`. The suite now seeds its own master chain in `beforeAll` — `showcase_account`, then `showcase_project`, which declares a required lookup to the account, so the order is forced — and passes that id to each of the four zoo inserts. The epoch-rejection case gets it too, so the 400 it asserts is still about `f_time` rather than about a missing master. Created rather than read out of the showcase seed on purpose: this file's assertions rely on it owning every row it reads. Also corrects the comment that claimed the object "requires only `name`". Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21 --- .../test/temporal-storage-e2e.dogfood.test.ts | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/qa/dogfood/test/temporal-storage-e2e.dogfood.test.ts b/packages/qa/dogfood/test/temporal-storage-e2e.dogfood.test.ts index b0790b8ea2..922bc13391 100644 --- a/packages/qa/dogfood/test/temporal-storage-e2e.dogfood.test.ts +++ b/packages/qa/dogfood/test/temporal-storage-e2e.dogfood.test.ts @@ -74,11 +74,47 @@ const DATE_SHAPES: Array<[string, string, string]> = [ describe('dogfood: temporal storage is one shape end-to-end (#3912/#3994/#4033)', () => { let stack: VerifyStack; let token: string; + /** The master every zoo row below hangs off — see the note in `beforeAll`. */ + let masterId: string; beforeAll(async () => { stack = await bootStack(showcaseStack); token = await stack.signIn(); + // `showcase_field_zoo.f_master_detail` is a REQUIRED master_detail, so + // every zoo row below needs a real `showcase_project` to hang off. + // + // It is CREATED here rather than resolved out of the showcase seed, to keep + // the property this file's assertions rest on: it owns every row it reads, + // so it cannot be perturbed by what another suite's seed data happens to + // contain. `showcase_project` in turn declares a REQUIRED lookup to + // `showcase_account`, so the account has to exist first — seeding them in + // the wrong order is refused rather than silently writing a project that + // points at nothing (#4441). + const newId = async (object: string, body: Record): Promise => { + const res = await stack.apiAs(token, 'POST', `/data/${object}`, body); + expect( + res.status, + `seed ${object}: ${res.status} ${await res.clone().text()}`, + ).toBeLessThan(300); + const json = (await res.json()) as { id?: string; record?: { id?: string } }; + const id = json.id ?? json.record?.id; + expect(id, `no id returned seeding ${object}`).toBeTruthy(); + return id as string; + }; + + const accountId = await newId('showcase_account', { + name: `${P}_ref_account`, + status: 'active', + }); + masterId = await newId('showcase_project', { + name: `${P}_ref_project`, + // `planned` is the state machine's declared initial state — anything else + // is refused with `invalid_initial_state`. + status: 'planned', + account: accountId, + }); + // `Field.time` fixtures — written through the REAL REST write path, which // is the half #3994 fixed. Writing them via the engine would bypass // `formatInput` and prove nothing. @@ -86,6 +122,7 @@ describe('dogfood: temporal storage is one shape end-to-end (#3912/#3994/#4033)' const res = await stack.apiAs(token, 'POST', '/data/showcase_field_zoo', { name: `${P}_time_${key}`, f_time: value, + f_master_detail: masterId, }); expect(res.status, `write f_time ${key}`).toBe(201); } @@ -94,16 +131,19 @@ describe('dogfood: temporal storage is one shape end-to-end (#3912/#3994/#4033)' (await stack.apiAs(token, 'POST', '/data/showcase_field_zoo', { name: `${P}_time_early`, f_time: '08:00:00', + f_master_detail: masterId, })).status, ).toBe(201); // `Field.date` fixtures on the same object — field-zoo carries all three - // temporal types and requires only `name`, so the fixture needs no lookup - // targets and cannot be perturbed by another suite's seed data. + // temporal types, so one object covers them and the rows cannot be + // perturbed by another suite's seed data. Its required master is the one + // seeded above; nothing else here needs a lookup target. for (const [key, written] of DATE_SHAPES) { const res = await stack.apiAs(token, 'POST', '/data/showcase_field_zoo', { name: `${P}_date_${key}`, f_date: written, + f_master_detail: masterId, }); expect(res.status, `write f_date ${key}`).toBe(201); } @@ -198,6 +238,9 @@ describe('dogfood: temporal storage is one shape end-to-end (#3912/#3994/#4033)' const res = await stack.apiAs(token, 'POST', '/data/showcase_field_zoo', { name: `${P}_time_epoch_rejected`, f_time: Date.UTC(2026, 0, 15, 14, 30, 0, 500), + // A valid master, so the 400 asserted below is about `f_time` and not + // about the required master_detail. + f_master_detail: masterId, }); expect(res.status).toBe(400); // Asserted via the standard envelope code + the offending field, rather