Skip to content

feat(release): reusable check-vendored-contracts workflow + path overrides#717

Merged
kojiromike merged 7 commits into
openemr:masterfrom
kojiromike:release-drift-check-workflow
May 13, 2026
Merged

feat(release): reusable check-vendored-contracts workflow + path overrides#717
kojiromike merged 7 commits into
openemr:masterfrom
kojiromike:release-drift-check-workflow

Conversation

@kojiromike

@kojiromike kojiromike commented May 13, 2026

Copy link
Copy Markdown
Member

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, and TagVerificationResult.php against 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.json copies — 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

  1. tools/release/src/VendoredFileChecker.php — optional canonical→consumer path-overrides map. Required because website-openemr vendored TagVerifier and TagVerificationResult into src/Release/ instead of src/, 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.

  2. 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.
  3. .github/workflows/check-vendored-contracts.ymlworkflow_call reusable workflow consumers invoke with one job:

    jobs:
      drift:
        uses: openemr/openemr-devops/.github/workflows/check-vendored-contracts.yml@master
        with:
          consumer_subpath: tools/release

    website-openemr passes path_overrides to map its src/Release/... layout.

  4. .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)

  • openemr/openemr — add a workflow that calls this one with 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).
  • openemr/website-openemr — add a workflow with consumer_subpath: tools/release-docs and the path overrides. Same drift-reconciliation prerequisite, plus the TagVerificationResultVerificationResult rename 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 validation
  • vendor/bin/phpcs — clean
  • vendor/bin/phpstan analyse — clean
  • actionlint on both new workflows — clean
  • CLI smoke-tested against both consumer checkouts — overrides resolve correctly, drift findings name the consumer's actual paths
  • Self-test workflow green on PR CI — both good and good-with-overrides jobs pass, validating the reusable workflow end-to-end

@kojiromike kojiromike added the github_actions Pull requests that update GitHub Actions code label May 13, 2026
@kojiromike kojiromike force-pushed the release-drift-check-workflow branch from 679a228 to 1da49e3 Compare May 13, 2026 12:47
@kojiromike kojiromike marked this pull request as ready for review May 13, 2026 13:00
Copilot AI review requested due to automatic review settings May 13, 2026 13:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 VendoredFileChecker and check-vendored.php to support canonical→consumer path overrides (repeatable --override CANON=CONSUMER).
  • Add a reusable workflow_call workflow 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.

Comment thread tools/release/src/VendoredFileChecker.php
Comment thread .github/workflows/check-vendored-contracts.yml Outdated
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
@kojiromike kojiromike force-pushed the release-drift-check-workflow branch from 0dd9717 to d63c585 Compare May 13, 2026 13:59
…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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/vendored-contracts-self-test.yml
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.

Comment thread tools/release/src/VendoredFileChecker.php
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.

@kojiromike kojiromike merged commit e1a5b49 into openemr:master May 13, 2026
13 checks passed
@kojiromike kojiromike deleted the release-drift-check-workflow branch May 13, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

github_actions Pull requests that update GitHub Actions code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants