From 528ffbe8330c1a8658367d42f54af83f9b3ceef8 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Thu, 25 Jun 2026 00:14:57 +0800 Subject: [PATCH] =?UTF-8?q?test(dogfood):=20package-first=20authoring=20ga?= =?UTF-8?q?te=20=E2=80=94=20reject=20runtime=20creates=20into=20read-only?= =?UTF-8?q?=20packages=20(ADR-0070=20Phase=206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boots the real CRM stack and asserts protocol.saveMetaItem(runtime-only) into a loaded code package throws writable_package_required (D1/D2), with a writable-base control. Runs under the required Dogfood Regression Gate; reverting D1 turns it red. Co-Authored-By: Claude Opus 4.8 --- .../package-first-authoring.dogfood.test.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 packages/dogfood/test/package-first-authoring.dogfood.test.ts diff --git a/packages/dogfood/test/package-first-authoring.dogfood.test.ts b/packages/dogfood/test/package-first-authoring.dogfood.test.ts new file mode 100644 index 0000000000..55e7ed4130 --- /dev/null +++ b/packages/dogfood/test/package-first-authoring.dogfood.test.ts @@ -0,0 +1,59 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// GOLDEN REGRESSION — ADR-0070 package-first authoring, exercised end-to-end +// through the real booted stack (not a mocked unit). The kernel must REJECT a +// runtime-only create that targets a read-only code/installed package with +// `writable_package_required` (D1/D2) instead of silently coercing it to a +// package-less orphan (the pre-ADR #2252 behavior). This is the contract the +// Studio + AI surfaces rely on as the backstop. +// +// Reverting D1 (the writable_package_required throw in saveMetaItem) turns this +// red — that is the point of the gate. + +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import crmStack from '@objectstack/example-crm'; +import { bootStack, type VerifyStack } from '@objectstack/verify'; + +describe('dogfood: package-first authoring rejects runtime creates into read-only packages (ADR-0070 D1/D2)', () => { + let stack: VerifyStack; + + beforeAll(async () => { + stack = await bootStack(crmStack); + }); + afterAll(async () => { + await stack?.stop?.(); + }); + + it('saveMetaItem(runtime-only) into a loaded code package throws writable_package_required', async () => { + // The objectql engine records every booted code package in its manifest map; + // any one of them is a read-only authoring target (isWritablePackage=false). + const ql = await stack.kernel.getServiceAsync('objectql'); + const manifests = (ql?.manifests ?? ql?.engine?.manifests) as Map | undefined; + const codePkgId = manifests && typeof manifests.keys === 'function' ? [...manifests.keys()][0] : undefined; + expect(codePkgId, 'expected at least one loaded code package in the booted stack').toBeTruthy(); + + const protocol = await stack.kernel.getServiceAsync('protocol'); + await expect( + protocol.saveMetaItem({ + type: 'object', + name: 'dogfood_pkgfirst_probe', + item: { name: 'dogfood_pkgfirst_probe', label: 'Probe', fields: { name: { type: 'text', label: 'Name' } } }, + packageId: codePkgId, + mode: 'draft', + }), + ).rejects.toMatchObject({ code: 'writable_package_required' }); + }); + + it('the same create into a fresh writable base id is NOT rejected (control)', async () => { + const protocol = await stack.kernel.getServiceAsync('protocol'); + // A bare, unregistered project-base id is writable — the write must succeed. + const res = await protocol.saveMetaItem({ + type: 'object', + name: 'dogfood_pkgfirst_ok', + item: { name: 'dogfood_pkgfirst_ok', label: 'OK', fields: { name: { type: 'text', label: 'Name' } } }, + packageId: 'app.dogfood_probe_base', + mode: 'draft', + }); + expect(res?.success ?? true).toBeTruthy(); + }); +});