Skip to content

feat: verify this installer against the contracts of the repos it clones - #596

Closed
goldyfruit wants to merge 6 commits into
fix/hivemind-docker-track-devfrom
feat/cross-repo-contract
Closed

goldyfruit wants to merge 6 commits into
fix/hivemind-docker-track-devfrom
feat/cross-repo-contract

Conversation

@goldyfruit

Copy link
Copy Markdown
Collaborator

Consumer half of a contract across the three repositories. Producer halves: ovos-docker#177, hivemind-docker#48.

Stacked on #595 — it is based on that branch, since the HIVEMIND_SITEID fix lives there. Merge #595 first, then this retargets to main cleanly.

Why

The containers method builds nothing. It clones ovos-docker and hivemind-docker at a pinned ref and runs the compose files out of them, which makes four things an interface — each of which has already broken an install:

coupling how it failed
pinned ref sat on hivemind-docker v2.0.0 for weeks after the fix shipped
compose file names named exactly here; a rename upstream fails the install
container names docker_container_exec targets them; a rename fails late, after the stack is up
variables the compose reads silent — a new variable with an inline default is used instead of the value collected here

The last row is why this is not just "are all required variables set?". HIVEMIND_SITEID has a fallback of the literal string default, so an installer that never set it produced a working satellite reporting the wrong site, with nothing failing anywhere. The contracts mark such variables owner: installer, and this refuses to read "has a default" as "nobody needs to set it".

How it is checked

Offline against the snapshots in tests/contracts/, through code_quality.bats — so every pull request runs it with no network.

--online re-fetches each contract at the ref pinned here and reports a snapshot that has drifted. --fail-on-stale additionally fails when a pin is behind a release. A scheduled weekly job runs both: a scheduled run that exits 0 tells nobody anything, so the stale pin has to be a failure to be an alert.

It found the bug on its own

Written against main first, where it immediately reported:

contract violations:
  hivemind-docker: HIVEMIND_SITEID is owned by the installer but is not set in env.j2 -
  the compose has a default, so this fails silently rather than loudly

That is the bug this stack fixes, rediscovered independently by the check rather than encoded into it after the fact.

Negative tests

1. installer stops setting an installer-owned variable
     hivemind-docker: HIVEMIND_SITEID is owned by the installer but is not set in env.j2
2. installer stops setting a required variable
     ovos-docker: HIVEMIND_USER is required by docker-compose.hivemind.yml, ... and is not set
     hivemind-docker: HIVEMIND_USER is required by docker-compose.satellite.yml and is not set
3. installer expects a compose file the repo does not ship
     hivemind-docker: compose file docker-compose.sat.yml is used here but not declared
     hivemind-docker: container hivemind_cli is referenced here but is not in any compose file
                      this installer runs
4. pin falls behind a release  (--fail-on-stale)
     hivemind-docker: pinned v2.0.0, latest release is v2.1.0

Case 3 cascading into case 3b is the behaviour I wanted: losing the compose file also loses the container the installer execs into, which is the Could not find container "ovos_cli" failure mode.

One ordering note

The snapshots are derived from the compose files at the pinned tags, because contract.yml itself only lands in the next release of each repository. Until then --online falls back to the snapshot and says so:

note: ovos-docker: v2.0.2 publishes no contract.yml yet; using the snapshot

Once both producer PRs are released, the fetch takes over and the snapshots become a cache that --online verifies rather than the source of truth.

167/167 bats pass; ansible-lint clean on the production profile.

🤖 Generated with Claude Code

The containers method builds nothing: it clones ovos-docker and
hivemind-docker at a pinned ref and runs the compose files out of them.
Four things are therefore an interface between those repositories and
this one, and each has broken an install: the compose file names, the
container names docker_container_exec targets, the variables the compose
reads, and the images pulled.

The third stays quiet. A compose file that starts reading a variable
with an inline default keeps working and uses the fallback, so a value
collected here is silently replaced - HIVEMIND_SITEID did exactly that
and every satellite reported the wrong site with nothing failing. The
contracts mark such a variable `owner: installer`, and this refuses to
read "has a default" as "nobody needs to set it".

Offline by default against the snapshots in tests/contracts/, so it runs
in every pull request through code_quality.bats. --online re-fetches each
contract at the ref pinned here and notices a snapshot that has drifted;
--fail-on-stale additionally fails when a pin is behind a release, which
is how an install sat on hivemind-docker v2.0.0 for weeks after the fix
it needed had shipped. A scheduled job runs both, because a scheduled
run that exits 0 tells nobody anything.

Written against main first, where it immediately reported HIVEMIND_SITEID
as unset - the bug this stack fixes - which is the case it exists for.

Negative-tested by dropping an installer-owned variable, dropping a
required variable, and renaming an expected compose file; each is
reported by name, and the missing compose file correctly cascades into
the container that is no longer reachable.

The snapshots are derived from the compose files at the pinned tags,
since contract.yml itself only lands in the next release of each
repository; --online falls back to them and says so until then.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: a4cb9c86-2d48-4265-bcda-a00c0de48885

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

goldyfruit and others added 5 commits September 9, 2026 12:20
ansible-lint runs over the whole tree, yamllint included, and rejected
the sequence indentation PyYAML emits by default. The generator in both
upstream repositories now indents sequences and writes a document start;
these snapshots are regenerated with it.

Caught only in CI because ansible-lint 26.8.0 enforces this and the
26.1.1 I had did not - and because I had run it scoped to ansible/
rather than at the root the way CI does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comparing the pin against the newest release cannot see the failure that
matters. Both can read v2.0.2 while ten days of fixes sit unreleased on
the default branch - which is how the fann2 gating, the intent-engine
assertion and the terminal client all failed to reach a single install
while every check here was green.

--online now also reports how far the newest release trails the branch
it is cut from, and --fail-on-stale makes it a failure:

  ovos-docker: v2.0.2 is 24 commit(s) behind dev - those fixes are in no
  release, so no install has them

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v2.1.0 and v2.1.1 ship contract.yml, so the snapshots stop being derived
from the compose files at those tags and become what --online verifies
them against. With the pins current, strict mode is quiet: no drift, no
pin behind a release, and no release behind the branch it is cut from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@goldyfruit

Copy link
Copy Markdown
Collaborator Author

Closed automatically when its base branch (#595) was merged and deleted; GitHub will not reopen a pull request whose base is gone. Continued as #597 from the same branch, based on main.

🤖 Generated with Claude Code

@goldyfruit goldyfruit self-assigned this Sep 11, 2026
@goldyfruit goldyfruit added the enhancement New feature or request label Sep 11, 2026
@goldyfruit goldyfruit added this to the Duck Hunt milestone Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant