Elnor Repo Reader

EC_Core_Addendum_A_V3_3_Compiled_Operative_Spec.md

Current Specs/EC Core/EC_Core_Addendum_A_V3_3_Compiled_Operative_Spec.md

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

Open text page · Open raw txt · Open path URL



**V3.3 changes from V3.2:** Additive corrective pass — fixed remaining purpose-statement placement issues, added the missing settings wireframes, and added explicit integration-target precision notes. No schema, code, route, SQL, or normative-rule changes.
# EC Core Addendum A — Data Operations, Processing, and System Management Architecture V3.3

**Date:** 2026-04-16  
**Status:** Compiled operative readability-remediated corrective revision. Carries forward the full V3.2 operative contract set and adds the remaining missing explanatory/wireframe/integration-precision fixes identified by the post-remediation audit, without altering schemas, routes, SQL, functions, or normative rules.  
**Supersedes:** EC Core Addendum A V3.2 in full.  
**Interpretation rule:** This document is the single operative EC Core companion spec for the current pair review cycle. It is not a delta overlay. Anything from prior EC Core Addendum A drafts that is not restated here is superseded.  
**Target doc:** EC Core (primary); cross-doc obligations into DOC72, DOC24, DOC8, DOC11, DOC12, DOC20, DOC21/22, DOC1, DOC15, DOC23  
**Scope:** Global memory hierarchy, effective runtime truth, route/command/read-model closure, one compiled policy engine, background orchestration, execution profile selection, token/cost governance, networking sync verification, backup/export/import, and settings wiring/inspection.

---

## 0. Governing posture and owner split

**Integrates into:** EC Core primary control-plane ownership notes, DOC24 runtime-consumer boundaries, DOC72/DOC1 ownership boundaries, DOC23 task-runtime posture, DOC11/OpenClaw runtime-truth seams.

**V3.2 changes from V3.1:** No contract changes. This section now restores the explanatory owner split and anti-phantom-control posture that earlier EC drafts carried in prose.

This section defines the control-plane rules that keep EC Core as the single runtime coordinator rather than a bag of disconnected toggles and helper services. It explains what EC owns, what it merely consumes from PropA/DOC72/DOC24, and why desired settings must never be confused with effective runtime truth.


### 0.1 Non-negotiables

1. **EC is the sole durable writer.**
2. **Q is read/control surface only.** Q SHALL NOT write durable truth directly.
3. **OpenClaw owns native runtime truth.** EC MAY observe, annotate, and route around OpenClaw; it SHALL NOT falsify OpenClaw runtime history.
4. **No phantom controls.** Every visible control SHALL map to a real command, route, durable write or explicit no-op, telemetry event, and refreshed read model.
5. **Desired config is not effective truth.** Every effective-state response SHALL distinguish `desired`, `effective`, and `divergence_reason_codes`.
6. **One compiled policy evaluator.** Runtime consumers SHALL NOT freehand-compose collection, visibility, source-policy, sharing, or exposure-guard logic.
7. **One canonical route registry.** PropA and EC shall not maintain parallel route inventories.
8. **One task registry.** All background work SHALL flow through EC’s orchestrator and task registry.
9. **No second durable writer through import/export, simulation, or inspection features.**
10. **Truthful degraded states are mandatory.**

### 0.2 Owner split

- **EC Core owns:** route contracts, command schemas for EC-owned settings, effective policy compilation/evaluation, effective runtime state, task registry, queue truth, execution-profile selection, token/cost governance, sync identity verification, import/export/backup infrastructure, manifest generation, and inspector surfaces.
- **PropA owns:** semantic policy inputs, prompt contracts, self-review artifacts, DSPy semantic rules, and PropA-owned command payload shapes.
- **DOC24 owns:** packet assembly, rendering, injection manifests, and runtime consumption of policy decisions.
- **DOC11/OpenClaw own:** final runtime dispatch truth.
- **DOC1 owns:** write gate, archive-not-delete, taint-aware promotion, and memory governance invariants.

### 0.3 Canonical collapse and supersession

This revision resolves all EC-side duplicate or partial contract realities from the V2 cycle.

**Normative rule:** the following are superseded and SHALL NOT be implemented:
- any non-EC-owned `ApiRouteContractRegistryEntrySchema`
- any partial EC route list not represented in the canonical registry
- any local/runtime evaluator logic that bypasses the compiled effective policy artifact
- any browser toggle whose behavior still captures metadata while claiming “browser off”

```ts
import { z } from "zod";

export const EcNonOperativeContractMarkerSchema = z.object({
  legacy_name: z.string().max(160),
  superseded_by: z.string().max(160),
  implementation_status: z.literal("MUST_NOT_IMPLEMENT"),
  reason: z.string().max(500),
  schema_version: z.literal(1),
});
```

### 0.4 Integration-target precision notes

EC Core owns the primary control-plane contracts in this companion pair, but some downstream surfaces and owner-doc landing points are still only known at the topic level rather than by exact subsection number. That uncertainty is about the later owner-doc adoption pass, not about the EC contracts in this document. Until that pass occurs, the topic-level landing zones named in each section are authoritative.

**Exact-number targets still pending later owner-doc adoption:**
- **DOC20** exact subsection numbers for Settings > Memory & Privacy, control-plane inspectors, export/backup surfaces, and why-blocked/policy simulation inspectors
- **DOC21 / DOC22** exact component ids and page ids for the new memory/privacy/processing/sync inspector surfaces
- **DOC24 / DOC11** exact runtime-consumer subsection numbers for packet gating / dispatch receipt consumers of EC policy decisions
- **DOC23** exact subsection numbers for task-registration rows, execution-profile requirements, and validation/canary runtime bindings

Coding agents SHALL use the named topic-level landing zones in this document until the later owner-doc compilation pass pins the exact numbered parent-doc sections.

---

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `EcNonOperativeContractMarkerSchema` | Consumed by coding agents, route/control-plane implementers, and companion-doc integrators | drafting, implementation planning, and owner-doc adoption | EC §0, PropA owner split, DOC24/DOC72/DOC1 boundary notes |

## 1. Global memory hierarchy and effective runtime truth

**Hierarchy concept (most aggressive → most granular)**

The controls in this section are deliberately hierarchical rather than flat. A coarse upstream control like the global kill switch or collection/application split must be able to dominate all lower settings, while more granular controls are still available for ordinary day-to-day tuning. That is why the read model distinguishes desired state from effective state and shows divergence reasons explicitly instead of pretending every toggle is independently authoritative.


**Integrates into:** EC Core primary settings/control plane, DOC20 (Settings > Memory & Privacy), DOC24 packet-application consumers, DOC11/OpenClaw runtime-truth display seams, DOC21/22 settings and inspector surfaces.

**V3.2 changes from V3.1:** No contract changes. This section now restores the lost hierarchy explanation, global-purpose prose, and user-facing control layouts for memory, processing, and incognito state.

This section defines the master control hierarchy for whether ELNOR collects memory, applies memory, pauses processing, or suppresses capture temporarily through incognito. It exists so every later collection, delivery, and learning feature has a single upstream truth about whether it is allowed to run and how that truth is shown honestly to the user.


### 1.1 Master settings schemas

**Settings UI wireframe — Settings > Memory & Privacy**

```text
Memory Controls
────────────────────────────────────────────────────────────────
MASTER
  Memory system enabled             [✓]
  Collection enabled                [✓]
  Application enabled               [✓]

PER-SURFACE COLLECTION
  Chat conversations                [✓]
  Browser metadata history          [ ]
  Browser entity extraction         [ ]
  Email processing                  [✓]
  Notes                             [✓]
  DOC12 rooms                       [✓]
  DOC12 panels                      [✓]
  DOC12 forums                      [✓]
  Demonstration sessions            [✓]
  Document viewer                   [✓]

PER-SUBSYSTEM APPLICATION
  DOC24 knowledge injection         [✓]
  Satisfaction Matrix influence     [✓]
  DOC8 learning outputs             [✓]
  Background processing outputs     [✓]

PROCESSING
  Pause all processing              [ ]
  Pause LLM processing only         [ ]

[Save settings] [Open effective-state inspector]
```

**Settings UI wireframe — Processing > Per-Task Controls**

```text
Per-Task Processing Controls
────────────────────────────────────────────────────────────────
Global
  Pause all processing              [ ]
  Pause LLM processing only         [ ]

Task types
  knowledge_deep_extraction         [✓]   Reason: —
  sensitivity_content_classification[✓]   Reason: —
  knowledge_self_review             [✓]   Reason: —
  dspy_optimizer                    [ ]   Reason: explicit user enable required
  satisfaction_bundle_compilation   [✓]   Reason: —

[Save processing controls] [Open orchestrator status]
```

**Settings UI wireframe — Behavioral Escape Hatches**

```text
Behavioral Escape Hatches
────────────────────────────────────────────────────────────────
Suppress onboarding questions       [ ]
Suppress DOC8 feedback requests     [ ]
Suppress standing procedure offers  [ ]
Suppress non-blocking questions     [ ]
Suppress all optional questions     [ ]

[Save escape hatches] [Preview effective runtime state]
```

```ts
export const BehavioralEscapeHatchesSchema = z.object({
  suppress_onboarding_questions: z.boolean().default(false),
  suppress_doc8_feedback_requests: z.boolean().default(false),
  suppress_standing_procedure_offers: z.boolean().default(false),
  suppress_non_blocking_questions: z.boolean().default(false),
  suppress_all_optional_questions: z.boolean().default(false),
  schema_version: z.literal(1),
});

export const GlobalMemoryControlSchemaV3 = z.object({
  memory_system_enabled: z.boolean().default(true),
  collection_enabled: z.boolean().default(true),
  application_enabled: z.boolean().default(true),
  surface_collection: z.object({
    chat_conversations: z.boolean().default(true),
    browser_metadata_history: z.boolean().default(true),
    browser_entity_extraction: z.boolean().default(true),
    email_processing: z.boolean().default(true),
    notes: z.boolean().default(true),
    doc12_rooms: z.boolean().default(true),
    doc12_panels: z.boolean().default(true),
    doc12_forums: z.boolean().default(true),
    demonstration_sessions: z.boolean().default(true),
    document_viewer: z.boolean().default(true),
  }).default({}),
  subsystem_application: z.object({
    doc24_knowledge_injection: z.boolean().default(true),
    satisfaction_matrix_influence: z.boolean().default(true),
    doc8_learning_outputs: z.boolean().default(true),
    background_processing_outputs: z.boolean().default(true),
  }).default({}),
  schema_version: z.literal(3),
});

export const IncognitoStateSchema = z.object({
  global_incognito: z.boolean().default(false),
  chat_incognito_session_ids: z.array(z.string().max(120)).default([]),
  browser_incognito_session_ids: z.array(z.string().max(120)).default([]),
  schema_version: z.literal(1),
});

export const ProcessingControlSchema = z.object({
  pause_all_processing: z.boolean().default(false),
  pause_llm_processing_only: z.boolean().default(false),
  per_task_type: z.record(z.string(), z.object({
    enabled: z.boolean().default(true),
    reason_codes: z.array(z.string().max(80)).default([]),
  })).default({}),
  schema_version: z.literal(1),
});
```

### 1.2 Effective runtime state

```ts
export const EffectiveRuntimeStateSchemaV4 = z.object({
  desired: z.object({
    memory_controls: GlobalMemoryControlSchemaV3,
    incognito_state: IncognitoStateSchema,
    processing_controls: ProcessingControlSchema,
    behavioral_escape_hatches: BehavioralEscapeHatchesSchema,
  }),
  effective: z.object({
    memory_system_enabled: z.boolean(),
    collection_enabled: z.boolean(),
    application_enabled: z.boolean(),
    chat_collection_enabled: z.boolean(),
    browser_metadata_capture_enabled: z.boolean(),
    browser_entity_extraction_enabled: z.boolean(),
    email_processing_enabled: z.boolean(),
    notes_processing_enabled: z.boolean(),
    orchestrator_enabled: z.boolean(),
  }),
  divergence_reason_codes: z.array(z.string().max(80)).default([]),
  upstream_inhibitions: z.array(z.object({
    control_id: z.string().max(120),
    upstream_inhibited: z.boolean().default(false),
    reason_codes: z.array(z.string().max(80)).default([]),
  })).default([]),
  last_mutation_event_id: z.string().max(120).optional(),
  computed_at: z.string().datetime(),
  schema_version: z.literal(4),
});

export const EffectiveRuntimeStateChangedEventSchema = z.object({
  event_id: z.string().max(120),
  changed_control_ids: z.array(z.string().max(120)).default([]),
  refreshed_read_model_ids: z.array(z.string().max(120)).default([]),
  created_at: z.string().datetime(),
  schema_version: z.literal(1),
});

export const ReadModelRefreshContractSchema = z.object({
  trigger_command_id: z.string().max(120),
  refreshed_read_model_ids: z.array(z.string().max(120)).default([]),
  runtime_event_name: z.literal("runtime.effective_state_changed"),
  schema_version: z.literal(1),
});
```

**Normative rules**
1. `browser_metadata_history = false` SHALL suppress URL/title/domain/dwell-time persistence.
2. Browser incognito SHALL suppress both metadata history and entity extraction.
3. `memory_system_enabled = false` SHALL force all lower collection/application controls effectively off.
4. On restart with an active incognito boot guard, effective collection SHALL remain off until explicit user acknowledgement.
5. Any mutation to memory controls, incognito state, processing controls, or behavioral escape hatches SHALL emit `runtime.effective_state_changed` and SHALL refresh the affected read models before UI success is reported.

### 1.3 Incognito boot guard

**Settings UI wireframe — Incognito and Temporary Capture Controls**

```text
Incognito Controls
────────────────────────────────────────────────────────────────
Global incognito                   [ ]
Current chat incognito             [ ]
Current browser session incognito  [ ]
Restore requires acknowledgement   [✓]

Boot guard status
  Guard active: no
  Reason codes: —

[Enable incognito] [Clear temporary suppressions]
```


```ts
export const IncognitoBootGuardSchema = z.object({
  guard_active: z.boolean().default(false),
  restore_requires_ack: z.boolean().default(true),
  reason_codes: z.array(z.string().max(80)).default([]),
  schema_version: z.literal(1),
});
```

**Normative rule:** if `guard_active = true`, effective collection SHALL be false until the user explicitly acknowledges restoration.

### 1.4 Effective-state computation pseudocode

```ts
export function computeEffectiveRuntimeState(input: {
  desired_controls: z.infer<typeof GlobalMemoryControlSchemaV3>;
  incognito_state: z.infer<typeof IncognitoStateSchema>;
  processing_controls: z.infer<typeof ProcessingControlSchema>;
  escape_hatches: z.infer<typeof BehavioralEscapeHatchesSchema>;
  boot_guard: z.infer<typeof IncognitoBootGuardSchema>;
}): z.infer<typeof EffectiveRuntimeStateSchemaV3> {
  const reasons: string[] = [];

  const memory_system_enabled = input.desired_controls.memory_system_enabled;
  const collection_enabled =
    memory_system_enabled &&
    input.desired_controls.collection_enabled &&
    !input.incognito_state.global_incognito &&
    !input.boot_guard.guard_active;

  if (!memory_system_enabled) reasons.push("memory_system_disabled");
  if (!input.desired_controls.collection_enabled) reasons.push("collection_disabled_by_user");
  if (input.incognito_state.global_incognito) reasons.push("global_incognito_active");
  if (input.boot_guard.guard_active) reasons.push("incognito_boot_guard_active");

  const browser_metadata_capture_enabled =
    collection_enabled &&
    input.desired_controls.surface_collection.browser_metadata_history &&
    !input.incognito_state.global_incognito;

  const browser_entity_extraction_enabled =
    collection_enabled &&
    input.desired_controls.surface_collection.browser_entity_extraction &&
    !input.incognito_state.global_incognito;

  return {
    desired: {
      memory_controls: input.desired_controls,
      incognito_state: input.incognito_state,
      processing_controls: input.processing_controls,
      behavioral_escape_hatches: input.escape_hatches,
    },
    effective: {
      memory_system_enabled,
      collection_enabled,
      application_enabled:
        memory_system_enabled && input.desired_controls.application_enabled,
      chat_collection_enabled:
        collection_enabled && input.desired_controls.surface_collection.chat_conversations,
      browser_metadata_capture_enabled,
      browser_entity_extraction_enabled,
      email_processing_enabled:
        collection_enabled && input.desired_controls.surface_collection.email_processing,
      notes_processing_enabled:
        collection_enabled && input.desired_controls.surface_collection.notes,
      orchestrator_enabled: !input.processing_controls.pause_all_processing,
    },
    divergence_reason_codes: reasons,
    upstream_inhibitions: [],
    computed_at: new Date().toISOString(),
    schema_version: 3,
  };
}
```

---

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `BehavioralEscapeHatchesSchema` | Consumed by settings persistence, effective-state computation, runtime inspectors, and any consumer that needs honest collection/application state | settings save, startup, incognito changes, read-model refresh, and live runtime inspection | EC §1, DOC20 settings/read surfaces, DOC24 packet gating consumers |
| `GlobalMemoryControlSchemaV3` | Consumed by settings persistence, effective-state computation, runtime inspectors, and any consumer that needs honest collection/application state | settings save, startup, incognito changes, read-model refresh, and live runtime inspection | EC §1, DOC20 settings/read surfaces, DOC24 packet gating consumers |
| `IncognitoStateSchema` | Consumed by settings persistence, effective-state computation, runtime inspectors, and any consumer that needs honest collection/application state | settings save, startup, incognito changes, read-model refresh, and live runtime inspection | EC §1, DOC20 settings/read surfaces, DOC24 packet gating consumers |
| `ProcessingControlSchema` | Consumed by settings persistence, effective-state computation, runtime inspectors, and any consumer that needs honest collection/application state | settings save, startup, incognito changes, read-model refresh, and live runtime inspection | EC §1, DOC20 settings/read surfaces, DOC24 packet gating consumers |
| `EffectiveRuntimeStateSchemaV4` | Consumed by settings persistence, effective-state computation, runtime inspectors, and any consumer that needs honest collection/application state | settings save, startup, incognito changes, read-model refresh, and live runtime inspection | EC §1, DOC20 settings/read surfaces, DOC24 packet gating consumers |
| `EffectiveRuntimeStateChangedEventSchema` | Consumed by settings persistence, effective-state computation, runtime inspectors, and any consumer that needs honest collection/application state | settings save, startup, incognito changes, read-model refresh, and live runtime inspection | EC §1, DOC20 settings/read surfaces, DOC24 packet gating consumers |
| `ReadModelRefreshContractSchema` | Consumed by settings persistence, effective-state computation, runtime inspectors, and any consumer that needs honest collection/application state | settings save, startup, incognito changes, read-model refresh, and live runtime inspection | EC §1, DOC20 settings/read surfaces, DOC24 packet gating consumers |
| `IncognitoBootGuardSchema` | Consumed by settings persistence, effective-state computation, runtime inspectors, and any consumer that needs honest collection/application state | settings save, startup, incognito changes, read-model refresh, and live runtime inspection | EC §1, DOC20 settings/read surfaces, DOC24 packet gating consumers |

## 2. Canonical route and command registry ownership

This section defines the single registry that proves which commands, routes, and success/read-model contracts are actually operative in the current pair. It exists so the UI, runtime, inspectors, and companion specs do not each invent their own partial route lists, and so a coding agent can trace every visible control back to one canonical registration surface. The wireframe below shows the operator-facing truth manifest and route inspector that consume this registry.

**Inspector wireframe — Canonical Truth Manifest / Route Registry**

```text
Canonical Truth Manifest
────────────────────────────────────────────────────────────────
Generation: manifest_2026_04_15_001
Routes registered: 84
Commands registered: 51
Task types registered: 19
Prompt contracts registered: 13
Policy evaluator hash: 8fa3...

[Routes] [Commands] [Task types] [Prompt registry] [Download manifest]

Selected route
  route_id: api.system.memory-controls.update
  owner_doc: EC Core
  request_schema: UpdateMemoryControlsCommandSchema
  success_read_model: EffectiveRuntimeStateSchemaV4
  telemetry: memory_controls.updated
```

**Why this exists:** EC owns the canonical registry so the UI, runtime, and companion specs do not each invent their own partial route lists.


**Why EC owns this registry**

The route registry is an EC concern because EC is the only place that can honestly say whether a control, command, or inspector path is real end-to-end. If companion specs maintained their own freehand route inventories, the system would immediately drift back into the phantom-button and half-wired-surface failures that earlier reviews were trying to eliminate.


**Owned by:** EC Core Addendum A (primary route/command registry infrastructure), with cross-doc obligations into PropA, DOC20, DOC21, and DOC22 for route inventory, settings wiring, and read-surface registration.

**V3.2 changes from V3.1:** No contract changes. This section now restores the explanatory prose for why EC owns the route registry and what the canonical truth manifest is used for.

This section defines the single canonical inventory of commands and HTTP routes that the current companion pair is allowed to expose. It exists to stop route drift, duplicate inventories, and decorative UI controls by forcing all visible behavior to be traceable back to one registered command/route contract.


### 2.1 Route contract registry

```ts
export const ApiRouteContractRegistryEntrySchemaV2 = z.object({
  route_id: z.string().max(160),
  method: z.enum(["GET", "POST"]),
  path: z.string().max(240),
  request_schema_ref: z.string().max(200).optional(),
  success_schema_ref: z.string().max(200),
  blocked_codes: z.array(z.string().max(80)).default([]),
  degraded_codes: z.array(z.string().max(80)).default([]),
  telemetry_event: z.string().max(120),
  invalidates_read_models: z.array(z.string().max(120)).default([]),
  runtime_consumer_id: z.string().max(160).optional(),
  schema_version: z.literal(2),
});
```

### 2.2 Required EC-owned route set

The coding agent SHALL register at least the following routes:

| Route ID | Method | Path | Success schema |
|---|---|---|---|
| `memory_controls_effective_get` | GET | `/api/system/memory-controls/effective` | `EffectiveRuntimeStateSchemaV4` |
| `memory_controls_update` | POST | `/api/system/memory-controls` | `EffectiveRuntimeStateSchemaV4` |
| `incognito_update` | POST | `/api/system/incognito` | `EffectiveRuntimeStateSchemaV4` |
| `processing_controls_get` | GET | `/api/system/processing-controls` | `ProcessingControlSchema` |
| `processing_controls_update` | POST | `/api/system/processing-controls` | `ProcessingControlSchema` |
| `behavioral_escapes_get` | GET | `/api/system/behavioral-escapes` | `BehavioralEscapeHatchesSchema` |
| `behavioral_escapes_update` | POST | `/api/system/behavioral-escapes` | `BehavioralEscapeHatchesSchema` |
| `background_agents_get` | GET | `/api/system/background-agents` | `BackgroundAgentProfileSchemaV5[]` |
| `background_agent_create` | POST | `/api/system/background-agents` | `BackgroundAgentProfileSchemaV5` |
| `background_agent_update` | POST | `/api/system/background-agents/:profileId` | `BackgroundAgentProfileSchemaV5` |
| `orchestrator_status_get` | GET | `/api/system/orchestrator/status` | `BackgroundJobOrchestratorStatusSchemaV2` |
| `token_governance_get` | GET | `/api/system/token-governance` | `TokenGovernanceSettingsSchemaV2` |
| `token_governance_update` | POST | `/api/system/token-governance` | `TokenGovernanceSettingsSchemaV2` |
| `processing_budget_effective_get` | GET | `/api/system/processing-budget/effective` | `EffectiveProcessingBudgetDecisionSchemaV2` |
| `sensitive_collection_policy_get` | GET | `/api/system/privacy/collection-policy` | `SensitiveCollectionPolicyReadModelSchema` |
| `sensitive_collection_policy_update` | POST | `/api/system/privacy/collection-policy` | `SensitiveCollectionPolicyReadModelSchema` |
| `source_rules_get` | GET | `/api/system/privacy/source-rules` | `SourceRuleRegistryReadModelSchema` |
| `source_rule_upsert` | POST | `/api/system/privacy/source-rules/upsert` | `SourceRuleRegistryReadModelSchema` |
| `source_rule_delete` | POST | `/api/system/privacy/source-rules/delete` | `SourceRuleRegistryReadModelSchema` |
| `extraction_visibility_get` | GET | `/api/system/privacy/extraction-visibility` | `ExtractionVisibilityPolicyReadModelSchema` |
| `extraction_visibility_update` | POST | `/api/system/privacy/extraction-visibility` | `ExtractionVisibilityPolicyReadModelSchema` |
| `sharing_policy_get` | GET | `/api/system/privacy/sharing-policy` | `SharingPolicyReadModelSchema` |
| `sharing_policy_update` | POST | `/api/system/privacy/sharing-policy` | `SharingPolicyReadModelSchema` |
| `policy_simulate_post` | POST | `/api/system/policy/simulate` | `PolicySimulationResultSchema` |
| `why_blocked_get` | GET | `/api/system/runtime/why-blocked` | `WhyBlockedInspectorResponseSchemaV2` |
| `canonical_truth_manifest_get` | GET | `/api/system/canonical-truth-manifest` | `CanonicalTruthManifestSchema` |
| `sync_preflight_post` | POST | `/api/system/sync/preflight` | `SyncBatchPreflightReceiptSchemaV3` |
| `sync_execute_post` | POST | `/api/system/sync/execute` | `SyncExecutionReceiptSchemaV2` |
| `exposure_override_post` | POST | `/api/knowledge/exposure-override` | `MemoryExposureOverrideReceiptSchemaV2` |
| `self_review_run_post` | POST | `/api/system/self-review/run` | `SelfReviewRunReceiptSchema` |
| `self_review_runs_get` | GET | `/api/system/self-review/runs/:runId` | `SelfReviewRunReadModelSchema` |
| `self_review_datasets_get` | GET | `/api/system/self-review/datasets` | `SelfReviewDatasetCatalogReadModelSchema` |
| `self_review_dataset_upsert` | POST | `/api/system/self-review/datasets/upsert` | `SelfReviewDatasetCatalogReadModelSchema` |
| `dspy_run_post` | POST | `/api/system/dspy/run` | `DspyRunReceiptSchema` |
| `dspy_catalog_get` | GET | `/api/system/dspy/catalog` | `DspyCatalogReadModelSchema` |
| `dspy_model_options_get` | GET | `/api/system/dspy/model-options` | `DspyModelOptionsReadModelSchema` |
| `review_item_action_post` | POST | `/api/knowledge/review-items/:itemId/action` | `KnowledgeReviewQueueItemSchemaV2` |
| `review_item_quarantine_post` | POST | `/api/knowledge/review-items/:itemId/quarantine` | `KnowledgeReviewQueueItemSchemaV2` |
| `node_reclassify_post` | POST | `/api/knowledge/nodes/:nodeId/reclassify` | `NodeMutationReceiptSchema` |
| `node_rescope_post` | POST | `/api/knowledge/nodes/:nodeId/rescope` | `NodeMutationReceiptSchema` |
| `node_merge_tombstone_post` | POST | `/api/knowledge/nodes/:nodeId/merge-tombstone` | `NodeMutationReceiptSchema` |

**Normative route rules**
1. Every route SHALL have request, success, blocked, degraded, and error behavior.
2. Every route SHALL declare telemetry and invalidation targets.
3. CI SHALL fail if a visible control lacks a registered route.
4. EC is the sole route-registry owner for the PropA/EC pair; PropA may define command payloads and read models, but SHALL NOT define a second canonical route registry.

### 2.3 Canonical truth manifest

```ts
export const CanonicalTruthManifestSchema = z.object({
  generated_at: z.string().datetime(),
  evaluator_generation_id: z.string().max(120),
  evaluator_impl_hash: z.string().length(64),
  route_registry_generation_id: z.string().max(120),
  task_registry_generation_id: z.string().max(120),
  prompt_registry_generation_id: z.string().max(120).optional(),
  artifacts: z.array(z.object({
    artifact_id: z.string().max(160),
    owner_doc: z.string().max(80),
    artifact_kind: z.enum(["schema", "route", "task_type", "prompt_contract", "policy_generation"]),
    version_ref: z.string().max(120),
  })).default([]),
  schema_version: z.literal(1),
});
```

---

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `ApiRouteContractRegistryEntrySchemaV2` | Consumed by route registration, UI builders, manifest generation, and closure evidence surfaces | route registration at build time and read-model/inspector access at runtime | EC §2, DOC20/DOC21/DOC22 route inventory and manifest consumers |
| `CanonicalTruthManifestSchema` | Consumed by route registration, UI builders, manifest generation, and closure evidence surfaces | route registration at build time and read-model/inspector access at runtime | EC §2, DOC20/DOC21/DOC22 route inventory and manifest consumers |

## 3. One compiled policy engine

This section defines the one EC-owned decision engine that turns collection state, visibility inputs, source-policy adapters, sharing semantics, exposure contexts, and live runtime conditions into actual enforcement decisions. It exists so privacy and delivery behavior are deterministic, replayable, and inspectable instead of being recomputed differently by every consumer. The wireframe below shows the operator-facing decision inspector and simulator that expose those compiled results.

**Inspector / operator wireframe — Policy Simulator and Why-Blocked**

```text
Policy Decision Engine
────────────────────────────────────────────────────────────────
[Decision inspector] [Dry-run simulator] [Recent receipts]

Decision inspector
  generation_id: policy_gen_2026_04_15_03
  evaluator_impl_hash: 19ce...
  decision_kind: sharing_gate
  result: block
  reason_codes: classification_not_settled, auto_injection_blocked_sensitive_memory

Reason trace
  1. effective_collection_mode -> collect_and_tag
  2. visibility_gate -> local_only
  3. classification_gate -> blocked (not settled)
  4. sharing_gate -> block

Dry-run simulator
  Input node id / sample payload: [_____________________]
  Destination: [▾ cloud_api ]
  Mode: [Run simulation]

[Export receipt] [Open source policy inputs]
```

**What “compiled” means here:** EC takes the currently active collection, visibility, sharing, source-policy, and runtime-control inputs, compiles them into a single generation artifact, and all runtime consumers use that generation instead of recomputing policy independently.


**How policy inputs become decisions**

PropA supplies semantic policy inputs: tags, findings, visibility classes, source-policy adapters, exposure-context rules, and governance metadata. EC takes those inputs together with live runtime state, compiles a concrete generation artifact, and then all runtime consumers use that generation to evaluate decisions. “Compiled” here means there is one evaluator, one generation identity, and one replayable receipt format rather than a dozen improvised compositions across the stack.


**Owned by:** EC Core Addendum A (compiled evaluation infrastructure), consumed by DOC24 packet assembly, DOC11 dispatch, PropA privacy/sharing semantics, and inspector/read surfaces in DOC20.

**V3.2 changes from V3.1:** No contract changes. This section now restores the concept prose for what “compiled” means here, why one evaluator exists, and how policy inputs become receipts and runtime decisions.

This section defines the one place in the system where collection state, visibility policy, source-policy adapters, sharing semantics, exposure contexts, and runtime conditions are composed into an actual decision. It exists so privacy and delivery behavior are deterministic, replayable, and inspectable instead of being recomputed differently by every consumer.


### 3.1 Compiled effective policy artifact

```ts
export const CompiledEffectivePolicyArtifactSchemaV4 = z.object({
  generation_id: z.string().max(120),
  collection_policy_hash: z.string().length(64),
  visibility_policy_hash: z.string().length(64),
  sharing_policy_hash: z.string().length(64),
  control_state_hash: z.string().length(64),
  evaluator_impl_hash: z.string().length(64),
  compiler_version: z.string().max(40),
  route_registry_generation_id: z.string().max(120),
  org_safe_fast_path_tags: z.array(z.string().max(80)).default([]),
  effective_collection_mode_per_surface: z.record(z.string(), EffectiveCollectionModeResultSchemaV2).default({}),
  generated_at: z.string().datetime(),
  schema_version: z.literal(4),
});
```

### 3.2 Policy evaluation IO

```ts
// imports from PropA:
// ExposureContextSchema
// SensitivityClassificationStateSchema
// SensitiveAutoInjectionGuardSettingsSchemaV3
// NodeGovernanceMetadataSchemaV3

export const InteractionModeSchema = z.enum([
  "interactive",
  "background_non_interactive",
]);

export const PolicyEvaluationInputSchemaV3 = z.object({
  node_id: z.string().max(120).optional(),
  destination: z.enum([
    "same_machine_local_runtime",
    "local_file_export",
    "local_network_peer",
    "firm_server",
    "remote_peer",
    "cloud_api",
    "email_outbound",
    "agent_messaging",
  ]),
  interaction_mode: InteractionModeSchema,
  exposure_context: z.enum([
    "automatic_packet_injection",
    "explicit_memory_attach",
    "user_authored_prompt",
  ]),
  contains_system_resolved_node: z.boolean().default(false),
  classification_state: z.enum([
    "unclassified",
    "provisional_source_only",
    "classified",
    "deferred_unavailable",
    "quarantined_review",
    "tombstoned",
  ]),
  node_scope: z.enum(["firm_shared", "matter_scoped", "personal", "private"]).optional(),
  effective_tags: z.array(z.string().max(80)).default([]),
  findings: z.array(z.string().max(80)).default([]),
  source_policy_result: z.enum([
    "allow",
    "warn_on_secret",
    "redact_secret",
    "block",
  ]).default("allow"),
  sharing_matrix_action: z.enum(["allow", "warn", "block", "strip", "redact"]),
  finding_overlay_action: z.enum(["allow", "warn", "block", "redact"]).default("allow"),
  review_exposure_class: z.enum(["local_only", "cloud_permitted", "blocked"]).optional(),
  local_execution_attestation: z.object({
    attested_local_execution: z.boolean().default(false),
    provider_network_egress: z.boolean().default(true),
  }).optional(),
  override_receipt_ref: z.string().max(240).optional(),
  request_fingerprint: z.string().length(64).optional(),
  schema_version: z.literal(3),
});

export const PolicyDecisionSchemaV3 = z.object({
  decision_id: z.string().max(120),
  action: z.enum([
    "allow",
    "warn",
    "block",
    "strip",
    "redact",
    "blocked_requires_consent",
    "defer",
  ]),
  destination: z.string().max(80),
  reason_codes: z.array(z.string().max(80)).default([]),
  reason_trace: z.array(z.object({
    step: z.string().max(80),
    outcome: z.string().max(80),
    detail: z.string().max(240).optional(),
  })).min(1),
  evaluator_generation_id: z.string().max(120),
  evaluator_impl_hash: z.string().length(64),
  redaction_required: z.boolean().default(false),
  created_at: z.string().datetime(),
  schema_version: z.literal(3),
});

export const PolicyDecisionReceiptSchemaV4 = z.object({
  receipt_id: z.string().max(120),
  decision_id: z.string().max(120),
  evaluator_generation_id: z.string().max(120),
  evaluator_impl_hash: z.string().length(64),
  created_at: z.string().datetime(),
  schema_version: z.literal(4),
});
```

### 3.3 Effective collection mode computation

```ts
export const EffectiveCollectionModeResultSchemaV2 = z.object({
  effective_mode: z.enum(["blocked", "collect_and_tag", "collect_without_tagging"]),
  reason_codes: z.array(z.string().max(80)).default([]),
  schema_version: z.literal(2),
});

export function computeEffectiveCollectionMode(input: {
  effective_runtime_state: z.infer<typeof EffectiveRuntimeStateSchemaV3>;
  source_rule_result: z.infer<any>; // PropA ResolvedSourceClassificationSchema
  collection_policy: z.infer<any>;  // PropA SensitiveContentCollectionPolicySchemaV2
  boundary_scan_tags: string[];
}): z.infer<typeof EffectiveCollectionModeResultSchemaV2> {
  const reasons: string[] = [];

  if (!input.effective_runtime_state.effective.collection_enabled) {
    return { effective_mode: "blocked", reason_codes: ["collection_effectively_disabled"], schema_version: 2 };
  }

  const finalTags = [...new Set([
    ...input.source_rule_result.source_tags,
    ...input.boundary_scan_tags,
  ])];

  for (const tag of finalTags) {
    if (input.collection_policy[tag] === "do_not_collect") {
      reasons.push(`blocked_by_policy:${tag}`);
      return { effective_mode: "blocked", reason_codes: reasons, schema_version: 2 };
    }
  }

  if (input.source_rule_result.effective_collection_mode === "collect_without_tagging") {
    reasons.push("source_rule_collect_without_tagging");
    return { effective_mode: "collect_without_tagging", reason_codes: reasons, schema_version: 2 };
  }

  reasons.push("collect_and_tag");
  return { effective_mode: "collect_and_tag", reason_codes: reasons, schema_version: 2 };
}
```

### 3.4 Evaluation order

```ts
function blocked(
  trace: Array<{ step: string; outcome: string; detail?: string }>,
  reasonCodes: string[],
  destination: string,
  compiled: z.infer<typeof CompiledEffectivePolicyArtifactSchemaV4>
): z.infer<typeof PolicyDecisionSchemaV3> {
  return {
    decision_id: crypto.randomUUID(),
    action: "block",
    destination,
    reason_codes: reasonCodes,
    reason_trace: trace,
    evaluator_generation_id: compiled.generation_id,
    evaluator_impl_hash: compiled.evaluator_impl_hash,
    redaction_required: false,
    created_at: new Date().toISOString(),
    schema_version: 3,
  };
}

function result(
  action: z.infer<typeof PolicyDecisionSchemaV3>["action"],
  trace: Array<{ step: string; outcome: string; detail?: string }>,
  reasonCodes: string[],
  destination: string,
  compiled: z.infer<typeof CompiledEffectivePolicyArtifactSchemaV4>,
  redactionRequired: boolean
): z.infer<typeof PolicyDecisionSchemaV3> {
  return {
    decision_id: crypto.randomUUID(),
    action,
    destination,
    reason_codes: reasonCodes,
    reason_trace: trace,
    evaluator_generation_id: compiled.generation_id,
    evaluator_impl_hash: compiled.evaluator_impl_hash,
    redaction_required: redactionRequired,
    created_at: new Date().toISOString(),
    schema_version: 3,
  };
}

function sharingActionRank(action: "allow" | "warn" | "strip" | "redact" | "block"): number {
  const rank = {
    allow: 0,
    warn: 1,
    strip: 2,
    redact: 3,
    block: 4,
  } as const;
  return rank[action];
}

function maxSharingAction(a: "allow" | "warn" | "strip" | "redact" | "block", b: "allow" | "warn" | "strip" | "redact" | "block") {
  return sharingActionRank(a) >= sharingActionRank(b) ? a : b;
}

export function evaluatePolicyDecisionV3(
  input: z.infer<typeof PolicyEvaluationInputSchemaV3>,
  compiled: z.infer<typeof CompiledEffectivePolicyArtifactSchemaV4>
): z.infer<typeof PolicyDecisionSchemaV3> {
  const trace: Array<{ step: string; outcome: string; detail?: string }> = [];
  const effectiveExposureContext =
    input.exposure_context === "user_authored_prompt" && input.contains_system_resolved_node
      ? "explicit_memory_attach"
      : input.exposure_context;

  // 1) source-policy adapter
  if (input.source_policy_result === "block") {
    trace.push({ step: "source_policy", outcome: "block", detail: "source_policy_block" });
    return blocked(trace, ["source_policy_block"], input.destination, compiled);
  }

  let action = maxSharingAction(input.sharing_matrix_action, input.finding_overlay_action);
  let redactionRequired = false;

  if (input.source_policy_result === "redact_secret") {
    redactionRequired = true;
    action = action === "allow" ? "redact" : maxSharingAction(action, "redact");
    trace.push({ step: "source_policy", outcome: "redact", detail: "source_policy_redact_secret" });
  }

  if (input.source_policy_result === "warn_on_secret" && action === "allow") {
    action = "warn";
    trace.push({ step: "source_policy", outcome: "warn", detail: "source_policy_warn_on_secret" });
  }

  // 2) visibility/classification fail-closed
  if (
    ["unclassified", "provisional_source_only", "deferred_unavailable", "quarantined_review"].includes(input.classification_state) &&
    input.destination !== "same_machine_local_runtime"
  ) {
    trace.push({ step: "classification_gate", outcome: "block", detail: "classification_not_settled" });
    return blocked(trace, ["classification_not_settled"], input.destination, compiled);
  }

  if (input.classification_state === "tombstoned") {
    trace.push({ step: "classification_gate", outcome: "block", detail: "tombstoned_node" });
    return blocked(trace, ["tombstoned_node"], input.destination, compiled);
  }

  // 3) self-review export exposure guard
  if (input.review_exposure_class === "blocked") {
    trace.push({ step: "review_exposure_guard", outcome: "block", detail: "review_export_blocked" });
    return blocked(trace, ["review_export_blocked"], input.destination, compiled);
  }
  if (input.review_exposure_class === "local_only" && input.destination !== "same_machine_local_runtime") {
    trace.push({ step: "review_exposure_guard", outcome: "block", detail: "review_export_local_only" });
    return blocked(trace, ["review_export_local_only"], input.destination, compiled);
  }

  // 4) exposure-context guard
  if (effectiveExposureContext === "automatic_packet_injection") {
    const blockedByScope = input.node_scope === "private";
    const blockedByTag = [
      "personal_private",
      "financial_personal",
      "health_personal",
      "contains_credentials",
      "contains_pii_third_party",
      "attorney_client_privileged",
      "firm_gc_privileged",
      "court_sealed",
    ].some((tag) => input.effective_tags.includes(tag));

    if (input.destination === "cloud_api" && (blockedByScope || blockedByTag)) {
      trace.push({ step: "exposure_context_guard", outcome: "block", detail: "automatic_injection_blocked" });
      return blocked(trace, ["automatic_injection_blocked"], input.destination, compiled);
    }
  }

  if (effectiveExposureContext === "explicit_memory_attach") {
    const blockedByScope = input.node_scope === "private";
    const sensitiveAttachTag = [
      "personal_private",
      "financial_personal",
      "health_personal",
      "contains_credentials",
      "contains_pii_third_party",
      "attorney_client_privileged",
      "firm_gc_privileged",
      "court_sealed",
    ].some((tag) => input.effective_tags.includes(tag));

    if (input.destination === "cloud_api" && (blockedByScope || sensitiveAttachTag) && !input.override_receipt_ref) {
      trace.push({ step: "exposure_context_guard", outcome: "blocked_requires_consent", detail: "explicit_attach_requires_one_turn_override" });
      return result("blocked_requires_consent", trace, ["explicit_attach_requires_one_turn_override"], input.destination, compiled, redactionRequired);
    }
  }

  if (effectiveExposureContext === "user_authored_prompt" && input.contains_system_resolved_node) {
    trace.push({ step: "exposure_context_guard", outcome: "coerce_attach", detail: "system_resolved_node_must_not_use_user_authored_prompt" });
  }

  // 5) interaction-mode enforcement
  if (action === "warn" && input.interaction_mode === "background_non_interactive") {
    trace.push({ step: "interaction_mode", outcome: "block", detail: "warn_requires_interactive" });
    return blocked(trace, ["warn_requires_interactive"], input.destination, compiled);
  }

  // 6) local-runtime attestation refinement
  if (
    input.destination === "same_machine_local_runtime" &&
    input.local_execution_attestation?.attested_local_execution &&
    input.local_execution_attestation.provider_network_egress === false
  ) {
    trace.push({ step: "local_attestation", outcome: "verified_local_runtime", detail: "same_machine_no_egress" });
  }

  trace.push({ step: "sharing_matrix", outcome: action, detail: "compiled_effective_policy" });
  return result(action, trace, [], input.destination, compiled, redactionRequired || action === "redact");
}
```

**Normative engine rules**
1. Runtime consumers SHALL consume only compiled policy generations and their evaluator hash.
2. `warn && interaction_mode = background_non_interactive` SHALL coerce to `block`.
3. `user_authored_prompt` SHALL not be used for system-resolved graph-node injection.
4. `redact_secret` SHALL produce explicit receipts and artifact tracking.
5. EC is the only runtime-consumable policy evaluator for the PropA/EC pair.

### 3.5 Policy simulation route

```ts
export const PolicySimulationRequestSchema = z.object({
  candidate_policy_patch_ref: z.string().max(240).optional(),
  mock_input: PolicyEvaluationInputSchemaV3,
  replay_recent_node_ids: z.array(z.string().max(120)).default([]),
  schema_version: z.literal(1),
});

export const PolicySimulationResultSchema = z.object({
  simulation_id: z.string().max(120),
  simulated_decisions: z.array(PolicyDecisionSchemaV3).default([]),
  impacted_node_ids: z.array(z.string().max(120)).default([]),
  schema_version: z.literal(1),
});
```

**Normative rule:** the simulation route is dry-run only. It SHALL NOT create durable graph state beyond receipts/logs.

### 3.6 Why-blocked inspector

```ts
export const WhyBlockedInspectorResponseSchemaV2 = z.object({
  subject_kind: z.enum(["node", "route", "task", "sync_batch"]),
  subject_id: z.string().max(120),
  evaluator_generation_id: z.string().max(120).optional(),
  evaluator_impl_hash: z.string().length(64).optional(),
  reason_tree: z.array(z.object({
    step: z.string().max(120),
    result: z.string().max(80),
    reason_codes: z.array(z.string().max(80)).default([]),
    detail: z.string().max(240).optional(),
  })).default([]),
  schema_version: z.literal(2),
});
```

---

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `CompiledEffectivePolicyArtifactSchemaV4` | Consumed by EC policy compilation/evaluation, DOC24 packet assembly, DOC11 dispatch, policy simulation, and why-blocked inspectors | collection gate resolution, visibility/sharing evaluation, sync preflight, simulation, and inspector reads | EC §3, PropA policy inputs, DOC24/DOC11 runtime consumption |
| `InteractionModeSchema` | Consumed by EC policy compilation/evaluation, DOC24 packet assembly, DOC11 dispatch, policy simulation, and why-blocked inspectors | collection gate resolution, visibility/sharing evaluation, sync preflight, simulation, and inspector reads | EC §3, PropA policy inputs, DOC24/DOC11 runtime consumption |
| `PolicyEvaluationInputSchemaV3` | Consumed by EC policy compilation/evaluation, DOC24 packet assembly, DOC11 dispatch, policy simulation, and why-blocked inspectors | collection gate resolution, visibility/sharing evaluation, sync preflight, simulation, and inspector reads | EC §3, PropA policy inputs, DOC24/DOC11 runtime consumption |
| `PolicyDecisionSchemaV3` | Consumed by EC policy compilation/evaluation, DOC24 packet assembly, DOC11 dispatch, policy simulation, and why-blocked inspectors | collection gate resolution, visibility/sharing evaluation, sync preflight, simulation, and inspector reads | EC §3, PropA policy inputs, DOC24/DOC11 runtime consumption |
| `PolicyDecisionReceiptSchemaV4` | Consumed by EC policy compilation/evaluation, DOC24 packet assembly, DOC11 dispatch, policy simulation, and why-blocked inspectors | collection gate resolution, visibility/sharing evaluation, sync preflight, simulation, and inspector reads | EC §3, PropA policy inputs, DOC24/DOC11 runtime consumption |
| `EffectiveCollectionModeResultSchemaV2` | Consumed by EC policy compilation/evaluation, DOC24 packet assembly, DOC11 dispatch, policy simulation, and why-blocked inspectors | collection gate resolution, visibility/sharing evaluation, sync preflight, simulation, and inspector reads | EC §3, PropA policy inputs, DOC24/DOC11 runtime consumption |
| `PolicySimulationRequestSchema` | Consumed by EC policy compilation/evaluation, DOC24 packet assembly, DOC11 dispatch, policy simulation, and why-blocked inspectors | collection gate resolution, visibility/sharing evaluation, sync preflight, simulation, and inspector reads | EC §3, PropA policy inputs, DOC24/DOC11 runtime consumption |
| `PolicySimulationResultSchema` | Consumed by EC policy compilation/evaluation, DOC24 packet assembly, DOC11 dispatch, policy simulation, and why-blocked inspectors | collection gate resolution, visibility/sharing evaluation, sync preflight, simulation, and inspector reads | EC §3, PropA policy inputs, DOC24/DOC11 runtime consumption |
| `WhyBlockedInspectorResponseSchemaV2` | Consumed by EC policy compilation/evaluation, DOC24 packet assembly, DOC11 dispatch, policy simulation, and why-blocked inspectors | collection gate resolution, visibility/sharing evaluation, sync preflight, simulation, and inspector reads | EC §3, PropA policy inputs, DOC24/DOC11 runtime consumption |

## 4. BackgroundJobOrchestrator and task registry

This section defines the single scheduler/runtime that all long-running extraction, learning, validation, and maintenance work must use. It exists so idle rules, pause behavior, retries, DLQ handling, and execution-profile selection are enforced in one place rather than being reimplemented inconsistently by every subsystem. The wireframe below shows the operator-facing queue and task-registry view for that runtime.

**Read-surface wireframe — Background Processing**

```text
Background Processing
────────────────────────────────────────────────────────────────
Orchestrator state: running
Active jobs: 3     Queued: 12     Blocked: 1     DLQ: 0

Task types
  knowledge_deep_extraction         enabled   high        local-or-cloud
  sensitivity_content_classification enabled standard    local-only when required
  knowledge_self_review             enabled   low         local-only on protected data
  dspy_optimizer                    disabled  manual      local-only guarded

Controls
  Pause all processing              [ ]
  Pause LLM processing only         [ ]
  [Open per-task controls]

Active job detail
  task_type_id: knowledge_self_review
  profile: local_qwen_reviewer
  checkpoint: m4_blinded_reevaluation
  retry count: 0
```

**Architecture note:** owner specs define what a task computes; the orchestrator decides when it runs, with which profile, under which pause/cost/idleness conditions.


**How the orchestrator works**

Owner docs say what a job computes; the orchestrator decides when it runs, how it is prioritized, whether it is paused, and which execution profile may process it. That lets EC enforce idle rules, local-only guarantees, cost ceilings, retries, DLQ behavior, and honest queue truth in one place instead of requiring each subsystem to ship its own scheduler.


**Owned by:** EC Core Addendum A (background scheduling/orchestration), with cross-doc obligations into DOC8, DOC72, DOC20, DOC23, DOC24 Addendum A, and PropA task registrations.

**V3.2 changes from V3.1:** No contract changes. This section now restores the orchestration architecture prose from earlier EC drafts and clarifies how owner docs define what a task computes while EC controls when and by whom it runs.

This section defines the single background-job runtime that all long-running learning, extraction, verification, and maintenance work must use. It exists to stop every subsystem from inventing its own scheduler, to keep pause/idle/cost policies consistent, and to provide one truthful place to inspect queue state and failure conditions.


### 4.1 Task requirements

```ts
export const ExecutionTrustClassSchema = z.enum([
  "same_machine_local_only",
  "local_or_cloud",
  "cloud_only",
]);

export const TaskExecutionRequirementSchemaV4 = z.object({
  task_type_id: z.string().max(120),
  requires_sensitive_content_support: z.boolean().default(false),
  min_context_tokens: z.number().int().positive().optional(),
  min_output_tokens: z.number().int().positive().optional(),
  requires_schema_locked_json: z.boolean().default(false),
  requires_scoring_capability: z.boolean().default(false),
  allowed_execution_trust_classes: z.array(ExecutionTrustClassSchema).default([]),
  review_exposure_class: z.enum(["local_only", "cloud_permitted", "blocked"]).optional(),
  schema_version: z.literal(4),
});
```

### 4.2 PropA / EC task registrations

```ts
export const BackgroundTaskTypeRegistryEntrySchemaV2 = z.object({
  task_type_id: z.string().max(120),
  requirement: TaskExecutionRequirementSchemaV4,
  default_profile_id: z.string().max(120).optional(),
  custom_profile_id_required: z.boolean().default(false),
  schema_version: z.literal(2),
});
```

**Required rows**
1. `knowledge_deep_extraction`
   - `requires_sensitive_content_support = true`
   - `requires_schema_locked_json = true`
   - `allowed_execution_trust_classes = ["same_machine_local_only", "local_or_cloud"]`
2. `sensitivity_content_classification`
   - `requires_sensitive_content_support = true`
   - `requires_schema_locked_json = true`
   - `allowed_execution_trust_classes = ["same_machine_local_only", "local_or_cloud"]`
3. `knowledge_self_review`
   - `requires_sensitive_content_support = true`
   - `requires_schema_locked_json = true`
   - `requires_scoring_capability = true`
   - `allowed_execution_trust_classes = ["same_machine_local_only", "local_or_cloud"]`
4. `knowledge_dspy_optimization`
   - `requires_sensitive_content_support = true`
   - `requires_schema_locked_json = true`
   - `requires_scoring_capability = true`
   - `allowed_execution_trust_classes = ["same_machine_local_only"]`
   - `custom_profile_id_required = false`
5. `doc8_nightly_learning`
   - `allowed_execution_trust_classes = ["same_machine_local_only", "local_or_cloud"]`

### 4.3 Queue truth and idempotency

```ts
export const BackgroundTaskStatusSchemaV2 = z.enum([
  "queued",
  "running",
  "blocked",
  "deferred",
  "deferred_aborted",
  "completed",
  "failed",
  "canceled",
]);

export const BackgroundTaskInstanceSchemaV3 = z.object({
  task_instance_id: z.string().max(120),
  task_type_id: z.string().max(120),
  status: BackgroundTaskStatusSchemaV2,
  idempotency_key: z.string().max(160),
  dedup_window_minutes: z.number().int().positive().default(120),
  profile_id: z.string().max(120).optional(),
  blocked_reason_codes: z.array(z.string().max(80)).default([]),
  created_at: z.string().datetime(),
  updated_at: z.string().datetime(),
  schema_version: z.literal(3),
});
```

**Normative queue rules**
1. Crash-requeued tasks SHALL reject enqueue if a matching active or recent-completed idempotency key exists within the dedup window.
2. Blocked tasks SHALL remain visible as blocked, not silently dropped.
3. Deferred tasks SHALL record why they were deferred.

### 4.4 Orchestrator status

```ts
export const BackgroundJobOrchestratorStatusSchemaV2 = z.object({
  orchestrator_state: z.enum(["running", "paused_all", "paused_llm_only", "degraded"]),
  active_job_count: z.number().int().nonnegative(),
  queued_job_count: z.number().int().nonnegative(),
  blocked_job_count: z.number().int().nonnegative(),
  deferred_job_count: z.number().int().nonnegative(),
  last_scheduler_tick_at: z.string().datetime().optional(),
  reason_codes: z.array(z.string().max(80)).default([]),
  schema_version: z.literal(2),
});
```

### 4.5 DLQ and anomaly breaker

```ts
export const ExtractionDeadLetterQueueEntrySchemaV2 = z.object({
  dlq_id: z.string().max(120),
  observation_id: z.string().max(120),
  failed_stage: z.enum(["extraction", "classification", "write_gate", "validation"]),
  reason_codes: z.array(z.string().max(80)).default([]),
  created_at: z.string().datetime(),
  schema_version: z.literal(2),
});

export const DlqAnomalyCircuitBreakerSchema = z.object({
  enabled: z.boolean().default(true),
  moving_average_days: z.number().int().positive().default(7),
  spike_multiplier: z.number().positive().default(3),
  schema_version: z.literal(1),
});
```

**Normative rule:** if DLQ volume spikes above the configured threshold, EC SHALL emit a system alert and MAY temporarily pause affected task types.

---

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `ExecutionTrustClassSchema` | Consumed by the orchestrator, task-registration code, status inspectors, DLQ monitoring, and owner-doc background job integrations | task registration, queueing, execution, retry, status display, and DLQ anomaly checks | EC §4, DOC8/DOC72/DOC23/DOC24A integrations |
| `TaskExecutionRequirementSchemaV4` | Consumed by the orchestrator, task-registration code, status inspectors, DLQ monitoring, and owner-doc background job integrations | task registration, queueing, execution, retry, status display, and DLQ anomaly checks | EC §4, DOC8/DOC72/DOC23/DOC24A integrations |
| `BackgroundTaskTypeRegistryEntrySchemaV2` | Consumed by the orchestrator, task-registration code, status inspectors, DLQ monitoring, and owner-doc background job integrations | task registration, queueing, execution, retry, status display, and DLQ anomaly checks | EC §4, DOC8/DOC72/DOC23/DOC24A integrations |
| `BackgroundTaskStatusSchemaV2` | Consumed by the orchestrator, task-registration code, status inspectors, DLQ monitoring, and owner-doc background job integrations | task registration, queueing, execution, retry, status display, and DLQ anomaly checks | EC §4, DOC8/DOC72/DOC23/DOC24A integrations |
| `BackgroundTaskInstanceSchemaV3` | Consumed by the orchestrator, task-registration code, status inspectors, DLQ monitoring, and owner-doc background job integrations | task registration, queueing, execution, retry, status display, and DLQ anomaly checks | EC §4, DOC8/DOC72/DOC23/DOC24A integrations |
| `BackgroundJobOrchestratorStatusSchemaV2` | Consumed by the orchestrator, task-registration code, status inspectors, DLQ monitoring, and owner-doc background job integrations | task registration, queueing, execution, retry, status display, and DLQ anomaly checks | EC §4, DOC8/DOC72/DOC23/DOC24A integrations |
| `ExtractionDeadLetterQueueEntrySchemaV2` | Consumed by the orchestrator, task-registration code, status inspectors, DLQ monitoring, and owner-doc background job integrations | task registration, queueing, execution, retry, status display, and DLQ anomaly checks | EC §4, DOC8/DOC72/DOC23/DOC24A integrations |
| `DlqAnomalyCircuitBreakerSchema` | Consumed by the orchestrator, task-registration code, status inspectors, DLQ monitoring, and owner-doc background job integrations | task registration, queueing, execution, retry, status display, and DLQ anomaly checks | EC §4, DOC8/DOC72/DOC23/DOC24A integrations |

## 5. Execution profiles, local-only guarantees, and compatibility compiler

This section defines the execution-profile catalog and the compiler that checks whether a task may legally and technically run on a given profile. It exists to make `local_only` and other trust/capability requirements operationally binding rather than best-effort preferences. The wireframe below shows the settings and inspection surface for those profiles and compatibility checks.

**Settings / operator wireframe — Background Agent Profiles**

```text
Background Agent Profiles
────────────────────────────────────────────────────────────────
Profile: local_qwen_reviewer
  Execution trust class            [ same_machine_local_only ]
  Provider network egress          [ false ]
  Supports sensitive content       [✓]
  Max context tokens               [ 32768 ]
  Max output tokens                [ 4096 ]
  Enabled                          [✓]

Profile: cloud_bulk_worker
  Execution trust class            [ cloud_permitted ]
  Provider network egress          [ true ]
  Supports sensitive content       [ ]
  Max context tokens               [ 128000 ]
  Max output tokens                [ 8192 ]
  Enabled                          [✓]

Compatibility check
  Task: sensitivity_content_classification
  Requirement: same_machine_local_only
  Compatible profiles: local_qwen_reviewer

[Save profile] [Run compatibility compile]
```


**Why trust class alone is not enough**

A profile can be privacy-safe and still be the wrong runtime for a task. That is why EC compiles both trust constraints and capability constraints—context window, output window, schema-locked JSON reliability, scoring capability, and sensitive-content support—before a profile is considered eligible to run a task.


**Owned by:** EC Core Addendum A (execution-profile and capability selection), consumed by PropA extraction/classification/self-review/DSPy tasks and DOC23 runtime dispatch.

**V3.2 changes from V3.1:** No contract changes. This section now adds the missing explanation of why safe model routing is a control-plane problem and how capability compatibility is compiled rather than guessed.

This section defines how EC chooses an actual execution profile that satisfies both privacy requirements and task capability requirements. It exists to make `local_only` operationally binding, to avoid cloud fallbacks sneaking into sensitive paths, and to prevent tasks from being scheduled onto profiles that are technically safe but functionally incapable.


### 5.1 Profiles

```ts
export const BackgroundAgentProfileSchemaV5 = z.object({
  profile_id: z.string().max(120),
  model_id: z.string().max(120),
  execution_trust_class: ExecutionTrustClassSchema,
  provider_network_egress: z.boolean(),
  supports_sensitive_content: z.boolean().default(false),
  supports_schema_locked_json: z.boolean().default(false),
  supports_scoring_capability: z.boolean().default(false),
  max_context_tokens: z.number().int().positive(),
  max_output_tokens: z.number().int().positive(),
  enabled: z.boolean().default(true),
  profile_version: z.number().int().positive().default(1),
  schema_version: z.literal(5),
});

export const CreateBackgroundAgentProfileCommandSchema = z.object({
  profile: BackgroundAgentProfileSchemaV5,
  schema_version: z.literal(1),
});

export const UpdateBackgroundAgentProfileCommandSchema = z.object({
  profile_id: z.string().max(120),
  patch_ref: z.string().max(240),
  expected_profile_version: z.number().int().positive(),
  schema_version: z.literal(1),
});
```

### 5.2 Local model proxy and optional triage

```ts
export const LocalModelProxyPolicySchema = z.object({
  proxy_base_url: z.string().url(),
  require_proxy_for_external_optimizers: z.boolean().default(true),
  allow_raw_provider_credentials_in_child_processes: z.boolean().default(false),
  schema_version: z.literal(1),
});

export const RestrictedContentTriagePolicySchema = z.object({
  enabled: z.boolean().default(false),
  triage_model_id: z.string().max(120).optional(),
  apply_to_visibility_classes: z.array(z.enum(["cloud_warn", "unclassified"]))
    .default(["cloud_warn", "unclassified"]),
  local_only_required: z.boolean().default(true),
  schema_version: z.literal(1),
});
```

### 5.3 Compatibility compiler

```ts
export const TaskProfileCompatibilityRuleSchema = z.object({
  task_type_id: z.string().max(120),
  allowed_profile_ids: z.array(z.string().max(120)).default([]),
  required_capabilities: z.array(z.string().max(120)).default([]),
  required_trust_classes: z.array(ExecutionTrustClassSchema).default([]),
  schema_version: z.literal(1),
});
```

### 5.4 Profile selection

```ts
export function selectExecutionProfileV4(input: {
  task_requirements: z.infer<typeof TaskExecutionRequirementSchemaV4>;
  profiles: z.infer<typeof BackgroundAgentProfileSchemaV5>[];
}): z.infer<typeof BackgroundAgentProfileSchemaV5> {
  if (input.task_requirements.review_exposure_class === "blocked") {
    throw new Error("The task is blocked by review exposure class and cannot be dispatched.");
  }

  const active = input.profiles.filter((p) => p.enabled);

  const compatible = active.filter((p) => {
    if (
      input.task_requirements.requires_sensitive_content_support &&
      !p.supports_sensitive_content
    ) return false;
    if (
      input.task_requirements.requires_schema_locked_json &&
      !p.supports_schema_locked_json
    ) return false;
    if (
      input.task_requirements.requires_scoring_capability &&
      !p.supports_scoring_capability
    ) return false;
    if (
      input.task_requirements.allowed_execution_trust_classes.length > 0 &&
      !input.task_requirements.allowed_execution_trust_classes.includes(p.execution_trust_class)
    ) return false;
    if (
      input.task_requirements.review_exposure_class === "local_only" &&
      p.execution_trust_class !== "same_machine_local_only"
    ) return false;
    if (
      input.task_requirements.min_context_tokens &&
      p.max_context_tokens < input.task_requirements.min_context_tokens
    ) return false;
    if (
      input.task_requirements.min_output_tokens &&
      p.max_output_tokens < input.task_requirements.min_output_tokens
    ) return false;
    return true;
  });

  if (compatible.length === 0) {
    throw new Error("No compatible execution profile exists for the requested task.");
  }

  const rank = {
    same_machine_local_only: 0,
    local_or_cloud: 1,
    cloud_only: 2,
  } as const;

  return compatible.sort((a, b) => rank[a.execution_trust_class] - rank[b.execution_trust_class])[0];
}
```

**Normative rules**
1. `local_only` work is not best-effort. It requires `same_machine_local_only`.
2. If no compatible local-only profile exists, block or defer; do not silently fall back to cloud.
3. Profile updates SHALL require optimistic concurrency via `expected_profile_version`.
4. Self-review and DSPy dispatch SHALL honor `review_exposure_class`; `local_only` exports MUST NOT route to non-local profiles.

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `BackgroundAgentProfileSchemaV5` | Consumed by profile management UI, task scheduling, trust-class enforcement, and compatibility compilation | profile create/update, task dispatch, local-only enforcement, and compatibility validation | EC §5, PropA extraction/classification/self-review/DSPy consumers |
| `CreateBackgroundAgentProfileCommandSchema` | Consumed by profile management UI, task scheduling, trust-class enforcement, and compatibility compilation | profile create/update, task dispatch, local-only enforcement, and compatibility validation | EC §5, PropA extraction/classification/self-review/DSPy consumers |
| `UpdateBackgroundAgentProfileCommandSchema` | Consumed by profile management UI, task scheduling, trust-class enforcement, and compatibility compilation | profile create/update, task dispatch, local-only enforcement, and compatibility validation | EC §5, PropA extraction/classification/self-review/DSPy consumers |
| `LocalModelProxyPolicySchema` | Consumed by profile management UI, task scheduling, trust-class enforcement, and compatibility compilation | profile create/update, task dispatch, local-only enforcement, and compatibility validation | EC §5, PropA extraction/classification/self-review/DSPy consumers |
| `RestrictedContentTriagePolicySchema` | Consumed by profile management UI, task scheduling, trust-class enforcement, and compatibility compilation | profile create/update, task dispatch, local-only enforcement, and compatibility validation | EC §5, PropA extraction/classification/self-review/DSPy consumers |
| `TaskProfileCompatibilityRuleSchema` | Consumed by profile management UI, task scheduling, trust-class enforcement, and compatibility compilation | profile create/update, task dispatch, local-only enforcement, and compatibility validation | EC §5, PropA extraction/classification/self-review/DSPy consumers |

## 6. Token/cost governance

This section defines the control plane for budgets, pauses, reserves, and effective spend decisions across background work. It exists so global EC limits, companion-spec limits, and run-local reserves compose into one runtime decision that the orchestrator, inspectors, and UI can all trust. The wireframe below shows the settings and inspection surface for those controls before the schema contracts are applied.

**Settings UI wireframe — Token and Cost Governance**

```text
Token & Cost Governance
────────────────────────────────────────────────────────────────
Global daily token cap            [ 1500000 ]
Monthly cloud USD cap             [ 250.00 ]
Pause when monthly cap reached    [✓]

Task reserves
  knowledge_self_review           [ 250000 tokens / day ]
  dspy_optimizer                  [ 150000 tokens / day ]
  sensitivity_classification      [ 300000 tokens / day ]

Effective decision preview
  Task: knowledge_self_review
  Remaining global tokens: 420000
  Remaining task reserve: 180000
  Remaining monthly USD: 71.24
  Effective allowed: yes

[Save governance settings] [Open usage inspector]
```

**Why tokens, not just dollars:** tokens are the most deterministic cross-provider control surface for scheduling and headroom; dollar estimates remain derived reporting.


**Why this section exists**

Global cost governance lives here because the system needs one honest place to decide whether work may run, defer, or pause when budgets are tight. PropA and other companion specs may narrow behavior further with pipeline-local caps, but EC is responsible for the final effective budget decision that the runtime and UI can actually trust.


**Owned by:** EC Core Addendum A (global cost governance), with cross-doc composition into PropA pipeline-local budgets and DOC24 Addendum A background processing policies; surfaced through DOC20/DOC21/22 settings and inspectors.

**V3.2 changes from V3.1:** No contract changes. This section now restores the purpose/design explanation for why EC governs budgets centrally and why runtime truth is measured in executed usage rather than optimistic config.

This section defines how EC limits or pauses expensive work across the whole system while still allowing owner-doc-specific budgets to narrow behavior further. It exists so self-review, DSPy, extraction, and learning jobs compete inside one honest budget framework rather than silently burning tokens under duplicated or conflicting caps.


### 6.1 Settings

```ts
export const TokenGovernanceSettingsSchemaV2 = z.object({
  global_daily_token_cap: z.number().int().nonnegative(),
  monthly_public_cloud_usd_cap: z.number().nonnegative(),
  pause_on_monthly_cloud_cap: z.boolean().default(true),
  schema_version: z.literal(2),
});

export const TaskBudgetReserveSchema = z.object({
  task_type_id: z.string().max(120),
  daily_token_reserve: z.number().int().nonnegative(),
  monthly_usd_reserve: z.number().nonnegative().default(0),
  schema_version: z.literal(1),
});
```

### 6.2 Effective budget decision

```ts
export const EffectiveProcessingBudgetDecisionSchemaV2 = z.object({
  task_type_id: z.string().max(120),
  global_daily_tokens_remaining: z.number().int().nonnegative(),
  pipeline_daily_tokens_remaining: z.number().int().nonnegative(),
  monthly_cloud_usd_remaining: z.number().nonnegative(),
  run_usd_cap_remaining: z.number().nonnegative().optional(),
  effective_allowed: z.boolean(),
  pause_reason: z.enum([
    "none",
    "global_daily_cap",
    "pipeline_daily_cap",
    "monthly_cloud_cap",
    "run_cap",
  ]).default("none"),
  schema_version: z.literal(2),
});
```

```ts
function computeEffectiveBudgetDecision(input: {
  globalRemainingTokens: number;
  pipelineRemainingTokens: number;
  monthlyCloudUsdRemaining: number;
  runUsdRemaining?: number;
  executionTrustClass: "same_machine_local_only" | "local_or_cloud" | "cloud_only";
}) {
  if (input.executionTrustClass !== "cloud_only") {
    return {
      task_type_id: "local_only",
      global_daily_tokens_remaining: input.globalRemainingTokens,
      pipeline_daily_tokens_remaining: input.pipelineRemainingTokens,
      monthly_cloud_usd_remaining: input.monthlyCloudUsdRemaining,
      run_usd_cap_remaining: input.runUsdRemaining,
      effective_allowed: true,
      pause_reason: "none",
      schema_version: 2,
    };
  }

  if (input.monthlyCloudUsdRemaining <= 0) return { effective_allowed: false, pause_reason: "monthly_cloud_cap" };
  if (input.runUsdRemaining !== undefined && input.runUsdRemaining <= 0) return { effective_allowed: false, pause_reason: "run_cap" };
  if (Math.min(input.globalRemainingTokens, input.pipelineRemainingTokens) <= 0) return { effective_allowed: false, pause_reason: "global_daily_cap" };
  return { effective_allowed: true, pause_reason: "none" };
}
```

**Normative rules**
- DSPy runs SHALL have runtime breakers, not just declarative caps.
- EC SHALL support a self-review budget reserve to avoid starvation by lower-priority work.

---

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `TokenGovernanceSettingsSchemaV2` | Consumed by budget settings UI, scheduling gates, task reserve logic, and usage inspectors | settings save, run-start gating, reserve computation, and budget inspection | EC §6 with PropA/Addendum A budget composition |
| `TaskBudgetReserveSchema` | Consumed by budget settings UI, scheduling gates, task reserve logic, and usage inspectors | settings save, run-start gating, reserve computation, and budget inspection | EC §6 with PropA/Addendum A budget composition |
| `EffectiveProcessingBudgetDecisionSchemaV2` | Consumed by budget settings UI, scheduling gates, task reserve logic, and usage inspectors | settings save, run-start gating, reserve computation, and budget inspection | EC §6 with PropA/Addendum A budget composition |

## 7. Networking, sync identity, and migration

This section defines the minimum infrastructure needed to make future sync and migration safe to reason about now: identity proof, preflight policy evaluation, edge stripping, conflict handling, and migration receipts. It exists so future firm-server or multi-node deployment cannot quietly bypass the same governance and privacy rules used on a single node. The wireframe below shows the preflight surface that explains those decisions before execution.

**Inspector / operator wireframe — Sync Preflight**

```text
Sync Preflight
────────────────────────────────────────────────────────────────
Source trust domain: personal_node
Destination: firm_server
Identity proof: verified
Nodes in batch: 42

Preflight results
  allow: 18
  manual_review: 9
  blocked: 15
  stripped_edges: 7
  tombstones propagated: 3

Selected blocked node
  node_id: mem_12345
  scope: private
  blocked reason: personal/private content cannot sync to firm_server

[Approve allowed subset] [Export preflight receipt] [Open edge-strip receipts]
```


**What this section is solving**

This section is not trying to fully implement a future firm-server topology today. It is creating the minimum infrastructure contracts required so that when sync and migration do happen, identity proof, trust domain, preflight policy evaluation, edge stripping, conflict handling, and migration receipts are already explicit rather than guessed at implementation time.


**Owned by:** EC Core Addendum A (sync/runtime infrastructure), with cross-doc obligations into PropA governance metadata, DOC72 node/edge semantics, DOC1 archive-not-delete rules, and future DOC20 sync inspectors.

**V3.2 changes from V3.1:** No contract changes. This section now restores the plain-language explanation of trust-domain verification, preflight, and migration ownership so future networking is not read as hand-wavy magic.

This section defines the infrastructure contracts that make future sync and migration safe enough to reason about now. It exists to ensure that identity proof, preflight policy evaluation, edge stripping, conflict resolution, and migration receipts are explicit before any future firm-server or multi-node deployment is attempted.


### 7.1 Identity proof

```ts
export const SyncRequestIdentityProofSchema = z.object({
  tenant_id: z.string().max(120),
  principal_id: z.string().max(120),
  destination_domain: z.enum(["personal_node", "firm_server", "matter_space", "peer_node"]),
  nonce: z.string().max(120),
  signed_assertion: z.string().min(1),
  key_id: z.string().max(120),
  issued_at: z.string().datetime(),
  expires_at: z.string().datetime(),
  schema_version: z.literal(1),
});
```

### 7.2 Preflight and execution

```ts
export const NetworkTrustSessionSchema = z.object({
  session_id: z.string().max(120),
  source_domain: z.enum(["personal_node", "firm_server", "matter_space", "peer_node"]),
  destination_domain: z.enum(["personal_node", "firm_server", "matter_space", "peer_node"]),
  tenant_id: z.string().max(120),
  principal_id: z.string().max(120),
  peer_identity_ref: z.string().max(240),
  transport: z.enum(["push", "pull", "bidirectional"]),
  auth_state: z.enum(["verified", "failed", "unknown"]),
  revocation_checked_at: z.string().datetime(),
  schema_version: z.literal(1),
});

export const SyncIdentityVerificationResultSchema = z.object({
  session_id: z.string().max(120),
  identity_proof_ref: z.string().max(240),
  verified: z.boolean().default(false),
  failure_reason_codes: z.array(z.string().max(80)).default([]),
  key_revocation_checked_at: z.string().datetime(),
  schema_version: z.literal(1),
});

// imported from PropA:
// EdgeTrustInheritancePolicySchema
// NodeTombstoneSyncPayloadSchema

export const SyncEdgeStripReceiptSchema = z.object({
  edge_id: z.string().max(120),
  source_node_id: z.string().max(120),
  target_node_id: z.string().max(120),
  stripped: z.boolean().default(false),
  reason_codes: z.array(z.string().max(80)).default([]),
  schema_version: z.literal(1),
});

export const SyncConflictResolutionPolicySchema = z.object({
  policy_id: z.string().max(120),
  resolution_mode: z.enum(["keep_source", "keep_destination", "manual_review"]),
  requires_manual_review_on_concurrent_mutation: z.boolean().default(true),
  schema_version: z.literal(1),
});

export const SyncBatchPreflightReceiptSchemaV3 = z.object({
  preflight_id: z.string().max(120),
  batch_node_ids: z.array(z.string().max(120)).default([]),
  allowed_node_ids: z.array(z.string().max(120)).default([]),
  blocked_node_ids: z.array(z.string().max(120)).default([]),
  stripped_edge_receipts: z.array(SyncEdgeStripReceiptSchema).default([]),
  reason_codes: z.array(z.string().max(80)).default([]),
  schema_version: z.literal(3),
});

export const SyncExecutionReceiptSchemaV2 = z.object({
  execution_id: z.string().max(120),
  preflight_id: z.string().max(120),
  destination_domain: z.enum(["personal_node", "firm_server", "matter_space", "peer_node"]),
  transferred_node_ids: z.array(z.string().max(120)).default([]),
  blocked_node_ids: z.array(z.string().max(120)).default([]),
  tombstone_payload_refs: z.array(z.string().max(240)).default([]),
  schema_version: z.literal(2),
});
```

**Normative rules**
1. Preflight SHALL verify identity proof before policy eligibility and SHALL fail closed if revocation checking cannot be completed.
2. Preflight SHALL evaluate every node in a batch; no silent partial sync.
3. Edge stripping SHALL be visible in receipts; EC SHALL NOT synthesize ordinary graph nodes to represent stripped edges.
4. Missing `principal_id`, `tenant_id`, or required allowlist identifiers SHALL block sync eligibility; missing context is not permissive.
5. Tombstoned nodes SHALL transfer only as tombstones through `NodeTombstoneSyncPayloadSchema`.

### 7.3 Import / merge conflict owner cleanup

```ts
export const ImportMergeConflictResolutionSchema = z.object({
  conflict_id: z.string().max(120),
  domain: z.enum(["knowledge_node", "settings", "policy", "registry_entry"]),
  entity_id: z.string().max(120),
  current_ref: z.string().max(240),
  imported_ref: z.string().max(240),
  resolution: z.enum(["keep_current", "keep_imported", "manual_review"]),
  schema_version: z.literal(1),
});
```

### 7.4 Migration contracts

```ts
export const MigrationContractSchema = z.object({
  migration_id: z.string().max(120),
  source_version: z.string().max(40),
  target_version: z.string().max(40),
  dry_run_supported: z.boolean().default(true),
  produces_audit_receipt: z.boolean().default(true),
  schema_version: z.literal(1),
});

export const MigrationReceiptSchemaV2 = z.object({
  migration_id: z.string().max(120),
  object_kind: z.enum(["node_governance", "policy", "route_registry", "task_registry", "artifact_retention"]),
  source_version: z.string().max(80),
  target_version: z.string().max(80),
  changed_ids: z.array(z.string().max(120)).default([]),
  schema_version: z.literal(2),
});
```

**Normative rules**
- Existing nodes missing governance metadata SHALL be backfilled conservatively and marked `migrated_requires_manual_review = true` when trust context is unsafe or unknown.
- Policy decision receipts SHALL remain replayable across migrations because the evaluator hash is pinned.
- Every migration SHALL be declared by `MigrationContractSchema` and SHALL emit a `MigrationReceiptSchemaV2` on success or dry-run completion.

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `SyncRequestIdentityProofSchema` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |
| `NetworkTrustSessionSchema` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |
| `SyncIdentityVerificationResultSchema` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |
| `SyncEdgeStripReceiptSchema` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |
| `SyncConflictResolutionPolicySchema` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |
| `SyncBatchPreflightReceiptSchemaV3` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |
| `SyncExecutionReceiptSchemaV2` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |
| `ImportMergeConflictResolutionSchema` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |
| `MigrationContractSchema` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |
| `MigrationReceiptSchemaV2` | Consumed by sync request handlers, preflight logic, migration tooling, and sync inspectors | identity verification, preflight, execution, conflict resolution, and migration runs | EC §7, PropA governance metadata, DOC72/DOC1 sync constraints |

## 8. Export, backup, and artifact retention

This section defines how EC packages durable artifacts for export, backup, and retention governance without creating a second mutable truth path. It exists so auditability, archive-not-delete behavior, and recovery are deterministic across all artifact classes introduced by the companion pair. The wireframe below shows the operator surface for export, backup, and retention preview.

**Settings / operator wireframe — Export & Backup**

```text
Export and Backup
────────────────────────────────────────────────────────────────
Export scope                      [▾ system_state + learning_artifacts ]
Include archived artifacts        [ ]
Include policy decision receipts  [✓]
Retention preview                 [Show matrix]

Backups
  Last successful backup          2026-04-15 02:14
  Backup target                   local encrypted archive
  Verify on completion            [✓]

[Run export] [Run backup now] [Open retention matrix]
```


**Retention and recovery note**

Export and backup are part of the control plane because they touch the same durable artifacts that the rest of the system governs. A backup/export path that does not understand artifact classes, receipt retention, and archive-not-delete constraints would create a second, weaker data-governance path outside EC’s normal truth model.


**Owned by:** EC Core Addendum A (backup/export infrastructure), with cross-doc obligations into DOC1 retention rules, DOC20 export surfaces, and PropA artifact-class retention expectations.

**V3.2 changes from V3.1:** No contract changes. This section now restores the purpose prose for export/backup and explains why retention rows are part of the operative infrastructure rather than an afterthought.

This section defines how EC packages durable state for export, backup, and retention governance without creating a second mutable truth store. It exists so auditability, archival behavior, and recovery paths are deterministic and so artifact classes introduced by companion specs have a home in a unified retention model.


### 8.1 Export

```ts
export const ExportScopeSchema = z.enum([
  "full_raw_backup",
  "firm_tenant_only",
  "sanitized_anonymized",
]);

export const ExportRequestSchemaV3 = z.object({
  export_scope: ExportScopeSchema,
  encryption_password: z.string().min(12),
  schema_version: z.literal(3),
});
```

### 8.2 Policy receipt retention row

```ts
export const ArtifactRetentionMatrixEntrySchema = z.object({
  artifact_kind: z.enum([
    "policy_decision_receipts",
    "exposure_incident_receipts",
    "compiled_effective_policy",
    "settings_wiring_manifest",
    "self_review_export",
    "dspy_dataset",
    "validation_artifact",
    "canary_artifact",
    "dlq_entry",
  ]),
  retention_days: z.number().int().nonnegative(),
  gc_policy: z.enum(["purge", "archive", "tombstone"]),
  schema_version: z.literal(1),
});
```

| Artifact class | Default TTL | Local-only restriction | Notes |
|---|---:|---|---|
| policy_decision_receipts | 90d | no | immutable for forensic replay |
| exposure_incident_receipts | 180d | no | incident truth |
| compiled_effective_policy | keep active + 5 prior generations | no | replayable with evaluator hash |
| settings_wiring_manifest | keep current generation | no | regenerated |
| self_review_export | 30d | yes when local_only | review evidence |
| dspy_dataset | until archived | enforce by data class | frozen datasets and slices |
| validation_artifact | 30d | mirror source exposure class | validation receipts |
| canary_artifact | 30d | mirror source exposure class | canary evidence |
| dlq_entry | 90d | no | retry/debug truth |

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `ExportScopeSchema` | Consumed by export/backup handlers, retention jobs, and audit/recovery tooling | manual export/backup, scheduled retention, and audit/recovery operations | EC §8, DOC1 retention, PropA artifact classes |
| `ExportRequestSchemaV3` | Consumed by export/backup handlers, retention jobs, and audit/recovery tooling | manual export/backup, scheduled retention, and audit/recovery operations | EC §8, DOC1 retention, PropA artifact classes |
| `ArtifactRetentionMatrixEntrySchema` | Consumed by export/backup handlers, retention jobs, and audit/recovery tooling | manual export/backup, scheduled retention, and audit/recovery operations | EC §8, DOC1 retention, PropA artifact classes |

## 9. Inspector surfaces, manifest generation, and closure evidence

This section defines the read surfaces and generated manifests that make the pair inspectable to humans and coding agents. It exists to expose effective runtime state, active policy generations, route wiring, closure evidence, and degraded states directly rather than forcing every later review to reconstruct them from code and logs. The summary below shows the inspection surfaces that consume those manifests and read models.

**Inspector summary**

```text
EC Inspector Surfaces
────────────────────────────────────────────────────────────────
- Effective runtime state inspector
- Canonical truth manifest
- Policy decision inspector / simulator
- Background processing dashboard
- Agent profile compatibility compiler
- Token / cost usage inspector
- Sync preflight + execution receipts
- Export / backup history
- Closure evidence ledger

Every inspector must show:
  loading / empty / blocked / degraded / error / stale-data
```


**Why the inspector layer matters**

These surfaces are the anti-phantom-feature proof layer for the companion pair. They let a human or coding agent see what settings are desired versus effective, what routes actually exist, which policy generation is active, and what closure evidence supports that claim. Without this layer, the system would be back to reconstructing truth from scattered docs and code guesses.


**Integrates into:** DOC20 inspector/read surfaces, DOC21 component inventory, DOC22 page inventory, companion closure ledgers, and route/read-model traceability for the current review cycle.

**V3.2 changes from V3.1:** No contract changes. This section now restores the explanation of what the inspector and manifest layers are for, why they matter, and how they prevent the old phantom-feature failure modes.

This section defines the read surfaces and generated manifests that let a human or coding agent inspect what the system believes is currently operative. It exists to make route wiring, policy decisions, active settings, and closure evidence visible and testable instead of forcing every future review to reconstruct them from raw code.


### 9.1 Wiring and component inventory

```ts
export const WiringManifestEntrySchemaV2 = z.object({
  control_id: z.string().max(160),
  command_schema_ref: z.string().max(200),
  route_id: z.string().max(160),
  read_model_id: z.string().max(160),
  runtime_consumer: z.string().max(160),
  acceptance_test_id: z.string().max(160),
  schema_version: z.literal(2),
});

export const SettingsComponentInventoryEntrySchemaV2 = z.object({
  component_id: z.string().max(160),
  page_id: z.string().max(120),
  route_id: z.string().max(160),
  loading_state_spec_ref: z.string().max(200),
  empty_state_spec_ref: z.string().max(200),
  blocked_state_spec_ref: z.string().max(200),
  degraded_state_spec_ref: z.string().max(200),
  schema_version: z.literal(2),
});
```

**Normative rule:** CI SHALL fail if a visible control lacks a wiring-manifest entry and acceptance test id.

### 9.2 Closure evidence

```ts
export const ClosureEvidenceLinkSchema = z.object({
  issue_id: z.string().max(120),
  landed_in_doc: z.string().max(80),
  section_ref: z.string().max(120),
  route_or_schema_ref: z.string().max(200).optional(),
  acceptance_test_id: z.string().max(120).optional(),
  schema_version: z.literal(1),
});

export const ClosureLedgerEntrySchemaV2 = z.object({
  issue_id: z.string().max(120),
  landing_status: z.enum(["closed_cleanly", "partial_close", "rejected"]),
  evidence_links: z.array(ClosureEvidenceLinkSchema).default([]),
  schema_version: z.literal(2),
});
```

### Schema consume-path map

| Schema | Consumed by | When | Consume path |
|---|---|---|---|
| `WiringManifestEntrySchemaV2` | Consumed by inspector pages, manifest generators, closure ledgers, and route/control-plane audits | read-surface rendering, manifest generation, review cycles, and closure verification | EC §9, DOC20/DOC21/DOC22 surfaces and pair-review evidence |
| `SettingsComponentInventoryEntrySchemaV2` | Consumed by inspector pages, manifest generators, closure ledgers, and route/control-plane audits | read-surface rendering, manifest generation, review cycles, and closure verification | EC §9, DOC20/DOC21/DOC22 surfaces and pair-review evidence |
| `ClosureEvidenceLinkSchema` | Consumed by inspector pages, manifest generators, closure ledgers, and route/control-plane audits | read-surface rendering, manifest generation, review cycles, and closure verification | EC §9, DOC20/DOC21/DOC22 surfaces and pair-review evidence |
| `ClosureLedgerEntrySchemaV2` | Consumed by inspector pages, manifest generators, closure ledgers, and route/control-plane audits | read-surface rendering, manifest generation, review cycles, and closure verification | EC §9, DOC20/DOC21/DOC22 surfaces and pair-review evidence |

## 10. Non-goals and explicit rejections

**Owned by:** EC Core Addendum A (no parent-doc integration — this section defines EC-side architectural boundaries and explicit non-goals for the current pair).

**V3.2 changes from V3.1:** No contract changes. This section now restores the rationale layer around the explicit rejections so later drafts do not quietly reintroduce them.

This section records what EC Core is intentionally not doing in this cycle. It exists to prevent later implementation from sneaking in second evaluators, fake local-only guarantees, or speculative infrastructure under the excuse that the schema surface looked available.


The next version SHALL NOT implement:
- a second policy evaluator,
- a second route registry,
- graph-signed “policy provenance nodes” as durable truth,
- MPC/TEE/zero-knowledge extraction proofs,
- differential privacy noise on embeddings as a requirement,
- ephemeral cloud proxy decryption,
- native OpenClaw history scrubbing.

At-rest protection for `ELNOR_MEMORY` SHOULD rely on host-OS protection (for example FileVault / Keychain-backed platform security), not custom application-layer cryptography.

---

## 11. Coding-agent implementation order

**Owned by:** EC Core Addendum A (implementation guidance for the current companion pair; consumed jointly with PropA implementation order and CI planning).

**V3.2 changes from V3.1:** No contract changes. This section now explains why the sequencing matters and what runtime-truth failures each phase is meant to prevent.

This section defines the safe order for implementing EC Core’s control-plane contracts so the runtime truth foundation is present before dependent settings, policies, and inspectors are layered on top. It exists to prevent a build from exposing toggles, routes, or policy receipts before the underlying engine can actually support them.


1. Canonical collapse and supersession markers
2. Effective runtime state and browser semantics fix
3. Route-contract registry + canonical truth manifest
4. Compiled policy artifact + evaluator + simulation / why-blocked routes
5. Task registry + queue truth + idempotency
6. Execution profiles + local proxy + compatibility compiler
7. Token/budget governance
8. Sync identity proof + preflight
9. Export/backup/retention
10. Wiring manifest + component inventory + acceptance tests



## 12. CI gates and acceptance tests

**Owned by:** EC Core Addendum A with cross-doc obligations into PropA CI, DOC21/22 wiring checks, route-registry validation, and task-registration verification.

**V3.2 changes from V3.1:** No contract changes. This section now restores the explanation of what each acceptance gate proves and why those proofs matter for runtime honesty.

This section defines the automated checks that keep EC’s control plane from regressing into ghost settings, duplicate registries, or inconsistent policy decisions. It exists to enforce the architectural promises of the document mechanically, not just rhetorically.


At minimum, CI SHALL enforce:

1. **Canonical collapse gate**
   - no duplicate operative route ids
   - no duplicate operative task ids
   - no duplicate operative policy evaluator exports

2. **Effective-mode composition test**
   - `computeEffectiveCollectionMode()` SHALL return `blocked` whenever EC global/surface controls are off, regardless of PropA source-rule permissiveness
   - `do_not_collect` source/policy categories SHALL dominate all lower-permission paths

3. **Browser-off truth test**
   - when `browser_metadata_history = false`, no browser metadata write occurs
   - when `browser_entity_extraction = false`, no browser extraction task is scheduled

4. **Local-only routing test**
   - any `same_machine_local_only` task SHALL fail or defer if no compatible local profile exists
   - no cloud fallback may occur

5. **Route-manifest completeness test**
   - every visible control has a wiring-manifest entry
   - every wiring-manifest entry points to a registered route and read model

6. **Sync identity test**
   - unsigned or expired `SyncRequestIdentityProofSchema` inputs SHALL block sync

7. **Receipt replay test**
   - policy decision receipts SHALL remain replayable against their pinned evaluator hash and generation id

8. **Budget breaker test**
   - DSPy and self-review runs SHALL stop when run caps or monthly cloud caps are crossed

---