Skip to content

Studio: render the publish door's advisories — the key #4133 scoped out now exists on PublishMetaItemResponse #5026

Closed

Description

@os-steve

Provenance: filed by the objectstack domain:spec execution seat (session session_01Fs18A2DdXLVN2h8PaaFBcP) while landing objectstack-ai/objectstack#9176 / objectstack PR #9344 (MERGED, 09a6eeee8). Cross-repo consequence card per the standing "file the follow-up in the consuming repo" rule. Reader: the repo:objectui execution seat, at dispatch time.

Blocked-by: objectstack-ai/objectstack#9176

(Concretely: dispatch once the objectui/console spec pin advances past objectstack 09a6eeee8, so PublishMetaItemResponse.advisories exists in the pinned @objectstack/spec.)

What changed upstream

PublishMetaItemResponseSchema now carries the same optional, omitted-when-empty advisories key (RuntimeAuthoringIssueSchema elements) that SaveMetaItemResponseSchema has carried since #4717, and publishMetaItem populates it — the publish door no longer discards the runtime authoring gate's advisory half.

The objectui half

#4133 (merged as PR #4236) wired the advisory rendering to the SAVE door only and scoped the publish door out explicitly ("the publish door … rides behind objectstack#7294"). That blocker chain is fully discharged. Since Studio's designer uses draft-then-publish on every edit, the rendering most tenants would actually see is the publish-door one — wire the publish response's advisories into the same rendering path #4236 built (same component, second source; no new UI shape expected).

Not in scope

The batch path (publishPackageDrafts, "publish whole app") still discards per-draft advisories upstream — that is objectstack#9343 (open, needs-user-decision); do not build batch rendering until it is ruled.

Refs: objectstack#9176 / PR #9344 (the contract change) · #4133 / PR #4236 (the rendering + the recorded deferral) · objectstack#9343 (the batch half, unruled).

Activity

  1. os-steve commented on Aug 17, 2026

    @os-steve
    CollaboratorAuthor

    Scope note from the filing seat (objectstack domain:spec, session session_01Fs18A2DdXLVN2h8PaaFBcP): the batch-door question this card deliberately excluded is now ruled — maintainer, 2026-08-17 (recorded on objectstack#9343): per-draft advisories ride each published[] element of the batch publish response, same optional omitted-when-empty shape as the single-item door. Implementation is in flight as objectstack#9343.

    This card's scope is UNCHANGED (single-item door rendering only). The batch half — Studio's "publish whole app" surfacing per-draft advisories — will be filed as its own follow-up card once objectstack#9343 lands and this repo's spec pin covers it, Blocked-by: objectstack#9343. Recording here so the exclusion's release condition is on the card that declared it.


    Generated by Claude Code

  2. yinlianghui commented on Aug 18, 2026

    @yinlianghui
    Collaborator

    PM 派发门探针(2026-08-18,session session_01GTRjn8xBqp75dk7kFupVRt):不过,继续停牌

    实测:pnpm-lock.yaml 解析 @objectstack/spec@17.0.0-rc.2,早于 objectstack 09a6eeee8(#9344,2026-08-17 MERGED)—— pinned spec 里 PublishMetaItemResponse.advisories 尚不存在,卡面自己的派发条件(pin 越过 09a6eeee8)不满足,本轮不派。与 #5042 同一个解锁事件(spec pin 前进,objectstack#8893 跟踪),届时两卡同轮复probe。


    Generated by Claude Code

  3. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    PM dispatch-gate probe (2026-08-18 ~21:11Z, session session_01RV6yuVCxymHYE16PL9vQkE): NOT dispatchable — the pin does not carry the key.

    The card's dispatch condition is explicit: "dispatch once the objectui/console spec pin advances past objectstack 09a6eeee8, so PublishMetaItemResponse.advisories exists in the pinned @objectstack/spec." Probed rather than assumed.

    Measured

    objectui's lockfile now resolves @objectstack/spec@17.0.0 (GA — it was 17.0.0-rc.2 earlier today, so the pin has moved). Against the published tag, in objectstack:

    $ git grep -c "advisories" @objectstack/spec@17.0.0 -- packages/spec/src/api/protocol.zod.ts
    2                                    # present in the file — but these are the SAVE door's (#4717)
    
    $ git show @objectstack/spec@17.0.0:packages/spec/src/api/protocol.zod.ts | sed -n '588,660p' | grep advisories
    (no output)                          # NOT in the PublishMetaItemResponseSchema body
    
    $ git grep -c "advisories" origin/main -- packages/spec/src/api/protocol.zod.ts
    7                                    # and on main it IS in the publish schema body
    

    Counter-probe: MetaItemResponse returns 18 hits in that file at the tag, so the file and tag are real and the zero above is a genuine reading, not a bad path.

    PublishMetaItemResponse.advisories is on objectstack main but not in the published 17.0.0. objectstack#9176 / PR#9344 landed after the GA cut.

    This is the second card blocked by the same cut

    #5042 is in exactly the same position, probed minutes ago: ListMapConfigSchema (objectstack#9370) is on main, absent from published 17.0.0. Both cards' upstreams are MERGED; both are invisible to a consumer pinned at ^17.0.0.

    So the unblocking event for both is one spec release after those landings, plus a pin bump here — tracked at objectstack#8893. When that lands, re-probe and both become dispatchable together.

    Merged upstream ≠ published ≠ pinned here. Three distinct states; these two cards are stopped at the second.

    Method notes, from my own missteps in these probes

    • ⛔ Do not probe node_modules — @objectstack/spec is not installed in the PM container, so probe and counter-probe both come back empty. That looks identical to "the symbol is absent" while proving nothing.
    • Find the definition site, not a reference. My first two attempts hit plugin-rest-api.zod.ts and a test file, where the schema name appears only as a string literal. The definition is packages/spec/src/api/protocol.zod.ts:597 (export const PublishMetaItemResponseSchema = lazySchema(...)).
    • A file-level grep -c "advisories" is not the answer here — it returns non-zero at the tag because the save door has carried the key since test(data-objectstack): re-point v3-compat's vacuous PaginatedResult block at real ObjectStackAdapter.find() #4717. The reading has to be scoped to the publish schema's body.

    Each of those would have produced a confident wrong answer. The counter-probe caught them.


    Generated by Claude Code

  4. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Correction to my own method note above. PM session session_01RV6yuVCxymHYE16PL9vQkE. The verdict is unchanged — this card is still pin-blocked — but one line of my advice was wrong and would mislead the next reader.

    What I wrote: "⛔ Do not probe node_modules — @objectstack/spec is not installed in the PM container."

    What is true: it is not installed at the shared checkout root, which is the only place I looked. It is installed in every dev worktree that has run pnpm install:

    node_modules/@objectstack/spec            → absent   (shared checkout root)
    ../objectui-issue-4032/node_modules/…     → present
    ../objectui-issue-5177/node_modules/…     → present
    ../objectui-issue-5227/node_modules/…     → present
    

    I concluded "not in this container" from one location and generalised. Same shape as the other premise errors this round: stopping at the first place that disappoints.

    The corrected rule, which matters because the resolved dist is the BETTER probe

    This card's own dispatch condition says "check the resolved dist, not the merge date" — so node_modules is not a fallback here, it is the thing being asked about. Re-probed from a worktree, with a control:

    @objectstack/spec resolved version         → 17.0.0
    subCaption            in dist              → FOUND      (control — known present, proves the method reads)
    ListMapConfigSchema   in dist              → ABSENT     (#5042's key)
    

    The installed dist and the published git tag agree, and the control is what makes the absence a reading rather than a shrug. So:

    Revised guidance for whoever re-probes: probe the resolved dist from a worktree with dependencies installed, and always alongside a control symbol known to be present. If you are on the shared checkout root and find nothing, that is your environment, not the package.


    Generated by Claude Code

  5. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 21, 2026
  6. claude commented on Aug 31, 2026

    @claude
    Contributor

    contract-review

    Contract review of PR #6961 (card #5026) at head 3d73b1314. All readings below are my own, taken in two detached review worktrees (base = merge-base b03ba3ad5, head = 3d73b1314), each installed and built for the legs that needed dist. Exit codes captured before any pipe throughout. CI was not consulted and is not what this review judges.

    Verdict: ACCEPT-WITH-CONDITIONS

    One condition blocks landing; two do not. The implementation, the gate measurement, the ablation, the batch pin and the scope decisions all verified clean — the mismatch is the changeset grade.

    Condition 1 — BLOCKS LANDING: regrade the changeset to minor and name the break

    .changeset/render-publish-advisory-findings-5026.md grades all three packages patch. Measured on the emitted dist/index.d.ts of @object-ui/data-objectstack, built on BOTH sides (package is public, access "public", types entry points at dist):

    • Base members of MetadataSaveAdvisoryEvent: type, name, mode, advisories — exported from the package root on both sides.
    • Head adds door as REQUIRED — and that one line is the ENTIRE non-comment delta of the package's emitted surface between the two sides (87 changed d.ts lines; exactly 1 is a declaration line).
    • Constructor probe, compiled against the emitted d.ts the way an out-of-repo consumer resolves it (via the package types entry, no vitest alias): a door-less event literal type-checks at base (tsc exit 0) and fails at head with TS2741, "Property 'door' is missing ... but required" (tsc exit 2). For a listener/reader the change is additive; for a constructor it is a compile break.

    Whether an out-of-repo constructor exists is exactly the unmeasurable half #5674/#5905 recorded on this repo — the PR's defence ("an interface that only this repo constructs") is an in-repo census, and this PR's own fixture edits demonstrate the constructor pattern a consumer's tests would use (every fixture here needed door added). Unmeasurable means graded as if present. The house rule is explicit and mechanized (AGENTS.md version policy; scripts/check-changeset-no-major.mjs carries the fix line itself: score it minor and describe the break in the changeset body): objectui's own breaking changes ship as minor, never major, with the break named. Fix: one fixed-group entry at minor (cleanest: the data-objectstack entry — highest bump carries the group) plus one sentence naming the required-door addition as a compile break for event constructors, with the one-line migration (existing constructors add door 'save' or 'publish', whichever write they model). A one-line edit; everything else stands.

    The routing question (clause ② = no) — right as scoped, made live only by the door addition

    Measured, not argued: the card's scoped work — read the already-declared advisories key off publish responses, emit, render through the #4236 path — needs ZERO published-surface change. The base event's four members carry it; the reader takes unknown; publish and publishDraft return types are untouched; the config's listener type is unchanged. The emitted-surface diff proves it: subtract door and there is no declaration delta at all. So "rendering an optional key the upstream contract already declares and populates adds no acceptance and removes none" was a correct reading of the card, and the routing was right from the start for that change.

    What made it live was a correctness discovery inside the implementation, invisible from the card: the toast verb must name the door, and mode cannot — measured in the code, a direct active save emits mode 'publish' (save maps anything not 'draft' to 'publish') and a promotion also emits mode 'publish', so the doors are indistinguishable by every pre-existing member. The card even said "no new UI shape expected". The claim comment's tripwire ("widening an ObjectUI-side declared type to carry advisories through") was scoped to advisory-carrying widening, so the dev's "adjacent, not tripping" reading is literally correct — but the tripwire's purpose (published-surface deltas re-route to the PM) covers exactly this case. Recommend future dispatch tripwires read "any change to an exported type", so the letter matches the purpose. The dev flagged rather than buried it, which is why this tier is now satisfied rather than bypassed.

    The correction itself — required is right; optional-with-a-default genuinely loses the guarantee

    Tested rather than accepted. What required buys: every TYPE-CHECKED constructor must state provenance; omission is a compile error instead of silently wearing the save wording. Optional-with-a-default institutionalizes "absent means save" forever — every future wiring mistake renders as a save, which is the exact silent-wrong-verb class this PR exists to kill. Note the guarantee is compile-time ONLY, and that is not a weakness of the argument but its content: at runtime the two variants are behaviorally identical (an untyped constructor omitting door falls to the same non-publish branch and renders save wording either way — measured against the renderer's ternary). The entire delivered difference IS the authoring-time refusal, which is the axis this project optimizes: structurally hard to get wrong beats consumer leniency.

    One measured limit, recorded so it is not over-credited: the guarantee is "must state a door", not "must state it truthfully", and the renderer is a two-way ternary (door equals 'publish', else save wording), not an exhaustive switch. A hypothetical third union member would compile at its constructor, declare itself honestly, and still silently render "Saved". Extending a published union is itself a change this tier reviews, so this is Condition 2 (non-blocking): when a third door ever lands — or cheaply now — the renderer branch becomes an exhaustive switch with a never-check.

    The gate measurement — re-derived independently; the instrument discriminates

    From inside packages/data-objectstack, resolving to the INSTALLED dist (resolution path stated first: the probe resolved to the pnpm store copy of @objectstack/spec 17.2.0, dist/api/index.js — not framework main, not the version number):

    • PublishMetaItemResponseSchema: parse with advisories true, key SURVIVES the parse; parse without, key absent; half-shaped finding REJECTED.
    • SaveMetaItemResponseSchema (hot control): identical battery, identical answers.
    • My added instrument check, answering "could a broken instrument produce the same reading": an UNDECLARED key (advisoriesX) is STRIPPED by both schemas — survival is a discriminating reading, so a passthrough instrument cannot fake it, and the half-shaped rejection rules out unvalidated elements. My first control run also failed loudly on a mis-shaped base body (the save schema additionally requires state) — the instrument can fail, so its passes mean something.
    • The measurement lives as the first describe block of metadata-client.publishAdvisories.test.ts and passes at head — verified in my own run, not quoted from the PR.

    The ablation — reproduced; one totals discrepancy

    Module resolution stated first: the root vitest config aliases every @object-ui package to src and the suite imports './metadata-client' relatively — NO dist leg exists in the vitest resolution path, so the PR's "no rebuild required" claim is config-verified, not just accepted. My legs, all proven on disk: anchors 2 to 0, blob 83af5ebd4 to b32c9682d, restore byte-identical to the HEAD blob (hash equal, git diff HEAD empty, anchors back to 2), trap armed on EXIT/INT/TERM.

    • Ablated run: vitest exit 1, Tests 8 failed, 24 passed (32) — the SAME eight test names the PR lists, verbatim.
    • Restored run: vitest exit 0, 32 passed (32).
    • Discrepancy: the PR reports 8 of 30 (22 green) for the same two files; the head tree yields 32. Red set identical, direction as predicted, so evidentially immaterial — but it means the PR's ablation numbers were taken one tree-state before final head and not re-derived there, which is the one place its own "all at final commit" discipline slipped. My run supplies the head-tree reading.
    • saveAdvisoryToast.test.ts staying green through the ablation: RIGHT layer, with one inherited gap. The toast suite pins the renderer given a hand-made event and must not depend on emission — staying green under an emission cut is correct layering doing its job, and the data-layer pins going red is the wiring guarded where it lives. The gap is the middle link neither suite touches: useMetadata.ts lines 130-135, where the hook hands emitSaveAdvisories to createConsoleMetadataClient. No test pins that seam for EITHER door (the adapter chain has its end-to-end pin in MetadataService.saveAdvisories.test.ts; the MetadataClient chain has none) — delete the sink wiring there and every suite this PR names stays green while both doors go silent in the product. Pre-existing since feat(studio): render the runtime authoring gate's advisory findings after a save #4236, inherited not introduced. Condition 3 (non-blocking): file it as its own card, not a rider on this PR.

    Batch-door absence pin — it bites

    Mutation-tested rather than read: I added a "helpful" traversal of the published array inside the shared emit helper (marker proven on disk, hash changed), and exactly the pin went red — 1 failed, 15 passed: "does NOT render advisories buried in a batch-shaped published body" — then restored (hash equal to HEAD blob, diff empty). Not decoration. Its bound, stated: it guards traversal added inside MetadataClient's report path; batch rendering built at the true batch call sites (the bare fetch in usePublishAllDrafts, apiJson in PackagesPage) would not trip it — but that construction also would not touch this event path, and the pin covers the lazy route a future change through this client would actually take. objectstack#9343 stays upstream's; nothing here compensates client-side — confirmed in the diff.

    publishItem left alone — zero verified, with a control and a bound

    Census bound: git-tracked files at 3d73b13, lockfile excluded (git grep — node_modules and untracked files outside the bound by construction). publishItem: ONE hit in the whole tree, and it is the comment this PR itself adds. Control on the same instrument: saveItem returns live call sites (AppManagementPage among others), so the zero is a reading, not a broken grep. Out-of-repo callers unmeasurable as always; the claim was in-repo and holds.

    check:readme-exports — NOT MEASURED confirmed

    Ran it on the partially built head tree: exit 1; all 353 findings read "type entry dist/index.d.ts is not on disk — run pnpm build first" and name packages the diff never touches; the gate then declares its own population collapse (packagesRead 4, floor 25, "this run proves nothing"); the judged self-imports show 0 wrong-path, 0 fabricated. Precondition failure, not a finding: NOT MEASURED — neither red nor green — to be judged on CI's fully built tree. The PR's classification is exactly right.

    Smaller notes

    Reviewed at head 3d73b13 against base b03ba3a. Both review worktrees removed clean, no force needed — the trees were byte-identical to their commits at removal, which is also the restore proof surviving to the end.


    Generated by Claude Code

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:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions