feat(release): reusable check-vendored-contracts workflow + path overrides#717
Merged
kojiromike merged 7 commits intoMay 13, 2026
Merged
Conversation
679a228 to
1da49e3
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Adds CI tooling to detect drift of cross-repo “vendored contracts” (dispatch schema + tag verification PHP classes) by introducing a reusable GitHub Actions workflow that consumer repos can call, plus enhancements to the vendored-file checker to support non-canonical consumer layouts via path overrides.
Changes:
- Extend
VendoredFileCheckerandcheck-vendored.phpto support canonical→consumer path overrides (repeatable--override CANON=CONSUMER). - Add a reusable
workflow_callworkflow to run the vendored-contract drift check in consumer repos, with optional newline-delimited path overrides. - Add self-test workflow + fixtures and PHPUnit coverage for override mapping and override-aware drift reporting.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tools/release/src/VendoredFileChecker.php |
Adds override mapping support and validates override keys. |
tools/release/bin/check-vendored.php |
Adds repeatable --override CLI option and parsing/validation. |
.github/workflows/check-vendored-contracts.yml |
New reusable workflow to run drift checks in consumer repos with optional overrides. |
.github/workflows/_test-vendored-contracts.yml |
New self-test workflow exercising the reusable workflow against fixtures. |
tools/release/tests/VendoredFileCheckerTest.php |
Adds PHPUnit tests covering override mapping, drift reporting, and unknown keys. |
tools/release/tests/fixtures/vendored/good/src/TagVerifier.php |
Fixture content for canonical-layout consumer. |
tools/release/tests/fixtures/vendored/good/src/TagVerificationResult.php |
Fixture content for canonical-layout consumer. |
tools/release/tests/fixtures/vendored/good/contracts/dispatch.schema.json |
Fixture content for canonical-layout consumer. |
tools/release/tests/fixtures/vendored/good-overrides/src/Release/TagVerifier.php |
Fixture content for override-layout consumer (website-style). |
tools/release/tests/fixtures/vendored/good-overrides/src/Release/TagVerificationResult.php |
Fixture content for override-layout consumer (website-style). |
tools/release/tests/fixtures/vendored/good-overrides/contracts/dispatch.schema.json |
Fixture content for override-layout consumer (website-style). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
kojiromike
added a commit
to kojiromike/openemr-devops
that referenced
this pull request
May 13, 2026
Review feedback on openemr#717: - Move newline-delimited override parsing from a bash step into the PHP CLI (--overrides flag). Reusable workflow becomes a single quoted-arg invocation; no GITHUB_OUTPUT round-trip, no SC2086 word-splitting, no shell-injection surface for caller-controlled path_overrides. - Rename self-test workflow file (drop _ prefix; no other workflow in the repo uses one). - Rename CLI locals canon/cons → canonicalPath/consumerPath; less shadowing of the surrounding $canonical option variable. No behavior change for existing --override callers. Refs openemr#664, openemr#717. Assisted-by: Claude Code
kojiromike
added a commit
to kojiromike/openemr-devops
that referenced
this pull request
May 13, 2026
Review feedback on openemr#717: - Move newline-delimited override parsing from a bash step into the PHP CLI (--overrides flag). Reusable workflow becomes a single quoted-arg invocation; no GITHUB_OUTPUT round-trip, no SC2086 word-splitting, no shell-injection surface for caller-controlled path_overrides. - Rename self-test workflow file (drop _ prefix; no other workflow in the repo uses one). - Rename CLI locals canon/cons → canonicalPath/consumerPath; less shadowing of the surrounding $canonical option variable. No behavior change for existing --override callers. Refs openemr#664, openemr#717. Assisted-by: Claude Code
0dd9717 to
d63c585
Compare
…hecker VendoredFileChecker assumed every consumer mirrored the canonical relative paths exactly. website-openemr vendored TagVerifier and TagVerificationResult into src/Release/ instead of src/, so the checker could never validate it even though openemr#668 already handles namespace and JSON-formatting drift. Add an optional canonical→consumer path-overrides map, with a CLI flag --override CANON=CONSUMER (repeatable). Drift findings report the consumer-relative path so the failure message points at the file the consumer actually has on disk. Refs openemr#664. Assisted-by: Claude Code
Lets consumer repos (openemr/openemr, openemr/website-openemr, any future
one) drift-check their vendored copies of dispatch.schema.json,
TagVerifier.php, and TagVerificationResult.php against canonical with a
single workflow_call line. Encapsulates the canonical openemr-devops
checkout, PHP setup, and the bin/check-vendored.php invocation so each
consumer adopts with one ~6-line workflow file and gets future drift-check
improvements automatically.
Inputs:
consumer_subpath required — path within the caller repo holding the
vendored copies (e.g. tools/release).
canonical_ref optional — defaults to master; pin if a consumer needs
a known-good ref while reconciling drift.
path_overrides optional — newline-delimited canonical=consumer pairs
for consumers that vendored into a non-canonical layout
(website-openemr).
Same shape as the reusable-workflow shim the May 8 follow-up on openemr#664
proposes for the conductor itself; building it here at lower stakes
validates the pattern.
Refs openemr#664.
Assisted-by: Claude Code
PR-only self-test that exercises the reusable workflow against committed
fixtures. Two jobs:
good — canonical layout, no overrides; validates
checkout wiring + composer install + CLI.
good-with-overrides — same content under a website-openemr-style
src/Release/ layout; exercises the
path_overrides input end-to-end.
Drift-detection failure propagation isn't tested separately — exit-code
→ job-status is GHA's intrinsic behavior, so a drifted fixture would
just turn the test red without proving anything new about the workflow.
canonical_ref pins to the PR head SHA so the test compares against this
PR's canonical files, not whatever's on master at run time.
Refs openemr#664.
Assisted-by: Claude Code
Review feedback on openemr#717: - Move newline-delimited override parsing from a bash step into the PHP CLI (--overrides flag). Reusable workflow becomes a single quoted-arg invocation; no GITHUB_OUTPUT round-trip, no SC2086 word-splitting, no shell-injection surface for caller-controlled path_overrides. - Rename self-test workflow file (drop _ prefix; no other workflow in the repo uses one). - Rename CLI locals canon/cons → canonicalPath/consumerPath; less shadowing of the surrounding $canonical option variable. No behavior change for existing --override callers. Refs openemr#664, openemr#717. Assisted-by: Claude Code
Reject overrides that are absolute or contain `..` segments — overrides must resolve inside the consumer dir. Defense-in-depth against a typo or malicious override that escapes the intended consumer layout. Assisted-by: Claude Code
089e872 to
60301a1
Compare
Add tools/release/composer.json and composer.lock to the self-test path filter. The reusable workflow runs `composer install --no-dev` from the canonical checkout, so dependency-resolution changes can break it end-to-end without touching any of the previously-filtered files. Assisted-by: Claude Code
Loosen the constructor's $pathOverrides param to array<array-key, mixed> and validate string=>string at runtime. Internal CLI callers always satisfy the contract, but the constructor is the trust boundary for arbitrary CI/config-derived input — surface a controlled InvalidArgumentException instead of fataling on string indexing of a non-string. Store into a strict array<string, string> property so downstream usage stays well-typed. Assisted-by: Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the missing piece for catching cross-repo contract drift in CI: a reusable workflow consumer repos call to drift-check their vendored copies of
dispatch.schema.json,TagVerifier.php, andTagVerificationResult.phpagainst canonical, plus path-override flags that let website-openemr's non-canonical layout participate.Refs #664. Closes the May 8 status snapshot's open follow-up: "Drift-check tooling for the vendored
dispatch.schema.jsoncopies — today proved the manual propagation is a real failure mode."Why now
The check-vendored tool itself has existed since #665 and was tightened in #668 (semantic equivalence: JSON canonicalization, namespace stripping). But no consumer's CI runs it. I confirmed this by running the tool locally against checkouts of openemr/openemr and openemr/website-openemr — both are silently drifted right now. The failure mode #700 hit (manual propagation skipped) is alive and well; without a CI gate in each consumer it'll keep biting us.
What this PR adds
tools/release/src/VendoredFileChecker.php— optional canonical→consumer path-overrides map. Required because website-openemr vendoredTagVerifierandTagVerificationResultintosrc/Release/instead ofsrc/, so the checker could never validate it before. Drift findings now report the consumer-relative path so the failure points at the file the consumer actually has on disk.tools/release/bin/check-vendored.php— two complementary flags:--override CANON=CONSUMER(repeatable) for direct CLI use.--overrides(single value, newline-delimited) for CI inputs that arrive as multi-line strings; the PHP CLI does the parsing so the calling workflow doesn't have to construct a flag list in shell..github/workflows/check-vendored-contracts.yml—workflow_callreusable workflow consumers invoke with one job:website-openemr passes
path_overridesto map itssrc/Release/...layout..github/workflows/vendored-contracts-self-test.yml+ fixtures — exercises the reusable workflow against a canonical-layout fixture and a website-style override fixture inside this PR's CI, so the workflow has end-to-end coverage before any consumer adopts it.Same shape as the reusable-workflow shim the May 8 follow-up on #664 proposes for the conductor itself; building it here at lower stakes validates the pattern before we apply it to the conductor.
Adoption (separate PRs, after this lands)
consumer_subpath: tools/release. Will fail until the existing PHP-file drift is reconciled (this PR doesn't touch the canonical content, only the checker).consumer_subpath: tools/release-docsand the path overrides. Same drift-reconciliation prerequisite, plus theTagVerificationResult↔VerificationResultrename to sort out.Both adoptions are blocked on a separate drift-reconciliation pass; this PR only ships the tooling so the gate can be added later without further changes here.
Test plan
vendor/bin/phpunit— 14 tests, +4 covering override mapping, override-aware drift reporting, and unknown-key validationvendor/bin/phpcs— cleanvendor/bin/phpstan analyse— cleanactionlinton both new workflows — cleangoodandgood-with-overridesjobs pass, validating the reusable workflow end-to-end