Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cbb0c27
feat(spec): the override declaration, the .custom pairing helpers, th…
claude Oct 31, 2026
67180a5
chore(spec): liveness rows for overrides, the export pin and the chan…
claude Oct 31, 2026
8e1b798
Merge origin/main into claude/issue-28404-override-declaration
claude Oct 31, 2026
5c2f55d
chore(spec): regenerate the report liveness count after the merge
claude Oct 31, 2026
2b51218
fix(spec): refuse a .custom overrides.package with a published patter…
claude Oct 31, 2026
104d9e8
chore(spec): regenerate artefacts for the override declaration; type …
claude Oct 31, 2026
e5e18c9
Merge origin/main into claude/issue-28404-override-declaration
claude Oct 31, 2026
395b5e1
docs(spec): count the override-declaration module in llms.txt
claude Oct 31, 2026
e8d402b
test(spec): record the override declaration in the form and flow-slot…
claude Oct 31, 2026
ecd9e41
Merge origin/main into claude/issue-28404-override-declaration
claude Oct 31, 2026
0f9936f
chore(spec): regenerate artefacts on the merged tree
claude Oct 31, 2026
020487b
Merge origin/main into claude/issue-28404-override-declaration
claude Oct 31, 2026
e0253ce
chore(spec): regenerate artefacts on the merged tree (app landing)
claude Oct 31, 2026
e55a4e7
test(metadata-protocol): count `overrides` in the served /meta/types …
claude Oct 31, 2026
5d79f48
feat(spec): record the maintainer's rulings in the override classific…
claude Oct 31, 2026
862035a
Merge origin/main into claude/issue-28404-override-declaration
claude Oct 31, 2026
d548ee9
Merge origin/main into claude/issue-28404-override-declaration
claude Oct 31, 2026
743ac88
Merge origin/main into claude/issue-28404-override-declaration
claude Oct 31, 2026
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
15 changes: 15 additions & 0 deletions .changeset/28404-override-declaration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@objectstack/spec': minor
---

feat(spec): one override declaration for code and Studio — `overrides: { package, basedOn }`, the `.custom` pairing helpers, the closed overridable-type list, the `__c` reservation and the base-field display whitelist (ADR-0146)

Clause-②: yes (widening)
Metadata-protocol: added

A customer's change to an installed package lives in its paired `<base>.custom` package (ADR-0146). This release declares the shared definitions every later door reads, so a developer's `hotcrm.custom` in a repository and an administrator's Studio customization write the same thing:

- **The override declaration.** `view`, `dashboard`, `report`, `translation`, `email_template`, `page`, `app`, `dataset`, `doc`, `book`, `mapping`, `flow` and `action` items accept an optional `overrides: { package, basedOn }`: this item replaces the base package's same-named item whole. `package` names the base (never a `.custom` id); `basedOn` is recorded and never judged. The key survives `defineStack()`, the metadata save and the package export. No door acts on it yet: the write gate and the effective read are separate changes.
- **The pair's identity** (`@objectstack/spec/kernel`): `CUSTOMIZATION_PACKAGE_SUFFIX` (`.custom`), `customizationPackageId`, `customizationBasePackageId`, `isCustomizationPackage`, `isCustomizationPairOf`, `customizationNamespace` (`<base_ns>_custom`; a base namespace longer than 13 characters gets none, with the named reason `base_namespace_too_long`, never a truncation) and `customizationDependencyRange` (`^<major>` of the base version). Each derivation answers `{ ok: true, value }` or `{ ok: false, reason }`.
- **The overridable-type list.** `OVERRIDE_TYPE_CLASSIFICATION` classifies every metadata type as `overridable`, `extend_only` (`object`) or `excluded` (`permission`, `position`, `capability`, the code-only types and others), with `flow`'s and `action`'s preconditions carried as data. `OVERRIDABLE_METADATA_TYPES` and `isOverridableMetadataType` read it. `seed`, `datasource`, `tool` and `skill` are `excluded` pending a ruling.
- **Customer extension fields** (`@objectstack/spec/data`): `EXTENSION_FIELD_SUFFIX` (`__c`), `isExtensionFieldName`, and `BASE_FIELD_DISPLAY_WHITELIST`: the base-field properties an extension may change (`label`, `description`, `inlineHelpText`, `placeholder`, and an option's `label`). Enforcing both is a separate change.
12 changes: 12 additions & 0 deletions content/docs/references/api/automation-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ const result = AutomationApiErrorCode.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |

### Nested Shape: `CreateFlowRequest.variables[number]`

Expand Down Expand Up @@ -186,6 +187,13 @@ const result = AutomationApiErrorCode.parse(data);
| **reason** | `string` | ✅ | User-visible reason shown when the lock blocks an action. |
| **docsUrl** | `string` | optional | Optional URL the Studio banner links to for more context. |

### Nested Shape: `CreateFlowRequest.overrides`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **package** | `string` | ✅ | The base package id whose same-named item this item replaces whole (e.g. "com.acme.crm"). Only an item of the base's paired "`<base>`.custom" package declares an override. |
| **basedOn** | `string` | ✅ | Recorded, never judged: the base package's `manifest.version` this override was taken from (e.g. "3.2.0"), not a history version of the item. No door compares it with the installed base, no upgrade diff is produced from it, and nothing is refused on it; it tells a person re-basing the override where it started (ADR-0146 D2). |


---

Expand Down Expand Up @@ -249,6 +257,7 @@ const result = AutomationApiErrorCode.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |


---
Expand Down Expand Up @@ -789,6 +798,7 @@ const result = AutomationApiErrorCode.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |


---
Expand Down Expand Up @@ -1098,6 +1108,7 @@ const result = AutomationApiErrorCode.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |


---
Expand Down Expand Up @@ -1162,6 +1173,7 @@ const result = AutomationApiErrorCode.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |


---
Expand Down
1 change: 1 addition & 0 deletions content/docs/references/api/metadata.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ const result = AppDefinitionResponseSchema.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |


---
Expand Down
9 changes: 9 additions & 0 deletions content/docs/references/automation/flow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const result = FlowSchema.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |

### Nested Shape: `Flow.variables[number]`

Expand Down Expand Up @@ -107,6 +108,13 @@ const result = FlowSchema.parse(data);
| **reason** | `string` | ✅ | User-visible reason shown when the lock blocks an action. |
| **docsUrl** | `string` | optional | Optional URL the Studio banner links to for more context. |

### Nested Shape: `Flow.overrides`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **package** | `string` | ✅ | The base package id whose same-named item this item replaces whole (e.g. "com.acme.crm"). Only an item of the base's paired "`<base>`.custom" package declares an override. |
| **basedOn** | `string` | ✅ | Recorded, never judged: the base package's `manifest.version` this override was taken from (e.g. "3.2.0"), not a history version of the item. No door compares it with the installed base, no upgrade diff is produced from it, and nothing is refused on it; it tells a person re-basing the override where it started (ADR-0146 D2). |


---

Expand Down Expand Up @@ -264,6 +272,7 @@ const result = FlowSchema.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |


---
Expand Down
8 changes: 8 additions & 0 deletions content/docs/references/data/mapping.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const result = ImportFieldMappingSchema.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |

### Nested Shape: `Mapping.fieldMapping[number]`

Expand All @@ -77,6 +78,13 @@ const result = ImportFieldMappingSchema.parse(data);
| **recordsPath** | `string` | optional | Dot path, within the action's result, to the array of records (e.g. `body.results`); omitted ⇒ the result's `body` is the array |
| **watermark** | `{ field: string; param: string }` | optional | Timestamp-incremental pull; omitted ⇒ every pull reads the full set. Either way a pull makes ONE action call and reads ONE response: the connector's paging is not followed, so a paged endpoint yields its first page only — and an incremental pull over a newest-first paged endpoint moves its starting point past the pages it never read |

### Nested Shape: `Mapping.overrides`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **package** | `string` | ✅ | The base package id whose same-named item this item replaces whole (e.g. "com.acme.crm"). Only an item of the base's paired "`<base>`.custom" package declares an override. |
| **basedOn** | `string` | ✅ | Recorded, never judged: the base package's `manifest.version` this override was taken from (e.g. "3.2.0"), not a history version of the item. No door compares it with the installed base, no upgrade diff is produced from it, and nothing is refused on it; it tells a person re-basing the override where it started (ADR-0146 D2). |


---

Expand Down
1 change: 1 addition & 0 deletions content/docs/references/data/object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ const result = ApiMethod.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |

### Nested Shape: `Object.protection`

Expand Down
9 changes: 5 additions & 4 deletions content/docs/references/index.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Protocol reference — every schema by module
navTitle: Protocol Reference
description: Every schema published by @objectstack/spec — 1577 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1578 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All @@ -26,15 +26,15 @@ counts are sums of the rows they head. Regenerate with
| [Data Protocol](/docs/references/data) | 31 | 174 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
| [Integration Protocol](/docs/references/integration) | 1 | 23 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
| [Kernel Protocol](/docs/references/kernel) | 29 | 151 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
| [Kernel Protocol](/docs/references/kernel) | 30 | 152 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
| [Marketplace Protocol](/docs/references/marketplace) | 4 | 30 | The package & marketplace format — package identity and versions, listing, publish, review, search, install, template manifests. |
| [QA Protocol](/docs/references/qa) | 1 | 8 | Declarative test suites — scenarios, steps, actions and assertions. |
| [Security Protocol](/docs/references/security) | 6 | 37 | Permission sets, row-level security, sharing rules, tenancy posture. |
| [Shared Protocol](/docs/references/shared) | 10 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 37 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 175 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **204** | **1577** | 14 protocol modules |
| **Total** | **205** | **1578** | 14 protocol modules |

---

Expand Down Expand Up @@ -204,7 +204,7 @@ The single connector protocol (ADR-0097) — catalog descriptors and provider-bo

## Kernel Protocol

**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **29 pages, 151 schemas**
**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **30 pages, 152 schemas**

Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry.

Expand All @@ -225,6 +225,7 @@ Plugin lifecycle and manifests, capabilities and security, metadata loading, ser
| [`metadata-loader.zod.ts`](/docs/references/kernel/metadata-loader) | `MetadataFallbackStrategy`, `MetadataManagerConfig` |
| [`metadata-plugin.zod.ts`](/docs/references/kernel/metadata-plugin) | `MetadataBulkResult`, `MetadataDependency`, `MetadataPluginConfig`, `MetadataPluginManifest`, `MetadataQuery`, `MetadataQueryResult`, `MetadataType`, `MetadataTypeRegistryEntry`, `MetadataValidationResult` |
| [`metadata-protection.zod.ts`](/docs/references/kernel/metadata-protection) | `MetadataLock`, `MetadataLockSource`, `MetadataProvenance` |
| [`override-declaration.zod.ts`](/docs/references/kernel/override-declaration) | `OverrideDeclaration` |
| [`package-artifact.zod.ts`](/docs/references/kernel/package-artifact) | `ArtifactChecksum`, `ArtifactFileEntry`, `ArtifactSignature`, `MetadataCategoryEnum`, `PackageArtifact` |
| [`package-registry.zod.ts`](/docs/references/kernel/package-registry) | `DisablePackageRequest`, `DisablePackageResponse`, `EnablePackageRequest`, `EnablePackageResponse`, `GetPackageRequest`, `GetPackageResponse`, `InstallPackageRequest`, `InstallPackageResponse`, `InstalledPackage`, `InstalledPackageSource`, `ListPackagesRequest`, `ListPackagesResponse`, `NamespaceConflictError`, `NamespaceRegistryEntry`, `PackageStatusEnum`, `UninstallPackageRequest`, `UninstallPackageResponse` |
| [`plugin.zod.ts`](/docs/references/kernel/plugin) | `Plugin` |
Expand Down
3 changes: 2 additions & 1 deletion content/docs/references/kernel/index.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Kernel Protocol — complete schema reference
navTitle: Kernel Protocol
description: "The ObjectStack Kernel Protocol in 29 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
description: "The ObjectStack Kernel Protocol in 30 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All @@ -24,6 +24,7 @@ This section contains all protocol schemas for the kernel layer of ObjectStack.
<Card href="/docs/references/kernel/metadata-loader" title="Metadata Loader" description="Source: packages/spec/src/kernel/metadata-loader.zod.ts" />
<Card href="/docs/references/kernel/metadata-plugin" title="Metadata Plugin" description="Source: packages/spec/src/kernel/metadata-plugin.zod.ts" />
<Card href="/docs/references/kernel/metadata-protection" title="Metadata Protection" description="Source: packages/spec/src/kernel/metadata-protection.zod.ts" />
<Card href="/docs/references/kernel/override-declaration" title="Override Declaration" description="Source: packages/spec/src/kernel/override-declaration.zod.ts" />
<Card href="/docs/references/kernel/package-artifact" title="Package Artifact" description="Source: packages/spec/src/kernel/package-artifact.zod.ts" />
<Card href="/docs/references/kernel/package-registry" title="Package Registry" description="Source: packages/spec/src/kernel/package-registry.zod.ts" />
<Card href="/docs/references/kernel/plugin" title="Plugin" description="Source: packages/spec/src/kernel/plugin.zod.ts" />
Expand Down
3 changes: 2 additions & 1 deletion content/docs/references/kernel/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"events-dlq",
"events-handlers",
"events-integrations",
"events-queue"
"events-queue",
"override-declaration"
]
}
1 change: 1 addition & 0 deletions content/docs/references/kernel/metadata-plugin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ const result = MetadataBulkResultSchema.parse(data);
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
| **overrides** | `{ package: string; basedOn: string }` | optional | Override declaration (ADR-0146 D2): `{ package, basedOn }` — this item of "`<base>`.custom" replaces the base package's same-named item whole. `basedOn` is recorded and never judged. |


---
Expand Down
Loading
Loading