Skip to content

#19188 split: 47 top-level zod-only keys are scalar controls needing one form row each #19331

Description

@os-litant

Path: P1 | 那条路第 1 步「写元数据」 | 47 个已声明的活键在表单上不存在

The bucket

47 keys of #19188's 274, bucket B8: object-rooted metadata type, liveness verdict live, and the schema node is a scalar (string / number / boolean / enum). ⇒ each needs exactly one form row, and the control is mechanical.

The census round names this 「the cheapest bucket and the best first per-bucket card」, and this seat agrees on the measurement rather than the adjective: it is the only bucket where the work per key is a single row with no design decision in it.

Examples named by the census: object.nameField · object.ownership · field.precision · action.shortcutKey · permission.license.

⛔ No view keys are in this bucket — view is union-rooted and its buckets are blocked on a separate ruling.

What it is NOT

⛔ Not a blanket assertion. Triage's grading is explicit: 「⛔ do not add a blanket assertion that would turn 276 absences into 276 red lines with no offers behind them」. This bucket lands offers, and the gate that would notice their absence is a separate prerequisite.

⚠️ This bucket cannot be verified by the gate until the top-level coordinate exists — see the prerequisite card filed alongside this one. Landing the rows is still useful without it; landing the gate without the rows is what triage refused.

Dedup words

scalar form rows object-rooted · metadata form offer scalar control · zodOnly bucket scalar · nameField ownership precision offer · top-level form row census


Origin: the #19188 census round, report comment 5749550902 (2026-09-20T11:37Z), base 596090efbe7. Its numbers were re-derived by the dev with the reconciliation gate's OWN helper block sliced verbatim (sha256 f6729dae2829…), ⛔ not by grepping source, with a lit control (name, offered by 17 of 17 forms) and a dark control (a fabricated key, 0) asserted inside the probe.
Filed-by: session_01LvwGppdonww4zGLWZo5rho (domain:spec execution seat 1), as the split triage asked for at 5747751499 — 「the claiming seat's first deliverable is the split, not the fix」. ⛔ Not graded and ⛔ not routed by this seat.


Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 22, 2026
  2. os-justin commented on Sep 22, 2026

    @os-justin
    Collaborator

    Claim: PM loop round 1 (wave 3)
    Session: session_01Sfe5YjBLwB9J3y8fvm2xq1
    Branch: claude/issue-19331-scalar-form-rows
    Worktree: objectstack-issue-19331
    Domain: domain:spec
    Seat: domain:spec#5
    File surface: the metadata-form definition files that carry the 47 object-rooted scalar keys (packages/spec/src/**/*.form.ts) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-opus-5 — default judgement tier. --tier gives no path mandate; taken at the default because these rows are the surface an AI author reads, so a wrong label or describe is an authoring trap, ⛔ not a cosmetic slip.
    Clause-②: no
    Thread-read: none
    Serial constraints cleared: *.form.ts read FREE across ALL 19 open PRs — ⛔ no PR in the census holds a single .form.ts path

    ⭐ This card's own prerequisite landed 13 minutes ago, and it was this seat's

    The body says 「This bucket cannot be verified by the gate until the top-level coordinate exists — see the prerequisite card filed alongside this one」. That prerequisite is #19329, and it is on main now: PR #19639 → 3a9b07f3b, single parent, and the capability verified by content rather than by card state — packages/spec/src/system/metadata-form-zod-reconciliation.test.ts:139 reads const ROOT_PATH = '(root)'; (control: censusRefusals/nestedLists ⇒ 16 hits in the same file).

    ⇒ the reconciliation ledger can now carry a root entry, so this bucket's rows are gate-visible rather than landing blind.

    Census — taken first-hand in this act

    Complete over 19 open PRs, 912 file rows. Seven PRs too new for a local merge-base were closed through the API rather than left as a gap; 0 API failures. Controls: LIT packages/spec/src/api/package-api.zod.ts ⇢ #19373; DARK zzz-no-such ⇢ 0.

    ⚠️ Sibling discipline: #19331, #19332 and #19333 are three splits of the same parent (#19188). 「被链接或同父的两单永不同批」 ⇒ only this one is taken. #19333 was released from pm:blocked to pm:queue in this same act (its blocker was the same #19329) and is ⛔ deliberately NOT dispatched alongside.

    Stamp: 2026-09-22T04:56Z


    Generated by Claude Code

  3. os-justin commented on Sep 22, 2026

    @os-justin
    Collaborator

    os-dev-report

    {
      "issue": 19331,
      "status": "done",
      "branch": "claude/issue-19331-scalar-form-rows",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19673",
      "session": "session_01Sfe5YjBLwB9J3y8fvm2xq1",
      "premise_still_valid": true,
      "premise_notes": "The bucket is real and the prerequisite is on main — verified by CONTENT on my own tree: metadata-form-zod-reconciliation.test.ts:139 reads const ROOT_PATH = '(root)', control censusRefusals/nestedLists resolves in the same file. What did NOT hold: the count (49, not 47) and three of the five example keys the card names — field.precision is already offered by field.form.ts, action.shortcutKey exists nowhere in the repository (the nearest is the retired action.shortcut), permission.license is neither declared by PermissionSetSchema nor a row in liveness/permission.json.",
      "population_rederived": {
        "count": 49,
        "rows_landed": 45,
        "left_out": 4,
        "method": "the reconciliation gate's OWN helper block sliced verbatim — packages/spec/src/system/metadata-form-zod-reconciliation.test.ts lines 104-469, sha256 25ef6c218394d1ea7156b8448377d65c5b3023a69afd894598d30bca48a92161 — into a throwaway vitest probe beside it, run against the live registry, then deleted (not in the diff)",
        "query": "for each of the 17 keys of METADATA_FORM_REGISTRY: offerableKeysAt(getMetadataTypeSchema(type), ROOT_PATH) MINUS topLevelFields(METADATA_FORM_REGISTRY[type]); then filter (a) unwrap(root).def.type === 'object' (drops union-rooted view), (b) JSON.parse(liveness/TYPE.json).props[key].status === 'live', (c) the property node peeled of WRAPPERS ONLY (optional/nullable/default/prefault/readonly/catch/nonoptional/lazy/pipe — never array or record, which would make a list read scalar) resolving to string | number | boolean | enum",
        "staged_readings": {
          "top_level_zod_only_overlay_skipped": 142,
          "object_rooted": 94,
          "union_rooted_view": 48,
          "object_rooted_and_live": 87,
          "object_rooted_live_and_scalar": 49,
          "same_probe_after_the_change": 4
        },
        "cross_check": "142 = the card's 274 minus the 132 ADR-0010 overlay keys, so the census arithmetic reproduces; the scalar slice does not (49, not 47)",
        "controls_asserted_inside_the_probe": {
          "lit": "name — offered by 17 of 17 forms (expect(offering.length).toBe(17))",
          "dark_form_side": "a fabricated key — offered by 0 of 17 forms",
          "dark_schema_side": "the same fabricated key — declared by 0 of 17 root schemas"
        },
        "no_view_keys": "confirmed — view is union-rooted and every one of its 48 top-level zod-only keys is excluded by filter (a)"
      },
      "keys_left_out": [
        "object.displayNameField — describe reads [DEPRECATED → nameField]; its canonical replacement nameField lands in this PR, so offering the alias beside it teaches the retired spelling",
        "app._unpublished — the schema text says Never authored: a machine-managed publish gate written by AI materialization and cleared by publish-drafts",
        "field.system — the auto-injected-column marker the platform stamps (applySystemFields, search-companion). Read on the WRITE path: record-validator skips required and multi-value checks for a flagged column, so a control lets an author assert a false provenance that silently disables validation",
        "field.format — its describe says Format string (e.g. email, phone) while the only citable consumer honours the named date styles short/relative (objectui DateCellRenderer, cited from dataset.zod.ts); the cited ledger evidence packages/objectql/src/engine.ts contains no read of the key. Ambiguous vocabulary, so no guess"
      ],
      "gates": "DERIVED with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on a tree at origin/main (stale-tree warning cleared by merging 8f30c188a first). 85 commands derived, all 85 run, each exit code captured BEFORE any pipe into a TSV. Reconciliation verdict line: \"Run reconciliation — 85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN.\" and \"✓ dispatch-gates --ran: 85 derived famil(ies) accounted for — 85 run, 0 NOT-MEASURED (a DERIVED zero — all 85 recorded an exit code and none of them is 3).\" Two exited 3 (PREREQUISITE NOT MET, no dist) on first pass — check:dual-build-cjs-loads and check:type-check-debt — which measured NOTHING and is neither a pass nor a finding; both re-run after the prerequisite build and both green, and those are the recorded codes. Also green: pnpm --filter @objectstack/spec run check:generated (all 15 generated artifacts up to date, working tree clean afterwards) and repo-wide pnpm lint (eslint . --no-inline-config) run WHOLE, not narrowed, exit 0. CI is not waited on.",
      "tests": "All at 3e453d8da, each through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-19331, verdicts read from its VERDICT command-exit line. @objectstack/spec: 512 files / 14953 passed, 1 todo. @objectstack/platform-objects: 54 files / 883 passed. @objectstack/lint: 108 files / 4111 passed. CONSUMER suites, named with a real reading each: @objectstack/objectql (getMetaTypes serves this registry) 303 files / 5050 passed; @objectstack/rest (the /meta/types payload) 194 files / 3265 passed, 1 skipped; @objectstack/cli unit tier (the i18n extractor walks this registry) 222 files / 3141 passed — integration tier declared to CI, the diff touches no spawn entry point. turbo run typecheck over spec + platform-objects + lint: 10/10 tasks green. No ablation was owed: this card lands offers, not a new gate.",
      "files_changed": [
        "packages/spec/src/data/object.form.ts (+6 rows)",
        "packages/spec/src/data/field.form.ts (+19 rows)",
        "packages/spec/src/ui/action.form.ts (+9 rows)",
        "packages/spec/src/ui/app.form.ts (+1 row)",
        "packages/spec/src/ui/page.form.ts (+1 row)",
        "packages/spec/src/identity/position.form.ts (+1 row)",
        "packages/spec/src/security/permission.form.ts (+4 rows)",
        "packages/spec/src/ai/agent.form.ts (+1 row)",
        "packages/spec/src/ai/skill.form.ts (+1 row)",
        "packages/spec/src/automation/flow.form.ts (+2 rows)",
        "packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts (regenerated)",
        "packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts (regenerated + 90 authored leaves)",
        "packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts (regenerated + 90 authored leaves)",
        "packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts (regenerated + 90 authored leaves)",
        "packages/platform-objects/src/apps/translations/{zh-CN,ja-JP,es-ES}.source-hashes.generated.ts (regenerated)",
        "packages/platform-objects/src/apps/translations/object-collapsed-sections-echo-decisions.test.ts (two measured pins moved: 45 to 55, 94 to 96)",
        "packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts (one measured pin moved: 538 to 583)",
        "packages/lint/src/validate-predicate-path-refs.test.ts (two corpus counts moved: 53 to 72, 41 to 52)",
        ".changeset/19331-scalar-form-rows.md (new)"
      ],
      "deviations": [
        "FILE SURFACE EXPANSION, declared not discovered. The claim declared packages/spec/src/**/*.form.ts. Eight further files changed (the four metadata-form bundles, the three source-hash companions, two platform-objects decision-ledger tests, one packages/lint pin test). It is mechanically forced: os i18n extract walks METADATA_FORM_REGISTRY, so a new row moves the generated catalogs and pnpm check:i18n reds without them; and the three translated catalogs are RATCHETED against undecided echoes, so the 90 new leaves had to be authored in zh-CN/ja-JP/es-ES rather than left as extractor fills. Verified from the provenance side: after re-extracting, 0 of the 90 keys remains in any locale source-hashes.generated.ts.",
        "MERGED origin/main (8f30c188a, a one-line change to scripts/check-tenant-audit-census.mjs) into the branch before deriving gates, because dispatch-gates refused to answer cleanly from a stale tree. Merge commit 3e453d8da; no generated artifact was touched by it and check:generated is green on the merged tree.",
        "NO LABEL WRITES, per the dispatch. Determination for the PM to apply: skip-changeset does NOT apply — a changeset is present and was judged against packages/spec/package.json files[] (dist is published and the new help text is in it, measured by grep with a lit control on an existing form helpText and a dark control that read zero), so @objectstack/spec takes minor and @objectstack/platform-objects patch (its bundles reach dist/metadata-translations, with non-ASCII escaped, which is why a CJK grep over dist reads zero). @objectstack/lint publishes dist only and its change here is a test file, so it takes none.",
        "THREE ENUM ROWS CARRY NO INLINE OPTIONS LIST — object.managedBy, action.execution, action.openIn — because FormSelectOptionSchema.value is a system identifier (^[a-z][a-z0-9_.]*$) and their members carry hyphens or a capital. Measured, not assumed: defineForm threw invalid_format on options[2].value at module load. Those rows derive the enum from the served JSON Schema and carry the meanings in help text."
      ],
      "mcp_calls": "0 — no MCP tool was called at any point; every GitHub read and write went through the REST proxy with curl and GITHUB_TOKEN. No MCP write tool appears in this run.",
      "api_writes": "2 REST writes + 7 git pushes. REST: POST /repos/objectstack-ai/objectstack/pulls (draft PR 19673, one create, no PATCH), POST /repos/objectstack-ai/objectstack/issues/19331/comments (this report, via scripts/pm/post-stamped.mjs). Reads (not writes): GET /issues/19331, GET /issues/19331/comments, GET /pulls/19673 (body read-back — stored byte-identical apart from one stripped trailing newline, single session-URL footer). ZERO label writes, zero issue creates, zero assignee writes, zero claim comments.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: c · object.form.ts fields.returnType offers six formula return types — text, number, boolean, date, datetime, currency — while FieldSchema.returnType is z.enum([number, text, boolean, date]); authoring datetime or currency from the object designer quick-add grid writes a value the parse refuses. The field.form.ts row added here lists the four declared members only. Seam: spec:FieldSchema.returnType → renderer:object.form.ts fields.returnType. carrier: none — not fixed here, the dispatch scopes this seat to B8 rows only. dedupe: returnType option datetime currency · form option refused enum member · quick-add grid returnType drift",
        "class: b · FormSelectOptionSchema.value is a system identifier (^[a-z][a-z0-9_.]*$), so a form row cannot enumerate an enum whose members carry a hyphen or a capital — system-data, engine-owned, append-only, better-auth, new-tab, perRecord are all unspellable as option values. Measured: defineForm threw invalid_format on options[2].value at module load in this worktree. Seam: spec:FormSelectOptionSchema.value → renderer:METADATA_FORM_REGISTRY option lists | consumer: the metadata-admin form renderer. dedupe: FormSelectOptionSchema value system identifier · option value hyphen refused · enum member unspellable form option",
        "class: c · field.format's liveness row is {status: live, evidence: packages/objectql/src/engine.ts} and that file contains no read of the key (its only format occurrences are a logger option and an unrelated validation-rule type). The citable consumer is objectui's date cell renderer honouring short/relative, which the key's own describe (Format string (e.g. email, phone)) contradicts — so an author following the describe writes a value nothing honours. dedupe: field.format evidence pointer rot · format short relative date style · describe contradicts consumer vocabulary",
        "carrier: sibling card 19333 (recorded reasons) · field.system, app._unpublished and object.displayNameField are deliberate zod-only omissions needing a ledgered reason rather than a control. NOT filed as new cards and NOT ledgered here — they sit inside that card's scope and are named for it.",
        "noted, not filed · page.form.ts's existing kind row says full or slotted while the derived enum offers five members (full, slotted, html, react, jsx). An under-description of an existing row; an omitted member is not a reproducible defect.",
        "noted, not filed · object.tenancy, app.contextSelectors and action.onSuccess each carry a liveness row with NO status field at all. None is a scalar so none entered this bucket; recorded because the probe had to classify them and a status-less row resolves to no verdict."
      ]
    }

    Generated by Claude Code

  4. os-justin commented on Sep 22, 2026

    @os-justin
    Collaborator

    落地 — PR #19673 merged. 2026-09-22T07:31Z

    domain:spec execution seat 5. pm:dispatched stripped in the same stroke as this comment (a closed card wearing an in-flight label is a half-state and it poisons every in-flight count computed from that label); the assignee stays as the record of who carried it.

    reading value
    merge commit on main 408ca2e36 — feat(spec): give the 45 declared-but-unoffered scalar metadata keys a form row each (#19673)
    parents 1 (2b80461268…) — the queue squashed 8 branch commits into one, ⛔ regardless of the PR's stored auto_merge.merge_method
    enqueued → merged 2026-09-22T07:06:06Z → this reading
    CI on the landed head 9e6e78dd1 32 success · 3 skipped · 0 incomplete · 0 red, latest-per-name
    card state closed / completed, by the PR's own Fixes line
    contract review of record 5772177833 — re-anchored to the landed head, Reviewed-by: + Served-tier: CONTRACT_REVIEW_TIER

    What landed

    45 declared-but-unoffered scalar metadata keys get one form row each, across ten *.form.ts files, with 90 translated leaves authored in zh-CN / ja-JP / es-ES rather than left as extractor fills. 4 keys are deliberately still unoffered and named on the PR. The population was re-derived as 49, not the card's 47, and three of the card's five example keys do not hold — each re-measured by this seat on origin/main rather than accepted from the report.

    Two rounds were sent back before ACCEPT, ⛔ neither waved through

    1. A comment mis-attributed two leaves to this card (object-collapsed-sections-echo-decisions.test.ts said 12 where the card brought 10), absorbing a shortfall that predates it. Fixed comment-only, 0 assertions moved.
    2. A false claim on its way to npm. The changeset said field.format's liveness evidence carries no read of the key. Measured FALSE: the read is TRANSITIVE through resolveAutonumberFormat (packages/spec/src/data/autonumber-format.ts:196-202), which packages/objectql/src/engine.ts:5043-5051 calls for every autonumber field, as does the SQL driver. ⇒ a pointer to a FILE is not refuted by grepping that file for the KEY. The decision (no control for that key) is unchanged and stronger.

    Filed out of this round by the seat, ⛔ not by the dev

    #19677 · #19678 · #19679 — each with its own measured readings and a lit control on the dedupe probe.

    ⚠️ The post-merge sweep on 408ca2e36 is ⛔ not finished at this reading. A red belonging to this diff is this seat's to answer, not a later reader's to discover.


    Generated by Claude Code

  5. os-justin commented on Sep 22, 2026

    @os-justin
    Collaborator

    Post-merge sweep on 408ca2e36 — it reads RED, and the red is ⛔ NOT this diff's. Measured, not waved away. 2026-09-22T08:01Z

    The landing comment above left this open in as many words (「a red belonging to this diff is this seat's to answer, not a later reader's to discover」). Here is the answer.

    What the board shows

    Test Core (the rollup) — failure. Alongside it, Analyze (javascript) and Lint & Repo Gates — cancelled, in two different workflow runs.

    What actually happened, shard by shard

    shard verdict
    Test Core (2/6) · (3/6) · (4/6) success
    Test Core (1/6) · (5/6) · (6/6) cancelled
    Test Core (rollup) failure — it requires all six, and three never returned one

    ⇒ No test failed. Three shards were cancelled, and a cancelled job measured NOTHING — the same class as a gate answering exit 3 PREREQUISITE NOT MET, which this lane already refuses to score as either a pass or a finding.

    Why they were cancelled — the cause is on main, not in the diff

    main moved past this commit while its post-merge sweep was still running: 408ca2e36 → 48f520023 (#19676) → 1f53b0b68 (#19379). The concurrency group cancels the superseded run, which is why the cancellations span three separate workflow runs rather than clustering in one job.

    The control that settles it

    main's current head 1f53b0b68 — which CONTAINS this diff plus two commits — reads 22 success · 13 skipped · 3 in progress · 0 red. If these 45 form rows, 90 translated leaves or three moved pins had broken a shard, the shard would be red there too, on a run nothing superseded.

    ⇒ ⛔ Nothing to fix, ⛔ nothing to re-run, ⛔ no test skipped or quarantined. The three shards still running on the live head stay in this seat's view until they finish.

    The generalizable half, recorded because it is cheap to get wrong

    A rollup can read failure purely because its members were CANCELLED, and that failure belongs to the supersede, not to the diff. ⇒ before answering a red rollup, read its members: a member that is cancelled returned no verdict, and 「the rollup is red」 is not 「a test failed」. ⛔ The converse discipline is unchanged — a genuinely failing test is never written off as infrastructure.


    Generated by Claude Code

  6. added 2 commits that reference this issue on Sep 28, 2026
    408ca2e
    655e8c0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions