fix(rest): /meta/:type/:name/published consults the active overlay before the code snapshot (#8278) - #8298
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #8278
Mirrors the dispatcher-side change that landed in PR #8254 for card #8031 (
packages/runtime/src/domains/meta.ts) onto the REST transport — the one that actually serves the cloud runtime, and the one PR #8254 did not reach.The defect
Both arities of the route resolved exclusively through
svc.getPublished(type, name)and never consulted the overlay:GET /api/v1/meta/:type/:name/publishedGET /api/v1/meta/:type/:section/:name/publishedTwo publish lifecycles write to two different places, and this route knew only the older one.
MetadataManager.publishPackagesnapshots a body into the row-localpublishedDefinitionkey of its own in-memory registry;publishPackageDrafts/promoteDraftflips the artifact'ssys_metadatarowstate:'draft' -> 'active'. So an item published at runtime answered404here while the ordinary readGET /api/v1/meta/:type/:nameserved it — a false statement about an item that IS published.Why the overlay is authoritative
Both authority claims were re-read on
origin/mainrather than taken from the card:status:'published', freezemanifestJson, computechecksum, assign semver; flip the draft'ssys_metadatarowsstate:'draft' → 'active'." The flip is the publish.The change
packages/rest/src/rest-server.ts, the twopublishedarities only. The overlay is read throughgetMetaItemLayered— the narrow primitive, whose overlay layer is a strictstate:'active'lookup that never reads a draft and is reported separately from the code layer. A null overlay is therefore positively "no runtime-published row" and falls through to the untouchedgetPublishedpath, so a code-published item resolves to byte-identical bytes. The broadergetMetaItemwould not do: it folds the code layer into its own answer, so the route could no longer tell the two stores apart.No new dependency, no new service member, no
packages/specchange:this.resolveProtocol()andgetMetaItemLayeredare both already used elsewhere in this same file.Two deliberate divergences from the dispatcher twin
organizationId.packages/restcarries noresolveActiveOrganizationIdand no org plumbing at all — the seampackage-routes.tsalready names at itsdeletePackagecall ("the dispatcher twin owns that seam"). Inventing org plumbing here to close a 404 would be a new seam smuggled in under a bug fix. Omitting it reads the env-wide (organization_id: null) row, symmetric with what an org-lesspublishPackageDrafts(request.organizationId ?? null) writes — so this door resolves exactly the publishes this door can produce. Environment scoping still holds: it comes from which protocolresolveProtocolreturns, not from the request payload (getMetaItemLayereddeclares noenvironmentIdmember).503.getMetaItemLayereddocuments a503 SERVICE_UNAVAILABLEfor an overlay read that failed for any reason other than the table not being provisioned yet (that benign case returns normally with a null overlay). Blanket-swallowing that throw — a literal mirror of the dispatcher — would let an availability failure fall through and reach the client as404 Not found, which is exactly the getMetaItem 的 overlay 读用裸 catch 把「sys_metadata 不可达」吞成「该项不存在」—— GET /meta/:type/:name 在存储故障时回一个无 code 的 400「not found」 #5532 defect this package pins inrest-meta-outage-vs-miss.test.ts. A declared status is re-thrown; anything undeclared still falls through, so this cannot make the code-published path newly fail closed.What is deliberately NOT changed
200with its current definition —getPublished's documented fallback, and a different fact from absence.GET /meta/:type/:name/published404s for a runtime-published item — the route is mounted but resolves from the code/registry snapshot, not the rowpublish-draftswrote #8031's own verification arm "an item that is genuinely unpublished still 404s" is false for a code-defined item by design; the version of that arm which is correct (a draft-only item must not be served) is §2 below.501when the resolved service lacks the optionalgetPublished— is out of scope and untouched. Recorded as a finding in [finding] REST/meta/:type/:name/publishedis a 501 on any topology whosemetadataslot lacks the optionalgetPublished#8297; that issue is not addressed here.GET /data/:object/:idfolds no query aliases — the CANONICALfieldsspelling is dropped while the aliasselectworks #8039 andrest-server.ts'slooksLikeMissingRelationmatches unquoted prose, where the shared leak predicate now requires the driver's quoted template #8264 also land in this file and were left strictly alone.Tests
New:
packages/rest/src/meta-published-overlay.test.ts— 9 arms driving the realObjectStackProtocolImplementationand realMetadataManagerover a stub engine (both already devDependencies; the engine double opens onassertEngineUpdateDispatch/assertEngineDeleteDispatch, so it cannot accept a call ObjectQL would refuse).§1 runtime-published item served, published body · §2 draft-only item is not served (the load-bearing arm, mirroring PR #8254's) · §3 code-published item byte-identical to
getPublished· §4 anti-vacuity: the two fixtures resolve from different stores · §5 neither store still 404s · §6 the compound arity · §7 outage is 503 not 404 · §8 the 501 arm survives · §9 the ordering consequence.Reverse verification, direction predicted before running: reverting only
rest-server.tstoorigin/mainturns §1, §4, §6, §7, §9 red and leaves §2, §3, §5, §8 green — observed exactly, 5 failed / 4 passed, with §1/§4/§6expected 404 to be 200, §7expected 404 to be 503(the reverted route really does answer 404 on an outage), §9expected 501 to be 200. Fix restored from its own commit.Full package suite post-merge with current
main: 109 files, 1809 tests passed;pnpm --filter @objectstack/rest typecheckclean.Gates re-derived against the final diff including the changeset, via
node scripts/pm/dispatch-gates.mjs. The five named at dispatch pass (check:authz-resolver,check:cross-package-test-inputs,check:filter-alias-parity,check:meta-type-normalized,check:route-envelope), plus the.changeset-triggered and convention-triggered families the dispatch list did not name:check:changeset-gate-self-tests,check:objectui-changeset,check-changeset-no-major,check:query-options-erasure,check:nul-bytes,check:engine-double-contract— all pass.check:objectui-pin-freshfails on a pre-existing condition unrelated to this diff (.objectui-shauntouched here; the container's/home/user/objectuiclone does not contain the SHA the gate wants).Generated by Claude Code