From 55f73167f83f767a46e76c60cb3e2c7a5a70c1ce Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 11:52:23 +0000 Subject: [PATCH 1/2] docs: reframe five DESIGN.md 09 comments as agreement, not shortfall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 09 changed direction on 2026-09-09 (1127e52, PR #40; final state 30fd863, PR #70): per-stage durations and the per-type "over SLA" are outside the V1.0 delivery surface, the legal bricks are 30-day review ageing and approval throughput, the executive position carries per-rung routing volume, and the cycle-time dataset carries stage-reached counts with no duration measure. Five comments under src/ still described the 09 that existed before that ruling, reading as "09 asks for X and we cannot deliver it" when 09 now declines X for the very reasons those comments give. The authority ran backwards: the source file was presented as falling short of the design while it is in fact what the design settled on. Each is now written as what the code delivers and why that is right, citing 09 as the authority that agrees. No measurement is deleted — Field.datetime stored as ISO text so AVG() answers an average year, the -0.85 that is the difference of two average years, ADR-0021 leaving the semantic layer no SQL and no expressions, sys_approval_request holding zero rows on a stock demo — because 09 now cites those same measurements as its reasons, and they were taken here. executive.dashboard.ts's header took #71's remedy: the copied 09 brick list is gone and a pointer replaces it, naming the one brick it advertised that this file never built so nobody pastes the list back. Three stale forward-promises went with the framing, all of them things 09 now forbids or the code does not have: - "a persisted review_due_at stamped by the daily job belongs to card 09" — 09 forbids filling the per-type breach from an application-side job, and decision #31 ruled the same way on the notification side (legal-review-sla .flow.ts already said so). - "a daily job stamping clm_contract ... Raised on the PR" for durations — same prohibition. - 'the group title says "Routing Load"' — there is no group title and no such string; the four tiles are titled "Routes: ". Every citation of objectstack#16737 now carries the qualifier: the in-flight platform fix makes the wrong path ERROR rather than adding date arithmetic, so a duration stays uncomputable after it lands. It merely stops rendering a plausible fake number. Comments only: no type, filter, options, dataset field, dimension or widget id, and no line outside comment syntax changed. These three files are inside all four gates' surface and the gates pass — but comments are invisible to tsc, which is exactly why the drift went unnoticed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR --- src/dashboards/executive.dashboard.ts | 39 ++++++++++++++--- src/dashboards/legal.dashboard.ts | 61 ++++++++++++++++++--------- src/datasets/cycle-time.dataset.ts | 28 +++++++++--- 3 files changed, 96 insertions(+), 32 deletions(-) diff --git a/src/dashboards/executive.dashboard.ts b/src/dashboards/executive.dashboard.ts index 8895f83..3785ae1 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,18 @@ 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: ` and no string on this board + // 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 * From f42dea15cba9b8cf2163db07310a63d69768f040 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 11:56:15 +0000 Subject: [PATCH 2/2] docs(dashboards): scope the no-dwell-wording claim to titles and descriptions The sentence claimed no string on the executive board says "bottleneck" or "dwell". The comment it sits in contains both words, so read literally it falsified itself. The claim that is true and checkable is about the strings a user sees: no widget title or description here or in either translation bundle carries either word, verified by grep over title/description/label lines. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR --- src/dashboards/executive.dashboard.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dashboards/executive.dashboard.ts b/src/dashboards/executive.dashboard.ts index 3785ae1..fe0d4d0 100644 --- a/src/dashboards/executive.dashboard.ts +++ b/src/dashboards/executive.dashboard.ts @@ -174,8 +174,9 @@ export const ExecutiveDashboard: Dashboard = { // // 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 tile is titled `Routes: ` and no string on this board - // says "bottleneck" or "dwell", because volume is neither. + // 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