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
29 changes: 27 additions & 2 deletions scripts/check-skills-token-ratchet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,23 @@ export const CEILINGS = new Map([
// 6004: +233 net, 9 absorbed by headroom, ceiling +224. Maintainer ruling
// 2026-10-27 on the skills-lane decision card 「skills 修复卡的教学和 eval 都撞
// token 天花板,怎么付?」, verbatim and untranslated: 「A:先削重复,余额按实测提高」.
['skills/objectstack-automation/SKILL.md', 6004],
// 6004 -> 6164 (card #26347, the skills half of the `record_link` screen
// field): the `screen` node row teaches the read-only `type: 'record_link'`
// field — it shows the `reference` record whose id `defaultValue` evaluates
// to, both keys required, the five input keys refused, the resume refuses a
// value under its name — with the one result-screen example over a
// `create_record` `outputVariable`, and the two workarounds an author
// otherwise reaches for (a completed `end` `message`, a markdown link in a
// screen `description`). Paid down first: the Filter operands bullet
// restated Valid-but-silently-wrong item 1's refusal detail
// (`expression-invalid`, "with its envelope") and was cut to the bare
// refusal (-41 bytes). Arithmetic in priced bytes (stamp line excluded):
// 24014 -> 24654 (+681 added, -41 cut, +640 net) -> ceil(24654 / 4) = 6164;
// headroom was 2 bytes, 0 tokens, so the ceiling moves +160, 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', 6164],
// 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 Expand Up @@ -737,7 +753,16 @@ export const CEILINGS = new Map([
// +568 tokens, nothing in the file to delete, zero headroom, ceiling +568.
// Same maintainer ruling as the SKILL.md row above (2026-10-27, verbatim and
// untranslated): 「A:先削重复,余额按实测提高」.
['skills/objectstack-automation/evals/flows-triggers-approvals.json', 1823],
// 1823 -> 2141 (card #26347): one eval prompt for the `record_link` result
// screen — a `convert_lead` screen flow that creates an account and a contact
// and ends on a screen linking both, the ideal answer written so it never
// spells the negated `type: 'record'`. Paid down first: the file's eight
// fixtures share no sentence, nothing to cut (0 bytes). 7289 -> 8561 bytes
// (+1272) -> ceil(8561 / 4) = 2141; headroom was 3 bytes, 0 tokens, so the
// ceiling moves +318, pinned AT the measurement. Same maintainer ruling as
// the SKILL.md row above (2026-10-27, verbatim and untranslated):
// 「A:先削重复,余额按实测提高」.
['skills/objectstack-automation/evals/flows-triggers-approvals.json', 2141],
// 894 -> 1095 (#26179): one regression case for the field permission `when`
// (the contract prompt: lock the legal terms for finance from approval on,
// legal keeps the edit) — expected: `when` on the finance set's entries with
Expand Down
6 changes: 3 additions & 3 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 |
| `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 |

#### Human Decision

Expand Down Expand Up @@ -206,8 +206,8 @@ 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 (`expression-invalid`)
with its envelope, and a flow variable never shadows a placeholder. Only
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.
- **An operand that evaluates to `null` refuses the node** (not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@
"must_contain": ["type: 'screen'", "type: 'file'", "create_record", "signed_copy"],
"must_not_contain": ["type: 'upload'", "type: 'attachment'"]
}
},
{
"id": 9,
"prompt": "Build a screen flow `convert_lead` that asks for a company name and a contact name, creates a `crm_account` and a `crm_contact` linked to it, and ends on a screen that shows links to both new records.",
"expected_output": "A `screen` flow (`type: 'screen'`): a first `screen` node collecting `company_name` and `contact_name`; two `create_record` nodes with `outputVariable` (`account`, `contact`), the contact's `fields` linking it with `account: { dialect: 'cel', source: 'account.id' }`; then a last `screen` node whose fields are two `type: 'record_link'` fields, each with `reference` naming its object (`crm_account`, `crm_contact`) and `defaultValue: { dialect: 'cel', source: 'account.id' }` / `{ dialect: 'cel', source: 'contact.id' }`, carrying no `required`, `placeholder` or `options`. The `end` node carries no `message` (refused on a completed end) and the screen `description` holds no markdown link — the links are the `record_link` fields, spelled `record_link`, never the object field type.",
"files": [],
"assertions": {
"must_contain": ["record_link", "reference", "defaultValue", "outputVariable", "dialect: 'cel'"],
"must_not_contain": ["type: 'record'", "]("]
}
}
]
}
Loading