Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/26756-automation-skill-screen-progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@objectstack/skills': patch
---

docs(skills): objectstack-automation says a paused screen's Next / Submit and step count are engine-served, never authored

Clause-②: no
Metadata-protocol: none

- **Before.** The `screen` node row described the fields an author declares and said nothing about the primary button or the step counter. An agent writing a wizard from the skill could hand-roll a "Submit" label or a "Step 2 of 4" into `fields`, or try to author `progress` on the screen node, and the skill did not say that the runtime answers both from the flow graph.
- **Now.** The `screen` node row says that Next / Submit and "Step N of M" are not authored: when a screen pauses the engine reads the graph and serves `progress` (`nextScreen: 'none' | 'possible' | 'certain'`; `step` / `stepCount` only when every path agrees), and the runner labels the button and draws the counter from that alone. A `decision`, a `fault` edge, a loop back, a non-screen pause (`approval`, `wait`) or a `subflow` after a screen answers `possible`, which shows "Next" with no count. A `progress` key in the node `config` is refused; a hand-rolled label or counter in `fields` is wrong.
- No schema, key or behaviour changes. No eval prompt: `evals/flows-triggers-approvals.json` sits at token-ratchet headroom 0.
23 changes: 22 additions & 1 deletion scripts/check-skills-token-ratchet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,28 @@ export const CEILINGS = new Map([
// the measurement. Maintainer ruling 2026-10-27 on the seat's decision card
// 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, tap A verbatim and
// untranslated: 「A:先削重复,余额按实测提高」.
['skills/objectstack-automation/SKILL.md', 6358],
// 25429 -> 25864 (#26756): the `screen` node row says Next / Submit and
// "Step N of M" are engine-served, never authored — when a screen pauses
// the engine reads the graph and serves `progress` (`nextScreen` `none` /
// `possible` / `certain`; `step` / `stepCount` only when every path
// agrees), a `decision`, `fault` edge, loop back, non-screen pause or
// `subflow` after a screen answers `possible` ("Next", no count), a
// `progress` key in the node config is refused, and a hand-rolled label or
// counter in `fields` is wrong. Paid down first, three restatements each
// with a surviving home: the Filter-operands bullet's "every other `{…}`
// token is refused" (Valid-but-silently-wrong item 1 lists every CRUD
// `filter` operand, -42 bytes); the Trigger Configuration lead-in "The
// binding is the START NODE" (the Triggers intro says the binding lives on
// the start node `config`, -23 bytes); item 3's "; flow functions don't"
// (its own bold lead says a flow function does not read or write the
// database, -22 bytes), plus the trimmed bullet's re-wrap indentation (-2
// bytes of whitespace). Arithmetic in priced bytes (stamp line excluded):
// 25429 -> 25864 (+524 added, -89 cut, +435 net) -> ceil(25864 / 4) =
// 6466; headroom was 3 bytes, 0 tokens, so the ceiling moves +108, pinned
// AT the measurement. Maintainer ruling 2026-10-27 on the seat's decision
// card 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, tap A
// verbatim and untranslated: 「A:先削重复,余额按实测提高」.
['skills/objectstack-automation/SKILL.md', 6466],
// 13783 -> 13892 (2026-08-31 app-repo-principles raise, see the block above).
// The invariant-vs-transition-gate choice, placed in the field-conditional-rule
// bullet list where the tool is actually picked. Both wrong picks are silent:
Expand Down
13 changes: 6 additions & 7 deletions skills/objectstack-automation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Flows are built from **20 built-in node types** (plugins register more via
| `notify` | Send a notification through the messaging service (inbox channel by default) |
| `connector_action` | Invoke a pre-built integration connector |
| `script` | Call a **registered** function named by `config.function` (see *Valid-but-silently-wrong* #3) |
| `screen` | Display a UI form to the user (screen flows only). `config.fields[]`: `{ name, label, type, required, options, defaultValue }`, `type` an open widget hint; `'file'` renders the upload widget and resumes with the `sys_file` id bound to `name` — a later `create_record` / `update_record` writes it into a `file` field, which claims the file: collect first, write after the last screen. `'record_link'` (never `'record'`, an object field type) takes no input — it shows the `reference` record whose id `defaultValue` evaluates to, by display name, as a link to its record page: the result screen of a flow that created records, `{ name: 'account_link', type: 'record_link', reference: 'crm_account', defaultValue: { dialect: 'cel', source: 'account.id' } }` over a `create_record` `outputVariable: 'account'`. Both keys required; `required` / `options` / `placeholder` / `min` / `max` refused; a resume value under its name refused (`400 INVALID_SCREEN_INPUT`). A completed `end` `message` is refused and a markdown link in a screen `description` renders literally |
| `screen` | Display a UI form to the user (screen flows only). `config.fields[]`: `{ name, label, type, required, options, defaultValue }`, `type` an open widget hint; `'file'` renders the upload widget and resumes with the `sys_file` id bound to `name` — a later `create_record` / `update_record` writes it into a `file` field, which claims the file: collect first, write after the last screen. `'record_link'` (never `'record'`, an object field type) takes no input — it shows the `reference` record whose id `defaultValue` evaluates to, by display name, as a link to its record page: the result screen of a flow that created records, `{ name: 'account_link', type: 'record_link', reference: 'crm_account', defaultValue: { dialect: 'cel', source: 'account.id' } }` over a `create_record` `outputVariable: 'account'`. Both keys required; `required` / `options` / `placeholder` / `min` / `max` refused; a resume value under its name refused (`400 INVALID_SCREEN_INPUT`). A completed `end` `message` is refused and a markdown link in a screen `description` renders literally. Next / Submit and "Step N of M" are not authored: the engine reads the graph when the screen pauses and serves `progress` (`nextScreen: 'none' | 'possible' | 'certain'`; `step` / `stepCount` only when every path agrees), which alone labels the button and draws the counter. A `decision`, `fault` edge, loop back, non-screen pause (`approval`, `wait`) or `subflow` after a screen answers `possible` — "Next", no count. `progress` in a node `config` is refused; never hand-roll a button label or step counter into `fields` |

#### Human Decision

Expand Down Expand Up @@ -217,10 +217,9 @@ variables: [
evaluated before the query (`filter: { id: { dialect: 'cel', source:
'record.id' } }`). A whole-string **query placeholder** (`{current_user_id}`,
`{today}`, `{current_year_start}`) passes through verbatim for the query
engine to expand; every other `{…}` token is refused, and a flow variable
never shadows a placeholder. Only
`filter` gets this hand-off — in a text slot `{{ current_year_start }}` is a
nonsense reference.
engine to expand; a flow variable never shadows a placeholder. Only `filter`
gets this hand-off — in a text slot `{{ current_year_start }}` is a nonsense
reference.
- **An operand that evaluates to `null` refuses the node** (not
`fault`-routable): a dropped condition does not narrow a query, it widens
it, so a routed `delete_record` would empty the object while the run reports
Expand Down Expand Up @@ -299,7 +298,7 @@ Legal metadata that authors — AI especially — get wrong; most are caught by
```

If you genuinely need data-lifecycle **side effects** (read/write other records),
that's an L2 **hook** (objectstack-data) — hooks get `ctx.api`; flow functions don't.
that's an L2 **hook** (objectstack-data) — hooks get `ctx.api`.

A function that writes where the platform cannot see **declares** it, so the
run reports "cannot say" rather than `acted: 0`:
Expand Down Expand Up @@ -381,7 +380,7 @@ node `config`**:

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

The binding is the START NODE — this is that node, not a whole flow (a flow also
This is the start node alone, not a whole flow (a flow also
owns `label`, `type`, `nodes` and the `edges` `FlowSchema` requires):

```typescript
Expand Down
Loading