02 — Governed Object Contract Hardening (Branch B) (2026-06-01)
02 — Governed Object Contract Hardening (Branch B)
Reviews decision-pack doc 02 (
…/02-governed-object-contract.md). Adversarial. No schema change.
B0. What the pack says (summary)
Doc 02 defines a governed object as anything affecting truth or authority (the test is "effect on truth or authority, not table-ness"); catalogues ~24 object types (§2.1.1, extensible incl. future_object_type); defines an ~18-link governance set resolved relationally (not as per-table columns — no owner_gov_code column anywhere, ownership is relational); grades minimum required links by 7 object classes (§2.3); and defines covered = valid owner path AND risk-required links (§2.4). It explicitly avoids requiring 18 links for every object (the mission's key warning) and resolves links from existing substrate.
Overall: the relational-resolution stance and risk-grading are exactly right and the strongest part of the pack. Three defects: a missing non-governed class (mirrors A2), an internal contradiction on exceptions (B5), and an under-specified inheritance rule that can hide child gaps (B4).
B1 — No Class 0 (non-governed) in the object-class table
- Original: §2.3's class table begins at "Low-risk read-only." Mission §5 requires category "1. Non-governed ephemeral artifact." §2.1.1 catalogues only governed types.
- Gap: there is no row, and no membership test, for objects that are out of governance entirely. Without it, the scanner has nowhere to put personal pins / UI prefs / session state / comments, so it either over-scans them (noise, A2) or silently drops them (unaudited exclusion — also bad).
- Hardened wording: add Class 0 — Non-governed ephemeral/personal artifact as the first row, with link requirement = none, and a membership test = the §1.0-SCOPE shared-truth-reachability test (doc 01 A2). State that Class-0 membership is decided by the L1 source's classification rule, not per object, and that the set of Class-0 sources is itself a COUNCIL-owned list (so the exclusion is governed, not silent).
- Acceptance test: every object the scanner sees resolves to exactly one class including Class 0; the count of Class-0-excluded objects is reported (auditable), never silently dropped.
- Open question: none (see OQ-A2 for the shareable-personal edge).
B2 — Convert §2.3 ✅/— matrix into named coverage profiles (operationalization)
- Original: §2.3 is an 8-column ✅/— matrix over 7 classes. It correctly grades by risk (answering the mission's "don't require 18 links for everything").
- Misimplementation risk: a ✅/— matrix is not directly a scanner predicate; an implementer must re-derive "which links are mandatory for class X" from a table, and will drift. The pack's own anti-pattern (
GOVERNANCE_SCHEMA_DRIFT) predicts this. - Hardened wording: name each row a coverage profile the scanner evaluates as a checklist, and bind each §2.1.1 object type to a default profile (data, not code — a column on the L1 source-inventory row):
Profile Mandatory links Default object types PROFILE-EPHEMERAL(Class 0)none personal pin, UI pref, comment, session state PROFILE-RO(low-risk read-only)owner, law_ref, audit(scan-log) read-only view, report pivot PROFILE-POLICYowner, capability, law_ref, approval(medium), audit, rollback(retire), issue-on-conflict grouping/threshold/pin/label/phantom policy PROFILE-DOTowner, capability, law_ref(Đ35), approval(per risk), audit(vps_deploy_log), rollback, paired_dot, issue mutating tier-B DOT, cleanup workflow PROFILE-SURFACEowner(MOUT), capability, law_ref(Đ28), approval-or-exception, audit, rollback, issue(coverage) route/API, render shell, design_template PROFILE-LAWowner(COUNCIL), law_ref(self), approval(high, Đ32 quorum), audit(minute), rollback(amend) law/normative/policy-change PROFILE-EXCEPTIONowner, law_ref(Đ33§13), approval+TTL, replacement_plan, audit, rollback, issue-on-overdue direct_pg_exception, temporary bypass - Acceptance test: every L1 source row carries a
default_profile; the scanner'scoveredpredicate is "all profile-mandatory links resolve"; adding a new object type = adding an L1 row with a profile (no code). - Open question: OQ-B2 — should
PROFILE-SURFACEfor a read-only route be allowed to droprollback? (A read-only GET has nothing to roll back.) Likely yes → introducePROFILE-SURFACE-RO.
B3 — design_ref "by convention" is unenforceable → contradicts computed-not-remembered
- Original: §2.2 resolves
design_reffrom "KBknowledge/dev/design/**(relational, by convention)." Gap typeDESIGN_REF_GAP(doc 03 §3.2) depends on it. - Contradiction: "by convention" = memory/convention, the exact dependence the pack's §4.5 ("computed, never remembered") forbids. There is no registry linking an object to its design doc, so
DESIGN_REF_GAPcannot be reliably computed; it would be guesswork → false positives/negatives. - Hardened wording: demote
design_refto an INFO-only, non-gating descriptive link (doc 03/07 already grade itinfo), and stop claiming it is "resolved." State plainly: "design_ref is advisory; it is not computed from a registry today, so DESIGN_REF_GAP is reported only where an explicit object→design link exists, and never gates." Optionally propose (future) a lightweightdesign_link(object_type, object_ref, design_path)registry to make it computable — but do not pretend it is computable now. - Acceptance test:
DESIGN_REF_GAPis never on the production gate; the pack does not claim design_ref is auto-resolved. - Open question: OQ-B3 — build a
design_linkregistry, or accept design_ref as permanently advisory?
B4 — Inheritance can hide a child's own-risk gaps (the mission's explicit worry)
- Original: §2.4 path 6: an inherited owner from a parent counts as a valid owner path "where a law explicitly allows inheritance." Doc 04 §4.4 adds DOT-never-inherits and bounded-depth.
- Loophole (mission §9 + red-team #10): inheritance is the scale lever and the biggest hiding place. A child policy object can inherit its parent registry's owner and thereby be marked
covered, even though the child has its own risk class requiring its own approval/audit/rollback that the parent's links don't satisfy. Example: a grouping-policy row inside a covered registry inherits the registry's owner and looks covered, but its policy change still needs its own APR — which inheritance does not provide. - Hardened wording (critical anti-hiding rule): "Inheritance resolves the owner-path link ONLY. Risk-required links (capability, approval-path, audit, rollback, dot_authority) are never inherited — they must resolve on the object itself per its own coverage profile. A child whose own profile mandates an approval path is a
governance-orphanuntil that path exists, regardless of a covered parent." Inheritance covers who ultimately owns it, never how its own changes are governed. - Acceptance test: a synthetic policy row inserted under a covered registry, with no own APR mapping, is reported as
APPROVAL_PATH_GAP(not masked as covered). - Open question: none.
B5 — Internal contradiction: "approved exception" is listed as a valid OWNER path
- Original: §2.4 and doc 04 §4.3 both list, as a valid owner path: "an approved exception (an
approval_requestsrow, action-type for exception, with TTL)." BUT doc 04 §4.2 listsapproved_exceptions(S)as a separate term of the accounting identity, distinct fromcovered(S). - Contradiction: an exception cannot be both (a) a way to be
coveredAND (b) a separate non-covered term. If an exception-object resolves a "valid owner path," the coverage view marks itcovered, so it lands incovered(S)— but the identity expects it inapproved_exceptions(S). The same object double-classifies; the identity can silently mis-balance, and worse, an exception (which has no owner at all) gets counted as owned. - Hardened wording: remove "approved exception" from the valid-owner-path list in §2.4 and §4.3. An exception means "governance-orphan by shape, but ratified and TTL-bounded" — it belongs only in the
approved_exceptionsterm. An object covered solely by an exception is NOTcovered; it isapproved_exception. (This aligns §2.4/§4.3 to §4.2, which is the correct definition.) - Acceptance test: an object whose only governance link is an approved exception is counted in
approved_exceptions, never incovered; the identitytotal = covered + orphans + exceptions + retiredhas no object in two terms. - Open question: none — this is a clean internal contradiction to resolve in favor of §4.2.
B6 — "18-link set" header invites the very over-stamping the mission warns against
- Original: §2.2 presents the full link table uniformly; doc 13 (pack self-review) calls it "the 18-link set."
- Risk: naming a fixed "18-link set" invites an implementer to treat all 18 as the target for every object (the mission: "Do not require the same 18 links for every object"). §2.3 grades correctly, but the framing fights it.
- Hardened wording: rename §2.2 to "the link vocabulary (resolved per profile)"; state that no object requires all links and that mandatory links are only those in its profile (B2). The 18 links are a vocabulary, not a checklist.
- Acceptance test: documentation never states a per-object link count > its profile's mandatory set.
- Open question: none.
B7 — §5.4-EXT object-edge is "the only structural change" but ownership-resolution today still needs it for non-law objects
- Original: §2.2 design rule: do not add
owner_gov_codecolumns; resolve relationally; "the only structural extension contemplated … is enabling agency→object edges (§5.4-EXT, deferred)." - Trap (links to I2): the relational model can resolve ownership for objects whose owner is expressible as agency→law→domain→object via
law_jurisdiction+ inheritance. But an object with no governing law domain (e.g. a bespoke route, a standalone policy table, a Direct-PG adapter) has no law to hang ownership on, and §5.4 CHECK blocks agency→object. So for those objects, ownership is currently inexpressible — not merely "cleaner later." The pack frames §5.4-EXT as optional; for a subset of objects it is required to ever becovered. - Hardened wording: distinguish two object populations: (i) law-domain-anchored objects (covered today via
law_jurisdiction+inheritance) and (ii) law-orphan objects (route/adapter/standalone-policy with no law domain) — for (ii), ownership is inexpressible until §5.4-EXT or agovernance_object_ownershiptable exists, so they areOWNER_GAPby construction, not by neglect. State this honestly so the gate baseline is understood (links to doc 06 F1, doc 09 I2). - Acceptance test: the coverage view, for a law-orphan object, returns
owner_path_kind = NONE (inexpressible)distinct fromowner_path_kind = MISSING (assignable)— so council sees "needs structure" vs "needs assignment." - Open question: OQ-B7 — bring §5.4-EXT (or
governance_object_ownership) forward out of "deferred," since the apply DOT (I2) and law-orphan coverage both depend on it. This is a candidate Tier-1 reclassification.
B-summary — Branch B verdict
| ID | Severity | Type | Disposition |
|---|---|---|---|
| B1 | high | gap | add Class 0 (mirrors A2) |
| B2 | medium | operationalization | named coverage profiles + L1 default_profile |
| B3 | medium | contradiction | demote design_ref to advisory/non-gating |
| B4 | high | loophole | inheritance covers owner-link only — anti-hiding rule |
| B5 | high | internal contradiction | exception is NOT an owner path; only the separate term |
| B6 | low | framing | "link vocabulary," not "18-link set" |
| B7 | high | trap | law-orphan objects inexpressible until §5.4-EXT → reclassify as Tier-1 |
B5 is a clean contradiction; B4 and B7 are the load-bearing scale/coverage correctness fixes.