Repository navigation
Commit 34ec30d
docs(adr-0053): addendum D-G1 — a client-evaluated today() names the compute-tz reference day, UTC while none reaches the client (#20409)
Fixes #20394
Clause-②: no
## What this changes
One file, `docs/adr/0053-date-and-datetime-semantics.md`, +13 / -1
lines, prose only:
- **Addendum D-G1 (2026-09-28)**, nested under Phase 2 item 6 (`today()`
/ `daysFromNow()` / `daysAgo()` become timezone-aware), just before item
7. It records the maintainer's ruling A on objectui#10903: a formula
evaluated in the client (objectui's expression evaluator,
`@object-ui/core` `FormulaFunctions`, where the function is spelled
`TODAY()`) resolves `today()` to the same reference day the server
resolves for that user on the compute-tz axis (items 5–6). While no
reference timezone reaches the client, it resolves to the UTC day. One
"today" per execution context, so a preview and the saved answer agree.
The two options not taken are also recorded: the viewer's local day, and
the UTC day as the permanent rule. The addendum names threading the
reference timezone to the client as a follow-up once Phase 2 exposes it.
- **Consumers line**: `@object-ui/core` (`FormulaFunctions`, the
client-evaluated `TODAY()`; addendum D-G1) now sits next to
`@objectstack/formula`.
The objectui half of ruling A (the `FormulaFunctions` `TODAY()` JSDoc)
is in objectui PR 10915. That JSDoc cites this ADR only by "Phase 2
items 5 and 6", not by a decision id, so choosing the id D-G1 here
breaks no citation.
The wording follows the seat's ruling on the cross-repo note (comment
5865501291). It says "the reference day the server resolves for that
user". It does not restate the resolver's precedence, and it does not
claim that a per-user override takes effect today. See Acceptance notes.
## Gates — all run at `1b063d17` (`git rev-parse --short HEAD`), exit
code captured before any pipe
The list comes from `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands`, which found 19 commands. The
`--ran` reconciliation reported `19 derived famil(ies) accounted for —
19 run, 0 NOT-MEASURED (a DERIVED zero — all 19 recorded an exit code
and none of them is 3)`.
| command | exit | verdict line |
|---|---|---|
| `node scripts/check-adr-links.mjs` | 0 | |
| `node scripts/check-adr-links.mjs --self-test` | 0 | |
| `node scripts/check-adr-symbol-anchors.mjs` | 0 | |
| `node scripts/check-adr-symbol-anchors.mjs --self-test` | 0 | |
| `node scripts/check-ci-filter-parity.mjs` | 0 | |
| `node scripts/check-closing-keyword-parity.mjs` | 0 | |
| `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | |
| `node scripts/check-comment-mask-corpus.mjs` | 0 | |
| `pnpm --filter @objectstack/lint run check:doc-formula-expressions` |
0 | the first run exited 3 (`PREREQUISITE NOT MET`, formula and lint not
built), so nothing was measured. After `turbo run build
--filter=@objectstack/formula --filter=@objectstack/lint` under
`os-verify-lock` (`VERDICT command-exit 0`), the rerun exited 0 |
| `pnpm check:adr-anchors` | 0 | `check-adr-anchors: OK (54 anchored
file(s) … 1123 decision-letter citation(s) on the anchored surface name
a decision the record makes)` |
| `pnpm check:cross-package-test-inputs` | 0 | |
| `pnpm check:doc-authoring` | 0 | |
| `pnpm check:driver-memory-census` | 0 | |
| `pnpm check:gitlink-declared` | 0 | |
| `pnpm check:nul-bytes` | 0 | `check-nul-bytes: OK (scanned 9892 text
file(s) … no raw ASCII control bytes)` |
| `pnpm check:pm-governed-merges` | 0 | |
| `pnpm check:pm-prior-rulings` | 0 | |
| `pnpm check:refd-timer-probe` | 0 | |
| `pnpm check:watch-hint-literal` | 0 | |
| `node scripts/check-issue-citations.mjs --base df3ba16` (named by
the dispatch; not in the derivation) | 0 | `no issue citations added …
(0 file(s) read)`. **NOT MEASURED for this diff**: `docs/adr/**` is one
of that gate's declared `DEFERRED_SURFACES` |
A separate control-byte scan of the edited file (`grep -naP` over the C0
range and DEL) found no match (exit 1).
Not run locally, and left to CI: the type-check lanes, the `Test Core`
shards, and the families the derivation places outside its list
(wide-population, artifact-roster, pending-changeset). The diff touches
no package, so there is no dependency-closure build and no package test
or typecheck to run.
## Line budget
| file | before | after |
|---|---|---|
| `docs/adr/0053-date-and-datetime-semantics.md` | 1210 lines · 75844
bytes · 18961 tokens | 1222 lines · 76692 bytes · 19173 tokens |
Tokens are counted as `ceil(bytes / 4)`, the unit
`check-skills-token-ratchet` uses. There is no package-level total
because the diff touches no package.
## Changeset
None, and the `skip-changeset` criterion holds: `docs/adr/**` is on the
fast-lane list. I also measured it. Across 69 non-private `package.json`
files, zero `files[]` entries name `adr` or reach outside their own
package. `Addendum D-G1` has zero hits in the built
`packages/formula/dist` and `packages/lint/dist`. As a positive control,
`ADR-0053` has 4 hits in `packages/formula/dist`.
## Acceptance notes
- **Why the id is D-G1.** The dispatch suggested "Addendum D-A2". D-A2
is already taken: it is the 2026-06-18 addendum's `temporalFilterValue`
decision, which the Status line marks as resolved on 2026-07-30. D-A4
would put the ruling inside the family of that analytics raw-SQL
addendum. Each existing addendum has its own letter family (D-A through
D-F), so the next free one, D-G, is used. The addendum sits under item 6
as a dated blockquote, the same shape as the `Resolved (2026-07-30)`
note under D-A2. Its blank lines make the item 5–7 list loose, which
affects rendering spacing only.
- **The Status line is unchanged.** The card asks for the addendum line
and the Consumers entry. Not every landed addendum is listed on the
Status line (D-D and D-E are not).
- **A detail in the card's premise.** The card says the server's
`today()` "is UTC midnight today (`startOfDayUtc`)". On `df3ba164`,
`packages/formula/src/stdlib.ts` `registerStdLib` already resolves
`today()` through `calendarDayUtc(now(), timezone)`: the
reference-timezone calendar day, with `'UTC'` as the default and
fallback. `startOfDayUtc` is that fallback branch. The ruling and this
addendum are unaffected, because both name "the reference day the server
resolves".
- **Drift, not changed here.** The ADR's Phase 2 text and the runtime
disagree about a per-user timezone:
- ADR text: Phase 2 item 5 ("optionally overridden by
`sys-user-preference.timezone`"), the resolver-precedence table's "User
override" row, Non-goals ("org-default + user-override only"), and the
Consumers entry `sys-user-preference.timezone`.
- Runtime: it resolves the organization level only.
`packages/core/src/security/resolve-authz-context.ts`
`resolveLocalizationContext` reads `localization.timezone`.
`packages/services/service-settings/src/manifests/localization.manifest.ts`
declares `scope: 'tenant'` and says "Per-user overrides are
intentionally out of scope for v1".
`packages/runtime/src/security/resolve-execution-context.test.ts` pins
"ignores per-user sys_user_preference rows (organization-level only)".
- The per-user tier was dropped by the released "Resolver 收编" change
(`packages/runtime/CHANGELOG.md`). No writer of a timezone preference
was found in this tree or in objectui `origin/main` `733fd5ac6`.
- Whether Phase 2's per-user layer is still the intent is a question for
the maintainer. D-G1 is worded so that it holds either way.
- **Duplicate heading.** `### D-D2` appears twice with two different
decisions: "Consequences for D-A3" in the #3777 addendum, and "The
primitive lives in `spec`" in the addendum that follows it. This is a
naming issue only, and it is not changed here.
## 维护者速读(草稿)
**改了什么**:在 ADR-0053 Phase 2 第 6 项(`today()` 按参考时区取日)下面加了一条附加条
D-G1,记下维护者对 objectui#10903 的裁决 A。同时在 ADR 的 Consumers 行加了 objectui
的前端公式求值器(`@object-ui/core` `FormulaFunctions`)。只改文档,不动任何代码和运行时行为。
**为什么改**:同一个用户在浏览器里预览公式看到的「今天」,要和服务端保存时算出的「今天」是同一天。裁决 A 已经批准,objectui
那一半(JSDoc)在 objectui PR 10915。objectstack 这一半要写进管这件事的 ADR,否则之后读 ADR 的人或
AI 不知道前端也受这条规则约束。
**风险与代价(含回滚)**:纯文档,没有运行时风险,ADR 多了 12 行。回滚只需 revert 本 PR 的一个
commit。有一处已知的不一致没在本 PR 改:Phase 2 第 5
项写的是参考时区「可由用户偏好覆盖」,但运行时现在只读组织级设置(这是有意的,有测试钉住)。附加条的措辞刻意没有承诺按用户覆盖。这一层是否仍是
Phase 2 的意图,需要您定。
**席位意见**:
**你要做的**:审阅 D-G1 的措辞,同意就在本 PR 上点 Approve。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01MjvgiFAmjHqsxy1XLiVYfH)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 65352b7 commit 34ec30d
1 file changed
Lines changed: 13 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
127 | 139 | | |
128 | 140 | | |
129 | 141 | | |
| |||
0 commit comments