Repository navigation
finding: ?preview=draft self-inflicts an invalid diagnostic — the injected _draft key is validated, so the read carries _diagnostics.valid:false #7656
Description
Activity
Triage — dual-state resolution:
pm:queue+domain:metadatakept;findingremoved (queue+finding is an illegal pair).- Why queue: located, reproducible, self-contained — the read injects
_draft:trueand then strict-validates with the key still present. Verified onorigin/main@5823d59: both injection sites live exactly as filed (packages/metadata-protocol/src/protocol.ts:4441single-item,:4261list overlay), andstripReadDecorationsis imported (:72) and used on the write path (:9581) but not on the draft-read_diagnosticspath. The fix direction (strip stamped keys before computing diagnostics) is the mechanism the codebase already owns — nothing to rule. - Known class: next instance of the closed meta:
applySystemFieldsstampsindexedonorganization_id— a keyFieldSchemarejects by name, so every registry-backed object read answers_diagnostics: { valid: false }#6810 pattern (stamped read-decoration key rejected by the strict schema); the in-flight finding:GET /api/v1/meta/diagnosticsreports 94/94 registry entries INVALID — the platform's own__searchcolumn carries a keyFieldSchemarejects, plus a missingconfig#7561 is theindex-key sibling atsearch-companion.ts— different site, not a duplicate, but the dispatching lane may want one dev to take both for the shared discipline. - Routing kept:
domain:metadata— both fix sites inpackages/metadata-protocol. target:v17: no — draft-preview diagnostics decoration, admin-facing cosmetic-semantic; not one of the four blocking classes.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Why queue: located, reproducible, self-contained — the read injects
huangyiirene commented
on Aug 12, 2026 CollaboratorAuthorMore actionsClaim: PM loop round 12
Session:session_01AmES43BMDg4bPrxTdi5q7t
Branch:claude/issue-7656-draft-preview-diagnostics
Worktree:objectstack-issue-7656
Domain:domain:metadata
File surface:packages/metadata-protocol/src/protocol.ts— region: the draft-preview read decoration and the_diagnosticscomputation it feeds (the_draftstamp sites and their validate path) — plus its tests. (stop on breach; explain in the report)
Container & model: M,mode:subagent,model: opus
Serial constraints cleared:protocol.tsis hot, with two siblings claimed on it this round in disjoint regions — #8136 (client-facing message construction in error/catch paths) and #8003 (theARRAY_VALUED_QUERY_SLOTSdocblock,~:1763). #7748 is held behind #8136 (audit-write sites). This card's region (~:4462/~:4672) does not intersect any of them. Mergemainbefore the PR and again if a sibling lands; conflicts go to the merge queue, ⛔ not hand-ordered.Verified on
origin/mainimmediately before dispatch:(data as any)._draft = trueatprotocol.ts:4462and(draftItem as any)._draft = trueatprotocol.ts:4672— the card says~4441/~4261, so both have drifted; re-locate by reading, not by line number.stripReadDecorationsis live and exported frompackages/metadata-protocol/src/index.ts, with existing pins inprotocol.read-decorations.test.ts.
Generated by Claude Code
huangyiirene commented
on Aug 12, 2026 CollaboratorAuthorMore actions{ "issue": 7656, "status": "done", "branch": "claude/issue-7656-draft-preview-diagnostics", "pr": "https://github.com/objectstack-ai/objectstack/pull/8179", "premise_still_valid": true, "summary": "Premise confirmed by reproduction before any edit: a draft preview read stamps `_draft:true` and then re-parses the item against the closed schema, so a valid draft came back `_diagnostics.valid:false / Unrecognized key(s) on this object: _draft` — on BOTH exits (single-item `getMetaItem` at protocol.ts:4672 and the list overlay at :4462). Root cause is one layer past the injection sites the card names: `computeMetadataDiagnostics` (packages/metadata-protocol/src/metadata-diagnostics.ts) stripped `_diagnostics` only, via a private one-key helper that predated `_draft` joining the shared METADATA_READ_DECORATIONS list. Fix = that function now calls the shared `stripReadDecorations`, which is the house shape: the module header of spec/kernel/metadata-read-decorations.ts names 'any re-parse of a served document' as a consumer that must strip, and read-time diagnostics are exactly that (third consumer after the #4326 write path and cloud#971's cold-boot bind). Closing it at the shared consumer fixes both injection sites in one edit, covers any future decoration key, and touches none of protocol.ts — deliberate, given #8136/#8003 are claimed on that file this round. PM assumption tested and upheld (strip, not whitelist); the item schema is NOT loosened and a test pins that it still rejects `_draft` by name. #6810's remedy (fix at the injection site) was read and deliberately not copied: `indexed` never belonged on the served body, whereas `_draft` is the badge the UI reads and is a declared read decoration.", "tests": "Reproduction first, red on origin/main: 4 new protocol-level cases failed with the card's verbatim message — \"Unrecognized key(s) on this object: `_draft`. Until #4001 closed this shape ...\" — on both the single-item preview read and the list overlay. After the fix: `pnpm --filter @objectstack/metadata-protocol test` => Test Files 75 passed (75), Tests 1100 passed (1100) (re-run after merging origin/main: same). Anti-vacuity: a genuinely broken draft (field `amount` with type `not_a_real_field_type`, seeded directly since saveMetaItem refuses it with 422) still reads `valid:false` with errors naming `amount` and not `_draft` — asserted on BOTH exits, so a fix that stopped computing diagnostics on the draft path would fail. Plus a drift guard (every METADATA_READ_DECORATIONS member must be invisible to the verdict) and an anti-loosening pin (the object schema still rejects `_draft` with unrecognized_keys). REVERSE VERIFICATION: reverting only metadata-diagnostics.ts to origin/main turns 5 of the 6 new cases red; the 6th (anti-loosening pin) is green in both directions BY DESIGN — it pins a constraint the fix must not relax, not the fix itself, so a direction change there would mean the schema had been loosened. Downstream consumer sweep (prefix filter direction, i.e. packages that DEPEND ON metadata-protocol, chosen as those whose suites exercise /meta reads, draft preview or _diagnostics): objectql 191 files/3390 tests, rest 102/1728, runtime 145/2195, metadata 31/603, service-automation 79/940 — all passed. (service-automation and rest first showed 7 collection failures in the fresh worktree: `Failed to resolve entry for package @objectstack/objectql` — unbuilt dist, the AGENTS.md build-closure trap, not the change; green after `pnpm --filter '<pkg>^...' build`.) Type surface unchanged; metadata-protocol has no typecheck script, so type checking came from its DTS build, which succeeded. Gates from scripts/pm/dispatch-gates.mjs on the actual changed files, all green locally: check:error-code-casing, check:cross-package-test-inputs, check:durability-log-level, check:changeset-gate-self-tests, check:objectui-changeset, check-changeset-no-major; plus check:nul-bytes (OK, 7422 files) with a manual control-byte self-scan (no hits) and eslint --no-inline-config on both changed files (clean). CI status at report time: in_progress — reporting at draft-PR time per the dispatch contract; the PM owns CI convergence and the ready-flip. Changeset added (patch, @objectstack/metadata-protocol). origin/main merged into the branch before the PR, cleanly.", "open_questions": [], "out_of_scope_findings": [] }Notes for the PM, outside the JSON:
- Scope: the declared surface was
protocol.ts— "the draft-preview read decoration and the_diagnosticscomputation it feeds". The edit landed in the module that is that computation (packages/metadata-protocol/src/metadata-diagnostics.ts, same package), not inprotocol.tsitself. Flagging it explicitly since the card named the file;protocol.tsis untouched, which also keeps this clear of [finding]metadata-protocolinterpolates raw driver text into client-facing messages — three downstream sanitizers each have a hole because of it (option C of #8086) #8136 and finding: the list-query normalizer'swherearity note still says a repeated?filter=is refused byisFilterASTfailing #8003. - Issue body read via the MCP path only —
curl https://api.github.com/...returns HTTP 403 in this container (no GitHub App connection;GH_TOKEN/GITHUB_TOKENpresent but empty, noghCLI), so the two-reader truncation check was not available. The body read complete: it ends with a full## Sourcesection naming QA run QA run · ai (FULL area) · 92f26f75 · 2026-08-11 · 4 PASS / 3 FAIL #7627.
Generated by Claude Code
Generated by Claude Code
- Scope: the declared surface was
huangyiirene commented
on Aug 12, 2026 CollaboratorAuthorMore actionsACCEPT — PR #8179. Verified against the diff on GitHub, not against the report's self-description.
The scope deviation is approved, and it is the best thing in this delivery. The claim declared
protocol.ts— "the draft-preview read decoration and the_diagnosticscomputation it feeds". The fix landed inmetadata-diagnostics.tsinstead, and the dev declared and justified that rather than either silently crossing or mechanically obeying a brief that pointed one layer too high. That is exactly the "stop on breach; explain in the report" contract. It is also strictly better: one edit closes both injection sites, covers any future decoration key, and touches none ofprotocol.ts— which is carrying two other claimed regions this round (#8136, #8003). Aprotocol.ts-local fix would have patched two sites, contended with two siblings, and left the next decoration to re-break it.Root cause is one layer past what the card named, and the card's suggested framing was superseded:
computeMetadataDiagnosticsstripped_diagnosticsonly, through a private one-key helper (stripDiagnostics) that predated_draftjoining the sharedMETADATA_READ_DECORATIONSlist. So the reader re-parsed its own badge against a closed schema. Calling the sharedstripReadDecorationsmakes read-time diagnostics the third consumer of that list, alongside the #4326 write-path persist and cloud#971's cold-boot bind — whose module header already names "any re-parse of a served document" as a required consumer. This is house shape, not a new invention.The PM assumption held and was tested rather than assumed. I offered strip-vs-whitelist and asked for the reasoning; the dev took the strip and pinned the alternative shut —
packages/metadata-protocol/src/protocol.read-decorations.test.tsasserts theobjectschema still rejects_draftwithunrecognized_keys. That matters: it keeps the #4326 write-path strip load-bearing instead of cosmetic. ⛔ The schema was not loosened.#6810 was read and deliberately not copied, with the distinction stated:
indexednever belonged on the served body and was removed at its injection site;_draftis the badge the UI reads and is a declared read decoration, so it belongs on the response and the fix goes where the list is consumed. Correctly reasoned rather than pattern-matched.The test evidence carries the parts I asked for:
- Anti-vacuity on both exits — a genuinely broken draft (a field whose
typeis not a field type, seeded directly because the save path refuses it with 422) still readsvalid:falsenamingamount, not_draft. Without this, a fix that simply stopped computing diagnostics on the draft path would have passed green. This was the specific trap in the dispatch order and it is closed on both exits. - Drift guard — every member of
METADATA_READ_DECORATIONSmust be invisible to the verdict, so a fourth decoration fails on a unit here rather than asvalid:falseon somebody's badge later. - ⭐ Reverse verification reported honestly. Reverting only
metadata-diagnostics.tsturns 5 of 6 new cases red; the sixth is green in both directions by design, and the dev said so and explained why (it pins a constraint the fix must not relax, not the fix itself). A dev claiming 6/6 would have been the answer to distrust. - Downstream consumer sweep in the correct direction (packages depending on
metadata-protocol): objectql, rest, runtime, metadata, service-automation — all green. The 7 initial collection failures were correctly diagnosed as the unbuilt-dist build-closure trap, not the change. - Gates taken from
scripts/pm/dispatch-gates.mjsagainst the actual changed files, per the leads-not-specs rule.
Changeset present (patch,
@objectstack/metadata-protocol). Path face clean — nodocs/adr/**, noskills/**— so this takes the normal landing route.CI was
in_progressat report time, which is the honest reading at draft-PR time; convergence and the ready-flip are mine. Flip is queued for the next patrol: gate-bearing jobs must show their ownconclusion: successfirst.
Generated by Claude Code
- Anti-vacuity on both exits — a genuinely broken draft (a field whose
- added a commit that references this issue
on Aug 17, 2026
Unconfirmed observation from the QA run — recorded for triage; the injection site was located but the fix (strip
_draftbefore computing_diagnostics, or whitelist the read-decoration keys in the diagnostics validation) is the natural next step to confirm.Symptom
Reading a metadata item with
?preview=draftreturns a response whose own diagnostics call the item invalid because of a key the read itself injected._diagnostics.valid:falsewith "Unrecognized key(s) …_draft"._diagnostics.valid:truefor an otherwise-valid draft — the preview marker must not be validated against the item's strict schema.Root cause (as located, unconfirmed)
The draft-preview read injects
_draft:trueonto the item and then validates the item with that key still present. Inpackages/metadata-protocol/src/protocol.tsthe single-item draft read stamps(draftItem as any)._draft = true(aroundprotocol.ts:4441, and the list overlay at:4261) and then decorates/validates it; the item schema is strict, so_draftis rejected by name.stripReadDecorationsexists to remove exactly these stamped keys before a strict re-parse, but it is not applied on the path that computes_diagnosticsfor the draft read. Mechanism still present onorigin/main.Related but distinct: #6810 (closed) was the same class of defect for the
indexedkey at a different injection site — see the module header inpackages/metadata-core/src/injected-system-columns.ts. This one is the_draftpreview marker.Reproduction
GET /api/v1/meta/<type>/<name>?preview=draft._diagnosticsin the response →valid:false, "Unrecognized key(s) …_draft".Source
Extracted from the QA run #7627 (framework 92f26f7, console 6314e87f).