Skip to content

feat(gate-67): ship the OpenRegister contract and enforce one definition (ADR-084) - #463

Merged
rubenvdlinde merged 2 commits into
mainfrom
feat/openregister-contract-parity
Aug 15, 2026
Merged

feat(gate-67): ship the OpenRegister contract and enforce one definition (ADR-084)#463
rubenvdlinde merged 2 commits into
mainfrom
feat/openregister-contract-parity

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

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

where who needs it why
openregister/lib/Contract/ openregister, at runtime implements resolves at autoload. conduction/hydra-gates is 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/ leaf apps, under PHPUnit A leaf app cannot load a class from another Nextcloud app — the whole reason the contract exists. hydra-gates is already require-dev everywhere, so this lands the interface in each consumer's 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:

plant drift
ObjectServiceInterface.php content drift — both sides exist, contents differ
OrphanInterface.php shipped only — consumers get an interface OpenRegister does not implement
UnshippedInterface.php canonical only — never reaches a consumer's vendor/, the gap ADR-084 exists to close

The equality check is symmetric, so both one-sided plants are needed: a
checker walking only lib/Contract/ passes OrphanInterface, one walking only
the shipped copy passes UnshippedInterface. Either omission leaves a real
drift invisible.

clean/ asserts byte-identity across the two trees — which is where a checker
comparing filenames, or method counts, or anything short of the bytes gets
caught, since it would pass planted/'s content drift too.

== openregister-contract-parity: planted/ ==
PASS gate-67 FAIL — 3 contract file(s) out of step
PASS gate-67 NAMES 'Contract/ObjectServiceInterface.php'
PASS gate-67 NAMES 'OrphanInterface.php'
PASS gate-67 NAMES 'Contract/UnshippedInterface.php'
== openregister-contract-parity: clean/ ==
PASS gate-67 PASS  (x3)

passed: 161   failed: 0   (63 of 68 gates fixtured)

Skip semantics are explicit: a repo with no lib/Contract/ doesn't own the
contract (exit 3), and lib/Contract/ with no shipped copy to compare
against is exit 4 — reported as "nothing was verified", not as a pass.

⚠️ That matrix output is from a LOCAL run, not from CI. No workflow in this
repo invokes test_gate_acceptance_matrix.sh, so the whole gate-acceptance
fixture suite — all 63 fixtured gates, not just this one — is currently
unexercised by CI. Worth fixing on its own; flagged here so the numbers above
are not mistaken for a green check.

Related

  • ADR-084 — hydra#576
  • The interfaces themselves — openregister#2498

🤖 Generated with Claude Code

Conduction Release Bot 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant