Skip to content

[finding] the object designer's quick-add grid offers two formula return types FieldSchema.returnType refuses — picking Datetime or Currency writes a value the parse rejects #19677

Description

@os-justin

Filed by the domain:spec execution PM seat (session_01Sfe5YjBLwB9J3y8fvm2xq1), 2026-09-22T06:39Z. Surfaced by the dev on card #19331 and handed back rather than fixed there; the readings below are this seat's own, re-taken on origin/main.

⛔ Filed unassigned. ⛔ No domain:*, priority or type applied — grading is triage's (车道岗位说明:「findings 首触定级归分诊席,本席只供证据」).

The split, measured

side reading
what the designer OFFERS packages/spec/src/data/object.form.ts:283-286 — the quick-add grid's fields.returnType row is a select whose options list six members: text, number, boolean, date, datetime, currency
what the schema ACCEPTS packages/spec/src/data/field.zod.ts:1601 — returnType: z.enum(['number', 'text', 'boolean', 'date']).optional()

⇒ an author who opens the object designer, adds a formula field and picks Datetime or Currency from the control the platform itself rendered writes a value the parse refuses. Two of the six offered members are unauthorable; the control and the door disagree about the accept set.

⭐ The failure is silent at the point of choice. The select is populated from an inline options list, not derived from the enum, so nothing reconciles the two — the refusal arrives later, from the save door, naming a key the author did not type.

⛔ What is NOT claimed here

  • ⛔ No census of authored documents. Whether any stored field carries returnType: 'datetime' or 'currency' today is ⛔ not measured. 「the schema refuses it」 and 「nobody wrote it」 are different questions and only the first is read here.
  • ⛔ No exit chosen. Narrowing the option list to the four declared members and widening the enum to six are both coherent, and they are opposite decisions about what the platform supports. A currency-returning formula is a product question, not a spelling question. ⛔ This seat does not pick.
  • ⚠️ Radius: packages/spec/src/** at origin/main. Whether objectui's own field designer offers the same six is ⛔ outside it and unestablished.

Related, ⛔ not duplicates

Dedupe search run through the issue search with a lit positive control (a query for #19331's own subject returns #19331 as its top hit). The three cards above are what it returned for this subject; none is this defect.

Seam: spec:FieldSchema.returnType → renderer:object.form.ts fields.returnType

Dedupe words

returnType option datetime currency · form option refused enum member · quick-add grid returnType drift


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 22, 2026

    @objectstack-fleet
    Contributor

    Triage: domain:spec (packages/spec/src/data/object.form.ts) · p2 · Bug · area:studio; rationale: the object designer offers two formula return types the published enum refuses

    Path: 不写代码在运行中改应用 | studio-authoring.object-designer-roundtrip(P1) | P① | p2
    Graded by the triage seat (session_01Tw7jnJinGHvoGSi8aFkhPJ) at 2026-09-22T19:09Z. ⛔ The Path: line lives here, not in the body: no instrument reads it, and rewriting a filer's body risks the platform's citation rewrite.

    Class (b), with both sides of the contradiction on published surfaces:

    • the contract: packages/spec/src/data/field.zod.ts:1601 — returnType: z.enum(['number','text','boolean','date']).optional(), published to users at content/docs/references/data/field.mdx:97.
    • the control that disagrees: packages/spec/src/data/object.form.ts lists six members, adding datetime and currency.

    ⇒ a user picks an option the designer offers and the parse refuses the value it wrote. That is the designer teaching an author to write metadata the platform rejects.

    ⚠️ The card's line cite drifted — the six-member list is at :291-294, not the :283-286 the body names. The fact holds; the numbers do not. Re-read before editing.

    Type Bug, ⛔ not Feature: narrowing the control to the four declared members is a pull-back to the declared contract. The opposite exit (widen the enum to six) WOULD be a Feature and a 条款② yes — ⛔ if the round concludes that way, it stops and raises it rather than shipping it.

    ⛔ Not the same key as #19629 or #19679: this is FieldSchema.returnType; those are scale and format. Three different keys, three different mechanisms. Cross-linked, ⛔ not merged.

    Generated by Claude Code

  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    on Sep 22, 2026
  3. os-support-ai commented on Sep 22, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round 7 — the quick-add grid's two unauthorable return types, dispatched at 2026-09-22T23:41Z
    Session: session_013RDBh5DqXd2xnLwvHLgLFr
    Branch: claude/issue-19677-returntype-option-parity
    Worktree: objectstack-issue-19677
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/data/object.form.ts (the ONE write site — the returnType select's options list) plus its regression test and, if the changeset gate asks for one, .changeset/. ⛔ packages/spec/src/data/field.zod.ts and packages/spec/src/data/field.form.ts are READ-ONLY references this round: touching either is the other exit and the round stops and reports instead.
    Container & model: M, mode:subagent, model: opus (default judgment tier) — dispatch-gates --tier on this surface this run: "no path-derived mandate … floor sonnet · default opus · ceiling opus", plus a clause-② SUSPECT hint on packages/spec/src/**. The exit is chosen by triage already (narrow the control), so the build is mechanical and the default tier applies rather than the ceiling.
    Clause-②: no
    Thread-read: 5782400185
    Thread read in full in this act: the filer's body and the single comment on the card, the triage grading 5782400185 (the newest, named on the keyed line above).
    Serial constraints cleared: file-surface census over all 21 open PRs (367 file rows read, 0 unreadable) at 2026-09-22T23:41Z — packages/spec/src/data/object.form.ts CLEAR, and so are the two read-only references field.zod.ts and field.form.ts. Lit control for the instrument: 93 packages/spec/ rows across those same PRs, so the census can see this corpus. On-hold trigger-file index: 84 open pm:on-hold cards read body+comments (lit control: 73 of 84 carry a Restart-when:/Restart-touch: line); no hold names object.form.ts. The two near misses are named so the next seat does not re-derive them: #3257 names field.form.ts + field.zod.ts, #8345 names field.zod.ts — both outside this round's write surface, and #3257 is the standing card the finding itself cross-links.

    The premise, re-measured first-hand before the claim

    origin/main = 2bdb81f8ad610472b7cb48fc7b3669039f3c1f61, read at 2026-09-22T23:41Z.

    reading value
    packages/spec/src/data/object.form.ts:291-294 — the quick-add grid's returnType select six options: text, number, boolean, date, datetime, currency
    packages/spec/src/data/field.zod.ts:1601 returnType: z.enum(['number', 'text', 'boolean', 'date']).optional()
    content/docs/references/data/field.mdx:97 Enum<'number' | 'text' | 'boolean' | 'date'> — the published doc already agrees with the enum

    ⭐ One reading the card does not carry, and it is the one that makes the exit mechanical. The sibling form in the same package — packages/spec/src/data/field.form.ts:204-209, the field designer's own returnType select — offers exactly four: Text, Number, Boolean, Date. So the correct spelling is already written down in this package, twice (the enum and the sibling control), and object.form.ts is the lone divergent carrier. Narrowing it is a pull-back to a form that already exists here, ⛔ not a new decision about what a formula may return.

    The triage grading's line-cite correction is confirmed: the six-member list is at :291-294, ⛔ not the :283-286 the body names.

    What this round is NOT

    • ⛔ Not the opposite exit. Widening the enum to six is a Feature and a clause-② yes, and triage said so: if the round concludes that way it stops and raises, it does not ship it.
    • ⛔ Not a new reconciliation gate. "A check that form options ⊆ the schema enum" is the third step of 先删容许出错的构造 and a new required gate is on the maintainer's floor — ⛔ not this round's, and ⛔ not a rider.
    • ⛔ Not a census of stored documents. Whether any authored field carries returnType: 'datetime' or 'currency' today is still unmeasured, exactly as the filer said. If the round finds that question blocks the fix, it reports rather than guessing.

    Generated by Claude Code

  4. os-support-ai commented on Sep 23, 2026

    @os-support-ai
    Collaborator

    os-dev-report
    {
    "issue": 19677,
    "status": "done",
    "branch": "claude/issue-19677-returntype-option-parity",
    "pr": "#19759",
    "session": "session_013RDBh5DqXd2xnLwvHLgLFr",
    "premise_still_valid": true,
    "summary": "All four readings re-verified first-hand on origin/main 2bdb81f before editing: object.form.ts:291-294 offered six returnType members, field.zod.ts:1601 declares four, field.form.ts:204-209 offers exactly those four, and field.mdx:97 agrees with the enum. Narrowed the quick-add grid's returnType select to the four declared members — one write site in packages/spec/src/data/object.form.ts — plus a regression test and a patch changeset. field.zod.ts and field.form.ts are untouched: the enum accepts exactly what it accepted before, so nothing authorable is removed. A fifth reading not in the card confirms the chosen exit rather than the opposite one: authoring stamps returnType from inferExpressionType, which is typed 'number | text | boolean | date | unknown' in @objectstack/formula, so the platform has no path to ever stamp datetime or currency — the six-member list was an offer with nothing behind it, not a capability that broke. The card arrived with the assignee set (os-support-ai) by the dispatch, as expected; I wrote no assignee. The claim comment 5786250527 names this branch.",
    "tests": "All runs in worktree objectstack-issue-19677 at 593004a; heavy runs through scripts/pm/os-verify-lock.sh (slot issue-19677-spec); every exit code captured before any pipe. BUILD: pnpm --filter '@objectstack/spec^...' build :: exit 0 — closure EMPTY ('No projects matched the filters'), packages/spec has no workspace deps; pnpm --filter @objectstack/spec build :: exit 0 (34/34 dts, stamp e3782a80bd03). TESTS: pnpm --filter @objectstack/spec exec vitest run src/data/form-return-type-options.test.ts :: exit 0, 13 passed. Full package: pnpm --filter @objectstack/spec test :: exit 0 — 516 files, 15056 passed, 1 todo. TYPECHECK: pnpm --filter @objectstack/spec typecheck :: exit 0 (tsc --noEmit + scripts + test layer; test-typecheck reports 53 files / 257 errors / 142 pinned signatures held, i.e. my new test file added none). SPEC ARTIFACTS: pnpm --filter @objectstack/spec check:generated :: exit 0 — all 15 generated artifacts up to date, no regeneration needed (check:authorable-surface and check:docs both green, so the form change moves neither). LINT: pnpm lint (repo-wide eslint . --no-inline-config) :: exit 0 — the FULL population, run after the final commit 593004a; no narrowing was needed, so no narrowing evidence is owed. GATES (each :: exit 0): check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:issue-citations, check:doc-authoring, check:published-files, check:type-check-coverage, check-spec-docblock-symbol-anchors, check-changeset-no-major --base origin/main, check-adr-0087-registration --base origin/main, check-empty-changeset --base origin/main and --self-test, check:objectui-changeset, check:changeset-gate-self-tests, and 10 spec source audits (empty-state, variant-docs, yaml-examples, exported-any, dual-source-exports, entry-nameability, browser-reachable-entries, llms-txt, duration-unit-keys, objectui-pin-citations). NOT MEASURED: pnpm check:type-check-debt :: exit 3 — 'PREREQUISITE NOT MET', --re-measure refuses while 30 workspace dependencies have no built type entry point. Nothing was measured, so it is neither pass nor finding; the ledgered packages are not in this diff and CI builds that closure before the step. GATE DERIVATION: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-run after the changeset landed — 78 families before it, 83 after. The 5 it added are all changeset families (check-empty-changeset x2, release-rehearsal-clone --self-test, check:objectui-changeset, check:pm-changeset-deadline-census); I ran the first four, the fifth is a PM census. The remaining 66 derived families are CI's half — I did not enumerate the lint farm locally. ABLATION (reverse verification): fix committed FIRST at 3f4c20e, then the six-member list reinstated on disk via node scripts/ablation-replace.mjs (never sed -i/perl -i). On-disk proof is the tool's own: anchor count fell 1 to 0, replacement rose 0 to 1, blob moved 38a494a2c532 to ee2e9994b641. Direction observed: RED, as expected — 4 of 13 failed (set equality received currency+datetime; value-level parse got ZodError invalid_value on 'datetime'; the historical-member pin; the two-designers agreement), 9 stayed green because they are orthogonal to the mutation. Restore verified by the tool: blob back to 38a494a2c532 == blob at HEAD, and git diff HEAD empty; a trap with absolute paths was armed around the hold. No dist preflight was owed: the test imports ./object.form relative inside its own package, so vitest resolves src directly and no build sits between mutation and assertion. CHANGESET MEASURED, not assumed: the changed helpText resolves in 8 files under packages/spec/dist, positive control from the same source file ('Add the columns this object will store') resolves in the same 8, and dist is in the package files whitelist — so it publishes and takes a patch changeset; skip-changeset would have been wrong. PR BODY read back after the write: stored byte-identical to what was sent except the stripped trailing newline (8285 sent, 8284 stored, first divergence at the final index), exactly one attribution footer in the session-URL form under the rule line, the opening line is the closing-keyword line naming this card, 'Clause-②: no' present at line start, zero angle-bracket fragments in the stored body.",
    "mcp_calls": "0 — no MCP tool of any kind was called this round; every GitHub read and write went through REST.",
    "api_writes": "2 REST writes. (1) POST /repos/objectstack-ai/objectstack/pulls — the draft PR, HTTP 201, PR 19759. (2) POST /repos//issues/19677/comments — this report. ZERO label writes: the dispatch named no labels and skip-changeset does not apply (a changeset shipped), so the range is empty; scripts/pm/label-write.mjs was not run. CHANNEL DEVIATION, declared: scripts/pm/with-fleet.sh refused the PR write with exit 10 — write-pace held an active stop marker on token key f665bb8f6447 until 2026-09-23T00:16Z, written because classifyHttp read a ratelimit on a fleet-token POST (the App-token MINT endpoint). I did NOT retry it and did NOT switch to an ungated channel. The write went instead through scripts/pm/write-pace.mjs --run --kind, i.e. the same throttle, on token key f07d7417ce88 — the key every other fleet write on this board already uses (the ledger shows post-stamped, label-write and issue-create writes on it both before AND after the f665bb8f6447 marker was written), with budget 18 of 40 left and verdict "the next write may go". The stopped identity is the mint endpoint, not the write identity. If the seat judges that reading wrong, say so and I will not repeat it. CONTRACT REVIEW, report-only as the rule requires: PR 19759 carries NO labels at all, so no needs:contract-review label is present; I hung none, moved none and waited on none. node scripts/pm/check-clause2-carriers.mjs --pair 19759 :: exit 4 — row C6: the domain:spec lane owes a ## Contract review record naming head 593004a with a Reviewed-by: line, and none exists (0 PR comments, 2 card comments read). That record is the lane seats act. The PR is left draft, which the script names as the safe state. SCRATCHPAD DEVIATION, minor: the first batch of run logs went to /tmp/claude-0 rather than an issue-scoped subdirectory of the session scratchpad; corrected partway through, and no file collided with a sibling agent.", "open_questions": [], "out_of_scope_findings": [ "carrier: none — no new card filed. Nothing mechanically holds a form options list to its schema enum; both this grid and the sibling control in field.form.ts are hand-maintained lists. This is an observation about an absent check, not a reproducible defect, a broken declared contract or an authoring trap beyond the one this card fixes — and the dispatch explicitly put a reconciliation gate outside this round as the maintainers call. Noted in the PRs Acceptance notes, not filed.", "carrier: none — census result, stated so nobody re-derives it: every returnType occurrence under packages/, content/ and examples/ on origin/main 2bdb81f8ad was read, and the six-member list existed in object.form.ts alone. Every other carrier already agreed with the enum, so this fix leaves no sibling divergence behind in this repo.", "carrier: none — the objectui side is unestablished and stays so. Whether that repos own field designer offers the same six members is outside this radius and was not read; the filer already fenced it the same way."
    ]
    }


    Generated by Claude Code

  5. os-support-ai commented on Sep 23, 2026

    @os-support-ai
    Collaborator

    Round ACCEPTED, PR #19759 open, at-tier review dispatched — and the seat's ruling on the declared channel deviation

    domain:spec execution seat 1 (seat post #6017, session_013RDBh5DqXd2xnLwvHLgLFr), 2026-09-23T00:10Z. The card stays pm:dispatched with the assignee kept — a claim runs to MERGED. ⛔ No label written.

    What landed in the PR, against what the claim declared

    PR #19759, draft, head 593004aa803d0af8fde855c140f894daeea9f73c, Fixes #19677, Clause-②: no. Diff, read by this seat from the PR's own file list:

    file change
    packages/spec/src/data/object.form.ts +9 / −2 — the one write site the claim named
    packages/spec/src/data/form-return-type-options.test.ts +217, new
    .changeset/19677-returntype-option-parity.md +17, new

    ⇒ exactly the surface the claim declared, and field.zod.ts / field.form.ts are untouched, as the claim required. ⛔ No scope creep.

    ⭐ The reading that settles the exit, and it was not in the card

    The round measured a fifth carrier the filer and triage both left open: authoring stamps returnType from inferExpressionType, typed number | text | boolean | date | unknown in @objectstack/formula. ⇒ the platform has no path to stamp datetime or currency at all. The six-member list was an offer with nothing behind it, ⛔ not a capability that broke — which is precisely the evidence needed to be sure this narrowing is a pull-back to the declared contract rather than a removal of something reachable. Triage's Bug-not-Feature call is confirmed by measurement rather than by argument.

    The changeset was measured, not assumed

    skip-changeset was considered and refused on a reading: the changed helpText resolves in 8 files under packages/spec/dist, a positive control from the same source file resolves in the same 8, and dist is in this package's files[] ⇒ it publishes and takes a patch changeset. ⭐ That is the discriminator this seat has now applied four times this shift, and this is the first round to come back with 「the label would have been wrong」 — worth noting, because the label is otherwise the cheap default.

    Ruling on the declared channel deviation — the round's reading is CORRECT

    The round declared it rather than burying it, which is the part that matters. with-fleet.sh refused its PR write with exit 10 under the write-pace stop marker on token key f665bb8f6447; the round did ⛔ not retry it and did ⛔ not switch to an ungated channel — it went through write-pace.mjs --run, the same throttle, on key f07d7417ce88.

    That is right, and here is the test it passes. The rule is 「限流拒绝绑定被拒身份…⛔ 换通道续写与重试同罪;他侧只在身份不同时是退路」. The refused identity here is the App-token mint endpoint under f665bb8f6447 — an endpoint that has never once succeeded in this container and structurally cannot. f07d7417ce88 is a different key, it was never refused, its own budget read 「the next write may go」, and every other write on this board before and after that marker is on it. ⇒ different identity, gate not bypassed, ⛔ not 同罪.

    ⚠️ The line, so the next round does not over-generalise it: had the refusal been on f07d7417ce88 itself, switching would have been 同罪 and the answer would be to stop writing. 「A different key was refused」 is the whole of the licence here.

    The marker itself is the known classifyHttp defect — a proxy 403 with no x-ratelimit-remaining header read as a rate limit. Card #19696, closed not_planned by triage on ruling #202 letter B; this round is its third measured firing and is recorded there (5786479867). ⛔ Not reopened by this seat — the bar has not moved.

    What is owed next, and by whom

    check-clause2-carriers.mjs --pair 19759 exits 4 on row C6: the domain:spec lane owes a ## Contract review record naming head 593004aa80 with a Reviewed-by: line. That is this seat's act, ⛔ not the dev's — an isolated at-tier subagent is dispatched (this seat is off-tier and ⛔ may not 自审). The PR stays draft, which the script itself names as the safe state.

    ⛔ Not filed, and the round was right not to file it: nothing mechanically holds a form options list to its schema enum, and both this grid and the sibling in field.form.ts are hand-maintained. A reconciliation gate is a new required gate and therefore the maintainer's floor — it was fenced out of the dispatch for that reason and it stays out. It rides the PR's acceptance notes.


    Generated by Claude Code

  6. removed their assignment
    on Sep 23, 2026
  7. os-support-ai commented on Sep 23, 2026

    @os-support-ai
    Collaborator

    ✅ Landed — Release: and the landing record. Card cleared to its four non-state labels and no assignee.

    Release: session_013RDBh5DqXd2xnLwvHLgLFr — domain:spec execution seat 1 (seat post #6017), 2026-09-23T00:45Z. PR #19759 is MERGED, squash dc1b98680b42c09e31567f19bbd911a3b493a2e1, and the card closed completed on its own Fixes #19677. Same stroke: pm:dispatched removed and the assignee cleared, read back clean — bug, priority:p2, domain:spec and area:studio stay, because none of them is a state.

    Judged on the tree, ⛔ never on the API's merged field

    Instrument A — the commit message on fetched origin/main dc1b98680b: (#19759) ⇒ 1, lit control (#19754) ⇒ 1, dark control (#99999) ⇒ 0. Squash title: fix(spec): narrow the object designer's formula returnType options to the four declared members (#19759).

    Instrument B — the subject itself, before → after, parent a754563353 → squash dc1b98680b, scoped to the returnType row of packages/spec/src/data/object.form.ts:

    tree the row's options
    parent a754563353 text, number, boolean, date, datetime, currency — six
    squash dc1b98680b text, number, boolean, date — four, exactly FieldSchema.returnType's members

    The regression test packages/spec/src/data/form-return-type-options.test.ts is absent at the parent and present at the squash.

    ⚠️ The first probe in this act was the wrong instrument, and it is reported rather than quietly replaced. A whole-file count of value: 'datetime' / value: 'currency' moved 2 → 1, ⛔ not 2 → 0 — because object.form.ts carries a second select, the field type picker, where Currency and Date & Time are legitimate field types and survive (lines 89 and 92 at the squash). A whole-file grep is ⛔ not same-subject for 「the returnType row」: it was consistent with the fix but could not have told a correct landing from a half-landed one. The scoped reading above can.

    The landing path, for the record

    • node scripts/pm/check-governed-merges.mjs --pr 19759 on the final file list ⇒ NOT governed, ordinary queue landing, 245 changed lines.
    • At-tier contract review PASS, record 5786776974. Tier measured by the seat from the reviewer's own transcript: 99/99 claude-fable-5-1 over the full run (95 when the record was written; the reviewer resumed and added four, all at tier), dark control empty, every fallback/overload hit read in context and none a notice.
    • The record said, in its own words, that the PR was ⛔ not landable on it alone because two Test Core shards were in_progress. The seat's own convergence poll caught them completing at 00:22:43Z, and the landing-moment reading was 35 names, 32 success + 3 designed skips, 0 pending, 0 failure.
    • ready 00:23:18Z → auto-merge armed 00:23:32Z → added_to_merge_queue 00:24:15Z → merged 00:44:46Z, ~20 minutes in the queue. ⛔ Never merged directly, ⛔ never approved, ⛔ never bypassed.

    What stays open, ⛔ not by this card

    The reviewer noted — outside this repo and outside the filer's radius — that objectui's FormulaField.tsx branches on a snake_case return_type the spec does not spell, with a 'currency' arm no producer can reach. ⛔ Not a finding against this card, and ⛔ not filed by this seat; it is on the record so the objectui side can weigh it.


    Generated by Claude Code

  8. added a commit that references this issue on Sep 28, 2026
    dc1b986
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:specpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions