Commit graph

7 commits

Author SHA1 Message Date
8318605e34 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
2026-08-09 12:29:05 +02:00
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
1747238a83 fix(materialize): scope §5 replacement to the running manifest's stem
The §3 ownership scan classified every ingest-stamped file as replaceable,
so materializing one manifest into a bundle deleted the ingest files another
manifest had stamped there — and removed their index links too.

Narrow ownership to files whose stamp names the running manifest by stem.
The stamp is `{stem}@{sha256[:16]}`; matching on the stem, not the whole
stamp, lets an edited manifest still reclaim the files a prior run of the
same manifest wrote (the stem is stable across content edits), while a
different manifest sharing the bundle keeps its own files. Implements the
trinn-e §10.2 decision. This does not close the operator-copy restriction
(a generated file copied into curated content), which stays documented in
ingest-spec.md:69-72, not enforced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBbjgS5A55RVavoyjJC4FX
2026-07-23 07:00:27 +02:00
c011534e9f fix(materialize): emit frontmatter and index values verbatim
a7e1bc8 unified title rendering at both sites and routed the open question
to commons, the spec owner: does §5's whitespace collapse apply to `title`,
or only to `source_query`? The ruling is source_query only. §5 mandates the
collapse for that one field — where a multi-line SQL SELECT must render on
one line — while every other value is validated single-line at manifest load
and emitted as-is: validation, not repair.

_single_line is renamed _collapse_whitespace and now runs for source_query
alone. Frontmatter values, the index link label, and the labels_by_target
map all pass through verbatim. A title carrying an internal whitespace run
(or any other operator-supplied bytes) now survives byte-for-byte at both
the frontmatter and the index-label site, instead of being silently altered.

No golden bytes change: no title in the shipped fixtures or either consumer
carries a collapsible whitespace run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBbjgS5A55RVavoyjJC4FX
2026-07-23 06:41:27 +02:00
a7e1bc88d8 fix(materialize): render a title identically in frontmatter and index
ingest-spec §4 makes the extraction title BOTH the `title` frontmatter and
the index link label — one value, two sites. The frontmatter renderer
single-lined its values while the index label was written raw, so a title
carrying a whitespace run rendered two different ways from one input:

    title: Energiforbruk 2024
    - [Energiforbruk  2024](ingest-orders.md)

Single-lining is now one named rule applied at every site that renders the
title, instead of a detail of frontmatter rendering that the index path did
not share.

No golden bytes change: no title in this repo, portfolio-optimiser or
portfolio-optimiser-claude carries a whitespace run.

Open question routed to commons, the spec owner: §5 mandates collapsing
whitespace runs in `source_query` only, while `title` is separately required
to be single-line and is already validated as such at manifest load. So
collapsing the title may be over-application — the alternative reading is
that both sites should emit it verbatim. Either reading keeps the two sites
in agreement, which is what this commit establishes; which of the two is
correct is a spec question, not ours.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdVgowYC4LARgvNdNMiuvz
2026-07-20 10:35:57 +02:00
d3a3bccfbb feat(api): expose the §5 provenance stamp on IngestResult
The stamp ({stem}@{sha256(raw)[:16]}) was computed internally but only
written into frontmatter, forcing consumers to recompute it from the
manifest bytes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 03:46:52 +02:00
0ff946696c feat(materialize): add §5 materialization with collision gate and replacement
TDD step 5: materialize_bundle takes three explicit inputs (manifest,
bundle dir, regex-validated ingested_at — no wall-clock default) and
stages every extraction in memory before the first disk mutation. The
§3 collision gate refuses to overwrite any file without the ingest
stamp, before any mutation; replacement removes exactly the stamped
set. Frontmatter is the seven §5 keys in spec order with whitespace
collapse; output is LF-only raw bytes with one trailing newline; the
provenance stamp hashes the same bytes that are parsed. Fresh index.md
gets bundle_summary plus idempotent links in extraction order (§6
creation path; preserve/removal rules come next). The §8 network gate
refuses http fail-fast without the per-run opt-in; the http transport
itself lands in step 7.

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