fix(email): guard specification.html against generator drift#2082
Merged
Conversation
Add write_artifact/check_artifact + a python -m gaia_agent_email.spec_html regenerate/--check CLI, and a drift test mirroring the openapi.email.json guard, so specification.html can never silently diverge from spec_html.py and drop hand-maintained sections on a regeneration. Closes #2080
3 tasks
itomek
approved these changes
Jul 15, 2026
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.
The email agent's
specification.htmlis a committed, integrator-facing doc that is a pure render ofspec_html.py— but nothing stopped the two from drifting apart. If a hand-edited section had ever landed in the file without a matching generator change, the nextrender_endpoint_spec_html()regeneration would silently drop it and no test would notice. This adds the same drift guardopenapi.email.jsonalready has: apython -m gaia_agent_email.spec_htmlregenerate/--checkCLI plus a test that fails loudly the moment the file and its generator diverge. (The pair is byte-identical today, so this is purely protective — no doc content changes.)Closes #2080
Test plan
python -m pytest tests/test_spec_html_artifact.py -xpassespython util/lint.py --allpassespython -m gaia_agent_email.spec_html --checkexits 0 on the synced pairspecification.html→--checkexits 1 and the drift test fails; revert → both pass again🔍 Technical details
Root cause:
specification.htmlhad no regeneration path and no drift test, unlike its sibling artifactopenapi.email.json(guarded byexport_openapi.check_artifact()+test_committed_openapi_artifact_is_up_to_date). Regeneration was therefore unsafe — a file-only edit would survive until the next regen, then vanish silently.Changes:
gaia_agent_email/spec_html.py— addARTIFACT_PATH(the committedspecification.htmlnext toopenapi.email.json),write_artifact(),check_artifact()(reads the file, compares to a freshrender_endpoint_spec_html(), never rewrites), and amain()with--check/--outputsopython -m gaia_agent_email.spec_htmlregenerates and--checkdiffs. Exported in__all__; addedif __name__ == "__main__". Directly mirrorsexport_openapi.py.tests/test_spec_html_artifact.py— new drift test mirroring the openapi one: asserts the committed file matches the generator, that a diverged/missing file is reported stale, and thatwrite_artifactround-trips throughcheck_artifact.Acceptance criteria: the generator reproduces the committed file byte-identically (verified —
check_artifact()returns True today); the new test fails loudly on divergence (verified by appending a line →--checkexits 1); any generator-emitted section (incl. the referenced #1892/#1889 content, which is present in both today) now survives regeneration by construction, since the file is the generator output and the guard forbids file-only edits.Verified:
pytest tests/test_spec_html_artifact.py→ 4 passed;tests/test_rest_contract.py→ all pass except the pre-existingtest_agent_version_matches_package_metadata(needs the wheelpip installed for dist metadata — an env artifact, not this change); black/isort/flake8 pass.