Skip to content

Commit d229a1d

Browse files
committed
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.
1 parent 562d918 commit d229a1d

4 files changed

Lines changed: 156 additions & 16 deletions

File tree

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
}

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
"dev:sockets": "cd apps/realtime && bun run dev",
1515
"dev:full": "bunx concurrently -n \"App,Realtime\" -c \"cyan,magenta\" \"cd apps/sim && bun run dev\" \"cd apps/realtime && bun run dev\"",
1616
"dev:full:capped": "bunx concurrently -n \"App,Realtime\" -c \"cyan,magenta\" \"cd apps/sim && bun run dev:capped\" \"cd apps/realtime && bun run dev\"",
17-
"test": "bun run test:setup && bun run test:npm-package-versions && bun run test:icon-path-precision && bun run test:tool-registry-boundary && bun run test:tool-request-boundary && bun run test:generators && turbo run test",
17+
"test": "bun run test:setup && bun run test:npm-package-versions && bun run test:icon-path-precision && bun run test:tool-registry-boundary && bun run test:tool-request-boundary && bun run test:migrations-safety && bun run test:generators && turbo run test",
1818
"test:setup": "bun run --cwd packages/sim-setup test",
1919
"test:npm-package-versions": "bunx vitest run scripts/bump-npm-package-versions.test.ts",
2020
"test:icon-path-precision": "bunx vitest run scripts/check-icon-path-precision.test.ts",
2121
"test:tool-registry-boundary": "bunx vitest run scripts/check-tool-registry-boundary.test.ts",
2222
"test:tool-request-boundary": "bunx vitest run scripts/check-tool-request-boundary.test.ts",
23+
"test:migrations-safety": "bunx vitest run scripts/check-migrations-safety.test.ts",
2324
"test:generators": "bunx vitest run scripts/generate-v2-cli-api.test.ts scripts/generate-cli-docs.test.ts scripts/generate-docs.test.ts",
2425
"format": "turbo run format",
2526
"format:check": "turbo run format:check",
@@ -57,6 +58,8 @@
5758
"check:migrations": "bun run scripts/check-migrations-safety.ts",
5859
"check:native-typecheck": "bun run scripts/check-native-typecheck.ts",
5960
"check:source-text": "bun run scripts/check-source-text.ts",
61+
"check:spec-example-ids": "bun run scripts/check-spec-example-ids.ts",
62+
"check:script-tests": "bun run scripts/check-script-test-coverage.ts",
6063
"check:audits": "bun run scripts/run-audits.ts",
6164
"check:skills": "bun run scripts/sync-skills.ts --check",
6265
"check:desktop-bridge": "bun run scripts/check-desktop-bridge-contract.ts --check",
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env bun
2+
/**
3+
* Asserts every `scripts/*.test.ts` file is reachable from the root `test` script.
4+
*
5+
* The root `test` script chains a hand-maintained list of `test:*` entries, and a hand-maintained
6+
* list silently drifts from the files on disk: a test added without a matching entry never runs,
7+
* in CI or locally, and nothing reports it. `scripts/check-migrations-safety.test.ts` sat
8+
* unreferenced and green for exactly that reason.
9+
*
10+
* `run-audits.ts` derives its own list from the `check:*` namespace precisely so a new audit is
11+
* picked up by default, so this guard registers itself simply by being named `check:*` — it cannot
12+
* drift out of the runner it belongs to.
13+
*/
14+
import { readdirSync } from 'node:fs'
15+
import path from 'node:path'
16+
17+
const ROOT = path.resolve(import.meta.dir, '..')
18+
const TEST_FILE_PATTERN = /scripts\/[\w.-]+\.test\.ts/g
19+
const SUB_SCRIPT_PATTERN = /bun run ([\w:-]+)/g
20+
21+
const manifest = await Bun.file(path.join(ROOT, 'package.json')).json()
22+
const commands = manifest.scripts as Record<string, string>
23+
24+
/** Walks the `test` script and every `test:*` entry it chains, collecting referenced test files. */
25+
function reachableTestFiles(entry: string): Set<string> {
26+
const referenced = new Set<string>()
27+
const seen = new Set<string>()
28+
const queue = [entry]
29+
30+
while (queue.length > 0) {
31+
const name = queue.pop() as string
32+
if (seen.has(name)) continue
33+
seen.add(name)
34+
35+
const command = commands[name]
36+
if (command === undefined) continue
37+
38+
for (const match of command.matchAll(TEST_FILE_PATTERN)) referenced.add(match[0])
39+
for (const match of command.matchAll(SUB_SCRIPT_PATTERN)) queue.push(match[1])
40+
}
41+
42+
return referenced
43+
}
44+
45+
const onDisk = readdirSync(path.join(ROOT, 'scripts'))
46+
.filter((file) => file.endsWith('.test.ts'))
47+
.map((file) => `scripts/${file}`)
48+
.sort()
49+
50+
const reachable = reachableTestFiles('test')
51+
const orphaned = onDisk.filter((file) => !reachable.has(file))
52+
const missing = [...reachable].filter((file) => !onDisk.includes(file)).sort()
53+
54+
if (orphaned.length > 0 || missing.length > 0) {
55+
if (orphaned.length > 0) {
56+
console.error(
57+
`Script tests never run by \`bun run test\`:\n${orphaned.map((file) => ` - ${file}`).join('\n')}\n` +
58+
'Add a `test:*` entry for each and chain it into the root `test` script.'
59+
)
60+
}
61+
if (missing.length > 0) {
62+
console.error(
63+
`Root \`test\` script references script tests that do not exist:\n${missing.map((file) => ` - ${file}`).join('\n')}`
64+
)
65+
}
66+
process.exit(1)
67+
}
68+
69+
console.log(
70+
`Script test coverage passed: ${onDisk.length} script tests reachable from \`bun run test\`.`
71+
)

scripts/check-spec-example-ids.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env bun
2+
/**
3+
* Flags example UUIDs in the published OpenAPI specs that do not look deliberately synthetic.
4+
*
5+
* The specs in `apps/docs/` ship in a public repository, so an example id copied from a real run
6+
* publishes an opaque workspace, workflow, or execution identifier. The repo's hand-crafted
7+
* placeholders are pandigital — every hex digit appears, none more than three times
8+
* (`3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36`) — a texture a real v4 UUID essentially never has.
9+
* That gives a mechanical signature for "this was generated, not authored".
10+
*
11+
* The directory is globbed rather than read from `OPENAPI_SPEC_FILES`, because the gap this closes
12+
* is precisely that `openapi-core.json` is absent from that manifest and from every other check.
13+
*/
14+
import { readdirSync } from 'node:fs'
15+
import path from 'node:path'
16+
17+
const ROOT = path.resolve(import.meta.dir, '..')
18+
const SPEC_DIR = path.join(ROOT, 'apps/docs')
19+
const UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g
20+
21+
/** Ids that are obviously synthetic to a reader but do not match the pandigital texture. */
22+
const ALLOWED: Record<string, string> = {
23+
'7c9e6679-7425-40de-944b-e07fc1f90ae7': "Wikipedia's canonical example UUID",
24+
'a3f1c0b2-7a44-4c1d-9d3a-2b8e5f0a1c77':
25+
'pre-existing hand-authored block id shared with the v2 workflow operations tests',
26+
}
27+
28+
/** All-zero and all-f sentinels read as synthetic on sight. */
29+
function isSentinel(uuid: string): boolean {
30+
return new Set(uuid.replace(/-/g, '')).size <= 2
31+
}
32+
33+
/** Hand-authored placeholders in this repo use every hex digit, none more than three times. */
34+
function isHouseStyle(uuid: string): boolean {
35+
const hex = uuid.replace(/-/g, '')
36+
const digits = new Set(hex)
37+
if (digits.size < 16) return false
38+
return [...digits].every((digit) => hex.split(digit).length - 1 <= 3)
39+
}
40+
41+
const specFiles = readdirSync(SPEC_DIR)
42+
.filter((file) => file.startsWith('openapi') && file.endsWith('.json'))
43+
.sort()
44+
45+
const findings: string[] = []
46+
47+
for (const file of specFiles) {
48+
const contents = await Bun.file(path.join(SPEC_DIR, file)).text()
49+
const seen = new Set(contents.match(UUID_PATTERN) ?? [])
50+
for (const uuid of [...seen].sort()) {
51+
if (uuid in ALLOWED || isSentinel(uuid) || isHouseStyle(uuid)) continue
52+
findings.push(` - ${file}: ${uuid}`)
53+
}
54+
}
55+
56+
if (findings.length > 0) {
57+
console.error(
58+
`Published OpenAPI specs contain example UUIDs that look real rather than hand-authored:\n${findings.join('\n')}\n` +
59+
'Replace each with a placeholder using every hex digit at most three times, or add it to ALLOWED with a reason.'
60+
)
61+
process.exit(1)
62+
}
63+
64+
console.log(
65+
`Spec example ids passed: ${specFiles.length} published specs contain no real-looking UUIDs.`
66+
)

0 commit comments

Comments
 (0)