Skip to content

[finding] REST /meta/:type/:name/published is a 501 on any topology whose metadata slot lacks the optional getPublished #8297

Closed

Description

@os-zhuang

Restart-when: any PR changes the getPublished member of IMetadataService in packages/spec/src/contracts/metadata-service.ts, or a topology ships a non-MetadataManager metadata slot, or any PR touches the /published arities in packages/rest/src/rest-server.ts

Recorded while implementing #8278 (overlay-first resolution on the REST /published arities). The card names this as a latent, independent second failure mode and fences it out of scope; this issue is that fence made durable, plus the one thing #8278's measurement changed about it.

The observation

packages/rest/src/rest-server.ts, both /published route arities, refuses before it reads:

const svc = await this.resolveMetadataService(environmentId, req);
if (typeof (svc as any)?.getPublished !== 'function') {
    res.status(501).json({
        error: { code: 'NOT_IMPLEMENTED', message: 'metadata.getPublished() is not available in this kernel' },
    });
    return;
}

getPublished is an optional member of IMetadataService with exactly one implementation in the repo (MetadataManager). Its store (publishedDefinition) is written by exactly one caller, MetadataManager.publishPackage, reachable only via POST /packages/:id/publish. So on any topology whose metadata slot is filled by something other than MetadataManager, this route is simply a 501.

What #8278 changed about it — the reason this is worth recording rather than dropping

#8278 puts the state:'active' overlay consult ahead of that arm, so the blast radius is now strictly narrower and the remaining 501 means something more specific than it used to.

Measured, as arms §8 and §9 of packages/rest/src/meta-published-overlay.test.ts:

  • §9 — a metadata slot with no getPublished, and a runtime-published item: now 200, served from the overlay. The optional member is not on that path at all.
  • §8 — the same slot with a null overlay: still 501, byte-identical to before.

So post-#8278 the 501 no longer means "this kernel cannot answer /published". It means "nothing is runtime-published and this kernel has no code/package store". That is a materially different (and much rarer) condition than the one the message describes.

Why this is a finding and not a bug

No topology in this repo reaches it today — the one implementation is the one that gets wired. Nothing is broken in a shipped configuration. What is recorded is: (a) an optional contract member with one implementation, one writer and one door, which is the shape that rots quietly; and (b) a 501 message that now over-states its own condition.

If someone picks this up

The interesting question is not "add a second implementation" — it is whether getPublished should remain optional at all, or whether the code/package snapshot it reads is still load-bearing now that the ADR-0027 overlay is the authoritative published store for anything authored at runtime. That is a contract question for the spec/metadata owner, not a REST-side patch. The getPublished fallback semantics are deliberate and documented (an existing-but-never-published item answers 200 with its current definition) and must not be collapsed into a 404 by anyone touching this — see #8278's trap section.

Related

#8278 (the overlay-first fix; records this as out of scope) · #8031 / PR #8254 (the dispatcher-side original) · packages/rest/src/rest-server.ts · packages/rest/src/meta-published-overlay.test.ts §8/§9


Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 13, 2026
  2. added
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Aug 13, 2026
  3. added theissue type on Aug 13, 2026
  4. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    Triage: finding held (correctly self-graded — no shipped topology reaches the 501 today), routed domain:spec, type Task.

    Anchoring rationale: the load-bearing question the card itself poses — whether getPublished remains an optional member of IMetadataService now that the ADR-0027 overlay is the authoritative store for anything runtime-published — lands on packages/spec/src/contracts/metadata-service.ts (location verified via code search), the spec seat's package. The REST-side 501 message overstatement is a consequence of that contract question, not the fix's landing point, so this does not route domain:cli.

    For the spec seat's next finding-triage round: post-#8278 the 501 means "nothing runtime-published and no code/package store" — a much rarer condition than the message claims; weigh that nothing is broken in any shipped configuration when grading. Guard carried from the card: the deliberate getPublished fallback semantics (existing-but-never-published ⇒ 200 with current definition) must not be collapsed into a 404 by whoever eventually touches this — see #8278's trap section.

    本评论来自分诊座位 Routine。


    Generated by Claude Code

  5. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    Concentrated findings round (maintainer-directed, 2026-08-13): HELD (finding + domain:spec unchanged, per the earlier routing).

    Why held: no shipped topology reaches the 501 (the one getPublished implementation is the one that gets wired), and the load-bearing question — should getPublished remain an optional member of IMetadataService now that the ADR-0027 overlay is the authoritative runtime-published store — is a contract-shape call that should ride the contract work already in flight rather than open a second front.

    Named restart conditions: ① the in-flight IMetadataService contract-statement revision (the spec seat's dispatched card on the superseded register ruling) lands — re-read this card against the revised contract text and decide optional-vs-required there; ② any topology ships a non-MetadataManager metadata slot (the 501 becomes reachable — at that moment the message's overstatement matters and the reword rides the fix); ③ anyone touches the /published arities — the #8278 trap section's fallback semantics (existing-but-never-published ⇒ 200) remain binding on them.


    Generated by Claude Code

  6. added
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 13, 2026
  7. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    State-machine migration, maintainer-authorized (2026-08-13, live triage session: 「我授权你先迁标签」; semantics on #8449): this card is a graded hold — verdict and restart conditions are in the grading comment(s) above — so finding → pm:on-hold. Verdict, restart conditions, domain unchanged.


    Generated by Claude Code

  8. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    on Aug 19, 2026
  9. self-assigned this
    on Aug 19, 2026
  10. os-zhuang commented on Aug 19, 2026

    @os-zhuang
    ContributorAuthor

    Hold released → decision inbox (triage seat, session session_014qTKTqjme5Fp5BH9iRmy6t, rider-clause audit): restart ① fired the same day the hold was written — the IMetadataService contract-statement revision it named landed as PR #8481 (2026-08-13, ~2.5h after the hold) — and the promised re-read never happened. Re-read done now on origin/main: getPublished is unchanged and still optional (metadata-service.ts:572), so REST /meta/:type/:name/published still 501s on any topology whose metadata slot lacks it. What remains is not a code fix but the contract question the hold deferred: optional-vs-required. That is a declared-contract shape change → human floor, so needs-user-decision (not queue), assigned to the inbox queue per the #9976 protocol.

    <!-- os-decision-facets -->

    • platform long-term coherence: an optional slot method behind a public REST route means the route's contract depends on topology — either the route's 501 is a declared, documented answer, or the method stops being optional. Both are coherent; the current undocumented split is not.
    • measured business pull: none measured — no consumer reported hitting the 501; the card came from contract-surface analysis, not a field report.
    • AI-agent error-resistance: an agent building against the REST surface cannot see from the spec that /published may 501; a declared verdict (required method, or documented 501 semantics on optional) is checkable either way. Required-method is the louder contract.
    • startup scope discipline: zero pull argues for the cheapest honest option — document the 501 as the optional-slot answer — over obliging every topology to implement getPublished.

    Options: A — keep getPublished optional, declare the 501 in the route's contract/docs (cheapest, honest). B — make getPublished required on IMetadataService (breaking for custom topologies; louder contract). C — keep optional but have the route degrade to getMeta+published-filter semantics (new behavior; widest change).

    Recommendation: A, on zero measured pull and scope discipline. Confidence gap: this analysis has not enumerated how many real topologies lack getPublished (only MetadataManager was checked historically); if every shipped topology already implements it, B costs nothing and is the better contract.


    Generated by Claude Code

  11. removed their assignment
    on Aug 19, 2026
  12. os-zhuang commented on Aug 19, 2026

    @os-zhuang
    ContributorAuthor

    Self-correction (same triage session): assignee cleared — the #9976 assign-to-inbox routing was retired by skill PR #9994 (merged 13:00Z today; decision cards are not pushed, the inbox is digested periodically). The needs-user-decision label and the facet block above are the operative record; nothing else changes.


    Generated by Claude Code

  13. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    ContributorAuthor

    Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session session_01PjAP6vbcsg2yMtvySPv1Qo)

    Ruled: minimal action. Provenance: maintainer accepted the batch, verbatim: 「其他接受你的建议。」

    S-tier mechanical; Clause-②: no expected. State transition in the same stroke: needs-user-decision → pm:queue.


    Generated by Claude Code

  14. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:specDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchrepo:objectstack

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions