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 기반 staticImportGraph를 artifacts/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_build는 dist의 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_PATH와
CANDIDATE_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(patchedv4branch)https://github.com/ChristopherHX/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7(patchedv4branch)
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-008의 test: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-010의 FE-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_DIGESTfor the immutable job-container image digest used by theubuntu-latestrunner label; the workflow exposes it to gates asCI_RUNNER_IMAGE(required by every tier)HOSTING_BASE_URLfor live header verificationFIELD_WEB_VITALS_INPUTfor the privacy-approved field sample documentMIN_ELIGIBLE_SAMPLESafter the baseline decisionVULNERABILITY_PROVIDER_COMMANDandPROVENANCE_PROVIDER_COMMANDfor the independently operated scanner and attestor commandsVULNERABILITY_PUBLIC_KEY_PATH,VULNERABILITY_KEY_ID,PROVENANCE_PUBLIC_KEY_PATH, andPROVENANCE_KEY_IDfor 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.