Sync development → main: version/tag agreement, and epic #241's Gate 2/3 - #262
Merged
Conversation
Full post-mortem on the arc from PR #239 through the release, in reports/post_mortems/ following the platform convention (views-faoapi's shape: period/scope/final-stats header, why, timeline, what we did, what we learned, process, what remains). WHAT IT RECORDS. We built a cross-repository guard surface, and a post-merge review found fifteen defects in the change that built the newest part of it — every one reproduced. Three would have shipped in the release: the guard against publishing a coordinate value PRINTED it, on the one event it fires for; the drift check fired on 12 of 25 rows this partner never reads; and 15 of 29 markdown assignment forms escaped the no-copy scan. The remedy was deletion three times out of five. The source-reading check broke twice in 24 hours, both times because a consumer improved its own code — commits 8615574 and 0c493ae each introduce the named constant AND add that repository's registry-binding test, so the improvement and the breakage were one edit. THE PROCESS SECTION IS THE POINT, and it is not flattering. 341 lines of code against 452 lines of prose. Commits per story 7, 7, 12, 5. Four named failures: a governance rule repealed in a permanent ADR on a claim that was false by six hours; each remediation introducing a defect of the class it was fixing; numbers quoted from memory and wrong twice in consecutive tracking comments; and a review reported as running when nothing had been launched. The root cause is recorded as architectural rather than personal: ADR-016 and ADR-017 contain DESCRIPTIONS OF CURRENT IMPLEMENTATION, so they rot on every change. An ADR records a decision; the moment it also describes the code it becomes a second copy of the code. Most of #248's twelve commits were repairing that. And what worked: independent review (five parallel reviewers found what four rounds of self-review had not), deletion as the default remedy, refusing to chase five surviving mutations in writing rather than silently, and the maintainer's mid-sprint intervention — after which #248 took twelve commits and #243 took five. Every figure in the document was produced by a command and re-verified before commit, which is the rule this arc had to adopt halfway through. Epic #241. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…may mean Three amendments, all short, all bought with the two days this arc cost. ADR-017 §7a — WHAT A CHECK MAY REST ON. A check here may rest on a fact we own, on a fact another repository has DECLARED in the public registry, or on an OUTCOME we can observe. Never on another repository's implementation. Where a needed fact is none of those three, the check is not built here: it is requested as a declaration, requested as a consumer obligation, or registered as an accepted gap with a named owner and trigger. ADR-003 already said declarations over inference. It had been applied rigorously to the product and not at all across the repository boundary, and every defect in C-89..C-92 sat on the wrong side of that line. The evidence is in the clause: the source-reading check broke twice in 24 hours, and commits 8615574 and 0c493ae each introduce the named constant AND add that repository's registry-binding test — the improvement and the breakage were one edit. ADR-017 §7b — AN ADR RECORDS A DECISION, NEVER THE CURRENT STATE OF THE CODE. This is the root cause the post-mortem identified. Both ADR-016 and ADR-017 described what the code did at the moment of writing, and every such sentence needed an erratum the first time the code moved: nine stale claims in two days, one of which repealed a rule in ADR-017 on a premise false by six hours. A decision record that doubles as a description of the code is a second copy of the code, and it rots on a schedule nobody watches. ADR-014 §2 — WHAT "MUTATION-PROVEN" MAY MEAN. Not a process rule but a diagnostic: if a guard can only be proven against inputs you invented, that is the signal it is on the wrong side of a boundary. Measured: thirteen forms proposed by the guard's author, all thirteen caught; twenty-nine proposed independently, fifteen missed. Prefer anchoring the mutant list in something real — this repo's corpus, the registry's rows, an observable outcome. Deliberately NOT adopted: independent mutants for every guard; the friction would exceed the disease for one maintainer, and it is worth buying only for the silent-failure class. C-93 resolved and moved to Resolved Concerns. The register's own guards caught me retitling it in place without moving it, and then caught the header counts — which is what they are for. Suite 415 passed / 1 skipped / 40 xfailed, ruff clean. Closes #250. Epic #241. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs: post-mortem on the guards arc — what shipped, and what did not work
#250: what a check may rest on, and what mutation-proven may mean
…ss stops recurring All ten re-verified against the tree as it stands, not taken from the issue. TWO GARBLED EDIT-SPLICES in tests/test_env_declaration.py — a `#:` block opening mid-sentence with its antecedent gone and a stranded trailing line, and a docstring reading "not our business, which is an IGNORED table disappearing is silent". Both were the residue of deleting a sentence from the middle of a paragraph. TWO WORKING-TREE CLAIMS, in run_pytest.yml and G7's docstring, describing readers that were moved to the sibling's `main` on 2026-08-11. Zero read the working tree now. ADR-017's "None of those three is in place yet" is DELETED rather than updated, which is #250's §7b applied on its first day: an ADR states a decision, not the current state of the code. Updating it would have been the churn §7b exists to stop. ADR-016's `public?` column keeps its honest paragraph and gains the trigger and owner ADR-014 §4 requires — re-read it when a repository changes visibility or CI fails to check one out — plus the reason it is deliberately not machine-checked: verifying it means a network call from a suite that makes none, and the failure it would catch already fails loudly at the checkout step. THE þing MIS-CITATION IS CORRECTED AT ALL THREE SITES. The ruling forbidding integration tests against the production Appwrite project is þing-01 D2, not þing-02 D2, it is CONDITIONAL, and it GRANTS read-only preflight validation. Citing the wrong verdict is how a permission read as a prohibition for weeks — and one of the two corrected sites was arguing that a preflight could not be built. `[test_environment]` stays IGNORED, but its comment no longer says "a fact about the platform, not about this package". It is the clause that says which live checks this package may build. Reading it mechanically waits on C-91, since its rows are bare strings. AND THE BLOB URL SHA NOW HAS A GUARD, which is the point of doing this story at all. The docstrings publish a registry URL whose sha is a third copy of the pin, and only the version was compared. That is how an annotated tag reached the pin: git peeled it, every check passed, and two PUBLIC modules published a link returning 404 three lines above the sentence "a pinned URL does not rot". Third time this class has bitten. Mutation-proven by desynchronising the URL from the constant. Suite 417 passed / 1 skipped / 40 xfailed, ruff clean. Closes #249. Epic #241. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#249: ten stale claims, and a guard so the worst class stops recurring
THE TAUTOLOGY. `assert not _unclassified_tables(base)` where `base` was built from `_TABLE_ROLE` reduces to `set(x) - set(x)`: empty for every possible value of the map and every possible upstream registry. It shipped claiming "the real registry's tables must all classify" about a file the test never opens. The silent direction is worth asserting; it just has to be asserted against an input the function did not derive from itself. Mutation-proven by making `_unclassified_tables` report everything. REGISTRY_CURRENT HAD NO TEST — the function `tests/seam_registry.py` was extracted to provide. Three now, plus two for `registry_at`'s refusal branches that nothing reached. The scratch repository differs on `main`, on `origin/main` and on disk, so preferring the wrong one is visible rather than a coin flip. Mutation-proven four ways, each reverted: reading HEAD (issue #196's defect, which used to leave the whole suite green), preferring `main` over `origin/main`, dropping the unreadable-blob refusal, dropping the TOML-parse wrapper. All four fail now. One of my five mutations was badly chosen and I am recording it rather than quietly replacing it: I mutated the ASSERTION to `is not None`, which is always true of a list, so it tested nothing and "survived". Mutating the assertion instead of the function is the same instrument-not-subject error #245 already produced. Redone against the function. The scratch repository runs git with `-c commit.gpgsign=false -c core.hooksPath=/dev/null`, which is C-91's third item arriving early — a contributor's global signing config would otherwise fail opaquely or block on pinentry with no timeout. C-90 resolved and moved to Resolved Concerns; 23 open -> 22. Suite 420 passed / 1 skipped / 40 xfailed, ruff clean. Closes #246. Epic #241. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-nothing #246: two proofs that proved nothing
…s stop hanging ONE CONDITION WAS PRODUCING TWO DIAGNOSES. A ref this clone cannot see and a ref that is not a frozen commit shared a message naming neither cause, which never said `git fetch`. Three branches now, with different remedies: a blank pin is a pin defect (an empty ref reads the index); an object this clone has never seen is a fetch problem (shallow, --single-branch, or older than the pin); a ref that resolves to a non-commit is a pin defect. The bogus-sha case is deliberately the second, because that is the truth — the reader cannot tell a bad pin from a missing fetch, and now says so instead of guessing. ROWS() REFUSES A SCALAR ROW BY NAME. `[test_environment]` on the live registry is top-level strings. Classifying such a table CONSUMED — which the partition check's own remediation message invites — used to return an AttributeError from a dict comprehension, in the module whose stated justification is failing legibly. THE SCRATCH REPOSITORIES ARE HERMETIC. All three run git with `-c commit.gpgsign=false -c core.hooksPath=/dev/null` and a timeout. Verified rather than assumed: ran under a HOME whose .gitconfig sets commit.gpgsign=true and points core.hooksPath at a nonexistent directory — four tests pass where they would have failed with a bare CalledProcessError, or with a passphrase-protected key blocked on pinentry and hung the run. The refusal proof's expectations moved with the split, which is the point of having it: two of its five cases now assert different messages because the messages became more precise. C-91 resolved and moved to Resolved Concerns; 22 open -> 21. Suite 421 passed / 1 skipped / 40 xfailed, ruff clean. Closes #247. Epic #241 complete. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#247: one condition, one diagnosis — and the scratch repos stop being able to hang the suite
…gree Tag 1.1.0 was cut at main on 2026-08-13 while pyproject.toml still said 1.0.0. An install from that tag reports the PREVIOUS release, and views-models#294's capability assert reads exactly that number to decide what the installed build can honour. Caught before either launcher pinned, which is the only reason re-pointing the tag is cheap. Third time in this arc a version has been declared in two places with a guard on one copy — the same shape as the appwrite_env docstring edition (#249) and the blob URL sha (#249). So this ships with the guard, not just the bump. TWO CHECKS, IN THE TWO DIRECTIONS THAT DIFFER. `test_a_release_tag_declares_the_version_the_package_declares` fires only when HEAD carries a release tag, and demands the numbers match. Silent on every untagged commit, because firing there would be the false alarm that gets a guard deleted (ADR-014 §3). `test_the_newest_release_tag_is_not_ahead_of_the_declared_version` runs everywhere and is the direction that actually bit: a tag moved and the file did not. The reverse — file ahead of tag — is an ordinary pre-release bump and stays silent. Mutation-proven both ways: reverting the file to 1.0.0 fails with the exact sentence describing what happened today; setting it to 1.2.0 passes. Suite 422 passed / 2 skipped / 40 xfailed, ruff clean. The second skip is the tag check on an untagged HEAD, which is correct here and will run on the re-pointed tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(release): the package version and the tag that installs it must agree
Polichinel
added a commit
that referenced
this pull request
Aug 13, 2026
…their evidence The ritual on #263, and it caught the sentence I had flagged as least certain plus one I had not. THE RULESET CLAIM HOLDS, and is better evidenced than I wrote. `protect_main` version 46391648 added `required_status_checks: [{context: "test"}]` at 2026-08-13 04:51 CEST; version 45955166 before it had no such rule. So the trigger fired at a datable instant and the "latent" assessment was true right up to it. The API also reports `current_user_can_bypass: "never"` for an admin account — stronger proof that admin status is no exemption than the argument-from-semantics the entry originally made. "PR #262 SAT AT BLOCKED UNTIL CI WENT GREEN" — WITHDRAWN. The required `test` check was never red on #262. The only failing check was `check-branch`, which is NOT required and blocks by a different mechanism. So the observation showed a failing check, not the requirement biting. Replaced with what the record supports: the requirement was in force before #262 opened, and the merge landed 21 seconds after `test` reported success. "A WEEK AGO CI CHECKED OUT TWO SIBLINGS, NOW ONE" — FALSE. On 2026-08-06 it checked out ONE, views-crafdapi. views-appwrite was added 08-10 once it went public; crafdapi was removed 08-12. The count is unchanged at one; what changed is WHICH repository and what the check does with it. The two-sibling window lasted about two and a half days. The real reduction is in the matching, not the count: `meta.version` then, 13 declared rows now. THE ENTRY WAS ASSERTING BOTH THINGS AT ONCE, in six places — title, Tier, Location, and three body paragraphs still said "two repositories" and "latent today". My "original assessment" marker also swept 122 lines including two later amendments that are not the original assessment and are not wrong. Scoped properly, and the original is now marked superseded rather than "wrong": it was true when written and correctly named both the mechanism and the moment. AND C-81 CONTRADICTED C-86 IN FIVE UNMARKED PLACES. Its enforcement half is DISCHARGED — that is what "the required check exists" means — and its own closing paragraph warned against making the check required and going live on an external dependency "in one unremarked step". That is exactly what happened on 2026-08-13, so it is remarked there. THE DECISION IS NO LONGER RECORDED AS SETTLED. `Owner` makes both responses console actions, which CLAUDE.md puts on the operator's side. It now reads as my recommendation pending assent. I had written it as decided, which was not mine to do. Also deleted `tests/test_falsification_campaign_4_1.py`: an `assert False` probe under `xfail(strict)`, whose entire purpose is to surface probes that start passing — and which could never flip. Its finding is resolved. C-36's resolution already records this residual for marker-style probes. The campaign table and the register entry that named the file are updated in the same change. Suite 422 passed / 2 skipped / 39 xfailed, ruff clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Second sync since the release. Everything here is guard quality and records — no delivered byte changes (
CONTRACT_VERSION1.5,tests/fixtures/byte-identical).Why this needs to reach
mainnowpyproject.tomlsays1.1.0ondevelopmentand1.0.0onmain. The tag1.1.0currently points atmain, so until this merges, the tag installs a package that reports the wrong version — and views-models#294's capability assert reads that number.Merge this, then re-point the tag.
What else is in it
Epic #241's Gate 2 and 3, all four stories:
registry_current's first testsrows()refuses a scalar row; scratch repos verified hermetic undercommit.gpgsign=truePlus the post-mortem in
reports/post_mortems/.🤖 Generated with Claude Code