"""The bundle contract as configuration (Phase 3). What a valid bundle looks like — which concept types exist, which frontmatter keys are emitted and in which order, which filename namespaces the doors own, and what an index line looks like — is a profile, not a set of constants scattered across the doors. `DEFAULT` is exactly the ingest-spec v1 + Phase 2 contract, so nothing observable changes for a caller that never mentions a profile; the golden fixtures are the byte-level proof. Two things deliberately do NOT live here. Security is the guard's, always: no disposition, origin or channel vocabulary belongs on a profile. And the reserved `verdict` layer is a spec invariant rather than profile config — it is refused at construction, so a profile admitting it cannot be built, let alone passed to a door. The `timestamp`/`generated` pair is refused the same way, and for the same reason: both are things a profile must not be able to express. Profiles are constructed in code. Config-file loading and inheritance chains are extension points, not v1 (settled with the operator at phase start). """ from __future__ import annotations import re from collections.abc import Collection, Mapping, Sequence from dataclasses import dataclass, field, replace # The one layer no profile may admit (ingest-spec §3): the promotion gate is # the only path into it. Compared case-insensitively, as both doors already do. RESERVED_OKF_TYPE = "verdict" # The one key PAIR no profile may name (OKF §13.1): `timestamp` is readable as # a legacy stand-in for `generated.at` only while `generated` is ABSENT, so a # schema able to name both can describe a document that has neither. Stated as # key names rather than as a judgement on values because every `generated` a # schema can express today is a scalar (`_is_legal_value`) and therefore # malformed as a v0.2 mapping — naming both IS the hazard here. When a value # model can express a well-formed `generated`, this narrows with it. # "Naming" spans EVERY field that puts a key in the schema's namespace — `order`, # `required`, `allowed` and `nullable` alike. A field left out of that union is a # hole in the gate, not a narrower gate: the namespace is open by default, so a # key named only by `nullable` is admitted just as surely as an emitted one. _TIMESTAMP_FALLBACK_PAIR = frozenset({"timestamp", "generated"}) @dataclass(frozen=True) class TypeRejection: """Why a profile refuses an `okf_type`, for the door to frame and raise. The policy does not raise: Door A refuses with `ManifestError` and Door B with `MaterializationError`, so the refusal has to be reported rather than thrown. `reason` completes the sentence "