feat(okf-v0.2): D4 — door C surfaces the §10 pointers it imports [skip-docs]

V6, settled by the operator today: import and report, not refuse.

Door C imports the POINTER to executable code and never the code — it
writes concepts verbatim and skips every non-`.md` file. So an imported
Attested Computation can name an `executor`/`attester` resource that did
not arrive, or one that RESOLVES against a file the destination tree
already holds under that path. The second is the outcome worth
surfacing: it looks valid.

Refusing was the plan's leaning and is not what shipped. §14 forbids a
consumer to reject a bundle over a broken cross-link and does not settle
whether `executor.resource` is one; §10.5 asks a consumer to surface
rather than silently drop. Reporting honours the second without testing
the first, and leaves the door's one invariant — verbatim bytes — alone.

`ImportResult.unverified_references` is an advisory over the merged set,
not a fifth bucket: every concept it names has already merged, the
bytes are unchanged, and a refused concept is never named (there is no
imported pointer to check).

The report is at KEY level, and that is a measured limit rather than a
choice. Resolving the resource means reading `executor.resource` — the
value the line-oriented parser cannot recover in either canonical form:
a block mapping flattens and collides, a flow mapping stays one opaque
string. A resource-level report would be empty or wrong on exactly the
forms upstream writes. Precision arrives with the structured reader
(D1b); the key-level signal is robust in both forms today.

[skip-docs] is on the CLAUDE.md half only: README carries the new
public surface (`unverified_references`), and the invariant this work
put in CLAUDE.md — flow form, never block — landed with the previous
commit and needs no restatement.

584 tests, mypy --strict clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKKMwi7e7PVHoFW6dJK5XP
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 21:44:27 +02:00
commit c6d64c3fd6
4 changed files with 188 additions and 0 deletions

View file

@ -61,6 +61,7 @@ from .importer import (
ImportResult,
MergedConcept,
RefusedConcept,
UnverifiedReference,
import_bundle,
)
from .manifest import (
@ -102,6 +103,7 @@ __all__ = [
"RenderError",
"SourceError",
"SqlSource",
"UnverifiedReference",
"extract_text",
"import_bundle",
"load_manifest",

View file

@ -36,6 +36,7 @@ from .extract import decode_text
from .materialize import (
check_filename_length,
link_in_index,
parse_frontmatter,
reduce_to_id_grammar,
validate_ingested_at,
write_bytes,
@ -57,6 +58,12 @@ from .profiles import DEFAULT
_DISPOSITION_MERGE = "warn"
_DISPOSITION_QUARANTINE = "quarantine_review"
# The OKF §10.2 frontmatter keys that point at executable code. Alphabetical
# because the report has to be deterministic and these two carry no precedence
# over each other — `executor` runs the computation, `attester` checks the
# receipt, and a concept naming either has imported a pointer we cannot follow.
_ATTESTED_POINTERS = ("attester", "executor")
# The guard's Origin/Channel vocabularies, likewise by value. Validated here
# because `trust_for` compares by enum IDENTITY: a value outside these sets
# would reach the guard as a plain string, miss the identity check, and be
@ -147,6 +154,35 @@ class FailedConcept:
error: IngestError
@dataclass(frozen=True)
class UnverifiedReference:
"""A merged concept declares an OKF §10 pointer to executable code.
Door C imports the POINTER and never the code: it writes concepts verbatim
and skips every non-`.md` file in the source tree. So an imported Attested
Computation can name an `executor` or `attester` resource that did not
arrive with it and if the destination tree already holds, or later gains,
a file at that path, the reference does not dangle, it RESOLVES to code the
sender never shipped. That second outcome is the one worth surfacing: it
looks valid.
Reported, not refused. §14 forbids a consumer to reject a bundle over a
broken cross-link and does not settle whether `executor.resource` is one,
while §10.5 asks a consumer to surface rather than silently drop a failing
attestation. Reporting honours the second without testing the first.
`key` names the pointer (`executor` or `attester`); the RESOURCE it names is
deliberately absent. Recovering it means reading a value this library's
line-oriented parser cannot represent a nested block mapping is flattened
and a flow mapping stays one opaque string so a resource-level report
would be empty or wrong on exactly the canonical forms. It arrives with the
structured reader.
"""
concept_path: str
key: str
@dataclass(frozen=True)
class ImportResult:
"""Every concept's outcome, in sorted concept-path order.
@ -156,6 +192,10 @@ class ImportResult:
would show up as a missing entry rather than as nothing at all. `log` is
the guard's log body and `ingested_at` the run's explicit timestamp the
caller persists them if their reserved-file policy says to.
`unverified_references` is not a fifth bucket and does not partition
anything: every concept it names has already merged. It is an advisory over
the merged set.
"""
merged: tuple[MergedConcept, ...]
@ -164,6 +204,7 @@ class ImportResult:
failed: tuple[FailedConcept, ...]
log: str
ingested_at: str
unverified_references: tuple[UnverifiedReference, ...] = ()
def import_slug(concept_path: str) -> str:
@ -292,6 +333,7 @@ def import_bundle(
merged: list[MergedConcept] = []
quarantined: list[RefusedConcept] = []
rejected: list[RefusedConcept] = []
unverified: list[UnverifiedReference] = []
log = ""
if documents:
@ -406,6 +448,19 @@ def import_bundle(
MergedConcept(concept_path=concept_path, path=path, reasons=verdict.reasons)
)
# §10 pointers, surfaced over what actually landed. Read AFTER the merge
# decision and never before it: this door's tolerance is structural —
# it writes the sender's bytes verbatim and judges no shape — and a
# reader consulted earlier is precisely what would start refusing
# senders on form. Read from the written file rather than from the
# staged text, so the report describes the concept that exists.
unverified.extend(
UnverifiedReference(concept_path=entry.concept_path, key=key)
for entry in merged
for key in _ATTESTED_POINTERS
if key in parse_frontmatter(entry.path)
)
# §6 index — the last disk mutation, and only when something merged.
if merged:
index_path = bundle / DEFAULT.index.name
@ -421,4 +476,5 @@ def import_bundle(
failed=tuple(sorted(failed, key=lambda entry: entry.concept_path)),
log=log,
ingested_at=ingested_at,
unverified_references=tuple(unverified),
)