Skip to content

Commit 16c3601

Browse files
huangyiireneclaude
andauthored
docs(skills): objectstack-automation factual sweep — 11 false behavioral facts corrected (#13808)
Flight 6 of the published-skills factual sweep. Every corrected claim was settled against the implementing code (spec schemas, plugin-approvals, service-automation, trigger-api / trigger-schedule, @objectstack/lint), never against another document, with executed parse / CEL / schedule-normalization probes where the claim is behaviour-bearing. Net: -24 tokens, -4 lines across the package; the two ratcheted files that moved stay under their existing ceilings (no ceiling touched). Claude-Session: https://claude.ai/code/session_01EnE7G31tqbxN1rqpQmzurT Co-authored-by: Claude <noreply@anthropic.com>
1 parent e72ded0 commit 16c3601

2 files changed

Lines changed: 17 additions & 21 deletions

File tree

‎skills/objectstack-automation/SKILL.md‎

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ variables: [
131131
### Flow Example — Auto-Escalate Overdue Cases
132132

133133
> **Nodes connect via `edges`, not a `next` property.** The engine traverses
134-
> `flow.edges` (`{ source, target }`); a bare `next:` on a node is ignored.
134+
> `flow.edges` (`{ source, target }`); a bare `next:` on a node is refused.
135135
> `update_record` selects rows with **`filter`** — an ObjectQL `where` **map**
136136
> of `field → value` / `field → { $operator: value }`, NOT the UI view-filter
137137
> `[{ field, operator, value }]` triples — and writes with **`fields`**
@@ -187,10 +187,9 @@ variables: [
187187
type: 'start',
188188
label: 'Daily at 09:00',
189189
// The cadence lives HERE, on the start node's config — FlowSchema has NO
190-
// top-level `schedule` key (one there is silently stripped and the flow
191-
// never binds). A bare cron string also works: schedule: '0 9 * * *'.
192-
// Do NOT use the cron`…` tagged template — its envelope is not a
193-
// recognized schedule shape.
190+
// top-level `schedule` key (one there is a named parse error, not a silent
191+
// strip). A bare cron string also works: schedule: '0 9 * * *'. Do NOT use
192+
// the cron`…` tagged template — its envelope is not a recognized shape.
194193
config: { schedule: { type: 'cron', expression: '0 9 * * *' } },
195194
},
196195
{
@@ -477,12 +476,12 @@ still missing.
477476
|:-------|:------------|
478477
| `user` | A specific user id (`value` = user id) |
479478
| `position` | Holders of a position — `value` = the position machine name, resolved via `sys_user_position` (ADR-0090 D3) |
480-
| `org_membership_level` | The better-auth **org-membership tier** — `value` is one of `owner`/`admin`/`member`, and nothing else. **NOT** a position: `{ type: 'org_membership_level', value: 'sales_manager' }` matches nobody; use `position`. Spelled `role` before ADR-0090 D3 — that spelling is deprecated, still resolves, and is removed in the next major |
479+
| `org_membership_level` | The **org-membership tier** — `value` is one of `owner`/`admin`/`delegated_admin`/`member`. **NOT** a position: `{ type: 'org_membership_level', value: 'sales_manager' }` matches nobody; use `position`. Spelled `role` before ADR-0090 D3 — that spelling is deprecated, still resolves, and is removed in the next major |
481480
| `team` | Members of a flat `sys_team` |
482481
| `department` | A department + all descendant departments |
483482
| `manager` | The submitter's manager (`sys_user.manager_id`) |
484483
| `field` | User id read from a record field (`value` = field name). Resolved against the record's **live** state at node entry, so a field written mid-flow routes correctly; a multi-select user field fans out into one approver per user |
485-
| `queue` | A data-ownership queue |
484+
| `queue` | ⛔ Declared but never resolved — the slot routes to nobody. Do not author |
486485
| `expression` | A **CEL expression** resolved at node entry (`value` = the expression) — see **Dynamic approvers** below. Only `current.*` / `trigger.*` / `vars.*` roots are available; the optional `resolveAs: 'user'(default) \| 'department' \| 'position' \| 'team'` re-expands each resolved id through the graph |
487486

488487
### Dynamic approvers (`type: 'expression'`)
@@ -602,11 +601,11 @@ pre-write row, both made total over the object's declared fields. See
602601
|:------|:--------|
603602
| `approvers` | Who may act (≥ 1 — see Approver Types above). Each approver may carry an optional **`group`** label (e.g. `{ type: 'position', value: 'auditor', group: 'finance' }`) — with `behavior: 'per_group'`, approvers sharing a label form one group; unlabelled approvers each form their own |
604603
| `behavior` | `first_response` (first approver decides), `unanimous` (all must approve), `quorum` (`minApprovals` of N — M-of-N collective sign-off), or `per_group` (EACH approver `group` must reach `minApprovals` — one-from-each-group sign-off, 会签). In every mode a single rejection finalizes the node as `rejected`. Default `first_response` |
605-
| `minApprovals` | Approvals required — total for `quorum`, per group for `per_group`. Default `1`; clamped at runtime to the resolvable approver count so a misconfiguration can never deadlock |
604+
| `minApprovals` | Approvals required — total for `quorum`, per group for `per_group`. Omitted ⇒ ALL resolvable approvers under `quorum`, `1` per group; clamped at runtime so a misconfiguration can never deadlock |
606605
| `lockRecord` | Lock the triggering record from edits while pending. Default `true` |
607606
| `approvalStatusField` | Business-object field to mirror `pending`/`approved`/`rejected`/`recalled` onto (should be readonly) |
608607
| `onEmptyApprovers` | What an EMPTY resolved slate does: `admin_rescue` (default — request opens, only a privileged admin can act via Reassign; never waves through, never kills the run), `fail` (node fails — treat an empty slate as a config bug), `auto_approve` (skip the request, continue down `approve` with `output.autoApproved = true` — opt-in because it silently waves the record through). Declare it explicitly on any node with an `expression` approver (linted) |
609-
| `decisionOutputs` | Decision outputs a decision may carry (author declares, approvers fill values). Entries are bare keys (free-text input) **or typed declarations** `{ key, label?, type: 'text'\|'user'\|'department'\|'position'\|'team', multiple? }` — a typed entry renders the matching record picker in the decision dialog (`multiple` collects an id array). Accepted outputs resume the run as `<nodeId>.<key>` variables; undeclared keys reject the decision; `decision`/`requestId` reserved |
608+
| `decisionOutputs` | Decision outputs a decision may carry (author declares, approvers fill values). Entries are bare keys (free-text input) **or typed declarations** `{ key, label?, type: 'text'\|'user'\|'department'\|'position'\|'team', multiple?, required? }` — a typed entry renders the matching record picker in the decision dialog (`multiple` collects an id array). Accepted outputs resume the run as `<nodeId>.<key>` variables; undeclared keys reject the decision; `decision`/`requestId` reserved |
610609
| `escalation` | Optional per-node SLA — `{ enabled, timeoutHours, action: reassign\|auto_approve\|auto_reject\|notify, escalateTo?, notifySubmitter }`. `escalateTo` is a **position machine name** (expanded to its holders via `sys_user_position`, ADR-0090 D3) or a specific user id — never a membership tier. `reassign` without `escalateTo` degrades to notify (linted) |
611610
| `maxRevisions` | ADR-0044 — max **send-backs-for-revision** per run before auto-reject. Default `3`; `0` disables send-back. Only meaningful when the node has a `revise` out-edge |
612611

@@ -620,13 +619,9 @@ These are wired on the **graph**, not in node config:
620619
`http`, a `notify` node, …) to the `approve` / `reject` out-edge.
621620
- **Roll back on reject** — route the `reject` edge as a **back-edge** to an
622621
earlier node so the submitter can revise (the old `back_to_previous`).
623-
- **Send back for revision (ADR-0044)** — distinct from a plain reject: an
624-
Approval node can emit a third decision **`revise`** on a `revise`-labeled
625-
out-edge that routes to an **`approval_revise`** rework window (not a plain
626-
`wait`). The submitter edits and resubmits, re-entering the node via an
627-
edge `type: 'back'` (a declared back-edge — traversed at run time but excluded
628-
from DAG cycle validation). `maxRevisions` (node config, default `3`) caps the
629-
loop before auto-reject.
622+
- **Send back for revision (ADR-0044)** — distinct from a plain reject: a
623+
`revise` out-edge into an **`approval_revise`** window, closed by a
624+
`type: 'back'` resubmit edge. See *Send-back for revision* above.
630625
- **Hard reject** — route the `reject` edge to an `end` node (the old
631626
`reject_process`).
632627

@@ -666,7 +661,7 @@ defineStack({
666661
// + 'job' for scheduled (cron) flows
667662
// + 'queue' for inbound-webhook ('api') flows — the trigger-api plugin
668663
// depends on the queue service; without it every inbound POST
669-
// returns 503 queue_unavailable.
664+
// returns 503 SERVICE_UNAVAILABLE.
670665
});
671666
```
672667

@@ -697,6 +692,7 @@ read at runtime, not Zod-validated):
697692
| `record-after-update` | after update | `afterUpdate` |
698693
| `record-before-delete` | before delete | `beforeDelete` |
699694
| `record-after-delete` | after delete | `afterDelete` |
695+
| `record-before-write` / `record-after-write` | create OR update — one flow, both events | both insert + update hooks |
700696

701697
### Trigger Configuration — on the `start` node
702698

@@ -725,7 +721,7 @@ read at runtime, not Zod-validated):
725721
> **`previous`** and **`record`** are the CEL variables available in update
726722
> triggers — `previous.x` is the value before the change, `record.x` is the
727723
> value after. (Salesforce-flavor `OLD` / `NEW` were removed in M9.5 and now
728-
> evaluate to `null`.) See [objectstack-formula](../objectstack-formula/SKILL.md).
724+
> fault the predicate.) See [objectstack-formula](../objectstack-formula/SKILL.md).
729725
730726
### Time-relative triggers — scheduled per-record date sweep
731727

@@ -850,7 +846,7 @@ them right the first time:
850846
❌ `'{ROUND(x, 2)}'` / `'{Math.round(x)}'` / `'{(x).toFixed(2)}'` — any other
851847
name in call position **fails the node** with a named error naming the
852848
supported set. The build does **not** catch these (conditions are checked,
853-
value expressions are not) and a `fault` edge cannot route it.
849+
call-position names are not) and a `fault` edge cannot route it.
854850

855851
7. **`create_record`'s `outputVariable` holds the created RECORD, not its id.**
856852
Reference a field explicitly.
@@ -968,7 +964,7 @@ syntax error, an unknown function (`PRIOR()`) or a `{…}`-wrapped reference
968964
**throws**, located and corrective — never silent.
969965

970966
**Node values** take the single-brace `flow-template` dialect (`'{round(x)}'`);
971-
no validator implements it, so an unknown function there is NOT build-checked —
967+
no validator checks its call names, so an unknown function is NOT build-checked —
972968
it throws `FlowExpressionFunctionError` at **run time**.
973969

974970
The quiet case is a typo'd *field* name: bare refs (`status == 'open'`) DO

‎skills/objectstack-automation/evals/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ flows, approval chains, triggers, and scheduled sweeps.
88

99
| Eval | Covers |
1010
|:-----|:-------|
11-
| [approvals/test-revise-loop.md](./approvals/test-revise-loop.md) | ADR-0044 send-back-for-revision: the `revise` branch, the signal `wait` node, and the resubmit edge declared `type: 'back'` |
11+
| [approvals/test-revise-loop.md](./approvals/test-revise-loop.md) | ADR-0044 send-back-for-revision: the `revise` branch, the `approval_revise` window, and the resubmit edge `type: 'back'` |
1212

1313
## Planned structure
1414

0 commit comments

Comments
 (0)