Skip to content

Commit 2eb4724

Browse files
os-billclaude
andauthored
docs(spec): ApproverType.describe() qualifies manager instead of offering it as a bare allowed value (#17640)
* docs(spec): ApproverType.describe() qualifies `manager` instead of offering it bare `ApproverType` carried no `.describe()` at all, so the generated reference page `content/docs/references/automation/approval.mdx` rendered `## ApproverType` with nothing but an `### Allowed Values` list: `manager` — the one rung an author cannot operate on a stock install, because `sys_user.manager_id` has no product write surface — read exactly like the nine members that work. The describe qualifies it and POINTS at the remedy rather than restating it: `MANAGER_ONLY_REMEDY` / `MANAGER_ONLY_ROUTES` in `packages/lint/src/validate-approval-approvers.ts` stay the single authoritative copy, and that file's `DEPENDENCY` docblock now names this new string among the lines that go stale if the column ever gains a write surface. No enum member is added, removed or renamed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH * docs(spec): regenerate the approval reference page and add the changeset `pnpm --filter @objectstack/spec gen:docs` rewrote all 222 reference pages; exactly one changed. The `## ApproverType` section now carries the qualifying describe above its `### Allowed Values` list, and the `type` rows of `ApprovalNodeApprover` and `ApprovalNodeConfig.approvers` — previously blank — carry it too. The page was never hand-edited. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0ced0aa commit 2eb4724

4 files changed

Lines changed: 83 additions & 3 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
`ApproverType` qualifies `manager` in its `.describe()` instead of offering it as a bare allowed value
6+
7+
`ApproverType` carried **no** `.describe()` at all, so the generated reference
8+
page rendered `## ApproverType` with nothing but an `### Allowed Values` list:
9+
`manager` — the one rung an author cannot operate on a stock install — read
10+
exactly like the nine members that work. `{ type: 'manager' }` resolves
11+
`sys_user.manager_id`, and that column still has no product write surface
12+
(re-measured on this tree: the identity write guard's managed-update whitelist
13+
for `sys_user` is `{name, image, locale}`; the column carries `readonly: true`;
14+
no `packages/plugins/plugin-auth` source writes it). An author who chose it got
15+
a chain that passed `validate` and `lint` and then stalled on its first
16+
submission.
17+
18+
The new describe says what is true about `manager` and **points** at the remedy
19+
rather than restating it: `MANAGER_ONLY_REMEDY` / `MANAGER_ONLY_ROUTES` in
20+
`packages/lint/src/validate-approval-approvers.ts` remain the single
21+
authoritative copy of the population routes, and that file's `DEPENDENCY`
22+
docblock now names this new string among the lines that go stale if the column
23+
ever gains a write surface. A pointer cannot drift into disagreement with what
24+
it points at, which is why no third copy of the 667-character remedy was added.
25+
26+
⛔ No member is added, removed or renamed, and no behaviour changes: the enum's
27+
accept set is byte-identical and `check:api-surface` is green on the rebuilt
28+
`dist/*.d.ts`.
29+
30+
**Why this ships, and why `patch`.** `@objectstack/spec`'s published `files[]`
31+
carries `dist`, `json-schema` and `src/**/*.zod.ts`, and the new string is
32+
measured in all three on the built tree — `dist/automation/index.js` and
33+
`.mjs` (2 files, against a lit control of an existing describe from the same
34+
module, also 2), four `json-schema/` documents (`ApproverType.json`,
35+
`ApprovalNodeApprover.json`, `ApprovalNodeConfig.json`, `objectstack.json`) and
36+
the shipped `approval.zod.ts` source. Prose only, no surface widening ⇒
37+
`patch`.
38+
39+
The `packages/lint` half is a docblock comment and is deliberately **not**
40+
graded: that package publishes `dist` only, and the new sentence is absent from
41+
it (0 files) while a runtime string from the same source file is present in 4
42+
and a pre-existing comment from the same docblock is absent in 0 — so comments
43+
are stripped by construction and nothing published moves there.

content/docs/references/automation/approval.mdx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const result = ApprovalDecision.parse(data);
5252

5353
| Property | Type | Required | Description |
5454
| :--- | :--- | :--- | :--- |
55-
| **type** | `Enum<'manager' \| 'position' \| 'department' \| 'team' \| 'field' \| 'expression' \| 'org_membership_level' \| 'role' \| 'user' \| 'queue'>` || |
55+
| **type** | `Enum<'manager' \| 'position' \| 'department' \| 'team' \| 'field' \| 'expression' \| 'org_membership_level' \| 'role' \| 'user' \| 'queue'>` || Approval step approver type. `manager` is a directory-sync dependency rather than something an author configures here: it resolves the submitter's `sys_user.manager_id` at runtime, and that column has no product write surface, so until an operator populates it from outside the product a manager step resolves to nobody and the request waits. `os lint` reports that at authoring time as `approval-approvers-may-resolve-empty` and carries the graded population routes and the full remedy; the Approvals guide states the same remedy in prose. |
5656
| **value** | `string` | optional | User id / membership tier / position / team / department / field — per `type`; for `expression`, a CEL expression over `current.*` / `trigger.*` / `vars.*` |
5757
| **resolveAs** | `Enum<'user' \| 'department' \| 'position' \| 'team'>` | optional | How an `expression` result is expanded into approvers (default 'user') |
5858
| **group** | `string` | optional | Group label for per_group sign-off (e.g. "legal", "finance") |
@@ -81,7 +81,7 @@ const result = ApprovalDecision.parse(data);
8181

8282
| Property | Type | Required | Description |
8383
| :--- | :--- | :--- | :--- |
84-
| **type** | `Enum<'manager' \| 'position' \| 'department' \| 'team' \| 'field' \| 'expression' \| …>` || |
84+
| **type** | `Enum<'manager' \| 'position' \| 'department' \| 'team' \| 'field' \| 'expression' \| …>` || Approval step approver type. `manager` is a directory-sync dependency rather than something an author configures here: it resolves the submitter's `sys_user.manager_id` at runtime, and that column has no product write surface, so until an operator populates it from outside the product a manager step resolves to nobody and the request waits. `os lint` reports that at authoring time as `approval-approvers-may-resolve-empty` and carries the graded population routes and the full remedy; the Approvals guide states the same remedy in prose. |
8585
| **value** | `string` | optional | User id / membership tier / position / team / department / field — per `type`; for `expression`, a CEL expression over `current.*` / `trigger.*` / `vars.*` |
8686
| **resolveAs** | `Enum<'user' \| 'department' \| 'position' \| 'team'>` | optional | How an `expression` result is expanded into approvers (default 'user') |
8787
| **group** | `string` | optional | Group label for per_group sign-off (e.g. "legal", "finance") |
@@ -112,6 +112,8 @@ const result = ApprovalDecision.parse(data);
112112

113113
## ApproverType
114114

115+
Approval step approver type. `manager` is a directory-sync dependency rather than something an author configures here: it resolves the submitter's `sys_user.manager_id` at runtime, and that column has no product write surface, so until an operator populates it from outside the product a manager step resolves to nobody and the request waits. `os lint` reports that at authoring time as `approval-approvers-may-resolve-empty` and carries the graded population routes and the full remedy; the Approvals guide states the same remedy in prose.
116+
115117
### Allowed Values
116118

117119
* `manager`

packages/lint/src/validate-approval-approvers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ const GROUP_ROUTED_TYPES = new Set(['position', 'team', 'department']);
139139
* a system-context write. Update them in the same change that opens the write
140140
* surface — and re-take the three grades above, which are readings of this
141141
* tree, not standing facts.
142+
*
143+
* Two other carriers assert the same fact and go stale with these strings, so
144+
* the list is theirs too: the `manager` callout in
145+
* `content/docs/automation/approvals.mdx`, and `ApproverType`'s `.describe()`
146+
* in `packages/spec/src/automation/approval.zod.ts` (rendered verbatim into
147+
* `content/docs/references/automation/approval.mdx`). Neither RESTATES the
148+
* remedy — both point back here, which is why there is still exactly one copy
149+
* to edit — but both assert that the column has no product write surface, and
150+
* that is the sentence which stops being true.
142151
*/
143152
// ⛔ The tracker ids stay in the comments above and never in this string:
144153
// `check:doc-authoring` Rule 3 — a runtime string reaches authors, operators and

packages/spec/src/automation/approval.zod.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,33 @@ export const ApproverType = z.enum([
7070
// (see NON_AUTHORABLE_APPROVER_TYPES). Re-admit only together with a real
7171
// ownership-queue implementation (queue entity + membership + claim).
7272
'queue',
73-
]);
73+
])
74+
// The generated reference page renders THIS string under `## ApproverType`;
75+
// the JSDoc above the enum renders nowhere (`renderSchemaSection` prints
76+
// `mainDef.description`, which only `.describe()` / `.meta({description})`
77+
// fills). Until this string existed the page listed `manager` as a bare
78+
// allowed value, so the one rung an author cannot operate on a stock install
79+
// read exactly like the nine that work.
80+
//
81+
// ⛔ It POINTS at the remedy, it does not restate it. The remedy's single
82+
// authoritative copy is `MANAGER_ONLY_REMEDY` / `MANAGER_ONLY_ROUTES` in
83+
// `packages/lint/src/validate-approval-approvers.ts`, mirrored for readers by
84+
// the Approvals guide; a third copy here would be one more line to keep in
85+
// step, and a pointer cannot drift into disagreement with what it points at.
86+
// That file's `⛔ DEPENDENCY` docblock now names this string among the lines
87+
// that go stale if `manager_id` ever gains a product write surface.
88+
//
89+
// ⛔ Tracker ids stay out of the string itself — it reaches authors and
90+
// generated surfaces, neither of whom can resolve one (`check:doc-authoring`
91+
// Rule 3).
92+
.describe(
93+
'Approval step approver type. `manager` is a directory-sync dependency rather than something ' +
94+
"an author configures here: it resolves the submitter's `sys_user.manager_id` at runtime, and " +
95+
'that column has no product write surface, so until an operator populates it from outside the ' +
96+
'product a manager step resolves to nobody and the request waits. `os lint` reports that at ' +
97+
'authoring time as `approval-approvers-may-resolve-empty` and carries the graded population ' +
98+
'routes and the full remedy; the Approvals guide states the same remedy in prose.',
99+
);
74100
export type ApproverType = z.input<typeof ApproverType>;
75101

76102
/**

0 commit comments

Comments
 (0)