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
337 changes: 328 additions & 9 deletions scripts/check-doc-authoring.mjs

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions scripts/check-ratchet-remedy-authority.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -690,6 +690,10 @@ const CONTROL = {
expect: 'excluded',
why: 'CROSS_PACKAGE_TEST_INPUTS declares which globs a package tests read. A naive-prototype false positive.',
},
'check-doc-authoring.mjs': {
expect: 'excluded',
why: 'Carries no ratchet at all. ROOTS, SKIP_PATHS, SKIP_FILES and GENERATED_SKILL_ARTIFACTS are declarations of what the two corpus rules read and what is generated rather than authored; each is path-scoped, and none is offered to the author as something to widen. Its published-catalog id rule reached this corpus by first shipping a per-passage allowlist and being turned red here for offering it — maintainer ruling 2026-08-25 replaced that passage with a placeholder and removed the list. If this flips, that list came back.',
},
'check-error-code-casing.mjs': {
expect: 'excluded',
why: 'EXEMPT_FILES records a file whose literals are not error codes.',
Expand Down
62 changes: 46 additions & 16 deletions scripts/check-skills-token-ratchet.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -130,6 +130,27 @@ export const CEILING_BASIS = {
main: '465bfce90',
pending10402: '7228d6c25',
from10402: ['skills/objectstack-data/SKILL.md', 'skills/objectstack-platform/SKILL.md'],
/**
* Every ceiling below was RE-MEASURED after the internal issue-id strip, on
* this sha plus that change. Maintainer ruling 2026-08-23: strip the internal
* issue-id references from the published catalog, per-file ceiling drops
* landing in the same PR.
*
* The re-measure supersedes both bases above as the origin of the numbers —
* they are kept because they still explain the SHAPE of the two rows that
* carried a second basis, not because any current number is read from them.
* ⚠️ #10402's reserved headroom was already spent when this landed:
* `objectstack-data` measured 13817 against a 13817 ceiling on the base
* below — exactly zero, which is the header's "the headroom returns to zero
* on its own", observed.
*
* Several rows drop by MORE than their file shrank, because a lowering also
* reclaims whatever slack the row already carried (`objectstack-formula`
* shrank 34 and its ceiling drops 53). That is the ratchet working as
* designed: shrink-only means a ceiling may be lowered to the measurement
* whenever one is taken, not merely by the size of the day's deletion.
*/
strippedInternalIds: '3f571a6d2',
};

/**
Expand All@@ -141,23 +162,21 @@ export const CEILING_BASIS = {
* branch head `7228d6c25` — see {@link CEILING_BASIS}.
*/
export const CEILINGS = new Map([
['skills/objectstack-ai/SKILL.md', 6824],
['skills/objectstack-api/SKILL.md', 6342],
['skills/objectstack-automation/SKILL.md', 12543],
// basis 7228d6c25 (PR #10402 head), not main — see CEILING_BASIS.
// +20 (13797→13817): maintainer ruling 2026-08-23 on PR #11141 — the three
// SECURITY_OWD_UNSET-required sharingModel keys. Ruling quoted in that PR's body.
['skills/objectstack-data/SKILL.md', 13817],
['skills/objectstack-formula/SKILL.md', 6055],
['skills/objectstack-i18n/SKILL.md', 6349],
// basis 7228d6c25 (PR #10402 head), not main — see CEILING_BASIS.
['skills/objectstack-platform/SKILL.md', 12716],
// Every row re-measured after the internal issue-id strip — see
// CEILING_BASIS.strippedInternalIds. `(was N)` is the ceiling this replaced.
['skills/objectstack-ai/SKILL.md', 6806], // -18 (was 6824)
['skills/objectstack-api/SKILL.md', 6331], // -11 (was 6342)
['skills/objectstack-automation/SKILL.md', 12511], // -32 (was 12543)
['skills/objectstack-data/SKILL.md', 13783], // -34 (was 13817)
['skills/objectstack-formula/SKILL.md', 6002], // -53 (was 6055)
['skills/objectstack-i18n/SKILL.md', 6338], // -11 (was 6349)
['skills/objectstack-platform/SKILL.md', 12705], // -11 (was 12716)
// Unchanged: this skill's only id-shaped tokens are a CLI usage line and a
// JSON shape example. The one edit there (`#457` -> `#<n>`) is byte-neutral.
['skills/objectstack-pm-dispatch/SKILL.md', 14239],
['skills/objectstack-query/SKILL.md', 5569],
['skills/objectstack-ui/SKILL.md', 25154],
// +10 (8325→8335): same 2026-08-23 ruling — crm_lead's SECURITY_OWD_UNSET-required
// sharingModel key (value mirrored from examples/app-crm per the same ruling).
['skills/objectstack-upgrade/SKILL.md', 8335],
['skills/objectstack-query/SKILL.md', 5552], // -17 (was 5569)
['skills/objectstack-ui/SKILL.md', 25125], // -29 (was 25154)
['skills/objectstack-upgrade/SKILL.md', 8333], // -2 (was 8335)
]);

/**
Expand DownExpand Up@@ -356,6 +375,17 @@ function selfTest() {
'skills/objectstack-data/SKILL.md,skills/objectstack-platform/SKILL.md'],
['both second-basis files carry a ceiling', CEILING_BASIS.from10402.every((p) => CEILINGS.has(p)), true],

// ── the internal-id strip re-measure ─────────────────────────────────
// Same reason as the two pins above: the gate never reads this sha, so a
// recorded provenance that drifts from the numbers it explains does so in
// silence. Pinned here, it moves only when someone means to move it.
['the internal-id-strip basis sha is recorded', CEILING_BASIS.strippedInternalIds, '3f571a6d2'],
// The direction, asserted rather than assumed: this re-measure LOWERED the
// bundle. A future edit that re-measures upward has to change this number
// and meet the maintainer-ruling bar in the header while doing it.
['the re-measure lowered the bundle total',
[...CEILINGS.values()].reduce((a, b) => a + b, 0) < 117943, true],

// ── the report line ──────────────────────────────────────────────────
['the report prints a bundle total',
reportLines([{ rel, tokens: 10, ceiling: 20 }]).some((l) => l.includes('bundle total')), true],
Expand Down
2 changes: 1 addition & 1 deletion skills/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ npx skills add objectstack-ai/objectstack/skills --all
```

The `/skills` subpath matters: it is the published catalog boundary — pointing
the skills CLI at the repo root would also pick up repo-internal skills (#3101).
the skills CLI at the repo root would also pick up repo-internal skills.

Each **domain** skill is self-contained: a `SKILL.md` with YAML frontmatter, plus a
`references/_index.md` that points into the authoritative Zod sources in
Expand Down
10 changes: 5 additions & 5 deletions skills/objectstack-ai/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ To grant data exploration to your own (platform-internal) agent, add
| `tools` | Direct tool references — legacy fallback |
| `surface` | `'ask' \| 'build'` — the product surface this agent is (default `'ask'`) |
| `model` | LLM model configuration — `provider`, `model`, `temperature`, `maxTokens`, `topP` |
| ~~`knowledge`~~ | REMOVED in protocol 17 (#3896 close-out) — declaring sources/indexes on an agent never scoped retrieval (`search_knowledge` takes `sourceIds` from the LLM's tool-call arguments). Restrict at the knowledge-service/source level; describe intended grounding in `instructions` |
| ~~`knowledge`~~ | REMOVED in protocol 17 — declaring sources/indexes on an agent never scoped retrieval (`search_knowledge` takes `sourceIds` from the LLM's tool-call arguments). Restrict at the knowledge-service/source level; describe intended grounding in `instructions` |
| `guardrails` | `maxTokensPerInvocation`, `maxExecutionTimeSec`, `blockedTopics` |
| `structuredOutput` | Output format (JSON schema, regex, etc.) |
| `planning` | Autonomous reasoning — `maxIterations` (default 10) |
Expand DownExpand Up@@ -298,10 +298,10 @@ A tool authored as metadata (`type: 'tool'`, `*.tool.ts`) is validated by
`ToolSchema`: required `name` / `label` / `description`, a **JSON Schema**
`parameters` object, plus optional `objectName` and `outputSchema`. `ToolSchema`
is **strict** — an unknown key (a typo, or a retired key) is a parse error, not
a silent strip. Retired in the #3896 close-out: `category`, `permissions`,
a silent strip. Retired in protocol 17: `category`, `permissions`,
`active` and `builtIn` (all were authorable and inert; `permissions` gated
nothing and `active: false` withdrew nothing — the rejection message carries
each key's replacement), joining `requiresConfirmation` (#3715).
each key's replacement), joining `requiresConfirmation`.

<!-- os:check -->
```typescript
Expand DownExpand Up@@ -334,7 +334,7 @@ enforced surface).
> executor loads a metadata-authored tool. The runtime executes a
> separately-registered `AIToolDefinition` (cloud `@objectstack/service-ai`);
> tool metadata is a one-way projection for Studio / discovery. Do not expect a
> hand-authored tool to run in the open edition (liveness audit #1878/#1892).
> hand-authored tool to run in the open edition.

### Inline Agent `tools[]` (legacy)

Expand DownExpand Up@@ -544,7 +544,7 @@ On validation failure the runtime retries by default
`ai.requiresConfirmation` on the **action**, or `approval: 'always'` on an
MCP tool binding. AI metadata edits are already gated: they land as drafts a
human must publish (ADR-0033).
⚠️ `requiresConfirmation` on the **tool** was REMOVED (#3715, ADR-0033 §2) —
⚠️ `requiresConfirmation` on the **tool** was REMOVED (ADR-0033 §2) —
it was read by no execution path, so it produced no pause. `ToolSchema` is
strict, so authoring it now fails the parse with the migration attached.
There is no `requireApprovalFor` field.
Expand Down
12 changes: 6 additions & 6 deletions skills/objectstack-api/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,7 +65,7 @@ aggregation goes through `POST /api/v1/data/{object}/query` with
> **Key rule:** If your object defines `apiMethods`, only those operations (and
> what derives from them) are exposed. For example, `apiMethods: ['get', 'list']`
> creates a read-only API. The authorable values are the SIX PRIMITIVES
> (`get/list/create/update/delete/bulk`, #3543); everything else (`export`,
> (`get/list/create/update/delete/bulk`); everything else (`export`,
> `search`, `upsert`, …) is DERIVED from them by the server — `['list']` grants
> aggregate/search/export for free, `['create','update']` grants upsert/import.
> An empty array `[]` means deny-all (fully closed).
Expand DownExpand Up@@ -149,7 +149,7 @@ The alternative — and usually the better one — is the **declarative** surfac
## Declarative Endpoints (`apis:`) — no handler code

`defineStack({ apis })` declares an HTTP endpoint as **metadata**. Declared
endpoints are **live from protocol 17** (#5040): the runtime matches
endpoints are **live from protocol 17**: the runtime matches
`METHOD` + `path`, runs the endpoint's policy keys, and delegates to the *same*
pipelines the built-in routes use — `object_operation` to the data pipeline
behind `/api/v1/data/{object}`, `flow` to the automation pipeline behind
Expand DownExpand Up@@ -282,7 +282,7 @@ only the scoped routes are registered; with `optional`/`auto` the bare

## API Methods (Operations)

The authorable `ApiMethod` enum is the SIX PRIMITIVES (#3543). The wider
The authorable `ApiMethod` enum is the SIX PRIMITIVES. The wider
EFFECTIVE operation vocabulary (`ApiOperation`, 14 values) is what gates and
responses speak — the eight extra verbs are DERIVED from the primitives, never
declared in `apiMethods`:
Expand All@@ -306,8 +306,8 @@ declared in `apiMethods`:
| `aggregate` | `list` | No dedicated route — use `POST /data/{object}/query` with `groupBy`/`aggregations` | Count, sum, avg, min, max |
| `history` | `get` ∧ `trackHistory` | Gating only — no dedicated generated route today | Audit trail access |
| `search` | `list` ∧ `searchable` | Global `GET /api/v1/search` (cross-object), not per-object | Full-text search |
| `restore` | never (trash retired, #2377) | Gating only | Restore a soft-deleted record (reserved — platform deletes are hard today) |
| `purge` | never (trash retired, #2377) | Gating only | Permanent deletion |
| `restore` | never (trash retired) | Gating only | Restore a soft-deleted record (reserved — platform deletes are hard today) |
| `purge` | never (trash retired) | Gating only | Permanent deletion |
| `import` | `create` ∨ `update` (writeMode-precise) | `POST /data/{object}/import` | Bulk data import |
| `export` | `list` | `GET /data/{object}/export` | Data export |

Expand DownExpand Up@@ -578,7 +578,7 @@ async function firstTenAccounts() {
external-facing APIs.
5. **Assuming `DELETE` is recoverable.** ObjectStack `DELETE` is a hard
delete — there is no recycle bin (the dead `enable.trash` flag was removed
in 16.x, #2377). For recoverability, use per-field `trackHistory` (audit
in 16.x). For recoverability, use per-field `trackHistory` (audit
trail) or a `lifecycle` archive policy instead of custom soft-delete logic.

---
Expand Down
Loading
Loading