Skip to content

fix(types): retire icon from the record:highlights fields[] entry arm - #9347

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-9280-record-highlights-icon-retire
Sep 13, 2026
Merged

fix(types): retire icon from the record:highlights fields[] entry arm#9347
os-sam merged 1 commit into
mainfrom
claude/issue-9280-record-highlights-icon-retire

Conversation

@os-sam

@os-sam os-sam commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Fixes #9280

Retires icon from the record:highlights fields[] entry across all three layers
that carried it. Direction is triage's ruling on the card (retire, ⛔ not widen): widening
@objectstack/spec to declare icon would widen a published accept set — a manual-floor
decision and an objectstack matter, ⛔ not this repo's call.

⚠️ Generics below are written with SQUARE brackets: GitHub's body sanitizer eats
tag-shaped fragments, fences included.

The two things the dispatch could not establish, established here

1. The spec-side reading — RE-MEASURED, and it holds. @objectstack/spec was not
installed in the triage or PM container, so the claim was unverified. Measured here from the
installed artifact (pin 17.4.0), reading the arm's own shape rather than transcribing
anything:

fields[] object-arm keys : ["label","name","readonly","type"]
arm catchall             : never   =>  $strict (an unlisted key is REFUSED, not stripped)

{ fields: [{ name:'x', label:'L' }] }      GREEN                          #  CONTROL
{ fields: [{ name:'x', icon:'star' }] }    RED  invalid_union at fields.0
{ fields: [{ name:'x', zzzNonsense:1 }] }  RED  invalid_union at fields.0  #  CONTROL
{ fields: ['x'] }                          GREEN                          #  CONTROL

All three controls fired: a declared key parses green, so the arm is not refusing
everything; an arbitrary key is refused with the same invalid_union code as icon, so
icon was not special-cased; the bare-string arm is unaffected, so only the object arm
moved. ⇒ the direction stands, and the Clause-②: no citation is now a measurement rather
than a claim.

2. Layer ② — LOCATED. Triage had read only a test comment describing the normalizer,
never the read site. The actual read site is
packages/plugin-detail/src/renderers/record-highlights.tsx, in the rawFields.map entry
normalizer: the line was literally icon: f?.icon,, forwarded into HeaderHighlight.
⭐ Re-derived by symbol, not by the ~1-day-old line numbers.

FROM to TO, per layer

layer file FROM TO
① type packages/types/src/record-components.ts { name; label?; icon?; type?; readonly? } { name; label?; type?; readonly? }
② normalizer packages/plugin-detail/src/renderers/record-highlights.tsx copies icon: f?.icon into the entry key dropped
③ manifest packages/plugin-detail/src/index.tsx fields description sketches {name,label?,icon?,type?,readonly?} {name,label?,type?,readonly?}

The key is removed, not tombstoned. A ?: never tombstone is the remedy for a
non-strict mirror that would otherwise strip in silence; this arm is $strict, so the
named refusal an author needs already exists upstream and arrives as invalid_union at the
entry.

Nothing that worked stops working. All three layers were already broken, not
functional: no author could get icon past the $strict arm, so the normalizer's read was
unreachable code and the manifest's promise was an impossible advertisement.
Measured here and worth recording: HeaderHighlight renders no .icon at all, so the
normalizer's copy had no consumer on the far side either.

In-repo consumer census

109 record:highlights regions across all tracked files score an entry-level icon of
0. Control in the same pass over the same regions: readonly scores 23 — the
instrument was not blind.

Verification

leg command result
build (dep closure) pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail^...' build exit 0
type-check pnpm --filter @object-ui/types type-check exit 0
type-check pnpm --filter @object-ui/plugin-detail type-check exit 0
tests pnpm --filter @object-ui/types test 183 files / 4197 tests passed
tests pnpm --filter @object-ui/plugin-detail test 175 files / 1625 tests passed
lint (the real gate) pnpm --filter @object-ui/types --filter @object-ui/plugin-detail lint exit 0 (0 errors)

⭐ The pin file was proven to be inside the type-check program before any claim was made
about it: tsc -p tsconfig.test.json --listFiles lists
record-highlights-fields-icon-9280.test.ts (control: the sibling ...-layout-9187.test.ts
is listed too). Without that, a green type-check would say nothing about the
@ts-expect-error legs.

Gates, derived from .github/workflows/lint.yml's step list (⛔ not package.json),
exit codes captured by redirect before any pipe:

gate exit its own verdict line
check-lint-coverage.mjs 0 lint coverage: 46/46 packages linted, 0 with outstanding errors
check-entry-guard.mjs 0
check-test-path-roots.mjs 0 OK (1917 filesystem call(s) in 399 of 3103 test file(s))
check-vi-mock-override-shape.mjs 0
check-changeset-presence.mjs 0 4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check-changeset-no-major.mjs 0 No changeset declares a major bump
check-governed-queue-guard.mjs --test 0 NOT GOVERNED — 6 path(s) checked ... none matched

pnpm lint is turbo run lint (per-package), and both affected packages ran it green
above. check-upstream-port-parity.mjs and check-bash32-floor.mjs are not implicated
— this diff touches no scripts/ file and no shell script — and are left to CI.

Ablation — the pin can actually fail

Run from the committed state. Mutation proven on disk before any result was read;
restore proven byte-identical, not by an exit code.

HEAD blob            : c3c500d8fbcdce89a5a2e45469325b746eca1ebd
PRE  'label?: string; icon?'  : 0        POST (injected) : 1
PRE  'label?: string; type?'  : 1        POST (removed)  : 0
mutated blob         : b43b88e88fe8c2a42ceaeff8c1eaaa09d4c6d119   (differs from HEAD => it landed)

ABLATION_VITEST_EXIT = 1    (non-zero, required)
ABLATION_TSC_EXIT    = 2    (non-zero, required)

restored blob        : c3c500d8fbcdce89a5a2e45469325b746eca1ebd   (== HEAD blob)
git diff HEAD is empty : yes

The failures are the right ones, not merely non-zero:

record-highlights-fields-icon-9280.test.ts(129,3): error TS2578: Unused '@ts-expect-error' directive.
record-highlights-fields-icon-9280.test.ts(92,3):  error TS2344: Type 'false' does not satisfy the constraint 'true'.
AssertionError: expected '...string | { name: string; label?...' not to contain 'icon'

TS2578 is the acceptance criterion: with icon back on the arm, the entry compiles
again, so the directive goes unused. TS2344 is the invariant Equal[...] key-set assertion
flipping to false. ⭐ The PREMISE legs stayed green through the ablation — they read
the spec only, so they are the premise and never the evidence, exactly as the file labels
them. No dist hop was involved: the pin imports '../record-components' relatively and
re-reads the same file from disk.

Pins added

  • packages/types/src/__tests__/record-highlights-fields-icon-9280.test.ts — three
    instruments that do not see the same thing: a tsc @ts-expect-error leg with a
    {name,label} control that stays green; safeParse legs against the installed spec with
    three controls; and a source-text read whose lit control is sections[].icon one
    interface up, so an empty result on the highlights arm is a reading rather than a matcher
    that can never match.
  • packages/plugin-detail/src/__tests__/recordHighlightsInputs.spec-parity.test.ts gains the
    reverse direction it was missing. It already failed when a spec entry key went
    undocumented; it now also fails when the description advertises an entry key the spec
    refuses — the exact drift this card was filed for.

Scope notes

  • sections[].icon is untouched, deliberately. It is a different member on a
    different face
    : the contract declares it and DetailSection genuinely draws it. Two keys
    sharing a word in one file are not the same key. It appears in this PR only as the lit
    control for the source-text instrument.
  • ⚠️ This PR does not decide whether a highlight chip should carry an icon. If there is
    real product pull, the route is an upstream @objectstack/spec widening on its own card.
    ⛔ Not pre-judged here.
  • RecordHighlightsRenderer.readonly.test.tsx's header comment still contains the word
    icon. Left as-is on purpose: it is past-tense history describing the pre-objectstack#5077
    normalizer, and it stays true.

Acceptance notes

  • HighlightField.icon (packages/types/src/views.ts) is a third face carrying this
    word — the HeaderHighlight component-prop type, reached via
    DetailViewSchema.highlightFields. Measured: HeaderHighlight renders no .icon, so
    after this change the key has no producer and no consumer in-repo. Noted, not filed:
    DetailViewSchema carries function props (onTabChange), so it is a React props face, not
    authored metadata that someone else stores and re-authors — it is not the metadata-trap
    class, and nothing here is a reproducible defect or a declared-contract breach. Its natural
    successor is the upstream spec-widening card above, if that is ever opened; there is no
    queued PR that would otherwise touch it.

Delivery posture

Draft, per the dispatch order. needs:contract-review hung on this PR: it is a breaking
narrowing of a published type
. ⛔ Not flipped ready, ⛔ not enqueued, ⛔ no auto-merge.


Generated by Claude Code

… arm

`@objectstack/spec` `RecordHighlightsProps.fields[]`'s object arm declares
exactly `name`/`label`/`type`/`readonly` behind a `never` catchall — it is
`$strict`, so an unlisted key is REFUSED, not stripped, and the refusal takes
the whole document with it. This repo declared a fifth key, `icon`, on three
layers: the published type, the renderer's entry normalizer, and the registry
manifest's `fields` input description.

All three were already broken rather than working. No author could get an
`icon` past the contract, so the normalizer's read was unreachable and the
manifest's promise was impossible to honour. `HeaderHighlight` renders no
`.icon` on the far side either. Retiring makes the three layers agree with the
one layer that has authority; widening the contract instead is an upstream
decision on its own card.

Re-measured against the installed pin (17.4.0) with three controls: a declared
key parses green, an arbitrary key is refused with the SAME `invalid_union`
code as `icon`, and the bare-string arm is unaffected.

`sections[].icon` on `RecordDetailsComponentProps` is a different key on a
different face — the contract declares it and `DetailSection` draws it — and
is deliberately untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.7 KB 3134.8 KB
Main entry chunk (gzip) 144.4 KB 350 KB
Entry file index-CLh4QV91.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 502.06KB 115.19KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.91KB 62.51KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.49KB 65.87KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

os-sam commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review — PASS · ceiling tier · adopted by the domain:spec @ objectui seat

Head reviewed: f51cbf43a73a85718c63a50feae6c84a39b9af09. Record posted per «记录 = 同形评论落 PR 或卡»; this is the record the carrier strip cites.

Tier — MEASURED, not asserted

This seat runs at the default tier (get_session: claude-opus-5 on all three fields) and is therefore ⛔ forbidden from clearing this carrier by its own judgement. The review was an isolated subagent with the model passed explicitly, and its transcript was verified before adoption:

probe reading
model stamps in transcript 97 × claude-fable-5-1, no other value
positive control (instrument reads the file) 86 assistant turns matched
residue, same regex family including fable 100 hits ⇒ the control fires
residue, non-fable tiers only 0 ⇒ a true zero, not a broken probe

⚠️ Recorded because it nearly went wrong: the first residue probe was written as grep … | sort | uniq -c || echo, where || tests uniq's exit code, not grep's — a zero and a broken instrument would have printed identically. Re-run with a same-family control, per «零命中须用确定存在的邻近词反查;控制词须与主张同主体».

What the review measured (⛔ not what the PR says about itself)

It fetched the published @objectstack/spec@17.4.0 tarball from the registry and probed it with zod 4.4.3, extracted this head into a scratchpad, installed offline, ran the pins, and ran both ablations itself with byte-identical restores.

# question verdict
1 Clause-②: no correct? ✅ MEASURED — adds no export, no key, no accepted input; test files cannot reach the published package (tsconfig.json excludes **/__tests__/**, files: ["dist", …])
2 cited contract text real? ✅ MEASURED against the published artifact — object-arm keys ["label","name","readonly","type"], catchall never; all four probes and three controls behaved as claimed
3 narrowing complete across three layers? ✅ MEASURED — one commit, repo-wide grep at head finds only CHANGELOG history and comments
4 do the pins actually fail? ✅ MEASURED — ablations re-run independently, both restored byte-identical
5 sections[].icon untouched? ✅ MEASURED — sole hunk is inside RecordHighlightsComponentProps; line 146 untouched, and the key is genuinely drawn (DetailSection.tsx:656,691)
6 changeset honest? ✅ MEASURED — minor both packages per AGENTS.md:252, FROM→TO per layer

The review found the contract is STRONGER than this PR claims. @objectstack/spec does not merely refuse icon — it names it retired: ui/component.zod.ts removes it under #10054 / ADR-0049 with a guidance.icon prescription, and the CHANGELOG places that in 17.2.0, below this repo's 17.4.0 pin. ⇒ the retirement here is not a judgement call; it is catching up to a retirement the protocol already shipped.

⭐ The review also independently re-derived the census rather than trusting the PR's 109/23: its own instrument over 36 files found 0 entry-level icon against a 104-hit readonly control — same conclusion, different instrument, and the control fires.

Observations recorded, ⛔ none blocking, ⛔ none filed

  • Layer ② has no pin. rawFields is any[], so a reintroduced icon: f?.icon would compile and no test would redden. ⛔ Not filed: it is not a reproducible defect, not a declared-contract breach, and not a metadata trap — type, manifest and contract all refuse the key and HeaderHighlight has no icon slot, so the path is unreachable from authored metadata. Recorded here so the next editor of that normalizer knows the guard is one-sided.
  • sdui.manifest.json in the sibling objectstack checkout still advertises icon? at the old .objectui-sha. It is generated and self-corrects at the next pin bump; ⛔ not this PR's to touch and ⛔ not a card.
  • The card carries no needs:contract-review, only the PR. That is correct here rather than an omission: the card-side carrier is required by «Clause-②: yes 认领同笔在卡上挂标», and this claim declared no (a narrowing). The PR-side carrier is the one the review clears.

Not reached — stated rather than implied

The PR's exact census counts (109/23) and full-suite totals (4197/1625) were not re-run; only the two pin files and the types tsc program were executed. check-clause2-carriers.mjs --pair 9347 was not run (carrier state read directly instead). check-widening-tells.mjs ran but reports all six files NOT MEASURED — no declared surface covers objectui paths ⇒ ⛔ that gate is not evidence here, in either direction.

PASS adopted. The carrier is stripped in the same act as this record. ⛔ Landing still waits on every check green — three test shards were open at the last reading, and in_progress is an honest "not yet", never a pass.

Seat: domain:spec @ objectui, os-sam, session session_01L5xpA5q533BgTTNADibEFt, 2026-09-13T04:4xZ.


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 13, 2026 04:46
@os-sam
os-sam added this pull request to the merge queue Sep 13, 2026
Merged via the queue into main with commit 7cbc724 Sep 13, 2026
38 checks passed
@os-sam
os-sam deleted the claude/issue-9280-record-highlights-icon-retire branch September 13, 2026 05:04

os-sam commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Addendum to the contract review record — a seat error, corrected in the open

This PR has landed. The addendum does not change its verdict; it repairs the record and names a mistake of mine so the next seat does not inherit it.

1 · The independence pair was missing from the record

references/contract-review.md requires it as a machine-readable part of the same-form record. It was absent from 5651206810. Supplying it:

Implemented-by: claude/issue-9280-record-highlights-icon-retire
Reviewed-by: session_01L5xpA5q533BgTTNADibEFt

⛔ Not SELF-REVIEW: the diff came from a mode:subagent dev (recorded by branch, per the discipline), and the judgement came from a context-isolated ceiling-tier subagent fed only the card, the existing rulings and the PR — ⛔ never the dispatch order or this seat's conclusions. This seat rendered and adopted; it did not judge.

2 · ⛔ I was WRONG about the card-side carrier, and the reviewer was right

The review raised it as observation (c): "card #9280 carries no needs:contract-review label — only the PR does; the PM seat should apply its own carrier discipline when clearing."

I answered that this was correct rather than an omission, on the reasoning that the card-side carrier is required only by «Clause-②: yes 认领同笔在卡上挂标», and this claim declared no.

That reasoning is wrong. references/contract-review.md is explicit: 「PR 与卡双载体同笔挂:PR 一存在即挂」, a DUAL carrier by maintainer ruling (2026-08-22, 「两边都挂好」), hung in one stroke and cleared in one stroke. It is not conditioned on the Clause-② value. The yes clause I cited governs when the card-side carrier is hung at claim time, ⛔ not whether a dual carrier exists at all.

⭐ And the reason the second carrier exists is exactly the thing I reasoned past: it is the only machine-readable evidence that a gate was CLEARED rather than STRIPPED — a legitimate clear leaves two removals seconds apart, a strip leaves one. Since 「闸门被剥不是红灯是放行」, 「stripped」 and 「never hung」 are otherwise indistinguishable in the evidence.

⇒ this PR's pair was never legible, and my dismissal removed the one signal that would have said so.

3 · Pre-landing check ② was not run, and cannot now be run

contract-review.md 落地前检 names three gates. ① (same-form review PASS on record) and ③ (every check green, ⛔ not a required subset) were both satisfied and are recorded above. ② was not: node scripts/pm/check-clause2-carriers.mjs --pair 9347 was never executed before the flip to ready.

Run post-hoc, it answers:

exit 2 — PR #9347 is not open ... ⛔ Not a clearance — the pair could not be
formed, so nothing about it was judged.

UNJUDGED, and permanently so — a closed PR cannot be paired, so this reading can never be recovered. ⛔ I am recording that as an unmeasured gate rather than back-filling a clean verdict I did not earn.

What the landing does rest on, stated so the record is not read as weaker than it is: the ceiling-tier review PASS (transcript-verified, 97 × claude-fable-5-1, 0 non-fable, control firing at 100); all 36 checks green with total_count equal to the array length; the path surface taken by get_files with no governed path and Governed Surface Queue Guard green; the carrier strip verified by read-back; and the landing itself verified by content on main with two firing controls. The missing item is the mechanical pair check, ⛔ not the substance.

4 · What changed as a result, on the lane rather than on this PR

  • objectui#9239's card now carries the dual carrier (it was PR-only; --pair 9348 read exit 4 — NOT clause-② legible on precisely this split).
  • objectui#9102 / PR fix(types): carry the protocol's registry metadata across both schema derivations #9349 carries the same split and is in a patch round; its re-hang must go on both carriers.
  • Pre-landing check ② is now run before the ready flip on every remaining PR this shift, ⛔ never after.

Seat: domain:spec @ objectui, os-sam, session session_01L5xpA5q533BgTTNADibEFt, 2026-09-13T05:1xZ.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): the record:highlights fields[] object arm declares icon, which the spec's strict arm refuses — tsc green, publish RED

1 participant