Developer CI/CD guide¶
This page explains how to add or change CI/CD safely in dpone. It belongs in Developer docs because workflow changes are implementation changes: they affect release safety, contributor UX, security posture, and public artifacts.
Principles¶
- Keep the default PR gate deterministic and credential-free.
- Keep vendor credentials in manual or scheduled gates only.
- Use least-privilege workflow permissions.
- Prefer explicit artifacts over hidden logs.
- Make every new workflow reproducible locally or document why it cannot be.
- Update user docs and runbooks in the same PR as workflow changes.
- Keep
masteras the default branch in workflow triggers and docs. - Cancel superseded work only inside one pull request; queue scheduled work in a separate stable group when every retained run matters.
- Treat skipped or unavailable provider evidence as
UNVERIFIED, never PASS. - Keep pull-request-reachable repository code read-only. Put any narrowly approved write/OIDC capability in a closed source-free action-only finalizer.
When to add a new workflow¶
Add a workflow only when one of these is true:
| Case | Prefer |
|---|---|
| Fast deterministic quality check | Extend .github/workflows/ci.yml. |
| Documentation build/deploy behavior | Extend .github/workflows/pages.yml. |
| Public package publishing | Extend .github/workflows/release.yml. |
| Long-running source/sink or connector certification | Manual/scheduled workflow. |
| Vendor credentials required | Manual/scheduled workflow with explicit secrets and docs. |
| Security/supply-chain posture | Dedicated security workflow with least-privilege permissions. |
Do not add a new always-on PR workflow for slow, flaky, vendor-dependent, or credential-dependent tests.
New workflow checklist¶
Before opening a PR:
- Choose the trigger:
push,pull_request,workflow_dispatch,schedule, or tag. - Choose minimum permissions. Start with
contents: read. - Decide whether the workflow must run on
master, PRs, tags, schedule, or manual dispatch. - Add local reproduction commands to Workflow reference.
- Add failure recovery to Failure runbooks.
- Add artifact names and retention expectations.
- Add or update tests that protect the docs/workflow contract.
- Confirm no job writes secrets to logs or artifacts.
- Run the pinned changed-workflow actionlint gate and its queue-waiver removal tripwire.
- Confirm release-sensitive workflows are byte-identical unless a separate approved release change owns them.
- Run the semantic privilege scanner and treat both
FAILandUNVERIFIEDas blocking. A write-scope allowlist entry cannot authorize a reachable route.
Changing the default CI gate¶
Default CI changes affect all contributors. Keep them boring and reproducible.
Required local gate:
uv sync --locked --all-extras
uv run ruff check .
uv run ruff format --check .
uv run mypy --config-file mypy.ini
uv run pytest -m "not integration_live" --cov=src/dpone --cov-report=xml
uv build
Rules:
- Do not require Docker for the default quality matrix unless the job is isolated like
postgres-xmin. - Do not require cloud/vendor credentials.
- Keep optional integration markers opt-in.
- If coverage minimum changes, document the rationale in the PR.
- Keep project synchronization locked. A stale
uv.lockmust fail and be repaired in a reviewed lock change; do not replace--lockedwith--frozenor an unlocked sync.
PR-reachable privilege checklist¶
Use this checklist whenever a workflow trigger, job, needs chain, reusable
workflow call, workflow_run, runner, environment, secret, permission, or
condition changes.
- Discover — determine whether any route begins at
pull_requestor the forbiddenpull_request_target. File-local actionlint is not a transitive authority proof. - Prepare — use an immutable checkout and run
uv sync --locked --all-extrasonce. This prerequisite may access the network, write the environment or uv cache, and use stderr; it is outside the scanner process contract. No GitHub credential is required by the scanner. - Configure — start with
permissions: {}or the smallest explicit read map. In a specified mapping omitted permissions becomenone. Do not useread-all,write-all, PR secrets, environments, or self-hosted runners.
The smallest copyable PR workflow keeps repository execution read-only and disables persisted checkout credentials:
name: PR checks
on:
pull_request:
branches: [master]
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78
with:
version: "0.11.28"
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
with:
python-version: "3.12"
- run: uv sync --locked --all-extras
- run: uv run --no-sync pytest tests/test_runtime_optional_import_safety.py -q
The workflow reference
distinguishes the legacy policy and
evals/agent/workflow-security.schema.json from the semantic policy,
evals/agent/workflow-security-privileged-policy.schema.json, and
evals/agent/workflow-security-privileged-report.schema.json. Start local
reuse from the literal-only caller/callee pair.
4. Isolate — repository commands, checkout, setup, install, cache, and
scripts stay in read-only jobs. A privileged PR-route job is acceptable only
when it is one exact mandatory closed profile.
5. Execute — run both public repository surfaces:
uv run --locked --no-sync --offline --no-python-downloads python -B \
tools/agent_policy/workflow_security_privileged.py \
--root . \
--format text
uv run --locked --no-sync --offline --no-python-downloads python -B \
tools/agent_policy/workflow_security.py . --format json
These are prepared-environment convenience commands. For exact standalone exit and stream evidence, bypass the uv wrapper:
The no-network/no-file-mutation guarantee begins when that scanner process
starts.
6. Observe — for the direct scanner invocation, standalone PASS exits
0; FAIL and UNVERIFIED emit a report on stdout and exit 1. The
umbrella keeps exactly status, errors, and warnings in JSON and maps
every semantic non-pass into errors after existing errors.
7. Diagnose and recover — follow the finding's route, permission source,
and recovery_command_id in the
semantic privilege runbook.
Never widen the fixture or policy to hide an unknown route.
8. Operate — keep the standalone scan and compatibility umbrella in the
normal workflow-control review, retain governance artifacts for 90 days,
and investigate every profile drift or non-deterministic replay.
9. Upgrade — treat a CodeQL pin update as a separately reviewed closed-profile
contract change. Synchronize the workflow, semantic policy, policy-schema
const, trusted report binding, producer-owned fixtures/contracts, and
certification evidence; run profile/schema/report/mutation tests, two
byte-identical scans, and hosted exact-head CodeQL. Any governance finalizer
or merge-closure fingerprint change requires a prior approved ADR 0037
amendment; the consuming PR cannot approve its own new exception.
Safe governance evidence topology is read-only producer code followed by the
exact source-free two-action finalizer. Emergency containment may disable the
finalizer, but that deliberately leaves attestation unavailable and the
mandatory profile red. It never authorizes restoring OIDC or attestation
permissions to the quality job or another job that runs repository code.
Calling a local reusable workflow safely¶
Closed v1 proves a local reusable call when every input is a YAML literal with
the callee's exact declared type. Save this read-only caller as
.github/workflows/pr-report.yml:
name: PR reusable report
on:
pull_request:
permissions: {}
jobs:
report:
name: Render report
permissions:
contents: read
uses: ./.github/workflows/reusable-report.yml
with:
strict: true
attempts: 2
label: pull-request
Save its callee as .github/workflows/reusable-report.yml:
name: Reusable report
on:
workflow_call:
inputs:
strict:
type: boolean
required: true
attempts:
type: number
default: 1
label:
type: string
required: true
permissions: {}
jobs:
report:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- run: printf '%s %s\n' "${{ inputs.label }}" "${{ inputs.attempts }}"
The caller job may contain only name, uses, with, secrets, needs,
if, concurrency, and permissions. strategy and input expressions such
as ${{ matrix.target }} are valid GitHub extensions but are UNVERIFIED in
closed v1; do not disguise expression text as a string literal. Boolean is not
a number, extra inputs are rejected, and every required input must be present.
secrets: inherit is rejected. An explicit named-secret map is structurally
recognized, but a secret reachable from pull_request is still a
PRIVILEGE_PR_SECRET_OR_ENVIRONMENT FAIL; structural acceptance never means
the route is safe. Keep PR callers secret-free, or move the operation to a
separately reviewed non-PR route.
Execute: after committing both files to an otherwise safe immutable checkout, run the prepared-environment scanner from the repository root:
uv run --locked --no-sync --offline --no-python-downloads python -B \
tools/agent_policy/workflow_security_privileged.py --root . --format text
Observe: the local report is status=PASS with finding_count=0; it
creates no file or artifact. When GitHub runs the caller, the reusable job log
contains pull-request 2; this example has no artifact upload or persisted
output. Local PASS classifies checked-out workflow bytes only and is not
hosted-run evidence.
Recover: for invalid workflow_call inputs, restore the exact literal
types and required names shown above, keep the PR caller secret-free, and rerun
the same command. Follow the
semantic privilege runbook
for the finding-specific recovery and evidence checklist; do not widen policy
or add an override.
Choosing concurrency behavior¶
Use a PR-number group with conditional cancellation for superseded contributor work. This prevents one PR from cancelling another and retains push/manual exact-commit runs:
concurrency:
group: ci-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
Use a separate source-free wrapper for scheduled work that must queue rather
than cancel. The Airflow nightly wrapper owns the stable
airflow-pack-compat-nightly group, queue: max, and
cancel-in-progress: false; the reusable implementation owns the matrix.
Never combine queue: max with active cancellation.
Pages is different: its complete build, freshness verification, and deploy sequence uses one PR/ref group. PR heads may cancel; non-PR runs serialize without a queue key. Do not move concurrency down to only the deploy job, because completion order could publish an older site last.
Pinned actionlint for changed workflows¶
PR 3A validates only changed workflows in its implementation allowlist with
actionlint 1.7.12; unrelated repository-wide findings are not reclassified.
An already installed binary is acceptable only when the first line is exact:
Otherwise download the official archive for the local platform and verify it before extracting. These are the approved release bytes:
| Platform | Archive | SHA-256 |
|---|---|---|
| Linux amd64 | actionlint_1.7.12_linux_amd64.tar.gz |
8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 |
| Darwin arm64 | actionlint_1.7.12_darwin_arm64.tar.gz |
aba9ced2dee8d27fecca3dc7feb1a7f9a52caefa1eb46f3271ea66b6e0e6953f |
CI additionally streams only the actionlint archive member into a new
$RUNNER_TEMP/actionlint-1.7.12 directory and verifies Linux binary size
6074530 plus SHA-256
c872d6db8c6bf83a8eaa704fc93999f027d55dffbc63b8a6abdccb47df5f4cd4
before execution. It never extracts unrelated members or overwrites a repository
path.
Run the non-queue paths together with no ignore:
actionlint \
-no-color -format '{{json .}}' -shellcheck '' -pyflakes '' \
.github/workflows/ci.yml \
.github/workflows/airflow-pack-compat.yml \
.github/workflows/pages.yml \
.github/workflows/dependency-review.yml
This invocation must exit 0, write exactly [] followed by one newline to
stdout, and leave stderr empty. The nightly wrapper is the only path allowed a
temporary diagnostic waiver while released 1.7.12 lacks
concurrency.queue support:
actionlint \
-no-color -format '{{json .}}' -shellcheck '' -pyflakes '' \
-ignore '^unexpected key "queue" for "concurrency" section\. expected one of "cancel-in-progress", "group"$' \
.github/workflows/airflow-pack-compat-nightly.yml
The same file must first be run without -ignore: require exit 1, empty
stderr, and one exact syntax-check object for the original filepath/message.
The waived run must exit 0 with exact []\n stdout and empty stderr. Extra
diagnostics or stream bytes fail the gate.
When a pinned released actionlint understands queue, the unwaived run exits
0. Treat that as ACTIONLINT_QUEUE_WAIVER_OBSOLETE; remove the ignore and the
old expected-error branch in the same approved update. A version upgrade changes
the version, applicable archive checksums, source URL, and version assertion
atomically. Floating latest, an upstream branch, or an unreleased commit is
not allowed.
CI-hygiene release boundary¶
Changes to ordinary CI do not authorize edits to release behavior. Keep these workflows byte-identical to the approved implementation base unless a separate release specification owns the change:
.github/workflows/release.yml;.github/workflows/runtime-image.yml;.github/workflows/certification-release-summary.yml;.github/workflows/route-certification-release.yml;.github/workflows/route-release-finalize.yml.
uv build and isolated uv pip install --python ... commands are not project
synchronization; do not mechanically rewrite them. Preserve required-check
names, protection, release credentials, tags, environments, and publication
semantics.
Adding a manual integration gate¶
Use this pattern for broad matrix, stress, benchmark, or connector certification workflows:
on:
workflow_dispatch:
inputs:
run_mode:
type: choice
options: [mock_contract, mock_local, vendor_live]
permissions:
contents: read
Design requirements:
- All input names must be documented.
- Every run writes artifacts even on failure with
if: always(). mock_contractandmock_localmust not require external vendor credentials.vendor_livemust clearly list required secrets.- Use focused filters such as
DPONE_MATRIX_CASE_IDfor debugging.
Adding secrets¶
Before adding a secret:
- Ask whether Trusted Publishing, OIDC, or local mock mode can avoid the secret.
- Scope the secret to the smallest provider/project/package possible.
- Use it only in the workflow/job that needs it.
- Redact diagnostics and artifacts.
- Add rotation instructions to the relevant runbook.
Never add secrets to ordinary PR CI from forks.
Adding artifacts¶
Artifacts should answer: what ran, with what inputs, what passed/failed, and what evidence exists.
Recommended artifact content:
- command and workflow inputs;
- source/sink/strategy coverage;
- row counts and checksums for data movement tests;
- warnings and skipped cases with reason;
- environment summary without secrets;
- JUnit XML when pytest is involved.
Recommended paths:
test_artifacts/integration_matrix/
test_artifacts/connectors/
test_artifacts/benchmarks/
test_artifacts/route_certification/
test_artifacts/route_certification_release/
test_artifacts/route_release_finalize/
test_artifacts/route_readiness/
test_artifacts/route_execution/
test_artifacts/route_rc_execution/
test_artifacts/route_state/
test_artifacts/cdc_apply/
test_artifacts/cdc_handoff/
test_artifacts/cdc_schema/
test_artifacts/cdc_schema_apply/
test_artifacts/cdc_promotion/
test_artifacts/cdc_runtime/
test_artifacts/cdc_poison_quarantine/
test_artifacts/cdc_compare_repair/
test_artifacts/certification/suite/
test_artifacts/observability/
test_artifacts/supply-chain/
Adding certification suite automation¶
Use Certification suite automation when a workflow needs to combine matrix correctness, benchmark regression, lineage, dbt lineage, and evidence checksums.
Recommended flow:
uv run dpone ops certification-run \
--artifact-dir test_artifacts/certification/current
uv run dpone ops benchmark-baseline \
--output-dir test_artifacts/benchmarks/current \
--metrics-json "$METRICS_JSON" \
--baseline-json "$BASELINE_JSON"
uv run dpone ops certification-suite \
--output-dir test_artifacts/certification/suite \
--suite-id "$GITHUB_RUN_ID" \
--certification-report test_artifacts/certification/current/certification_report.json \
--benchmark-baseline test_artifacts/benchmarks/current/benchmark_baseline.json \
--require-benchmark \
--format json
Rules:
- Keep this workflow manual or scheduled unless it is credential-free and fast.
- Upload
test_artifacts/certification/suite/withif: always(). - For critical source -> sink routes, generate route evidence first and then run
uv run dpone ops route-certification-pack; uploadtest_artifacts/route_certification/and any embeddedtest_artifacts/route_readiness/outputs withif: always(). - For new or changed source -> sink onboarding UX, run
uv run dpone ops connection-doctor,uv run dpone ops source-discover,uv run dpone ops route-bootstrap, anduv run dpone ops route-doctoragainst credential-free schema fixtures. Uploadtest_artifacts/onboarding/withif: always()and treatroute_doctor.jsonas the pre-readiness user-facing go/no-go artifact; see Route bootstrap and doctor. - For route acceptance changes, run
uv run dpone ops route-conformance runfor each changedsource -> sink -> strategypair and thenuv run dpone ops route-conformance release-gateover the produced artifacts. Uploadtest_artifacts/route_conformance/withif: always(). Ordinary OSS CI can use deterministic credential-free synthetic datasets; Docker-live/vendor-live jobs should attach live evidence separately. See Route Conformance Lab. - For protected Docker-live route acceptance, run
DPONE_VENDOR_LIVE=1 DPONE_LIVE_ROUTE_CONFORMANCE=1 uv run dpone ops route-conformance live-runwith--adapter vendor_liveor--adapter dockeragainst disposable Postgres, MSSQL, and ClickHouse services. The built-in protected bindings coverpostgres -> mssqlandmssql -> clickhouseforincremental_merge. Uploadroute_conformance_live.json,live_source_snapshot.json,live_sink_snapshot.json, and the embeddedroute_conformance.jsonwithif: always(). Keep the default adapterin_memoryfor ordinary OSS CI and register real Postgres, MSSQL, or ClickHouse adapters only in opt-in jobs. - For route release candidates, run
uv run dpone ops route-rc-executewithout--executefirst and uploadroute_rc_execution.json. Add--executeonly behind an explicit manual workflow input and after Docker or vendor-live services are ready. - When retry/resume, state commit, fencing, repair, resync, or route execution
ordering changes, run
uv run dpone ops route-execution-ledgerfor the affected route and uploadtest_artifacts/route_execution/withif: always(). The required route readiness evidence isroute_execution_ledger. Use--store-backend sqlite --store-uri test_artifacts/route_execution/shared/route_execution_ledger.sqlite3when a workflow has more than one local runner or needs atomic compare-and-swap lease fencing evidence; see Route execution ledger. - When source state, offset, xmin, LSN, cursor, or checkpoint promotion changes,
run
uv run dpone ops route-state-promoteafter route execution ledger evidence and uploadtest_artifacts/route_state/withif: always(). The required route readiness evidence isstate_promotion. Use--state-backend sqlite --state-uri test_artifacts/route_state/shared/route_state_store.sqlite3when a workflow has more than one local runner or needs atomic compare-and-swap state evidence; see Route state promotion. - For route release candidates, run
uv run dpone ops route-release-gateafterroute_readiness.json,route_certification_pack.json,route_execution_ledger.json,state_promotion.json, and any route-specific benchmark/schema/CDC evidence exist. Uploadtest_artifacts/route_release/withif: always()and treatroute_release_gate.jsonas the final route-scoped go/no-go artifact; see Route release gate. - For Docker-live or vendor-live route release candidates, run
uv run dpone ops route-live-certificationafter the upstream live checks have produced route evidence. Uploadtest_artifacts/route_live/withif: always()and pass the live bundle toroute-release-gate:
--artifact route_live_evidence_bundle=<route_live_certification.json> \
--require route_live_evidence_bundle
See
Route live certification.
- For route releases, run uv run dpone ops route-certify after refresh
execution, snapshot capture, exact verification, readiness, checklist, and
evidence-chain artifacts exist. Use --profile oss_ci in ordinary CI and
--profile vendor_live only in opt-in Docker-live/vendor-live jobs that also
attach route_live_evidence_bundle. Upload test_artifacts/route_certify/
with if: always() and treat route_certification_bundle.json as the final
route promotion artifact; see Route certify.
- For release candidates that claim first-class route certification, run
uv run dpone ops route-certify-release after every required route has a
route_certification_bundle.json. Upload
test_artifacts/route_certification_release/ with if: always() and treat
route_certification_release.json as the release-level go/no-go artifact;
see Route certify release.
- For final route-certified release review, run
uv run dpone ops route-release-finalize after route bundles are in a stable
bundle root. Upload test_artifacts/route_release_finalize/ with
if: always() and treat route_release_finalizer.json plus
route_certification_history_index.json as the final route release evidence;
see Route release finalize.
- For route release candidates that need the full train in one receipt, run
uv run dpone ops route-rc-orchestrator. Upload
test_artifacts/route_rc/ with if: always() and treat
route_rc_orchestration.json plus the nested release_evidence_pack.json as
the review bundle; see
Route release candidate orchestrator.
- For release candidates that merge a stacked train, export every PR with
gh pr view --json number,title,baseRefName,headRefName,state,mergeStateStatus,isDraft,url,statusCheckRollup,
run uv run dpone ops release-rc-collect, and then run the generated
uv run dpone ops release-rc-finalize command after route, docs, package,
and release evidence exists. Upload test_artifacts/release_rc_collect/ and
test_artifacts/release_rc_finalizer/ with if: always() and block tags
unless release_rc_finalizer.json is rc_ready; see
Release RC collector and
Release RC finalizer.
- For CDC promotion routes, generate or export a credential-free fixture, run
uv run dpone ops cdc-apply-certification, then run or review the embedded
uv run dpone ops cdc-handoff report; upload test_artifacts/cdc_apply/
with if: always().
- When CDC evidence is produced by another specialized job, generate snapshot
boundary, CDC window, retention, apply correctness, delete semantics, typed
hash, and schema drift artifacts first, then run uv run dpone ops cdc-handoff; upload
test_artifacts/cdc_handoff/ with if: always().
- When CDC telemetry or SLO behavior changes, run
uv run dpone ops cdc-observability-evidence with the handoff JSON, apply
certification JSON, metrics JSON, and optional SLO profile; upload
test_artifacts/cdc_observability/ with if: always().
- When CDC recovery behavior changes, run
uv run dpone ops cdc-recovery-evidence with handoff, apply,
observability, scenario, and optional policy JSON; upload
test_artifacts/cdc_recovery/ with if: always().
- When CDC schema evolution or target DDL governance changes, run
uv run dpone ops cdc-schema-evolution-evidence with handoff, apply,
observability, recovery, schema-change, and optional policy JSON; upload
test_artifacts/cdc_schema/ with if: always().
- When CDC target DDL apply or typed serving refresh behavior changes, run
uv run dpone ops cdc-schema-apply --mode dry_run and, for approved local
Docker routes, uv run dpone ops cdc-schema-apply --mode apply; upload
test_artifacts/cdc_schema_apply/ with if: always(); see
CDC schema apply. The opt-in live check must verify
additive column DDL, backfill SQL, cdc_schema_apply_result.json, and
typed_refresh/cdc_typed_materialization.json when --typed-refresh is set.
- When a CDC stream is ready for release or environment promotion, run
uv run dpone ops cdc-promotion-gate with apply, handoff, observability,
recovery, and schema evolution JSON; upload test_artifacts/cdc_promotion/
with if: always(). The gate records production_ready and
promote_offsets; it does not promote offsets itself.
- When runtime CDC apply behavior changes, run
uv run dpone ops cdc-runtime-run with a bounded events JSON and local
checkpoint JSON; upload test_artifacts/cdc_runtime/ with if: always().
This verifies that offsets commit only after durable sink apply.
- When CDC poison classification, quarantine, or replay behavior changes, run
uv run dpone ops cdc-runtime-run --poison-mode quarantine_and_continue,
uv run dpone ops cdc-quarantine-inspect, and
uv run dpone ops cdc-replay-execute; upload
test_artifacts/cdc_poison_quarantine/ with if: always(). See
CDC poison quarantine and replay. The check must
prove cdc_poison_quarantine.json, cdc_quarantine_inspection.json,
cdc_replay_execution.json, duplicate_events_skipped, and no offset
mutation during replay.
- When CDC compare, repair planning, or repair execution changes, run
uv run dpone ops cdc-compare-repair and, for approved bounded plans,
uv run dpone ops cdc-repair-execute; upload
test_artifacts/cdc_compare_repair/ with if: always(). See
CDC compare and repair. The check must prove
cdc_compare_repair.json, cdc_repair_plan.json,
cdc_repair_execution.json, source-to-ClickHouse CDC log current-state
comparison, and no offset mutation during repair.
- When CDC retention gap auto-resync changes, run
uv run dpone ops cdc-retention-check, uv run dpone ops cdc-resync-plan,
and, for approved bounded plans, uv run dpone ops cdc-resync-execute;
upload test_artifacts/cdc_retention_resync/ with if: always(). See
CDC retention gap auto-resync. The check must
prove cdc_retention_check.json, cdc_resync_plan.json,
cdc_resync_actions.json, cdc_resync_execution.json, Docker-live
mssql -> clickhouse probe coverage when credentials are available, and no
offset mutation during resync.
- When live CDC runtime adapters change, run the local smoke above and an
opt-in vendor-live uv run dpone ops cdc-runtime-run --mode live check for
mssql -> clickhouse; see CDC live runtime adapters.
The live check must verify SQL Server read, ClickHouse apply, and no offset
commit on non-durable sink receipts.
- When ClickHouse CDC serving materialization changes, run
uv run dpone ops cdc-materialize-clickhouse against a fixture or opt-in
Docker route and upload test_artifacts/cdc_materialization/ with
if: always(); see ClickHouse CDC materialization.
The check must verify active-row and tombstone delete modes.
- When ClickHouse CDC typed serving materialization changes, run
uv run dpone ops cdc-materialize-clickhouse-typed against a fixture or
opt-in Docker route and upload test_artifacts/cdc_typed_materialization/
with if: always(); see
ClickHouse CDC typed materialization.
The check must verify declared column types, active-row mode, tombstone mode,
--fail-on-parse-errors, --schema-drift-mode strict, and
cdc_typed_parse_quarantine.json for malformed payloads.
- Upload test_artifacts/observability/ with if: always() when a workflow exports runtime metrics.
- Add required secrets only to vendor-live jobs.
- Update Developer certification suite when adding evidence types.
Adding observability artifacts¶
Use Runtime observability when a workflow needs Prometheus/OpenTelemetry evidence for a run.
Recommended flow:
uv run dpone run "$MANIFEST" --format json > test_artifacts/runs/run_report.json
uv run dpone observability metrics-export \
--run-report test_artifacts/runs/run_report.json \
--output-dir test_artifacts/observability/current \
--label ci_run_id="$GITHUB_RUN_ID" \
--label branch="$GITHUB_REF_NAME" \
--format json
Rules:
- Keep labels secret-free and low-cardinality.
- Keep collector upload optional; artifact generation must work locally.
- Link any new failure mode to Failure runbooks.
- Update Developer observability guide when adding renderers or metric sources.
Adding supply-chain evidence¶
Use Supply-chain evidence when a release workflow needs SBOM, provenance, signature envelope, and checksum evidence.
Recommended flow:
uv build
uv run twine check dist/*
subjects=()
for artifact in dist/*.whl dist/*.tar.gz; do
subjects+=(--subject "$artifact")
done
uv run dpone supply-chain attest \
--release "$GITHUB_REF_NAME" \
"${subjects[@]}" \
--repository "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" \
--commit-sha "$GITHUB_SHA" \
--builder-id "github-actions:$GITHUB_RUN_ID" \
--signing-key "$DPONE_LOCAL_ATTESTATION_KEY" \
--signing-key-id github-actions \
--output-dir test_artifacts/supply-chain/current \
--format json
mkdir -p test_artifacts/supply-chain/github-attestations
for artifact in dist/*.whl dist/*.tar.gz; do
gh attestation verify "$artifact" \
--repo "$GITHUB_REPOSITORY" \
--source-ref "$GITHUB_REF" \
--source-digest "$GITHUB_SHA" \
--format json \
> "test_artifacts/supply-chain/github-attestations/$(basename "$artifact").github-attestation.json"
done
Rules:
- Upload
test_artifacts/supply-chain/withif: always(). - Keep
DPONE_LOCAL_ATTESTATION_KEYin the release environment secrets and fail the release when it is missing. - Do not store local HMAC signing keys in artifacts or logs.
- Use and verify GitHub Artifact Attestations or Sigstore/cosign for public identity-backed trust before PyPI publication.
- Run
uv run python tools/agent_policy/governance_gate.py --base-ref origin/master --output test_artifacts/agent-policy/agent_governance_gate.jsonwhen CI/CD, agent policy, release evidence, or supply-chain controls change. - Pull-request CI additionally passes the trusted event head through
--head-commit. This keeps the signed receipt bound to reviewed headHwhile GitHub attestation truthfully retains the synthetic merge commitMfromrefs/pull/<current PR number>/mergeas provenance. - Update Developer supply-chain guide when adding formats or signers.
Docs contract tests¶
When CI/CD documentation changes, add or update docs contract tests. A good test verifies:
- public workflow files are named in docs;
- default branch remains
master; - runbooks mention every required workflow;
- developer docs link to CI/CD implementation guidance.
PR description template for CI/CD changes¶
## CI/CD change
- Workflow(s):
- Trigger(s):
- Permissions:
- Secrets required:
- Artifacts produced:
- Local reproduction:
- Failure runbook updated:
- Docs updated: