From 9b702a62aaa74900216fb742fd6a42d01d3168e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 04:56:26 +0000 Subject: [PATCH 1/3] feat(spec): METADATA_CREATE_SEEDS.object states its OWD explicitly (sharingModel: 'private') MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runtime already resolves an absent sharingModel on a custom object to 'private' (fail-closed, ADR-0090 D1 — effectiveSharingModel in plugin-sharing). Seeding the operative default makes the platform's own minimal create body an authored decision, so security-owd-unset can move onto the runtime publish surface (#7891) without refusing the seed. Part of the #7891 programme; blocker A (#8308). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Euoy6wyfzgiWtgCg4s6JK2 --- packages/spec/src/kernel/metadata-create-seeds.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/spec/src/kernel/metadata-create-seeds.ts b/packages/spec/src/kernel/metadata-create-seeds.ts index f0135d70b0..cfc893af9d 100644 --- a/packages/spec/src/kernel/metadata-create-seeds.ts +++ b/packages/spec/src/kernel/metadata-create-seeds.ts @@ -102,6 +102,15 @@ const BUILTIN_METADATA_CREATE_SEEDS: Partial> = { label: 'New Object', pluralLabel: 'New Objects', fields: {}, + // [#8308 / ADR-0090 D1] The OWD baseline is an AUTHORED decision, never an + // accident. The runtime already resolves an absent `sharingModel` on a + // custom object to 'private' (fail-closed — `effectiveSharingModel`, + // packages/plugins/plugin-sharing/src/sharing-service.ts), so seeding + // 'private' changes no tenant's effective sharing; it makes the operative + // default explicit in the platform's own minimal create body, which is + // what lets `security-owd-unset` enforce at the runtime publish door + // (#7891 programme) without refusing the platform's own seed. + sharingModel: 'private', }, agent: { name: 'new_agent', From 384f175e95f4ed5adb7e40809d8767504c61588d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 05:41:45 +0000 Subject: [PATCH 2/3] test(metadata-protocol): author the OWD on every active-state object fixture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-measured today through the publish gate's call site: 56 active object writes across 10 test files, every one of which security-owd-unset would refuse once #8310 declares 'object' in runtimeTypes (up from PR #7886's 26/8 — the suite grew). Each fixture now authors sharingModel: 'private', so the suite passes both before and after the #8310 flip. Part of #8308. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Euoy6wyfzgiWtgCg4s6JK2 --- .../src/protocol-publish-drafts-endpoint-gate.test.ts | 3 +++ .../src/protocol-publish-drafts-org-scope.test.ts | 3 +++ .../src/protocol.audit-field-governance.test.ts | 3 +++ .../src/protocol.injected-system-columns.test.ts | 3 +++ .../src/protocol.org-scoped-write-refused.test.ts | 5 +++++ .../src/protocol.read-decorations.test.ts | 3 +++ .../src/protocol.runtime-authoring-gate.test.ts | 7 +++++++ .../src/protocol.save-receipt-wording.test.ts | 5 +++++ .../sys-metadata-repository.package-writability.test.ts | 5 ++++- .../src/view-container-runtime-expansion.test.ts | 5 ++++- 10 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/metadata-protocol/src/protocol-publish-drafts-endpoint-gate.test.ts b/packages/metadata-protocol/src/protocol-publish-drafts-endpoint-gate.test.ts index 21f609af5d..97d856d55d 100644 --- a/packages/metadata-protocol/src/protocol-publish-drafts-endpoint-gate.test.ts +++ b/packages/metadata-protocol/src/protocol-publish-drafts-endpoint-gate.test.ts @@ -218,6 +218,9 @@ const validEndpoint = (over: Record = {}) => ({ const objectBody = (name: string) => ({ name, label: 'Thing', + // [#8308] Authored OWD: the publish gate refuses an OWD-less custom object + // (`security-owd-unset`) once #8310 declares `object` in `runtimeTypes`. + sharingModel: 'private', fields: { title: { type: 'text', label: 'Title' } }, }); diff --git a/packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts b/packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts index b6a6ef2f60..7d34f46e00 100644 --- a/packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts +++ b/packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts @@ -183,6 +183,9 @@ function makeStubEngine() { const objectBody = (name: string) => ({ name, label: 'Project Task', + // [#8308] Authored OWD: the publish gate refuses an OWD-less custom object + // (`security-owd-unset`) once #8310 declares `object` in `runtimeTypes`. + sharingModel: 'private', fields: { title: { type: 'text', label: 'Title' }, done: { type: 'boolean', label: 'Done' }, diff --git a/packages/metadata-protocol/src/protocol.audit-field-governance.test.ts b/packages/metadata-protocol/src/protocol.audit-field-governance.test.ts index 8f27658548..35dfda6666 100644 --- a/packages/metadata-protocol/src/protocol.audit-field-governance.test.ts +++ b/packages/metadata-protocol/src/protocol.audit-field-governance.test.ts @@ -133,6 +133,9 @@ function makeStubEngine() { const artifactObject = (name: string) => ({ name, label: 'Invoice', + // [#8308] Authored OWD: the publish gate refuses an OWD-less custom object + // (`security-owd-unset`) once #8310 declares `object` in `runtimeTypes`. + sharingModel: 'private', fields: { amount: { type: 'currency', label: 'Amount' }, created_at: { diff --git a/packages/metadata-protocol/src/protocol.injected-system-columns.test.ts b/packages/metadata-protocol/src/protocol.injected-system-columns.test.ts index 39b569f20d..0247ddfbef 100644 --- a/packages/metadata-protocol/src/protocol.injected-system-columns.test.ts +++ b/packages/metadata-protocol/src/protocol.injected-system-columns.test.ts @@ -136,6 +136,9 @@ const storedBody = (rows: Map, name: string) => const authored = (name: string) => ({ name, label: 'Invoice', + // [#8308] Authored OWD: the publish gate refuses an OWD-less custom object + // (`security-owd-unset`) once #8310 declares `object` in `runtimeTypes`. + sharingModel: 'private', fields: { amount: { type: 'currency', label: 'Amount' } }, }); diff --git a/packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts b/packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts index 26afe865dd..a7410e325b 100644 --- a/packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts +++ b/packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts @@ -240,6 +240,11 @@ async function seedLegacyOrgDraft( const OBJECT = { name: 'org_widget', label: 'Org Widget', + // [#8308] Authored OWD. This file pins the NOT_OVERRIDABLE org-scope + // refusal, and the authoring gate runs BEFORE it — an OWD-less body would + // swap the observed refusal for `security-owd-unset` 422 once #8310 + // declares `object` in `runtimeTypes`. + sharingModel: 'private', fields: { title: { type: 'text', label: 'Title' } }, }; diff --git a/packages/metadata-protocol/src/protocol.read-decorations.test.ts b/packages/metadata-protocol/src/protocol.read-decorations.test.ts index cf6c4fd614..3a3c97d6c9 100644 --- a/packages/metadata-protocol/src/protocol.read-decorations.test.ts +++ b/packages/metadata-protocol/src/protocol.read-decorations.test.ts @@ -118,6 +118,9 @@ const storedBody = (rows: Map, name: string) => { const objectBody = (name: string) => ({ name, label: 'Invoice', + // [#8308] Authored OWD: the publish gate refuses an OWD-less custom object + // (`security-owd-unset`) once #8310 declares `object` in `runtimeTypes`. + sharingModel: 'private', fields: { amount: { type: 'currency', label: 'Amount' } }, }); diff --git a/packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts b/packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts index e052819605..73d08dd52d 100644 --- a/packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts +++ b/packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts @@ -325,6 +325,12 @@ describe('runtime authoring gate on saveMetaItem (#4463)', () => { // `object` writes are deliberately outside P1 — see the registry's // RUNTIME_OBJECT_WRITES_P2 reason. A type nobody declared must pass // through untouched rather than be silently half-checked. + // + // [#8308] The body carries an authored `sharingModel` so this write is + // ALSO clean on the gated side: it succeeds today because nothing runs, + // and keeps succeeding when #8310 declares `object` in `runtimeTypes` + // (at which point this case's "no rule declares" premise ends — #8310 + // owns re-pinning what this test asserts). const { protocol } = makeProtocol(); const result = await protocol.saveMetaItem({ type: 'object', @@ -332,6 +338,7 @@ describe('runtime authoring gate on saveMetaItem (#4463)', () => { item: { name: 'leave_request', label: 'Leave Request', + sharingModel: 'private', fields: { owner: { type: 'text', label: 'Owner' } }, }, }); diff --git a/packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts b/packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts index f4258276b5..fbbeadfe6b 100644 --- a/packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts +++ b/packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts @@ -121,6 +121,11 @@ const OVERLAYLESS_PROBES: Record> = { object: { name: 'rc5_acct', label: 'Account', + // [#8308] Authored OWD: the publish gate refuses an OWD-less custom + // object (`security-owd-unset`) once #8310 declares `object` in + // `runtimeTypes` — and this probe pins the RECEIPT wording, not that + // refusal. + sharingModel: 'private', fields: { name: { type: 'text', label: 'Name' } }, }, hook: { name: 'rc5_acct', object: 'task', events: ['beforeUpdate'] }, diff --git a/packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts b/packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts index bacfbad5e6..5ae2db4ffa 100644 --- a/packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts +++ b/packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts @@ -155,7 +155,10 @@ function makeFakeEngine() { }; } -const objectBody = { name: 'showcase_task', label: 'Task', fields: { name: { type: 'text', label: 'Name' } } }; +// [#8308] `sharingModel` authored: the publish gate refuses an OWD-less custom +// object (`security-owd-unset`) once #8310 declares `object` in `runtimeTypes`, +// and this file pins the package-writability refusals, not that one. +const objectBody = { name: 'showcase_task', label: 'Task', sharingModel: 'private', fields: { name: { type: 'text', label: 'Name' } } }; /** * `put` with everything but the base fixed, so each case differs in ONE way. diff --git a/packages/metadata-protocol/src/view-container-runtime-expansion.test.ts b/packages/metadata-protocol/src/view-container-runtime-expansion.test.ts index 7a9e70e963..49316e3932 100644 --- a/packages/metadata-protocol/src/view-container-runtime-expansion.test.ts +++ b/packages/metadata-protocol/src/view-container-runtime-expansion.test.ts @@ -227,7 +227,10 @@ describe('#7736 a runtime-authored view container is served', () => { const { engine, rows } = makeStubEngine(); const protocol = new ObjectStackProtocolImplementation(engine); - const authored = { name: 'crm_invoice', label: 'Invoice', fields: { amount: { type: 'currency', label: 'Amount' } } }; + // [#8308] `sharingModel` authored: the publish gate refuses an + // OWD-less custom object once #8310 declares `object` in + // `runtimeTypes`; this case pins byte-identical storage, not that. + const authored = { name: 'crm_invoice', label: 'Invoice', sharingModel: 'private', fields: { amount: { type: 'currency', label: 'Amount' } } }; await protocol.saveMetaItem({ type: 'object', name: 'crm_invoice', item: authored }); const stored = Array.from(rows.values()).find((r) => r.name === 'crm_invoice')!; From 282fa3997ff486d50146a311d154b1cd1d2fda22 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 06:15:42 +0000 Subject: [PATCH 3/3] test(lint): pin the repaired object create seed as clean at the gate mirror; add changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runtime-surface measurement test now consumes the REAL seed from @objectstack/spec/kernel and asserts the gate's mirror adds nothing — so a seed regression re-opens the pin instead of passing silently. The pre-#8308 OWD-less body stays as the refusal's positive control. The registry entry (runtimeTypes) is untouched; the flip is #8310's. Part of #8308. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Euoy6wyfzgiWtgCg4s6JK2 --- .changeset/object-seed-authored-owd.md | 5 +++++ ...e-security-posture.runtime-surface.test.ts | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 .changeset/object-seed-authored-owd.md diff --git a/.changeset/object-seed-authored-owd.md b/.changeset/object-seed-authored-owd.md new file mode 100644 index 0000000000..498c733f92 --- /dev/null +++ b/.changeset/object-seed-authored-owd.md @@ -0,0 +1,5 @@ +--- +'@objectstack/spec': patch +--- + +`METADATA_CREATE_SEEDS.object` now authors its org-wide default explicitly (`sharingModel: 'private'` — the value the runtime already resolves an absent OWD to, ADR-0090 D1). A freshly created object from the Studio designer, CLI or API create flows carries the authored baseline instead of relying on the implicit fail-closed default; effective sharing is unchanged. This is blocker A of the #7891 strictness rollout: it lets `security-owd-unset` move onto the runtime publish surface without refusing the platform's own minimal create body. diff --git a/packages/lint/src/validate-security-posture.runtime-surface.test.ts b/packages/lint/src/validate-security-posture.runtime-surface.test.ts index eed84363cf..7bd8c48130 100644 --- a/packages/lint/src/validate-security-posture.runtime-surface.test.ts +++ b/packages/lint/src/validate-security-posture.runtime-surface.test.ts @@ -43,6 +43,8 @@ import { describe, it, expect } from 'vitest'; +import { getMetadataCreateSeed } from '@objectstack/spec/kernel'; + import { AUTHORING_RULES } from './authoring-rules.js'; import { runRuntimeAuthoringRules, runtimeAuthoringRulesFor, stackKeyForType } from './runtime-gate.js'; import { @@ -162,8 +164,9 @@ describe('validateSecurityPosture at the runtime publish surface (#7576)', () => }); it('an OWD-less object write WOULD be refused — the strictness this card escalates', () => { - // `METADATA_CREATE_SEEDS.object` is exactly this body: name, label, - // pluralLabel, fields — and no `sharingModel`. + // The body `METADATA_CREATE_SEEDS.object` carried BEFORE #8308: name, + // label, pluralLabel, fields — and no `sharingModel`. Kept literal as the + // refusal's positive control. const added = wouldGateAdd('objects', { name: 'new_object', label: 'New Object', fields: {} }); expect(added.map((f) => f.rule)).toEqual([SECURITY_OWD_UNSET]); expect(added[0].severity).toBe('error'); @@ -176,6 +179,18 @@ describe('validateSecurityPosture at the runtime publish surface (#7576)', () => ).toEqual([]); }); + it('[#8308] the REAL create seed is clean at this gate — blocker A repaired', () => { + // The platform's own minimal create body now AUTHORS its OWD + // (`sharingModel: 'private'` — the measured runtime default, ADR-0090 D1 / + // `effectiveSharingModel` in plugin-sharing), so the gate that #8310 will + // register for `object` refuses nothing on the platform's own create path. + // Consumed from the seed registry, not re-spelled, so a seed regression + // re-opens THIS pin rather than passing silently. + const seed = getMetadataCreateSeed('object') as AnyRec; + expect(seed.sharingModel).toBe('private'); + expect(wouldGateAdd('objects', seed)).toEqual([]); + }); + it('a permission-set write INVENTS a finding the whole-stack run does not', () => { // Whole stack: `shop_clerk` grants the detail, so nothing is ungranted. expect(