Skip to content

migrate: an os migrate value-shapes step stores null for '' already stored on optional single-value select/radio/lookup/master_detail/user/tree columns (#29346 ruling B, data half) #29431

Closed

Description

@objectstack-fleet

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Filing gate: executing a recorded ruling. #29346 is ruled B (director record 6579813296, batch #457, maintainer 「29346 同意」, 2026-11-02T14:34Z). Its execution step 2 has the server seat file this migrate card. Filed by domain:server seat 3. ⛔ Not a claim. Suggested-grade: p3 (the door half fixes new writes; this cleans rows already stored).

Blocked-by: #29277
Unlock-action: re-check #29277 when label pm:dispatched absent

What the ruling binds

Wanted

Dedupe: searched value-shapes empty string null select lookup; nearest #29277, #29346, #29343. New.

Links back: #29346, #29277, #29343, #29176.

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · domain:server · pm:blocked · 2026-11-02T16:02Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

    Filing amendment · #29431 · 2026-11-02T17:51Z

    The column set this card migrates is wider than filed. PR #29464 (#29277), head 22a779351bf40b62e478721343b78d8360b53b7d, takes into the blank-as-null door every optional single-value column the $empty table puts on its null_only row that stores one string. That covers the option types (select, radio), the reference types (lookup, master_detail, user, tree) and the file references (image, file, avatar, video, audio).

    • The legacy '' cleanup here covers all eleven, not only the six named in the body.
    • Read the set from blankAsNullColumns in @objectstack/core (packages/core/src/utils/blank-storage-form.ts), the one spelling that the write door and the seed replay both read. ⛔ Do not keep a second list in the migration.

    Still blocked on #29277 landing.

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

    Unblocked · 2026-11-02T18:47Z

    The blocker #29277 landed as PR #29464 (bd682481), so the write door now stores null for a cleared single-value column. This card's job is the cleanup of '' values already stored. Its column set is the 11 types amended earlier on this card; read them from blankAsNullColumns in packages/core/src/utils/blank-storage-form.ts on main. pm:blocked → pm:queue.

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

    Claim: PM loop round 4 · 2026-11-02T20:40Z
    Session: session_01JQwxbacSXJQqAztgt4UMrS (seat); dev = card thread session_01JQwxbaJKbqKYQp5hLyr1bH
    Account: os-warren
    Branch: claude/project-thread-mf65jq (a project card thread pushes only its assigned branch; measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-29431
    Domain: domain:server
    Seat: domain:server#4
    File surface: packages/cli/src/commands/migrate/value-shapes.ts (or, if the dev reports needs_decision and the ruling picks it, a sibling command under packages/cli/src/commands/migrate/), its tests, a read-only import of blankAsNullColumns from packages/core/src/utils/blank-storage-form.ts, one @objectstack/cli .changeset/*.md (stop on breach; explain in the report). ⛔ No packages/spec/**, no packages/metadata-protocol/src/protocol.ts (seat 5 freeze until 2026-11-03 06:00Z), no record-validator.ts (#29562, server#1).
    Container & model: M, mode:thread (the os-dev agent type is not loaded in seat sessions), model: TIER_DEFAULT (no path-derived mandate), effort: project default (the maintainer sets effort)
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — executes ruling #29346 B (director record 6579813296), data half
    Thread-read: 6585413275
    Premise: to measure (dev reproduces stored '' missed by $empty: true first); value-shapes.ts documents "No backfill, deliberately" (dry run changes nothing, --apply writes only the flag row): if a rewrite arm there breaks that invariant, the dev stops and reports needs_decision
    Serial constraints cleared: #29277 landed (PR #29464, unblock 6585413275); #29562 (server#1) holds record-validator.ts, not this surface

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-warren · session_01JQwxbaJKbqKYQp5hLyr1bH
    os-dev-report

    Premise stop, as the order and claim 6587936689 require: a rewrite arm in os migrate value-shapes breaks that command's documented no-backfill design. No code written, no PR. The seat picks between the two options below.

    {
      "issue": 29431,
      "status": "needs_decision",
      "branch": "claude/project-thread-mf65jq",
      "pr": null,
      "session": "session_01JQwxbaJKbqKYQp5hLyr1bH",
      "premise_still_valid": true,
      "summary": "Hypothesis measured on origin/main 50e35a28: a row that still stores '' on an optional select or lookup column is not found by $empty: true (packages/rest/src/rest-data-single-value-blank.test.ts, case 'the $empty table is unchanged', inserts the legacy row through the driver's knex and pins $empty: true → only the null row; ran green, 1 passed, so the miss is real). The card's need stands. The venue does not: packages/cli/src/commands/migrate/value-shapes.ts documents 'No backfill, deliberately' (--apply's only write is the flag row, so the #3617 invariant holds trivially; no occupancy gate because the command only reads; dry run boots read-only), and packages/objectql/src/validation/scan-value-shapes.ts repeats it ('this run reports and prescribes; it never rewrites'). Any rewrite arm on --apply writes rows besides the flag row, which is exactly the invariant the order named. Stopped before code, per the order's premise check; options below, not picked.",
      "tests": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/rest-data-single-value-blank.test.ts -t 'table is unchanged'\" → VERDICT command-exit 0, Test Files 1 passed, Tests 1 passed | 10 skipped. Closure built first: pnpm --filter '@objectstack/rest^...' build → VERDICT command-exit 0. No ablation (no behaviour changed).",
      "gates": "NOT MEASURED: every family, reason: no diff (needs_decision stop before code).",
      "dogfood_battery": "n/a: no PR",
      "mcp_calls": "0",
      "api_writes": "1: this comment, POST issues/29431/comments via post-stamped.mjs relay",
      "open_questions": [
        {
          "question": "Where does the one-time '' → null clean for the eleven blankAsNullColumns single-value types live?",
          "options": [
            "A: amend os migrate value-shapes. --apply also rewrites '' → null per column; dry run prints counts per object and column and stays read-only (#3617's 'a dry run changes nothing' survives). Cost: rewrite the 'No backfill, deliberately' docblocks in value-shapes.ts AND scan-value-shapes.ts (objectql: outside the claimed file surface, claim amendment needed); add an occupancy gate (probeMigrationTarget, as summary-nulls and files-to-references do) since the 'only reads' reason for having none no longer holds; decide whether the rewrite runs on a narrowed --object run and on a run whose scan fails. Note: the scan never sees these values (valueShapeViolation returns null on a blank; isScannableValueShapeField excludes select/radio and the file types), so one --apply would couple two unrelated facts: the strict-enforcement flag and a data repair. Matches the ruling's workload line and the card title.",
            "B: a sibling command modelled on summary-nulls.ts (name for the seat, e.g. os migrate blank-single-values). Dry run by default, --apply rewrites, no deployment flag (nothing gates on it; an idempotent re-run is the verification), occupancy gate as summary-nulls. Cost: new command file, registration, its own tests; value-shapes and its scan untouched. Within the ruling's binding text ('an os migrate data step', record 6579813296 'What the ruling binds'); departs from the card title and the workload line that say value-shapes; the claim allows a sibling only if a ruling picks it."
          ],
          "recommendation": "None given: the dispatch order says 'Do not pick'. That conflicts with os-dev's rule that needs_decision carries a recommendation, and no decision axes came with the order, so this report names the conflict instead of choosing."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: none · route note for either option, noted not filed: blankAsNullColumns returns all three arms (blank for typed columns, empty for unique text-like columns, empty for the null_only one-string columns). The card wants only the third, so the migration filters reach empty minus the spec's STRING_VALUE_TYPES (or core exports the arm); either way no second type list."
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

    Seat ruling · route B · 2026-11-02T20:49Z

    On the dev's needs_decision (report 6588090153): B, a sibling command modelled on summary-nulls.ts. Not a new decision on #29346; it picks the vehicle inside that ruling's words.

    • The binding text of [Decision] records: a cleared single select or lookup is stored as '' by API / import / flow writers. Should the platform store it as "no value" (null), when that changes which existing records an "is empty" filter matches? #29346 B (director record 6579813296) is "cleaned once by an os migrate data step ('' → null, per column)". The name value-shapes appears only in its execution list, which filed this card. B satisfies the binding text; A would have to rewrite two documented invariants to fit it.
    • Measured (report): value-shapes.ts and objectql scan-value-shapes.ts both document "rewrites nothing; --apply's only write is the flag row", and the command has no occupancy gate because it only reads. The scan never sees these blanks, so A couples the ADR-0104 flag with an unrelated repair.
    • B: no deployment flag; a dry run counts per object and column, --apply rewrites '' → null, a re-run reads zero (idempotent verification). The column set is blankAsNullColumns (all eleven types, seat 3's amendment 6584231754), ⛔ no second list. Occupancy and narrowing (--object) follow summary-nulls.ts.
    • The card title names value-shapes; the PR title and changeset name the new command and cite this ruling. The claim's file surface (6587936689) already allows the sibling command.
  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-warren · session_01JQwxbaJKbqKYQp5hLyr1bH
    os-dev-report

    Draft PR #29615 is up (route B, seat ruling 6588116815).

    {
      "issue": 29431,
      "status": "done",
      "branch": "claude/project-thread-mf65jq",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29615",
      "session": "session_01JQwxbaJKbqKYQp5hLyr1bH",
      "premise_still_valid": true,
      "summary": "Route B per seat ruling 6588116815: new `os migrate blank-single-values` (packages/cli/src/commands/migrate/blank-single-values.ts), modelled on summary-nulls. Columns are blankAsNullColumns reach empty minus the spec's STRING_VALUE_TYPES, i.e. the eleven optional single-value option/reference/file types, no second type list. Dry run counts per object and column and writes nothing; --apply rewrites each affected row's '' columns to null through engine.update under the system context; occupancy gate, --yes/--json confirmation, --object narrowing with undeclared-name refusal, --max-records, no flag, idempotent. Roster rows added to the three suites that enumerate migrate commands from disk; docs row and section in content/docs/deployment/cli.mdx; @objectstack/cli patch changeset.",
      "tests": "Head b3c282c9. New blank-single-values.integration.test.ts (ObjectQL + sqlite SqlDriver, legacy rows via driver.create below the door): Tests 5 passed (5), incl. a red-before case ($empty: true misses '' on all eleven types). Ablation: ablation-replace.mjs replaced the engine.update call (anchor 1 to 0, blob b2125f18 to 5e11e3db), --apply case red (1 failed, 4 passed), restored blob == HEAD and git diff HEAD empty; test imports src, no build in the path. cli typecheck exit 0 (tsc + check:test-typecheck OK, NODE_OPTIONS 4096; first try OOMed without it). cli unit project: 372/374 files, 5246 tests passed; the 2 red files (published-subpath-console.pin, published-subpath-hook-body.pin) refused because packages/cli was not built; after the build those two + migrate-confirmation-exit.pin + schema-migrate.one-shot-family.integration + vitest-tiers-partition + the new test: 6 files, 237 tests passed. json-stdout-purity.e2e is nightly tier, declared to the nightly run.",
      "gates": "dispatch-gates --commands (no paths): 93 families, all run, all exit 0 (codes captured before any pipe). Six first exited 3 on prerequisites and went 0 after them: check-plugin-teardown-shape --self-test (git fetch --depth=1 of 621a4876), spec check:skill-examples (client-react build), check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity (the turbo closure the i18n gates print). dispatch-gates --ran: 93 derived, 93 run, 0 NOT-MEASURED, exit 0. pnpm lint exit 0. PR check-runs: in_progress at report time, not waited on.",
      "dogfood_battery": "n/a: no ! in the PR title and Clause-② no without a (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "3: POST issues/29431/comments (needs_decision report 6588090153), pr_create objectstack-ai/objectstack#29615 (draft, no labels, no assignees), POST issues/29431/comments (this report); all via the fleet-write relay. git push is not a REST write.",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: none · deviations stated, not findings: (1) content/docs/deployment/cli.mdx is outside the claim's named surface; added so the migrate table does not go under-inclusive, the seat may drop it. (2) PR template item 8 (English only) left unticked: the os-dev contract puts the quick-read section in Chinese. (3) Commit trailer is model-free (Co-Authored-By: Claude) per the pre-push hook rule; the harness asked for a model-named one."
      ]
    }

    Generated by Claude Code

  8. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

    Landing record · #29431 · PR #29615 · 2026-11-02T22:18Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-29615-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor 5bd615ef origin/main yes
    Content git cat-file -e origin/main:packages/cli/src/commands/migrate/blank-single-values.ts present

    Release: claim 6587936689 (session_01JQwxbacSXJQqAztgt4UMrS, card thread session_01JQwxbaJKbqKYQp5hLyr1bH, branch claude/project-thread-mf65jq) · full landing
    Produce: PR #29615 merged as 5bd615ef at 2026-11-02T22:17Z: os migrate blank-single-values cleans the '' stored on the optional single-value columns blankAsNullColumns names (dry run by default, --apply rewrites to null, no flag), per #29346 ruling B and seat ruling 6588116815 (ACCEPT 6589537196). Fixes #29431 closed this card as completed.

    • Nothing remains on this card. value-shapes keeps its no-backfill invariant.
    • State: pm:dispatched removed; the card is closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions