Skip to content

Commit 034f5a3

Browse files
os-billclaude
andauthored
test(spec): pin the OpenAPI WRITE-refusal test on the generator's diagnostic (#18790)
Fixes #18591 Clause-②: no `refuses to WRITE the artifact when the document is inconsistent` (`packages/spec/scripts/openapi-self-consistency.test.ts`) asserted only a non-zero exit status and a missing artifact. **A subprocess that never starts satisfies both**, so the case stayed green in exactly the scenario where the gate it exists to pin never ran once. The fix is the shape its three sibling spawn tests already carry: assert the generator's own diagnostic text as well. Test file only, 8 lines added, nothing else touched. ## Which text to assert — measured, not assumed The claim comment could not say what the generator actually prints for the injected `ApiErrorTypo` ref. The mutated generator was run once and read: ```text Error: OpenAPI document is not self-consistent: 1 unresolvable $ref(s). - #/components/schemas/ApiErrorTypo (referenced at components.schemas.Broken) defined components.schemas: [CreateRequest, UpdateRequest, SingleRecordResponse, ListRecordResponse, DeleteResponse, ApiError, BulkRequest, BulkResponse, BaseResponse, Broken] ``` So the sibling's pair does apply verbatim, and both halves are now asserted: `toMatch(/unresolvable \$ref/)` and `toContain('#/components/schemas/ApiErrorTypo')`. ## Acceptance controls Probe: park `node_modules/.pnpm/tsx@4.23.12` aside, so `npx tsx` resolves the pnpm shim and the shim's target is gone — the generator subprocess never starts. Proven in effect before any result was read (`npx tsx --version` exit 1, `Cannot find module …/tsx/dist/cli.mjs`), restored under a `trap … EXIT INT TERM` with absolute paths and proven byte-identical after each leg (same inode 689295, same tar-stream sha256 `e8d303f8…67e6`). ### LIT — the red leg Same command both times: `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 scripts/openapi-self-consistency.test.ts` | tsx parked | file counts | `refuses to WRITE …` | |:---|:---|:---| | **before** (`2085be2b2d`) | `Tests 8 failed \| 13 passed (21)` | **PASSED** — the vacuous pass | | **after** (`cd4904d361`) | `Tests 9 failed \| 12 passed (21)` | **FAILED** | The new failure, in full: ```text FAIL |local| scripts/openapi-self-consistency.test.ts > build-openapi.ts end to end > refuses to WRITE the artifact when the document is inconsistent AssertionError: expected 'node:internal/modules/cjs/loader:1386…' to match /unresolvable \$ref/ ``` All nine spawn tests in the file now go red under the probe; before this change, eight did. ### DARK — normal tree | tsx in place | result | |:---|:---| | **before** (`2085be2b2d`) | `Test Files 1 passed (1)` · `Tests 21 passed (21)` | | **after** (`cd4904d361`) | `Test Files 1 passed (1)` · `Tests 21 passed (21)` | Nothing reddened. Whole package at `cd4904d361`: `pnpm --filter @objectstack/spec test` → `Test Files 485 passed | 1 skipped (486)` · `Tests 14015 passed | 1 skipped (14016)`, exit 0. `pnpm --filter @objectstack/spec typecheck` → exit 0 (`tsc --noEmit`, `check:scripts-typecheck`, `check:test-typecheck`; the test-typecheck debt ledger did not move). ## Changeset — measured `npm pack --dry-run --json` in `packages/spec` at `cd4904d361`: **275** published entries, **0** of them under `scripts/`, so the changed file is not in this package's `files[]`. Positive control on the same reading: `src/**/*.zod.ts` is present (`src/ai/agent.zod.ts`, …), so the instrument is live. Nothing published moves ⇒ no changeset is owed; `skip-changeset` applies and is declared, not omitted. ## Gates `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran …` at `cd4904d361`: 56 derived families — **51 run exit 0**, **5 NOT MEASURED** (recorded exit 3, `PREREQUISITE NOT MET`: `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content`, `check:type-check-debt`). All five read a built `dist/` across the workspace and refuse without one; this diff emits nothing to any `dist/`, so they are CI's closure-build run, not a red. Dependency-closure build (`--filter '@objectstack/spec^...'`) is an empty run: `packages/spec` has no workspace dependencies. `eslint --no-inline-config --format json` on the changed file: 1 file, 0 errors, 0 warnings. The repo-wide `pnpm lint` sweep is CI's run. Invariance leg, quoted from `eslint.config.mjs`'s own header: "this repo runs one `eslint.config.mjs`, which never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file" — so this diff cannot move any untouched file's verdict. The universe count was not measured, so this is a targeted run, not a proven narrowing. ## Acceptance notes - Swept the repo for the same vacuous shape: `git grep "status).not.toBe(0)" -- '*.test.ts'` returns 9 hits across 4 files; the 5 outside this file each carry an output assertion on the adjacent line, so no second instance of this defect class exists in that spelling. Observation, not a card. - `runGenerator()` could immunise the whole spawn family structurally by refusing a launch failure once, instead of each case re-deriving it from diagnostic text. Out of scope here — the card prescribes the siblings' shape and forbids inventing one — and moot for this file now that all nine spawn cases assert output. Noted, not filed. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 00b38d7 commit 034f5a3

1 file changed

Lines changed: 8 additions & 0 deletions

File tree

‎packages/spec/scripts/openapi-self-consistency.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,14 @@ describe('build-openapi.ts end to end', () => {
375375
),
376376
);
377377
expect(run.status).not.toBe(0);
378+
// A non-zero status and a missing artifact are BOTH satisfied by a subprocess
379+
// that never started, so on their own they cannot tell "the gate refused the
380+
// write" apart from "the spawn failed to launch" — which is how this case
381+
// stayed green while the gate it pins never ran once. The generator's own
382+
// diagnostic is what pins the refusal to the gate, and it is the assertion
383+
// the three siblings above already carry.
384+
expect(run.output).toMatch(/unresolvable \$ref/);
385+
expect(run.output).toContain('#/components/schemas/ApiErrorTypo');
378386
// The gate runs before the write, so no half-broken document is published.
379387
expect(fs.existsSync(run.artifact)).toBe(false);
380388
});

0 commit comments

Comments
 (0)