Skip to content

fix(spec): the object-grid page-size migration entry states that a stored page still saves, with an advisory CLI finding - #20884

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20860-page-size-entry-advisory
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20860-page-size-entry-advisory

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20860
Clause-②: no

The ADR-0087 D3 entry ui-object-grid-page-size-positive-integer-refused (protocol 18) said, in its acceptanceCriteria, that a stored page whose object-grid node carries pageSize: 0 "is refused on its next authoring-path save with a per-key issue at pagination.pageSize". The save path does not do that. This PR corrects the sentence to the measured behaviour. It is a text-only change: no entry id, conversion, schema or matching logic moves.

The two measurements (tree: objectstack-ai/objectstack at 6c96b37be8, the branch base)

1. The save. The real saveMetaItem write path was driven over a stub engine. The harness is copied from protocol.runtime-authoring-gate.test.ts, with an environment id and the default authoring channel, so the runtime authoring gate is live. The probe lived in scratch, not in the tree. PUT /api/v1/meta/:type/:name in rest-server.ts hands its body to this same call with writeFace: 'meta-envelope', so the probe ran both faces. The HTTP door itself was not booted. Every case was saved, stored and read back through getMetaItem:

page body (object-grid node properties) both faces
pagination: { pageSize: 0 } success: true, no advisories member, one active row, reads back { pageSize: 0 }
pageSize: 25.5 (flat shorthand) success: true, one active row, reads back 25.5
pagination: { pageSize: 10, pageSizeOptions: [10, 0] } success: true, one active row, reads back as written
pagination: { pageSize: 25 } (well-formed) success: true
control: a component type: 'user:profile' (retired by name) refused: 422 INVALID_METADATA, issue at regions.0.components.0.type

The control leg shows that the harness can return a refusal, so the successes above are real readings. The one runtime-publish rule registered for type page is not validateComponentProps. The registry reports runtimeAuthoringRulesFor('page') has length 1, and it does not include that rule.

2. The CLI. The same page was run through runAuthoringRules(command, …) for each of validate, build and lint. packages/cli/src/commands/{validate,compile,lint}.ts call this function. The registry row is validateComponentProps with tier=advisory, surfaces=["cli"] and commands=["validate","build","lint"]. The output was identical for all three commands: 0 errors and 3 advisories.

warning component-props-invalid pages[0].regions[0].components[0].properties.pagination.pageSize | pagination.pageSize: Too small: expected number to be >0 (received 0)
warning component-props-invalid pages[1].regions[0].components[0].properties.pageSize | pageSize: Invalid input: expected int, received number
warning component-props-invalid pages[2].regions[0].components[0].properties.pagination.pageSizeOptions.1 | pagination.pageSizeOptions.1: Too small: expected number to be >0 (received 0)

The code read in the card holds. PageComponentSchema.properties is z.record(z.string(), z.unknown()). validateComponentProps / ComponentPropsMap have 0 non-test hits under packages/metadata-protocol/src, packages/objectql/src, packages/metadata/src, packages/rest/src and packages/runtime/src. The control hit is packages/lint/src/authoring-rules.ts:113. The rule's own hint already says "the props bag is not parsed on the storage path either".

The correction

acceptanceCriteria now says, in the wording triage gave: the page still saves and loads; properties on a page component is not parsed on the metadata save path; the component-props gate reports an advisory component-props-invalid finding at pagination.pageSize on os validate, os build and os lint. Measurement 2 shows that a pageSizeOptions entry and the flat pageSize shorthand are reported the same way at their own paths, so the criterion says that too. It ends with the author's check: os validate reports no such finding for that node.

surface, replacement and reason were read for the same false claim. None of them states a save-path refusal, so they are unchanged.

Sibling object-grid-default-filters-rule-array: measured the same way. A bare string defaultFilters: 'status' and a malformed rule list [{ field: 'status' }] both save with success: true on both faces. The CLI gives one advisory component-props-invalid each, at properties.defaultFilters and at properties.defaultFilters.0.operator. The entry already says the key's door "refuses such a value only as the component-props gate's advisory finding (os validate, os build, os lint), since a re-save through the metadata API is not refused there". That is true, so the sibling is unchanged.

Generated copy

packages/spec/src/migrations/registry.ts was regenerated with pnpm --filter @objectstack/spec gen:migration-registry and was not edited by hand. It has one hunk. With leading whitespace stripped, its +/- lines are byte-identical to the entry file's (cmp exit 0, 10 lines each). The word-diff of both files shows the same replacement. spec-changes.json and the protocol upgrade guide project protocol 17 only. As measured, check:spec-changes and check:upgrade-guide are green with no regeneration, and neither file is in the diff.

Changeset

.changeset/20860-page-size-entry-advisory.md: @objectstack/spec patch, Clause-②: no. It is text-only because the entry ships in the package's dist.

Local verification (all at head a3fd6c8aef)

  • Build: pnpm --filter @objectstack/spec build passed (exit 0). @objectstack/spec has no workspace dependencies, so step 1 of the closure build is empty.
  • Package tests: pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 gave Test Files 579 passed (579) and Tests 17090 passed | 1 todo. pnpm --filter @objectstack/spec typecheck exited 0 (tsc --noEmit, check:scripts-typecheck, check:test-typecheck OK). The public surface is unchanged (check:api-surface: "public API surface + factory signatures unchanged"), so no importer owes a test.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 82 commands. Each exit code was captured to a file before any pipe. --ran reconciliation: "82 derived famil(ies) accounted for — 81 run, 1 NOT-MEASURED". 80 exited 0 on the first pass. check:lean-entry-closure exited 3 (PREREQUISITE NOT MET, objectql dist absent), and after @objectstack/objectql... was built it exited 0. Named verdicts: check:migration-registry "registry.ts is current", check:spec-changes "spec-changes.json is up to date", check:upgrade-guide "protocol-upgrade-guide.md is up to date", check:doc-authoring clean, check:nul-bytes OK, check:adr-0087-registration "no declared-breaking changeset", check:empty-changeset OK.
  • NOT MEASURED: check:dual-build-cjs-loads. Reason: it loads the built CJS entry of every workspace package and needs a whole-workspace pnpm build (60+ packages have no dist here). This diff changes one string literal inside @objectstack/spec, whose own build passed. CI's Lint & Repo Gates and Build Core measure it.
  • Stale-tree note: the gate list was derived at a3fd6c8aef (base 6c96b37be8). By reconciliation time origin/main had moved 6 commits, and 4 files that families derive from had changed there (check-stack-collection-maps.mjs, an i18n-walk-parity fixture, one adr-anchor JSON, role-word-baseline.json). None of them reads this diff's paths. The branch is not merged with main, and CI measures the merge ref.
  • Not run locally, declared to CI: pnpm lint (repo-wide, CI-owned), the type-check lanes and the path-scheduled jobs the derivation lists.

Acceptance notes

  • The released @objectstack/spec CHANGELOG entry for the narrowing (9bb059d) says "the object-grid page-component door now refuses a page size of 0". That is true of the ComponentPropsMap parse. It does not name the save path, so it is not the false claim this card corrects. CHANGELOG is release-owned. Carrier: none.

Owed before enqueue: the at-tier contract review by the owning seat (review face packages/spec/src/** non-test). This PR does not carry that record.


Generated by Claude Code

…ill saves, with an advisory CLI finding

The acceptance criterion of `ui-object-grid-page-size-positive-integer-refused`
said a stored page whose `object-grid` node carries `pageSize: 0` is refused on
its next authoring-path save. Nothing on the metadata save path parses a page
component's `properties`; the component-props gate is an advisory, CLI-only
rule. The criterion now states the measured outcome: the page saves and loads,
and `os validate` / `os build` / `os lint` report an advisory
`component-props-invalid` finding at `pagination.pageSize`.

Text only. The registry is regenerated with gen:migration-registry.

Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 9905e61ca2fddb43d266c23cdb12ced5019c1a74 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6a6b641a4b9dc447ed8f5facaa02a1d35ec7f179 — the merge of head a3fd6c8aef0f9696f736ad32fa1d516f6c900a1b into base 9905e61ca2fddb43d266c23cdb12ced5019c1a74, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6a6b641a4b9dc447ed8f5facaa02a1d35ec7f179 && git checkout 6a6b641a4b9dc447ed8f5facaa02a1d35ec7f179
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9905e61ca2fddb43d266c23cdb12ced5019c1a74 a3fd6c8aef0f9696f736ad32fa1d516f6c900a1b && git checkout -B drift-repro 9905e61ca2fddb43d266c23cdb12ced5019c1a74 && git merge --no-ff a3fd6c8aef0f9696f736ad32fa1d516f6c900a1b

node scripts/docs-audit/affected-docs.mjs --json 9905e61ca2fddb43d266c23cdb12ced5019c1a74

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Sep 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

PR #20884 (card #20860) — fix(spec): the object-grid page-size migration entry states that a stored page still saves, with an advisory CLI finding.

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a3fd6c8aef0f9696f736ad32fa1d516f6c900a1b
Local-runs: none

Inputs: the card body and its three comments (triage 5912835288, the dispatch claim 5913355847, the os-dev-report 5914065022); the PR body, its file list and the net diff origin/main...refs/review/pr20884 (merge-base 6c96b37be8); the check-runs on the head. Every factual claim below was checked against origin/main at 0803a8b871 with git show / git grep, never the shared working tree. The one merge probe is the sanctioned driverless merge-tree --write-tree from a bare probe clone in scratch that shares the object store and registers no merge driver.

① Derived judgments

The diff. Three files, 39/6. (1) packages/spec/src/migrations/entries/semantic/18.ui-object-grid-page-size-positive-integer-refused.ts: one sentence of acceptanceCriteria replaced; id, surface, replacement, reason untouched. (2) packages/spec/src/migrations/registry.ts: one hunk in the step-18 region. Read independently: with leading whitespace stripped, the registry hunk's +/- lines are byte-identical to the entry hunk's (cmp exit 0, 10 lines each), which is what gen:migration-registry produces from the entry. (3) .changeset/20860-page-size-entry-advisory.md. No Zod schema, no conversion, no export, no matching logic and no test moves.

  • Accept set: unchanged — right. The diff touches string literals only. Nothing an author can write parses differently at head than at base.
  • Public surface: unchanged — right. No export added, removed or renarrowed; the dev's check:api-surface reading ("unchanged") is consistent with the file list.

The corrected sentence, claim by claim, against origin/main:

  • "still saves and loads — properties on a page component is not parsed on the metadata save path" — right. PageComponentSchema.properties is z.record(z.string(), z.unknown()).optional().default({}) (packages/spec/src/ui/page.zod.ts:324). validateComponentProps / ComponentPropsMap have 0 non-test hits under packages/metadata-protocol/src, packages/objectql/src, packages/metadata/src, packages/rest/src and packages/runtime/src; the control hit is packages/lint/src/authoring-rules.ts:113. The route the card names, PUT /api/v1/meta/:type/:name, hands its body to p.saveMetaItem(saveRequest) with writeFace: 'meta-envelope' (packages/rest/src/rest-server.ts:7008, :7018), the call the dev drove on both faces. The rule's own hint says the same thing: "the props bag is not parsed on the storage path either" (packages/lint/src/validate-component-props.ts:325-326).
  • "the component-props gate reports it as an advisory component-props-invalid finding at pagination.pageSize on os validate, os build and os lint" — right. The rule row validateComponentProps (authoring-rules.ts:1021-1030) carries tier: 'advisory', commands: ALL, surfaces: CLI_ONLY. The finding's rule id is COMPONENT_PROPS_INVALID = 'component-props-invalid' (validate-component-props.ts:115) and its message is built as the path relative to the props bag, so it reads pagination.pageSize: Too small … (:322). The three commands each call the rule runner: validate.ts:461 runAuthoringRules('validate', …), compile.ts:494 ('build', …), lint.ts:680 ('lint', …).
  • "a pageSizeOptions entry and the flat pageSize shorthand are reported the same way at their own paths" — right. ObjectGridPropsSchema declares pagination: z.looseObject({ pageSize: GridPageSizeSchema.optional(), pageSizeOptions: z.array(GridPageSizeSchema).optional() }) and the flat pageSize: GridPageSizeSchema.optional(), with GridPageSizeSchema = z.number().int().positive() (packages/spec/src/ui/component.zod.ts:3405, :3612-3618). One schema, one rule, three paths.
  • "the author deletes the key or writes the page size they meant, and os validate then reports no component-props-invalid finding for that node" — right. Both members are optional and the pagination bag is looseObject, so an emptied bag and a positive integer both parse clean.
  • The sentence is text os migrate meta shows an author as verify:; packages/cli/test/migrate-meta-engine-guidance.test.ts pins that every entry's acceptanceCriteria carries no tracker number. The new text carries none — right; that pin runs in Test Core, in progress at this reading.

Scope held. Triage's direction was the sentence and nothing on the save path; the diff changes no judgment. surface, replacement and reason state no save-path refusal at head, so leaving them is right. The sibling 18.object-grid-default-filters-rule-array.ts is unchanged on both origin/main and the head, and its reason already says the key's door "refuses such a value only as the component-props gate's advisory finding … since a re-save through the metadata API is not refused there" — leaving it is right. The exact old phrase survives on the head only in the two ui-list-view-*-padded-refused entries, which are view-arm entries outside this card's surface and not judged here.

Generated copy against a moved main. registry.ts is not a merge=os-regen path (read off .gitattributes), so the queue text-merges it. Since the base, origin/main brought 35 insertions to registry.ts (the step-18 region PR #20868 added) and did not touch either entry file. The old sentence occurs exactly once in origin/main's registry.ts, and the driverless merge-tree --write-tree of 0803a8b871 with a3fd6c8aef wrote tree e9c6c611 with no conflicted path. The byte-exact confirmation is the queue's check:migration-registry on the merge ref — a gate the dev read green at head, and the Lint & Repo Gates run on the merge ref is what settles it.

Governance and shape. No path in the file list is a register row (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, docs/NORTH-STAR.md); Governed Surface Queue Guard is success. Head repo equals base repo. Draft, no auto-merge armed. The PR body's Clause-②: no line is present.

Check-runs on the head, read at 2026-09-30T15:18Z — 33 runs: 20 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke — opt-in), 10 in progress, none failed. Required-set readings: Build Core success; Dogfood Regression Gate success (all three shards); Governed Surface Queue Guard success; Lint & Repo Gates in progress; Temporal Conformance (live PG + MySQL) in progress; Test Core (1/6 through 6/6) in progress; Type Check · source gates and · debt ledger success, · workspace and · consumer gates in progress. Also success: Check Changeset, Check PR Size, Spec property liveness, the claim/branch/single-writer guards. These conclusions are the gate verdicts; this record judges the diff, and enqueue still waits on every required check reaching success.

② Semver level

@objectstack/spec patch — right. The entry text ships inside the package's dist and is printed to authors by os migrate meta, so the diff publishes from a released package and skip-changeset would be wrong; it changes no accept set and no export, so patch is the level and nothing higher is owed.

Clause-②: no — right, and consistent across the dispatch claim, the PR body and the changeset. Not breaking, so no ADR-0087 disposition marker is owed; the dev's check:adr-0087-registration reading ("no declared-breaking changeset") matches. The changeset body carries the migration-entry id, the measured outcome and no model identifier. The commit at head ends with the model-free trailer pair (Claude-Session: plus a plain Co-authored-by: Claude), and the PR body ends with the session-URL footer.

③ Boundary flags

Dev open_questions: none. Dev deviations, each answered:

  1. Stale tree (origin/main six commits ahead, not merged). Answered above: entry files unchanged on main; registry.ts moved in a disjoint region; driverless merge-tree clean; the queue measures the merge ref. Accepted.
  2. HTTP door not booted; save measured through saveMetaItem. The PUT handler's write is that call with writeFace: 'meta-envelope' (rest-server.ts:7008-7018), and the probe ran both faces with the runtime authoring gate live. The sentence claims the metadata save path, which is what was measured. Accepted.
  3. vitest bare -- relaunch, self-corrected. Process hygiene, no bearing on the diff or on the green numbers, which come from the relaunch. Accepted.
  4. Scratchpad file moved. No bearing. Accepted.
  5. Attribution conflict resolved in favour of AGENTS.md. Verified at head: commit trailers are the model-free pair, PR body footer is the session-URL form. Accepted.

Dev out_of_scope_findings — the released @objectstack/spec CHANGELOG entry for 9bb059d ("the object-grid page-component door now refuses a page size of 0 …"). Read at packages/spec/CHANGELOG.md:4763-4769: the "door" it names is the ComponentPropsMap schema, which does refuse 0 (GridPageSizeSchema), and its body attributes the earlier success: true to "this schema". It states no save-path outcome, so it is not class (a) false text; the CHANGELOG is release-owned. The dev's disposition (noted, not filed) is accepted; nothing to escalate.

The card's own ⛔ "not measured" (a save through the route) is discharged by the two measurements in the PR body; triage's ⛔ "not a change to what the save path judges" holds — the diff changes no judgment. The PR body's "owed before enqueue" flag is this record.

Implemented-by: claude/issue-20860-page-size-entry-advisory
Reviewed-by: session_018fxqvRJW12TaHC7DUQ89Y6

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 15:32
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 26437ae Sep 30, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20860-page-size-entry-advisory branch September 30, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants