Filed by the domain:devx @ objectui execution seat from the objectui#7646 implementation run (R46). Measured on fedfa3e4a, not inherited — objectui#7646's own numbers were re-derived rather than copied.
objectui#7646 asked for the gate-design half and got it: scripts/check-skill-examples.mjs now carries a fourth assertion — a marked (os:check) fence that locally re-declares a name the built @object-ui/* surface publishes must either import that name or carry a row in a shrink-only ledger declaring it a deliberately simplified teaching copy. This card is the other half: the fence repairs themselves.
Why they are not in that PR
skills/** and .claude/skills/** are GOVERNED surfaces — agent drafts, human merges (scripts/check-governed-queue-guard.mjs, GOVERNED_SURFACES). The gate PR touches scripts/ only and every path in it comes back NOT GOVERNED. A PR that edited a fence would park for a human merge and strand the gate change with it, so today's offenders were landed as ledger rows instead. Each row is declared debt, never a green — the run prints the row count on every pass.
The population, re-derived on fedfa3e4a
Census over every ts / tsx / typescript fence in skills/, .claude/skills/ and content/docs/, matching each fence's local type NAME / interface NAME against the exported type and interface names of the workspace packages, excluding any name the same fence imports:
- 602 fences in the corpus, 13 carry
os:check.
- 28 re-declare a name the workspace exports; 26 distinct names.
- Split by root:
skills/ 8, .claude/skills/ 2, content/docs/ 18.
- Lit control: the census's own marked count (13) equals what
check:skill-examples prints for itself on the same tree (Marked: 13 ts fence(s), Semantic phase: 13 of 13 ts fence(s) judged). The instrument was not blind.
This card owns the 10 under the two governed roots. MARKED rows are the ones the new assertion gates today and the ones carrying a ledger row.
| # |
fence |
shadows |
published from |
| 1 |
skills/objectui/guides/architecture.md:20 |
BaseSchema |
@object-ui/types |
| 2 |
MARKED skills/objectui/guides/auth-permissions.md:59 |
AuthUser |
@object-ui/auth |
| 3 |
skills/objectui/guides/data-integration.md:27 |
DataSource |
@object-ui/types |
| 4 |
skills/objectui/guides/data-integration.md:63 |
QueryParams |
@object-ui/types |
| 5 |
MARKED skills/objectui/guides/data-integration.md:81 |
QueryResult |
@object-ui/types |
| 6 |
MARKED skills/objectui/guides/plugin-development.md:92 |
ComponentInput, ComponentInputControlType |
@object-ui/types |
| 7 |
skills/objectui/guides/plugin-development.md:275 |
FieldWidgetComponentProps |
@object-ui/fields |
| 8 |
skills/objectui/rules/protocol.md:48 |
BaseSchema |
@object-ui/types |
| 9 |
.claude/skills/objectui-contributor/guides/console-development.md:154 |
MetadataResourceConfig |
@object-ui/app-shell |
| 10 |
.claude/skills/objectui-contributor/guides/console-development.md:279 |
NavigationContextType |
⚠️ not on the published surface — internal to packages/app-shell/src/context/NavigationContext.tsx, so no import exists for the reader to write and the new assertion does NOT flag it. Listed for completeness; the repair here is prose, not an import. |
What a repair is, per fence — ⛔ not a sweep
objectui#7646's triage ruled option 3 (blanket conversion to imports) out: a guide's fence is often a deliberately simplified view of a large published type, and a mechanical pass would make several guides less readable. So each row is one of three judgements, made by the guide's owner:
- Import it.
import type { AuthUser } from '@object-ui/auth'; and delete the local copy. Drift becomes a compile error at the point it happens. Best when the fence's copy is already the whole type.
- Derive from it. Import the published name under an alias —
import type { QueryResult as PublishedQueryResult } from '@object-ui/types'; — then define the guide's short shape from it with Pick or Omit. Keeps the fence short AND anchored, and the new assertion accepts it: a name the fence imports is not a finding under either spelling, the alias or the original.
- Keep the teaching copy and say so. Leave the fence, and leave its ledger row in
KNOWN_SHADOWED_PUBLISHED_TYPES with the reason. ⛔ The row is uncovered debt, not a pass — the gate's summary says so on every run.
Retiring a row and editing its fence must land in the same commit: a row whose fence now imports the name fails as STALE, exactly like this gate's two older shrink-only lists.
The evidence this is worth paying for
Reproduced on fedfa3e4a with trap/restore (mutation confirmed on disk by anchored counts; restore proven by blob hash equal to the HEAD blob and an empty git diff HEAD), on fence 6:
- Arm B — an internally CONSISTENT lie:
name: number where the published required member is string, all five ADR-0049 retirement tombstones restored as writable, plus an invented frobnicate that exists on no type at all. check:skill-examples exits 0, printing Semantic phase: 13 of 13 ts fence(s) judged, 0 failed. The fence is INSIDE the judged count — the gate read it and passed it.
- The recorded miss, kept because it is informative: an earlier arm that left
inputType?: never in place and added inputType?: string went RED with TS2300 (twice) and TS2717. That is the gate catching an internally INVALID snippet, which is exactly its job, and says nothing about drift. ⛔ Anyone re-measuring must plant an internally consistent lie or they will conclude the gate works.
⛔ Not in this card's scope
- The 18
content/docs/ fences. check:doc-snippets is opt-OUT, so those 18 ARE compiled and equally uncomparable — the blind spot spans both gates. Widening the assertion into scripts/check-doc-snippet-types.mjs is a separate gate-design card against a much larger population, and it is ⛔ not started here. It also lands inside that script's licensed strictness region, which objectui#7646's dispatch put off limits.
- The gate itself. Landed in the objectui#7646 PR; this card changes no script.
Merge posture
⚠️ Every path this card touches is GOVERNED. Its PR is agent drafts, human merges: it parks in draft for a maintainer, ⛔ is not flipped ready by a seat, and ⛔ does not self-queue.
Back-links: objectui#7646 (the finding and the gate), objectui#7636 (the instance that paid for it first).
Generated by Claude Code
Filed by the
domain:devx @ objectuiexecution seat from the objectui#7646 implementation run (R46). Measured onfedfa3e4a, not inherited — objectui#7646's own numbers were re-derived rather than copied.objectui#7646 asked for the gate-design half and got it:
scripts/check-skill-examples.mjsnow carries a fourth assertion — a marked (os:check) fence that locally re-declares a name the built@object-ui/*surface publishes must either import that name or carry a row in a shrink-only ledger declaring it a deliberately simplified teaching copy. This card is the other half: the fence repairs themselves.Why they are not in that PR
skills/**and.claude/skills/**are GOVERNED surfaces — agent drafts, human merges (scripts/check-governed-queue-guard.mjs,GOVERNED_SURFACES). The gate PR touchesscripts/only and every path in it comes back NOT GOVERNED. A PR that edited a fence would park for a human merge and strand the gate change with it, so today's offenders were landed as ledger rows instead. Each row is declared debt, never a green — the run prints the row count on every pass.The population, re-derived on
fedfa3e4aCensus over every
ts/tsx/typescriptfence inskills/,.claude/skills/andcontent/docs/, matching each fence's localtype NAME/interface NAMEagainst the exported type and interface names of the workspace packages, excluding any name the same fence imports:os:check.skills/8,.claude/skills/2,content/docs/18.check:skill-examplesprints for itself on the same tree (Marked: 13 ts fence(s),Semantic phase: 13 of 13 ts fence(s) judged). The instrument was not blind.This card owns the 10 under the two governed roots. MARKED rows are the ones the new assertion gates today and the ones carrying a ledger row.
skills/objectui/guides/architecture.md:20BaseSchema@object-ui/typesskills/objectui/guides/auth-permissions.md:59AuthUser@object-ui/authskills/objectui/guides/data-integration.md:27DataSource@object-ui/typesskills/objectui/guides/data-integration.md:63QueryParams@object-ui/typesskills/objectui/guides/data-integration.md:81QueryResult@object-ui/typesskills/objectui/guides/plugin-development.md:92ComponentInput,ComponentInputControlType@object-ui/typesskills/objectui/guides/plugin-development.md:275FieldWidgetComponentProps@object-ui/fieldsskills/objectui/rules/protocol.md:48BaseSchema@object-ui/types.claude/skills/objectui-contributor/guides/console-development.md:154MetadataResourceConfig@object-ui/app-shell.claude/skills/objectui-contributor/guides/console-development.md:279NavigationContextTypepackages/app-shell/src/context/NavigationContext.tsx, so no import exists for the reader to write and the new assertion does NOT flag it. Listed for completeness; the repair here is prose, not an import.What a repair is, per fence — ⛔ not a sweep
objectui#7646's triage ruled option 3 (blanket conversion to imports) out: a guide's fence is often a deliberately simplified view of a large published type, and a mechanical pass would make several guides less readable. So each row is one of three judgements, made by the guide's owner:
import type { AuthUser } from '@object-ui/auth';and delete the local copy. Drift becomes a compile error at the point it happens. Best when the fence's copy is already the whole type.import type { QueryResult as PublishedQueryResult } from '@object-ui/types';— then define the guide's short shape from it withPickorOmit. Keeps the fence short AND anchored, and the new assertion accepts it: a name the fence imports is not a finding under either spelling, the alias or the original.KNOWN_SHADOWED_PUBLISHED_TYPESwith the reason. ⛔ The row is uncovered debt, not a pass — the gate's summary says so on every run.Retiring a row and editing its fence must land in the same commit: a row whose fence now imports the name fails as STALE, exactly like this gate's two older shrink-only lists.
The evidence this is worth paying for
Reproduced on
fedfa3e4awith trap/restore (mutation confirmed on disk by anchored counts; restore proven by blob hash equal to the HEAD blob and an emptygit diff HEAD), on fence 6:name: numberwhere the published required member isstring, all five ADR-0049 retirement tombstones restored as writable, plus an inventedfrobnicatethat exists on no type at all.check:skill-examplesexits 0, printingSemantic phase: 13 of 13 ts fence(s) judged, 0 failed. The fence is INSIDE the judged count — the gate read it and passed it.inputType?: neverin place and addedinputType?: stringwent RED withTS2300(twice) andTS2717. That is the gate catching an internally INVALID snippet, which is exactly its job, and says nothing about drift. ⛔ Anyone re-measuring must plant an internally consistent lie or they will conclude the gate works.⛔ Not in this card's scope
content/docs/fences.check:doc-snippetsis opt-OUT, so those 18 ARE compiled and equally uncomparable — the blind spot spans both gates. Widening the assertion intoscripts/check-doc-snippet-types.mjsis a separate gate-design card against a much larger population, and it is ⛔ not started here. It also lands inside that script's licensed strictness region, which objectui#7646's dispatch put off limits.Merge posture
Back-links: objectui#7646 (the finding and the gate), objectui#7636 (the instance that paid for it first).
Generated by Claude Code