KB-52A1

02 — Governed Object Contract Hardening (Branch B) (2026-06-01)

13 min read Revision 1
one-roof-governanceclause-hardeningbranch-bgoverned-objectcoverage-profilesmin-linksexception-not-ownerinheritance-anti-hiding2026-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-POLICY owner, capability, law_ref, approval(medium), audit, rollback(retire), issue-on-conflict grouping/threshold/pin/label/phantom policy
    PROFILE-DOT owner, capability, law_ref(Đ35), approval(per risk), audit(vps_deploy_log), rollback, paired_dot, issue mutating tier-B DOT, cleanup workflow
    PROFILE-SURFACE owner(MOUT), capability, law_ref(Đ28), approval-or-exception, audit, rollback, issue(coverage) route/API, render shell, design_template
    PROFILE-LAW owner(COUNCIL), law_ref(self), approval(high, Đ32 quorum), audit(minute), rollback(amend) law/normative/policy-change
    PROFILE-EXCEPTION owner, 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's covered predicate 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-SURFACE for a read-only route be allowed to drop rollback? (A read-only GET has nothing to roll back.) Likely yes → introduce PROFILE-SURFACE-RO.

B3 — design_ref "by convention" is unenforceable → contradicts computed-not-remembered

  • Original: §2.2 resolves design_ref from "KB knowledge/dev/design/** (relational, by convention)." Gap type DESIGN_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_GAP cannot be reliably computed; it would be guesswork → false positives/negatives.
  • Hardened wording: demote design_ref to an INFO-only, non-gating descriptive link (doc 03/07 already grade it info), 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 lightweight design_link(object_type, object_ref, design_path) registry to make it computable — but do not pretend it is computable now.
  • Acceptance test: DESIGN_REF_GAP is never on the production gate; the pack does not claim design_ref is auto-resolved.
  • Open question: OQ-B3 — build a design_link registry, 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-orphan until 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_requests row, action-type for exception, with TTL)." BUT doc 04 §4.2 lists approved_exceptions(S) as a separate term of the accounting identity, distinct from covered(S).
  • Contradiction: an exception cannot be both (a) a way to be covered AND (b) a separate non-covered term. If an exception-object resolves a "valid owner path," the coverage view marks it covered, so it lands in covered(S) — but the identity expects it in approved_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_exceptions term. An object covered solely by an exception is NOT covered; it is approved_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 in covered; the identity total = covered + orphans + exceptions + retired has no object in two terms.
  • Open question: none — this is a clean internal contradiction to resolve in favor of §4.2.

  • 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_code columns; 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 be covered.
  • 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 a governance_object_ownership table exists, so they are OWNER_GAP by 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 from owner_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.

Back to Knowledge Hub knowledge/dev/reports/architecture/one-roof-governance-clause-review-hardening-2026-06-01/02-governed-object-contract-hardening.md