1
0
Fork 0
Commit graph

24 commits

Author SHA1 Message Date
6cf0eb1263
chore(release): 1.10.0 2026-11-01 18:55:08 +01:00
0eab4345bf
feat(output): decode hex-wrapped credentials at the persist gate
Gap 1 closed. The output gate finds runs of 32+ hex digits
(calibration.HEX_WRAP_MIN_LEN), decodes both byte alignments, keeps text
that is at least 80% printable (the base64 bar, now one shared helper in
entropy) and runs the credential patterns on it. A hex-wrapped AWS key
surfaces as decoded:egress:aws-access-key-id with "in hex run @ N" as
evidence; the value is never in it. Credentials only: a hex-wrapped
injection is not decoded.

Measured before and after on the same trees: all 3583 clean documents
(three FP populations and the 78 pinned OKF concepts) are identical line
for line under both presets (disposition, labels, severity, count,
offset); OKF import stays 47/31. They and 569 READMEs hold 2403 hex runs
of 32+ digits, none of which decodes to text. scan_output CPU 122.9 s
before, 123.4 s after; one solid hex run at the 1M cap 4.2 s -> 5.8 s.

14 new tests (10 red for the right reason first), 6 of 6 mutations red.
Matrix 146/146 + 5/5 gaps, suite 1274 passed, ReDoS sweep 169 patterns.
2026-11-01 17:06:44 +01:00
61d7d521e4
chore(release): 1.9.0 2026-11-01 14:57:53 +01:00
d43403b8aa
chore(release): 1.8.0 2026-10-31 21:28:17 +01:00
e7742dceba
chore(release): 1.7.0 2026-10-31 19:49:27 +01:00
eeb7bf07f6
feat(active): report a run of images whose order carries a value
Session 6 of the 2026 threat plan (attack row 3): A05 output was red.
CamoLeak renders a secret as one image per character from a URL alphabet;
each URL is ordinary, so each image graded LOW and the gate WARNed.

- active:markdown-image-sequence (MEDIUM): at least 4 ordinary external
  images, inline or reference-style, on one URL template whose varying
  tokens are 2 chars or fewer, or digit runs. Digits that count up in
  document order are a counter and carry nothing. The per-image grade is
  unchanged; the finding sits beside it.
- Accepted twins (flag tables, figures 1a/1b) go to review; measured
  evasions (word dictionary, 3-char symbols, counter increments, run of 3,
  a template per symbol) are in LIMITATIONS.

Guard rule before/after: FP 106/198, 196/2918, 108/389 identical. Those
corpora hold no external markdown image, so the rule was also run on 577
package READMEs (312 with external images): 0 firings at run lengths 3-8.
OKF import 47/31 identical. Matrix 140/140, ReDoS 163 patterns 0 flagged,
suite 1179 passed + 5 xfailed. Row 3: 13 caught, 1 gap, 5 open.
2026-10-31 19:33:51 +01:00
d00d1b0658
chore(release): 1.6.0 2026-10-31 17:04:02 +01:00
3eb8e9e9a9
chore(release): 1.5.0 2026-10-31 14:28:48 +01:00
1c6816cdfe
fix(owasp): qualify every catalog number with the 2026 edition
OWASP's Top 10 for LLM Applications 2026 renumbered the list; only LLM01
and LLM02 kept their meaning. Every finding's `owasp` was written against
2025 with no year, so EchoLeak findings said LLM05, which in 2026 means
Data and Model Poisoning. Row 2 of the 2026 done-criterion counted 95
such sites at 6702d7a (82 old meaning, 13 wrong); it is now 0.

- Producers name the risk, never the number: one table (`_owasp.py`)
  owns `LLMnn:2026`. The vendored lexicon stays byte-equal with commons
  and gets its year where the finding is made; a commons number whose
  meaning moved between editions raises instead of being guessed.
- Mechanical map from the 2025 meaning (verified against the publisher
  in session 1): LLM05 -> LLM10:2026, LLM06 -> LLM03:2026,
  LLM10 -> LLM06:2026, LLM08 -> LLM09:2026, LLM04 -> LLM05:2026.

The 13 wrong-meaning sites, judged:
- Factual poisoning (PoisonedRAG gap, and the test that follows it):
  LLM05:2026. The 2026 text names "RAG Knowledge Base Poisoning" as its
  example 6. It said LLM09, which was Misinformation.
- Fail-secure (README row, matrix row): no number. It is the guard's own
  failure semantics; no edition describes it. Was LLM10.
- Parser-surface refusals (nested mapping, block scalar, quoted flow
  element, off-allowlist key): no number. Refusing a shape the parser
  would have to interpret is not resource consumption. Was LLM10. The
  anchor and merge-key rows stay LLM06:2026 (alias expansion is DoS).
- `resource` URL and cross-link scheme validation (4): no number. They
  validate received bundle metadata; LLM10:2026 is about model output
  and says input validation belongs to LLM01. Was LLM05.
- `_raise_case` default (OKF path traversal, absolute path, reserved
  name, non-.md, bundle escape): no number. None is prompt injection.
  Was LLM01.
A doubtful number would be the misreading the year exists to prevent,
so `Case.owasp` is now Optional and the matrix prints "-".

Self-safety (input caps, ReDoS) keeps LLM06:2026: the 2026 text, read at
the publisher, covers "input flood ... exploiting processing
inefficiencies" and application-side rechunking. The README's LLM09
anchor (Misinformation) is dropped; it was the factual-poisoning row.

The permanent test (tests/test_catalog_numbers.py) fails on an old or
wrong number, on a number without the year outside the vendored
lexicon, and on any finding the coverage matrix drives whose `owasp`
lacks the newest year; "newest" is read from the catalog register. The
census holds its wrong-rules to their counts at the frozen revision
only, so the frozen listing (da1aa0fa...6f) is unchanged.
2026-10-31 14:20:35 +01:00
1241075ef8
chore(release): 1.4.2 2026-10-29 08:31:59 +01:00
57c91aeb11
chore(release): 1.4.1
Patch release for 639da03: a flow-sequence element admits '#' and ':'
where YAML reads them as text, so okf's own `references: [...]` lines
with #fragments and scheme:// links parse (0 / 5 467 raise on the
consumer's bundle, was 2 038). Also ships 79285e1, which brought the doc
surfaces up to 1.3.0/1.4.0 behaviour.

Unlike the 1.4.0 release commit, this one bumps every live version
surface: pyproject.toml, __init__.py, README badge + status + install
pin, ADOPTION-BRIEF status + "as of" + test count (893 -> 910), BRIEF
status, CHANGELOG heading. Provenance references to 1.4.0 are left as
they are.

No exported surface changed. Gates on this tree: 910 passed, coverage
exit 0 (130/130 + 6/6), redos-sweep exit 0, 45 LIMITATIONS entries.
2026-09-23 12:46:32 +02:00
79285e1265
docs: finish the 1.4.0 release on the doc surfaces
The 1.4.0 release commit (d19de8c) dated the CHANGELOG heading and nothing
else, so the tag went out with README, ADOPTION-BRIEF and BRIEF still
naming 1.3.0 and the install pin still installing 1.3.0. This bumps the
badge, status line and install pin in README, the status and "as of"
lines in ADOPTION-BRIEF (test count 868 -> 893) and BRIEF's status line.
The tag itself is not moved.

Three claims that went false earlier are corrected against the code:

- README said `resource` is deliberately not on the frontmatter allowlist
  and that a `sources` block list of mappings is refused. Both stopped
  being true in 1.3.0. The paragraph now names the four mapping carriers
  and says `resource` / `usage_window` are admitted inside a `sources`
  entry only.
- README described the `resource` https-allowlist without its scope. It
  covers the top-level `resource` only; `sources[].resource` is never
  URL-validated (measured: `javascript:` there imports with no error).
- LIMITATIONS still listed `tags: [a, b]` among the routes that fail.
  It parses since 1.4.0.

Gates after the edit: 893 passed, coverage exit 0, redos-sweep exit 0,
45 LIMITATIONS entries.
2026-09-23 12:25:33 +02:00
44e2b31afd test(okf): pin the two boundary classes the new carriers introduced
Probed after 1.3.0 landed, on advisor challenge. Both came back clean --
these pin them so they cannot regress silently.

1. A `- ` item whose text begins with a YAML indicator AND carries a `": "`
   could in principle route into the block-mapping path with a
   half-validated key instead of into `_reject_dangerous_value`. It does
   not: `&anchor id` fails `_KEY_RE` on the key side, so the item falls
   through intact and raises on the indicator. One row each for & * ! %
   backtick, plus the merge key inside an entry.

2. `_consume_block_mapping` ends at a blank line and at column zero, and
   hands an index back to `_consume_block_list`, which hands one back to
   `_parse_flat`. A line orphaned by that boundary -- a `resource:` left
   over from a mapping that closed early -- must RAISE, not be dropped. A
   pointer that vanishes rather than failing is this repo's failure class,
   so it gets a test rather than a probe. Plus the return-index contract:
   a top-level key after a multi-entry list is neither swallowed nor
   re-read as an item.

868 green (was 859), 130/130 classes. Test-only; no source change.
2026-09-02 17:26:08 +02:00
a965e8ac5b feat(okf): sources becomes expressible, and the parent key is what admits resource
Order 20260902T150716Z from .claude -- a K5 blocker in the OKF programme.
`parse_frontmatter` rejected `sources` in every form the spec and its
producers actually use. Measured 02.09 by two consumers independently:
`sources: [{ id: a, resource: x }]` raised on the `[` indicator (one entry
as well as two), and the block sequence of block mappings -- SPEC.md 5.1's
OWN example -- raised "nested mappings are not supported". `resource` is
REQUIRED within a `sources` entry, so the whole provenance family was
unwritable and a bundle written the way the spec documents it was refused.

Measured against the spec before coding, not reasoned: 5.1's example block
is the canonical carrier for a REQUIRED field and 11.1 defines conformance
as parseable frontmatter, so refusing it refuses a conformant bundle. Both
carriers now parse to the same list of dicts.

The load-bearing change is not the carrier, it is WHO admits `resource`.
1.2.0 left it off the allowlist arguing the parser could not tell
`sources[].resource` (5.1, a citation) from `executor.resource` /
`attester.resource` (10, a pointer to code to be run -- the door-C route
closed in 1.1.0). That premise was false: the owning key is in scope at
every call site and was simply never threaded through. It is threaded now,
so the discrimination is structural, and door C stays shut through EVERY
carrier including the two this adds -- pinned by a new test that drives
`executor`/`attester` through all four.

Refusal stays the default elsewhere. A flow sequence of plain scalars
(`tags: [a, b]`) still raises: the sequence carrier is opened for the flow
mapping element and nothing else. A `sources` entry admits scalar leaves
only, so 5.1's optional PER-ENTRY `usage_window` is refused -- no nesting
past depth 1 is a security property and it was not spent here; registered
as a conformance gap rather than left as an oversight. A block list may not
mix scalars and mappings, because a consumer reading `entry.get("id")` over
one gets an AttributeError off the first str.

New residual registered: `sources[].resource` is scanned as text (T1) but
never URL-validated. T3's https allowlist cannot reach it without
over-blocking conformant bundles -- 5.1 permits bundle-relative paths and
scope descriptors, and the producers' own golden emits `resource: fixture`.
A consumer that dereferences it must call `validate_resource_url` itself.

Suite 834 -> 859 green. 25 new rows; four pre-existing rows changed because
this release changed the behaviour they pinned, two of them renamed since
their names asserted the old invariant (`exactly_one_route_to_a_mapping`,
`two_keys_per_item_is_where_the_block_list_hard_rejects`). Not "unchanged".
130/130 classes, 6/6 gaps hold, 44 -> 45 limitations, ReDoS 0/152 (the
sweep adds no evidence here -- this change adds no regex and the splitting
is linear). Six version surfaces bumped by hand, no sed. Re-measured alone
after the bump.

No exported surface changed; no detector behaviour and no calibration
changed.
2026-09-02 17:21:26 +02:00
9aeceb02c0 release: 1.2.0 -- the OKF mapping-form fix becomes pinnable
Order 20260823T175853Z from .claude: commit 5870483 (the G3 nine-key
allowlist fix) was untagged -- llm-ingestion-okf measured 4/9 concepts
through Doer C on it, blocked from 8/9 only by the absence of a
pinnable tag, on a suite already green (595/596) at that commit.

Six live version surfaces bumped by hand (no sed -- provenance is
never bumped): pyproject.toml, __init__.__version__, README badge +
Status + the pinned pip install tag, docs/BRIEF.md, docs/ADOPTION-BRIEF.md
Status, and CLAUDE.md. ADOPTION-BRIEF's snapshot line moves with it:
129/129 -> 130/130 classes, 802 -> 834 passing (the new row is the
off-allowlist-key coverage case added by the G3 fix itself).

NOT bumped, matching the 1.1.0 precedent: every "closed in 1.1.0" /
"the 1.1.0 defect" reference across CHANGELOG.md, CLAUDE.md,
docs/LIMITATIONS.md, docs/redos-sweep.py, src/llm_ingestion_guard/okf.py
and tests/ names a historical baseline, not the current version --
rewriting those would erase what they document. docs/BRIEF.md's
GuardLLM v1.1.0 reference is a third party's version, untouched.

Re-measured after the bump, alone: 834 passed, 130/130 classes, 6/6
gaps hold, 43 limitations, ReDoS sweep 0/152 candidates flagged.

No exported surface changed; no detector behaviour and no calibration
changed. Pure release action -- no functional code touched.
2026-08-23 20:13:50 +02:00
ca4f97c8c9 release: 1.1.0 -- the first behaviour change shipped under the freeze
Six live version surfaces bumped by hand (no sed -- provenance is never
bumped): pyproject.toml, __init__.__version__, README badge + Status + the
pinned pip install tag, docs/BRIEF.md, docs/ADOPTION-BRIEF.md Status, and
CLAUDE.md. ADOPTION-BRIEF's test count 792 -> 802.

NOT bumped, and deliberately: SECURITY.md's two `1.0.0` references name the
freeze BASELINE, not the current version -- "a payload that disposes WARN on
1.0.0 may dispose FAIL_SECURE on a later 1.x" is the promise this release
instantiates, so rewriting it to 1.1.0 would erase what it promised. The
GATE-G and PLAN-v1 numbers are the 1.0.0 gate record. The Forge repo
description carries no version (verified against the API last session).

This is the case SECURITY.md and the 1.0.0 CHANGELOG entry described in
advance: the exported surface is frozen, detection behaviour is not. No
exported name moved. A document that disposed WARN on 1.0.0 may dispose
FAIL_SECURE here; a consumer whose frontmatter carries an unquoted ": " in a
value will see those concepts refused at import, and quoting it parses.

Re-measured after the bump, alone: 802 passed, 129/129 classes, 6/6 gaps hold,
35 limitations.
2026-08-13 22:58:36 +02:00
98ebc07b56 release: 1.0.0 -- the exported Python surface is frozen under semver
Nine current-state surfaces bumped by hand. The classification sweep ran
FIRST, before the first edit: 421 hits on 'v?0.N(.N)' across all tracked
files, each read and sorted current-state vs provenance. Provenance is
untouched -- 'New in v0.4.0', 'measured against 0.3.1', every '0.7.0'
in a code comment or a census candidate name still says what it measured.

The sweep found two surfaces the plan's nine-item list did not name:
README's status BADGE (still 'alpha' -- a version string grep cannot see
it) and ADOPTION-BRIEF's test count, which said 791 against a suite that
runs 792. Both corrected.

pyproject also moves Development Status :: 3 - Alpha -> 5 -
Production/Stable, likewise invisible to a version grep.

CHANGELOG [1.0.0] references [0.3.0] and [0.3.1] for the behaviour
changes rather than repeating them, and carries the freeze point itself:
what is frozen (the exported surface), what is deliberately NOT (all
detection calibration), the three conceded limitations, the one known
open defect (:43), and the runtime-coverage gap -- no external consumer
has run 0.7.0.

No code changed. Per docs/PLAN-v1.md the release gate is the whole suite
green, not a new test: 792 passed, coverage matrix 129/129 + 6/6, exit 0.
2026-08-13 22:20:48 +02:00
fcfaee4589 feat(active-content): raw HTML graded on carrier, and a tag naming no target is inert
Two changes that had to ship together, because they co-occur.

`active:raw-html-link` (MEDIUM) splits the click-required carriers out of
`active:raw-html`. The same URL was LOW as `[t](url)` and HIGH as
`<a href="url">` — an asymmetry produced by syntax, not by affordance, on a
carrier the markdown path has graded MEDIUM since 0.3.1. The event-handler test
runs first, so `<a onclick=...>` stays HIGH. The url-attribute branch stays HIGH
too: a name outside the active set has unknown rendering, and grading
`<Card src=...>` as a link would be reasoning rather than measurement.

The no-URL narrowing makes `</a>`, `<Frame>`, `<video />` and `<img alt=...>`
without `src` inert — `<base />`'s argument from 0.6.0 applied to the rest of the
name branch. It tests for the URL attribute's PRESENCE, not for a readable value,
so the fail-secure gap `_url_attr_is_external` leaves open is not reopened here.

WHY TOGETHER: the narrowing strips a document's `</a>`/`<Frame>` and what remains
is the `<a href=...>` the split grades down, so each alone leaves the document
blocked by the other's residue.

`active_tag_class` is now the classification point and `is_active_tag` wraps it.
The census patches the former: a boolean could only express a narrowing, never a
regrade, so every carrier candidate would have measured equal to PRODUCTION —
silently, and in the direction that reads as "no change helps".

TWO COSTS, BOTH RECORDED RATHER THAN GLOSSED:

- The split TIGHTENS the trusted tier. One finding becomes two, and >=2 findings
  at MEDIUM+ trip the compound overlay, so a document carrying both an `<img src>`
  and an `<a href>` goes WARN -> quarantine_review on PRESET_TRUSTED_SOURCE. On
  that preset it is the only direction the split can move anything. The census
  now reports a TIGHTENS column on both trust tiers against the previously
  shipped row — "frees N" without "tightens M" is a one-sided number.
- `count` drops on documents containing `</a>`, a published field moving under a
  meaning that did not change.

MEASURED: reference-corpus (389) 54 -> 53 fail_secure, tightens 0/0, and the
census `PRODUCTION` row equals its `C1 + D` candidate row for row. The census
also reproduces 133/3/13/108/25 exactly, so it is calibrated against every
published historical number. The two wiki corpora are NOT yet re-measured; the
tree says so explicitly in the docstring, LIMITATIONS and CHANGELOG rather than
carrying probe numbers as fact.

791 tests (was 759), coverage 129/129, 6/6 documented gaps holding. Version
bumped to 0.7.0 across every surface; no tag is set until the measurement lands.
2026-08-12 00:42:44 +02:00
0903785187 release(0.6.1): the ZWJ fix gets a tag a consumer can pin
a59184b fixed a hard-block that reached every emoji-composed document, and
llm-ingestion-okf was told so -- while the fix sat untagged on main. A consumer
cannot pin what has no tag, so the notice was a promise the repo had not kept.

SURFACES MOVED (the eight the 0.5.0 sweep established, plus the ninth verified)

  pyproject.toml           0.6.0 -> 0.6.1
  __init__.py              0.6.0 -> 0.6.1
  CHANGELOG.md             [0.6.1] entry, [Unreleased] reset to "Nothing yet."
  README.md                badge, install tag @v0.6.1
  docs/ADOPTION-BRIEF.md   `v0.6.0` x2, and 727 -> 736 passing
  CLAUDE.md                one clause: ZWJ judged by context, not identity
  SECURITY.md              `0.6.x` -- still true at 0.6.1, verified, not moved
  docs/BRIEF.md            `v0.6 (alpha)` -- likewise
  README.md:36             `v0.6`, alpha -- likewise
  forge description        re-verified THIS release: 178 codepoints, no version
                           claim. Not inherited from 0.6.0's check.

PATCH, NOT MINOR -- and it was asked, not assumed. 6bcb898 made "loosens the
upload door" the criterion for minor, and this loosens it too, so the reading
was put to the operator with the count rather than settled quietly. 0.6.0 was a
policy choice (`<base>` left the active name set by decision); this restores a
contract the module already published, against a class never meant to be
blocked. Measured loosening: 1 document of 1126 across the three FP populations
(reference-corpus 1/389, vendor-harvest 0/187, generated-notes 0/550), against
0.6.0's 25 + 2 + 2. Operator chose patch.

VERIFIED ON THE BUMPED TREE, NOT THE PRE-BUMP ONE

  736 passed; coverage 128/128 recall, 6/6 documented gaps hold
  `git grep '0\.6\.0'` returns provenance only -- LIMITATIONS history, the
    census script, source docstrings, tests, README's pre-0.6.0 numbers
  docs/LIMITATIONS.md: 34 items, README says 34
  __version__ and pyproject agree at 0.6.1
  no tracked file carries a stale current-state version or test count

The sweep tool itself was wrong first: `git grep -E '\b727\b'` returns nothing,
because POSIX ERE has no `\b`. An empty result read as "clean" when the claim
was there. Every sweep above is plain `git grep`.

Still to prove before this is announced: a scratch-venv install at the tag with
the resolved version asserted -- the README line above is only true once v0.6.1
resolves.
2026-08-11 22:30:32 +02:00
6bcb898632 release(0.6.0): the raw-html narrowing ships, and the version claims it already made become true
The narrowing landed in 736f370 and its prose asserted `0.6.0` in thirteen places
-- README's public front page among them -- while every version surface still read
0.5.0. That is the same defect class the last three commits were spent correcting:
a published number no measurement backs. Two ways out, land it or neutralize the
references; operator chose to cut.

SURFACES MOVED (the eight the 0.5.0 sweep established, plus the ninth verified)

  pyproject.toml           0.5.0 -> 0.6.0
  __init__.py              0.5.0 -> 0.6.0
  CHANGELOG.md             [Unreleased] -> [0.6.0], fresh [Unreleased]
  README.md                badge, `Status: v0.6`, install tag @v0.6.0
  SECURITY.md              support window `0.5.x` -> `0.6.x`
  docs/BRIEF.md            `v0.5 (alpha)` -> `v0.6 (alpha)`
  docs/ADOPTION-BRIEF.md   `v0.5.0` x2, and 717 -> 727 passing
  CLAUDE.md                `v0.5 (alpha)` -> `v0.6`, plus what 0.6.0 changed
  forge description        178 codepoints, carries no version claim -- verified,
                           not moved. A surface can be checked and stay still.

Measurement provenance is left alone, as in 0.5.0: `tests/test_disposition.py`'s
"0.5.0 axis separation", `disposition.py` and `calibration.py` docstrings,
LIMITATIONS' 0.5.0 reference, every dated claim in docs/PLAN-v1.md. Bumping those
falsifies the record rather than updating it.

MINOR, NOT PATCH: 0.6.0 loosens the upload door. A document whose only finding was
a doc-relative URL attribute on an inactive tag name, or an attribute-less
`<base />`, now WARNs where it was held -- 25 documents in the reference corpus, 2
in each wiki corpus.

VERIFIED BEFORE COMMITTING, NOT AFTER

  727 passed; coverage 128/128 recall, 6/6 documented gaps hold
  docs/LIMITATIONS.md: 33 items, README says 33
  rawhtml-census PRODUCTION row equals `A + base-url` on all three populations --
    the shipped predicate measured, not a hypothesis about it
  redos-sweep: 0 candidates of 152 patterns
  docs/fp-sweep.py still imports the private names it reaches into
  no tracked file carries a stale current-state version claim

Still to prove before the tag: `git show` over this commit's README, and a clean
clone install at this sha.
2026-08-11 18:01:36 +02:00
f669479777 release(0.5.0): the assessment axis ships, and every version surface moves with it
0.5.0 is the axis separation `de09711` built: `Risk` (assessment) alongside
`Disposition` (action), `Policy.action_map` as the supported override, and the
fail-closed path pinned to both axes. Additive and measured to be so — 717
passing with no test changed, matrix 128/128 with 6/6 documented gaps, the
`PRESET_USER_UPLOAD` grading table unchanged row by row. Plus the field FP
measurement (`d1bff60`) and the 0.3.3 behaviour-change correction (`d3d0928`).

WHY THIS COMMIT TOUCHES EIGHT FILES AND 0.4.0's TOUCHED THREE

0.4.0's release commit updated CHANGELOG, pyproject.toml and __init__.py, and
deferred README deliberately: the install block should not name a tag before a
clean-venv install had proven it resolved. Sound reasoning, and the proof step
never ran — so tag v0.4.0 permanently advertises v0.3.4. The tag is not moved.
The ordering is.

Sweeping every tracked file for a version claim, instead of ticking the four
surfaces the checklist named, found five more that no release had ever touched:

  SECURITY.md          "pre-1.0 (0.2.x)" — the one with a consequence for an
                       outsider: it named a support window two minor lines
                       behind the code.
  README.md            "**Status:** v0.3" — the front page, stale since 0.4.0.
  docs/BRIEF.md        "v0.2 (alpha)" — stale since 0.3.0.
  CLAUDE.md            "v0.2 (alpha)" and "12 moduler" where src/ has 15.
  docs/ADOPTION-BRIEF  "703 passing" where the suite is at 717.

Measurement provenance is deliberately left alone: "New in v0.4.0", "verified
identical on 0.2.0 and 0.3.1", "measured against the v0.3.1 tag", every
"post-0.4.0 tree" in LIMITATIONS. Bumping those falsifies the record instead of
updating it, which is why this cannot be a sed sweep — the surfaces have to be
sorted into current-state and provenance before a single edit.

Found because llm-ingestion-okf took our report of this defect class as a
hypothesis about their own repo, measured it, found a worse instance on their
public front page, and sent back the generalization: writing down a trap is not
the same as applying it.

VERIFIED BEFORE COMMITTING, NOT AFTER

  717 passed; coverage 128/128 recall, 6/6 documented gaps hold
  docs/LIMITATIONS.md: 33 items, README says 33
  fp-sweep reproduced all three published numbers exactly on the bumped tree —
    vendor-harvest 98/185 (53.0%), generated-notes 88/547 (16.1%),
    reference-corpus 133/389 (34.2%) — and self-docs runs clean, so the
    untested script survived the bump it imports names from
  forge description: 178 codepoints, under the 180 cap
  no tracked file carries a stale current-state version claim

Still to prove before the tag: a clean-venv install from this commit's sha, and
`git show <sha>` over the README. The install proves the package builds; only
the grep proves the text the tag will carry is right. That second check is the
one the old ordering could not perform, because by then the tag existed.
2026-08-11 06:42:56 +02:00
3c56d50e05 docs(readme,adoption-brief): advertise v0.4.0, now that it installs
The tag was pushed first and installed into a clean venv before this commit:
0.4.0 resolves from the forge, both new caps fire, and the transform raise is
present. Only then does the install block point at it — a README that advertises
a tag nobody has resolved is how an install line goes stale without anyone
noticing.

The adoption brief was three releases behind (`v0.2`, 126 classes, 4 gaps, 522
tests). Re-measured rather than incremented: 128/128, 6/6, 703 passing.
2026-08-10 14:51:11 +02:00
0bf07295c2 docs(readme,security,contributing): meet the repo standard — 0 ERROR
The per-repo gate flagged four ERRORs and two WARNs. Fixed, in the order the
work actually gets done in:

MISSING
- `## Honest limitations` -> `## Known limitations`, `## Out-of-scope
  (documented boundary)` -> `## Non-goals`. Both sections existed under names
  no reader or agent scans for; the contract wants predictable top-level
  headings. Pointers followed: the in-README anchor, SECURITY.md's out-of-scope
  preamble, CONTRIBUTING.md's scope section, and the consumer-facing
  docs/ADOPTION-BRIEF.md. Historical records (CHANGELOG, docs/PLAN.md,
  docs/OKF-INGESTION-BRIEF.md) keep the name they were written with.

WEAKENING
- README now opens with one line identical to the forge description, above the
  badges. That is the only place a machine can check description == README.
- Forge description shortened 207 -> 178 codepoints (bound 180), and the same
  string written to pyproject's `description` so the fourth copy cannot drift.

The tests badge is dropped, not updated. `tests-699_passing` as a static image
is a claim dressed as evidence: there is no CI runner on this forge, so nothing
verifies it. Replaced with the honest substitute in Install — the single command
that runs the suite from a clean clone, stated together with the fact that
nothing runs it automatically.

Two WARNs deliberately left standing:
- H1 `# llm-ingestion-guard` != repo name. The register itself records
  llm-ingestion-guard as "a package, not a repo"; the H1 names what you pip
  install. Renaming the repo is the operator's call, not this commit's.
- The `Status` badge trips the same claim-badge regex, but `alpha` asserts
  maturity, not a run — the same reason version/license/platform are exempt.
  Measured false positive in the gate's classifier, reported upstream.
2026-08-03 21:59:07 +02:00
a1f3fe1983 docs(adoption): reusable consumer adoption brief for OKF second-brain repos
Self-contained brief a consumer repo can plan an inclusion from: what the
guard is (write-time, not query-time), the two bookends + 8-step contract,
the shipped OKF adapter (import_bundle mode-b, per-concept gates), how to
verify (coverage matrix -> 126 classes), how to depend (stdlib-only core),
and a planning checklist for WHEN/WHERE to wire it (untrusted boundary, not
first-party onboarding). Every claim verified against v0.2 code.
2026-07-15 12:35:09 +02:00