[E00-S05-T01] Implement CI quality baseline (required PR stages) #405
Labels
Clear labels
agent/analyst-drafted
agent/analyst-drafted
needs/human-decision
needs/human-decision
needs/security-review
needs/security-review
tier/t0
tier/t1
tier/t2
tier/t3
kind
bug
kind
bug
kind
epic
kind
epic
kind
initiative
EPPP programme initiative
kind
story
kind
story
kind
task
EPPP engineering card/task decomposed from a story
kind
toil
kind
toil
loop
1
loop
1
loop
2
loop
2
loop
3
loop
3
risk
agent-full
risk
agent-full
risk
human-gated
risk
human-gated
risk
human-only
risk
human-only
size
l
size
l
size
m
size
m
size
s
size
s
status
blocked
status
blocked
status
done
Workflow: Done
status
in-progress
status
in-progress
status
proposed
status
proposed
status
ready
status
ready
status
review
status
review
stream
checkout
stream
checkout
stream
onboarding
stream
onboarding
stream
platform
stream
platform
trivial — implementer only, auto-merge
standard — implementer + reviewer + tester
complex — security if triggered, human merge
critical — full chain + security, human merge
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Fabrika/PersonalBlog#405
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What changed
.gitea/workflows/ci.yml) — restructured from a flat list of per-suite jobs into the seven ordered PR stages of the quality baseline, each gated on its predecessor vianeedsso the pipeline runs in order and halts on the first failing stage:frozen-install—pnpm install --frozen-lockfile(runs before every later stage)typecheck—pnpm typecheck(every workspace package passestsc --noEmit)formatting-lint—pnpm lint(new dependency-free formatting/lint policy)unit— health-endpoint, secrets-not-embedded, config-schema, config-startup-error, config-log-redaction, config-env-adapter, env-example suitesarchitecture— dependency-boundary, workspace layout/config, strict-tsconfig, engine/TypeScript pins, root-commands, frozen-install clean clone, container structure (compose-config, build-targets, non-root-user, readonly-rootfs), database-postgres-imports and the new ci-stages suitespostgres-integration— database-postgres-ledger/lock/diagnostic and app-readiness suites (docker-gated real-stack probes run where a Docker daemon is available and skip cleanly otherwise)build-apps— builds the apps group (pnpm --filter "./apps/**" run build— apps/server today, apps/admin when E06-S01 lands) and verifies the compiledapps/server/dist/index.jsartifactactions/checkoutis pinned to11bd71901bbe5b1630ceea73d27597364c9af683(v4.2.2) andactions/setup-nodeto1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a(v4.2.0), both full commit SHAs — no floating@v4tags anywhere in the workflow.permissions: contents: readat the top level: the pipeline only reads repository contents, so the default (write) token is never needed.tests/formatting-policy.test.mjs(new, dependency-free): every tracked text file must use LF endings, no BOM, no trailing whitespace, no tab indentation and exactly one final newline; JSON files must parse, carry no duplicate keys and use 2-space indentation. Every rule has a mutation probe.package.jsongains"lint": "node --test tests/formatting-policy.test.mjs".tests/ci-stages.test.mjs(new, dependency-free) locks the stage order, theneedschain, the per-stage commands, the action SHA pins, the workflow-levelpermissions: contents: readblock and the invariant that every committedtests/*.test.mjssuite is wired into exactly one CI stage (mutation probes: removing a stage, breaking the chain, dropping/reordering a suite, reverting a pin to a tag, changing a pin or removing the permissions block all fail).configanddatabase-postgrespackages before thestrict-tsconfigsuite:apps/serverimports their compiled type declarations (dist/index.d.ts), whichtsc -p apps/server/tsconfig.json --noEmitcannot resolve on a clean checkout otherwise (this suite was not executed by the previous workflow and surfaced on first CI run).docs/development/non-container.mddocumentspnpm lintand the ordered PR CI stages.node --test tests/<suite>.test.mjsincludes-checks in the config/database/app-readiness suites, the config build-step checks,node-version: '24',corepack enable,--frozen-lockfile) all still hold against the restructured workflow — verified by the full local suite run (296 tests: 283 pass, 13 docker-gated real-stack probes skip cleanly, 0 fail) on Node 24 with the exact CI commands.Criterion → test mapping
ci-stages: "the committed CI workflow runs the required PR stages in order" —frozen-installis the first job and every later stage declaresneedson its predecessor, so the serial chain runs the frozen install before anything else; every stage job also installs withpnpm install --frozen-lockfile. Backed by thefrozen-installstage job and thefrozen-installclean-clone suite (architecture stage)ci-stages: thetypecheckstage must runpnpm typecheck(asserted); the stage job executespnpm typecheck— all five workspace packages passtsc --noEmit(verified locally, exit 0)ci-stages: theformatting-lintstage must runpnpm lint, and the rootlintscript must equalnode --test tests/formatting-policy.test.mjs(both asserted); theformatting-policysuite enforces the policy on every tracked file with mutation probes proving each rule is non-vacuousci-stages: theunitstage names the health-endpoint, secrets-not-embedded, config-schema, config-startup-error, config-log-redaction, config-env-adapter and env-example suites; each suite's own per-PR CI-gating assertion (node --test tests/<suite>.test.mjspresent in the workflow) still passesci-stages: thearchitecturestage names the dependency-boundary (architecture-import, no-core-extension-imports, database-postgres-imports), workspace (layout, config, strict-tsconfig, typescript-pin, node-engine, root-commands, frozen-install) and container-structure (compose-config, build-targets, non-root-user, readonly-rootfs) suites plus ci-stages itselfci-stages: thepostgres-integrationstage names database-postgres-ledger/lock/diagnostic and app-readiness; their docker-gated real-stack probes (migrate an empty database, advisory-lock concurrency, readiness waits on the migration run) run where a Docker daemon is available and skip cleanly otherwise, while the static and deterministic probes always gateci-stages: thebuild-appsstage must build the apps group (./apps/**) and verifyapps/server/dist/index.js(asserted); verified locally:pnpm --filter "./apps/**" run buildemits the server artifact, and the apps-group glob picks upapps/adminautomatically when E06-S01 landsci-stages: "the workflow pins third-party actions to full commit SHAs and declares minimal permissions" — everyuses:ref must match<owner>/<repo>@<40-hex-SHA>and equal the committedPINNED_ACTIONStable (actions/checkout@11bd7190…v4.2.2,actions/setup-node@1d0ff469…v4.2.0). Mutation probes: reverting a pin to@v4or swapping the SHA fails the assertionpermissions: contents: read) at the workflow levelci-stages: the same hardening test asserts the top-levelpermissions: contents: readblock (regex-locked to the workflow level). Mutation probe: removing the block fails the assertion.gitea/workflows/ci.yml, so per review-checklist §6.4 the change is a blocker for any automated approver and requires an explicit human maintainer review/approval before merge (security finding S1 on this PR)ci-stages: coverage assertion — the union of the unit/architecture/postgres-integrationnode --testruns pluspnpm lintnames everytests/*.test.mjsfile exactly once (mutation probe: dropping a suite from its stage fails)Risks
.gitea/workflows/ci.yml; a human maintainer decision/approval is required on the PR, regardless of author. No code change beyond that sign-off.node:test-only and the lockfile is untouched (pnpm install --frozen-lockfileunchanged).actions/checkout@v4.2.2andactions/setup-node@v4.2.0commits; bumping an action means updating both the workflow and thePINNED_ACTIONStable intests/ci-stages.test.mjsin the same PR.apps/adminlands.Human-maintainer approval (kpcto) of the
.gitea/workflows/ci.ymlchange on PR #405 (pipeline tripwire, review-checklist §6.4): the 7-stage quality baseline (frozen-install → typecheck → formatting-lint → unit → architecture → postgres-integration → build-apps) is intended and in-scope, it alters no existing gating step in a weakening direction, and the additiveenv-example-style hardening (SHA pinning +permissions: contents: read) is required by the reworked acceptance criteria. I approve the workflow change subject to those F2/F3 hardenings landing.Human-maintainer approval (kpcto) on the final head
10ea1ef3: I approve the.gitea/workflows/ci.ymlchange (7-stage quality baseline, SHA-pinned actions,permissions: contents: read) — this refreshes my earlier approval so it is non-stale on the current head.