From 4d5121beec3021ef9e95eb07ef0c157de77b8ff6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 16:30:18 +0000 Subject: [PATCH] refactor(metadata-protocol): move the object-owner verdicts out of protocol.ts (#25467 cut 17) Move the bodies of classifyObjectContribution and releaseAdoptedObjectOwner unedited into protocol-object-owner.ts, behind an ObjectOwnerHost that hands over the engine as a getter. The class keeps both methods, their docblocks and declared signatures; each body becomes a host literal plus a .call. Claude-Session: https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6 Co-authored-by: Claude --- .../src/protocol-object-owner.ts | 111 ++++++++++++++++++ packages/metadata-protocol/src/protocol.ts | 65 +++------- 2 files changed, 125 insertions(+), 51 deletions(-) create mode 100644 packages/metadata-protocol/src/protocol-object-owner.ts diff --git a/packages/metadata-protocol/src/protocol-object-owner.ts b/packages/metadata-protocol/src/protocol-object-owner.ts new file mode 100644 index 00000000000..adf947f10e1 --- /dev/null +++ b/packages/metadata-protocol/src/protocol-object-owner.ts @@ -0,0 +1,111 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * [#25467 cut 17] The object-owner verdicts of + * `ObjectStackProtocolImplementation`: which contributor kind a stored object + * row registers as against the registry's owner of that name + * (`classifyObjectContribution`), and handing an adopted object's name from the + * `'sys_metadata'` sentinel owner to its rebound row + * (`releaseAdoptedObjectOwner`), moved out of `protocol.ts` whole. + * + * A MOVE, not a redesign. Each function below is the body of the + * `ObjectStackProtocolImplementation` method of the same name as it stood, + * re-indented one level and otherwise unedited: the same registry reads in the + * same order, the same verdicts, and the same logged line. + * + * ## How the moved bodies reach the class + * + * The shape #23050 set for `rest-server.ts` (its `data-action-routes.ts` + * header carries the full reasoning), as `protocol-batch-data.ts` applies it. + * The class keeps every method — the published `dist/*.d.ts` lists each one's + * name, docblock and declared signature — and each body is one literal of + * exactly the {@link ObjectOwnerHost} members that method reads, plus a + * `.call`. The engine is handed over as a getter, so the body reads the + * protocol's engine (and so its registry) at call time. Every other reader of + * these two methods keeps calling the class method. + * + * Neither body reads a module-level declaration of `protocol.ts`, and neither + * imports anything, so this module imports only the class type. Nothing + * re-exports this module, so the published surface does not move. + */ + +import type { ObjectStackProtocolImplementation } from './protocol.js'; + +/** + * The `ObjectStackProtocolImplementation` members the functions below read — + * every one of them, and no other; each function's `this` is the `Pick` of the + * members IT reads. Each is typed AS the class member (an indexed access on + * the class), so a change to a member's signature on the class is a change + * here too, and naming a member the class does not have is a compile error. + */ +export interface ObjectOwnerHost { + readonly engine: ObjectStackProtocolImplementation['engine']; +} + +/** The body of `ObjectStackProtocolImplementation#classifyObjectContribution`; the method's docblock there is its contract. */ +export function classifyObjectContribution( + this: Pick, + name: string, + rowPackageId: string | null | undefined, +): + | { kind: 'own' | 'overlay'; packageId: string } + | { kind: 'mismatch'; packageId: string; ownerPackageId: string } { + // `||`, not `??`: an empty-string binding is "no package", the same + // normalisation both hydration seams apply to `package_id`. + const binding = rowPackageId || ''; + const registry: any = (this.engine as any)?.registry; + const owner = typeof registry?.getPackagedObjectOwner === 'function' + ? registry.getPackagedObjectOwner(name) + : undefined; + const ownerPackageId: unknown = owner?.packageId; + if (typeof ownerPackageId !== 'string' || ownerPackageId === '') { + // [#23416] No packaged owner: the runtime-authored one, if any. + const runtimeOwner = typeof registry?.getObjectOwner === 'function' + ? registry.getObjectOwner(name) + : undefined; + const runtimeOwnerPackageId: unknown = runtimeOwner?.packageId; + // [#23514] The `'sys_metadata'` sentinel owner is judged too: see the header. + if ( + binding !== '' + && typeof runtimeOwnerPackageId === 'string' + && runtimeOwnerPackageId !== '' + && binding !== runtimeOwnerPackageId + ) { + return { kind: 'mismatch', packageId: binding, ownerPackageId: runtimeOwnerPackageId }; + } + return { kind: 'own', packageId: binding || 'sys_metadata' }; + } + if (binding !== '' && binding !== ownerPackageId) { + return { kind: 'mismatch', packageId: binding, ownerPackageId }; + } + return { kind: 'overlay', packageId: binding || 'sys_metadata' }; +} + +/** The body of `ObjectStackProtocolImplementation#releaseAdoptedObjectOwner`; the method's docblock there is its contract. */ +export function releaseAdoptedObjectOwner( + this: Pick, + name: string, item: unknown, targetPackageId: string, +): boolean { + const registry: any = (this.engine as any)?.registry; + if (typeof registry?.registerObject !== 'function') return false; + if (!item || typeof item !== 'object' || typeof (item as { name?: unknown }).name !== 'string') return false; + const owner: { packageId?: unknown } | undefined = typeof registry.getObjectOwner === 'function' + ? registry.getObjectOwner(name) + : undefined; + if (owner === undefined) return true; + if (owner.packageId !== 'sys_metadata' || typeof registry.unregisterObject !== 'function') return false; + try { + registry.unregisterObject(name); + return true; + } catch (err: any) { + console.error( + `[Protocol] reassignOrphanedMetadata: object '${name}' was adopted into package ` + + `'${targetPackageId}' in sys_metadata, but the registry keeps its package-less definition ` + + `(owner 'sys_metadata') until the next restart: ${err?.message ?? err}. Until then the data ` + + `plane serves the pre-adoption definition, and a save of the object naming '${targetPackageId}' ` + + 'answers success without going live. Fix: restart the server; the boot registers the object ' + + `under '${targetPackageId}'.`, + ); + return false; + } +} diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index e600ddae370..96e0352065c 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -397,6 +397,8 @@ import { applySeedBodies, collectBatchPendingDeclarations } from './protocol-pub import { artifactLockLayerAt, lookupArtifactItem, shippedArtifactsOf, shippingPackagesOf } from './protocol-artifact-lookup.js'; // [#25467 cut 16] The stored-overlay row reads: a type's active rows, their parsed entries, the names with a row of their own, and one item's rows. import { namesWithOwnStoredRow, readActiveOverlayRows, readItemActiveRows, storedOverlayEntries } from './protocol-stored-overlay-reads.js'; +// [#25467 cut 17] The object-owner verdicts: which contributor kind a stored object row registers as, and handing an adopted object's name to its rebound row. +import { classifyObjectContribution, releaseAdoptedObjectOwner } from './protocol-object-owner.js'; /** * Canonical Zod schema per metadata type lives in @@ -17515,35 +17517,12 @@ export class ObjectStackProtocolImplementation implements ): | { kind: 'own' | 'overlay'; packageId: string } | { kind: 'mismatch'; packageId: string; ownerPackageId: string } { - // `||`, not `??`: an empty-string binding is "no package", the same - // normalisation both hydration seams apply to `package_id`. - const binding = rowPackageId || ''; - const registry: any = (this.engine as any)?.registry; - const owner = typeof registry?.getPackagedObjectOwner === 'function' - ? registry.getPackagedObjectOwner(name) - : undefined; - const ownerPackageId: unknown = owner?.packageId; - if (typeof ownerPackageId !== 'string' || ownerPackageId === '') { - // [#23416] No packaged owner: the runtime-authored one, if any. - const runtimeOwner = typeof registry?.getObjectOwner === 'function' - ? registry.getObjectOwner(name) - : undefined; - const runtimeOwnerPackageId: unknown = runtimeOwner?.packageId; - // [#23514] The `'sys_metadata'` sentinel owner is judged too: see the header. - if ( - binding !== '' - && typeof runtimeOwnerPackageId === 'string' - && runtimeOwnerPackageId !== '' - && binding !== runtimeOwnerPackageId - ) { - return { kind: 'mismatch', packageId: binding, ownerPackageId: runtimeOwnerPackageId }; - } - return { kind: 'own', packageId: binding || 'sys_metadata' }; - } - if (binding !== '' && binding !== ownerPackageId) { - return { kind: 'mismatch', packageId: binding, ownerPackageId }; - } - return { kind: 'overlay', packageId: binding || 'sys_metadata' }; + // [#25467 cut 17] The body moved to `protocol-object-owner.ts` (see `ObjectOwnerHost`). + const readEngine = () => this.engine; + const host: ThisParameterType = { + get engine() { return readEngine(); }, + }; + return classifyObjectContribution.call(host, name, rowPackageId); } /** @@ -26123,28 +26102,12 @@ export class ObjectStackProtocolImplementation implements * for a body with no name, which register nothing. */ private releaseAdoptedObjectOwner(name: string, item: unknown, targetPackageId: string): boolean { - const registry: any = (this.engine as any)?.registry; - if (typeof registry?.registerObject !== 'function') return false; - if (!item || typeof item !== 'object' || typeof (item as { name?: unknown }).name !== 'string') return false; - const owner: { packageId?: unknown } | undefined = typeof registry.getObjectOwner === 'function' - ? registry.getObjectOwner(name) - : undefined; - if (owner === undefined) return true; - if (owner.packageId !== 'sys_metadata' || typeof registry.unregisterObject !== 'function') return false; - try { - registry.unregisterObject(name); - return true; - } catch (err: any) { - console.error( - `[Protocol] reassignOrphanedMetadata: object '${name}' was adopted into package ` - + `'${targetPackageId}' in sys_metadata, but the registry keeps its package-less definition ` - + `(owner 'sys_metadata') until the next restart: ${err?.message ?? err}. Until then the data ` - + `plane serves the pre-adoption definition, and a save of the object naming '${targetPackageId}' ` - + 'answers success without going live. Fix: restart the server; the boot registers the object ' - + `under '${targetPackageId}'.`, - ); - return false; - } + // [#25467 cut 17] The body moved to `protocol-object-owner.ts` (see `ObjectOwnerHost`). + const readEngine = () => this.engine; + const host: ThisParameterType = { + get engine() { return readEngine(); }, + }; + return releaseAdoptedObjectOwner.call(host, name, item, targetPackageId); } // ─────────────────────────────────────────────────────────────────────