1
0
Fork 0

feat(okf): the mapping class gets one expressible form, typed and allowlisted

OKF v0.2 writes its whole trust and provenance layer as mappings, and T2 gave
the mapping class no expressible form. A consumer measured 0 of 53 upstream
concepts through the gate on 0.3.4, 1.0.0 and 1.1.0. That was a contract
collision, not a calibration setting: SPEC.md @ 62432a09 uses flow mappings in
its own 5.1/5.2 examples, and 11 carries a hard MUST for consumers ("MUST treat
a bare `verified` mapping as a one-element list") that presupposes they parse.

Admitted: a flow mapping, as a value or as a block-list item, whose every key is
on a nine-name allowlist and whose every leaf is a plain scalar run through the
unchanged dangerous-value and mapping-construct predicates. The form is safe
because the allowlist inspects every key -- the blanket refusal was the
enforcement, not the point.

Refused, each on its own rule and ground-truthed against PyYAML 6.0.3: a key off
the allowlist, a nested collection, a quoted leaf, a duplicate key, an empty or
unclosed mapping, trailing junk, and {a:b} (which PyYAML reads as the KEY a:b).
A refused mapping still raises rather than degrading into a string, so the 1.1.0
type-confusion defect is not reopened, and the block, dotted and inline-colon
routes still raise.

`resource` is deliberately off the allowlist though SPEC.md 5.1 names it inside
a `sources` entry: it is a pointer rather than a label and the only key T3
exists for, so admitting it would let `executor: { resource: skills/run.md }`
carry an executable-code pointer through in typed clothes -- the door-C route
closed in 1.1.0. It costs nothing today, because the conformant carrier for
sources[].resource is a block sequence of block mappings, which stays refused.

Mapping leaves are scanned like every other frontmatter value (T1). Coverage
matrix 130/130 (new row: the off-allowlist key). No exported surface, detector
behaviour or calibration changed.
This commit is contained in:
Kjell Tore Guttormsen 2026-08-21 21:04:51 +02:00
commit 58704834b6
7 changed files with 432 additions and 43 deletions

View file

@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added — OKF frontmatter can express one mapping form: typed and allowlisted
`okf.parse_frontmatter` gave the mapping *class* no expressible form at all. OKF
v0.2 writes its whole trust and provenance layer as mappings — SPEC.md @
`62432a09` uses flow form in its own §5.1/§5.2 examples, and §11 carries a hard
MUST for consumers ("MUST treat a bare `verified` mapping as a one-element
list") that presupposes they parse. A consumer measured **0 of 53** upstream
concepts through the gate on 0.3.4, 1.0.0 and 1.1.0. That was a contract
collision, not a calibration setting: no threshold would have moved it.
Admitted now: a flow mapping (`generated: { by: x, at: y }`), as a value or as a
block-list item, whose every key is on a nine-name allowlist (`by`, `at`, `from`,
`to`, `id`, `title`, `author`, `usage_count`, `last_modified`) and whose every
leaf is a plain scalar run through the *unchanged* dangerous-value and
mapping-construct predicates.
The form is additive and refusal stays the default. A key off the allowlist, a
nested collection, a quoted leaf, a duplicate key, an empty or unclosed mapping,
and `{a:b}` (which PyYAML 6.0.3 reads as the *key* `a:b`) all raise, and a
refused mapping still raises rather than degrading into a string — the 1.1.0
defect is not reopened. Nested-block (`k:\n sub: v`), dotted (`k.sub: v`) and
inline-second-colon (`k: sub: v`) routes to a mapping still raise, each on its
own rule.
**`resource` is deliberately off the allowlist**, though SPEC.md §5.1 names it
inside a `sources` entry. It is a pointer rather than a label and the only key
T3 exists for: admitting it would let `executor: { resource: skills/run.md }`
carry an executable-code pointer through in typed clothes, which is the door-C
route closed in 1.1.0. It costs nothing today — the conformant carrier for
`sources[].resource` is a block sequence of block mappings, which this form does
not admit either way.
Mapping leaves are scanned like every other frontmatter value (T1), so an
injection parked in `generated: { by: ... }` reaches `scan_output`. Coverage
matrix: 130 classes, up from 129 (the new row is the off-allowlist key).
No exported surface changed; no detector behaviour and no calibration changed.
### Changed — the ReDoS sweep now measures on the same clock as the bounds it justifies
`docs/redos-sweep.py` timed on `time.monotonic()` while every ReDoS bound in the