Files
clean-architecture-frontend…/docs/operations/ci-quality-gates.md
T

9.4 KiB

CI quality-gate orchestration

config/ci/gates.json is the executable registry for all 26 gates. The Gitea adapter runs each gate as an independent matrix check with full fan-out and no soft-fail wiring.

The dependency graph is:

MERGE_READY
  -> RELEASE_READY
    -> PROD_PROMOTION_READY
      -> FIELD_SLO_READY

DOCUMENTATION_READY (off-chain)

check:architecture는 dependency-cruiser 결과와 별도로 Babel parser/Node resolver 기반 staticImportGraphartifacts/quality/dependency-report.json에 기록한다. TS/TSX의 static, dynamic, type, CommonJS 및 JSDoc import를 검사하고, 로컬 source import에는 실제 TypeScript 확장자를 요구한다. 실행 source나 로컬 specifier에 .js/.jsx/.mjs/.cjs가 있거나 unresolved dependency, parse failure, error-severity layer violation, cycle 또는 지원하지 않는 architecture rule shape가 하나라도 있으면 gate는 실패한다. resolver, unresolved import, layer edge와 cycle regression fixture도 같은 명령에서 실행된다.

Gitea workflow는 run ID/attempt, checkout SHA와 ref에서 build/release 식별자를 만들고 최소 contents: read 권한만 요청한다. Gate runner는 HEAD의 full commit ID와 commit timestamp를 한 번 읽어 SOURCE_DATE_EPOCH를 유도한 뒤 workflow의 SHA와 실제 checkout이 일치하는지 확인한다. CI=true에서 이 식별자 중 하나라도 없거나 불일치하면 build step 전에 fail-closed하며, build와 release manifest도 같은 build ID, commit SHA, release ID와 timestamp를 가져야 한다.

Release tier의 immutable_builddist의 hidden .vite entries, raw pnpm-lock.yaml, build/module inventory와 local release/security evidence를 한 번 생성해 run ID/attempt로 구분된 archive로 게시한다. Raw lock bytes의 SHA-256은 dependency inventory와 candidate manifest 양쪽의 lockfileSha256과 같아야 한다. Vulnerability/provenance provider job은 같은 archive를 각각 내려받고 CANDIDATE_LOCKFILE_PATH, CANDIDATE_DIST_SHA256를 외부 command에 전달한다. Promotion은 같은 archive와 두 report를 별도 경로로 내려받은 뒤 local evidence를 read-only로 다시 계산하고 Ed25519 signature/digest를 확인한다. Promotion job에는 build/rebuild command가 없으며 검증한 archive 자체를 그대로 승격한다.

Provider baseline은 Gitea 1.26.4 이상과 Gitea Runner 1.0.0 이상이다. 이 workflow를 required check로 전환하기 전에 staging instance에서 permissions, gitea.run_attempt, actions/upload-artifact@v4를 포함한 한 번의 전체 provider smoke를 통과시켜야 한다. 모든 setup step은 node-version-file: .nvmrc를 사용하므로 CI Node.js 버전은 .nvmrc의 exact pin과 같다. CI contract는 .nvmrc가 full semantic version인지, 모든 job이 이 파일을 사용하는지 함께 검사한다. ubuntu-latest runner label은 관리자가 임의 환경에 매핑할 수 있으므로 provenance로 사용하지 않는다. 대신 그 label을 immutable container image에 매핑하고 동일한 image digest를 repository variable RUNNER_IMAGE_DIGEST에 설정한다. Workflow는 이 값을 runtime environment의 CI_RUNNER_IMAGE로 전달하며, 값이 비어 있으면 모든 gate가 실행 전에 실패한다. Gitea repository variable 이름에는 CI prefix를 사용할 수 없으므로 두 이름을 의도적으로 구분한다.

Pull requests and develop pushes evaluate merge readiness. Version tags evaluate merge then release readiness. Production and field evaluation require an explicit workflow dispatch. The field tier cannot pass until the 28-day sample threshold decision is recorded. Documentation readiness consumes the canonical project-note evidence in which both scoped diagrams already received 100/100 PASS_SCOPED; the repo ledger preserves the evidence scope and canonical digests.

All jobs upload the shared artifacts/ tree even after failure. Numeric retention remains an organization/provider decision; the workflow intentionally does not invent retention-days. The relative minimums are recorded in the registry: merge evidence through the PR decision, coherent release evidence through the next release promotion, drill evidence through the next production promotion, and field evidence through aggregation.

Runbook jobs write to the stable path artifacts/runbooks/<runbook-id>/record.json; the dynamic release identity is stored inside the record. This keeps gate evidence lookup independent from slashes or other provider-specific characters in RELEASE_ID, while each workflow artifact remains scoped to its own run.

Browser-backed merge gates install and execute the pinned Chromium, Firefox, and WebKit engines. This makes route behavior, reflow, native dialog semantics, theme persistence, and automated accessibility a cross-engine contract rather than a Chromium-only smoke check.

FE-GATE-005의 unit suite에는 client cache/storage의 deterministic contract가 포함된다. storage-registry.test.ts는 key별 codec, schema/TTL, 16,384-byte 상한, quota/security/corrupt cleanup과 memory fallback을, cross-tab-invalidation.test.ts는 2,048-byte exact wire, topic/release epoch, self/duplicate/out-of-order/gap, BroadcastChannel → localStorage → local-only degrade와 cleanup을 검증한다. tanstack-cache-coordinator.test.ts는 topic을 local namespace로만 resolve하고 query key/data를 wire에 보내지 않으며 mutation 중 remote hint를 coalesce하는지 검증한다. 결과는 기존 artifacts/tests/unit.xml과 coverage evidence에 포함된다.

FE-GATE-006과 runtime adapter unit은 QueryInvalidationProvider/coordinator가 실제 production provider tree와 bootstrap에 존재하고 browser capability getter가 실패해도 boot가 DEGRADED_LOCAL_ONLY로 계속되는지 확인한다.

FE-GATE-008test:browser-capabilities suite는 File/Blob, native file input, IndexedDB, OPFS, Cache Storage와 StorageManager reference runtime을 Vite dev origin의 실제 browser API에 연결한다. API가 없는 engine에서는 skip하지 않고 adapter의 명시적인 UNSUPPORTED fallback을 검증한다. 생성한 database, OPFS namespace와 owned cache는 각 test가 자신이 만든 opaque namespace만 정리한다.

FE-GATE-010은 application/domain/presentation에서 raw browser storage와 picker global 접근을 막고, 선택되지 않은 reference runtime을 bootstrap이나 installed feature가 import하는 것도 거절한다. catalog의 referenceRuntime.sourceRoots와 conformance script가 실제로 존재해야 하며, productionComposition은 project capability 결정 전까지 false다. 같은 gate의 realtime source/fixture 검사는 native SSE/WebSocket/Web Push API가 소유 adapter 밖으로 새는 것, presentation timer owner, 미선택 runtime의 bootstrap/installed-feature 조합을 차단한다. optional recipe source gate는 uncomposed runtime 전체를 tree-shaking 없이 합성해 catalog의 gzip 예산도 blocking으로 검증한다. FE-GATE-020은 reference runtime source·전용 test·catalog metadata를 제거한 임시 repository에서도 base typecheck, architecture, test, build가 통과하는지 검증해 skeleton의 선택성을 유지한다. browser file/storage와 realtime runtime은 각각 독립 removal fixture와 JUnit evidence를 가지며, realtime fixture는 공통 event authority, SSE, WebSocket, bounded Polling, Web Push source와 공개 export, 전용 boundary script를 제거한 뒤 base gate를 다시 실행한다.

같은 FE-GATE-010FE-REG-QUERY governance는 installed query마다 namespace, serialization/identity, invalidation topic, version, crossContext: "invalidate-only"persistence: "disabled"를 요구한다. 따라서 cross-tab invalidation은 조립됐지만 query persistence와 기존 IndexedDB reference runtime은 계속 미조립이다.

현재 FE-GATE-008 browser-capability suite에는 실제 두 page의 BroadcastChannel/localStorage fallback scenario가 아직 없다. unit fake 통과를 native multi-tab promotion evidence로 간주하지 않는다. 이 gap은 Client cache and browser storage platform의 완료 기준에 미완료로 남아 있다. 현재 장애 분류와 안전한 local-only 복구 절차는 Client cache and Web Storage recovery를 따른다.

Repository variables required by higher tiers:

  • RUNNER_IMAGE_DIGEST for the immutable job-container image digest used by the ubuntu-latest runner label; the workflow exposes it to gates as CI_RUNNER_IMAGE (required by every tier)
  • HOSTING_BASE_URL for live header verification
  • FIELD_WEB_VITALS_INPUT for the privacy-approved field sample document
  • MIN_ELIGIBLE_SAMPLES after the baseline decision
  • VULNERABILITY_PROVIDER_COMMAND and PROVENANCE_PROVIDER_COMMAND for the independently operated scanner and attestor commands
  • VULNERABILITY_PUBLIC_KEY_PATH, VULNERABILITY_KEY_ID, PROVENANCE_PUBLIC_KEY_PATH, and PROVENANCE_KEY_ID for separately managed trusted Ed25519 verification material

If any external provider command, report, trust path, or key ID is absent, promotion remains unavailable with FAIL_UNVERIFIED; there is no local generator/restore fallback.

Branch protection must mark each FE-GATE-* / <name> check required for its declared tier. This repository cannot configure server-side protection by committing a file.