Skip to content

Commit 6bbabbb

Browse files
authored
fix(cli): close follow-up gaps (#7137)
* chore: run the orphaned migration-safety test, and stop publishing real-looking ids One of the eight script tests was reachable from no entrypoint, so it had never run in CI — it passes, it was simply never invoked. This is the second time that hand-maintained list has drifted from the files beside it; the audit runner's own header records the first. The guard against a third is a `check:*` script rather than a test, because the runner derives its list from that namespace and so picks the guard up by name — a test would have had to be hand-added to the very list it guards. A published spec sat outside the generator's manifest and so outside its drift check, and carried six example ids with the texture of real generated ones rather than the pandigital placeholders the rest of the repo uses. The new check globs the directory instead of reading the manifest, since the manifest is what omitted the file. The one pre-existing borderline id is allowlisted with a reason: loosening the threshold to admit it would have hidden one of the six. * fix(cli): close the gaps black-box testing the shipped CLI found `sim profiles <anything>` still exited 0, so a probe reading the exit code to ask whether a command exists was told yes — the one group the earlier guard missed. The exemption was written for commands that are both a group and a leaf, but only `files restore` takes an operand; `profiles` takes none. Registering its listing as a default subcommand puts it back among the pure dispatchers the existing guard already covers, so the guard itself did not need widening. Three commands refuse a workspace API key and said nothing, while their menu siblings said so — reading as though they accept one. They are hand-written, so they never reached the code that appends the note. That note now comes from a helper taking the operation, so a command names the operation it invokes and the two cannot disagree, and a test fails if a hand-written command ever calls a restricted operation without it. A blank numeric value in a request body still became a real zero, the same coercion already fixed for query strings: the guard keyed off the slot when the distinction is the field's declared type. Twenty-one fields across fifteen operations were affected. An empty body string still clears a description. Blank values for the root endpoint, workspace and profile flags fell back to what was configured instead of being refused, and a whitespace workspace was accepted verbatim. A hand-written profile name carrying padding listed as reachable but resolved to defaults rather than erroring. Two schema descriptions named request fields that no flag spells, and a rejected value was echoed unredacted by four messages while their siblings redacted it. A write now re-emits a section header it was not asked to touch byte for byte. The blank-line normalisation around it is left alone: making the writer position-faithful is a change to its model, not a fix. * fix(scripts): match example uuids case-insensitively in the spec audit The pattern only recognised lowercase hex, so an uppercase id in a published spec was never examined and the audit reported success without having looked at it. Matching case-insensitively is not enough on its own: hex is case-insensitive, so a mixed-case id counts `A` and `a` as two digits and reports twenty distinct ones rather than sixteen. That inflated count clears the threshold the texture test uses to recognise a hand-authored placeholder, so a real id could have passed for one. The allowlist is an exact-string lookup and would likewise have missed an uppercase spelling of an entry. Both checks and the lookup now take a normalised id, while the finding still reports the spelling as it appears in the file. * fix(cli): stop a refusal being swallowed, and gate example ids by name A blank root flag was refused everywhere except `profiles`, where the catch that lets a broken profile still list absorbed it and the command exited 0 after printing the table. The refusal now carries its own error class, which is what the listing rethrows on — the two are distinguished by type rather than by matching message text, and a genuinely broken profile still lists. The unknown-profile message redacted the name the caller typed but not the suggestion or the list of configured names beside it, which come from the same file and are equally attacker-influenced once it has been hand-edited. Those are redacted now, as is every other message in these two files that quotes a name read out of the config, and the profile listing flattens the names it renders the way it already flattened the error column. The example-id audit judged a uuid by its digit texture, on the premise that a real one essentially never looks hand-authored. Measured against ten million generated ids, 0.81% of them do — one in 124, where this change alone replaced six. Requiring each digit exactly twice takes that to zero but rejects all fourteen placeholders now in the specs, so it is no cheaper than the alternative. The audit now holds the eighteen ids the specs actually use, which is one file rather than the twenty-seven a reserved format would touch, and a new id fails until someone lists it — which is the review the check exists to force. * fix(scripts): match the uuid sentinels exactly rather than by shape Accepting any id built from at most two distinct hex digits let something through that was never on the approved list. A generated id essentially never has that shape, so the practical risk was small — but this check had just stopped being a shape test and become a list, and a structural exception is the one thing that undoes that. The two ids it exists for are the nil and max sentinels, and both are matched by value now.
1 parent f02ee99 commit 6bbabbb

30 files changed

Lines changed: 791 additions & 112 deletions

apps/docs/content/docs/en/cli/credentials.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ Update Credential (personal API key required)
123123
sim credentials create <providerId> [options]
124124
```
125125

126+
Create a service-account credential using its discovered provider schema (personal API key required)
127+
126128
**Arguments**
127129

128130
<CommandTable>
@@ -152,6 +154,8 @@ sim credentials create <providerId> [options]
152154
sim credentials connect <providerId> [options]
153155
```
154156

157+
Create a short-lived link for connecting an OAuth provider (personal API key required)
158+
155159
**Arguments**
156160

157161
<CommandTable>
@@ -178,6 +182,8 @@ sim credentials connect <providerId> [options]
178182
sim credentials reconnect <credentialId>
179183
```
180184

185+
Create a short-lived link for reconnecting an OAuth credential (personal API key required)
186+
181187
**Arguments**
182188

183189
<CommandTable>

apps/docs/content/docs/en/cli/logs.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ sim logs list [options]
8585
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
8686
| `--status <value>` | No | Comma-separated execution statuses to include, from `pending` \| `running` \| `paused` \| `redacting` \| `completed` \| `failed` \| `cancelled`. An empty entry is rejected. ANDed with `level`, which reports severity rather than lifecycle. |
8787
| `--workflow-name <value>` | No | Case-insensitive substring match against the run's workflow name. Runs whose workflow has been deleted match nothing, because the name is no longer joinable. |
88-
| `--include-job-runs` | No | Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: "job"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set (`workflowIds`, `workflowName`, `folderPaths`, `model`, or `status`), so a filter never means two different things across the union. Accepted only under `sortBy=startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings. |
88+
| `--include-job-runs` | No | Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: "job"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set: by workflow, workflow name, folder, model, or status. A filter therefore never means two different things across the union. Accepted only when sorting by `startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings. |
8989
| `--no-include-job-runs` | No | Send --include-job-runs as false. |
9090
| `--run-id <value>` | No | Exact run identifier to match. |
91-
| `--sort-by <value>` | No | Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected together with `includeJobRuns=true`. Accepted values: `startedAt`, `durationMs`, `cost`, `status`. |
91+
| `--sort-by <value>` | No | Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected when job runs are included. Accepted values: `startedAt`, `durationMs`, `cost`, `status`. |
9292
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
9393
| `--folder <value...>` | No | Folder path as shown in the app; the leading / is optional (space-separated, or @path / @- with one value per line; @@value for a literal leading @). |
9494

apps/docs/content/docs/en/cli/reference.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ sim credentials update <credentialId> [options]
479479

480480
### sim credentials create
481481

482-
Create a service-account credential using its discovered provider schema
482+
Create a service-account credential using its discovered provider schema (personal API key required)
483483

484484
```bash
485485
sim credentials create <providerId> [options]
@@ -510,7 +510,7 @@ sim credentials create <providerId> [options]
510510

511511
### sim credentials connect
512512

513-
Create a short-lived link for connecting an OAuth provider
513+
Create a short-lived link for connecting an OAuth provider (personal API key required)
514514

515515
```bash
516516
sim credentials connect <providerId> [options]
@@ -538,7 +538,7 @@ sim credentials connect <providerId> [options]
538538

539539
### sim credentials reconnect
540540

541-
Create a short-lived link for reconnecting an OAuth credential
541+
Create a short-lived link for reconnecting an OAuth credential (personal API key required)
542542

543543
```bash
544544
sim credentials reconnect <credentialId>
@@ -2475,10 +2475,10 @@ sim logs list [options]
24752475
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
24762476
| `--status <value>` | No | Comma-separated execution statuses to include, from `pending` \| `running` \| `paused` \| `redacting` \| `completed` \| `failed` \| `cancelled`. An empty entry is rejected. ANDed with `level`, which reports severity rather than lifecycle. |
24772477
| `--workflow-name <value>` | No | Case-insensitive substring match against the run's workflow name. Runs whose workflow has been deleted match nothing, because the name is no longer joinable. |
2478-
| `--include-job-runs` | No | Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: "job"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set (`workflowIds`, `workflowName`, `folderPaths`, `model`, or `status`), so a filter never means two different things across the union. Accepted only under `sortBy=startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings. |
2478+
| `--include-job-runs` | No | Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: "job"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set: by workflow, workflow name, folder, model, or status. A filter therefore never means two different things across the union. Accepted only when sorting by `startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings. |
24792479
| `--no-include-job-runs` | No | Send --include-job-runs as false. |
24802480
| `--run-id <value>` | No | Exact run identifier to match. |
2481-
| `--sort-by <value>` | No | Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected together with `includeJobRuns=true`. Accepted values: `startedAt`, `durationMs`, `cost`, `status`. |
2481+
| `--sort-by <value>` | No | Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected when job runs are included. Accepted values: `startedAt`, `durationMs`, `cost`, `status`. |
24822482
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
24832483
| `--folder <value...>` | No | Folder path as shown in the app; the leading / is optional (space-separated, or @path / @- with one value per line; @@value for a literal leading @). |
24842484

@@ -2743,7 +2743,7 @@ sim secrets list [options]
27432743

27442744
### sim secrets set
27452745

2746-
Create or replace a named secret
2746+
Create or replace a named secret (personal API key required)
27472747

27482748
```bash
27492749
sim secrets set <name> [options]
@@ -5410,7 +5410,7 @@ sim workflows list [options]
54105410

54115411
| Option | Required | Description |
54125412
| --- | --- | --- |
5413-
| `--scope <value>` | No | Which lifecycle set to list: `active` (default) for live workflows, `archived` for workflows a `DELETE` archived. `folderPath` resolves against active folders only, so pairing it with `scope=archived` returns an empty page when the containing folder was archived too. Accepted values: `active`, `archived`. |
5413+
| `--scope <value>` | No | Which lifecycle set to list: `active` (default) for live workflows, `archived` for workflows a `DELETE` archived. The folder filter resolves against active folders only, so pairing it with `archived` returns an empty page when the containing folder was archived too. Accepted values: `active`, `archived`. |
54145414
| `--folder <value>` | No | Folder path as shown in the app; the leading / is optional. |
54155415
| `--deployed-only` | No | Return only workflows with an active deployment when true. |
54165416
| `--no-deployed-only` | No | Send --deployed-only as false. |

apps/docs/content/docs/en/cli/secrets.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ List Secrets (personal API key required)
6666
sim secrets set <name> [options]
6767
```
6868

69+
Create or replace a named secret (personal API key required)
70+
6971
**Arguments**
7072

7173
<CommandTable>

apps/docs/content/docs/en/cli/workflows.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,7 @@ sim workflows list [options]
767767

768768
| Option | Required | Description |
769769
| --- | --- | --- |
770-
| `--scope <value>` | No | Which lifecycle set to list: `active` (default) for live workflows, `archived` for workflows a `DELETE` archived. `folderPath` resolves against active folders only, so pairing it with `scope=archived` returns an empty page when the containing folder was archived too. Accepted values: `active`, `archived`. |
770+
| `--scope <value>` | No | Which lifecycle set to list: `active` (default) for live workflows, `archived` for workflows a `DELETE` archived. The folder filter resolves against active folders only, so pairing it with `archived` returns an empty page when the containing folder was archived too. Accepted values: `active`, `archived`. |
771771
| `--folder <value>` | No | Folder path as shown in the app; the leading / is optional. |
772772
| `--deployed-only` | No | Return only workflows with an active deployment when true. |
773773
| `--no-deployed-only` | No | Send --deployed-only as false. |

apps/docs/openapi-core.json

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@
211211
"description": "Comma-separated block-output selectors. A bare `blockId` returns that block's full output; a dot-path like `blockId.field` or `blockId.nested.path` returns just that value. Results are returned in the `blockOutputs` map keyed by the selector string.",
212212
"schema": {
213213
"type": "string",
214-
"example": "c1b90bce-8a82-42a5-b6a5-5762846c2eaf,c1b90bce-8a82-42a5-b6a5-5762846c2eaf.waitDuration"
214+
"example": "a6f0c8d2-3e57-4b19-8d4a-1c9e2f6b0a35,a6f0c8d2-3e57-4b19-8d4a-1c9e2f6b0a35.waitDuration"
215215
}
216216
}
217217
],
@@ -227,8 +227,8 @@
227227
"completed": {
228228
"summary": "Completed run",
229229
"value": {
230-
"executionId": "9254f1c9-5a11-4a12-91e3-8065293f3609",
231-
"workflowId": "81f661e1-d704-4861-b5c1-5bb3cf57e6a7",
230+
"executionId": "e4f8d2b6-9a1c-4e3d-8b7f-5c0a2d9e6f13",
231+
"workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36",
232232
"status": "completed",
233233
"trigger": "api",
234234
"level": "info",
@@ -247,8 +247,8 @@
247247
"paused": {
248248
"summary": "Currently paused run",
249249
"value": {
250-
"executionId": "772749f6-ee81-414c-a2c3-671549dd62b8",
251-
"workflowId": "81f661e1-d704-4861-b5c1-5bb3cf57e6a7",
250+
"executionId": "d5e1a3c7-8f60-4b29-9c4d-2a6e0f8b3d17",
251+
"workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36",
252252
"status": "paused",
253253
"trigger": "manual",
254254
"level": "info",
@@ -259,8 +259,8 @@
259259
"pausedAt": "2026-05-15T22:25:57.216Z",
260260
"resumeAt": "2026-05-16T18:25:57.200Z",
261261
"pauseKind": "time",
262-
"blockedOnBlockId": "c1b90bce-8a82-42a5-b6a5-5762846c2eaf",
263-
"pausedExecutionId": "438bf05b-bd3c-4011-b78e-b19c112eeb66",
262+
"blockedOnBlockId": "a6f0c8d2-3e57-4b19-8d4a-1c9e2f6b0a35",
263+
"pausedExecutionId": "9d3b7f10-2c8e-4a56-b0f4-6e1a8c5d2b97",
264264
"pausePointCount": 1,
265265
"resumedCount": 0
266266
},
@@ -275,8 +275,8 @@
275275
"failed": {
276276
"summary": "Failed run",
277277
"value": {
278-
"executionId": "3ccfdeed-a63c-4e86-98e2-8bec723bca52",
279-
"workflowId": "81f661e1-d704-4861-b5c1-5bb3cf57e6a7",
278+
"executionId": "b8c2e60f-1a47-4d35-9e8b-3f0d5a7c2e19",
279+
"workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36",
280280
"status": "failed",
281281
"trigger": "api",
282282
"level": "error",
@@ -1537,12 +1537,12 @@
15371537
"executionId": {
15381538
"type": "string",
15391539
"description": "The unique identifier of the execution.",
1540-
"example": "9254f1c9-5a11-4a12-91e3-8065293f3609"
1540+
"example": "e4f8d2b6-9a1c-4e3d-8b7f-5c0a2d9e6f13"
15411541
},
15421542
"workflowId": {
15431543
"type": "string",
15441544
"description": "The unique identifier of the workflow.",
1545-
"example": "81f661e1-d704-4861-b5c1-5bb3cf57e6a7"
1545+
"example": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36"
15461546
},
15471547
"status": {
15481548
"type": "string",
@@ -1610,12 +1610,12 @@
16101610
"type": "string",
16111611
"nullable": true,
16121612
"description": "The block currently blocking resume.",
1613-
"example": "c1b90bce-8a82-42a5-b6a5-5762846c2eaf"
1613+
"example": "a6f0c8d2-3e57-4b19-8d4a-1c9e2f6b0a35"
16141614
},
16151615
"pausedExecutionId": {
16161616
"type": "string",
16171617
"description": "ID of the paused-execution row, useful for cross-referencing with the human-in-the-loop endpoints.",
1618-
"example": "438bf05b-bd3c-4011-b78e-b19c112eeb66"
1618+
"example": "9d3b7f10-2c8e-4a56-b0f4-6e1a8c5d2b97"
16191619
},
16201620
"pausePointCount": {
16211621
"type": "integer",
@@ -1659,8 +1659,8 @@
16591659
"description": "Per-block outputs keyed by the selector string. Returned only when `?selectedOutputs` is set.",
16601660
"additionalProperties": true,
16611661
"example": {
1662-
"c1b90bce-8a82-42a5-b6a5-5762846c2eaf.waitDuration": 60000,
1663-
"c1b90bce-8a82-42a5-b6a5-5762846c2eaf.status": "completed"
1662+
"a6f0c8d2-3e57-4b19-8d4a-1c9e2f6b0a35.waitDuration": 60000,
1663+
"a6f0c8d2-3e57-4b19-8d4a-1c9e2f6b0a35.status": "completed"
16641664
}
16651665
}
16661666
}

apps/docs/openapi-v2-logs.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,9 @@
244244
"name": "includeJobRuns",
245245
"in": "query",
246246
"required": false,
247-
"description": "Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: \"job\"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set (`workflowIds`, `workflowName`, `folderPaths`, `model`, or `status`), so a filter never means two different things across the union. Accepted only under `sortBy=startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings.",
247+
"description": "Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: \"job\"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set: by workflow, workflow name, folder, model, or status. A filter therefore never means two different things across the union. Accepted only when sorting by `startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings.",
248248
"schema": {
249-
"description": "Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: \"job\"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set (`workflowIds`, `workflowName`, `folderPaths`, `model`, or `status`), so a filter never means two different things across the union. Accepted only under `sortBy=startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings.",
249+
"description": "Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: \"job\"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set: by workflow, workflow name, folder, model, or status. A filter therefore never means two different things across the union. Accepted only when sorting by `startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings.",
250250
"type": "boolean"
251251
}
252252
},
@@ -267,10 +267,10 @@
267267
"name": "sortBy",
268268
"in": "query",
269269
"required": false,
270-
"description": "Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected together with `includeJobRuns=true`.",
270+
"description": "Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected when job runs are included.",
271271
"schema": {
272272
"default": "startedAt",
273-
"description": "Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected together with `includeJobRuns=true`.",
273+
"description": "Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected when job runs are included.",
274274
"type": "string",
275275
"enum": ["startedAt", "durationMs", "cost", "status"]
276276
}

0 commit comments

Comments
 (0)