Elnor Repo Reader

DOC81_consolidated_red_team_final_review_AUDITED.md

Memory Rebuild Docs/Stage_6_Charters/E1_E2_DOC81_Scope_Policy/Reviews/DOC81_consolidated_red_team_final_review_AUDITED.md

Short text page e5fbc8d6d07d. Generated 2026-06-09T01:23:58.539Z from commit dbaa25962edc11ab30e8d4ca1715f9ae5bf77331. Worktree: clean.

Open readable HTML page · Open raw txt · Open path URL

ELNOR REPO READER TEXT MIRROR
Original path: Memory Rebuild Docs/Stage_6_Charters/E1_E2_DOC81_Scope_Policy/Reviews/DOC81_consolidated_red_team_final_review_AUDITED.md
Source repo: /Users/OpenClaw1/Elnor/Elnor Specs
Git branch: main
Git commit: dbaa25962edc11ab30e8d4ca1715f9ae5bf77331
Generated: 2026-06-09T01:23:58.539Z

---

# ELNOR DOC81 Scope & Policy Charter — Consolidated Red-Team Review and Patch Pack

**Target:** `Memory Rebuild Docs/Stage_6_Charters/E1_E2_DOC81_Scope_Policy/DOC81_Scope_Policy_Charter_Draft.md`  
**Repo:** `wbrody/Elnor-Specs`, branch `main`  
**Reviewer posture:** independent design red-team; substantive review, not fidelity audit  
**Deliverable status:** consolidated final analysis from the full review sequence in this conversation  
**Final verdict:** `DESIGN_REVISION_NEEDED_BEFORE_RATIFICATION`  

## One-line bottom line

DOC81 has the right architecture and should not be rewritten from scratch, but it is not yet ratifiable because too many runtime-critical policy/scope invariants remain prose-backed instead of schema-backed: stamp validity, restamp ceilings, policy freshness, egress destination specificity, disclosure aggregation, confidence/threshold semantics, obligation discharge, relation traversal, source revocation, collection suppression, cache keys, and state transitions all need executable contracts.

---

# 0. Executive summary

DOC81 correctly splits **scope** from **policy**. Scope resolves identity, boundary, relation, population, affinity, and conservative floors. Policy decides capability and disclosure through a dimensional meet. That architecture is right and should be preserved.

The issue is not that DOC81 picked the wrong system boundary. The issue is that DOC81 is currently a good architectural charter with several implementation traps. It often says the correct invariant, adds a lint name, and states that EC enforces it, but it does not always give EC the data structure needed to prove enforcement. Since DOC81 is the Scope & Policy foundation consumed by DOC82, DOC83, DOC84, DOC85, DOC86, DOC87, and EC, this is not acceptable at ratification.

The strongest conclusion after three passes is:

> DOC81 should become a small executable policy/scope calculus: **evaluation context → policy decisions → per-axis meet → disclosure-vector meet → conservatism floor → action predicate → stamp/export/discharge proof**, all keyed by current runtime freshness.

The present draft has many of these concepts in prose, but not enough executable schema. A targeted schema patch round should be enough; this is not a return-to-zero.

## Current grade

| Dimension | Grade | Rationale |
|---|---:|---|
| Architecture direction | A− | Correct DOC80/DOC81 split, scope/policy separation, DOC81→DOC86 export direction, dimensional policy model. |
| Executable policy calculus | B− / C+ | Meet is directionally right but under-keyed, malformed-axis handling is unsafe, floor mapping absent, egress specificity under-specified. |
| Schema completeness | B− | Many strong objects exist; several are missing proof/discharge/freshness/context fields. |
| Quantitative/function correctness | C+ | Thresholds, confidence, aggregation, cache keys, quotas, and state transitions are mostly named, not fully specified. |
| Runtime freshness / cache safety | C | `policy_generation_id` alone is not enough; EC effective state and compiled evaluator hash must be part of cache/stamp validity. |
| UI/export seam | C+ | Correct direction, insufficient payload for DOC86 to render without recomputing or guessing. |
| Overall | B / B+ | Strong design skeleton; not yet ratifiable without schema-level patches. |

## Ratification posture

**Do not ratify as-is.** Require one targeted revision round. The revision should be schema-level, not just explanatory prose. After the patch bundle below, DOC81 should reach A−/A. With property-style fixtures and complete lints, it can reach A/A+.

---

# 1. Source basis and review frame

The review sequence followed the committed DOC81 design-review commission. The review pack included, in substance:

- `E1_E2_Design_Review_Prompt.md`
- `DOC81_Scope_Policy_Charter_Draft.md`
- `Charter_Opening_Brief.md`
- `Charter_Input_Deck.md`
- `Reviews/E1_E2_Fidelity_Audit_CODEX.md`
- `E1_E2_CODEX_Audit_Fix_Record.md`
- `E0_DOC80_Core/DOC80_Core_Charter_Draft.md`
- `DOC80_Skeletal_Target_Baseline.md`
- `DOC80_Owner_Map.md`
- `DOC80_Import_Graph.md`
- `STAGE_6_CHARTER_INPUT_INDEX.md`
- `Architect_Decision_Queue.md`
- Round D Policy / Scope / UI micro-patch material
- OPA / PropA carried-row bodies
- EC Core Addendum A V3.3 compiled operative material

No repo files were modified in this conversation. This document consolidates the final settled findings and patch recommendations from the review passes.

**Self-audit correction applied:** this audited copy corrects the E0 egress vocabulary used in the consolidated patch pack. The E0 outbound destination enum is the PropA/E0 eight-value set (`same_machine_local_runtime`, `local_file_export`, `local_network_peer`, `firm_server`, `remote_peer`, `cloud_api`, `email_outbound`, `agent_messaging`). `unknown_destination` remains a DOC81 relation/fail-closed state, not an E0 outbound destination class. `background_non_interactive` is the E0 interaction-mode value, not `background`. No substantive finding was removed.

---

# 2. Final ranked patch queue

These are the changes I would make before ratification, in priority order.

1. **Triple-bind `PolicyStamp` / `PolicyStampScope` to action/destination-specific `EffectiveMemoryPolicyRef`s.** A stamp currently has one policy ref but can claim multiple actions/destinations.
2. **Add `PolicyRuntimeFreshnessKey` with `effective_state_generation_id` and compiled evaluator hash.** `policy_generation_id` alone cannot invalidate cached policy after runtime toggle/incognito/application changes.
3. **Replace the meet pseudo-code with `meet_v2`: context-keyed, malformed-axis conservative, destination-specific egress, disclosure-vector meet.**
4. **Add required `PolicyEvaluationContext` to every `MemoryPolicyDecision`.** Include exposure context, model class, client kind, interaction mode, principal, surface, destination, and bounded user instruction.
5. **Add `PolicyCeilingSnapshot` and root restamp-chain integrity.** Restamp ceilings must be axis-comparable and root-bound.
6. **Make `PolicyStampRestamp` a discriminated union.** Blocked restamps must not carry an authorizing new effective policy ref.
7. **Add `ConservatismFloorEffect` table.** Scope floors need deterministic axis effects.
8. **Add `DestinationPolicyCrosswalk`.** `relation_to_destination` must never substitute for `E0OutboundDestinationClass`.
9. **Add `DisclosurePermissionVector`; derive scalar `DisclosureClass`.** Scalar disclosure alone cannot safely aggregate existence/count/title/source/reason permissions.
10. **Add `ThresholdEvaluation`, confidence breakdowns, and pairwise equivalence confidence.** Thresholds need comparator/equality/hysteresis/version semantics.
11. **Add `PolicyObligationConflict` + `PolicyObligationDischarge`; expand PropA obligation kinds.** Obligations must be conflict-checked and proof-discharged before movement.
12. **Close relation traversal with default-deny registry, budget, and cycle handling.** Remove `| string` escape hatches.
13. **Add `CascadingSourceInvalidationRun` with per-plane idempotent state and receipts.** Five-plane cascade is correct but needs execution proof.
14. **Add destructive-job legal-hold registry and selector semantics.** Future destructive jobs must register the legal-hold gate.
15. **Patch collection suppression with ambiguity, rank aggregation, and existing-material backfill for `exclude`.**
16. **Patch source exclusion with input/output-cycle fix and derived-artifact closure.**
17. **Expand DOC81→DOC86 `PolicyUIExport` to per-action/per-destination eligibility, safe-label constraints, restamp/disambiguation metadata, and freshness.**
18. **Add action-closure and action-permission predicate tables.** Terminal actions must imply prerequisite policy checks.
19. **Add cache keys for scope/policy/UI exports.** Include source/classification/population/safe-label/effective-state generations.
20. **Add fan-out quota envelopes for restamp, cascade, relation traversal, legal-hold scans, and collection backfill.**

---

# 3. Consolidated finding register

## Blocking findings

| ID | Type | Severity | Finding | Fix package |
|---|---|---:|---|---|
| B1 | BUG | Blocker | `PolicyStamp` points to one `EffectiveMemoryPolicyRef` while `PolicyStampScope` can cover many actions/destinations. | `PolicyStampScopeItem`, per-triple effective-policy refs. |
| B2 | BUG | Blocker | `policy_generation_id` alone is insufficient freshness. | `PolicyRuntimeFreshnessKey` on every runtime policy artifact. |
| B3 | BUG | Blocker | Meet applicability is under-keyed and can bleed across principal/surface/exposure/model/client context. | Required `PolicyEvaluationContext`; `meet_v2`. |
| B4 | BUG | Blocker | Destinationless decisions can accidentally authorize egress. | Destination-specific egress precondition. |
| B5 | BUG | Blocker | Meet pseudo-code filters missing axis values, allowing malformed decisions to fail open on that axis. | Explicit rank maps; malformed applicable decisions bottom the axis or block. |
| B6 | BUG | Blocker | Restamp ceiling is opaque and chain root is not explicit. | `PolicyCeilingSnapshot`, `RestampChainIntegrity`, axis comparison. |
| B7 | BUG | Blocker | `PolicyStampRestamp` requires `new_effective_policy_ref` even when `block`. | Discriminated union. |
| B8 | GAP | Blocker | `minimum_conservatism_floor` has no executable axis-effect table. | `ConservatismFloorEffect`. |
| B9 | GAP | Blocker | Destination relation and egress destination class have no crosswalk. | `DestinationPolicyCrosswalk`. |
| B10 | BUG | Blocker | Disclosure scalar cannot safely aggregate all disclosure permissions. | `DisclosurePermissionVector` meet. |
| B11 | GAP | Blocker | PropA `ExposureContextSchema` is textually named but not structurally carried. | `PolicyEvaluationContext.exposure_context_ref`; PropA obligations. |
| B12 | BUG | Blocker | `PolicyObligation` lacks conflict model and discharge proof. | `PolicyObligationConflict`, `PolicyObligationDischarge`. |
| B13 | BUG | Blocker | Relation traversal allows arbitrary `string` relation kinds. | Closed registry + default-deny unknowns. |
| B14 | GAP | Blocker | Legal hold covers known destructive jobs by prose only. | `DestructiveJobLegalHoldRegistry`. |
| B15 | GAP | Blocker | DOC81→DOC86 export payload is too thin for safe rendering. | Expanded `PolicyUIExport`. |

## High-value substantive findings

| ID | Type | Severity | Finding | Fix package |
|---|---|---:|---|---|
| S1 | BUG | High | `ScopeIdentityRoot` lacks synthetic unknown scope representation. | `unknown_synthetic` scope kind and lifecycle. |
| S2 | GAP | High | `ScopeResolutionResult.confidence` is a naked scalar. | Component breakdown with min aggregation. |
| S3 | GAP | High | `ScopeEquivalenceBinding` collapses clusters with one scalar confidence. | Pairwise evidence matrix. |
| S4 | GAP | High | Thresholds lack comparator/equality/version/hysteresis. | `ThresholdEvaluation`. |
| S5 | GAP | High | `ScopePopulationHealth` cannot invalidate cached resolution results because result lacks population generation inputs. | Add population-generation inputs to `ScopeResolutionResult`. |
| S6 | GAP | High | Sensitivity/protection states are lossy singletons. | Multi-flag `ScopeProtectionStateDerivation`. |
| S7 | BUG | High | `learning_scope` lacks `same_firewall_only`, required by PropA firewalled learning. | Add lattice value. |
| S8 | BUG | High | `SourceExclusionFilterRule.effective_policy_ref` creates an input/output cycle. | Replace with `policy_input_decision_ref`. |
| S9 | GAP | High | Source exclusion does not close over derived artifacts. | `exclusion_closure` and artifact classes. |
| S10 | GAP | High | Collection `exclude` promises existing-material filtering but lifecycle covers only future admission. | Backfill/scan contract. |
| S11 | GAP | High | Collection suppression topic ambiguity can collect protected content. | `CollectionSuppressionEvaluation`. |
| S12 | GAP | High | Five-plane revocation lacks idempotent execution state and receipts. | `CascadingSourceInvalidationRun`. |
| S13 | GAP | High | `last_active_support_edge_lost` is a conclusion boolean without denominator proof. | `LastActiveSupportEdgeEvaluation`. |
| S14 | GAP | High | `PolicyCappedDAMSInput` not keyed to context product request. | Add product request ID/kind and derivation. |
| S15 | GAP | High | Contamination threshold rule lacks actual measurement/threshold/comparator. | `ContaminationRiskMeasurement` + executable threshold. |
| S16 | GAP | High | Not-found vs not-searched needs search coverage proof. | `ScopeSearchCoverageProof`. |
| S17 | GAP | High | Cache keys are missing for scope/policy/UI exports. | `ScopeResolutionCacheKey`, `EffectivePolicyCacheKey`. |
| S18 | GAP | High | Relation traversal can cycle or blow up. | `RelationTraversalBudget`, execution trace. |
| S19 | GAP | High | Fan-out operations lack cost/quota envelopes. | `DOC81BatchOperationQuotaEnvelope`. |
| S20 | BUG | High | `PolicyDisambiguationRequest` lacks answer/effect object; answer can be misused as direct allow. | `PolicyDisambiguationAnswer`. |

## Medium cleanups

| ID | Type | Severity | Finding | Fix package |
|---|---|---:|---|---|
| M1 | SUGGESTION | Medium | `ScopeContainerRelation` uses `source_membership` / `topic_membership`, risking DOC87 membership confusion. | Rename to topology terms. |
| M2 | GAP | Medium | `ScopeContainerRelation.policy_load_bearing` boolean is too coarse. | Add load-bearing effect list by action/axis. |
| M3 | GAP | Medium | `PolicyMembraneDecision.crossing_disposition` needs derivation table. | `PolicyMembraneDispositionDerivation`. |
| M4 | GAP | Medium | `TopicRiskClass` needs confirmation proof. | `TopicRiskConfirmation`. |
| M5 | GAP | Medium | `SafeLabelDisclosurePolicy.default_label_ref` should be conditional. | Forbid default label when not disclosable. |
| M6 | GAP | Medium | Published-view revocation lint lacks plane owner. | Assign to DOC84 or DOC86 explicitly. |
| M7 | BUG | Medium | `VisibilityClass = string` is too loose. | External vocabulary value ref. |
| M8 | BUG | Medium | Type aliases claim branded refs but are plain strings. | TS `Brand<T,B>` pattern. |
| M9 | GAP | Medium | `LegalHoldState` selector semantics are ambiguous. | Discriminated selector union. |
| M10 | CLEANUP | Medium | E0 header/status drift should be cleaned in ratification pack. | Update header or add status pointer. |

---

# 4. Core patch pack

This section consolidates the concrete schemas and formulas I would add to DOC81. Some values are architect-confirmable, but the contract shapes are not optional if DOC81 is to be implementable without guessing.

## 4.1 Shared branded refs and external vocabulary refs

DOC81 currently says refs are branded, but TypeScript `type X = string` is not an actual brand. Use a lightweight brand pattern.

```typescript
type Brand<T, B extends string> = T & { readonly __brand: B };

type ContentHash = Brand<string, 'ContentHash'>;
type SchemaVersionRef = Brand<string, 'SchemaVersionRef'>;
type OwnerDocId = Brand<string, 'OwnerDocId'>;

type MemoryObjectRef = Brand<string, 'MemoryObjectRef'>;
type ScopeRef = Brand<string, 'ScopeRef'>;
type ScopeBoundaryRef = Brand<string, 'ScopeBoundaryRef'>;
type ScopeResolutionResultRef = Brand<string, 'ScopeResolutionResultRef'>;
type ScopeContainerRelationRef = Brand<string, 'ScopeContainerRelationRef'>;
type ScopeEquivalenceBindingRef = Brand<string, 'ScopeEquivalenceBindingRef'>;

type MemoryPolicyDecisionRef = Brand<string, 'MemoryPolicyDecisionRef'>;
type EffectiveMemoryPolicyRef = Brand<string, 'EffectiveMemoryPolicyRef'>;
type PolicyEvaluationContextRef = Brand<string, 'PolicyEvaluationContextRef'>;
type PolicyStampRef = Brand<string, 'PolicyStampRef'>;
type PolicyStampScopeRef = Brand<string, 'PolicyStampScopeRef'>;
type PolicyCeilingRef = Brand<string, 'PolicyCeilingRef'>;
type PolicyObligationRef = Brand<string, 'PolicyObligationRef'>;
type PolicyObligationConflictRef = Brand<string, 'PolicyObligationConflictRef'>;
type PolicyObligationDischargeRef = Brand<string, 'PolicyObligationDischargeRef'>;
type SafeReferenceLabelRef = Brand<string, 'SafeReferenceLabelRef'>;
type PolicyDisambiguationRequestRef = Brand<string, 'PolicyDisambiguationRequestRef'>;
type PolicyDisambiguationAnswerRef = Brand<string, 'PolicyDisambiguationAnswerRef'>;
type ThresholdEvaluationRef = Brand<string, 'ThresholdEvaluationRef'>;
type EffectiveStateGenerationId = Brand<string, 'EffectiveStateGenerationId'>;

type DomainProfileId = Brand<string, 'DomainProfileId'>;
type PolicyGenerationId = Brand<string, 'PolicyGenerationId'>;
type ReasonCodeId = Brand<string, 'ReasonCodeId'>;

type PrincipalRef = Brand<string, 'PrincipalRef'>;
type SurfaceRef = Brand<string, 'SurfaceRef'>;
type SourceRef = Brand<string, 'SourceRef'>;
type TopicRef = Brand<string, 'TopicRef'>;

type MemoryFlowCertificateId = Brand<string, 'MemoryFlowCertificateId'>;
type E0InteractionMode = 'interactive' | 'background_non_interactive' | 'scheduled' | 'agent_initiated';
```

Replace loose foreign-vocabulary strings with owner/version-qualified refs.

```typescript
interface ExternalVocabularyValueRef {
  owner_doc: OwnerDocId;
  registry_id: string;
  value_id: string;
  registry_version: SchemaVersionRef;
}

type VisibilityClassRef = ExternalVocabularyValueRef;
```

**Lints:**

```text
schema.branded_ref_declared_as_plain_string
schema.external_vocabulary_unversioned_string
```

---

## 4.2 Runtime freshness key

`policy_generation_id` is necessary but not sufficient. Runtime toggles, incognito/application state, compiled evaluator version, registry versions, and safe-label vocabulary changes must invalidate cached policy artifacts.

```typescript
interface PolicyRuntimeFreshnessKey {
  policy_generation_id: PolicyGenerationId;
  effective_state_generation_id: EffectiveStateGenerationId;
  compiled_policy_evaluator_hash: ContentHash;
  domain_profile_registry_version: SchemaVersionRef;
  reason_code_registry_version: SchemaVersionRef;
  safe_label_vocabulary_version?: SchemaVersionRef;
}

interface RequiresPolicyRuntimeFreshness {
  freshness_key: PolicyRuntimeFreshnessKey;
}
```

Apply `freshness_key` to all runtime-consumed DOC81 artifacts:

```text
MemoryPolicyDecision
EffectiveMemoryPolicy
PolicyStamp
PolicyStampRestamp
PolicyStampInvalidation
ExtractionRoutePolicyEnvelope
PolicyCappedDAMSInput
SafeLabelDisclosurePolicy
PolicyDisambiguationRequest
PolicyDisambiguationAnswer
PolicyUIExport
SourceExclusionFilterRule
CollectionModeSuppressionGovernance
CollectionSuppressionEvaluation
CascadingSourceInvalidation
CascadingSourceInvalidationRun
```

**Rule:**

```text
A DOC81 runtime artifact is reusable only if its PolicyRuntimeFreshnessKey exactly matches the active policy runtime freshness key. No artifact may be reused across policy_generation_id, effective_state_generation_id, compiled_policy_evaluator_hash, domain profile registry, reason-code registry, or safe-label vocabulary changes.
```

**Lints / fixtures:**

```text
policy.artifact_missing_effective_state_generation
policy.stamp_reused_after_effective_state_change
cache.policy_reused_across_effective_state_generation
cache.safe_label_vocab_change_not_in_ui_export_key
fixture.policy.application_toggle_invalidates_policy_exports
fixture.policy.incognito_toggle_blocks_cached_collection_policy
fixture.cache.policy_export_invalidates_on_safe_label_vocab_change
```

---

## 4.3 Policy evaluation context

The draft under-keys `MemoryPolicyDecision`. It needs an explicit evaluation context so decisions cannot bleed across principal, surface, exposure, model, client, interaction, or user-instruction conditions.

```typescript
type E0OutboundDestinationClass =
  | 'same_machine_local_runtime'
  | 'local_file_export'
  | 'local_network_peer'
  | 'firm_server'
  | 'remote_peer'
  | 'cloud_api'
  | 'email_outbound'
  | 'agent_messaging';

// Unknown/unresolved destinations are NOT members of E0OutboundDestinationClass.
// They fail closed before attestation and are represented only in DOC81's
// relation_to_destination / DestinationPolicyCrosswalk layer.

type MemoryPolicyAction =
  | 'collect'
  | 'extract'
  | 'classify'
  | 'write_candidate'
  | 'write_durable'
  | 'retrieve'
  | 'render_inline'
  | 'render_reference_only'
  | 'render_safe_label'
  | 'export'
  | 'delegate'
  | 'carryover'
  | 'learn_same_scope'
  | 'learn_partitioned'
  | 'learn_global'
  | 'ui_disclose'
  | 'inspect';

interface PolicyEvaluationContext extends E0DurableRecord {
  evaluation_context_id: PolicyEvaluationContextRef;
  schema_owner: 'DOC81';

  object_ref: MemoryObjectRef;
  action: MemoryPolicyAction;
  destination?: E0OutboundDestinationClass;

  principal_ref: PrincipalRef;
  surface_ref?: SurfaceRef;

  /** PropA-owned ExposureContextSchema instance/ref; DOC81 references, does not redefine. */
  exposure_context_ref: string;

  model_class:
    | 'same_machine_local_model'
    | 'cloud_model'
    | 'hybrid_model'
    | 'non_model_tool'
    | 'unknown_model_class';

  client_kind:
    | 'interactive_user'
    | 'background_job'
    | 'scheduled_job'
    | 'agent_initiated'
    | 'connector_adapter';

  interaction_mode: E0InteractionMode;

  user_instruction_ref?: string;
  user_instruction_policy_bound: true;

  scope_resolution_ref?: ScopeResolutionResultRef;
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

Then require it on `MemoryPolicyDecision`:

```typescript
type ContentFidelityLevel = 'none' | 'safe_label' | 'reference_only' | 'redacted' | 'full';
type LocalityLevel = 'blocked' | 'local_only' | 'approved_external';
type LearningScopeLevel =
  | 'none'
  | 'audit_only'
  | 'same_scope_only'
  | 'same_firewall_only'
  | 'partitioned'
  | 'global_allowed';
type MutationAuthorityLevel = 'none' | 'candidate_only' | 'durable_requires_review' | 'durable_allowed';
type DisclosureClass = 'not_disclosable' | 'existence_only' | 'generic_safe_label_only' | 'redacted_summary' | 'full';

interface MemoryPolicyDecision extends E0DurableRecord {
  decision_id: MemoryPolicyDecisionRef;
  schema_owner: 'DOC81';

  policy_evaluation_context_ref: PolicyEvaluationContextRef;

  object_ref: MemoryObjectRef;
  action: MemoryPolicyAction;
  destination?: E0OutboundDestinationClass;

  content_fidelity: ContentFidelityLevel;
  locality: LocalityLevel;
  learning_scope: LearningScopeLevel;
  mutation_authority: MutationAuthorityLevel;
  disclosure_class: DisclosureClass;
  disclosure_vector: DisclosurePermissionVector;

  obligations: PolicyObligationRef[];
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

**Applicability rule:**

```text
A MemoryPolicyDecision is applicable only if:
  decision.object_ref == requested.object_ref
  decision.action == requested.action
  decision.policy_evaluation_context_ref resolves to a context compatible with:
    principal, surface, exposure_context, model_class, client_kind,
    interaction_mode, destination, and active PolicyRuntimeFreshnessKey.
Destinationless decisions may tighten non-egress results but may not authorize egress.
```

**Lints / fixtures:**

```text
policy.decision_context_not_part_of_key
policy.local_interactive_decision_reused_for_background_agent
propa.policy_decision_without_exposure_context
policy.decision_missing_model_or_interaction_context
fixture.policy.cloud_model_render_policy_differs_from_local_model_render
fixture.propa.exposure_context_changes_policy_decision_key
```

---

## 4.4 Explicit axis rank maps

Never rely on enum declaration order. Use explicit rank maps.

```typescript
const AXIS_RANKS = {
  content_fidelity: {
    none: 0,
    safe_label: 1,
    reference_only: 2,
    redacted: 3,
    full: 4,
  },
  locality: {
    blocked: 0,
    local_only: 1,
    approved_external: 2,
  },
  learning_scope: {
    none: 0,
    audit_only: 1,
    same_scope_only: 2,
    same_firewall_only: 3,
    partitioned: 4,
    global_allowed: 5,
  },
  mutation_authority: {
    none: 0,
    candidate_only: 1,
    durable_requires_review: 2,
    durable_allowed: 3,
  },
  disclosure_class: {
    not_disclosable: 0,
    existence_only: 1,
    generic_safe_label_only: 2,
    redacted_summary: 3,
    full: 4,
  },
} as const;
```

**Rule:**

```text
Missing, NaN, unknown, out-of-vocabulary, or incomparable axis values resolve conservatively. For an applicable decision, a missing axis is not filtered out; it floors the affected axis or blocks the effective policy for the action.
```

**Lints / fixtures:**

```text
policy.malformed_applicable_decision_axis_filtered_out
policy.axis_missing_not_floored_conservative
fixture.policy.missing_axis_in_one_applicable_decision_floors_that_axis
fixture.policy.five_axes_present_and_independent
```

---

## 4.5 Disclosure permission vector

A scalar `DisclosureClass` is useful for display and coarse meet summaries, but it cannot be the only executable disclosure model. Disclosure should be a vector and the scalar class should be derived after vector meet.

```typescript
type CountDisclosureMode = 'none' | 'bucketed' | 'exact';
type SummaryFidelity = 'none' | 'generic_reason_only' | 'redacted_reason' | 'full_reason';

interface DisclosurePermissionVector {
  may_disclose_existence: boolean;
  may_disclose_container_type: boolean;
  may_disclose_topic_label: boolean;
  may_disclose_source_title: boolean;

  count_disclosure_mode: CountDisclosureMode;
  count_bucket_policy_ref?: string;

  may_disclose_reason_summary: boolean;
  reason_summary_template_ref?: SafeReferenceLabelRef;

  max_summary_fidelity: SummaryFidelity;
}

function bottomDisclosureVector(): DisclosurePermissionVector {
  return {
    may_disclose_existence: false,
    may_disclose_container_type: false,
    may_disclose_topic_label: false,
    may_disclose_source_title: false,
    count_disclosure_mode: 'none',
    may_disclose_reason_summary: false,
    max_summary_fidelity: 'none',
  };
}

const COUNT_MODE_RANK: Record<CountDisclosureMode, number> = { none: 0, bucketed: 1, exact: 2 };
const SUMMARY_FIDELITY_RANK: Record<SummaryFidelity, number> = {
  none: 0,
  generic_reason_only: 1,
  redacted_reason: 2,
  full_reason: 3,
};

function minCountMode(values: CountDisclosureMode[]): CountDisclosureMode {
  return values.reduce((a, b) => COUNT_MODE_RANK[b] < COUNT_MODE_RANK[a] ? b : a, 'exact');
}

function minSummaryFidelity(values: SummaryFidelity[]): SummaryFidelity {
  return values.reduce((a, b) => SUMMARY_FIDELITY_RANK[b] < SUMMARY_FIDELITY_RANK[a] ? b : a, 'full_reason');
}

function meetDisclosureVectors(vectors: DisclosurePermissionVector[]): DisclosurePermissionVector {
  if (vectors.length === 0) return bottomDisclosureVector();

  return {
    may_disclose_existence: vectors.every(v => v.may_disclose_existence),
    may_disclose_container_type: vectors.every(v => v.may_disclose_container_type),
    may_disclose_topic_label: vectors.every(v => v.may_disclose_topic_label),
    may_disclose_source_title: vectors.every(v => v.may_disclose_source_title),
    count_disclosure_mode: minCountMode(vectors.map(v => v.count_disclosure_mode)),
    may_disclose_reason_summary: vectors.every(v => v.may_disclose_reason_summary),
    max_summary_fidelity: minSummaryFidelity(vectors.map(v => v.max_summary_fidelity)),
  };
}

function deriveDisclosureClass(v: DisclosurePermissionVector): DisclosureClass {
  if (!v.may_disclose_existence) return 'not_disclosable';
  if (
    v.may_disclose_existence &&
    !v.may_disclose_container_type &&
    !v.may_disclose_topic_label &&
    !v.may_disclose_source_title &&
    v.count_disclosure_mode === 'none' &&
    !v.may_disclose_reason_summary
  ) return 'existence_only';
  if (v.max_summary_fidelity === 'none') return 'generic_safe_label_only';
  if (v.max_summary_fidelity === 'redacted_reason') return 'redacted_summary';
  return 'full';
}
```

**Count-disclosure policy:**

```typescript
interface CountDisclosurePolicy {
  mode: CountDisclosureMode;
  bucket_scheme_ref?: string;
  exact_count_allowed_only_if_disclosure_class: 'full';
}
```

Default bucket scheme:

```text
0 → "none"
1 → "one"
2-5 → "a few"
6-10 → "several"
>10 → "multiple"
```

**Lints / fixtures:**

```text
disclosure.scalar_class_used_without_permission_vector
disclosure.count_disclosed_exact_when_only_bucket_allowed
disclosure.enum_order_matches_rank_table
safe_label.exact_count_disclosed_without_full_disclosure
fixture.disclosure.vector_meet_ands_boolean_permissions
fixture.disclosure.count_bucketed_unless_full
```

---

## 4.6 Effective policy and meet algorithm v2

```typescript
interface EffectiveMemoryPolicy extends E0DurableRecord {
  effective_policy_id: EffectiveMemoryPolicyRef;
  schema_owner: 'DOC81';

  object_ref: MemoryObjectRef;
  action: MemoryPolicyAction;
  destination?: E0OutboundDestinationClass;
  policy_evaluation_context_ref: PolicyEvaluationContextRef;

  contributing_decision_refs: MemoryPolicyDecisionRef[];
  excluded_decision_refs: Array<{
    decision_ref: MemoryPolicyDecisionRef;
    exclusion_reason:
      | 'wrong_object'
      | 'wrong_action'
      | 'wrong_destination'
      | 'wrong_principal'
      | 'wrong_surface'
      | 'wrong_exposure_context'
      | 'wrong_model_class'
      | 'wrong_client_kind'
      | 'wrong_interaction_mode'
      | 'stale_generation'
      | 'stale_effective_state'
      | 'malformed_axis';
  }>;

  effective_content_fidelity: ContentFidelityLevel;
  effective_locality: LocalityLevel;
  effective_learning_scope: LearningScopeLevel;
  effective_mutation_authority: MutationAuthorityLevel;
  effective_disclosure_class: DisclosureClass;
  effective_disclosure_vector: DisclosurePermissionVector;

  conservatism_floor_applied?: ScopeConservatismFloor;
  floor_effect_ref?: ConservatismFloorEffectRef;

  meet_kind: 'per_dimension_most_restrictive';
  meet_algorithm_version: 'doc81.meet.v2';

  obligations: PolicyObligationRef[];
  obligation_conflict_refs: PolicyObligationConflictRef[];

  original_ceiling_ref?: PolicyCeilingRef;
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

Algorithm:

```text
function meet_v2(request, decisions, scopeResolution):
  validate request.action is a known MemoryPolicyAction
  validate request.freshness_key matches active policy/effective-state generations

  if request.action in {export, delegate, carryover}:
    require destination
    require destination ∈ E0OutboundDestinationClass
    require at least one destination-specific decision where d.destination == destination
    destinationless decisions may tighten but never authorize egress

  applicable = []
  excluded = []

  for d in decisions:
    if !contextCompatible(d, request): excluded.add(...)
    else if any required axis is missing or unrecognized:
       return blockedBottomPolicy(reason = malformed_axis)
    else applicable.add(d)

  if applicable is empty:
    return blockedBottomPolicy(reason = policy.no_applicable_decision)

  for each capability axis:
    eff[axis] = min_by_rank(applicable.map(d => d[axis]))

  eff.disclosure_vector = meetDisclosureVectors(applicable.map(d => d.disclosure_vector))
  eff.disclosure_class = deriveDisclosureClass(eff.disclosure_vector)

  eff = applyConservatismFloor(eff, scopeResolution.minimum_conservatism_floor)

  obligation_conflicts = detectObligationConflicts(unionObligations(applicable))
  if obligation_conflicts.any(blocking):
    return blockedBottomPolicy(reason = policy.obligation_conflict)

  return eff
```

Destination-specific egress precondition:

```text
If action ∈ {export, delegate, carryover}:
  a. destination MUST be present.
  b. destination MUST resolve to a recognized E0OutboundDestinationClass.
  c. destination MUST NOT be inferred from relation_to_destination.
  d. at least one applicable MemoryPolicyDecision MUST have d.destination === destination.
     Destinationless decisions may tighten the result but may not satisfy the destination-specific policy requirement.
  e. if destination !== 'same_machine_local_runtime', the downstream egress gate MUST produce an E0EgressAttestation whose policy_decision_destination === outbound_destination_class.
```

**Lints / fixtures:**

```text
policy.egress_authorized_by_destinationless_decision
policy.destination_required_action_without_destination
policy.meet_across_actions
policy.no_applicable_decision_not_fail_closed
policy.malformed_axis_not_failed_closed
fixture.egress.destinationless_policy_decision_cannot_authorize_export
fixture.policy.meet_is_per_dimension_most_restrictive
fixture.policy.empty_inputs_fail_closed
fixture.policy.malformed_axis_in_applicable_decision_floors_axis
```

---

## 4.7 Conservatism floor effect table

The draft names floors but does not map them to axis effects. Add a table.

```typescript
type ScopeConservatismFloor =
  | 'normal_policy_check'
  | 'reference_only_candidate'
  | 'safe_label_candidate'
  | 'user_disambiguation_candidate'
  | 'fail_closed_candidate';

type ConservatismFloorEffectRef = Brand<string, 'ConservatismFloorEffectRef'>;

interface ConservatismFloorEffect extends E0DurableRecord {
  floor: ScopeConservatismFloor;
  schema_owner: 'DOC81';

  max_content_fidelity: ContentFidelityLevel;
  max_locality: LocalityLevel;
  max_learning_scope: LearningScopeLevel;
  max_mutation_authority: MutationAuthorityLevel;
  max_disclosure_class: DisclosureClass;
  max_disclosure_vector: DisclosurePermissionVector;

  movement_allowed: boolean;
  disambiguation_required: boolean;
  allowed_actions_before_disambiguation: MemoryPolicyAction[];

  reason_codes: ReasonCodeId[];
}
```

Seed table:

```typescript
const DOC81_CONSERVATISM_FLOOR_TABLE: ConservatismFloorEffect[] = [
  {
    floor: 'normal_policy_check',
    schema_owner: 'DOC81',
    max_content_fidelity: 'full',
    max_locality: 'approved_external',
    max_learning_scope: 'global_allowed',
    max_mutation_authority: 'durable_allowed',
    max_disclosure_class: 'full',
    max_disclosure_vector: {
      may_disclose_existence: true,
      may_disclose_container_type: true,
      may_disclose_topic_label: true,
      may_disclose_source_title: true,
      count_disclosure_mode: 'exact',
      may_disclose_reason_summary: true,
      max_summary_fidelity: 'full_reason',
    },
    movement_allowed: true,
    disambiguation_required: false,
    allowed_actions_before_disambiguation: [
      'collect', 'extract', 'classify', 'write_candidate', 'write_durable',
      'retrieve', 'render_inline', 'render_reference_only', 'render_safe_label',
      'export', 'delegate', 'carryover',
      'learn_same_scope', 'learn_partitioned', 'learn_global',
      'ui_disclose', 'inspect'
    ],
    reason_codes: []
  },
  {
    floor: 'reference_only_candidate',
    schema_owner: 'DOC81',
    max_content_fidelity: 'reference_only',
    max_locality: 'local_only',
    max_learning_scope: 'audit_only',
    max_mutation_authority: 'candidate_only',
    max_disclosure_class: 'generic_safe_label_only',
    max_disclosure_vector: {
      may_disclose_existence: true,
      may_disclose_container_type: true,
      may_disclose_topic_label: false,
      may_disclose_source_title: false,
      count_disclosure_mode: 'bucketed',
      may_disclose_reason_summary: true,
      max_summary_fidelity: 'generic_reason_only',
    },
    movement_allowed: true,
    disambiguation_required: false,
    allowed_actions_before_disambiguation: [
      'retrieve', 'render_reference_only', 'render_safe_label', 'ui_disclose', 'inspect'
    ],
    reason_codes: ['DOC81.policy.reference_only_floor_applied']
  },
  {
    floor: 'safe_label_candidate',
    schema_owner: 'DOC81',
    max_content_fidelity: 'safe_label',
    max_locality: 'local_only',
    max_learning_scope: 'none',
    max_mutation_authority: 'none',
    max_disclosure_class: 'generic_safe_label_only',
    max_disclosure_vector: {
      may_disclose_existence: true,
      may_disclose_container_type: false,
      may_disclose_topic_label: false,
      may_disclose_source_title: false,
      count_disclosure_mode: 'none',
      may_disclose_reason_summary: false,
      max_summary_fidelity: 'none',
    },
    movement_allowed: true,
    disambiguation_required: false,
    allowed_actions_before_disambiguation: ['render_safe_label', 'ui_disclose', 'inspect'],
    reason_codes: ['DOC81.policy.safe_label_floor_applied']
  },
  {
    floor: 'user_disambiguation_candidate',
    schema_owner: 'DOC81',
    max_content_fidelity: 'none',
    max_locality: 'blocked',
    max_learning_scope: 'none',
    max_mutation_authority: 'none',
    max_disclosure_class: 'generic_safe_label_only',
    max_disclosure_vector: {
      may_disclose_existence: true,
      may_disclose_container_type: false,
      may_disclose_topic_label: false,
      may_disclose_source_title: false,
      count_disclosure_mode: 'none',
      may_disclose_reason_summary: false,
      max_summary_fidelity: 'none',
    },
    movement_allowed: false,
    disambiguation_required: true,
    allowed_actions_before_disambiguation: ['render_safe_label', 'ui_disclose'],
    reason_codes: ['DOC81.policy.disambiguation_required_before_use']
  },
  {
    floor: 'fail_closed_candidate',
    schema_owner: 'DOC81',
    max_content_fidelity: 'none',
    max_locality: 'blocked',
    max_learning_scope: 'none',
    max_mutation_authority: 'none',
    max_disclosure_class: 'not_disclosable',
    max_disclosure_vector: bottomDisclosureVector(),
    movement_allowed: false,
    disambiguation_required: false,
    allowed_actions_before_disambiguation: [],
    reason_codes: ['DOC81.policy.fail_closed_floor_applied']
  }
];
```

**Meet step:**

```text
After computing the per-axis most-restrictive meet, EC applies ConservatismFloorEffect by taking the stricter value of each effective axis and the floor’s max axis. If movement_allowed = false, the effective policy blocks all movement actions unless a valid ceiling-bounded restamp or disambiguation answer creates a new policy decision under the current freshness key.
```

**Lints / fixtures:**

```text
policy.effective_undercut_scope_floor
policy.floor_without_axis_effect_table
fixture.policy.floor_mapping_tightens_each_axis
fixture.scope.uncertain_affinity_floors_reference_only
```

---

## 4.8 Destination policy crosswalk

`relation_to_destination` is a scope relation. `E0OutboundDestinationClass` is an egress class. They must not collapse into each other.

```typescript
interface DestinationPolicyCrosswalk extends E0DurableRecord {
  crosswalk_id: string;
  schema_owner: 'DOC81';

  relation_to_destination:
    | 'same_runtime'
    | 'same_machine_local'
    | 'same_principal'
    | 'same_project'
    | 'same_firewall'
    | 'external_destination'
    | 'unknown_destination'
    | 'blocked_destination';

  outbound_destination_class?: E0OutboundDestinationClass;
  allowed_outbound_destination_classes?: E0OutboundDestinationClass[];

  policy_floor: ScopeConservatismFloor;
  egress_attestation_required: boolean;
  policy_lookup_required: boolean;

  disposition:
    | 'no_egress_same_runtime_only'
    | 'same_machine_runtime_only'
    | 'policy_bound_egress'
    | 'block_until_terminal_destination_resolved'
    | 'block';

  reason_codes: ReasonCodeId[];
}
```

Seed rules:

```typescript
const DOC81_DESTINATION_CROSSWALK_RULES: DestinationPolicyCrosswalk[] = [
  {
    crosswalk_id: 'doc81.dest.same_runtime',
    schema_owner: 'DOC81',
    relation_to_destination: 'same_runtime',
    outbound_destination_class: 'same_machine_local_runtime',
    policy_floor: 'normal_policy_check',
    egress_attestation_required: false,
    policy_lookup_required: false,
    disposition: 'no_egress_same_runtime_only',
    reason_codes: []
  },
  {
    crosswalk_id: 'doc81.dest.same_machine_local_file_export',
    schema_owner: 'DOC81',
    relation_to_destination: 'same_machine_local',
    outbound_destination_class: 'local_file_export',
    policy_floor: 'reference_only_candidate',
    egress_attestation_required: true,
    policy_lookup_required: true,
    disposition: 'policy_bound_egress',
    reason_codes: ['DOC81.egress.local_file_export_requires_policy_decision']
  },
  {
    crosswalk_id: 'doc81.dest.external',
    schema_owner: 'DOC81',
    relation_to_destination: 'external_destination',
    allowed_outbound_destination_classes: [
      'local_network_peer', 'firm_server', 'remote_peer',
      'cloud_api', 'email_outbound', 'agent_messaging'
    ],
    policy_floor: 'fail_closed_candidate',
    egress_attestation_required: true,
    policy_lookup_required: true,
    disposition: 'policy_bound_egress',
    reason_codes: ['DOC81.egress.external_destination_requires_attestation']
  },
  {
    crosswalk_id: 'doc81.dest.unknown',
    schema_owner: 'DOC81',
    relation_to_destination: 'unknown_destination',
    policy_floor: 'fail_closed_candidate',
    egress_attestation_required: false,
    policy_lookup_required: false,
    disposition: 'block_until_terminal_destination_resolved',
    reason_codes: ['DOC81.egress.unknown_terminal_destination_blocked']
  },
  {
    crosswalk_id: 'doc81.dest.blocked',
    schema_owner: 'DOC81',
    relation_to_destination: 'blocked_destination',
    policy_floor: 'fail_closed_candidate',
    egress_attestation_required: false,
    policy_lookup_required: false,
    disposition: 'block',
    reason_codes: ['DOC81.egress.destination_blocked_by_policy']
  }
];
```

**Rule:**

```text
relation_to_destination NEVER substitutes for E0OutboundDestinationClass. Any action with terminal bytes leaving same-machine runtime must resolve an E0 destination class and, unless the class is exactly same_machine_local_runtime, must carry an E0EgressAttestation.
```

**Lints / fixtures:**

```text
egress.relation_to_destination_used_as_egress_class
egress.local_file_export_treated_as_same_runtime
fixture.egress.same_machine_local_file_export_requires_attestation
```

---

## 4.9 Policy stamp, scope items, ceiling snapshot, and restamp

### 4.9.1 Fix stamp triple binding

```typescript
interface PolicyStampScopeItem {
  action: MemoryPolicyAction;
  destination?: E0OutboundDestinationClass; // required iff action ∈ {export, delegate, carryover}
  effective_policy_ref: EffectiveMemoryPolicyRef; // MUST point to same object/action/destination/context triple
  original_ceiling_ref: PolicyCeilingRef;
}

interface PolicyStamp extends E0DurableRecord {
  stamp_id: PolicyStampRef;
  schema_owner: 'DOC81';
  object_ref: MemoryObjectRef;
  issued_by: 'EC_compiled_policy_evaluator';
  freshness_key: PolicyRuntimeFreshnessKey;
  scope_items: PolicyStampScopeItem[]; // non-empty
  expires_at?: string; // RFC3339-UTC
  lifecycle_state: 'active' | 'superseded' | 'invalidated' | 'expired' | 'blocked';
  reason_codes: ReasonCodeId[];
}

interface PolicyStampScope extends E0DurableRecord {
  stamp_scope_id: PolicyStampScopeRef;
  schema_owner: 'DOC81';
  stamp_ref: PolicyStampRef;
  object_ref: MemoryObjectRef;
  scope_items: PolicyStampScopeItem[];
  freshness_key: PolicyRuntimeFreshnessKey;
  expires_at?: string;
}
```

**Lints / fixtures:**

```text
policy.stamp_scope_action_without_matching_effective_policy
policy.retrieval_stamp_used_for_render_or_export
fixture.policy.stamp_scope_item_matches_effective_policy_triple
fixture.policy.retrieval_stamp_cannot_authorize_render_or_export
```

### 4.9.2 Ceiling snapshot

```typescript
interface PolicyCeilingSnapshot extends E0DurableRecord {
  ceiling_id: PolicyCeilingRef;
  schema_owner: 'DOC81';

  root_stamp_ref: PolicyStampRef;
  object_ref: MemoryObjectRef;

  ceiling_items: Array<{
    action: MemoryPolicyAction;
    destination?: E0OutboundDestinationClass;
    max_content_fidelity: ContentFidelityLevel;
    max_locality: LocalityLevel;
    max_learning_scope: LearningScopeLevel;
    max_mutation_authority: MutationAuthorityLevel;
    max_disclosure_class: DisclosureClass;
    max_disclosure_vector: DisclosurePermissionVector;
  }>;

  issued_under_freshness_key: PolicyRuntimeFreshnessKey;
  issued_at: string; // RFC3339-UTC
  issued_by: 'EC';
  reason_codes: ReasonCodeId[];
}

interface RestampChainIntegrity extends E0DurableRecord {
  chain_id: string;
  schema_owner: 'DOC81';
  root_stamp_ref: PolicyStampRef;
  ceiling_snapshot_ref: PolicyCeilingRef;
  restamp_refs: string[];
  invariant: 'every_restamp_compared_to_root_ceiling_snapshot';
}
```

**Rule:**

```text
A restamp is valid only if every action/destination item is compared against the root PolicyCeilingSnapshot and every axis comparison is within_ceiling or downgraded. Any exceeds_ceiling result blocks issuance and emits restamp.exceeds_original_ceiling.
```

**Lints / fixtures:**

```text
restamp.chain_ceiling_rebased_to_prior_restamp
restamp.axis_comparison_missing
fixture.policy.restamp_of_restamp_compares_against_root_ceiling
fixture.policy.restamp_ceiling_vector_blocks_axis_widening
```

### 4.9.3 Restamp discriminated union

```typescript
type PolicyStampRestamp =
  | PolicyStampRestampKeep
  | PolicyStampRestampDowngrade
  | PolicyStampRestampBlock;

interface PolicyAxisCeilingComparison {
  axis:
    | 'content_fidelity'
    | 'locality'
    | 'learning_scope'
    | 'mutation_authority'
    | 'disclosure_class'
    | 'disclosure_vector';
  comparison: 'within_ceiling' | 'downgraded' | 'exceeds_ceiling';
  prior_value: string;
  new_value?: string;
  ceiling_value: string;
}

interface PolicyStampRestampBase extends E0DurableRecord {
  restamp_id: string;
  schema_owner: 'DOC81';
  object_ref: MemoryObjectRef;
  prior_stamp_ref: PolicyStampRef;
  root_stamp_ref: PolicyStampRef;
  prior_freshness_key: PolicyRuntimeFreshnessKey;
  new_freshness_key: PolicyRuntimeFreshnessKey;
  original_ceiling_ref: PolicyCeilingRef;
  memory_flow_certificate_ref: MemoryFlowCertificateId;
  ceiling_comparisons: PolicyAxisCeilingComparison[];
  reason_codes: ReasonCodeId[];
}

interface PolicyStampRestampKeep extends PolicyStampRestampBase {
  restamp_disposition: 'keep';
  new_effective_policy_refs: EffectiveMemoryPolicyRef[];
  ceiling_compliance_attested: true;
}

interface PolicyStampRestampDowngrade extends PolicyStampRestampBase {
  restamp_disposition: 'downgrade';
  new_effective_policy_refs: EffectiveMemoryPolicyRef[];
  downgraded_axes: Array<
    'content_fidelity' | 'locality' | 'learning_scope' | 'mutation_authority' | 'disclosure_class' | 'disclosure_vector'
  >;
  ceiling_compliance_attested: true;
}

interface PolicyStampRestampBlock extends PolicyStampRestampBase {
  restamp_disposition: 'block';
  new_effective_policy_refs?: never;
  blocked_reason_codes: ReasonCodeId[];
  ceiling_compliance_attested: true;
}
```

**Lints / fixtures:**

```text
restamp.block_disposition_with_new_effective_policy_ref
restamp.exceeds_original_ceiling
fixture.policy.blocked_restamp_has_no_authorizing_effective_policy
```

### 4.9.4 Invalidation deltas

```typescript
interface PolicyAxisDelta {
  axis:
    | 'content_fidelity'
    | 'locality'
    | 'learning_scope'
    | 'mutation_authority'
    | 'disclosure_class'
    | 'disclosure_vector';

  prior_value: string;
  new_value: string;
  delta_kind: 'tightened' | 'unchanged' | 'widened_illegal';
}

interface PolicyStampInvalidationScopeItem {
  action: MemoryPolicyAction;
  destination?: E0OutboundDestinationClass;
  prior_effective_policy_ref: EffectiveMemoryPolicyRef;
  new_effective_policy_ref?: EffectiveMemoryPolicyRef;
  axis_deltas: PolicyAxisDelta[];
  required_remediation: 'restamp' | 'downgrade' | 'block';
}

interface PolicyStampInvalidation extends E0DurableRecord {
  invalidation_id: string;
  schema_owner: 'DOC81';

  invalidated_stamp_ref: PolicyStampRef;
  object_ref: MemoryObjectRef;

  prior_freshness_key: PolicyRuntimeFreshnessKey;
  new_freshness_key: PolicyRuntimeFreshnessKey;

  affected_scope_items: PolicyStampInvalidationScopeItem[];

  user_visible_summary_ref: SafeReferenceLabelRef;
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
policy.invalidation_axis_delta_unmapped_to_action
fixture.policy.invalidation_reports_axis_delta_per_action_destination
```

---

## 4.10 Action closure and permission predicates

Action-specific policy checking is not enough; terminal actions imply prerequisite policy checks.

```typescript
interface MemoryPolicyActionClosureRule extends E0DurableRecord {
  rule_id: string;
  schema_owner: 'DOC81';

  requested_action: MemoryPolicyAction;
  required_policy_actions: MemoryPolicyAction[];

  egress_attestation_required: boolean;
  memory_flow_certificate_required: boolean;
  scope_resolution_required: boolean;

  require_effective_policy_per_action: true;
  reason_codes: ReasonCodeId[];
}

const DOC81_ACTION_CLOSURE_RULES: MemoryPolicyActionClosureRule[] = [
  {
    rule_id: 'doc81.action.render_inline',
    schema_owner: 'DOC81',
    requested_action: 'render_inline',
    required_policy_actions: ['retrieve', 'render_inline', 'ui_disclose'],
    egress_attestation_required: false,
    memory_flow_certificate_required: true,
    scope_resolution_required: true,
    require_effective_policy_per_action: true,
    reason_codes: []
  },
  {
    rule_id: 'doc81.action.export',
    schema_owner: 'DOC81',
    requested_action: 'export',
    required_policy_actions: ['retrieve', 'export', 'ui_disclose'],
    egress_attestation_required: true,
    memory_flow_certificate_required: true,
    scope_resolution_required: true,
    require_effective_policy_per_action: true,
    reason_codes: ['DOC81.egress.export_requires_policy_and_attestation']
  },
  {
    rule_id: 'doc81.action.learn_global',
    schema_owner: 'DOC81',
    requested_action: 'learn_global',
    required_policy_actions: ['retrieve', 'learn_global'],
    egress_attestation_required: false,
    memory_flow_certificate_required: true,
    scope_resolution_required: true,
    require_effective_policy_per_action: true,
    reason_codes: ['DOC81.learning.requires_policy_gate']
  }
];
```

Action predicate table:

```typescript
interface ActionPermissionPredicate extends E0DurableRecord {
  predicate_id: string;
  schema_owner: 'DOC81';

  action: MemoryPolicyAction;

  required_minima: Partial<{
    content_fidelity: ContentFidelityLevel;
    locality: LocalityLevel;
    learning_scope: LearningScopeLevel;
    mutation_authority: MutationAuthorityLevel;
    disclosure_class: DisclosureClass;
  }>;

  forbidden_values?: Partial<Record<
    'content_fidelity' | 'locality' | 'learning_scope' | 'mutation_authority' | 'disclosure_class',
    string[]
  >>;

  requires_destination: boolean;
  requires_egress_attestation: boolean;
  requires_mfc: boolean;
  requires_obligation_discharge: boolean;

  reason_codes: ReasonCodeId[];
}

const DOC81_ACTION_PERMISSION_PREDICATES: ActionPermissionPredicate[] = [
  {
    predicate_id: 'doc81.predicate.retrieve',
    schema_owner: 'DOC81',
    action: 'retrieve',
    required_minima: {
      content_fidelity: 'reference_only',
      locality: 'local_only',
    },
    requires_destination: false,
    requires_egress_attestation: false,
    requires_mfc: false,
    requires_obligation_discharge: true,
    reason_codes: []
  },
  {
    predicate_id: 'doc81.predicate.render_inline',
    schema_owner: 'DOC81',
    action: 'render_inline',
    required_minima: {
      content_fidelity: 'redacted',
      locality: 'local_only',
      disclosure_class: 'redacted_summary',
    },
    requires_destination: false,
    requires_egress_attestation: false,
    requires_mfc: true,
    requires_obligation_discharge: true,
    reason_codes: []
  },
  {
    predicate_id: 'doc81.predicate.export',
    schema_owner: 'DOC81',
    action: 'export',
    required_minima: {
      content_fidelity: 'redacted',
      locality: 'approved_external',
      disclosure_class: 'redacted_summary',
    },
    requires_destination: true,
    requires_egress_attestation: true,
    requires_mfc: true,
    requires_obligation_discharge: true,
    reason_codes: ['DOC81.egress.export_requires_destination_policy']
  },
  {
    predicate_id: 'doc81.predicate.learn_global',
    schema_owner: 'DOC81',
    action: 'learn_global',
    required_minima: {
      learning_scope: 'global_allowed',
    },
    requires_destination: false,
    requires_egress_attestation: false,
    requires_mfc: true,
    requires_obligation_discharge: true,
    reason_codes: ['DOC81.learning.global_learning_requires_policy_clearance']
  }
];
```

The exact minima are architect-confirmable; the existence of a predicate table is not optional.

**Lints / fixtures:**

```text
policy.terminal_action_checked_without_prerequisite_actions
policy.action_predicate_missing
fixture.policy.render_inline_requires_retrieve_and_ui_disclose_policy
fixture.policy.export_requires_retrieve_export_ui_disclose_and_egress
```

---

## 4.11 Policy obligations, conflicts, discharge, and PropA kinds

### 4.11.1 Expanded obligation kinds

```typescript
type PolicyObligationKind =
  | 'require_redaction'
  | 'require_safe_label'
  | 'require_user_confirmation'
  | 'require_restamp_before_action'
  | 'require_audit_certificate'
  | 'require_final_prompt_proof'
  | 'forbid_global_learning'
  | 'forbid_cross_scope_relation_traversal'
  | 'require_source_sandbox_isolation'
  | 'require_prompt_injection_isolation'
  | 'require_review_queue'
  | 'require_ui_warning'
  | 'hide_existence'
  | 'show_generic_existence_only'
  | 'partition_learning'
  | 'receipt_required'

  // PropA / DOC73 V1.5.1 fold-in obligations:
  | 'require_migration_plan'
  | 'route_manual_review_only'
  | 'require_verifier_calibration_current'
  | 'exclude_uncalibrated_verifier_signals'
  | 'forbid_sealed_source_fixture_generation'
  | 'restrict_firewalled_fixture_generation_to_same_firewall'
  | 'discard_sealed_learning_signals'
  | 'restrict_firewalled_learning_to_same_firewall'
  | 'require_dspy_target_eligibility'
  | 'require_post_retrieval_critique_policy_gate'
  | 'require_counterfactual_prompt_policy_gate'
  | 'require_additive_synthesis_prompt_policy_gate';
```

### 4.11.2 Enforcement owners

DOC84 and DOC86 must be valid owners because delivery/render and UI/Inspector surfaces discharge several policy obligations.

```typescript
type PolicyObligationEnforcementOwner =
  | 'EC'
  | 'PropA'
  | 'DOC24'
  | 'KDA'
  | 'DOC84'
  | 'DOC86'
  | 'DOC11'
  | 'DOC20'
  | 'DOC1'
  | 'DOC25'
  | 'DOC73'
  | 'DOC85';
```

### 4.11.3 Typed obligation union

```typescript
interface PolicyObligationBase extends E0DurableRecord {
  obligation_id: PolicyObligationRef;
  schema_owner: 'DOC81';
  obligation_kind: PolicyObligationKind;
  applies_to_actions: MemoryPolicyAction[];
  enforcement_owner: PolicyObligationEnforcementOwner;
  blocking: boolean;
  reason_code: ReasonCodeId;
}

type PolicyObligation =
  | RequireRedactionObligation
  | RequireSafeLabelObligation
  | RequireMigrationPlanObligation
  | RequireVerifierCalibrationCurrentObligation
  | RestrictFirewalledLearningObligation
  | ReceiptRequiredObligation
  | GenericReviewQueueObligation;

interface RequireRedactionObligation extends PolicyObligationBase {
  obligation_kind: 'require_redaction';
  parameters: {
    redaction_map_required: true;
    redaction_map_ref?: string;
  };
}

interface RequireSafeLabelObligation extends PolicyObligationBase {
  obligation_kind: 'require_safe_label';
  parameters: {
    safe_label_policy_ref: SafeReferenceLabelRef;
    allow_existence_disclosure_only_if_policy_allows: true;
  };
}

interface RequireMigrationPlanObligation extends PolicyObligationBase {
  obligation_kind: 'require_migration_plan';
  parameters: {
    schema_migration_plan_ref?: string;
    if_missing: 'route_manual_review_only';
  };
}

interface RequireVerifierCalibrationCurrentObligation extends PolicyObligationBase {
  obligation_kind: 'require_verifier_calibration_current';
  parameters: {
    verifier_calibration_ledger_ref?: string;
    if_uncalibrated_or_drifting: 'exclude_verifier_signal';
  };
}

interface RestrictFirewalledLearningObligation extends PolicyObligationBase {
  obligation_kind: 'restrict_firewalled_learning_to_same_firewall';
  parameters: {
    firewall_scope_ref: ScopeRef;
  };
}

interface ReceiptRequiredObligation extends PolicyObligationBase {
  obligation_kind: 'receipt_required';
  parameters: {
    receipt_kind: 'memory_flow_certificate' | 'egress_attestation' | 'obligation_discharge' | 'ui_notice_receipt';
  };
}

interface GenericReviewQueueObligation extends PolicyObligationBase {
  obligation_kind: 'require_review_queue' | 'route_manual_review_only';
  parameters: {
    queue_kind: 'privacy_review' | 'schema_migration_review' | 'policy_conflict_review' | 'source_revocation_review';
  };
}
```

### 4.11.4 Conflict model

```typescript
interface PolicyObligationConflict extends E0DurableRecord {
  conflict_id: PolicyObligationConflictRef;
  schema_owner: 'DOC81';

  effective_policy_ref: EffectiveMemoryPolicyRef;
  action: MemoryPolicyAction;
  destination?: E0OutboundDestinationClass;

  conflicting_obligation_refs: PolicyObligationRef[];
  conflict_kind:
    | 'disclosure_vs_ui_notice'
    | 'redaction_vs_hide_existence'
    | 'receipt_vs_suppressed_manifest'
    | 'confirmation_prompt_would_leak'
    | 'enforcement_owner_conflict'
    | 'parameter_conflict'
    | 'unknown_conflict';

  disposition: 'fail_closed_for_action';
  blocked_reason_codes: ReasonCodeId[];
}
```

### 4.11.5 Discharge proof

```typescript
interface PolicyObligationDischarge extends E0DurableRecord {
  discharge_id: PolicyObligationDischargeRef;
  schema_owner: 'DOC81';

  obligation_ref: PolicyObligationRef;
  enforcement_owner: PolicyObligationEnforcementOwner;
  discharged_by_ref: string;
  discharge_status: 'satisfied' | 'blocked' | 'waived_by_policy' | 'failed';
  blocking: boolean;

  required_before_actions: MemoryPolicyAction[];
  certificate_ref?: MemoryFlowCertificateId;
  receipt_ref?: string;
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
policy.undefined_policy_obligation
policy.obligation_dropped_under_meet
policy.obligation_conflict_not_failed_closed
policy.obligation_owner_missing_doc84_doc86
policy.obligation_parameters_untyped
policy.obligation_not_discharged_before_movement
fixture.policy.conflicting_obligations_fail_closed_for_action
fixture.policy.require_redaction_obligation_needs_redaction_map
```

---

## 4.12 PropA row landings correction

The CODEX fix aligned §7.3 with actual PropA carried rows, but the lineage table and executable obligation schema must match it.

Paste-ready §14.2 replacement:

```markdown
### §14.2 OPA-row landings — corrected after CODEX fix

| OPA row | canonical body | DOC81 landing site |
|---|---|---|
| `OBL-PROPA-NEW-01` | PropA-owned `ExposureContextSchema`; DOC24 passes exposure context to EC PolicyDecisionEngine | §7.3 + §3.1/§3.2: `ExposureContextSchemaRef` is a named policy-evaluation input; DOC81 references PropA, never redefines the enum. |
| `OBL-PROPA-NEW-02` | Policy hard-block precedence over BDSM `force_level` | §7.3 + §3.2 + §4.4: policy block is final; `force_level` / DAMS salience may operate only inside `PolicyCappedDAMSInput` eligibility. |
| `OBL-PROPA-NEW-V15-01` | `LearningVisibilityScope` filtering; sealed discarded; firewalled same-firewall only | §7.3 + §3.3 + §6.3: learning-policy partition gate consumed by DOC85 / PropA. |
| `OBL-PROPA-NEW-V15-02` | `SchemaMigrationPlan` required for schema-changing prompt iterations; plan-less iterations route `manual_review_only` | §7.3 + §3.3: `require_migration_plan` / `route_manual_review_only` obligations. |
| `OBL-PROPA-NEW-V15-03` | `VerifierCalibrationLedger` gating; uncalibrated/drifting/failed verifier signals excluded | §7.3 + §3.3: verifier-calibration policy obligation consumed by DOC85 / PropA. |
| `OBL-PROPA-NEW-V15-04` | AVAPO regression fixture exclusion; sealed fixtures not generated; firewalled fixtures same-firewall | §7.3 + §3.3: fixture-generation privacy gate. |
| `OBL-PROPA-NEW-V15-05` | Post-retrieval critique prompt surface | §7.3: critique prompt registration / critique-utility eligibility, bounded by policy ceilings. |
| `OBL-PROPA-NEW-V15-06` | Counterfactual ambiguity classification + additive synthesis prompt surfaces | §7.3: prompt-surface eligibility and learning-policy ceilings. |
| `OBL-XDOC-PROPA-DSPY-TARGETS-01` | Four DSPy target IDs + eligibility discipline | §7.3 + §4.4 + §6.3: PropA owns registry; DOC81 supplies eligibility ceilings. |
| `OBL-PROPA-NEW-SOURCE-EXCLUSION-FILTER-01` | Source-exclusion rule | §7.2. |
| `OBL-D81-TOPIC-COLLECTION-SUPPRESSION-01` | Topic-level collection suppression | §7.1. |
| `OBL-PROPA-LOCALFILEEXPORT-OUTBOUND-PATCH-01` | `local_file_export` treated as egress | §7.4 + DestinationPolicyCrosswalk. |
```

**Lints / fixtures:**

```text
propa.opa_landing_table_stale_after_fix
propa.schema_changing_iteration_without_migration_plan_not_manual_review
fixture.propa.firewalled_learning_signal_same_firewall_only
fixture.propa.schema_changing_iteration_without_migration_plan_routes_manual_review
```

---

## 4.13 Safe-label disclosure policy

```typescript
interface SafeLabelDisclosurePolicy extends E0DurableRecord {
  policy_id: string;
  schema_owner: 'DOC81';

  disclosure_class: DisclosureClass;
  disclosure_vector: DisclosurePermissionVector;

  protected_reason_class:
    | 'privileged'
    | 'sealed'
    | 'firewalled'
    | 'personal'
    | 'client_confidential'
    | 'matter_specific'
    | 'policy_unknown';

  count_disclosure_policy: CountDisclosurePolicy;

  /** Required iff may_disclose_existence = true. Forbidden when disclosure_class = 'not_disclosable'. */
  default_label_ref?: SafeReferenceLabelRef;

  /** Internal-only label for audit/Inspector redaction paths; never model-visible unless disclosure permits. */
  internal_suppressed_manifest_label_ref?: SafeReferenceLabelRef;

  inspector_label_ref?: SafeReferenceLabelRef;
  effective_policy_ref: EffectiveMemoryPolicyRef;
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
safe_label.default_label_present_when_not_disclosable
safe_label.existence_disclosed_when_not_disclosable
safe_label.exact_count_disclosed_without_full_disclosure
fixture.disclosure.not_disclosable_has_no_user_visible_label
fixture.disclosure.count_bucketed_unless_full
```

---

## 4.14 Policy UI export for DOC86

DOC86 must render from DOC81 output without recomputing policy.

```typescript
interface PolicyUIExport extends E0DurableRecord {
  export_id: string;
  schema_owner: 'DOC81';

  object_ref: MemoryObjectRef;
  coordination_trace_ref: string;

  policy_stamp_ref: PolicyStampRef;
  policy_stamp_scope_ref: PolicyStampScopeRef;
  scope_items: Array<{
    action: MemoryPolicyAction;
    destination?: E0OutboundDestinationClass;
    effective_policy_ref: EffectiveMemoryPolicyRef;
    restamp_eligibility: 'eligible' | 'downgrade_only' | 'blocked' | 'requires_disambiguation';
    visible_action_disposition:
      | 'enabled'
      | 'disabled_policy_blocked'
      | 'disabled_requires_restamp'
      | 'disabled_requires_disambiguation'
      | 'hidden_not_disclosable';
  }>;

  disclosure_class: DisclosureClass;
  disclosure_vector: DisclosurePermissionVector;

  safe_label_policy_ref: string;
  safe_label_constraints: {
    default_label_ref?: SafeReferenceLabelRef;
    inspector_label_ref?: SafeReferenceLabelRef;
    may_disclose_existence: boolean;
    may_disclose_container_type: boolean;
    may_disclose_topic_label: boolean;
    may_disclose_source_title: boolean;
    count_disclosure_mode: CountDisclosureMode;
    may_disclose_reason_summary: boolean;
  };

  pending_disambiguation_ref?: PolicyDisambiguationRequestRef;
  expires_at?: string;
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

**Rule:**

```text
DOC86 consumes PolicyUIExport as a render input only. DOC86 may map it to DOC86-owned AvailabilityDisposition, VisibleContextActionSpec, InspectorVisibilityPlan, and notices, but it MUST NOT recompute EffectiveMemoryPolicy.
```

**Lints / fixtures:**

```text
ui.policy_export_missing_action_destination_eligibility
ui.doc86_recomputed_effective_policy
fixture.ui.policy_export_renders_without_recomputing_meet
fixture.cache.safe_label_vocab_change_invalidates_policy_ui_export
```

---

## 4.15 Disambiguation request and answer

### 4.15.1 Request union

`fallback_if_unanswered = defer` should only be legal when `blocked_until_answered = false`.

```typescript
type PolicyDisambiguationRequest =
  | BlockingPolicyDisambiguationRequest
  | NonBlockingPolicyDisambiguationRequest;

interface PolicyDisambiguationRequestBase extends E0DurableRecord {
  request_id: PolicyDisambiguationRequestRef;
  schema_owner: 'DOC81';
  question_kind:
    | 'scope_confirmation'
    | 'destination_confirmation'
    | 'identity_confirmation'
    | 'privilege_review'
    | 'export_confirmation'
    | 'carryover_confirmation';
  safe_prompt_label_ref: SafeReferenceLabelRef;
  may_name_object: boolean;
  may_name_scope: boolean;
  policy_decision_ref: MemoryPolicyDecisionRef;
  deadline_at?: string;
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}

interface BlockingPolicyDisambiguationRequest extends PolicyDisambiguationRequestBase {
  blocked_until_answered: true;
  fallback_if_unanswered: 'block' | 'reference_only' | 'search_only';
}

interface NonBlockingPolicyDisambiguationRequest extends PolicyDisambiguationRequestBase {
  blocked_until_answered: false;
  fallback_if_unanswered: 'block' | 'reference_only' | 'search_only' | 'defer';
}
```

### 4.15.2 Answer/effect object

```typescript
interface PolicyDisambiguationAnswer extends E0DurableRecord {
  answer_id: PolicyDisambiguationAnswerRef;
  schema_owner: 'DOC81';

  request_ref: PolicyDisambiguationRequestRef;
  answered_at: string; // RFC3339-UTC
  answered_by: PrincipalRef;

  answer_value:
    | 'confirm_scope'
    | 'deny_scope'
    | 'confirm_destination'
    | 'deny_destination'
    | 'narrow_to_safe_material_only'
    | 'unknown_or_no_answer';

  /** An answer never grants capability directly. It can only create a new scope/policy input under the current generation, then force a new meet. */
  effect:
    | 'new_scope_resolution_required'
    | 'new_policy_decision_required'
    | 'restamp_required'
    | 'fallback_applied'
    | 'block';

  resulting_scope_resolution_ref?: ScopeResolutionResultRef;
  resulting_policy_decision_ref?: MemoryPolicyDecisionRef;
  resulting_effective_policy_ref?: EffectiveMemoryPolicyRef;

  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
policy.blocking_disambiguation_with_defer_fallback
policy.disambiguation_answer_used_as_direct_allow
state.disambiguation_without_terminal_state
fixture.policy.blocking_disambiguation_cannot_defer_forever
fixture.policy.user_confirmation_forces_new_meet_not_direct_allow
```

---

## 4.16 Scope model patches

### 4.16.1 Synthetic unknown scope

```typescript
type ScopeKind =
  | 'project'
  | 'matter'
  | 'engagement'
  | 'research_topic'
  | 'initiative'
  | 'library'
  | 'personal_domain'
  | 'global'
  | 'unknown_synthetic';

interface ScopeIdentityRoot extends E0DurableRecord {
  scope_ref: ScopeRef;
  schema_owner: 'DOC81';
  display_name?: string; // NOT a safe label; not for disclosure
  scope_kind: ScopeKind;
  owner_principal_ref?: PrincipalRef;
  visibility_class_ref?: VisibilityClassRef;
  lifecycle_state: 'active' | 'deprecated' | 'synthetic_unknown';
}
```

**Lints / fixtures:**

```text
scope.unknown_resolution_reused_global_scope
fixture.scope.unresolvable_object_gets_synthetic_unknown_scope_not_global
```

### 4.16.2 Pairwise equivalence confidence

```typescript
interface ScopeEquivalencePairEvidence {
  left_scope_ref: ScopeRef;
  right_scope_ref: ScopeRef;
  confidence: number; // 0..1
  evidence_refs: string[];
  evaluated_under_model_generation_id?: string;
  reason_codes: ReasonCodeId[];
}

interface ScopeEquivalenceBinding extends E0DurableRecord {
  binding_id: ScopeEquivalenceBindingRef;
  schema_owner: 'DOC81';

  scope_refs: ScopeRef[]; // ≥2
  equivalence_basis:
    | 'explicit_user_binding'
    | 'entity_resolution'
    | 'folder_binding'
    | 'project_binding'
    | 'corpus_binding'
    | 'manual_merge'
    | 'migration_inferred';

  pairwise_evidence: ScopeEquivalencePairEvidence[];

  /** Computed as min(pairwise_evidence.confidence) across every required pair. Any missing pair for a cluster of size >2 is treated as 0. */
  cluster_confidence: number;

  threshold_evaluation_ref: ThresholdEvaluationRef;
  collapse_disposition:
    | 'collapse_confirmed'
    | 'do_not_collapse_below_threshold'
    | 'requires_user_confirmation'
    | 'invalid_missing_pairwise_evidence';

  domain_threshold_ref: DomainProfileId;
  reason_codes: ReasonCodeId[];
}
```

Formula:

```text
required_pairs = n * (n - 1) / 2
if pairwise_evidence.count < required_pairs:
  cluster_confidence = 0
else:
  cluster_confidence = min(pair.confidence for all required pairs)

collapse_allowed iff cluster_confidence >= resolved_threshold_value
```

**Lints / fixtures:**

```text
scope.equivalence_cluster_without_pairwise_matrix
scope.equivalence_cluster_confidence_not_min_pairwise
fixture.scope.three_scope_cluster_low_pair_prevents_collapse
```

### 4.16.3 Scope resolution confidence breakdown

```typescript
interface ScopeResolutionConfidenceBreakdown {
  identity_confidence?: number;
  boundary_confidence?: number;
  affinity_confidence?: number;
  destination_relation_confidence?: number;
  sensitivity_classification_confidence?: number;
  population_freshness_confidence?: number;

  aggregation_method: 'minimum_of_required_components';
  required_components: Array<
    | 'identity_confidence'
    | 'boundary_confidence'
    | 'destination_relation_confidence'
    | 'sensitivity_classification_confidence'
    | 'population_freshness_confidence'
  >;

  aggregate_confidence: number; // min(required component values); missing required component = 0
}
```

Formula:

```text
aggregate_confidence =
  min(component_value for component in required_components)
  where missing required component = 0

if aggregate_confidence < resolved_threshold_value:
  minimum_conservatism_floor >= reference_only_candidate
```

### 4.16.4 Scope resolution result additions

```typescript
interface ScopeResolutionResult extends E0DurableRecord {
  resolution_ref: ScopeResolutionResultRef;
  schema_owner: 'DOC81';

  request_ref: string;
  object_ref: MemoryObjectRef;
  object_scope_ref: ScopeRef;
  request_scope_ref?: ScopeRef;
  destination_scope_ref?: ScopeRef;

  relation_to_request: ScopeAffinity;
  relation_to_destination?: string;
  boundary_ref: ScopeBoundaryRef;

  minimum_conservatism_floor: ScopeConservatismFloor;
  confidence_breakdown: ScopeResolutionConfidenceBreakdown;
  threshold_evaluation_ref: ThresholdEvaluationRef;

  population_generation_inputs: Array<{
    scope_ref: ScopeRef;
    scope_population_generation_id: string;
    population_state_at_resolution: 'healthy' | 'sparse' | 'stale' | 'rebuilding' | 'unavailable';
  }>;

  sensitivity_inputs: Array<{
    source_ref?: SourceRef;
    propa_classification_ref: string;
    classification_generation_id: string;
    classification_state:
      | 'final'
      | 'provisional_source_only'
      | 'unclassified'
      | 'deferred_unavailable'
      | 'quarantined_review';
    fail_closed_required: boolean;
  }>;

  protection_derivation: ScopeProtectionStateDerivation;

  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

### 4.16.5 Protection flags

```typescript
type ScopeProtectionFlag =
  | 'matter_or_project_sensitive'
  | 'firewalled'
  | 'sealed'
  | 'personal_private'
  | 'client_confidential'
  | 'privileged'
  | 'classification_unknown';

interface ScopeProtectionStateDerivation {
  active_flags: ScopeProtectionFlag[];

  /** Most restrictive resolved state for backward-compatible summary displays. */
  resolved_protection_state:
    | 'ordinary'
    | 'matter_or_project_sensitive'
    | 'personal_private'
    | 'client_confidential'
    | 'firewalled'
    | 'privileged'
    | 'sealed'
    | 'unknown_sensitive';

  derivation_rule: 'max_restrictiveness_over_active_flags';
  classification_input_refs: string[];
  classification_generation_id: string;
  reason_codes: ReasonCodeId[];
}
```

Rank:

```text
ordinary < matter_or_project_sensitive < personal_private < client_confidential < firewalled < privileged < sealed < unknown_sensitive
```

**Lints / fixtures:**

```text
scope.resolution_confidence_weighted_average_used
scope.resolution_missing_confidence_component_not_zeroed
scope.population_generation_not_pinned_to_resolution
scope.multiple_protection_flags_collapsed_without_derivation
fixture.scope.low_destination_confidence_forces_conservative_floor
fixture.scope.population_generation_bump_invalidates_scope_resolution_result
fixture.scope.sealed_plus_personal_resolves_to_sealed_but_preserves_flags
```

### 4.16.6 Scope container relation cleanup

Rename misleading membership-like relation kinds:

```typescript
type ScopeContainerRelationKind =
  | 'contains'
  | 'linked_library'
  | 'source_topology_link'     // replaces source_membership
  | 'project_binding'
  | 'topic_topology_link'      // replaces topic_membership
  | 'episode_touched_scope'
  | 'analogical_relation';
```

Add load-bearing effects:

```typescript
interface ScopeContainerRelation extends E0DurableRecord {
  relation_id: ScopeContainerRelationRef;
  schema_owner: 'DOC81';
  container_scope_ref: ScopeRef;
  member_scope_ref: ScopeRef;
  relation_kind: ScopeContainerRelationKind;

  policy_load_bearing: boolean;
  load_bearing_effects?: Array<{
    applies_to_actions: MemoryPolicyAction[];
    tightens_axes: Array<
      'content_fidelity' | 'locality' | 'learning_scope' | 'mutation_authority' | 'disclosure_class'
    >;
    max_floor?: ScopeConservatismFloor;
    may_widen: false;
    reason_codes: ReasonCodeId[];
  }>;

  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
scope.container_relation_widened_meet
scope.membership_term_used_for_topology_relation
scope.policy_load_bearing_boolean_without_effects
fixture.scope.container_relation_cannot_widen
```

---

## 4.17 Threshold evaluation

Threshold semantics must include comparator, equality rule, versions, and conservative handling of missing/NaN.

```typescript
interface ThresholdEvaluation extends E0DurableRecord {
  evaluation_id: ThresholdEvaluationRef;
  schema_owner: 'DOC81';

  threshold_kind:
    | 'scope_equivalence_confidence'
    | 'scope_resolution_confidence'
    | 'contamination_risk'
    | 'topic_risk_score'
    | 'population_freshness'
    | 'topic_match_confidence';

  domain_profile_ref: DomainProfileId;
  domain_profile_registry_version: SchemaVersionRef;

  observed_value: number;          // finite number
  threshold_value: number;         // finite number
  comparator: 'gte_pass' | 'gt_pass' | 'lte_pass' | 'lt_pass';

  /** Conservative equality rule:
   *  confidence passes at >= threshold;
   *  risk blocks at >= threshold.
   */
  equality_rule: 'pass_on_equal' | 'block_on_equal';

  prior_state?: string;
  enter_threshold_value?: number;
  exit_threshold_value?: number;
  hysteresis_applied: boolean;

  disposition:
    | 'pass'
    | 'fail_closed'
    | 'requires_review'
    | 'defer_until_more_evidence';

  reason_codes: ReasonCodeId[];
}
```

Recommended defaults:

```text
confidence gates: pass iff observed_value >= threshold_value
risk gates: block/reroute iff observed_value >= threshold_value
unknown / NaN / missing observed_value: fail_closed
hysteresis: allowed for UI/review state flapping; forbidden for hard safety gates unless it tightens
```

**Lints / fixtures:**

```text
threshold.equality_rule_missing
threshold.nan_or_missing_value_not_failed_closed
threshold.hysteresis_widened_safety_gate
fixture.threshold.risk_equal_threshold_blocks
fixture.threshold.confidence_equal_threshold_passes
```

---

## 4.18 Collection suppression

### 4.18.1 Collection mode rank

```typescript
type CollectionMode = 'collect' | 'suppress' | 'exclude';

const COLLECTION_MODE_RANK: Record<CollectionMode, number> = {
  collect: 0,
  suppress: 1,
  exclude: 2,
};

function meetCollectionMode(modes: CollectionMode[]): CollectionMode {
  if (modes.length === 0) return 'suppress'; // fail-closed until topic/governance resolved
  return modes.reduce((a, b) =>
    COLLECTION_MODE_RANK[b] > COLLECTION_MODE_RANK[a] ? b : a
  );
}
```

### 4.18.2 Governance and evaluation

```typescript
interface CollectionModeSuppressionGovernance extends E0DurableRecord {
  governance_id: string;
  schema_owner: 'DOC81';

  topic_ref: TopicRef;
  collection_mode: CollectionMode;

  backfill_on_mode_change:
    | 'none'
    | 'hide_existing'
    | 'reference_only_existing'
    | 'invalidate_existing_until_review'
    | 'legal_hold_preserve_but_suppress_injection';

  existing_material_scan_required: boolean; // required when collection_mode = 'exclude'
  existing_material_scan_ref?: string;

  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}

interface CollectionSuppressionEvaluation extends E0DurableRecord {
  evaluation_id: string;
  schema_owner: 'DOC81';

  candidate_object_ref?: MemoryObjectRef;
  topic_ref_candidates: TopicRef[];
  matched_governance_refs: string[];

  topic_match_confidence_max: number;
  topic_match_confidence_min: number;
  ambiguity_state: 'unambiguous' | 'ambiguous' | 'no_topic_match';

  ec_surface_collection_enabled: boolean;
  ec_incognito_active: boolean;
  effective_state_generation_id: EffectiveStateGenerationId;

  effective_collection_mode: CollectionMode;

  disposition:
    | 'admit_collect'
    | 'refuse_suppress'
    | 'refuse_exclude_and_backfill'
    | 'defer_review_fail_closed';

  existing_material_backfill_required: boolean;
  existing_material_backfill_ref?: string;

  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

Rules:

```text
If any matched governance is exclude → effective_collection_mode = exclude.
If any matched governance is suppress and none exclude → suppress.
If topic match is ambiguous and any candidate is suppress/exclude → defer_review_fail_closed.
If EC collection disabled or incognito active → suppress regardless of DOC81 mode.
If collection_mode changes to exclude → existing_material_backfill_required = true.
```

**Lints / fixtures:**

```text
collection.ambiguous_topic_match_collected
collection.exclude_without_existing_material_backfill
collection.incognito_not_part_of_collection_policy_freshness
fixture.collection.ambiguous_privacy_topic_fails_closed
fixture.collection.exclude_filters_existing_material
```

---

## 4.19 Source exclusion and extraction route envelope

### 4.19.1 Fix input/output cycle and add closure

```typescript
interface SourceExclusionFilterRule extends E0DurableRecord {
  rule_id: string;
  schema_owner: 'DOC81';

  excluded_source_ref: SourceRef;
  exclusion_scope_ref?: ScopeRef;
  applies_to_actions: MemoryPolicyAction[];
  exclusion_reason_code: ReasonCodeId;

  policy_input_decision_ref: MemoryPolicyDecisionRef;
  contributes_to_effective_policy: true;

  exclusion_closure:
    | 'direct_source_only'
    | 'direct_source_plus_segments'
    | 'source_and_derived_artifacts'
    | 'source_derived_learning_and_delivery_artifacts';

  derived_artifact_classes_blocked: Array<
    | 'artifact_segment'
    | 'evidence_support_edge'
    | 'source_bound_synthesis'
    | 'context_product'
    | 'learning_signal'
    | 'cached_delivery_artifact'
  >;

  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

**Rule:**

```text
SourceExclusionFilterRule is an input to EffectiveMemoryPolicy, not a dependent output. The resulting EffectiveMemoryPolicy.contributing_decision_refs records the policy decision that carried the source-exclusion rule.
```

### 4.19.2 Extraction route envelope

```typescript
type ExtractionRouteContextRef = Brand<string, 'ExtractionRouteContextRef'>;
type CollectionGateResultRef = Brand<string, 'CollectionGateResultRef'>;
type SourceExclusionFilterRuleRef = Brand<string, 'SourceExclusionFilterRuleRef'>;
type CollectionModeSuppressionGovernanceRef = Brand<string, 'CollectionModeSuppressionGovernanceRef'>;

interface ExtractionRoutePolicyEnvelope extends E0DurableRecord {
  envelope_id: string;
  schema_owner: 'DOC81';

  extraction_route_context_ref: ExtractionRouteContextRef; // DOC82-owned route/provenance context
  source_ref?: SourceRef;

  permitted_actions: Extract<
    MemoryPolicyAction,
    'collect' | 'extract' | 'classify' | 'write_candidate'
  >[];

  effective_policy_ref: EffectiveMemoryPolicyRef;
  scope_resolution_ref: ScopeResolutionResultRef;
  collection_gate_result_ref?: CollectionGateResultRef;

  source_exclusion_rule_refs: SourceExclusionFilterRuleRef[];
  topic_collection_governance_ref?: CollectionModeSuppressionGovernanceRef;

  unknown_source_disposition: 'fail_closed';
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
source_exclusion.effective_policy_ref_input_output_cycle
source_exclusion.derived_artifact_not_excluded
extraction.source_exclusion_boolean_without_rule_ref
fixture.source_exclusion.derived_artifact_exclusion_closure
fixture.extraction.source_exclusion_rule_ref_blocks_matching_source
```

---

## 4.20 Source revocation cascade

### 4.20.1 Cascade run state

```typescript
interface CascadingSourceInvalidation extends E0DurableRecord {
  invalidation_id: string;
  schema_owner: 'DOC81';

  source_ref: SourceRef;
  affected_set_manifest_ref: string;
  trigger_reason: 'source_deleted' | 'source_revoked' | 'source_policy_changed' | 'source_integrity_failed';

  required_plane_outcomes: {
    doc82_support_edges: 'invalidated' | 'verify_required';
    doc87_memberships: 'restamped' | 'removed' | 'hidden';
    doc84_delivery_artifacts: 'invalidated';
    doc84_published_views: 'invalidated_or_restamped';
    doc85_learning_signals: 'ineligible_for_future_utility';
    doc86_surfaces: 'safe_labeled' | 'suppressed';
  };

  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}

interface PlaneCascadeStatus {
  status: 'pending' | 'completed' | 'blocked' | 'degraded' | 'not_applicable';
  started_at?: string;
  completed_at?: string;
  receipt_refs: string[];
  retry_count: number;
  reason_codes: ReasonCodeId[];
}

interface CascadingSourceInvalidationRun extends E0DurableRecord {
  run_id: string;
  schema_owner: 'DOC81';

  invalidation_ref: string;
  idempotency_key: string;
  execution_order:
    | 'freeze_admission_then_support_membership_delivery_learning_ui'
    | 'architect_confirmed_other';

  plane_statuses: {
    doc83_inflight_extraction?: PlaneCascadeStatus;
    doc82_support_edges: PlaneCascadeStatus;
    doc87_memberships: PlaneCascadeStatus;
    doc84_delivery_artifacts: PlaneCascadeStatus;
    doc84_published_views: PlaneCascadeStatus;
    doc85_learning_signals: PlaneCascadeStatus;
    doc86_surfaces: PlaneCascadeStatus;
  };

  all_required_planes_complete: boolean;
  degraded_reason_codes: ReasonCodeId[];
  freshness_key: PolicyRuntimeFreshnessKey;
}
```

`doc83_inflight_extraction` is a pre-fanout freeze, not a sixth settled cascade plane. It prevents in-flight extraction candidates from using revoked material while the cascade is underway.

### 4.20.2 Last active support edge proof

```typescript
interface LastActiveSupportEdgeEvaluation extends E0DurableRecord {
  evaluation_id: string;
  schema_owner: 'DOC81';

  source_ref: SourceRef;
  affected_set_manifest_ref: string;
  evaluated_variant_refs: MemoryObjectRef[];

  lawful_support_edges_remaining_count: number;
  remaining_support_edge_refs: string[];
  last_active_support_edge_lost: boolean;

  polarity_recompute_trace_ref?: string; // required if net warrant rises after contrary-source removal
  evaluated_under_freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
revocation.published_view_not_invalidated_after_revocation
revocation.cascade_plane_without_receipt
revocation.last_active_support_edge_boolean_without_denominator
fixture.revocation.cascade_run_is_idempotent_per_plane
fixture.quota.revocation_cascade_chunks_without_skipping_planes
```

---

## 4.21 Legal hold

### 4.21.1 Selector semantics

```typescript
type LegalHoldSelector =
  | { selector_kind: 'scope_wide'; hold_scope_ref: ScopeRef }
  | { selector_kind: 'explicit_objects'; object_refs: MemoryObjectRef[] }
  | { selector_kind: 'scope_plus_explicit_objects'; hold_scope_ref: ScopeRef; object_refs: MemoryObjectRef[] };

interface LegalHoldState extends E0DurableRecord {
  hold_id: string;
  schema_owner: 'DOC81';
  state: 'held' | 'released';

  selector: LegalHoldSelector;

  placed_at: string;
  placed_by: PrincipalRef;
  release_clearance_ref?: string;
  released_at?: string;

  reason_codes: ReasonCodeId[];
}
```

### 4.21.2 Destructive job registry

```typescript
interface DestructiveJobLegalHoldGate extends E0DurableRecord {
  gate_id: string;
  schema_owner: 'DOC81';

  job_kind: string;
  owning_doc: OwnerDocId;
  executor: 'EC' | 'DOC23' | 'DOC25' | 'DOC72' | 'DOC84' | 'DOC85' | 'other';

  destructive_effect:
    | 'hard_destruction'
    | 'redaction'
    | 'retention_expiry'
    | 'materialization_clearing'
    | 'semantic_folding'
    | 'tombstone'
    | 'archive_or_suppress';

  target_selector_ref: string;
  legal_hold_query_required: true;

  held_object_disposition:
    | 'skip'
    | 'require_manual_clearance'
    | 'redact_only_with_clearance'
    | 'block_job';

  clearance_required_for: Array<'hard_destruction' | 'redaction' | 'materialization_clearing'>;

  clearance_ref_required: boolean;
  reason_codes: ReasonCodeId[];
}

interface DestructiveJobLegalHoldRegistry extends E0DurableRecord {
  registry_id: 'doc81.destructive_job_legal_hold_registry';
  schema_owner: 'DOC81';

  jobs: DestructiveJobLegalHoldGate[];
  default_for_unregistered_destructive_job: 'block';
  reason_code_for_unregistered_job: ReasonCodeId;
}
```

**Lints / fixtures:**

```text
legal_hold.destructive_job_unregistered
legal_hold.selector_semantics_ambiguous
fixture.legal_hold.unregistered_destructive_job_fails_closed
```

---

## 4.22 Relation traversal

Remove `| string`. Unknown relation kinds must default-deny.

```typescript
type RelationTraversalPolicyKind =
  | 'conflict_projection'
  | 'analogy'
  | 'supersession'
  | 'comparison_injection';

type RelationTraversalUnknownDisposition =
  | 'block'
  | 'safe_blocked_notice_only_if_existence_disclosable';

interface RelationTraversalScopeCheckPolicy extends E0DurableRecord {
  policy_id: string;
  schema_owner: 'DOC81';

  relation_kind: RelationTraversalPolicyKind;

  related_variant_check_required: true;
  required_scope_check: 'resolve_and_policy_check_every_related_variant';

  policy_actions_required: MemoryPolicyAction[];
  required_effective_policy_ref_per_variant: true;

  cross_scope_disposition: 'may_not_render_summarize_or_name';
  firewalled_disposition: 'may_not_render_summarize_or_name';
  not_disclosable_disposition: 'safe_blocked_notice_only_if_existence_disclosable';

  allowed_notice_product_kinds: Array<'blocked_scope_notice' | 'reference_only_notice'>;
  forbidden_product_kinds: Array<'assertion_packet' | 'topic_slice' | 'library_source_slice' | 'cu_source_bound_synthesis'>;

  obligations: PolicyObligationRef[];
  reason_codes: ReasonCodeId[];
}

interface RelationTraversalPolicyRegistry extends E0DurableRecord {
  registry_id: 'doc81.relation_traversal_policy_registry';
  schema_owner: 'DOC81';

  policies: RelationTraversalScopeCheckPolicy[];

  unknown_relation_kind_disposition: RelationTraversalUnknownDisposition;
  unknown_relation_kind_reason_code: ReasonCodeId;
}
```

Budget and trace:

```typescript
interface RelationTraversalBudget extends E0DurableRecord {
  budget_id: string;
  schema_owner: 'DOC81';

  relation_kind: RelationTraversalPolicyKind;

  max_depth: number;
  max_related_variants: number;
  max_scope_resolutions: number;
  cycle_handling: 'visited_set_skip_and_record' | 'fail_closed_on_cycle';
  on_budget_exceeded:
    | 'fail_closed'
    | 'render_safe_truncated_notice_if_disclosable';

  reason_codes: ReasonCodeId[];
}

interface RelationTraversalExecutionTrace extends E0DurableRecord {
  trace_id: string;
  schema_owner: 'DOC81';

  root_object_ref: MemoryObjectRef;
  relation_kind: string;

  visited_object_refs: MemoryObjectRef[];
  skipped_cycle_refs: MemoryObjectRef[];
  truncated: boolean;
  budget_ref: string;

  per_variant_policy_refs: Array<{
    object_ref: MemoryObjectRef;
    scope_resolution_ref: ScopeResolutionResultRef;
    effective_policy_ref: EffectiveMemoryPolicyRef;
  }>;

  final_disposition:
    | 'all_variants_checked'
    | 'blocked_budget_exceeded'
    | 'safe_truncated_notice_only'
    | 'blocked_unknown_relation_kind';

  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
relation.unknown_kind_without_default_deny
relation.traversal_without_budget
relation.traversal_cycle_not_handled
fixture.relation.unknown_relation_kind_fails_closed
fixture.relation.cyclic_relation_graph_does_not_loop_or_leak
fixture.relation.budget_exceeded_fails_closed
```

---

## 4.23 Policy membrane disposition derivation

The membrane decision should be derived from effective policy, not independently synthesized.

```typescript
interface PolicyMembraneDispositionDerivation extends E0DurableRecord {
  derivation_id: string;
  schema_owner: 'DOC81';

  effective_policy_ref: EffectiveMemoryPolicyRef;
  boundary_ref: ScopeBoundaryRef;
  action: MemoryPolicyAction;

  derived_crossing_disposition:
    | 'crossing_permitted'
    | 'crossing_permitted_with_obligations'
    | 'crossing_reference_only'
    | 'crossing_blocked'
    | 'crossing_requires_disambiguation';

  derivation_inputs: {
    effective_content_fidelity: ContentFidelityLevel;
    effective_locality: LocalityLevel;
    effective_disclosure_class: DisclosureClass;
    boundary_kind: ScopeBoundaryKind;
    obligations: PolicyObligationRef[];
  };

  invariant: 'membrane_disposition_derived_from_effective_policy_not_independent';
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
policy.membrane_disposition_not_derived_from_effective_policy
fixture.policy.membrane_crossing_blocked_when_effective_locality_blocked
```

---

## 4.24 Topic risk confirmation

```typescript
type TopicRiskClassRef = Brand<string, 'TopicRiskClassRef'>;
type TopicRiskConfirmationRef = Brand<string, 'TopicRiskConfirmationRef'>;
type ActorRef = PrincipalRef;

interface TopicRiskConfirmation extends E0DurableRecord {
  confirmation_id: TopicRiskConfirmationRef;
  schema_owner: 'DOC81';

  topic_ref: TopicRef;
  risk_class_ref: TopicRiskClassRef;

  confirmed_by: ActorRef;
  confirmed_at: string; // RFC3339-UTC
  confirmation_scope:
    | 'permit_collection'
    | 'permit_substantive_injection'
    | 'permit_both';

  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}

interface TopicRiskClass extends E0DurableRecord {
  risk_class_id: TopicRiskClassRef;
  schema_owner: 'DOC81';
  topic_ref: TopicRef;

  risk_level: 'low' | 'medium' | 'high' | 'unknown';
  auto_created_lens_only: boolean;

  collection_permitted: boolean;
  substantive_injection_permitted: boolean;
  requires_user_confirmation: boolean;

  confirmation_ref?: TopicRiskConfirmationRef; // required if collection/substantive injection permitted by user confirmation

  effective_policy_ref: EffectiveMemoryPolicyRef;
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
topic_risk.collection_permitted_without_confirmation
fixture.topic_risk.auto_created_topic_lens_only_until_confirmed
```

---

## 4.25 DAMS policy cap derivation

`PolicyCappedDAMSInput` should be keyed to a context product request and its ceiling should be derived.

```typescript
type DAMSEligibilityCeiling =
  | 'inline_allowed'
  | 'reference_only_max'
  | 'search_only_max'
  | 'notice_only_max'
  | 'blocked';

interface PolicyCappedDAMSInput extends E0DurableRecord {
  capped_input_id: string;
  schema_owner: 'DOC81';

  object_ref: MemoryObjectRef;
  action: MemoryPolicyAction;
  context_product_request_id: string;
  context_product_kind: string;

  eligibility_ceiling: DAMSEligibilityCeiling;

  effective_policy_ref: EffectiveMemoryPolicyRef;
  scope_affinity?: ScopeAffinity;
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}

interface EligibilityCeilingDerivation extends E0DurableRecord {
  derivation_id: string;
  schema_owner: 'DOC81';

  context_product_request_id: string;
  context_product_kind: string;

  effective_policy_ref: EffectiveMemoryPolicyRef;
  effective_content_fidelity: ContentFidelityLevel;
  effective_locality: LocalityLevel;
  effective_disclosure_class: DisclosureClass;
  effective_disclosure_vector: DisclosurePermissionVector;

  derived_ceiling: DAMSEligibilityCeiling;

  derivation_rule: 'doc81.dams_ceiling.v1';
  reason_codes: ReasonCodeId[];
}
```

Formula:

```text
if effective_locality == blocked
   or effective_content_fidelity == none
   or effective_disclosure_class == not_disclosable:
      blocked

else if effective_disclosure_class == existence_only:
      notice_only_max

else if effective_content_fidelity == safe_label:
      notice_only_max

else if effective_content_fidelity == reference_only:
      reference_only_max

else if effective_content_fidelity == redacted:
      reference_only_max unless context product kind allows redacted inline

else if effective_content_fidelity == full:
      inline_allowed unless another axis/obligation caps it
```

Override hook:

```typescript
interface ProductKindCeilingOverride {
  context_product_kind: string;
  max_allowed_ceiling: DAMSEligibilityCeiling;
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
dams.ceiling_without_derivation
dams.redacted_treated_as_full_without_product_override
fixture.dams.ceiling_derives_from_effective_policy_axes
```

---

## 4.26 Contamination risk measurement and threshold

```typescript
interface ContaminationRiskMeasurement extends E0DurableRecord {
  measurement_id: string;
  schema_owner: 'DOC81';

  object_ref: MemoryObjectRef;
  context_product_request_id: string;

  risk_model_ref: string;              // DOC84-owned model; DOC81 references
  risk_model_generation_id: string;

  risk_score: number;                  // 0..1
  confidence: number;                  // 0..1
  contributing_feature_refs: string[];

  measured_under_policy_ref: EffectiveMemoryPolicyRef;
  freshness_key: PolicyRuntimeFreshnessKey;
  reason_codes: ReasonCodeId[];
}

interface ContaminationRiskThresholdRule extends E0DurableRecord {
  rule_id: string;
  schema_owner: 'DOC81';

  domain_profile_ref: DomainProfileId;
  risk_model_ref: string;
  risk_model_generation_id: string;

  threshold_value: number; // 0..1
  comparator: 'gte';
  equality_rule: 'block_on_equal';

  over_threshold_disposition:
    | 'reroute_warning_constraint'
    | 'reroute_blocked_scope_notice'
    | 'reroute_reference_only_notice'
    | 'suppress';

  reason_codes: ReasonCodeId[];
}
```

Formula:

```text
if risk_score is missing/NaN/outside [0,1] → suppress or fail_closed
if risk_score >= threshold_value → over_threshold_disposition
else → ordinary DAMS ranking within PolicyCappedDAMSInput ceiling
```

**Lints / fixtures:**

```text
contamination.threshold_without_value_or_model
threshold.risk_equal_threshold_blocks
fixture.contamination.over_threshold_reroutes_not_linear_penalty
```

---

## 4.27 Search coverage proof for not-found vs not-searched

```typescript
interface ScopeSearchCoverageProof extends E0DurableRecord {
  proof_id: string;
  schema_owner: 'DOC81';

  request_ref: string;
  scope_resolution_ref: ScopeResolutionResultRef;

  required_scope_refs: ScopeRef[];
  searched_scope_refs: ScopeRef[];
  not_searched_scope_refs: ScopeRef[];

  coverage_ratio: number; // searched / required
  coverage_denominator: number;
  coverage_numerator: number;

  result_disposition:
    | 'fully_searched'
    | 'partially_searched'
    | 'not_searched'
    | 'search_unavailable_fail_closed';

  may_emit_not_found: boolean; // true iff fully_searched
  reason_codes: ReasonCodeId[];
}
```

Formula:

```text
coverage_denominator = count(required_scope_refs)
coverage_numerator = count(intersection(required_scope_refs, searched_scope_refs))
coverage_ratio = coverage_numerator / max(coverage_denominator, 1)

may_emit_not_found = coverage_denominator > 0
                     and coverage_numerator == coverage_denominator
                     and not_searched_scope_refs is empty
```

**Lints / fixtures:**

```text
disclosure.not_found_without_scope_search_coverage_proof
fixture.disclosure.partial_search_cannot_emit_not_found
```

---

## 4.28 Cache keys

DOC81 should specialize E0 reproducibility into scope/policy/UI cache keys.

```typescript
interface ScopeResolutionCacheKey {
  request_input_hash: ContentHash;
  request_scope_ref?: ScopeRef;
  object_ref: MemoryObjectRef;
  object_scope_ref?: ScopeRef;
  source_scope_ref?: ScopeRef;
  destination_scope_ref?: ScopeRef;
  principal_ref?: PrincipalRef;

  scope_topology_generation_id: string;
  equivalence_binding_generation_id: string;
  container_relation_generation_id: string;
  scope_population_generation_inputs: Array<{
    scope_ref: ScopeRef;
    scope_population_generation_id: string;
  }>;

  classification_generation_id?: string;
  domain_profile_registry_version: SchemaVersionRef;
  policy_generation_id: PolicyGenerationId;
  effective_state_generation_id: EffectiveStateGenerationId;
}

interface EffectivePolicyCacheKey {
  object_ref: MemoryObjectRef;
  action: MemoryPolicyAction;
  destination?: E0OutboundDestinationClass;

  policy_evaluation_context_ref: PolicyEvaluationContextRef;
  scope_resolution_ref?: ScopeResolutionResultRef;

  policy_generation_id: PolicyGenerationId;
  effective_state_generation_id: EffectiveStateGenerationId;
  compiled_policy_evaluator_hash: ContentHash;

  contributing_decision_hash: ContentHash;
  obligation_set_hash: ContentHash;

  reason_code_registry_version: SchemaVersionRef;
  domain_profile_registry_version: SchemaVersionRef;
  safe_label_vocabulary_version?: SchemaVersionRef;
}
```

Rule:

```text
A cached ScopeResolutionResult / EffectiveMemoryPolicy / PolicyUIExport is reusable only if its cache key exactly matches the current key. No partial reuse across policy_generation_id, effective_state_generation_id, compiled_policy_evaluator_hash, safe-label vocabulary, source/classification generation, or population generation.
```

**Lints / fixtures:**

```text
cache.policy_reused_across_effective_state_generation
cache.safe_label_vocab_change_not_in_ui_export_key
cache.scope_resolution_reused_after_population_generation_change
fixture.cache.scope_resolution_invalidates_on_population_generation_change
fixture.cache.policy_export_invalidates_on_safe_label_vocab_change
```

---

## 4.29 Batch/fan-out quota envelopes

DOC81 fan-out operations include policy generation restamp, source revocation cascade, collection exclude backfill, relation traversal, and legal-hold scans.

```typescript
interface DOC81BatchOperationQuotaEnvelope extends E0DurableRecord {
  quota_envelope_id: string;
  schema_owner: 'DOC81';

  operation_kind:
    | 'policy_generation_restamp_batch'
    | 'source_revocation_cascade'
    | 'collection_exclude_backfill'
    | 'relation_traversal_scope_check'
    | 'legal_hold_destructive_job_scan';

  estimated_object_count: number;
  estimated_policy_decision_count: number;
  estimated_scope_resolution_count: number;
  estimated_plane_updates: number;

  max_object_count: number;
  max_policy_decision_count: number;
  max_wall_clock_ms?: number;
  max_cost_usd?: number;

  on_quota_exceeded:
    | 'chunk_and_continue'
    | 'pause_and_surface_review'
    | 'fail_closed_for_affected_items'
    | 'manual_approval_required';

  idempotency_key: string;
  progress_ref?: string;
  reason_codes: ReasonCodeId[];
}
```

**Lints / fixtures:**

```text
quota.policy_restamp_batch_unbounded
quota.collection_exclude_backfill_unbounded
quota.relation_traversal_unbounded
quota.revocation_cascade_unbounded
fixture.quota.revocation_cascade_chunks_without_skipping_planes
```

---

## 4.30 Lifecycle state pattern

Several DOC81 objects describe lifecycle in prose but do not carry state. Add either this common field pattern or per-object discriminated unions.

```typescript
type DOC81LifecycleState =
  | 'candidate'
  | 'active'
  | 'confirmed'
  | 'superseded'
  | 'invalidated'
  | 'expired'
  | 'blocked'
  | 'archived';

interface DOC81LifecycleFields {
  lifecycle_state: DOC81LifecycleState;
  activated_at?: string;
  superseded_at?: string;
  invalidated_at?: string;
  invalidated_by_ref?: string;
  replacement_ref?: string;
}
```

Objects needing lifecycle fields or discriminated unions:

```text
ScopeEquivalenceBinding
ScopeContainerRelation
PolicyStamp
PolicyStampRestamp
PolicyDisambiguationRequest
CascadingSourceInvalidation
CollectionModeSuppressionGovernance
SourceExclusionFilterRule
TopicRiskClass
LegalHoldState
```

Example disambiguation state machine:

```typescript
type PolicyDisambiguationLifecycleRecord =
  | PolicyDisambiguationRaised
  | PolicyDisambiguationAnswered
  | PolicyDisambiguationTimedOut
  | PolicyDisambiguationFallbackApplied;

interface PolicyDisambiguationRaised extends E0DurableRecord {
  state: 'raised';
  request_id: PolicyDisambiguationRequestRef;
  deadline_at: string;
  blocked_until_answered: boolean;
  fallback_if_unanswered: 'block' | 'reference_only' | 'search_only' | 'defer';
}

interface PolicyDisambiguationAnswered extends E0DurableRecord {
  state: 'answered';
  request_id: PolicyDisambiguationRequestRef;
  answer_ref: PolicyDisambiguationAnswerRef;
  resumed_pipeline_ref: string;
}

interface PolicyDisambiguationTimedOut extends E0DurableRecord {
  state: 'timed_out';
  request_id: PolicyDisambiguationRequestRef;
  timed_out_at: string;
  fallback_if_unanswered: 'block' | 'reference_only' | 'search_only'; // no defer here
}

interface PolicyDisambiguationFallbackApplied extends E0DurableRecord {
  state: 'fallback_applied';
  request_id: PolicyDisambiguationRequestRef;
  fallback_applied: 'block' | 'reference_only' | 'search_only';
  resulting_effective_policy_ref?: EffectiveMemoryPolicyRef;
}
```

**Lint family:**

```text
state.proposed_scope_equivalence_consumed
state.invalidated_policy_stamp_used
state.disambiguation_without_terminal_state
state.source_exclusion_rule_used_after_invalidated_generation
state.topic_risk_class_permitted_without_confirmed_state
```

---

# 5. Quantitative / spec-function / technical-function audit

## 5.1 Overall function-audit grade

**Grade:** `C+`

The architecture is strong qualitatively, but many quantitative and functional contracts are not yet executable. The draft needs formulas, denominators, rank maps, cache keys, state transition invariants, and edge-case behavior for thresholds and aggregation.

## 5.2 Function audit table

| Function / mechanism | Current state | Edge case | Required patch |
|---|---|---|---|
| Per-axis policy meet | Has pseudo-code and conceptual rank order. | Missing axis in one applicable decision is filtered out. | Explicit rank maps; malformed applicable decision floors/blocks. |
| Destination-specific egress gate | Destinationless decisions can join meet. | Destinationless export policy authorizes cloud/email export. | Egress precondition; require destination-specific decision. |
| Policy freshness | Mostly `policy_generation_id`. | EC application/incognito toggle changes but cached policy reused. | `PolicyRuntimeFreshnessKey`. |
| Decision applicability | Object/action/destination only. | Local interactive decision reused for background/cloud render. | `PolicyEvaluationContext`. |
| Stamp validity | One policy ref, multi-action scope. | Retrieval stamp authorizes render/export. | `PolicyStampScopeItem`. |
| Restamp ceiling | Opaque `original_ceiling_ref`. | Restamp chain rebases to prior lowered/widened restamp. | `PolicyCeilingSnapshot`, root chain integrity. |
| Scope equivalence confidence | One scalar per cluster. | A≈B high, B≈C high, A≈C low collapses all. | Pairwise matrix; cluster confidence = min pairwise. |
| Scope resolution confidence | One scalar. | High identity masks low destination/classification confidence. | Minimum-of-required-components breakdown. |
| Thresholds | Pointers/enums. | Risk exactly equals threshold; NaN/missing differs by implementation. | `ThresholdEvaluation`. |
| Conservatism floor | Named enum only. | DOC84/DOC86 interpret `safe_label_candidate` differently. | `ConservatismFloorEffect`. |
| Disclosure meet | Scalar class. | Count/source/title disclosure permissions mis-aggregated. | `DisclosurePermissionVector`. |
| DAMS cap | Enum only. | Redacted object treated as full inline. | `EligibilityCeilingDerivation`. |
| Contamination threshold | Threshold ref only. | No risk model/value/comparator. | `ContaminationRiskMeasurement` + threshold rule. |
| Collection suppression aggregation | “Most restrictive wins” prose. | Ambiguous privacy topic gets collected. | Rank table + `CollectionSuppressionEvaluation`. |
| Source revocation fan-out | Five-plane outcome object. | One plane silently fails. | `CascadingSourceInvalidationRun`. |
| Last active support edge | Boolean. | Boolean set true despite remaining support edge. | Denominator proof object. |
| Relation traversal | Required check but no budget/cycle semantics. | Cyclic graph or 10k related variants. | Budget + trace. |
| Cache hashes | E0 has general reproducibility key; DOC81 lacks specialized keys. | Safe-label vocab changes but UI export reused. | Scope/policy/UI cache keys. |
| State transitions | Lifecycle prose. | Proposed equivalence consumed as confirmed. | State fields / discriminated unions. |
| Cost/quota | E0 has quota lints; DOC81 operations unbounded. | Restamp/cascade storm. | Batch quota envelopes. |
| Not-found vs not-searched | Correct prose. | Partial scope search emits “not found.” | `ScopeSearchCoverageProof`. |

---

# 6. Updated schema grades

| Contract / schema | Final grade | Key reason |
|---|---:|---|
| `ScopeIdentityRoot` | B | Needs `unknown_synthetic`, branded refs, visibility vocab ref. |
| `ScopeEquivalenceBinding` | B− | Scalar confidence inadequate for clusters; needs pairwise matrix. |
| `ScopeContainerRelation` | B− | Topology correct; names and `policy_load_bearing` too coarse. |
| `ScopeBoundary` | A− | Clean topology-only object. |
| `ScopeAffinity` | B+ | Union acceptable; `shared` needs definition/confirmation. |
| `ScopeResolutionResult` | B− | Good role; needs confidence breakdown, population gen, sensitivity/protection provenance. |
| `ScopeResolutionTrace` | A− | Strong inspectability concept. |
| `ScopePopulationHealth` | B+ | Good, but must link into resolution cache invalidation. |
| `MemoryPolicyDecision` | B− | Good 5-axis model; missing policy evaluation context and exposure context. |
| `EffectiveMemoryPolicy` | B− | Meet backbone right; context, egress, malformed-axis, floor, disclosure-vector gaps. |
| `PolicyMembraneDecision` | C+ | Separate from dimensional policy, but needs derivation object. |
| `PolicyObligation` | C | Missing PropA kinds, typed parameters, conflict/discharge model, DOC84/DOC86 owners. |
| `PolicyStamp` / `PolicyStampScope` | C− | Single policy ref conflicts with multi-action/multi-destination scope. |
| `PolicyStampInvalidation` | B− | Needs per-action/destination axis deltas. |
| `PolicyStampRestamp` | C | Needs discriminated union, root ceiling, blocked-no-policy semantics. |
| `PolicyDisambiguationRequest` | B− | Safe prompt design good; `defer` constraint and answer/effect missing. |
| `EpisodePolicyEpoch` | A− | Strong policy-generation boundary, but should include runtime freshness. |
| `SafeLabelDisclosurePolicy` | B− | Good direction; needs disclosure vector/count policy/default label conditionality. |
| `ExtractionRoutePolicyEnvelope` | B− | Right object; booleans/provenance too weak. |
| `PolicyCappedDAMSInput` | B− | Correct cap idea; needs product request and derivation formula. |
| `ContaminationRiskThresholdRule` | C | Veto concept right; lacks measurement/threshold/comparator. |
| `TopicRiskClass` | B− | Needs confirmation proof state. |
| `CascadingSourceInvalidation` | B− | Five-plane target right; lacks run state/idempotency/proofs. |
| `LegalHoldState` | B− | Correct invariant; selector and destructive-job registry missing. |
| `CollectionModeSuppressionGovernance` | B− | Placement right; ambiguity and backfill missing. |
| `SourceExclusionFilterRule` | C+ | Good row landing; input/output cycle and closure missing. |
| `RelationTraversalScopeCheckPolicy` | C+ | Rule right; `| string`, budget, cycle handling missing. |
| `PolicyUIExport` | C+ | Direction right; payload thin and not per-triple. |

---

# 7. Responses to the commission’s specific questions

## 7.1 Does the `EffectiveMemoryPolicy` meet work?

**Conceptually yes; mechanically not yet.**

The per-axis most-restrictive meet is the right backbone. It must not collapse to a scalar. It must not meet across actions. It must accumulate obligations. It must treat missing/unknown/incomparable values conservatively.

But the current meet is under-keyed and has a pseudo-code bug: it filters out missing axis values instead of flooring them. It also does not require destination-specific policy decisions for egress and does not incorporate disclosure-vector aggregation. Patch with `PolicyEvaluationContext`, rank maps, `DisclosurePermissionVector`, destination-specific preconditions, and `meet_v2`.

## 7.2 Are restamp and monotonicity sufficient?

**No.**

The monotonicity invariant is correct, but enforcement is not executable. `original_ceiling_ref` is opaque; restamp chains lack `root_stamp_ref`; `PolicyStampRestamp` is not a discriminated union; and block restamps still require a new effective policy ref.

Patch with `PolicyCeilingSnapshot`, `RestampChainIntegrity`, axis comparisons, and a restamp union.

## 7.3 Does the five-plane source revocation cascade cover all necessary fan-out?

**Mostly yes, but not proof-complete.**

The five settled planes are correct: support edges, memberships, delivery artifacts, learning signals, and UI/surfaces. The draft should add per-plane run state, idempotency key, receipts, retry/degraded status, published-view ownership, and a DOC83 in-flight extraction freeze before fan-out.

## 7.4 Is `LegalHoldState` sufficient?

**No.**

The flag and invariant are correct, but future destructive jobs can bypass it unless there is a `DestructiveJobLegalHoldRegistry` with default block for unregistered destructive jobs. Selector semantics also need a discriminated union.

## 7.5 Does collection-mode suppression block dummy/privacy topics?

**Directionally yes, but not fully.**

The design composes with EC toggles/incognito, which is right. The missing pieces are an executable rank table, ambiguity behavior, and `exclude` backfill for existing material. Ambiguous topic matches involving any suppress/exclude candidate must fail closed or route to review.

## 7.6 Are PropA obligations folded cleanly?

**Partially.**

§7.3 appears directionally corrected after the CODEX fix. But the lineage/landing table must be corrected, `ExposureContextSchema` must be structurally carried on policy decisions, `LearningScopeLevel` must add `same_firewall_only`, and `PolicyObligationKind` must include PropA V15 obligation kinds.

## 7.7 Is relation traversal dense enough?

**No.**

The rule is right: every related variant must be scope-resolved and policy-checked. The schema is not dense enough because `relation_kind | string` defeats exhaustiveness. Add a registry, default-deny unknowns, traversal budget, visited-set/cycle handling, and per-variant policy refs.

## 7.8 Is DOC81→DOC86 export sufficient?

**No.**

The direction is correct, but the payload is too thin. DOC86 needs per-action/per-destination eligibility, effective policies, restamp disposition, disambiguation refs, safe-label constraints, disclosure vector, count policy, and freshness key. DOC86 must render from this export and not recompute policy.

## 7.9 Is the scope model sufficient?

**Broadly yes, but not executable enough.**

Boundary vs affinity is good. Topology vs membership separation is good. Needed fixes: synthetic unknown scope, pairwise equivalence confidence, component confidence aggregation, population generation pinning, sensitivity/protection provenance, and better load-bearing effect definitions.

## 7.10 Should DOC81 be ratified?

**Not yet.**

The architecture should be approved directionally, but the draft needs one schema-level patch round before ratification.

---

# 8. A/A+ gap

## To reach A

DOC81 needs these executable foundations:

```text
PolicyRuntimeFreshnessKey
PolicyEvaluationContext
meet_v2
ConservatismFloorEffect
PolicyCeilingSnapshot
RestampChainIntegrity
DisclosurePermissionVector
ThresholdEvaluation
ActionPermissionPredicate
PolicyStampScopeItem
PolicyObligationConflict
PolicyObligationDischarge
CascadingSourceInvalidationRun
ScopeResolutionConfidenceBreakdown
ScopeEquivalencePairEvidence
ScopeSearchCoverageProof
RelationTraversalBudget
DOC81BatchOperationQuotaEnvelope
```

## To reach A+

It needs property-style fixtures and edge-case test vectors:

```text
fixture.policy.stamp_scope_item_matches_effective_policy_triple
fixture.policy.retrieval_stamp_cannot_authorize_render_or_export
fixture.policy.effective_state_generation_change_invalidates_cached_stamp
fixture.policy.exposure_context_changes_policy_decision_key
fixture.policy.destinationless_decision_cannot_authorize_egress
fixture.policy.malformed_axis_in_applicable_decision_floors_axis
fixture.policy.floor_mapping_tightens_each_axis
fixture.policy.restamp_of_restamp_compares_against_root_ceiling
fixture.policy.blocked_restamp_has_no_authorizing_effective_policy
fixture.policy.conflicting_obligations_fail_closed_for_action
fixture.disclosure.vector_meet_ands_boolean_permissions
fixture.disclosure.count_bucketed_unless_full
fixture.scope.three_scope_cluster_low_pair_prevents_collapse
fixture.scope.low_destination_confidence_forces_conservative_floor
fixture.scope.population_generation_bump_invalidates_scope_resolution_result
fixture.threshold.risk_equal_threshold_blocks
fixture.threshold.confidence_equal_threshold_passes
fixture.collection.ambiguous_privacy_topic_fails_closed
fixture.collection.exclude_filters_existing_material
fixture.source_exclusion.derived_artifact_exclusion_closure
fixture.revocation.cascade_run_is_idempotent_per_plane
fixture.cache.safe_label_vocab_change_invalidates_policy_ui_export
fixture.relation.cyclic_relation_graph_does_not_loop_or_leak
fixture.relation.budget_exceeded_fails_closed
fixture.quota.revocation_cascade_chunks_without_skipping_planes
fixture.disambiguation.answer_forces_new_meet_not_direct_allow
fixture.ui.policy_export_renders_without_recomputing_meet
```

---

# 9. Suggested amendment structure for DOC81

I would not scatter these fixes across prose paragraphs. I would add a consolidated patch section and then thread references back through the existing sections.

## 9.1 Add a new §3.0 “Policy evaluation context and runtime freshness”

Include:

- `PolicyRuntimeFreshnessKey`
- `PolicyEvaluationContext`
- cache/freshness invalidation rule
- active EC effective-state key requirement

## 9.2 Replace §3.2 meet pseudo-code with `meet_v2`

Include:

- explicit axis rank maps
- malformed-axis behavior
- context compatibility
- destination-specific egress precondition
- disclosure-vector meet
- floor application
- obligation conflict fail-closed

## 9.3 Replace §3.4 stamp/restamp model

Include:

- `PolicyStampScopeItem`
- revised `PolicyStamp`
- `PolicyCeilingSnapshot`
- restamp discriminated union
- root-chain integrity
- per-action/destination invalidation deltas

## 9.4 Expand §3.3 obligations

Include:

- expanded PropA obligation kinds
- typed obligation union
- conflict model
- discharge proof
- DOC84/DOC86 as owners

## 9.5 Add §4.1A disclosure vector

Include:

- `DisclosurePermissionVector`
- `CountDisclosurePolicy`
- scalar derivation
- safe-label default-label conditionality

## 9.6 Add §4.6A thresholds and confidence

Include:

- `ThresholdEvaluation`
- scope equivalence pairwise formula
- scope resolution confidence breakdown
- risk equal-threshold block rule

## 9.7 Add §4.7 action predicates

Include:

- action closure table
- permission predicate table
- prerequisite-policy checks

## 9.8 Expand §5 source revocation

Include:

- `CascadingSourceInvalidationRun`
- plane receipts
- last-active-support-edge proof
- DOC83 in-flight extraction freeze

## 9.9 Expand §6 legal hold and state transitions

Include:

- destructive job registry
- legal hold selector
- lifecycle state conventions

## 9.10 Replace §7.1/§7.2 source/suppression patches

Include:

- collection suppression rank and evaluation
- existing-material backfill
- source-exclusion input-cycle fix
- derived-artifact closure

## 9.11 Replace §8 relation traversal

Include:

- closed registry
- unknown default-deny
- budget/cycle handling
- trace

## 9.12 Replace §9 policy UI export

Include:

- per-triple export payload
- safe-label constraints
- disambiguation refs
- freshness key
- no recomputation rule

## 9.13 Correct §14.2 OPA landings

Use the replacement table in §4.12 of this document.

---

# 10. Final recommendation

The draft should receive a targeted schema-level patch round before ratification. The architectural split is sound and should be preserved. The revision should focus on making every runtime-critical invariant mechanically checkable.

The highest-risk runtime bug remains the `PolicyStamp` triple-binding issue: a stamp can appear valid for an action/destination not actually covered by its single `EffectiveMemoryPolicyRef`. The highest-risk systemic gap is freshness: without `effective_state_generation_id` and compiled evaluator hash, cached policy artifacts can survive runtime-state changes that should invalidate them.

A good acceptance criterion for the next draft is:

```text
A Stage 7 implementer can implement DOC81 policy/scope without inventing:
  - a policy decision key,
  - a meet algorithm edge case,
  - a disclosure aggregation formula,
  - a restamp ceiling comparison,
  - an egress destination rule,
  - an obligation discharge model,
  - a source revocation run state,
  - a collection suppression ambiguity rule,
  - a relation traversal budget,
  - or a cache freshness key.
```

Until that is true, DOC81 should remain `DESIGN_REVISION_NEEDED_BEFORE_RATIFICATION`.