Skip to content

S1 (#182) — appwrite_env logs before it raises; ADR-008 pinned across both entry validators - #193

Merged
Polichinel merged 1 commit into
developmentfrom
feat/s1-appwrite-env-log-before-raise
Aug 1, 2026
Merged

Polichinel merged 1 commit into
developmentfrom
feat/s1-appwrite-env-log-before-raise

Conversation

@Polichinel

Copy link
Copy Markdown
Collaborator

Closes #182. Part of epic #181. Registers C-74 and files #192.

What

appwrite_env.assert_env_declared now logs at ERROR before raising, per ADR-008:48/51. Two lines.

Why it was missing, which is the interesting part

contract/launch_config.py was written in #149 by deliberately mirroring this module — same shape, same failure style, same dependency-light constraint. It inherited the flaw, the S1 diff review caught it in the new code, and the new module was fixed. The old one was left alone for scope discipline.

So the pair ended up inconsistent in the wrong direction: the module written to match the pattern was the only one that had it.

The test therefore parametrises the ADR-008 obligation over both validators rather than pinning one. Adding a third is one line in _REFUSALS, not a new test.

WET before DRY — explicitly refused

No shared assert_declared helper. They answer different questions (did the launcher assemble the environment vs did it declare the delivery mode) against different sources (os.getenv vs a passed dict). Named trigger to revisit: a third entry-validation module. Recorded in #181 so the next contributor's instinct to extract meets an argument rather than a silence.

The secret

CONNECTION_ENV carries APPWRITE_DATASTORE_API_KEY. Logging a refusal is only safe because missing holds names: membership is decided by os.getenv(name) being falsy and the resolved value is never read. A second test sets a sentinel value and asserts it never reaches a record, so a future "log the environment for debuggability" cannot land quietly.

Mutation-tested

Removing the logger.error line fails 2 of the new tests. A guard that passes on the bug it was written for is decoration.

Review found something bigger — C-74, now #192

Reading tests/test_redaction_guard.py (cited by the new test as "the wider rule") showed it is not enforcing that rule. It pins the þing-01 #135 audit over five module roots; #153 moved the machinery from unfao/ to contract/ and four were never re-pointed:

declared root exists scanned
unfao/wire no 0
delivery yes 6
unfao/historical.py no 0
unfao/track_a_source.py no 0
unfao/frame_extraction.py no 0

Silent by construction: rglob on a nonexistent directory yields nothing rather than raising, so a missing root and a clean root look identical. 6 files scanned instead of ~16, passing.

Not a leak — the relocated modules were checked directly and are still credential-blind. What is gone is the thing that would notice them ceasing to be.

Two register claims corrected in place: C-57 said the redaction clause was "already mechanically enforced", and Cluster I's fix strategy cites this exact file as the guard pattern working. Both were written in good faith; both were overclaims.

Sixth instance of the stale-claim class in two days. The shape has a name now: a move leaves prose and paths behind, and nothing here asserts that a declared path exists. Handed to S9's ADR question.

Review findings addressed

Verification

ruff check .                                  # clean
pytest -q                                     # 310 passed, 44 xfailed, 5 known pyarrow (C-72)
git diff --stat origin/main -- tests/fixtures/  # empty — ADR-013 byte-frozen
pytest -q tests/test_register_integrity.py    # 8 passed

Note: the epic's freeze check originally said main, not origin/main. A stale local main reported 10 changed fixture files on a branch that touched none. Corrected in #181/#186/#190/#191 — a verification command that cries wolf gets ignored, which would have defeated the point.

🤖 Generated with Claude Code

…ver both entry validators

ADR-008:48 requires raised structural failures to be logged at ERROR; :51 that
"raising is not a substitute for logging." `assert_env_declared` raised
`EnvironmentError` naming every missing variable and wrote no log record, so an
operator reading logs after a refused run found nothing (register C-71).

The two-line fix matters less than why it was missing. `contract/launch_config.py`
was written in #149 by mirroring this module, inherited the flaw, was fixed in
review, and left the module it copied as the odd one out. The new test therefore
parametrises the ADR-008 obligation over BOTH validators rather than pinning one:
adding a third is one line, and the pair cannot drift again.

Deliberately NOT extracted into a shared helper. WET before DRY — they answer
different questions (did the launcher assemble the environment / did it declare
the delivery mode) against different sources (os.getenv / a passed dict). Named
trigger to revisit: a third entry-validation module. Recorded in epic #181.

Logging here is only safe because `missing` holds NAMES: membership is decided by
`os.getenv(name)` being falsy and the resolved value is never read. CONNECTION_ENV
carries the APPWRITE_DATASTORE_API_KEY secret slot, so a second test sets a
sentinel value and asserts it never reaches a record — a future "log the current
environment for debuggability" cannot land quietly.

Mutation-tested: removing the logger.error line fails 2 of the new tests.

Also registers C-74, found by this story's own review. tests/test_redaction_guard.py
pins the þing-01 #135 redaction audit over five module roots; #153 moved the
machinery from unfao/ to contract/ and four were never re-pointed. rglob on a
nonexistent directory yields nothing rather than raising, so the guard scans 6
files instead of ~16 and passes. Not a leak — the relocated modules are still
credential-blind — but C-57 and Cluster I both vouched for that guard in writing,
and both are corrected in place. Tracked as #192.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Polichinel
Polichinel merged commit 923667d into development Aug 1, 2026
4 checks passed
@Polichinel
Polichinel deleted the feat/s1-appwrite-env-log-before-raise branch August 1, 2026 22:34
Polichinel added a commit that referenced this pull request Aug 2, 2026
…l day (#205)

assert_env_declared has logged before raising since #193. The entry stayed under
Open while eight further stories shipped, in the epic whose entire thesis is that
a record should not outlive the thing it describes.

S2's guard did not catch it, and that is by design rather than by defect: it
matches two declarative phrasings, and this entry's closing language is prose —
"a two-line change; the natural place to take it is S8". That is the false
negative S2 deliberately accepted, and the cost came due within hours.

Widening the guard would be the wrong fix; no regex separates "describes work that
is done" from prose. The gap is that #182 was closed without disposing of the
entry it named in the same change. The generalisable rule — a story that names a
register entry disposes of it in the same PR — goes to S9's ADR question alongside
the CIC lagging three consecutive stories. Both are records updated by memory
rather than by the change that invalidates them.

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