docs(okf-v0.2): widen the exposure question on two axes, and run it on ourselves

A pilot's finding was that the report's question ("do you have a value-literal
`generated` predicate?") invites a search of the ingest path, while the class
also lives in code that WRITES concepts — a consumer searching where they
consume our output finds nothing and answers honestly.

Running the widened question on our own tracked tree added the second axis. Our
one instance (Door B's collision gate, `inbox.py:218`) gates a destructive
action rather than trust, so it fails CLOSED: it refuses to overwrite instead of
admitting foreign content. That direction is the one a consumer dismisses on
finding it, because refusing looks safe — but it breaks the same promise, by
turning "a re-run, nothing more" into a re-run that errors on its own prior
output. Neither direction shows up in a byte comparison.

Ours is measured inert and bounded twice (Door B writes and tests the same
literal; the second conjunct `source_file` is emitted by no other door). Not
fixed here: routing it through `DEFAULT.ownership.owns` would pre-decide half of
the already-deferred Door B stamp question by binding Door B to `DEFAULT`'s
actor, which is commons' and the operator's call. Recorded in the F5/F6 class.

The consumer's repo, file and line stay in the mailbox; the class is recorded
unattributed, as in fcb6e4c.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H2Z8pTafcSPgt4rETsxxEP
This commit is contained in:
Kjell Tore Guttormsen 2026-08-09 21:04:08 +02:00
commit 25415612dc

View file

@ -1352,6 +1352,48 @@ asks every consumer to look for a value-literal `generated` predicate by name,
instead of treating the shape change as inert for anyone who reports no golden
churn.
**That question was still too narrow on two axes, and a real instance can answer
it "no" in good faith.** Both widenings were earned rather than reasoned — the
first came back from a pilot, the second from running the question on ourselves.
- **Where it lives: the write path counts, not only the read path.** "Do you have
a value-literal `generated` predicate?" invites a search of the code that
consumes our output, and the class is not confined there — code that *writes*
concepts derives ownership of the value too. A consumer who searches only their
ingest path finds nothing and answers honestly.
- **Which way it fails: closed counts, not only open.** A predicate gating *trust*
("is this ours?") fails open and misclassifies our output as not-ours. A
predicate gating a *destructive* action ("may I replace this?") fails closed and
refuses to replace our own prior output. Neither direction shows up in a byte
comparison, and the second is the one a consumer will dismiss on finding it,
because refusing looks safe. It is not, measured against the promise we sell: it
turns "a re-run, nothing more" into "a re-run that errors on its own prior
output".
So the question the report asks is: **does anything in your codebase derive
ownership or authenticity from `generated`'s value — in the read path or the write
path — and in which direction does it fail?**
**We ran it on ourselves, and the answer was not zero.** Enumerating the tracked
tree rather than one package: exactly one value-literal comparison, Door B's
collision gate (`inbox.py:218`). It asks "may I replace this?", so it fails
**closed** — it refuses to overwrite rather than admitting foreign content — which
is why it has never surfaced. Door A's equivalent already routes through
`profile.ownership.owns` (`materialize.py:155-156`), so the two doors disagree on
mechanism. It is inert today, and bounded twice: Door B both writes and tests the
literal, and the predicate's second conjunct is `source_file`, which no other door
emits — so Door A's O2-stamped output cannot reach that namespace even by name. It
stops being inert the day Door B's stamp moves, because Door B would then refuse to
re-run in place over its own output.
**Not fixed here, deliberately, and this is not the same record as the deferral
below.** Routing the predicate through `DEFAULT.ownership.owns` looks
behaviour-preserving — recognition is one-way, so `owns("true")` is still true —
but it would pre-decide half of the deferred question by binding Door B to
`DEFAULT`'s actor, and which actor Door B's stamp takes is commons' and the
operator's call. Recorded and proposed, in the F5/F6 class: a real defect, not
ordered, not started here.
### The ingest stamp's byte form is the ownership contract — decided 2026-08-02
Door A's `generated` value is `{ by: process:okf-ingest, at: <ingested_at> }`, and
@ -1563,7 +1605,12 @@ oversight:** Door B (`inbox.py`) still writes `generated: true` and gates on it.
Door B is not the ingest-spec's, its marker is `generated` + `source_file`
(disjoint from Door A's `ingest_manifest`), and the divergence predates V1 —
`OKF_V0_2` has carried the actor since D2. Whether Door B should follow is a
question for the operator, not a consequence of this change.
question for the operator, not a consequence of this change. **The gate half of
that sentence acquired a second reading on 2026-08-09** — it is this codebase's
only instance of the value-literal ownership class the exposure report asks nine
consumers about, measured fail-closed and inert; see the GA-notification section
above. The deferral is unchanged; what changed is that we can no longer call it
purely a Door-B styling question.
**V2 — Which D1 direction?** (c) recommended above; needs a decision before code
because it determines whether `mypy --strict` churn lands across the existing