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
44 changes: 44 additions & 0 deletions .changeset/spec-compose-key-dispositions-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
'@objectstack/spec': minor
---

feat(spec): export `COMPOSE_KEY_DISPOSITIONS` and `STACK_DEFINITION_KEYS` — the artifact envelope's top-level key set and each key's composition rule, derivable from one source instead of hand-copied per consumer (#14877)

`minor`, additive: two new named exports and two new exported types on the
root entry; nothing renamed, narrowed or removed. Every existing import keeps
compiling and every behaviour of `composeStacks` is unchanged — the table it
reads is the same object, now frozen and public.

- `COMPOSE_KEY_DISPOSITIONS` — a frozen, read-only record from every top-level
key `ObjectStackDefinitionSchema` declares (`manifest`, `packages`,
`requires`, `objects`, … `onEnable`) to its composition rule: `'concat'`
(an array collection, concatenated in stack order), `'single'` (identical
declarations pass through, differing ones refuse naming the key),
`'manifest'` (picked by the `manifest` option), `'objects'` (the
`objectConflict` strategy) or `'functions'` (merged by handler name).
Literal-typed, so `(typeof COMPOSE_KEY_DISPOSITIONS)[K]` is K's disposition,
not the union.
- `STACK_DEFINITION_KEYS` — the top-level key set, derived from that table by
`Object.keys` (never a second literal), frozen.
- `StackDefinitionKey` and `ComposeDisposition` — the key union and the
disposition union, for a consumer that types its own seam against them.

Why: the collection half of this key set was already derivable downstream
(`PLURAL_TO_SINGULAR`, `METADATA_ALIASES`), but the non-collection keys —
`manifest`, `requires`, `packages`, and whatever comes next — had to be
hand-copied by every consumer that walks an artifact's top level, and that
copy drifted silently twice: objectstack-ai/cloud#897 (`roles` → `positions`
dropped every hosted `positions[]`) and objectstack-ai/cloud#1888 (`packages[]`
dropped by an artifact merge, recreating downstream the duplicate-ownership
state #14599 had repaired at the door). A seam that derives its key set from
`STACK_DEFINITION_KEYS` — and asks `COMPOSE_KEY_DISPOSITIONS[key] === 'concat'`
whether a key may be concatenated across artifacts — picks up the next key
(#14865's `grantedPermissions`) the day the schema declares it, with no edit of
its own.

Pinned (`compose-key-dispositions-export.pin.test.ts`): the exported key set
equals `ObjectStackDefinitionSchema`'s declared top-level key set in both
directions, the view is frozen, every value is a declared disposition, every
`'concat'` key is what `composeStacks` concatenates and every `'single'` key is
what it passes through or refuses, and the key list is `Object.keys` of the
table.
8 changes: 8 additions & 0 deletions content/docs/getting-started/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,14 @@ blocks first — make them identical, or declare `i18n` in only one of the
stacks. Composition never picks a locale set for you: each stack's
`translations` bundles are written against the locales that stack declares.

Every top-level key's composition rule is exported: `COMPOSE_KEY_DISPOSITIONS`
maps each key `ObjectStackDefinitionSchema` declares to its rule (`concat`,
`single`, `manifest`, `objects`, `functions`) and `STACK_DEFINITION_KEYS` is
the key set derived from it — a downstream seam that walks an artifact's top
level (a hosted publish, an artifact merge) derives the key set and each key's
disposition from those two exports instead of copying a list, so a key added
to the schema reaches it without an edit.

### Short Names Are Canonical

Each app declares a `namespace` in its manifest, but **the short object name is what you use everywhere** — in `engine.find()`, hooks, formulas, lookups, REST URLs, and physical tables. The namespace is internal metadata used only for package provenance and cross-package disambiguation.
Expand Down
4 changes: 4 additions & 0 deletions packages/spec/api-surface/root.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@
"Book (type)",
"BuiltinIdentityName (type)",
"BuiltinMembershipRole (type)",
"COMPOSE_KEY_DISPOSITIONS (const)",
"CONVERSIONS_BY_MAJOR (const)",
"CONVERSION_CONFLICT_CODE (const)",
"CONVERSION_NOTICE_CODE (const)",
"CapabilityClassification (interface)",
"CapabilityEdition (type)",
"CapabilityProviderStatus (type)",
"ComposeDisposition (type)",
"ComposeStacksOptions (type)",
"ComposeStacksOptionsParsed (type)",
"ComposeStacksOptionsSchema (const)",
Expand Down Expand Up @@ -124,6 +126,7 @@
"PredicateSchema (const)",
"RETIRED_DEFS_BY_MAJOR (const)",
"RETIRED_KEYS_BY_MAJOR (const)",
"STACK_DEFINITION_KEYS (const)",
"STACK_KEY_GUIDANCE (const)",
"STACK_RUNTIME_MEMBERS (const)",
"SemanticMigration (interface)",
Expand All @@ -138,6 +141,7 @@
"SpecSurfaceAddSchema (const)",
"SpecSurfaceRemove (type)",
"SpecSurfaceRemoveSchema (const)",
"StackDefinitionKey (type)",
"StateNodeConfig (type)",
"StoredConversionOptions (type)",
"SurfaceDiff (interface)",
Expand Down
4 changes: 4 additions & 0 deletions packages/spec/export-origins/root.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@
"Book": "src/system/book.zod.ts#Book (type)",
"BuiltinIdentityName": "src/identity/eval-user.zod.ts#BuiltinIdentityName (type)",
"BuiltinMembershipRole": "src/identity/membership-role.ts#BuiltinMembershipRole (type)",
"COMPOSE_KEY_DISPOSITIONS": "src/stack.zod.ts#COMPOSE_KEY_DISPOSITIONS (const)",
"CONVERSIONS_BY_MAJOR": "src/conversions/registry.ts#CONVERSIONS_BY_MAJOR (const)",
"CONVERSION_CONFLICT_CODE": "src/conversions/types.ts#CONVERSION_CONFLICT_CODE (const)",
"CONVERSION_NOTICE_CODE": "src/conversions/types.ts#CONVERSION_NOTICE_CODE (const)",
"CapabilityClassification": "src/kernel/platform-capabilities.ts#CapabilityClassification (interface)",
"CapabilityEdition": "src/kernel/platform-capabilities.ts#CapabilityEdition (type)",
"CapabilityProviderStatus": "src/kernel/platform-capabilities.ts#CapabilityProviderStatus (type)",
"ComposeDisposition": "src/stack.zod.ts#ComposeDisposition (type)",
"ComposeStacksOptions": "src/stack.zod.ts#ComposeStacksOptions (type)",
"ComposeStacksOptionsParsed": "src/stack.zod.ts#ComposeStacksOptionsParsed (type)",
"ComposeStacksOptionsSchema": "src/stack.zod.ts#ComposeStacksOptionsSchema (const)",
Expand Down Expand Up @@ -124,6 +126,7 @@
"PredicateSchema": "src/shared/expression.zod.ts#PredicateSchema (const)",
"RETIRED_DEFS_BY_MAJOR": "src/migrations/registry.ts#RETIRED_DEFS_BY_MAJOR (const)",
"RETIRED_KEYS_BY_MAJOR": "src/migrations/registry.ts#RETIRED_KEYS_BY_MAJOR (const)",
"STACK_DEFINITION_KEYS": "src/stack.zod.ts#STACK_DEFINITION_KEYS (const)",
"STACK_KEY_GUIDANCE": "src/data/authoring-key-lint.ts#STACK_KEY_GUIDANCE (const)",
"STACK_RUNTIME_MEMBERS": "src/data/authoring-key-lint.ts#STACK_RUNTIME_MEMBERS (const)",
"SemanticMigration": "src/migrations/types.ts#SemanticMigration (interface)",
Expand All @@ -138,6 +141,7 @@
"SpecSurfaceAddSchema": "src/migrations/spec-changes.ts#SpecSurfaceAddSchema (const)",
"SpecSurfaceRemove": "src/migrations/spec-changes.ts#SpecSurfaceRemove (type)",
"SpecSurfaceRemoveSchema": "src/migrations/spec-changes.ts#SpecSurfaceRemoveSchema (const)",
"StackDefinitionKey": "src/stack.zod.ts#StackDefinitionKey (type)",
"StateNodeConfig": "src/automation/state-machine.zod.ts#StateNodeConfig (type)",
"StoredConversionOptions": "src/conversions/stored.ts#StoredConversionOptions (type)",
"SurfaceDiff": "src/migrations/spec-changes.ts#SurfaceDiff (interface)",
Expand Down
Loading
Loading