You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(types): MarkdownSchema.sanitize and .components are declared and mirrored but read by no renderer — and sanitize implies a switch that does not exist #6972
Measured on main = 8f1d995ef447 while converging MarkdownSchema for #6172.
The fact
MarkdownSchema declares two members that no renderer reads:
member
declared at
read by
sanitize?: boolean (@default true)
packages/types/src/data-display.ts
nothing
components?: Record<string, any>
same
nothing
The Zod mirror carries both as well (packages/types/src/zod/data-display.zod.ts).
The registered markdown renderer is MarkdownRenderer in packages/plugin-markdown/src/index.tsx. It forwards exactly two values to the
implementation — content and className:
MarkdownImplProps accepts only those two. Nothing anywhere destructures or
indexes schema.sanitize or schema.components.
sanitize is the more pointed of the two, because the key implies a switch that
does not exist: sanitization is unconditional. rehypeSanitize is a fixed
link in the rehype chain in MarkdownImpl.tsx, documented there as "the final
gate". Authoring sanitize: false is accepted by the type and by the Zod mirror,
changes nothing, and reads as a security-relevant control.
Why it is worth a card
ADR-0049 enforce-or-remove: declared = enforced. This is the same class as #6942
(TextSchema.variant) and #6910 (EventHandlersSchema), and it is the shape that
makes AI-authored metadata wrong in a way nothing catches: the key is offered by
the type, accepted by the validator, and silently inert.
⚠️ The population grew slightly as a side effect of #6172's convergence: with @object-ui/plugin-markdown now re-exporting the one authority instead of
declaring its own narrower copy, both members are visible on the plugin's
published face too. That was a deliberate, recorded trade in that PR (converging
onto the canonical shape) and is called out in its body — but it does mean the
retirement, if that is the ruling, now covers two published faces rather than one.
Not fixed there: removing a published key is an ADR-0049 retirement with its own
route (registries, mirror, parity test, changeset), not a convergence.
✅ 0, against a control of 27 files reading schema.content in the same query shape
⭐ "Sanitization is unconditional" — checked as control flow, not as a grep hit
A single grep line naming rehypeSanitize would not have settled this; a plugin can be conditionally included. It is not. rehypePlugins is a module-level const array (:99–:107), built once with [rehypeSanitize, sanitizeSchema] as its last link, and handed straight to ReactMarkdown at :226. No ternary, no if, no runtime assembly.
⇒ There is no code path on which sanitize: false could disable anything. The key type-checks, passes zod validation, and is inert.
Why p2 rather than the usual p3 for an inert key
⚠️ The failure is fail-safe — nothing is currently exploitable, since the un-disableable behaviour is the safe one. That is why this is not p1.
But it is a declared, validated, security-shaped control that does not exist, on a surface whose whole point is AI-authored metadata. Both readings of it mislead: an author writing sanitize: false believes they turned XSS filtering off, and an author writing sanitize: true believes they turned it on. Neither is true. A key that lies about a security gate is worth more than the ordinary inert-key p3.
⭐ The two keys do NOT have the same disposition — and that is the useful output
The card treats them as one population. Under ADR-0049 enforce-or-remove they diverge:
sanitize — the enforce arm means building a real switch that disables XSS sanitization. ⛔ That is not an acceptable outcome. So for this key enforce-or-remove collapses to remove, which makes it dispatchable rather than decision-shaped.
components — a conventional renderer-override extension point that simply was never wired. Both arms are genuinely open here, and wiring it is a plausible feature rather than a hazard.
⛔ Not splitting the card. Same file, same mirror, same retirement route, same lane — two cards would cost more than the shared context is worth. Recording the asymmetry is enough for whoever takes it.
Lane
domain:spec, not ui: the declaration and its mirror both live in packages/types, which the objectui three-stream split assigns to the contract face, and removing a key changes accept/reject behaviour ⇒ Clause ② at dispatch. Same routing as its siblings #6910 and #6942.
⚠️ Carried forward from the card and ⛔ not re-measured by me: #6172's convergence means the retirement now covers two published faces, since @object-ui/plugin-markdown re-exports the canonical shape rather than declaring a narrower copy.
Claim: PM loop round R1
Session: session_01BAZFhALsQsGqxui8sNqM8s
Branch: claude/issue-6972-markdown-inert-keys-retire
Worktree: objectui-issue-6972
Domain: domain:spec
File surface (region level): packages/types/src/data-display.ts — the MarkdownSchema interface only (sanitize / components at :1164–1171) · packages/types/src/zod/data-display.zod.ts — the MarkdownSchema mirror only (:292–293) · packages/types/src/zod/tombstone.zod.ts — read only (reuse retirementTombstone / handlerKeyRefusal; a new helper, if one is truly needed, goes below retirementTombstone, ⛔ never inside the handlerKeyRefusal region, which #6854 owns this batch) · packages/types/src/__tests__/ — one new refusal pin; zod-mirror-parity.test.ts only if the ledger needs a row · packages/plugin-markdown/src/types.ts — the re-exported authority (expected read-only; measure) · the markdown docs page under content/docs/components/** · .changeset/* (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: fable = CONTRACT_REVIEW_TIER — two declared keys become refusals on two published faces (@object-ui/types and @object-ui/plugin-markdown's re-export); objectui has no dispatch-gates.mjs, tier judged from card content
Clause-②: yes
Serial constraints cleared (file granularity per the maintainer ruling recorded at #5734 comment 5551274423): the #7122 chain holds packages/types/src/{field-types,form,objectql,select-option,index}.ts, zod/{form,index}.zod.ts and the __tests__ files named on its claim — none of this card's regions; #7701 already landed the DrillDownConfig mirror in data-display.zod.ts, a different region · #6854 (dispatched this batch) owns zod/app.zod.ts and the handlerKeyRefusal region of tombstone.zod.ts plus packages/runner/** — this card reads tombstone.zod.ts and does not edit that region · #6939 (domain:ui) has all eight groups on main, nothing in flight · the dev merges origin/main before opening the PR and again after any sibling lands; conflicts go to the merge queue
Disposition executed, and where each half's authority comes from
sanitize — remove. Triage (5535325734) collapsed enforce-or-remove for this key: the enforce arm is a switch that disables XSS sanitisation, which is not an acceptable outcome. Verified at claim on origin/main4ce14f1: rehypePlugins is a module-level const with rehypeSanitize as its last link, no conditional path; zero readers of schema.sanitize against a control of 19 files reading schema.content. Retire as an ADR-0049 tombstone on both faces (type member removed; zod face refuses the key by name with guidance that sanitisation is unconditional).
components — remove, by this seat's discretion under a declared veto window. Triage left both arms open. This seat's reading: a Record<string, any> of React component overrides is not a JSON-authorable value — it is the same shape as the handler keys objectui#6124 retired ("JSON has no function value"), so it folds into that already-ruled refusal class rather than needing a fresh decision (zero readers, zero pull, a permanent obligation if kept). Four axes: long-term — one fewer declared-but-inert member on a published schema; pull — none measured; AI-error — a declared key that does nothing is the silent failure ADR-0049 exists to end; startup focus — remove over declare-and-maintain. Falsifiable premise for the dev: if any host path (props, plugin API, a MarkdownImpl prop) actually consumes a components map, or if a runtime-slot disposition (TS twin stays callable for hosts) is the honest shape, stop on that half and report the fork — do not silently pick. The maintainer may veto this half on the card; the PR stays draft until in-seat contract review.
Route: both faces of @object-ui/types; @object-ui/plugin-markdown re-exports MarkdownSchema from ./types (measure whether that is a local declaration or a re-export of the authority — #6172 converged it); docs rows removed; a refusal pin asserting the refusal and the guidance text; changeset per the repo's tombstone precedents (feat(types)!: retirements #7639 / #7669 today, minor under the launch-window convention — the dev reads those PRs' changesets rather than inferring).
Claim (dev): implementing for PM loop round R1, dispatch of session_01BAZFhALsQsGqxui8sNqM8s.
Branch: claude/issue-6972-markdown-inert-keys-retire (pushed; the empty-branch write probe succeeded before any edit)
Worktree: ../objectui-issue-6972, cut from origin/main = 4ce14f1
Assignee: set by the PM seat, not touched here.
Both halves executed as ruled / dispositioned: sanitize retired as an ADR-0049 tombstone (both faces), components retired the same way after re-measuring the falsifiable premise on the branch base — no host path consumes a components map (MarkdownImplProps accepts only content and className; the components map the renderer hands to ReactMarkdown is its own module-level mdComponents, never merged with anything off the schema; zero MarkdownSchema consumers outside packages/types and packages/plugin-markdown). runtime-slot therefore does not apply. The handlerKeyRefusal region of tombstone.zod.ts was not touched; both keys use retirementTombstone().
One dispatch assumption falsified in passing: the docs page is content/docs/plugins/plugin-markdown.mdx, not under content/docs/components/**.
{
"issue": 6972,
"status": "done",
"branch": "claude/issue-6972-markdown-inert-keys-retire",
"pr": "https://github.com/objectstack-ai/objectui/pull/7718",
"premise_still_valid": true,
"summary": "Both inert MarkdownSchema keys retired as ADR-0049 tombstones on both published faces, on the route PR #7639 / PR #7669 established: `?: never` on the TS interface (packages/types/src/data-display.ts) and a retirementTombstone() named refusal on the Zod mirror (zod/data-display.zod.ts); the handlerKeyRefusal region of tombstone.zod.ts was not touched and no new helper was needed. `sanitize` half executed on the triage collapse (enforce = XSS-off switch); the guidance string states that sanitization is unconditional, re-verified as control flow on 4ce14f1 (rehypePlugins is a module-level const, last link [rehypeSanitize, sanitizeSchema], handed straight to ReactMarkdown, no conditional path). `components` half executed under the PM's veto window after re-measuring the falsifiable premise: no host path consumes a components map (MarkdownImplProps accepts only content and className; the components map the renderer passes is its own module-level mdComponents, never merged with the schema; LazyMarkdown receives only content and className; zero MarkdownSchema consumers outside packages/types and packages/plugin-markdown), so runtime-slot does not apply. Readers: zero for schema.sanitize / schema.components over packages/ apps/, control schema.content read in 20 .tsx files (28 ts+tsx). Two faces confirmed: plugin-markdown/src/types.ts is a type re-export of the authority, no plugin source edit; its own index.test.ts gained a two-face pin. One fixture authored the key (packages/types/examples/data-display-examples.json, sanitize: true) and lost it; docs rows replaced by a Retired blockquote. Assignee was set by the PM; not touched. origin/main moved by one unrelated commit (f96a781, schema-catalog fixture) after the PR opened; not re-merged, the queue rebuilds on current main.",
"tests": "All under the verify lock, exit captured before any pipe, on HEAD 61eccaa (= pushed head). `pnpm exec vitest run --maxWorkers=2 packages/types/ scripts/__tests__/one-authority-per-exported-name-6273.test.ts`: Test Files 113 passed (113), Tests 1955 passed (1955). `pnpm exec vitest run --maxWorkers=2 packages/plugin-markdown/`: Test Files 5 passed (5), Tests 49 passed (49). `pnpm --filter @object-ui/types build` (dist completeness: 120 emitted files verified) then `pnpm --filter @object-ui/types type-check` (script echoed: tsc --noEmit, tsconfig.examples.json, tsconfig.test.json): exit 0. `pnpm --filter @object-ui/plugin-markdown type-check` against the rebuilt types dist: exit 0. New pin packages/types/src/__tests__/markdown-inert-keys-retired-6972.test.ts = 19 tests (per key: envelope path/invalid_type/expected never, message = guidance literal and contains the prescriptive sentence, message = .describe(), key stays in .shape; controls: absent green, className green, wrong content still red, fixture green, undeclared key rides passthrough = the DELETED row live; TS half: fresh literal, document form, widened value under @ts-expect-error enforced by tsconfig.test.json). ABLATION (fix committed first, trap-guarded, absolute paths): both declaration files reverted to 4ce14f1; on-disk markers sanitize?: never 0 / sanitize?: boolean 1 / components?: never 0 / zod tombstones 0; types rebuilt; dist markers dist/data-display.d.ts never 0 boolean 1, dist/zod/data-display.zod.d.ts ZodNever 0 ZodBoolean 2. Direction RED as expected: pin 8 failed | 11 passed (19) exit 1 (4 refusal legs, 2 guidance legs, 2 keeps-declared legs), types type-check exit 2 with 6 TS2578, plugin-markdown type-check exit 2 with 2 TS2578 through the re-exported face. Restore: git checkout HEAD -- both paths, git diff HEAD --stat empty, git hash-object equals HEAD blobs e3ef905ac426 / 5657e60c825d, rebuilt, dist markers back (never 1 boolean 0; ZodNever 2 ZodBoolean 0), pin 19/19 green, plugin-markdown type-check exit 0. Vitest spelling: the dispatch's `pnpm --filter X exec vitest run` form is refused by the repo's invocation guard (root not repo root, objectui#3378); root-relative paths used instead, same config CI runs. Affected census: TURBO_SCM_BASE=4ce14f1 turbo ls --affected = 43 packages (everything downstream of @object-ui/types); declared narrowing: only a package naming MarkdownSchema or authoring sanitize/components on a markdown node can be affected, and all three censuses are zero outside the two packages run; the remaining 41 are CI's.",
"mcp_calls": "6 — add_issue_comment (claim), create_pull_request, pull_request_read get (body read-back), issue_read get_labels (failed: PR numbers do not resolve there), issue_write update (label union write), add_issue_comment (this report). Issue/comments/PR labels were read through the zero-quota web payload channel; REST reads are 403 from this container (measured on the issue).",
"gates": [
{"command": "pnpm exec vitest run --maxWorkers=2 packages/types/ scripts/__tests__/one-authority-per-exported-name-6273.test.ts", "exit": 0, "head": "61eccaa", "verdict": "Test Files 113 passed (113) · Tests 1955 passed (1955)"},
{"command": "pnpm exec vitest run --maxWorkers=2 packages/plugin-markdown/", "exit": 0, "head": "61eccaa", "verdict": "Test Files 5 passed (5) · Tests 49 passed (49)"},
{"command": "pnpm --filter @object-ui/types build && pnpm --filter @object-ui/types type-check", "exit": 0, "head": "61eccaa", "verdict": "clean; script name echoed"},
{"command": "pnpm --filter @object-ui/plugin-markdown type-check", "exit": 0, "head": "61eccaa", "verdict": "clean; script name echoed"},
{"command": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "61eccaa", "verdict": "4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"},
{"command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "head": "61eccaa", "verdict": "All workspace packages are in the changeset fixed group"},
{"command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "head": "61eccaa", "verdict": "No changeset declares a major bump"},
{"command": "node scripts/check-changeset-overwrite.mjs", "exit": 0, "head": "61eccaa", "verdict": "No pre-existing changeset was modified or deleted"},
{"command": "pnpm check:control-bytes", "exit": 0, "head": "61eccaa", "verdict": "OK (scanned 6293 tracked text file(s))"},
{"command": "pnpm check:spec-symbols", "exit": 0, "head": "61eccaa", "verdict": "no comment cites a key its spec symbol does not declare"},
{"command": "pnpm check:doc-types", "exit": 0, "head": "61eccaa", "verdict": "Every documented component type is registered"},
{"command": "pnpm check:doc-fences", "exit": 0, "head": "61eccaa", "verdict": "every TypeScript block in 227 document(s) is fenced ts/tsx/typescript"},
{"command": "node scripts/check-doc-links.mjs", "exit": 0, "head": "61eccaa", "verdict": "Links are valid across 17 scan roots"},
{"command": "pnpm check:doc-snippets", "exit": 2, "head": "61eccaa", "verdict": "NOT MEASURED — PRECONDITION NOT MET (unbuilt plugin packages; the gate says it could not run, not that it found errors); declared to CI; the only doc edit is a table row removal plus a blockquote, no fence changed"},
{"command": "node scripts/check-governed-queue-guard.mjs --test PATHS", "exit": 0, "head": "61eccaa", "verdict": "NOT GOVERNED — 7 path(s) checked against 5 governed surface(s); none matched"},
{"command": "grep -naP control-character scan over the 7 changed files", "exit": 1, "head": "61eccaa", "verdict": "no control bytes (grep exit 1 = no match)"},
{"command": "pnpm --filter '@object-ui/plugin-markdown^...' build (dependency closure, first command in the worktree)", "exit": 0, "head": "4ce14f1", "verdict": "VERDICT command-exit 0 · held the lock 70s"}
],
"line_budget": "n/a",
"deviations": [
"packages/plugin-markdown/src/index.test.ts edited (claim listed the plugin as expected read-only): the two-face pin lives there because it is the only place the re-exported face can be asserted; PR #7639 set the precedent.",
"packages/types/examples/data-display-examples.json edited: dropped the one in-repo `sanitize: true` write, which the tombstone would refuse; the fixture is now pinned to parse green.",
"Docs page is content/docs/plugins/plugin-markdown.mdx, not under content/docs/components/** as the dispatch assumed; rows removed and a Retired blockquote added in the report-schema.mdx form. check:doc-* gates cannot see table rows, so the docs disposition is carried by prose.",
"Dispatch gate spelling `pnpm --filter @object-ui/types exec vitest run --maxWorkers=2` is refused by this repo's vitest invocation guard (package-dir root); the sanctioned root-relative path form was run instead.",
"check:doc-snippets not measured locally (precondition: 20+ plugin builds); declared to CI.",
"Affected census (43 packages) narrowed to packages/types + packages/plugin-markdown + the one-authority pin, with the narrowing argument stated in the PR body; remaining 41 declared to CI.",
"Claim comment was posted after implementation rather than before (5551424489); the PM claim 5551288628 already held the branch.",
"Label write went through MCP issue_write (REST is 403 here): read current set via the web payload, wrote the union {package: types, plugin, plugin: markdown, tests, needs:contract-review}, read back the same five — nothing stripped.",
"origin/main advanced by one unrelated commit (f96a781) after the PR opened; not re-merged, the merge queue rebuilds on current main."
],
"files_changed": [
".changeset/6972-markdown-inert-keys-retired.md",
"content/docs/plugins/plugin-markdown.mdx",
"packages/plugin-markdown/src/index.test.ts",
"packages/types/examples/data-display-examples.json",
"packages/types/src/__tests__/markdown-inert-keys-retired-6972.test.ts",
"packages/types/src/data-display.ts",
"packages/types/src/zod/data-display.zod.ts"
],
"clause_2": [
"sanitize: ACCEPT-SET CHANGE on both faces — a markdown node authoring `sanitize` (either value) parsed GREEN on 4ce14f1 and now parses RED (path sanitize, code invalid_type, expected never) with guidance stating sanitization is unconditional; TS `sanitize?: never`. Authority: triage collapse (enforce = XSS-off switch) + ADR-0049; ruled, not re-decided.",
"components: ACCEPT-SET CHANGE on both faces — `components` parsed GREEN and now parses RED by name; TS `components?: never`. Authority: PM disposition under a declared veto window (claim 5551288628), NOT a maintainer ruling; falsifiable premise (no host consumer) re-measured on 4ce14f1 and holds, so runtime-slot was not the honest shape; PR stays draft for contract review and the maintainer may veto this half on the card."
],
"open_questions": [],
"out_of_scope_findings": []
}
Commit trailers carry the session URL; the PR body (read back in full after creation, all sections intact, platform appended its own footer) carries the session id in prose as a code span. Dev of session_01BAZFhALsQsGqxui8sNqM8s.
In-seat contract review — PASS — and ACCEPT of PR #7718 (head 61eccaa), verified on GitHub and the tree at 2026-09-05T11:3xZ
Implemented-by: branch claude/issue-6972-markdown-inert-keys-retire (mode:subagent dev of this seat's dispatch, CONTRACT_REVIEW_TIER)
Reviewed-by: session_01BAZFhALsQsGqxui8sNqM8s (os-justin), served tier re-read before this verdict: get_session → last_served_model = claude-fable-5-1 = CONTRACT_REVIEW_TIER (11:29Z)
① Derived judgments — each accept-set / public-face change in the diff, judged
change
face
before → after
judgment
MarkdownSchema.sanitize
zod mirror (zod/data-display.zod.ts:292)
z.boolean().optional() → retirementTombstone(…) refusing by name, code: invalid_type, expected: never, guidance in the message
correct — triage's collapse holds: the enforce arm would be an XSS-off switch. Verified on the PR head myself: MarkdownImpl.tsx:99–107rehypePlugins is a module-level const array whose last link is [rehypeSanitize, sanitizeSchema] (line 106), handed straight to ReactMarkdown; no conditional path. The guidance sentence is therefore true.
MarkdownSchema.sanitize
TS face (data-display.ts)
?: boolean → ?: never with the retirement docblock
correct — tombstone, not deletion; BaseSchema's index signature would have re-admitted a deleted key as any (the precedent's reason, #5905).
MarkdownSchema.components
zod mirror
z.record(z.string(), z.any()).optional() → tombstone by name
correct under the declared veto window — my disposition, ⛔ not a maintainer ruling. The falsifiable premise (no host path consumes a components map) was re-measured by the dev and re-checked here: MarkdownImplProps (MarkdownImpl.tsx:27–36) declares only content and className; the components={mdComponents} it passes is the plugin's own module-level map (:173); index.tsx:38–40 forwards only content and className. runtime-slot does not apply (nothing reaches a renderer).
MarkdownSchema.components
TS face
?: Record<string, any> → ?: never
correct, same reasoning.
second published face
@object-ui/plugin-markdown
no source change; src/types.ts:40 re-exports the authority, so its face narrows identically; a two-face tsc pin added in index.test.ts
correct — one authority (#6172), the changeset names the plugin so its CHANGELOG carries the note.
correct and required — the only in-repo author of either key; now pinned to parse green.
docs
content/docs/plugins/plugin-markdown.mdx
two rows → a Retired (objectui#6972) blockquote
correct (the page is under plugins/, not components/ as my brief assumed — dev's correction accepted).
Boundary controls in the new pin are the right shape: absent stays green, className accepted, wrong content still refused (the mirror still validates), the undeclared-key row rides .passthrough() live (the "deleted" contrast), the envelope is asserted (path, code, expected) not just success === false, and the guidance string is one literal asserted equal on both channels (message = .describe()).
② Semver
minor for @object-ui/types and @object-ui/plugin-markdown, commit subject feat(types)!: — identical grading to the tombstone precedents on main (.changeset/5905-componentinput-inputtype-tombstone.md: same two packages, minor) and to the fixed-group convention check-changeset-no-major enforces. Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check are all success on this head. Consistent.
③ Boundary flags
open_questions empty; no needs_decision. The one governance statement in the PR — the components half rests on this seat's discretion — is declared on the card (claim 5551288628), in the PR body, in the changeset and in both docblocks. Veto window stands: the maintainer may reverse that half on this card; the reversal is a one-file re-declaration, so the landing is not held for it.
Checklist readings
Form: draft ✓ · base main ✓ · first line Fixes #6972 ✓ · no closing keyword adjacent to another open card number ✓.
Tests: real commands with verdict lines on HEAD 61eccaa = pushed head ✓; ablation red/green with on-disk and dist markers ✓; Doc Snippet Type Check is success on CI, closing the dev's local NOT MEASURED ✓.
Deviations: all ten declared; none silent.
Farm at this reading: 26 of 32 terminal green, Lint / Type Check / four Test shards still in_progress — landing waits for them.
Landing: needs:contract-review cleared on the PR in this stroke (provenance comment on #7718 cites this PASS). Ready + auto-merge once every check is green; flip re-check armed. pm:dispatched comes off at the MERGED reading.
Patch round (landing window, not a REWORK) — PR #7718Test (shard 1/4) is red on 61eccaa and the cause is this PR's own docs edit: scripts/__tests__/check-doc-snippet-types.test.ts:624 pins a README sample block in content/docs/plugins/plugin-markdown.mdxby fence line (195), and the 13-line Retired blockquote that replaced two table rows moved that fence. Signature: AssertionError: no compiled block at content/docs/plugins/plugin-markdown.mdx:195. Every other check on the head is green (Lint, Type Check, three shards, the docs gates). The original dev is re-engaged to re-point the line constant inside this PR and push; the ready flip waits for the new head's farm. ⛔ Not a re-run: the failure is deterministic and this PR's.
{
"issue": 6972,
"status": "done",
"round": "patch 1 — CI Test (shard 1/4) red on 61eccaa (job 101298650018)",
"branch": "claude/issue-6972-markdown-inert-keys-retire",
"pr": "https://github.com/objectstack-ai/objectui/pull/7718",
"head": "5fe2a1f8d7370ecdf070514429bf92560d1f8dcb",
"premise_still_valid": true,
"summary": "scripts/__tests__/check-doc-snippet-types.test.ts pins the plugin-markdown README sample fence by LINE on purpose (README_SAMPLE_FENCE_LINE; an edit above it forces a re-declaration). The objectui#6972 retirement blockquote replaced two prop-table rows above that fence with 13 lines, so the fence moved 195 -> 206 (re-measured on the head: `fenceLine: i + 1`, opening fence at line 206, base 4ce14f1 had it at 195). Smallest honest fix: the constant is re-declared as 206 with a two-line comment naming the move; assertion shape unchanged; the blockquote is neither shrunk nor moved; the pin is not re-anchored by content (that is its own card). Worktree recreated from the remote branch for the patch, no rebase, no force-push; one commit 5fe2a1f pushed on top of 61eccaa. PR body gained the Deviations line and the re-run row.",
"tests": "`pnpm exec vitest run --maxWorkers=2 scripts/__tests__/check-doc-snippet-types.test.ts` under the verify lock on 5fe2a1f: Test Files 1 passed (1), Tests 70 passed (70), VERDICT command-exit 0. `pnpm check:control-bytes`: exit 0, `OK (scanned 6293 tracked text file(s))`. `pnpm check:doc-snippets`: exit 2, PRECONDITION NOT MET (unbuilt plugin packages; the gate says it could not run) — NOT MEASURED, unchanged from round 1, declared to CI.",
"gates": [
{"command": "pnpm exec vitest run --maxWorkers=2 scripts/__tests__/check-doc-snippet-types.test.ts", "exit": 0, "head": "5fe2a1f", "verdict": "Test Files 1 passed (1) · Tests 70 passed (70)"},
{"command": "pnpm check:control-bytes", "exit": 0, "head": "5fe2a1f", "verdict": "OK (scanned 6293 tracked text file(s))"},
{"command": "pnpm check:doc-snippets", "exit": 2, "head": "5fe2a1f", "verdict": "NOT MEASURED — PRECONDITION NOT MET (unbuilt packages); declared to CI"}
],
"line_budget": "n/a",
"deviations": [
"scripts/__tests__/check-doc-snippet-types.test.ts is outside the claim's file surface; edited only to re-declare the line constant the docs edit moved, as the coordinator's patch instruction directed."
],
"files_changed": [
"scripts/__tests__/check-doc-snippet-types.test.ts"
],
"mcp_calls": "this round 3 so far — update_pull_request (Deviations line + re-run row), add_issue_comment (this report), plus one read-back to follow; cumulative for the run 10 (7 in round 1). PR body and labels otherwise read through the zero-quota web payload channel.",
"open_questions": [],
"out_of_scope_findings": []
}
Landing: PR #7718 marked ready for review and auto-merge enabled (explicit SQUASH) at 2026-09-05T11:58Z (session session_01BAZFhALsQsGqxui8sNqM8s, domain:spec @ objectui seat).
Readings at the flip, head 5fe2a1f8d7370ecdf070514429bf92560d1f8dcb (patch commit re-declaring README_SAMPLE_FENCE_LINE 195 → 206), base f96a781 = origin/main tip at 11:58Z:
check runs on the head: 32 total, all completed — 29 success, 3 skipped (dependabot, Test (coverage), the ${{ matrix.shard }} placeholder). Test (shard 1/4), red on 61eccaa for the line-anchored fence pin, is success on 5fe2a1f (11:55:27Z). The ready flip re-ran Governed Surface Queue Guard (11:58:55Z, success).
merge-queue refs at 11:59:15Z: gh-readonly-queue/main/pr-7718-adb2a86… present, queued behind pr-7720-f96a781… (positive probe; the enable call's echo is not relied on).
pre-landing three: ① in-seat contract review PASS on this card (comment 5551465094) ✓; ② carriers: PR-side needs:contract-review cleared with provenance (PR feat(types)!: retire MarkdownSchema.sanitize and .components as ADR-0049 tombstones (objectui#6972) #7718 comment 5551468483) — check-clause2-carriers.mjs --pair 7718 returned exit 3 from this seat (REST 403, session-gated), so that reading is NOT a clean-board reading and the pair was verified by hand ✓; ③ every check green, not just the required subset ✓. Governed-surface test on all 8 paths: not governed.
Next reading: MERGED confirmation by two readings (origin/main contains the squash + PR merged: true), then pm:dispatched is stripped from this card.
MERGED — two readings at 2026-09-05T12:17Z (session session_01BAZFhALsQsGqxui8sNqM8s, domain:spec @ objectui seat): this card's closing PR #7718 reads state MERGED (card closed completed 12:14:18Z through Fixes #6972); origin/main tip is 446d93d4e226d90a0c89b66690f35a49642824a1 = the squash feat(types)!: retire MarkdownSchema.sanitize and .components as ADR-0049 tombstones (objectui#6972) (#7718); the queue ref pr-7718-adb2a86… is gone. pm:dispatched stripped in the same stroke (label set now bug, priority:p2, domain:spec).
Executed: sanitize and components are ADR-0049 tombstones on both faces of MarkdownSchema (types + the plugin's re-exported face); the components half rests on this seat's disposition under the declared veto window (claim 5551288628) — reversible by a one-file re-declaration if the maintainer vetoes.
Activity
huangyiirene commentedon Sep 4, 2026
Triage — first touch. Graded
bug·pm:queue·priority:p2·domain:spec. Re-verified one30ed155cb71, 03:44Z (card measured8f1d995ef447).Every claim reproduces
sanitize?: booleandeclaredpackages/types/src/data-display.ts:1167components?: Record<string, any>declared:1171zod/data-display.zod.ts:292/:293MarkdownImpl({ content, className }: MarkdownImplProps)@MarkdownImpl.tsx:196schema.contentin the same query shape⭐ "Sanitization is unconditional" — checked as control flow, not as a grep hit
A single grep line naming
rehypeSanitizewould not have settled this; a plugin can be conditionally included. It is not.rehypePluginsis a module-levelconstarray (:99–:107), built once with[rehypeSanitize, sanitizeSchema]as its last link, and handed straight toReactMarkdownat:226. No ternary, noif, no runtime assembly.⇒ There is no code path on which
sanitize: falsecould disable anything. The key type-checks, passes zod validation, and is inert.Why p2 rather than the usual p3 for an inert key
But it is a declared, validated, security-shaped control that does not exist, on a surface whose whole point is AI-authored metadata. Both readings of it mislead: an author writing
sanitize: falsebelieves they turned XSS filtering off, and an author writingsanitize: truebelieves they turned it on. Neither is true. A key that lies about a security gate is worth more than the ordinary inert-key p3.⭐ The two keys do NOT have the same disposition — and that is the useful output
The card treats them as one population. Under ADR-0049 enforce-or-remove they diverge:
sanitize— the enforce arm means building a real switch that disables XSS sanitization. ⛔ That is not an acceptable outcome. So for this key enforce-or-remove collapses to remove, which makes it dispatchable rather than decision-shaped.components— a conventional renderer-override extension point that simply was never wired. Both arms are genuinely open here, and wiring it is a plausible feature rather than a hazard.⛔ Not splitting the card. Same file, same mirror, same retirement route, same lane — two cards would cost more than the shared context is worth. Recording the asymmetry is enough for whoever takes it.
Lane
domain:spec, not ui: the declaration and its mirror both live inpackages/types, which the objectui three-stream split assigns to the contract face, and removing a key changes accept/reject behaviour ⇒ Clause ② at dispatch. Same routing as its siblings #6910 and #6942.@object-ui/plugin-markdownre-exports the canonical shape rather than declaring a narrower copy.Generated by Claude Code
os-justin commentedon Sep 5, 2026
Claim: PM loop round R1
Session:
session_01BAZFhALsQsGqxui8sNqM8sBranch:
claude/issue-6972-markdown-inert-keys-retireWorktree:
objectui-issue-6972Domain:
domain:specFile surface (region level):
packages/types/src/data-display.ts— theMarkdownSchemainterface only (sanitize/componentsat :1164–1171) ·packages/types/src/zod/data-display.zod.ts— theMarkdownSchemamirror only (:292–293) ·packages/types/src/zod/tombstone.zod.ts— read only (reuseretirementTombstone/handlerKeyRefusal; a new helper, if one is truly needed, goes belowretirementTombstone, ⛔ never inside thehandlerKeyRefusalregion, which #6854 owns this batch) ·packages/types/src/__tests__/— one new refusal pin;zod-mirror-parity.test.tsonly if the ledger needs a row ·packages/plugin-markdown/src/types.ts— the re-exported authority (expected read-only; measure) · the markdown docs page undercontent/docs/components/**·.changeset/*(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: fable=CONTRACT_REVIEW_TIER— two declared keys become refusals on two published faces (@object-ui/typesand@object-ui/plugin-markdown's re-export); objectui has nodispatch-gates.mjs, tier judged from card contentClause-②: yes
Serial constraints cleared (file granularity per the maintainer ruling recorded at #5734 comment 5551274423): the #7122 chain holds
packages/types/src/{field-types,form,objectql,select-option,index}.ts,zod/{form,index}.zod.tsand the__tests__files named on its claim — none of this card's regions;#7701already landed theDrillDownConfigmirror indata-display.zod.ts, a different region · #6854 (dispatched this batch) ownszod/app.zod.tsand thehandlerKeyRefusalregion oftombstone.zod.tspluspackages/runner/**— this card readstombstone.zod.tsand does not edit that region · #6939 (domain:ui) has all eight groups onmain, nothing in flight · the dev mergesorigin/mainbefore opening the PR and again after any sibling lands; conflicts go to the merge queueDisposition executed, and where each half's authority comes from
sanitize— remove. Triage (5535325734) collapsed enforce-or-remove for this key: the enforce arm is a switch that disables XSS sanitisation, which is not an acceptable outcome. Verified at claim onorigin/main4ce14f1:rehypePluginsis a module-levelconstwithrehypeSanitizeas its last link, no conditional path; zero readers ofschema.sanitizeagainst a control of 19 files readingschema.content. Retire as an ADR-0049 tombstone on both faces (type member removed; zod face refuses the key by name with guidance that sanitisation is unconditional).components— remove, by this seat's discretion under a declared veto window. Triage left both arms open. This seat's reading: aRecord<string, any>of React component overrides is not a JSON-authorable value — it is the same shape as the handler keys objectui#6124 retired ("JSON has no function value"), so it folds into that already-ruled refusal class rather than needing a fresh decision (zero readers, zero pull, a permanent obligation if kept). Four axes: long-term — one fewer declared-but-inert member on a published schema; pull — none measured; AI-error — a declared key that does nothing is the silent failure ADR-0049 exists to end; startup focus — remove over declare-and-maintain. Falsifiable premise for the dev: if any host path (props, plugin API, aMarkdownImplprop) actually consumes acomponentsmap, or if aruntime-slotdisposition (TS twin stays callable for hosts) is the honest shape, stop on that half and report the fork — do not silently pick. The maintainer may veto this half on the card; the PR stays draft until in-seat contract review.Route: both faces of
@object-ui/types;@object-ui/plugin-markdownre-exportsMarkdownSchemafrom./types(measure whether that is a local declaration or a re-export of the authority — #6172 converged it); docs rows removed; a refusal pin asserting the refusal and the guidance text; changeset per the repo's tombstone precedents (feat(types)!:retirements #7639 / #7669 today, minor under the launch-window convention — the dev reads those PRs' changesets rather than inferring).Generated by Claude Code
os-justin commentedon Sep 5, 2026
Claim (dev): implementing for PM loop round R1, dispatch of
session_01BAZFhALsQsGqxui8sNqM8s.claude/issue-6972-markdown-inert-keys-retire(pushed; the empty-branch write probe succeeded before any edit)../objectui-issue-6972, cut fromorigin/main=4ce14f1sanitizeretired as an ADR-0049 tombstone (both faces),componentsretired the same way after re-measuring the falsifiable premise on the branch base — no host path consumes acomponentsmap (MarkdownImplPropsaccepts onlycontentandclassName; thecomponentsmap the renderer hands toReactMarkdownis its own module-levelmdComponents, never merged with anything off the schema; zeroMarkdownSchemaconsumers outsidepackages/typesandpackages/plugin-markdown).runtime-slottherefore does not apply. ThehandlerKeyRefusalregion oftombstone.zod.tswas not touched; both keys useretirementTombstone().content/docs/plugins/plugin-markdown.mdx, not undercontent/docs/components/**.Draft PR and the
os-dev-reportcomment follow.Generated by Claude Code
os-justin commentedon Sep 5, 2026
os-dev-report
{ "issue": 6972, "status": "done", "branch": "claude/issue-6972-markdown-inert-keys-retire", "pr": "https://github.com/objectstack-ai/objectui/pull/7718", "premise_still_valid": true, "summary": "Both inert MarkdownSchema keys retired as ADR-0049 tombstones on both published faces, on the route PR #7639 / PR #7669 established: `?: never` on the TS interface (packages/types/src/data-display.ts) and a retirementTombstone() named refusal on the Zod mirror (zod/data-display.zod.ts); the handlerKeyRefusal region of tombstone.zod.ts was not touched and no new helper was needed. `sanitize` half executed on the triage collapse (enforce = XSS-off switch); the guidance string states that sanitization is unconditional, re-verified as control flow on 4ce14f1 (rehypePlugins is a module-level const, last link [rehypeSanitize, sanitizeSchema], handed straight to ReactMarkdown, no conditional path). `components` half executed under the PM's veto window after re-measuring the falsifiable premise: no host path consumes a components map (MarkdownImplProps accepts only content and className; the components map the renderer passes is its own module-level mdComponents, never merged with the schema; LazyMarkdown receives only content and className; zero MarkdownSchema consumers outside packages/types and packages/plugin-markdown), so runtime-slot does not apply. Readers: zero for schema.sanitize / schema.components over packages/ apps/, control schema.content read in 20 .tsx files (28 ts+tsx). Two faces confirmed: plugin-markdown/src/types.ts is a type re-export of the authority, no plugin source edit; its own index.test.ts gained a two-face pin. One fixture authored the key (packages/types/examples/data-display-examples.json, sanitize: true) and lost it; docs rows replaced by a Retired blockquote. Assignee was set by the PM; not touched. origin/main moved by one unrelated commit (f96a781, schema-catalog fixture) after the PR opened; not re-merged, the queue rebuilds on current main.", "tests": "All under the verify lock, exit captured before any pipe, on HEAD 61eccaa (= pushed head). `pnpm exec vitest run --maxWorkers=2 packages/types/ scripts/__tests__/one-authority-per-exported-name-6273.test.ts`: Test Files 113 passed (113), Tests 1955 passed (1955). `pnpm exec vitest run --maxWorkers=2 packages/plugin-markdown/`: Test Files 5 passed (5), Tests 49 passed (49). `pnpm --filter @object-ui/types build` (dist completeness: 120 emitted files verified) then `pnpm --filter @object-ui/types type-check` (script echoed: tsc --noEmit, tsconfig.examples.json, tsconfig.test.json): exit 0. `pnpm --filter @object-ui/plugin-markdown type-check` against the rebuilt types dist: exit 0. New pin packages/types/src/__tests__/markdown-inert-keys-retired-6972.test.ts = 19 tests (per key: envelope path/invalid_type/expected never, message = guidance literal and contains the prescriptive sentence, message = .describe(), key stays in .shape; controls: absent green, className green, wrong content still red, fixture green, undeclared key rides passthrough = the DELETED row live; TS half: fresh literal, document form, widened value under @ts-expect-error enforced by tsconfig.test.json). ABLATION (fix committed first, trap-guarded, absolute paths): both declaration files reverted to 4ce14f1; on-disk markers sanitize?: never 0 / sanitize?: boolean 1 / components?: never 0 / zod tombstones 0; types rebuilt; dist markers dist/data-display.d.ts never 0 boolean 1, dist/zod/data-display.zod.d.ts ZodNever 0 ZodBoolean 2. Direction RED as expected: pin 8 failed | 11 passed (19) exit 1 (4 refusal legs, 2 guidance legs, 2 keeps-declared legs), types type-check exit 2 with 6 TS2578, plugin-markdown type-check exit 2 with 2 TS2578 through the re-exported face. Restore: git checkout HEAD -- both paths, git diff HEAD --stat empty, git hash-object equals HEAD blobs e3ef905ac426 / 5657e60c825d, rebuilt, dist markers back (never 1 boolean 0; ZodNever 2 ZodBoolean 0), pin 19/19 green, plugin-markdown type-check exit 0. Vitest spelling: the dispatch's `pnpm --filter X exec vitest run` form is refused by the repo's invocation guard (root not repo root, objectui#3378); root-relative paths used instead, same config CI runs. Affected census: TURBO_SCM_BASE=4ce14f1 turbo ls --affected = 43 packages (everything downstream of @object-ui/types); declared narrowing: only a package naming MarkdownSchema or authoring sanitize/components on a markdown node can be affected, and all three censuses are zero outside the two packages run; the remaining 41 are CI's.", "mcp_calls": "6 — add_issue_comment (claim), create_pull_request, pull_request_read get (body read-back), issue_read get_labels (failed: PR numbers do not resolve there), issue_write update (label union write), add_issue_comment (this report). Issue/comments/PR labels were read through the zero-quota web payload channel; REST reads are 403 from this container (measured on the issue).", "gates": [ {"command": "pnpm exec vitest run --maxWorkers=2 packages/types/ scripts/__tests__/one-authority-per-exported-name-6273.test.ts", "exit": 0, "head": "61eccaa", "verdict": "Test Files 113 passed (113) · Tests 1955 passed (1955)"}, {"command": "pnpm exec vitest run --maxWorkers=2 packages/plugin-markdown/", "exit": 0, "head": "61eccaa", "verdict": "Test Files 5 passed (5) · Tests 49 passed (49)"}, {"command": "pnpm --filter @object-ui/types build && pnpm --filter @object-ui/types type-check", "exit": 0, "head": "61eccaa", "verdict": "clean; script name echoed"}, {"command": "pnpm --filter @object-ui/plugin-markdown type-check", "exit": 0, "head": "61eccaa", "verdict": "clean; script name echoed"}, {"command": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "61eccaa", "verdict": "4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"}, {"command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "head": "61eccaa", "verdict": "All workspace packages are in the changeset fixed group"}, {"command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "head": "61eccaa", "verdict": "No changeset declares a major bump"}, {"command": "node scripts/check-changeset-overwrite.mjs", "exit": 0, "head": "61eccaa", "verdict": "No pre-existing changeset was modified or deleted"}, {"command": "pnpm check:control-bytes", "exit": 0, "head": "61eccaa", "verdict": "OK (scanned 6293 tracked text file(s))"}, {"command": "pnpm check:spec-symbols", "exit": 0, "head": "61eccaa", "verdict": "no comment cites a key its spec symbol does not declare"}, {"command": "pnpm check:doc-types", "exit": 0, "head": "61eccaa", "verdict": "Every documented component type is registered"}, {"command": "pnpm check:doc-fences", "exit": 0, "head": "61eccaa", "verdict": "every TypeScript block in 227 document(s) is fenced ts/tsx/typescript"}, {"command": "node scripts/check-doc-links.mjs", "exit": 0, "head": "61eccaa", "verdict": "Links are valid across 17 scan roots"}, {"command": "pnpm check:doc-snippets", "exit": 2, "head": "61eccaa", "verdict": "NOT MEASURED — PRECONDITION NOT MET (unbuilt plugin packages; the gate says it could not run, not that it found errors); declared to CI; the only doc edit is a table row removal plus a blockquote, no fence changed"}, {"command": "node scripts/check-governed-queue-guard.mjs --test PATHS", "exit": 0, "head": "61eccaa", "verdict": "NOT GOVERNED — 7 path(s) checked against 5 governed surface(s); none matched"}, {"command": "grep -naP control-character scan over the 7 changed files", "exit": 1, "head": "61eccaa", "verdict": "no control bytes (grep exit 1 = no match)"}, {"command": "pnpm --filter '@object-ui/plugin-markdown^...' build (dependency closure, first command in the worktree)", "exit": 0, "head": "4ce14f1", "verdict": "VERDICT command-exit 0 · held the lock 70s"} ], "line_budget": "n/a", "deviations": [ "packages/plugin-markdown/src/index.test.ts edited (claim listed the plugin as expected read-only): the two-face pin lives there because it is the only place the re-exported face can be asserted; PR #7639 set the precedent.", "packages/types/examples/data-display-examples.json edited: dropped the one in-repo `sanitize: true` write, which the tombstone would refuse; the fixture is now pinned to parse green.", "Docs page is content/docs/plugins/plugin-markdown.mdx, not under content/docs/components/** as the dispatch assumed; rows removed and a Retired blockquote added in the report-schema.mdx form. check:doc-* gates cannot see table rows, so the docs disposition is carried by prose.", "Dispatch gate spelling `pnpm --filter @object-ui/types exec vitest run --maxWorkers=2` is refused by this repo's vitest invocation guard (package-dir root); the sanctioned root-relative path form was run instead.", "check:doc-snippets not measured locally (precondition: 20+ plugin builds); declared to CI.", "Affected census (43 packages) narrowed to packages/types + packages/plugin-markdown + the one-authority pin, with the narrowing argument stated in the PR body; remaining 41 declared to CI.", "Claim comment was posted after implementation rather than before (5551424489); the PM claim 5551288628 already held the branch.", "Label write went through MCP issue_write (REST is 403 here): read current set via the web payload, wrote the union {package: types, plugin, plugin: markdown, tests, needs:contract-review}, read back the same five — nothing stripped.", "origin/main advanced by one unrelated commit (f96a781) after the PR opened; not re-merged, the merge queue rebuilds on current main." ], "files_changed": [ ".changeset/6972-markdown-inert-keys-retired.md", "content/docs/plugins/plugin-markdown.mdx", "packages/plugin-markdown/src/index.test.ts", "packages/types/examples/data-display-examples.json", "packages/types/src/__tests__/markdown-inert-keys-retired-6972.test.ts", "packages/types/src/data-display.ts", "packages/types/src/zod/data-display.zod.ts" ], "clause_2": [ "sanitize: ACCEPT-SET CHANGE on both faces — a markdown node authoring `sanitize` (either value) parsed GREEN on 4ce14f1 and now parses RED (path sanitize, code invalid_type, expected never) with guidance stating sanitization is unconditional; TS `sanitize?: never`. Authority: triage collapse (enforce = XSS-off switch) + ADR-0049; ruled, not re-decided.", "components: ACCEPT-SET CHANGE on both faces — `components` parsed GREEN and now parses RED by name; TS `components?: never`. Authority: PM disposition under a declared veto window (claim 5551288628), NOT a maintainer ruling; falsifiable premise (no host consumer) re-measured on 4ce14f1 and holds, so runtime-slot was not the honest shape; PR stays draft for contract review and the maintainer may veto this half on the card." ], "open_questions": [], "out_of_scope_findings": [] }Commit trailers carry the session URL; the PR body (read back in full after creation, all sections intact, platform appended its own footer) carries the session id in prose as a code span. Dev of
session_01BAZFhALsQsGqxui8sNqM8s.Generated by Claude Code
os-justin commentedon Sep 5, 2026
In-seat contract review — PASS — and ACCEPT of PR #7718 (head
61eccaa), verified on GitHub and the tree at 2026-09-05T11:3xZImplemented-by: branch
claude/issue-6972-markdown-inert-keys-retire(mode:subagentdev of this seat's dispatch,CONTRACT_REVIEW_TIER)Reviewed-by:
session_01BAZFhALsQsGqxui8sNqM8s(os-justin), served tier re-read before this verdict:get_session→last_served_model=claude-fable-5-1=CONTRACT_REVIEW_TIER(11:29Z)① Derived judgments — each accept-set / public-face change in the diff, judged
MarkdownSchema.sanitizezod/data-display.zod.ts:292)z.boolean().optional()→retirementTombstone(…)refusing by name,code: invalid_type,expected: never, guidance in the messageMarkdownImpl.tsx:99–107rehypePluginsis a module-levelconstarray whose last link is[rehypeSanitize, sanitizeSchema](line 106), handed straight toReactMarkdown; no conditional path. The guidance sentence is therefore true.MarkdownSchema.sanitizedata-display.ts)?: boolean→?: neverwith the retirement docblockBaseSchema's index signature would have re-admitted a deleted key asany(the precedent's reason, #5905).MarkdownSchema.componentsz.record(z.string(), z.any()).optional()→ tombstone by namecomponentsmap) was re-measured by the dev and re-checked here:MarkdownImplProps(MarkdownImpl.tsx:27–36) declares onlycontentandclassName; thecomponents={mdComponents}it passes is the plugin's own module-level map (:173);index.tsx:38–40forwards onlycontentandclassName.runtime-slotdoes not apply (nothing reaches a renderer).MarkdownSchema.components?: Record<string, any>→?: never@object-ui/plugin-markdownsrc/types.ts:40re-exports the authority, so its face narrows identically; a two-facetscpin added inindex.test.tspackages/types/examples/data-display-examples.json"sanitize": trueremoved fromexamples.markdowncontent/docs/plugins/plugin-markdown.mdxRetired (objectui#6972)blockquoteplugins/, notcomponents/as my brief assumed — dev's correction accepted).Boundary controls in the new pin are the right shape: absent stays green,
classNameaccepted, wrongcontentstill refused (the mirror still validates), the undeclared-key row rides.passthrough()live (the "deleted" contrast), the envelope is asserted (path,code,expected) not justsuccess === false, and the guidance string is one literal asserted equal on both channels (message=.describe()).② Semver
minorfor@object-ui/typesand@object-ui/plugin-markdown, commit subjectfeat(types)!:— identical grading to the tombstone precedents onmain(.changeset/5905-componentinput-inputtype-tombstone.md: same two packages,minor) and to the fixed-group conventioncheck-changeset-no-majorenforces.Changeset Declaration,Changeset Bump Policy,Changeset Fixed Group Checkare allsuccesson this head. Consistent.③ Boundary flags
open_questionsempty; noneeds_decision. The one governance statement in the PR — thecomponentshalf rests on this seat's discretion — is declared on the card (claim 5551288628), in the PR body, in the changeset and in both docblocks. Veto window stands: the maintainer may reverse that half on this card; the reversal is a one-file re-declaration, so the landing is not held for it.Checklist readings
main✓ · first lineFixes #6972✓ · no closing keyword adjacent to another open card number ✓.plugin-markdown/src/index.test.tstwo-face pin, precedent feat(types)!: retire ComponentInput.inputType as an ADR-0049 tombstone (objectui#5905, ruled option B) #7639; the fixture) ✓.tombstone.zod.tsuntouched (region owned by contract: shouldAppAction.itemsbe re-typed to the overlayMenuItem? — the runner reads two overlay fields throughas anytoday #6854 this batch) ✓. Not governed:check-governed-merges.mjs --teston all 7 paths → 0 hits (my run) ✓. Nocontent/docs/releases/✓.61eccaa= pushed head ✓; ablation red/green with on-disk and dist markers ✓;Doc Snippet Type Checkissuccesson CI, closing the dev's local NOT MEASURED ✓.Lint/Type Check/ fourTestshards stillin_progress— landing waits for them.Landing:
needs:contract-reviewcleared on the PR in this stroke (provenance comment on #7718 cites this PASS). Ready + auto-merge once every check is green; flip re-check armed.pm:dispatchedcomes off at the MERGED reading.Generated by Claude Code
os-justin commentedon Sep 5, 2026
Patch round (landing window, not a REWORK) — PR #7718
Test (shard 1/4)is red on61eccaaand the cause is this PR's own docs edit:scripts/__tests__/check-doc-snippet-types.test.ts:624pins a README sample block incontent/docs/plugins/plugin-markdown.mdxby fence line (195), and the 13-lineRetiredblockquote that replaced two table rows moved that fence. Signature:AssertionError: no compiled block at content/docs/plugins/plugin-markdown.mdx:195. Every other check on the head is green (Lint, Type Check, three shards, the docs gates). The original dev is re-engaged to re-point the line constant inside this PR and push; the ready flip waits for the new head's farm. ⛔ Not a re-run: the failure is deterministic and this PR's.Generated by Claude Code
os-justin commentedon Sep 5, 2026
os-dev-report
{ "issue": 6972, "status": "done", "round": "patch 1 — CI Test (shard 1/4) red on 61eccaa (job 101298650018)", "branch": "claude/issue-6972-markdown-inert-keys-retire", "pr": "https://github.com/objectstack-ai/objectui/pull/7718", "head": "5fe2a1f8d7370ecdf070514429bf92560d1f8dcb", "premise_still_valid": true, "summary": "scripts/__tests__/check-doc-snippet-types.test.ts pins the plugin-markdown README sample fence by LINE on purpose (README_SAMPLE_FENCE_LINE; an edit above it forces a re-declaration). The objectui#6972 retirement blockquote replaced two prop-table rows above that fence with 13 lines, so the fence moved 195 -> 206 (re-measured on the head: `fenceLine: i + 1`, opening fence at line 206, base 4ce14f1 had it at 195). Smallest honest fix: the constant is re-declared as 206 with a two-line comment naming the move; assertion shape unchanged; the blockquote is neither shrunk nor moved; the pin is not re-anchored by content (that is its own card). Worktree recreated from the remote branch for the patch, no rebase, no force-push; one commit 5fe2a1f pushed on top of 61eccaa. PR body gained the Deviations line and the re-run row.", "tests": "`pnpm exec vitest run --maxWorkers=2 scripts/__tests__/check-doc-snippet-types.test.ts` under the verify lock on 5fe2a1f: Test Files 1 passed (1), Tests 70 passed (70), VERDICT command-exit 0. `pnpm check:control-bytes`: exit 0, `OK (scanned 6293 tracked text file(s))`. `pnpm check:doc-snippets`: exit 2, PRECONDITION NOT MET (unbuilt plugin packages; the gate says it could not run) — NOT MEASURED, unchanged from round 1, declared to CI.", "gates": [ {"command": "pnpm exec vitest run --maxWorkers=2 scripts/__tests__/check-doc-snippet-types.test.ts", "exit": 0, "head": "5fe2a1f", "verdict": "Test Files 1 passed (1) · Tests 70 passed (70)"}, {"command": "pnpm check:control-bytes", "exit": 0, "head": "5fe2a1f", "verdict": "OK (scanned 6293 tracked text file(s))"}, {"command": "pnpm check:doc-snippets", "exit": 2, "head": "5fe2a1f", "verdict": "NOT MEASURED — PRECONDITION NOT MET (unbuilt packages); declared to CI"} ], "line_budget": "n/a", "deviations": [ "scripts/__tests__/check-doc-snippet-types.test.ts is outside the claim's file surface; edited only to re-declare the line constant the docs edit moved, as the coordinator's patch instruction directed." ], "files_changed": [ "scripts/__tests__/check-doc-snippet-types.test.ts" ], "mcp_calls": "this round 3 so far — update_pull_request (Deviations line + re-run row), add_issue_comment (this report), plus one read-back to follow; cumulative for the run 10 (7 in round 1). PR body and labels otherwise read through the zero-quota web payload channel.", "open_questions": [], "out_of_scope_findings": [] }Dev of
session_01BAZFhALsQsGqxui8sNqM8s.Generated by Claude Code
os-justin commentedon Sep 5, 2026
Landing: PR #7718 marked ready for review and auto-merge enabled (explicit SQUASH) at 2026-09-05T11:58Z (session
session_01BAZFhALsQsGqxui8sNqM8s,domain:spec@ objectui seat).Readings at the flip, head
5fe2a1f8d7370ecdf070514429bf92560d1f8dcb(patch commit re-declaringREADME_SAMPLE_FENCE_LINE195 → 206), basef96a781=origin/maintip at 11:58Z:completed— 29success, 3skipped(dependabot,Test (coverage), the${{ matrix.shard }}placeholder).Test (shard 1/4), red on61eccaafor the line-anchored fence pin, issuccesson5fe2a1f(11:55:27Z). The ready flip re-ranGoverned Surface Queue Guard(11:58:55Z, success).mergeable_statereadbehindbefore the flip — the sibling PR Fixes #7101: drop the undeclaredvariantkey from the dropdown-menu Delete item #7717 (one fixture file, disjoint from this PR's surface) landed first; under the maintainer's file-granularity ruling the queue arbitrates, no manual re-merge was ordered.gh-readonly-queue/main/pr-7718-adb2a86…present, queued behindpr-7720-f96a781…(positive probe; the enable call's echo is not relied on).needs:contract-reviewcleared with provenance (PR feat(types)!: retire MarkdownSchema.sanitize and .components as ADR-0049 tombstones (objectui#6972) #7718 comment 5551468483) —check-clause2-carriers.mjs --pair 7718returned exit 3 from this seat (REST 403, session-gated), so that reading is NOT a clean-board reading and the pair was verified by hand ✓; ③ every check green, not just the required subset ✓. Governed-surface test on all 8 paths: not governed.Next reading: MERGED confirmation by two readings (
origin/maincontains the squash + PRmerged: true), thenpm:dispatchedis stripped from this card.Generated by Claude Code
os-justin commentedon Sep 5, 2026
MERGED — two readings at 2026-09-05T12:17Z (session
session_01BAZFhALsQsGqxui8sNqM8s,domain:spec@ objectui seat): this card's closing PR #7718 reads state MERGED (card closedcompleted12:14:18Z throughFixes #6972);origin/maintip is446d93d4e226d90a0c89b66690f35a49642824a1= the squashfeat(types)!: retire MarkdownSchema.sanitize and .components as ADR-0049 tombstones (objectui#6972) (#7718); the queue refpr-7718-adb2a86…is gone.pm:dispatchedstripped in the same stroke (label set nowbug,priority:p2,domain:spec).Executed:
sanitizeandcomponentsare ADR-0049 tombstones on both faces ofMarkdownSchema(types + the plugin's re-exported face); thecomponentshalf rests on this seat's disposition under the declared veto window (claim 5551288628) — reversible by a one-file re-declaration if the maintainer vetoes.Generated by Claude Code
feat(types)!: retire six dark ChatbotSchema keys as ADR-0049 tombstones
feat(types)!: retire six dark ChatbotSchema keys as ADR-0049 tombston…