feat(profiles): DEFAULT stamps commons' O2 generated, V1 executed

Commons ratified V1 2026-08-02 and executed it at `54e0ec7`; verified
against their tree rather than taken on report. ingest-spec.md:217 now
defines `generated` as `{ by: process:okf-ingest, at: <ingested_at> }`,
unquoted, `at` repeating `ingested_at` verbatim. `generated: true` no
longer appears in the spec.

`DEFAULT` states commons' §5 layer, so its stamp is theirs to decide.
`DEFAULT.ownership` gains the actor; the four goldens this repo's plan
named in advance were regenerated by RUNNING the materializer, each on
its own case's `ingested-at.txt`. The v0.2 golden was untouched, as
predicted -- it has carried the O2 form since D5.

Not a migration onto OKF v0.2: `DEFAULT` stays v0.1 on every axis
upstream owns and still emits no `sources`. Commons' spec and the Google
version are independent axes, and comments that narrated them as one
were rewritten rather than left to mislead. README and CLAUDE.md said
the additive rule without that boundary, which would have told a
consumer their DEFAULT bytes can never move; both now state it.

V-A3 is amended, not dropped. `DEFAULT` must OWN the mapping it now
writes -- a profile refusing its own output fires the collision gate on
files its own previous run wrote -- while a mapping naming a foreign
actor, or §7's `human:` actor on curated content, stays unowned. That
half is what carried the safety and it is asserted directly.

§11's stamp-integrity condition moved with the value: the forgeable
stamp was `true` and is now the mapping naming the ingest actor. The
defence was never the value -- the §3 scan globs `ingest-*.md`, so a
Door C import is unreachable however well it forges. Second spoof test
added; both were hand-mutated (glob widened to `*.md`) to confirm they
can fail.

The characterization test derived its foreign-stamp fixture from the
literal `generated: true`, which V1 leaves without a referent -- a
silent no-op waiting to happen. It now derives the needle from the
profile and asserts the substitution occurred.

Door B is deliberately untouched: not the ingest-spec's, marker is
`generated` + `source_file`, disjoint from Door A's `ingest_manifest`,
and the divergence predates V1.

Nothing released or notified. The pilot set pins `v0.5.0a2`, not `main`,
so this is invisible to portfolio-optimiser's freeze and demo; the
consumer exposure report is owed at the release that carries this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwcjUXbKySLbEG5WqTNkta
This commit is contained in:
Kjell Tore Guttormsen 2026-08-09 12:29:05 +02:00
commit 8318605e34
15 changed files with 280 additions and 50 deletions

View file

@ -135,8 +135,9 @@ def _is_ingest_owned(path: Path, manifest_stem: str, *, profile: BundleProfile =
# reference. Promoted verdict files carry neither key, so they can never
# classify as ingest-owned.
#
# The stamp is the PROFILE's because it differs per profile (v0.1 writes
# `generated: true`, v0.2 a `{ by: ..., at: ... }` mapping) — and because the
# The stamp is the PROFILE's because it differs per profile (the older form
# is `generated: true`, the O2 form a `{ by: ..., at: ... }` mapping, and
# which one a profile writes follows the contract it states) — and because the
# emitter and this predicate are coupled through that value. Changing the
# emitted form without the predicate is what makes the library stop
# recognising its own output, firing the collision gate on the files its own

View file

@ -286,24 +286,37 @@ class OwnershipPolicy:
Ownership is the §3 collision gate's question — may this run replace the
file already sitting at a generated name? and the answer is a profile's,
because the stamp differs per profile. v0.1 writes the literal `true`; v0.2
writes `generated: { by: <actor>, at: <ingested_at> }` (§5), where the actor
takes §7's `process:<id>` form.
because the stamp differs per profile. The literal `true` is the older
form; the O2 form is `generated: { by: <actor>, at: <ingested_at> }` (§5),
where the actor takes §7's `process:<id>` form.
`actor` is `None` for the v0.1 stamp. Where it is set it carries no version,
Which form a profile writes is NOT read off an upstream version. `DEFAULT`
states commons' ingest-spec layer and writes the O2 form because commons
ratified it (V1, `54e0ec7`), while remaining v0.1 on every axis upstream
owns; `STRICT_V1` names no `generated` at all. The stamp tracks whichever
contract the profile states, and those contracts move independently.
`actor` is `None` for the literal stamp. Where it is set it carries no version,
deliberately: the value sits inside a byte-compared golden, so a producer
version there would fire golden regression on every release without any
contract having changed, and would make a shared cross-implementation
fixture impossible by construction (plan V1(d), operator 2026-07-27).
Recognition is ONE-WAY, and both directions are decisions rather than
accidents. A v0.2 profile owns the v0.1 stamp as well, so a bundle written
under `DEFAULT` re-runs IN PLACE the black-box promise is that an upstream
release costs a consumer a re-run and nothing more. The reverse is refused:
`DEFAULT` meeting a v0.2 file fails the run rather than replacing a file
whose shape it does not read (V-A3).
accidents. A profile with an actor owns the literal stamp as well, so a
bundle written before V1 re-runs IN PLACE the black-box promise is that a
spec release costs a consumer a re-run and nothing more. The reverse is
refused: a profile without an actor fails the run rather than replacing a
file whose shape it does not read (V-A3).
The v0.2 test is a PREFIX rather than an equality, because the value carries
"Owns the O2 form" is never "owns any mapping". The prefix binds the
profile's OWN actor, so a mapping naming a different one — another
implementation's, or §7's `human:` actor on curated content stays
unowned. That is what keeps the key's mere presence from proving authorship,
which upstream v0.2 makes load-bearing by writing `generated` on
hand-authored files too.
The actor test is a PREFIX rather than an equality, because the value carries
`ingested_at` and therefore differs on every run by design. It works because
`parse_frontmatter` returns the whole flow mapping as one opaque string
(V-A2) no structure this library cannot yet read is parsed here.
@ -601,9 +614,14 @@ class BundleProfile:
ownership: OwnershipPolicy = field(default_factory=OwnershipPolicy)
# The ingest-spec v1 + Phase 2 contract, unchanged. Every value here was a
# constant in `manifest`, `materialize`, `inbox` or `importer` before this
# module existed; the golden suite is what proves the move changed no bytes.
# The ingest-spec + Phase 2 contract. Every value here was a constant in
# `manifest`, `materialize`, `inbox` or `importer` before this module existed;
# the golden suite is what proves that move changed no bytes.
#
# This profile tracks COMMONS' spec, not an upstream Google version — the two
# axes are independent, and `ownership` is where they visibly part company. Its
# `generated` is the O2 mapping because commons ratified that shape for their
# §5 layer, while the profile remains v0.1 on every axis upstream owns.
DEFAULT = BundleProfile(
types=TypePolicy(allowed=None),
frontmatter=FrontmatterSchema(
@ -633,6 +651,12 @@ DEFAULT = BundleProfile(
link_template="- [{label}]({target})",
link_pattern=re.compile(r"^- \[(?P<label>[^\]]*)\]\((?P<target>[^)]+)\)$"),
),
# V1, ratified 2026-08-02 and executed by commons 2026-08-09 (`54e0ec7`).
# The actor is the same constant `OKF_V0_2` carries, and that is commons'
# doing rather than a merge of the two profiles: ingest-spec §7 names
# `process:okf-ingest` as THE ingest actor, so any profile stating that
# spec's layer writes it. The profiles still differ everywhere else.
ownership=OwnershipPolicy(actor="process:okf-ingest"),
)
@ -804,14 +828,24 @@ OKF_V0_2 = BundleProfile(
# (§8, §12). Built with `replace` rather than restated so a later change to
# the shared shape cannot drift between the two.
index=replace(DEFAULT.index, root_frontmatter=("okf_version",)),
# Byte-identical to `DEFAULT.ownership` since V1, and deliberately NOT
# written as a reference to it. The two agree by coincidence of commons
# adopting §7's actor, not by dependency: this profile states UPSTREAM's
# v0.2, so if commons ever moves their actor again, this one must not
# follow. Restating it is what keeps that independence expressible.
ownership=OwnershipPolicy(actor="process:okf-ingest"),
)
# "The latest version supported as STABLE", not the latest present in this
# module. It therefore keeps v0.1 semantics for as long as v0.2 is provisional,
# and flipping it is the GA event — one auditable action rather than a side
# effect of a merge.
# module. It therefore keeps v0.1 UPSTREAM semantics for as long as v0.2 is
# provisional, and flipping it is the GA event — one auditable action rather
# than a side effect of a merge.
#
# "v0.1 semantics" is about upstream and has never covered commons' layer. V1
# moved `DEFAULT`'s stamp, so this alias's bytes moved with it, before GA and
# without the flip. That is not a leak in the alias: the two contracts are
# independent axes, and a consumer bound here tracks both by construction.
#
# The tradeoff is stated rather than hidden: an alias that moves means a consumer
# bound to it inherits upstream's breaking changes on a library upgrade. The