Skip to content

Commit 5d1ee50

Browse files
committed
fix(runtime): keep the dispatcher's save verdict inside handleMetadataRequest; drop two citations that no longer resolve
Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP Co-authored-by: Claude <noreply@anthropic.com>
1 parent b1e916d commit 5d1ee50

2 files changed

Lines changed: 35 additions & 34 deletions

File tree

‎packages/rest/src/meta-item-read-gate.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1712,7 +1712,7 @@ export interface MetaListTranslationSources {
17121712
}
17131713

17141714
/**
1715-
* [#6349 · #20320] Translate a metadata LIST — a bare array or the
1715+
* [#20320] Translate a metadata LIST — a bare array or the
17161716
* `{ type, items }` envelope, answered in the shape it came in — for the
17171717
* request's locale. `metaType` is the canonical singular (the caller folds its
17181718
* URL segment once): `TRANSLATABLE_METADATA_TYPES` is singular-only, so a

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

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -198,27 +198,6 @@ export function createMetaDomain(deps: DomainHandlerDeps): DomainRoute {
198198
};
199199
}
200200

201-
/**
202-
* [#12702 · #20320] May this caller SAVE `:type/:name` on THIS transport? The
203-
* admission of the `PUT` branch below, spelled ONCE: that branch asks it, and
204-
* so does the `?state=draft` read's author exemption (ruling 5856774816,
205-
* item 1: 「whoever can save it must see it whole, or a save drops entries
206-
* silently」), which `MetaReadGateCaller.mayWriteItem` says must be the
207-
* transport's own save-door answer. A second spelling at the read could drift
208-
* from the door it stands for. `canonicalType` is the folded segment and
209-
* `activeOrganizationId` the one resolution the caller already made, so
210-
* authorization and scope read one value (#8919).
211-
*/
212-
function metaSaveVerdict(ec: any, canonicalType: string, activeOrganizationId: string | undefined) {
213-
return metaWriteCapabilityVerdict({
214-
isSystem: ec?.isSystem === true,
215-
systemPermissions: ec?.systemPermissions,
216-
canonicalType,
217-
activeOrganizationId,
218-
operation: 'save',
219-
});
220-
}
221-
222201
/**
223202
* [#20320] The READ route shape this domain would serve `parts` with, as the
224203
* shared `isPublicAudienceRead` names it — or `undefined` for every shape that
@@ -379,8 +358,8 @@ const NAV_PRUNE_LOGGED = new Set<string>();
379358
*
380359
* [#20320] `mayWriteItem` — handed in only by a door whose policy honours the
381360
* author exemption (the `?state=draft` read): this transport's own save-door
382-
* answer ({@link metaSaveVerdict}), carried on a COPY of the context — the
383-
* same object serves every other consumer of this request.
361+
* answer (`saveVerdict` in {@link handleMetadataRequest}), carried on a COPY of
362+
* the context — the same object serves every other consumer of this request.
384363
*/
385364
function metaItemReadGateSources(
386365
deps: DomainHandlerDeps,
@@ -530,6 +509,9 @@ async function answerMetaList(
530509
}
531510
}
532511

512+
/** [#20320] This transport's save-door admission of `:type/:name` — see `saveVerdict` in {@link handleMetadataRequest}. */
513+
type MetaSaveVerdict = (canonicalType: string, activeOrganizationId: string | undefined) => { allowed: boolean };
514+
533515
/**
534516
* [#20320] `GET /meta/:type/:name?state=draft` for a caller who may read
535517
* drafts — the pending draft ROW (ADR-0033), answered exactly as `RestServer`'s
@@ -543,9 +525,9 @@ async function answerMetaList(
543525
* never the published item;
544526
* - the per-caller gate under `STORED_VERSION_DOOR_POLICY` — the constant
545527
* `RestServer`'s draft branch runs (#20290): per-caller arms only, and an
546-
* app WHOLE for a caller this transport's save door admits
547-
* ({@link metaSaveVerdict}, carried as `mayWriteItem`), pruned per caller
548-
* for everyone else;
528+
* app WHOLE for a caller this transport's save door admits (`saveVerdict`,
529+
* the `PUT` branch's own admission, carried as `mayWriteItem`), pruned per
530+
* caller for everyone else;
549531
* - the ADR-0106 mask for an object, as every object exit here runs it.
550532
*
551533
* Only an admitted caller arrives: the switch is declared with
@@ -558,6 +540,7 @@ async function readPendingDraft(
558540
name: string,
559541
packageId: string | undefined,
560542
previewDrafts: boolean,
543+
saveVerdict: MetaSaveVerdict,
561544
): Promise<HttpDispatcherResult> {
562545
const singularType = pluralToSingular(type);
563546
const protocol = await resolveProtocol(deps, context);
@@ -575,9 +558,7 @@ async function readPendingDraft(
575558
}
576559
if (envelope?.item == null) return { handled: true, response: deps.error('Not found', 404) };
577560

578-
const mayWriteItem = metaSaveVerdict(
579-
context.executionContext, canonicalMetaUrlType(type), activeOrganizationId,
580-
).allowed;
561+
const mayWriteItem = saveVerdict(canonicalMetaUrlType(type), activeOrganizationId).allowed;
581562
const gated = await gateMetaItemDocument(
582563
deps, context, protocol, singularType, name, envelope.item, STORED_VERSION_DOOR_POLICY, mayWriteItem,
583564
);
@@ -851,6 +832,27 @@ export async function handleMetadataRequest(deps: DomainHandlerDeps, path: strin
851832
// Extract optional package filter from query string
852833
const packageId = query?.package || undefined;
853834

835+
// [#12702 · #20320] May this caller SAVE `:type/:name` on THIS
836+
// transport? The admission of the `PUT` branch below, spelled ONCE:
837+
// that branch asks it, and so does the `?state=draft` read's author
838+
// exemption (ruling 5856774816, item 1: 「whoever can save it must see
839+
// it whole, or a save drops entries silently」), which
840+
// `MetaReadGateCaller.mayWriteItem` says must be the transport's own
841+
// save-door answer. A second spelling at the read could drift from the
842+
// door it stands for. `canonicalType` is the folded segment and
843+
// `activeOrganizationId` the one resolution each caller already made,
844+
// so authorization and scope read one value.
845+
const saveVerdict: MetaSaveVerdict = (canonicalType, activeOrganizationId) => {
846+
const ec: any = _context.executionContext;
847+
return metaWriteCapabilityVerdict({
848+
isSystem: ec?.isSystem === true,
849+
systemPermissions: ec?.systemPermissions,
850+
canonicalType,
851+
activeOrganizationId,
852+
operation: 'save',
853+
});
854+
};
855+
854856
// PUT /metadata/:type/:name (Save)
855857
//
856858
// [#8842] The condition is the METHOD alone. It used to be
@@ -910,14 +912,13 @@ export async function handleMetadataRequest(deps: DomainHandlerDeps, path: strin
910912
// single-resolution shape the REST doors carry, #8919). Resolving
911913
// it is a session read, not a protocol probe: the 403-vs-501
912914
// discipline above is untouched.
913-
const ec: any = _context.executionContext;
914915
// [#10503] Folded at the boundary, once — the verdict and the
915916
// scope decision below must read the same spelling.
916917
const canonicalType = canonicalMetaUrlType(type);
917918
const activeOrganizationId = await deps.resolveActiveOrganizationId(_context);
918-
// [#20320] Spelled once ({@link metaSaveVerdict}), because the
919+
// [#20320] Spelled once (`saveVerdict` above), because the
919920
// `?state=draft` read's author exemption asks this same question.
920-
const verdict = metaSaveVerdict(ec, canonicalType, activeOrganizationId);
921+
const verdict = saveVerdict(canonicalType, activeOrganizationId);
921922
if (!verdict.allowed) {
922923
// `deps.error(msg, 403)` derives the code from the status —
923924
// `PERMISSION_DENIED`, this transport's pinned spelling.
@@ -1136,7 +1137,7 @@ export async function handleMetadataRequest(deps: DomainHandlerDeps, path: strin
11361137
&& mayReadPendingDrafts(_context.executionContext);
11371138

11381139
try {
1139-
if (isDraftRead) return await readPendingDraft(deps, _context, type, name, packageId, previewDrafts);
1140+
if (isDraftRead) return await readPendingDraft(deps, _context, type, name, packageId, previewDrafts, saveVerdict);
11401141

11411142
// Try specific calls based on type
11421143
if (type === 'objects' || type === 'object') {

0 commit comments

Comments
 (0)