@objectstack/metadata 17.0.0-rc.6 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,2528 @@
1
1
  # @objectstack/metadata
2
2
 
3
+ ## 17.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - ac6c0be: refactor(metadata)!: remove the `artifact-api` artifact source (#4246)
8
+
9
+ `MetadataPluginOptions.artifactSource` loses its `artifact-api` union member;
10
+ `{ mode: 'local-file', path }` is now the single artifact source. The
11
+ `_loadFromArtifactApi` loader, its `environmentId` pre-flight guard, and the
12
+ Bearer-token support in `_fetchJson` go with it.
13
+
14
+ **Why removal, not the doc fix this branch first carried.** #4246 found the
15
+ declaration and the implementation contradicting each other — the option's
16
+ comment called `artifact-api` "reserved for M3/M4" while the loader shipped and
17
+ all three bootstrap modes dispatched to it — and asked the owner to pick a
18
+ direction. Auditing both repos to answer that settled it:
19
+
20
+ - **Zero consumers anywhere.** No `mode: 'artifact-api'` call site exists in
21
+ this repo or in cloud. The two real "pull an artifact from the cloud" paths
22
+ both bypass it: the cloud runtime uses its own `ArtifactApiClient` (TTL
23
+ cache, singleflight, hostname resolution, runtime config injection — a
24
+ superset this option was never going to grow into), and package distribution
25
+ into a running OSS instance goes through `@objectstack/cloud-connection`
26
+ (`os package install`, ADR-0008).
27
+ - **Half its input contract had been dead since v5.0 with no one noticing.**
28
+ The URL builder decided "append the canonical path vs use as-is" by testing
29
+ for an `/api/v{n}/cloud/projects/` segment that the v5.0
30
+ `project → environment` rename deleted, so every already-resolved URL got
31
+ the path appended a second time and 404'd. A year of silence on a bug like
32
+ that is consumer-count evidence of its own.
33
+ - **Its one non-replaceable capability was declined.** A Bearer-authenticated
34
+ pull of a _private_ environment artifact is the single thing `local-file`
35
+ cannot do (`local-file` URLs fetch verbatim, unauthenticated). The owner
36
+ confirmed that sealed-private-artifact deployments are not a supported need
37
+ right now, which removed the last reason to keep the mode.
38
+
39
+ **Migration.** Public or commit-pinned artifacts load through the existing
40
+ `local-file` URL form, which every bootstrap mode already honors:
41
+
42
+ ```ts
43
+ artifactSource: {
44
+ mode: 'local-file',
45
+ path: 'https://cloud.example.com/pub/v1/environments/env_42/artifact?commit=cmt_1a2b',
46
+ }
47
+ ```
48
+
49
+ (`private` environments still serve exact-commit deep links through the same
50
+ `/pub` route; fully private pulls have no replacement — by decision, not
51
+ oversight.) For installing packages into a running runtime, use
52
+ `os package install` / `@objectstack/cloud-connection`.
53
+
54
+ **The removal is loud, not silent.** A still-configured `artifact-api` source
55
+ (reachable from JS or `any`-typed config now that the TS union is
56
+ single-member) throws at `start()` with the migration pointer above. This
57
+ guard exists because the dispatch's old fall-through would have treated
58
+ "unsupported source" as "no source" — under `eager` that silently scans the
59
+ filesystem instead of loading the artifact the caller named. Tests pin the
60
+ rejection in `artifact-only` and `eager`, and pin the migration target
61
+ (`local-file` fetching an http(s) URL and registering the envelope) so the
62
+ path the error message points at stays real.
63
+
64
+ Also replaces a test that passed for the wrong reason: "artifact-only
65
+ bootstrap rejects the not-yet-implemented artifact-api source" matched
66
+ `/artifact-api/` against the missing-`environmentId` guard's message — which
67
+ merely contained the string — proving nothing about implementation status.
68
+ The doc comment, `implementation-status.mdx`, `metadata-service.mdx`, and the
69
+ package ROADMAP now all describe the single `local-file` source, ending the
70
+ docs-audit loop #4246 was filed to stop.
71
+
72
+ - 9960cd2: fix(metadata): remove the second, stale-keyed producer of `idx_sys_metadata_overlay_active` (#6771)
73
+
74
+ **Breaking:** `addSysMetadataOverlayIndex` and its `AddSysMetadataOverlayIndexResult`
75
+ type are removed from `@objectstack/metadata/migrations`. Nothing needs to replace
76
+ them — see below.
77
+
78
+ One index name, `idx_sys_metadata_overlay_active`, had **two** producers with
79
+ **different** keys:
80
+
81
+ | producer | key |
82
+ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
83
+ | `metadata-protocol`'s `ensureMetadataOverlayIndexes` (runtime, ADR-0048) | `(type, name, organization_id, COALESCE(package_id, ''))` `WHERE state = 'active'` |
84
+ | this package's `addSysMetadataOverlayIndex` | `(type, name, organization_id, environment_id, scope)` |
85
+
86
+ The second key is the pre-ADR-0048 one. `environment_id` has been retired since
87
+ ADR-0005 (2026-05 revision) — `saveMetaItem` no longer writes it and overlay reads
88
+ never consult it, so it is NULL on every new row, and SQL UNIQUE treats NULLs as
89
+ DISTINCT. `scope` is not part of the current discriminator at all. Both producers
90
+ used `IF NOT EXISTS`, so whichever ran first claimed the name and the other
91
+ silently became a no-op — decided by boot order, not by any declaration.
92
+
93
+ Measured against real SQLite before removal:
94
+
95
+ - On a normal `DatabaseLoader` boot the stored DDL is
96
+ `` CREATE UNIQUE INDEX `idx_sys_metadata_overlay_active` on `sys_metadata` (`type`, `name`, `organization_id`, `package_id`) `` —
97
+ the **declared** index from `metadata-core`'s `sys-metadata.object.ts`, materialized
98
+ by `SqlDriver.syncDeclaredIndexes`, already holds the name with the current key.
99
+ `addSysMetadataOverlayIndex` therefore changed nothing, while still returning
100
+ `status: 'created'`.
101
+ - In the one window where it was _not_ a no-op — the table present but its declared
102
+ indexes not yet materialized, which the engine path hits by construction because
103
+ ObjectQL's startup owns the sync — it installed the **retired** key. Since
104
+ `syncDeclaredIndexes` skips by name, nothing ever repaired it afterwards, and
105
+ overlay uniqueness was left unenforced on every new row.
106
+
107
+ So the function could only ever do nothing or do harm. Overlay uniqueness keeps the
108
+ two producers that are correctly keyed and deliberate: the runtime partial,
109
+ NULL-safe index from `metadata-protocol`, and — for stacks assembled without it —
110
+ the coarser unrestricted UNIQUE that the declaration in `metadata-core` materializes,
111
+ exactly as that file documents.
112
+
113
+ Both call sites in `DatabaseLoader.ensureSchema()` are gone with it, and the empty
114
+ `catch` that surrounded the engine-path one now reports per the ADR-0120 D4 shape
115
+ (name what did not happen, point at the fix, never block the boot) instead of
116
+ swallowing driver-resolution failures.
117
+
118
+ **Migration:** if you called `addSysMetadataOverlayIndex(driver)` directly, delete
119
+ the call. Assemble `metadata-protocol` for the partial, active-scoped index, or rely
120
+ on the declared index that `syncSchema` already builds.
121
+
122
+ <!-- adr-0087: not-required (no-migration-prescription) what is removed is a TypeScript function export, not an authored metadata surface: no metadata key, no key spelling and no stored value moves, so `objectstack migrate meta` has nothing to rewrite and the ledger has no upgrader to reach. The index itself is unchanged in the only spelling that ever reached a database from a correct producer. Measured: the export had zero call sites outside its own package across objectstack, cloud and objectui. -->
123
+
124
+ ### Minor Changes
125
+
126
+ - ffb003c: **ADR-0110 — an action's identity is its `name`, and anything executable over a
127
+ governed surface must have a declaration.**
128
+
129
+ `POST /api/v1/actions/:object/:action` resolved the DECLARATION from the URL
130
+ segment as a `name` but dispatched the HANDLER using that same segment as a
131
+ registry key. For a target-bound action (`{ name: 'complete_task', target:
132
+ 'completeTask' }`) those are different strings, so the two documented callers
133
+ each worked on exactly the half the other broke: the documented curl resolved
134
+ the declaration then 404ed, while the Console's `target`-addressed call
135
+ dispatched fine and resolved no declaration — silently skipping the ADR-0066 D4
136
+ capability gate and the ADR-0104 param contract (#3935).
137
+
138
+ - **D1/D2** — identity is always the declarative `name`; the handler key is
139
+ derived from the resolved declaration through a rotation now shared with the
140
+ MCP `run_action` bridge (`resolveActionHandlerKeys`, `executeRegisteredAction`).
141
+ The REST route previously rotated only the object key, never the handler key.
142
+ - **D3 (breaking)** — declaration resolution is a trichotomy. A genuinely
143
+ undeclared handler is **refused (404)** with the `defineAction` to add, rather
144
+ than executed ungated with system privileges; an unreachable metadata plane is
145
+ a **503** rather than a silent ungating (`MetadataManager.loadDiagnosed` tells
146
+ a clean miss from an outage). `OS_ALLOW_UNDECLARED_ACTIONS=1` is the migration
147
+ valve — it warns on every invocation and is removed in 18.
148
+ - **D5** — `reconcileActionRegistrations` plus `ObjectQLEngine.listRegisteredActions`
149
+ power a `kernel:ready` inventory logging every registered-but-undeclared
150
+ handler (refused at dispatch) and every declared script action bound to no
151
+ handler — the ADR-0078 converse, mechanised.
152
+ - **D6** — security-gate strictness is opt-**out** (`OS_ALLOW_*`), never opt-in.
153
+
154
+ Apps whose actions are all declared need no changes beyond gaining enforcement
155
+ of the `requiredPermissions` they already declared.
156
+
157
+ - ecc61ab: feat(metadata): 端点匹配器 —— `MetadataManager.matchEndpoint` 惰性索引实现 (#5089)
158
+
159
+ `IMetadataService.matchEndpoint?` 的契约在 #5080/#5097 落地(声明先行),本变更补上
160
+ `metadata` 槽位占位者 `MetadataManager` 的实现:把已声明的 `api` 元数据条目编成
161
+ **METHOD → 精确路径 → 端点** 的惰性索引,供 HTTP 分发器在「没有内建域认领这条路径」
162
+ 与「回答语义 404」之间做一次查表。这是 #5040 端点执行器程序的 E2 单。
163
+
164
+ **结构性不可达,零行为变更。** 17.x 里没有任何东西会调用 `matchEndpoint`:挂载 seam
165
+ 是 #5090 的面,而 publish/validate 对非空 `apis:` 仍然硬拒(#4936)。新代码在真实组合
166
+ 里不暴露任何 HTTP 行为;测试直接驱动服务,这正是 #5040 设计选定的验收姿态。
167
+
168
+ 实现要点(逐字实现契约文本,`packages/spec/src/contracts/metadata-service.ts`):
169
+
170
+ - **匹配维度**:`method` 大写规整后比较(请求动词大小写不敏感);`path` 去掉**一个**
171
+ 尾斜杠后**整串精确**比较,两侧同规则。17.x 不做百分号解码、不做 Unicode 规整、
172
+ 不做大小写折叠 —— 原串即键。词表(ADR-0121)未定义任何路径模板语法,因此
173
+ `params` **恒为 `{}`**;此处不发明只存在于实现里的方言。
174
+ - **答案是 parse 后的形状**:每条经 `ApiEndpointSchema.safeParse`,默认值已物化 ——
175
+ 作者省略 `authRequired` 时消费方拿到的是 `true`,不可能把「缺省」误读为放行。
176
+ - **坏条目响亮缺席**:解析失败的存量条目被跳过并以 `error` 级点名(说明该路由将回 404
177
+ 及如何修),绝不返回半合法形状,也绝不牵连同批的好条目。
178
+ - **重复声明确定性收敛**:两条条目声明同一 METHOD+path 时,`name` 字典序在前者保留
179
+ 路由,被弃者连同规则一并 `error` 级点名 —— 不是静默 last-write-wins,每个节点、每次
180
+ 启动的解析结果一致。
181
+ - **断存储抛错,不伪装 404**:`undefined` 只表示「无声明拥有这条路由」;读不到存储时
182
+ 抛出(与 `loadDiagnosed` 的 miss/outage 之分同源,ADR-0110 D3),因为 miss 会变成
183
+ 404,而故障不得伪装成 404。构建失败不缓存,下次调用重试。
184
+ - **失效**:挂在仓内既有机制上,不新造事件系统 —— `invalidateListCache('api')` 覆盖
185
+ 全部本地写入(含 artifact 装载 / HMR 的 `{ notify: false }` 写入,这些按构造不经过
186
+ watcher),`subscribe('api', …)` 覆盖集群对端回放(它只经 `notifyWatchersLocal`)。
187
+ 失效后下次调用整体重建。
188
+
189
+ `ApiEndpointSchema` 与 `packages/spec` 未做任何改动(词表冻结)。
190
+
191
+ - c52e608: fix(metadata,spec): the endpoint publish gates now guard the metadata write path too (#5189, #5040 E7b)
192
+
193
+ #5111 (E7) hung the five per-endpoint `apis:` gates on
194
+ `ObjectStackDefinitionSchema`, which every path that parses a **stack** runs
195
+ through — `defineStack`, `os validate`, the lint scorer, artifact ingest,
196
+ `EnvironmentArtifactSchema.metadata`. #5189 proved a stored `api` item need
197
+ never have been part of a stack: `MetadataManager.publishPackage`, a direct
198
+ `metadata.register()` and a Studio metadata write each mint one item at a time
199
+ and saw no gate at all.
200
+
201
+ Three of the five gates degrade safely when bypassed — the executor answers a
202
+ structured 501 naming the item, and a path outside the `apps/<namespace>/`
203
+ carve-out simply matches nothing. **ADR-0121 D6 has no runtime counterpart**:
204
+ the runtime honours `authRequired: false` faithfully and `deriveBucketConfig`
205
+ returns `null` for a budget whose `enabled` is not `true`, so the bypass minted
206
+ an anonymous, zero-quota execution entry point — the exact shape D6 exists to
207
+ forbid.
208
+
209
+ Two doors now, both running the SAME gate function rather than a second copy of
210
+ the criteria:
211
+
212
+ - **Publish** — `MetadataManager.publishPackage` runs
213
+ `validateApiEndpointDeclarations` over the package's `api` items and fails
214
+ the publish, naming each endpoint and the key to fix, on the same
215
+ `validationErrors` surface it already uses. This pass is **not** governed by
216
+ `options.validate`: an opt-out on a security gate is the bypass this fixed.
217
+ - **Load** — the endpoint matcher's index build re-applies the _identity-free_
218
+ subset (supported subset, mapping, policy/D6) to every stored item. A
219
+ declaration that never passed publish is EXCLUDED from the index and named at
220
+ `error` level, so a bypassed endpoint answers 404 with a loud log instead of
221
+ answering anonymously and unmetered. The namespace and uniqueness gates are
222
+ deliberately not applied there — both need a stack identity a stored row does
223
+ not carry.
224
+
225
+ **New in `@objectstack/spec/api`** (the module was package-internal in #5111,
226
+ whose only consumer was one file away):
227
+ `validateApiEndpointDeclarations`, `identityFreeEndpointGateFailure`,
228
+ `EndpointGateIssue`, `EndpointGateIdentity`.
229
+
230
+ **New option — `publishPackage(id, { namespace })`.** `MetadataManager` indexes
231
+ items by `packageId` and carries no manifest, so it cannot prove a namespace on
232
+ its own and will **not** infer one from the items it is judging (an
233
+ author-supplied value would make the ADR-0121 D1/D2 carve-out gate vacuous).
234
+ Callers that hold the package manifest pass its explicit `manifest.namespace`;
235
+ without it the namespace gate fails and the package's `api` items do not
236
+ publish — which is the rule, not a limitation: a publish that cannot prove a
237
+ namespace must not mint a URL under one. Packages that declare no `api` items
238
+ are untouched.
239
+
240
+ - 2f8328c: feat(spec,metadata,mcp): let a plural metadata read say it is known-partial (#6504)
241
+
242
+ `IMetadataService.list(type)` returns an array whether every loader answered or
243
+ one of them was down. A consumer receiving a short list therefore had no way to
244
+ ask whether it was short because that is all anyone declared, or because a
245
+ loader was unreachable — the #5840 / PR #6051 defect on the plural read.
246
+
247
+ The verdict already existed and was already being thrown away.
248
+ `MetadataManager.readListUncached()` has computed a `degraded` flag since #5184,
249
+ and `list()` spent it entirely on picking a cache TTL. This is sharper than the
250
+ singular case rather than merely analogous: `list` is the read whose answer
251
+ carries a **count**, and a consumer restating `items.length` as "this
252
+ environment contains N items" makes a positive, numeric claim out of a read that
253
+ partly did not happen.
254
+
255
+ **New optional contract member — `listDiagnosed?(type)`.** Returns
256
+ `{ items, degraded, errors }`, the plural counterpart of `getDiagnosed`.
257
+ Optional for the same reason its singular twin is: an implementation that
258
+ predates it cannot report the distinction, so a consumer probes for it and falls
259
+ back to `list()`, which reports nothing degraded. `list()` itself is unchanged
260
+ in every direction — same items, same array instance, same best-effort posture —
261
+ so no existing caller has to do anything.
262
+
263
+ `MetadataManager` implements it through the same cache entry and the same
264
+ single-flight slot `list()` uses, so asking for the verdict costs no extra
265
+ loader walk and the two members cannot drift.
266
+
267
+ **MCP consumers, classified individually** (PR #6051's discipline, not a blanket
268
+ switch):
269
+
270
+ - `objectstack://objects` **mis-described**, and its degraded body changes. It
271
+ rendered `{ objects, totalCount }`, and during an outage `totalCount` was
272
+ simply false. A healthy read is byte-identical to before. A degraded read now
273
+ serves the same `objects` — the reachable set is still the most useful true
274
+ thing here — with `totalCount` **absent** and `partial: true`,
275
+ `returnedCount`, `warning`, plus the `code: 'SERVICE_UNAVAILABLE'` / `status:
276
+ 503` envelope the sibling `objectstack://objects/{objectName}` resource
277
+ already carries. Dropping the key rather than reporting a smaller number is
278
+ the point: a client reading `body.totalCount` now gets `undefined`, where a
279
+ plausible-looking integer would have been believed.
280
+ - the `agent_prompt` sibling **skill bridge** is a snapshot and its output is
281
+ unchanged. It publishes no count to any client, so a degraded read costs it
282
+ silently-unregistered prompts instead of a false statement; the verdict goes
283
+ to the operator as a `warn` naming the loader, the fact that the skills are
284
+ missing rather than undeclared, and that the stdio transport's snapshot stays
285
+ short until restart while the HTTP transport self-heals.
286
+
287
+ If you consume `objectstack://objects` and read `totalCount` unconditionally,
288
+ branch on `partial` (or on the key's absence) before treating any count from
289
+ this resource as a total.
290
+
291
+ - 9319586: feat(core,metadata,objectql): `IMetadataService.register` refuses ambiguous writes, and type stores key on the canonical type (#7378)
292
+
293
+ The maintainer's three-cell ruling of 2026-08-12 on #7378, implemented in every
294
+ shipped `IMetadataService` implementation — `createMemoryMetadata`
295
+ (`@objectstack/core`), `MetadataManager` (`@objectstack/metadata`) and
296
+ `MetadataFacade` (`@objectstack/objectql`) — through one shared guard,
297
+ `assertMetadataRegisterContract` / `canonicalMetadataServiceType`, newly
298
+ exported from `@objectstack/core`:
299
+
300
+ - **A `data.name` that disagrees with the `name` argument is refused** with a
301
+ locating `VALIDATION_ERROR` (status 400), before anything is stored. The
302
+ previous behaviours resolved the disagreement silently in opposite
303
+ directions per implementation (argument-wins on the Map-backed stores,
304
+ document-wins on the pre-#7511 facade), either of which can file an item
305
+ under a key the author never wrote. A document carrying no `name` of its own
306
+ still registers under the argument — absence is not a disagreement.
307
+ - **A non-object `data` (primitive, `null`, array) is refused** the same way.
308
+ It was previously accepted-then-dropped by `MetadataFacade` (readable back
309
+ through no member) and interim-fixed by boxing into `{ name, content }`; the
310
+ ruling forbids both the drop and the coercion.
311
+ - **Type stores are keyed on the canonical (singular) type**: `'objects'` and
312
+ `'object'` now address ONE store on every implementation, in both the write
313
+ and the read direction, converging with the platform's enforced
314
+ plural→singular normalization (`PLURAL_TO_SINGULAR`, `canonicalMetaType`
315
+ #4432, `check:meta-type-normalized`).
316
+
317
+ Callers that register with a matching (or absent) `data.name` and plain-object
318
+ documents — every in-tree caller — are unaffected. A caller that relied on a
319
+ mismatched `data.name` being silently resolved must pass the intended key as
320
+ the argument and make `data.name` match it; a caller storing a bare value must
321
+ wrap it in a document whose shape its type's schema accepts.
322
+
323
+ ### Patch Changes
324
+
325
+ - d21c001: feat(spec)!: declarative `apis:` publishes again — the blanket refusal narrows to per-endpoint publish gates, and declared endpoints go LIVE (#5111, #5040 E7)
326
+
327
+ ⚠️ **Read this as a security note, not a schema note.** Declarative endpoints
328
+ **execute** from protocol 17. Before this release the surface was inert end to
329
+ end — nothing mounted a declared `path`, no matcher existed, and every key
330
+ including `authRequired` parsed green and gated nothing — which is why #4936
331
+ refused a non-empty `apis:` outright. The #5040 E-series built the executor
332
+ (mount seam, endpoint matcher, policy keys, execution targets, mapping keys,
333
+ OpenAPI enrichment), so the refusal's premise is gone and keeping it would be
334
+ the lie in the other direction.
335
+
336
+ ## BREAKING — the refusal narrows, and what passes it is served
337
+
338
+ `apis: [ …endpoints… ]` no longer fails wholesale. Each entry is now gated
339
+ individually, and **an endpoint that passes the gate is mounted and answers
340
+ real requests as soon as the stack is published.**
341
+
342
+ **Before you upgrade, review every historical `apis:` block** — including any
343
+ you restored, generated from an older doc, or left in place because it was
344
+ known to do nothing. Pay particular attention to any entry that explicitly
345
+ declares **`authRequired: false`**: the schema default is `true`, so an
346
+ _omission_ is safe and needs no review, while an explicit `false` is the only
347
+ thing that opens **anonymous** access to that endpoint. ADR-0121 D6 now pairs
348
+ it with a mandatory armed rate limit — and "armed" means
349
+ `rateLimit: { enabled: true, … }`, because `enabled` defaults to `false`, so a
350
+ budget written without it meters nothing.
351
+
352
+ ## The gates, each rejecting with its own prescription
353
+
354
+ | gate | rejected shape |
355
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
356
+ | **namespace** (ADR-0121 D1/D2) | a `path` that is not `/api/v1/apps/<manifest.namespace>/<subpath>`, or a stack that declares `apis:` without an explicit `manifest.namespace` (no derivation from `manifest.id`) |
357
+ | **supported subset** | `type: 'script'` / `'proxy'`; an `object_operation` missing `objectParams.object` or `.operation`; a `flow` with an empty `target` |
358
+ | **mapping** | any `transform`; an unusable `source`/`target` path (empty, empty segment `a..b`, `__proto__`/`prototype`/`constructor`); two entries whose `target`s collide (same path, or one inside another); `inputMapping` on a `find`/`get`/`delete` operation, which never reads a body |
359
+ | **policy** | `authRequired: false` without `rateLimit.enabled === true`; an armed budget with `maxRequests`/`windowMs` ≤ 0; a negative `cacheTtl`; `cacheTtl` on a non-GET method |
360
+ | **uniqueness** | two endpoints in one stack claiming the same METHOD + path (one trailing slash trimmed, the matcher's own rule) |
361
+
362
+ **FROM → TO.** `path: '/api/v1/<anything>/thing'` →
363
+ `path: '/api/v1/apps/<manifest.namespace>/thing'`, with `manifest.namespace`
364
+ declared explicitly. `authRequired: false` → either delete the key (the safe
365
+ default `true` applies) or keep it **and** add
366
+ `rateLimit: { enabled: true, windowMs: 60000, maxRequests: 100 }`. Every other
367
+ key is unchanged: the `ApiEndpoint` vocabulary is frozen — this release adds,
368
+ removes and renames nothing on it. The gates are validation logic over the keys
369
+ that already existed.
370
+
371
+ The runtime keeps its own refusals for a declaration that reached the store
372
+ without passing publish (a direct `metadata.register()`), so the two ends agree:
373
+ what publish accepts is exactly what the executor serves.
374
+
375
+ `normalizeEndpointPath` is now exported from `@objectstack/spec/api` and is the
376
+ one canonical form of a declared path — the publish gate and the endpoint
377
+ matcher (`@objectstack/metadata`) read the same rule instead of each carrying a
378
+ copy, so a stack can never publish a duplicate the matcher would silently
379
+ resolve to a single winner.
380
+
381
+ - 2e836de: chore(packaging): CHANGELOG.md ships in every npm tarball (#4261)
382
+
383
+ The AGENTS.md post-task checklist requires breaking changesets to carry their
384
+ FROM → TO migration because "this text ships to consumers as `CHANGELOG.md`
385
+ inside the npm package and is what an upgrading agent greps after the tombstone
386
+ error." That delivery path was severed for 68 of the 69 publishable packages:
387
+ npm packs `package.json` / `README*` / `LICENSE*` unconditionally but — unlike
388
+ older npm versions — not `CHANGELOG.md`, and the canonical
389
+ `"files": ["dist", "README.md"]` whitelist never named it. Measured on npm
390
+ 10.9.7: `npm pack --dry-run` on `@objectstack/types` shipped 3 files while its
391
+ 70KB `CHANGELOG.md` stayed behind. Only `@objectstack/spec` listed it
392
+ explicitly.
393
+
394
+ The tombstone-error scenario is precisely the one where the repo is out of
395
+ reach — the upgrading agent has `node_modules` and nothing else — so the
396
+ migration text has to ride in the tarball. Every publishable package now
397
+ declares `CHANGELOG.md` in `files`, and the canonical whitelist is
398
+ `["dist", "README.md", "CHANGELOG.md"]`.
399
+
400
+ The other half is the gate: `check:published-files` gains a fifth invariant,
401
+ COMPLETE — a whitelist that fails to cover `CHANGELOG.md` fails the
402
+ always-required lint job, so the next package cannot silently sever the path
403
+ again. `@objectstack/spec`'s per-package EXTRA_ENTRIES exemption dissolves
404
+ into the canonical set.
405
+
406
+ Consumer-visible change: one more file per install (the package's changelog,
407
+ e.g. 70.8KB for `@objectstack/types`), and `grep -r "removed key"
408
+ node_modules/@objectstack/*/CHANGELOG.md` now finds the migration it was
409
+ promised.
410
+
411
+ - 533a0a4: fix(metadata): 集群对端的元数据写入现在会失效本节点的 `listCache` / registry (#5109)
412
+
413
+ 多节点部署下,节点 A 改一条 `view` / `permission` / `flow`,节点 B 收到
414
+ `metadata.changed` 广播后**只叫醒了 watcher,却没有失效自己的缓存**。
415
+ `attachClusterPubSub()` 的订阅回调此前只做一件事 —— `notifyWatchersLocal()`,
416
+ 既不碰 `this.registry` 也不碰 `this.listCache`。后果是 B 上任何走 `list(type)`
417
+ 的读在 `LIST_CACHE_TTL_MS`(30 秒)窗口内继续返回改动前的清单;更糟的是,被叫醒的
418
+ watcher(ObjectQL SchemaRegistry 桥、Studio HMR SSE)如果回头调 `list()` 重新拉取,
419
+ 拉到的还是旧的 —— 一份「失效通知」附带着失效数据。单机部署完全无感,只有多节点才暴露。
420
+
421
+ 这与该通道自己声明的用途相反(`ClusterMetadataChangedPayload`:"consumed by peers
422
+ to **invalidate their local caches**",另见 `content/docs/kernel/cluster.mdx` §6.2
423
+ 与 `metadata-lifecycle.mdx`);现在实现与声明一致。
424
+
425
+ 修法沿用同文件里 `applyRepoEvent()` 自 ADR-0008 PR-6 起就用对的那条路径,并把两条
426
+ 「外部写入」缝(仓库 watch 循环、集群对端回放)收敛到同一个私有方法
427
+ `invalidateForForeignWrite(type, name)`:
428
+
429
+ - **删除而不预填。** 即便事件带着 body,也只删除 registry 条目而不写入 ——
430
+ 那份 body 是别人那次写入的快照,可能已被后续写入取代,预填会与真实 head 竞态,
431
+ 并要求我们去规范化一份自己没有加载过的定义。删除后 `get()` 自然穿透到 loader /
432
+ repository,也就是真相所在。
433
+ - **同步失效,先失效再通知。** 失效发生在收到消息的当拍(不在 `setImmediate` 内),
434
+ 通知仍然延迟派发。`setImmediate` 的存在理由是不让**消费方的 watcher 回调**背压
435
+ pubsub 派发循环;而失效只是两次 `Map.delete`,不执行任何消费方代码,没有需要延迟的
436
+ 东西——把它一起延迟只会留下「已收到广播、尚未失效」的读窗口,请求处理器里任何一个
437
+ `await` 都足以撞进去。先失效后通知也与本文件其他写入路径
438
+ (`register` / `unregister` / `applyRepoEvent`)一致,于是回头 `list()` 的 watcher
439
+ 拿到的是写后清单。
440
+ - **无名事件只失效清单缓存。** `MetadataWatchEvent.name` 在 spec 里是可选的,无名事件
441
+ 无法定位 registry 条目;此时不会把整个 type 的 registry 一并清掉 —— 那会驱逐
442
+ `registerInMemory()` 注册的、任何 loader 都无法恢复的代码态构件(如 `origin:'code'`
443
+ 的 datasource)。
444
+
445
+ 回环抑制(`originNode`)仍然先于失效判断,本节点自己的广播不会让自己白白重建缓存。
446
+
447
+ - c4ab50b: fix(metadata): `sys_metadata` 的 DDL 失败不再被静默吞掉 —— 只有「表已存在」这一种原因可以静音 (#4728)
448
+
449
+ `DatabaseLoader.ensureSchema()` 过去用一个空 `catch` 吞掉 **全部** DDL 失败,并且照样把
450
+ `schemaReady` 置为 `true`:
451
+
452
+ ```ts
453
+ } catch {
454
+ // If syncSchema fails (e.g. table already exists), mark ready and continue
455
+ this.schemaReady = true;
456
+ }
457
+ ```
458
+
459
+ 注释里的免责理由只覆盖了失败原因中最良性的一种,却用它为**所有**原因开脱。真实的失败
460
+ (权限不足、数据源根本没连上、列类型冲突)之后,表或新列压根不存在,而进程的状态与成功
461
+ 路径**逐字节相同**,启动日志里一行痕迹都没有 —— 这正是 #4420 的形态:声称已持久化、实
462
+ 际没落盘、系统看起来完全健康。#4632 把它定成规则(AGENTS.md → "Degradation log levels"),
463
+ 机械检查 `pnpm check:durability-log-level` 已经能发现这一处。
464
+
465
+ 现在按**错误类型**判别,而不是按注释里的乐观假设:
466
+
467
+ - **良性的「已存在」**(SQLite 的 `table … already exists` / `duplicate column name`、
468
+ Postgres 的 SQLSTATE `42P07`/`42701`/`42710`、MySQL 的 `ER_TABLE_EXISTS_ERROR` 等及其
469
+ `errno`,并跟随 `cause` 链)—— 表确实已就绪,当作 no-op 静默通过,并照常执行后续的
470
+ `project_id → environment_id` 迁移与 ADR-0005 索引。
471
+ - **其余一切失败** —— 以 `console.error` 上报,文案同时说清**后果**(`sys_metadata` 的表/
472
+ 列未创建,后续每一次元数据写入都会报错、或在宽松驱动上悄悄丢列,而服务器仍报告健康)
473
+ 与**修复动作**(修掉下面那条驱动/数据源错误后重启)。只说**一次**,不是每次写入都刷屏。
474
+ - `schemaReady` **不再**在真实失败后置 `true`。启动依旧不被阻断(该方法不抛),但 loader
475
+ 不再声称一个它并不具备的就绪状态,下一次元数据操作会重试 —— 数据源只是还在连接这类瞬
476
+ 时故障因此可以自愈,恢复时补一条 `info`。
477
+
478
+ `ensureHistorySchema()` 按同一规则对齐:良性「已存在」不再每次写入都打一条 `error`(过度
479
+ 使用 `error` 是镜像失败),真实失败则同样只响亮一次并保持重试。
480
+
481
+ 无 API / schema 变更;新增内部工具 `isSchemaAlreadyExistsError()`(未从包入口导出)。
482
+ `scripts/durability-degradation.baseline.json` 中指向本单的条目随之删除(该文件 shrink-only)。
483
+
484
+ - 3133cda: fix(metadata): `DatabaseLoader` 的读故障不再被吞成「什么都没声明」(#5108)
485
+
486
+ `DatabaseLoader` 的五个读方法此前都把**任何**存储异常 `catch {}` 成各自的空值 ——
487
+ `load` → `null`、`loadMany` → `[]`、`exists` → `false`、`stat` → `null`、
488
+ `list` → `[]`。于是 `sys_metadata` 所在库不可达时,`loadMany('permission')` 与
489
+ 「这个环境一条 permission 都没声明」返回**完全一样的值**,而且异常是在 loader 内部
490
+ 就被抹掉的:`MetadataManager` 那几个 `try/catch` 降级分支拿到的是一次「成功的空读」,
491
+ 根本不会触发,整条链上没有任何一处会说出「读失败了」。
492
+
493
+ 现在按**错误类型**判决(#4632 立的规矩,#4728 / #4825 已经在同一个文件里用过两次的
494
+ 形状,判据复用现成的 `isMissingTableError`):
495
+
496
+ - 唯一良性的失败原因是 `sys_metadata` 尚未 provisioned —— 那时确实没有行,
497
+ 「什么都没声明」就是事实,首次启动照旧返回空值、不报错、不缓存;
498
+ - 其余全部原因(连接断开、超时、权限不足、查询出错)意味着行还在、只是这次没读到,
499
+ 一律把驱动原始异常**原样抛出**,由调用方决定降级姿态。判据保守:无法正面识别为
500
+ 「表不存在」的错误一律当作真故障。
501
+
502
+ 由此上层三个已有的机制第一次真的生效:
503
+
504
+ - `MetadataManager.list()` 的降级分支会真的进,并且**升级到 `error`**
505
+ (AGENTS.md「Degradation log levels」:系统看着正常、它声称掌握的清单其实是残缺的),
506
+ 日志写明后果与修法,每次故障只说一次、恢复时再说一次;`list()` 仍然尽力返回可读
507
+ loader 的内容 —— 这个 best-effort 姿态是刻意保留的。兄弟方法
508
+ `MetadataManager.loadMany()` 的同一条缝走同一个判决,不让同一次故障在同一个文件里
509
+ 报出两个级别;
510
+ - `MetadataManager.loadDiagnosed()`(ADR-0110 D3)对 `DatabaseLoader` 终于能报出
511
+ `degraded` / `errors`,而不是把 outage 报成 miss;
512
+ - `listForIndex()` / `matchEndpoint`(#5089)契约要求「读不到存储必须抛出,不得伪装成
513
+ miss(miss 会变成 404)」—— 这条此前对 `MemoryLoader` / `RemoteLoader` 有效、对
514
+ `DatabaseLoader` 无效,现在对真实的 datasource loader 也成立了。
515
+
516
+ **行为变化**:`MetadataManager.exists()` 与 `listNames()` 本来就没有 `try/catch`,
517
+ 所以存储故障现在会从它们抛出,而不再静默答「不存在」/「空清单」。这正是本次修复要的
518
+ 姿态 —— 可用性故障不是一次「没有」。
519
+
520
+ - c794f78: fix(metadata): a known-partial `list()` result is cached as degraded, on a 2s TTL instead of 30s (#5184)
521
+
522
+ Since #5108 a loader that cannot read its store throws rather than answering
523
+ `[]`, so `MetadataManager.list()` catches, reports the outage once at `error`,
524
+ and keeps serving what the reachable loaders hold. That best-effort posture is
525
+ deliberate. What was not deliberate is what happened on the next line: the
526
+ known-short result went into `listCache` on the same 30s TTL as a complete read,
527
+ with nothing on the entry to say it was partial.
528
+
529
+ The consequences were all invisible from outside. That one `error` line covered a
530
+ **30s window in which the failing loader was never asked again** — no retry, no
531
+ second signal, the manager simply re-served a set it already knew was short. When
532
+ the store came back, nothing noticed for up to another 30s, so #5108's recovery
533
+ line (`reportLoaderReadRecovered`) arrived that late too. And because the entry
534
+ carried no marker, no consumer of the cache — including that once-only report —
535
+ could tell a partial answer from a complete one.
536
+
537
+ Not caching degraded reads at all was considered and rejected on evidence. The
538
+ `listCache` field comment records why the cache exists: security middleware
539
+ calling `list('permission')` from inside a user-initiated DB transaction, where
540
+ `DatabaseLoader`'s `engine.find('sys_metadata', …)` tries to take a second knex
541
+ connection while the transaction holds SQLite's only one, and knex waits out
542
+ `acquireConnectionTimeout` (60s). That hazard was re-verified against the current
543
+ driver stack and is still live — `DatabaseLoader._find()` still does not thread
544
+ the caller's transaction, `driver-sql` still models SQLite as a
545
+ single-connection pool (`activeTransactions`, `assertBareKnexSafe`, the latter a
546
+ dev/test guard that no-ops in production), and `plugin-audit` still threads the
547
+ transaction by hand for the same reason. Skipping the cache would have traded one
548
+ 30s silent window for a fresh 60s stall per call.
549
+
550
+ So the entry is still cached, but as what it is:
551
+
552
+ - `listCache` entries carry a `degraded` flag, set when at least one loader threw
553
+ while the result was being assembled. It lives on the entry rather than in a
554
+ side table, so every reader can distinguish a complete answer from a partial
555
+ one; entries are read through a single `readCachedList()` helper that applies
556
+ the flag and its TTL in one place.
557
+ - A degraded entry expires after **2s** (`DEGRADED_LIST_CACHE_TTL_MS`) instead of
558
+ 30s. The burst of repeated lookups inside one transaction is still absorbed —
559
+ those are milliseconds apart — while the window in which a known-short set is
560
+ served without re-asking anyone shrinks 15×, and recovery is noticed (and
561
+ logged) within seconds of the store healing.
562
+ - A complete read is unchanged: cached, not degraded, 30s TTL.
563
+ - The outage message now names the degraded TTL as the retry interval, since it
564
+ previously promised the 30s one.
565
+
566
+ Also closes a `declared ≠ enforced` defect in the same field's comment: it claimed
567
+ the cache kept "only positive (non-empty) hits or repeated hits with a stable miss
568
+ signature". No such condition ever existed in `cacheListResult()`. The comment now
569
+ describes the policy the code actually implements, and the behaviour it claims
570
+ (an empty complete read _is_ cached) is pinned by a test.
571
+
572
+ Internal caching policy only — no change to the `IMetadataService` contract or to
573
+ any public export.
574
+
575
+ - 55da611: fix(metadata,objectql): stop restating the object name inside driver queries — and stop casting away the query's type to do it (#6231)
576
+
577
+ `DriverQuery` (`Omit<QueryAST, 'object'>`) landed in #6076 and five drivers
578
+ followed in #6075, but five **call sites** stayed as they were, because they
579
+ were hidden behind a cast where the compiler could not see them. This removes
580
+ the redundant key at all five and, with it, the casts that existed only to
581
+ carry it.
582
+
583
+ The redundant key was never the expensive half. `git grep 'query\.object' --
584
+ 'packages/drivers/*/src'` is zero: no driver reads it, so the key itself was
585
+ inert. **The cast was the cost.** `as any` on a query argument does not
586
+ suppress one key — it switches off checking for `where`, `orderBy` and
587
+ `fields` as well, which is precisely the account #5181's changeset opened
588
+ (cloud#1053 measured 20 such sites; cloud#1030's `$like` — an operator the
589
+ filter dialect does not have — survived compilation and reached the runtime
590
+ through exactly this hole). `packages/metadata`'s `DatabaseLoader` is the
591
+ main metadata read path, so it was the worst place to be running unchecked.
592
+
593
+ The five sites:
594
+
595
+ - `metadata` `DatabaseLoader._find` / `._findOne` / `._count` — each was
596
+ `driver.find(table, { object: table, ...query } as any)`. The helpers now
597
+ declare `query: DriverQuery` and hand it to the driver unchanged and uncast,
598
+ so all nine of their call sites' `where` / `orderBy` / `fields` are checked
599
+ again.
600
+ - `objectql` `ObjectQL.resolveSecret` — the `sys_secret` read was
601
+ `{ object: 'sys_secret', where: { id } } as QueryAST`, where the cast existed
602
+ only to satisfy the AST's then-required `object`. Both are gone.
603
+ - `objectql` `LifecycleService` governance counter — `count(obj.name,
604
+ { object: obj.name })` carried no cast; it was admitted by a hand-written
605
+ driver shape whose `query` was `Record<string, unknown>`, which would equally
606
+ have admitted a `where` the dialect does not have. That shape is now the named
607
+ `CountCapableDriver` typed with `DriverQuery`, and the call passes argument
608
+ one only.
609
+
610
+ No behaviour changes: the key was inert on every path, and the object name has
611
+ always travelled as the driver methods' first argument. What changes is that
612
+ these call sites are type-checked again, and that re-adding the key is now a
613
+ compile error (`TS2353`) rather than something a cast quietly absorbs.
614
+
615
+ - 3c7bcc0: feat(spec)!: converge the 11 contracts-vs-domain dual-source type names (#4538)
616
+
617
+ `packages/spec/src/contracts/` hand-wrote parameter/result interfaces whose
618
+ names collided with same-named zod-derived types in the domains — the #4411
619
+ trap, tracked as 11 rows of `dual-source-exports.baseline.json`. Each name was
620
+ judged individually against a three-repo import-level scan (framework, cloud,
621
+ objectui): which declaration actually flows at runtime decides the direction.
622
+ All 11 rows are deleted from the baseline; no name below is exported twice
623
+ anymore.
624
+
625
+ **Converged — `./contracts` now re-exports the domain zod type (same
626
+ declaration on both entries, imports keep compiling from either):**
627
+
628
+ - `NotificationChannel` → `system/notification.zod`'s
629
+ `z.infer<NotificationChannelSchema>` (member sets were identical).
630
+ - `ValidationResult` → `kernel/plugin-validator.zod` (shapes were identical).
631
+ - `HealthStatus` → `kernel/startup-orchestrator.zod` (`details` narrows
632
+ `Record<string, any>` → `Record<string, unknown>`).
633
+ - `PluginStartupResult` → `kernel/startup-orchestrator.zod`. FROM `plugin:
634
+ Plugin` (live object) and `error?: Error` TO the serializable projection
635
+ (`plugin: { name, version? }`-passthrough, `error?: { name, message,
636
+ stack?, code? }`). Neither side had any consumer outside spec; the
637
+ zod-validatable shape wins.
638
+ - `StartupOptions` → `kernel/startup-orchestrator.zod` — the PARSED tier
639
+ (defaults applied). `IStartupOrchestrator.orchestrateStartup` now takes
640
+ `StartupOptionsInput` (the caller-authored all-optional tier, also
641
+ re-exported from `./contracts`). Fix for callers typed to the old
642
+ all-optional `StartupOptions`: rename to `StartupOptionsInput`.
643
+ - `JobExecution` → `system/job.zod`. The system schema's `duration` field is
644
+ RENAMED `durationMs` — that is what every job adapter produces and what the
645
+ `sys_job_run.duration_ms` column round-trips; the schema described records
646
+ nothing ever wrote. Fix: `duration` → `durationMs` when parsing
647
+ `JobExecutionSchema` payloads.
648
+ - `AnalyticsQuery` → `data/analytics.zod`. The domain schema aligned to the
649
+ contract's semantics first: `timezone` LOST its `.default('UTC')` — absence
650
+ is meaningful (the engine resolves org timezone, #1982/#2018; the
651
+ `/analytics` entry always refused to apply that default). The schema is now
652
+ transform-free, so `AnalyticsQuery` ≡ `AnalyticsQueryInput` (both kept
653
+ exported). Fix for code that relied on `.parse()` injecting `timezone:
654
+ 'UTC'`: pass the timezone explicitly or resolve it via the engine chain
655
+ (`selection.timezone ?? context.timezone ?? 'UTC'`).
656
+
657
+ **Renamed — two genuinely different concepts were sharing one name (both
658
+ flow at runtime):**
659
+
660
+ - `./contracts` `DriverCapabilities` → **`AnalyticsDriverCapabilities`**
661
+ (`{ nativeSql, objectqlAggregate, inMemory }`, the analytics strategy-chain
662
+ execution-path probe). The `DriverCapabilities` name now belongs solely to
663
+ the data domain's driver feature-flag record (`DriverCapabilitiesSchema`,
664
+ what `IDataDriver.supports` declares). Fix: importers of the trio from
665
+ `@objectstack/spec/contracts` (or `@objectstack/service-analytics`, whose
666
+ re-export is renamed in lockstep) rename the import; importers who meant
667
+ the driver flags import `DriverCapabilities` from `@objectstack/spec/data`.
668
+
669
+ **Removed — the domain-side declaration was dead (zero import-level consumers
670
+ in framework/cloud/objectui; the #4411 family's last survivors):**
671
+
672
+ - `system` `MetadataExportOptionsSchema` / `MetadataExportOptions` and
673
+ `MetadataImportOptionsSchema` / `MetadataImportOptions` (the
674
+ `output`/`source`-directory bags). The names now have ONE declaration each:
675
+ the `IMetadataService.exportMetadata` / `importMetadata` parameter
676
+ interfaces on `./contracts` (`types`/`namespaces`/`format` and
677
+ `conflictResolution`/`validate`/`dryRun`), which `MetadataManager`
678
+ implements. No tombstone/D2 conversion, deliberately — these are runtime
679
+ option-bag types, not authorable metadata (same reasoning as #4458).
680
+ `@objectstack/metadata` re-exports the two names from `./contracts` now
681
+ (it previously re-exported the dead system-side shapes its own manager
682
+ did not accept).
683
+ - `system` `JobSchedule` (the `= Schedule` back-compat alias). The name's one
684
+ declaration is the `IJobService.schedule` boundary shape on `./contracts`
685
+ (plain-string cron `expression`); the authored metadata type keeps its real
686
+ name `Schedule`. Fix: `import type { JobSchedule } from
687
+ '@objectstack/spec/system'` → `Schedule` (authoring tier) or the
688
+ `./contracts` `JobSchedule` (service boundary), whichever you meant.
689
+
690
+ - 8b06bba: fix(spec): `EngineQueryOptionsSchema.search` accepts the bare query string ADR-0061 D1 calls canonical (#7178)
691
+
692
+ Two sibling schemas in `packages/spec` described the same key and disagreed.
693
+ `BaseQuerySchema.search` (`query.zod.ts`, hence `QueryAST`, hence `DriverQuery`)
694
+ has been `z.union([z.string(), FullTextSearchSchema])` since its own drift
695
+ repair, with a doc comment saying why: the bare string **is** the canonical
696
+ Tier-1 contract (ADR-0061 D1 — "the client sends only the query text; the server
697
+ resolves which fields to search from object metadata"), it is what every surface
698
+ sends, and it is what the dogfood HTTP proof pins.
699
+ `EngineQueryOptionsSchema.search` — the options type of `IDataEngine.find` /
700
+ `findOne` — declared the structured `FullTextSearchSchema` **only**.
701
+
702
+ The runtime never agreed with that narrowing. `expandSearchOnAst`
703
+ (`objectql/src/engine.ts`) reads `search` through `normalizeSearch`, whose first
704
+ line is `if (typeof raw === 'string') return { query: raw }`, and
705
+ `protocol-data.test.ts` asserts the protocol layer hands the engine a bare
706
+ string. So the type forbade what the engine serves, and callers paid the
707
+ standard price: `as any` on the query argument — which does not suppress
708
+ `search` alone, it switches off checking for `where` / `orderBy` / `fields` in
709
+ the same literal. Since this schema is not `.strict()`, an unknown key there is
710
+ **silently dropped**, so the cast this divergence forced was precisely the cast
711
+ `check:query-options-erasure` exists to stop.
712
+
713
+ This is the same-family drift REPAIR, not a new dialect — the identical fix
714
+ `BaseQuerySchema.search` already carries, for the identical reason. On the query
715
+ side the divergence surfaced as a validation failure the moment #3899 started
716
+ validating request bodies; here it surfaced as a type error, when #6231 retyped
717
+ `DatabaseLoader`'s read helpers to `DriverQuery` and the **engine** branch alone
718
+ refused to compile (TS2345 — `DriverQuery` not assignable to
719
+ `EngineQueryOptionsParsed`, purely because of `search`; nothing else differs).
720
+
721
+ Consumer census before landing, per the card's own guard: every site that reads
722
+ object-form members off an engine-options `search` already narrows with `typeof`
723
+ — `engine.ts` (`typeof raw === 'object' ? raw?.fields : undefined`),
724
+ `search-filter.ts` `normalizeSearch`, and `metadata-protocol/protocol.ts`'s
725
+ `searchFields` ingress gate. No consumer needed a guard added, and none changes
726
+ behavior: they were all written for the union already. `count` is untouched —
727
+ `EngineCountOptionsSchema` declares no `search` key at all.
728
+
729
+ With the schemas agreed, the casts the divergence forced are deleted:
730
+ `DatabaseLoader`'s three engine-branch `as any` (`_find` / `_findOne` /
731
+ `_count`), which restores real `where` / `orderBy` / `fields` checking on the
732
+ metadata main read path, and the seven `as any` in
733
+ `engine-findone-contract.test.ts` that were passing the canonical spelling.
734
+ `scripts/query-options-erasure-baseline.json` is ratcheted down accordingly.
735
+
736
+ - 2b2175b: fix(metadata): an unreadable file is no longer announced as `data: null` (#5228)
737
+
738
+ `NodeMetadataManager.handleFileEvent()` — the chokidar handler behind
739
+ `watch: true` — wrapped its re-read in a `try/catch` that logged
740
+ "Failed to load changed file" and returned without announcing. That `catch` was
741
+ **unreachable for the failure it was written to catch**. `load()` is
742
+ `(await loadDiagnosed(...)).data`, and `loadDiagnosed` (ADR-0110 D3)
743
+ deliberately absorbs a loader throw: it records the message in `errors[]` and
744
+ answers `{ data: null, degraded: true }`. `FilesystemLoader.load()` does throw
745
+ on an unparseable file — the throw simply died one frame below the handler, so
746
+ the `catch` never ran and the `logger.error` inside it never printed once.
747
+
748
+ What went out instead was a watch event carrying `data: null`, which is the wire
749
+ shape of "this metadata legitimately holds nothing". A file the loader could not
750
+ read and a file the author had emptied reached every subscriber in exactly the
751
+ same shape — the miss/outage distinction ADR-0110 D3 exists to preserve, erased
752
+ at the one call site that had picked the variant which throws it away.
753
+
754
+ The handler now reads through `loadDiagnosed` and splits on `degraded`:
755
+
756
+ - **Degraded** (a loader threw and none answered — an unreadable or unparseable
757
+ file): take the road the dead `catch` meant to take. Log `filePath`, the
758
+ metadata type and name, and `loadDiagnosed`'s `errors[]`, and announce
759
+ nothing. A developer who breaks a metadata file now gets told; before, the
760
+ event claimed the definition had been emptied and nothing was logged.
761
+ - **Clean miss** (`data: null`, no loader threw — the file is gone or
762
+ legitimately empty): unchanged, announced exactly as before.
763
+ - **Deleted** events never read, so a deletion can never be degraded and is
764
+ always announced.
765
+
766
+ Cache invalidation is unaffected and deliberately runs **before** the read, so
767
+ the read's verdict can never decide whether the caches are dropped. #5218's
768
+ contract holds in full: an unreadable file is still a real change to the stored
769
+ set (`loadMany` skips it), so `listCache` and the `registry` entry still go, and
770
+ the `api` endpoint index still rebuilds — `invalidateListCache` is that index's
771
+ first invalidation seam (#5089), so suppressing the announcement costs it
772
+ nothing.
773
+
774
+ No in-repo subscriber loses invalidation or reload correctness: the endpoint
775
+ index is covered by the seam above, `ObjectQLPlugin`'s `subscribe('object', …)`
776
+ answers events by re-reading (an unreadable file yields nothing to re-read
777
+ either way), and the email-template bridge falls through `event.data ?? get(...)`
778
+ to the same empty result. One behaviour does change for the dev HMR/SSE stream:
779
+ a file left permanently unparseable no longer wakes the Studio, which keeps
780
+ showing the last known-good definition until the next event instead of watching
781
+ it vanish.
782
+
783
+ - 729a43a: fix(metadata): 文件系统改动同样失效本节点的 `listCache`/`registry`,不再只叫醒 watcher (#5218)
784
+
785
+ `NodeMetadataManager.handleFileEvent()` 在 chokidar 报告 `add` / `change` /
786
+ `unlink` 之后只做两件事:重新 `load()` 一次文件内容,然后 `notifyWatchers()`。
787
+ 它既不碰 `listCache` 也不碰 `registry` —— 而 `load()` 是纯读路径(它委托给
788
+ `loadDiagnosed`,后者只遍历 loader),两个缓存都不写。
789
+
790
+ 后果是**同一个 manager 的两个读接口互相矛盾**。手改 `rootDir` 下的
791
+ `view/<name>.json` 之后:
792
+
793
+ - `get(type, name)` 是新的 —— 它穿透到 `FilesystemLoader`;
794
+ - `list(type)` 在 `LIST_CACHE_TTL_MS`(30 秒)窗口内继续返回改动前的清单 ——
795
+ REST `/api/v1/metadata/:type`、Studio 左栏、`listViews()` 等一切走 `list()`
796
+ 的读都受影响。
797
+
798
+ 更糟的是被这次事件叫醒的消费者(Studio HMR/SSE 流、ObjectQL SchemaRegistry
799
+ 桥)正是通过回头拉 `list()` 来响应的,于是这次唤醒**递回了它自己刚刚宣告已失效
800
+ 的那份数据**。
801
+
802
+ 这与 #5109(集群对端写入不失效本节点缓存)是同一形状、不同触发源,因此复用该
803
+ 修复落地的 `invalidateForForeignWrite(type, name)`(可见性由 `private` 放宽为
804
+ `protected`):文件改动正是「不是经由本 manager 写接口发生的写入」,没有任何东西
805
+ 替它刷新过缓存,delete-而非-预填 的语义也正好对上 —— 穿透回 loader 读到的就是
806
+ 文件的真相。
807
+
808
+ 两点与基类其余写路径一致的约束:
809
+
810
+ - **先失效,再通知**(`register` / `unregister` / `applyRepoEvent` / 集群订阅者
811
+ 都是这个次序),使 watcher 不可能同时观察到事件与事件前的缓存;
812
+ - **registry 条目一并删除**,不只是列表缓存。FS 加载的条目本来就不进 registry,
813
+ 通常无可删;但当同名条目此前被 `register()` / `registerInMemory()` 写过时,
814
+ 它在 `get()` 和 `list()` 中都会**遮蔽** loader,只删列表缓存会让那份陈旧副本
815
+ 一直应答下去。
816
+
817
+ 命中面主要是开发期:`MetadataPlugin` 默认 `watch: true`,在
818
+ `bootstrap: 'artifact-only'` 下被强制关闭,`standalone-stack` 显式传
819
+ `watch: false`。因此 artifact 模式的 `os dev` 与 standalone 不受影响,非 artifact
820
+ 的默认 `MetadataPlugin` 装配受影响。
821
+
822
+ `type === 'api'` 的行为不变:端点索引此前已由 #5089 装的 `subscribe('api', …)`
823
+ 那条缝覆盖,本次改动把 `invalidateListCache` 那条缝也接上,两条缝对称。
824
+ `EndpointMatcher.invalidate()` 是两次赋 `undefined`,重复失效幂等。
825
+
826
+ - f985b3f: fix(spec,core,cloud-connection,metadata): one HTTP contract, one canonical slot name — and the dead shadow copy that helped cause the false exemption is deleted (#4251)
827
+
828
+ **`packages/core/src/contracts/` was a dead near-copy of the real contracts,
829
+ and it is gone.** The directory (http-server.ts, data-engine.ts, logger.ts) had
830
+ ZERO importers — no relative import, no subpath export, not a tsup entry;
831
+ core's barrel has re-exported the `@objectstack/spec/contracts` versions all
832
+ along ("Re-export contracts from @objectstack/spec for backward
833
+ compatibility"). But the shadow had already **diverged** from the live
834
+ contract (spec's `IHttpResponse` grew `write?`/`end?` and `IHttpRequest` grew
835
+ `rawBody?`; the copy never did), so anyone who grepped their way into it read a
836
+ stale contract that nothing enforces — the exact both-humans-and-AI failure
837
+ mode behind the false `http.server` exemption (#4382). Deleting it is
838
+ zero-risk by construction: nothing could reach it.
839
+
840
+ **`http.server` is the canonical slot name, and the ledger now says so.**
841
+ `ServiceSlotContracts` gains `'http.server': IHttpServer` plus the deprecated
842
+ `'http-server'` alias entry (same instance — hono-plugin and qa's node-plugin
843
+ register both two lines apart; cloud's two server entrypoints do the same).
844
+ Canonical is the only name present on EVERY provider path: runtime's
845
+ `config.server` path registers no alias, so the three cloud-connection plugins
846
+ that read the alias alone (marketplace-proxy, runtime-config,
847
+ marketplace-install-local) found an empty slot there — a live miss, now fixed:
848
+ all readers go canonical-first with the alias as a fallback that dies with the
849
+ alias registrations. The registrations themselves are untouched this release;
850
+ both sites now carry the deprecation note.
851
+
852
+ **`getRawApp?(): any` joins `IHttpServer`** — the deliberate framework-handle
853
+ escape, declared once. Four consumers were each declaring it locally
854
+ (cloud-connection ×2, metadata's HMR routes, cloud's serverless node-server);
855
+ those local `RawAppHost`/`HttpServerWithRawApp` types are deleted. The `any`
856
+ return is deliberate and documented at the single declaration: the handle's
857
+ real type belongs to the framework, and naming it would give the contract a
858
+ framework dependency. Adapters are not required to expose it; consumers
859
+ feature-detect.
860
+
861
+ **`IMetadataService.bulkRegister`/`bulkUnregister` declare the write options
862
+ their implementation has always accepted.** `bulkRegister`'s contract options
863
+ dropped the `MetadataWriteOptions` half its implementation intersects in
864
+ (`notify` is destructured on the method's first line); `bulkUnregister`
865
+ declared no options at all while the manager takes them. Same shape as the
866
+ `IDataEngine` read-methods gap from B2: a caller typed to the contract could
867
+ not reach the channel without erasing the lookup. Both additive; no implementor
868
+ or caller breaks.
869
+
870
+ Slot-lookup baseline ratchets 168 → 167 (marketplace-install-local's lookup
871
+ typed while touched).
872
+
873
+ - 10575f3: fix(lint,metadata): revoke the `http.server` lint exemption — its stated reason was false (#4251)
874
+
875
+ `http.server` was added to `UNCONTRACTED_SLOTS` in #4321 on the ground that
876
+ "no IHttpServer contract exists". The contract does exist —
877
+ `packages/spec/src/contracts/http-server.ts` — and eight call sites were
878
+ already resolving the slot as `getService<IHttpServer>(…)` when the exemption
879
+ was written. An exemption is a claim like any other, and this one rested on a
880
+ premise nobody checked: the same shape as the gaps the rule exists to find.
881
+
882
+ Revoked. That surfaced **9 erasures the exemption had been hiding** — 7 in
883
+ files never grandfathered, 2 as count growth inside grandfathered ones, none of
884
+ which the baseline could legally absorb. All typed to `IHttpServer`;
885
+ `packages/metadata/src/plugin.ts` came out clean entirely, so the baseline
886
+ ratchets **DOWN to 168 sites in 36 files** and loses a file.
887
+
888
+ Two things confirmed on the way, reported rather than changed:
889
+
890
+ **`http.server` and `http-server` are the same instance under two names.**
891
+ plugin-hono-server and qa's node-plugin each register it twice, two lines
892
+ apart; runtime's `config.server` path registers only `http.server`.
893
+ `metadata/src/plugin.ts` reads both with a `??`, which is how it survived. No
894
+ registration is removed here — that is a runtime-behaviour change and belongs
895
+ with whoever picks the canonical name.
896
+
897
+ **`IHttpServer` is defined twice and the two have already diverged.**
898
+ `packages/spec/src/contracts/http-server.ts` (15 importers) declares `write?()`
899
+ and `end?()`; `packages/core/src/contracts/http-server.ts` (8 importers) does
900
+ not. Spec's is the superset and the one the ledger points at, so it is the
901
+ source; core's is a stale near-copy and should re-export it. Left for its own
902
+ change — collapsing a duplicated contract is not a lint fix.
903
+
904
+ Also worth a note for whoever writes the wider HTTP contract: `getRawApp()` now
905
+ has a **third** independent consumer (metadata's HMR routes, joining
906
+ cloud-connection's two). It is deliberately absent from `IHttpServer` — the
907
+ contract is framework-agnostic and the raw app is the framework's own handle —
908
+ so each consumer names it locally. Three is enough evidence to decide whether
909
+ that stays the right answer.
910
+
911
+ - 9fd9ae7: Init-time service consumption is now declared everywhere, and the declaration is enforced (#4471, ADR-0116). A new CI gate (`check:init-service-contract`) walks every plugin's `init()` call graph — including private helpers, the shape that shipped #4420 — and errors on any init-reachable `getService('X')` of a workspace-provided service that is not covered by `dependencies`, `optionalDependencies`, or `requiresServices`. Eleven previously undeclared init-time consumers (metadata, rest, cli serve plugins, and seven services) now declare `optionalDependencies` on their providers, so the kernel orders them deterministically instead of by registration luck; each still degrades on purpose when the provider is not composed. Plugin authors: a best-effort init-time `getService` must declare its provider in `optionalDependencies` (declared tolerance) — the checker never exempts it.
912
+ - 95b4f0d: fix(metadata): `list()` reads are single-flight, so the "one loader hit per TTL window" promise finally holds for concurrent callers too (#5253)
913
+
914
+ `MetadataManager.list()` was a bare "read the cache → walk the loaders → write
915
+ the cache" sequence. The cache is written only once a read has **finished**, so
916
+ it absorbed the caller that arrived second in _time_ but never the caller that
917
+ arrived second in _flight_: every `list(type)` issued while the first read was
918
+ still walking the loaders missed, and each one walked every loader itself. The
919
+ `listCache` field comment states the guarantee the cache exists to provide —
920
+ "the loader is only hit once per TTL window" — and that guarantee held for
921
+ sequential callers only.
922
+
923
+ That is not a rounding error on the path the cache was built for. The comment
924
+ names it: security/permission middleware calling `list('permission')` on the
925
+ request path while `DatabaseLoader`'s read sits inside a transaction that holds
926
+ SQLite's only connection, waiting out knex's `acquireConnectionTimeout` (60s).
927
+ Every concurrent request arriving during those 60s used to burn its own 60s,
928
+ because nothing had been written to the cache yet. The everyday version is
929
+ milder but constant: cold start, and the small burst of concurrent `list()`
930
+ calls that follows every invalidation point — `register()` / `unregister()`, a
931
+ cluster peer's write (#5109), a filesystem change (#5218) — each repeated the
932
+ full loader walk.
933
+
934
+ Reads of one metadata type are now single-flight. A `list(type)` that finds a
935
+ read already running for that type joins it instead of starting a second
936
+ identical walk.
937
+
938
+ - **Sharers share the outcome — as an explicit contract, not an accident.**
939
+ Every caller joining an in-flight read receives that read's exact result,
940
+ including when a loader was unreadable and the answer is known-partial.
941
+ `list()` is the best-effort listing seam and does not throw (the strict
942
+ counterparts remain `listForIndex()` and `loadDiagnosed()`), so a lost loader
943
+ is not an error to fail over from — it is the answer, and re-running the read
944
+ privately for a joiner would walk the same loaders against the same outage in
945
+ the same window.
946
+ - **#5184's degraded judgment is unchanged and is not bypassed.** A shared read
947
+ that lost a loader is still memoized `degraded: true` on the 2s TTL, never
948
+ laundered onto the 30s healthy TTL by having been shared, and every sharer
949
+ received that same partial set.
950
+ - **A write landing mid-read wins.** `invalidateListCache()` now retracts the
951
+ in-flight read as well as the finished entry. The retracted read keeps running
952
+ for the callers already waiting on it — they asked before the write — but it
953
+ loses the right to memoize its pre-write answer, so that answer cannot outlive
954
+ the write it predates; and a caller arriving after the write starts a fresh
955
+ read rather than joining a pre-write one. That second half is #5219 / #5229's
956
+ ordering bar restated for concurrency: a consumer woken by a metadata change
957
+ must not observe the event and pre-event state together.
958
+ - The in-flight map is self-cleaning — an entry is dropped when its read
959
+ settles, by that read only, so a fresh read that replaced it keeps its slot.
960
+
961
+ Internal caching policy only — no change to the `IMetadataService` contract or to
962
+ any public export. Sequential callers behave exactly as before.
963
+
964
+ - f78dd83: fix(metadata,client): `subscribeMetadata` callbacks receive real `MetadataEvent`s — the producer now fulfils the declared contract (#4602)
965
+
966
+ `@objectstack/spec/api`'s `MetadataEvent` declares top-level `id` (uuid,
967
+ required), `metadataType`, `name`, `definition?`, `userId?` — and after
968
+ #4587's convergence it is the **only** declared contract for realtime
969
+ metadata-change events. But the producer (`MetadataManager`) published a raw
970
+ `RealtimeEventPayload` envelope with everything nested under `payload` and no
971
+ `id`/`userId`, while the client SDK force-cast that envelope into the callback
972
+ (`callback(event as any as MetadataEvent)`). Subscribers who wrote
973
+ `event.name` / `event.metadataType` — exactly what the types promised —
974
+ compiled green and read `undefined` at runtime.
975
+
976
+ Producer now fulfils the contract:
977
+
978
+ - `MetadataManager.register()` / `unregister()` build a true `MetadataEvent`
979
+ (generated uuid `id`, flattened top-level fields, `userId` when the write
980
+ declares an actor) and validate it with `MetadataEventSchema.parse` before
981
+ publishing. The transport envelope is unchanged (`RealtimeEventPayload`,
982
+ with `payload` carrying the complete `MetadataEvent`).
983
+ - A `register()` **overwrite now publishes `metadata.{type}.updated`** instead
984
+ of a second `.created`, mirroring the existing `added`/`changed` watcher
985
+ split. Previously `.updated` was declared with no producer at all.
986
+ - `MetadataEventType` is a closed enum: metadata types outside it (e.g.
987
+ `translation`) have no declared realtime event, so nothing is published for
988
+ them (debug-logged) instead of emitting an event every schema-compliant
989
+ consumer must reject.
990
+
991
+ Consumer validates instead of casting:
992
+
993
+ - `@objectstack/client`'s `subscribeMetadata` (and therefore
994
+ `@objectstack/client-react`'s metadata hooks, which delegate to it) unwraps
995
+ the envelope and runs `MetadataEventSchema.safeParse` at the boundary. An
996
+ off-contract payload is rejected loudly (handler error, callback never
997
+ invoked) — never coerced or passed through. The `as any as MetadataEvent`
998
+ double-cast is gone.
999
+
1000
+ New seam: `MetadataWriteOptions.userId` (`@objectstack/spec/contracts`) lets
1001
+ write paths that know the acting user carry it into the published event's
1002
+ `userId`. Existing callers are unaffected — the field is optional and absence
1003
+ means "no human actor".
1004
+
1005
+ - 1c625ca: metadata: `getDiagnosed` — a metadata read that FAILED stops arriving as "nobody declared this"
1006
+
1007
+ `MetadataManager.loadDiagnosed` computes the ADR-0110 D3 verdict (a MISS and an OUTAGE
1008
+ are different facts with opposite security meanings) and `get()` discarded it two hops
1009
+ later: `load()` kept only `.data`, `get()` turned that `null` into `undefined`. Every
1010
+ consumer of `get()` therefore received one `undefined` for two opposite facts and could
1011
+ not have told them apart even if it had wanted to.
1012
+
1013
+ **New read.** `MetadataManager.getDiagnosed(type, name)` returns
1014
+ `{ data, degraded, errors }` — the registry-first counterpart of `loadDiagnosed`, declared
1015
+ as an optional member of `IMetadataService`. A registry hit is never degraded (it
1016
+ consulted no loader); a clean miss is never degraded (every loader answered).
1017
+
1018
+ **`get()` is unchanged — zero breaking.** Same signature, same answer, same behaviour for
1019
+ every existing caller, including the microtask-level ordering `register()`'s watchers
1020
+ depend on. Only callers that ASK for the verdict pay for it. Making `get()` throw on
1021
+ `degraded` was deliberately not done: the boot path degrades on purpose.
1022
+
1023
+ **Consumers switched**, each with a disposition argued for its own context rather than one
1024
+ blanket rule:
1025
+
1026
+ - `getMetaItem` / `getMetaItemCached` — a degraded MetadataService read with nothing in
1027
+ the registry now raises `503 SERVICE_UNAVAILABLE` instead of falling through to
1028
+ `404 RESOURCE_NOT_FOUND`. This is the half that made the existing `#5532` comment ("
1029
+ reaching here now means a real miss") untrue.
1030
+ - `getMetaItemLayered` — the `code` layer joins the rule its `overlay` layer already
1031
+ followed. `code: null` is a positive claim, and `lockSource = code ?? overlay ?? {}`
1032
+ derives from it, so an outage could render an item the packager locked
1033
+ (`_lock: 'full'`) as `editable: true, deletable: true`.
1034
+ - `ObjectQLPlugin`'s `object` metadata-event refresh — logs `warn` naming the consequence
1035
+ (the registry keeps the previous definition; nothing retries) and the fix, instead of
1036
+ `debug` "metadata service has no fresh body". `warn` and not `error` because the write
1037
+ already landed; only a re-read failed.
1038
+
1039
+ Hosts whose `metadata` slot is a shim that predates `getDiagnosed` are read as
1040
+ "not degraded" — exactly what they could express before — so their behaviour is unchanged.
1041
+
1042
+ - b5459bc: fix(metadata): `capabilities.write` now means BOTH directions — a writable datasource loader must implement `delete()` (#5276)
1043
+
1044
+ `MetadataLoader` declared `save?` and no `delete`, so `capabilities.write` meant
1045
+ two different things at the two ends of an item's life: to `register()` it meant
1046
+ "persist into me", and to `unregister()` it guaranteed nothing at all.
1047
+ `unregister()` duck-typed `delete` at the call site and, when a loader had none,
1048
+ **silently skipped it** — then dropped the registry entry, invalidated the list
1049
+ cache and announced a `deleted` event anyway. The caller (Studio/Setup, REST
1050
+ DELETE, the CLI, a package teardown) was told the delete succeeded while the row
1051
+ stayed in the loader's store, was read straight back out by the next
1052
+ `list()`/`get()`, and survived every restart with nothing to retry it.
1053
+
1054
+ Two changes, both making the declaration binding instead of decorative:
1055
+
1056
+ - **`MetadataLoader` now declares `delete?(type: string, name: string): Promise<void>`.**
1057
+ The capability is stated on the contract, next to `save?`, instead of being
1058
+ guessed at by each caller. A loader implemented against the interface can now
1059
+ see that the method exists.
1060
+ - **`MetadataManager.registerLoader()` rejects the combination that cannot
1061
+ honour it.** A loader declaring `protocol: 'datasource:'` **and**
1062
+ `capabilities.write: true` **without** a `delete()` method is refused at
1063
+ registration with an error naming the loader, the consequence, and both
1064
+ repairs. `registerLoader()` is the sole writer of the loader map — the
1065
+ constructor's `config.loaders` funnel through it — so the combination can no
1066
+ longer reach the runtime and lose a deletion there.
1067
+
1068
+ **Does this affect you?** Only if you register a custom metadata loader that
1069
+ declares `protocol: 'datasource:'` with `capabilities.write: true`. If it does
1070
+ and has no `delete()`, registration now throws where it previously succeeded and
1071
+ quietly discarded your deletions. Two ways to fix it, both stated in the error:
1072
+
1073
+ 1. implement `async delete(type: string, name: string): Promise<void>` on the
1074
+ loader, removing the item from its store (`DatabaseLoader` in this package is
1075
+ the reference implementation); or
1076
+ 2. if the loader is genuinely read-only, declare `capabilities.write: false` — a
1077
+ read-only `datasource:` loader registers without complaint and is never
1078
+ written to in the first place.
1079
+
1080
+ Loaders on the other protocols (`file:`, `memory:`, `http:`, `s3:`) are
1081
+ unaffected in either direction: `MetadataManager` never persists to them at
1082
+ runtime, so it has no deletion of its own to take back, and they may declare
1083
+ `capabilities.write` without a `delete()` exactly as before. The one
1084
+ `datasource:` loader shipped in this package, `DatabaseLoader`, has always
1085
+ implemented `delete()` and is unchanged.
1086
+
1087
+ - 1624f4a: fix(metadata): `capabilities.write` now also binds `save()` — a writable datasource loader must implement both halves of the write (#5654)
1088
+
1089
+ #5276 (shipped in v17.0.0-rc) made `capabilities.write` binding on `delete()`:
1090
+ a loader declaring `protocol: 'datasource:'` with `capabilities.write: true`
1091
+ and no `delete()` is refused at registration, because `unregister()` used to
1092
+ skip it silently and announce the deletion anyway. The gate stopped there, so
1093
+ **one declaration was binding at one end of an item's life and decorative at
1094
+ the other**.
1095
+
1096
+ `MetadataManager.register()` had the identical hole one direction over. Its
1097
+ persistence loop read `loader.save &&` first, so a `datasource:` loader
1098
+ declaring `capabilities.write: true` **without** a `save()` method was
1099
+ **silently skipped** — no warn, no error. `register()` then wrote the in-memory
1100
+ registry, invalidated the list cache, announced `created`/`updated` and notified
1101
+ watchers, so the caller (Studio/Setup, REST PUT, the CLI, a package publish) was
1102
+ told the write succeeded. The item read back correctly for the life of the
1103
+ process and was **gone at the next restart**, with nothing to retry it — a
1104
+ durability degradation that leaves the system looking entirely healthy.
1105
+
1106
+ `registerLoader()`'s gate (renamed `assertWritableLoaderContract`) now requires
1107
+ **both** `save()` and `delete()` for that combination, and rejects with one
1108
+ message naming which method is missing, the consequence, and both repairs.
1109
+ `registerLoader()` is the sole writer of the loader map — the constructor's
1110
+ `config.loaders` funnel through it — so the combination can no longer reach the
1111
+ runtime and lose a write there. The `save` short-circuit inside `register()`
1112
+ survives as defensive code whose unreachability is now guaranteed by
1113
+ construction, exactly like `unregister()`'s.
1114
+
1115
+ **Does this affect you?** Only if you register a custom metadata loader that
1116
+ declares `protocol: 'datasource:'` with `capabilities.write: true`. If it does
1117
+ and has no `save()`, registration now throws where it previously succeeded and
1118
+ quietly discarded your writes. Two ways to fix it, both stated in the error:
1119
+
1120
+ 1. implement
1121
+ `async save(type: string, name: string, data: any, options?: MetadataSaveOptions): Promise<MetadataSaveResult>`
1122
+ on the loader, persisting the item into its store (`DatabaseLoader` in this
1123
+ package is the reference implementation); or
1124
+ 2. if the loader is genuinely read-only, declare `capabilities.write: false` — a
1125
+ read-only `datasource:` loader registers without complaint and is never
1126
+ written to in the first place.
1127
+
1128
+ Loaders on the other protocols (`file:`, `memory:`, `http:`, `s3:`) are
1129
+ unaffected: `MetadataManager` never persists to them at runtime, so they may
1130
+ declare `capabilities.write` without a `save()`/`delete()` exactly as before.
1131
+ The one `datasource:` loader shipped in this package, `DatabaseLoader`, has
1132
+ always implemented both and is unchanged.
1133
+
1134
+ - 7c6261a: refactor(metadata): peel the stored envelope before an `api` row is parsed as an endpoint (#5309)
1135
+
1136
+ Internal refactor — no authored format changes, no observable acceptance change
1137
+ for the shapes the platform stores today.
1138
+
1139
+ A metadata _type name_ is worn by two different documents: the **authored
1140
+ declaration** (exactly its spec vocabulary) and the **stored row** (that
1141
+ declaration plus the metadata layer's own bookkeeping — `packageId`, `state`,
1142
+ `version`, `publishedDefinition`, `publishedAt`, `publishedBy`, written by
1143
+ `MetadataManager.register` / `publishPackage` and read back by `publishPackage`'s
1144
+ package filter). Both `ApiEndpointSchema` parse sites — `buildEndpointIndex` (the
1145
+ load-time backstop) and `gateApiItemsForPublish` (the publish gate) — used to hand
1146
+ the whole stored row to the schema, and only its unknown-key _stripping_ kept the
1147
+ bookkeeping from being judged as endpoint vocabulary.
1148
+
1149
+ `peelStoredEnvelope` (`packages/metadata/src/stored-envelope.ts`) now takes the
1150
+ envelope off first, so the schema sees the authored body and nothing else:
1151
+
1152
+ - a row carrying a `metadata` value IS an envelope around it — the body is that
1153
+ value, everything beside it is bookkeeping. This is the `data.metadata ?? data`
1154
+ rule the publish gate, `publishedDefinition` and `getPublished` already shared;
1155
+ - otherwise the body is the row minus the declared bookkeeping keys.
1156
+
1157
+ The peel returns views and never mutates the row, so every existing envelope
1158
+ reader (`publishPackage`'s `packageId` filter, `query`'s `state` / `packageId`
1159
+ filters, `revertPackage`) is untouched, and `publishedDefinition` still snapshots
1160
+ `data.metadata ?? data` verbatim.
1161
+
1162
+ One consequence worth naming: `buildEndpointIndex` was the last reader that did
1163
+ NOT follow the layer's body-selection rule, so a publish envelope
1164
+ (`{ name, packageId, state, metadata: {…} }`) used to pass the publish gate and
1165
+ then be excluded from the endpoint index — its route answered 404. The two doors
1166
+ now read the same document.
1167
+
1168
+ This is the prerequisite for tightening `ApiEndpointSchema` (#5384): with the
1169
+ schema flipped to `strictObject` locally, `packages/metadata` went from 11 failing
1170
+ tests to 1, and the one left is an authored non-vocabulary key being refused by
1171
+ name — which is what that tightening is for.
1172
+
1173
+ - 1da39f5: fix(metadata): `isMissingTableError` no longer reads Postgres' write-path missing-COLUMN message as a missing TABLE (#6347)
1174
+
1175
+ `isMissingTableError` is the single predicate that licenses a caller to treat a
1176
+ failed read as "the table is not provisioned yet, so there are genuinely no
1177
+ rows". Its own docblock names `column "x" does not exist` (SQLSTATE 42703) as a
1178
+ real failure that must stay loud — "a case where 'start numbering at 1' would be
1179
+ the wrong answer against a table that may be full of rows" — and the code did
1180
+ not honour that, in one direction only.
1181
+
1182
+ Postgres has **two** missing-column phrasings:
1183
+
1184
+ | path | message | judged |
1185
+ | :-------------------------------- | :----------------------------------------------------- | :---------------------------- |
1186
+ | read (`SELECT`) | `column "bogus" does not exist` | correctly NOT a missing table |
1187
+ | write (`INSERT`/`UPDATE`/`ALTER`) | `column "label" of relation "sys_team" does not exist` | **wrongly** a missing table |
1188
+
1189
+ The write-path phrase contains a complete, legal missing-table phrase —
1190
+ `relation "sys_team" does not exist` — as a substring, so the table-scoped
1191
+ message test matched it. The code channel did not rescue it either: the matcher
1192
+ is a sequential OR, so an error carrying `code: '42703'` falls past both code
1193
+ lines and is decided by its message. The same superstring covers every other
1194
+ sub-object of a relation Postgres phrases this way, e.g.
1195
+ `constraint "uq_x" of relation "sys_team" does not exist` (42704).
1196
+
1197
+ A message regex can never exclude a superstring, so the repair is a
1198
+ **front-exclusion** evaluated before any positive test: the column-level
1199
+ SQLSTATEs the docblock already names (`42703`, `42704`, `3D000`) and the
1200
+ `"x" of relation "y"` sub-object phrasing. Recognising one ends the question
1201
+ with `false` — it does not descend into `cause`, because an error that
1202
+ identifies as "a column of an existing relation" is that error whatever it
1203
+ wraps.
1204
+
1205
+ What changes for you: a driver error of that shape now propagates instead of
1206
+ being silenced. Every consumer of the predicate is affected the same way, and
1207
+ all of them get louder rather than quieter — `DatabaseLoader.nextEventSeq` and
1208
+ `SysMetadataRepository`'s history counters no longer restart `event_seq` at 1,
1209
+ `ObjectQLEngine`'s autonumber seed no longer reseeds from 0, and the metadata
1210
+ loaders no longer answer "nothing declared". The set of errors judged benign
1211
+ shrinks; nothing that was loud becomes quiet. Genuine missing-table detection is
1212
+ unchanged for PostgreSQL, MySQL/MariaDB and the SQLite family.
1213
+
1214
+ - beefe89: fix(metadata): 历史序号 `event_seq` 不再从一次失败的读里凭空发号 —— 只有「表还没建」可以从 1 开始 (#4825)
1215
+
1216
+ `DatabaseLoader.nextEventSeq()` 过去把读 `sys_metadata_history` 的**全部**失败折成同一个答案:
1217
+
1218
+ ```ts
1219
+ } catch {
1220
+ // Table not provisioned yet or driver error — start at 1.
1221
+ return 1;
1222
+ }
1223
+ ```
1224
+
1225
+ 注释同时点名了两种原因,然后用同一个 `return 1` 对待。这是 #4728 刚修掉的同一种形状,但危害是
1226
+ **更贵的那一半**:#4728 是「字节没落盘」,本条是「**落盘的字节是错的**」。历史表里已经有 N 行时,
1227
+ 一次瞬时读失败(连接抖动、超时、权限)会让下一条历史拿到 `event_seq = 1`,与既有行**直接撞号**,
1228
+ 而 insert **成功**、日志**一行没有**。`event_seq` 正是历史列表排序与 rollback 定位的依据,撞号之后
1229
+ 版本顺序就永久不可信 —— 重试不修、重启也不修。
1230
+
1231
+ 现在按**错误类型**判别,复用 #4728 落地的那套判别机制(`packages/metadata/src/utils/schema-sync-errors.ts`
1232
+ 里新增的 `isMissingTableError()` 与既有 `isSchemaAlreadyExistsError()` 共用同一个 code / errno /
1233
+ message + `cause` 链匹配器,而不是在同一个包里另起一套错误判别):
1234
+
1235
+ - **良性的「表还没建」**(SQLite `no such table: …`、Postgres SQLSTATE `42P01` /
1236
+ `relation "…" does not exist`、MySQL `ER_NO_SUCH_TABLE` / errno `1146`,并跟随 `cause` 链)——
1237
+ 没有行,就没有可撞的号,`1` 确实是下一个号,静默返回。
1238
+ - **其余一切读失败** —— `nextEventSeq()` 原样抛出。调用方 `createHistoryRecord()` 以
1239
+ `console.error` 上报**后果**(该条历史记录未写入;元数据写入本身已成功,所以服务器仍报告健康,
1240
+ 而变更历史正在悄悄出现空洞,版本时间线与 rollback 目标将不完整)、**为什么是空洞而不是错号**
1241
+ (从 1 发号会与既有行撞号,把「不完整」变成「顺序错误」,后者无人能发现)与**修复动作**,
1242
+ 然后**跳过这条历史记录**。
1243
+ - 判别的方向刻意保守:凡是没有被正面识别为「表不存在」的,一律当作真实失败。`does not exist`
1244
+ 本身不够 —— `role "…" does not exist`、`database "…" does not exist`、`column "…" does not exist`
1245
+ 都是真实失败,对着一张可能满是行的表返回 1 正是要避免的事,所以消息匹配要求 table/relation 与
1246
+ 该短语同现。
1247
+
1248
+ 两条边界保持不变:元数据写入本身**不**因此失败(记录已经落盘,把它报成失败是比原缺陷更糟的谎),
1249
+ 以及本路径已知的并发撞号限制(非事务,canonical producer 仍是 `SysMetadataRepository`)——那是被
1250
+ 记录过的限制,与「读失败静默重置到 1」是两回事。报告只说**一次**,恢复时补一条 `info`。
1251
+
1252
+ 无 API / schema 变更;新增内部工具 `isMissingTableError()`(未从包入口导出)。
1253
+
1254
+ - 4e9e184: chore(deps): OSV security batch — bump tar to ^7.5.21 (GHSA-r292-9mhp-454m) and
1255
+ js-yaml to ^5.2.2 (GHSA-pm4m-ph32-ghv5)
1256
+
1257
+ Both are declared-range bumps to the patched releases, so downstream installs
1258
+ resolve the fixed versions from the published manifests, not just this
1259
+ workspace's lockfile. The same batch clears the remaining transitive advisories
1260
+ (next 16.2.11 in apps/docs; workspace overrides for brace-expansion, sharp,
1261
+ react-router, @sveltejs/kit, @hono/node-server) — those live in pnpm-workspace.yaml
1262
+ and the private docs app, which do not ship.
1263
+
1264
+ - d13004a: feat(core,runtime): plugin ordering is a declared, kernel-enforced contract (ADR-0116, #4131)
1265
+
1266
+ `kernel.use()` registration order was never a contract — the kernel resolves
1267
+ init/start order from the plugin dependency graph — but a plugin that needed a
1268
+ service at init _when its provider is composed_ while also booting _without_
1269
+ the provider had no way to declare that. `AppPlugin` was the standing example:
1270
+ it grabs `manifest`/`objectql` synchronously in `init()`, declared nothing
1271
+ (a hard dependency would break empty-env / metadata-only / mock-engine
1272
+ kernels), and so its correctness rode on which array slot each caller put it
1273
+ in. That convention failed the same way twice (`DefaultDatasourcePlugin`'s
1274
+ first cut; then #4085, disguised for months as "crashes when the artifact is
1275
+ missing").
1276
+
1277
+ The kernel `Plugin` contract gains three additive fields, enforced by both
1278
+ `ObjectKernel` and `LiteKernel` through one shared implementation
1279
+ (`plugin-order.ts` — the previously duplicated topological sort is unified
1280
+ there):
1281
+
1282
+ - **`optionalDependencies: string[]`** — order-if-present: hoisted ahead
1283
+ exactly like `dependencies` when composed (real topology edges, including
1284
+ cycle detection), silently skipped when absent.
1285
+ - **`requiresServices: string[]`** — services resolved synchronously during
1286
+ `init()` with no fallback. Validated **before Phase 1**: a required service
1287
+ whose only declared provider initializes later fails the boot with an error
1288
+ naming both plugins, both slots, and the fix — before any init side
1289
+ effects. Re-checked immediately before the plugin's own init, where a still-
1290
+ missing service becomes a named composition error exactly where the old
1291
+ bare `Service not found` crash fired.
1292
+ - **`providesServices: string[]`** — services a plugin's `init()`
1293
+ unconditionally registers; powers the validation and the diagnostics.
1294
+
1295
+ Plugins that declare nothing get the diagnosis too: a `getService` miss
1296
+ during Phase 1 now appends which plugin was initializing and — when a
1297
+ composed plugin declares the service — who provides it and how to declare the
1298
+ ordering. The `Service '<name>' not found` prefix and the factory-backed
1299
+ `is async - use await` message are unchanged.
1300
+
1301
+ First adopters: `AppPlugin` declares
1302
+ `optionalDependencies: ['com.objectstack.engine.objectql']` +
1303
+ `requiresServices: ['manifest']` (cleared on the empty-env no-op path), so
1304
+ the #4085 composition — AppPlugin registered before the engine — now boots
1305
+ correctly in every slot; `ObjectQLPlugin` declares
1306
+ `providesServices: ['objectql', 'data', 'manifest', 'lifecycle']` and
1307
+ `MetadataPlugin` declares `providesServices: ['metadata']`.
1308
+
1309
+ Everything is additive — plugins that declare nothing keep their exact
1310
+ ordering semantics; no existing declaration changes meaning.
1311
+
1312
+ - 91cefb8: refactor(types,rest,metadata,analytics): Postgres 的 `"x" of relation "y"` 短语收归一处,三个包不再各修一遍同一个超串洞(#6615)
1313
+
1314
+ Postgres 把「关系内部某个子对象」的失败写成 `column "label" of relation "sys_team" does not exist`——里面**逐字包含**一句合法的「表不存在」短语 `relation "sys_team" does not exist`,含义却相反:关系正因为存在才被点名。任何对「这句话是不是在说表没了」的正则收紧都消不掉这个匹配,短语确实在里面;唯一的修法是**先问更具体的问题**。所以修的是**顺序**,不是模式。
1315
+
1316
+ 正因为如此,这个短语被分三次教给了这个仓库,分属三个包、三个 PR,其中两次是在别处已经踩过同一个洞之后:`@objectstack/rest` 的 `mapDataError`(#5352)、`@objectstack/service-analytics` 的缺列扣除(#6035 / PR #6346)、`@objectstack/metadata` 的 `MISSING_TABLE.excludes`(#6347 / PR #6613)。本次把它收进 `@objectstack/types`,与 `isUniqueViolationError`(#6250)和 `isModuleNotFoundError`(framework#3265)同一个理由与同一个位置。
1317
+
1318
+ **两种宽度,故意保留成两个导出。** 三个消费者要的并不是同一条正则,差别也不是随手写的,而是**每个站点哪个方向的误差是安全的**:
1319
+
1320
+ - `matchMissingColumnOfRelation(message)` —— 严格提取器,锚定 Postgres 的 errmsg 模板 `column "%s" of relation "%s" does not exist`,返回列名。`rest` 用它把 42703 答成 `400 INVALID_FIELD` 而不是 `404`;`service-analytics` 用它在分类前扣除缺列。这两处**过宽**会把真正缺失的表变成硬失败、回退 #5033 刻意保留的宽容,**漏匹配**只是让消息含糊一点——所以必须严格。
1321
+ - `isRelationSubObjectPhrase(message)` —— 宽检测器,丢掉 `column` / `[a-z0-9_]+` / `does not exist` 三个锚点:任意子对象、任意带引号标识符、任意判词。`metadata` 用它做排除。这一处**过宽**只会把良性判定变成响亮判定,**漏匹配**却会让 `event_seq` 从 1 重新开始、撞进一张已有行的历史表——方向正好相反。
1322
+
1323
+ 把两者合并成一条正则,无论哪种宽度胜出都会对其中一个调用方是错的;这是卡片记录在案的风险,两个导出即为此而设,理由是承重的而非风格的。仓库里第四份拷贝(`service-analytics` 测试内用于守护 fixture 的那条正则)同时收编:它本是为「两张面孔别对不上」而写,却把断言打在其中一面的私有复述上,因而正是它要防的漂移。
1324
+
1325
+ 行为逐字保持不变:搬进来的两条模式与原站点逐字节相同。`@objectstack/service-analytics` 因此新增一条对 `@objectstack/types` 的依赖边——这是本次唯一的依赖变化,构造上无环(`@objectstack/types` 只依赖 `@objectstack/spec`,后者无仓内依赖),且仓库 73 个包中已有 25 个、16 个 service 中已有 5 个携带同一条边。
1326
+
1327
+ - 857a6cf: fix(cli,core,metadata,runtime): `os serve` boots with no compiled artifact — the platform does not need an application to start (#4085)
1328
+
1329
+ The artifact (`dist/objectstack.json`) defines an **application**. ObjectStack is
1330
+ a development platform, so it has to start without one — but `os serve
1331
+ objectstack.config.ts` died during boot whenever the artifact was absent:
1332
+
1333
+ ```
1334
+ Loading objectstack.config.ts...
1335
+ [StandaloneStack] artifact read FAILED: path='…/dist/objectstack.json' error=ENOENT…
1336
+
1337
+ ✗ Service 'manifest' is async - use await
1338
+ ```
1339
+
1340
+ Exit 1 — on a **known-good app** (`examples/app-todo` fails the same way with
1341
+ only its `dist/objectstack.json` moved aside), and on every freshly authored
1342
+ project between `os init` and its first `os compile`. The message named neither
1343
+ the missing artifact nor a fix, so it read as an internal kernel fault.
1344
+
1345
+ Three separate faults, each of which alone was enough to refuse the boot:
1346
+
1347
+ - **`serve` registered the config-derived `AppPlugin` before the stack's own
1348
+ `plugins[]`.** Registration order _is_ the kernel's init/start order, and that
1349
+ slot sits ahead of `ObjectQLPlugin` (which registers `manifest`/`objectql`) and
1350
+ `DefaultDatasourcePlugin` (which connects the database the app seeds through).
1351
+ The wrap is now **appended** to `plugins[]`, the same slot
1352
+ `createStandaloneStack` gives its artifact-derived `AppPlugin` — so config-boot
1353
+ and artifact-boot share one plugin order. The artifact path never hit this,
1354
+ which is exactly what made a plugin-**order** bug look artifact-related.
1355
+
1356
+ - **`ctx.getService()` reported a never-registered service as "is async".**
1357
+ `PluginLoader.getService` is an `async` method, so its return value is _always_
1358
+ a Promise and its internal "not found" rejection can never surface
1359
+ synchronously — the kernel read the answer off that Promise and told every
1360
+ caller to `await` a service that did not exist, while the `not found` branch
1361
+ below it was unreachable. It now decides from the registry: absent ⇒
1362
+ `[Kernel] Service 'x' not found`, registered-but-uninstantiated ⇒ the unchanged
1363
+ `Service 'x' is async - use await`. The same crash now reads
1364
+ `[Kernel] Service 'manifest' not found`, which points at the layer that is
1365
+ actually wrong.
1366
+
1367
+ - **`MetadataPlugin` treated an absent `local-file` artifact as fatal.**
1368
+ `createStandaloneStack` always points it at `dist/objectstack.json`, so a stack
1369
+ with no app at all could not boot. A **missing** local artifact is now "nothing
1370
+ compiled yet": it logs, starts empty, and leaves the artifact watcher armed, so
1371
+ a later `os compile` hydrates the running server. The tolerance is
1372
+ ENOENT-only — a malformed or unreadable artifact stays fatal — and
1373
+ `bootstrap: 'artifact-only'` (sealed runtime, where the artifact _is_ the
1374
+ deployment) keeps failing loudly rather than silently serving an empty runtime.
1375
+
1376
+ `[StandaloneStack] artifact read FAILED … ENOENT` is likewise no longer shouted
1377
+ at callers for whom "no artifact" is a healthy state; a present-but-unusable
1378
+ artifact keeps the loud warning.
1379
+
1380
+ Pinned by an e2e pair that drives the real `os serve` with **no `os compile`
1381
+ anywhere**: an app defined only by `objectstack.config.ts` (asserting its object
1382
+ is in the started plugin set, not merely that boot survived) and a bare
1383
+ `export default {}` platform. The #4012 fixture drops the `os compile` this bug
1384
+ had forced on it.
1385
+
1386
+ - 3de535b: fix(metadata,repo): every enumeration of the stack-collection set is now answerable to `stack.zod.ts`, and the artifact map stops aiming `data:` at the analytics kind (#6242)
1387
+
1388
+ `ObjectStackDefinitionSchema` decides which collections a stack may declare — 32
1389
+ of them today. **Seven** other places re-enumerate that same set by hand (eight
1390
+ enumerations in all, because ObjectQL declares its list twice), and nothing
1391
+ compared any of them to the schema or to each other:
1392
+
1393
+ | Enumeration | Site |
1394
+ | --------------------------------------------- | ----------------------------------------------------- |
1395
+ | `MAP_SUPPORTED_FIELDS` / `PLURAL_TO_SINGULAR` | `packages/spec/src/shared/metadata-collection.zod.ts` |
1396
+ | `MetadataCategoryEnum` | `packages/spec/src/kernel/package-artifact.zod.ts` |
1397
+ | `metadataArrayKeys` ×2 | `packages/objectql/src/engine.ts` |
1398
+ | `ARTIFACT_FIELD_TO_TYPE` | `packages/metadata/src/plugin.ts` |
1399
+ | `APP_CATEGORY_KEYS` | `packages/runtime/src/app-plugin.ts` |
1400
+ | `STACK_COLLECTION_COVERAGE` | `examples/app-showcase/src/coverage.ts` |
1401
+
1402
+ They had drifted independently: `ragPipelines` mapped in three of them though no
1403
+ schema declares it; `workflows` / `approvals` / `roles` / `profiles` / `policies`
1404
+ still iterated by both ObjectQL loops after ADR-0019 / ADR-0020 / ADR-0088 /
1405
+ ADR-0090 retired them; `triggers` + `workflows` still legal artifact categories;
1406
+ 19 of 32 collections absent from that enum.
1407
+
1408
+ Every row looks like a one-line typo in isolation, and each **has** been fixed
1409
+ one line at a time before — `docs` in `ARTIFACT_FIELD_TO_TYPE`, `roles` →
1410
+ `positions` in the same map, `capabilities` in `metadataArrayKeys` — each still
1411
+ carrying its "this key was missing and it silently dropped X" comment. The cause
1412
+ is structural: `KIND_COVERAGE` is answerable to the metadata-type registry and
1413
+ fails CI when a kind is added without an entry, and the liveness ledger is
1414
+ answerable to the same registry. The collection maps were answerable to nothing.
1415
+
1416
+ **The gate.** `pnpm check:stack-collection-maps` (root
1417
+ `scripts/check-stack-collection-maps.mjs`, wired into the lint job) derives the
1418
+ collection set from `ObjectStackDefinitionSchema` — top-level keys whose value is
1419
+ `z.array(<X>Schema)`, a mechanical rule rather than a second hand-kept list — and
1420
+ reconciles all eight enumerations against it in **both** directions. Deriving them
1421
+ is not possible today (they disagree on purpose as often as by accident: `views`
1422
+ has no `name`, `data` seeds key by `object`, `translations` is a record), so each
1423
+ deviation must instead be a waiver row **carrying its reason**, and the list is a
1424
+ ratchet: a waiver that no longer applies fails, like a stale ledger row. An
1425
+ enumeration whose symbol cannot be extracted fails too — an empty list would
1426
+ reconcile against everything.
1427
+
1428
+ Writing it immediately found a **seventh** site the hand-audit had missed
1429
+ (`APP_CATEGORY_KEYS`) and one divergence _between_ the two ObjectQL copies that
1430
+ neither list shows alone: `jobs`, `emailTemplates`, `tools` and `skills` are
1431
+ registered from a manifest and **not** from a nested plugin, so a package
1432
+ shipping them from a nested plugin registers nothing and stamps no ADR-0010
1433
+ provenance. `capabilities` was added to that copy for exactly this reason
1434
+ (#5870); nobody then asked what else the two lists disagreed about. Recorded as
1435
+ a waiver with the measurement, not fixed here — closing it changes what a nested
1436
+ plugin registers at boot.
1437
+
1438
+ **The one code change**: `ARTIFACT_FIELD_TO_TYPE` no longer maps `data:` (the
1439
+ SEED collection) to `'dataset'` (the ADR-0021 analytics kind) — the exact name
1440
+ collision `metadata-plugin.zod.ts` warns about in prose. The entry was provably
1441
+ inert (`SeedSchema` declares no `name`, and the ingest loop skips nameless
1442
+ items), so nothing changes at runtime; what changes is that a dead pointer aimed
1443
+ at the wrong kind is gone, instead of waiting for either side to move. Not
1444
+ repointed at `'seed'`: seeds are applied by `SeedLoaderService` off the bundle,
1445
+ never registered as metadata items, so that would be new behaviour rather than a
1446
+ corrected name. The absence is now pinned by the gate.
1447
+
1448
+ Everything else the gate reports is recorded as a waiver with its reason and left
1449
+ alone, deliberately — three of the drift rows sit on **acceptance faces**
1450
+ (`MetadataCategoryEnum` decides what a published artifact may declare) and the
1451
+ rest are `engine-core` behaviour changes owing their own verification. The value
1452
+ landing today is that all eight enumerations now have a checked relationship to
1453
+ the schema rather than an assumed one.
1454
+
1455
+ - 5d21a48: feat(spec,metadata-protocol,metadata,objectql,service-automation): stored metadata replays the full conversion chain at rehydration (#3903)
1456
+
1457
+ Every mechanism the platform has for evolving the metadata contract — schema
1458
+ transforms, the ADR-0087 D2 conversion layer, the D3 migration chain, the
1459
+ protocol-17 tombstones — operated on **authored source** only. Metadata **at
1460
+ rest** (`sys_metadata` rows written by Studio or the runtime authoring APIs)
1461
+ was rehydrated unparsed and unconverted, so the authored and stored contracts
1462
+ silently diverged: a pre-17 row carrying `conditionalRequired` or `execute`
1463
+ read as whatever each ad-hoc consumer happened to do with it.
1464
+
1465
+ **New spec primitive — `applyConversionsToStoredItem(type, item, options?)`**
1466
+ (exported from the package root). Wraps one stored item of a given metadata
1467
+ type and replays the **full** conversion chain over it — `retiredFromLoadPath`
1468
+ entries included, because retirement is an _authoring-surface_ event: the
1469
+ window exists to teach a live author, and a row at rest has no author to
1470
+ teach. Idempotent, never throws, never validates.
1471
+
1472
+ Wired at every stored-row rehydration seam:
1473
+
1474
+ - `metadata-protocol`: `loadMetaFromDb`, `getMetaItems` (active + draft
1475
+ preview), `getMetaItem` (active + draft), `getMetaItemLayered`, and
1476
+ `duplicatePackage` (a copy re-saves through the schema gate, so legacy
1477
+ sources now duplicate successfully — and the copy is canonical).
1478
+ - `metadata`: the DatabaseLoader's live-row reads (`load` / `loadMany`).
1479
+ History reads stay verbatim — history records what was written.
1480
+ - `objectql`: the authored-action / authored-hook direct table reads, so
1481
+ runtime-authored actions stored with the removed `execute` alias dispatch
1482
+ via `target` again.
1483
+ - `service-automation`: `AutomationEngine.registerFlow` now passes
1484
+ `includeRetired` — stored flows keep canonicalizing after their conversions
1485
+ graduate out of the load window. (The generic metadata seams deliberately
1486
+ skip `type: 'flow'`: flow conversions carry the open-namespace conflict
1487
+ guard, which needs this engine's live executor registry.)
1488
+
1489
+ **Boot hydration diagnoses instead of shrugging.** `loadMetaFromDb` now
1490
+ returns `{ loaded, errors, invalid }`: each row is validated against its
1491
+ type's spec schema _after_ conversion, and a genuine contract violation is
1492
+ counted and warned with a stable `[metadata_spec_invalid]` marker — but still
1493
+ registered, deliberately: refusing at boot would unhook live tables and make
1494
+ the row unlistable and unfixable in Studio. The write path (`saveMetaItem` → 422) and the read-side `_diagnostics` envelope remain the enforcing gates; the
1495
+ `SchemaRegistry.registerItem` validation hook is now documented as exactly
1496
+ that diagnostic.
1497
+
1498
+ **Retired accommodation.** With the chain running on every stored read path,
1499
+ the rule-validator's `requiredWhen ?? conditionalRequired` fallback — kept in
1500
+ #3883 with a retirement promise that had no mechanism — is deleted. If you
1501
+ call `evaluateValidationRules` directly with raw legacy field definitions,
1502
+ convert them first (`applyConversionsToStoredItem('object', def)`) or author
1503
+ `requiredWhen`; the platform's own read paths already hand you canonical
1504
+ shapes.
1505
+
1506
+ - dca25e1: fix(metadata-protocol): `SysMetadataRepository` 的 `event_seq` / `version` 不再从一次失败的读里凭空发号 —— 只有「表还没建」可以从 1 开始 (#4867)
1507
+
1508
+ `SysMetadataRepository.nextEventSeq()` 与 `nextItemVersion()` 各有一个同形的 `catch`,把读
1509
+ `sys_metadata_history` 的**全部**失败折成同一个答案:
1510
+
1511
+ ```ts
1512
+ } catch {
1513
+ // Table not provisioned yet (fresh DB) — start at 1.
1514
+ return 1;
1515
+ }
1516
+ ```
1517
+
1518
+ 这是 #4825 刚在 `DatabaseLoader`(TSDoc 自称 legacy、非事务的那条路径)上修掉的形状,原样长在
1519
+ **canonical 路径**上 —— #4825 正文把 `SysMetadataRepository` 称作「历史写入应当收敛过去的地方」。
1520
+ 而且这里有两个数字:
1521
+
1522
+ - **`event_seq`** —— 历史排序与 rollback 定位的依据。表里已有 N 行时,一次瞬时读失败(连接抖动、
1523
+ 超时、权限)让下一条拿到 `1`,与既有行撞号;
1524
+ - **`version`** —— `nextItemVersion()` 的 TSDoc 明说它刻意从 history 取 MAX「so delete + recreate
1525
+ continues incrementing instead of restarting at 1」。一次读失败正好把它**恢复成它明确要避免的那个
1526
+ 行为**:lineage 从 1 重启并与既有 lineage 撞号,而 `MetadataManager.rollback(type, name, version)`
1527
+ 与 `POST /api/v1/meta/:type/:name/rollback` 正是按这个数字定位快照 —— 撞号之后回滚可能落到另一条
1528
+ 记录的同号版本上。
1529
+
1530
+ 关键危害与 #4825 相同,是「**落盘的字节是错的**」而不是「字节没落盘」:insert 成功、日志一行没有、
1531
+ 系统对外完全正常,重试不修、重启也不修。
1532
+
1533
+ **「在事务里」并不能挡住它。** 事务解决的是*并发*撞号;它对「从一次失败的读推导出来的数字」没有任何
1534
+ 意见,一个成功提交的事务照样把错号提交得同样持久。事务真正给出的是干净的补救:抛出去,整笔写入回滚,
1535
+ 而不是提交一个编造的号。
1536
+
1537
+ 现在按**错误类型**判别,复用 #4825 落地的那套判别器(不另起一套):
1538
+
1539
+ - **良性的「表还没建」** —— 没有行,就没有可撞的号,`1` 确实是下一个号,静默返回,fresh DB 照常启动;
1540
+ - **其余一切读失败** —— 按 AGENTS.md「Degradation log levels」以 `error` 上报**后果**(写入已被中止、
1541
+ 事务回滚、什么都没提交;若按旧行为发 `1` 会与既有行撞号,使版本顺序不可信、回滚目标可能指向另一条
1542
+ 记录的同号版本,且无人能发现、重启也修不回来)与**修复动作**(修数据源/驱动错误后重试写入),然后
1543
+ **原样抛出**,让事务回滚。一次故障只说一次,恢复时补一条 `info`。
1544
+
1545
+ ### `@objectstack/metadata` 新增子路径导出 `@objectstack/metadata/errors`
1546
+
1547
+ 判别器 `isMissingTableError()`(#4728/#4825 家族)此前是 `@objectstack/metadata` 的内部工具,而本次
1548
+ 消费者在另一个包。三个选项中选了「从现有归属地**显式导出**」:在 `metadata-protocol` 里复制一份会重建
1549
+ #4825 刚消灭的双源问题(同一个问题两套「哪些驱动错误算良性」的词汇表,谁先学会一个驱动怪癖谁就先漂移);
1550
+ 下沉到公共依赖本轮不可行(`packages/spec` 冻结、`packages/types` 有并行改动),且本次导出并不妨碍维护者
1551
+ 之后再下沉。
1552
+
1553
+ 新增的是一个**叶子子路径**而不是包入口导出:`@objectstack/metadata` 的根入口会拖进 manager、全部
1554
+ loader 与其 YAML/文件系统依赖,只为一个 40 行谓词付这个重量,正是把下一个作者推回「复制一份」的原因。
1555
+ `@objectstack/metadata/errors` 只 re-export 一个叶子模块,跨包依赖边因此仍是叶子边,也是将来下沉时
1556
+ 一个可 grep、可删除的单点。仅导出 `isMissingTableError`;同族的 `isSchemaAlreadyExistsError` 在包外
1557
+ 没有消费者,保持内部(导出一个无人 import 的符号是白许的承诺)。
1558
+
1559
+ 无 API 破坏、无 schema 变更、无 `packages/spec` 改动。
1560
+
1561
+ - 52d1a7d: Fix commit-revert answering `VERSION_NOT_FOUND` over a row `/history` lists, and the package-level revert route answering 500
1562
+
1563
+ **Revert (`revertCommit` / `rollbackMetaItem`).** Both revert callers resolved their overlay repository from the caller's _active organization_, while the publish that recorded the commit routes each draft to the draft's **own** scope (the ADR-0005 / #3115 rule `SysMetadataRepository.listDrafts` states, and `publishPackageDrafts` already follows). So an env-wide artifact — what Studio and AI authoring write — published from a console request carrying an active org stored its `sys_metadata_history` rows at `organization_id = NULL` and was then read back at `organization_id = <org>`: no match, and the revert answered `VERSION_NOT_FOUND: No history row at version 2` for a version the history endpoint lists. The revert now resolves the scope the item's lineage actually lives in (the caller's own overlay first, env-wide second), per item for a batch revert. The same resolution reaches the `#6602` registry heal and the `#4636` package-binding read, which an org-scoped revert of an env-wide row was previously skipping while reporting success.
1564
+
1565
+ **`POST /packages/:id/revert`.** The route now answers a declared 4xx instead of 500 (ADR-0112). The cause was entirely in the thrown shape, not the route: `MetadataManager.revertPackage` threw bare `Error`s carrying no `code` or `status`, and `errorFromThrown` — which the route's handler already reaches through one enclosing `catch` — falls back to 500 only when it finds neither. An unknown package id now answers `RESOURCE_NOT_FOUND` / 404 and a never-published package `RESOURCE_CONFLICT` / 409; 500 remains only as the fallback for a genuinely unexpected throw.
1566
+
1567
+ - 7309c81: test(runtime,client,metadata): back the remaining suites with in-memory SQLite instead of the mingo driver (#4065)
1568
+
1569
+ Ten test files used `InMemoryDriver` as a convenience backing store — somewhere
1570
+ for rows to go while the suite proved something else (REST routing, datasource
1571
+ auto-connect, the batch `$ref` contract, metadata history). They now run on
1572
+ `SqliteWasmDriver` at `:memory:`, the same engine `@objectstack/verify`'s
1573
+ `bootStack` already gives the dogfood gate: pure JS (no native build, CI-safe on
1574
+ any runner) and real SQL semantics.
1575
+
1576
+ The point is fidelity, not tidiness. Production runs SQL, and mingo differs from
1577
+ it in ways that let a suite pass while the behaviour it stands for is broken.
1578
+ Every failure this migration produced was a fixture defect the memory driver had
1579
+ been absorbing:
1580
+
1581
+ - **Tables were never created.** `driver.create()` on the memory driver is a
1582
+ bare `table.push()` onto an auto-vivified array, so an object registered
1583
+ _after_ `kernel.bootstrap()` — which misses the boot-time schema sync — looked
1584
+ fine. On SQL the first write fails with `no such table`, which the REST error
1585
+ mapper turns into a **404 `OBJECT_NOT_FOUND`**: a routing-shaped symptom for a
1586
+ DDL-shaped cause. Four suites needed an explicit `syncObjectSchema`.
1587
+ - **A missing object declaration read as working.** `notifications.hono.integration`
1588
+ writes `sys_notification`, which `MessagingServicePlugin` does not declare —
1589
+ it is a platform object, and that lean kernel never booted `platform-objects`.
1590
+ Auto-vivification hid the omission entirely. The suite now registers the real
1591
+ `SysNotification` rather than a hand-copied stand-in, so there is still exactly
1592
+ one schema for it (Prime Directive #12).
1593
+ - **`connect()` was optional.** The memory driver needs none; a SQL driver does.
1594
+
1595
+ What deliberately did NOT move: `read-coercion-conformance` keeps its two-driver
1596
+ matrix (proving a stored value reads back as its declared type on _both_ engines
1597
+ is the entire point of that gate), and the suites whose subject IS the memory
1598
+ driver or its wiring — `standalone-stack` (`memory://` scheme),
1599
+ `sqlite-driver-fallback` (the dev step-down), the CLI's driver-label tests, and
1600
+ driver-memory's own suite.
1601
+
1602
+ `datasource-autoconnect` is in that second group as of #4083, which landed a
1603
+ regression test there for exactly the memory-pool property this PR originally
1604
+ proposed to migrate away from. Moving that file to SQLite would have left the
1605
+ new test passing vacuously — a wasm-SQLite pool never writes `.objectstack/` at
1606
+ all — so it stays on the memory driver and keeps guarding what it was written
1607
+ to guard.
1608
+
1609
+ No new coverage is claimed here: each suite asserts exactly what it asserted
1610
+ before, against a more faithful store.
1611
+
1612
+ - e92e2c3: fix(metadata): `unregister()` invalidates the list cache AFTER the storage delete lands (#5259)
1613
+
1614
+ `MetadataManager.unregister()` dropped the registry entry and called
1615
+ `invalidateListCache(type)` **before** awaiting `loader.delete()`. Those two steps
1616
+ are separated by a real await window — one DB round-trip per writable loader — and
1617
+ inside it the manager held a state that exists nowhere else: **registry already
1618
+ empty, loader not yet empty**. `list()` merges the two, so a read arriving in that
1619
+ window missed the just-cleared cache, assembled the still-stored row into its
1620
+ answer, and memoized it as a _complete_ read — the full 30s healthy TTL, because no
1621
+ loader threw and #5184's 2s degraded TTL therefore never applied.
1622
+
1623
+ Nothing invalidated again once the delete landed (`notifyWatchers()` does not touch
1624
+ `listCache`), so an item that was gone from storage kept being enumerated for up to
1625
+ half a minute. `list()` is the enumeration seam behind `GET /api/v1/metadata/:type`,
1626
+ the Studio left rail, sync/export and every consumer that decides existence from a
1627
+ declared set — and `get()`, which never reads that cache, said the item was gone the
1628
+ whole time. For a gating type (`permission`, `api`) the two faces of one manager
1629
+ answered opposite questions about whether a declaration exists.
1630
+
1631
+ **Fixed by ordering, not by an extra invalidation.** `register()` never had this
1632
+ defect because it writes the registry _first_ and the registry outranks every loader
1633
+ in the merge, so its own save window already shows the post-write state. The
1634
+ invariant is therefore not "invalidate early" but _invalidate last, once every store
1635
+ already holds the announced state_. `unregister()` now deletes from storage first,
1636
+ then drops the registry entry and invalidates with **nothing awaited between them**,
1637
+ then publishes and announces — #5219's invalidate-before-notify discipline unchanged.
1638
+ A `list()` racing the delete now either sees a coherent pre-delete state (the delete
1639
+ has not landed and has not been announced — that answer is the truth) or the
1640
+ post-delete state; it can no longer cache the pre-delete answer past the delete.
1641
+
1642
+ This composes with #5253's single-flight rather than duplicating it: a read still
1643
+ _in flight_ when the delete lands cannot be reached by dropping `listCache` — it has
1644
+ not written its entry yet and would write the pre-delete answer afterwards.
1645
+ `invalidateListCache()` also retracts that read's `inflightListReads` registration,
1646
+ so it resolves for the callers already waiting on it but loses the right to memoize,
1647
+ while a caller arriving later starts a fresh read.
1648
+
1649
+ **A storage delete that fails is now loud.** It used to `logger.warn('Failed to
1650
+ delete …')` and continue. Per AGENTS.md "Degradation log levels" this is
1651
+ durability/consistency degradation, not functional: `unregister()` resolves
1652
+ normally, the caller is told the delete succeeded, and the surviving row is read
1653
+ straight back out of storage by the very next `list()`/`get()` — permanently, since
1654
+ nothing retries it. It now logs at `error`, once per un-deleted item, naming the
1655
+ consequence and the fix. The registry entry is still dropped in that case,
1656
+ deliberately: the loader still holds the row so the item is served either way, and
1657
+ keeping the entry would only pin an in-memory copy on top of a stored row nobody
1658
+ maintains — dropping it makes the next read fall through to storage, which is the
1659
+ actual truth after a failed delete, and makes it visible immediately instead of at
1660
+ the next restart.
1661
+
1662
+ No API change. `unregister()` still resolves rather than throwing when a loader
1663
+ refuses the delete.
1664
+
1665
+ - Updated dependencies [50616d9]
1666
+ - Updated dependencies [430dcc2]
1667
+ - Updated dependencies [690ccf2]
1668
+ - Updated dependencies [6a67d7a]
1669
+ - Updated dependencies [098f4bb]
1670
+ - Updated dependencies [333a374]
1671
+ - Updated dependencies [9fe9c1d]
1672
+ - Updated dependencies [3d5c090]
1673
+ - Updated dependencies [e5bd768]
1674
+ - Updated dependencies [08b5a3d]
1675
+ - Updated dependencies [e027b3e]
1676
+ - Updated dependencies [e6ac4bd]
1677
+ - Updated dependencies [c2429b0]
1678
+ - Updated dependencies [445a0c2]
1679
+ - Updated dependencies [d99aeb3]
1680
+ - Updated dependencies [f6609e6]
1681
+ - Updated dependencies [4727eb8]
1682
+ - Updated dependencies [a70358a]
1683
+ - Updated dependencies [0ecc656]
1684
+ - Updated dependencies [06772eb]
1685
+ - Updated dependencies [d4e0809]
1686
+ - Updated dependencies [80334c7]
1687
+ - Updated dependencies [f63cd09]
1688
+ - Updated dependencies [97e7e3c]
1689
+ - Updated dependencies [ce5242c]
1690
+ - Updated dependencies [a7163ea]
1691
+ - Updated dependencies [e6e9379]
1692
+ - Updated dependencies [5823d59]
1693
+ - Updated dependencies [3140f9c]
1694
+ - Updated dependencies [9500ba4]
1695
+ - Updated dependencies [fa3d0cf]
1696
+ - Updated dependencies [af5a224]
1697
+ - Updated dependencies [71f76e1]
1698
+ - Updated dependencies [37b1346]
1699
+ - Updated dependencies [99736a0]
1700
+ - Updated dependencies [fe67e34]
1701
+ - Updated dependencies [fdb4f50]
1702
+ - Updated dependencies [270650f]
1703
+ - Updated dependencies [3aef718]
1704
+ - Updated dependencies [1bd5652]
1705
+ - Updated dependencies [14252d3]
1706
+ - Updated dependencies [7fb436c]
1707
+ - Updated dependencies [879ea13]
1708
+ - Updated dependencies [8828b9e]
1709
+ - Updated dependencies [1ea6bce]
1710
+ - Updated dependencies [c1dcacd]
1711
+ - Updated dependencies [ad303ed]
1712
+ - Updated dependencies [32ccb23]
1713
+ - Updated dependencies [f5a4ef0]
1714
+ - Updated dependencies [2d3e255]
1715
+ - Updated dependencies [a8940e4]
1716
+ - Updated dependencies [7d7521f]
1717
+ - Updated dependencies [5dc4d02]
1718
+ - Updated dependencies [f724f69]
1719
+ - Updated dependencies [98877c9]
1720
+ - Updated dependencies [98877c9]
1721
+ - Updated dependencies [53068c1]
1722
+ - Updated dependencies [ee58392]
1723
+ - Updated dependencies [f16e54e]
1724
+ - Updated dependencies [c44dd5e]
1725
+ - Updated dependencies [06be54e]
1726
+ - Updated dependencies [28ad90e]
1727
+ - Updated dependencies [76d74ec]
1728
+ - Updated dependencies [201b31f]
1729
+ - Updated dependencies [e6b1b69]
1730
+ - Updated dependencies [259459d]
1731
+ - Updated dependencies [3f7f14e]
1732
+ - Updated dependencies [e2616e0]
1733
+ - Updated dependencies [6fdc5c6]
1734
+ - Updated dependencies [8b9d71e]
1735
+ - Updated dependencies [05154a1]
1736
+ - Updated dependencies [33f5e23]
1737
+ - Updated dependencies [259af21]
1738
+ - Updated dependencies [f8644c7]
1739
+ - Updated dependencies [306ca50]
1740
+ - Updated dependencies [840ee4b]
1741
+ - Updated dependencies [978fed2]
1742
+ - Updated dependencies [cfc293f]
1743
+ - Updated dependencies [587fc91]
1744
+ - Updated dependencies [de70b42]
1745
+ - Updated dependencies [9b6fe7c]
1746
+ - Updated dependencies [64cd010]
1747
+ - Updated dependencies [fb3d99b]
1748
+ - Updated dependencies [1986594]
1749
+ - Updated dependencies [6968885]
1750
+ - Updated dependencies [eaed61f]
1751
+ - Updated dependencies [52200b4]
1752
+ - Updated dependencies [cdfbee2]
1753
+ - Updated dependencies [ad4af62]
1754
+ - Updated dependencies [debe2f6]
1755
+ - Updated dependencies [d44dbfa]
1756
+ - Updated dependencies [29c6c9d]
1757
+ - Updated dependencies [d21c001]
1758
+ - Updated dependencies [ad047d2]
1759
+ - Updated dependencies [8c711fb]
1760
+ - Updated dependencies [f1cc3a3]
1761
+ - Updated dependencies [09e4547]
1762
+ - Updated dependencies [97b0798]
1763
+ - Updated dependencies [474fe39]
1764
+ - Updated dependencies [0bc685a]
1765
+ - Updated dependencies [b949059]
1766
+ - Updated dependencies [2826d1e]
1767
+ - Updated dependencies [be1c52c]
1768
+ - Updated dependencies [c5ff96d]
1769
+ - Updated dependencies [5a84d41]
1770
+ - Updated dependencies [84e7be9]
1771
+ - Updated dependencies [91f4c78]
1772
+ - Updated dependencies [ddc2527]
1773
+ - Updated dependencies [820eff9]
1774
+ - Updated dependencies [a6c3f38]
1775
+ - Updated dependencies [5fa04fb]
1776
+ - Updated dependencies [debc23a]
1777
+ - Updated dependencies [0f8ad09]
1778
+ - Updated dependencies [553a47f]
1779
+ - Updated dependencies [43a7a8d]
1780
+ - Updated dependencies [a98085f]
1781
+ - Updated dependencies [20b1a9e]
1782
+ - Updated dependencies [344a22a]
1783
+ - Updated dependencies [4827e91]
1784
+ - Updated dependencies [8d895ff]
1785
+ - Updated dependencies [86f7a20]
1786
+ - Updated dependencies [a3a884d]
1787
+ - Updated dependencies [cfed092]
1788
+ - Updated dependencies [203a449]
1789
+ - Updated dependencies [8f9689f]
1790
+ - Updated dependencies [73f69dc]
1791
+ - Updated dependencies [04c56aa]
1792
+ - Updated dependencies [f6472d7]
1793
+ - Updated dependencies [57a3bb3]
1794
+ - Updated dependencies [b3efeb7]
1795
+ - Updated dependencies [ddd075a]
1796
+ - Updated dependencies [88154be]
1797
+ - Updated dependencies [e8dc61e]
1798
+ - Updated dependencies [9c82146]
1799
+ - Updated dependencies [5f9a987]
1800
+ - Updated dependencies [744b8f5]
1801
+ - Updated dependencies [9f5cc79]
1802
+ - Updated dependencies [ac37fc6]
1803
+ - Updated dependencies [9f060e5]
1804
+ - Updated dependencies [bc17d39]
1805
+ - Updated dependencies [2f3e793]
1806
+ - Updated dependencies [4820f55]
1807
+ - Updated dependencies [462d9c4]
1808
+ - Updated dependencies [78caf51]
1809
+ - Updated dependencies [7d21581]
1810
+ - Updated dependencies [37785ed]
1811
+ - Updated dependencies [62a789b]
1812
+ - Updated dependencies [2e284b2]
1813
+ - Updated dependencies [d8e8d9c]
1814
+ - Updated dependencies [789ad63]
1815
+ - Updated dependencies [f2445c9]
1816
+ - Updated dependencies [94e749b]
1817
+ - Updated dependencies [ea1d916]
1818
+ - Updated dependencies [2af1988]
1819
+ - Updated dependencies [0af50a3]
1820
+ - Updated dependencies [1b49eaf]
1821
+ - Updated dependencies [ae31a19]
1822
+ - Updated dependencies [2e836de]
1823
+ - Updated dependencies [e0f300b]
1824
+ - Updated dependencies [0161c7f]
1825
+ - Updated dependencies [e900015]
1826
+ - Updated dependencies [db02d47]
1827
+ - Updated dependencies [b5bdf48]
1828
+ - Updated dependencies [23338c3]
1829
+ - Updated dependencies [12a19a8]
1830
+ - Updated dependencies [5b843fb]
1831
+ - Updated dependencies [62b6a2f]
1832
+ - Updated dependencies [7e5af5c]
1833
+ - Updated dependencies [5b4780b]
1834
+ - Updated dependencies [a933452]
1835
+ - Updated dependencies [9d1d9c7]
1836
+ - Updated dependencies [8140915]
1837
+ - Updated dependencies [a019e52]
1838
+ - Updated dependencies [e8f8f6c]
1839
+ - Updated dependencies [41dcda3]
1840
+ - Updated dependencies [7b48cf9]
1841
+ - Updated dependencies [b5404f4]
1842
+ - Updated dependencies [64fc6d5]
1843
+ - Updated dependencies [b746aa0]
1844
+ - Updated dependencies [b4487aa]
1845
+ - Updated dependencies [1007379]
1846
+ - Updated dependencies [65ca83a]
1847
+ - Updated dependencies [0bfdf46]
1848
+ - Updated dependencies [947d4f9]
1849
+ - Updated dependencies [f764691]
1850
+ - Updated dependencies [e120a5a]
1851
+ - Updated dependencies [e5bd2f6]
1852
+ - Updated dependencies [e650d67]
1853
+ - Updated dependencies [121852d]
1854
+ - Updated dependencies [04476e7]
1855
+ - Updated dependencies [67bf2e2]
1856
+ - Updated dependencies [eaaf03c]
1857
+ - Updated dependencies [d17df80]
1858
+ - Updated dependencies [7d0e7b5]
1859
+ - Updated dependencies [c6d1cb4]
1860
+ - Updated dependencies [6513c17]
1861
+ - Updated dependencies [36030ff]
1862
+ - Updated dependencies [79228cd]
1863
+ - Updated dependencies [6117f7b]
1864
+ - Updated dependencies [87aca93]
1865
+ - Updated dependencies [e533b0b]
1866
+ - Updated dependencies [cdf4d9a]
1867
+ - Updated dependencies [aee1806]
1868
+ - Updated dependencies [c13350b]
1869
+ - Updated dependencies [c13350b]
1870
+ - Updated dependencies [2c1988c]
1871
+ - Updated dependencies [9ca2d85]
1872
+ - Updated dependencies [c13350b]
1873
+ - Updated dependencies [891d345]
1874
+ - Updated dependencies [c8124e5]
1875
+ - Updated dependencies [a52e2ef]
1876
+ - Updated dependencies [5293114]
1877
+ - Updated dependencies [376a061]
1878
+ - Updated dependencies [c142ced]
1879
+ - Updated dependencies [211abdb]
1880
+ - Updated dependencies [b3363e9]
1881
+ - Updated dependencies [eda599e]
1882
+ - Updated dependencies [a1a4140]
1883
+ - Updated dependencies [c20b875]
1884
+ - Updated dependencies [7c7e246]
1885
+ - Updated dependencies [2ef1807]
1886
+ - Updated dependencies [f35cdc5]
1887
+ - Updated dependencies [d03fe25]
1888
+ - Updated dependencies [2a37694]
1889
+ - Updated dependencies [217e2e6]
1890
+ - Updated dependencies [2672f85]
1891
+ - Updated dependencies [20bc357]
1892
+ - Updated dependencies [11066f6]
1893
+ - Updated dependencies [916af17]
1894
+ - Updated dependencies [84c86fb]
1895
+ - Updated dependencies [2a2a9fb]
1896
+ - Updated dependencies [86a71d1]
1897
+ - Updated dependencies [c001422]
1898
+ - Updated dependencies [77022a9]
1899
+ - Updated dependencies [d5c75e2]
1900
+ - Updated dependencies [03d26f7]
1901
+ - Updated dependencies [5966c2a]
1902
+ - Updated dependencies [2382580]
1903
+ - Updated dependencies [9ea2bc5]
1904
+ - Updated dependencies [a2e157c]
1905
+ - Updated dependencies [95c4227]
1906
+ - Updated dependencies [2a61116]
1907
+ - Updated dependencies [52760bf]
1908
+ - Updated dependencies [5543020]
1909
+ - Updated dependencies [880d343]
1910
+ - Updated dependencies [6e82972]
1911
+ - Updated dependencies [d4df105]
1912
+ - Updated dependencies [4615a18]
1913
+ - Updated dependencies [f505689]
1914
+ - Updated dependencies [d9fa683]
1915
+ - Updated dependencies [32d3800]
1916
+ - Updated dependencies [606d577]
1917
+ - Updated dependencies [4384921]
1918
+ - Updated dependencies [e2798fa]
1919
+ - Updated dependencies [3c628ce]
1920
+ - Updated dependencies [c2d9098]
1921
+ - Updated dependencies [0fd8556]
1922
+ - Updated dependencies [3c7bcc0]
1923
+ - Updated dependencies [4b6cac7]
1924
+ - Updated dependencies [7631964]
1925
+ - Updated dependencies [ac471a0]
1926
+ - Updated dependencies [60ae58e]
1927
+ - Updated dependencies [7f62706]
1928
+ - Updated dependencies [667fa44]
1929
+ - Updated dependencies [37e38d1]
1930
+ - Updated dependencies [e906126]
1931
+ - Updated dependencies [ce92674]
1932
+ - Updated dependencies [08363a0]
1933
+ - Updated dependencies [444de5b]
1934
+ - Updated dependencies [a227ed7]
1935
+ - Updated dependencies [7cb922e]
1936
+ - Updated dependencies [1d22114]
1937
+ - Updated dependencies [1eb13a0]
1938
+ - Updated dependencies [c52e608]
1939
+ - Updated dependencies [9613396]
1940
+ - Updated dependencies [3f7b4ff]
1941
+ - Updated dependencies [74155c7]
1942
+ - Updated dependencies [b5f9397]
1943
+ - Updated dependencies [db0d53c]
1944
+ - Updated dependencies [ed77493]
1945
+ - Updated dependencies [6908830]
1946
+ - Updated dependencies [8b06bba]
1947
+ - Updated dependencies [58a03d2]
1948
+ - Updated dependencies [2bacd1a]
1949
+ - Updated dependencies [e47b342]
1950
+ - Updated dependencies [4c54037]
1951
+ - Updated dependencies [dc530b4]
1952
+ - Updated dependencies [9f601e8]
1953
+ - Updated dependencies [6a9dec6]
1954
+ - Updated dependencies [0f7157b]
1955
+ - Updated dependencies [4dc1c7d]
1956
+ - Updated dependencies [d9bef45]
1957
+ - Updated dependencies [f598aa8]
1958
+ - Updated dependencies [4dfd002]
1959
+ - Updated dependencies [f549a0d]
1960
+ - Updated dependencies [51c5227]
1961
+ - Updated dependencies [82da264]
1962
+ - Updated dependencies [f586f1a]
1963
+ - Updated dependencies [77be690]
1964
+ - Updated dependencies [4ed7ed4]
1965
+ - Updated dependencies [9b9b70f]
1966
+ - Updated dependencies [f5a9bc2]
1967
+ - Updated dependencies [e59786e]
1968
+ - Updated dependencies [2fa4ca1]
1969
+ - Updated dependencies [bcf1112]
1970
+ - Updated dependencies [baeb4f0]
1971
+ - Updated dependencies [29488cc]
1972
+ - Updated dependencies [881a3cc]
1973
+ - Updated dependencies [f5a2320]
1974
+ - Updated dependencies [ad6317b]
1975
+ - Updated dependencies [811c30c]
1976
+ - Updated dependencies [a4a85c8]
1977
+ - Updated dependencies [859cb83]
1978
+ - Updated dependencies [07a4e26]
1979
+ - Updated dependencies [9774b78]
1980
+ - Updated dependencies [8a88885]
1981
+ - Updated dependencies [deb538f]
1982
+ - Updated dependencies [b49ccfd]
1983
+ - Updated dependencies [5b89711]
1984
+ - Updated dependencies [85d95e7]
1985
+ - Updated dependencies [08cd163]
1986
+ - Updated dependencies [0c8a22f]
1987
+ - Updated dependencies [5f7669e]
1988
+ - Updated dependencies [becbe53]
1989
+ - Updated dependencies [b127c8b]
1990
+ - Updated dependencies [763931e]
1991
+ - Updated dependencies [ec975f1]
1992
+ - Updated dependencies [168f60f]
1993
+ - Updated dependencies [b07d829]
1994
+ - Updated dependencies [de9af8a]
1995
+ - Updated dependencies [eb4204b]
1996
+ - Updated dependencies [a80302a]
1997
+ - Updated dependencies [a648e96]
1998
+ - Updated dependencies [a47ac06]
1999
+ - Updated dependencies [e4c61a7]
2000
+ - Updated dependencies [cc60165]
2001
+ - Updated dependencies [474f131]
2002
+ - Updated dependencies [081aa6f]
2003
+ - Updated dependencies [91f4c78]
2004
+ - Updated dependencies [050cd82]
2005
+ - Updated dependencies [4d552af]
2006
+ - Updated dependencies [44d677c]
2007
+ - Updated dependencies [c32944d]
2008
+ - Updated dependencies [1dd780f]
2009
+ - Updated dependencies [e8d0c21]
2010
+ - Updated dependencies [244ca86]
2011
+ - Updated dependencies [546ab3c]
2012
+ - Updated dependencies [c4df271]
2013
+ - Updated dependencies [c8d6f6e]
2014
+ - Updated dependencies [0b51bb6]
2015
+ - Updated dependencies [08f93bc]
2016
+ - Updated dependencies [a1b66ef]
2017
+ - Updated dependencies [d9971d3]
2018
+ - Updated dependencies [7dc1067]
2019
+ - Updated dependencies [4f13be2]
2020
+ - Updated dependencies [a41ba5c]
2021
+ - Updated dependencies [189854c]
2022
+ - Updated dependencies [0e3a226]
2023
+ - Updated dependencies [92a67f2]
2024
+ - Updated dependencies [9136327]
2025
+ - Updated dependencies [bf0ae99]
2026
+ - Updated dependencies [c7e7900]
2027
+ - Updated dependencies [eb3e650]
2028
+ - Updated dependencies [abeb375]
2029
+ - Updated dependencies [cb3b6cd]
2030
+ - Updated dependencies [73b7234]
2031
+ - Updated dependencies [d2b97c3]
2032
+ - Updated dependencies [61cc079]
2033
+ - Updated dependencies [45dc446]
2034
+ - Updated dependencies [0e96e46]
2035
+ - Updated dependencies [c1d44f7]
2036
+ - Updated dependencies [59b794f]
2037
+ - Updated dependencies [ef4efa8]
2038
+ - Updated dependencies [cbb6a5c]
2039
+ - Updated dependencies [fc3a36a]
2040
+ - Updated dependencies [ab9fb5c]
2041
+ - Updated dependencies [69787f0]
2042
+ - Updated dependencies [5d022a1]
2043
+ - Updated dependencies [042b9ee]
2044
+ - Updated dependencies [b25a116]
2045
+ - Updated dependencies [02dc076]
2046
+ - Updated dependencies [f985b3f]
2047
+ - Updated dependencies [795b6e1]
2048
+ - Updated dependencies [d52d4fe]
2049
+ - Updated dependencies [742cebb]
2050
+ - Updated dependencies [175d789]
2051
+ - Updated dependencies [f549a0d]
2052
+ - Updated dependencies [524151c]
2053
+ - Updated dependencies [427344c]
2054
+ - Updated dependencies [8af76ae]
2055
+ - Updated dependencies [1d4756e]
2056
+ - Updated dependencies [720c5ad]
2057
+ - Updated dependencies [a8d1e24]
2058
+ - Updated dependencies [b85cc54]
2059
+ - Updated dependencies [a36db28]
2060
+ - Updated dependencies [7a8476f]
2061
+ - Updated dependencies [518ca7a]
2062
+ - Updated dependencies [d1cabaa]
2063
+ - Updated dependencies [41642b0]
2064
+ - Updated dependencies [4cca74c]
2065
+ - Updated dependencies [88ef03e]
2066
+ - Updated dependencies [9a4932a]
2067
+ - Updated dependencies [3f8817a]
2068
+ - Updated dependencies [a2443e3]
2069
+ - Updated dependencies [e1554b1]
2070
+ - Updated dependencies [9e2caf3]
2071
+ - Updated dependencies [4856789]
2072
+ - Updated dependencies [81ce41a]
2073
+ - Updated dependencies [85e1e4e]
2074
+ - Updated dependencies [c3f4916]
2075
+ - Updated dependencies [55dbbba]
2076
+ - Updated dependencies [33e0385]
2077
+ - Updated dependencies [dac6a08]
2078
+ - Updated dependencies [72c3c86]
2079
+ - Updated dependencies [3670cf9]
2080
+ - Updated dependencies [2d8dba3]
2081
+ - Updated dependencies [7f1a635]
2082
+ - Updated dependencies [2205363]
2083
+ - Updated dependencies [09fe58d]
2084
+ - Updated dependencies [f9fc874]
2085
+ - Updated dependencies [d62f8eb]
2086
+ - Updated dependencies [d0a5ceb]
2087
+ - Updated dependencies [a7586cd]
2088
+ - Updated dependencies [4c5e80e]
2089
+ - Updated dependencies [4b5702a]
2090
+ - Updated dependencies [011b386]
2091
+ - Updated dependencies [e18a162]
2092
+ - Updated dependencies [e98fb14]
2093
+ - Updated dependencies [394b7a1]
2094
+ - Updated dependencies [ce92674]
2095
+ - Updated dependencies [0f2fdcd]
2096
+ - Updated dependencies [d6d1a50]
2097
+ - Updated dependencies [cf2c9b7]
2098
+ - Updated dependencies [8ffa8b9]
2099
+ - Updated dependencies [d127ff0]
2100
+ - Updated dependencies [674ac99]
2101
+ - Updated dependencies [833b512]
2102
+ - Updated dependencies [9881074]
2103
+ - Updated dependencies [1b9a53b]
2104
+ - Updated dependencies [36d90fc]
2105
+ - Updated dependencies [7777e8f]
2106
+ - Updated dependencies [9b86cf6]
2107
+ - Updated dependencies [d063a96]
2108
+ - Updated dependencies [8825a06]
2109
+ - Updated dependencies [5087ac6]
2110
+ - Updated dependencies [677b591]
2111
+ - Updated dependencies [cf7c694]
2112
+ - Updated dependencies [ddd0f06]
2113
+ - Updated dependencies [d77d1b7]
2114
+ - Updated dependencies [0f9faa2]
2115
+ - Updated dependencies [2d1ddf0]
2116
+ - Updated dependencies [354b00f]
2117
+ - Updated dependencies [3de535b]
2118
+ - Updated dependencies [fe2e15a]
2119
+ - Updated dependencies [5b79a34]
2120
+ - Updated dependencies [502564d]
2121
+ - Updated dependencies [603cab8]
2122
+ - Updated dependencies [c757854]
2123
+ - Updated dependencies [471839d]
2124
+ - Updated dependencies [507b92a]
2125
+ - Updated dependencies [46365ab]
2126
+ - Updated dependencies [b508244]
2127
+ - Updated dependencies [df95346]
2128
+ - Updated dependencies [3dede58]
2129
+ - Updated dependencies [c6b6bb4]
2130
+ - Updated dependencies [594508e]
2131
+ - Updated dependencies [7cf42fe]
2132
+ - Updated dependencies [5966c2a]
2133
+ - Updated dependencies [59c544d]
2134
+ - Updated dependencies [0045682]
2135
+ - Updated dependencies [7309c81]
2136
+ - Updated dependencies [2f59da0]
2137
+ - Updated dependencies [7372d46]
2138
+ - Updated dependencies [5e247fd]
2139
+ - Updated dependencies [d56012f]
2140
+ - Updated dependencies [1a53a02]
2141
+ - Updated dependencies [f78dd83]
2142
+ - Updated dependencies [a2cd18a]
2143
+ - Updated dependencies [9051802]
2144
+ - Updated dependencies [20bc1ec]
2145
+ - Updated dependencies [a1686f9]
2146
+ - Updated dependencies [ab07b53]
2147
+ - Updated dependencies [1c625ca]
2148
+ - Updated dependencies [2f8328c]
2149
+ - Updated dependencies [a954634]
2150
+ - Updated dependencies [2a6c279]
2151
+ - Updated dependencies [9319586]
2152
+ - Updated dependencies [8c8f0df]
2153
+ - Updated dependencies [8ad609c]
2154
+ - Updated dependencies [bbee302]
2155
+ - Updated dependencies [90c2b15]
2156
+ - Updated dependencies [4638aaa]
2157
+ - Updated dependencies [0222d3c]
2158
+ - Updated dependencies [08863dd]
2159
+ - Updated dependencies [39eb01b]
2160
+ - Updated dependencies [071d0dc]
2161
+ - Updated dependencies [f293d45]
2162
+ - Updated dependencies [56664f5]
2163
+ - Updated dependencies [71f205d]
2164
+ - Updated dependencies [f067930]
2165
+ - Updated dependencies [414395b]
2166
+ - Updated dependencies [42eeb7d]
2167
+ - Updated dependencies [31cbe90]
2168
+ - Updated dependencies [6b7129a]
2169
+ - Updated dependencies [c5adfe1]
2170
+ - Updated dependencies [97ace2a]
2171
+ - Updated dependencies [26e1029]
2172
+ - Updated dependencies [0a936ea]
2173
+ - Updated dependencies [90bbf25]
2174
+ - Updated dependencies [023c00b]
2175
+ - Updated dependencies [eb91eba]
2176
+ - Updated dependencies [42da73d]
2177
+ - Updated dependencies [01e124d]
2178
+ - Updated dependencies [ef7b5ef]
2179
+ - Updated dependencies [9514767]
2180
+ - Updated dependencies [8f20201]
2181
+ - Updated dependencies [155507e]
2182
+ - Updated dependencies [643b7c7]
2183
+ - Updated dependencies [7bba90b]
2184
+ - Updated dependencies [8813b90]
2185
+ - Updated dependencies [108ba8d]
2186
+ - Updated dependencies [2a5f04a]
2187
+ - Updated dependencies [4f740b0]
2188
+ - Updated dependencies [030125b]
2189
+ - Updated dependencies [7ce02eb]
2190
+ - Updated dependencies [b4ad984]
2191
+ - Updated dependencies [e7a7506]
2192
+ - Updated dependencies [a9f32df]
2193
+ - Updated dependencies [aeb9b27]
2194
+ - Updated dependencies [7d27da0]
2195
+ - Updated dependencies [d0d5205]
2196
+ - Updated dependencies [1a15893]
2197
+ - Updated dependencies [b70e534]
2198
+ - Updated dependencies [7e05d8e]
2199
+ - Updated dependencies [8f1851e]
2200
+ - Updated dependencies [b4b2c7d]
2201
+ - Updated dependencies [fda61e4]
2202
+ - Updated dependencies [61ea810]
2203
+ - Updated dependencies [2233a85]
2204
+ - Updated dependencies [67452d1]
2205
+ - Updated dependencies [089767f]
2206
+ - Updated dependencies [a13827e]
2207
+ - Updated dependencies [66d99ec]
2208
+ - Updated dependencies [cb43296]
2209
+ - Updated dependencies [b61afc1]
2210
+ - Updated dependencies [79021fc]
2211
+ - Updated dependencies [7733604]
2212
+ - Updated dependencies [4921a95]
2213
+ - Updated dependencies [40e420f]
2214
+ - Updated dependencies [62dd69a]
2215
+ - Updated dependencies [d13004a]
2216
+ - Updated dependencies [be7360c]
2217
+ - Updated dependencies [e15e679]
2218
+ - Updated dependencies [2ab1257]
2219
+ - Updated dependencies [0fc6219]
2220
+ - Updated dependencies [061406d]
2221
+ - Updated dependencies [e4c8b6c]
2222
+ - Updated dependencies [acb10f6]
2223
+ - Updated dependencies [605e190]
2224
+ - Updated dependencies [c6c59f1]
2225
+ - Updated dependencies [b0e78a8]
2226
+ - Updated dependencies [f31cc8d]
2227
+ - Updated dependencies [f343dc4]
2228
+ - Updated dependencies [8269e32]
2229
+ - Updated dependencies [74f7339]
2230
+ - Updated dependencies [a6c35a2]
2231
+ - Updated dependencies [c2f1002]
2232
+ - Updated dependencies [4cc4fb7]
2233
+ - Updated dependencies [97b6658]
2234
+ - Updated dependencies [28d1eb7]
2235
+ - Updated dependencies [06770c0]
2236
+ - Updated dependencies [2c26040]
2237
+ - Updated dependencies [f758cec]
2238
+ - Updated dependencies [5b47ab5]
2239
+ - Updated dependencies [b09d8d9]
2240
+ - Updated dependencies [b09d8d9]
2241
+ - Updated dependencies [8675db6]
2242
+ - Updated dependencies [b09d8d9]
2243
+ - Updated dependencies [27358d5]
2244
+ - Updated dependencies [684ab22]
2245
+ - Updated dependencies [1c3da1f]
2246
+ - Updated dependencies [c1f344b]
2247
+ - Updated dependencies [db48ad5]
2248
+ - Updated dependencies [3eb1b2b]
2249
+ - Updated dependencies [9c93465]
2250
+ - Updated dependencies [a34fd2e]
2251
+ - Updated dependencies [ebb209c]
2252
+ - Updated dependencies [76bcb83]
2253
+ - Updated dependencies [59b85c0]
2254
+ - Updated dependencies [889ae47]
2255
+ - Updated dependencies [4f4c3fb]
2256
+ - Updated dependencies [78f0be8]
2257
+ - Updated dependencies [65f184b]
2258
+ - Updated dependencies [6e357ed]
2259
+ - Updated dependencies [d6938bf]
2260
+ - Updated dependencies [35f7fb4]
2261
+ - Updated dependencies [0410522]
2262
+ - Updated dependencies [63b33e6]
2263
+ - Updated dependencies [f163028]
2264
+ - Updated dependencies [814db6d]
2265
+ - Updated dependencies [a5302c7]
2266
+ - Updated dependencies [31e0be9]
2267
+ - Updated dependencies [4bfd455]
2268
+ - Updated dependencies [ffd2ce2]
2269
+ - Updated dependencies [2a44c1d]
2270
+ - Updated dependencies [7084313]
2271
+ - Updated dependencies [f07808c]
2272
+ - Updated dependencies [91cefb8]
2273
+ - Updated dependencies [7ffc3d3]
2274
+ - Updated dependencies [88346ba]
2275
+ - Updated dependencies [4631592]
2276
+ - Updated dependencies [62f8017]
2277
+ - Updated dependencies [32ff033]
2278
+ - Updated dependencies [a831df1]
2279
+ - Updated dependencies [f752ee3]
2280
+ - Updated dependencies [a1b61e0]
2281
+ - Updated dependencies [cd6b9f2]
2282
+ - Updated dependencies [2cb6d3c]
2283
+ - Updated dependencies [af2a095]
2284
+ - Updated dependencies [5ac93d4]
2285
+ - Updated dependencies [695cfbd]
2286
+ - Updated dependencies [0e043d8]
2287
+ - Updated dependencies [93f267f]
2288
+ - Updated dependencies [7445149]
2289
+ - Updated dependencies [ec796d5]
2290
+ - Updated dependencies [071d0dc]
2291
+ - Updated dependencies [0024abf]
2292
+ - Updated dependencies [8dd98bf]
2293
+ - Updated dependencies [e87fea1]
2294
+ - Updated dependencies [c65e529]
2295
+ - Updated dependencies [0848bea]
2296
+ - Updated dependencies [d51bed2]
2297
+ - Updated dependencies [dadd1ad]
2298
+ - Updated dependencies [acbf364]
2299
+ - Updated dependencies [3ca34c1]
2300
+ - Updated dependencies [7adc841]
2301
+ - Updated dependencies [239c3a3]
2302
+ - Updated dependencies [b8b3c64]
2303
+ - Updated dependencies [2f2e63c]
2304
+ - Updated dependencies [4845f85]
2305
+ - Updated dependencies [486d526]
2306
+ - Updated dependencies [94a0bbc]
2307
+ - Updated dependencies [d6bfb3d]
2308
+ - Updated dependencies [8a9c079]
2309
+ - Updated dependencies [7b005b4]
2310
+ - Updated dependencies [cc3555e]
2311
+ - Updated dependencies [a2266a6]
2312
+ - Updated dependencies [d25a0ec]
2313
+ - Updated dependencies [89d7b35]
2314
+ - Updated dependencies [94f7b6a]
2315
+ - Updated dependencies [5c94f83]
2316
+ - Updated dependencies [ea936f3]
2317
+ - Updated dependencies [0c0fbd9]
2318
+ - Updated dependencies [667b83e]
2319
+ - Updated dependencies [f3141d8]
2320
+ - Updated dependencies [5487c20]
2321
+ - Updated dependencies [aa8b847]
2322
+ - Updated dependencies [7687f7b]
2323
+ - Updated dependencies [5a84d41]
2324
+ - Updated dependencies [fd3013a]
2325
+ - Updated dependencies [85ec26d]
2326
+ - Updated dependencies [73e576f]
2327
+ - Updated dependencies [f6476fc]
2328
+ - Updated dependencies [69ac82c]
2329
+ - Updated dependencies [4ac12ef]
2330
+ - Updated dependencies [833ed84]
2331
+ - Updated dependencies [a18abf3]
2332
+ - Updated dependencies [c6a4eeb]
2333
+ - Updated dependencies [1659072]
2334
+ - Updated dependencies [f450ae7]
2335
+ - Updated dependencies [abceb0d]
2336
+ - Updated dependencies [627b188]
2337
+ - Updated dependencies [8d4eae7]
2338
+ - Updated dependencies [c5a5996]
2339
+ - Updated dependencies [0c302a7]
2340
+ - Updated dependencies [b88f5e8]
2341
+ - Updated dependencies [857a6cf]
2342
+ - Updated dependencies [65a3a84]
2343
+ - Updated dependencies [6633337]
2344
+ - Updated dependencies [21676eb]
2345
+ - Updated dependencies [3f296bf]
2346
+ - Updated dependencies [e474853]
2347
+ - Updated dependencies [e9cb9ab]
2348
+ - Updated dependencies [42cc219]
2349
+ - Updated dependencies [d42a92f]
2350
+ - Updated dependencies [569611f]
2351
+ - Updated dependencies [51d74ad]
2352
+ - Updated dependencies [d7e0b42]
2353
+ - Updated dependencies [3510e4a]
2354
+ - Updated dependencies [d5749d7]
2355
+ - Updated dependencies [f00d8d4]
2356
+ - Updated dependencies [5326b36]
2357
+ - Updated dependencies [aa4b90d]
2358
+ - Updated dependencies [ccd9397]
2359
+ - Updated dependencies [503be86]
2360
+ - Updated dependencies [54299ca]
2361
+ - Updated dependencies [51a587d]
2362
+ - Updated dependencies [ae490ef]
2363
+ - Updated dependencies [e124711]
2364
+ - Updated dependencies [dc61def]
2365
+ - Updated dependencies [bca935b]
2366
+ - Updated dependencies [d92c72d]
2367
+ - Updated dependencies [c54c822]
2368
+ - Updated dependencies [8dcc0f5]
2369
+ - Updated dependencies [75b9e51]
2370
+ - Updated dependencies [f61c8cf]
2371
+ - Updated dependencies [e3ef52b]
2372
+ - Updated dependencies [0a2f233]
2373
+ - Updated dependencies [8621cdd]
2374
+ - Updated dependencies [251e888]
2375
+ - Updated dependencies [07f1822]
2376
+ - Updated dependencies [e336549]
2377
+ - Updated dependencies [3bb9340]
2378
+ - Updated dependencies [1e604c4]
2379
+ - Updated dependencies [04fab5e]
2380
+ - Updated dependencies [183b4c4]
2381
+ - Updated dependencies [7f713b6]
2382
+ - Updated dependencies [d40f43a]
2383
+ - Updated dependencies [2fdb36e]
2384
+ - Updated dependencies [e787608]
2385
+ - Updated dependencies [6f23667]
2386
+ - Updated dependencies [cde1975]
2387
+ - Updated dependencies [0bc685a]
2388
+ - Updated dependencies [20526f5]
2389
+ - Updated dependencies [efedd28]
2390
+ - Updated dependencies [5d21a48]
2391
+ - Updated dependencies [5278e11]
2392
+ - Updated dependencies [c5eef1d]
2393
+ - Updated dependencies [e5e7ee0]
2394
+ - Updated dependencies [23dba62]
2395
+ - Updated dependencies [e0f300b]
2396
+ - Updated dependencies [761a0ba]
2397
+ - Updated dependencies [c960170]
2398
+ - Updated dependencies [19365b7]
2399
+ - Updated dependencies [ba98e26]
2400
+ - Updated dependencies [b7ed26d]
2401
+ - Updated dependencies [a2ebea2]
2402
+ - Updated dependencies [800bdb0]
2403
+ - Updated dependencies [9d4dfc4]
2404
+ - Updated dependencies [1059965]
2405
+ - Updated dependencies [def5919]
2406
+ - Updated dependencies [ee264b2]
2407
+ - Updated dependencies [60b672e]
2408
+ - Updated dependencies [f104bab]
2409
+ - Updated dependencies [68dea0b]
2410
+ - Updated dependencies [6b441a8]
2411
+ - Updated dependencies [64f8cbe]
2412
+ - Updated dependencies [6cb81c7]
2413
+ - Updated dependencies [61282f9]
2414
+ - Updated dependencies [c073b8c]
2415
+ - Updated dependencies [ce0cfe9]
2416
+ - Updated dependencies [04f1182]
2417
+ - Updated dependencies [3a2dde7]
2418
+ - Updated dependencies [8c20f75]
2419
+ - Updated dependencies [be87153]
2420
+ - Updated dependencies [dd0f681]
2421
+ - Updated dependencies [60f0dd8]
2422
+ - Updated dependencies [a87c5cd]
2423
+ - Updated dependencies [a47f338]
2424
+ - Updated dependencies [b3a3d83]
2425
+ - Updated dependencies [7a55913]
2426
+ - Updated dependencies [35accbf]
2427
+ - Updated dependencies [6038de7]
2428
+ - Updated dependencies [fc5f536]
2429
+ - Updated dependencies [5647006]
2430
+ - Updated dependencies [e654bfd]
2431
+ - Updated dependencies [01a7337]
2432
+ - Updated dependencies [b45c71e]
2433
+ - Updated dependencies [d71ff32]
2434
+ - Updated dependencies [f8cfbb4]
2435
+ - Updated dependencies [6e6c872]
2436
+ - Updated dependencies [2598216]
2437
+ - Updated dependencies [11949fc]
2438
+ - Updated dependencies [2c7e62d]
2439
+ - Updated dependencies [eb95d97]
2440
+ - Updated dependencies [b098b0e]
2441
+ - Updated dependencies [4d00b13]
2442
+ - Updated dependencies [1363084]
2443
+ - Updated dependencies [fa5758e]
2444
+ - Updated dependencies [38f7e4f]
2445
+ - Updated dependencies [eb7613c]
2446
+ - Updated dependencies [c57f3cf]
2447
+ - Updated dependencies [ecc9110]
2448
+ - Updated dependencies [9aa5510]
2449
+ - Updated dependencies [e4c2dc8]
2450
+ - Updated dependencies [97faca3]
2451
+ - Updated dependencies [57bab76]
2452
+ - Updated dependencies [c89d18c]
2453
+ - Updated dependencies [1bd2795]
2454
+ - Updated dependencies [f7bd4e2]
2455
+ - Updated dependencies [694c350]
2456
+ - Updated dependencies [361bd5b]
2457
+ - Updated dependencies [aac90a5]
2458
+ - Updated dependencies [3da3da5]
2459
+ - Updated dependencies [1e6ab15]
2460
+ - Updated dependencies [b90086a]
2461
+ - Updated dependencies [129b378]
2462
+ - Updated dependencies [88f9d94]
2463
+ - Updated dependencies [8186a70]
2464
+ - Updated dependencies [a329cca]
2465
+ - Updated dependencies [c87ef70]
2466
+ - Updated dependencies [3cb0618]
2467
+ - Updated dependencies [32a0874]
2468
+ - Updated dependencies [6eec18c]
2469
+ - Updated dependencies [4d7bebf]
2470
+ - Updated dependencies [821ac7a]
2471
+ - Updated dependencies [8f81731]
2472
+ - Updated dependencies [7055c22]
2473
+ - Updated dependencies [785a748]
2474
+ - Updated dependencies [3af0354]
2475
+ - Updated dependencies [866ff16]
2476
+ - Updated dependencies [5a85e67]
2477
+ - Updated dependencies [8b50cb3]
2478
+ - Updated dependencies [a0fdc56]
2479
+ - Updated dependencies [946a131]
2480
+ - Updated dependencies [b95577a]
2481
+ - Updated dependencies [0dcbc11]
2482
+ - Updated dependencies [d88f3e9]
2483
+ - Updated dependencies [ad5fe25]
2484
+ - Updated dependencies [c183a12]
2485
+ - Updated dependencies [83c161f]
2486
+ - Updated dependencies [d8c4957]
2487
+ - Updated dependencies [b9f930b]
2488
+ - Updated dependencies [f24cb83]
2489
+ - Updated dependencies [5dbbb92]
2490
+ - Updated dependencies [ea90179]
2491
+ - Updated dependencies [1818998]
2492
+ - Updated dependencies [ce92674]
2493
+ - Updated dependencies [5ef0b5b]
2494
+ - Updated dependencies [8c2db68]
2495
+ - Updated dependencies [22b5e54]
2496
+ - Updated dependencies [0166bd5]
2497
+ - Updated dependencies [3d4c545]
2498
+ - Updated dependencies [bb7cb41]
2499
+ - Updated dependencies [8064b07]
2500
+ - Updated dependencies [09ee21c]
2501
+ - Updated dependencies [4a56dbd]
2502
+ - Updated dependencies [289d04a]
2503
+ - Updated dependencies [f549a0d]
2504
+ - Updated dependencies [48fbacb]
2505
+ - Updated dependencies [06df4fa]
2506
+ - Updated dependencies [3fc2e48]
2507
+ - Updated dependencies [c9b809f]
2508
+ - Updated dependencies [e8f435c]
2509
+ - Updated dependencies [32386f8]
2510
+ - Updated dependencies [9b702dc]
2511
+ - Updated dependencies [ab16331]
2512
+ - Updated dependencies [41610f6]
2513
+ - Updated dependencies [69f1dfd]
2514
+ - Updated dependencies [bbe05de]
2515
+ - Updated dependencies [355e951]
2516
+ - Updated dependencies [a1dd1e4]
2517
+ - Updated dependencies [dadb43f]
2518
+ - Updated dependencies [3556b67]
2519
+ - @objectstack/spec@17.0.0
2520
+ - @objectstack/core@17.0.0
2521
+ - @objectstack/platform-objects@17.0.0
2522
+ - @objectstack/types@17.0.0
2523
+ - @objectstack/metadata-core@17.0.0
2524
+ - @objectstack/metadata-fs@17.0.0
2525
+
3
2526
  ## 17.0.0-rc.6
4
2527
 
5
2528
  ### Major Changes