Commit graph

28 commits

Author SHA1 Message Date
0a7a77d5a8 docs(okf-v0.2): U7 resolved — the 'not:' family is an example, not a field
Zero occurrences in the v0.2 SPEC.md, zero in the v0.1 SPEC.md at the parent of
the migration commit, no reference in upstream's reference_agent source, and one
file in the whole bundle set. An undocumented hand-authored key, legal under
§14's unknown-keys tolerance — a demonstration of the open namespace rather than
a field to support.

The same sweep settles '# Cited by': body prose in two policy files, absent from
the spec, and independently DERIVED by the visualizer (viz.html:56) from sources.
Both fit the upstream pattern already visible in credibility scores and trust
tiers — OKF stores signals, consumers derive views.

Answering this needed the v0.1 spec text, upstream's source, and a whole-tree
grep at once, which is what the local pinned clone made possible. That is U8's
lesson applied rather than restated.
2026-07-27 16:32:40 +02:00
e5e574df86 docs(okf-v0.2): U8 — sources replaces the citations LIST, not the attribution
Per-claim attribution moves to a markdown footnote keyed to a sources[].id
(SPEC §4.2 :224-225, §5.1 :347-360), and the shipped acme_retail bundle carries
it inside a schema table. Our supersession row stated the destination and not
the mechanism, so a consumer following it would migrate the list and drop the
claim->source binding.

Measurable at the pinned commit the whole time; surfaced by Google's launch post
2026-07-27. U1-U7 measured frontmatter in all four reference bundles and walked
past the bodies. V-A1's discharge is narrowed rather than reworded: the pin
holds, the reading's depth did not.

Consequence outside the doc: the v0.2 exposure report sent to the wiki states
the incomplete half under the heading that exists to size their cost. Correction
owed, same class as A-E3.
2026-07-27 16:24:56 +02:00
aac765fa5a docs(okf-v0.2): A-E3 no longer outlives the decision it encoded
The operator chose V1(d) for OKF_V0_2, so the pilot expectation A-E3 was
written against — a producer-and-version actor — is stale. Corrected in place,
with the reason, before po-claude receives the tag rather than after they run.

V1's own section records what the choice does and does not settle: DEFAULT's
generated is commons' §5 layer and stays in their queue as item 8. TDD step 5
is marked done at c90171d.
2026-07-27 09:12:07 +02:00
99cf98749f docs(okf-v0.2): A-E6 does not say WHERE, and that silence is now load-bearing
Upstream §12 puts okf_version in the root index.md's frontmatter;
catalog's own spec says an index.md has no frontmatter and carries the
marker as a body line. A bundle conforming to one is non-conformant
against the other, so the placement A-E6 leaves unstated is no longer
cosmetic. Their gate cannot settle it: the shape regex is unanchored and
matches in both places, so a green TEST B measures the value and never
the placement. D5 stays unfrozen until catalog has verified against
upstream; A-E6 becomes placement-explicit before the pilot receives it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 21:38:46 +02:00
b1a92c940d docs(okf-v0.2): the guard measurement removes the form question, and A-E5 grows the second run
Two corrections, both measured against guard 0.2.0 today rather than
reasoned.

Requirement 1: the T2 parser refuses every route to a mapping — the flow
form on the disallowed-indicator set, the block form on the nested-mapping
check, dotted keys on the key pattern. A v0.2 `generated` mapping has no
expressible form through Door C at all, so the chosen flow form is not
worse than the alternatives; there are none. The decision stands on its
original grounds. What changes is a claim: no round-trip through a
guard-gated import may be asserted. Door B never parses frontmatter, so
the grammar binds import, not emission.

A-E5: po-claude located a hole in A-E1. The emitter's `generated: true`
and the ownership predicate are coupled through that literal, and
OKF_V0_2 changes exactly it — one run compared byte for byte cannot see
the predicate stall, since that needs two runs into the same directory.
A-E5 now names the same-directory second run and expects it to succeed.
The separate in-place DEFAULT-to-v0.2 question is marked undecided rather
than quietly turned into an expectation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 21:34:19 +02:00
c85347e8a5 docs(okf-v0.2): steps 1-3 are done, and step 3 was not behavior-neutral
The plan's own line said steps 1-3 change no behavior. V-A7's construction gate
made that untrue, and a stale premise in a plan is the class of thing this repo
has already paid for twice. Recorded where it was wrong, with the operator
decision that put the gate before D2 rather than at it, and with Door C's six
tolerance cases measured rather than expected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 20:10:37 +02:00
2d0328aaa7 docs(okf-v0.2): requirement 1 re-sized -- the emitter does not change at all
U4 escalated tag requirement 1: real v0.2 frontmatter is block YAML, and "emit
accepts a block-list value" had been scoped against a list of strings when
upstream's canonical sources is a list of five-key mappings. Re-sized against
the code rather than against its tests, and the answer runs the other way.

Measured: emit (profiles.py:197) takes Mapping[str, str] and renders one
"key: value" line per key through _render (:211), verbatim but for
source_query's whitespace collapse; _is_legal_value (:97) admits a string or a
non-empty list of strings, never mappings; the only production call sites are
materialize.py:166 and inbox.py:129, both passing dict[str, str].

The earlier "discharged" note read the validator correctly and drew too much
from it. But the gap it left is not U4's either: A-E4 owes at least a resource,
not upstream's five keys, and a manifest source has no author, no last_modified,
and no bundle-internal resource in upstream's sense. A field with no reader is
not written. So the question was never how large a YAML emitter we need -- it
was which form sources takes.

Chosen: sources: [{ id: <id>, resource: <ref> }], the same inline flow form A-E3
already chose for generated. It keeps parse_frontmatter line-oriented, which
matters because a block list pollutes that parser with false keys (- id,
resource) -- the failure mode documented for po-claude -- and _is_ingest_owned,
the one piece of this tag we called risky, reads THROUGH that parser. The block
form would force the collision gate and the parser to be hardened in the same
session. It also satisfies commons' section 5 single-line MUST, so the DEFAULT
seam U4 opened stays closed, and section 11 asks for parseable YAML, not block
YAML. The deviation from upstream's emitted shape is real and is recorded as a
decision, like U1, not as compliance we may claim.

Consequences: requirement 1 is smaller than both earlier sizings -- zero emitter
work -- and D1a drops off the critical path to v0.5.0a1. A-E4 is corrected from
"block list" to the flow form before the pilot receives it; a test spec that
outlives its decision is the A-E6 defect class.

Swept the same defect class while here: the TDD order and three other passages
wrote okf_version: "0.2" QUOTED, contradicting A-E6, which is precisely the form
catalog measured as exit 1. All five now unquoted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 19:57:29 +02:00
bec5608106 docs(okf-v0.2): V6 -- Door C imports the pointer, never the code it points at
Measured, after asserting the opposite to the security repo an hour earlier: no
door in this library can create, import, or persist an executable file. Door C
skips every suffix that is not .md (importer.py:226-231); Door B has no extractor
for .py in either the core or the optional set (extract.py:113,131-142); Door A
writes only ingest-*.md. We told the guard a third-party attester .py could reach
a consumer's tree through our door. It cannot. Reasoned, not measured, corrected
the same hour.

The real exposure is the inverse. Door C writes a merged concept verbatim,
frontmatter included, and under v0.2 that frontmatter can carry
executor.resource and attester.resource. So we import the reference to executable
code while never importing the code. Either the pointer dangles -- noisy, safe --
or it resolves to something else already in, or later added to, the destination
tree, and the imported concept references code it never shipped. That second one
is a substituted reference, not a broken one, and it looks valid.

Also recorded: the guard is a PERSIST gate, and executable code carries its risk
at EXECUTION. A .py entirely safe to store may be dangerous to run, so no persist
gate covers the class section 10 introduces, and the execution boundary has no
owner in this stack -- upstream deferred the attester ABI and sandboxing, and we
declared runtime out of scope on that same deferral. Written down because "the
guard covers it" is a reasonable conclusion to draw and a hard one to catch later.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 19:41:20 +02:00
b541e794a9 docs(okf-v0.2): GA is not done when the code ships, but when consumers know
Operator directive: when the v0.2 implementation is ready, every OKF-consuming
repo is told through the coord mailbox what to do or test. That was a step title
in the runbook and is now a table with content -- nine repos, each with the
action asked of them and, more importantly, what happens if they do nothing.

For most the honest answer is "nothing changes", and saying that plainly is more
useful than an open invitation to review. That is the black-box commitment made
checkable rather than asserted.

Two new GA exit criteria: every repo in the table has been sent its message and
the table was re-derived rather than reused; and the security repo's section 10
boundary questions are either answered or their being open is recorded as an
accepted, stated limit. The second matters because Door C is the path an
external bundle's executable attester would take into a consumer's tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 19:34:18 +02:00
99491fa277 docs(okf-v0.2): D6's checklist item points at the runbook
The verification criterion said the release checklist carries an
upstream-version re-check. It now names the procedure that check runs, which is
the part that makes it more than a reminder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 19:19:46 +02:00
4669f32b47 docs(okf-v0.2): the pilot baseline was ambiguous and we asserted it was shared
Two corrections to claims this repo made, both measured rather than argued.

1. `examples/ingest-golden-file/` names TWO different fixtures. Ours holds
   ingest-orders.md + ingest-products.md; po-claude's holds ingest-costs.md +
   ingest-edge.md; the manifests differ at line 3 char 41; content-file overlap
   is zero and the only shared filename, index.md, is byte-different. We agreed
   a pilot baseline by name, and the name pointed at two files. A-E1 is the stop
   condition, so a mismatched baseline would have surfaced as a profile defect.
   The pilot runs THEIR fixture -- A-E1 asserts byte-identity with what their
   pinned version produces, which is a claim about their baseline, not ours --
   and the report must state repo and path.

2. "The shared fixture, ratified by two independent implementations" was false.
   We took it from an incoming message and passed it on the same day without
   measuring, while holding a standing rule that incoming messages are premises,
   not facts, and applying that rule only to spec readings. Commons caught it.

The :29 argument in V1 survives in commons' stronger form: the clause requires
reproducing "the shared golden extractions", the definite article presumes one
set where there are two, so it has no referent today. (b) therefore does not
break a shared fixture we have -- it cements that one can never arise, since a
producer-bearing field inside the byte-compared prefix makes convergence
impossible by construction. The :280 half stays observable and fires on our next
release regardless.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 19:13:38 +02:00
9bffc92abc docs(okf-v0.2): read upstream's reference bundles, not just its spec
V-A1 is discharged: everything is now read at pinned commit 3fcbb9f, not off
main. v0.2 itself landed in 780fe9d; SPEC.md was edited after it, so "the v0.2
commit" and "the current spec text" are different objects.

Session 19 read the spec. It never read the four bundles the tooling migrated
to v0.2, and reading them corrected things a spec reading had settled wrongly.
okf/samples/ga4_merch_store is not a bundle -- it is a runner config that
generates one, so it says nothing about output shape.

- U1 No reference bundle declares okf_version. All four root index.md files
  have no frontmatter at all. A-E6 is therefore a stricter posture than
  upstream's own implementation -- defensible, but a decision rather than an
  assumption. po-claude's "deviation" #2 was conformance. catalog's gate reports
  MISSING on every official Google bundle.
- U2 Root index.md is the ONLY index permitted to carry frontmatter (12).
  Constrains the fixture and the index policy.
- U3 `generated` is used for HUMAN-authored content upstream, so it cannot be a
  machine-generated or ownership predicate. Empirical confirmation of commons'
  axis finding, stronger than their spec-text argument.
- U4 Real v0.2 frontmatter is multi-line block YAML: flow sequences, block lists
  of flow mappings, nested mappings, block lists of multi-key mappings, booleans,
  dates. Requirement 1 was scoped against a list of strings; the canonical shape
  is a list of five-key mappings. The one-session sizing must be redone.
- U5 7's actor convention gives `<producer>/<version>` for tools, and upstream
  uses it. That is real evidence for (b), against the (d) we recommended to
  commons today. Sent to them as a correction.
- U6 Conformance and the consumer-tolerance MUSTs quoted verbatim, including the
  bare-`verified`-as-one-element-list requirement on D1's reader.
- U7 An undocumented `not:` family observed in a bundle, unresolved, recorded so
  it is not mistaken for checked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 19:09:03 +02:00
d65231c3ce docs(okf-v0.2): V1 moves off our own recommendation, on commons' finding
We carried (b) -- generated.by naming this library and its version. Commons
found it collides with ingest-spec's conformance clause: :29 requires a
conformant implementation to reproduce the shared goldens byte for byte, and
`generated` sits inside the mandatory ordered prefix. A producer's name and
version inside a shared fixture means no sibling implementation can reproduce
it, and Golden regression fires on every version bump with no contract change.

They measured it as normative-only for them, since examples/ingest-golden-*
does not exist in commons. Measured here, it is observable today: four fixtures
carry `generated:` on line 8 inside the byte-compared prefix, and
ingest-golden-file is both the shared fixture against the sibling
implementation and the baseline po-claude runs the pilot against.

So we back (d) -- `process:<fixed id>` per v0.2 section 7. Second reason,
visible only from this side: it keeps _is_ingest_owned an equality check.
Under (b) the collision gate must either prefix-match and ignore the version --
parsing a value we had just made structured -- or stop recognizing files an
earlier version wrote, which fails safe but breaks re-running a manifest across
a version bump.

The cost of (d) is recorded rather than minimized: producer identity leaves the
bundle, and a log does not travel through Door C. The decision stays commons'
to ratify.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 17:09:20 +02:00
f7385fafdb docs(okf-v0.2): the pilot answered before the tag existed, and it changed the tag
All three pilots accepted. Their measurements land in the plan now rather than
as failed runs later, which is the entire return on asking before building.

Six corrections, each measured by the consumer rather than reasoned by us:

- A-E6 specified `okf_version: "0.2"` quoted. Catalog measured the quoted form
  failing their shape regex with exit 1, and a UTF-8 BOM making the marker
  invisible while still exiting 0. Both now stated as raw-byte expectations.
- A-E2 said the diff lies only *inside* the frontmatter block. No bundle whose
  root index has no frontmatter block can satisfy that together with A-E6.
- B-E3 covers one of catalog's three gates; the other two cannot see our
  fixture. Recorded so the report is not read as broader than it is.
- Our claim that no gate keys on the version value was wrong as formulated:
  check-okf-parity.mjs puts the raw value in its signature. It compares without
  judging -- verdict-invariant, not value-blind.
- C-E1's ground truth is 524 non-reserved documents, not 522; 522 was correct
  at d2c12d2.
- `materialize_bundle` gains keyword-only `profile=DEFAULT` on po-claude's
  request. The signature already has a `*`, so their three-positional call path
  stays source-compatible and additivity becomes a property of the signature
  instead of something a consumer measures.

Also recorded: po-claude closed their index-parser defect themselves (f41264f),
so do not design around it; the assumption under it -- "a generated index has no
frontmatter" -- is not peculiar to them. Dropping the superseded `timestamp`
draws one catalog warning per concept file, decided after the pilot, not before.

The estimate's open caveat is discharged by reading profiles.py:197: emit is one
formatting path, the validator already admits list values, so only the emitter
side is missing and the sizing stands.

CLAUDE.md gains a standing constraint: consumer content stays at form level in
public files. The wiki's bundle is private pending an Anthropic ToS assessment,
their report goes back through coord, and a push is not reversible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 17:03:05 +02:00
95b1ef0bcd docs(okf-v0.2): the pilot tag needs far less than the deliverable list implied
Walking each pilot test back to what it actually exercises produces a result
the deliverable ordering had obscured: none of the three needs D1's
structured reader.

Test A emits v0.2 -- `generated` is an inline flow mapping, a formatted
string, and `sources` needs the emitter to accept a block-list value. Neither
requires reading one back. Test B runs catalog's gate on Test A's output.
Test C validates the wiki's v0.1-shaped documents against a v0.2 variant
profile; their documents carry no v0.2 families, so block-list reading is
never reached.

The structured reader exists to consume third-party v0.2 bundles -- D3, Door
C -- and no pilot repo sends us one. So D1 splits: the emitter half is in the
tag, and the reader half moves behind the pilot where the feedback can inform
it. Two consequences worth stating plainly.

V2 is off the critical path. It chooses the reader's design, and the reader
now lands after the pilot; the tag is not waiting on a decision.

The tag is roughly a session of work, not a phase: an emitter change, a
profile constant following the existing STRICT_V1 pattern, a profile-aware
ownership predicate, `sources` derivation, one generated fixture.

Risk concentrates rather than spreads. `_is_ingest_owned` is the pre-mutation
collision gate, so a defect there is expensive and quiet -- its
characterization test is written before it is touched. The emitter change
runs through DEFAULT's path, so V-A6 is proven by the golden suite rather
than assumed.

The sizing itself carries a caveat in the doc: it treats
FrontmatterSchema.emit as a single formatting function, inferred from its
tests rather than read. That is a premise like any other and gets verified
before the estimate is relied on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 16:56:45 +02:00
5de590ebdf docs(okf-v0.2): the pilot tests state their expected results up front
The pilot invitations asked for feedback without specifying what to run or
what we expect to see. That returns a description, not a measurement: only a
stated expectation lets someone else's run falsify our model instead of
merely confirming it.

Each of the three tests now carries a numbered procedure, numbered expected
outcomes, and an explicit list of what would surprise us. Naming the
surprises is the load-bearing part -- a consumer who sees something odd but
passing otherwise has no reason to mention it.

Test A (producer, portfolio-optimiser-claude) runs one already-working
manifest twice with the same explicit ingested_at, once under DEFAULT as
baseline and once under OKF_V0_2, and diffs. A-E1 is the stop condition: if
the DEFAULT run is not byte-identical to their current pinned output, we have
broken a v0.1 consumer and the pilot ends there. The named surprises include
`at` differing from the ingested_at they passed, which would mean a
wall-clock crept in, and a collision refusal on a file that IS theirs, which
is the inverse of the fail-safe we predicted -- we expect foreign files to be
refused, so a false refusal of their own is the defect.

Test B (gate, catalog) runs their unmodified gate on the v0.2 fixture and on
a 0.1 control. B-E3 -- that no gate other than the version gate behaves
differently between the two -- is what converts our reading of their form
regex into a measurement. A membership list or equality comparison anywhere
in their chain is exactly what this is paid for.

Test C (expressiveness, wiki) is run by us, read-only at a recorded commit,
and the report goes to them. What we ask them to check is the part we cannot
measure from outside: whether a field we called an optional addition is in
fact load-bearing in their pipeline, and whether "no change required" holds
operationally rather than only formally.

Feedback gains a per-expectation verdict line so three independent runs are
comparable and a disagreement is located rather than merely reported. The
request also says outright that a wrong expectation is a better result than a
clean run, since a clean run only confirms what we already believed.

Inputs come from this repo at the pre-release tag; nothing is transported
through the mailbox except the specification.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 14:10:40 +02:00
a27017e635 docs(okf-v0.2): ship to a pilot set before general availability
Operator directive 2026-07-26: consumers are better served by getting the
latest version early and reporting back than by us holding it until we judge
it finished.

This closes a real gap. Every test in the plan -- including V-A8 against
upstream's reference implementation -- asks whether output is conformant.
None asks whether it is usable: whether a bundle is awkward to construct,
whether a rejection message is actionable, whether the profile can express
what a consumer's actual data needs. Only real data surfaces that.

Pilot set is three repos, one axis each, chosen for signal:
portfolio-optimiser-claude for the producer path (one real manifest run),
catalog for gate acceptance (their gate on our fixture, measured rather than
inferred from the regex), and claude-code-llm-wiki for expressiveness across
522 real documents.

The load-bearing design detail is that most of the pilot is read-only on our
side: we run the v0.2 reader over real consumer bundles and send the report.
That needs no adoption, no writes into their trees (O2 holds), and no change
to a contract their operator ratified. Only the producer axis asks a
consumer to do anything, and it asks for one run.

Excluded with reasons rather than silently: okr (Node side not yet lifted),
linkedin-studio (v0.2's provenance families would put implicit pressure on
the ingest/published carve-out we agreed not to normalize), commons (they
are deciding V1 -- a participant, not a test site).

Shipping a provisional surface without owing stability rests on three rules,
not on saying "provisional": OKF_LATEST does not point at v0.2 until GA, so
flipping it is the GA event rather than a merge side effect; the docstring
and CHANGELOG name the pilot repos; and breaking changes during the pilot
get no deprecation cycle. Stating that last one up front is what buys the
freedom to act on feedback -- discovering it later is what turns a pilot
into a de-facto release.

Feedback is requested in five named parts, because unstructured reports are
not comparable across three repos. The one that matters most is "what was
awkward but worked": workarounds are the highest-value signal and never
appear as a failure.

GA exit criteria are testable, and carry one honest limit: a three-repo
pilot exercises only what those three use, so `sources` with usage_window,
multi-verifier `verified`, and Attested Computation will likely go
unexercised. Those stay marked provisional at GA instead of being silently
promoted -- claiming otherwise would be the same unearned-claim pattern that
"conform first, claim after" exists to prevent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 13:50:28 +02:00
cb23de4700 docs(okf-v0.2): supporting the latest OKF version becomes standing policy
Operator directive 2026-07-26: the library always supports the current
latest version of Google OKF. v0.2 shipped 2026-07-25, so v0.2 support is
committed work rather than something a consumer has to request. This
overrides the previous default answer to open question V3 ("no until a
named consumer asks"), which is kept in the plan marked superseded so the
override reads as deliberate.

Support is additive: a new profile, never a migration of the existing two.
That single design choice is what makes an always-latest policy sustainable,
and it resolves the tension the directive would otherwise create with three
constraints that do not yield to it:

- DEFAULT states commons' ingest-spec section 5 layer, so its `generated`
  shape is commons' call. Under the additive design this stops blocking us,
  which takes commons off the critical path.
- STRICT_V1 mirrors the proving consumer's ratified contract; changing
  another repo's contract from here would violate O2.
- v0.2 defers the attestation receipt and verdict wire formats upstream, so
  the format is supportable and the unspecified runtime is not. It re-enters
  scope when upstream specifies it.

This is also the first time the phase-3 profile abstraction is forced by
something outside this repo rather than by a second consumer, which is the
better test of whether the seam was cut in the right place.

Deliverables D1-D6 replace the earlier decision-round framing: a frontmatter
model that can carry block lists (`sources`, multi-verifier `verified`), an
OKF_V0_2 profile plus an OKF_LATEST alias whose moving-target tradeoff is
documented rather than hidden, Door C conformance against the consumer
tolerance rules, `Attested Computation` round-trip, v0.2 golden fixtures, and
a release-checklist re-check so the standing policy cannot decay silently.

Two new assumptions carry the weight. V-A7 forbids any profile from emitting
`timestamp` together with a malformed `generated`, since that combination
would have neither a valid `generated.at` nor an eligible section 13.1
fallback. V-A8 validates our own v0.2 fixture against upstream's reference
implementation, because every other test in the suite only asks whether we
agree with ourselves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 13:41:30 +02:00
ca453b2895 docs(okf-v0.2): the alignment track lands before the contract freezes
Everything built so far targets OKF v0.1. Upstream published v0.2 on
2026-07-25, so the plan records how the library relates to it and, more
importantly, who owns each decision.

Read from the spec itself rather than secondhand, which corrected two
readings that a summary had gotten wrong:

- STRICT_V1's `timestamp` is NOT a defect. Section 13.1 grants consumers a
  documented fallback to legacy `timestamp` precisely when `generated` is
  absent, and STRICT_V1 emits no `generated`. Nothing is asked of the wiki.
- The one measured shape problem is DEFAULT's `generated: "true"`, because
  v0.2 requires `generated.by` within `generated`. That key was not reserved
  in v0.1, so it was legal when written; v0.2 claimed the name. DEFAULT
  states commons' ingest-spec section 5 layer, so the fix is commons' call
  and is raised there as open question V1 rather than patched locally.

Two findings shrink the work. The canonical form for `generated` and a
single `verified` is an inline flow mapping, which the existing scalar
parser already round-trips as an opaque string, so block-list support is
only needed for `sources` and multi-verifier `verified` -- and only if a
named consumer asks. And the collision degrades safely: `_is_ingest_owned`
returns False for a v0.2 mapping, so a foreign concept is refused rather
than overwritten.

The track sits between Phases 3 and 4 because Phase 4 freezes the
cross-runtime contract. Freezing a v0.1 shape into two runtimes would let
the shared fixture suite certify the drift instead of catching it.

Self-imposed rule, since the spec does not require it: conform first, claim
after. Declaring `okf_version: "0.2"` is a MAY with no conformance
checkpoint, so claiming it early would be permitted -- and would be the same
class of true-sounding misleading claim as reporting a 0.2.0 measurement
under a 0.3.1 heading.

Also moves the guard 0.3.1 measurement procedure out of session state and
into execution-order.md, where it belongs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 11:42:37 +02:00
c5e6f083e6 docs(phase-4): record linkedin-studio's two named requirements before the freeze
They named both on a live channel today, having withdrawn the half of their
original question that turned out not to bind them. Recorded here rather than in
a reply so the scope freeze has to answer them explicitly.

`write_index` ownership was genuinely ambiguous in this plan — they read
deliverable 4's "write" and asked which line owns it. It is deliverable 3, and
the plan now says so.

Their vocabulary requirement is already the Python half's shape:
`TypePolicy(allowed=None)` is the default and rewrites nothing, so there is no
DEFAULT_TYPE to snap to. The reference checker's collapse is a known deviation to
resolve with the convention owner under step 1, not a behaviour to lift.
2026-07-25 20:34:54 +02:00
cf1f7984c4 docs(phase-3): the four open questions are answered; required set is eight
The proving consumer answered Q1-Q4 on 2026-07-25, each measured or read out
of their code that day. No row in the mapping moved. Three of the four answers
carry a sharper reason than this table drafted, and one reverses a correction
this table made a day earlier.

Q2 is the substantive change. This table had corrected the plan's eight-key
required set down to four, against the consumer's REQUIRED_KEYS constant.
Their operator ratified eight: the constant is their emit-path minimum, not
their contract, and all eight keys are present on 522/522 of their documents.
STRICT_V1 is therefore deliberately stricter than the consumer's own
validator; if they ever ship a class lacking source_sha they bump the profile
rather than bend the bundle, and they say so before shipping it. The eleven-key
allowlist is unchanged and still a different thing from the required set:
version/date are layer-specific and summary is the one nullable key, so all
three stay optional-when-absent.

Q1, Q3 and Q4 keep their Consumer/Guard assignments, now with the reasons that
justify them: gate 5 escalates on run state (disposition == verbatim) rather
than document class, so a profile structurally cannot see it; gate 4's
changelog rule is a vendor slug list that is not scoped to concepts/ at all;
gates 19 and 22 need external state, one of them by construction never
blocking. Assumption C2 is confirmed at 25 gates and the index shape is
confirmed non-negotiable.

Both reviews - the consumer's and the operator's - are now in, so the port may
begin. The Guard column is unaffected by all of it.
2026-07-25 15:21:59 +02:00
d8e466791c docs(phase-3): split table over all 25 gates, and two corrected premises
Phase 3 starts with a written mapping of the proving consumer's gates to
exactly one of Profile / Guard / Consumer, reviewed before any porting.
Drafted here against claude-code-llm-wiki's validate.py and bundle.py at
their 9ee5a8e, which is their single gate entrypoint, so the enumeration
is exhaustive against that commit and no other.

Reading the validator corrected two things this plan asserted before it
had been read, both of which would have produced a wrong STRICT_V1:

- Required frontmatter keys are FOUR (type, title, description,
  timestamp), not the eight the plan listed. Those eight were a
  truncation of FRONTMATTER_KEY_ORDER, which is an eleven-key allowlist
  doubling as canonical emission order. frontmatter_schema therefore
  needs three fields -- required, allowlisted, ordered -- not one
  required/optional pair.
- Reserved files are THREE basenames, not two: README.md is reserved
  like index.md and log.md but in a third way, present and exempt from
  the document gates while still content-scanned. A two-valued
  required/required-absent policy cannot express it.

Two of the 25 gates bundle checks from different columns
(LLM_OUTPUT_LINT, BUNDLE_URL_ALLOWLIST). They are decomposed per check
rather than assigned wholesale to a dominant column, which would have
moved the residue to the wrong side of the boundary.

Also recorded: index policy is the widest gap between the two profiles
(per-bundle vs per-directory, two link formats, append-if-absent vs
exact match, root index with or without frontmatter), and C4 is
confirmed with the operator -- profiles in code, no config-file loading
in v1.

Four open questions are addressed to the proving consumer in the
artifact; no porting until they and the table come back reviewed.
2026-07-25 12:30:51 +02:00
0b77c58a1c docs(plan): correct the Phase 4 parity oracle and the catalog re-pin premise
Both were wrong, and the convention owner corrected them: this library is not
a marketplace plugin, so there is no catalog ref to re-pin -- the integration
is a registry entry in the catalog's parity gate. And the catalog's vendored
okf-check.mjs is not byte-identical to okr's (~141 differing lines of code, an
intentional layer-1 floor), so it cannot serve as a conformance oracle. Parity
is behavioural over the spec section 3 floor in default read-mode, per file,
measured by that gate over an adversarial corpus.
2026-07-25 07:35:54 +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
f10fc60de2 feat(import): Door C flow against an injected import gate (Phase 2 step 5)
Reads an external OKF bundle as {bundle-relative path -> document text},
hands it WHOLE to an injected gate over the guard's okf.import_bundle (a
bundle-level call: it resolves the cross-link graph across concepts), and
merges only concepts clearing the non-blocking floor. Same injection pattern
as Door B, so the core stays dependency-free while the CI channel for the
real guard is settled.

Two constraints shaped the design and are pinned by tests:

- A merged concept is written VERBATIM. Stamping provenance into it would
  require round-tripping its frontmatter through this library's line-oriented
  parser, which cannot represent the block lists the guard's parser accepts --
  silent data loss -- and would persist bytes the guard never screened.
- Ownership is therefore proven by content identity: identical bytes at the
  target name are a no-op re-merge (re-import of an unchanged bundle is
  idempotent), and anything else at the name is refused. Curated content and
  an updated concept are refused alike; refusing is what never destroys.

The floor is fail-closed beyond the plan's "no error" wording: an error, an
unrecognised disposition, and a concept the gate returned no verdict for are
all refusals. quarantine_review stays its own bucket, as at Door B.
origin/channel are validated against the guard's pinned vocabulary -- it
derives trust from origin by enum identity, so an unrecognised string would be
silently downgraded rather than caught.

Three primitives promoted for reuse rather than duplicated:
reduce_to_id_grammar and check_filename_length to materialize.py, and
extract.decode_text. Door C slugs the WHOLE concept path, so tables/users.md
and views/users.md stay distinct. Concept discovery folds case explicitly
rather than globbing *.md, whose case-sensitivity follows the filesystem and
would import the same bundle differently on APFS and ext4.

README's "what is gated today" section corrected: it claimed nothing is gated,
which is no longer true, but the honest statement is narrower than "the doors
are gated" -- the library cannot verify that an injected adapter is a real
guard, and a permissive stub is believed.

405 tests green; ruff, ruff format and mypy --strict clean.
2026-07-25 06:57:25 +02:00
99c899f4ed docs(plan): correct Door C guard call — v0.2 has no allow_reserved kwarg
Stage 0 guard-readiness check (this session) verified the real
`llm-ingestion-guard` v0.2.0 surface against ground truth: the Door C plan
assumed `okf.import_bundle(bundle, origin=…, channel=…, allow_reserved=False)`,
but the shipped signature is `import_bundle(bundle, *, origin, channel)` — no
`allow_reserved` parameter. Reserved-name rejection of `index.md`/`log.md` is
unconditional in the guard (OKFPathError, no toggle), so the intended default
semantics are unchanged; only the documented call was wrong and would have
raised TypeError if written verbatim. Correct the plan line before Phase 2
code is written against it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBbjgS5A55RVavoyjJC4FX
2026-07-24 20:10:57 +02:00
0e18a6c0a9 docs(plan): add cross-phase execution ordering
Sequences the remaining roadmap (Stage 0 guard gate -> Phase 2 -> Phase 3
-> Phase 4) with the quality rationale for the order: guard readiness
verified before any Phase 2 code, Phase 2 split guard-independent-first,
Phase 3's configurability extracted only after two concrete consumers
exist, Phase 4 code last on a frozen contract with its coordination
started early. Connective tissue over the four per-phase plans; owns the
order and reasons, not the phase detail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBbjgS5A55RVavoyjJC4FX
2026-07-24 20:01:45 +02:00
b0ad2aedfb docs(plan): add detailed phase 1-4 implementation plans
Phase 1 (Door A): ingest-spec v1 implementation + §11 golden fixtures,
pinned to the current spec revision, with TDD order and byte-exact
verification criteria. Phase 2 (Doors B/C): guard-gated inbox and
external bundle import. Phase 3: configurable bundle contract via a
profile object, default profile locked to the golden suite. Phase 4:
zero-dep Node half, coordination-first with owner sign-offs.

Each plan carries explicit key assumptions with tests and a
verification section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QeqhJpYQyghASjiJo5EhGg
2026-07-16 10:58:08 +02:00