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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/23298-env-kernel-registry-write-through.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@objectstack/metadata-protocol': patch
---

fix(metadata-protocol): an environment kernel that hydrates its own `sys_metadata` writes a published body through to its engine registry, so a narrowed permission set stops granting its old body without a restart

Clause-②: no

- **What was wrong.** The protocol's registry write-through skipped every type except `object` whenever the protocol had an `environmentId`. A kernel that hydrates its own store at boot (the standalone stack's `env_local`, or a hosted kernel with `hydrateMetadataFromDb`) put every stored body into its registry at boot and then never updated it. A save was accepted and served by `GET /api/v1/meta/:type/:name`, but the engine registry kept the boot-time body until a restart, and held nothing for an item created after boot. The security catalog reads that registry first, so a permission set narrowed through `PUT /api/v1/meta/permission/:name` kept resolving its earlier, wider body, and a permission set published after boot was not in the catalog at all: a user granted it was refused, and an `isDefault` set recorded no `everyone` suggestion.
- **What changed.** The write-through now asks whose registry it is, not whether the kernel has an environment. It registers the published body wherever the registry takes this kernel's stored rows: on an unscoped kernel, as before, and on an environment kernel once the boot has hydrated that kernel's own store into it (`loadMetaFromDb`). The delete's heal re-registers a MetadataService baseline on the same kernels, so a save followed by a reset does not leave the deleted body in the registry.
- **The owner.** The registry entry of a stored row now carries the row's package binding as `_packageId` on the boot and on the write-through, as a list read already stamped it, so a runtime package's `isDefault` set published through the metadata door is owned in the security catalog at once and records its `everyone` suggestion, on a host kernel and on a hydrating environment kernel alike; a row bound to no package stays unowned, and an artifact's own envelope still wins.
- **Unchanged.** An environment kernel whose store is never hydrated into its registry still writes nothing into it, so a registry shared with unscoped callers never receives a project-scoped row. The `object` branch and the organization-scope refusal are unchanged. What the registry holds after a save is what it holds after a restart.
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ const SITES: ReadonlyArray<Site & {
+ 'and a row bound to another package is refused (D9.9, OBJECT_OVERLAY_PACKAGE_MISMATCH)',
},
{
member: 'hydrateOverlayIntoRegistry', args: 'type, mergeArtifactProtection(type, stateTenantAuthorship(data), envelope), \'name\' as any',
// [#23233] The body carries the row's package binding (`withRowBinding`); the disposition is unchanged.
member: 'hydrateOverlayIntoRegistry', args: 'type, mergeArtifactProtection(type, stateTenantAuthorship(withRowBinding(data, options.packageId)), envelope), \'name\' as any',
disposition: 'yes, view only: skips a name another package ships',
path: 'the list\'s hydration and the write-through on an unscoped kernel; the boot\'s loadMetaFromDb on every kernel',
// [#23298] The write-through runs wherever the registry takes the kernel's stored rows.
path: 'the list\'s hydration on an unscoped kernel; the write-through on an unscoped kernel and on an environment kernel that hydrated its own store; the boot\'s loadMetaFromDb on every kernel',
why: 'a stored row: a view row bound to a package is not registered where another package ships its name, and the '
+ 'reads answer it from its row; a package-less row, and every other type, register as before (#4624)',
viewOnlyBy: 'const bound = canonicalType === \'view\' ? boundPackageOf(options.packageId) : undefined;',
Expand Down
Loading
Loading