Repository navigation
spec(report): a report declares a dataset-dimension date range and export options #26548
Description
Ruling-ref: 6408567749
Clause-②: yes (widening)
Metadata-protocol: added
Filing gate ③, a task the maintainer set: ruling 6408567749 on #25873 (letter B, maintainer 「同意」 2026-10-26T04:56Z). The ruling's execution order files this spec card first; #25873 carries Blocked-by: this card and then becomes the ui/ renderer work. ⛔ Not a claim. Grade p2 inherited from #25873.
维护者速读
- 事由: 报表页只有「编辑」:没有时间范围,没有导出。裁决 B(6408567749)要求报表复用仪表盘的
dateRange词汇与预设(数据集有时间维度时生效),并通过现有的列表导出权限导出报表表格(CSV / XLSX),不做订阅。今天ReportSchema两样都没有声明,渲染器也没有地方可读。 - 要做的事: 在
ReportSchema上新增两个可选键。①dateRange: { dimension?, defaultRange, allowCustomRange }:defaultRange/allowCustomRange与仪表盘逐字相同(同一份预设表、同样的默认值);dimension指数据集里一个type: 'date'的维度(ADR-0021 的语法:报表按名字选维度,不写原始字段),省略时取数据集唯一的日期维度。②exportOptions: { formats?, includeHeaders?, fileNamePrefix? }:成员取自列表的导出块,格式只留csv/xlsx;谁能导出由现有的allowExport权限轴决定,不新增权限位。另加别名、表单、生成的 schema、liveness 行、lint 规则和文档。 - 风险: (1) 仪表盘的
dateRange.field今天按原始对象字段生效(默认created_at,见 analytics: a dashboard global filter whose field shares a dataset dimension's name is lowered to that dimension — the spec says it resolves against the dataset object #25814),而 ADR-0021 第 392 行计划的是维度名。本卡按 ADR 走dimension,field只作为改名提示的别名;如果维护者要求报表与仪表盘同一个键名field,评审时改名,形状不变。(2) 列表的 XLSX 只走服务端流(GET /data/:object/export),而那条门导出的是记录,不是聚合行。所以报表的 XLSX 要在浏览器端生成,权限只能管住按钮,管不住数据(看得到的人本来就能读这些行)。这一点在 ui 卡里说明,不在这里新开服务端门。(3) 新键在 ui 卡落地前没有读取方,liveness 记planned,并给作者警告。
What was measured
All readings at origin/main f486e9c.
What a report declares today: no date range, no export.
packages/spec/src/ui/report.zod.ts:347-591—ReportSchemais astrictObjectwithname,label,description,type,dataset(:436),rows(:441),columns(:448),values(:453),runtimeFilter(:459),order(:484),drilldown(:490),chart(:500),blocks(:515),protection(:525) and the protection fields.git grep -n -iE 'dateRange|timeRange|export' -- packages/spec/src/ui/report.zod.tsfinds only TSexportstatements. No time-range key, no export key.- Alias table :375-404 (
dataset/values/rows/runtimeFilter/ordertargets) and guidance :405-417 (drillDown). The comment at :351-368 requires the report and block tables to stay parallel:report.test.tsderives that parity, andalias-integrity.test.tsproves that every alias targets a declared key and that no alias is filed under a declared key. JoinedReportBlockSchema:238-329 has its own alias and guidance tables. Its guidance uses wrong-layer pointers (blocks/drilldown/protection) and one retired key (chart, [finding] a joined report'schart— container andblocks[].chart— parses and is never drawn, while the liveness ledger names the joined branch as its reader #20161).- The superRefine at :532-591 already refuses one presentation key on a
joinedcontainer (chart, :585-587), with no pointer ontoblocks[]. That is the precedent for a container key a joined report cannot honour. packages/spec/src/ui/report.form.ts:18-117has four sections (Basics / Dataset binding / Joined blocks / Filter & chart). None of them offers a period or export control.packages/spec/liveness/report.jsonhas rows for every current key. A new key needs a row, orcheck:livenessreports it asUNCLASSIFIED.
The dashboard's vocabulary.
packages/spec/src/ui/dashboard.zod.ts:1829-1837—dashboard.dateRangeisstrictObject{ field?: string, defaultRange: enum(DATE_RANGE_DEFAULT_RANGES).default('this_month'), allowCustomRange: boolean.default(true) }, with aliasesdateField/fieldName→field,preset/range/default→defaultRange,allowCustom/custom→allowCustomRange. There is nopresetskey. The chip offers the whole preset list.- :1778 — dashboard top-level aliases
dateFilter/timeRange→dateRange. - :1523 re-exports
DATE_RANGE_PRESETSfrompackages/spec/src/data/date-range-presets.ts:42-49:today, yesterday, this/last_week, this/last_month, this/last_quarter, this/last_year, last_7/30/90_days. :1539DATE_RANGE_DEFAULT_RANGES = [...DATE_RANGE_PRESETS, 'custom'](custom= open the picker, no window). - :1408-1418 —
filterBindings: a dashboard date range binds a widget field, defaulting todateRange.field ?? 'created_at'. In the shipped dashboard,fieldis a raw object field merged into the widget'sruntimeFilter. That is consistent with analytics: a dashboard global filter whose field shares a dataset dimension's name is lowered to that dimension — the spec says it resolves against the dataset object #25814 (closed 2026-10-26), where global-filterfieldresolves against the dataset object. - :1241-1268 —
compareTo.dimensionis the dashboard's own dataset-dimension key for "which date column". The executor resolves an omitted one as "the selection's time dimensions that carry adateRange; exactly one is used; zero or several is a loud error naming what it found". Its aliases arefield/dateField/timeDimension→dimension.
What a time dimension is.
packages/spec/src/ui/dataset.zod.ts:190—DatasetDimension.type: enum(['string','number','date','boolean','lookup']).optional(); :192dateGranularity(default bucket for date dimensions).packages/services/service-analytics/src/dataset-compiler.ts:538-546—dimensionType:date→ cube'time'; absent type →'string'. So a dimension over a date column that does not declaretype: 'date'is not a time dimension. "The dataset has a time dimension" therefore means "declares a dimension withtype: 'date'".- Example:
examples/app-todo/src/datasets/task.dataset.ts:30-31declares two (due_date,completed_date). A report on it has to name which one is meant.
What the server accepts today (measured by reading, not executed).
packages/spec/src/api/analytics.zod.ts:571-713—DatasetSelectionSchema(thePOST /api/v1/analytics/dataset/querybody) carriestimeDimensions, taken by reference fromAnalyticsQuerySchema.packages/spec/src/data/analytics.zod.ts:1010-1031: each entry is{ dimension, granularity?, dateRange? }, wheredateRangeis a preset name from the sameDATE_RANGE_PRESETSor a[start, end]pair (:900AnalyticsDateRangeSchema). Anything else answers400 ANALYTICS_DATE_RANGE_UNRECOGNIZED.packages/services/service-analytics/src/dataset-executor.ts:134-135resolves{date-macro}tokens insidetimeDimensions[].dateRange.packages/services/service-analytics/src/analytics-service.ts:612-614(analytics: 只用来限定「日期区间」的 timeDimension 被补上默认 dateGranularity,于是网格被静默按月拆分 —— 「按 Owner 统计」加个日期筛选就变成「按 Owner × 月」 #5688): an entry that names no granularity and whose dimension is not grouped stays a window, a filter. It is not bucketed.- ⇒ A runtime date window on a dataset report needs no server change. The report page does not send one today.
ui/packages/plugin-report/src/DatasetReportRenderer.tsx:578-584posts{ dimensions, measures, runtimeFilter?, totals?, order? }and notimeDimensions. The renderer's only external filter input is theruntimeFilterprop (:214). - There is no report-service: the saved-report
/api/v1/reportsfamily is retired (packages/rest/src/reports-routes-retired.test.ts, retire the saved-report stack —sys_saved_report/sys_report_schedule,/api/v1/reports,client.reports,IReportService, thereportscapability and@objectstack/plugin-reports(zero consumers; not thereportmetadata kind) #20102). The report page reads only throughanalytics.queryDataset(packages/rest/src/rest-route-ledger.ts:392).
The list-export surface this reuses.
packages/spec/src/ui/view.zod.ts:3176-3182—view.exportOptionsis the union of a legacy bare format array andListViewExportOptionsSchema.packages/spec/src/ui/list-view-export-options.ts:70—ListViewExportFormatSchema = enum(['csv','xlsx','json'])(pdfretired). :92-103 — membersformats,maxRecords,includeHeaders,fileNamePrefix,streaming. :94 — "XLSX is delivered by the server stream only".- The permission is
packages/spec/src/security/permission.zod.ts:514,ObjectPermission.allowExport(opt-in,read ∧ grant, :270). The server resolves it intoapiOperationson/me/permissionsand enforces it atGET /data/:object/export→403 EXPORT_NOT_PERMITTED(:510-513). - The list affordance gate is
ui/packages/plugin-list/src/ListView.tsx:2092-2095:exportPermitted = schema.allowExport !== false && schema.operations?.export !== false && (effectiveApiOps ? effectiveApiOps.includes('export') : true). The same predicate is inui/packages/plugin-grid/src/ObjectGrid.tsx:5730. ui/packages/plugin-report/src/ReportExportEngine.ts/LiveReportExporter.tsalready hold a client-side CSV writer, for the legacyReportComponentSchema.exceljsis already a ui dependency (ui/packages/plugin-grid/package.json:37).
What ADR-0021 plans.
docs/adr/0021-analytics-dataset-semantic-layer.md:392(WS1): "dashboard.zod.ts(…; globalFilters/dateRange → dimension names)". :273: "Dashboard-levelglobalFiltersanddateRangebind to dimension names, not raw fields."- :44: a report "renders a pivot grid … with subtotals, drill-to-record, export".
- :207-232 (D2
ReportSchemasketch): no date-range key. The report's only scope key isruntimeFilter. - So the ADR plans dimension-named date ranges for the dashboard and names export as a report capability. It plans no report date key. This card applies the dimension-name grammar to the report.
Proposed shape
Both keys are optional and top-level on ReportSchema. An existing report parses and renders exactly as today.
1. dateRange
dateRange: strictObject({
surface: 'this report date range',
history: <ReportSchema history>,
aliases: {
// the dashboard's own rename hints, verbatim (dashboard.zod.ts:1832)
preset: 'defaultRange', range: 'defaultRange', default: 'defaultRange',
allowCustom: 'allowCustomRange', custom: 'allowCustomRange',
// the dashboard compareTo's "which date column" hints (dashboard.zod.ts:1263-1268)
field: 'dimension', dateField: 'dimension', fieldName: 'dimension', timeDimension: 'dimension',
},
}, {
dimension: z.string().optional()
.describe('Dataset dimension (declared `type: \'date\'`) the window applies to; omitted = the dataset\'s one date dimension'),
defaultRange: z.enum(DATE_RANGE_DEFAULT_RANGES).default('this_month')
.describe('Default date range preset'),
allowCustomRange: z.boolean().default(true)
.describe('Allow users to pick a custom date range'),
}).optional()defaultRangeandallowCustomRangeare the dashboard's declarations: the same enum (DATE_RANGE_DEFAULT_RANGES), the same defaults and the same describes. If review wants one declaration and not two, factor a sharedDateRangeChoiceShapebesideDATE_RANGE_DEFAULT_RANGESand spread it into both. The dashboard's JSON Schema must not change.dimensionnames a dataset dimension, not an object field (ADR-0021:273/392). A dimension carries its own relationship path, soaccount.created_atworks throughincludewith no new grammar.- Lowering (contract stated in the describe and TSDoc; implemented by the ui card): the active window rides
selection.timeDimensions: [{ dimension, dateRange }]with nogranularity. That makes it a filter window under the analytics: 只用来限定「日期区间」的 timeDimension 被补上默认 dateGranularity,于是网格被静默按月拆分 —— 「按 Owner 统计」加个日期筛选就变成「按 Owner × 月」 #5688 rule, ANDed withruntimeFilterand the dataset's intrinsic filter. A preset goes on the wire by name. A custom pick goes as[start, end]. Applying it needs no server change. - Resolution, one rule with the dashboard executor's
compareTo: withdimensionomitted, the dataset's dimensions declaringtype: 'date'are the candidates. Exactly one is used. Zero or several is an authoring error that names what it found. - Where that is judged:
ReportSchemaparses without the dataset, so the parse checks only the shape. The dataset-aware half is a new@objectstack/lintrule besidevalidate-chart-bindings.ts, which already resolves a report against its dataset. It answers three cases:report-date-range-dimension-unknown(the name is not a dimension of the dataset),report-date-range-dimension-not-date(the dimension is nottype: 'date'), andreport-date-range-dimension-ambiguous/-nonewhendimensionis omitted. Each gets an entry inrule-explanations.ts. "Applied when the dataset has a time dimension" becomes something the build checks, not something the renderer guesses (ruling facet ③). joined: refused on ajoinedcontainer in this card. Blocks may bind different datasets, so a singledimensioncannot name a column in all of them. This uses the same superRefine arm and the same no-pointer refusal style as the containerchart(:585-587).JoinedReportBlockSchemagets aguidanceentry fordateRangesaying it is a non-joined report key. A per-block or conformed-dimension range is a later widening (ADR-0021 Q3), not this card.- Top-level
ReportSchemaaliases: adddateFilter: 'dateRange'andtimeRange: 'dateRange'(the dashboard's, :1778).
2. exportOptions
exportOptions: ReportExportOptionsSchema.optional()
// declared in packages/spec/src/ui/list-view-export-options.ts beside its sibling:
ReportExportOptionsSchema = closed object {
formats?: Array<ListViewExportFormatSchema.exclude(['json'])>, // 'csv' | 'xlsx'
includeHeaders?: <the list block's member, by identity>,
fileNamePrefix?: <the list block's member, by identity>,
}- Key name, members and format values are the list's (
view.exportOptions). No new vocabulary is minted.formatsis the list enum narrowed with.exclude(['json']), socsv/xlsxas ruled.'json'is refused with a message naming the two report formats, and'pdf'keeps the list's retirement prescription. - Default on: with
exportOptionsabsent, a non-empty report offers['csv', 'xlsx'], the same as the list's default-allow.formats: []turns the control off. That empty list is the "export flag" the ruling names, and it needs no second key. (If review prefers an explicit boolean, the alternative isexportOptions: false. That would be a union, and union 分支里的 unknown-key 处方永远到不了作者:zodIssuesToFields只映射顶层 issue,失败的 union 只剩Invalid input#5014 shows a union buries its errors, so this card does not propose it.) - Who may export: the existing axis, no new bit. The control is offered when the viewer's effective
apiOperationsfor the dataset's base object (dataset.object, echoed asobjecton the query answer) includes'export'. That is theObjectPermission.allowExportaxis, through the same predicate asListView.tsx:2092-2095. The describe states this. The spec declares no permission key of its own. - What is exported (contract in the TSDoc; built by the ui card): the report table as rendered, within the active date range and
runtimeFilter. That covers the selected rows × values, the matrix pivot and the server totals, with headers from the dimension and measure labels. The file name defaults to the report label plus the period, andfileNamePrefixreplaces the label. - Guidance entries on
ReportSchema:allowExport: "the export permission is the permission-set axisObjectPermission.allowExportand is not declared on a report. A report's export control isexportOptions; who may export is decided by that axis."subscription/schedule: "a report subscription is not part of the report metadata: ruling 6408567749 on [Decision] The report page offers only "Edit": should a shipped report get a time-range filter, export and subscription, and in which order #25873 deferred it (C), pending customer pull."
Not added
- No subscription or scheduled delivery (ruling: C waits; scheduled work is off on Free plans, service-automation: a hosted environment has no way to enable
schedule/time_relativeflows for an installed app (OS_AUTOMATION_SCHEDULED_WORK_ENABLED is deployment-wide and off on cloud) #25442). - No new permission key and no new server route. There is no dataset-rows export door, and XLSX is written in the browser (risk 2 above).
- No
jsonorpdfreport format. NomaxRecordsorstreaming: those are record-stream members, and report rows are already aggregated and bounded by the selection. - No
presetslist key (the dashboard has none) and no new preset. In particular, noall_timedefault. An author who wants "all time" by default leavesdateRangeundeclared. Making "All time" a declarable default would widenDATE_RANGE_DEFAULT_RANGESfor dashboards too, which needs its own decision. - No
granularityinsidedateRange. Bucketing stays on the dimension'sdateGranularityand the report'srows/columns. - No
dateRangeon blocks or on ajoinedcontainer (see above). No change todashboard.dateRange.
Acceptance
Spec tests (packages/spec/src/ui/report.test.ts, plus a focused report-date-range-export.test.ts):
- A report with neither key parses byte-identically to today, and its parsed output has no
dateRangeorexportOptions. dateRange: {}parses to{ defaultRange: 'this_month', allowCustomRange: true }. Every member ofDATE_RANGE_DEFAULT_RANGESis accepted.'last_6_months'is refused, with the enum message naming the presets. An unknown member is refused, anddateField/preset/allowCustomeach earn their rename hint.dateRangeon atype: 'joined'report is refused at['dateRange'].blocks[i].dateRangeis refused with its guidance text.exportOptions: { formats: ['csv','xlsx'] }and{ formats: [] }parse.'json'and'pdf'are refused with their messages, andmaxRecords/streamingare refused as unknown keys.- The top-level aliases
dateFilter/timeRangeand the guidanceallowExport/scheduleanswer as specified. - The block↔report alias parity test and
alias-integrity.test.tsstay green.
Lint (packages/lint): the new date-range rule has tests for all four cases: unknown, not a date dimension, two date dimensions with dimension omitted (using task.dataset.ts's due_date / completed_date shape), and none. Each case has an os explain entry.
Registration surfaces:
report.form.tsgets a new collapsible "Period & export" section:dateRange(composite;visibleWhen: "data.type != 'joined'") andexportOptions(composite).metadata-form-zod-reconciliation.test.tsstays green.- The form's new labels and help texts get catalog entries in the nine translation bundles (
node scripts/check-i18n-bundles.mjs --write,pnpm check:i18n). packages/spec/liveness/report.jsongetsdateRangeandexportOptionsrows,plannedwith an author warning, and a note pointing at [Decision] The report page offers only "Edit": should a shipped report get a time-range filter, export and subscription, and in which order #25873. They flip tolivewithui/…#Symbolevidence when the renderer lands.
Generated artifacts: pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated --fix (gen:schema for the JSON Schema and the authorable surface, gen:docs for content/docs/references/ui/report.mdx, gen:api-surface for the new exported schema(s)). check:liveness stays green.
Docs:
content/docs/ui/reports.mdxgets a "Period and export" section: the dimension rule, the window lowering, the export permission axis and what is not offered.skills/objectstack-ui/rules/dashboards.md§ Report Configuration gets one example withdateRange: { dimension: 'close_date', defaultRange: 'this_quarter' }.
Changeset: minor on @objectstack/spec (and @objectstack/lint). The body carries Clause-②: yes (widening) and Metadata-protocol: added. No ADR-0087 disposition is needed, because nothing is removed or narrowed.
After this lands
- [Decision] The report page offers only "Edit": should a shipped report get a time-range filter, export and subscription, and in which order #25873 drops its
Blocked-by:and becomes theui/renderer card: a Console report page date-range chip that lowers toselection.timeDimensions[]inDatasetReportRenderer'suseDatasetRows, and an export control gated onapiOperations'export'for the dataset's base object, writing CSV and XLSX client-side. The liveness rows flip tolivethere. - HotCRM declares
dateRangeon its ten reports once a release carries both halves. Not measured here: whether each HotCRM report's dataset declares exactly onetype: 'date'dimension. The lint rule above will say which ones needdimensionnamed or a dimension typed. - C (subscription) stays deferred under the ruling. If a customer asks for it, it becomes a card of its own.
Dedupe: MCP search_issues on this repo with "report dateRange time range filter" (hit #1752, closed, drill range: different), "report export CSV XLSX table export permission" (hits #3544 and #3710, closed: the allowExport axis this card reuses, and the retired scheduled-report CSV door: different), "ReportSchema new key spec report period date window dataset time dimension" (#21702, #16185, #14933, #1752: all closed, different) and "dashboard dateRange field bind to dataset dimension name instead of raw field" (#25814, closed: the field-vs-dimension namespace this card sides on; #5688, closed: the window-only rule this lowering relies on). area:reports open issues: only #25873. git grep -n -iE 'dateRange|exportOptions' origin/main -- packages/spec/src/ui/report.zod.ts → 0 key hits. New.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
enhancement·priority:p1(from p2) ·domain:spec·area:reports·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T07:56Z⛔ Not a claim, ⛔ not a dispatch.
Metadata-protocol: added:ReportSchema.dateRangeandexportOptions). Under [Decision] Triage priority caliber for protocol additions: keep 「协议新增定 p1」 for every addition, or grade additions an app asks for at p2 unless a release needs them #26368 ruling A, every protocol addition is p1, app-driven or not, so the p2 inherited from [Decision] The report page offers only "Edit": should a shipped report get a time-range filter, export and subscription, and in which order #25873 goes up. Handled directly in 18.x, executing [Decision] The report page offers only "Edit": should a shipped report get a time-range filter, export and subscription, and in which order #25873 ruling B (6408567749). Reuse the dashboard'sdateRangevocabulary and presets verbatim, ⛔ with no second preset table. Export goes through the existing list-export permission, CSV and XLSX only.Skills-docs:line (the #26184 audit Part B rule draft) — protocol-kind only, protocol-kind plus narrowing, or not adopted #26369 B): the PR writes itsSkills-docs:line. skills(data,automation,platform): teach ownership queues and assignment rules (ADR-0142 phase 2, PR #26337) with eval prompts #26475 already carries the data-skill package, so a follow-up card may share it.Blocked-by:this card for the renderer half.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1 · 2026-10-27T08:40Z
Session:
session_01WGufzfhobWEgUUrjz4rrbhAccount:
os-support-aiBranch:
claude/project-thread-xsyisg(card threadsession_01WGufzhMJrMdCkQS33DYtmu. The platform assigns the thread's branch and the thread can push only that one, so the branch is written as measured instead ofclaude/issue-26548-<slug>.)Worktree:
objectstack-issue-26548Domain:
domain:specSeat:
domain:spec#1File surface:
packages/spec/src/ui/report.zod.ts(dateRange,exportOptions, their aliases),report.form.ts,list-view-export-options.ts(a report export schema beside its list sibling),report.test.tsand one focused test,packages/spec/liveness/report.json, the generated spec artifacts, one newpackages/lintrule besidevalidate-chart-bindings.tswith its explanations and tests, the translation bundles viacheck-i18n-bundles --write,content/docs/ui/reports.mdx,skills/objectstack-ui/rules/dashboards.md(Skills-docs), one changeset (@objectstack/spec+@objectstack/lintminor). ⛔ Noui/renderer (that is #25873) and no server export door. Stop on a breach and explain it in the report.Container & model:
M,mode:thread,model: TIER_DEFAULT,effort: xhighClause-②: yes (widening)
Metadata-protocol: added
Responsibility: n/a. Filing gate ③ of ruling
6408567749on #25873; #25873 staysBlocked-by:this card for its renderer half.Thread-read: 6436657773
Premise: holds (read at
origin/main1c632311:report.zod.tsdeclares nodateRangeand noexportOptions;DATE_RANGE_DEFAULT_RANGESis re-exported fromdashboard.zod.ts)Serial constraints cleared: yes. No open PR or claim names
report.zod.ts. Askills/**touch makes the landing Tier H.Ruling-ref: 6408567749
objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 26548, "status": "done", "branch": "claude/project-thread-xsyisg", "pr": "https://github.com/objectstack-ai/objectstack/pull/26628", "session": "session_01WGufzhMJrMdCkQS33DYtmu", "premise_still_valid": true, "summary": "ReportSchema gains two optional top-level keys in the card's proposed shape: dateRange { dimension?, defaultRange, allowCustomRange } (the dashboard's DATE_RANGE_DEFAULT_RANGES, defaults and rename hints, held equal to the dashboard declaration by test; dimension names a type: 'date' dataset dimension) and exportOptions { formats?, includeHeaders?, fileNamePrefix? } (ReportExportOptionsSchema beside the list block, csv / xlsx only, no permission key, allowExport stays the axis). A joined report refuses both, on the container and on a block; dateFilter / timeRange are aliases, allowExport / subscription / schedule are guidance. The dataset-aware half is a new @objectstack/lint reference-integrity rule validateReportDateRange (four error verdicts with os explain entries, also on the report publish door); liveness rows are planned with an author warning and drilled, the form gains a translated Period & export section, and the docs page and skills example are written. ui/ renderer work stays on #25873; no server export door.", "tests": "pnpm --filter @objectstack/spec test: Test Files 729 passed | 1 skipped (730), Tests 22095 passed | 1 skipped | 1 todo. pnpm --filter @objectstack/lint test: Test Files 167 passed (167), Tests 7411 passed (7411), after moving the predicate census pins 88 → 89 and 63 → 64 for the new form gate. @objectstack/platform-objects vitest run: Test Files 96 passed (96), Tests 1361 passed (1361). New focused files: report-date-range-export.test.ts 48 passed; validate-report-date-range.test.ts plus runtime-gate.inert-type-writes, lint-liveness-properties, reference-integrity-suite, rule-explanations 191 passed. Typecheck spec / lint / platform-objects: exit 0 each. No ablation: no tested behaviour changed, only added.", "gates": "local subset: node scripts/pm/dispatch-gates.mjs --commands named 113 families at 4dca57f78, all run on a full workspace build (turbo 118/118) with exit codes recorded before any pipe; 112 exit 0, check:empty-state exit 1 (the dateRange TSDoc's absent-window sentence was unclassified). Fixed in 8c03abecb (sentence beside the key, dateRange registered as a scope empty state); that commit derived one more family. Re-run after it, all exit 0: check:empty-state, check:pnpm-flag-spellings, check:spec-docblock-symbol-anchors, spec check:liveness, spec check:docs, spec check:api-surface, check:nul-bytes. dispatch-gates --ran: 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN. After the final main merge (0c548c59a): spec check:generated --fix rebuilt spec, 0 of 14 stale; check:empty-state and check:liveness exit 0. Local-only: check:dev-prereqs exit 0, check:skill-frame-freshness exit 0. pnpm check:i18n OK (10 packages in sync). eslint on 15 changed source files exit 0. CI: check-runs not yet reported when the PR was opened, in_progress.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "read-only only: mcp__github__issue_read (the card, its comments, the ruling thread on #25873, #26475) and mcp__github__pull_request_read get (#26628 read-back). No write tool.", "api_writes": "3 relay writes, each a POST /repos/objectstack-ai/objectstack/dispatches through scripts/pm/fleet-write: pr_create with assignees (#26628, body read back identical, 13039 bytes), pr_request_reviewers os-support-ai (ordered best-effort), and this os-dev-report comment. Plus git push of the branch.", "open_questions": [ { "question": "Should a joined report refuse exportOptions on the container? The card decides the joined case for dateRange only; this PR refuses exportOptions there too.", "options": [ "A keep the refusal: a joined report draws one table per block, so there is no single table to export; widening later is non-breaking", "B accept a container exportOptions that applies to every block's table, and spell what the download holds (one file per block, or one workbook with a sheet per block)" ], "recommendation": "A, because B needs a decision on the file shape the renderer card has not made, and refusing now leaves B open as a pure widening." } ], "out_of_scope_findings": [] }Deviations (also in the PR body, Acceptance notes): exportOptions refused on a joined container (the open question above); json and pdf carry report-side messages instead of the list's pdf prescription; the subscription / schedule guidance cites no ruling because runtime strings carry no tracker numbers; validateReportDateRange also runs at the report publish door; the skills example uses this_year, not this_quarter (the example matrix buckets by quarter); the liveness lint now walks reports (packages/lint); both liveness containers are drilled; translations landed in @objectstack/platform-objects (patch in the changeset). Skills-docs: follow-up owed — skills/objectstack-ui/evals/analytics-inline-vs-dataset.json (both ui eval files are at their token ceiling, headroom 0).
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Landed ·
domain:specseat 1 (#6017) · sessionsession_01WGufzfhobWEgUUrjz4rrbh· 2026-10-27T14:16ZPR #26628 merged at
bb91ae86(merged 2026-10-27T14:16Z, maintainer-merged as Tier H).bb91ae86is an ancestor oforigin/main. The PR touched 30 paths. On 29 of them the merge tree matches the accepted headbe4a0204byte for byte. The 30th,packages/lint/src/rule-explanations.ts, differs only bymain's own hunk from #26658 (the controlledByParent owner arm), not by this PR's content.A report now declares
dateRangeandexportOptionsin the published schema, the form and the lint, and a joined container refuses both (ruling A).@objectstack/specand@objectstack/lintminor,@objectstack/platform-objectspatch,Metadata-protocol: added. The card closed throughFixes #26548, and this seat removespm:dispatched.Not done here: the eval prompt is #26633. The renderer is #25873.
Merge origin/main into claude/project-thread-g33c7k for a conflict
Merge origin/main into claude/project-thread-hjxyd2