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

13 KiB

CI quality-gate orchestration

config/ci/gates.json is the executable registry for all 26 gates and the sole workflow model. .gitea/workflows/quality-gates.yml is generated from that strict v2 contract; it is not an independent configuration authority. Update the registry, run corepack pnpm generate:ci-workflow, and commit the generated bytes together. corepack pnpm check:ci-workflow rejects missing, stale, CRLF, or otherwise byte-different output. corepack pnpm check:ci runs artifact-schema checking, the typed contract checker, and this workflow drift check. 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과 같아야 한다. Producer는 게시 전에 manifest가 선언한 exact member set과 각 byte digest를 검증하고 archive SHA-256을 job output으로 고정한다. Vulnerability/provenance provider job은 같은 archive를 각각 내려받아 output SHA-256과 exact member set을 확인한 뒤 checkout과 분리된 root에만 추출하고, CANDIDATE_LOCKFILE_PATHCANDIDATE_DIST_SHA256를 외부 command에 전달한다. Provider command 실행 후에는 archive와 추출된 candidate를 다시 검증하고, strict provider JSON과 candidate digest가 일치할 때만 report를 업로드한다. Promotion은 같은 archive와 두 report를 별도 경로로 내려받고 SHA/member 검증을 마친 뒤 격리된 root에 추출하여 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의 provider job은 Linux runner에서 실행 권한이 있는 /usr/bin/bwrap를 필수로 요구하며, 사용할 Node 실행 파일도 sandbox 안의 /usr/local/bin/node로 고정한다. Provider command는 bubblewrap 안에서 /bin/sh -eu -c로 비대화식 실행되고 30분 안에 종료되어야 한다. Sandbox는 workspace를 read-only로 bind하고 .git을 가리며, 별도의 untrusted raw-evidence 하위 디렉터리만 writable로 노출한다. 따라서 command는 전달된 candidate/environment 값을 읽고 지정된 raw report 하나만 기록해야 하며, workspace 수정, host home/toolcache 접근, sealed evidence 직접 기록에 의존하면 안 된다. Supervisor는 provider 종류에 해당하는 credential prefix와 제한된 환경만 전달하고, sandbox 또는 출력 경계를 만들 수 없으면 fail closed한다.

Workflow가 실행하는 action은 scripts/contracts/ci-gates.ts의 단일 typed, runtime-frozen registry에서만 resolve된다. uses:에는 repository 별칭, tag, branch 또는 short SHA를 허용하지 않고 다음 absolute upstream URL과 full 40-hex commit만 사용한다.

  • https://github.com/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 (v4.3.1)
  • https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 (v4.4.0)
  • https://github.com/ChristopherHX/gitea-upload-artifact@81f940d004763f986ba3582c007fd842dd5cb0d7 (patched v4 branch)
  • https://github.com/ChristopherHX/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7 (patched v4 branch)

Gitea 1.22 공식 Actions 문서는 artifact v4 호환 경로로 이 ChristopherHX patched upload/download fork를 권고한다. 이 권고는 실제 설치 환경의 호환성을 증명하지 않으므로, 지원 baseline은 계속 Gitea 1.26.4+ 및 Gitea Runner 1.0.0+이며 action 변경은 registry, semantic tests, snapshot과 generated workflow를 함께 갱신해야 한다.

이 workflow를 required check로 전환하기 전에 staging instance에서 permissions, gitea.run_attempt, 위 digest-pinned patched artifact action을 포함한 한 번의 실제 end-to-end provider smoke를 반드시 통과시켜야 한다. mock/local generation 결과는 required-check enablement evidence가 아니다. 모든 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.

Promotion verification/staging step과 promoted-release upload action 사이에는 어떤 step도 둘 수 없고 upload에는 if: always()를 사용할 수 없다. 이 인접성은 실패한 검증의 publication을 막지만 staging path를 upload action에 cryptographically hand off하지는 않는다. 따라서 promotion runner는 trusted, exclusive, single-tenant여야 하고 staging step 종료부터 바로 다음 upload action이 파일을 여는 시점까지 같은 UID로 실행되는 provider command나 다른 process가 살아남지 않는다는 운영 가정을 만족해야 한다. 업로드된 artifact의 downstream consumer도 artifact service나 transfer action을 신뢰 경계 밖으로 보고, candidate manifest와 signed provider evidence에 바인딩된 digest를 다운로드 후 다시 검증해야 한다. 현재 producer-side adjacency 자체는 consumer-side digest revalidation을 대신하지 않는다.

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.