@@ -26,11 +26,37 @@ export const OpportunityDataset = defineDataset({
2626 { name : 'forecast_category' , label : 'Forecast Category' , field : 'forecast_category' , type : 'string' } ,
2727 { name : 'type' , label : 'Deal Type' , field : 'type' , type : 'string' } ,
2828 { name : 'owner' , label : 'Owner' , field : 'owner' , type : 'lookup' } ,
29- // NOTE: no `dateGranularity` here — a bucketed date dimension routes the
30- // query through the platform's ObjectQL aggregate bridge, which drops the
31- // caller's ExecutionContext and fail-closes the read scope (id =
32- // '__deny_all__') on @objectstack 16.1, returning zero rows. Trend widgets
33- // group by the raw date until that is fixed upstream.
29+ // ─── Why no `dateGranularity` on any date dimension (hotcrm#523) ────
30+ // This is the canonical note; the other datasets point here.
31+ //
32+ // Declaring a bucket on a dataset dimension is the supported mechanism and
33+ // IS what these dimensions want (month for the trend widgets, quarter for
34+ // pipeline_coverage_by_quarter, day for cases_opened_by_day_priority). It
35+ // cannot be declared while the app is pinned to @objectstack 16.x: doing so
36+ // makes every affected surface render EMPTY, which is worse than the
37+ // un-bucketed columns it would fix. Two independent 16.x gaps, both
38+ // verified against the pinned packages, both fixed in 17.0.0-rc.0:
39+ //
40+ // 1. Read scope. A granular dimension is refused by NativeSQLStrategy
41+ // (service-analytics `canHandle` bails on any timeDimension carrying a
42+ // granularity), so the query falls through to ObjectQLStrategy → the
43+ // auto-bridged `executeAggregate`, which calls `engine.aggregate()`
44+ // WITHOUT the caller's ExecutionContext. The sharing middleware then
45+ // sees an empty principal and composes `{ id: '__deny_all__' }` for
46+ // every `sharingModel: 'private'` object — which is all of ours. Fixed
47+ // upstream by #3602/#3597: the bridge threads `context`, and the
48+ // strategy applies the read scope itself.
49+ // 2. Bucketing. A `Field.datetime()` column lands in SQLite as INTEGER
50+ // epoch millis, and 16.x buckets with a bare `strftime('%Y-%m', col)`
51+ // — NULL for every row, i.e. a single '—' column. 17.0's driver-sql
52+ // normalises epoch storage before formatting. `Field.date()` columns
53+ // (close_date) are TEXT and bucket correctly even on 16.x, but they
54+ // still hit gap 1.
55+ //
56+ // The intended bucket per dimension is not lost — it lives in
57+ // `test/dataset-granularity.test.ts`, whose guard flips from "must not
58+ // declare" to "must declare" the moment this app moves to @objectstack 17.
59+ // Until then the trend widgets and matrix reports group by the raw date.
3460 { name : 'close_date' , label : 'Close Date' , field : 'close_date' , type : 'date' } ,
3561 // Cross-object dimension: account industry via the crm_account relationship.
3662 { name : 'account_industry' , label : 'Account Industry' , field : 'crm_account.industry' , type : 'string' } ,
0 commit comments