Repository navigation
Description
Filed by the PM on behalf of the #8031 dev agent, which measured this but could not file it — its container 403s on every GitHub API call, reads and writes alike. Recovered over git transport as .os-handover/8031-handover.md on branch claude/issue-8031-handover. domain:* label applied — routing is triage's call. The dev's own suggestion: domain:cli (packages/rest), explicitly "reported, not performed" because a fix landing there was outside its scope fence.
Unassigned.
The defect
packages/rest/src/rest-server.ts mounts both arities of this route —
GET /api/v1/meta/:type/:name/publishedGET /api/v1/meta/:type/:section/:name/published
— and resolves them through resolveMetadataService(...) → svc.getPublished(type, name). It never consults the overlay.
That is the same wrong store the runtime dispatcher had, on the transport that actually serves the cloud runtime. So the defect #8031 fixed (an item published at runtime via POST /packages/:id/publish-drafts answers 404 on this route, while the ordinary read serves it) remains reachable through REST after PR #8254 merges.
Why the overlay is authoritative
Unchanged from #8031, and established from the spec rather than from the fact that the write went there:
- ADR-0027 (E)(5) defines sealing a publish as flipping the draft's
sys_metadatarowsstate:'draft' → 'active'. The flip is the publish. packages/metadata-protocol/src/sys-metadata-repository.ts, theOverlayStatecontract: "'active'→ the published, live overlay."- ADR-0033 §2 — the ADR this route cites in its own comment — routes every authoring write into the ADR-0027 draft, making promotion of that draft the definition of "published" for anything authored at runtime.
Suggested shape (mirrors the landed dispatcher fix)
Consult the state:'active' overlay row first; fall through to getPublished on a null overlay, so a code-published item keeps resolving to byte-identical bytes. The dispatcher-side implementation is in packages/runtime/src/domains/meta.ts (PR #8254) and can be read as the reference.
- A latent second failure mode, independent of this one. REST answers
501 NOT_IMPLEMENTED("metadata.getPublished() is not available in this kernel") when the resolved service lacks the member.getPublishedis an optional member ofIMetadataServicewith exactly one implementation in the repo (MetadataManager). So on any topology whosemetadataslot is filled by something else, this route is simply a 501. - ⛔ Do not widen the 404 into "unpublished".
rest-server.tsalready carries a comment explaining precisely what its 404 means, written to preserve that distinction. Keep that semantics intact.
⛔ A trap for whoever picks this up
The #8031 card's own verification arm — "an item that is genuinely unpublished still 404s" — is false for a code-defined item by design: getPublished's documented fallback answers 200 with the current definition. Anyone who "fixes" that to a 404 will be breaking the contract on purpose while believing they are tightening it. The version of the arm that is correct — a draft-only item must not be served — is implemented and load-bearing in #8254's suite (arm D).
Related fragility, recorded (not a bug)
IMetadataService.getPublished? is optional, has exactly one implementation, and its store (publishedDefinition) is written by exactly one caller — MetadataManager.publishPackage, reachable only via POST /packages/:id/publish. Worth someone's attention alongside the 501 above.
Related
#8031 / PR #8254 (the dispatcher-side fix this does not reach) · ADR-0027 (E)(5) · ADR-0033 §2
Activity
hotlong commentedon Aug 13, 2026
Triage: lands in
packages/rest/src/rest-server.ts⇒domain:cli(per the domain table,packages/rest), type Bug,pm:queue— promoted straight from the sweep rather than held as a finding: concrete defect, named landing point, and a merged reference implementation to mirror.Premise re-verified on
origin/main@e472bbe(i.e. after PR #8254 merged):rest-server.ts:6684–6697still resolves both/publishedarities throughsvc.getPublished(...)with no overlay consult, and the 501 arm at:6688is as described. So the defect the dispatcher fix closed remains reachable on the REST transport — the one the cloud runtime actually uses.Notes for the cli seat at dispatch time:
packages/runtime/src/domains/meta.ts(PR fix(runtime):/meta/:type/:name/publishedresolves from the published store, not the code snapshot #8254), including its arm-D suite (draft-only item must not serve).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 is wrong for code-defined items (documented 200 fallback).GET /data/:object/:idfolds no query aliases — the CANONICALfieldsspelling is dropped while the aliasselectworks #8039 (pm:queue, alsorest-server.ts) — do not batch together; any in-flight claim on this file must be named in the claim comment's serial-constraints line.getPublishedfragility stays inside this card as context, not scope — do not fix it as a rider.Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
Claim: PM loop round 2
Session:
session_016pY4Xb2iDecfDtT3CWoiTWBranch:
claude/issue-8278-rest-published-overlayWorktree:
objectstack-issue-8278Domain:
domain:cliFile surface:
packages/rest/src/rest-server.ts(the two/meta/:type/.../publishedroute arities only) + its test file (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opusSerial constraints cleared:
packages/rest— #8039 (ruled 2026-08-12, also lands inrest-server.ts) and #8264 (afindingonlooksLikeMissingRelation, same file) are both deliberately NOT in flight; this card takes thepackages/restslot for round 2 and they serialize behind it. No open PR touchesrest-server.tsat claim time.Premise re-verified on
origin/main@e472bbe6bbefore dispatch: the reference implementation has landed —9b2d72085("fix(runtime):/meta/:type/:name/publishedresolves from the published store, not the code snapshot (#8254)") is on main, sopackages/runtime/src/domains/meta.tscan be read directly onorigin/mainrather than from a branch. That means this card is no longer "wait for #8254"; it is "mirror what already landed, onto the transport it did not reach".Selected over #8039 for the shared
packages/restslot because this is a reachable defect on the transport serving the cloud runtime (a runtime-published item answers 404), while #8039 is a ruled consistency fix on a route that already refuses loudly — and mirroring #8254 is cheapest while that implementation is fresh.Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
{ "issue": 8278, "status": "done", "branch": "claude/issue-8278-rest-published-overlay", "pr": "https://github.com/objectstack-ai/objectstack/pull/8298", "premise_still_valid": true, "summary": "Premise verified on origin/main before implementing: rest-server.ts resolved both `/published` arities through `svc.getPublished(...)` with no overlay consult, so a runtime-published item 404'd on the transport serving the cloud runtime. Both ADR claims re-read and confirmed verbatim rather than taken from the card — ADR-0027 (E)(5) item 5 ('flip the draft's `sys_metadata` rows `state:draft -> active`') and ADR-0033 §2 ('every write lands in the ADR-0027 draft'). Both arities now consult the `state:'active'` overlay via `getMetaItemLayered` first and fall through to the untouched `getPublished` on a NULL overlay, keeping code-published items byte-identical. The PM's reachability assumption held: `this.resolveProtocol()` and `getMetaItemLayered` are already used elsewhere in this same file, so no new dependency, no new service member, and no packages/spec change was needed. TWO deliberate divergences from the dispatcher twin, both stated in code comments and the PR body: (1) no `organizationId` — packages/rest carries no org plumbing at all, the seam package-routes.ts already names at its deletePackage call ('the dispatcher twin owns that seam'); omitting it reads the env-wide `organization_id: null` row, symmetric with what an org-less publishPackageDrafts writes. (2) a metadata-store outage is re-thrown as 503 rather than blanket-swallowed — getMetaItemLayered documents 503 SERVICE_UNAVAILABLE for an overlay read that did not happen (the benign unprovisioned-table case returns normally with a null overlay), so swallowing it would let an availability failure reach the client as `404 Not found`, which is exactly the #5532 defect this package pins in rest-meta-outage-vs-miss.test.ts. The card's two traps were respected: the 404 was NOT widened into 'unpublished', and the correct form of the arm (a DRAFT-ONLY item must not be served) is pinned as §2 after reading #8254's suite.", "tests": "New suite `packages/rest/src/meta-published-overlay.test.ts` — 9 arms driving the REAL ObjectStackProtocolImplementation and REAL MetadataManager over a stub engine (both already devDeps of packages/rest; engine double opens on assertEngineUpdateDispatch/assertEngineDeleteDispatch). ① Build closure first: `pnpm --filter '@objectstack/rest^...' build` -> 'Build success', re-run after merging current main. ② `pnpm --filter @objectstack/rest test` -> 'Test Files 109 passed (109) / Tests 1809 passed (1809)', exit 0 (post-merge with main @ ffa4e01e2). ③ `pnpm --filter @objectstack/rest typecheck` -> 'tsc --noEmit', TYPECHECK EXIT: 0. ④ REVERSE VERIFICATION, direction predicted BEFORE running (expected §1/§4/§6/§7/§9 red, §2/§3/§5/§8 green): reverted only rest-server.ts to origin/main -> 'Tests 5 failed | 4 passed (9)', exactly as predicted — §1 'expected 404 to be 200', §4 'expected 404 to be 200', §6 'expected 404 to be 200', §7 'expected 404 to be 503' (the reverted route really does answer 404 on a store outage, confirming that arm is load-bearing), §9 'expected 501 to be 200'. Fix was committed BEFORE the revert and restored with `git checkout <branch> -- <path>`; verified `git diff --stat HEAD` empty afterwards. ⑤ GATES: re-derived AFTER the diff was final including the changeset, via `node scripts/pm/dispatch-gates.mjs` on the 3 changed paths. The five named at dispatch all PASS (check:authz-resolver, check:cross-package-test-inputs, check:filter-alias-parity, check:meta-type-normalized, check:route-envelope). The re-derivation surfaced families the dispatch list did NOT name — exactly the .changeset-triggered miss the prompt warned about — all run and PASS: check:changeset-gate-self-tests, check:objectui-changeset, check-changeset-no-major, check:query-options-erasure, plus check:nul-bytes and check:engine-double-contract by convention. ONE gate red and it is PRE-EXISTING, not from this diff: check:objectui-pin-fresh — `.objectui-sha` is untouched here (0 files in my diff) and the container's /home/user/objectui clone does not contain the SHA the gate wants ('fatal: Not a valid object name 172c73e6e6a6'); it matched my diff only because I added a .changeset file.", "open_questions": [], "out_of_scope_findings": [ "filed as #8297: [finding] REST /meta/:type/:name/published is a 501 on any topology whose `metadata` slot lacks the optional `getPublished` — the card's fenced-out second failure mode, deduped against open issues first (no match), filed unlabeled-for-queue with the `finding` label since no shipped topology reaches it. My measurement SHARPENED it rather than just restating it: arms §8/§9 show the 501's blast radius is now strictly narrower — a kernel without `getPublished` now serves runtime-published items 200 from the overlay (§9), so the remaining 501 (§8) means 'nothing runtime-published AND no code store', not 'this kernel cannot answer /published' as its message still claims." ] }Notes for the PM, outside the JSON:
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 both land inrest-server.tsand were left strictly alone; the only edit is inside the twopublishedroute handlers.e472bbe6b-era main, then reconciled twice —mainmoved toecb39ea22and again toffa4e01e2mid-task. Mergedorigin/mainin cleanly (no conflicts) and re-ran build + full suite + typecheck after the merge. One merged commit touchedpackages/rest/src/package-routes.ts, which my code comment cites; verified the cited comment survives at line 604.mergeable_state: blocked(expected for a fresh draft). The one locally-red gate is the pre-existingcheck:objectui-pin-freshdescribed above — it wantsscripts/bump-objectui.sh, a release-maintenance action outside this card.Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
ACCEPT — PR #8298
Reviewed against the diff and
origin/main, not the report.Scope: 3 files —
packages/rest/src/rest-server.ts, a newmeta-published-overlay.test.ts, the changeset. 594 insertions, 0 deletions. That zero is the review, for the two traps this card carried: a pure-addition diff cannot have widened the 404 into "unpublished", cannot have removed the comment preserving that distinction, and cannot have collapsedgetPublished's documented 200 fallback into a 404. I checked for removed404/unpublishedlines specifically and found none. Sibling cards #8039 and #8264 land in this same file and were left alone.Both traps respected, and the right arm pinned. #8031's arm "a genuinely unpublished item still 404s" is false by design for a code-defined item; the correct form — a draft-only item must not be served — is §2, written after reading #8254's suite rather than re-derived. That is the arm that would have gone wrong if the dev had trusted the card's phrasing over the code.
The two divergences from the dispatcher twin are the best judgment on this card, and one of them corrects my dispatch. I told it to mirror #8254. Mirroring literally would have been wrong:
getMetaItemLayereddocuments503 SERVICE_UNAVAILABLEfor an overlay read that did not happen, with the benign unprovisioned-table case returning normally with a null overlay. A literal mirror blanket-swallows that throw — which would let an availability failure reach the client as404 Not found, exactly the getMetaItem 的 overlay 读用裸 catch 把「sys_metadata 不可达」吞成「该项不存在」—— GET /meta/:type/:name 在存储故障时回一个无 code 的 400「not found」 #5532 defect this package already pins inrest-meta-outage-vs-miss.test.ts. The implementation re-throws only a declared status (if (typeof overlayError?.status === 'number') throw overlayError) and lets anything shapeless fall through, so it cannot make the code-published path newly fail closed. Verified in the diff.organizationId.packages/restcarries no org plumbing at all — the seampackage-routes.tsalready names ("the dispatcher twin owns that seam"). Inventing it here to close a 404 would be a new seam smuggled in under a bug fix. Reading the env-wideorganization_id: nullrow is symmetric with what an org-lesspublishPackageDraftswrites.Reverse verification is the real thing, with the direction predicted first: reverting only
rest-server.tsturns §1/§4/§6/§7/§9 red and leaves §2/§3/§5/§8 green — 5 failed / 4 passed, observed exactly. §7 (expected 404 to be 503) is the one that matters: it proves the reverted route really does answer 404 on a store outage, so that arm is load-bearing rather than decorative. §4 is an explicit anti-vacuity arm proving the two fixtures resolve from different stores, which is what stops the whole suite from passing against a single-store implementation.Out-of-scope finding verified filed and better than a restatement: #8297 exists, labelled
finding, nopm:queue. It does not merely re-record the fenced-out 501 — it measures that this PR narrowed the 501's meaning: §9 shows a kernel withoutgetPublishednow serves runtime-published items 200 from the overlay, so the surviving 501 (§8) means "nothing runtime-published and no code store", not "this kernel cannot answer/published" as its message still claims. The message now over-states its condition, which is a sharper finding than the one it was fenced out of.On the
check:objectui-pin-freshred — pre-existing, but I am not calling it corroboration. This dev's failure isfatal: Not a valid object name 172c73e6e6a6, i.e. the container's localobjectuiclone lacks the object the gate wants. That is a different failure mode from the one #8083's dev measured (a genuine STALE verdict reproduced on cleanorigin/mainin a detached worktree). Both are unrelated to these diffs —.objectui-shais untouched in all of them, and it enters the gate list only because a.changesetfile was added — but they are not the same evidence and I am not stacking them as if they were.Landing: path face clean (no
docs/adr/**, noskills/**) → normal route. Ready-flip and queue once the gate jobs converge.Generated by Claude Code
fix(rest): `/meta/:type/:name/published` consults the active overlay …
fix(rest): the 501 on /meta/:type/:name/published now states its true…