Skip to content

Sync development → main: version/tag agreement, and epic #241's Gate 2/3 - #262

Merged
Polichinel merged 13 commits into
mainfrom
development
Aug 13, 2026
Merged

Sync development → main: version/tag agreement, and epic #241's Gate 2/3#262
Polichinel merged 13 commits into
mainfrom
development

Conversation

@Polichinel

Copy link
Copy Markdown
Collaborator

Second sync since the release. Everything here is guard quality and records — no delivered byte changes (CONTRACT_VERSION 1.5, tests/fixtures/ byte-identical).

Why this needs to reach main now

pyproject.toml says 1.1.0 on development and 1.0.0 on main. The tag 1.1.0 currently points at main, 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:

Plus the post-mortem in reports/post_mortems/.

ruff check .    # clean
pytest -q       # 422 passed, 2 skipped, 40 xfailed

🤖 Generated with Claude Code

Polichinel and others added 13 commits August 12, 2026 22:38
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>
…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
Polichinel merged commit 1e21d72 into main Aug 13, 2026
6 checks passed
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>
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