Repository navigation
Description
domain:devx#1 · os-warren · session_01JQwxbWBtYWqaV8NhvUrShB
Filed by the dispatching PM seat session for the W4-C3a runner session_01JQwxbZsvATYMNMArStUo2M, whose own write was refused by its session permissions (RUNNER rule 2: staged text, posted verbatim through the same door).
立卡门类别: ① 产品缺陷 — 具名落点 (firstTypeMismatch, the formula date-arithmetic build gate) + 复现
reach: 公开入口实测一次错误结果 — os build / os validate / os lint exit 0 on record.end_date - record.start_date, and REST GET /api/v1/data/... then returns the field as null (two fresh boots at 7fa9873)
业务理由: an app author who writes the most natural date-span formula (end minus start, or days since a date) gets a green build and then blank values in production, with only a server WARN. The gate exists to catch exactly this "runtime surprise" (#3306), and it already catches the + 1 shape. Not doing it: span and age fields silently show nothing for every record.
动手的读者: the seat owning the formula build gate (packages/lint / packages/formula); triage first touch confirms the lane, then os-dev.
查重: "date arithmetic" → 34 hits, 0 open; daysBetween → 21 hits, 0 open (#3306 closed is the + 1 fix, #28660 closed is unparseable date()). No duplicate.
QA-source: #28942 · api-backend.formula-gates · observed beside the clauses
What happens. The build-time date-arithmetic gate catches (record.end - record.start) + 1 but not the bare subtraction itself, so the most natural span formula builds green and reads null at runtime.
Mechanism. firstTypeMismatch types date fields as google.protobuf.Timestamp. Timestamp - Timestamp is a legal CEL Duration, so only a later Duration + int (the #3306 … + 1 shape) faults the check. At runtime a Field.date value is a 'YYYY-MM-DD' string, so record.end_date - record.start_date fails with no such overload: dyn‹string› - dyn‹string› and the field reads null; today() - record.start_date evaluates to a Duration, which serialises as {}. os build, os validate and os lint all exit 0 on both, with no warning — the "runtime surprise" the gate's own message describes. Related: #3306 (closed), which fixed the + 1 shape.
Reproduction (framework 7fa9873; reproduced on two fresh boots and DBs):
- Scaffold a blank app (
create-objectstack -t blank) and add a plain objectqa_doctor_time_off(sharingModel public_read_write) with fieldstitle(text),start_dateandend_date(Field.date), and formulas:qa_span_bare: 'record.end_date - record.start_date'qa_span_daysbetween: 'daysBetween(record.start_date, record.end_date)'qa_since_today: 'today() - record.start_date'
os build,os validate,os lint→ all exit 0.os dev --seed-admin -p PORT -d file:RUN/data.db; as adminPOST /api/v1/data/qa_doctor_time_off {"title":"t1","start_date":"2026-11-01","end_date":"2026-11-05"}→ 201.GET /api/v1/data/qa_doctor_time_off/ID→qa_span_bare: null,qa_span_daysbetween: 4,qa_since_today: {}. Server log:WARN formula field 'qa_span_bare' … could not be evaluated … (runtime: no such overload: dyn‹string› - dyn‹string›).- Second boot, new DB, dates 2026-10-01 → 2026-10-31:
null/30/{}.
- Expected: a build-time error with the
daysBetweenprescription, as(end - start) + 1already gets (date arithmetic google.protobuf.Duration + int, exit 1). - Actual: exit 0 at build, then null or
{}at runtime.
Full evidence: QA run record #28942.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: api-backend.formula-gates
Triage: priority:p2 · domain:server · pm:queue · 2026-11-02T05:03Z
⛔ Not a claim.
firstTypeMismatch(packages/formula/src/validate.ts) types date fields as Timestamp, so the gate acceptsend - startandtoday() - d, and those read null or{}at runtime. The formula package isdomain:server. A green build that then shows blank values is a user-visible behaviour defect, so it is p2. The location and repro are named, so it goes topm:queue.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo
Claim: PM loop round 4 · card thread · 2026-11-02T05:22Z
Session:
session_0186vfjHHMsRXC9EUT9hTFMo(the seat); the card thread issession_01FEkQ5nXr5BHHskLQ7qUH5xAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-3vt4ig(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」. With 2026-11-01T12:11Z 「定级你可以自己决定」 (triage graded this card) and 2026-11-02T05:15Z 「队列进了很多任务,抓紧派发」; seat 1 split the queue and gave this card to seat 2. Seat's route: the formula gate refuses date minus date and Timestamp minus date, by typing date-field operands as what the runtime holds, and prescribes
daysBetween(…). Shipped formulas are measured first, and the card is handed back if any would be refused.File surface:
packages/formula/src/validate.tstyping and the faces that call it (build, validate, lint, save door); their tests; ADR-0087 D3 entry; changesetContainer & model:
M,mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: mediumClause-②: yes (narrowing) expected; the dev measures and declares; CONTRACT_REVIEW_TIER review
Metadata-protocol: narrowed (expected: authors' formulas that built are now refused; minor, BREAKING banner, ADR-0087 D3 entry; the dev measures and declares)
Responsibility: the dev measures shipped formulas, then adds the typing with one judge for every face, red first with ablation; the seat reviews and lands
Thread-read: none
Premise: measured by triage (
6569503923) on mainSerial constraints cleared: none; overlap re-read of open PRs on packages/formula/src/ and the lint formula gates before pr_create
Resume-from: none — fresh from
origin/mainState: →
pm:dispatched, assigned toos-project-manager, recorded by this claim.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-project-manager · session_01FEkQ5nXr5BHHskLQ7qUH5x
os-dev-report
{
"issue": 28944,
"status": "done",
"branch": "claude/project-thread-3vt4ig",
"pr": "#29058",
"session": "session_01FEkQ5nXr5BHHskLQ7qUH5x",
"premise_still_valid": true,
"summary": "The type-soundness gate now re-checks what its Timestamp typing passes with every date/datetime field declared as DateText, the text the runtime holds (firstDateTextVerdict in packages/formula/src/cel-engine.ts, with only the overloads the evaluator answers). record.end_date - record.start_date (either kind) is refused as date-text-arithmetic (reads null) and a formula computing a Duration (today() - record.start_date, now() - record.start_at) as date-duration-value (reads {}), both prescribing daysBetween. The Timestamp check still answers first, so the + 1 shape keeps its date-arithmetic words; build/validate/lint and the save door all run validateExpression, measured live. Shipped formulas measured first (packages, examples, skills, content docs, hotcrm d4ffe99): none subtracts dates. Datetime measured live: end_at - start_at reads null, now() - start_at reads {}, so it is included.",
"tests": "At head e029ea7: formula test+typecheck 64/64 files, 1628/1628 tests, VERDICT command-exit 0; lint test+typecheck 189/189, 7830/7830, VERDICT 0; spec vitest src/migrations 5/5, 194/194. Red first: three formula src files restored to 135054e, validate-date-text-arithmetic.test.ts 10 failed / 17 passed (every refused row red, every control and the + 1 test green on base), restored, git diff HEAD empty. Live red at 135054e (os dev --fresh, examples/app-todo probe object): os validate/lint/build exit 0; REST qa_span_bare null, qa_span_daysbetween 4, qa_since_today {}, qa_dt_span null, qa_dt_since_now {}. Live green on branch: validate/lint/build exit 1 with four expression-invalid errors; save door PUT /api/v1/meta/object 422 INVALID_METADATA for both shapes, 200 for daysBetween. Ablation via scripts/ablation-replace.mjs (src-resolved suites, no dist leg): dropping the DateText typing (blob d48b48384db5 to 0157c4c635ea) 8 failed / 180 passed, only date-text-arithmetic cases; dropping the duration-value arm (787a1532d20b to b6de2a9964e0) 7 failed / 181 passed, only date-duration-value cases; both restored blob == HEAD, git diff HEAD empty.",
"gates": "dispatch-gates --commands at e029ea7: 106 commands, all exit 0 (check-plugin-teardown-shape --self-test exit 3 on the shallow clone, exit 0 after git fetch --depth=1 of 621a487); --ran: 106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN. Record: pm-orders/server/28944-gates-ran.txt. CI check-runs on PR 29058: in_progress at report time.",
"dogfood_battery": "e029ea77 · os-verify-lock: VERDICT command-exit 0 (held 1747s) · Test Files 383 passed, 3 skipped (386) · Tests 3163 passed, 28 skipped (3191) · no failure",
"mcp_calls": "3 — mcp__github__issue_read (get, get_comments), mcp__github__list_pull_requests; no write tool. One REST read through gh api (the claim comment 6569744035).",
"api_writes": "2 — fleet-write relay pr_create (POST repos/objectstack-ai/objectstack/dispatches, run 41400468133, read-back identical, no assignees); this os-dev-report comment via post-stamped. git push x2 to the harness branch (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: published skill text made false by this PR · skills/objectstack-formula/SKILL.md No-date-arithmetic callout says end - start does not fault and yields a duration stored as {} (it read null and is now refused at build); skills/objectstack-ui/evals/analytics-inline-vs-dataset.json id 6 prompt describes a stored end_date - start_date formula · Skills-docs: follow-up owed (Tier H skills/**, outside the claim's file surface) · dedupe words: objectstack-formula SKILL date arithmetic, end - start duration, daysBetween skill callout",
"carrier: none · noted, not filed: @objectstack/rest bundles a copy of @objectstack/formula into dist (UNSOUND_OVERLOAD_RE and the refusal codes appear in packages/rest/dist/index.js); it follows on rest's next build"
]
}
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo
Release:
session_0186vfjHHMsRXC9EUT9hTFMo· cause: the maintainer's wrap-up (「进展中的开发先提交代码然后暂停,先确保已有的pr都合并」, 「草稿的也交给下一任,在座位帖写清楚」); seat 2 signs off · next: PR #29058 is ACCEPTed (6571004528) with review PASS (6570993197) and is in the merge queue;Fixes #28944closes this card on merge. Next: landing record; the skills follow-up #29061 unblocks on merge. ⛔ Do not re-dispatch while #29058 is queued.Resume-from:
claude/project-thread-3vt4ig@e029ea772b1555ea33b2946e0978ff4175dbdd3eState:
pm:dispatched→pm:queue, assignee cleared, in this same relay stroke. Seat hand-off: mailbox #28284 comment 6571506250; seat post #20966.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
Claim: PM loop round 1 · seat takeover · 2026-11-02T08:12Z
Session:
session_01ADHkmQxwoGb5fSnrFkpxmt(the seat)Account:
os-steve(the seat's linked user; the card's assignee)Branch:
claude/project-thread-3vt4ig(continued from the released claim; PR #29058)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: the maintainer, verbatim: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」 (os-steve project "objectstack spec"). Takes over the hand-back
Release:6571743956 of seat 2's previous term (session_0186vfjHHMsRXC9EUT9hTFMo).File surface:
packages/formula/src/validate.tstyping and the faces that call it; their tests; ADR-0087 D3 entry; changeset (unchanged from claim 6569744035)Container & model:
M,mode:thread,model: opus(as the previous claim),effort: mediumClause-②: yes (narrowing) — as declared on PR #29058; CONTRACT_REVIEW_TIER review PASS 6570993197 on record
Metadata-protocol: narrowed — as declared on PR #29058
Responsibility: the dev added the date-subtraction typing with one judge for every face (claim 6569744035); the seat follows PR #29058 to MERGED; skills follow-up #29061 unblocks then
Thread-read: 6571743956
Premise: holds (measured by the previous term's dev; re-read on the PR head before ACCEPT)
Serial constraints cleared: none new; the PR's file list is re-read against live claims before landing
Resume-from:
claude/project-thread-3vt4ig@e029ea772b1555ea33b2946e0978ff4175dbdd3eStatus: PR #29058 is ACCEPTed (6571004528) and in the merge queue; no new dev work. ⛔ No re-dispatch while it is queued.
State:
pm:queue→pm:dispatched, assigned toos-steve, recorded by this claim.