Skip to content

feat(lint)!: the visibility-predicate family runs on the page save door (ruling B on #23388) - #24509

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-24491-page-visibility-predicates-runtime
Oct 19, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-24491-page-visibility-predicates-runtime

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #24491

Clause-②: no (narrowing)

The visibility-predicate family now runs at the runtime save door for a page as it does for a view. This follows the principle of ruling B on #23388 (ruling of record 6161173492, the maintainer: 「23388 如果页面有问题,不是应该修改了再保存吗?」): a page with a finding is fixed, then saved, so one rule holds at every door that accepts a page. A page component visibleWhen that os validate / os build / os lint refuse is now refused at Studio, PUT /api/v1/meta/page/:name and MCP with the build's own finding: same rule, location, message and hint.

What changes

Premises (each measured on this branch)

P1. Holds. One probe ran before and after, on the page { type: 'page:header', properties: { title }, visibleWhen: 'totallyBogusFn(1)' }.

  • Before (origin/main ce2e08ee3b, lint built from it):
    • runtimeAuthoringRulesFor('page') = ["validatePresetComparands","validateComponentProps","validateComponentTypes","validatePrintPageBlocks"].
    • The door's errors = [].
    • runAuthoringRules('validate') errors = [{"rule":"visibility-predicate-unknown-function","path":"pages[0].regions[0].components[0]","message":"visibility predicate calls unregistered totallyBogusFn (found no matching overload for 'totallyBogusFn(int)'), so view/page elements always render and actions hide for everyone"}].
    • Through the real saveMetaItem (the new protocol block, run against that lint build): AssertionError: the save resolved — the door still accepts the page predicate: expected { success: true, …(4) } to be an instance of Error. Four of the block's seven cases were red: (a), (a) promotion, (b) and (d). The three admitted cases were green.
  • After (this branch):
    • runtimeAuthoringRulesFor('page') = the four above + "validateVisibilityPredicates","validatePredicatePathRefs".
    • The door's errors = [{"rule":"visibility-predicate-unknown-function","path":"pages[0].regions[0].components[0]"}], the build's finding.
    • The protocol block: 7/7 green, inside 152/152 for the file.

P2. Holds for both rules, with one reading the seat should see.

  • validateVisibilityPredicates reads stack.views and stack.pages and no other collection. It needs no object context. A page component takes the rule's default runtime layer, which is the correct layer for a page.
  • validatePredicatePathRefs reads stack.views only (collectionEntries(stack.views, 'views'), with no pages read anywhere in the file). Its oracle is getMetadataTypeSchema(schemaId) of a schema-bound form view, and a page component declares no schemaId. So the object-context question does not arise at the page door: the rule judges nothing on a page, at the door or at the build. That means no false finding and no finding at all.
  • The door's page rule set (all six rules) was run over every page body the platform and the examples ship: 0 errors and 0 advisories, before and after. The direct calls of the two rules found 0, and the build's family errors were 0.

P3. Holds. No shipped page carries a predicate either rule refuses. The pages ran as follows (36 distinct; crm_welcome was read twice, from the composed stack and from its module):

  • examples/app-crm (composed stack): crm_welcome.
  • examples/app-showcase (src/ui/pages/index.ts, the barrel objectstack.config.ts imports; the composed stack did not load in this worktree because @objectstack/connector-mcp was unbuilt): 30 pages, showcase_active_projects … showcase_task_workbench.
  • examples/app-todo and examples/app-multi-package (composed stacks): 0 pages.
  • packages/platform-objects: sys_user_detail, sys_organization_detail.
  • packages/plugins/plugin-approvals: sys_approval_request_detail.
  • packages/cloud-connection: cloud_connection_settings.
  • packages/mcp: connect_agent.
  • packages/qa/downstream-contract: dc_welcome.
  • Templates: packages/create-objectstack ships no page (a git grep for regions / pages: there: 0 hits).

The 8 component visibleWhen sites are sys_user_detail (1, a P envelope reading ctx.user.id), showcase_contact_form (1), showcase_my_work (1), showcase_page_variables (4) and showcase_task_detail (1). All 8 are clean.

Mechanism assumptions, measured

  • G1. Shape held, with one leg falsified. "A wrong path ref is refused" does not hold for the path rule on a page, because validatePredicatePathRefs never reads pages (P2). On a page, a wrong reference is the visibility rule's verdict:

    • a bare identifier is refused (visibility-bare-identifier), and that is pinned;
    • a data.-rooted path draws the advisory visibility-root-mislayered at both doors and no refusal, and that is pinned as "the path half judges nothing on a page".

    Following the ruling's intent, both entries are wired anyway. The family is one wall on the type axis: authoring-rule-wiring.test.ts pins one type set for both entries, and the path rule cannot refuse a valid page because it reads none.

  • G2. Held. No runtime-gate.ts change was needed (above).

  • G3. Held in part. The level is minor + BREAKING, as in PR feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418, with a ! title, Clause-②: no (narrowing) and FROM / TO / Fix lines.

  • G4. Main moved twice (ce2e08ee3b → 7e58e0a27), and I merged it through bash scripts/pm/os-regen-merge.sh. Steps 1–3 left no additional changes. Step 4: the spec closure was rebuilt and pnpm --filter @objectstack/spec check:generated reports all 14 artifacts up to date. This branch edits no generated artifact.

Stored pages

As ruling B read for the two component rules: no stored preview and no migration. A stored page with such a predicate keeps loading and serving, and its next save is refused with the fix (pin (d)). P3 found no shipped page that would be refused.

File surface beyond the claim, and why

  • packages/lint/src/validate-predicate-path-refs.test.ts (one assertion and its comment): its registry pin asserted runtimeTypes equal to ['view'], so it went red with this change (1 failed of 6888). It now asserts ['view', 'page']. None of the 7 open PRs touches this file (their file lists were read in this act). The excluded validate-visibility-predicates.test.ts (PR feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478) is untouched, and it carries no runtimeTypes pin.
  • content/docs/deployment/validating-metadata.mdx (one table row): the runtime-publish cell lists one superscript per declared type, and ✓ᵛ became false. It now reads ✓ᵛᵖ, and the row title names page components, with schema path refs scoped to views[] only (patch round 1, contract review 6264267243 ③ 1: validatePredicatePathRefs walks stack.views only, so no door judges schema path refs on a page component).

Verification (head 079ebf8b5)

  • pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 146 files, 6893 tests passed. typecheck (with check:test-typecheck) passed.
  • pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2: 247 passed, 3 skipped (250 files); 20699 tests passed, 19 skipped. This ran on the pre-merge head 58b7f78bc; the merge brought no metadata-protocol change. On 079ebf8b5, src/protocol.runtime-authoring-gate.test.ts passed 152/152, and typecheck passed.
  • Ablation, after the fix was committed:
    • Mutation: validateVisibilityPredicates declares ['view', 'ablated_24491_no_page'] instead of ['view', 'page'], through scripts/ablation-replace.mjs (anchor 1 → 0, blob 2572e080ca6d → 76e2558ad8df). Lint was rebuilt, and ablation-dist-preflight found the marker in 4 built files.
    • Mutated: the lint pin had 14 red and 1 green (the view control). The protocol block had 4 red ((a), (a) promotion, (b), (d)) and 3 green (the admitted pages).
    • Restore leg: git checkout HEAD, blob equal to the HEAD blob, git diff HEAD empty, rebuild, --absent (absent from all 20 built files, tree clean). Lint pin 15/15 and protocol block 7/7 green.
  • Consumer suites were not rerun. A git grep for visibleWhen across every *.test.ts that saves a page names no test outside lint and metadata-protocol that saves one through the door.

Gates (head 079ebf8b5; patch round 1 at 6d416dfc3 changed one docs line, re-ran the 40 families that docs path reaches, all exit 0, and --ran reconciled 93 derived: 92 run, 1 NOT-MEASURED, check:dual-build-cjs-loads)

  • Local, derived. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths (6 changed paths vs merge base 7e58e0a27), derived 93 families. Each ran verbatim, with its exit code recorded before any pipe. The --ran reconciliation answered "93 derived famil(ies) accounted for — 92 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", exit 0.
    • 92 exited 0. They include check-adr-0087-registration --base origin/main, check-empty-changeset --base origin/main, check-changeset-no-major --base origin/main, check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage and check:type-check-debt.
    • check:skill-examples and check:lean-entry-closure first exited 3, because the client, client-react and objectql dist were unbuilt. I built those three packages and re-ran both: exit 0.
    • NOT MEASURED: check:dual-build-cjs-loads. It exited 3 (PREREQUISITE NOT MET) because it needs every package built, and 72 have no dist here. Left to CI.
  • pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts are up to date, after the merge and a spec rebuild.
  • Not run locally, with the reason: test:repo (no repo-project test changed, and the derivation does not name it) and pnpm lint (a repo-wide scan, which CI owns).
  • CI check-runs: this body is written once, at create. The seat reads the PR's check-runs.

Acceptance notes


Generated by Claude Code

…e verdict (#24491)

Red before the registry change: the page door does not run the family yet.

Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK
Co-authored-by: Claude <noreply@anthropic.com>
…or (ruling B on #23388)

validateVisibilityPredicates and validatePredicatePathRefs declare
runtimeTypes ['view', 'page'], so a page component visibleWhen the build
refuses is refused at Studio, REST /meta and MCP with the build's finding.

Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 19, 2026
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 2 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/validating-metadata.mdx (via AUTHORING_RULES (symbol, a top-level const object), runtimeTypes (symbol, a field of const object AUTHORING_RULES))
What this run could not see
  • 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 — 4 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 3ae0b777d4b94187abedc74f3ae316ff8e063e35 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 185a71d91c9db652a17a19dd1ad453b260b326d8 — the merge of head 6d416dfc398aa693a11f807b9577fb30e55053bb into base 3ae0b777d4b94187abedc74f3ae316ff8e063e35, 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 185a71d91c9db652a17a19dd1ad453b260b326d8 && git checkout 185a71d91c9db652a17a19dd1ad453b260b326d8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3ae0b777d4b94187abedc74f3ae316ff8e063e35 6d416dfc398aa693a11f807b9577fb30e55053bb && git checkout -B drift-repro 3ae0b777d4b94187abedc74f3ae316ff8e063e35 && git merge --no-ff 6d416dfc398aa693a11f807b9577fb30e55053bb

node scripts/docs-audit/affected-docs.mjs --json 3ae0b777d4b94187abedc74f3ae316ff8e063e35

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 3ae0b777d4b94187abedc74f3ae316ff8e063e35 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 (seat check) · PR #24509 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T09:22Z

ACCEPT at head 079ebf8b5302c0bb1d47be394c98c7c05213c46d (claim 6262575675, os-dev subagent, report on #24491). A contract review at CONTRACT_REVIEW_TIER is owed: the narrowing face, because a metadata save door accepts less. It runs on this head, isolated from the seat. needs:contract-review is on the PR until its record is adopted.

item reading
Shape Draft, base main, assignee zhuangjianguo. Line 1 is Fixes #24491. Clause-②: no (narrowing) starts a line. 6 files, +423 / −7. Not governed. The changeset is minor on @objectstack/lint with a ! title, FROM / TO / Fix lines, and an ADR-0087 not-required (no-migration-prescription) marker.
The change validateVisibilityPredicates and validatePredicatePathRefs declare runtimeTypes: ['view', 'page'] (packages/lint/src/authoring-rules.ts), with the page-crossing block above them. runtime-gate.ts and protocol.ts are unchanged (G2 measured: page already maps to pages).
Premises P1 true: before, the page rule set had 4 rules and the door returned no error for totallyBogusFn(1) while os validate refused it. After, the door returns the build's visibility-predicate-unknown-function at pages[0].regions[0].components[0]. P2 true: neither rule gives a false finding on a page. validatePredicatePathRefs walks stack.views only, so it judges nothing on a page at either door. P3 true: 36 shipped pages carry 8 component visibleWhen sites, with 0 findings and 0 door errors, before and after.
Pins A new lint pin, runtime-gate.page-visibility-predicate-writes.test.ts (15 cases), and a 7-case block in protocol.runtime-authoring-gate.test.ts through the real saveMetaItem, publish and package-draft publish. Before the registry change: 14 of 15 red and 4 of 7 red. The ablation turns the same cases red. A stored page loads, and its next save is refused (case (d)).
G1, one leg false "A wrong path ref is refused" cannot hold on a page, because the path rule reads views only. The pin covers the bare-identifier refusal instead, and shows the path half inert. Both entries stay wired because authoring-rule-wiring.test.ts pins one type set for the family. This keeps door = build: the build does not judge a page's path refs either.
CI On this head at this act: 38 runs, 17 success, 5 skipped, 16 in progress, 0 failure. Landing waits for green.
Accepted deviations (1) Two files outside the claim's surface, each made false by the change: validate-predicate-path-refs.test.ts pinned ['view'], and the content/docs/deployment/validating-metadata.mdx table cell moves from ✓ᵛ to ✓ᵛᵖ. (2)–(6) are measurement notes with no contract effect.
For the review The docs row now reads "views[] and page-component conditional-visibility predicates — … schema path refs". On a page component, schema path refs are not judged at any door. Judge whether the row over-claims (Prime Directive #10) and whether it needs a scoping word.
Landing After a contract-review PASS and a green head: pr_ready then automerge_enable. Fixes #24491 closes the card.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 079ebf8b5302c0bb1d47be394c98c7c05213c46d
Local-runs: none
Scope: packages/lint/src/authoring-rules.ts, .changeset/24491-page-save-door-visibility-predicates.md + packages/lint/src/runtime-gate.ts (runtimeAuthoringRulesFor, TYPE_TO_STACK_KEY, CONTEXT_STACK_KEY_ORDER, nameKeyFindingPath, runRules; unchanged), packages/lint/src/validate-visibility-predicates.ts and packages/lint/src/page-walk.ts (the page walk; unchanged), packages/lint/src/validate-predicate-path-refs.ts (unchanged), packages/lint/src/authoring-rule-wiring.test.ts (the family pin; unchanged), packages/metadata-protocol/src/protocol.ts (saveMetaItem 23558 and 23707, promoteDraftForPublish 25416 under publishMetaItem and publishPackageDrafts; 0 lines changed) and packages/metadata-protocol/src/runtime-authoring-gate.ts (evaluateRuntimeAuthoringGate; unchanged), packages/lint/src/runtime-gate.page-visibility-predicate-writes.test.ts (new), packages/lint/src/validate-predicate-path-refs.test.ts, packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts (test block only), content/docs/deployment/validating-metadata.mdx, packages/spec/src/ui/page.zod.ts (the visibility tombstone) and packages/spec/src/conversions/registry.ts (every page and element conversion), the 8 shipped visibleWhen sites under examples/app-showcase/src/ui/pages and packages/platform-objects/src/pages, .changeset/pre.json, .changeset/pre/23388-page-component-rules-runtime-door.md and the four .changeset/pre/22032-object-save-door-*.md, scripts/check-adr-0087-registration.mjs, scripts/check-empty-changeset.mjs, scripts/check-changeset-no-major.mjs and .github/workflows/pr-automation.yml (the Check Changeset job).

Narrowing face: a metadata save door accepts less. Inputs were the card's body and every comment (6262435305, 6262575675, 6262629357, 6263909370), the PR body, its six-file list and the net diff against merge base 7e58e0a27, and the check-runs on the head. The diff is read against the merge base; the PR body's premises P1 to P3 and its ablation are the dev's readings and are checked here against the code, not adopted. Ruling of record 6161173492 on #23388 (letter B) and PR #23418 were read as the precedent.

① Derived judgments

  1. Public surface — right. The diff moves two registry rows and nothing else: validateVisibilityPredicates and validatePredicatePathRefs in AUTHORING_RULES go from runtimeTypes: ['view'] to ['view', 'page']. No export, signature, schema or wire shape moves. runtimeAuthoringRulesFor('page') now returns six rules in registry order — validatePresetComparands, validateComponentProps, validateComponentTypes, validatePrintPageBlocks, validateVisibilityPredicates, validatePredicatePathRefs — which is exactly the list the new lint pin asserts (read off the registry at the head). runtimeAuthoringRulesFor('view') is unchanged.

  2. Which doors narrow — right, and the changeset's FROM line names them exactly. evaluateRuntimeAuthoringGate returns { error: null } for any state other than 'active', so a mode: 'draft' save is not judged. The three gated writes are saveMetaItem in publish mode (protocol.ts 23707), the draft promotion publishMetaItem and the package publish publishPackageDrafts, both of which reach the gate through promoteDraftForPublish with state: 'active' on the draft body (25416). A refusal is 422 INVALID_METADATA with issues[] and rulesRun; OS_ALLOW_UNLINTED_METADATA_WRITES=1 turns it into a console.warn and a write. Every sentence of the changeset's FROM, TO, Fix and Unchanged blocks matches this code.

  3. Door = build, finding for finding — holds, at every location the build walks. The door calls the same registry closure validateVisibilityPredicates(stack) on a snapshot { pages: [item] } plus the context collections; the build calls it on the whole stack. The page half of the rule is the shared walkPageComponents (page-walk.ts 124): regions[].components[], slots.slotName as one component or an array, and the spec-derived slot positions inside properties (children, page:card footer, page:tabs and page:accordion items[].children[]), with source-authored html/react/jsx pages skipped at both doors. The layer is the 'runtime' default at both doors, since both call the same run closure with no opts. where is page "name" at both. The path is positional at both: pages is not in CONTEXT_STACK_KEY_ORDER (objects, permissions, books, datasets), so nameKeyFindingPath leaves pages[0].regions[0].components[0] as the rule spelled it; the build's pages[N] differs only by the written item's slot, as for the [Decision] Pages saved through Studio, REST or MCP skip the component-props rule that os validate refuses: cross it to the runtime door, and on what evidence #23388 rules. visibility-root-mislayered is warning at both doors and rides the save response's advisories, which SaveMetaItemResponseSchema already carries.

  4. The runtime input tier — no path hands the rule a page body it cannot read. The door hands the body as persisted, without normalizeStackInput. That is safe for this family on a page for three facts read in the tree: (a) saveMetaItem parses the body against the spec (schema.safeParse, protocol.ts 23558) before the gate runs, and page.zod.ts 777 declares visibility: retiredKey(...), so the retired page-side spelling is a 422 before the rule sees it; (b) every page and element conversion in conversions/registry.ts is retiredFromLoadPath: true (25 ids checked, page-component-visibility-to-visibleWhen, page-card-body-to-children, page-tabs-type-to-tab-style and page-component-filter-record-to-rule-array among them), so no live D2 fold moves a component or its predicate between the raw tier the door reads and the normalized tier the build reads; (c) the rule reads the one key visibleWhen as a string or a { dialect, source } envelope, and the value survives the parse intact. The registry comment's new sentence about the page-side visibility tombstone is accurate.

  5. No false refusal — holds on the sites the tree ships. The 8 component visibleWhen sites the dev counted are the 8 a git grep at the head finds: contact-form.page.ts 96 (page.inquiryEmail), my-work.page.ts 98 (current_user.email), page-variables.page.ts 71, 81, 86, 95 (page.selectedProjectId), task-detail.page.ts 95 (has(record.status) && record.status), and sys-user.page.ts 95 (a P envelope reading record.id, record.email_verified and ctx.user.id). Every root is bound: current_user and page through VIEW_PAGE_EXTRA_ROOTS, record and ctx through SCOPE_ROOTS in packages/formula/src/cel-engine.ts; every call is has(). packages/create-objectstack ships no page. No shipped page, template or platform-package page is refused by this door. The 36-page count and the 0-finding door run are the dev's measurement and were not re-run here; the 8 predicate sites and their roots were read, which is what a refusal would turn on.

  6. The inert sibling — acceptable as parity with the build; the honesty cost lands on the docs row (③ item 1). validatePredicatePathRefs reads collectionEntries(stack.views, 'views') only (validate-predicate-path-refs.ts 816) and no pages anywhere, so on a page write it is dispatched and emits nothing. Declaring page is not a Prime Directive chore: version packages #10 violation on its own: runtimeTypes is the gate's dispatch filter, the rule does run (and rulesRun on the wire says so truthfully), the build dispatches it over the whole stack with the same empty result, no machine-readable surface derives a "page path refs are judged" claim from runtimeTypes (its non-test consumers are the gate, the reference-integrity suite and an unrelated local in protocol.ts), and the registry comment, the path-refs pin and the new lint pin each say in words that it judges nothing on a page. The wiring pin forces one type set on the family. The narrower fix, if the seat ever wants one, is to keep ['view'] on the path rule and make the wiring pin's type-axis assertion compare type sets restricted to the collections each rule reads; it changes no behaviour and is not required here.

  7. Stored pages and drafts — right. The boot and read paths run no authoring rule, so a stored page with such a predicate loads, registers and is served as stored; its next publish-mode save is refused; the stored row is untouched by the refusal; a draft save of the same body succeeds and its promotion is refused. Pin (d) and pin (a)-promotion assert exactly this through the real saveMetaItem, publishMetaItem, loadMetaFromDb and getMetaItem. This is the shape ruling B bound for the two component rules (items 1, 4b and 5 of 6161173492: no stored preview, no migration, stored pages keep serving, the next save is refused with the fix) and the shape PR feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418 landed; the card left the stored-page question to the lane under ruling B, and the lane answered it the same way. The maintainer's pull-forward (6262575675, 「24491 插队,现在可以派」) ships it as a refusal in 18.0, which the triage's "18.0 if claimed today" lean anticipated.

  8. Pins — right, and non-vacuous. The lint pin covers five refused bodies (unknown function bare, in the envelope and as a method call; a bare identifier; a syntax fault), six admitted bodies, whole-finding parity against runAuthoringRules('build', ...) with a non-vacuity assertion on the build side, the path half on a page (data. draws the advisory and no refusal at both doors), and the view control including a live predicate-path-unresolved on a schema-bound form. The protocol block covers (a), (a)-promotion, (b) key-by-key parity on rule, where, path, message, hint, (c) three admitted pages, (d) the stored page. The validate-predicate-path-refs.test.ts registry pin moves from ['view'] to ['view', 'page'] because the change made it false; the excluded validate-visibility-predicates.test.ts carries no runtimeTypes pin, so PR feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478's file is untouched.

  9. The cross-lane rider — confirmed test-only. git diff --stat of packages/metadata-protocol/src/protocol.ts between the merge base and the head is empty; the only metadata-protocol path in the diff is the test file. The claim records the rider as declared to domain:engine on [PM seat] domain:engine — 🟢 os-project-manager #6367.

  10. Check-runs on the head, state as read in this act (ids from the check-runs listing): Lint & Repo Gates 117984585303 success; Build Core 117982755522 success; Governed Surface Queue Guard 117982567115 success; Check Changeset 117982567559 success and 117984485446 success; Lint · ESLint 117982568662 success; Lint · CI, release & engine gates, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, Lint · scoped · slot lookup & corpus, Lint · scoped · query-options erasure, Lint · docs & code guards, Lint · repo & tooling gates success; Type Check · source gates 117982568620 success; Type Check · debt ledger 117982568637 success; Type Check · workspace 117982568444 success; Type Check · consumer gates 117982568612 success; the required aggregate TypeScript Type Check 117987833039 success; Build Docs, Check Documentation Links, Flag docs affected by code changes, Spec property liveness, Dogfood Regression Gate (aggregate), Check PR Size, Auto Label, and the four single-writer, claim and part-of guards success; Test Core (1/6) 117982755581, (5/6) 117982755583 and (6/6) 117982755557 success; Test Core (2/6) 117982755624, (3/6) 117982755715 and (4/6) 117982755553 in_progress on the second read of this act; the required aggregate Test Core not yet listed; Packed-tarball smoke (opt-in), the three dogfood shards, Console Pin Gate and Temporal Conformance skipped. Nothing on the head had concluded red on either read; the three in-progress Test Core shards are what still executes the new pins, and the lane seat reads them before arming.

② Semver level

③ Boundary flags

  1. FAIL — the docs row over-claims, and needs one scoping phrase. content/docs/deployment/validating-metadata.mdx now titles the row "views[] and page-component conditional-visibility predicates — CEL syntax, parse budget, bare identifiers, binding-root layer, schema path refs". The runtime cell ✓ᵛᵖ is correct by the legend's own definition (one superscript per declared type; runtimeTypes is the authority). The title is not: on a page component, schema path refs are judged at no door, the build's three columns included, because validatePredicatePathRefs walks stack.views only (① item 6). Before this PR the row named views[] alone and was true in every column; after it, an author asking "are schema path refs checked on my page" reads four ticks for a check that never runs. Prime Directive chore: version packages #10's corollary (keep the claim as narrow as the enforcement) applies to the one phrase. The fix is the title's fifth clause, exactly: replace "binding-root layer, schema path refs (ADR-0089 D3b, Publish-time validation of predicate path references — a spec-delivered predicate naming a nonexistent path should fail at authoring, not evaluate to a guess at render (#6936 companion) #7010; page components at the save door lint: the visibility-predicate rules run at the runtime save door for a view but not for a page, so a page visibleWhen calling an unknown function is saved through Studio, REST or MCP and refused only by os validate (ruling B on #23388's principle) #24491)" with "binding-root layer; schema path refs on views[] only (ADR-0089 D3b, Publish-time validation of predicate path references — a spec-delivered predicate naming a nonexistent path should fail at authoring, not evaluate to a guess at render (#6936 companion) #7010; page components at the save door lint: the visibility-predicate rules run at the runtime save door for a view but not for a page, so a page visibleWhen calling an unknown function is saved through Studio, REST or MCP and refused only by os validate (ruling B on #23388's principle) #24491)". The cells stay as they are. A new head with that edit, and this record re-rendered on it, is what the lane needs.

  2. Dev deviation 1 (two files outside the claim's surface) — accepted. The path-refs registry pin and the docs cell each became false under the diff, and a text a change makes false is fixed in the same change; no open PR touches the test file. The docs row's title wording is item 1 above.

  3. Dev deviation 3 (the full metadata-protocol suite ran on the pre-merge head 58b7f78bc) — accepted, and the premise checked. git diff --stat ce2e08ee3b 7e58e0a27 over packages/lint and packages/metadata-protocol shows three packages/lint files (validate-expressions.ts and two tests) and no metadata-protocol file, so the merge brought nothing that package's suite would see; the lint suite was re-run on the final head.

  4. Dev deviations 2, 4, 5 and 6 (the bare -- spelling on the first lint run, later re-run with exec vitest run; test:repo, pnpm lint and the consumer suites left to CI; showcase pages read from the barrel the config imports; the dev's own gate runner stopped by recorded PID when main moved) — accepted. Lint · ESLint is green on the head; Test Core is in progress and the lane seat reads it before arming.

  5. G1's falsified leg ("a wrong path ref is refused" cannot hold for the path rule on a page) — answered in ① item 6: the path half is inert on a page by construction and the pin names it as measured; the honest claim lives in the registry comment and the pins, and the docs title must match it (item 1).

  6. G2 (no gate change) and G4 (the main merge via os-regen-merge.sh, no generated artifact moves) — right. TYPE_TO_STACK_KEY already maps page to pages, runtime-gate.ts and protocol.ts are unchanged, and the diff touches no generated artifact.

  7. Acceptance notes — the wiring test's "both are wired, for view" comment is left as a historical record and its view list is unchanged: fine. "If page components ever gain a schema-bound data source the path rule lights with no registry edit": true as read, and the reason the parity declaration is tolerable.

  8. Open questions and out-of-scope findings — the report lists none; none found here beyond item 1, which is a scope-internal fix, not a filing.

Implemented-by: claude/issue-24491-page-visibility-predicates-runtime
Reviewed-by: session_019oFY6Hi35J7atvgVFnmoaK

VERDICT: FAIL

One item: the docs row title in content/docs/deployment/validating-metadata.mdx claims schema path refs on page components at every door; scope the clause to views[] as written in ③ item 1 and push; everything else in the diff holds as judged above and needs no change.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 (seat check) · PR #24509 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T09:41Z

REWORK (patch round 1, same claim 6262575675, same os-dev subagent) at head 079ebf8b5302c0bb1d47be394c98c7c05213c46d, from contract review 6264267243 (FAIL, one item). Everything else in that record and in the seat check 6263951253 holds.


Generated by Claude Code

…s to views[] (#24491)

validatePredicatePathRefs walks stack.views only, so no door judges schema
path refs on a page component.

Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6d416dfc398aa693a11f807b9577fb30e55053bb
Local-runs: none
Scope: packages/lint/src/authoring-rules.ts, .changeset/24491-page-save-door-visibility-predicates.md, packages/lint/src/runtime-gate.page-visibility-predicate-writes.test.ts, packages/lint/src/validate-predicate-path-refs.test.ts, packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts, content/docs/deployment/validating-metadata.mdx + packages/lint/src/validate-predicate-path-refs.ts (validatePredicatePathRefs, the one collection walk; unchanged), packages/lint/src/validate-visibility-predicates.ts (the function-existence arm; unchanged), and on current main at 6b42b6bc8 the files round 1 cited that moved there since the merge base: packages/formula/src/cel-engine.ts, packages/formula/src/stdlib.ts, packages/formula/src/types.ts, packages/formula/src/index.ts, packages/spec/src/ui/page.zod.ts, packages/lint/src/validate-visibility-predicates.test.ts; plus .changeset/pre.json, the five cited .changeset/pre/ changesets and the two shipped page trees, read on main for drift.

Round 2 of contract review 6264267243 (FAIL, one item at ③ 1), on the narrowing face, after REWORK 6264283922 and the dev's patch-round report 6264586099 on the card. Inputs were those three records, the PR body as it now reads, the PR's six-file list, git diff between round 1's head 079ebf8b5302c0bb1d47be394c98c7c05213c46d and this head, the per-path blob ids at both heads, git diff from the merge base 7e58e0a27998194640c145344b7519f1ccc9fad4 to origin/main at 6b42b6bc8a11b58cfaa5054e9edbb86b1898f2e7, and the check-runs on the head, read twice. Nothing was built, run or re-run locally; the shape came from record-recognisers.mjs --template.

① Derived judgments

  1. Round 1's one FAIL item is fixed exactly, and the fix is true of the code. At the head, content/docs/deployment/validating-metadata.mdx line 487 reads, as one line, "| views[] and page-component conditional-visibility predicates — CEL syntax, parse budget, bare identifiers, binding-root layer; schema path refs on views[] only (ADR-0089 D3b, Publish-time validation of predicate path references — a spec-delivered predicate naming a nonexistent path should fail at authoring, not evaluate to a guess at render (#6936 companion) #7010; page components at the save door lint: the visibility-predicate rules run at the runtime save door for a view but not for a page, so a page visibleWhen calling an unknown function is saved through Studio, REST or MCP and refused only by os validate (ruling B on #23388's principle) #24491) | ✓ | ✓ | ✓ | ✓ᵛᵖ |". Exact-line count 1; the old phrase "binding-root layer, schema path refs (ADR-0089 D3b" count 0; the four cells are the ones round 1 accepted. The clause is true: validatePredicatePathRefs (packages/lint/src/validate-predicate-path-refs.ts 808) has one collection walk, collectionEntries(stack.views, 'views') at 816, and the file names stack.pages, walkPageComponents or a pages token nowhere, so on a page component schema path refs are judged at no door, and the title now says so. Against origin/main the doc differs by that one row and nothing else (+1/-1), the row having gone from naming views[] alone to this title.

  2. Nothing else moved, so round 1's ① items 1 to 9 and its ② carry over as written. git diff 079ebf8b5302c0bb1d47be394c98c7c05213c46d 6d416dfc398aa693a11f807b9577fb30e55053bb is one file, one hunk, +1/-1, the line in item 1; the branch gained one commit, 6d416dfc3 docs(validating-metadata): scope the visibility row's schema path refs to views[] (#24491), whose parent is round 1's head. Per-path blob ids at the two heads: .changeset/24491-page-save-door-visibility-predicates.md ec703bd92 at both; packages/lint/src/authoring-rules.ts 2572e080c at both (rows 1623 and 1664 still runtimeTypes: ['view', 'page'] on validateVisibilityPredicates and validatePredicatePathRefs); packages/lint/src/runtime-gate.page-visibility-predicate-writes.test.ts c365bfc9b at both; packages/lint/src/validate-predicate-path-refs.test.ts 1866c338b at both; packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts 4991e862e at both; content/docs/deployment/validating-metadata.mdx 4e3f636be then e238ec869, which is the blob the PR files listing reports for the head. Five of six files byte-identical, the sixth changed by the one line: the public surface, the three gated doors, the door-equals-build parity, the raw-tier safety, the no-false-refusal reading on the 8 shipped sites, the inert path sibling, the stored-page shape, the pins and the test-only rider are the readings of record 6264267243 and are not redone here.

  3. Dev deviation 1 (main not merged this round) — accepted, and the drift measured; it changes no round-1 judgment. The merge base is still 7e58e0a27; origin/main moved six commits to 6b42b6bc8 (225 paths): ca426cb65 (feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478, current_user.hasCapability(name)), 8eac32199 (spec extension refusals for hooks and listViews), f72ce934f (service-automation runAs: 'user'), 666283aea (plugin-approvals chip), 820fd5aef (chore: version packages (next), chore: version packages (next) #24477) and 6b42b6bc8 (ci(publish-smoke): a cancelled smoke reports pending "nothing measured", never failure #24503, publish-smoke). Over the PR's six paths git diff 7e58e0a27 origin/main is empty. Over the files round 1 read as unchanged, two moved, both in feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478: packages/formula/src/cel-engine.ts (a doc comment, and systemPermissions: ctx.systemPermissions handed to buildEnv at 1809) and packages/spec/src/ui/page.zod.ts (the .describe() text of visibleWhen at 769 only; the visibility tombstone and its comment on the following lines are untouched, so ① 4a holds). feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478 also registers hasCapability in packages/formula/src/stdlib.ts as a receiver-form function (dyn.hasCapability(dyn): bool, registered unconditionally beside can) and adds EvalSystemPermissions and EvalContext.systemPermissions in types.ts. What that does to this PR's door: the lint rule's existence arm is firstUnknownFunctionCall(source) from @objectstack/formula (validate-visibility-predicates.ts 1127 to 1128), keyed on the environment and not on a list in lint, so the door now admits one more function name on a page and refuses nothing it admitted before; main's own new test at validate-visibility-predicates.test.ts 1280 measures exactly that on a page component node (current_user.hasCapability('manage_users') admitted, hasCapabilities refused as visibility-predicate-unknown-function at error), with no runtimeTypes pin in that file. None of this PR's pinned refused bodies is the new name: the lint pin refuses totallyBogusFn(1) bare and in the envelope and current_user.canApprove(record, 'approve') (lines 87 to 89), the protocol block refuses totallyBogusFn(1) (bogusPage), so no pin flips on the merge ref. The two shipped page trees (examples/app-showcase/src/ui/pages, packages/platform-objects/src/pages) have zero diff from the merge base to main, so the 8-site no-false-refusal reading stands. packages/lint/src/validate-predicate-path-refs.test.ts did not move on main (its line 499 there still pins ['view'], which this PR's blob rewrites to ['view', 'page']), and runtime-gate.ts, page-walk.ts, authoring-rule-wiring.test.ts, protocol.ts, runtime-authoring-gate.ts and conversions/registry.ts are unchanged on main. The rest of the 225 paths is the version cut and three unrelated fixes (object-extension refusal prose in object.zod.ts and a new spec test among them), outside every reading above. On the rule itself: AGENTS.md Multi-agent discipline §10 allows a merge on an open PR only for a GitHub-reported conflict, a main-caused CI red or a queue removal; the PR reads mergeable: true, no run on either head concluded red, and it is not queued; the queue rebuilds the entry onto current main (§7). The dev stated the conflict with the dispatch's step instead of resolving it silently, which is the right order.

  4. Dev deviations 2 and 3 — accepted. The STALE local dispatch-gates derivation is from .github/workflows/publish-smoke.yml moving on main (ci(publish-smoke): a cancelled smoke reports pending "nothing measured", never failure #24503); CI's reading on the merge ref is Lint · PM dispatch-gates 117998310277 success. The worktree recreated at 079ebf8b5 is the head's parent, as the commit graph shows.

  5. The PR body's two edits are true. Edit 1 is stored byte-exact (new text count 1, old text count 0): the docs bullet now says the row title names page components "with schema path refs scoped to views[] only", citing review 6264267243 ③ 1 and the fact that validatePredicatePathRefs walks stack.views only; both halves are what item 1 reads in the code and the doc. Edit 2 is stored byte-exact (count 1, old heading count 0): the ## Gates heading now names head 079ebf8b5 plus patch round 1 at 6d416dfc3 changing one docs line, 40 families re-run at exit 0 and --ran reconciling 93 derived as 92 run and 1 NOT-MEASURED (check:dual-build-cjs-loads). The one-docs-line claim is item 2's diff; the family counts and exit codes are the dev's own readings, consistent with report 6264586099, and were not re-run here. The body still carries Fixes #24491 on line 1, Clause-②: no (narrowing) on line 3, and one session-URL footer. The PR is a same-repository branch, draft, with no auto-merge armed and the needs:contract-review label on.

  6. Check-runs on the head, state as read in this act (ids from the listing; first read 2026-10-19T10:02Z 45 runs, second read 2026-10-19T10:06Z 46 runs). Success on the second read: the required Lint & Repo Gates 118001491400; the required Build Core 117998449607; the required Governed Surface Queue Guard 117998310725; Check Changeset 117998308763 and 117999455492; Lint · ESLint 117998310530; Lint · CI, release & engine gates 117998310265; Lint · PM dispatch-gates 117998310277; Lint · docs & code guards 117998310539; Lint · repo & tooling gates 117998310270; Lint · scoped · tooling self-tests 117998310185; Lint · scoped · slot lookup & corpus 117998310369; Lint · scoped · query-options erasure 117998310398; Type Check · source gates 117998310212; Type Check · debt ledger 117998310429; Type Check · consumer gates 117998310211; Build Docs 117998449640; Check Documentation Links 117998309294; Flag docs affected by code changes 117998309330; Spec property liveness 117998309971; Dogfood Regression Gate (aggregate) 117998450496; Check PR Size 117998309141; Auto Label 117998454214; filter 117998310065; and the four claim, single-writer and part-of guards, two runs each (117998309505, 117999455017, 117998309945, 117999455583, 117998309519, 117999455988, 117998309625, 117999455119). Skipped: Console Pin Gate 117998452417, the per-shard Dogfood Regression Gate run 117998451386, Dogfood Verify CLI 117998451728, Packed-tarball smoke (opt-in) 117998310714, Temporal Conformance (live PG + MySQL) 117998451757, and the second Auto Label 117999460203 and Check PR Size 117999457316 runs. In progress on the second read: Test Core (1/6) 117998449787, (2/6) 117998449629, (3/6) 117998449698, (4/6) 117998449599, (5/6) 117998449676, (6/6) 117998449773, and Type Check · workspace 117998309822; the required aggregates TypeScript Type Check and Test Core not yet listed. No run on the head concluded failure, cancelled, timed_out or action_required on either read. main's tip 6b42b6bc8 was committed at 2026-10-19T09:04Z, before the push, so these runs build the merge ref against current main, item 3's reading included. Green means conclusion: success on the gate-carrying jobs (AGENTS.md §7), so the lane seat reads Test Core and TypeScript Type Check concluded before arming; nothing in this round's diff is executed by them beyond the docs line, and the pins they execute are round 1's, unchanged.

② Semver level

  • Carried over from 6264267243 ②: @objectstack/lint minor with the BREAKING banner, ! title and Clause-②: no (narrowing) on the PR body and in the changeset, ADR-0087 disposition not-required (no-migration-prescription); no other package publishes a change (the metadata-protocol path is a test file). The changeset blob is identical to round 1's.
  • Re-read against current main for the version cut that landed there (chore: version packages (next) #24477): .changeset/pre.json is blob d20efa9e7 at the merge base, on main and at the head (mode: "pre", tag: "next"), so the launch-window level check-changeset-no-major.mjs takes is unchanged; the cut moved the root changesets of its own generation into .changeset/pre/ and the five cited there (23388-page-component-rules-runtime-door.md, the four 22032-object-save-door-*.md) are blob-identical; the PR's changeset is absent on main, so the merge adds one new root changeset and collides with nothing; check-changeset-no-major.mjs, check-empty-changeset.mjs, check-adr-0087-registration.mjs and .github/workflows/pr-automation.yml did not move. Both Check Changeset runs on the head concluded success.

③ Boundary flags

  1. Round 1 ③ 1 — closed. The title's fifth clause is the prescribed text, verbatim, and the cells are unchanged (① 1). Prime Directive chore: version packages #10's corollary is satisfied for the row: the claim is now as narrow as the enforcement.

  2. Round 1 ③ 2 to 8 — carry over. The two files outside the claim's surface, the metadata-protocol suite's pre-merge run, the four tooling deviations, G1's falsified leg, G2, G4 and the acceptance notes were judged on blobs that have not changed; nothing in this round reopens them.

  3. Deviations 1 to 3 of the patch round — accepted in ① 3 and ① 4; the main-side drift was measured and changes no judgment.

  4. Open questions and out-of-scope findings — the patch-round report lists none; none found here.

Implemented-by: claude/issue-24491-page-visibility-predicates-runtime
Reviewed-by: session_019oFY6Hi35J7atvgVFnmoaK

VERDICT: PASS

Round 1's one item is fixed exactly and truthfully; the rest of the diff is byte-identical to the head round 1 judged; main's movement since the merge base touches none of the PR's paths and widens only what the door admits; nothing on the head has concluded red. The lane seat lands it once Test Core and TypeScript Type Check conclude success.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review adopted · domain:spec seat 2 · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T10:29Z

  • Adopted: contract review round 2, 6264782254.
    • Served-tier: CONTRACT_REVIEW_TIER, the narrowing face.
    • Head-sha 6d416dfc398aa693a11f807b9577fb30e55053bb is the current head.
    • PASS, with no FAIL item.
    • Round 1's one FAIL item (6264267243 ③ 1: the docs row over-claimed schema path refs on page components) is closed by the one-line scoping, verbatim from the review.
    • The other five files are blob-identical to round 1's head, so round 1's judgments carry over.
    • needs:contract-review is removed in this act.
  • Not merging origin/main: the dev's deviation, accepted by the review. The PR is mergeable_state: clean. main's movement since the merge base touches none of the PR's six paths. feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478's hasCapability registration widens what the existence arm admits and flips none of the pinned refusals.
  • Pre-landing on this head:
    • 48 check-runs: 41 success, 7 skipped. The required contexts are green.
    • check-expected-skips: OK.
    • check-governed-merges: not governed, +423/−7.
  • Landing: pr_ready then automerge_enable, in this act. Fixes #24491 closes the card.

Generated by Claude Code

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/m tests tooling

Projects

None yet

2 participants