Skip to content

Commit 79c967f

Browse files
hotlongclaude
andcommitted
refactor(runtime): the save-door verdict stays inside handleMetadataRequest, hoisted above every branch
The layered read asks the same `saveVerdict` the `PUT` branch and the `?state=draft` read ask; it is now declared once at the top of the handler instead of in a new module-level function, so the elevation-read census anchors it where it already did. Two moved comments now cite a record that resolves. Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289 Co-authored-by: Claude <noreply@anthropic.com>
1 parent f5a486b commit 79c967f

2 files changed

Lines changed: 37 additions & 43 deletions

File tree

‎packages/rest/src/rest-server.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3762,7 +3762,8 @@ export class RestServer {
37623762
// why naming the org unconditionally would resurrect #6190's phantoms).
37633763
const layeredCtx = await this.resolveExecCtx(environmentId, req)
37643764
.catch(rethrowAuthzStoreUnavailable);
3765-
// [#10340] FOLDED, not raw — see the PUT door's org-scope comment for
3765+
// [folded-type commit 26f3588fb] (the original card no longer
3766+
// resolves) FOLDED, not raw — see the PUT door's org-scope comment for
37663767
// the measurement. [#20478] Asked of `metaReadOrganizationId` (the
37673768
// same fold over the vetted `tenantId`), the one answer the runtime
37683769
// dispatcher's layered read asks too.

‎packages/runtime/src/domains/meta.ts‎

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -649,37 +649,12 @@ async function answerMetaItem(
649649
}
650650
}
651651

652-
/** [#20320] This transport's save-door admission of `:type/:name` — see {@link metaSaveVerdictOf}. */
652+
/** [#20320] This transport's save-door admission of `:type/:name` — see `saveVerdict` in {@link handleMetadataRequest}. */
653653
type MetaSaveVerdict = (
654654
canonicalType: string,
655655
activeOrganizationId: string | undefined,
656656
) => ReturnType<typeof metaWriteCapabilityVerdict>;
657657

658-
/**
659-
* [#12702 · #20320 · #20478] May this caller SAVE `:type/:name` on THIS
660-
* transport? The admission of the item branch's `PUT`, spelled ONCE: that door
661-
* asks it, and so does every read door that honours the author exemption —
662-
* the `?state=draft` read and the layered view, on both of its spellings
663-
* (ruling 5856774816, item 1: 「whoever can save it must see it whole, or a
664-
* save drops entries silently」), which `MetaReadGateCaller.mayWriteItem` says
665-
* must be the transport's own save-door answer. A second spelling at a read
666-
* could drift from the door it stands for. `canonicalType` is the folded
667-
* segment and `activeOrganizationId` the one resolution each caller already
668-
* made, so authorization and scope read one value.
669-
*/
670-
function metaSaveVerdictOf(context: HttpProtocolContext): MetaSaveVerdict {
671-
return (canonicalType, activeOrganizationId) => {
672-
const ec: any = context.executionContext;
673-
return metaWriteCapabilityVerdict({
674-
isSystem: ec?.isSystem === true,
675-
systemPermissions: ec?.systemPermissions,
676-
canonicalType,
677-
activeOrganizationId,
678-
operation: 'save',
679-
});
680-
};
681-
}
682-
683658
/**
684659
* [#20320] `GET /meta/:type/:name?state=draft` for a caller who may read
685660
* drafts — the pending draft ROW (ADR-0033), answered exactly as `RestServer`'s
@@ -782,12 +757,12 @@ type MetaLayeredProtocol = MetaDomainProtocol & Required<Pick<MetaDomainProtocol
782757
* ({@link metaReadOrganizationId}, the partition the plain read reads — ⛔ never
783758
* the session's claim as stored) and to `?package=` (ADR-0048). The chain
784759
* judges every present layer under `STORED_VERSION_DOOR_POLICY` — whole for a
785-
* caller this transport's save door admits ({@link metaSaveVerdictOf}, carried
786-
* as `mayWriteItem`), pruned as the plain read prunes it for everyone else
787-
* (ruling 5856774816) — and projects every layer through the ADR-0106 mask
788-
* under the posture resolved before the read. The dispatcher served neither
789-
* spelling: the route answered a located `404 ROUTE_NOT_FOUND`, and the flag
790-
* answered the PLAIN read's `{ type, name, item }` with a `200`.
760+
* caller this transport's save door admits (`saveVerdict`, the `PUT` branch's
761+
* own admission, carried as `mayWriteItem`), pruned as the plain read prunes
762+
* it for everyone else (ruling 5856774816) — and projects every layer through
763+
* the ADR-0106 mask under the posture resolved before the read. The dispatcher
764+
* served neither spelling: the route answered a located `404 ROUTE_NOT_FOUND`,
765+
* and the flag answered the PLAIN read's `{ type, name, item }` with a `200`.
791766
*
792767
* The answers, in this transport's envelope, with `RestServer`'s status and
793768
* code: the layered answer (no `Vary`: it is not translated; `private,
@@ -805,12 +780,13 @@ async function answerMetaLayered(
805780
context: HttpProtocolContext,
806781
protocol: MetaLayeredProtocol,
807782
request: { type: string; name: string; packageId: string | undefined; maskPosture: ObjectSchemaMaskPosture },
783+
saveVerdict: MetaSaveVerdict,
808784
headers: Readonly<Record<string, string | undefined>> = {},
809785
): Promise<HttpDispatcherResult> {
810786
const { type, name, packageId, maskPosture } = request;
811787
const caller = context.executionContext as MetaReadGateCaller | undefined;
812788
const organizationId = metaReadOrganizationId(type, caller);
813-
const mayWriteItem = metaSaveVerdictOf(context)(canonicalMetaUrlType(type), metaCallerOrganizationId(caller)).allowed;
789+
const mayWriteItem = saveVerdict(canonicalMetaUrlType(type), metaCallerOrganizationId(caller)).allowed;
814790
let answer: MetaLayeredAnswer;
815791
try {
816792
const layered = await protocol.getMetaItemLayered({
@@ -881,6 +857,29 @@ function decodeMetaNameSegment(segment: string): string {
881857
*/
882858
export async function handleMetadataRequest(deps: DomainHandlerDeps, path: string, _context: HttpProtocolContext, method?: string, body?: any, query?: any): Promise<HttpDispatcherResult> {
883859
const parts = path.replace(/^\/+/, '').split('/').filter(Boolean);
860+
861+
// [#12702 · #20320] May this caller SAVE `:type/:name` on THIS transport?
862+
// The admission of the item branch's `PUT`, spelled ONCE: that door asks
863+
// it, and so does every read door that honours the author exemption — the
864+
// `?state=draft` read and [#20478] the layered view, on both of its
865+
// spellings (ruling 5856774816, item 1: 「whoever can save it must see it
866+
// whole, or a save drops entries silently」), which
867+
// `MetaReadGateCaller.mayWriteItem` says must be the transport's own
868+
// save-door answer. A second spelling at a read could drift from the door
869+
// it stands for. `canonicalType` is the folded segment and
870+
// `activeOrganizationId` the one resolution each caller already made, so
871+
// authorization and scope read one value. [#20478] Declared here, above
872+
// every branch, so the `/layers` branch asks the same one.
873+
const saveVerdict: MetaSaveVerdict = (canonicalType, activeOrganizationId) => {
874+
const ec: any = _context.executionContext;
875+
return metaWriteCapabilityVerdict({
876+
isSystem: ec?.isSystem === true,
877+
systemPermissions: ec?.systemPermissions,
878+
canonicalType,
879+
activeOrganizationId,
880+
operation: 'save',
881+
});
882+
};
884883
// Defense-in-depth: the metadata catch-all must honour the same
885884
// anonymous-deny (#2567) as the REST `/meta` routes (which serve `/meta` on
886885
// the cloud runtime). Object/field schemas — SYSTEM-object schemas on a
@@ -1009,7 +1008,7 @@ export async function handleMetadataRequest(deps: DomainHandlerDeps, path: strin
10091008
// path fell to the located `ROUTE_NOT_FOUND` tail below. The whole answer is
10101009
// `createMetaLayeredAnswer` in `@objectstack/rest` ({@link
10111010
// answerMetaLayered}), the one `RestServer`'s handler calls. EXACTLY three
1012-
// segments, like `/published` beside it (#12195). The anonymous gate above
1011+
// segments, like `/published` beside it. The anonymous gate above
10131012
// keeps its deny here (`metaReadRouteOf` names no route), as `RestServer`'s
10141013
// does.
10151014
if (parts.length === 3 && parts[2] === 'layers' && (!method || method.toUpperCase() === 'GET')) {
@@ -1038,7 +1037,7 @@ export async function handleMetadataRequest(deps: DomainHandlerDeps, path: strin
10381037
const packageId = query?.package || undefined;
10391038
return answerMetaLayered(
10401039
deps, _context, protocol as MetaLayeredProtocol,
1041-
{ type, name, packageId, maskPosture },
1040+
{ type, name, packageId, maskPosture }, saveVerdict,
10421041
);
10431042
}
10441043

@@ -1189,12 +1188,6 @@ export async function handleMetadataRequest(deps: DomainHandlerDeps, path: strin
11891188
// Extract optional package filter from query string
11901189
const packageId = query?.package || undefined;
11911190

1192-
// [#12702 · #20320] May this caller SAVE `:type/:name` on THIS
1193-
// transport? The admission of the `PUT` branch below, spelled ONCE
1194-
// ({@link metaSaveVerdictOf}): that branch asks it, and so do the read
1195-
// doors that honour the author exemption — the `?state=draft` read
1196-
// below and [#20478] the layered view.
1197-
const saveVerdict: MetaSaveVerdict = metaSaveVerdictOf(_context);
11981191

11991192
// PUT /metadata/:type/:name (Save)
12001193
//
@@ -1529,7 +1522,7 @@ export async function handleMetadataRequest(deps: DomainHandlerDeps, path: strin
15291522
if (protocol && typeof protocol.getMetaItemLayered === 'function') {
15301523
return await answerMetaLayered(
15311524
deps, _context, protocol as MetaLayeredProtocol,
1532-
{ type, name, packageId, maskPosture },
1525+
{ type, name, packageId, maskPosture }, saveVerdict,
15331526
metaItemLayersDeprecationHeaders(requestedItemPath(_context)),
15341527
);
15351528
}

0 commit comments

Comments
 (0)