From ff95b6edebd15a97b333b5d3f3fb9c9c0aed66c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 06:20:59 +0000 Subject: [PATCH 1/2] docs(spec): ApproverType.describe() qualifies `manager` instead of offering it bare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH --- .../lint/src/validate-approval-approvers.ts | 9 ++++++ packages/spec/src/automation/approval.zod.ts | 28 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/lint/src/validate-approval-approvers.ts b/packages/lint/src/validate-approval-approvers.ts index 220014cf7a..0430971303 100644 --- a/packages/lint/src/validate-approval-approvers.ts +++ b/packages/lint/src/validate-approval-approvers.ts @@ -139,6 +139,15 @@ const GROUP_ROUTED_TYPES = new Set(['position', 'team', 'department']); * a system-context write. Update them in the same change that opens the write * surface — and re-take the three grades above, which are readings of this * tree, not standing facts. + * + * Two other carriers assert the same fact and go stale with these strings, so + * the list is theirs too: the `manager` callout in + * `content/docs/automation/approvals.mdx`, and `ApproverType`'s `.describe()` + * in `packages/spec/src/automation/approval.zod.ts` (rendered verbatim into + * `content/docs/references/automation/approval.mdx`). Neither RESTATES the + * remedy — both point back here, which is why there is still exactly one copy + * to edit — but both assert that the column has no product write surface, and + * that is the sentence which stops being true. */ // ⛔ The tracker ids stay in the comments above and never in this string: // `check:doc-authoring` Rule 3 — a runtime string reaches authors, operators and diff --git a/packages/spec/src/automation/approval.zod.ts b/packages/spec/src/automation/approval.zod.ts index afbd3fec3b..56026b83c5 100644 --- a/packages/spec/src/automation/approval.zod.ts +++ b/packages/spec/src/automation/approval.zod.ts @@ -70,7 +70,33 @@ export const ApproverType = z.enum([ // (see NON_AUTHORABLE_APPROVER_TYPES). Re-admit only together with a real // ownership-queue implementation (queue entity + membership + claim). 'queue', -]); +]) + // The generated reference page renders THIS string under `## ApproverType`; + // the JSDoc above the enum renders nowhere (`renderSchemaSection` prints + // `mainDef.description`, which only `.describe()` / `.meta({description})` + // fills). Until this string existed the page listed `manager` as a bare + // allowed value, so the one rung an author cannot operate on a stock install + // read exactly like the nine that work. + // + // ⛔ It POINTS at the remedy, it does not restate it. The remedy's single + // authoritative copy is `MANAGER_ONLY_REMEDY` / `MANAGER_ONLY_ROUTES` in + // `packages/lint/src/validate-approval-approvers.ts`, mirrored for readers by + // the Approvals guide; a third copy here would be one more line to keep in + // step, and a pointer cannot drift into disagreement with what it points at. + // That file's `⛔ DEPENDENCY` docblock now names this string among the lines + // that go stale if `manager_id` ever gains a product write surface. + // + // ⛔ Tracker ids stay out of the string itself — it reaches authors and + // generated surfaces, neither of whom can resolve one (`check:doc-authoring` + // Rule 3). + .describe( + '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.', + ); export type ApproverType = z.input; /** From 1aca42a2d7599ada6b901c9a56a85f7293b8d757 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 07:15:19 +0000 Subject: [PATCH 2/2] docs(spec): regenerate the approval reference page and add the changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH --- .../17579-approver-type-manager-describe.md | 43 +++++++++++++++++++ .../docs/references/automation/approval.mdx | 6 ++- 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 .changeset/17579-approver-type-manager-describe.md diff --git a/.changeset/17579-approver-type-manager-describe.md b/.changeset/17579-approver-type-manager-describe.md new file mode 100644 index 0000000000..d4e9fb9879 --- /dev/null +++ b/.changeset/17579-approver-type-manager-describe.md @@ -0,0 +1,43 @@ +--- +'@objectstack/spec': patch +--- + +`ApproverType` qualifies `manager` in its `.describe()` instead of offering it as a bare allowed value + +`ApproverType` carried **no** `.describe()` at all, so the generated reference +page rendered `## ApproverType` with nothing but an `### Allowed Values` list: +`manager` — the one rung an author cannot operate on a stock install — read +exactly like the nine members that work. `{ type: 'manager' }` resolves +`sys_user.manager_id`, and that column still has no product write surface +(re-measured on this tree: the identity write guard's managed-update whitelist +for `sys_user` is `{name, image, locale}`; the column carries `readonly: true`; +no `packages/plugins/plugin-auth` source writes it). An author who chose it got +a chain that passed `validate` and `lint` and then stalled on its first +submission. + +The new describe says what is true about `manager` and **points** at the remedy +rather than restating it: `MANAGER_ONLY_REMEDY` / `MANAGER_ONLY_ROUTES` in +`packages/lint/src/validate-approval-approvers.ts` remain the single +authoritative copy of the population routes, 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. A pointer cannot drift into disagreement with what +it points at, which is why no third copy of the 667-character remedy was added. + +⛔ No member is added, removed or renamed, and no behaviour changes: the enum's +accept set is byte-identical and `check:api-surface` is green on the rebuilt +`dist/*.d.ts`. + +**Why this ships, and why `patch`.** `@objectstack/spec`'s published `files[]` +carries `dist`, `json-schema` and `src/**/*.zod.ts`, and the new string is +measured in all three on the built tree — `dist/automation/index.js` and +`.mjs` (2 files, against a lit control of an existing describe from the same +module, also 2), four `json-schema/` documents (`ApproverType.json`, +`ApprovalNodeApprover.json`, `ApprovalNodeConfig.json`, `objectstack.json`) and +the shipped `approval.zod.ts` source. Prose only, no surface widening ⇒ +`patch`. + +The `packages/lint` half is a docblock comment and is deliberately **not** +graded: that package publishes `dist` only, and the new sentence is absent from +it (0 files) while a runtime string from the same source file is present in 4 +and a pre-existing comment from the same docblock is absent in 0 — so comments +are stripped by construction and nothing published moves there. diff --git a/content/docs/references/automation/approval.mdx b/content/docs/references/automation/approval.mdx index 364d1def95..f16e1cd79f 100644 --- a/content/docs/references/automation/approval.mdx +++ b/content/docs/references/automation/approval.mdx @@ -52,7 +52,7 @@ const result = ApprovalDecision.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **type** | `Enum<'manager' \| 'position' \| 'department' \| 'team' \| 'field' \| 'expression' \| 'org_membership_level' \| 'role' \| 'user' \| 'queue'>` | ✅ | | +| **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. | | **value** | `string` | optional | User id / membership tier / position / team / department / field — per `type`; for `expression`, a CEL expression over `current.*` / `trigger.*` / `vars.*` | | **resolveAs** | `Enum<'user' \| 'department' \| 'position' \| 'team'>` | optional | How an `expression` result is expanded into approvers (default 'user') | | **group** | `string` | optional | Group label for per_group sign-off (e.g. "legal", "finance") | @@ -81,7 +81,7 @@ const result = ApprovalDecision.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **type** | `Enum<'manager' \| 'position' \| 'department' \| 'team' \| 'field' \| 'expression' \| …>` | ✅ | | +| **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. | | **value** | `string` | optional | User id / membership tier / position / team / department / field — per `type`; for `expression`, a CEL expression over `current.*` / `trigger.*` / `vars.*` | | **resolveAs** | `Enum<'user' \| 'department' \| 'position' \| 'team'>` | optional | How an `expression` result is expanded into approvers (default 'user') | | **group** | `string` | optional | Group label for per_group sign-off (e.g. "legal", "finance") | @@ -112,6 +112,8 @@ const result = ApprovalDecision.parse(data); ## ApproverType +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. + ### Allowed Values * `manager`