Repository navigation
Commit 4889e22
docs(skills): the objectstack-ui report example declares its download in exportOptions under the base object's allowExport (#27318)
Fixes #26777
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:`skills/objectstack-ui/rules/dashboards.md` 〈Report
Configuration〉的 `defineReport` 示例在 `dateRange` 下加一行 `exportOptions: {
formats: ['csv', 'xlsx'] }`,行尾注释点名可选的 `includeHeaders` /
`fileNamePrefix`;注释块加一句:`exportOptions` 只声明下载,谁能下载是基对象权限集上的
`allowExport`,`joined` 报表既不接受 `dateRange` 也不接受 `exportOptions`。token
棘轮用同文件折叠支付:删掉〈Server-side date bucketing〉已教过的 `dateGranularity`
一条和〈Drilldown〉已教过的 `drilldown` 一句;文件 6221 → 6214 tokens,上限 6221 不动。
- 为什么改:PR #26774 为卡 #26633 落地的 eval 要求报表声明 `exportOptions` 并以
`allowExport` 命名下载权限,而技能规则只教 `dateRange`;照技能写报表的 agent 没有下载声明的样例,可能自造
`permissions.export` 这类键或漏掉导出。改后 eval 若失败只会是 agent 的错,不再是教学的缺口。
- 风险与回滚:只动一个受管 markdown 规则文件和一个 patch changeset,不改
schema、键或运行时;折掉的两处文字在同文件另两节原样存在。回滚即 revert 本 PR。
- 先看:`skills/objectstack-ui/rules/dashboards.md`
L419–421(注释块新句,措辞即契约)、L401(示例新行)、原 L406–409 与 L414–415
的删除(折叠是否真有第二处);`.changeset/26777-report-export-options-rule.md`。
- 两分钟验证:`node scripts/check-skills-token-ratchet.mjs`,预期打印
`skills/objectstack-ui/rules/dashboards.md is 6214 tokens (ceiling 6221;
headroom 7)` 并以 0 退出。
- 需要你做的:批准本 PR(`skills/**` 受管面,Tier H;PR 保持 draft,由席位落地)。
## What changes
One governed rule file and one changeset.
**`skills/objectstack-ui/rules/dashboards.md` 〈Report Configuration〉** —
the report section the skill routes to (`SKILL.md` L148 points at this
file for report config):
- The `defineReport` example gains one line under `dateRange` (the diff
below): `exportOptions` with `formats: ['csv', 'xlsx']`, and a trailing
comment naming the optional `includeHeaders` / `fileNamePrefix`. Every
key is read off `packages/spec/src/ui/report.zod.ts` on `origin/main`:
the report's `exportOptions` is `ReportExportOptionsSchema.optional()`
whose describe reads "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)"; `formats` admits exactly `'csv' | 'xlsx'` (the reverse leg
under Tests shows `'pdf'` refused by the type).
- The note block gains one sentence: `exportOptions` declares the
download, never who may download — that is the base object's
permission-set `allowExport`; a `joined` report takes neither
`dateRange` nor `exportOptions`. It is the schema's own docblock
shortened — "Who may export is NOT declared here — there is no report
permission key. The control is offered to a viewer whose effective API
operations on the dataset's base object include `export`: the
`ObjectPermission.allowExport` axis" — plus the two joined-container
refusals `JOINED_CONTAINER_DATE_RANGE_REFUSED` and
`JOINED_CONTAINER_EXPORT_OPTIONS_REFUSED`. The example writes no
`allowExport: true` anywhere: `allowExport` is named in words as a
permission-set key, which is what the eval's `must_not_contain` guards.
- Paid under the token ratchet by two folds of wording the same file
already teaches, measured line by line (table below):
- the `dateGranularity` bullet (old L406–409): its meaning lives in
〈Server-side date bucketing — `dateGranularity` (ADR-0021)〉 — the lead
paragraph (the dataset dimension carries it; selecting it groups
server-side; without it every timestamp becomes its own category) and
the five-value bucket table — in the Level A table under 〈Dataset-Bound
Widgets〉 (date-bucket `day/week/month/quarter/year` fits a dataset; a
computed bucket escalates), and in the example's own comment two lines
above the call (`close_date` with `dateGranularity: 'quarter'`);
- the `drilldown` sentence (old L414–415): its meaning lives in
〈Drilldown〉 — "Reports drill the same way. A `summary` / `matrix` report
(`drilldown` defaults `true`) opens the identical in-place drawer on
row/cell click".
- Not folded: the `rows` / `columns` axis sentence and the `order`
nuances (`by` must be a selected name; list the `columns` key first)
have no second home in the file beyond the example's inline comments, so
they stay. One further candidate was left for a later shrink: the
example's `order` comment ("by measure; dates default to chronological")
restates the note's ASCENDING sentence.
**`.changeset/26777-report-export-options-rule.md`**:
`@objectstack/skills` patch — the published bundle is built from
`skills/**` (`packages/skills` ships `dist`, its `build` is
`sync-catalog.mjs`) — both declaration lines at line start, one
sentence.
Not touched, by the ruling: `scripts/check-skills-token-ratchet.mjs`
(the `dashboards.md` row stays 6221 — the standing ruling
「A:先削重复,余额按实测提高」 has no locatable comment id, so no raise; the file now
sits 7 tokens under its ceiling, so none is needed either) and
`skills/objectstack-ui/evals/**` (the eval already expects this
teaching).
### The edited region, before → after
```diff
@@ -400,2 +400,3 @@ export const PipelineCoverageReport = defineReport({
dateRange: { dimension: 'close_date', defaultRange: 'this_year' },
+ exportOptions: { formats: ['csv', 'xlsx'] }, // the download; optional `includeHeaders`, `fileNamePrefix`
order: [{ by: 'amount_sum', direction: 'desc' }], // by measure; dates default to chronological
@@ -405,6 +406,2 @@
-> **`dateGranularity`** lives on the dataset's date **dimension**
-> (`day | week | month | quarter | year`); selecting that dimension buckets the
-> field server-side in a single aggregate query — do **not** pre-compute virtual
-> columns for this.
> **`rows`** are the report's grouping dimensions (selected from the dataset by
@@ -413,4 +410,3 @@
> put both axes in `rows`. Multi-level grouping on either axis = multiple
-> dimension names in that array. `drilldown` (default `true`) makes cells
-> click-through to the underlying records.
+> dimension names in that array.
> **`order`** sorts the result server-side — a list of `{ by, direction }`, most
@@ -422,2 +418,5 @@
> report orders per block (`blocks[].order`), never on the container.
+> **`exportOptions`** declares the download, never who may download — that is the
+> base object's permission-set `allowExport`; a `joined` report takes neither
+> `dateRange` nor `exportOptions`.
```
**Skills readings** (`skills/**` touched; before = `origin/main`
a0aa8a1, the branch base; after = head; tokens in the ratchet's
`ceil(utf8 bytes / 4)` convention):
| Reading | Before | After |
| --- | --- | --- |
| `rules/dashboards.md` lines / bytes / tokens | 461 / 24881 / 6221
(ceiling 6221, headroom 0) | 460 / 24853 / 6214 (ceiling 6221, headroom
7) |
| added lines | — | L401 108 B (27.00 tok) · L419 84 B (21.00) · L420 78
B (19.50) · L421 35 B (8.75) → 305 B, 76.25 tok |
| folded lines | old L406 66 B (16.50 tok) · L407 80 B (20.00) · L408 83
B (20.75) · L409 20 B (5.00) · L414–415 tail 84 B (21.00) → 333 B, 83.25
tok | — |
| net on the file | — | −28 B = −7 tokens; no re-wrap counted as payment
|
| `skills/objectstack-ui/**` lines | 2286 | 2285 |
| all `SKILL.md` lines | 4615 | 4615 |
| whole `skills/**` tree lines | 19268 | 19267 |
| bundle total tokens (ratchet report) | 215826 | 215819 |
## Why
The eval landed by PR #26774 for card #26633
(`skills/objectstack-ui/evals/report-period-export.json`) scores a
report-authoring answer on `exportOptions` with `'csv'` / `'xlsx'` and
on `allowExport` named in words, and refuses `permissions.export`,
`canExport`, `allowExport: true` and `'pdf'`. On `origin/main` the rules
teach `dateRange` and nothing about the download (`git grep -n
"exportOptions\|allowExport" skills` → hits only under `evals/`), so an
agent authoring from the skill had no example of the declaration and the
eval could fail for a gap in the teaching rather than in the agent. The
premise held on the branch base a0aa8a1: the same grep, the same zero
hits under `rules/**`.
## Tests
- `node scripts/check-skills-token-ratchet.mjs` — before:
`skills/objectstack-ui/rules/dashboards.md is 6221 tokens (ceiling 6221;
headroom 0)`; after: `… is 6214 tokens (ceiling 6221; headroom 7)`, `82
authored bundle file(s) within their ceilings`, exit 0 both runs. Its
`--self-test` exit 0.
- `pnpm --filter @objectstack/spec check:skill-examples` (the gate that
type-checks every `os:check` block; it is `packages/spec`'s script, run
by CI after the workspace build, and not in the `dispatch-gates
--commands` list for this diff) — after building `@objectstack/spec` and
the `@objectstack/client-react...` closure under
`scripts/pm/os-verify-lock.sh` (VERDICT command-exit 0, held 73 s and
264 s): `✅ 270 prose examples type-check across 3 surface(s) — every
marked block parsed, so tsc ran the SEMANTIC pass on all of them`, exit
0. So `formats: ['csv', 'xlsx']` as string literals is accepted by
`defineReport`'s input type (hypothesis 4 holds).
- Reverse leg, proving the gate reads this very block and not a cached
reading — run after the commit, on the committed file, through `node
scripts/ablation-replace.mjs --file
skills/objectstack-ui/rules/dashboards.md --anchor "formats: ['csv',
'xlsx'] }" --replacement "formats: ['csv', 'pdf'] }" -- pnpm --filter
@objectstack/spec check:skill-examples`: mutation landed on disk (anchor
1 → 0, blob b522363 → afe444cb), the gate went red with `error TS2322:
Type '"pdf"' is not assignable to type '"csv" | "xlsx"'`, command exited
1; restored by the tool, then proven: the on-disk blob hash equals the
HEAD blob (b522363), `git diff HEAD`
empty, `git status` clean. Expected direction red; observed red. No
permanent test file left.
- No package source is touched, so no package `test` / `typecheck` is
owed; no test file edited. The eval JSON is executed by no gate (the
repo has no eval runner).
## Acceptance notes
- Hypotheses from the dispatch, measured: (1) the schema block carries
`exportOptions` with `formats` `'csv' | 'xlsx'`, `includeHeaders?`,
`fileNamePrefix?`, and the joined refusals at `report.zod.ts` L187–207 —
confirmed, quoted above; (2) `content/docs/references/ui/report.mdx` L71
and L116–122 teach the same keys — confirmed (a generated reference
page; the rule text here is shorter); (3) the folds — confirmed for
`dateGranularity` and `drilldown`; the `rows` / `columns` / `order`
wording was not a restatement and stays; (4) the `os:check` compile —
confirmed, with the reverse leg; (5) the derived gate list carried 43
families and did not name `check:pm-governed-prose` (the diff touches
neither `AGENTS.md` nor the governed register), so it was not run.
- `origin/main` moved from a0aa8a1 (the branch base) to d4427ae (one
`ui/packages/components` test commit) while the branch was open; merged
in as 94cb20a with no overlap on any touched surface, and the gate
union below ran on that head.
- The ratchet script's header says lowering a ceiling to a shrunken
file's count is "always legitimate and encouraged"; the row was left at
6221 because the ruling section names the script as not touched. Noted
for the seat.
- Nothing found worth filing: no reproducible defect, no contract
violation, no authoring trap met on this card.
## Verification
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at head `94cb20ad` (the final commit,
derived from the merge base with no paths passed) printed 43 commands,
identical to the derivation before the `origin/main` merge. All 43 ran
sequentially in the worktree after that commit, each exit captured
before any pipe (`bash -c "$cmd" >> log 2>&1; EXIT=$?`): 42 exit 0 —
among them `node scripts/check-skills-token-ratchet.mjs` and its
`--self-test`, `node scripts/check-empty-changeset.mjs --base
origin/main`, `node scripts/check-adr-0087-registration.mjs --base
origin/main`, `node scripts/check-changeset-no-major.mjs --base
origin/main`, `node scripts/check-closing-keyword-parity.mjs`, `pnpm
check:changeset-gate-self-tests`, `pnpm check:pm-governed-merges`, `pnpm
check:published-files`, `pnpm check:skill-compatibility`, `pnpm
check:skill-frame-sync`, `pnpm check:skill-identifier-liveness`, `pnpm
check:doc-authoring`, `pnpm check:role-word`, `pnpm
check:corpus-claim-drift`, `pnpm check:nul-bytes`, `pnpm
check:pm-skill-id-lint`, `pnpm check:dts-closure`, `pnpm
check:lean-entry-closure`; 1 exit 3 — `pnpm check:dual-build-cjs-loads`,
PREREQUISITE NOT MET ("some package has no dist/": 33 packages, it asks
for a whole-workspace `pnpm build`), NOT MEASURED locally; CI's `Lint &
Repo Gates` runs it after its build.
- `dispatch-gates --ran`: `✓ dispatch-gates --ran: 43 derived famil(ies)
accounted for — 42 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit
3)`, exit 0.
- Local-only families: `pnpm check:dev-prereqs` read this box as not
fully built (68 of 106 packages without `dist`; exit 1) — a state of the
worktree, not of the diff, since only `@objectstack/spec` and the
`client-react` closure were built here; `check:skill-frame-freshness` is
not implicated (no four-axis framework file touched).
- The wide-population families, the repo-wide `pnpm lint`, the
type-check lanes and the path-scheduled jobs are CI's runs on this PR.
- Head at the time of the union: `git rev-parse --short HEAD` =
94cb20a.
## Human Minimum
Each item is one rule from
[CONTRIBUTING.md › Human
Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum),
which links the AGENTS.md text that states it.
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No schema touched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes (the repo-wide run is
CI's; no source file edited).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: n/a, not touched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent b4eecd5 commit 4889e22
2 files changed
Lines changed: 15 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
398 | 398 | | |
399 | 399 | | |
400 | 400 | | |
| 401 | + | |
401 | 402 | | |
402 | 403 | | |
403 | 404 | | |
404 | 405 | | |
405 | 406 | | |
406 | | - | |
407 | | - | |
408 | | - | |
409 | | - | |
410 | 407 | | |
411 | 408 | | |
412 | 409 | | |
413 | 410 | | |
414 | | - | |
415 | | - | |
| 411 | + | |
416 | 412 | | |
417 | 413 | | |
418 | 414 | | |
419 | 415 | | |
420 | 416 | | |
421 | 417 | | |
422 | 418 | | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
423 | 422 | | |
424 | 423 | | |
425 | 424 | | |
| |||
0 commit comments