Skip to content

Commit 96c8d55

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-gru2pt
2 parents 012d9d0 + 94bab5a commit 96c8d55

62 files changed

Lines changed: 3590 additions & 151 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
fix(objectql)!: inside one bulk create, a child rule that reads a parent summary counts the earlier rows of the same batch its rules admitted, so a batch is judged as its rows would be one by one (#25579)
6+
7+
Clause-②: no (narrowing) — a bulk create (`insert(array)`, `insertMany`, and the doors on them: `createManyData`, `insertManyData`, the import's batched creates) refuses a row whose rule reads a parent `summary` that the earlier admitted rows of the same batch move past the rule's limit, which it admitted before. Nothing is widened.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type changes: an existing validation rule is now enforced on the rows of one bulk create the way it is already enforced one write at a time. The refusal is the rule's own VALIDATION_FAILED, worded by its author, so there is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing at the bulk insert (`@objectstack/objectql`), served by every bulk create door.
13+
14+
- **Before.** A bulk insert read the related rows its rules read through a reference once, before any row of the batch was written, and recomputed parent summaries once, after. So "each supplier has at most one primary contact" (a `count` summary filtered on `{ is_primary: true }`, and the child rule `record.is_primary == true && record.supplier.primary_contact_count > 0`) refused a second primary contact written on its own, but admitted two primary contacts for one supplier in one import: both rows read `0`.
15+
- **Now.** Each row's rules read the parent summary as it stands after the earlier rows of the same batch that the rules admitted. The batch's admitted rows are accumulated in memory per summary and per parent; ⛔ nothing is re-read from the database per row. In the card's import, one contact is created and the other row reports the rule's error. A multi-row insert preview (`validate` with an array, mode `insert`) answers the same way.
16+
- **Unchanged.** Which rows land and how a refusal is reported stay each door's own: `insertMany` / `insertManyData` (the import) report the refused row and write the rest; `insert(array)` / `createManyData` refuse the whole batch. A row the rules refuse is not counted. A single write reads the stored summary as before. The summary written after the batch is the same recompute as before.
17+
- **Which summaries accumulate.** `count`, and `sum` / `min` / `max` over number values, with the summary's `filter` evaluated on the new row. An `avg` summary, a `sum` / `min` / `max` that meets a value that is not a number, and a summary whose `filter` reads a dotted path through another reference keep reading the stored value, as before.
18+
- **Not covered.** Concurrent writes: two writes in flight at the same time each read the summary they started from (批量等于逐条,并发不保证).
19+
- **Who sees it.** An app whose child rule reads a parent summary and that bulk-creates children: a batch that broke the rule was admitted whole before, and now its offending rows are refused.
20+
21+
**The remedy.** None needed for a rule that meant what it says. A batch that must land whole has to satisfy the rule row by row, in order.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': minor
4+
'@objectstack/platform-objects': patch
5+
---
6+
7+
feat(spec): a report declares the period it covers (`dateRange`) and the export of its table (`exportOptions`)
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: added
11+
12+
`ReportSchema` gains two optional top-level keys. A report that declares neither parses and renders exactly as before.
13+
14+
- **`dateRange: { dimension?, defaultRange, allowCustomRange }`.** A date window over one dimension of the report's dataset. `defaultRange` and `allowCustomRange` are the dashboard's own declaration, verbatim: the same preset table (`DATE_RANGE_DEFAULT_RANGES`), the same defaults (`this_month`, `true`) and the same rename hints. `dimension` names a dataset **dimension** that declares `type: 'date'`, not a raw object field; omitted, the dataset's one `type: 'date'` dimension is used. The window is a filter, not a bucket. The renderer sends it as `selection.timeDimensions: [{ dimension, dateRange }]` with no `granularity`, which the analytics service already accepts. `dateFilter` / `timeRange` are renamed onto `dateRange`, the dashboard's own spellings.
15+
- **`exportOptions: { formats?, includeHeaders?, fileNamePrefix? }`.** Three members of the list export block, under its key name. `formats` is `csv` / `xlsx` only (`json` and `pdf` are refused with a report-side message). Export is on by default (both formats) and `formats: []` turns it off. `maxRecords` and `streaming` are list-only and are refused with that reason. There is no report permission key: the control is gated on the dataset base object's `ObjectPermission.allowExport`, the axis the list toolbar uses. `allowExport` written on a report is answered with that pointer.
16+
- **Refused on a `joined` report**, on the container and on a block. The blocks may bind different datasets, and the report draws one table per block.
17+
- **No subscription.** `subscription` / `schedule` on a report are answered: there is no scheduled delivery of a report.
18+
19+
`@objectstack/lint` adds the dataset-aware half as a reference-integrity rule, `validateReportDateRange`. It runs on `os validate`, `os lint` and `os build`, and at the report publish door. Four `error` verdicts, each with an `os explain` entry: `report-date-range-dimension-unknown`, `report-date-range-dimension-not-date`, `report-date-range-dimension-ambiguous` (dimension omitted, two or more date dimensions) and `report-date-range-dimension-none` (dimension omitted, no date dimension). The liveness author-warning lint now walks `reports`, and both keys warn (`liveness-planned-property`) until the Console report page reads them. The report editor form gains a collapsed "Period & export" section, translated in `@objectstack/platform-objects` for zh-CN, ja-JP and es-ES.
20+
21+
Nothing renders either key yet. The Console report page's period chip and export control are the renderer's half, and until it lands an author declaring either key gets a planned-property warning that says so.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): flow screen date, date-time and boolean fields use the platform controls
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A screen flow's input screen was the last form surface still drawing the browser's native date input, so a `date` field showed the machine's format (`mm/dd/yyyy` under a Chinese console), a `datetime` field was a bare text box, and a `boolean` field was a lone checkbox on the line under its label, which read like an empty radio button.
11+
12+
What changes:
13+
14+
- A `date` or `datetime` screen field is now the record form's date picker: a text box in the display locale's format, a calendar popover, a clear button and the today / tomorrow shortcuts. The value it hands the flow is the stored shape: `YYYY-MM-DD` for a date and an ISO instant for a date-time, and `''` when cleared, as the native date input wrote.
15+
- A `boolean` (or `checkbox`) screen field is now the inline row the action-parameter dialog draws: the checkbox with its label beside it, and the help text under the row.
16+
17+
What to do: nothing. No flow metadata changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
'@object-ui/plugin-kanban': patch
4+
---
5+
6+
fix(ui): the stage path and the kanban lay a select's options out by their declared lifecycle `kind` (#26612)
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
A select option may declare `kind: 'open' | 'won' | 'lost'` (`SelectOption.kind` in `@objectstack/spec`). Two readers now follow it instead of the option order or the value's spelling.
12+
13+
- **`record:path`.** Once the status field declares a kind on any option, an option without one reads as `open`, the spec's rule for an absent kind, and is never classified from a `closed_won` / `closed_lost` spelling. The stages are laid out by kind: the open options as the steps in their authored order, then every won option as the forward terminus, then the lost options apart from the rail. A lost option authored mid-list no longer pulls the options after it into the lost group. A field that declares no kind keeps the spelling fallback and its positional layout as before, and a dev build now warns once, naming the object, the field and the stages it guessed.
14+
- **Kanban.** Lanes the board derives from the lane field's options run open → won → lost when the field declares a kind, each class in its authored order, and every lost lane starts collapsed: a spine with its card count that the viewer can open. Authored `columns` and fields that declare no kind keep their order; the board guesses nothing from spelling.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/plugin-audit': patch
3+
---
4+
5+
fix(plugin-audit): the compliance ledger keeps no copy of secret-store key material
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The audit writer copies each audited row into `sys_audit_log` (`new_value` / `old_value`) and into `sys_activity.metadata`. For the secret store and the two settings holders, that copy carried the encrypted key-material column. Every new copy now omits those columns, the way a field declared `internal` is omitted:
11+
12+
- **What is still recorded.** Each create, update and delete of those rows keeps its ledger and activity row: which record, its producer-scoped label, key id, algorithm and version, who changed it and when. A write that changes only the key material still records its row, with the material on neither side.
13+
- **Which columns.** The list is closed, held by the writer (`KEY_MATERIAL_COPY_COLUMNS`), and keyed by the platform-object definitions' own names. A column of the same name on any other object is recorded as before.
14+
- **Copies already written.** `migrateKeyMaterialCopies(engine, logger, { apply })` and `migrateArchivedKeyMaterialCopies(driver, logger, { apply })` remove the columns from copies stored before this release, in the hot `sys_audit_log` / `sys_activity` tables and in an archive datasource. Both runs are dry by default, idempotent, change only the copy column, and keep each row's stored `updated_at`. Their reports hold counts only. The pure planners `planKeyMaterialAuditRowPatch` and `planKeyMaterialActivityRowPatch` are exported with them.

‎.claude/skills/pm-dispatch/references/decision-analysis.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
- 入批前认领:`Brief-claimed-by:` 行的写法、跳过与接管判据住 `lanes/director.md` 职责二。
1111
- 回批一行式逐卡对应字母选项;带的附加条件随执行落地,⛔ 不为它另起请示轮。
1212
- 呈递详解 = 逐卡散文按正文六项;⛔ 表格行、判词、栅格不构成详解,压缩批次是违规。
13-
- 受管草稿批例外:一 PR 一行(改了什么/为什么/席位意见/一个动作),⛔ 不套六项;速读住 PR。
1413
- 重分析轮同纪律;适用面 = 任何席位向维护者呈递的待裁项,⛔ 不及结果陈述如轮报表格。
1514

1615
## 落卡分析模板

‎.claude/skills/pm-dispatch/references/lanes/director.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,8 @@
5454
- 入箱勤务:校验/补全四棱块、速读与 `Prior rulings on this card:` 行;入箱同笔现读前提。
5555
- 线程有 `Ruling:`/RULED 的卡再入箱,该行逐 id 各一句变化;缺行 ⇒ 拒入,`pm:retriage` 回立卡者。
5656
- 一次只开一批:维护者对批 N 回字(一字即可)后,新到才合成批 N+1;续批至清零才收工。
57-
- 受管草稿批:两仓 `is:pr is:open is:draft label:needs-user-decision` 对照等人合清单,差集点名。
58-
- 有席内 ACCEPT 者呈一批 ≤5 行,写法见 `../decision-analysis.md`;四件缺一者列缺口不呈。
59-
- 回批原话逐 PR 记到 PR 上,缺的请审补上即止步;批准与合并恒是维护者的点击,⛔ 不代。
57+
- 受管 PR 待人批前核四件:席位记录、维护者速读、中继 `pr_request_reviewers` 请审、绿 head。
58+
- 缺件即补;⛔ 不入批、不求聊天「同意」;批准与合并恒是维护者的点击,⛔ 不代。
6059
- 呈报前逐卡逐仓重跑 `git grep -n -iE 'TERMS' origin/main -- AGENTS.md docs/adr packages/spec/src`。
6160
- 以协议为准:消费面对齐协议 ⇒ 荐对齐呈批,协议没有的能力荐删;⛔ 不自裁。
6261
- 协议是否正确由本席按①读协议原文与主流平台分析,有误即荐改动呈批,⛔ 不自裁。

‎content/docs/data-modeling/fields.mdx‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,23 @@ row is unaffected — a user still needs edit access to change any other field o
309309
it — and the summary column stays subject to the parent's field-level security
310310
on read.
311311

312+
A child validation rule can read a parent summary one hop through its
313+
reference, for example "each supplier has at most one primary contact" as a
314+
`count` summary filtered on `{ is_primary: true }` plus the child rule
315+
`record.is_primary == true && record.supplier.primary_contact_count > 0`.
316+
**A bulk create equals one-by-one creates (批量等于逐条):** inside one bulk
317+
insert (an import batch, `createManyData`, `insertManyData`) each row's rules
318+
read the summary as it stands after the earlier rows of the same batch that the
319+
rules admitted, accumulated in memory, so two primary contacts in one import
320+
create one row and refuse the other with the rule's error. A row the rules
321+
refuse is not counted. This holds for `count`, and for `sum`, `min` and `max`
322+
over number values. An `avg` summary, a `sum`/`min`/`max` meeting a value that
323+
is not a number, and a summary whose `filter` reads a dotted path through
324+
another reference keep reading the stored value, as they did before.
325+
**Concurrent writes are not guaranteed (并发不保证):** two writes in flight at
326+
the same time each read the summary they started from, so a guard like the one
327+
above can still admit one row from each.
328+
312329
### Specialized Types
313330

314331
| Type | Factory | Description |

‎content/docs/references/ui/report.mdx‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ const result = JoinedReportBlockSchema.parse(data);
6767
| **order** | `{ by: string; direction?: Enum<'asc' \| 'desc'> }[]` | optional | Result ordering, most significant key first |
6868
| **drilldown** | `boolean` | optional (default: `true`) | Click-through to underlying records |
6969
| **chart** | `{ type: Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'line' \| 'area' \| 'pie' \| 'donut' \| …>; title?: string \| Record<string, string>; subtitle?: string \| Record<string, string>; description?: string \| Record<string, string>; … }` | optional | Embedded chart configuration (refused on a joined report, which draws tables only) |
70+
| **dateRange** | `{ dimension?: string; defaultRange?: Enum<'today' \| 'yesterday' \| 'this_week' \| 'last_week' \| 'this_month' \| 'last_month' \| …>; allowCustomRange?: boolean }` | optional | Report date range: a window over one dataset date dimension (refused on a joined report) |
71+
| **exportOptions** | `{ formats?: Enum<'csv' \| 'xlsx'>[]; includeHeaders?: boolean; fileNamePrefix?: string }` | optional | Report export control: `{ formats?, includeHeaders?, fileNamePrefix? }`, csv / xlsx, on by default; who may export is the base object's export permission (refused on a joined report) |
7072
| **blocks** | `{ name: string; label?: string \| Record<string, string>; description?: string \| Record<string, string>; type?: Enum<'tabular' \| 'summary' \| 'matrix'>; … }[]` | optional | Sub-reports for type=joined |
7173
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this report. |
7274
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
@@ -103,6 +105,22 @@ const result = JoinedReportBlockSchema.parse(data);
103105
| **interaction** | `{ tooltips?: boolean; brush?: boolean }` | optional | Interaction toggles: `{ tooltips?, brush? }` |
104106
| **aria** | `never` | optional | [REMOVED] `ChartConfig.aria` — authored as `dashboard.widgets[].chartConfig.aria`, `report.chart.aria` and `report.blocks[].chart.aria` — was removed in @objectstack/spec 17 (ADR-0049 D2). No chart renderer ever applied it: the chart implementation declares no `aria` prop, the presentation lowering names it nowhere, and the react `<ObjectChart>` block never published it, so ARIA attributes declared here parsed and then silently did not reach the DOM. Delete the key. The accessible name that IS applied on this same chart config is its sibling `description`, which the chart renderer lowers onto the chart graphic as `role="img"` plus `aria-label`. The shared `AriaProps` shape is NOT gone — `ariaLabel` / `ariaDescribedBy` / `role` stay live in the `aria` block on `page.aria`, `page.components[].aria` and the list view `aria`. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
105107

108+
### Nested Shape: `Report.dateRange`
109+
110+
| Property | Type | Required | Description |
111+
| :--- | :--- | :--- | :--- |
112+
| **dimension** | `string` | optional | Dataset dimension (declared `type: 'date'`) the window applies to; omitted = the dataset's one date dimension |
113+
| **defaultRange** | `Enum<'today' \| 'yesterday' \| 'this_week' \| 'last_week' \| 'this_month' \| 'last_month' \| …>` | optional (default: `"this_month"`) | Default date range preset |
114+
| **allowCustomRange** | `boolean` | optional (default: `true`) | Allow users to pick a custom date range |
115+
116+
### Nested Shape: `Report.exportOptions`
117+
118+
| Property | Type | Required | Description |
119+
| :--- | :--- | :--- | :--- |
120+
| **formats** | `Enum<'csv' \| 'xlsx'>[]` | optional | Formats the report's export control offers (default: ['csv', 'xlsx']); an empty list turns the control off |
121+
| **includeHeaders** | `boolean` | optional | Include column headers in the exported file (default true) |
122+
| **fileNamePrefix** | `string` | optional | Download file name prefix — replaces the report label in the generated file name |
123+
106124
### Nested Shape: `Report.blocks[number]`
107125

108126
| Property | Type | Required | Description |

0 commit comments

Comments
 (0)