Commit graph

4 commits

Author SHA1 Message Date
7c608bed1a fix: scope Door A's ownership scan to its own filename prefix
The §3 ownership scan globbed every *.md file regardless of which door
wrote it, then unconditionally unlinked whatever _is_ingest_owned agreed
to. Because _is_ingest_owned reads through the line-oriented parser that
flattens nested blocks (pinned in
test_two_nested_block_mappings_sharing_a_key_collide_in_the_scalar_parser),
a Door B/C file whose nested content happened to share a key name with
the ownership markers (generated, ingest_manifest) could get promoted to
top level and spoof ownership -- silently deleting content this door
never wrote.

Scoping the glob to ingest_prefix closes this by construction: a Door
B/C file is never even a candidate for the scan, regardless of what its
frontmatter parses to. Traced from a coordination tip from
portfolio-optimiser-claude about the same flattening mechanism hitting
their `type` field.
2026-08-01 20:18:57 +02:00
805a2d50d9 fix(okf-v0.2): generated.by is process:okf-ingest — the id commons decided
v0.5.0a1 shipped the one actor value the spec owner had already excluded.
Caught before any pilot was notified, so it costs a tag, not a migration.

Commons decided <fast id> = process:okf-ingest on 2026-07-31, on this repo's
own proposal, superseding option (d) (process:llm-ingestion-okf) chosen here on
07-27. The exclusion is ingest-spec.md:7-8, frozen on the spec being
framework-neutral: normalising OUR repo name into the normative id would force
every other conformant implementation to write it into its own output. Verified
against three independent sources before touching anything — commons' coord
message 20260731T154140Z, their plan :215-216/:244, and their STATE :37.

Why this had to land before the pilot notifications rather than after:

  actor is both the stamp written and the value owned back (OwnershipPolicy),
  and recognition is one-way. A pilot that had run Test A against the excluded
  id would hold bundles this library stops recognising the moment the id is
  corrected — collision_unstamped on their OWN files. That is the A-E5 failure
  mode, and we would have inflicted it.

Worse, it would not have shown up as a failure: the plan's A-E3 expectation
(:854) named the same excluded value as the code, so Test A would have PASSED
and confirmed the error. Expectation and implementation agreeing is not
evidence when both predate the decision.

Nothing in the wild carried the old value: OKF_V0_2 did not exist at v0.4.0, so
the profile has never been released. v0.5.0a1 is abandoned, not moved — a tag
already on a public remote does not get force-pushed, and the history should
say plainly that a1 was wrong.

A-E3 now records both corrections with dates. The V1 paragraph at :1169 is
superseded in place rather than rewritten: its reasoning still holds, only its
outcome moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVTup4v7tW9QiVyBENk2LV
2026-07-31 20:50:21 +02:00
2504011010 feat(okf-v0.2): D5 — the v0.2 golden fixture, with okf_version in root frontmatter
Placement settled by catalog's own reading of upstream at the pinned commit
3fcbb9f: SS8:509-510 and SS12:773-775 both put `okf_version` in a bundle-root
`index.md` frontmatter block, and SS12 calls it the only place frontmatter is
permitted in an index. Catalog's spec says the opposite about the same file;
that divergence is theirs against upstream, and we conform to upstream.

The value never touches a profile. `OKF_V0_2.index.root_frontmatter` names the
key; the caller supplies the value through a new keyword-only
`root_frontmatter_values` mapping. That keeps V4/V-A5 intact - `okf_version`'s
value tracks the upstream Google version and belongs to catalog (E1), so a
constant here would claim a decision we do not own and would have to be chased
on every upstream release. In the fixture the value is fixture DATA
(`okf-version.txt`), not a literal in our source.

Ordering comes from the policy, not the caller's mapping: a dict preserves
insertion order, so two callers passing the same keys would otherwise emit
different bytes. A key the policy does not name is refused fail-fast, before
any disk mutation. Omitting the argument emits no block at all - SS12 is a MAY
and none of upstream's four reference bundles declares the key.

The block is written only when the index is CREATED, so a re-run into an
existing bundle stays byte-identical (A-E5).

Raw-byte assertions rather than parsed ones, on the committed fixture as well
as on fresh runs: catalog measured that a quoted value fails their shape regex
with exit 1 and that a BOM hides the marker while still exiting 0.
`yaml.safe_load` returns "0.2" either way and strips a BOM first, so a parsed
assertion masks exactly those two defects. Asserting the frozen fixture catches
what a self-comparison cannot - regenerating from a broken emitter moves both
sides together.

A-E6 is now placement-explicit (promised catalog in 99cf987), and separates the
two byte properties: BOM-free is a property of the file, unquoted is a property
of CATALOG'S GATE and not of OKF v0.2 - upstream's own SS12 example is quoted,
so their gate rejects the spec's canonical form.

README gains the upstream-version section it was missing; CLAUDE.md gains the
mechanism behind "no profile hard-codes an upstream version": a profile names a
key, a caller owns its value.

550 -> 559 tests. test_profile_threading's `OKF_V0_2.index is DEFAULT.index`
assertion is replaced rather than deleted: object identity was a proxy for "the
shipped profiles differ in no NAME-bearing field", which is what makes the
synthetic test profile necessary, so the guard now asserts that directly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dgkSPjkLpACjMayd9R5jx
2026-07-31 17:27:48 +02:00
ed08ac15e9 feat(profiles): materialize_bundle takes a keyword-only profile (req 6)
`OKF_V0_2` landed in D2 but was unreachable from outside: no door took a
profile. This threads one through, keyword-only behind the `*` the signature
already carried, so every three-positional call site stays source-compatible —
which is what po-claude asked for, and what makes additivity a property of the
signature rather than something a consumer measures.

Nine sites, not the ~6 STATE claimed. The load-bearing one is the call at
materialize.py:378: the CONTENT phase has accepted `profile` since D2, but the
call site never passed one, so A-E3/A-E4/A-E5 were all unreachable. The other
eight are the disk phase (ownership glob, index name, index maintenance,
concept filenames) plus `generated_filename` in manifest.py.

`link_in_index` is public and called from all three doors, so it gets
`*, profile=DEFAULT` rather than having the lookup moved to the call site:
doors B and C keep exactly the behaviour they had, and which profile THEY own
stays an open question instead of being decided silently by a signature change.

Byte-neutrality is proven, not asserted: `OKF_V0_2.paths is DEFAULT.paths` and
`.index is DEFAULT.index`, and the golden suite is green. That identity is also
why six of the nine sites cannot be proven reachable by any shipped-profile
test — no assertion distinguishes two names for one object. A synthetic
test-only profile renaming the index and the concept files closes that gap, so
a site left on `DEFAULT` fails by name rather than passing quietly.

Scope stated rather than glossed: the profile does NOT reach manifest type
validation (`manifest.py:198` still reads `DEFAULT.types`; measured equal to
`OKF_V0_2.types`, so nothing is hidden today), and `STRICT_V1` is not supported
here — its index policy sets three judging fields the materializer does not
honour. Both are named in the docstring.

No `okf_version` anywhere: that lands once, at D5, when the §12 placement
question closes.

550 tests pass (was 542).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tf2BbC8uSRVU4ApQ9NL7QR
2026-07-31 15:37:17 +02:00