Skip to content

Commit 2c86039

Browse files
refactor(metadata-protocol): move the lock-state group out of protocol.ts into protocol-lock-state.ts (#25467 cut 10) (#25740)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XEYwDcE3TbthGXa5Y8ze43D)_ Part of #25467 Clause-②: no Metadata-protocol: none Ruling-ref: 6270097840 Cut 10 of the `protocol.ts` split (#24586 ruling A, recorded in `6270097840`). Cuts 1–9 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719 and #25731. The claim is `6392807595`; the scope is the lock-state group the cut 9 report (`6392271625`) proposed from Release `6385903828`, as the server seat approved it. Six `ObjectStackProtocolImplementation` members move unedited into a new module, `packages/metadata-protocol/src/protocol-lock-state.ts` (#23050 shape B; no getter). `protocol.ts` loses 4 net lines (29,795 to 29,791). The cut is move-only, and the card stays open for later cuts. Branch `claude/project-thread-5tpuo2` is the harness-pinned branch for this thread. It names no issue, so the claim comment is the identity record. It holds one commit, `3ebaea97`, on `main` `ec4ef94ead`. **Why this group alone.** The order allowed adding registry materialize/strip if this group was too small to stand alone. It is the size of cut 9 (451 body tokens against cut 9's 496; 242 changed lines against 299), it is one closed group (the six call only one another and four class members that stay), and the claim's file surface names one module for it. So this cut carries the group alone, and registry materialize/strip is proposed as cut 11 in the report. **Serial with the file's in-flight PRs.** Measured just before this PR was opened: of the 17 open PRs, only #25737 touches `packages/metadata-protocol/src/`, in `protocol.runtime-authoring-gate.test.ts`, a test that reads no source by path and names no moved member, outside this cut's file surface. The release PR #25640 touches the package's `CHANGELOG.md` and `package.json` only. #25718's 9,211-file list was read by `git diff --name-only` against its merge base: 0 paths under `packages/metadata-protocol/src/`. `origin/main` moved to `e2b40a264b` after the base; `git log ec4ef94..origin/main -- packages/metadata-protocol` is empty, and `git merge-tree` of this head with `origin/main` is clean. ## What moves | Member | Base line | Visibility | Tokens (params + return + body) | |---|---|---|---| | `servedLockState` | `:15893` | private | 214 | | `isShippedFlowName` | `:15972` | public | 58 | | `isStoredFlowEntryOfShippedName` | `:15987` | private | 43 | | `declinesStoredRow` | `:16048` | public | 59 | | `isUnservedSealedOverlay` | `:16070` | private | 34 | | `isStoredEntryOfDeclinedName` | `:16084` | private | 43 | ## The shape (#23050 shape B, as cuts 1–9 apply it) - **The class keeps every member** with its name, docblock, signature and position. Each body is now one host literal plus `fn.call(host, …)`, marked `[#25467 cut 10]`. `servedLockState` keeps its `storedRowServed = false` default in the class signature and in the module function's parameter list, and the stub passes the parameter through. - **Typing.** `LockStateHost` types every member as the class member itself (`ObjectStackProtocolImplementation['m']`). Each moved function types its `this` as the `Pick` of the host over exactly the members its body reads. Each stub annotates its literal as `ThisParameterType` of `typeof fn`. No cast, and no member changes visibility. - **Members read**, all as late-bound closures: `packagedBaseRefusal`, `packagedArtifactOwner`, `isDeclaredCodeDatasource` and `isSealedManagedItem` (which stay in the class), and `isShippedFlowName`, `isUnservedSealedOverlay` and `declinesStoredRow` (moved bodies calling one another through the protocol). No field is read, so the host has no getter. - **Every other caller still calls the class method**: the reads in `protocol.ts` (`getMetaItem`, `readFlattenedMetaItems`, `getMetaItemLayered`, `restoreArtifactRegistryView`, `loadMetaFromDb` and others), cut 2's `MetaDiagnosticsHost` (its `servedLockState` closure), and the published doors in `packages/rest/src/meta-item-history-routes.ts` and `packages/runtime/src/domains/meta.ts`, which ask `declinesStoredRow` on the protocol instance. ## Module-level declarations, by reader - **None moves, and none gains `export`.** The one module-level declaration of `protocol.ts` the bodies read is `canonicalMetaType` (in `isUnservedSealedOverlay`), already exported; the new module imports it from `protocol.js` as cut 5's `protocol-lock-version-gates.ts` does. The cycle is evaluation-safe: it is read only inside a function body. - **Everything else** the bodies read is imported from the module `protocol.ts` imports it from: `@objectstack/spec/kernel` (`extractProtection`, `evaluateLockForWrite`, `evaluateLockForDelete`, `MetadataLockSchema`, and `resolveLockState` as a type), `./packaged-base-regime.js` (`isOriginGatedType`), `@objectstack/metadata-core` (`isCodeArtifactBody`), `@objectstack/spec/shared` (`PLURAL_TO_SINGULAR`), `./item-lock.js` (`ItemLock`, type). - **Imports in `protocol.ts`.** `extractProtection`, `evaluateLockForWrite`, `evaluateLockForDelete` and `MetadataLockSchema` lose their last reader and leave the `@objectstack/spec/kernel` import list; `resolveLockState` keeps a reader (the class signature of `servedLockState`) and stays. `isOriginGatedType`, `isCodeArtifactBody`, `PLURAL_TO_SINGULAR` and `canonicalMetaType` keep other readers. The one new statement is the import of the new module. ## The published `.d.ts` The series bar is ruling `6348891160`, sorted-union identity, measured first. Base `ec4ef94ead` and head `3ebaea97` were built with `pnpm --filter @objectstack/metadata-protocol build` against one dependency tree (the closure built once at head, then the package's `src/` swapped tree-only to base for the base build and restored from `HEAD` under a trap; `git diff HEAD` empty and `git status` clean after, `protocol.ts` blob equal to `HEAD`'s). - **Raw sha256**: `325970c1661efaa6a3762ce7de9e6bb205ea16099784f2b6c5950d62d175b331` at base and at head, on both `dist/index.d.ts` and `dist/index.d.cts`, 772,838 bytes each (the hash cuts 8 and 9 reported). `cmp` exits 0 on both pairs. **Raw identity holds**, so sorted-union identity holds too. The two public members keep their declared `boolean` return type, so their declarations do not move (`declinesStoredRow(type: string, name: unknown): boolean;`), and the four private ones stay `private NAME;` lines. - **Normalised sha256**: `9562749edf7b8bba67ed2f1779799a68b3d6934a8605074934572d9507c55ba6` at both ends, both files (109 string-literal union runs sorted). This run's normaliser is its own script; its figure happens to equal cut 9's. - **The two builds differ** where they should: `index.js` hashes `a41e81cf2349514e…` at base (cut 9's head hash) and `b99c5148b08d3fc5…` at head; `servedLockState.call` and `declinesStoredRow.call` each occur 0 times in the base bundle and once in the head bundle (ESM and CJS alike). - **Positive control** on a copy of the head `index.d.ts`, `declinesStoredRow(type: string, name: unknown)` changed to `name: string`: raw `c74ae751a3cb7a09…`, normalised `fd072bd9d1ab499a…`. Both move. - **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 four. None of the moved functions or the host type appears. No changeset: `skip-changeset`, per ruling `6348891160`. ## Proof of move-only - **Bodies, by AST token** (the TypeScript compiler's AST leaf tokens over parameters less the added `this`, return type and body): 451 tokens compared, **0 differ**. Leaf tokens rather than the raw scanner because the scanner, run without template rescanning, swallows the rest of `servedLockState`'s body into one template-tail token after its error message's first `${…}`, which would compare whitespace. The parameter, return type and body text, comments included, is also equal with whitespace collapsed, for all six. Negative control: one changed token in `isUnservedSealedOverlay`'s body (`=== 'permission'` to `!==`, on a copy) makes the check report `differ=1`. - **Class side.** 299 members at base and at head, in the same order. The 293 that did not move are byte-identical to base, leading comments included. Each of the 6 stubs sits at its base position and keeps its docblock and signature byte-identical. - **Top level of `protocol.ts`.** 236 of the 237 base statements other than the class are byte-identical, leading comments included. The other one is the `@objectstack/spec/kernel` import that loses four names. Head adds one statement, the new module's import. - **Size.** +198 / −44 over 3 files, 242 changed lines. `protocol-lock-state.ts` has 155 lines. ## Readers of the moved lines Every test and script that reads `protocol.ts` by path was grepped for each moved member's name and for the calls their bodies make (`packagedBaseRefusal`, `packagedArtifactOwner`, `isDeclaredCodeDatasource`, `isSealedManagedItem`, `isCodeArtifactBody`, `isOriginGatedType`, `evaluateLockFor*`, `extractProtection`, `MetadataLockSchema`, the error message). - **The six test files the order named** call the method on a protocol instance or name it in prose; none reads its body in `protocol.ts` by path. **The hypothesis holds**, and every member moves: - `protocol.code-defined-datasource-door.test.ts`: `declinesStoredRow` named in a comment only. - `protocol.declines-stored-row-published.test.ts`: calls `protocol.isShippedFlowName(…)` and `protocol.declinesStoredRow(…)`. - `protocol.reads-environment-only.test.ts`: calls `protocol.declinesStoredRow(…)`. - `packages/plugins/plugin-security/src/security-catalog-echo.test.ts`: `servedLockState` named in a comment only. 19/19 pass at head. - `packages/rest/src/meta-published-overlay.test.ts`: calls `protocol.isShippedFlowName(…)` and hides `declinesStoredRow` behind a `Proxy` to pin that the door asks that one predicate. The stub's closures call through `this`, the method stays on the class, and 16/16 pass at head. - `packages/runtime/src/domains/meta-published-runtime-publish.test.ts`: the same two shapes as the REST test. 12/12 pass at head. - **Other name hits**, all prose or calls: `protocol.flow-by-name-shipped-name.test.ts` and `protocol.flow-layered-shipped-name.test.ts` (docblocks), `packages/qa/dogfood/test/flow-shipped-name-published-door.dogfood.test.ts` (a comment), `protocol-meta-diagnostics.ts` (the cut 2 host type and its call), `item-lock.ts` (a comment). - **Bracketed-refusal pin** (`PRODUCERS`): `servedLockState`'s unreachable-state `throw new Error(` moves with its body, so the new module gets a row, `'protocol-lock-state.ts': 1`, the module's own `new Error(` count, beside the rows cuts 1, 3, 5, 6 and 7 added. `protocol.ts` goes from 75 to 74 such sites, still above its floor of 5. Control: with the module's throw changed to `new TypeError(` (on disk, restored from `HEAD` under a trap, hash equal to `HEAD`'s after), the new row fails with "protocol-lock-state.ts no longer constructs errors here … expected 0 to be greater than or equal to 1" (1 failed, 12 passed); restored, 13/13 pass. - **`protocol.read-verb-canonical-fold.test.ts`** lists the members whose body folds through `canonicalizeMetaRequestType(request)`. None of the six does; the list is unchanged and green. - **`docs/qa/platform-checklist/areas/studio-authoring.json`** cites "protocol.ts servedLockState". The method keeps its name in `protocol.ts`; `check-platform-checklist --changed-from origin/main`: OK, 29 anchors judged, 0 lost. - **ADR anchors and decision letters.** `check:adr-anchors` exits 0 at both ends: 85 anchored files, 52,570 citations, **1,846 → 1,846** decision-letter citations; the only difference in its output is the file count, 6,623 → 6,624. The `protocol.ts` anchor entry's ADR-0126 §2 / §3 paragraph names `isShippedFlowName` / `isStoredFlowEntryOfShippedName`, but the mechanism it describes, `hydrateOverlayIntoRegistry` registering a stored flow row without the envelope and `readFlattenedMetaItems` serving the loader's entries alone, stays in `protocol.ts`, and both names stay there as class members. So the new module gets no entry (see Acceptance notes). - **Durability checker** (`check-durability-degradation-log-level --list`): identical at both ends once `[@:][0-9]+` is stripped (159 lines). The gate exits 0 at both ends with identical output. - **Engine-double contract** (`check-engine-double-contract`): exits 0 at both ends. The `updateData`/`deleteData` seams keep their entries at new line numbers (14039 → 14044, 14064 → 14069). `productionFiles` is 3,686 at base and 3,688 at head: +1 is the new file, +1 is the gitignored `packages/spec/src/migrations/registry.ts`, which the spec build writes in the head tree and the base tree had not built. - **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`. `check-nul-bytes` differs only in the scanned file count, +1. The base-end readings were taken in a clean detached worktree at `ec4ef94ead`. - **Governed surface.** `check-governed-merges --branch claude/project-thread-5tpuo2`: 0 of 3 paths hit the register, NOT governed; 242 changed lines, under the 3,000-line human-merge bar. ## Verification All at head `3ebaea97`. - `pnpm --filter @objectstack/metadata-protocol typecheck` exits 0. `pnpm exec eslint --no-inline-config packages/metadata-protocol` exits 0, no output. - **`packages/metadata-protocol` suite**, base and head, compared per test name and status (vitest `--maxWorkers=2`, JSON reporter, each run under the shared verify lock, `VERDICT command-exit 0` both legs): - Base `ec4ef94ead`: 21,504 tests (21,485 passed, 0 failed, 19 skipped), 266 files. - Head `3ebaea97`: 21,505 tests (21,486 passed, 0 failed, 19 skipped), 266 files. - None lost, none changed status, one added: the new `PRODUCERS` row's case. The base leg is the package `src/` swapped tree-only from `ec4ef94ead` (the new module removed from the tree) and restored from `HEAD` under a trap; `git diff HEAD` empty and `git status` clean after. - **Derived gates.** `dispatch-gates --commands --repo objectstack-ai/objectstack` derives 59 gates on 3 changed paths; all 59 exit 0. `check-plugin-teardown-shape --self-test`, `check:dual-build-cjs-loads` and `check:lean-entry-closure` first answered PREREQUISITE NOT MET (exit 3: a pinned fixture commit outside the shallow clone; unbuilt `dist/`) and exit 0 once the commit was fetched and `pnpm build` ran. `dispatch-gates --ran`: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. ## Acceptance notes - No new ADR anchor entry: the moved bodies are the predicates the ADR-0126 paragraph names, and the read and hydration paths that paragraph describes stay in `protocol.ts`. If the seat reads the predicates themselves as the mechanism, the fix is a `packages__metadata-protocol__src__protocol-lock-state.ts.json` entry citing ADR-0126; the letter count does not move either way. - `origin/main` moved to `e2b40a264b` after the base (8 commits, none in `packages/metadata-protocol`, merge-tree clean); not merged before this PR, so the suite and gates measured `ec4ef94ead`-based trees. The merge queue rebuilds on current `main`. ## Carry-forward Unchanged from cut 9: public members whose return type is inferred, the bodies kept by a reader (`probeRecord`, `getMetaItemCached`, `findReferencesToMeta`, `packagelessAddress`, `refuseUnmintableMetaType`, `metaTypeNamespaceExists`, `viewContainerNameCollisionRefusal` with `overlaidShippedContainerViewNames`, `reportPageRequiresAbsentAtLoad`, the 7 `applyRegistryWriteThrough` callers, `readCodeLayerForCarryForward`), and the private-static readers stay in `protocol.ts`. --- _Generated by [Claude Code](https://claude.ai/code/session_01RobcokwdH6frowqrsE1fRc)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent c1bb4b5 commit 2c86039

3 files changed

Lines changed: 198 additions & 44 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#25467 cut 10] The served lock state and the stored-row decline of
5+
* `ObjectStackProtocolImplementation` — the read envelope's lock derivation
6+
* (`servedLockState`), the shipped-flow-name predicate and its registry half
7+
* (`isShippedFlowName`, `isStoredFlowEntryOfShippedName`), the one by-name
8+
* decision the active reads take before they serve a stored row
9+
* (`declinesStoredRow`), its sealed-overlay limb (`isUnservedSealedOverlay`)
10+
* and its registry half (`isStoredEntryOfDeclinedName`) — moved out of
11+
* `protocol.ts` whole.
12+
*
13+
* A MOVE, not a redesign. Each function below is the body of the
14+
* `ObjectStackProtocolImplementation` method of the same name as it stood,
15+
* re-indented one level and otherwise unedited: the same reads, in the same
16+
* order, throwing exactly where they did with the same message.
17+
*
18+
* ## How the moved bodies reach the class
19+
*
20+
* The shape #23050 set for `rest-server.ts` (its `data-action-routes.ts`
21+
* header carries the full reasoning), as `protocol-batch-data.ts` applies it.
22+
* The class keeps every method — the published `dist/*.d.ts` lists each one's
23+
* name, docblock and declared signature — and each body is one literal of
24+
* exactly the {@link LockStateHost} members that method reads, plus a
25+
* `.call`. A method is handed over as a closure that calls it on the protocol
26+
* at call time, so a test double or spy set on the instance is still the one
27+
* the body reaches, and the moved bodies that call one another do so through
28+
* the protocol, exactly as before. Every other reader of these six methods
29+
* keeps calling the class method, the REST and runtime published doors among
30+
* them.
31+
*
32+
* The bodies read no field, so the host carries no getter.
33+
*
34+
* The one module-level declaration of `protocol.ts` the bodies read,
35+
* `canonicalMetaType`, stays there, where code that did not move reads it too,
36+
* and is imported from there: the module cycle is evaluation-safe because it
37+
* is read only inside a function body, never while the two modules are first
38+
* evaluated. Everything else is imported from the module `protocol.ts`
39+
* imports it from. Nothing re-exports this module, so the published surface
40+
* does not move.
41+
*/
42+
43+
import type { ObjectStackProtocolImplementation } from './protocol.js';
44+
import { canonicalMetaType } from './protocol.js';
45+
import type { ItemLock } from './item-lock.js';
46+
import { isOriginGatedType } from './packaged-base-regime.js';
47+
import { isCodeArtifactBody } from '@objectstack/metadata-core';
48+
import { PLURAL_TO_SINGULAR } from '@objectstack/spec/shared';
49+
import {
50+
extractProtection,
51+
evaluateLockForWrite,
52+
evaluateLockForDelete,
53+
MetadataLockSchema,
54+
} from '@objectstack/spec/kernel';
55+
import type { resolveLockState } from '@objectstack/spec/kernel';
56+
57+
/**
58+
* The `ObjectStackProtocolImplementation` members the functions below read —
59+
* every one of them, and no other; each function's `this` is the `Pick` of the
60+
* members IT reads. Each is typed AS the class member (an indexed access on
61+
* the class), so a change to a member's signature on the class is a change
62+
* here too, and naming a member the class does not have is a compile error.
63+
*/
64+
export interface LockStateHost {
65+
declinesStoredRow: ObjectStackProtocolImplementation['declinesStoredRow'];
66+
isDeclaredCodeDatasource: ObjectStackProtocolImplementation['isDeclaredCodeDatasource'];
67+
isSealedManagedItem: ObjectStackProtocolImplementation['isSealedManagedItem'];
68+
isShippedFlowName: ObjectStackProtocolImplementation['isShippedFlowName'];
69+
isUnservedSealedOverlay: ObjectStackProtocolImplementation['isUnservedSealedOverlay'];
70+
packagedArtifactOwner: ObjectStackProtocolImplementation['packagedArtifactOwner'];
71+
packagedBaseRefusal: ObjectStackProtocolImplementation['packagedBaseRefusal'];
72+
}
73+
74+
/** The body of `ObjectStackProtocolImplementation#servedLockState`; the method's docblock there is its contract. */
75+
export function servedLockState(
76+
this: Pick<LockStateHost, 'packagedBaseRefusal'>,
77+
type: string,
78+
name: string,
79+
document: unknown,
80+
artifactBacked: boolean,
81+
itemLock: ItemLock,
82+
storedRowServed = false,
83+
): ReturnType<typeof resolveLockState> {
84+
const { provenance, packageId, packageVersion } = extractProtection(document);
85+
const editable = evaluateLockForWrite(itemLock.lock) === null
86+
&& this.packagedBaseRefusal({ type, name, operation: 'save' }) === null;
87+
const removalRefusal = this.packagedBaseRefusal({ type, name, operation: 'delete' });
88+
const deletable = evaluateLockForDelete(itemLock.lock) === null
89+
&& (removalRefusal === null || (storedRowServed && isOriginGatedType(type)));
90+
const lock = MetadataLockSchema.options.find((state) =>
91+
(evaluateLockForWrite(state) === null) === editable
92+
&& (evaluateLockForDelete(state) === null) === deletable);
93+
if (lock === undefined) {
94+
// Unreachable while the lock algebra covers all four verdict pairs;
95+
// a state added to it without a write/delete answer must fail here,
96+
// loudly, not publish a guessed lock.
97+
throw new Error(
98+
`No ADR-0010 lock state answers editable=${editable}, deletable=${deletable}.`,
99+
);
100+
}
101+
return {
102+
lock,
103+
lockReason: itemLock.lockReason,
104+
lockSource: itemLock.lockSource,
105+
lockDocsUrl: itemLock.lockDocsUrl,
106+
provenance,
107+
packageId,
108+
packageVersion,
109+
editable,
110+
deletable,
111+
resettable: artifactBacked,
112+
};
113+
}
114+
115+
/** The body of `ObjectStackProtocolImplementation#isShippedFlowName`; the method's docblock there is its contract. */
116+
export function isShippedFlowName(
117+
this: Pick<LockStateHost, 'packagedArtifactOwner'>,
118+
type: string, name: unknown): boolean {
119+
if ((PLURAL_TO_SINGULAR[type] ?? type) !== 'flow') return false;
120+
if (typeof name !== 'string' || name === '') return false;
121+
return this.packagedArtifactOwner({ type: 'flow', name }) !== undefined;
122+
}
123+
124+
/** The body of `ObjectStackProtocolImplementation#isStoredFlowEntryOfShippedName`; the method's docblock there is its contract. */
125+
export function isStoredFlowEntryOfShippedName(
126+
this: Pick<LockStateHost, 'isShippedFlowName'>,
127+
type: string, item: unknown): boolean {
128+
return this.isShippedFlowName(type, (item as { name?: unknown } | null | undefined)?.name)
129+
&& !isCodeArtifactBody(item);
130+
}
131+
132+
/** The body of `ObjectStackProtocolImplementation#declinesStoredRow`; the method's docblock there is its contract. */
133+
export function declinesStoredRow(
134+
this: Pick<LockStateHost, 'isDeclaredCodeDatasource' | 'isShippedFlowName' | 'isUnservedSealedOverlay'>,
135+
type: string, name: unknown): boolean {
136+
if (this.isShippedFlowName(type, name)) return true;
137+
if (typeof name !== 'string' || name === '') return false;
138+
return this.isDeclaredCodeDatasource(type, name) || this.isUnservedSealedOverlay(type, name);
139+
}
140+
141+
/** The body of `ObjectStackProtocolImplementation#isUnservedSealedOverlay`; the method's docblock there is its contract. */
142+
export function isUnservedSealedOverlay(
143+
this: Pick<LockStateHost, 'isSealedManagedItem'>,
144+
type: string, name: string): boolean {
145+
if (canonicalMetaType(type) === 'permission') return false;
146+
return this.isSealedManagedItem(type, name);
147+
}
148+
149+
/** The body of `ObjectStackProtocolImplementation#isStoredEntryOfDeclinedName`; the method's docblock there is its contract. */
150+
export function isStoredEntryOfDeclinedName(
151+
this: Pick<LockStateHost, 'declinesStoredRow'>,
152+
type: string, item: unknown): boolean {
153+
return this.declinesStoredRow(type, (item as { name?: unknown } | null | undefined)?.name)
154+
&& !isCodeArtifactBody(item);
155+
}

‎packages/metadata-protocol/src/protocol.bracketed-refusal-opener-absence.test.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ const PRODUCERS: Readonly<Record<string, number>> = {
8484
'protocol-save-door-refusals.ts': 5,
8585
// Package install and update, moved out of `protocol.ts` whole on the same terms.
8686
'protocol-package-install.ts': 3,
87+
// The served lock state and the stored-row decline, moved out of `protocol.ts`
88+
// whole on the same terms.
89+
'protocol-lock-state.ts': 1,
8790
'sys-metadata-repository.ts': 5,
8891
'runtime-authoring-gate.ts': 1,
8992
};

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

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,7 @@ export type { FormFieldInput };
195195
import { METADATA_FORM_REGISTRY, CORE_SERVICE_PROVIDER, serviceUnavailableMessage, inProcessServiceMessage } from '@objectstack/spec/system';
196196
import { DEFAULT_METADATA_TYPE_REGISTRY, getMetadataTypeSchema, getMetadataTypeActions, getMetadataCreateSeed, PROTOCOL_VERSION } from '@objectstack/spec/kernel';
197197
import {
198-
extractProtection,
199-
evaluateLockForWrite,
200-
evaluateLockForDelete,
201198
resolveLockState,
202-
MetadataLockSchema,
203199
type MetadataLock,
204200
type MetadataLockSource,
205201
type MetadataProvenance,
@@ -381,6 +377,15 @@ import {
381377
restoredCredentialPathsFor,
382378
storedBodyForCarryForward,
383379
} from './protocol-credential-carry-forward.js';
380+
// [#25467 cut 10] The served lock state and the stored-row decline.
381+
import {
382+
declinesStoredRow,
383+
isShippedFlowName,
384+
isStoredEntryOfDeclinedName,
385+
isStoredFlowEntryOfShippedName,
386+
isUnservedSealedOverlay,
387+
servedLockState,
388+
} from './protocol-lock-state.js';
384389

385390
/**
386391
* Canonical Zod schema per metadata type lives in
@@ -15898,35 +15903,11 @@ export class ObjectStackProtocolImplementation implements
1589815903
itemLock: ItemLock,
1589915904
storedRowServed = false,
1590015905
): ReturnType<typeof resolveLockState> {
15901-
const { provenance, packageId, packageVersion } = extractProtection(document);
15902-
const editable = evaluateLockForWrite(itemLock.lock) === null
15903-
&& this.packagedBaseRefusal({ type, name, operation: 'save' }) === null;
15904-
const removalRefusal = this.packagedBaseRefusal({ type, name, operation: 'delete' });
15905-
const deletable = evaluateLockForDelete(itemLock.lock) === null
15906-
&& (removalRefusal === null || (storedRowServed && isOriginGatedType(type)));
15907-
const lock = MetadataLockSchema.options.find((state) =>
15908-
(evaluateLockForWrite(state) === null) === editable
15909-
&& (evaluateLockForDelete(state) === null) === deletable);
15910-
if (lock === undefined) {
15911-
// Unreachable while the lock algebra covers all four verdict pairs;
15912-
// a state added to it without a write/delete answer must fail here,
15913-
// loudly, not publish a guessed lock.
15914-
throw new Error(
15915-
`No ADR-0010 lock state answers editable=${editable}, deletable=${deletable}.`,
15916-
);
15917-
}
15918-
return {
15919-
lock,
15920-
lockReason: itemLock.lockReason,
15921-
lockSource: itemLock.lockSource,
15922-
lockDocsUrl: itemLock.lockDocsUrl,
15923-
provenance,
15924-
packageId,
15925-
packageVersion,
15926-
editable,
15927-
deletable,
15928-
resettable: artifactBacked,
15906+
// [#25467 cut 10] The body moved to `protocol-lock-state.ts` (see `LockStateHost`).
15907+
const host: ThisParameterType<typeof servedLockState> = {
15908+
packagedBaseRefusal: (...a) => this.packagedBaseRefusal(...a),
1592915909
};
15910+
return servedLockState.call(host, type, name, document, artifactBacked, itemLock, storedRowServed);
1593015911
}
1593115912

1593215913
/**
@@ -15970,9 +15951,11 @@ export class ObjectStackProtocolImplementation implements
1597015951
* the code definition too.
1597115952
*/
1597215953
isShippedFlowName(type: string, name: unknown): boolean {
15973-
if ((PLURAL_TO_SINGULAR[type] ?? type) !== 'flow') return false;
15974-
if (typeof name !== 'string' || name === '') return false;
15975-
return this.packagedArtifactOwner({ type: 'flow', name }) !== undefined;
15954+
// [#25467 cut 10] The body moved to `protocol-lock-state.ts` (see `LockStateHost`).
15955+
const host: ThisParameterType<typeof isShippedFlowName> = {
15956+
packagedArtifactOwner: (...a) => this.packagedArtifactOwner(...a),
15957+
};
15958+
return isShippedFlowName.call(host, type, name);
1597615959
}
1597715960

1597815961
/**
@@ -15985,8 +15968,11 @@ export class ObjectStackProtocolImplementation implements
1598515968
* envelope ({@link hydrateOverlayIntoRegistry}), so it never passes it.
1598615969
*/
1598715970
private isStoredFlowEntryOfShippedName(type: string, item: unknown): boolean {
15988-
return this.isShippedFlowName(type, (item as { name?: unknown } | null | undefined)?.name)
15989-
&& !isCodeArtifactBody(item);
15971+
// [#25467 cut 10] The body moved to `protocol-lock-state.ts` (see `LockStateHost`).
15972+
const host: ThisParameterType<typeof isStoredFlowEntryOfShippedName> = {
15973+
isShippedFlowName: (...a) => this.isShippedFlowName(...a),
15974+
};
15975+
return isStoredFlowEntryOfShippedName.call(host, type, item);
1599015976
}
1599115977

1599215978
/**
@@ -16046,9 +16032,13 @@ export class ObjectStackProtocolImplementation implements
1604616032
* ask it; the writes keep their own refusals.
1604716033
*/
1604816034
declinesStoredRow(type: string, name: unknown): boolean {
16049-
if (this.isShippedFlowName(type, name)) return true;
16050-
if (typeof name !== 'string' || name === '') return false;
16051-
return this.isDeclaredCodeDatasource(type, name) || this.isUnservedSealedOverlay(type, name);
16035+
// [#25467 cut 10] The body moved to `protocol-lock-state.ts` (see `LockStateHost`).
16036+
const host: ThisParameterType<typeof declinesStoredRow> = {
16037+
isDeclaredCodeDatasource: (...a) => this.isDeclaredCodeDatasource(...a),
16038+
isShippedFlowName: (...a) => this.isShippedFlowName(...a),
16039+
isUnservedSealedOverlay: (...a) => this.isUnservedSealedOverlay(...a),
16040+
};
16041+
return declinesStoredRow.call(host, type, name);
1605216042
}
1605316043

1605416044
/**
@@ -16068,8 +16058,11 @@ export class ObjectStackProtocolImplementation implements
1606816058
* operator's act, so it keeps that ruling's disposition.
1606916059
*/
1607016060
private isUnservedSealedOverlay(type: string, name: string): boolean {
16071-
if (canonicalMetaType(type) === 'permission') return false;
16072-
return this.isSealedManagedItem(type, name);
16061+
// [#25467 cut 10] The body moved to `protocol-lock-state.ts` (see `LockStateHost`).
16062+
const host: ThisParameterType<typeof isUnservedSealedOverlay> = {
16063+
isSealedManagedItem: (...a) => this.isSealedManagedItem(...a),
16064+
};
16065+
return isUnservedSealedOverlay.call(host, type, name);
1607316066
}
1607416067

1607516068
/**
@@ -16082,8 +16075,11 @@ export class ObjectStackProtocolImplementation implements
1608216075
* MetadataService's), so every entry it matches is a hydrated row.
1608316076
*/
1608416077
private isStoredEntryOfDeclinedName(type: string, item: unknown): boolean {
16085-
return this.declinesStoredRow(type, (item as { name?: unknown } | null | undefined)?.name)
16086-
&& !isCodeArtifactBody(item);
16078+
// [#25467 cut 10] The body moved to `protocol-lock-state.ts` (see `LockStateHost`).
16079+
const host: ThisParameterType<typeof isStoredEntryOfDeclinedName> = {
16080+
declinesStoredRow: (...a) => this.declinesStoredRow(...a),
16081+
};
16082+
return isStoredEntryOfDeclinedName.call(host, type, item);
1608716083
}
1608816084

1608916085
/**

0 commit comments

Comments
 (0)