Skip to content

Commit cf2c9b7

Browse files
os-zhuangclaude
andauthored
refactor(spec)!: remove the kernel metadata-loader envelope family — 11 names declared twice with different shapes (#4411) (#4458)
* refactor(spec)!: remove the kernel metadata-loader envelope family — 11 names declared twice with different shapes (#4411) `@objectstack/spec` exported eleven names TWICE, with a different shape each time, on two subpath entries — so which type a consumer got depended on nothing but the import path: import type { MetadataWatchEvent } from '@objectstack/spec/kernel'; // one shape import type { MetadataWatchEvent } from '@objectstack/spec/system'; // another `MetadataFormat`, `MetadataStats`, `MetadataLoadOptions`, `MetadataSaveOptions`, `MetadataExportOptions`, `MetadataImportOptions`, `MetadataLoadResult`, `MetadataSaveResult`, `MetadataWatchEvent`, `MetadataCollectionInfo` and `MetadataLoaderContract` are removed from `kernel/metadata-loader.zod`. The `system/metadata-persistence.zod` copies stay as the single source. Why the kernel side goes, and why this was worth removing rather than living with: - Zero consumers. Import-statement scans across this repo, `cloud` and `objectui` find every consumer on `./system` (or `./contracts`' own interface); only `kernel/metadata-loader.test.ts` ever parsed the kernel copies. ADR-0049 enforce-or-remove. - The naming intuition pointed the wrong way, which is what made this sharper than an ordinary duplicate. The kernel copies were the ones that LOOKED canonical — normalized enums, required fields, a `.describe()` per property — and they were the dead ones; the live copy is the loose superset its own consumer calls "legacy". Picking by name, or by which reads as more rigorous, picked the dead one, and because the shapes overlap heavily that choice compiled and failed later, at an edge value (`add` vs `added`) or on a field one copy made required. No tombstone and no ADR-0087 conversion, deliberately: these are runtime envelope types, not authorable metadata, so no authored source can carry them and there is nothing for `os migrate meta` to rewrite (the plugin-runtime / dev-plugin precedents). `MetadataManagerConfig` and `MetadataFallbackStrategy` are untouched — they were never duplicated (kernel owns them, system re-exports them), and that is the split that survives: manager wiring is kernel's, the loader/watch envelope is system's, nothing is declared twice. `MetadataManagerConfig.formats` now reads the `shared` format enum (same four members, leaf module, no cycle) rather than a fourth local copy. Also: - `contracts/metadata-service.ts` drops the "spec carries TWO types named MetadataWatchEvent" warning added in #4404 — it no longer does. - `expression-conformance.ledger.ts` drops the now-absent `kernel/metadata-loader.zod.ts:filter` CEL surface (the surviving system options never declared a `filter`, so no loader predicate was ever evaluated through it). - Baselines dropped deliberately: `json-schema.manifest.json` −11 entries, `authorable-surface.json` −65 lines (nothing can author these, so no `[RETIRED]` markers). `api-surface.json` regenerated: 22 exports leave `./kernel`. `references/kernel/metadata-persistence.mdx` removed by `gen:docs`. v17 release notes + upgrade checklist extended. No runtime behaviour changes — nothing read the removed copies. The system shapes are NOT tightened here; narrowing them would be a separate change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL * chore(spec): write the hand-edited baselines the way the generator does The two baselines this branch edits by hand — `json-schema.manifest.json` and `authorable-surface.json` — came out with `—` escaped as `—`, because the edit went through Python's `json.dump`, whose `ensure_ascii` defaults to true. `build-schemas.ts` writes them with `JSON.stringify`, which emits the character literally. No gate catches this: the manifest is only rewritten when the SCHEMA KEY SET changes, so the escape would have sat in the file until the next PR that adds a schema, where the generator would silently rewrite it back and hand that author an unrelated one-line diff to explain. Re-serialised with `ensure_ascii=False`. Both files now differ from main by exactly the intended removals and nothing else: 11 manifest keys, 65 authorable-surface lines, zero incidental churn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent dadb43f commit cf2c9b7

17 files changed

Lines changed: 171 additions & 1130 deletions
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
refactor(spec)!: remove the `kernel` metadata-loader envelope family — eleven names that each existed twice, with different shapes, on two subpath entries (#4411)
6+
7+
`MetadataFormat`, `MetadataStats`, `MetadataLoadOptions`, `MetadataSaveOptions`,
8+
`MetadataExportOptions`, `MetadataImportOptions`, `MetadataLoadResult`,
9+
`MetadataSaveResult`, `MetadataWatchEvent`, `MetadataCollectionInfo` and
10+
`MetadataLoaderContract` (plus each one's `…Schema`) are removed from
11+
`@objectstack/spec/kernel` (`kernel/metadata-loader.zod`). Every one of those
12+
names *also* existed, with a **different shape**, in
13+
`@objectstack/spec/system` (`system/metadata-persistence.zod`).
14+
15+
Which type you got depended on nothing but your import path:
16+
17+
```ts
18+
import type { MetadataWatchEvent } from '@objectstack/spec/kernel'; // one shape
19+
import type { MetadataWatchEvent } from '@objectstack/spec/system'; // another
20+
```
21+
22+
- **The `kernel` copies had zero consumers.** Import-statement scans across this
23+
repo, `cloud` and `objectui` found every consumer importing from
24+
`./system` (or, for the export/import options, `./contracts`' own interface).
25+
Nothing but `kernel/metadata-loader.test.ts` ever parsed the `kernel` copies.
26+
- **The naming intuition pointed the wrong way**, which is what made this worse
27+
than an ordinary duplicate. The `kernel` copies were the ones that *looked*
28+
canonical — normalized enums, required fields, a `.describe()` on every
29+
property — and they were the dead ones. The live copy is the loose superset,
30+
and `metadata-manager.ts` calls it "legacy" in its own comments. An
31+
auto-import or a model completion picking by name, or by which one reads as
32+
more rigorous, picked the dead one; because the shapes overlap heavily, that
33+
choice compiled and only failed later, at an edge value (`add` vs `added`) or
34+
on a field one copy made required.
35+
- **No load path parsed them.** These are runtime envelope types, not authorable
36+
metadata — no authored source can carry them. So there is deliberately **no**
37+
`retiredKey()` tombstone and **no** ADR-0087 conversion: a prescription nobody
38+
can receive is noise, and there is nothing for `os migrate meta` to rewrite
39+
(the `plugin-runtime.zod.ts` / dev-plugin precedents, #3950, #4149).
40+
41+
**FROM → TO — change the import path, keep the name:**
42+
43+
```diff
44+
-import type { MetadataWatchEvent, MetadataStats } from '@objectstack/spec/kernel';
45+
+import type { MetadataWatchEvent, MetadataStats } from '@objectstack/spec/system';
46+
```
47+
48+
The surviving `system` copy is the **looser** of the two, so a *reader* of these
49+
types may need narrowing it did not need before; a *producer* needs nothing. The
50+
differences that actually bite:
51+
52+
| Type | `kernel` (removed) | `system` (keep) |
53+
| --- | --- | --- |
54+
| `MetadataWatchEvent.type` | `'added' \| 'changed' \| 'deleted'` | also `'add' \| 'change' \| 'unlink'` — the raw watcher values the runtime really emits |
55+
| `MetadataWatchEvent` | `metadataType` / `name` / `timestamp` required | all three optional; adds `stats` |
56+
| `MetadataStats` | `size` / `modifiedAt` / `etag` / `format` required | all optional; adds `mtime`, `hash` |
57+
| `MetadataFormat` | `json \| yaml \| typescript \| javascript` | also the `yml` / `ts` / `js` aliases |
58+
| `MetadataSaveResult.path` | required | optional; adds `stats` |
59+
| `MetadataImportOptions` | `conflictResolution` / `dryRun` / `continueOnError` / `transform` | `source` / `strategy` / `validate` |
60+
| `MetadataCollectionInfo` | `formats: MetadataFormat[]` | `namespaces: string[]` |
61+
62+
No runtime behaviour changes: nothing read the removed copies. The `system`
63+
shapes are **not** tightened here — they describe what `MetadataManager`
64+
actually emits, and narrowing them would be a separate behaviour change.
65+
66+
`MetadataManagerConfig` and `MetadataFallbackStrategy` are **unaffected**. They
67+
were never duplicated — `kernel` owns them and `system` re-exports them — and
68+
that is the split that survives: manager *wiring* is kernel's, the loader/watch
69+
*envelope* is system's, and nothing is declared twice.
70+
71+
The retirement kit: baselines dropped deliberately
72+
(`json-schema.manifest.json` minus the 11 `kernel/Metadata*` entries;
73+
`authorable-surface.json` minus the 65 matching lines — nothing can author
74+
these, so no `[RETIRED]` markers); `api-surface.json` regenerated (22 exports
75+
leave `./kernel`); `references/kernel/metadata-persistence.mdx` removed by
76+
`gen:docs`; v17 release notes' dead-clusters table and upgrade checklist
77+
extended. No liveness-ledger entries existed (the ledger tracks authorable
78+
metadata types; these were never one).

content/docs/references/kernel/meta.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"execution-context",
2828
"metadata-customization",
2929
"metadata-loader",
30-
"metadata-persistence",
3130
"metadata-plugin",
3231
"metadata-protection",
3332
"service-registry",

content/docs/references/kernel/metadata-loader.mdx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ description: Metadata Loader protocol schemas
55

66
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
77

8-
# Metadata Loader Protocol
8+
# Metadata Manager Configuration
99

10-
Defines the standard interface for loading and saving metadata in ObjectStack.
10+
How the runtime `MetadataManager` is wired: which datasource backs `sys_metadata`, what to fall back to when that datasource is unreachable, cache / watch / validation settings, and the persistence write gates.
1111

12-
This protocol enables consistent metadata operations across different storage backends
13-
14-
(filesystem, HTTP, S3, databases) and serialization formats (JSON, YAML, TypeScript).
12+
The loader and watch *envelope* types (`MetadataFormat`, `MetadataStats`, `MetadataLoadOptions`, `MetadataWatchEvent`, `MetadataLoaderContract`, …) are NOT here — they live in `@objectstack/spec/system` (`system/metadata-persistence.zod`), which is their single source.
1513

1614
<Callout type="info">
1715
**Source:** `packages/spec/src/kernel/metadata-loader.zod.ts`
@@ -50,7 +48,7 @@ const result = MetadataFallbackStrategy.parse(data);
5048
| **tableName** | `string` || Database table name for metadata storage |
5149
| **fallback** | `Enum<'filesystem' \| 'memory' \| 'none'>` || Fallback strategy when datasource is unavailable |
5250
| **rootDir** | `string` | optional | Root directory path |
53-
| **formats** | `Enum<'json' \| 'yaml' \| 'typescript' \| 'javascript'>[]` || Enabled formats |
51+
| **formats** | `Enum<'yaml' \| 'json' \| 'typescript' \| 'javascript'>[]` || Enabled formats |
5452
| **cache** | `{ enabled: boolean; ttl: integer; maxSize?: integer; databaseLoader?: object }` | optional | Cache settings |
5553
| **watch** | `boolean` || Enable file watching |
5654
| **watchOptions** | `{ ignored?: string[]; persistent: boolean; ignoreInitial: boolean }` | optional | File watcher options |

content/docs/references/kernel/metadata-persistence.mdx

Lines changed: 0 additions & 196 deletions
This file was deleted.

content/docs/references/kernel/metadata-plugin.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ cohesive plugin that "takes over" the entire platform's metadata management:
5757

5858
## References
5959

60-
- [kernel/metadata-loader.zod.ts](/docs/references/kernel/metadata-loader)Storage backend protocol
60+
- [kernel/metadata-loader.zod.ts](/docs/references/kernel/metadata-loader)MetadataManager wiring (datasource, cache, write gates)
6161

6262
- [kernel/metadata-customization.zod.ts](/docs/references/kernel/metadata-customization) — Overlay/merge protocol
6363

64-
- [system/metadata-persistence.zod.ts](/docs/references/system/metadata-persistence) — Database record format
64+
- [system/metadata-persistence.zod.ts](/docs/references/system/metadata-persistence) — Database record format + loader/watch envelope types
6565

6666
- contracts/metadata-service.ts — Service interface
6767

content/docs/references/system/metadata-loader.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const result = MetadataFallbackStrategy.parse(data);
3838
| **tableName** | `string` || Database table name for metadata storage |
3939
| **fallback** | `Enum<'filesystem' \| 'memory' \| 'none'>` || Fallback strategy when datasource is unavailable |
4040
| **rootDir** | `string` | optional | Root directory path |
41-
| **formats** | `Enum<'json' \| 'yaml' \| 'typescript' \| 'javascript'>[]` || Enabled formats |
41+
| **formats** | `Enum<'yaml' \| 'json' \| 'typescript' \| 'javascript'>[]` || Enabled formats |
4242
| **cache** | `{ enabled: boolean; ttl: integer; maxSize?: integer; databaseLoader?: object }` | optional | Cache settings |
4343
| **watch** | `boolean` || Enable file watching |
4444
| **watchOptions** | `{ ignored?: string[]; persistent: boolean; ignoreInitial: boolean }` | optional | File watcher options |

content/docs/releases/v17.mdx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,7 @@ import or the authored key.
10171017
| `ChartInteraction.zoom` / `.clickAction` | never implemented (#3752) |
10181018
| The `workflow` service slot — `CoreServiceName 'workflow'`, `IWorkflowService`, `WorkflowProtocol`, the `Get/WorkflowState/Config/Transition` schema cluster, discovery `routes.workflow` / `services.workflow` / `features.workflow`, the `RestApiRouteCategory 'workflow'` member and the stray `graphql` provider entry | declared end to end and implemented nowhere: nothing ever registered or resolved the slot (ADR-0115 Evidence 5), no method of `WorkflowProtocol` was ever implemented, no host ever mounted `/api/v1/workflow`. State machines are `state_machine` validation rules; approvals are flow nodes (ADR-0019); record-triggered automation is hooks + `record_change` flows (#4451) |
10191019
| `datasource.readReplicas` | replica connections nothing ever opened — no driver reads the key and no query path splits reads from writes, so every statement went to the primary. #4410 had just taught the schema to validate each entry against the declared driver's contract, which made a dead slot look rigorously alive (#4468) |
1020+
| The `kernel` metadata-loader envelope family — `MetadataFormat`, `MetadataStats`, `MetadataLoadOptions`, `MetadataSaveOptions`, `MetadataExportOptions`, `MetadataImportOptions`, `MetadataLoadResult`, `MetadataSaveResult`, `MetadataWatchEvent`, `MetadataCollectionInfo`, `MetadataLoaderContract` (`@objectstack/spec/kernel`) | eleven names that each existed **twice**, with a different shape, on `./kernel` and `./system` — so which type you got depended on your import path. Every consumer imported the `./system` copy; the `./kernel` copies had zero consumers. Import them from `@objectstack/spec/system` (#4411, ADR-0049). `MetadataManagerConfig` / `MetadataFallbackStrategy` are unaffected and still ship from both entries |
10201021

10211022
The Console side follows: `@object-ui/types` drops its
10221023
`ObjectStack`/`ObjectOS`/`ObjectQL`/`ObjectUI` Capabilities re-exports, which
@@ -2103,7 +2104,16 @@ covers are folded into the list below rather than left to the changelog.)
21032104
`maplibre-gl` 5→6 / `chalk` 5→6 major bumps.
21042105
- **Type importers:** replace `ObjectStackProtocol` / `ObjectStackProtocolSchema`
21052106
with the narrowest per-domain slices; drop GraphQL types and any of the removed
2106-
dead spec clusters.
2107+
dead spec clusters. If you imported `MetadataFormat`, `MetadataStats`,
2108+
`MetadataLoadOptions`, `MetadataSaveOptions`, `MetadataExportOptions`,
2109+
`MetadataImportOptions`, `MetadataLoadResult`, `MetadataSaveResult`,
2110+
`MetadataWatchEvent`, `MetadataCollectionInfo` or `MetadataLoaderContract`
2111+
from `@objectstack/spec/kernel`, change the path to `@objectstack/spec/system`
2112+
— same names, and that copy is the one the runtime has always emitted. It is
2113+
the *looser* of the two, so a reader may need new narrowing: notably
2114+
`MetadataWatchEvent.type` also carries the raw watcher values
2115+
`add`/`change`/`unlink`, and `metadataType`/`name`/`timestamp` are optional
2116+
there. Nothing to migrate at runtime — the values were always these.
21072117
- **Multi-org:** the `group` posture requires the enterprise runtime — deployments
21082118
relying on it self-activating must install `@objectstack/organizations` or move
21092119
to `isolated`.

packages/qa/dogfood/test/expression-conformance.ledger.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,12 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
155155
covers: [
156156
'automation/flow.zod.ts:condition',
157157
'automation/sync.zod.ts:condition',
158-
'kernel/metadata-loader.zod.ts:filter',
158+
// `kernel/metadata-loader.zod.ts:filter` (on MetadataLoadOptions and
159+
// MetadataExportOptions) was removed with the rest of that file's
160+
// zero-consumer duplicate envelope family in #4411. The surviving
161+
// `system/metadata-persistence.zod` copies of those options never
162+
// declared a `filter` — so no loader predicate was ever evaluated
163+
// through this surface, and there is nothing to re-point at.
159164
],
160165
},
161166
{

0 commit comments

Comments
 (0)