Skip to content

Commit 161e00e

Browse files
refactor(metadata-protocol): move the authoring gate's deployment-posture probes out of protocol.ts into protocol-authoring-posture-probes.ts (#25467 cut 26) (#26873)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X7JKgiM29GUcitRvoGGj5BU)_ Part of #25467 Clause-②: no Metadata-protocol: none Ruling-ref: 6270097840 Cut 26 of the `protocol.ts` split (#24586 ruling A, recorded in `6270097840`). Cuts 1–25 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719, #25731, #25740, #25797, #25939, #25972, #26014, #26104, #26168, #26264, #26317, #26350, #26366, #26404, #26449, #26654, #26704 and #26807. The claim is `6449989109`. It takes the cut 26 proposal of the cut 25 report (`6448075845`), a themed group. All four members passed the AST screen and the read-seam census, and all four moved unedited. Four `ObjectStackProtocolImplementation` members move unedited into one new module, `packages/metadata-protocol/src/protocol-authoring-posture-probes.ts` (#23050 shape B). They are the runtime authoring gate's deployment-posture probes: the four reads of this deployment that `assertRuntimeAuthoringRules` hands the gate on every write. - `orgWallEnforced`: does the configured tenancy posture wall organizations (#6285)? An unreadable posture 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)? `protocol.ts` loses 17 net lines (29,127 to 29,110). The cut is move-only, and the card stays open for later cuts. Branch `claude/project-thread-ybjm3h` is the harness-pinned branch for this thread. It was restarted from `main` `aeb9cf0ddc` (after cut 25's merge). It names no issue, so the claim comment is the identity record. It holds the move commit `98200caa10` and one merge of `main`, `a885bdbbdf` (10 commits). Unlike the last cuts, **this merge does touch `packages/metadata-protocol/`**. It brings in #26811 (#26644, `findData`/`getData` in `protocol.ts`), which merged while this cut was being measured, and the seed-loader change. `protocol.ts` auto-merged with no conflict. So the merged tree was measured against `main` itself as well (see "The merged tree against `main`"). ## Serial with the file's in-flight PRs Measured just before this PR was opened, against the merged head `a885bdbbdf`: of the 38 open PRs, two edit `protocol.ts`, and each one's `git merge-tree` is clean against this head. - **#26856** (the draft verdict harness) edits `protocol-authoring-gate.ts` and `protocol.ts`. It names three of the moved members, in two ways: - it adds a `RuntimeRulesHost` `Pick` over `AuthoringGateHost` that lists them; - it adds a second set of `(...a) => this.hierarchyResolverMounted(...a)`-style closures in `protocol.ts`. Both reach the class methods, which this cut keeps as stubs with unchanged signatures. Its merge with this head was built and type-checked: `git merge-tree` to a tree, then `tsc --noEmit` in `packages/metadata-protocol` on that tree exits 0. - **#26861** edits `protocol.ts` around `:11300–11500` (the `lookupArtifactItem` call sites), far from this cut's hunks. Two more touch only test files of this package, each merge-tree clean against head: #26851 (2 files) and #26831 (1). #26811, which the claim asked about, had merged before this PR was opened, so it is part of the merge above rather than an overlap. `main` moved six commits after the merge: #26742, #26769, #26799, #26823, #26827 and #26829. Two edit test files of this package: `protocol.item-name-grammar.test.ts` (#26769) and `runtime-authoring-gate.email-template-writes.test.ts` (#26799). None edits `protocol.ts` or the gate's source, and `git merge-tree` with this head is clean. The derivation found nothing stale, so `main` was not merged again. The PR's merge ref runs those tests against this cut. ## The order's screen (private statics, `new Error(`, the read-seam census) All four members passed: - **No private-static read.** The AST walk found no access on the `ObjectStackProtocolImplementation` identifier and none on `this.constructor`. - **No error constructed.** No body has `new Error(`, so no `PRODUCERS` row is owed. - **Read-seam census.** `check-durability-degradation-log-level --list` was taken on the base and again on the cut: `88 read seam(s)` and an identical list once `[@:][0-9]+` is stripped. The full readings are under "The durability `--list`". ## What moves | Member | Base line | Visibility | Body lines | AST tokens (params + return + body) | Reads | |---|---|---|---|---|---| | `orgWallEnforced` | `:7394` | private | 5 | 24 | no member | | `tenancyPostureInForce` | `:7428` | private | 5 | 34 | `getServicesRegistry` (field) | | `rlsMembershipResolverKeys` | `:7450` | private | 12 | 120 | `getServicesRegistry` (field) | | `hierarchyResolverMounted` | `:7478` | private | 8 | 55 | `getServicesRegistry` (field) | `protocol-authoring-posture-probes.ts` has 114 lines. ## The shape (#23050 shape B, as cuts 1–25 apply it) - **The class keeps all four members.** Each keeps its name, docblock, signature and position. Each body now delegates, marked `[#25467 cut 26]`. - **`orgWallEnforced` reads no member.** This was measured by an AST walk over `this` accesses, with `as` casts, parentheses and `!` stripped, and bare `this` checked. So its stub is a plain call, `return orgWallEnforced();`, as cuts 16, 21 and 23 did. - **The other three read only the constructor-supplied `getServicesRegistry` field.** `AuthoringPostureProbesHost` types it as the class member itself (`ObjectStackProtocolImplementation['getServicesRegistry']`). Each stub hands it over as a getter (the hand-over of cuts 20 and 24). So each probe reads the registry at call time, and a resolver registered after this protocol is assembled is seen by the next write, as before. - **No member changes visibility.** - **Every caller still calls the class method.** The only callers are the four closures in cut 8's `assertRuntimeAuthoringRules` stub (`:7102`, `:7103`, `:7106`, `:7107`, base). They do not change. ## Module-level declarations, by reader: four imports leave `protocol.ts` Measured by an AST walk over each body's identifiers, against `protocol.ts`'s top-level declarations and import bindings (property names excluded): | Name | Where `protocol.ts` imported it | Other readers in `protocol.ts` | What this cut does | |---|---|---|---| | `postureEnforcesWall` | `@objectstack/spec/security` | **none** (docblock mentions only) | **dropped from that import**; the new module imports it from there | | `anonymousFormIntakePosture` | `@objectstack/metadata-core` | **none** | **dropped from that import** | | `RLS_MEMBERSHIP_RESOLVER_SERVICE` | `@objectstack/spec/contracts` | **none** | **dropped from that import** (`FIELD_FILE_SERVICE` stays) | | `HIERARCHY_SCOPE_RESOLVER_SERVICE` | `./runtime-authoring-gate.js` | **none** | **dropped from that import** | | `resolveTenancyPosture` | `@objectstack/types` | yes (`:15259`) | stays; imported from there too | | `TenancyPosture` (type) | `@objectstack/spec/security` | yes (the stub's declared return type) | stays; imported as a type too | **Two import comments move with their imports.** Each explained exactly the name that left: - the `[#6285]` comment over the `@objectstack/spec/security` import ("ADR-0105 D1's authority on 'does this deployment wall organizations?'… the posture, and only the posture, is what the runtime authoring gate is told"); - the `[#21476]` comment over `anonymousFormIntakePosture` inside the `@objectstack/metadata-core` import. Both now sit, byte-identical, over the same imports in the new module. Leaving them in `protocol.ts` would have left two comments explaining imports it no longer has. ## The published `.d.ts` The series bar is ruling `6348891160`: sorted-union identity, measured first, with raw identity reported when it holds. **Both hold for this cut**, including on the merged tree against `main`. As the claim warned, four imports leave `protocol.ts` and nine lines leave the import block, so the emitted specifiers and the cut 25 comment-offset trap were watched. Neither moved: the raw hash is identical at both ends. Base `aeb9cf0ddc` and head `98200caa10` were built with `pnpm --filter @objectstack/metadata-protocol build` against one dependency tree. The closure was built at head by a full `pnpm build` (116/116). For the base build, the package's `src/` was swapped tree-only to base (the new module removed) and restored from `HEAD` under a trap. Afterwards `git diff HEAD` was empty, `git status` was clean, and both touched blobs equalled `HEAD`'s. - **Normalised sha256** (the ruling's bar): `6de2d665caaedb0a17a1db1c77288a3a8108df3e78edb5f7d4e490f09fbccde4` at both ends, on `dist/index.d.ts` and `dist/index.d.cts`. The normaliser sorted 80 string-literal union runs. **Sorted-union identity holds.** - **Raw sha256**: `b73f06345eff65ebf7ffa0180caf62d8b3ebb16a29931060a115b6d949d4e203` at both ends, 777,497 bytes. **Raw identity holds.** - **The two builds differ where they should.** - `index.js` hashes `f40d769609a618d7…` at base and `fda9ac7d969284cc…` at head (`index.cjs` `4144276cf3ac53bc…` → `736a7dde582b26bb…`). - `….call(host` goes 0 → 1 for the three host-reading members in both bundles, and `orgWallEnforced` has no `.call`. - Each `function …(` declaration goes 0 → 1. - **Positive control** on the head tree. `private` was dropped from `rlsMembershipResolverKeys` on disk (restored from `HEAD` under a trap) and the package rebuilt. Both `.d.ts` files move to `90baecbeff563e05…` (normalised `f75a46f7798ad08d…`, which differs from the bar's `6de2d665…`) and print `rlsMembershipResolverKeys(): readonly string[] | undefined;`. So the bar can see a change on these members. - **Export tables.** The ESM and CJS bundles, loaded at runtime from the package, each export the same 78 names at base and head. The sorted list hashes to `9f18a0177ac736ed…` on all of them. None of the four functions, and not `AuthoringPostureProbesHost`, appears. ### The merged tree against `main` The merge brought in #26811's `protocol.ts` change, so the merged tree `a885bdbbdf` was measured against `main` `e1bfde3601`, its second parent, in the same way. The closure was rebuilt by a full `pnpm build` (116/116), and `main`'s package `src/` was swapped in tree-only and restored under a trap. The merged tree differs from `main` only by this cut: 2 files, +138 / −41. - **`.d.ts`**: raw `455d53280fb9c922…` (777,661 bytes) and normalised `c5edca148ff159d4…`, the same at `main` and merged. Both identities hold. - **`index.js`**: `c2202f9429366633…` at `main`, `c9423dc904760728…` merged. Export tables: 78 names, `9f18a0177ac736ed…` on both. - **AST compare**, `main`'s `protocol.ts` against the merged one: 233 tokens, 0 differ. - **Suite**: `main` and merged both give 21,688 tests (21,662 passed, 0 failed, 26 skipped, 274 files), per test identical. No changeset: `skip-changeset`, per ruling `6348891160`. ## The durability `--list` `check-durability-degradation-log-level` exits 0 at both ends with identical output: `88 read seam(s)` at both ends. Its `--list` is **identical once `[@:][0-9]+` is stripped** (164 lines, and no entry changes file). - Each moved `try` guards a service-registry `get` or the posture read, none in the gate's read vocabulary (`find`/`findOne`/`count`). - No seam enters or leaves the census. - On the merged tree the verdict still reads `88 read seam(s)`. ## Proof of move-only **Bodies, by AST token.** The compare reads the TypeScript compiler's AST leaf tokens, with JSDoc skipped, over the parameters (less the added `this`), the return type and the body. It compares base `protocol.ts` at `aeb9cf0ddc` against the head module: ``` orgWallEnforced: base tokens 24, head tokens 24, differ 0, text-equal(ws-collapsed, comments incl.) true tenancyPostureInForce: base tokens 34, head tokens 34, differ 0, text-equal(ws-collapsed, comments incl.) true rlsMembershipResolverKeys: base tokens 120, head tokens 120, differ 0, text-equal(ws-collapsed, comments incl.) true hierarchyResolverMounted: base tokens 55, head tokens 55, differ 0, text-equal(ws-collapsed, comments incl.) true TOTAL tokens=233 differ=0 members=4 CLASS members base=301 head=301 order-same=true byte-identical=297 stubs-with-identical-docblock+signature=4 other=[] TOP base=255 head=256 byte-identical=251 ``` **Negative controls**, one per member. Each changed one token in an in-memory copy of the head module, and only the mutated member moved (differ 1; the other three stayed 0): ``` "return true;" -> "return false;" orgWallEnforced differ 1 "get('tenancy')" -> "get('tenant')" tenancyPostureInForce differ 1 "typeof k === 'string'" -> "typeof k === 'number'" rlsMembershipResolverKeys differ 1 "resolver !== undefined && resolver !== null" -> "… || …" hierarchyResolverMounted differ 1 ``` - **Class side.** There are 301 members at base and at head, in the same order. The 297 that did not move are byte-identical to base, leading comments included. All four stubs sit at their base positions and keep their docblocks and signatures byte-identical. - **Top level of `protocol.ts`.** Of the 255 base statements, 251 are byte-identical, leading comments included. The other four are the import statements that each lost one name: - `@objectstack/spec/security` (with its `[#6285]` comment); - `./runtime-authoring-gate.js`; - `@objectstack/metadata-core` (with the `[#21476]` comment inside it); - `@objectstack/spec/contracts`. Nothing else changed in any of them. Head adds one statement, the new module's import. - **Size.** +138 / −41 over 2 files, 179 changed lines (the proposal's estimate was 160–220). ## Readers of the moved lines The tests and scripts that read `protocol.ts` by path were grepped for the four member names, the four dropped imports and the two moved comments' tags (`#6285`, `#21476`). These are the 26 files that name `protocol.ts` and call `readFileSync`. So were the test files in `packages/`, `docs/qa/` and the `@objectstack/spec` liveness ledgers. - **By path: none reads a moved line.** The one textual hit is `orgWallEnforced` in `protocol.bracketed-refusal-opener-absence.test.ts`. It is the gate's input key, passed to `evaluateRuntimeAuthoringGate`, not the member. - **Behaviour through the gate, no edit owed.** These pass the same keys as gate inputs, or drive the gate through the protocol: - `protocol.platform-schedule-org-gate.test.ts`, `runtime-authoring-gate.public-form-intake.test.ts` and `runtime-authoring-gate.hierarchy-scope.test.ts` (metadata-protocol); - `validate-membership-key-declarations.test.ts` and `validate-rls-predicate-enforceability.membership-keys.test.ts` (lint); - `explain-engine.test.ts` (plugin-security). The metadata-protocol ones pass at both ends, in the package suite. - **The liveness ledgers.** No row points at any of the four. Carried forward: `manifest.json`'s `dependencies` row → `protocol.ts#resolveWritePackageScope` (cut 19), and `seed.json` → `protocol.ts#applySeedBodies` (cut 14). These are `packages/spec/**`, outside this card. - **ADR anchors.** `check-adr-anchors` exits 0 at both ends. - **The decision-letter count goes 1,898 → 1,897.** The moved `[#6285]` comment cites `ADR-0105 D1` and left the anchored `protocol.ts`, as in cuts 8 and 12. - The citation count is unchanged at 59,994, since that citation now counts in the new module, which has no ADR in its own header. The file count goes 8,405 → 8,406. - **Engine-double contract** (`check-engine-double-contract`): exits 0 at both ends. - The `updateData`/`deleteData` seams keep their entries at new line numbers (13838 → 13821, 13863 → 13846). - `productionFiles` is 3,804 at base and 3,806 at head. The +2 are the new file and the gitignored `packages/spec/src/migrations/registry.ts` that the spec build writes in the head tree, as in cuts 20–25. - **Identical output at both ends:** `check-adr-symbol-anchors`, `check-system-context-census`, `check-tenant-audit-census`, `check-query-options-erasure-ratchet`, `check-filter-alias-parity` and `check-durability-degradation-log-level`. `check-nul-bytes` differs only in the scanned file count, +1. The base-end readings were taken in a clean detached worktree at `aeb9cf0ddc`. ## Verification - `tsc --noEmit` in `packages/metadata-protocol` exits 0 at head and on the merged tree. - `pnpm exec eslint --no-inline-config packages/metadata-protocol` exits 0 with no output (the whole touched package, with the root `lint` script's flags). - **`packages/metadata-protocol` suite**, compared per test name and status. Each leg ran vitest `--maxWorkers=2` with the JSON reporter under the shared verify lock. - Base `aeb9cf0ddc`: 21,648 tests (21,622 passed, 0 failed, 26 skipped), 273 files. - Head `98200caa10`: 21,648 tests (21,622 passed, 0 failed, 26 skipped), 273 files. - None lost, none added, none changed status. The base leg is the package `src/` swapped tree-only from `aeb9cf0ddc` (the new module removed) and restored from `HEAD` under a trap. - The merged head `a885bdbbdf` and `main` `e1bfde3601` both give 21,688 tests, per test identical (0 lost, 0 added, 0 changed). - `check-platform-checklist --changed-from origin/main` (merged tree): OK, 0 anchors lost. - **Governed surface.** `check-governed-merges --branch claude/project-thread-ybjm3h` finds 0 of 2 paths in the register (13 surfaces), so the PR is NOT governed. It has 179 changed lines, under the 3,000-line human-merge bar. - **Derived gates.** `dispatch-gates --commands --repo objectstack-ai/objectstack` derives 46 gates on 2 changed paths from `a885bdbbdf`, with no stale-tree finding. All 46 exit 0 after the full `pnpm build` (116/116). `dispatch-gates --ran` reports 46 derived, 46 run, 0 NOT-MEASURED and 0 UNRUN, and exits 0. - `check-durability-degradation-log-level --list` on `a885bdbbdf`: `88 read seam(s)`. ## Carry-forward These stay in `protocol.ts`: - **The read-seam members** (cut 21): `findEnvironmentRows`, `packagelessAddress`, `findServedOverlayRow`, `foldStoredCollection`, `applyObjectRegistryMutation`, `unmanagedImportCollisions`, `rollBackUnmanagedImport`, `readRevertableCommit` and `storedFlowBindingAgrees`. `rethrowUnlessMetadataStoreUnprovisioned` cannot move at all. - Public members whose return type is inferred. - **The bodies kept by a by-path reader:** - `probeRecord`, `getMetaItemCached`, `findReferencesToMeta`, `refuseUnmintableMetaType` and `metaTypeNamespaceExists`. - `viewContainerNameCollisionRefusal` with `overlaidShippedContainerViewNames`, and `reportPageRequiresAbsentAtLoad`. - The 7 `applyRegistryWriteThrough` callers, `readCodeLayerForCarryForward` and `ensureObjectStorage`. - `emitMetadataMutation`. - The private-static readers: `refusePackagedBaseRemoval`, `packagedBaseRefusal`, `runPublishSideEffects`, `promoteDraftForPublish`, `publishPackageDrafts` and `discardPackageDrafts`. - The field writers: `warnClosureReadUnavailable` and `resolveSduiManifest`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6 Co-authored-by: Claude <noreply@anthropic.com>
1 parent a51ba63 commit 161e00e

2 files changed

Lines changed: 138 additions & 41 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#25467 cut 26] The runtime authoring gate's deployment-posture probes, from
5+
* `ObjectStackProtocolImplementation`, moved out of `protocol.ts` whole: the
6+
* four reads of THIS deployment that `assertRuntimeAuthoringRules` hands the
7+
* gate on every write.
8+
*
9+
* - `orgWallEnforced` — does the configured tenancy posture wall
10+
* organizations (#6285)? Unreadable reads as walled.
11+
* - `tenancyPostureInForce` — the posture the `tenancy` service reports in
12+
* force, for the public-form intake advisory (#21476).
13+
* - `rlsMembershipResolverKeys` — the membership keys the mounted RLS
14+
* resolver declares.
15+
* - `hierarchyResolverMounted` — is a hierarchy resolver mounted (#26188)?
16+
*
17+
* A MOVE, not a redesign. Each function below is the body of the
18+
* `ObjectStackProtocolImplementation` method of the same name as it stood,
19+
* re-indented one level and otherwise unedited: the same service keys, the
20+
* same `try`s and the same answers when a read throws.
21+
*
22+
* ## How the moved bodies reach the class
23+
*
24+
* The shape #23050 set for `rest-server.ts` (its `data-action-routes.ts`
25+
* header carries the full reasoning), as `protocol-batch-data.ts` applies it.
26+
* The class keeps each method — the published `dist/*.d.ts` lists its name,
27+
* docblock and declared signature — and its body delegates here. Three read
28+
* the constructor-supplied `getServicesRegistry` field, handed over as a
29+
* getter ({@link AuthoringPostureProbesHost}), as
30+
* `protocol-metadata-service-read.ts` hands that field over, so each reads the
31+
* registry at call time — a resolver registered after this protocol is
32+
* assembled is seen by the next write. `orgWallEnforced` reads no member, so
33+
* its stub calls the plain function. The callers — the closures in
34+
* `assertRuntimeAuthoringRules`'s stub — keep calling the class methods.
35+
*
36+
* What the bodies read from elsewhere, they import from where `protocol.ts`
37+
* imported it; four of those names had no other reader there and left its
38+
* imports, with the comments that explained them. Nothing re-exports this
39+
* module, so the published surface does not move.
40+
*/
41+
42+
import type { TenancyPosture } from '@objectstack/spec/security';
43+
// [#6285] ADR-0105 D1's authority on "does this deployment wall organizations?".
44+
// `resolveMultiOrgEnabled()` is DEMOTED and its own doc comment says answering
45+
// this question with it is a bug (cloud#1020, #5233) — so the posture, and only
46+
// the posture, is what the runtime authoring gate is told.
47+
import { postureEnforcesWall } from '@objectstack/spec/security';
48+
import { resolveTenancyPosture } from '@objectstack/types';
49+
import {
50+
// [#21476] The posture IN FORCE, read off the `tenancy` service the one way
51+
// the anonymous form doors read it — the runtime authoring gate's input for
52+
// its public-form intake advisory (see `tenancyPostureInForce()`).
53+
anonymousFormIntakePosture,
54+
} from '@objectstack/metadata-core';
55+
import { RLS_MEMBERSHIP_RESOLVER_SERVICE } from '@objectstack/spec/contracts';
56+
import { HIERARCHY_SCOPE_RESOLVER_SERVICE } from './runtime-authoring-gate.js';
57+
import type { ObjectStackProtocolImplementation } from './protocol.js';
58+
59+
/**
60+
* The `ObjectStackProtocolImplementation` members the functions below read —
61+
* every one of them, and no other; each function's `this` is the `Pick` of
62+
* the members it reads. Each is typed AS the class member (an indexed access
63+
* on the class), so a change to a member's type on the class is a change
64+
* here too, and naming a member the class does not have is a compile error.
65+
*/
66+
export interface AuthoringPostureProbesHost {
67+
readonly getServicesRegistry: ObjectStackProtocolImplementation['getServicesRegistry'];
68+
}
69+
70+
/** The body of `ObjectStackProtocolImplementation#orgWallEnforced`; the method's docblock there is its contract. */
71+
export function orgWallEnforced(): boolean {
72+
try {
73+
return postureEnforcesWall(resolveTenancyPosture());
74+
} catch {
75+
return true;
76+
}
77+
}
78+
79+
/** The body of `ObjectStackProtocolImplementation#tenancyPostureInForce`; the method's docblock there is its contract. */
80+
export function tenancyPostureInForce(this: Pick<AuthoringPostureProbesHost, 'getServicesRegistry'>): TenancyPosture | undefined {
81+
try {
82+
return anonymousFormIntakePosture(this.getServicesRegistry?.().get('tenancy'));
83+
} catch {
84+
return undefined;
85+
}
86+
}
87+
88+
/** The body of `ObjectStackProtocolImplementation#rlsMembershipResolverKeys`; the method's docblock there is its contract. */
89+
export function rlsMembershipResolverKeys(this: Pick<AuthoringPostureProbesHost, 'getServicesRegistry'>): readonly string[] | undefined {
90+
const registry = this.getServicesRegistry?.();
91+
if (!registry) return undefined;
92+
let resolver: unknown;
93+
try {
94+
resolver = registry.get(RLS_MEMBERSHIP_RESOLVER_SERVICE);
95+
} catch {
96+
return undefined;
97+
}
98+
if (resolver === undefined || resolver === null) return [];
99+
const keys = (resolver as { keys?: unknown }).keys;
100+
if (!Array.isArray(keys) || !keys.every((k) => typeof k === 'string')) return undefined;
101+
return keys as readonly string[];
102+
}
103+
104+
/** The body of `ObjectStackProtocolImplementation#hierarchyResolverMounted`; the method's docblock there is its contract. */
105+
export function hierarchyResolverMounted(this: Pick<AuthoringPostureProbesHost, 'getServicesRegistry'>): boolean | undefined {
106+
const registry = this.getServicesRegistry?.();
107+
if (!registry) return undefined;
108+
try {
109+
const resolver = registry.get(HIERARCHY_SCOPE_RESOLVER_SERVICE);
110+
return resolver !== undefined && resolver !== null;
111+
} catch {
112+
return undefined;
113+
}
114+
}

‎packages/metadata-protocol/src/protocol.ts‎

Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ import type {
55
} from '@objectstack/spec/api';
66
import { IDataEngine, engineCanRollBack, recordNotFoundError } from '@objectstack/core';
77
import { declaredHttpStatus, declaredUserMessage, isSandboxOrigin, resolveTenancyPosture, resolveThrownHttpError, sandboxBusinessMessage } from '@objectstack/types';
8-
// [#6285] ADR-0105 D1's authority on "does this deployment wall organizations?".
9-
// `resolveMultiOrgEnabled()` is DEMOTED and its own doc comment says answering
10-
// this question with it is a bug (cloud#1020, #5233) — so the posture, and only
11-
// the posture, is what the runtime authoring gate is told.
12-
import { judgePermissionSetOwnerBinding, postureEnforcesWall, type TenancyPosture } from '@objectstack/spec/security';
8+
import { judgePermissionSetOwnerBinding, type TenancyPosture } from '@objectstack/spec/security';
139
// [commit 376c70f98] The derived `version` this file's `getDiscovery()` serves as the
1410
// `DiscoverySchema` "System Identity" field — never a literal again.
1511
import { resolveDiscoveryVersion } from './discovery-version.js';
@@ -27,7 +23,6 @@ import { stripUnauthorableProperties } from './unauthorable-nodes.js';
2723
// admits anything. See the module header for the predicate and its controls.
2824
import { markErasedAuthoringInput } from './erased-authoring-mark.js';
2925
import {
30-
HIERARCHY_SCOPE_RESOLVER_SERVICE,
3126
SDUI_MANIFEST_SERVICE,
3227
isUsableSduiManifest,
3328
stampHtmlPageRequires,
@@ -105,10 +100,6 @@ import {
105100
// {@link ObjectStackProtocolImplementation.getMetaItemLayered}'s code-layer
106101
// fallback so a hydrated row is never answered as the code layer.
107102
isTenantAuthored,
108-
// [#21476] The posture IN FORCE, read off the `tenancy` service the one way
109-
// the anonymous form doors read it — the runtime authoring gate's input for
110-
// its public-form intake advisory (see `tenancyPostureInForce()`).
111-
anonymousFormIntakePosture,
112103
// [ADR-0131 D6/D7] A legacy organization's rows, off the physical column,
113104
// on a database the ceremony has not migrated — see migrateStoredMetadata.
114105
readLegacyOrganizationRows,
@@ -167,7 +158,7 @@ import { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL, canonicalMetaUrlType, metaUrlSp
167158
// depends on `@objectstack/service-cluster`; a bridge plugin there hands the
168159
// live transport in through `attachMetadataMutationPubSub`.
169160
import type { IFieldFileService, IPubSub } from '@objectstack/spec/contracts';
170-
import { FIELD_FILE_SERVICE, RLS_MEMBERSHIP_RESOLVER_SERVICE } from '@objectstack/spec/contracts';
161+
import { FIELD_FILE_SERVICE } from '@objectstack/spec/contracts';
171162
import { applyConversionsToFlow, type ConversionNotice, type ConversionTodoNotice } from '@objectstack/spec';
172163
import { type FormView, type I18nLabel, isAggregatedViewContainer, expandViewContainer } from '@objectstack/spec/ui';
173164
// [commit ece4dad31] Emitted-specifier pin. This module's inferred public declarations
@@ -420,6 +411,8 @@ import { listLiveMetadataTypes } from './protocol-live-metadata-types.js';
420411
import { saveConflict } from './protocol-save-conflict.js';
421412
// [#25467 cut 25] The post-persistence mutation fan-out: projector, local listeners, cluster publish, publish listeners.
422413
import { emitMetaItemPublished, notifyMutationListenersLocal, publishMetadataMutation, runMutationProjector } from './protocol-mutation-fan-out.js';
414+
// [#25467 cut 26] The runtime authoring gate's deployment-posture probes: org wall, tenancy, RLS keys, hierarchy.
415+
import { hierarchyResolverMounted, orgWallEnforced, rlsMembershipResolverKeys, tenancyPostureInForce } from './protocol-authoring-posture-probes.js';
423416

424417
/**
425418
* Canonical Zod schema per metadata type lives in
@@ -7460,11 +7453,8 @@ export class ObjectStackProtocolImplementation implements
74607453
* refusal combination to be turned away.
74617454
*/
74627455
private orgWallEnforced(): boolean {
7463-
try {
7464-
return postureEnforcesWall(resolveTenancyPosture());
7465-
} catch {
7466-
return true;
7467-
}
7456+
// [#25467 cut 26] The body moved to `protocol-authoring-posture-probes.ts`; it reads no member.
7457+
return orgWallEnforced();
74687458
}
74697459

74707460
/**
@@ -7494,11 +7484,12 @@ export class ObjectStackProtocolImplementation implements
74947484
* reports none, and the advisory is simply not raised.
74957485
*/
74967486
private tenancyPostureInForce(): TenancyPosture | undefined {
7497-
try {
7498-
return anonymousFormIntakePosture(this.getServicesRegistry?.().get('tenancy'));
7499-
} catch {
7500-
return undefined;
7501-
}
7487+
// [#25467 cut 26] The body moved to `protocol-authoring-posture-probes.ts` (see `AuthoringPostureProbesHost`).
7488+
const readGetServicesRegistry = () => this.getServicesRegistry;
7489+
const host: ThisParameterType<typeof tenancyPostureInForce> = {
7490+
get getServicesRegistry() { return readGetServicesRegistry(); },
7491+
};
7492+
return tenancyPostureInForce.call(host);
75027493
}
75037494

75047495
/**
@@ -7516,18 +7507,12 @@ export class ObjectStackProtocolImplementation implements
75167507
* wrong ones.
75177508
*/
75187509
private rlsMembershipResolverKeys(): readonly string[] | undefined {
7519-
const registry = this.getServicesRegistry?.();
7520-
if (!registry) return undefined;
7521-
let resolver: unknown;
7522-
try {
7523-
resolver = registry.get(RLS_MEMBERSHIP_RESOLVER_SERVICE);
7524-
} catch {
7525-
return undefined;
7526-
}
7527-
if (resolver === undefined || resolver === null) return [];
7528-
const keys = (resolver as { keys?: unknown }).keys;
7529-
if (!Array.isArray(keys) || !keys.every((k) => typeof k === 'string')) return undefined;
7530-
return keys as readonly string[];
7510+
// [#25467 cut 26] The body moved to `protocol-authoring-posture-probes.ts` (see `AuthoringPostureProbesHost`).
7511+
const readGetServicesRegistry = () => this.getServicesRegistry;
7512+
const host: ThisParameterType<typeof rlsMembershipResolverKeys> = {
7513+
get getServicesRegistry() { return readGetServicesRegistry(); },
7514+
};
7515+
return rlsMembershipResolverKeys.call(host);
75317516
}
75327517

75337518
/**
@@ -7544,14 +7529,12 @@ export class ObjectStackProtocolImplementation implements
75447529
* throws.
75457530
*/
75467531
private hierarchyResolverMounted(): boolean | undefined {
7547-
const registry = this.getServicesRegistry?.();
7548-
if (!registry) return undefined;
7549-
try {
7550-
const resolver = registry.get(HIERARCHY_SCOPE_RESOLVER_SERVICE);
7551-
return resolver !== undefined && resolver !== null;
7552-
} catch {
7553-
return undefined;
7554-
}
7532+
// [#25467 cut 26] The body moved to `protocol-authoring-posture-probes.ts` (see `AuthoringPostureProbesHost`).
7533+
const readGetServicesRegistry = () => this.getServicesRegistry;
7534+
const host: ThisParameterType<typeof hierarchyResolverMounted> = {
7535+
get getServicesRegistry() { return readGetServicesRegistry(); },
7536+
};
7537+
return hierarchyResolverMounted.call(host);
75557538
}
75567539

75577540
/**

0 commit comments

Comments
 (0)