diff --git a/src/dashboards/executive.dashboard.ts b/src/dashboards/executive.dashboard.ts index 8895f83..fe0d4d0 100644 --- a/src/dashboards/executive.dashboard.ts +++ b/src/dashboards/executive.dashboard.ts @@ -1,8 +1,16 @@ import type { Dashboard } from '@objectstack/spec/ui'; /** - * 管理层 — DESIGN.md §09's second dashboard: 生效合同额 · 90 天内到期 · - * 高风险合同 · 审批瓶颈(各台阶平均停留)· 按方向的合同额趋势. + * 管理层 — DESIGN.md §09's second dashboard. + * + * §09's brick list is deliberately NOT copied here, for the reason PR #74 gave + * when it took the sibling copy off `legal.dashboard.ts`: a copy drifts, a + * pointer cannot. This one had already drifted. It advertised one brick this + * file has never built — 审批瓶颈(各台阶平均停留)— and §09 stopped asking for + * that brick on 2026-09-09 (维护者裁定, PR #40 `1127e52`), replacing it with the + * per-rung routing volume the four `route_*` tiles below deliver. §09 as merged + * (PR #70, `30fd863`) is the source; every widget below carries its own comment + * for its own shape. * * Every widget binds `contract_metrics`, so the two global filters below reach * a column that exists on every one of them. See `legal.dashboard.ts` for the @@ -136,10 +144,19 @@ export const ExecutiveDashboard: Dashboard = { colorVariant: 'danger', layout: { x: 9, y: 0, w: 3, h: 2 }, }, - // ─── §09's 审批瓶颈, as the four rungs of DESIGN.md §04's ladder ─────── + // ─── The four rungs of DESIGN.md §04's ladder, read as ROUTING VOLUME ─── + // + // These four tiles are the brick §09 puts at this position, and they build + // it in full: one tile per rung, reading the four `route_*` flags F2 stamps + // on the contract. §09 says of this position that it carries traffic and + // not dwell — the board and the design agree here, and §09 (PR #70, + // `30fd863`) is where that wording lives rather than a copy of it here. // - // §09 asks for 各台阶平均停留 — average DWELL per approval step. Two - // independent facts rule the dwell out, both measured rather than assumed: + // The average DWELL per rung that §09 listed BEFORE 2026-09-09 is not + // deliverable, and §09 no longer asks for it: the maintainer ruling in + // PR #40 (`1127e52`) placed it outside the V1.0 surface, and §09 now gives + // these two measurements — both taken on this board — as the reasons it + // declines: // // 1. There is no dwell to read. `sys_approval_request` holds 0 rows on a // stock `pnpm demo` — the seed stamps `approval_status` directly and @@ -147,10 +164,19 @@ export const ExecutiveDashboard: Dashboard = { // 2. Even with rows, a duration is not expressible in the semantic layer: // see the measurement in `cycle-time.dataset.ts`. // + // ⚠️ Neither reads as a TODO. Fact 1 is independent of the platform and + // survives any upgrade; and the in-flight platform work + // (`objectstack-ai/objectstack#16737`) makes the wrong path ERROR rather + // than adding date arithmetic, so the dwell does not arrive when it closes + // either. ⛔ Do not re-promise the metric on the strength of that issue, and + // ⛔ do not stamp it from an application-side daily job — §09 forbids that + // route in as many words, as replicating a platform rule in the app. + // // What IS persisted is the routing: F2 stamps four booleans on the contract, // one per rung. These four tiles read them — how much traffic each rung - // carries. Each title names its rung and the group title says "Routing - // Load", never "bottleneck", because volume is not dwell. + // carries. Each tile is titled `Routes: `; no title or description on + // this board — here or in either translation bundle — says "bottleneck" or + // "dwell", because volume is neither. // // FOUR TILES, not one four-measure chart, and the shape was forced by a // measurement. The rungs are four COLUMNS, not four values of one column, so diff --git a/src/dashboards/legal.dashboard.ts b/src/dashboards/legal.dashboard.ts index b3a3850..7ee7356 100644 --- a/src/dashboards/legal.dashboard.ts +++ b/src/dashboards/legal.dashboard.ts @@ -140,15 +140,29 @@ export const LegalDashboard: Dashboard = { }, { /** - * §09's 超 SLA tile, with the one honest change its threshold had to - * take. The SLA is PER TYPE (`clm_contract_type.review_sla_days`: 2, 3, 5 - * or 10 days across the nine seeded types), and a breach is a row-wise - * comparison of `review_started_at` against another object's column. - * Analytics cannot express that: there is no formula filter (§12 gap - * #10), and a cross-object filter is refused outright on the strategy - * every bucketed query lands on. The alternative — a persisted - * `review_due_at` stamped by the daily job — is the §12 gap-#7 shape and - * belongs to card 09, not here. + * The review-ageing tile, at a FIXED 30-day threshold written into its + * own title — which is the brick §09 prescribes here, not a degraded + * stand-in for one. §09 places the per-type「超 SLA」 outside the V1.0 + * delivery surface (维护者裁定 2026-09-09, PR #40 `1127e52`; §09 as + * merged, PR #70 `30fd863`) and asks in its place for exactly this: a + * fixed threshold, self-declared in the brick's title. + * + * The measurement behind that ruling was taken here, which is why it is + * recorded here. The SLA is PER TYPE (`clm_contract_type.review_sla_days`: + * 2, 3, 5 or 10 days across the nine seeded types), and a breach is a + * row-wise comparison of `review_started_at` against another object's + * column. Analytics cannot express that: there is no formula filter + * (§12 gap #10), and a cross-object filter is refused outright on the + * strategy every bucketed query lands on. + * + * ⛔ The remaining route — a persisted `review_due_at` stamped by a daily + * job, the §12 gap-#7 shape — is closed for this metric, and not merely + * unbuilt: §09 forbids filling the per-type breach from an + * application-side job because that replicates a platform rule inside the + * application, and decision #31 ruled the same way on the notification + * side (1C + 2B, 2026-09-09 — see `legal-review-sla.flow.ts`, whose F3 + * reminder fires on this same fixed 30 days so the tile and the reminder + * cannot tell two stories). * * So the threshold is FIXED, stated in the title, and above every seeded * SLA: a review older than 30 days is late under any of the nine. The @@ -179,20 +193,29 @@ export const LegalDashboard: Dashboard = { // ─── Row 2: throughput, period over period ─────────────────────────── { /** - * §09 asks for 平均周转(本月 vs 上月) — an AVERAGE TURNAROUND compared - * with the previous period. The comparison half is delivered exactly as - * the card requires, with the platform primitive (`compareTo`, the one - * shape `DatasetSelection.compareTo` implements) rather than a hardcoded - * delta. The average-duration half is NOT delivered, and this tile - * measures THROUGHPUT instead: how many contracts legal got to `approved` - * in the window. + * §09's 审批吞吐 brick: how many contracts legal got to `approved` in the + * window, against the previous period — and the period comparison uses + * the platform primitive §09 names (`compareTo`, the one shape + * `DatasetSelection.compareTo` implements) rather than a hardcoded delta. + * This tile IS that brick; it is not a substitute for a missing one. * - * Why: a duration cannot be computed in the semantic layer on this + * §09 did ask for 平均周转 — an AVERAGE TURNAROUND — until 2026-09-09, + * when the maintainer ruling in PR #40 (`1127e52`) placed 各段时长 outside + * the V1.0 delivery surface and rewrote this brick to throughput (§09 as + * merged: PR #70, `30fd863`). The measurement behind that ruling was + * taken here: a duration cannot be computed in the semantic layer on this * platform version — `Field.datetime` persists ISO text, so `AVG()` over * a stage stamp answers the average YEAR (2025.9166…), and the dataset * layer has no expression in which to subtract two dates. The full - * measurement is in `cycle-time.dataset.ts`. Substituting a number that - * is honest and saying so beats rendering a plausible one that is not. + * measurement is in `cycle-time.dataset.ts`. A number that is honest and + * says so beats a plausible one that is not, which is the reasoning §09 + * itself now carries. + * + * ⚠️ This tile does not become an average turnaround when + * `objectstack-ai/objectstack#16737` closes. That in-flight fix makes the + * wrong path ERROR instead of returning a plausible fake number; it does + * not add date arithmetic, so the duration stays uncomputable after it + * lands. ⛔ Do not read the tracker as a queue this metric is waiting in. * * The window is stated HERE, on the widget, and it is bounded on both * ends on purpose: `DatasetWidget` lowers only a `{ $gte, $lte }` pair diff --git a/src/datasets/cycle-time.dataset.ts b/src/datasets/cycle-time.dataset.ts index 55a5600..dbb67bb 100644 --- a/src/datasets/cycle-time.dataset.ts +++ b/src/datasets/cycle-time.dataset.ts @@ -4,8 +4,15 @@ import { defineDataset } from '@objectstack/spec/ui'; * How far contracts get, and when — DESIGN.md §09's second dataset, by * contract type and legal owner. * - * ## ⚠️ §09 asks for 各段时长 (per-stage DURATIONS). This dataset does not carry - * one, because on this platform version none can be computed honestly. + * ## ⚠️ No duration measure — and §09 does not ask for one + * + * §09's bullet for this dataset asks for stage-reached counts and rules out any + * duration measure, which is exactly what is declared below. 各段时长 (per-stage + * DURATIONS) was placed outside the V1.0 delivery surface by the maintainer + * ruling of 2026-09-09 (PR #40, `1127e52`; §09 as merged, PR #70, `30fd863`), + * and the measurements in this comment are the ones §09 now cites as its + * reason. They were taken here, on this dataset — which is why they are + * recorded here, and not because this file falls short of the design. * * Measured on `@objectstack/spec` 17.3.0 + `@objectstack/service-analytics` * 17.3.0 + better-sqlite3 13.0.3, against the 820-row demo: @@ -27,10 +34,19 @@ import { defineDataset } from '@objectstack/spec/ui'; * form is the one that renders a clean, plausible number. Shipping it would be * exactly the "seeded number that looks computed" AGENTS.md forbids. * - * A duration needs a PERSISTED numeric — the same shape DESIGN.md §12 already - * prescribes for the other date-arithmetic gap (#7: "到期、逾期由日任务盖戳字段"), - * i.e. a daily job stamping `clm_contract`. That field does not exist and - * creating it is a schema change, not an analytics one. Raised on the PR. + * ⚠️ None of this changes when `objectstack-ai/objectstack#16737` closes. The + * in-flight platform fix makes the wrong path ERROR instead of returning that + * plausible number; it does not add date arithmetic, so a duration remains + * uncomputable after it lands. The unlock criterion decision #31 records is + * "this repo upgrades to a version where the wrong path actually errors" — the + * end of a fake number, not the arrival of the metric. + * + * A duration would need a PERSISTED numeric — the shape DESIGN.md §12 gives the + * other date-arithmetic gap (#7), a daily job stamping the column, as the + * obligation and payment sweeps do for 到期 / 逾期. ⛔ That route is closed for + * this one, and not merely unbuilt: §09 forbids filling 各段时长 or the per-type + * 「超 SLA」 from an application-side daily job, because it replicates a platform + * rule inside the application. * * ## What it DOES carry *