diff --git a/.changeset/retire-generated-paths-filter.md b/.changeset/retire-generated-paths-filter.md new file mode 100644 index 0000000000..b42e26f7b1 --- /dev/null +++ b/.changeset/retire-generated-paths-filter.md @@ -0,0 +1,30 @@ +--- +"@objectstack/spec": patch +--- + +Retire ci.yml's `generated` paths filter and its `Check Generated Artifacts` job; every +spec artifact gate now runs in lint.yml's unfiltered, required TypeScript Type Check job. + +The filter was a hand-maintained duplicate of each gate's input set, and nothing +reconciled the two. It drifted three times on record, each found by accident and written +up in a comment rather than gated: + +- #2584 moved a generated page and the filter kept watching the old path, so hand-edits + to the generated block went unchecked for months. +- #3855 listed specific spec paths but no schema dirs, so `check:authorable-surface` went + dormant on exactly the PRs that remove an authorable key. +- `packages/spec/json-schema.manifest.json` — the #2978 ratchet, and the only durable + record of every schema ever emitted since `json-schema/` is gitignored — was never + watched at all, so a PR retiring a key from it skipped its own verifier. + +Six of the ten gates had already escaped to the typecheck job one at a time, each with a +comment explaining that the filter had failed them. This moves the last four +(`check:skill-docs`, `check:spec-changes`, `check:upgrade-guide`, +`check:authorable-surface`) and deletes the filter, so there is no second ledger to keep +in sync — the failure mode #4255 fixed for the `check:generated` ledger, removed at the +source here rather than gated. + +Affordable because the work was already being done: `check:docs` in that job runs +`gen:schema` — the same `scripts/build-schemas.ts` that backs `check:authorable-surface` — +and that entire step measures 4s in CI, against a 5-minute job dominated by the workspace +build. All four read source via tsx and need no build. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3c566d4a4..e32065a72f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,6 @@ jobs: outputs: docs: ${{ steps.changes.outputs.docs }} core: ${{ steps.changes.outputs.core }} - generated: ${{ steps.changes.outputs.generated }} steps: - name: Checkout repository uses: actions/checkout@v7 @@ -39,37 +38,6 @@ jobs: - 'content/**' - 'pnpm-lock.yaml' - '.github/workflows/ci.yml' - # Inputs AND outputs of every generated artifact checked by the - # `check-generated` job. A path missing here makes that gate dormant on - # exactly the PRs that can break it, so keep the two in lockstep. - generated: - # check:skill-docs — catalog source is skills/*/SKILL.md; the derived - # listings are skills/README.md + the reference page below. (That page - # was content/docs/guides/skills.mdx until #2584 moved it; this filter - # kept watching the old path, so hand-edits to the generated block went - # unchecked from then until now.) - - 'skills/**' - - 'content/docs/ai/skills-reference.mdx' - - 'packages/spec/scripts/build-skill-docs.ts' - # check:spec-changes / check:upgrade-guide — derived from the ADR-0087 - # registries and the protocol version, none of which the skills paths - # above cover. Both gates were dormant on spec-only PRs: a protocol bump - # or a new migration could not trigger the job that verifies them. - - 'packages/spec/scripts/build-spec-changes.ts' - - 'packages/spec/scripts/build-upgrade-guide.ts' - - 'packages/spec/src/migrations/**' - - 'packages/spec/src/conversions/**' - - 'packages/spec/src/kernel/protocol-version.ts' - - 'packages/spec/api-surface.json' - - 'packages/spec/spec-changes.json' - - 'docs/protocol-upgrade-guide.md' - # check:authorable-surface — the ratchet's input is every Zod schema - # in the spec, so the filter has to be that broad or the gate goes - # dormant on exactly the PRs that remove an authorable key (#3855). - - 'packages/spec/src/**' - - 'packages/spec/scripts/build-schemas.ts' - - 'packages/spec/authorable-surface.json' - - '.github/workflows/ci.yml' core: - 'packages/**' - 'examples/**' @@ -728,67 +696,3 @@ jobs: - name: Build Docs run: pnpm --filter @objectstack/docs build - - # Generated artifacts that are committed to the tree: regenerate, fail on drift. - # Named for the class, not for skill docs alone — it has checked the ADR-0087 - # protocol artifacts for a while, and a name that hid them is part of why their - # paths went missing from the filter above. - # - # The generated reference docs (content/docs/references/**) are deliberately NOT - # here: they need to gate spec-only PRs, and this whole job is filter-gated, so - # they live in lint.yml's required, unfiltered "TypeScript Type Check" job. - check-generated: - name: Check Generated Artifacts - needs: filter - if: needs.filter.outputs.generated == 'true' - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - - name: Setup Node.js - uses: actions/setup-node@v7 - with: - node-version: '22' - - - name: Enable Corepack - run: corepack enable - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - name: Setup pnpm cache - uses: actions/cache@v6 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store-v3- - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Check skill docs are generated from SKILL.md frontmatter - run: pnpm --filter @objectstack/spec check:skill-docs - - - name: Check spec-changes.json is regenerated with the ADR-0087 registries - run: pnpm --filter @objectstack/spec check:spec-changes - - - name: Check the protocol upgrade guide is regenerated with the ADR-0087 registries - run: pnpm --filter @objectstack/spec check:upgrade-guide - - # The authorable KEY surface — what a metadata author may write, which for - # this platform is the third-party API. `api-surface.json` records exported - # names and `api-surface-signatures.json` hashes factory types as TypeScript - # PRINTS them (a reference, never structurally expanded), so neither sees a - # key added to or removed from a schema. #3883 removed three authorable keys - # with every witness green; #3733 did it by accident. ADR-0059 §5 deferred - # this gate until a narrowing actually slipped both — it has. - - name: Check the authorable key surface is recorded and nothing vanished - run: pnpm --filter @objectstack/spec check:authorable-surface diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ec5e64698f..2cfc49c79d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -248,14 +248,55 @@ jobs: # while the wrapper AGENTS.md prescribes exited red on `main`, running zero # gates. Twice in three days: #4177 (`check:variant-docs`, fixed only when # #4194 collided with the same wall) and #4232 (`check:strictness-ledger`, - # caught while wiring this step). Not in ci.yml's `check-generated` job: - # its `generated` paths filter does not watch packages/spec/package.json — - # the one file every offending PR must touch — so both offenders skipped - # that job entirely. This job is unfiltered and required, so the meta-gate - # cannot go dormant. Reads package.json only; no build, sub-second. + # caught while wiring this step). It could not live in ci.yml's + # `check-generated` job — that job was gated on a `generated` paths filter + # that never watched packages/spec/package.json, the one file every + # offending PR must touch. That job is gone now (see the four gates below); + # this job is unfiltered and required, so the meta-gate cannot go dormant. + # Reads package.json only; no build, sub-second. - name: "Check every check:/gen: script is classified in the check:generated ledger" run: pnpm --filter @objectstack/spec check:generated --reconcile-only + # The last four artifact gates, moved here from ci.yml's `check-generated` + # job when that job and its `generated` paths filter were deleted (#4291). + # + # The filter was a hand-maintained duplicate of each gate's input set, and + # nothing reconciled the two. It drifted three times on record, each found + # by accident and written up in a comment rather than gated: #2584 moved a + # generated page and the filter kept watching the old path, so hand-edits + # went unchecked for months; #3855 listed specific spec paths but no schema + # dirs, so `check:authorable-surface` went dormant on exactly the PRs that + # remove an authorable key; and `json-schema.manifest.json` — the #2978 + # ratchet that is the ONLY durable record of every emitted schema, since + # json-schema/ is gitignored — was never watched at all, so a PR retiring a + # key from it skipped its own verifier. Six gates had already escaped to + # this job one at a time, each with a comment saying the filter had failed + # it; these four finish the migration and retire the duplicate ledger. + # + # Affordable because the work was already being done here: `check:docs` + # below runs `gen:schema` — the same scripts/build-schemas.ts that backs + # `check:authorable-surface` — and that whole step measures 4s in CI. All + # four read source via tsx and need no build, so they run before the + # workspace build, as the gates below already do. + - name: Check skill docs are generated from SKILL.md frontmatter + run: pnpm --filter @objectstack/spec check:skill-docs + + - name: Check spec-changes.json is regenerated with the ADR-0087 registries + run: pnpm --filter @objectstack/spec check:spec-changes + + - name: Check the protocol upgrade guide is regenerated with the ADR-0087 registries + run: pnpm --filter @objectstack/spec check:upgrade-guide + + # The authorable KEY surface — what a metadata author may write, which for + # this platform is the third-party API. `api-surface.json` records exported + # names and `api-surface-signatures.json` hashes factory types as TypeScript + # PRINTS them (a reference, never structurally expanded), so neither sees a + # key added to or removed from a schema. #3883 removed three authorable keys + # with every witness green; #3733 did it by accident. ADR-0059 §5 deferred + # this gate until a narrowing actually slipped both — it has. + - name: Check the authorable key surface is recorded and nothing vanished + run: pnpm --filter @objectstack/spec check:authorable-surface + # Generated-docs gate: content/docs/references/** is generated from the spec # by `gen:schema && gen:docs` and committed. Nothing regenerated it in CI, so # it drifted silently — #3076 added RowCrudActionOverride to the spec and the @@ -278,10 +319,12 @@ jobs: # drift is served straight to consumers' agents — 6 of 113 schema pointers named # files the spec had already deleted or renamed. # - # Not in ci.yml's `check-generated` job for the reason above: its `generated` - # filter lists specific spec paths (migrations/, conversions/, protocol-version) - # but no schema dirs, so a PR touching src/data/** or src/ui/** — exactly what - # drives these two artifacts — never triggers it. It is not required, either. + # These moved here early, when ci.yml still had a filter-gated + # `check-generated` job: its `generated` filter listed specific spec paths + # (migrations/, conversions/, protocol-version) but no schema dirs, so a PR + # touching src/data/** or src/ui/** — exactly what drives these two + # artifacts — never triggered it, and it was not required either. That job + # and its filter are gone (#4291); every artifact gate now lives here. # # Both read packages/spec/src via tsx and need no build (verified with every # workspace dist/ removed), so they run before the workspace build. check:skill-refs diff --git a/AGENTS.md b/AGENTS.md index 9ca598b805..fe531a76de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -229,10 +229,12 @@ Root also exports: `defineStack`, `composeStacks`, `defineView`, `defineApp`, `d ### Touched `packages/spec`? Regenerate its artifacts BEFORE pushing `packages/spec` has **eight** checked-in generated artifacts, each with its own CI gate. -They live in two different jobs (`TypeScript Type Check` in `lint.yml`, `Check Generated -Artifacts` in `ci.yml`), and each job runs its gates **sequentially** — so the first -stale artifact masks every one behind it, and you get one red build per artifact instead -of one for all of them. Match the change to the gate and regenerate up front: +All of them live in one job — `TypeScript Type Check` in `lint.yml`, which is required and +has no paths filter, so no gate can go dormant on the PR that breaks it (#4291 retired the +filtered `Check Generated Artifacts` job for exactly that reason). That job runs its gates +**sequentially**, so the first stale artifact masks every one behind it, and you get one +red build per artifact instead of one for all of them. Match the change to the gate and +regenerate up front: | You changed | Gate that fails | Regenerate with `pnpm --filter @objectstack/spec …` | |:---|:---|:---| diff --git a/packages/spec/scripts/check-generated.ts b/packages/spec/scripts/check-generated.ts index 0302cd58ce..7c5c3cba8c 100644 --- a/packages/spec/scripts/check-generated.ts +++ b/packages/spec/scripts/check-generated.ts @@ -5,8 +5,8 @@ * Run **every** generated-artifact gate and report **all** stale artifacts in one * pass. * - * CI runs these gates as separate sequential steps across two jobs, so the first - * stale artifact masks the rest: you fix it, push, and discover the next one on + * CI runs these gates as separate sequential steps, so the first stale artifact + * masks the rest: you fix it, push, and discover the next one on * the following run. That happened twice on #4040 (`check:docs`, then * `check:api-surface`) and twice again on #4161 (`check:spec-changes`, then * `check:upgrade-guide`) — four pushes spent learning something one local run @@ -153,11 +153,12 @@ const fix = process.argv.includes('--fix'); // individual steps, so an unclassified `check:`/`gen:` script kept every CI gate // green while this wrapper exited red on `main` before running a single gate. // Twice in three days — #4177 (fixed only by colliding with #4194) and #4232 -// (caught wiring this flag in). ci.yml's `check-generated` job cannot host the -// fix: its `generated` paths filter does not watch packages/spec/package.json, -// the one file every offending PR must touch — both offenders skipped that job -// entirely. So lint.yml's unfiltered, required "TypeScript Type Check" job runs -// this mode instead. Reads package.json and the arrays above; no build, <1s. +// (caught wiring this flag in). It could not go in ci.yml's `check-generated` +// job: that job was gated on a `generated` paths filter that never watched +// packages/spec/package.json, the one file every offending PR must touch, so +// both offenders skipped it entirely. #4291 deleted that job and its filter and +// moved every gate to lint.yml's unfiltered, required "TypeScript Type Check" +// job, which runs this mode too. Reads package.json and the arrays above; <1s. const reconcileOnly = process.argv.includes('--reconcile-only'); const scripts = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8')).scripts ?? {}; reconcileLedger(scripts);