Skip to content

guard(deploy): the SLM publish contract exists twice, in YAML and shell, with nothing asserting the two agree #15724

Description

@mrveiss

Raised while reviewing #15721, which correctly collapsed three shell copies of the SLM-frontend build-and-publish into one. That was the right move, and it leaves a residual worth naming.

Two canonical implementations, one contract

The publish contract — build with build:slm, abort hard on failure, build into a fresh directory, prove a non-empty index.html, flip through .current.next with mv -T — now exists twice:

implementation consumers
autobot-slm-backend/ansible/roles/_shared/tasks/build_publish_slm_frontend.yml the Ansible entry points (#15557)
autobot-infrastructure/autobot-slm-frontend/templates/build-publish-slm-frontend.sh bootstrap-slm.sh, sync-frontend.sh (#15721)

Two languages, so this is not fixable by deleting one — a shell script on a freshly bootstrapped node cannot invoke an Ansible task. The duplication is structural.

Why it needs more than "both are tested"

Each side has a guard asserting the same five properties, and both pass today. But they are parallel assertion lists, not a shared definition. If the idiom changes — say a future issue replaces mv -T with something better, as #15610 replaced the two-directory-rename shape — the Ansible guard is updated with it, and the shell guard keeps asserting the old contract and keeps passing.

That is the same failure mode #15557 fixed at the level below: four entry points each individually correct, with nothing holding them to one another. Collapsing to two implementations makes it much less likely, not impossible, and the property nobody checks is the agreement itself.

The stakes are unchanged from the outages that produced this family: vite empties its output directory before writing, so a publisher that drifts back to building into the served directory takes the site down, and a failed build behind an ungated publish leaves it down (#15430, #15462, #15557, #15610).

Acceptance criteria

  • The contract is stated once, in one machine-readable place, and both guards read their expectations from it rather than each restating them
  • A test fails when one implementation satisfies the contract and the other does not — proven by mutation: weaken one side, confirm red
  • Adding a third implementation in a third language is caught rather than accommodated: the guard enumerates known implementations and fails on an unlisted one
  • The reach floor binds to implementations discovered, not to findings, so a sweep that stops finding either side fails loudly

Related

Activity

  1. mrveiss commented on Sep 7, 2026

    @mrveiss
    OwnerAuthor

    Closed by #15901, merged as 539873f75. Verified against merged base.

    AC Evidence
    The contract is stated once, machine-readable, both guards reading their expectations from it repo_tests/slm_frontend_publish_contract.py — CLAUSES as a tuple of Clause(name, why, patterns), one patterns entry per implementation
    A test fails when one implementation satisfies the contract and the other does not, proven by mutation test_every_clause_holds_in_every_implementation; 10/10 mutants caught
    Adding a third implementation in a third language is caught rather than accommodated test_no_unlisted_publisher_exists — REPO_ROOT.rglob("*") over .sh/.yml/.yaml/.j2/.py, matched against _PUBLISHER_HINTS, with a _NOT_PUBLISHERS allowlist that requires a recorded reason
    The reach floor binds to implementations discovered, not to findings MIN_IMPLEMENTATIONS = 3 against len(IMPLEMENTATIONS), with the stated rationale that a floor tracking findings "would relax itself as the code improved"

    The premise was wrong in the author's favour, and that is the finding

    This issue says the contract exists twice. It exists three times: services/slm_frontend_build.py satisfies the same five clauses in Python, spelling the atomic flip as os.replace over a staged symlink rather than mv -T.

    The discovery search that produced "twice" keyed on the shell spelling of the idiom. So a detector that recognises one language cannot find an implementation written in another, and reports a complete set — which is why AC3 asks for an unlisted implementation to be caught rather than accommodated. The hints are language-plural now.

    Independently re-run on a different anchor (build:slm) during review: no fourth implementation. Two other callers exist — sync-frontend.sh and bootstrap-slm.sh — and both delegate.

    A correction I owe this issue

    I filed #15904 claiming this guard "catches deletion and never addition", having read MIN_IMPLEMENTATIONS flooring the registry without checking for a sibling test covering the other direction. That was wrong: the floor and test_no_unlisted_publisher_exists are two tests doing two jobs — the floor catches the registry collapsing, the sweep catches the tree growing past it. Corrected on #15904, which now stands on one instance rather than two.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions