This document is for a consuming repo wiring the version-drift-enforcement
gate into its CI. Every concrete value (workflow path, tag, secret name,
variable name, input name) is read from the live workflow files in this repo.
This gate cannot run without org-level infrastructure. Check every item here before wiring. If any prerequisite is missing the gate fails with a generic auth error — not a clear "prerequisite missing" message.
| App | Org variable that holds its client-id | Permission required | What it does |
|---|---|---|---|
composer-resolver |
vars.COMPOSER_RESOLVER_CLIENT_ID |
Contents: Read | Checks out sparxstar-code-conformance to read config/version-policy.yml and git tags |
contract-sync |
vars.CONTRACT_SYNC_CLIENT_ID |
Contents: Read/Write | Commits the drift record to drift/<slug>.json in sparxstar-code-conformance |
Both Apps must be installed in the Starisian-Technologies org and their
installation must include sparxstar-code-conformance in their repository
scope. An App existing in the org is not the same as it being scoped to
the repo. Verify under org Settings → GitHub Apps → (App name) →
Repository access. A missing scope produces a "repository not found" error
with no indication the App caused it.
composer-resolver is required on every run, even when write_drift_record: false. contract-sync is only exercised when write_drift_record: true
(the default), but its secret must still be declared in the caller's
secrets: block.
Must exist at org or repo level under Settings → Secrets and variables → Actions → Secrets before a consumer passes them. These are secrets — do not store them as Variables.
| Secret name | What it holds |
|---|---|
COMPOSER_RESOLVER_PRIVATE_KEY |
PEM private key for the composer-resolver App |
CONTRACT_SYNC_PRIVATE_KEY |
PEM private key for the contract-sync App |
Must exist as Variables (not Secrets) under Settings → Secrets and variables → Actions → Variables. A client-id stored in the wrong slot (or stored as a Secret instead of a Variable) causes the token-mint step to fail with an opaque 401.
| Variable name | What it holds |
|---|---|
COMPOSER_RESOLVER_CLIENT_ID |
Client-id string for the composer-resolver App |
CONTRACT_SYNC_CLIENT_ID |
Client-id string for the contract-sync App |
App installation and org secret/variable creation require org-admin rights. A consuming repo's developer typically cannot do this. Request provisioning from your org administrator and confirm all four items above exist before wiring the gate.
The version-drift-enforcement reusable workflow checks whether a consuming
repo's pinned contract_ref meets the minimum version floor defined in
config/version-policy.yml and reports how far it drifts from the current
recommended version. It applies a three-tier result: an exact match with the
recommended version passes silently; a ref that is behind the recommended but
at or above the floor passes with a ::warning:: annotation visible in the
Actions UI; a ref below the floor hard-fails the job unconditionally
regardless of any enforcement_mode setting. On every passing or warning run
the gate also commits a JSON drift record to drift/ in this repo via the
contract-sync App, giving the platform team a single place to see every
consumer's version state without querying each repo individually.
uses: Starisian-Technologies/sparxstar-code-conformance/.github/workflows/version-drift-enforcement.yml@v1.0.0| Tag | Commit | Type | Meaning |
|---|---|---|---|
v1.0.0 |
64b0ccf |
Immutable semver release | Exact point-in-time snapshot. Never moves. |
v1 |
64b0ccf |
Moving major alias | Advances to each new v1.x.x release. |
Pin @v1.0.0. This is the org-wide locked recommendation.
The v1.0.0 tag is immutable — it always resolves to the same commit. When
a new release is cut (e.g. v1.1.0), v1.0.0 does not move. This means your
pinned gate is stable until you deliberately upgrade the pin, which makes
audit, rollback, and incident diagnosis straightforward.
@v1 is the moving major alias — it will advance to future v1.x.x releases
automatically. It is available and will continue to work, but it is not the
documented recommendation for consumers. Do not use @main.
Read from on.workflow_call.inputs in
.github/workflows/version-drift-enforcement.yml:
| Input | Required | Type | Default | Purpose and valid values |
|---|---|---|---|---|
contract_ref |
yes | string |
— | The ref the calling repo pins its enforcement workflow calls to. Must be an existing tag matching vMAJOR, vMAJOR.MINOR, or vMAJOR.MINOR.PATCH (e.g. v1.0.0). Hard-fails if the tag does not exist on sparxstar-code-conformance or falls below the floor in config/version-policy.yml. |
calling_repo |
no | string |
'' |
Full owner/repo of the caller. Defaults to github.repository when empty. Override only when the caller needs to report a different identity — this is rare. |
write_drift_record |
no | boolean |
true |
Whether to commit a JSON drift record to drift/ in this repo via contract-sync. Set false for read-only runs. Omit on normal wiring. |
Read from on.workflow_call.secrets in
.github/workflows/version-drift-enforcement.yml.
secrets:
COMPOSER_RESOLVER_PRIVATE_KEY: ${{ secrets.COMPOSER_RESOLVER_PRIVATE_KEY }}
CONTRACT_SYNC_PRIVATE_KEY: ${{ secrets.CONTRACT_SYNC_PRIVATE_KEY }}| Secret name | Required | Purpose |
|---|---|---|
COMPOSER_RESOLVER_PRIVATE_KEY |
always | Private key for the composer-resolver App. Mints a read token to check out sparxstar-code-conformance. |
CONTRACT_SYNC_PRIVATE_KEY |
required when write_drift_record: true (the default) |
Private key for the contract-sync App. Mints a write token to commit the drift record. Must be declared even when write_drift_record: false — omitting it causes a workflow validation error. |
secrets: inherit is prohibited. Pass each secret by name as shown. Only
these two cross the boundary.
Org variables are not passed by the consumer. COMPOSER_RESOLVER_CLIENT_ID
and CONTRACT_SYNC_CLIENT_ID are org Variables (referenced as vars.* inside
the workflow). They propagate automatically and must not appear in the caller's
secrets: block.
Trigger: use pull_request (not pull_request_target). The workflow reads
config from the checked-out repo. pull_request_target runs with base-branch
context and elevated write access that is both unnecessary and unsafe for this
gate.
Minimum permissions for the calling job: none beyond defaults. The workflow
mints its own scoped tokens internally. No permissions: block is needed in
the caller job.
Required files in your repo: none. The floor config (config/version-policy.yml)
lives in sparxstar-code-conformance and is checked out by the gate. No
config files need to be added to the consuming repo.
When wiring any gate for the first time, set enforcement_mode: advisory.
In advisory mode, violations are reported as warnings in the Actions UI but
never block merge. This prevents a consuming repo from being held hostage
by a gate it does not yet pass.
Switch a gate from advisory to required (blocking) only when all three of the following are true:
- Zero violations on the most recent advisory run. Do not flip to blocking with known unresolved violations — that red-walls every PR immediately.
- Conformance is confirmed. For code-conformance gates: enforcement jobs are green. For spec or ADR gates: conformance tests exist and pass.
- The switch is a deliberate, recorded decision. The repo is declaring
"we now conform and intend to stay conforming." Switching to
requiredis a commitment, not a default to leave set.
Once set to required, a violation blocks merge. That is the point — it is
the commitment that the repo stays clean. Do not switch to required as a
goal in itself; switch when the repo is provably clean and you intend to keep
it that way.
The platform expectation: start advisory, earn gate. A gate flipped to blocking on a repo that is not yet clean is a misconfiguration, not enforcement.
The version-drift-enforcement workflow does not accept an enforcement_mode
input. The three-tier check is always enforced as written: warn for refs above
floor but below recommended, hard-fail for refs below floor. There is no
advisory-only mode for the floor check. Wire this gate when the prerequisites
in Section 0 are satisfied — the gate itself does not need an onboarding period
because it only checks the version tag you pin, which you control.
jobs:
version-check:
uses: Starisian-Technologies/sparxstar-code-conformance/.github/workflows/version-drift-enforcement.yml@v1.0.0
with:
contract_ref: v1.0.0 # ← the ref you pin your other enforcement jobs to
secrets:
COMPOSER_RESOLVER_PRIVATE_KEY: ${{ secrets.COMPOSER_RESOLVER_PRIVATE_KEY }}
CONTRACT_SYNC_PRIVATE_KEY: ${{ secrets.CONTRACT_SYNC_PRIVATE_KEY }}Values to substitute: contract_ref — set this to the same ref you
pass to @ when calling other enforcement workflows in this same caller
file. If your other jobs are pinned @v1.0.0, pass v1.0.0 here.
Everything else is literal. Do not add secrets: inherit.
A secret must be declared in this reusable workflow's on.workflow_call.secrets
block before a consumer can pass it. If you pass a secret the workflow has not
yet declared, GitHub silently drops it rather than forwarding it. Conversely, if
this workflow adds a new secret declaration after a consumer has pinned an older
tag, the consumer's pinned tag still contains the old declaration and will not
forward the new secret.
Always re-tag (move v1 to the new commit and cut a new immutable semver tag)
after any change to secrets, inputs, or job logic. Consumers pinned to an
immutable tag must update their pin to receive the new declaration. Notify
consuming repos when a secrets block changes — they cannot pick it up
automatically on an immutable pin.