feat(consume): derive trust_tier, and withhold what cannot be tiered
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
1ec3f5289c
commit
aa33555208
13 changed files with 278 additions and 0 deletions
|
|
@ -304,3 +304,102 @@ def _body(path: Path) -> str:
|
|||
if line.strip() == "---":
|
||||
return "\n".join(lines[offset:])
|
||||
return text
|
||||
|
||||
|
||||
#: SS 6.2, from SPEC SS 5.3, lowest to highest. Imported from the checker's own
|
||||
#: constant would be circular (the checker is a separate tool); spelled here and
|
||||
#: held to the checker's set by the anti-drift test.
|
||||
TrustTier = Literal["unverified", "machine-confirmed", "human-reviewed"]
|
||||
|
||||
#: The prefix that makes an actor a person. A PREFIX, never a substring: an
|
||||
#: actor id `bot/human:2` contains the literal and is a machine, and promoting
|
||||
#: it would be fabricated provenance produced by a matching bug.
|
||||
HUMAN_ACTOR_PREFIX = "human:"
|
||||
|
||||
|
||||
def trust_tier(verified_raw: str | None) -> TrustTier | None:
|
||||
"""The tier `verified` implies, or `None` when the value cannot be read.
|
||||
|
||||
Three inputs, three different facts, and collapsing any two is the defect:
|
||||
|
||||
- `None` -- the key is ABSENT. SS 6.2: no `verified` key means
|
||||
`unverified`, and SS 6.3 forbids rejecting a concept for it.
|
||||
- `""` -- the key is PRESENT and this library cannot read it. Measured
|
||||
2026-09-07: the line-oriented `parse_frontmatter` returns `''` for a
|
||||
block-form value and the full string for a flow one, so the two are
|
||||
distinguishable. Returning `None` here is not a tier; the caller withholds
|
||||
the concept under a named rule. Emitting `unverified` instead would assert
|
||||
a fact nobody measured, which is exactly what SS 6.4 forbids.
|
||||
- a flow value -- decoded, and the tier follows the actors.
|
||||
"""
|
||||
if verified_raw is None:
|
||||
return "unverified"
|
||||
if not verified_raw.strip():
|
||||
return None
|
||||
entries = _parse_flow_mappings(verified_raw)
|
||||
if entries is None:
|
||||
return None
|
||||
actors: list[str] = []
|
||||
for entry in entries:
|
||||
actor = entry.get("by")
|
||||
if not actor:
|
||||
raise ConsumeError(
|
||||
f"a `verified` entry names no `by` actor ({verified_raw!r}); "
|
||||
"refusing to tier it, because the tier IS a claim about who "
|
||||
"checked and there is nobody to name",
|
||||
code="verified_actorless",
|
||||
)
|
||||
actors.append(actor)
|
||||
if not actors:
|
||||
return None
|
||||
if any(actor.startswith(HUMAN_ACTOR_PREFIX) for actor in actors):
|
||||
return "human-reviewed"
|
||||
return "machine-confirmed"
|
||||
|
||||
|
||||
def _parse_flow_mappings(value: str) -> list[dict[str, str]] | None:
|
||||
"""A YAML flow sequence of flow mappings, or `None` when it is not one.
|
||||
|
||||
Modelled on `structure._parse_flow_list` -- flow form only, because this
|
||||
library's standing rule is that a value it can write is a value it can read
|
||||
back. Not reused: that one splits on every comma, and `{ by: x, at: y }`
|
||||
carries a comma INSIDE a mapping, so it would return four fragments where
|
||||
there are two pairs.
|
||||
"""
|
||||
stripped = value.strip()
|
||||
if not (stripped.startswith("[") and stripped.endswith("]")):
|
||||
return None
|
||||
body = stripped[1:-1].strip()
|
||||
if not body:
|
||||
return []
|
||||
mappings: list[dict[str, str]] = []
|
||||
for chunk in _split_top_level(body, "{", "}"):
|
||||
item = chunk.strip()
|
||||
if not (item.startswith("{") and item.endswith("}")):
|
||||
return None
|
||||
pairs: dict[str, str] = {}
|
||||
for field in item[1:-1].split(","):
|
||||
key, separator, raw = field.partition(":")
|
||||
if separator:
|
||||
pairs[key.strip()] = raw.strip()
|
||||
mappings.append(pairs)
|
||||
return mappings
|
||||
|
||||
|
||||
def _split_top_level(body: str, opener: str, closer: str) -> list[str]:
|
||||
"""Split on commas that are not inside a `{...}`."""
|
||||
parts: list[str] = []
|
||||
depth = 0
|
||||
current: list[str] = []
|
||||
for character in body:
|
||||
if character == opener:
|
||||
depth += 1
|
||||
elif character == closer:
|
||||
depth -= 1
|
||||
if character == "," and depth == 0:
|
||||
parts.append("".join(current))
|
||||
current = []
|
||||
continue
|
||||
current.append(character)
|
||||
parts.append("".join(current))
|
||||
return [part for part in parts if part.strip()]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue