Commit graph

5 commits

Author SHA1 Message Date
3233b19b30 feat(watch): weekly OKF upstream watch that can prove it found nothing
The operator asked for a job that checks at least weekly whether Google OKF has
moved, and messages the right repo immediately when it has. It belongs here
rather than in `.claude` because knowing what a meaningful spec change IS
requires owning the pin, the runbook and the always-latest policy.

`tools/okf_watch.py`, stdlib only, driving git against the local read-only
mirror. It lives outside `src/` so it never enters a wheel; a new packaging test
holds that as a promise rather than an accident of the build config.

Three properties carry the design, and each closes a failure this repo has
actually met:

1. A failed call is never an empty result. Every git invocation raises on a
   non-zero exit and carries stderr, so a caller reading "" knows the query ran.
   The precedent is `grep ... | head; echo $?` reporting head's exit status - a
   broken query read as a quiet upstream.
2. It proves it can find, every run. Before believing any zero it re-runs the
   full detect-and-classify path over `ad30107^1..ad30107`, a range known to have
   changed SPEC.md. An empty known-positive aborts loudly rather than reporting a
   clean sweep. Network failure likewise raises; it never degrades to "no change".
3. It reports on change, not on state. A pin-keyed state file records what has
   been announced; moving the pin resets it, because a pin move means everything
   behind it was absorbed.

Quiet is the enumerated list, not signal. Enumerating what counts as normative
can only match what upstream has already invented, so anything new would fall
outside it and the watch would go silent - failing in the direction nobody
notices. A small measured quiet list, everything else reports. README.md is
deliberately not quiet: the repository move was announced in a README commit.

Sixteen tests build their own git repository in tmp_path rather than skipping
when the mirror is absent - a skipped test preserves nothing on the machine
where the dependency exists. All four load-bearing behaviours were mutation-
tested red before this landed.

Two more tests exist because building this fired a real false alarm: running
with `--pin` and without `--dry-run` delivered two live coord messages. The
override now implies dry-run, enforced in argument parsing rather than
remembered, and `.claude` has the correction.

The runbook gains a section stating what the watch CANNOT do, because that is
the part a future session will otherwise assume away: it sees commits, not
meaning. It would have fired on the 2026-08 tightening because SPEC.md changed,
but no commit list says a value that conformed last month no longer does, and
none says is_stale reversed. Its output is "run the runbook", never "here is
your exposure".
2026-08-23 20:38:37 +02:00
f536e1384d feat(guard): bump the pin to >=0.3,<0.4 and pin Door C's allow_reserved=False
Measure first, widen after. The 19-fixture guard-surface suite was re-run
against v0.3.4 in a scratch venv before the range moved, and reproduced the
three deltas measured against v0.3.3 exactly, with none added. v0.3.4 is the
tag pinned rather than v0.3.3 because it shipped first and repairs a quadratic
regex (okf._MD_LINK_RE) that sits on Door C's own call path.

Door C now passes allow_reserved=False explicitly. The guard added the keyword
in the 0.3 line and defaults it True for received bundles, which would merge a
sender's index.md / log.md instead of rejecting them. The override keeps the
unconditional reserved-name refusal committed to before the keyword existed,
and the reason is structural rather than a second opinion on the guard's scan:
Door C generates the merged bundle's index.md from what it merged and writes
every merged concept verbatim, so a sender's index.md would be a second and
irreconcilable claim on one path.

This is not a behaviour change for anyone on the previous pin: under v0.2.0
the keyword did not exist and reserved names were refused by construction.

The floor is >=0.3 and not >=0.2 for a measured reason. allow_reserved is
absent in v0.2.0 and present from v0.3.0 onward, checked across all five tags:
a >=0.2 floor would admit a version that raises TypeError on every Door C
import. That measurement also corrects a recorded premise -- the plan said the
keyword "shipped in v0.3.3", which read the first version we ran the suite
against as the version it was introduced in. The conclusion held; the reason
did not, and the reason is what a future bump would have relied on.

test_door_c_pins_allow_reserved_false_against_the_guards_default locks both
halves: that the guard still defaults True, without which the override is a
no-op that would pass forever over nothing, and that Door C overrides it.

586 tests, mypy --strict clean, goldens byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V2v1hrDhrff2H3y2TNJHkF
2026-08-02 21:08:53 +02:00
af6e6afe36 chore(release): v0.5.0a1 — version sync, and the entry that makes the pilot provisional
The tag was specified to land on 128109b, where the package still declared
0.4.0 in both places a version is written. The pilots install FROM the tag, so
they would have pinned a pre-release that reports the previous release.

Two requirements the "minimally requires" list did not cover, both stated
elsewhere in the plan:

- The CHANGELOG entry is load-bearing, not ceremony: the plan makes the
  provisional status real by having the profile docstring AND the entry both
  say the surface may change without a deprecation cycle, and name the pilot
  repos. The docstring had it (profiles.py:694); the CHANGELOG stopped at
  0.4.0.
- pyproject.toml and __init__.py both said 0.4.0.

Nothing in the run path reads __version__, which is why a stale one survives a
green suite. test_the_declared_version_agrees_with_the_packaged_one is the only
machine check on it — written failing first, against a one-sided bump. The tag
NAME remains a human step; no test can catch that one.

README keeps pointing at v0.4.0 on purpose: a pre-release for three named
pilots is not the general install line, and OKF_LATEST does not move until GA.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVTup4v7tW9QiVyBENk2LV
2026-07-31 18:26:48 +02:00
241e00f27a feat(guard): wire Doors B and C to the real guard (Phase 2 step 4)
Adds llm_ingestion_guard>=0.2,<0.3 as this library's first and only runtime
dependency, and guard_adapter.py -- the one module that imports it. The flows
themselves are unchanged: they still take an injected gate, and importing the
package still does not import the guard, so a Door A consumer is unaffected by
the dependency's state.

Door B screens the exact bytes it persists. The guard's §6 bookends
(prepare_input -> model -> screen_output) assume a model call in between; this
library makes none, and prepare_input returns prompt-shaped text (sanitized
AND spotlight-fenced with a per-call nonce) that must never reach disk. So the
adapter calls screen_output alone, on the extracted text as it stands, and
hands that same text back -- the verdict is then a statement about the bytes
actually written. This supersedes the plan's "bookends" wording, recorded
there under "Settled during implementation (step 4)".

It follows that the gate refuses rather than repairs: a file carrying an
invisible carrier is rejected, not stripped and persisted. Sanitizing first
would write a document differing invisibly from the operator's file while
source_sha256 still points at the original bytes. Operator decision; the
policy is PRESET_USER_UPLOAD, so any finding at all is held back.

Door C hands the bundle over whole to okf.import_bundle, which resolves the
cross-link graph across concepts. Per-concept reasons are derived from the
scan findings (severity:label) because stamp_concept keeps the disposition and
drops the reason strings behind it.

Assumption B1 closes as a signature smoke test over what the adapters actually
call -- screen_output and okf.import_bundle signatures, the Disposition values
both doors compare by value, the Origin/Channel vocabularies Door C validates,
the result fields read, and the upload preset's shape. prepare_input is not
pinned: drift there cannot reach this library. Behaviour is pinned against the
real scanner too, including the persist-gate proof that a fail-secure fixture
leaves the bundle byte-identical.

B2 closes with it: git+https tag install over anonymously readable HTTPS, no
credential. A direct reference is an install-time channel, not the pin -- the
range stays in pyproject, is satisfied by the tag install today, and resolves
normally once the package index exists. A packaging test enforces that the
guard remains the only runtime dependency (verified by hand-mutation).
2026-07-25 07:32:45 +02:00
9ec25d27d0 feat(typing): ship the PEP 561 py.typed marker
Consumers run mypy --strict against the inline annotations; without the
marker every imported symbol degrades to Any.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 03:46:51 +02:00