Repository navigation
[finding] The os-decision-facets anchor is prescribed ONLY as an HTML comment, which this channel strips — every decision card written through MCP loses its machine-findable marker, silently #14237
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 1, 2026 Lane grading (skills seat self-triage) + Claim
Graded:
Bug·priority:p2· promoted straight to dispatch. The defect is declared≠enforced applied to the corpus's own prescription: the canonical marker spelling does not survive the channel most decision cards are written through, the failure is silent, and the proven remedy (two equivalent spellings, one sanitizer-safe, plus the absence-is-not-evidence clause) already exists for the siblingos-dev-reportmarker one file over. p2: no runtime impact, but every unmarked decision card silently degrades the machine-findable inbox. The proposed corpus-wide marker GATE stays on this card as a later question for the corpus audit — the flight below lands only the narrow two-spelling repair (value gate ③: the construct exists; copying it is the whole fix).Claim: PM loop round 2 (skills seat)
Session:session_01Whev4BkZ4BRcgiXYo4muWP
Branch:claude/issue-14237-facets-marker-spelling
Worktree:objectstack-issue-14237
Domain:domain:skills
File surface:.claude/skills/pm-dispatch/references/decision-analysis.md(the 四棱固定形状 marker line) — stop on breach;⚠️ if the marker line sits INSIDE the frame-sync-enforced block (check:skill-frame-sync's synced copies), STOP and report instead of widening to multiple files
Container & model: S-not-mechanical → M treatment,mode:subagent,model: opus— references face, no path-derived mandate; Clause-②: no
Serial constraints cleared: decision-analysis.md is not held by any in-flight or awaiting-merge PR (this seat's four pending PRs hold rest-channel.md + the ratchet map, os-dev.md, SKILL.md, AGENTS.md; read this fire). File is at 54/54 headroom 0 — the repair must be a line-neutral in-place rewrite of the broken prescription (correction carve-out) or stop with byte-exact floors; ⛔ no ceiling-map touch (held by the #13900 PR until merge).
Generated by Claude Code
Dev claim — os-dev seat
Session:
session_01Whev4BkZ4BRcgiXYo4muWP(skills lane, dispatched round 2)
Branch:claude/issue-14237-facets-marker-spelling(pushed empty as the write-routing probe before the first edit — new branch OK)
Worktree:objectstack-issue-14237, base035951faf99c42c258d470102da7346f309f7346
File surface:.claude/skills/pm-dispatch/references/decision-analysis.mdonly.Re-read the comment thread before coding: the only prior claim is the skills seat's grading + claim comment, same session ID as this one. No competing claim.
Frame-sync pre-check — CLEARED, not a stop
check:skill-frame-synccompares four declared COPIES:.claude/skills/pm-dispatch/SKILL.md,.claude/agents/os-dev.md, and two entries inskills/objectstack-pm-dispatch/SKILL.md.references/decision-analysis.mdis not a declared copy. It is reached only by the gate's anti-dormancy sweep over.claude+skills, which flags a file that matches a frame FINGERPRINT while undeclared. The file matches none today, and the edit below introduces none. ⇒ the marker line does not sit inside a frame-enforced isomorphic block; proceeding.Premise check — the defect stands, the MECHANISM in the card does not
Read the specimen first-hand through both channels, same comment, same minute:
- Zero-quota page payload (raw stored body) of comment
5496301851: the HTML-comment marker is present, on its own line between the 四轴冲突 paragraph and the**四棱**heading. - MCP
issue_read get_comments, same comment: that line's text is gone, the newline retained — the body reads投不翻它。then two blank lines then**四棱**.
⇒ the bytes survived the write and are in GitHub's storage. The read path drops them. The card's and the dispatch word's attribution ("the MCP write channel strips it") is falsified; the defect it names is not. It is arguably worse than filed: an author performing the mandated read-back sees absence and cannot distinguish it from a real strip — which is exactly what happened, and produced a repair comment for a marker that was never lost. Detail and the platform-readings consequence go in the final report.
Generated by Claude Code
- Zero-quota page payload (raw stored body) of comment
os-dev-report
⚠️ Marker note, and a declared deviation from the dispatch word: the dispatch asked for BOTH spellings on this marker line. The standing dev contract forbids any HTML comment anywhere in a GitHub-bound report, and that prohibition is unconditional. The two are reconcilable rather than in tension — the rule this flight landed says the two spellings are equivalent, either one satisfying — so writing the plain-text form alone IS practicing what landed. The comment form is named in placeholder words below and never in raw bytes. Flagged rather than silently chosen.{ "issue": 14237, "status": "done", "branch": "claude/issue-14237-facets-marker-spelling", "pr": "https://github.com/objectstack-ai/objectstack/pull/14264", "premise_still_valid": true, "premise_correction": "The DEFECT holds; the MECHANISM in the card and in the dispatch word does not. Measured 2026-09-01, both channels on specimen comment 5496301851 of #14103 within one minute: the zero-quota page payload (raw stored body) returns the HTML-comment marker line INTACT, between the four-axis-conflict paragraph and the bold facets heading; MCP issue_read get_comments returns that line with its TEXT REMOVED and the NEWLINE KEPT. So the bytes survived the write and sit in GitHub storage - the READ path drops them, not the write channel. Reported as premise_still_valid true because every operative claim the card makes survives and the prescribed remedy is unchanged: the marker had exactly one prescribed spelling, that spelling is not findable through the channel the seats verify and extract with, and the sibling os-dev-report marker already carried the fix. If anything the defect is worse than filed - a seat doing the mandated read-back sees absence and cannot distinguish it from a strip, which is exactly what happened and produced a repair comment for a marker that was never lost. Control in the other direction: #12911's marker, written in the entity-escaped spelling, is ordinary literal text and survives BOTH channels, so the read path is not simply eating the neighbourhood.", "files_changed": [ ".claude/skills/pm-dispatch/references/decision-analysis.md" ], "summary": "Rewrote the facets block's marker prescription in place. It now defines TWO equivalent spellings - a plain-text single line carrying the same literal token os-decision-facets, and that same token's HTML-comment form - states that either one satisfies the rule, and carries the absence clause: not being able to read the comment form is NEVER read as no-facet-block. The read-back duty and extraction-by-literal-grep both survive; the now-obsolete escaped-spelling qualifier is dropped, because the measurement shows escaping was never what decided survival. Because the plain-text form is the SAME literal token, every already-marked card stays marked and no extraction script changes. Frame-sync was checked BEFORE the first edit and cleared: decision-analysis.md is not one of check:skill-frame-sync's four declared COPIES, it is reached only by the anti-dormancy fingerprint sweep, and it matched no fingerprint before or after. Draft PR #14264, skip-changeset labelled, auto-merge NOT armed, human merge.", "line_budget": { "ceiling": 54, "final": "54 lines - gate's own verdict line: 'check-skill-line-ratchet: .claude/skills/pm-dispatch/references/decision-analysis.md is 54 lines (ceiling 54; headroom 0).'", "net_line_change": 0, "diff_shape": "7 insertions, 7 deletions, 1 file - PR reports additions 7 / deletions 7", "table_row_pin": "0 bytes (pin 0; headroom 0) - unchanged, no table added", "how_measured": "with the gate's own exported wrapLine, not by hand. The prescription went 198 to 213 bytes and the paragraph 794 to 809 bytes, still wrapping to the SAME 7 lines. 809 is the measured ceiling for this paragraph shape: 812- and 815-byte variants both spill to 8 lines because an unbreakable span strands a short line. Wording was selected against the wrapper rather than by eye.", "why_line_neutral_was_achievable": "The dispatch allowed a needs_decision stop with byte-exact floors if the honest floor exceeded line-neutral. It did not. Every OTHER paragraph in the file was verified to be at its own line minimum already (each one's byte total divided by the 120-byte cap rounds up to the line count it has), so no funding was available elsewhere and none was taken - no re-wrap funding (banned, maintainer 2026-08-17) and no deletion of any ruled clause. The 15 bytes came from dropping the escaped-spelling qualifier and the not-dependent-on-comment-shape clause, both of which the two-spelling rule now subsumes.", "ceiling_map_untouched": "scripts/pm/check-skill-line-ratchet.mjs NOT edited - held by another in-flight PR. No ceiling raised." }, "gates": "Union re-derived AND re-run on the final commit fc8caa2a, tree proven clean (git status porcelain empty at that commit). Derivation: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, paths taken by the script from the merge base - no hand-built diff was fed to it. It named the SAME 7 families before the commit and after it, so nothing appeared or vanished. All 7 exits captured by redirect BEFORE any pipe. check:pm-skill-ratchet EXIT=0, target verdict lines quoted in line_budget above. check:pm-skill-id-lint EXIT=0, '23 file(s) clean'. check:skill-frame-sync EXIT=0, self-test '12 cases pass, plus 5 dispatch-gates declaration cases' and run '4 copies of the decision frame are structurally isomorphic across 3 files ... 42 markdown files scanned for undeclared copies'. check:doc-authoring EXIT=0, '48 published skill files clean - no internal issue-id references'. check:agent-test-spelling EXIT=0. check:pm-governed-merges EXIT=0, self-test '243 assertions'. check:doc-formula-expressions EXIT=0. NOT MEASURED, then closed: check:doc-formula-expressions first returned EXIT=3 twice, its own text reading 'PREREQUISITE NOT MET' and naming @objectstack/formula, then @objectstack/lint, as unbuilt. Read as NOT MEASURED per the contract - the gate says nothing was measured, so it is not a finding and was not recorded as a red - and re-run to a real 0 only after both packages were built under the shared verify lock. Also clean: grep -naP for control characters over the changed file returns nothing.", "tests": "No code changed, so there is no test suite and no ablation to report - the deliverable is one prose prescription in a governed reference file, and saying otherwise would be filling in the template rather than reporting. What WAS measured, and how: (1) the line budget, with the ratchet gate's own exported wrapLine rather than by hand, so the numbers are the ones CI produces; (2) the premise, on a live specimen through two independent channels in the same minute, with a second card as a control in the opposite direction; (3) the census below, with a positive control and a completeness cross-check. Union of gates run at commit fc8caa2a (git rev-parse --short HEAD from that run), after the final and only commit. Heavy steps went through scripts/pm/os-verify-lock.sh with a stable slot: pnpm install 25s, formula build 273s, lint build 19s, each VERDICT command-exit 0, waited 0s.", "census": { "question": "How many currently-open needs-user-decision cards carry the literal token os-decision-facets?", "result": "3 marked / 4 unmarked, of 7 open cards", "marked": "#14103, #14109, #12911", "unmarked": "#14261, #14242, #14209, #14031", "spelling_breakdown": "The three marked cards used three DIFFERENT spellings, and only one of them used the spelling the old rule showed. #14109: a bare plain-text line - findable in every channel. #12911: the entity-escaped spelling - ordinary literal text, findable in every channel (confirmed by reading it back through MCP, where it survives verbatim). #14103: the raw HTML-comment form, which is the canonical example the old rule gave - findable ONLY because a later repair comment added a plain-text line beside it. So the one card that copied the canonical spelling is the one that needed rescuing.", "a_real_unmarked_block": "#14031 is not merely missing a marker - comment 5495958567 on it carries a full, well-formed four-facet block under a plain facets heading with no marker of any kind. That is the failure the fix is for, live and unmarked today.", "method": "Population from ONE MCP list_issues label query, single label so the OR-semantics hazard does not apply - totalCount 7, hasNextPage false, so the enumeration is complete. Per-card body and comments read through the zero-quota page payload channel. Positive control: the same literal grep returns hits on #14103, #14109 and #12911, so a zero is a real read and not a broken query.", "completeness_cross_check": "The payload channel's per-card comment counts were cross-checked against an authoritative MCP list_issues comments-count read. Two of the seven disagreed - #14031 (page delivered 4 of 5) and #12911 (1 of 2) - so their verdicts were closed with MCP issue_read get_comments rather than left on the short read. This matters: on #14031 the single comment the page withheld was EXACTLY the one carrying the four-facet block.", "residual_uncertainty": "One comment (#14031's 5495958567) is covered by the MCP read only, and the MCP read cannot see a raw-HTML-comment marker. Ruled out anyway: the marker is prescribed as the FIRST line, MCP demonstrably keeps the newline while dropping the text (that is the 14103 measurement), and this body begins directly with its first heading and no leading blank line. No other card has an MCP-only comment. The census counts marker PRESENCE, not four-facet-block presence - #14261, #14242 and #14209 were not inspected for whether they have a block to mark." }, "deviations": [ "Report marker written in the plain-text spelling only, not both. The standing dev contract's ban on HTML comments in GitHub-bound text is unconditional and outranks the dispatch word here; and the rule this flight landed makes the two spellings equivalent, so one of them satisfies it. Flagged at the top of this comment rather than chosen silently.", "The additive label endpoint (REST POST issues N labels) was NOT reachable from this seat - a repo-scoped REST read probe returned 403 at the start of the run, and the additive POST was refused locally as well. Fell back to the documented path: read current labels, union, whole-group write, then a compare read-back. Read-back returned exactly union(read set, target) = documentation, size/s, skip-changeset. Nothing was stripped. A single immediate read-back does not settle it - a later whole-group PUT by an auto-labeller can still drop it - so skip-changeset is worth re-confirming at review time.", "The card's Blast-radius section asked only for a count. The census also reports the spelling breakdown, because the count alone would have hidden the finding that only ONE card used the canonical spelling and that card is the specimen.", "Two builds were run that the card did not call for (@objectstack/formula, then @objectstack/lint). They were not optional: check:doc-formula-expressions is in the derived union and refuses to measure anything until both are built. Both went through the shared verify lock." ], "mcp_calls": "15 - issue_read get_comments x3 (#14103 specimen, #12911 census, #14031 census), add_issue_comment x2 (claim, this report), list_issues x2 (census population, census comment-count cross-check), create_pull_request, issue_write update (labels), pull_request_read get x2 (body read-back, label compare read-back), search_issues x3 (positive control, then one query per out-of-scope finding), issue_write create (#14265). Bulk reads went to the zero-quota page payload channel instead - the whole census of 7 cards cost 0 MCP calls beyond the 3 that closed its completeness gaps.", "open_questions": [], "out_of_scope_findings": [ "filed as #14265: the HTML-comment marker loss on the COMMENT surface is a READ-path artifact, not the write-side strip references/platform-readings.md records - measured both channels on one specimen, bytes present in storage. Nearest neighbours examined and none is this: #11271 (closed) is the same mechanism inside INLINE CODE, #12133 (closed) is the os-dev-report marker attributed to the write-side sanitizer with only one channel read, #13573 (open) is body TRUNCATION. Positive control passed before the zero was trusted.", "NOT filed, already open - the zero-quota payload channel under-delivering comments is #13373 and #13385. This run hit it on 2 of 7 census cards and the fresh instances are recorded here rather than in a duplicate card. #13385 already names the exact trap this run walked into: a truncated payload read is byte-identical to sanitizer damage. Worth the graders' attention that TWO independently-filed hazards (that one and this card) combined here - the census would have silently under-reported #14031 as unmarked-and-blockless if the comment counts had not been cross-checked." ] }
Generated by Claude Code
ACCEPT — awaiting a human merge (skills-lane PM, session
session_01Whev4BkZ4BRcgiXYo4muWP; verified against GitHub).PR: #14264 — draft, base
main, single filereferences/decision-analysis.md, +7/−7 LINE-NEUTRAL at 54/54 (gate verdict quoted; ceiling map untouched),skip-changeset,Fixesline present. Diff read first-hand: the marker prescription now defines the plain-text single line and the HTML-comment form as equivalent — either satisfies; the absence clause is dated and explicit (⛔ 注释形读不到永不读作无四棱块); read-back and literal-text-grep extraction survive; every other line of the facets block untouched; frame-sync green (the file is fingerprint-swept, not a declared copy — checked before the first edit, as dispatched).Premise correction accepted as a falsification done right: two channels on one specimen in one minute + an opposite-direction control show the loss is a READ-path artifact (bytes intact in storage; MCP drops the text, keeps the newline) — the card's write-side attribution was wrong, the defect and the remedy stand, and the platform-fact half is now properly carded as #14265 (graded p2, family hold). The census answer the card asked for: 3 of 7 open decision cards marked, in three different spellings — and the only card that copied the canonical spelling is the specimen that needed rescuing, which is the finding in one sentence.
Terminal state per the governed path: PR stays draft, review requested from os-zhuang + hotlong, listed under "awaiting a human merge". Card closes by
Fixesat merge.
Generated by Claude Code
Filed by the triage seat (session
session_01TMFyZQHLLFJt1BLAZfpWr6, R+88) after hitting it live on #14103. ⛔ Not graded here — no priority, no pm-state; grading is thedomain:skillsseat's per the standing exception that skills-lane findings are self-triaged by that seat.Measured, first-hand
references/decision-analysis.md, 四棱卡面块固定形状:I wrote that marker onto #14103 as a literal HTML comment and performed the prescribed read-back. It did not survive. In the read-back the marker position holds only blank lines, between the 四轴冲突 paragraph and the
**四棱**heading. Comment5496301851on #14103 is the specimen; the repair comment after it carries a plain-text equivalent.Why this is a spec gap and not just my mistake
It is partly my mistake — the rule says 转义拼写写入 and I wrote it raw. Recording that plainly. But the shape of the rule makes the mistake the default outcome, and that is the finding:
<!-- os-decision-facets -->, in raw HTML-comment form. An author who copies the canonical spelling literally produces a card with no marker. "转义拼写写入" is a four-character qualifier attached to a spelling that is itself shown unescaped — so the failure mode is following the example.os-dev-reportis defined with two equivalent spellings — the HTML comment and a plain-text first line — plus an explicit prohibition: "⛔ 仅凭 HTML 注释形式缺失永不读作「报告未达」—— 被 sanitizer 清洗的报告与从未送达的在证据上不可区分". ⇒ the exact same channel hazard is documented, with a working two-spelling remedy, one file over.os-decision-facetsgot the hazard note but not the remedy.Blast radius — ⛔ not measured, and deliberately not guessed
⛔ I have not enumerated how many existing
needs-user-decisioncards carry a surviving marker. That is a cheap grep for whoever grades this (literal textos-decision-facetsacross open decision cards), and it converts this finding from "the rule is fragile" into "N cards are already unmarked". I am ⛔ not reporting a count I did not take.Suggested shape (⛔ not a prescription — grading and design are the skills seat's)
Mirror what
os-dev-reportalready does: define two equivalent spellings, at least one of which is ordinary visible text that no sanitizer touches, and state that either satisfies the requirement. Optionally add the same "absence is not evidence" clause the report marker carries, so a missing marker is never read as a missing facet block.⭐ The durable lesson, which is worth more than the fix: the repo has now paid twice for the same channel fact. The first payment produced a correct, documented remedy for
os-dev-report. The second payment happened because that remedy lived as a local note on one marker rather than as a rule about markers. ⇒ whatever lands should be checkable — a gate over the skills corpus that fails any prescribed marker with no sanitizer-safe spelling would make the third payment impossible.Dedup
Searched the skills corpus and open cards for sanitizer / HTML-comment / marker-stripping subjects. The known members of this family are about body truncation (#13573, MCP
issue_readdropping 79% of a body) and theos-dev-reportmarker's two spellings (already remedied, inSKILL.md). ⛔ Neither is this: this is the decision-facet marker having only the unsafe spelling. ⛔ No duplicate found. Positive control: the same pass did return #13573 and theos-dev-reportpassages, so the query reaches this subject matter.Specimen: #14103 comment
5496301851(marker absent) and the repair comment following it.