Skip to content

_diagnostics.valid=true and editable/deletable are asserted for metadata rows no consumer reads — the envelope has no "in effect" axis #8169

Closed

Description

@huangyiirene

Restart-when: closed #7893 — re-read on the merged ref; if inert-but-valid rows remain representable, promote via a maintainer decision card covering this + #7656 + #8154 together

Split from #7893. Filed by the PM on behalf of the dev agent, which measured it but could not file it: GitHub REST returned 403 on every call from its container (git transport works, the API does not). Full text preserved verbatim from its handover; the wording below is the dev's.

Unassigned, finding — grading belongs to the findings-triage round, not to the filer.

The observation

GET /api/v1/meta/field/<object>.<name> on a runtime-created field returns:

{"type":"field","name":"showcase_task.zz_probe",
 "item":{"name":"zz_probe","label":"Probe","type":"text",
         "_diagnostics":{"valid":true}},
 "lock":"none","editable":true,"deletable":true}

for a row that reaches no object's fields, and therefore no ObjectQL query, no physical column, and no GET /meta/object/:name.

⛔ valid is correctly computed and should NOT simply be flipped

computeMetadataDiagnostics (packages/metadata-protocol/src/metadata-diagnostics.ts) resolves the type's Zod schema and .safeParse()s the document, so valid: true asserts this document is well-formed — which is true.

The same function deliberately backs both the save path's 422 and the read path's diagnostics, so that — per its own doc comment — "a document's verdict cannot depend on whether it was being saved or being opened." That property is worth preserving, and it is why the fix here is not "make it say false".

The actual defect

Well-formed is the only axis the envelope has, and consumers read it as in-effect.

MetadataValidationResult already models a third state on a different axis: computeMetadataDiagnostics returns undefined when a type has no registered schema, and its contract requires callers to treat that as "no opinion — not as 'valid'". There is no equivalent state for "well-formed but consumed by nothing."

lock / editable / deletable compound it: they are computed elsewhere (not traced in this investigation) and positively advertise the row as a live, editable, deletable item.

Ask

Whichever way #7893 is decided, decide separately what a read-time diagnostic owes a caller about effectivity. If a type can persist rows that no read path consumes, the envelope should either distinguish "well-formed" from "in effect", or state plainly that it does not model effectivity at all.

This is the same false-compliance shape ADR-0049 forbids, one layer down from #7893 — and it survives #7893 either way: under the implement-write-through option it disappears for field but remains for any future fragment type; under the retire-the-channel option the rows already written stay in sys_metadata, still reporting valid: true.

Reproduction

Rebuild the four-arm probe from packages/runtime/src/meta-field-overlay-lock.test.ts's harness — PUT /field/showcase_task.zz_probe, then GET the same path.

⚠️ Read the body at body.data.item. The dev's first run asserted against body.item, which returns [], and a defect assertion passed against an empty read. Any arm here needs an anti-vacuity control.

Dedup

Searched open issues for _diagnostics and for valid/effectivity wording before filing; the only hit is #7656, which is a distinct defect in the same envelope and worth reading alongside this one:

Both say the envelope's verdict is computed over the wrong subject; they point in opposite directions and have different fixes. ⚠️ Whoever takes either should read the other first — a change to what computeMetadataDiagnostics is fed could easily fix one and deepen the other.

Related

#7893 (parent) · #7656 (same envelope, opposite direction) · #7743 · ADR-0049


Generated by Claude Code

Activity

  1. huangyiirene commented on Aug 12, 2026

    @huangyiirene
    CollaboratorAuthor

    Checked against PR #8179 (#7656), which lands in computeMetadataDiagnostics — the exact function this card is about. Verdict: it does not deepen this finding, and it slightly strengthens the case for it.

    I attached a warning to this card an hour ago: "a change to what computeMetadataDiagnostics is fed could fix one and deepen the other." #8179 is that change, so here is the check rather than the assumption.

    What #8179 does: the function previously stripped _diagnostics only, via a private one-key helper that predated _draft joining METADATA_READ_DECORATIONS. It now calls the shared stripReadDecorations, removing every declared read decoration before the re-parse.

    Why that does not touch this card:

    Why it strengthens the case:

    #8179 adds a drift guard pinning that every member of METADATA_READ_DECORATIONS is invisible to the verdict, and an anti-loosening pin keeping the item schema closed against _draft. Together those make the function's contract sharper and more explicit: the verdict is computed purely over the document, deliberately and now test-enforced. That is precisely the premise this card rests on — valid is document-scoped by design and correctly computed, so ⛔ the fix here is not to make it return false. After #8179 that design intent is pinned rather than merely commented, which makes "add a separate effectivity signal" the clearly correct shape and "flip the boolean" clearly wrong.

    Unchanged for whoever grades this card: the ask is still to decide what a read-time diagnostic owes a caller about effectivity, and it still survives #7893 either way. The lock / editable / deletable half is untouched by #8179 and remains untraced.

    ⚠️ One sequencing note: if this card is ever implemented, it will land in the same function #8179 just changed. Read #8179's final diff first — its comment block explains why the strip lives where it does, and an effectivity signal added without that context could easily reintroduce a private subset of the decoration list, which is the exact defect #7656 was.


    Generated by Claude Code

  2. added theissue type on Aug 13, 2026
  3. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    Finding-grading round: held as finding, type Task, with a named re-grade trigger. Rationale: the ask — a new "in effect"/effectivity axis on the meta read envelope — is a public-surface expansion, which is maintainer-floor if pursued; but its sharpest motivating instance (a runtime-created field stored valid=true yet never folded into the object) is exactly what queued card #7893 exists to fix. Re-grade trigger: when #7893's fix lands, re-read this card on the merged ref — if inert-but-valid rows remain representable after it, this promotes to needs-user-decision with the four-prong block (the #8179 pins have already settled that flipping valid is the wrong shape); if the write path fix makes the state unreachable, close as superseded. Sequencing note already on record from the seat comment: any implementation lands in computeMetadataDiagnostics and must read #8179's diff first. (Process note: this card's body renders complete on GitHub; an API read truncates it at the <name> token — double-read before treating it as sanitizer-damaged.)

    本评论来自分诊座位 Routine。


    Generated by Claude Code

  4. os-zhuang commented on Aug 13, 2026

    @os-zhuang
    Contributor

    Findings-triage round — HELD as finding. Recording why, so the hold is a decision rather than a card nobody got to.

    Premise re-checked on origin/main: deletable is still asserted at 4 sites in protocol.ts, so the observation stands. It is held anyway, for three reasons that are about shape, not doubt.

    1. It is a contract-shape question, not a defect. The ask — an "in effect" axis on the _diagnostics envelope — widens a published response surface. Under this repo's mechanical border test that is not a bug fix and not PM-delegable; it needs a maintainer ruling with a four-cornered card face. ⛔ Promoting it to pm:queue would hand a dev a decision it is not allowed to make, and the honest exits (needs_decision) would just spend a dispatch to arrive back here.

    2. It is one of three cards on one envelope, pointing different directions — and they must be read together.

    ⇒ ⚠️ A change to what computeMetadataDiagnostics is fed could fix one of these and deepen another. Ruling them one at a time is how this lane produced three cards from one seam twice already. Whoever writes the decision card should carry all three.

    3. valid:true is correctly computed and must not simply be flipped. computeMetadataDiagnostics resolves the type's Zod schema and safeParses the document, so valid means "this isolated document is well-formed" — nothing more. That document-scoping is a deliberate good property: the same function backs the save path's 422 and the read path's diagnostics, so a document's verdict cannot depend on whether it was being saved or opened. The gap is a missing signal, not a wrong value.

    Release condition: promote when a maintainer decision card covering the envelope as a whole (this + #7656 + #8154's ordering constraint) is written and ruled. ⛔ Do not promote it alone, and ⛔ do not fold it into an unrelated card that happens to touch metadata-diagnostics.ts.


    Generated by Claude Code

  5. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    State-machine migration, maintainer-authorized (2026-08-13, live triage session: 「我授权你先迁标签」; semantics on #8449): graded hold — release condition stands (promote only via a maintainer decision card covering this + #7656 + #8154's ordering constraint together) — finding → pm:on-hold. Verdict, release condition, domain unchanged.


    Generated by Claude Code

  6. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Daily executable-criteria batch (triage seat): the Restart-when: fired — #7893 closed completed via merged PR #8496 (the field runtime-create door was retired). Per the card's own instruction, the firing action is a merged-ref re-read: are inert-but-valid rows still representable through other doors after that retirement?

    That re-read needs a code pass and is scheduled into the next triage round's first-touch budget (recorded here so the next round executes rather than re-derives). If inert-but-valid rows remain representable, the promotion shape is a maintainer decision card covering this + #7656 + #8154 together, per the card. Hold stands until the re-read lands.


    Generated by Claude Code

  7. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Restart-when re-read executed (triage seat, merged-ref pass) — verdict: inert-but-valid rows remain representable after #7893 / PR #8496, so per the card's own instruction this promotes to the maintainer decision card covering this + #7656 + #8154 together. pm:on-hold → needs-user-decision.

    Evidence, all against origin/main:

    Decision asked of the maintainer — what a read-time diagnostic owes a caller about effectivity, ruled once for the envelope as a whole (this card + #7656's document-scoping pins + #8154's ordering constraint that diagnostics are computed on the raw stored body BEFORE redaction):

    Four-prong card face:

    • Platform long-term coherence — B shrinks the inert surface itself; A adds one axis but leaves the doors open and creates a permanent envelope obligation; C leaves the recurring three-cards-per-seam pattern in place.
    • Measured business pull — the sharpest user-reachable instance (runtime field) is already retired; remaining pull is Studio/operator diagnostics fidelity. No user-blocking failure measured today.
    • AI-agent error-resistance — valid: true on a row nothing consumes teaches an agent its authoring took effect; A is the loud signal, B removes the trap at the door (louder still), C preserves the trap.
    • Startup scope discipline — B is incremental and already the practiced route; A is declare-and-maintain with zero consumers of the new axis today.

    Recommendation: Option B (three of four prongs; A wins only on breadth of signal). Standing constraints either way: ⛔ flipping valid is settled wrong (#8179 pins); any implementation lands in computeMetadataDiagnostics and must read #8179's diff first; #8154's compute-before-redaction ordering is load-bearing.

    本评论来自分诊座位 Routine。


    Generated by Claude Code

  8. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Maintainer ruling (2026-08-17, recorded by PM session session_01NYgmGheCzM6NrHZN436Cxf). The maintainer accepted the decision-inbox report's recommendations in full, verbatim: 「其他接受你的所有建议」. For this card (ruled once for the envelope family: this + #7656 + #8154):

    Ruled: Option B — no effectivity axis is added to the meta read envelope. The route is continued per-door retirement (ADR-0049 enforce-or-remove, the #7893 pattern).

    This card's decision is made and its route is the existing practice — nothing further is dispatchable from it. Closing as completed; #7656 / #8154 pins stand as constraints of record.


    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

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions