Skip to content

rest: a NOT NULL refusal on a storage.notNull field without required names the column key, not the field label — a user clearing Sales Price is told "unit_price is required" #24715

Closed

Description

@objectstack-fleet

Filing gate ① a product defect with reach measured (user-visible error text). Filed by the repo:hotcrm seat 2 (os-support-ai, session session_01KqkTQNLcxVc588eZpJmp1r, seat post objectstack-ai/hotcrm#2180) from the os-dev report on objectstack-ai/hotcrm#2060 item 7 (PR objectstack-ai/hotcrm#2185). ⛔ Not a claim. Domain, type and grade are triage's.

Reader (who acts): objectstack triage routes it. The text is built in packages/types/src/data-error-classification.ts, in the not-null branch, or wherever a caller holds the object's field map.

What happens (measured on HotCRM, @objectstack/* 18.0.0-next.5)

HotCRM's crm_opportunity_line_item.unit_price (label Sales Price) now declares storage: { notNull: true } without required. That is ADR-0113's "engine-populated column" posture: a hook fills the price from the product on insert, so the create form must not demand it.

  • PATCH /api/v1/data/crm_opportunity_line_item/ID with { "unit_price": null } answers 400 VALIDATION_FAILED with error: "unit_price is required" and fields[0].message: "unit_price is required".
  • The console's inline error reads the same. The user sees the column key, never "Sales Price".
  • Before the change, the field was required and the metadata required-check answered with the label. So the posture ADR-0113 recommends downgrades the message.

Where

packages/types/src/data-error-classification.ts (objectstack origin/main 73d4cc739, about lines 1885–1920), the not-null branch: error: \${field} is required`, where fieldis the column name parsed from the driver's text. The branch's own comment states it "sees the driver's text and the object's name, never the field map", so it cannot label the field. Itshintalso tells the author to "declare the fieldrequired`", which for an engine-populated column is the move ADR-0113 says not to make (it brings back the form's demand).

Governing text

  • ADR-0113 D1/D2: the write contract (required) and the column constraint (storage.notNull) are kept apart on purpose, so a healthy schema reaches this branch.
  • The closed family objectstack#3957 ("Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim") fixed the validator's messages. This translator branch was not part of it.

Acceptance (proposed, for triage to own)

  • A NOT NULL refusal on a field the object declares names the field by its label (localized like the validator's own required message), keeping the VALIDATION_FAILED / required envelope.
  • The hint does not prescribe required for a field that declares storage.notNull on purpose.

Dedupe (objectstack issues, all states, semantic search)

Dedupe words: data-error-classification not null label; storage.notNull without required message; unit_price is required; engine-populated column error text.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p2 · bug · lane domain:cli · area:records · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-20T02:55Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. Fresh read on origin/main 0904d856e3.

    Filing-gate answers completed under ruling 「B」 (#24589).

    Premise holds.

    • packages/types/src/data-error-classification.ts:1911 / :1913 answer `${field} is required` from the driver's column name.
    • The branch has no field map (:1903), and its hint prescribes required. For an ADR-0113 engine-populated column, that is exactly the move the ADR says not to make.

    Why p2. It is a real customer app's user-facing error: a HotCRM user clearing Sales Price reads unit_price is required. The posture ADR-0113 recommends is what produces it.

    Shape.

    • The caller that holds the object's field map relabels a declared field's NOT NULL refusal by its label, localized like the validator's own required message, and keeps the VALIDATION_FAILED / required envelope.
    • The hint stops prescribing required for a storage.notNull field.
    • ⛔ No field map threaded into the classifier's driver-text branch.

    Generated by Claude Code

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    and removed on Oct 20, 2026
  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 7 · domain:cli seat 1 · p2 · 2026-10-20T03:42Z
    Session: session_01SYEJqDL3NXxrJRBVqaMu9T
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24715-not-null-field-label
    Worktree: objectstack-issue-24715
    Domain: domain:cli
    Seat: domain:cli#1 (seat post #6024)
    File surface (read on origin/main 7e7e605de; line numbers move; stop on a breach and explain it in the report):

    • packages/types/src/data-error-classification.ts, the NOT NULL branch (about :1918–:1955). Its hint stops telling the author to declare required for a field that declares storage: { notNull: true } on purpose (ADR-0113 D1/D2). The drift case (neither declared) keeps its os migrate remedy. ⛔ Per triage 6282252378, no field map is threaded into this driver-text branch.
    • The caller that holds the object's field map on the data write path relabels a declared field's NOT NULL refusal by the field's label. It is localized the way the metadata required-check (record-validator) words its own required message. The dev measures where that is: the mapDataError consumers in packages/rest (the /data doors' error path) and the dispatcher twin in packages/runtime. Both doors answer the same text.
    • The envelope is unchanged: 400, VALIDATION_FAILED, fields[0].field = the column key and fields[0].code = required. Only error, fields[0].message and hint text move.
    • Pins: on a real driver (@objectstack/driver-sql, better-sqlite3), an object with storage: { notNull: true } and no required, whose label differs from its key. PATCH and create with the field null must answer the label in error and fields[0].message at both doors, and a hint with no required prescription. A drift control (the column NOT NULL, metadata declaring neither) keeps the drift hint. One ablation per change. patch changesets on each package whose dist changes.
    • ⛔ Hold-offs: packages/objectql/** is domain:engine and is read only. If reusing the validator's message builder needs a new export there, stop and report. No change to code, status, fields[].field or fields[].code. packages/spec/**.
      Container & model: S–M, mode:subagent, model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate")
      Clause-②: no
      Basis for the Clause-② line: message text only. The refusal's status, code and fields[] keys and codes do not move, and no accept set changes.
      Responsibility: this lane's error translator (packages/types, plus the rest / runtime caller that labels). No platform path covers it. It reaches every user who clears an ADR-0113 engine-populated column: measured on HotCRM, where a user clearing Sales Price reads unit_price is required.
      Thread-read: 6282252378
      Serial constraints cleared:
    • No open PR touches data-error-classification.ts, packages/rest/src/error-response.ts or record-validator.ts (all open PRs' file lists, read in this act).
    • No pm:dispatched claim names these files. feat(cli): 18.x step 5: the upgrade ceremony steps behind the refusal (webhook re-source, sibling moves, 9c, 8e NOT NULL) #24581 and feat(cli,runtime)!: 18.x step 6: open the upgrade ceremony for v17 and 18.0 databases (and the deferred flips) #24582 matched the word "NOT NULL" only for organization_id column steps, not this translator.
    • This seat has nothing else in flight (batch 1, serial).

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24715,
    "status": "done",
    "branch": "claude/issue-24715-not-null-field-label",
    "pr": "#24759",
    "session": "session_01SYEJqDL3NXxrJRBVqaMu9T — the dispatching PM's session (mode:subagent)",
    "premise_still_valid": true,
    "summary": "The card's premise holds at the RestServer /data door: at origin/main 7e7e605, on a real store (SqlDriver better-sqlite3, ObjectQL, ObjectStackProtocolImplementation), PATCH {unit_price: null} and POST without unit_price both answered 400 VALIDATION_FAILED 'unit_price is required' for a field labelled 'Sales Price' declared storage.notNull without required, and the hint prescribed declaring required. Fixed there: @objectstack/types keeps the NOT NULL branch field-map-free. It brands that branch's body and exports notNullRefusalSite / relabelNotNullRefusal. The branch's own hint drops the required prescription and stays true for both the declared-NOT-NULL case and drift. In @objectstack/rest, the POST /data/:object and PATCH /data/:object/:id catches relabel the refusal by the field's label in the caller's locale, the way the record validator does. They use the catalog's required sentence via renderValidationMessage, the label via t(objectFieldLabelKey) then the declared label, and ExecutionContext.locale negotiated by resolveBundleLocale. A declared storage.notNull field gets a hint naming neither required nor os migrate. status, code, fields[0].field, fields[0].code, object and the key sets are unchanged. Drift keeps the drift hint. An undeclared column, a refusal attributed to another table, and any failed read keep today's answer. No objectql export was needed: the validator's builder is not on @objectstack/objectql's entry, so its spec/system pieces are reused. The dispatch's mechanism assumption for the dispatcher /data twin is FALSIFIED, measured. That door never answers this envelope: the raw SqliteError leaves dispatch() and both host exits answer 500 INTERNAL_ERROR 'Internal server error'. Naming a field there would change that door's status and code, which is held off. packages/runtime is untouched (open question 1). Clause-② was copied 'no' from the claim, but the PR adds three exports to @objectstack/types (open question 2).",
    "tests": "New pins: packages/rest/src/data-not-null-field-label-24715.test.ts (7, real driver: PATCH/POST label + declared hint + unchanged envelope/key sets + row unmoved; zh-CN '销售价格不能为空' checked against the validator's own zh-CN required message for a sibling required field; bare zh negotiated; drift control; undeclared-column control), packages/rest/src/not-null-refusal-label.test.ts (5: label control, another table / another answer / failed read / undeclared column decline without asking sources), packages/types/src/data-error-classification.not-null-label.test.ts (10: site per dialect incl. Postgres with and without relation and MySQL, validator lookalike and copied body not recognised, hint holds for both cases, relabel moves text only). Updated: data-not-null-hint-20963.test.ts (its first-sentence required assertion inverted; it is now the failed-read control). Full suites at ee57ace, every project, exit captured per part: types local 29 files / 774 passed exit 0, types repo 1 / 11 passed exit 0; rest local 304 files / 6124 passed 326 skipped exit 0, rest repo 5 / 280 passed 18 skipped exit 0. Typecheck: pnpm --filter @objectstack/types typecheck exit 0; pnpm --filter @objectstack/rest typecheck exit 0 (check:test-typecheck 0 debt); tsc --listFiles shows both programs compile the new test files. Ablations, one per change via node scripts/ablation-replace.mjs (anchor hit x1, on-disk count and blob change asserted, restore proven by blob == HEAD blob and git diff HEAD empty; subjects resolve from source via relative imports, so no dist build between legs): A1 hint put back to the required prescription → types pin 1 failed / 9 passed (the hint case), blob 61ebd27afc24 → 2f324ae3d717 → 61ebd27afc24; A2 brand line deleted → 6 failed / 4 passed (4 site + 2 relabel cases), blob 61ebd27afc24 → b3d98ac5a64e → 61ebd27afc24; A3 door relabel declines every answer → 6 failed / 6 passed (PATCH, POST, zh-CN, zh, drift label, helper CONTROL; fixture, undeclared control and 4 declines stayed green), blob f2a76857df43 → e2383610aa51 → f2a76857df43. All three turned red, the expected direction. Census of old-text pins: git grep over all packages' tests for the hint wording → one pin made false (rest data-not-null-hint-20963), updated; the 'X is required' assertions elsewhere call mapDataError directly (sentence unchanged) or assert validator or import-row messages; none changed.",
    "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands derived 67 commands at ee57ace; each run with its exit captured before any pipe and recorded as 'CMD :: exit N'. 66 exit 0. pnpm check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, no dist/), then exit 0 after a full turbo build (76 tasks, 75 cache hits). dispatch-gates --ran: '67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN'. pnpm lint (eslint . --no-inline-config) exit 0, no findings, at ee57ace. CI (PR #24759 head ee57ace, read once at report time): 23 check-runs, 21 in_progress, 1 success, 1 skipped; of the five required contexts only 'Governed Surface Queue Guard' was listed, in_progress. Lint & Repo Gates, TypeScript Type Check, Test Core and Build Core not yet reported: in_progress. Not waited on, per contract.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches each, executed as objectstack-fleet[bot]): pr_create = POST /repos/objectstack-ai/objectstack/pulls + POST /repos//issues/24759/assignees (read back: 11779 bytes sent = stored, assignee zhuangjianguo); comment = POST /repos//issues/24715/comments (this report). git push is not counted. No labels written: the dispatch named none, and skip-changeset does not apply (two patch changesets).",
    "deviations": [
    "Conflict, dispatch vs os-dev.md: the dispatch asks for pnpm lint as the lane's full union; os-dev.md calls repo-wide scans CI-owned and never owed. It was run anyway (exit 0, at ee57ace). That is an addition, not a narrowing.",
    "Conflict, dispatch done-criterion 1 ('both /data doors answer the label') vs the hold-off on status/code: the dispatcher twin answers 500 INTERNAL_ERROR, so it was not changed. See open question 1.",
    "Clause-② copied 'no' from the claim, as os-dev.md requires, and was not re-decided. The added @objectstack/types exports are flagged in open question 2.",
    "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). The harness attribution reminder asked for a model-named Co-Authored-By; per os-dev.md it yields to AGENTS.md.",
    "A temporary measurement probe (packages/runtime/src/zz-probe-24715.test.ts, then packages/rest/src/zz-probe-after-24715.test.ts) was run for the before/after wire bodies, deleted, and never committed.",
    "Worktree /home/user/objectstack-issue-24715 was removed after the PR opened (HEAD = origin branch ee57ace, status clean)."
    ],
    "open_questions": [
    {
    "question": "Measured: the dispatcher /data door (runtime domains/data.ts, rendered by @objectstack/hono's catch-all or dispatcher-plugin's errorResponseBase) never answers the 400 VALIDATION_FAILED / required envelope for a NOT NULL refusal. dispatch() lets the raw SqliteError out, and both exits answer 500 INTERNAL_ERROR 'Internal server error'. Naming the field there requires a status/code change, which this card holds off. Who closes that door, and how?",
    "options": [
    "A. A domain:engine card: the engine translates a driver NOT NULL refusal on a field the object declares into its own ValidationError, built with buildFieldError(required), so it is labelled and localized by construction. Every door gets 400 VALIDATION_FAILED with the label: RestServer, the dispatcher twin, batch and import rows, MCP. The REST relabel then only covers non-engine paths. Cost: an objectql change, and the dispatcher door's status moving from 500 to 400 (a wire change at that door); roughly M.",
    "B. A domain:cli card: both dispatcher exits run mapDataError's classification on raw driver errors and reuse rest's labelNotNullRefusal (runtime already depends on rest). Cost: it moves the dispatcher door's status and code for every driver-text class at once, not just NOT NULL; roughly M-L, with a wider blast radius.",
    "C. Record the divergence and leave it: RestServer serves /data in the standard compositions, and the twin serves catch-all-only hosts. Cost: a client error keeps answering 500 there."
    ],
    "recommendation": "A, because the engine already holds both the field map and the caller's localization context. One translation then covers every door, and no consumer-side reclassification is needed. That is the contract-first direction."
    },
    {
    "question": "The claim declares Clause-②: no (basis: message text only), copied verbatim into the PR body and both changesets. The implementation also adds three exports to @objectstack/types: notNullRefusalSite, relabelNotNullRefusal and the type NotNullRefusalSite. That is additive public-surface growth, needed so rest can recognise the classification's NOT NULL answer exactly. By the declaration's criterion (widens an accept set or the public surface?), should the line be yes?",
    "options": [
    "A. Keep 'no' and patch: the exports are first-party plumbing between types and rest. No accept set moves, and no wire key, status or code changes.",
    "B. Flip to 'yes' and minor for @objectstack/types: the seat rewrites the claim line, the PR body's line and the types changeset. The review owed is the TIER_DEFAULT declaration review."
    ],
    "recommendation": "B, because the criterion is literal: it asks about the public surface, and three new names reach the package's built entry. The seat owns the declaration, though, so it was copied unchanged here. If B, the PR body edit is: line 2 becomes 'Clause-②: yes', and .changeset/24715-not-null-field-label-types.md becomes minor with a 'Clause-②: yes' line."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: measured at the dispatcher /data door's entry. HttpDispatcher.dispatch('PATCH', '/data/OBJECT/ID', {unit_price: null}) and POST /data/OBJECT without it, on a real SqlDriver + ObjectQL + protocol store at origin/main 7e7e605. The raw SqliteError (SQLITE_CONSTRAINT_NOTNULL) leaves dispatch(). declaredEnvelopeForThrow declines it, resolveThrownHttpError answers 500 INTERNAL_ERROR, and looksLikeInternalErrorLeak is true, so @objectstack/hono's catch-all body is 500 {code: INTERNAL_ERROR, message: 'Internal server error'}. RestServer answers 400 VALIDATION_FAILED for the same write. Not driven through a booted Hono app: dispatch() plus the exits' own renderers. · evidence: the probe log in this session (PR body, 'Before and after'). · dedupe words: dispatcher data door NOT NULL 500; INTERNAL_ERROR not-null constraint hono catch-all; driver refusal classification dispatcher parity mapDataError; SQLITE_CONSTRAINT_NOTNULL dispatcher",
    "carrier: none (承接者:无) · noted, not filed. The same column-key sentence remains at the other REST doors that serve the NOT NULL classification: /data/:object/batch and /batch rows, import rows, the public form submit and data actions. None was measured with a labelled field, so this is in the PR's Acceptance notes only. Option A of open question 1 would cover them."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment · domain:cli seat 1 · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-20T04:59Z · amends claim 6282809559


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24715,
    "status": "done",
    "branch": "claude/issue-24715-not-null-field-label",
    "pr": "#24759",
    "session": "session_01SYEJqDL3NXxrJRBVqaMu9T — the dispatching PM's session (mode:subagent)",
    "premise_still_valid": true,
    "summary": "Patch round 1 (open question 2 answered B): .changeset/24715-not-null-field-label-types.md is now '@objectstack/types': minor with the line-initial 'Clause-②: yes (widening: three additive exports ...)'; the rest of its text is kept. Committed as 055d545 on the same branch; no merge of origin/main. @objectstack/rest stays patch / Clause-② no. Export-list measurement of the built packages/types/dist/index.d.ts (TypeScript checker, getExportsOfModule), origin/main 580fd4e against head 055d545: 122 → 125 names. diff: '+ NotNullRefusalSite', '+ notNullRefusalSite', '+ relabelNotNullRefusal', nothing removed. The index.d.mts twin gives the same three-line diff. Control: mapDataError and truncateClientMessage are present in both lists. packages/types is byte-identical between the merge base 7e7e605 and 580fd4e. Open question 1 was answered by the seat (a finding card, option A recorded); nothing more is owed in this PR. Round 0, unchanged: the RestServer POST/PATCH /data doors name a NOT NULL refusal by the field's label in the caller's locale. The types classification stays field-map-free, and its hint no longer prescribes required. A declared storage.notNull field gets a hint naming neither required nor os migrate. status, code, fields[0].field, fields[0].code and the key sets do not move. The dispatcher /data twin answers 500 INTERNAL_ERROR for this refusal and is untouched.",
    "tests": "Round 1 changed only a changeset .md, so no test re-run was owed; the round-0 readings stand for the unchanged source. New pins: packages/rest/src/data-not-null-field-label-24715.test.ts (7, real driver: PATCH/POST label + declared hint + unchanged envelope/key sets + row unmoved; zh-CN '销售价格不能为空' checked against the validator's own zh-CN required message for a sibling required field; bare zh negotiated; drift control; undeclared-column control), packages/rest/src/not-null-refusal-label.test.ts (5: label control, another table / another answer / failed read / undeclared column decline without asking sources), packages/types/src/data-error-classification.not-null-label.test.ts (10: site per dialect incl. Postgres with and without relation and MySQL, validator lookalike and copied body not recognised, hint holds for both cases, relabel moves text only). Updated: data-not-null-hint-20963.test.ts (its first-sentence required assertion inverted; it is now the failed-read control). Full suites at ee57ace, every project, exit captured per part: types local 29 files / 774 passed exit 0, types repo 1 / 11 passed exit 0; rest local 304 files / 6124 passed 326 skipped exit 0, rest repo 5 / 280 passed 18 skipped exit 0. Typecheck: pnpm --filter @objectstack/types typecheck exit 0; pnpm --filter @objectstack/rest typecheck exit 0 (check:test-typecheck 0 debt); tsc --listFiles shows both programs compile the new test files. Ablations, one per change via node scripts/ablation-replace.mjs (anchor hit x1, on-disk count and blob change asserted, restore proven by blob == HEAD blob and git diff HEAD empty; subjects resolve from source via relative imports, so no dist build between legs): A1 hint put back to the required prescription → types pin 1 failed / 9 passed (the hint case), blob 61ebd27afc24 → 2f324ae3d717 → 61ebd27afc24; A2 brand line deleted → 6 failed / 4 passed (4 site + 2 relabel cases), blob 61ebd27afc24 → b3d98ac5a64e → 61ebd27afc24; A3 door relabel declines every answer → 6 failed / 6 passed (PATCH, POST, zh-CN, zh, drift label, helper CONTROL; fixture, undeclared control and 4 declines stayed green), blob f2a76857df43 → e2383610aa51 → f2a76857df43. All three turned red, the expected direction. Census of old-text pins: git grep over all packages' tests for the hint wording → one pin made false (rest data-not-null-hint-20963), updated; the 'X is required' assertions elsewhere call mapDataError directly (sentence unchanged) or assert validator or import-row messages; none changed.",
    "gates": "Round 1 at 055d545: node scripts/pm/dispatch-gates.mjs --commands derived the same 67 commands as round 0. All 67 were re-run, each exit captured before any pipe; all 67 exit 0. That includes the families matched via the changed .changeset path: check-changeset-no-major.mjs --base origin/main and --self-test, check-adr-0087-registration.mjs --base origin/main and --self-test, check-empty-changeset.mjs --base origin/main and --self-test, check:changeset-gate-self-tests, check:objectui-changeset, check:pm-changeset-deadline-census, check:published-files, release-rehearsal-clone.mjs --self-test and release-pending-publish.mjs --self-test. check:dual-build-cjs-loads exited 0 after a cached full build (76/76 cache hits). dispatch-gates --ran: '67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN'. Check Changeset's local twin (GITHUB_EVENT_NAME=pull_request node scripts/check-changeset-no-major.mjs --base MERGE_BASE --event PAYLOAD) exits 0 on the live body ('LEVEL AXIS: this PR declares clause-② no ...') and exits 0 on the body with body_edits applied ('declares clause-② yes (widening) ... the declared widening is accounted for'). pnpm lint exit 0, no findings, at 055d545 (and at ee57ace in round 0). CI on PR #24759 head 055d545, read once: 39 check-runs, 31 success, 6 skipped, 2 in_progress. Lint & Repo Gates success, TypeScript Type Check success, Build Core success, Governed Surface Queue Guard success, Check Changeset success (on the body still declaring no). Test Core shards 1/6, 2/6, 5/6 and 6/6 success; 3/6 and 4/6 in_progress. Not waited on.",
    "mcp_calls": "0",
    "api_writes": "Round 1: 1 relay stroke (POST /repos/objectstack-ai/objectstack/dispatches → comment = POST /repos//issues/24715/comments, this report). The PR body was NOT patched: body_edits are returned for the seat. git push is not counted. Round 0: 2 relay strokes: pr_create = POST /repos/objectstack-ai/objectstack/pulls + POST /repos//issues/24759/assignees; comment = POST /repos//issues/24715/comments (comment 6283722892).",
    "deviations": [
    "Round 1: the worktree was recreated from the existing branch at ee57ace (= origin) and removed again after the push (HEAD 055d545 = origin, status clean). A throwaway detached worktree at origin/main 580fd4e was used only to build and read the before d.ts, then removed. origin/main was not merged (os-dev.md does not require it: no conflict, no main-caused red, no queue removal).",
    "Round 1: the changeset's line is 'Clause-②: yes (widening: …)'. The clause-② reader parses it as value yes, arm widening (readClause2Line verified). The PR body's line 2 is still 'no' until the seat applies body_edits; Check Changeset passes on both.",
    "Conflict, dispatch vs os-dev.md: the dispatch asks for pnpm lint as the lane's full union; os-dev.md calls repo-wide scans CI-owned and never owed. It was run anyway (exit 0, at ee57ace). That is an addition, not a narrowing.",
    "Conflict, dispatch done-criterion 1 ('both /data doors answer the label') vs the hold-off on status/code: the dispatcher twin answers 500 INTERNAL_ERROR, so it was not changed. See open question 1.",
    "Clause-② copied 'no' from the claim, as os-dev.md requires, and was not re-decided. The added @objectstack/types exports are flagged in open question 2.",
    "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). The harness attribution reminder asked for a model-named Co-Authored-By; per os-dev.md it yields to AGENTS.md.",
    "A temporary measurement probe (packages/runtime/src/zz-probe-24715.test.ts, then packages/rest/src/zz-probe-after-24715.test.ts) was run for the before/after wire bodies, deleted, and never committed.",
    "Worktree /home/user/objectstack-issue-24715 was removed after the PR opened (HEAD = origin branch ee57ace, status clean)."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: the seat's finding card (open question 1, answered in round 1) · noted, not filed by the dev: the dispatcher /data door answers 500 INTERNAL_ERROR 'Internal server error' for a driver NOT NULL refusal, where RestServer answers 400 VALIDATION_FAILED (measured at dispatch() + both exits' renderers, origin/main 7e7e605), and the other REST doors (/data/:object/batch, /batch, import rows, public form submit, data actions) keep the column-key sentence; option A (engine-side translation) recorded as the proposed direction · dedupe words: dispatcher data door NOT NULL 500; INTERNAL_ERROR not-null constraint hono catch-all; driver refusal classification dispatcher parity mapDataError"
    ],
    "body_edits": [
    {
    "old_str": "Clause-②: no\nBasis for the Clause-② line (the claim's, copied): message text only. The refusal's status, code and fields[] keys and codes do not move, and no accept set changes. ⚠️ Flagged for the seat in the report: this PR also adds three exports to @objectstack/types (notNullRefusalSite, relabelNotNullRefusal and the type NotNullRefusalSite). By the declaration's own question ("does the card widen an accept set or the public surface?") that is additive public-surface growth that the claim's basis did not foresee. The line above is copied, not re-decided.",
    "new_str": "Clause-②: yes (widening: three additive exports, notNullRefusalSite, relabelNotNullRefusal and the type NotNullRefusalSite, consumed by @objectstack/rest's /data doors; no accept set, status or code moves)\nBasis, per package: @objectstack/types widens its published surface by exactly those three names, which reach its built entry through export * (packages/types/src/index.ts:98). Measured on the built dist/index.d.ts export list, origin/main 580fd4ed3 against this branch: 122 → 125 names, three added, none removed. Its changeset is therefore minor, Clause-②: yes (widening). @objectstack/rest changes message text only: the refusal's status, code and fields[] keys and codes do not move, and its new module is not on its entry. Its changeset stays patch, Clause-②: no."
    },
    {
    "old_str": "All local runs below were at ee57acee6.",
    "new_str": "The test suites, typecheck and the first lint ran at ee57acee6. The one later commit, 055d54575, edits .changeset/24715-not-null-field-label-types.md alone (minor, Clause-②: yes (widening)). The derived gates and lint were re-run at 055d54575."
    },
    {
    "old_str": "- Derived gates. node scripts/pm/dispatch-gates.mjs --commands derived 67 commands, and all 67 were run with their exit codes recorded.\n - pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no dist/). It exited 0 after a full turbo run build.\n - dispatch-gates --ran then printed: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN.\n- Lint. pnpm lint (eslint . --no-inline-config, the whole repository, the lane's full union) exits 0 with no findings, at ee57acee6.",
    "new_str": "- Derived gates. node scripts/pm/dispatch-gates.mjs --commands derived 67 commands, and all 67 were run with their exit codes recorded.\n - At ee57acee6: pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no dist/). It exited 0 after a full turbo run build. dispatch-gates --ran then printed: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN.\n - Re-run at 055d54575 (the same 67 derived): all 67 exit 0, the changeset readers included (check-changeset-no-major.mjs, check-adr-0087-registration.mjs, check-empty-changeset.mjs, check:changeset-gate-self-tests, check:pm-changeset-deadline-census, check:published-files). dispatch-gates --ran printed: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN.\n - Check Changeset's level axis, run locally as check-changeset-no-major.mjs --event against this PR's payload: it passes with the body declaring no, and it passes with this body declaring yes (widening) ("the declared widening is accounted for").\n- Lint. pnpm lint (eslint . --no-inline-config, the whole repository, the lane's full union) exits 0 with no findings, at ee57acee6 and again at 055d54575."
    }
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24759 comment 6284129225 · domain:cli seat 1 · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-20T05:30Z · head 055d54575 (after patch round 1: Clause-②: yes, with minor on @objectstack/types). Landing waits on that head's checks.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #24759 → 3230d66cc · domain:cli seat 1 · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-20T06:14Z

    Reading Result
    PR #24759 object merged: true, merge commit 3230d66cc, merged 2026-10-20T06:13Z through the merge queue (enqueued 2026-10-20T05:42Z)
    origin/main 3230d66cc is an ancestor of origin/main, with a single parent (squash). The 10 files and +874 / −50 match what was reviewed at 055d54575 (round 1 changed one changeset line)
    queue branch no gh-readonly-queue/main/*pr-24759* head remains

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions