Repository navigation
fix(metadata-protocol): a draft's _diagnostics reports what its publish will refuse - #26856
Conversation
…sh will refuse The draft read folds the runtime authoring gate's verdict on the stored draft body into _diagnostics: valid:false, each refused rule id as an entry's code, the publish's own message. The publish gate's body is split into judgeRuntimeAuthoringRules (the verdict) and assertRuntimeAuthoringRules (the verdict plus the throw), so the draft read and the publish read one judgement. The draft save is unchanged. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5oVR19x7NPLf4YzCzj
The edit page read `_diagnostics` from the layered read alone, which judges the published body, so a pending draft the publish refuses reached the canvas Problems panel and the diagnostics banner as valid. The draft read's `_diagnostics` now carries the publish's verdict on the draft; the page takes it whenever it holds a draft, and merges its server-only errors with the live client parse. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5oVR19x7NPLf4YzCzj
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5oVR19x7NPLf4YzCzj
…publish verdict Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5oVR19x7NPLf4YzCzj
…'s bound and joins the engine-double ledger Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5oVR19x7NPLf4YzCzj
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 454498c972b9ccc5c2edafbf79dda877fd561d45 && git checkout 454498c972b9ccc5c2edafbf79dda877fd561d45
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a51ba639c165861a8c4ac51015e554911960bf24 d95733e04d49ee6b0161f1741de32ce75a846235 && git checkout -B drift-repro a51ba639c165861a8c4ac51015e554911960bf24 && git merge --no-ff d95733e04d49ee6b0161f1741de32ce75a846235
node scripts/docs-audit/affected-docs.mjs --json a51ba639c165861a8c4ac51015e554911960bf24
|
CI: the two
|
…-8295kq Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5oVR19x7NPLf4YzCzj
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Fixes #26707
Clause-②: no (the draft verdict reports more; no accepted value is refused; the dev re-measures)
Metadata-protocol: none
维护者速读
?state=draft与 previewDrafts)的_diagnostics现在跑发布时那一套运行时规则,发布会拒的草稿读出来就是valid:false,带同一个规则 id 和同一句话;Studio 编辑页和画布 Problems 有草稿时显示草稿的服务端裁决。_diagnostics、Problems 都说「有效」,到点发布才 422,而且一个这样的草稿会让整包发布全部回滚。packages/metadata-protocol/src/protocol-authoring-gate.ts(judge/assert 拆分与draftPublishVerdict)、packages/metadata-protocol/src/protocol.ts的getMetaItem中serveDraft、ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx的serverDiagnostics。pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.draft-publish-verdict.test.ts,预期 12 条全绿(每条规则:草稿读valid:false且与发布 422 的规则 id、消息一一相同)。What changes
Server (
@objectstack/metadata-protocol). The publish's runtime authoring gate is split intojudgeRuntimeAuthoringRules(returns the verdict) and the existingassertRuntimeAuthoringRules(throws it), so there is still one rule set and one gathering of its host facts. A newdraftPublishVerdictruns that judge on a stored draft exactly aspromoteDraftForPublishdoes: the raw stored draft body,state: 'active', the row's package scope, the package's pending drafts as the batch closure (so a reference to a sibling draft that the same Publish-all lands is not an error), and the credential paths the publish would restore.getMetaItemserves its issues in the draft's_diagnostics(withPublishVerdict, metadata-diagnostics.ts):valid: false, and one error per issue withcode= the gate's rule id andmessage= the publish's own message. Paths inside the item are served item-relative (objects.invoice.fields.total.summaryOperationsbecomesfields.total.summaryOperations,flows[0].nodes[0].config.secretbecomesnodes.0.config.secret); a where-style path (a flow edge's condition) is served as the gate wrote it. The arms that do this are the strict draft read (?state=draft) and thepreviewDraftsread of an item with a pending draft. ⛔ No draft save is refused, what publish refuses is unchanged, and package publish keeps its atomic rollback.Studio (
@object-ui/app-shell). The edit page read_diagnosticsfrom the layered read alone, which judges the published body. It now keeps the draft read's_diagnostics(draftVerdict.ts) and shows it whenever a draft is pending: on load, after each autosave, after publish and after discard. The canvas Problems panel and the diagnostics banner get it. Where a live client parse exists, the server errors that parse did not raise are shown beside it, each once. No verdict is computed on the client.Tests
protocol.draft-publish-verdict.test.ts(new, 12 tests): for each of the six rules the card names (expression-invalidon a flow edge,flow-api-trigger-secret-missing,rls-predicate-unenforceable,field/summary-without-operations,dashboard-filter-field-unknown,dataset-include-unknown) the draft save answers 200, the draft read answersvalid: false, and the(code, message)pairs equal the publish refusal's(rule, message)pairs; package publish refuses and the first failed draft readsvalid: false; a clean draft reads{ valid: true }; an in-package forward reference to a pending dataset reads valid and publishes; a dangling one readswidget-dataset-unknown; the preview read equals the strict read; item-relative paths are pinned. Red on base (three source files at8b276385e6): 9 failed, 3 passed (the clean, closure and preview controls).protocol.item-execution-read.test.ts: the served/execution parity for the strict draft and preview arms now includes the draft's publish verdict (the fixture draft declares nosharingModel, which its publish refuses assecurity-owd-unset).ResourceEditPage.draftPublishVerdict-26707.test.tsx(new, 3 tests): the canvas receives the draft read's refusal while the layered verdict says valid; an autosave of a fixed draft clears it; a control with no draft. Red on baseResourceEditPage.tsx: 2 failed, 1 passed (the control).draftVerdict.test.ts(new, 3 tests).Acceptance notes
runAuthoringGate) and the package build probes thatpublish-draftsruns. The list reads (getMetaItemswithpreviewDrafts) and thegetMetaDiagnosticssweep keep their read-time verdicts.CelPredicateFieldstill says "Valid CEL" from its client lint for an RLS predicate over a formula field and for a flow edge condition, while the banner and Problems now show the server refusal. Making those fields read the server verdict for their own path needs the verdict plumbed to the field editors, and the flow edge refusal has a where-style path, not a key path. Handed back as a decision with a proposed child card.{}, which fails the spec schema even as a draft, so it never reaches the runtime gate. The pinned dashboard case isdashboard-filter-field-unknown.protocol.ts(fix(metadata-protocol)!: the Data API refuses an empty projection and a count flag that is neither true nor false #26811, refactor(metadata-protocol): move the post-persistence mutation fan-out out of protocol.ts into protocol-mutation-fan-out.ts (#25467 cut 25) #26807) merge cleanly with this branch (git merge-tree), and their hunks are not adjacent. feat(formula,plugin-email,lint)!: a mail template hole takes one formatter and a literal currency code, refused at authoring and at render #26799 touchespackages/lint/src/authoring-rules.ts, which this PR does not change. metadata: the save door and the package publish door accept a package id that is not installed (object, dashboard, page …), storing and even publishing items into a phantom package; Studio renders a writable builder for it #26708's branch merges cleanly too.Verification
Live REST red/green on the showcase dev server
Branch (
pnpm dev -- --fresh, writable packageapp.probe26707):Base (
metadata-protocolat8b276385e6, rebuilt):Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon the final diff: 74 commands, each run with its exit code captured before any pipe;--ranreconciliation: 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN. The first pass had two reds, both fixed in d996462:check:objectql-double-limit(the new harness'sfinddouble ignoredlimit; it now applies it after the filter) andcheck:engine-double-contract(the new test's pinned doubles registered with--write). Scoped lint: root eslint on the five metadata-protocol files, 0 problems; app-shell eslint on the four touched files, 0 errors (51 warnings, all inResourceEditPage.tsx, which has 54 on base).@objectstack/metadata-protocolfull suite: 272 of 273 files passed on the first run; the one red file,protocol.item-execution-read.test.ts(two parity rows on the draft arms), was updated in 6f54ceb and passes.@object-ui/app-shellmetadata-admin/: 611 files, 6646 tests pass;type-checkexit 0. Downstream draft-read suites (objectqlprotocol-meta,restrest.test,runtimeseed read decorations) pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5oVR19x7NPLf4YzCzj