Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/object-seed-authored-owd.md
Original file line numberDiff line numberDiff line change
@@ -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.
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand DownExpand Up@@ -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');
Expand All@@ -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(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,6 +218,9 @@ const validEndpoint = (over: Record<string, unknown> = {}) => ({
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' } },
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -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' },
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,9 @@ const storedBody = (rows: Map<string, Row>, 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' } },
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -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' } },
};

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,9 @@ const storedBody = (rows: Map<string, Row>, 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' } },
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,13 +325,20 @@ 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',
name: 'leave_request',
item: {
name: 'leave_request',
label: 'Leave Request',
sharingModel: 'private',
fields: { owner: { type: 'text', label: 'Owner' } },
},
});
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -121,6 +121,11 @@ const OVERLAYLESS_PROBES: Record<string, Record<string, unknown>> = {
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'] },
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -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.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -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')!;
Expand Down
9 changes: 9 additions & 0 deletions packages/spec/src/kernel/metadata-create-seeds.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,6 +102,15 @@ const BUILTIN_METADATA_CREATE_SEEDS: Partial<Record<MetadataType, unknown>> = {
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',
Expand Down
Loading