Skip to content

Commit 3ebaea9

Browse files
committed
refactor(metadata-protocol): move the lock-state group out of protocol.ts into protocol-lock-state.ts (#25467 cut 10)
Move-only. The bodies of servedLockState, isShippedFlowName, isStoredFlowEntryOfShippedName, declinesStoredRow, isUnservedSealedOverlay and isStoredEntryOfDeclinedName move to the new protocol-lock-state.ts (shape B: every method keeps its name, docblock and declared signature in the class; each body is a LockStateHost literal plus a .call). The bracketed-refusal pin follows the moved throw site with a PRODUCERS row. Claude-Session: https://claude.ai/code/session_01RobcokwdH6frowqrsE1fRc Co-authored-by: Claude <noreply@anthropic.com>
1 parent ec4ef94 commit 3ebaea9

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)