From 98200caa1001b80b5d47dc2f8df8d195780e5884 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 18:18:05 +0000 Subject: [PATCH] refactor(metadata-protocol): move the authoring gate's deployment-posture probes out of protocol.ts into protocol-authoring-posture-probes.ts (#25467 cut 26) Move-only, shape B. orgWallEnforced, tenancyPostureInForce, rlsMembershipResolverKeys and hierarchyResolverMounted move, unedited, into one module. orgWallEnforced reads no member, so its stub calls the plain function; the other three get the getServicesRegistry field as a getter. postureEnforcesWall, anonymousFormIntakePosture, RLS_MEMBERSHIP_RESOLVER_SERVICE and HIERARCHY_SCOPE_RESOLVER_SERVICE lose their last reader in protocol.ts and leave its imports, with the comments that explained them. Claude-Session: https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6 Co-authored-by: Claude --- .../src/protocol-authoring-posture-probes.ts | 114 ++++++++++++++++++ packages/metadata-protocol/src/protocol.ts | 65 ++++------ 2 files changed, 138 insertions(+), 41 deletions(-) create mode 100644 packages/metadata-protocol/src/protocol-authoring-posture-probes.ts diff --git a/packages/metadata-protocol/src/protocol-authoring-posture-probes.ts b/packages/metadata-protocol/src/protocol-authoring-posture-probes.ts new file mode 100644 index 00000000000..d0926914765 --- /dev/null +++ b/packages/metadata-protocol/src/protocol-authoring-posture-probes.ts @@ -0,0 +1,114 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * [#25467 cut 26] The runtime authoring gate's deployment-posture probes, from + * `ObjectStackProtocolImplementation`, moved out of `protocol.ts` whole: the + * four reads of THIS deployment that `assertRuntimeAuthoringRules` hands the + * gate on every write. + * + * - `orgWallEnforced` — does the configured tenancy posture wall + * organizations (#6285)? Unreadable reads as walled. + * - `tenancyPostureInForce` — the posture the `tenancy` service reports in + * force, for the public-form intake advisory (#21476). + * - `rlsMembershipResolverKeys` — the membership keys the mounted RLS + * resolver declares. + * - `hierarchyResolverMounted` — is a hierarchy resolver mounted (#26188)? + * + * 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 service keys, the + * same `try`s and the same answers when a read throws. + * + * ## 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 each method — the published `dist/*.d.ts` lists its name, + * docblock and declared signature — and its body delegates here. Three read + * the constructor-supplied `getServicesRegistry` field, handed over as a + * getter ({@link AuthoringPostureProbesHost}), as + * `protocol-metadata-service-read.ts` hands that field over, so each reads the + * registry at call time — a resolver registered after this protocol is + * assembled is seen by the next write. `orgWallEnforced` reads no member, so + * its stub calls the plain function. The callers — the closures in + * `assertRuntimeAuthoringRules`'s stub — keep calling the class methods. + * + * What the bodies read from elsewhere, they import from where `protocol.ts` + * imported it; four of those names had no other reader there and left its + * imports, with the comments that explained them. Nothing re-exports this + * module, so the published surface does not move. + */ + +import type { TenancyPosture } from '@objectstack/spec/security'; +// [#6285] ADR-0105 D1's authority on "does this deployment wall organizations?". +// `resolveMultiOrgEnabled()` is DEMOTED and its own doc comment says answering +// this question with it is a bug (cloud#1020, #5233) — so the posture, and only +// the posture, is what the runtime authoring gate is told. +import { postureEnforcesWall } from '@objectstack/spec/security'; +import { resolveTenancyPosture } from '@objectstack/types'; +import { + // [#21476] The posture IN FORCE, read off the `tenancy` service the one way + // the anonymous form doors read it — the runtime authoring gate's input for + // its public-form intake advisory (see `tenancyPostureInForce()`). + anonymousFormIntakePosture, +} from '@objectstack/metadata-core'; +import { RLS_MEMBERSHIP_RESOLVER_SERVICE } from '@objectstack/spec/contracts'; +import { HIERARCHY_SCOPE_RESOLVER_SERVICE } from './runtime-authoring-gate.js'; +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 type on the class is a change + * here too, and naming a member the class does not have is a compile error. + */ +export interface AuthoringPostureProbesHost { + readonly getServicesRegistry: ObjectStackProtocolImplementation['getServicesRegistry']; +} + +/** The body of `ObjectStackProtocolImplementation#orgWallEnforced`; the method's docblock there is its contract. */ +export function orgWallEnforced(): boolean { + try { + return postureEnforcesWall(resolveTenancyPosture()); + } catch { + return true; + } +} + +/** The body of `ObjectStackProtocolImplementation#tenancyPostureInForce`; the method's docblock there is its contract. */ +export function tenancyPostureInForce(this: Pick): TenancyPosture | undefined { + try { + return anonymousFormIntakePosture(this.getServicesRegistry?.().get('tenancy')); + } catch { + return undefined; + } +} + +/** The body of `ObjectStackProtocolImplementation#rlsMembershipResolverKeys`; the method's docblock there is its contract. */ +export function rlsMembershipResolverKeys(this: Pick): readonly string[] | undefined { + const registry = this.getServicesRegistry?.(); + if (!registry) return undefined; + let resolver: unknown; + try { + resolver = registry.get(RLS_MEMBERSHIP_RESOLVER_SERVICE); + } catch { + return undefined; + } + if (resolver === undefined || resolver === null) return []; + const keys = (resolver as { keys?: unknown }).keys; + if (!Array.isArray(keys) || !keys.every((k) => typeof k === 'string')) return undefined; + return keys as readonly string[]; +} + +/** The body of `ObjectStackProtocolImplementation#hierarchyResolverMounted`; the method's docblock there is its contract. */ +export function hierarchyResolverMounted(this: Pick): boolean | undefined { + const registry = this.getServicesRegistry?.(); + if (!registry) return undefined; + try { + const resolver = registry.get(HIERARCHY_SCOPE_RESOLVER_SERVICE); + return resolver !== undefined && resolver !== null; + } catch { + return undefined; + } +} diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index 72fefdac01a..591d390d113 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -5,11 +5,7 @@ import type { } from '@objectstack/spec/api'; import { IDataEngine, engineCanRollBack, recordNotFoundError } from '@objectstack/core'; import { declaredHttpStatus, declaredUserMessage, isSandboxOrigin, resolveTenancyPosture, resolveThrownHttpError, sandboxBusinessMessage } from '@objectstack/types'; -// [#6285] ADR-0105 D1's authority on "does this deployment wall organizations?". -// `resolveMultiOrgEnabled()` is DEMOTED and its own doc comment says answering -// this question with it is a bug (cloud#1020, #5233) — so the posture, and only -// the posture, is what the runtime authoring gate is told. -import { judgePermissionSetOwnerBinding, postureEnforcesWall, type TenancyPosture } from '@objectstack/spec/security'; +import { judgePermissionSetOwnerBinding, type TenancyPosture } from '@objectstack/spec/security'; // [commit 376c70f98] The derived `version` this file's `getDiscovery()` serves as the // `DiscoverySchema` "System Identity" field — never a literal again. import { resolveDiscoveryVersion } from './discovery-version.js'; @@ -27,7 +23,6 @@ import { stripUnauthorableProperties } from './unauthorable-nodes.js'; // admits anything. See the module header for the predicate and its controls. import { markErasedAuthoringInput } from './erased-authoring-mark.js'; import { - HIERARCHY_SCOPE_RESOLVER_SERVICE, SDUI_MANIFEST_SERVICE, isUsableSduiManifest, stampHtmlPageRequires, @@ -105,10 +100,6 @@ import { // {@link ObjectStackProtocolImplementation.getMetaItemLayered}'s code-layer // fallback so a hydrated row is never answered as the code layer. isTenantAuthored, - // [#21476] The posture IN FORCE, read off the `tenancy` service the one way - // the anonymous form doors read it — the runtime authoring gate's input for - // its public-form intake advisory (see `tenancyPostureInForce()`). - anonymousFormIntakePosture, // [ADR-0131 D6/D7] A legacy organization's rows, off the physical column, // on a database the ceremony has not migrated — see migrateStoredMetadata. readLegacyOrganizationRows, @@ -166,7 +157,7 @@ import { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL, canonicalMetaUrlType, metaUrlSp // depends on `@objectstack/service-cluster`; a bridge plugin there hands the // live transport in through `attachMetadataMutationPubSub`. import type { IFieldFileService, IPubSub } from '@objectstack/spec/contracts'; -import { FIELD_FILE_SERVICE, RLS_MEMBERSHIP_RESOLVER_SERVICE } from '@objectstack/spec/contracts'; +import { FIELD_FILE_SERVICE } from '@objectstack/spec/contracts'; import { applyConversionsToFlow, type ConversionNotice, type ConversionTodoNotice } from '@objectstack/spec'; import { type FormView, type I18nLabel, isAggregatedViewContainer, expandViewContainer } from '@objectstack/spec/ui'; // [commit ece4dad31] Emitted-specifier pin. This module's inferred public declarations @@ -419,6 +410,8 @@ import { listLiveMetadataTypes } from './protocol-live-metadata-types.js'; import { saveConflict } from './protocol-save-conflict.js'; // [#25467 cut 25] The post-persistence mutation fan-out: projector, local listeners, cluster publish, publish listeners. import { emitMetaItemPublished, notifyMutationListenersLocal, publishMetadataMutation, runMutationProjector } from './protocol-mutation-fan-out.js'; +// [#25467 cut 26] The runtime authoring gate's deployment-posture probes: org wall, tenancy, RLS keys, hierarchy. +import { hierarchyResolverMounted, orgWallEnforced, rlsMembershipResolverKeys, tenancyPostureInForce } from './protocol-authoring-posture-probes.js'; /** * Canonical Zod schema per metadata type lives in @@ -7392,11 +7385,8 @@ export class ObjectStackProtocolImplementation implements * refusal combination to be turned away. */ private orgWallEnforced(): boolean { - try { - return postureEnforcesWall(resolveTenancyPosture()); - } catch { - return true; - } + // [#25467 cut 26] The body moved to `protocol-authoring-posture-probes.ts`; it reads no member. + return orgWallEnforced(); } /** @@ -7426,11 +7416,12 @@ export class ObjectStackProtocolImplementation implements * reports none, and the advisory is simply not raised. */ private tenancyPostureInForce(): TenancyPosture | undefined { - try { - return anonymousFormIntakePosture(this.getServicesRegistry?.().get('tenancy')); - } catch { - return undefined; - } + // [#25467 cut 26] The body moved to `protocol-authoring-posture-probes.ts` (see `AuthoringPostureProbesHost`). + const readGetServicesRegistry = () => this.getServicesRegistry; + const host: ThisParameterType = { + get getServicesRegistry() { return readGetServicesRegistry(); }, + }; + return tenancyPostureInForce.call(host); } /** @@ -7448,18 +7439,12 @@ export class ObjectStackProtocolImplementation implements * wrong ones. */ private rlsMembershipResolverKeys(): readonly string[] | undefined { - const registry = this.getServicesRegistry?.(); - if (!registry) return undefined; - let resolver: unknown; - try { - resolver = registry.get(RLS_MEMBERSHIP_RESOLVER_SERVICE); - } catch { - return undefined; - } - if (resolver === undefined || resolver === null) return []; - const keys = (resolver as { keys?: unknown }).keys; - if (!Array.isArray(keys) || !keys.every((k) => typeof k === 'string')) return undefined; - return keys as readonly string[]; + // [#25467 cut 26] The body moved to `protocol-authoring-posture-probes.ts` (see `AuthoringPostureProbesHost`). + const readGetServicesRegistry = () => this.getServicesRegistry; + const host: ThisParameterType = { + get getServicesRegistry() { return readGetServicesRegistry(); }, + }; + return rlsMembershipResolverKeys.call(host); } /** @@ -7476,14 +7461,12 @@ export class ObjectStackProtocolImplementation implements * throws. */ private hierarchyResolverMounted(): boolean | undefined { - const registry = this.getServicesRegistry?.(); - if (!registry) return undefined; - try { - const resolver = registry.get(HIERARCHY_SCOPE_RESOLVER_SERVICE); - return resolver !== undefined && resolver !== null; - } catch { - return undefined; - } + // [#25467 cut 26] The body moved to `protocol-authoring-posture-probes.ts` (see `AuthoringPostureProbesHost`). + const readGetServicesRegistry = () => this.getServicesRegistry; + const host: ThisParameterType = { + get getServicesRegistry() { return readGetServicesRegistry(); }, + }; + return hierarchyResolverMounted.call(host); } /**