feat(gate-67): ship the OpenRegister contract and enforce one definition (ADR-084) - #463
Merged
Merged
Conversation
added 2 commits
August 15, 2026 07:41
ADR-084 has OpenRegister publish the surface it is consumed through, so that
sixteen apps stop hand-rolling doubles of a class they do not own. Ten of them
already had one, declaring between 0 and 13 methods against a real class of 88,
with a union of 23. Four declared zero: shells that satisfy a type-hint and
check nothing.
Delivering that needs the interface in two places, for reasons that do not
overlap:
lib/Contract/ openregister, at RUNTIME. `implements` is resolved at
autoload, and hydra-gates is require-dev in all ten
apps measured -- absent from a production install. If
the interface lived only here, every OpenRegister
route would fatal, which is exactly the failure
ADR-083 rule 2 exists to prevent.
hydra-gates/contracts/ leaf apps, under PHPUnit. hydra-gates is ALREADY
require-dev everywhere, so this puts the interface in
each consumer's vendor/ where a double can implement
it -- with no composer change in any app.
Two copies is a drift risk, and "we will keep them in step" is not a mechanism.
Gate 67 is the mechanism: byte-identical, or red.
The gate is not diff-scoped. Parity is a property of the tree, and a PR that
edits one copy and not the other is precisely the case to catch -- diff-scoping
would make the untouched side invisible.
Acceptance: three plants, three genuinely different drifts, each naming its own
file so no plant can be satisfied by another -- content drift, shipped-only
(consumers get an interface nothing implements) and canonical-only (never
reaches a consumer's vendor/). The equality check is symmetric, so both
one-sided plants are needed: a checker walking only one side passes the other.
clean/ asserts byte-identity across the two trees, which is where a checker
comparing filenames or method counts instead of bytes gets caught.
Matrix: 161 passed, 0 failed; 63 of 68 gates fixtured.
The canonical files gained psalm and phpmd fixes (array<array-key, mixed> on getObject, and the three PHPMD suppressions ObjectService itself already carries). The shipped copy has to match byte-for-byte or gate-67 fails — which is the gate doing its job on its own first change.
This was referenced Aug 15, 2026
Merged
Merged
Merged
Merged
Merged
Merged
Merged
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.
Why
ADR-084 has OpenRegister publish
the surface it is consumed through, so sixteen apps stop hand-rolling doubles of a
class they do not own. Ten already had one, declaring 0–13 methods against a
real class of 88, union 23. Four declared zero — shells that satisfy a
type-hint and check nothing.
The interface has to live in two places, and they don't overlap
openregister/lib/Contract/implementsresolves at autoload.conduction/hydra-gatesis require-dev in all ten apps measured, so it is absent from a production install (--no-dev). Interface only there ⇒ every OpenRegister route fatals — the exact failure ADR-083 rule 2 exists to prevent.hydra-gates/contracts/vendor/with no composer change in any app.Two copies is a drift risk, so this PR ships the mechanism
"We'll keep them in step" is not a mechanism. Gate 67 is: byte-identical, or red.
Not diff-scoped — parity is a property of the tree, and a PR that edits one copy
and not the other is precisely the case to catch. Diff-scoping would make the
untouched side invisible.
Acceptance
Three plants, three genuinely different drifts, each naming its own file so no
plant can be satisfied by another:
ObjectServiceInterface.phpOrphanInterface.phpUnshippedInterface.phpvendor/, the gap ADR-084 exists to closeThe equality check is symmetric, so both one-sided plants are needed: a
checker walking only
lib/Contract/passesOrphanInterface, one walking onlythe shipped copy passes
UnshippedInterface. Either omission leaves a realdrift invisible.
clean/asserts byte-identity across the two trees — which is where a checkercomparing filenames, or method counts, or anything short of the bytes gets
caught, since it would pass planted/'s content drift too.
Skip semantics are explicit: a repo with no
lib/Contract/doesn't own thecontract (exit 3), and
lib/Contract/with no shipped copy to compareagainst is exit 4 — reported as "nothing was verified", not as a pass.
Related
🤖 Generated with Claude Code