refactor: generate CI workflow from gate contracts
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
# CI quality-gate orchestration
|
||||
|
||||
`config/ci/gates.json` is the executable registry for all 26 gates. The Gitea
|
||||
`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.
|
||||
|
||||
@@ -36,16 +42,54 @@ 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`과 같아야 한다.
|
||||
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 자체를 그대로 승격한다.
|
||||
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를 required check로 전환하기 전에 staging instance에서
|
||||
`permissions`, `gitea.run_attempt`, `actions/upload-artifact@v4`를 포함한 한
|
||||
번의 전체 provider smoke를 통과시켜야 한다. 모든 setup step은
|
||||
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은
|
||||
@@ -152,6 +196,18 @@ 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.
|
||||
|
||||
@@ -45,10 +45,16 @@ including Ed448, are rejected even if a document labels its algorithm
|
||||
`.vite` files), the build manifest, module inventory, release verification,
|
||||
secret-scan result, and local supply-chain evidence once. The candidate
|
||||
manifest hashes the raw lockfile bytes and requires that digest to equal the
|
||||
dependency inventory's `lockfileSha256`. The two provider jobs download this
|
||||
same archive separately and receive `CANDIDATE_LOCKFILE_PATH` and
|
||||
`CANDIDATE_DIST_SHA256`; configured `VULNERABILITY_PROVIDER_COMMAND` and
|
||||
`PROVENANCE_PROVIDER_COMMAND` must emit the signed reports.
|
||||
dependency inventory's `lockfileSha256`. Before upload, the producer validates
|
||||
the manifest-bound exact archive member set and every member digest, then
|
||||
publishes the archive SHA-256 as an immutable job output. The two provider jobs
|
||||
download this same archive separately, compare that output digest, validate the
|
||||
exact member set before extracting only into isolated roots, and receive
|
||||
`CANDIDATE_LOCKFILE_PATH` and `CANDIDATE_DIST_SHA256`; configured
|
||||
`VULNERABILITY_PROVIDER_COMMAND` and `PROVENANCE_PROVIDER_COMMAND` must emit the
|
||||
signed reports. After each external command returns, provider upload validation
|
||||
rechecks the unchanged archive and extracted candidate, parses the provider JSON
|
||||
with its strict schema, and binds its dist and lockfile digests before upload.
|
||||
|
||||
If either provider input is absent, local verification remains meaningful but
|
||||
`artifacts/security/supply-chain-verification.json` records
|
||||
@@ -57,8 +63,59 @@ If either provider input is absent, local verification remains meaningful but
|
||||
set and digests, then read-only revalidates the archived executable schemas,
|
||||
raw lockfile, module inventory, build outputs, release coherence, SBOM,
|
||||
provenance, security scan and supply-chain coherence. It never rebuilds or
|
||||
rewrites candidate evidence. Scanner or signing outages are not converted to
|
||||
an empty PASS.
|
||||
rewrites candidate evidence. Promotion uploads the already verified archive
|
||||
itself with the two provider reports and verification records; it does not
|
||||
create a replacement archive from extracted files. Scanner or signing outages
|
||||
are not converted to an empty PASS.
|
||||
|
||||
The generated workflow is also a supply-chain control. `config/ci/gates.json`
|
||||
is its sole typed authority. Run `corepack pnpm generate:ci-workflow` after a
|
||||
contract change and `corepack pnpm check:ci-workflow` (or the encompassing
|
||||
`corepack pnpm check:ci`) to reject byte drift in the checked-in Gitea adapter.
|
||||
|
||||
Action resolution is separately closed over one typed, runtime-frozen registry
|
||||
in `scripts/contracts/ci-gates.ts`. Every generated `uses:` value is an absolute
|
||||
upstream URL pinned to a full commit SHA:
|
||||
|
||||
- `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)
|
||||
|
||||
Unknown actions, relative repositories, tags/branches and short SHAs are
|
||||
rejected. Gitea 1.22's official Actions documentation recommends the
|
||||
`ChristopherHX` patched artifact forks for v4 compatibility; the supported
|
||||
deployment baseline is nevertheless Gitea 1.26.4+ with Gitea Runner 1.0.0+.
|
||||
A real end-to-end provider smoke on the staging Gitea instance remains
|
||||
mandatory before any generated job becomes a required check.
|
||||
|
||||
External provider supervision is fail-closed and requires a Linux runner with
|
||||
an executable `/usr/bin/bwrap`. Bubblewrap mounts the repository workspace
|
||||
read-only, hides `.git`, pins the trusted Node executable at
|
||||
`/usr/local/bin/node`, and exposes only the sibling `untrusted` raw-evidence
|
||||
directory as writable. Provider commands run non-interactively through
|
||||
`/bin/sh -eu -c`, receive a minimized environment plus only their own
|
||||
provider-prefixed credentials, and have a 30-minute limit. They must consume
|
||||
the supplied candidate paths and digests, write exactly the configured raw
|
||||
report, and must not depend on workspace mutation, host home/toolcache access,
|
||||
or direct access to the sealed evidence path. Missing sandbox support, stale or
|
||||
misplaced outputs, command failure/timeout, and post-command candidate drift
|
||||
all stop publication.
|
||||
|
||||
The final promotion verification/staging step must be immediately adjacent to
|
||||
the promoted-release upload, and that upload must not use `always()`. This
|
||||
reduces the post-verification mutation window but does not seal a pathname
|
||||
across two action steps. The runner is therefore required to be trusted,
|
||||
exclusive and single-tenant, with no provider command or other same-UID process
|
||||
surviving from staging into the immediately following upload. The artifact
|
||||
service and transfer actions also remain outside the candidate's cryptographic
|
||||
identity: every downstream consumer must revalidate the downloaded archive,
|
||||
manifest member digests and signed provider evidence. Producer-side adjacency
|
||||
does not provide consumer-side digest revalidation.
|
||||
|
||||
Approved vulnerability exceptions require vulnerability/package identity,
|
||||
owner, a different reviewer, reason and expiry. Expired or self-approved
|
||||
|
||||
@@ -65,6 +65,20 @@
|
||||
- [ ] Replace regex/token workflow checks with `node scripts/generate-ci-workflow.ts --check`; generated YAML must match byte-for-byte.
|
||||
- [ ] Re-run tests and check mode; commit with `git commit -m "refactor: generate CI workflow from gate contracts"`.
|
||||
|
||||
### Task 3b: Semantic validation for every CI evidence format
|
||||
|
||||
**Files:**
|
||||
- Modify: `scripts/contracts/release-artifacts.ts`
|
||||
- Modify: producer scripts for the remaining generic JSON evidence
|
||||
- Modify: `scripts/lib/ci-artifact-validator.ts`
|
||||
- Modify: `config/ci/gates.json`
|
||||
- Modify: `tests/unit/ci-artifact-contract.test.ts`
|
||||
|
||||
- [ ] Inventory every artifact still mapped to `generic-json-object` and export/reuse the producer's strict schema, including cross-field status/failure/count invariants. Do not treat a non-empty JSON object as semantic evidence.
|
||||
- [ ] Replace substring-only JUnit/HTML acceptance with bounded well-formed document validation. Reject DTD/entities, malformed nesting, duplicate/invalid roots, and trailing non-whitespace content.
|
||||
- [ ] Add invalid-but-pattern-matching fixtures for all structured kinds and a table proving every configured artifact resolves to a semantic validator.
|
||||
- [ ] Run focused artifact tests, `corepack pnpm check:ci`, types, lint, and diff checks; commit separately so this evidence-quality closeout is independently reviewable.
|
||||
|
||||
### Task 4: One authoritative architecture graph
|
||||
|
||||
**Files:**
|
||||
|
||||
Reference in New Issue
Block a user