Repository navigation
feat(lint)!: every component-props finding is an error at os validate, os build and os lint - #23244
Conversation
…, os build and os lint The component-props gate's recorded plan, second step: every component-props-invalid verdict that is not a retired key, and every component-props-unknown-key finding, move from warning to error. The retired-key class keeps its own fix text. The rule's surfaceReason is corrected to the measured page-local reason. Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude <noreply@anthropic.com>
…te now files Twenty-five D3 migration entries said the component-props gate reports their refusal as an advisory finding; it is an error now. The gate-table rows and the layout-dsl callout, one lint comment and two spec comments say the same. Adds the breaking changeset. Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude <noreply@anthropic.com>
…mponent-props-error-rest
…ks name the props gate's error Eighteen step-18 rationale fragments said the component-props gate reads their refusal as advisory, and three component.zod.ts docblocks said every finding but a retired key stays advisory. Both shipped in 18.0.0-next.0 and both are false once the gate files every finding as an error. Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude <noreply@anthropic.com>
…mponent-props-error-rest
…he props gate's error Under the amended surface: three conversions/registry.ts docblocks, a metadata-protocol test comment, the row-on-success test header that arrived from main, and the pending page-action-row changeset each said the component-props gate files a warning. It files an error now. Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 141 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c6beda8b17a2820fc7256e47c10f3eb14d1f2a94 && git checkout c6beda8b17a2820fc7256e47c10f3eb14d1f2a94
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin afbf960c2649d87ff6503721b9c290ec679356c6 80db1670d71505278815bb3dcfbe978a348be81b && git checkout -B drift-repro afbf960c2649d87ff6503721b9c290ec679356c6 && git merge --no-ff 80db1670d71505278815bb3dcfbe978a348be81b
node scripts/docs-audit/affected-docs.mjs --json afbf960c2649d87ff6503721b9c290ec679356c6
|
|
Contract reviewServed-tier: Inputs: card #23147 (body and all seven comments: two pointers, triage, the claim and its amendment 6143270198, the ① Derived judgmentsThe refused set, route by route (
The measured-zero baseline (acceptance 1 and 2): the population is the right one. The four examples ( Overlap with PR #23186 (card #23145): no textual and no semantic conflict. #23186 edits
② Semver level
③ Boundary flags
Why PASS. The refused set is exactly every finding the gate files and nothing wider — each of the six routes errors on its row's own accept set, with the unregistered-type skip, the non-object bag skip and the one-arm union rule unchanged; the baseline is the right population with planted controls on each; Implemented-by: VERDICT: PASS |
…ions pdf The class pin spelled element:text variant heading, which the spec's tree-scoped absence pin refuses outside its retirement kit. A retired 'pdf' export format is the same class (an enum refusal carrying its prescription, not a tombstone) and no tree-scoped pin guards it. Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude <noreply@anthropic.com>
|
The failure.
Why it is not this PR's.
What was done. Failed jobs of run 38394146025 were re-run once, in this act. A second failure is treated as real.
Generated by Claude Code |
Contract reviewServed-tier: Round 2 (delta) on card #23147. Inputs: the card body and all nine comments (two pointers, triage, the claim and its amendment 6143270198, the two ① Derived judgments1. The delta is exactly one file, 2. It is a genuine retired ENUM VALUE, not a 3. Path and severity as asserted — RIGHT on the head's rule. 4. No tree-scoped absence pin matches 5. The normalized tier sees the refusal too — RIGHT. The only D2 conversion that touches 6. Carry-over — RIGHT, stated so this record stands alone. The compare's file list is the one file above, so each of the other 42 files carries the same blob at 7. Overlap with PR #23186 (card #23145) at
8. CI on ② Semver levelUnchanged from round 1 and restated: The DELIBERATE CORRECTION on The PR body's "Fix round (seat note 6144802415)" section and the updated Acceptance-notes input-tier sentence — true against the head, bullet by bullet: ③ Boundary flags
Why PASS. The delta is one test literal that pins the retired-enum-value class more faithfully than before — an Implemented-by: VERDICT: PASS |
Fixes #23147
Clause-②: no (narrowing: findings the CLI doors warn on today are refused)
What this does
This is the last step of the component-props gate's recorded plan (the maintainer's #5068 ruling, direction A). The gate's header said: "the warning-period inventory is the acceptance baseline for the error upgrade, and the upgrade is its own step once the inventory is empty." #23026 moved the retired-key class. This PR moves the rest, class (b), in one step with no staged window:
component-props-invalidverdict that is not aretiredKey()tombstone: a wrong type, a missing required prop, a closed-set refusal (a url action'stargetscheme, arecord:detailssection'scount), a retired enum value, and a missingdataSource.objectbinding;component-props-unknown-keyfinding, on all three routes that file it: a strict schema'sunrecognized_keys, a strict union arm, and the walker over a strip-mode object below the bag.os validate,os buildandos lintnow refuse each of these.ComponentPropsSeveritynarrows to'error'. The retired-key classifier stays, because it picks the fix text (the tombstone's prescription, never a schema change). It no longer picks the severity. No new gate and no new rule id.Door probe at
c88fe84c1: a fixture page withpage:headertitelandrecord:related_listlimit: 'five'.os validate --json: exit 1,valid: false. Both findings are listed undererrorswith severityerror:component-props-unknown-keyat...properties.titelandcomponent-props-invalidat...properties.limit.os lint --json: exit 1, with the same two errors.os build: exit 1 ("Author-time rules failed"), and nodist/was written.os validate --jsonat the same head: exit 0, 0 component-props findings.The inventory (acceptance 1 and 2), each class counted separately
The rule ran at its own input tier:
loadConfig, thennormalizeStackInput, thenauthoringRuleUnionStack, thenvalidateComponentProps, using the built@objectstack/lintfrom this worktree.examples/app-crm,app-todo,app-multi-package,app-showcase8323cad97, and again at the final headc88fe84c1platform-objectsx3,plugin-approvals,qa/downstream-contract,cloud-connectionx2,mcpobjectstack-ai/hotcrm: its 8 authored pages (src/sales/pagesx7,src/service/pagesx1)0f84c9c72dc2e2ced4990de28b737c5597a3d5b4(shallow clone, read-only)component-props-unknown-keyfinding on every population that has a page. A planted wrong-typed prop drew 1component-props-invalidfinding. The one exception isplugin-approvals, whose first judged component has no string prop to retype. On the final head, both controls fileerror.app-todo,app-multi-packageand thecreate-objectstacktemplate author no page, and neither doespackages/apps/*(git grepfinds nopages,definePageorregions).defineStackrefuses its eightemailTemplates[].categorywith the v18 tombstone. So its page exports were judged directly, and the stack's own load was not run. The pages import only@objectstack/spec, which resolved to this worktree's build.sys_metadatapage rows, any other repository, and docs code samples that carry noos:check-yamltag.H2 (the split): both classes measured 0 on every population, so there is no partial move and no decision-box item.
surfaceReason(H3): measured false, correctedRUNTIME_NEEDS_FULL_SNAPSHOTreads "reads a stack-wide collection the per-write snapshot does not carry".stack.pagesand nothing else (validate-component-props.ts, therecordsOf(stack.pages)loop). Each page's components come throughwalkPageComponents(page-walk.ts:113), which reads only that page plus spec's static slot list. Each component is judged against the staticComponentPropsMap(lintUnknownKeysAgainstSchemaandschema.safeParse), plus its owndataSource.page: 'pages'(runtime-gate.tsTYPE_TO_STACK_KEY). So apagewrite's snapshot carries the written page aspages[0](snapshot keysobjects, permissions, books, datasets, pages).buildRuntimeWriteSnapshots, against the whole-stack run. The per-page findings were identical as authored (0 = 0, 28/28 pages equal). They were also identical with an undeclared key and a wrong-typed prop planted on each page's first judged component (19 = 19, 28/28 equal).So the reason was false, and the entry now carries the page-local reason
validateComponentTypesalready states: the rule is held off the runtime door pending a measured false-refusal budget over stored tenant page rows. The rule is not added to the runtime publish door (triage6137942125).Pins and ablation
validate-component-props.test.tshas a new block, "every other class is an error too". It pins each class and each code route on rule id, severity and path, and leaves the text unpinned. The #23026 block keeps the retired-key pins. The sibling tests that assertedwarning(validate-record-approval-decision-22472,validate-record-approvals-attachments-22537) now asserterror.Ablation. Five one-time legs ran through
scripts/ablation-replace.mjs. Each leg reverted one severity site towarning. Every anchor hit exactly once (x1 to x0), and every blob moved. The tool's trap restored each leg by absolute path, and the restore was proven: blobe3ed3effd342equals HEAD's, andgit diff HEADwas empty. Nothing needed building: the test imports the rule fromsrc. Each leg reddened exactly its own pins:dataSource.objectbinding#11509it.eachrows, the class pin, the pipeline pinunrecognized_keystitelpin, the#7751specimen, two pipeline pins, the class pintarget,record:detailscount, retired enum value, mixed union verdict, per-issue classificationADR-0087 (H4) and the changeset
.changeset/23147-component-props-every-finding-refused.md:@objectstack/lint: major, plus@objectstack/spec: patch(text only);Clause-②: no (narrowing), a**BREAKING**line, and a FROM → TO table with its one-line fix;adr-0087: not-required (already-registered …)marker. It names the 26 step-18 D3 entries whose refusal the gate carries: the 25 whose reason this PR amends, pluselement-flat-data-binding-retired.check-adr-0087-registration --base origin/mainexits 0 ("1 declared-breaking changeset(s), each carrying an ADR-0087 disposition").check-changeset-no-majorexits 0.check:future-spec-majorexits 0 (H5).One correction to a pending release note, to be confirmed here.
.changeset/23103-page-action-row-on-success-and-executor.md(#23164, merged, not yet released) said the props gate files its finding as "(that gate's one tier, a warning)". This PR changes that, and both notes ship in the same release. So the parenthesis now reads "(an error, like every finding of that gate)", and nothing else in the note changed. That is the DELIBERATE CORRECTION class, socheck-empty-changesetstays red on purpose.Check Changesetis not a required context. I ask the approver to confirm this edit in writing on this PR.Every gate-tier sentence this change made false, BEFORE → AFTER
The claim's file surface (
6140712434) was amended by6143270198. It now covers every comment, docblock or prose sentence in the repository that states the component-props gate's tier for a prop finding and is false on this head. The edits are text only: no schema, guidance string or.describe()changed, and no other rule's text. Published means shipped in@objectstack/spec@18.0.0-next.0(dist/migrationscarries the D3 reasons and the step-18 rationale;src/**/*.zod.tsships as source).Original surface (claim
6140712434)packages/lint/src/validate-component-props.ts: the header's Severity section (the second step is recorded, with both inventories), the severity type, and every finding'sseverity. Three message tails changed: "nothing refuses it today, so the renderer receives the props as written", the missing-binding sentence's equivalent, and the unknown-key walker's "so nothing verifies it". Each now says the finding is refused and the stack does not pass until it is fixed.packages/lint/src/authoring-rules.ts: the entry comment, and thesurfaceReason(above).content/docs/deployment/validating-metadata.mdxandcontent/docs/protocol/objectui/layout-dsl.mdx(published docs site):content/docs/deployment/validating-metadata.mdxcontent/docs/deployment/validating-metadata.mdxcontent/docs/protocol/objectui/layout-dsl.mdx18.ui-object-master-detail-form-details-closed.ts:42-43, the string seat pointer6139673366names, is in the D3 list below. Every finding of the gate is now an error, so "reports a failing entry or column as a ... error" is true for thesortFieldtombstone too.Published, outside the original list: 25 step-18 D3 entry reasons (
packages/spec/src/migrations/entries/semantic/)packages/spec/src/migrations/entries/semantic/18.element-data-source-and-object-block-filter-rule-array.tspackages/spec/src/migrations/entries/semantic/18.element-text-variant-heading-subheading-retired.tspackages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.tspackages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-block-grouping-config-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-form-custom-fields-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-form-fields-names-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-form-members-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-form-sections-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-gantt-markers-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-grid-columns-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-grid-page-size-positive-integer-refused.tspackages/spec/src/migrations/entries/semantic/18.ui-object-grid-row-members-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-map-gantt-tree-navigation-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-metric-aggregate-trend-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-metric-compare-to-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-report-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-timeline-items-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-object-timeline-mapping-typed.tspackages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.tsPublished, outside the original list: step-18 rationale (
packages/spec/src/migrations/registry.ts.template,STEP18_RATIONALE)Eighteen fragments carried the same clause. BEFORE "Read by the component-props gate (advisory);" → AFTER "Read by the component-props gate (refused as an error);". The fragments are
ui-action-group-menu-member-params-array-only,ui-action-group-menu-members-typed,ui-object-form-custom-fields-typed,ui-object-form-fields-names-typed,ui-object-form-members-typed,ui-object-form-sections-typed,ui-object-gantt-markers-typed,ui-object-grid-columns-typed,ui-object-grid-kanban-calendar-list-members-typed,ui-object-grid-row-members-typed,ui-object-kanban-conditional-formatting-typed,ui-object-map-gantt-tree-navigation-typed,ui-object-metric-aggregate-trend-typed,ui-object-metric-compare-to-typed,ui-object-metric-drill-down-report-typed,ui-object-metric-drill-down-typed,ui-object-timeline-items-typedandui-object-timeline-mapping-typed. Two more were worded differently:The generated projections move with them.
render-projection-diff.ts --basethe merge base givesspec-changes.json+48 −48 andprotocol-upgrade-guide.md+26 −26. All 74 removed lines carry "advisory", all 74 added lines carry "error", and no added line carries "advisory".Published, outside the original list: three
packages/spec/src/ui/component.zod.tsdocblocks (shipped as source)The 「…」 ruling quotation in the #7751 section is byte-identical: 18 quotation lines before and after.
packages/spec/src/ui/component.zod.tspackages/spec/src/ui/component.zod.tspackages/spec/src/ui/component.zod.tsUnpublished, outside the original list (amendment
6143270198)packages/spec/src/conversions/registry.tspackages/spec/src/conversions/registry.tspackages/spec/src/conversions/registry.tspackages/spec/src/conversions/registry.tspackages/spec/src/conversions/walk.tspackages/lint/src/validate-page-field-bindings.tspackages/metadata-protocol/src/protocol.stored-migration.test.tspackages/spec/src/ui/component.test.tspackages/lint/src/validate-component-props-url-target.test.tspackages/lint/src/validate-component-props-row-on-success.test.ts.changeset/23103-page-action-row-on-success-and-executor.mdClass grep on the final head
c88fe84c1The grep searched every tracked file that names the gate (
component-props,props gate,props lint,validateComponentProps,ComponentPropsMap,props bag), outsideCHANGELOG.md,content/docs/releases/**,.changeset/pre/**andcontent/docs/references/**. It flagged every line within eight lines of a gate word that carries a tier word (advisory,warning,warn,non-gating,still passes,exit 0,Validation passed,WARNING-level). That is 22 hits in 13 files, every one either edited above or true as written:authoring-rules.ts:1164("the warning-period inventory was empty");validate-component-types.ts:36,38("launched advisory");component.zod.ts:288("was WARNING-level in this first step");component.zod.ts:661andretired-keys/18.ui__PageHeaderProps__breadcrumb.ts:17("advisory in every release that shipped the default": the default shipped only in 17.x, when the lint was advisory);component.zod.ts:716("until this declaration …warning: undeclared");validate-component-props.test.ts:530,536("used to warn");component-objectui-held-typed-members.pin.test.ts:25(the old pass);walk.ts:371, now "(advisory then)".18.ui-action-group-menu-member-params-array-only.ts:28,18.ui-object-form-fields-names-typed.ts:32,component-action-member-params-array-only.pin.test.ts:14,expression-conformance.ledger.ts:495,519;variantenum value:18.ui-object-timeline-items-typed.ts:23;record:alertprop value:validate-component-props.test.ts:1062,1077.FALSE remaining: none.
Verification, on
c88fe84c1(origin/main01a3421f4merged in)All test runs went through
scripts/pm/os-verify-lock.sh, each with aVERDICT command-exit 0, on a shared box.@objectstack/lintpnpm test@objectstack/linttypecheckcheck:test-typecheck: OK, debt ledger unchanged)@objectstack/specpnpm test@objectstack/spectypecheck@objectstack/speccheck:generated, after a real build of this head@objectstack/metadata-protocolsrc/protocol.stored-migration.test.ts42/42,typecheckpassed. The diff there is one comment, so the rest of the package's tests are declared to CI.Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 110 lines from this head (43 paths, +346/−224). All 110 ran.--ranwith an exit-coded record: "110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN". 109 exited 0.check-empty-changesetexited 1: the deliberate correction above, red on purpose.Type Check · source gatescommands the derivation does not list. All exited 0:check:future-spec-major,check:driver-conformance,check:object-def-param-keys, the fourcheck:stall-guard*/check:rerun-safety-verdictgates,check:swallow-census-controls, speccheck:generated --reconcile-only/check:skill-docs/check:meta-url-spelling/check:react-blocks/check:template-manifests, the four skill-frame and skill-key gates,check:agent-model-declared,check:ratchet-remedy-authority, and the skills token ratchet.render-projection-diff.tswith--basethe merge base exited 0.Fix round (seat note 6144802415)
Test Core (1/6)was red atc88fe84c1. The spectest:repoabsence pinsrc/ui/element-text-variant-heading-retirement.test.ts:148refused the new class pin'selement:textvariant: 'heading'inpackages/lint/src/validate-component-props.test.ts.object-gridexportOptions: { formats: ['pdf'] }and asserts onecomponent-props-invaliderroratproperties.exportOptions.formats.0. That is zodinvalid_value, carrying the'pdf' was removed from view.exportOptions formatsprescription, and it is not a tombstone.packages/andscripts/that walks the tree (readdirSync,walk(, glob) was grepped for apdfmatcher, and none has one. The only hits arepackages/spec/src/ui/view.test.ts, which parses and walks nothing, and a.pdfaccept string incomponent-form-custom-fields-sections-typed.pin.test.ts, which is not a matcher for this value. No D2 conversion lifts'pdf', so the normalized tier sees this refusal too.80db1670d: one file changed, +3/−3. The absence pin (repo project) is green. Its lit control plantedvariant: 'heading'back throughablation-replace.mjs, and the pin went red naming the file. The restore brought the blob back to HEAD's89d9fb165dca.@objectstack/spectest:repo: 58 files / 1007 passed.@objectstack/lint: 138 files / 6686 passed, typecheck passed. lint has no repo-tier project.check-empty-changesetexit 1, the designed red. Its verdict lines are identical to 6144550809's, so that comment's facts are unchanged.Acceptance notes
/meta, MCP) still does not run this rule. ThesurfaceReasonnow states the measured reason. Crossing needs its own false-refusal budget over stored tenant page rows.normalized) stack, so an ADR-0087 D2 conversion (e.g.element-text-variant-heading-levels) rewrites a retired spelling before it is judged. The retired-enum-value pin authors a value no D2 conversion lifts (object-gridexportOptions.formats: ['pdf']), so the normalized tier refuses it as well.@objectstack/*17.7.0. Under this tree's spec its stack is refused atdefineStackforemail_template.category, whichos migrate meta --from 17names. That is not class (b) and not this gate. The tombstone text there says "removed in @objectstack/spec 17" for a key hotcrm uses on 17.7.0. That is the wrong-version family spec(ui): theobject-griddefaultFiltersand element flat-binding tombstones say "removed in @objectstack/spec 17", but 17.7.0 shipped those keys live; the v18 train removes them in 18 #23145 carries, and PR fix(spec): tombstones for keys live in @objectstack/spec 17.7.0 name 18 as their removal version #23186 editsemail-template.zod.ts.validate-component-props.test.ts(hunks at about :379 and :483). This PR does not touch those lines.Generated by Claude Code