DOC15_Cognitive_Infrastructure_Layer_R7_1_Consolidated_Current_Draft.md
Current Specs/DOC15/DOC15_Cognitive_Infrastructure_Layer_R7_1_Consolidated_Current_Draft.md
# DOC15 Cognitive Infrastructure Layer (CIL) — R7.1 [Consolidated Current Draft]
## Revision Lineage (must persist in all later versions)
Based on:
- DOC15 Cognitive Infrastructure Layer (CIL) — R7 [Consolidated Current Draft]
- DOC15 FUTURE ADDITION FOR R7 — Authority Salience and Injection Governance
- DOC15 Authority Salience Addendum — Merge Checklist R1
- DOC15 R7 Amendment Brief
- DOC14 Cross-Document Delta Companion R1
- DOC16 Deferred Additions R5.1 COMPLETE
- DOC10 Orchestration Integration Ledger R10 [Consolidated]
- DOC15 R6 red-team synthesis / review pack
This R7.1 document fully subsumes the prior operative DOC15 R7 draft and fully consumes the remaining authority-salience / injection-governance delta that had remained in a separate addendum. Earlier R6/R6.x/R7 planning-note variants remain historical provenance only.
## Consolidation Rule
This file is the current single operative DOC15 specification for the R7.1 line. Part 1 preserves the inherited baseline lineage. Part 2 preserves the merged R7 revision lineage. Part 3 fully integrates the remaining authority-salience additions and governs over any overlapping or conflicting earlier statement.
## Included Source Chain
1. Inherited Baseline — DOC15 Cognitive Infrastructure Layer (CIL) — R6 [Consolidated Current]
2. Merged Revision — DOC15 R7 Amendment Brief
3. Merged Revision — Authority Salience, Injection Governance, and Instruction Cooling
4. Merge-normalization source — DOC15 Authority Salience Addendum — Merge Checklist R1
# DOC15 Cognitive Infrastructure Layer (CIL) — R6 [Consolidated Current]
## Revision Lineage (must persist in all later versions)
Based on DOC15 R6.1 Cognitive Infrastructure Layer Draft plus DOC15 R6.2 (Graph / Topology Consumption Alignment). This historical provenance block is preserved for lineage only. Any earlier statement that the separate authority-salience addendum was not incorporated is superseded by Part 3 of this R7.1 draft.
## Consolidation Rule
This file is the current single operative DOC15 specification for the R6 line. Later merged revision blocks govern over earlier baseline statements on overlapping subjects.
## Included Source Chain
- 1. Inherited Baseline — DOC15 R6.1 Cognitive Infrastructure Layer Draft — source file: `DOC15_R6_1_Cognitive_Infrastructure_Layer_DRAFT.md`
- 2. Merged Revision — DOC15 R6.2 (Graph / Topology Consumption Alignment) — source file: `DOC15_R6_2_Cognitive_Infrastructure_Layer_Graph_Topology_Consumption.md`
---
# Part 1 — Inherited Baseline — DOC15 R6.1 Cognitive Infrastructure Layer Draft
# DOC 15 R6 — Cognitive Infrastructure Layer (CIL)
# ELNOR Suite Specification
**Date:** March 8, 2026
**Status:** Draft for review
**Companions:** DOC15 Addendum A v1.3, DOC15 Cross-Document Integration Contract v1.1 Draft, DOC16 Punch List entries 16.1 and 16.3
**Build assumption:** clean-room rebuild, local-first, EC remains sole durable writer, OpenClaw remains runtime truth.
---
## R6 Revision Summary
R6 is a buildability and integration-hardening revision. It keeps the R5 architecture, but resolves the consensus blockers identified in the red-team bundle:
- payload-kind drift is eliminated with one canonical `PayloadKindSchema`
- `confidence.mean` is removed; all mean checks compute via `betaMean(confidence)`
- shadow-gate logic becomes payload-kind specific (`either`, `both`, `confirm_and_confidence`)
- suggestion cards become machine-real through `ResolvedSuggestionElementSchema` and `SuggestionEditDeltaSchema`
- `signals.jsonl` becomes a declared canonical store with retention, recovery, and archival rules
- the DOC8 bridge becomes nightly, alias-safe, and quarantine-capable
- user-authored knowledge nodes remain heuristic and never silently become authority memory
- the advisory path becomes state-versioned, snapshot-safe, and routed through the generic advisor dispatcher seam
- the spec adds the missing schemas, missing normative functions, missing UI contracts, initialization order, shutdown behavior, and Phase 5 checkpointing
- the integration summary and companion contract are reconciled to include capture points 25–40, authority-scope capture, and the ownership corrections for context-change emission
- durable corrections become scope-aware and approval-gated, while one-off stream-of-thought instructions are modeled as transient per-dispatch constraints rather than silent global rules
This revision is intentionally larger than R5. Phasing remains an implementation concern, not a completeness excuse.
---
## How to Read This Document
This document is normative for EC-owned CIL behavior. Anything under OpenClaw native runtime truth remains owned by DOC11/DOC4/OpenClaw and is not redefined here.
- **Architecture** subsections define what exists, why it exists, and what it must not do.
- **Schemas** subsections define typed contracts; Zod is preferred and all field names are normative.
- **Wiring** subsections define cross-system ownership, data flow, and failure behavior.
- **Code** subsections provide file paths and pseudocode or TypeScript strong enough for a coding agent to build without guessing.
- **Endpoints** subsections define external or internal route contracts.
- **UI** subsections define user-visible behavior, states, and actions.
- **Acceptance** subsections define at least one concrete test scenario.
---
## Part I — Governing Design Principles
### §1.1 The Falsification Principle
**Architecture**
CIL exists to make EC’s decision-making more consistent, inspectable, and improvable without pretending certainty it does not have. CIL must prefer disconfirmation to flattering anecdotes. Every recommendation, node, and learning outcome must be traceable to concrete evidence and must remain reversible.
### §1.2 The OpenClaw Native Preservation Rule
**Architecture**
CIL is EC-internal cognitive infrastructure. It is not a replacement runtime, not a second chat agent, and not a shadow orchestrator. Protected OpenClaw-native artifacts are preserved exactly as runtime truth and may only be referenced or decorated, never rewritten, summarized, optimized, or canonicalized by CIL.
### §1.2A Native Context Protection Enforcement
**Schemas**
```ts
export const ProtectedNativeContextClassSchema = z.enum([
"soul_md",
"native_session_history",
"native_memory_md",
"tool_policy",
"skills_manifest",
"agent_dir_state",
"runtime_bootstrap_packet",
"gateway_runtime_truth"
]);
```
**Wiring**
- Source of truth: DOC11 / OpenClaw runtime.
- Destination: CIL may read protection metadata only.
- Failure behavior: if CIL attempts to rewrite protected native context, EC must reject the write with `CIL_NATIVE_CONTEXT_VIOLATION` and log the attempted mutation.
### §1.3 The Precedence Chain
**Architecture**
Precedence order is:
1. protected native runtime truth
2. authority memory (standing orders, explicit corrections, never rules)
3. user-authored heuristic nodes
4. validated heuristic nodes
5. shadow or pending heuristic nodes
6. broad pooled heuristics
7. convenience recommendations (document suggestions, workspace defaults)
The chain governs conflict resolution, context injection, UI explanation, and retirement behavior.
### §1.3A Intra-Authority Conflict Resolution
**Wiring**
Authority conflicts are not statistically resolved. EC must surface the conflict count, inject conflict annotations, and emit `standing_order_conflict` diagnostics. CIL may recommend cleanup, but may not auto-resolve the conflict.
### §1.3B Correction Capture and Scope Rule
**Architecture**
Durable corrections are intentionally strong, but they must be difficult to create accidentally. CIL must not treat freeform stream-of-thought instructions as global rules. A sentence like “draft this in Word; no markdown” is a current-operation or session constraint unless the user explicitly saves it more broadly or approves a broader correction proposal.
The governing rule is:
- **transient instruction first**
- **durable authority only after explicit save / approved proposal / structured command**
- **every durable authority record must carry scope**
- **if scope is ambiguous, the record stays transient or candidate-only**
**Wiring**
- DOC1 / EC own candidate → pending → explicit approval for durable standing knowledge.
- DOC15 consumes approved durable authority records plus transient per-dispatch instructions carried on the operation envelope.
- Failure behavior: if durability intent or scope cannot be proven, EC must not write a durable authority record.
**Acceptance**
Given a chat turn containing “don’t use markdown, draft this in Word” and no explicit save action, the instruction is represented only as a transient per-dispatch constraint and does not appear as durable authority memory on the next unrelated dispatch.
### §1.4 The Single-Writer Discipline
**Architecture**
EC remains the sole durable writer. Q never writes durable state directly. OpenClaw owns native runtime history and semantics. CIL stores durable state only through EC-owned append-only JSONL and atomic current-view artifacts.
### §1.5 CIL Is Not a Layer That Owns All Intelligence
**Architecture**
CIL compiles operations, plans context, records checkpoints, values signals, and promotes or retires heuristic knowledge. It does not autonomously launch workflows, does not bypass explicit operations, and does not magically make every button “memory-aware.”
### §1.6 CIL Is a Reviewer-Operations Layer
**Architecture**
Early value comes from panel/task/self-heal/self-improvement operations routed through EC. Chat can become a compiler front door later, but must route through the same operation model.
### §1.7 Optimize for Surprise, Not Volume
**Architecture**
CIL should prefer findings and configurations that are useful, novel, and outcome-improving. Binary accept/reject is insufficient. Capture points such as novelty, starred findings, and ReviewOutcome remain critical because they tell the system what mattered, not just what happened.
**Acceptance**
A red-team workflow that yields many accepted but non-novel findings must not outscore a smaller set of starred, surprising findings when enough novelty signals exist.
---
## Part II — Architecture Overview
### §2.1 The Problem Being Solved
**Architecture**
Before CIL, EC can run operations and keep durable state, but it does not have a single deterministic substrate for:
- turning user intent into a structured operation envelope
- selecting context in a bounded and explainable way
- turning outcomes into reusable recommendation nodes
- explaining why a recommendation exists and what evidence supports it
- learning from dispatches, panel closes, room closes, friction, and document usage without inventing hidden state
### §2.2 System Components
**Architecture**
CIL has five core services:
1. `OperationCompiler` — turns `OperationIntent` into `ResolvedOperation`
2. `ProfileRegistry` — maps `ContextFacts` to profiles and capability adapters
3. `MemoryService` — retrieves authority memory and validated heuristic nodes
4. `ContextPlanner` — produces bounded `ContextPlan` objects and `DispatchCheckpoint` records
5. `LearningPipeline` — ingests `SignalBatch` records, bridges DOC8, updates beta state, promotes or retires nodes, and produces nightly artifacts
### §2.3 Architecture Diagram
```text
Q surface / automation / chat
-> OperationIntent
-> OperationCompiler
-> buildContextFacts()
-> resolveProfiles()
-> MemoryService.query()
-> ContextPlanner.plan()/seal()
-> ResolvedOperation
-> user applies / edits / dismisses suggestion OR operation runs directly
-> Dispatch orchestrator
-> write DispatchCheckpoint (sync)
-> execute via DOC11 / assembleContextCard(contextPlan?)
-> append SignalBatch to signal_spool.jsonl (sync)
-> nightly pipeline
-> recover / flush / bridge DOC8 / update beta state / evaluate gates / promote-retire / emit nightly cards
-> advisory path
-> buildQueryContext()
-> DOC10 advisor dispatcher
-> CIL advisor reads snapshot-safe state + DocIndex metadata
```
### §2.4 What CIL Does Not Do
**Architecture**
CIL does not:
- mutate OpenClaw-native protected artifacts
- write durable state from Q
- own runtime truth or native session history
- infer authority memory from heuristic edits
- autonomously start operations without an explicit initiating action
- treat cost or budget exhaustion as a quality signal
- replace DOC8’s optimizer/replay/canary ownership
### §2.5 Repository Layout
**Code**
```text
packages/contracts/src/cil/
schemas.ts
constants.ts
types.ts
apps/ec-service/src/cil/
OperationCompiler.ts
ProfileRegistry.ts
MemoryService.ts
ContextPlanner.ts
LearningPipeline.ts
SignalStore.ts
AdvisoryStateReader.ts
bootstrap.ts
shutdown.ts
health.ts
ui-read-models.ts
matchers.ts
beta-math.ts
docs.ts
apps/ec-service/src/routes/
cil-routes.ts
advisor-dispatch-proxy.ts
apps/q-frontend/src/features/cil/
SuggestionCard.tsx
KnowledgeNodeBrowser.tsx
ContextInspectorDrawer.tsx
SignalExplorer.tsx
ProfileDashboard.tsx
CILHealthDashboard.tsx
WhatChangedCard.tsx
buildQueryContext.ts
```
### §2.5A CILConfigSchema and Defaults
**Schemas**
```ts
export const CILConfigSchema = z.object({
schema_version: z.literal(6),
shadow_gate_rules: z.array(ShadowGateRuleSchema).default(DEFAULT_SHADOW_RULES),
advisory_timeout_ms: z.number().int().positive().default(5000),
advisory_retry_enabled: z.boolean().default(false),
docindex_registration_retry_backoff_ms: z.number().int().positive().default(30000),
signal_spool_rotation_threshold_bytes: z.number().int().positive().default(5_000_000),
signal_spool_recovery_policy: z.enum(["truncate_partial_tail", "quarantine_corrupt_file"]).default("truncate_partial_tail"),
phase5_checkpoint_frequency_nodes: z.number().int().positive().default(25),
confidence_tier_thresholds: z.object({
very_limited_max_sessions: z.number().int().positive().default(1),
limited_max_sessions: z.number().int().positive().default(3),
moderate_max_sessions: z.number().int().positive().default(7)
}),
flushed_signal_retention_days: z.number().int().positive().default(180),
checkpoint_archive_days: z.number().int().positive().default(180),
max_query_context_payload_bytes: z.number().int().positive().default(20000),
browser_page_size_default: z.number().int().positive().default(50),
signal_explorer_page_size_default: z.number().int().positive().default(50),
signal_explorer_default_lookback_days: z.number().int().positive().default(14)
});
```
### §2.6 Initialization Sequence
**Code**
```ts
export async function initializeCIL(): Promise<void> {
await ensureDirectoryLayout();
await recoverSignalSpool();
await loadConfigAndValidateVersions();
await loadProfilesAndTraits();
await buildKnowledgeNodeIndex();
await registerCILStateFilesInDocIndex();
await registerSpecDocumentsInDocIndex();
await registerBridgeHooks();
await emitCILEvent("CIL_INITIALIZED", { phase: "boot" });
}
```
**Wiring**
- EC owns initialization.
- DOC10/DOC11/DOC4 are not allowed to mutate CIL state on initialization; they may only register dependent routes or agent metadata.
- If DocIndex registration fails, CIL startup continues in degraded mode and emits `CIL_DOCINDEX_REGISTRATION_FAILED`.
### §2.7 Graceful Shutdown
**Code**
```ts
export async function shutdownCILGracefully(): Promise<void> {
await flushInMemorySignalBufferToSpool();
await persistIndexesAndCursors();
await emitCILEvent("CIL_SHUTDOWN_COMPLETE", {});
}
```
**Failure behavior**
- If graceful shutdown runs, no valid appended signal batch may be lost.
- A process kill may lose the final in-memory batch, but may not corrupt existing durable files.
**Acceptance**
Kill the EC process after one complete dispatch and before nightly. On restart, `recoverSignalSpool()` truncates any partial final line, logs `CIL_SPOOL_TRUNCATED` if needed, and all previously valid batches remain replayable.
---
## Part III — The Profile System
### §3.1 Purpose
**Architecture**
Profiles are typed bundles of capabilities, limits, and behavioral switches chosen from `ContextFacts`. They let CIL say “this is a memory red-team review in a panel with a document target” without scattering if/else logic across EC.
### §3.2 Traits (Composition, Not Inheritance)
**Architecture**
Profiles compose reusable traits. Traits may add capability adapters, hard limits, and matcher conditions. Capability uniqueness is keyed by `(capability_kind, handler)` unless explicitly overridden.
### §3.2A TraitDefinitionSchema
```ts
export const TraitDefinitionSchema = z.object({
trait_id: z.string(),
description: z.string(),
capabilities: z.array(z.string()).default([]),
hard_limits: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).default({}),
applies_when: z.lazy(() => ContextMatcherSchema).optional()
});
```
### §3.3 ContextFactsSchema
**Architecture**
`ContextFacts` is the single input to profile resolution. It contains only durable or derivable context, never runtime-fabricated hidden state.
**Schemas**
```ts
export const ArtifactRefSchema = z.object({
artifact_id: z.string(),
artifact_kind: z.enum(["document", "code_diff", "codebase_scope", "image", "note", "spec", "other"]),
label: z.string().optional(),
doc_role: z.string().optional()
});
export const AuthorityScopeSchema = z.enum([
"operation",
"session",
"thread",
"panel",
"room",
"task",
"workspace",
"matter",
"global"
]);
export const AuthorityCreationPathSchema = z.enum([
"explicit_user_save",
"approved_proposal",
"structured_command",
"migrated"
]);
export const AuthorityAppliesToSchema = z.object({
operation_families: z.array(z.string()).default([]),
interaction_modes: z.array(z.enum(["panel", "task", "room", "chat", "self_healing", "automation"])).default([]),
surface_kinds: z.array(z.enum(["panel", "task", "room", "chat", "self_healing", "automation"])).default([]),
workspace_ids: z.array(z.string()).default([]),
panel_ids: z.array(z.string()).default([]),
thread_ids: z.array(z.string()).default([]),
room_ids: z.array(z.string()).default([]),
matter_ids: z.array(z.string()).default([]),
task_types: z.array(z.string()).default([]),
tags: z.array(z.string()).default([])
}).default({});
export const TransientInstructionSchema = z.object({
instruction_id: z.string().uuid(),
text: z.string(),
scope: z.enum(["operation", "session"]).default("operation"),
source_surface: z.enum(["panel_setup", "task_setup", "room_setup", "chat", "automation", "self_heal"]),
created_at: z.string().datetime(),
expires_at: z.string().datetime().optional()
});
export const AuthorityMemoryRecordSchema = z.object({
authority_id: z.string(),
authority_kind: z.enum(["standing_order", "correction", "never_rule"]),
text: z.string(),
scope: AuthorityScopeSchema,
applies_to: AuthorityAppliesToSchema,
creation_path: AuthorityCreationPathSchema,
source_surface: z.enum(["panel_setup", "task_setup", "room_setup", "chat", "automation", "self_heal"]).optional(),
origin_dispatch_id: z.string().optional(),
memory_firewall_ref: z.string().optional(),
expires_at: z.string().datetime().optional(),
created_at: z.string().datetime()
});
export const ContextChangeEventSchema = z.object({
change_type: z.enum(["model_registry", "standing_orders", "profile_definitions"]),
severity: z.enum(["minor", "moderate", "major"])
});
export const NodeSignalStatsSchema = z.object({
signal_count: z.number().nonnegative(),
positive_ratio: z.number().min(0).max(1),
independent_sessions: z.number().nonnegative(),
authority_conflict: z.boolean().default(false)
});
export const ContextFactsSchema = z.object({
schema_version: z.literal(6),
operation_family: z.string(),
interaction_mode: z.enum(["panel", "task", "room", "chat", "self_healing", "automation"]),
surface_kind: z.enum(["panel", "task", "room", "chat", "self_healing", "automation"]),
task_type: z.string(),
workspace_id: z.string(),
panel_id: z.string().optional(),
task_id: z.string().optional(),
thread_id: z.string().optional(),
room_id: z.string().optional(),
matter_id: z.string().optional(),
linked_room_group_id: z.string().optional(),
workflow_stage: z.string().optional(),
logical_role_key: z.string().optional(),
review_target_kind: z.enum([
"document",
"code_diff",
"codebase_scope",
"spec",
"image",
"general",
"other"
]).optional(),
evidence_domain: z.enum(["section_ref", "file_line", "image_region", "message_range", "mixed", "none"]).optional(),
runtime_type: z.enum(["local", "cloud", "mixed"]).optional(),
model_id: z.string().optional(),
topology: z.string().optional(),
prompt_method: z.array(z.string()).default([]),
explicit_profile_id: z.string().optional(),
artifact_refs: z.array(ArtifactRefSchema).default([]),
tags: z.array(z.string()).default([]),
user_initiated: z.boolean().default(true)
});
```
### §3.3B OperationIntent → ContextFacts Mapping Table
**Schemas**
```ts
export const OperationIntentToContextFactsMappingSchema = z.object({
operation_family: z.string(),
interaction_mode: ContextFactsSchema.shape.interaction_mode,
surface_kind: ContextFactsSchema.shape.surface_kind,
task_type: z.string(),
default_review_target_kind: ContextFactsSchema.shape.review_target_kind.optional()
});
```
**Normative mapping**
| operation_family | interaction_mode | surface_kind | task_type | default review_target_kind |
|---|---|---|---|---|
| `panel_review` | `panel` | `panel` | `red_team` | `document` |
| `task_dispatch` | `task` | `task` | `general_task` | `general` |
| `self_heal` | `self_healing` | `self_healing` | `self_heal` | `general` |
| `standing_order` | `automation` | `automation` | `standing_order` | `general` |
| `generate_and_review` | `panel` | `panel` | `generate` | `document` |
| `explore` | `chat` | `chat` | `explore` | `general` |
| `room_red_team` | `room` | `room` | `red_team` | `document` |
| `room_spec_review` | `room` | `room` | `spec_review` | `spec` |
| `matter_review` | `panel` | `panel` | `matter_review` | `document` |
### §3.4 ContextMatcherSchema
**Schemas**
```ts
export const MatcherValueSchema = z.union([
z.string(),
z.number(),
z.boolean(),
z.array(z.union([z.string(), z.number(), z.boolean()]))
]);
export const ContextMatcherRuleSchema = z.object({
field: z.string(),
equals: MatcherValueSchema.optional(),
in: z.array(z.union([z.string(), z.number(), z.boolean()])).optional(),
not_in: z.array(z.union([z.string(), z.number(), z.boolean()])).optional(),
exists: z.boolean().optional(),
min: z.number().optional(),
max: z.number().optional()
}).superRefine((rule, ctx) => {
if (!rule.equals && !rule.in && !rule.not_in && rule.exists === undefined && rule.min === undefined && rule.max === undefined) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "ContextMatcherRule requires at least one operator" });
}
});
export const ContextMatcherSchema = z.object({
required: z.array(ContextMatcherRuleSchema).default([]),
preferred: z.array(ContextMatcherRuleSchema).default([]),
excluded: z.array(ContextMatcherRuleSchema).default([]),
specificity_score: z.number().default(0)
});
```
**Code**
```ts
export function evaluateContextMatcher(contextFacts: ContextFacts, matcher: ContextMatcher): {
matches: boolean;
specificity: number;
} {
const requiredOk = matcher.required.every(rule => evaluateRule(contextFacts, rule));
const excludedHit = matcher.excluded.some(rule => evaluateRule(contextFacts, rule));
if (!requiredOk || excludedHit) return { matches: false, specificity: 0 };
const preferredHits = matcher.preferred.filter(rule => evaluateRule(contextFacts, rule)).length;
return { matches: true, specificity: matcher.required.length * 10 + preferredHits + matcher.specificity_score };
}
```
**Acceptance**
A matcher with `workspace_id in [A, B]` and `review_target_kind = document` must match both workspaces A and B without dropping the workspace dimension from the node.
### §3.5 CapabilityAdapterSchema
```ts
export const CapabilityAdapterSchema = z.object({
capability_id: z.string(),
capability_kind: z.string(),
handler: z.string(),
enabled: z.boolean().default(true),
config: z.record(z.string(), z.any()).default({}),
hard_limits: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).default({})
});
```
### §3.6 CapabilityRegistryEntrySchema
```ts
export const CapabilityRegistryEntrySchema = z.object({
capability_kind: z.string(),
handler: z.string(),
source_file: z.string(),
function_name: z.string(),
description: z.string(),
owner_doc: z.string()
});
```
### §3.7 ProfileDefinitionSchema
```ts
export const ProfileDefinitionSchema = z.object({
profile_id: z.string(),
description: z.string(),
traits: z.array(z.string()).default([]),
applies_when: ContextMatcherSchema,
capabilities: z.array(CapabilityAdapterSchema).default([]),
hard_limits: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).default({}),
shadow_gate_profile_override: z.record(z.string(), z.any()).optional(),
min_node_confidence: z.number().min(0).max(1).default(0.55)
});
```
### §3.8 ProfileStateSchema and Activation Records
```ts
export const ProfileStateSchema = z.object({
profile_id: z.string(),
active: z.boolean().default(true),
last_resolved_at: z.string().datetime().optional(),
activation_count: z.number().int().nonnegative().default(0),
last_context_hash: z.string().optional(),
last_reload_at: z.string().datetime().optional()
});
export const ProfileActivationRecordSchema = z.object({
record_id: z.string().uuid(),
profile_id: z.string(),
dispatch_id: z.string().optional(),
workspace_id: z.string(),
activated_at: z.string().datetime()
});
```
### §3.9 ProfileRegistry Behavior
**Code**
```ts
export async function buildContextFacts(intent: OperationIntent): Promise<ContextFacts> {
const derived = deriveOperationFamilyDefaults(intent.operation_family);
return {
schema_version: 6,
operation_family: intent.operation_family,
interaction_mode: derived.interaction_mode,
surface_kind: derived.surface_kind,
task_type: derived.task_type,
workspace_id: intent.workspace_id,
panel_id: intent.panel_id,
task_id: intent.task_id,
thread_id: intent.thread_id,
room_id: intent.room_id,
matter_id: intent.matter_id,
linked_room_group_id: intent.linked_room_group_id,
workflow_stage: intent.workflow_stage,
logical_role_key: intent.logical_role_key,
review_target_kind: intent.review_target_kind ?? deriveReviewTargetKind(intent.artifact_refs),
evidence_domain: deriveEvidenceDomain(intent.artifact_refs),
runtime_type: intent.runtime_type,
model_id: intent.desired_model_id,
topology: intent.desired_topology,
prompt_method: intent.desired_prompt_method ?? [],
explicit_profile_id: intent.explicit_profile_id,
artifact_refs: intent.artifact_refs ?? [],
tags: intent.tags ?? [],
user_initiated: intent.user_initiated ?? true
};
}
export async function resolveProfiles(contextFacts: ContextFacts): Promise<{ resolved: string[]; fallback_used: boolean }> {
if (contextFacts.explicit_profile_id) {
return { resolved: [contextFacts.explicit_profile_id], fallback_used: false };
}
const matches = allProfiles
.map(profile => ({ profile, result: evaluateContextMatcher(contextFacts, profile.applies_when) }))
.filter(x => x.result.matches)
.sort((a, b) => b.result.specificity - a.result.specificity);
if (matches.length === 0) {
return { resolved: ["context_default_dispatch"], fallback_used: true };
}
const topSpecificity = matches[0].result.specificity;
const tied = matches.filter(m => m.result.specificity === topSpecificity);
if (tied.length === 1) return { resolved: [tied[0].profile.profile_id], fallback_used: false };
const mostRecent = tied
.map(t => ({ id: t.profile.profile_id, recent: getMostRecentActivationTs(t.profile.profile_id) }))
.sort((a, b) => (b.recent ?? "").localeCompare(a.recent ?? ""))[0];
return { resolved: [mostRecent?.id ?? "context_default_dispatch"], fallback_used: !mostRecent };
}
```
**Wiring**
- Profile definitions live under `ELNOR_MEMORY/system/cil/profiles/`.
- Q may submit profile-edit commands, but EC performs reload and validation.
- On any committed profile edit, `ProfileRegistry.reload(profile_id)` must run before the next dispatch.
**Acceptance**
Edit a profile via Q, then trigger a panel setup without restarting EC. The next `ResolvedOperation` must reflect the updated profile and `ProfileState.last_reload_at` must advance.
---
## Part IV — MemoryService
### §4.1 Purpose
**Architecture**
MemoryService retrieves authority memory and validated heuristic nodes for the active profiles. It never rewrites authority memory, never invents state, and never treats user-authored nodes as authority artifacts.
### §4.2 Memory Tiers
**Architecture**
- **Protected native runtime truth:** not owned by CIL; reference only
- **Authority memory:** standing orders, explicit corrections, never rules
- **User-authored heuristic nodes:** heuristic, pinned, not auto-retired
- **Validated heuristic nodes:** live recommendation substrate
- **Shadow / pending nodes:** learning substrate not yet trusted for live use
### §4.2A Durable Authority Capture, Scope Filtering, and Transient Instructions
**Architecture**
Authority memory is strong because it is explicit, scoped, and approved. CIL must therefore distinguish three classes:
1. **durable authority records** — standing orders, approved corrections, never rules
2. **transient instructions** — current operation/session constraints that must not be written as durable authority
3. **candidate corrections** — owner-doc proposals that remain outside durable authority until approved through the DOC1 Memory Firewall
DOC15 consumes class (1) directly and class (2) on the current operation envelope. It does **not** auto-promote freeform chat text into class (1).
**Code**
```ts
export function authorityRecordAppliesToContext(record: AuthorityMemoryRecord, contextFacts: ContextFacts, nowIso = new Date().toISOString()): boolean {
if (record.expires_at && record.expires_at <= nowIso) return false;
const a = record.applies_to;
const matches = <T>(values: T[] | undefined, current: T | undefined) => !values || values.length === 0 || (current !== undefined && values.includes(current));
return matches(a.operation_families, contextFacts.operation_family)
&& matches(a.interaction_modes, contextFacts.interaction_mode)
&& matches(a.surface_kinds, contextFacts.surface_kind)
&& matches(a.workspace_ids, contextFacts.workspace_id)
&& matches(a.panel_ids, contextFacts.panel_id)
&& matches(a.thread_ids, contextFacts.thread_id)
&& matches(a.room_ids, contextFacts.room_id)
&& matches(a.matter_ids, contextFacts.matter_id)
&& matches(a.task_types, contextFacts.task_type)
&& (a.tags.length === 0 || a.tags.some(tag => contextFacts.tags.includes(tag)));
}
export function mayPersistAsDurableAuthority(input: {
requested_scope: z.infer<typeof AuthorityScopeSchema>;
creation_path: z.infer<typeof AuthorityCreationPathSchema> | "inferred_candidate";
explicit_user_confirmation: boolean;
}): boolean {
if (input.creation_path === "inferred_candidate") return false;
if (input.requested_scope === "operation" || input.requested_scope === "session") return input.explicit_user_confirmation;
return input.explicit_user_confirmation && input.creation_path !== "structured_command"
? true
: input.creation_path === "approved_proposal" || input.creation_path === "explicit_user_save";
}
```
**Acceptance**
- A task-local formatting instruction may appear in `transient_instructions` for the current dispatch, but it may not be written as a durable `AuthorityMemoryRecord`.
- A workspace-scoped correction approved through the authority path must apply only inside that workspace and must be visible with its scope badge in the inspector and advisor.
### §4.3 Knowledge Payloads and Nodes
**Schemas**
```ts
export const PayloadKindSchema = z.enum([
"routing_hint",
"context_packaging_hint",
"error_pattern",
"prompt_fragment",
"topic_continuity_summary",
"prompt_effectiveness",
"prompt_method_preference",
"configuration_preference",
"workspace_default"
]);
export const WorkspaceDefaultValueSchema = z.discriminatedUnion("default_type", [
z.object({
default_type: z.literal("document_set"),
doc_ids: z.array(z.string()).min(1),
labels: z.array(z.string()).default([])
}),
z.object({
default_type: z.literal("model_preference"),
model_id: z.string()
}),
z.object({
default_type: z.literal("topology_preference"),
topology: z.string()
})
]);
export const EvidenceGradeSchema = z.object({
grade: z.enum(["observational", "replay_supported", "canary_supported"]),
direct_sessions: z.number().nonnegative().default(0),
pooled_sessions_effective: z.number().nonnegative().default(0),
replay_runs: z.number().nonnegative().default(0),
replay_improved_runs: z.number().nonnegative().default(0),
canary_runs: z.number().nonnegative().default(0),
canary_success_runs: z.number().nonnegative().default(0),
last_updated_at: z.string().datetime()
});
export const BetaConfidenceSchema = z.object({
alpha: z.number().positive(),
beta_param: z.number().positive(),
total_observations: z.number().nonnegative(),
independent_sessions: z.number().nonnegative(),
virtual_observations: z.number().nonnegative().default(0),
last_updated_at: z.string().datetime(),
context_version_hash: z.string().optional()
});
export const KnowledgePayloadSchema = z.discriminatedUnion("payload_kind", [
z.object({ payload_kind: z.literal("routing_hint"), recommended_profile_id: z.string(), reason: z.string() }),
z.object({ payload_kind: z.literal("context_packaging_hint"), hint_key: z.string(), hint_value: z.string(), reason: z.string() }),
z.object({ payload_kind: z.literal("error_pattern"), pattern_key: z.string(), avoid_reason: z.string(), suggested_fix: z.string().optional() }),
z.object({ payload_kind: z.literal("prompt_fragment"), slot: z.enum(["system_prefix", "instruction_suffix", "task_note", "inspector_note"]), text: z.string(), reason: z.string() }),
z.object({ payload_kind: z.literal("topic_continuity_summary"), summary: z.string(), topic_key: z.string() }),
z.object({ payload_kind: z.literal("prompt_effectiveness"), prompt_method: z.string(), recommendation: z.string(), reason: z.string() }),
z.object({ payload_kind: z.literal("prompt_method_preference"), prompt_method: z.string(), reason: z.string() }),
z.object({ payload_kind: z.literal("configuration_preference"), model_id: z.string().optional(), topology: z.string().optional(), prompt_method: z.array(z.string()).default([]), reason: z.string() }),
z.object({ payload_kind: z.literal("workspace_default"), value: WorkspaceDefaultValueSchema, reason: z.string() })
]);
export const KnowledgeNodeSchema = z.object({
node_id: z.string().uuid(),
payload_kind: PayloadKindSchema,
profile_id: z.string(),
status: z.enum(["pending", "shadow", "validated", "retired", "suppressed"]),
applies_when: ContextMatcherSchema,
payload: KnowledgePayloadSchema,
confidence: BetaConfidenceSchema,
evidence_grade: EvidenceGradeSchema,
evidence_refs: z.array(z.string()).default([]),
display_summary: z.string(),
source_checkpoint_ids: z.array(z.string()).default([]),
user_authored: z.boolean().default(false),
user_pinned: z.boolean().default(false),
family_key: z.string().optional(),
retired_reason: z.string().optional(),
created_at: z.string().datetime(),
updated_at: z.string().datetime()
});
```
**Design rule**
User-authored or user-edited knowledge nodes remain heuristic. They gain retention and precedence protections through `user_authored` / `user_pinned`, not fake statistical confidence and not authority-tier mutation.
### §4.4 Storage Layout, Query, and Retention
**Storage layout**
```text
ELNOR_MEMORY/system/cil/
config.json
context_hashes.json
signal_spool.jsonl
signal_spool_cursor.json
signals.jsonl
signals_index.json
quarantined_signals.jsonl
knowledge_nodes.jsonl
knowledge_nodes.snapshot.jsonl
review_outcomes.jsonl
profiles/
checkpoints/
checkpoints_archive/
nightly/
archives/
spec_reference_map.json
```
**Retention**
- `signal_spool.jsonl` is transient append-only intake and may be rotated after successful flush.
- `signals.jsonl` is the canonical flushed signal store and must be retained for 180 days by default, then archived.
- `checkpoints/` retain any checkpoint referenced by an active or shadow node; unreferenced checkpoints may archive after 180 days.
- `knowledge_nodes.jsonl` is the canonical node store and must be written atomically.
**Code**
```ts
export async function queryMemory(input: {
contextFacts: ContextFacts;
profileIds: string[];
minConfidence?: number;
}): Promise<{
authority_memory: AuthorityMemoryRecord[];
heuristic_nodes: KnowledgeNode[];
}> {
const authority_memory = (await loadAuthorityMemory(input.contextFacts))
.filter(record => authorityRecordAppliesToContext(record, input.contextFacts));
const allNodes = await readKnowledgeNodes();
const minConfidence = input.minConfidence ?? 0.55;
const heuristic_nodes = allNodes
.filter(node => input.profileIds.includes(node.profile_id))
.filter(node => node.status === "validated" || node.status === "shadow")
.filter(node => evaluateContextMatcher(input.contextFacts, node.applies_when).matches)
.filter(node => betaMean(node.confidence) >= minConfidence || node.user_authored)
.sort((a, b) => {
const conf = betaMean(b.confidence) - betaMean(a.confidence);
if (conf !== 0) return conf;
return evaluateContextMatcher(input.contextFacts, b.applies_when).specificity -
evaluateContextMatcher(input.contextFacts, a.applies_when).specificity;
});
return { authority_memory, heuristic_nodes };
}
```
### §4.4A display_summary Generation
**Code**
```ts
export function generateDisplaySummary(node: KnowledgeNode): string {
const conf = confidenceTier(node.confidence);
switch (node.payload.payload_kind) {
case "routing_hint":
return `Use profile ${node.payload.recommended_profile_id} for this pattern (${conf} confidence).`;
case "configuration_preference":
return `Configuration preference learned: ${node.payload.reason} (${conf} confidence).`;
case "workspace_default":
return node.payload.value.default_type === "document_set"
? `Common document set for this workspace (${node.payload.value.doc_ids.length} docs, ${conf} confidence).`
: `Workspace default learned (${conf} confidence).`;
default:
return `${node.payload.payload_kind.replaceAll("_", " ")} (${conf} confidence).`;
}
}
```
### §4.5 Lifecycle, Decay, and Compaction
**Architecture**
Node lifecycle states are:
- `pending` — candidate generated but not yet evaluated
- `shadow` — evaluated but not yet active for user-facing application
- `validated` — eligible for live recommendation or context use
- `suppressed` — hidden due to family or user suppression
- `retired` — inactive and no longer used
**Code**
```ts
export function isNodePromotable(node: KnowledgeNode, stats: NodeSignalStats): boolean {
if (node.user_authored) return true;
if (stats.authority_conflict) return false;
return (
stats.signal_count >= 3 &&
stats.positive_ratio >= 0.60 &&
betaMean(node.confidence) >= 0.60 &&
stats.independent_sessions >= 2
);
}
```
**Compaction**
- pending nodes older than 30 days with no new evidence may retire as `retired_reason = "stale_pending"`
- retired nodes may move to `archives/knowledge_nodes_YYYYMM.jsonl`
- compaction may never merge or mutate live nodes in place; only archive copies may be condensed
### §4.6 Storage Durability and Atomic Writes
**Code**
```ts
export async function writeAtomicJsonl(path: string, rows: string[]): Promise<void> {
const tmp = `${path}.tmp`;
await fs.promises.writeFile(tmp, rows.join("
") + "
", "utf8");
await fs.promises.rename(tmp, path);
}
```
**Wiring**
- Advisory reads must never read mid-write mutable files.
- Nightly jobs produce `.snapshot` copies before mutating `knowledge_nodes.jsonl` or other current-view artifacts.
**Acceptance**
During nightly Phase 5, trigger a `Why?` advisory read. The advisor must read the pre-Phase-5 snapshot and return consistent node/evidence data even while the nightly writer updates live files.
---
## Part V — ContextPlanner
### §5.1 Purpose and Statelessness
**Architecture**
ContextPlanner produces bounded context plans from authority memory, heuristic nodes, document hints, and task context. It is stateless across calls; all durable state belongs to checkpoints and the learning pipeline.
### §5.2 Two-Pass Planning
**Architecture**
Planning stages:
1. gather authority memory and candidate heuristic nodes
2. build draft budget allocations and bucket directives
3. optionally call `MaterializationPreview` for document/token estimates
4. seal the plan, compute dropped items, and prepare `DispatchCheckpoint`
### §5.2A MaterializationPreviewResultSchema
```ts
export const MaterializationPreviewResultSchema = z.object({
doc_id: z.string(),
estimated_tokens: z.number().nonnegative(),
recommended_mode: z.enum(["full", "summary", "skeleton", "drop"]),
summary_lineage: z.string().optional(),
cache_eligible: z.boolean().default(false)
});
```
### §5.3 ContextPlanSchema
**Schemas**
```ts
export const BUDGET_ALLOCATION_KEYS = [
"authority_memory",
"heuristic_memory",
"knowledge_nodes",
"document_content",
"conversation_history",
"task_context",
"scratchpad",
"overhead"
] as const;
export const BudgetAllocationKeySchema = z.enum(BUDGET_ALLOCATION_KEYS);
export const DocumentPriorityHintSchema = z.object({
doc_id: z.string(),
priority: z.enum(["critical", "useful", "background"]),
reason: z.string(),
source_node_ids: z.array(z.string()).default([])
});
export const ContextPlanSchema = z.object({
plan_id: z.string().uuid(),
profile_ids: z.array(z.string()).min(1),
bucket_directives: z.array(z.object({
bucket_id: z.string(),
include: z.boolean().default(true),
mode: z.enum(["full", "summary", "skeleton", "drop"]),
hierarchical_level: z.number().int().min(0).default(0)
})).default([]),
budget_allocations: z.record(BudgetAllocationKeySchema, z.number().nonnegative()),
document_priority_hints: z.array(DocumentPriorityHintSchema).default([]),
transient_instructions: z.array(TransientInstructionSchema).default([]),
candidate_node_ids: z.array(z.string()).default([]),
injected_node_ids: z.array(z.string()).default([]),
dropped_node_ids: z.array(z.string()).default([]),
dropped_doc_ids: z.array(z.string()).default([]),
remaining_budget_after_authority: z.number().int().nonnegative(),
prompt_fragments: z.array(z.object({ slot: z.string(), text: z.string(), source_node_id: z.string() })).default([]),
created_at: z.string().datetime()
});
```
### §5.3A Authority Token Estimation Rule
Authority-memory tokens are estimated during planning before heuristic selection. `remaining_budget_after_authority` is computed as total model budget minus estimated authority-memory tokens minus reserved overhead. Current-dispatch `transient_instructions` count against the same authority-like budget for the active dispatch, but they are never written back as durable authority memory. CIL may not pretend that authority memory is free.
### §5.4 Attention-Aware Ordering and Cache Telemetry
**Wiring**
DOC11 must expose prompt-prefix cache telemetry. When present, ContextPlanner can order stable prefix content before volatile content. When absent, CIL must degrade cleanly and record `cache_optimized = false`.
### §5.5 Scratchpad and Active Review Target Protection
**Architecture**
Scratchpad remains runtime or room owned. CIL may request scratchpad budget but not durable ownership.
In review rooms, the active review target and current-round submissions are treated as effective `critical` document hints. DOC12 owns pin transitions; CIL only consumes the pin state.
### §5.6 CIL Overhead Watchdog
**Architecture**
ContextPlanner must refuse to add more than the configured CIL overhead budget. If it exceeds budget, the plan seals with fewer heuristic nodes and emits `CIL_OVERHEAD_CLAMPED`.
### §5.7 Knowledge Node Injection Formatting
**Architecture**
Node injection must be plain language, bounded, and source-labeled. Prompt fragments have explicit slots:
- `system_prefix`
- `instruction_suffix`
- `task_note`
- `inspector_note`
`prompt_fragment` nodes must declare one of those slots. `transient_instructions` render ahead of heuristic nodes for the current dispatch only and must be labeled as current-request constraints rather than learned memory.
**Code**
```ts
export function renderInjectedKnowledge(node: KnowledgeNode): string {
switch (node.payload.payload_kind) {
case "routing_hint":
return `Recommendation: use profile ${node.payload.recommended_profile_id}. Reason: ${node.payload.reason}`;
case "configuration_preference":
return `Learned configuration preference: ${node.payload.reason}`;
case "prompt_fragment":
return node.payload.text;
default:
return node.display_summary;
}
}
```
### §5.8 ConfigurationTupleRef and DispatchCheckpoint
**Schemas**
```ts
export const ConfigurationTupleRefSchema = z.object({
model_id: z.string(),
topology: z.string().optional(),
prompt_method: z.array(z.string()).default([]),
workspace_id: z.string(),
matter_id: z.string().optional(),
workflow_stage: z.string().optional(),
review_target_kind: ContextFactsSchema.shape.review_target_kind.optional(),
task_type: z.string(),
surface_kind: ContextFactsSchema.shape.surface_kind
});
export const DispatchCheckpointSchema = z.object({
checkpoint_id: z.string().uuid(),
dispatch_id: z.string(),
operation_id: z.string(),
configuration_tuple: ConfigurationTupleRefSchema,
plan_id: z.string().uuid(),
planned_node_ids: z.array(z.string()).default([]),
effective_node_ids: z.array(z.string()).default([]),
dropped_node_ids: z.array(z.string()).default([]),
applied_authority_ids: z.array(z.string()).default([]),
skipped_authority_ids: z.array(z.string()).default([]),
applied_transient_instruction_ids: z.array(z.string()).default([]),
document_priority_hints: z.array(DocumentPriorityHintSchema).default([]),
runtime_truth_ref: z.object({
runtime_session_id: z.string().optional(),
effective_model_id: z.string().optional(),
effective_topology: z.string().optional()
}),
cache_telemetry: z.object({
cache_optimized: z.boolean().default(false),
prefix_hash: z.string().optional(),
hit: z.boolean().optional()
}).default({ cache_optimized: false }),
cost_usd: z.number().nonnegative().optional(),
signal_flush_pending: z.boolean().default(false),
created_at: z.string().datetime()
});
export const ContextPlanDiffRecordSchema = z.object({
diff_id: z.string().uuid(),
dispatch_id: z.string(),
added_node_ids: z.array(z.string()).default([]),
dropped_node_ids: z.array(z.string()).default([]),
added_doc_ids: z.array(z.string()).default([]),
dropped_doc_ids: z.array(z.string()).default([]),
created_at: z.string().datetime()
});
```
**Code**
```ts
export function buildContextPlanDiff(actual: ContextPlan, planned: ContextPlan): ContextPlanDiffRecord {
return {
diff_id: crypto.randomUUID(),
dispatch_id: "unknown_dispatch",
added_node_ids: actual.injected_node_ids.filter(id => !planned.injected_node_ids.includes(id)),
dropped_node_ids: planned.injected_node_ids.filter(id => !actual.injected_node_ids.includes(id)),
added_doc_ids: actual.document_priority_hints.map(x => x.doc_id).filter(id => !planned.document_priority_hints.map(x => x.doc_id).includes(id)),
dropped_doc_ids: planned.document_priority_hints.map(x => x.doc_id).filter(id => !actual.document_priority_hints.map(x => x.doc_id).includes(id)),
created_at: new Date().toISOString()
};
}
export function buildConfigurationTuple(input: {
contextFacts: ContextFacts;
runtimeTruth?: { effective_model_id?: string; effective_topology?: string };
}): ConfigurationTupleRef {
return {
model_id: input.runtimeTruth?.effective_model_id ?? input.contextFacts.model_id ?? "unknown_model",
topology: input.runtimeTruth?.effective_topology ?? input.contextFacts.topology,
prompt_method: input.contextFacts.prompt_method ?? [],
workspace_id: input.contextFacts.workspace_id,
matter_id: input.contextFacts.matter_id,
workflow_stage: input.contextFacts.workflow_stage,
review_target_kind: input.contextFacts.review_target_kind,
task_type: input.contextFacts.task_type,
surface_kind: input.contextFacts.surface_kind
};
}
```
**Wiring**
- `DispatchCheckpoint` must be written synchronously before signal append.
- Q may inspect checkpoints but may not mutate them.
- Any checkpoint referenced by an active node must remain durable until all references are gone.
**Acceptance**
Given a dispatch with `task_type` absent from the raw UI payload, `buildContextFacts()` and `buildConfigurationTuple()` still produce a valid checkpoint through the mapping table, and no coder-invented UI fallback is needed.
---
## Part VI — LearningPipeline
### §6.1 Purpose
**Architecture**
LearningPipeline converts durable dispatch evidence into durable node governance. It ingests signals, bridges external sources, updates distributions, computes evidence grades, and produces inspectable outcomes.
### §6.2 The Three Learning Loops
1. **Operational loop** — dispatch outcomes, suggestion feedback, configuration dissatisfaction
2. **Review-quality loop** — findings, review outcomes, room health, prompt lineage
3. **Behavioral loop** — friction bridge, document usage, passive signals, context utilization
### §6.3 Signal Taxonomy and Durable Emission
**Schemas**
```ts
export const LearningSignalSchema = z.object({
signal_id: z.string().uuid(),
schema_version: z.literal(2),
batch_id: z.string().uuid().optional(),
dispatch_id: z.string().optional(),
original_dispatch_id: z.string().optional(),
profile_id: z.string().optional(),
signal_type: z.string(),
source_type: z.enum(["manual_judgment", "system_event", "review_artifact", "bridge", "advisor", "passive_telemetry"]),
origin_system: z.enum(["doc6", "doc8", "doc10", "doc12", "doc14", "doc15", "core", "q"]),
source_id: z.string(),
timestamp: z.string().datetime(),
payload: z.record(z.string(), z.any()).default({}),
upstream_ref: z.object({
node_id: z.string().optional(),
operation_id: z.string().optional(),
checkpoint_id: z.string().optional(),
friction_event_id: z.string().optional(),
finding_id: z.string().optional(),
doc_id: z.string().optional()
}).optional()
});
export const SuggestionFeedbackPayloadSchema = z.object({
resolution_id: z.string().uuid(),
applied_element_ids: z.array(z.string()).default([]),
edited_element_ids: z.array(z.string()).default([]),
rejected_element_ids: z.array(z.string()).default([]),
notes: z.string().optional()
});
export const SuggestionEditDeltaSchema = z.object({
resolution_id: z.string().uuid(),
changed: z.array(z.object({
element_id: z.string().uuid(),
field_path: z.string(),
old_value: z.unknown(),
new_value: z.unknown(),
action: z.enum(["accepted", "edited", "rejected"])
})),
created_at: z.string().datetime()
});
export const SignalBatchSchema = z.object({
batch_id: z.string().uuid(),
original_dispatch_id: z.string().optional(),
created_at: z.string().datetime(),
signals: z.array(LearningSignalSchema).min(1)
});
```
**Code**
```ts
const NON_DEFAULTABLE_FIELDS = new Set(["signal_type", "source_type", "source_id", "timestamp"]);
export function validateIncomingSignal(raw: unknown): { status: "ok" | "warn" | "quarantined"; parsed?: LearningSignal; warning?: string } {
const parsed = LearningSignalSchema.safeParse(raw);
if (parsed.success) return { status: "ok", parsed: parsed.data };
const missingSemanticField = parsed.error.issues.some(i => NON_DEFAULTABLE_FIELDS.has(String(i.path[0])));
if (missingSemanticField) {
return { status: "quarantined", warning: "missing required semantic field" };
}
return { status: "warn", warning: "optional enrichment fields missing" };
}
```
**Durability rules**
- `SignalBatch` append to `signal_spool.jsonl` is synchronous before response return for dispatch-completion events.
- Post-dispatch user actions must carry the **original** `dispatch_id` so later attribution points back to the producing dispatch.
- Flusher dedupes by `batch_id` when copying into `signals.jsonl`.
### §6.3A DOC8 Signal Bridge
**Architecture**
The DOC8 bridge runs nightly after DOC8 rollup and before Phase 5 node governance. It is not real-time.
**Code**
```ts
const DOC8_TO_CIL: Record<string, { signal_type: string; direction: "positive" | "negative" | "neutral"; strength: number } | null> = {
tool_error: { signal_type: "friction_bridged:tool_error", direction: "negative", strength: 0.5 },
schema_violation: { signal_type: "friction_bridged:schema_error", direction: "negative", strength: 0.3 },
timeout: { signal_type: "friction_bridged:timeout", direction: "negative", strength: 0.5 },
rate_limit: { signal_type: "friction_bridged:rate_limit", direction: "neutral", strength: 0.0 },
context_overflow: { signal_type: "friction_bridged:context_overflow", direction: "negative", strength: 0.75 },
model_refusal: { signal_type: "friction_bridged:model_refusal", direction: "negative", strength: 1.0 },
quality_regression: { signal_type: "friction_bridged:quality_regression", direction: "negative", strength: 1.5 },
prevented_friction: { signal_type: "friction_bridged:prevented", direction: "positive", strength: 0.5 },
user_reported: { signal_type: "friction_bridged:user_reported", direction: "negative", strength: 1.0 },
stale_context: { signal_type: "friction_bridged:stale_context", direction: "negative", strength: 0.5 },
budget_exhausted: null,
budget_warning: null,
burst_suppressed: null,
rollup_error: null,
merge_conflict: null
};
const DOC8_ALIASES: Record<string, string> = {
schema_error: "schema_violation",
quality_drop: "quality_regression"
};
export function mapDOC8FrictionToCILSignal(frictionType: string) {
const canonical = DOC8_ALIASES[frictionType] ?? frictionType;
const mapping = DOC8_TO_CIL[canonical];
if (mapping === undefined) return { status: "quarantine", canonical } as const;
if (mapping === null) return { status: "drop", canonical } as const;
return { status: "ok", canonical, mapping } as const;
}
```
### §6.4 Passive Signal Collection
**Signals**
- `configuration_dissatisfaction`
- `finding_cited_in_decision`
- `finding_exported`
- `finding_became_proposal`
- `finding_survived_dedup`
- `decision_without_basis`
- `context_utilization:never_referenced`
- `room_kept_open`
- `document_accessed`
- `room_document_bound` (future/domain-pack)
- `transfer_packet_imported` (future/domain-pack)
- `approval_checkpoint_resolved` (future/domain-pack)
### §6.5–§6.7 Signal Families
**Architecture**
Tiering continues from R5:
- Tier 1 — direct judgments and card feedback
- Tier 2 — review outcome aggregates
- Tier 3 — novelty / starred reviewer-quality judgments
- Tier 4 — downstream use and dissatisfaction
- Tier 5 — passive telemetry
### §6.8 Finding Quality Metric
**Wiring**
DOC14 must emit `novelty`, `starred`, `finding_exported`, and related finding actions. DOC15 consumes them during nightly aggregation to optimize for surprise and usefulness.
### §6.9 ReviewOutcomeSchema
**Schemas**
```ts
export const ReviewGoalTypeSchema = z.enum([
"stress_test",
"spec_review",
"factual_challenge",
"risk_scan",
"litigation_review",
"code_review",
"other"
]);
export const ReviewCloseReasonSchema = z.enum([
"satisfied_exceeded",
"satisfied_goal_met",
"wrong_approach",
"wrong_scope",
"abandoned_explicit",
"abandoned_timeout",
"partial_useful",
"no_issues_found",
"handoff",
"superseded",
"blocked_external",
"other"
]);
export const ReviewOutcomeSchema = z.object({
review_outcome_id: z.string().uuid(),
dispatch_id: z.string().optional(),
panel_id: z.string().optional(),
room_id: z.string().optional(),
goal_type: ReviewGoalTypeSchema,
close_reason: ReviewCloseReasonSchema,
user_goal_met: z.enum(["fully", "partially", "not_at_all"]).optional(),
findings_starred: z.number().int().nonnegative().default(0),
findings_by_novelty: z.object({ known_issue: z.number().default(0), new_angle: z.number().default(0), surprising: z.number().default(0) }).optional(),
notes: z.string().optional(),
created_at: z.string().datetime()
});
```
### §6.10 Prompt Lineage Tracking
**Architecture**
Prompt lineage must track enough information to say what configuration and prompt method was actually used. CIL does not own room-turn capture; it owns the consumer model.
### §6.11 Prompt Characteristics Classification
Prompt characteristics remain multi-label. Classification may be manual or future automated. CIL stores derived learning, not raw runtime truth.
### §6.12 Context Utilization Detection
**Architecture**
Context utilization is negative-only weak evidence. It may widen uncertainty or add small negative mass, but must not be used as positive evidence and must not override direct user feedback.
### §6.13 Moderator Self-Assessment
Moderator self-assessment remains disabled for weighting and may only be logged for diagnostics.
### §6.14 SignalStore Durability
**Schemas**
```ts
export const SignalStoreCursorSchema = z.object({
last_flushed_batch_id: z.string().optional(),
last_flushed_byte_offset: z.number().int().nonnegative().default(0),
updated_at: z.string().datetime()
});
export const SignalsIndexEntrySchema = z.object({
batch_id: z.string().uuid(),
original_dispatch_id: z.string().optional(),
first_signal_ts: z.string().datetime(),
last_signal_ts: z.string().datetime(),
file_offset: z.number().int().nonnegative()
});
```
**Code**
```ts
export async function recoverSignalSpool(): Promise<void> {
const lastCompleteOffset = await findLastCompleteJsonlOffset("ELNOR_MEMORY/system/cil/signal_spool.jsonl");
await truncateFile("ELNOR_MEMORY/system/cil/signal_spool.jsonl", lastCompleteOffset);
}
export async function flushSignalSpool(): Promise<void> {
const lock = await acquireFileLock("ELNOR_MEMORY/system/cil/signal_spool.lock");
try {
const batches = await readUnflushedBatches();
for (const batch of batches) {
if (await batchAlreadyFlushed(batch.batch_id)) continue;
await appendJsonl("ELNOR_MEMORY/system/cil/signals.jsonl", batch);
await updateCursor(batch.batch_id);
}
} finally {
await lock.release();
}
}
```
**Acceptance**
Append two identical batches with the same `batch_id` to the spool. After flush, `signals.jsonl` contains one canonical batch and cursor state advances once.
---
## Part VII — Signal Valuation Framework
### §7.1 Why This Exists
CIL needs a lightweight, inspectable way to combine small numbers of direct signals with broader pooled evidence while still expressing uncertainty honestly.
### §7.2 The Beta Distribution Model
**Architecture**
Each node carries a beta distribution over success-like outcomes. Signal strengths are virtual observations, not magic certainty. The beta mean is the recommendation score; the total effective observations determine how wide or narrow that confidence should feel.
### §7.3 Signal Weights
**Architecture**
R5 weights are preserved unless noted otherwise. `suggestion_dismissed` remains `-1.0`.
### §7.4 Context-Change Decay
**Schemas**
```ts
export const ContextHashesSchema = z.object({
model_registry_hash: z.string().optional(),
standing_orders_hash: z.string().optional(),
profile_definitions_hash: z.string().optional(),
updated_at: z.string().datetime()
});
```
**Code**
```ts
export function applyContextChangeDecay(conf: BetaConfidence, severity: "minor" | "moderate" | "major"): BetaConfidence {
const factor = severity === "minor" ? 0.90 : severity === "moderate" ? 0.875 : 0.85;
return {
...conf,
alpha: conf.alpha * factor,
beta_param: conf.beta_param * factor,
total_observations: conf.total_observations * factor,
virtual_observations: conf.virtual_observations * factor,
last_updated_at: new Date().toISOString()
};
}
export function buildContextHashesAndDetectChanges(current: ContextHashes, previous: ContextHashes | null): ContextChangeEvent[] {
if (!previous) return [];
const events: ContextChangeEvent[] = [];
if (current.model_registry_hash !== previous.model_registry_hash) events.push({ change_type: "model_registry", severity: "major" });
if (current.standing_orders_hash !== previous.standing_orders_hash) events.push({ change_type: "standing_orders", severity: "major" });
if (current.profile_definitions_hash !== previous.profile_definitions_hash) events.push({ change_type: "profile_definitions", severity: "moderate" });
return debounceContextChanges(events, { windowMs: 60 * 60 * 1000 });
}
```
**Rule**
Context-change events are debounced per `change_type` with a 1-hour window. Multiple changes of the same type inside the window are consolidated into one event at the highest severity.
### §7.5 Hierarchical Category Pooling
**Code**
```ts
export function computePoolWeight(ownSessions: number): number {
return Math.max(0.1, 0.3 / (1 + ownSessions / 10));
}
```
**Architecture**
Pooling hierarchy is:
1. exact tuple
2. same workspace + target kind + task type
3. same target kind + task type
4. same task family
The effective pooled contribution must be clamped and recorded in `EvidenceGrade.pooled_sessions_effective`.
### §7.6 Rolling Window Divergence Detection
**Architecture**
Divergence detection may compare recent and older windows to highlight unstable nodes. If the implementation uses interval overlap or other math beyond the beta helpers below, the algorithm must be fully defined in code here, not outsourced to “implementation detail.”
### §7.7 Nightly Beta Update Process
**Schemas**
```ts
export const NightlyPhase5CheckpointSchema = z.object({
run_id: z.string().uuid(),
processed_node_ids: z.array(z.string()).default([]),
last_processed_batch_id: z.string().optional(),
completed_steps: z.array(z.string()).default([]),
updated_at: z.string().datetime()
});
```
**Code**
```ts
export function deriveMatcherFromTupleSet(tuples: ConfigurationTupleRef[]): ContextMatcher {
const workspaceValues = [...new Set(tuples.map(t => t.workspace_id).filter(Boolean))];
const reviewTargetValues = [...new Set(tuples.map(t => t.review_target_kind).filter(Boolean))];
return {
required: [
workspaceValues.length <= 1
? { field: "workspace_id", equals: workspaceValues[0] }
: { field: "workspace_id", in: workspaceValues as string[] },
reviewTargetValues.length <= 1
? { field: "review_target_kind", equals: reviewTargetValues[0] }
: { field: "review_target_kind", in: reviewTargetValues as string[] }
].filter(Boolean) as ContextMatcherRule[],
preferred: [],
excluded: [],
specificity_score: 0
};
}
export function attributeSignalsToNodes(signals: LearningSignal[], checkpoints: Map<string, DispatchCheckpoint>, nodes: KnowledgeNode[]): Map<string, LearningSignal[]> {
const byNode = new Map<string, LearningSignal[]>();
const tupleGroups = new Map<string, LearningSignal[]>();
for (const signal of signals) {
const cp = signal.original_dispatch_id ? checkpoints.get(signal.original_dispatch_id) : undefined;
if (!cp) continue;
const key = JSON.stringify(cp.configuration_tuple);
tupleGroups.set(key, [...(tupleGroups.get(key) ?? []), signal]);
}
for (const node of nodes) {
const matcher = node.applies_when;
for (const [key, groupedSignals] of tupleGroups.entries()) {
const tuple = JSON.parse(key);
if (evaluateContextMatcher(tuple as any, matcher).matches) {
byNode.set(node.node_id, [...(byNode.get(node.node_id) ?? []), ...groupedSignals]);
}
}
}
return byNode;
}
export async function runNightlyPhase5(runId: string): Promise<void> {
const signals = await readCanonicalSignalsSinceCursor();
const checkpoints = await readRelevantCheckpoints();
const nodes = await readKnowledgeNodes();
const groupedByTuple = attributeSignalsToNodes(signals, checkpoints, nodes);
for (const node of nodes) {
const nodeSignals = groupedByTuple.get(node.node_id) ?? [];
const updated = applySignalsToNode(node, nodeSignals);
updated.evidence_grade = updateEvidenceGrade(updated.evidence_grade, nodeSignals);
await persistPhase5NodeProgress(runId, node.node_id);
}
}
```
**Evidence-grade update logic**
- `direct_sessions` = count of unique `original_dispatch_id` values from exact-tuple signals
- `pooled_sessions_effective` = sum of pooled matches × pool weight
- `replay_runs` / `replay_improved_runs` update only from replay lane artifacts
- `canary_runs` / `canary_success_runs` update only from canary lane artifacts
### §7.8 Per-Module Application Map
R5 mapping remains, with context utilization kept weak and negative only.
### §7.9 User Telemetry — Confidence Display
**Code**
```ts
export function betaMean(conf: BetaConfidence): number {
return conf.alpha / (conf.alpha + conf.beta_param);
}
export function confidenceTier(conf: BetaConfidence): "very_limited" | "limited" | "moderate" | "strong" {
const sessions = conf.independent_sessions;
if (sessions < 2) return "very_limited";
if (sessions < 4) return "limited";
if (sessions < 8) return "moderate";
return "strong";
}
```
### §7.10 Worked Example
A node with `alpha=15`, `beta_param=2` has mean `15 / 17 = 0.88235`. A minor → major → minor decay multiplies both parameters by `0.90 × 0.85 × 0.90 = 0.6885`, preserving mean but widening uncertainty.
### §7.11 Normative Implementation Code
```ts
export function betaVariance(conf: BetaConfidence): number {
const sum = conf.alpha + conf.beta_param;
return (conf.alpha * conf.beta_param) / (sum * sum * (sum + 1));
}
export function betaCredibleIntervalApprox(conf: BetaConfidence, zScore = 1.96): { low: number; high: number } {
const mean = betaMean(conf);
const sd = Math.sqrt(betaVariance(conf));
return {
low: Math.max(0, mean - zScore * sd),
high: Math.min(1, mean + zScore * sd)
};
}
export function updateBetaDistribution(conf: BetaConfidence, direction: "positive" | "negative" | "neutral", strength: number): BetaConfidence {
if (direction === "neutral" || strength === 0) return conf;
return {
...conf,
alpha: direction === "positive" ? conf.alpha + strength : conf.alpha,
beta_param: direction === "negative" ? conf.beta_param + Math.abs(strength) : conf.beta_param,
total_observations: conf.total_observations + Math.abs(strength),
virtual_observations: conf.virtual_observations + Math.abs(strength),
last_updated_at: new Date().toISOString()
};
}
```
**Acceptance**
No persisted schema may contain a `mean` field. Unit tests must assert that every threshold comparison calls `betaMean(confidence)` dynamically.
---
## Part VIII — Dispatch Execution Loop
### §8.1 Orchestrator Sequence
**Architecture**
The dispatch path is:
1. receive `OperationIntent`
2. build `ContextFacts`
3. resolve profiles
4. query authority memory and heuristic nodes
5. build and seal `ContextPlan`
6. return `ResolvedOperation` if in suggest/prepare mode, or continue to execute mode
7. assemble context via `assembleContextCard(contextPlan?)`
8. execute through DOC11/Gateway/OpenClaw
9. write `DispatchCheckpoint` synchronously
10. append `SignalBatch` synchronously
11. return execution result
### §8.2 StatePatchIntent Pattern
StatePatchIntent remains a non-durable mutation request pattern inside EC. It may not bypass checkpointing or signal emission.
### §8.3 CapabilityRunner
CapabilityRunner executes profile capabilities selected during planning. It must never mutate protected native context and must declare all runtime-affecting deltas in the checkpoint.
### §8.4 Trusted Path for Execute Mode
**Architecture**
`mode = execute` is allowed only when one of these is true:
- the operation comes from a trusted automation with a previously accepted configuration
- the user explicitly confirmed execution on the current configuration
- the operation family is marked `execute_safe_by_default` in the profile definition and uses no context-shaping nodes still in shadow
Otherwise the compiler must downgrade to `prepare` or `suggest` and return a confirmation-required recommendation.
**Acceptance**
A shadow-only `context_packaging_hint` must never be applied in execute mode without either passing the stricter shadow gate or receiving explicit user confirmation.
---
## Part IX — Nightly Pipeline
### §9.1 Phase Ordering
**Architecture**
Nightly phases are:
1. existing deterministic EC memory/freshness phases
2. existing panel/system maintenance phases
3. existing DOC8 friction rollup
4. CIL profile maintenance
4.5 DOC8→CIL bridge
5. CIL knowledge governance and beta updates
6. replay/eval lane
7. compile check / integrity audit
### §9.2 Bootstrap Knowledge Nodes
Bootstrap nodes may seed only low-risk hints. Any bootstrap node that influences context packaging or prompt method must begin in `shadow`.
### §9.3 CIL File Integrity and Snapshots
**Code**
```ts
export async function createNightlySnapshots(): Promise<void> {
await copyFile("ELNOR_MEMORY/system/cil/knowledge_nodes.jsonl", "ELNOR_MEMORY/system/cil/knowledge_nodes.snapshot.jsonl");
await copyFile("ELNOR_MEMORY/system/cil/signals.jsonl", "ELNOR_MEMORY/system/cil/signals.snapshot.jsonl");
}
```
**Wiring**
- Advisors read snapshot files during nightly mutations.
- Snapshots may be removed at the end of Phase 5 if no advisory session still references them.
### §9.4 Recovery Semantics
If Phase 5 crashes, restart from `NightlyPhase5CheckpointSchema` and skip already processed node IDs. No assumption of JSONL order is allowed.
**Acceptance**
Crash Phase 5 halfway through 200 nodes. On restart, previously processed nodes are not reprocessed and the final node store matches a clean uninterrupted run.
---
## Part X — Reserved (Migration)
Migration rules remain reserved. Any future schema migration must preserve append-only artifacts and record migration versions in `config.json`.
---
## Part XI — Training Engine and Prompt Laboratory
### §11.1 Purpose
The Training Engine remains fully specced. Implementation may phase it, but the architecture is retained because replay, canary, prompt-variant, and optimizer work are real downstream consumers of CIL learning outputs.
### §11.2 Training Target Types
Training targets include:
- topology candidates
- prompt method candidates
- configuration bundles
- prompt variants for structured review workflows
### §11.3 Replay as Eval Lane
Replay lane runs separately from live nightly governance and may update `EvidenceGrade` from replay artifacts.
### §11.4–§11.5 Optimizer Candidates and Canary Rules
Canary and replay ownership remain coordinated with DOC8. CIL may emit candidates; DOC8 owns experiments.
### §11.6 Prompt Method Laboratory
Prompt methods remain classified and comparable. CIL may recommend methods observationally before any canary support exists, but the UI must say so.
### §11.7 Prompt Method Taxonomy
Taxonomy remains extensible strings with canonical aliases recorded in `config.json`.
**Acceptance**
A prompt method recommendation with only observational support must render as observational in the UI and in advisory explanations, not as “proven.”
---
## Part XII — Operation Initiation Model
### §12.1 The Problem
Buttons, setup forms, automations, and later chat should not each invent their own recommendation logic. They must call one compiler.
### §12.2 Front Doors and Ownership
There is one compiler front door for operations and one generic advisor front door for explanatory queries:
- `POST /api/cil/operation` — operation compilation and suggestion flow (owned by EC/CIL)
- `POST /api/advisors/dispatch` — explanatory/advisory queries, including CIL deep links (owned by DOC10 dispatcher, consumed by CIL)
### §12.3 OperationIntentSchema
```ts
export const OperationIntentSchema = z.object({
intent_id: z.string().uuid(),
source_surface: z.enum(["panel_setup", "task_setup", "room_setup", "chat", "automation", "self_heal"]),
operation_family: z.string(),
mode: z.enum(["suggest", "prepare", "execute", "shadow"]),
workspace_id: z.string(),
panel_id: z.string().optional(),
task_id: z.string().optional(),
thread_id: z.string().optional(),
room_id: z.string().optional(),
matter_id: z.string().optional(),
linked_room_group_id: z.string().optional(),
workflow_stage: z.string().optional(),
logical_role_key: z.string().optional(),
review_target_kind: ContextFactsSchema.shape.review_target_kind.optional(),
artifact_refs: z.array(ArtifactRefSchema).default([]),
explicit_profile_id: z.string().optional(),
desired_model_id: z.string().optional(),
desired_topology: z.string().optional(),
desired_prompt_method: z.array(z.string()).default([]),
transient_instructions: z.array(TransientInstructionSchema).default([]),
runtime_type: z.enum(["local", "cloud", "mixed"]).optional(),
tags: z.array(z.string()).default([]),
user_initiated: z.boolean().default(true)
});
```
### §12.4 ResolvedSuggestionElementSchema and ResolvedOperationSchema
```ts
export const ResolvedSuggestionElementSchema = z.object({
element_id: z.string().uuid(),
element_kind: z.enum(["model", "method", "topology", "intensity", "context_hint", "document_set"]),
field_path: z.string(),
proposed_value: z.unknown(),
confidence_mean: z.number().min(0).max(1),
confidence_tier: z.enum(["very_limited", "limited", "moderate", "strong"]),
evidence_grade: EvidenceGradeSchema,
supporting_node_ids: z.array(z.string()).default([]),
weak: z.boolean().default(false),
editable: z.boolean().default(true)
});
export const ResolvedOperationSchema = z.object({
resolution_id: z.string().uuid(),
intent_ref: z.string().uuid(),
resolved_profile_ids: z.array(z.string()).min(1),
suggestion_elements: z.array(ResolvedSuggestionElementSchema).default([]),
reasons: z.array(z.string()).default([]),
zero_node_reason: z.string().optional(),
plan_preview: ContextPlanSchema.optional(),
created_at: z.string().datetime()
});
```
### §12.5 Operation Compiler Flow
**Code**
```ts
export async function compileOperation(intent: OperationIntent): Promise<ResolvedOperation> {
const contextFacts = await buildContextFacts(intent);
const profiles = await resolveProfiles(contextFacts);
const memory = await queryMemory({ contextFacts, profileIds: profiles.resolved });
const plan = await buildContextPlan({ contextFacts, profileIds: profiles.resolved, memory, transientInstructions: intent.transient_instructions ?? [] });
return buildResolvedOperationFromNodes({ intent, contextFacts, profiles, memory, plan });
}
export function buildResolvedOperationFromNodes(input: {
intent: OperationIntent;
contextFacts: ContextFacts;
profiles: { resolved: string[]; fallback_used: boolean };
memory: { authority_memory: AuthorityMemoryRecord[]; heuristic_nodes: KnowledgeNode[] };
plan: ContextPlan;
}): ResolvedOperation {
const suggestion_elements = input.memory.heuristic_nodes.slice(0, 5).map(node => ({
element_id: crypto.randomUUID(),
element_kind: mapPayloadKindToElementKind(node.payload_kind),
field_path: mapPayloadKindToFieldPath(node.payload_kind),
proposed_value: extractProposedValue(node.payload),
confidence_mean: betaMean(node.confidence),
confidence_tier: confidenceTier(node.confidence),
evidence_grade: node.evidence_grade,
supporting_node_ids: [node.node_id],
weak: confidenceTier(node.confidence) === "very_limited",
editable: true
}));
return {
resolution_id: crypto.randomUUID(),
intent_ref: input.intent.intent_id,
resolved_profile_ids: input.profiles.resolved,
suggestion_elements,
reasons: input.memory.heuristic_nodes.slice(0, 3).map(n => n.display_summary),
zero_node_reason: suggestion_elements.length === 0 ? "No validated nodes matched; using profile-only defaults." : undefined,
plan_preview: input.plan,
created_at: new Date().toISOString()
};
}
```
### §12.6 The “Ask Elnor” Button Pattern
**UI**
The button appears on panel/task setup surfaces and opens the suggestion card area inline. It never mutates the live configuration until the user applies or executes.
Suggestion card actions:
- `Apply All`
- `Edit First`
- `Dismiss`
- `Why?`
When a recommendation has `confidence_tier = strong` but `evidence_grade.grade = observational` with material pooled contribution, the card should display: `Mostly based on similar tasks; a controlled test would strengthen this recommendation.`
### §12.7 Suggestion Cards as Learning Signal
**Code**
```ts
export function applySuggestionEditSignals(delta: SuggestionEditDelta, originalDispatchId: string, supportingNodeIds: string[]): SignalBatch {
const signals: LearningSignal[] = [];
for (const change of delta.changed) {
if (change.action === "accepted") {
signals.push(makeSignal("suggestion_element_applied", originalDispatchId, change.element_id, supportingNodeIds));
} else if (change.action === "edited") {
signals.push(makeSignal("suggestion_element_edited", originalDispatchId, change.element_id, supportingNodeIds, { old_value: change.old_value, new_value: change.new_value }));
} else {
signals.push(makeSignal("suggestion_element_rejected", originalDispatchId, change.element_id, supportingNodeIds));
}
}
return { batch_id: crypto.randomUUID(), original_dispatch_id: originalDispatchId, created_at: new Date().toISOString(), signals };
}
```
**Signal semantics**
- unchanged accepted element: mild positive evidence
- edited element: negative evidence on the proposed value
- rejected element: stronger negative evidence
- dismiss whole card: `suggestion_dismissed` on the card-level recommendation, not on every element individually
### §12.8 Chat as Operation Compiler (Phase 3+)
Chat may route through the same compiler once chat operations use the EC path. Until then, chat bypass remains outside CIL scope.
### §12.9 Advise Button and Advisory Deep Links
**Wiring**
CIL surfaces use `buildQueryContext()` helpers and route to `POST /api/advisors/dispatch` with `payload_kind = "cil_query_context"`.
**Endpoints**
`POST /api/cil/operation`
Request: `OperationIntentSchema`
Response: `ResolvedOperationSchema`
Errors:
- `400` invalid intent
- `409` protected-native conflict detected
- `422` unresolved required profile or tuple derivation failure
- `503` CIL unavailable or initializing
**Acceptance**
A panel setup surface calling `POST /api/cil/operation` must receive a suggestion card payload with per-element confidence and evidence grades even when only one element is strongly supported.
---
## Part XIII — Q Dashboard & User Experience
### §13.1 UI Philosophy
CIL surfaces must explain recommendations without turning the UI into an admin maze. The primary surfaces are:
- the consumer-owned suggestion card area
- a context inspector drawer
- a learning/browser surface
- a compact health surface
### §13.2 Knowledge Node Browser
**Endpoints**
- `GET /api/cil/knowledge-nodes?status=&payload_kind=&profile_id=&page=`
- `POST /api/cil/knowledge-nodes/:node_id/actions`
Action request:
```ts
export const KnowledgeNodeActionRequestSchema = z.object({
action: z.enum(["confirm", "doubt", "retire", "suppress_family", "unsuppress_family", "pin", "unpin", "edit_display_summary"]),
value: z.string().optional()
});
```
**UI**
States:
- loading: table skeleton
- empty: “No matching nodes yet” plus filter reset
- error: inline error + retry
- populated: sortable rows with `display_summary`, profile, payload kind, confidence, evidence grade, last updated
Actions:
- `Why?` opens advisory
- `Confirm`, `Doubt`, `Retire`, `Pin`
- `Suppress family` for family-key-based suppression
Mobile: row actions collapse into a bottom sheet.
### §13.3 Context Inspector
**Schemas**
```ts
export const AuthorityApplicationTraceSchema = z.object({
authority_id: z.string(),
authority_kind: AuthorityMemoryRecordSchema.shape.authority_kind,
text: z.string(),
scope: AuthorityScopeSchema,
creation_path: AuthorityCreationPathSchema,
applies_because: z.array(z.string()).default([]),
skipped_reason: z.enum(["scope_mismatch", "expired", "superseded"]).optional()
});
export const ContextInspectorResponseSchema = z.object({
dispatch_id: z.string(),
planned_node_ids: z.array(z.string()).default([]),
effective_node_ids: z.array(z.string()).default([]),
dropped_node_ids: z.array(z.string()).default([]),
document_hints: z.array(DocumentPriorityHintSchema).default([]),
cache_telemetry: DispatchCheckpointSchema.shape.cache_telemetry,
applied_authority: z.array(AuthorityApplicationTraceSchema).default([]),
skipped_authority: z.array(AuthorityApplicationTraceSchema).default([]),
transient_instructions: z.array(TransientInstructionSchema).default([])
});
```
**Endpoints**
- `GET /api/cil/context-inspector/:dispatch_id`
Response contract must include planned node IDs, effective node IDs, dropped items, document hints, cache telemetry, applied authority records with scope badges, skipped authority records with reasons, and transient current-request constraints.
**UI**
Rows must show:
- scope badge (`Operation`, `Session`, `Workspace`, `Global`, etc.)
- creation-path badge (`Explicit Save`, `Approved Proposal`, `Structured Command`, `Migrated`)
- `Why?` action for any authority or heuristic entry
**Acceptance**
Given a dispatch with a global citation rule and an operation-only “no markdown” instruction, the inspector shows both, but the operation-only instruction appears under `transient_instructions` and does not appear as durable authority on the next unrelated dispatch.
### §13.4 Signal Explorer
**Endpoints**
- `GET /api/cil/signals?from=&to=&signal_type=&page=`
Defaults:
- page size 50
- default lookback 14 days
### §13.5 Profile Dashboard
Displays profile definitions, recent activations, fallback events, and reload timestamps.
### §13.6 What Changed? Nightly Cards
**Endpoints**
- `GET /api/cil/nightly/:run_id`
Payload includes changed node IDs, before/after confidence, evidence-grade deltas, retired/promoted counts, and any warnings.
### §13.7 Inline Context Enhancement Indicator
Tiny chip shown on executions where CIL altered context or configuration.
### §13.8 Chat Coverage Indicator
Chat surfaces not yet routed through EC must show “CIL coverage: not active here yet” rather than silently implying parity.
### §13.9 Context Plan Diff for Shadow Mode
Shadow mode shows a concise diff of what would have been added/dropped if CIL were live.
### §13.10 Stale Standing Order and Wrong-Scope Detection
If standing-order conflicts, recent context-change decay, or authority scope mismatches are affecting nodes, show an inline notice linking to the browser and inspector. Wrong-scope notices must explain whether the record was skipped because of workspace, room, task, or expiry mismatch.
### §13.11 CIL Health Dashboard
**Schemas**
```ts
export const CILIntegrationWarningEventSchema = z.object({
event_id: z.string().uuid(),
event_type: z.enum([
"CIL_DOCINDEX_REGISTRATION_FAILED",
"CIL_SPOOL_TRUNCATED",
"CIL_TUPLE_DERIVATION_INCOMPLETE",
"CIL_BRIDGE_UNKNOWN_DOC8_SIGNAL",
"CIL_ADVISORY_SNAPSHOT_UNAVAILABLE",
"CIL_OVERHEAD_CLAMPED"
]),
severity: z.enum(["info", "warning", "error"]),
details: z.record(z.string(), z.any()).default({}),
created_at: z.string().datetime()
});
```
**Endpoints**
- `GET /api/cil/health`
Response contract:
```ts
export const CILEventTypeSchema = z.enum([
"CIL_INITIALIZED",
"CIL_SHUTDOWN_COMPLETE",
"CIL_DOCINDEX_REGISTRATION_FAILED",
"CIL_SPOOL_TRUNCATED",
"CIL_TUPLE_DERIVATION_INCOMPLETE",
"CIL_BRIDGE_UNKNOWN_DOC8_SIGNAL",
"CIL_ADVISORY_SNAPSHOT_UNAVAILABLE",
"CIL_OVERHEAD_CLAMPED"
]);
export const CILDocIndexRegistrationFailureSchema = z.object({
event_id: z.string().uuid(),
event_type: z.literal("CIL_DOCINDEX_REGISTRATION_FAILED"),
aliases_failed: z.array(z.string()).default([]),
created_at: z.string().datetime()
});
export const CILHealthResponseSchema = z.object({
status: z.enum(["healthy", "degraded", "error"]),
warnings: z.array(CILIntegrationWarningEventSchema).default([]),
profile_count: z.number().nonnegative(),
active_node_count: z.number().nonnegative(),
shadow_node_count: z.number().nonnegative(),
last_nightly_run_at: z.string().datetime().optional(),
docindex_status: z.enum(["ok", "degraded", "unavailable"]),
bridge_status: z.object({ doc8: z.enum(["ok", "degraded", "unavailable"]) })
});
```
**Acceptance**
If DocIndex registration fails but CIL otherwise boots, `/api/cil/health` must return `status = degraded` and include a typed warning event.
---
## Part XIV — Governance and Feature Flags
### §14.1 Single-Writer Enforcement
CIL writes durable state only through EC. Q submits commands and renders read models.
### §14.2 Wrap-First Rule
When adding new learnable behaviors, prefer composing existing schemas, signals, and checkpoint artifacts before inventing new opaque stores.
### §14.3 Feature Flags
Feature flags must exist for:
- `cil_enabled`
- `cil_shadow_mode`
- `cil_docindex_learning`
- `cil_advisory_enabled`
- `cil_replay_enabled`
- `cil_prompt_lab_enabled`
### §14.4 Shadow Gate
**Schemas**
```ts
export const ShadowGateModeSchema = z.enum(["either", "both", "confirm_and_confidence"]);
export const ShadowGateRuleSchema = z.object({
payload_kind: PayloadKindSchema,
min_dispatches: z.number().int().nonnegative(),
min_confidence: z.number().min(0).max(1),
min_independent_sessions: z.number().int().nonnegative().default(2),
gate_mode: ShadowGateModeSchema
});
export const DEFAULT_SHADOW_RULES: z.infer<typeof ShadowGateRuleSchema>[] = [
{ payload_kind: "routing_hint", min_dispatches: 3, min_confidence: 0.60, gate_mode: "either" },
{ payload_kind: "context_packaging_hint", min_dispatches: 5, min_confidence: 0.65, gate_mode: "both" },
{ payload_kind: "error_pattern", min_dispatches: 1, min_confidence: 0.50, gate_mode: "confirm_and_confidence" },
{ payload_kind: "prompt_fragment", min_dispatches: 5, min_confidence: 0.60, gate_mode: "both" },
{ payload_kind: "topic_continuity_summary", min_dispatches: 3, min_confidence: 0.55, gate_mode: "either" },
{ payload_kind: "prompt_effectiveness", min_dispatches: 10, min_confidence: 0.65, gate_mode: "both" },
{ payload_kind: "prompt_method_preference", min_dispatches: 5, min_confidence: 0.60, gate_mode: "both" },
{ payload_kind: "configuration_preference", min_dispatches: 5, min_confidence: 0.60, gate_mode: "both" },
{ payload_kind: "workspace_default", min_dispatches: 3, min_confidence: 0.55, gate_mode: "either" }
];
```
**Code**
```ts
export function evaluateShadowGate(rule: z.infer<typeof ShadowGateRuleSchema>, stats: {
dispatches: number;
confidence: number;
independent_sessions: number;
user_confirmed?: boolean;
}): boolean {
const dispatchOk = stats.dispatches >= rule.min_dispatches && stats.independent_sessions >= rule.min_independent_sessions;
const confidenceOk = stats.confidence >= rule.min_confidence;
if (rule.gate_mode === "either") return dispatchOk || confidenceOk;
if (rule.gate_mode === "both") return dispatchOk && confidenceOk;
return Boolean(stats.user_confirmed) && confidenceOk;
}
```
### §14.5 StrictSignalValidator
Missing required semantic fields quarantine the signal. Optional enrichment gaps warn and default.
**Acceptance**
A signal missing `source_id` must land in `quarantined_signals.jsonl` and may not alter node confidence.
---
## Part XV — CIL Advisory System
### §15.1 Purpose
The advisory system explains why CIL recommended something, what evidence supports it, what changed, and which records matter. It must not mutate CIL state.
### §15.2 Architecture and Session Lifetime
**Architecture**
- Q surfaces build deep-link payloads client-side.
- DOC10’s dispatcher owns advisory transport and thread reuse.
- CIL advisor reads CIL state snapshots and spec metadata.
- Advisors persist for the duration of a single advisory thread, not across unrelated conversations.
### §15.3 CILQueryContextPayload
```ts
export const CILQueryContextPayloadSchema = z.object({
source_surface: z.enum([
"suggestion_card",
"knowledge_node_browser",
"nightly_card",
"context_inspector",
"inline_pill",
"cil_health_tab",
"general_ask"
]),
source_id: z.string().optional(),
referenced_node_ids: z.array(z.string()).default([]),
referenced_authority_ids: z.array(z.string()).default([]),
referenced_signal_ids: z.array(z.string()).default([]),
referenced_dispatch_id: z.string().optional(),
referenced_profile_id: z.string().optional(),
state_revision: z.object({
checkpoint_manifest_hash: z.string().optional(),
spec_map_version: z.string().optional(),
asked_against_at: z.string().datetime()
}).optional(),
snapshot: z.object({
confidence: z.number().optional(),
confidence_tier: z.enum(["very_limited", "limited", "moderate", "strong"]).optional(),
evidence_grade: EvidenceGradeSchema.optional(),
authority_scope: AuthorityScopeSchema.optional(),
creation_path: AuthorityCreationPathSchema.optional(),
display_summary: z.string().optional()
}).optional(),
user_question: z.string().optional(),
asked_at: z.string().datetime()
});
export const AdvisoryFailureSchema = z.object({
failure_code: z.enum([
"ADVISOR_UNAVAILABLE",
"ADVISOR_TIMEOUT",
"SPEC_MAP_STALE",
"STATE_SNAPSHOT_UNAVAILABLE",
"PAYLOAD_INVALID"
]),
message: z.string(),
recoverable: z.boolean(),
retry_after_ms: z.number().int().nonnegative().optional()
});
export const AdvisoryResponseSchema = z.object({
thread_id: z.string().optional(),
answer_markdown: z.string(),
referenced_node_ids: z.array(z.string()).default([]),
referenced_dispatch_id: z.string().optional(),
failure: AdvisoryFailureSchema.optional()
});
export function buildQueryContext(input: {
source_surface: z.infer<typeof CILQueryContextPayloadSchema>['source_surface'];
snapshot?: z.infer<typeof CILQueryContextPayloadSchema>['snapshot'];
referenced_node_ids?: string[];
referenced_authority_ids?: string[];
referenced_dispatch_id?: string;
}): z.infer<typeof CILQueryContextPayloadSchema> {
return {
source_surface: input.source_surface,
referenced_node_ids: input.referenced_node_ids ?? [],
referenced_authority_ids: input.referenced_authority_ids ?? [],
referenced_dispatch_id: input.referenced_dispatch_id,
snapshot: input.snapshot,
asked_at: new Date().toISOString(),
state_revision: { asked_against_at: new Date().toISOString() }
};
}
export function resolveSpecReferenceMap(payloadKind: string, map: z.infer<typeof SpecReferenceMapSchema>): z.infer<typeof SpecReferenceMapEntrySchema>[] {
return map.mappings[payloadKind] ?? [];
}
```
### §15.4 CIL Advisor Agent Definition
**Wiring**
- target role: `cil_advisor`
- model: mid-tier reasoning model
- document dependencies: CIL state snapshots + spec docs via DocIndex aliases
- mutation rights: none
### §15.5 Spec Reference Map
```ts
export const SpecReferenceMapEntrySchema = z.object({
spec: z.string(),
section_label: z.string().optional(),
description: z.string(),
anchor_hash: z.string()
});
export const SpecReferenceMapSchema = z.object({
version: z.string(),
updated_at: z.string().datetime(),
mappings: z.record(z.array(SpecReferenceMapEntrySchema))
});
```
**Rule**
Section-number-only maps are insufficient. `anchor_hash` is required so the advisor can survive section renumbering.
**Auto-generation rule**
`spec_reference_map.json` should be generated from the current spec documents at build/release time when possible. Manual edits are allowed only as overrides and must increment `version`. If the advisor sees a `spec_map_version` mismatch, it should return `SPEC_MAP_STALE` rather than cite guessed sections.
### §15.6 Dispatcher Payload Pass-Through Rule
DOC10 dispatcher must pass the payload through unmodified. It may add routing metadata, but may not strip or normalize CIL payload fields.
### §15.7 Advisory Snapshot Safety
**Code**
```ts
export async function readConsistentCILSnapshotForAdvisor(): Promise<{ nodesPath: string; signalsPath: string }> {
const nodesSnapshot = "ELNOR_MEMORY/system/cil/knowledge_nodes.snapshot.jsonl";
const signalsSnapshot = "ELNOR_MEMORY/system/cil/signals.snapshot.jsonl";
const nodesExists = await exists(nodesSnapshot);
const signalsExists = await exists(signalsSnapshot);
if (nodesExists && signalsExists) return { nodesPath: nodesSnapshot, signalsPath: signalsSnapshot };
return { nodesPath: "ELNOR_MEMORY/system/cil/knowledge_nodes.jsonl", signalsPath: "ELNOR_MEMORY/system/cil/signals.jsonl" };
}
```
**Acceptance**
A `Why?` click on a suggestion card while nightly is running must produce either a correct snapshot-based answer or a typed advisory failure, never a silent partial hallucination.
---
## Part XVI — DocIndex Integration
### §16.1 Purpose
DocIndex makes CIL’s document-level behavior visible, reusable, and explainable. It supports document access learning, state-file lookup, and proactive document surfacing.
### §16.2 Document Access Patterns as Passive Signals
`document_accessed` remains neutral by default but is used for workspace default learning and document recommendation discovery.
### §16.3 Workspace Document Sets as CIL-Learned Defaults
**Architecture**
When the user consistently accesses the same documents within the same workspace/task context, CIL may promote a `workspace_default` node containing the learned set. The suggestion card may then offer: “Load your standard documents for this workspace?”
**Rule**
Document co-access is defined as either:
- same `dispatch_id`, or
- within 30 minutes in the same `workspace_id`
### §16.4 CIL State Files Registered in DocIndex
**Wiring**
On CIL initialization, register:
- `knowledge_nodes.jsonl`
- `signals.jsonl`
- `config.json`
- `profiles/`
- `nightly/`
with aliases suitable for advisor lookup.
**Compression rule**
CIL state files used for advisory or governance must be marked `never_compress = true` in DocIndex or read directly by path. Skeleton compression is unacceptable for canonical advisory state reads.
### §16.5 Spec Documents Should Be DocIndex-Indexed
All ELNOR specs, integration contracts, Addendum A, and punch lists should be indexed with aliases such as `DOC15`, `CIL spec`, `learning spec`, and common user phrasing. Aliases should include formal names, short names, topic names, and common user phrasing Will uses.
### §16.6 Proactive Document Surfacing and Budget Hints
**Code**
```ts
export async function proposeDocumentHints(contextFacts: ContextFacts): Promise<DocumentPriorityHint[]> {
const nodes = await findMatchingWorkspaceDefaultNodes(contextFacts);
return nodes.flatMap(node => {
if (node.payload.payload_kind !== "workspace_default" || node.payload.value.default_type !== "document_set") return [];
return node.payload.value.doc_ids.map(doc_id => ({
doc_id,
priority: "useful" as const,
reason: node.payload.reason,
source_node_ids: [node.node_id]
}));
});
}
```
### §16.7 MemorySearchService Ownership (Seam Note)
DOC15 should eventually own an extractable `MemorySearchService` for semantic retrieval provider selection, embedding generation, vector storage, hybrid scoring, degrade logic, and debug introspection. This is a seam note, not a full semantic-search appendix in this document.
### §16.8 Agent Document Dependencies
Advisor and review-helper agents may declare document dependencies that DOC10 resolves through DocIndex at spawn time. CIL itself does not own the spawn resolver; it only defines the dependency needs.
**Acceptance**
A workspace with a learned `workspace_default` document set should surface a document recommendation in the suggestion card and hand the doc IDs to DOC10/DocIndex for loading when the user accepts.
---
## Part XVII — Cross-Document Integration Summary
### §17.1 Integration Overview
CIL depends on source docs for emissions, runtime truth, planner support, and advisory transport. The companion integration contract is authoritative for owner-doc capture points.
### §17.2 Capture Points 25–40 (R6 Reconciled)
| # | Source | Target | Capture Point | Priority |
|---|---|---|---|---|
| 25 | DOC11 | DOC15 | cache telemetry in runtime truth (hit/miss, prefix hash) | Immediate |
| 26 | DOC7 + DOC11 | DOC15 | summary lineage export and materialization preview lineage | Important |
| 27 | DOC14 | DOC15 | room_health -> CIL signal bridge | Important |
| 28 | DOC12 | DOC15 | moderator round-transition advisory hook | Future |
| 29 | EC Core | DOC15 | context-change event emission for standing-order edits | Immediate |
| 30 | EC Core | DOC15 | context-change event emission for profile config changes | Immediate |
| 31 | EC Core / DocIndex | DOC15 | document access events -> CIL passive signals | Important |
| 32 | DOC15 | EC Core / DocIndex | CIL state files registered in DocIndex on init | Immediate |
| 33 | DOC10 | DOC15 | generic advisor dispatch for CIL surfaces (`+ask/+advise`) | Immediate |
| 34 | DOC10 | EC Core / DocIndex | agent document dependency declarations -> DocIndex auto-resolution | Important |
| 35 | DOC15 | DOC10 | CIL suggestion cards include document recommendations for loading | Important |
| 36 | DOC1 / EC Core | DOC15 | durable authority records carry scope, creation path, applicability filters, and origin dispatch | Immediate |
| 37 | DOC1 / EC Core | DOC15 | broader-than-session corrections require candidate → pending → explicit approval | Immediate |
| 38 | DOC10 / DOC6 | EC Core / DOC15 | freeform task instructions are passed as `transient_instructions`, with explicit “save as correction” path separated | Immediate |
| 39 | EC Core | DOC15 / Q | context assembly exports applied authority, skipped authority, and transient instruction traces | Immediate |
| 40 | DOC10 | DOC15 / Q | `Ask`, `Why?`, and memory-origin telemetry are emitted for authority and advisory inspection flows | Important |
---
## Part XVIII — Design Decisions Register
| # | Decision | Rationale |
|---|---|---|
| D64 | EvidenceGrade separates confidence from causal support | Trust and honesty |
| D65 | `user_node_retire` is a hard override | User intent is authoritative |
| D66 | Suggestion cards present complete configurations | Fragmentary recommendations are confusing |
| D67 | Abandonment is split into timeout vs explicit abandonment | Timeout is not necessarily quality failure |
| D68 | Graduated context-change decay is preserved | Different change classes should not decay equally |
| D69 | Pool weight shrinks as own sample grows | Broad priors must fade as direct evidence accumulates |
| D70 | Shadow gate uses payload-specific operators | Low-risk and high-risk hints do not deserve the same release rule |
| D71 | Advisory uses agents, not custom mutation logic | Explanations are LLM-native but state remains file-truth |
| D72 | CILQueryContextPayload deep-links advisors | Avoids vague explanation requests |
| D73 | DocIndex learning stays in the spec | Implementation may phase it, but architecture remains |
| D74 | `display_summary` is required on all nodes | Human-readable inspection is mandatory |
| D75 | `upstream_ref` is required for cross-system provenance | Explanation and attribution require durable links |
| D76 | `ConfigurationTupleRef` is mandatory on checkpoints | Signal-to-tuple attribution depends on it |
| D77 | Spec reference map must use stable anchors | Section renumbering should not silently break advisory |
| D78 | R6 assumes a clean-room rebuild | No monolith line-number dependencies |
| D79 | Training Engine remains fully specced | Completeness beats premature excision |
| D80 | Shadow gate is per-payload, not global OR logic | Prevents garbage auto-promotion |
| D81 | `confidence.mean` is never persisted | Avoids schema drift and stale computed values |
| D82 | User-edited nodes remain heuristic with `user_authored` | Resolves authority/heuristic contradiction |
| D83 | Suggestion learning is per-element | UI and learning contracts must match |
| D84 | Context-change events are debounced | Prevents over-decay from admin flurries |
| D85 | DOC8 bridge runs nightly, not real-time | Simpler, safer Phase 0–2 behavior |
| D86 | Durable corrections are scope-tagged | Strong rules must still know where they apply |
| D87 | Freeform stream-of-thought instructions default to transient | One-off task wording must not silently become global authority |
| D88 | Broader durable corrections require explicit save or approved proposal | Strength is safe only when creation is conservative |
| D89 | Advisors and inspectors expose authority scope and creation path | Users must be able to see why a correction applied |
---
## Appendix A — Latency Budget
| Operation | Target |
|---|---|
| Profile resolution | < 5ms |
| Knowledge node query (200 nodes) | < 10ms |
| ContextPlan generation | < 40ms |
| Signal batch append | < 5ms |
| Full CIL dispatch overhead | < 60ms p50, < 120ms p95 |
| Nightly Phase 5 (200 nodes, 350 signals/week) | < 15 min |
| Advisory answer | < 2s p50, < 5s hard timeout |
## Annex B — Implementation FAQ
**Q: Do user-authored nodes become authority memory?**
No. They remain heuristic and gain retention/precedence protections through explicit fields.
**Q: If I say “don’t use markdown” once, does that become a global correction?**
No. Freeform task instructions default to `transient_instructions` for the current operation or session. A broader durable correction requires an explicit save path or an approved proposal through the authority-memory pipeline.
**Q: Does advice go through `/api/cil/operation`?**
No. Operations and advisory queries use different entry points.
**Q: May CIL summarize SOUL.md or native history?**
No. Protected native context classes are preserved and only referenced.
**Q: Where do document recommendations come from?**
From `workspace_default` nodes and future document-usage learning, not from hidden runtime state.
## Annex C — Helper Function Contracts
All helper functions referenced above are owned by EC/CIL unless explicitly delegated to another owner doc. In a clean-room build, these signatures are normative:
```ts
export type ContextFacts = z.infer<typeof ContextFactsSchema>;
export type ContextMatcher = z.infer<typeof ContextMatcherSchema>;
export type ContextMatcherRule = z.infer<typeof ContextMatcherRuleSchema>;
export type BetaConfidence = z.infer<typeof BetaConfidenceSchema>;
export type KnowledgeNode = z.infer<typeof KnowledgeNodeSchema>;
export type OperationIntent = z.infer<typeof OperationIntentSchema>;
export type ResolvedOperation = z.infer<typeof ResolvedOperationSchema>;
export type DispatchCheckpoint = z.infer<typeof DispatchCheckpointSchema>;
export type ContextPlan = z.infer<typeof ContextPlanSchema>;
export type LearningSignal = z.infer<typeof LearningSignalSchema>;
export type NodeSignalStats = z.infer<typeof NodeSignalStatsSchema>;
export type ContextChangeEvent = z.infer<typeof ContextChangeEventSchema>;
export type AuthorityMemoryRecord = z.infer<typeof AuthorityMemoryRecordSchema>;
export type TransientInstruction = z.infer<typeof TransientInstructionSchema>;
export function deriveOperationFamilyDefaults(operationFamily: string): { interaction_mode: ContextFacts["interaction_mode"]; surface_kind: ContextFacts["surface_kind"]; task_type: string };
export function deriveReviewTargetKind(artifactRefs: z.infer<typeof ArtifactRefSchema>[]): ContextFacts["review_target_kind"];
export function deriveEvidenceDomain(artifactRefs: z.infer<typeof ArtifactRefSchema>[]): ContextFacts["evidence_domain"];
export function getMostRecentActivationTs(profileId: string): string | undefined;
export function evaluateRule(contextFacts: Record<string, unknown>, rule: ContextMatcherRule): boolean;
export function authorityRecordAppliesToContext(record: AuthorityMemoryRecord, contextFacts: ContextFacts, nowIso?: string): boolean;
export function normalizeTransientInstructions(items: TransientInstruction[]): TransientInstruction[];
export function loadAuthorityMemory(contextFacts: ContextFacts): Promise<AuthorityMemoryRecord[]>;
export function readKnowledgeNodes(): Promise<KnowledgeNode[]>;
export function buildContextPlan(input: { contextFacts: ContextFacts; profileIds: string[]; memory: { authority_memory: AuthorityMemoryRecord[]; heuristic_nodes: KnowledgeNode[] }; transientInstructions?: TransientInstruction[] }): Promise<ContextPlan>;
export function makeSignal(signalType: string, originalDispatchId: string, elementId: string, supportingNodeIds: string[], payload?: Record<string, unknown>): LearningSignal;
export function mapPayloadKindToElementKind(payloadKind: z.infer<typeof PayloadKindSchema>): z.infer<typeof ResolvedSuggestionElementSchema>["element_kind"];
export function mapPayloadKindToFieldPath(payloadKind: z.infer<typeof PayloadKindSchema>): string;
export function extractProposedValue(payload: z.infer<typeof KnowledgePayloadSchema>): unknown;
export function applySignalsToNode(node: KnowledgeNode, nodeSignals: LearningSignal[]): KnowledgeNode;
export function updateEvidenceGrade(current: z.infer<typeof EvidenceGradeSchema>, nodeSignals: LearningSignal[]): z.infer<typeof EvidenceGradeSchema>;
export function persistPhase5NodeProgress(runId: string, nodeId: string): Promise<void>;
export function archiveJsonlOlderThan(path: string, cutoffMs: number, archiveDir: string): Promise<void>;
export function archiveUnreferencedCheckpointsOlderThan(cutoffMs: number): Promise<void>;
export function acquireFileLock(path: string): Promise<{ release(): Promise<void> }>;
export function readUnflushedBatches(): Promise<z.infer<typeof SignalBatchSchema>[]>;
export function batchAlreadyFlushed(batchId: string): Promise<boolean>;
export function updateCursor(batchId: string): Promise<void>;
export function appendJsonl(path: string, row: unknown): Promise<void>;
export function findLastCompleteJsonlOffset(path: string): Promise<number>;
export function truncateFile(path: string, offset: number): Promise<void>;
export function exists(path: string): Promise<boolean>;
export function findMatchingWorkspaceDefaultNodes(contextFacts: ContextFacts): Promise<KnowledgeNode[]>;
export function ensureDirectoryLayout(): Promise<void>;
export function loadConfigAndValidateVersions(): Promise<void>;
export function loadProfilesAndTraits(): Promise<void>;
export function buildKnowledgeNodeIndex(): Promise<void>;
export function registerCILStateFilesInDocIndex(): Promise<void>;
export function registerSpecDocumentsInDocIndex(): Promise<void>;
export function registerBridgeHooks(): Promise<void>;
export function flushInMemorySignalBufferToSpool(): Promise<void>;
export function persistIndexesAndCursors(): Promise<void>;
export function emitCILEvent(eventType: z.infer<typeof CILEventTypeSchema>, details: Record<string, unknown>): Promise<void>;
```
If any implementation needs additional helper functions, they must be added to this annex or inlined near the calling section before coding starts.
## Annex D — Signal Weight Reference
R5 calibrated weights are preserved unless this document explicitly overrides a rule. `suggestion_dismissed` remains `-1.0`.
---
# Part 2 — Merged Revision — DOC15 R6.2 (Graph / Topology Consumption Alignment)
# DOC15 — Cognitive Infrastructure Layer (CIL)
## R6.2 — Graph / Topology Consumption Alignment
**Date:** March 10, 2026
**Status:** targeted revision draft — Wave C consumer alignment
**Supersedes:** DOC15 R6.1 only for the subjects covered here
**Companions:** DOC15 Contract v1.1.1; DOC16 R3.1 / R1.1; DOC10 R10.1; DOC18 R1.1
---
## Why this revision exists
R6.1 already gave CIL the right seams for:
- deterministic operation compilation,
- authority vs heuristic separation,
- context planning,
- document-access learning,
- `workspace_default` document sets,
- advisory explanations,
- a future `MemorySearchService` seam note.
Wave A and Wave B then clarified that the broader knowledge topology should be preserved as a **derived read-model** and that retrieval/provider truth should remain explicit. R6.2 therefore adds the missing consumer-side contracts so CIL can:
- consume one-hop relation-aware memory/document signals,
- emit graph-aware `document_priority_hints` and support-pack suggestions,
- use topology metadata in advisor explanations,
- condition recommendations on legal/workspace/matter-specific context facts,
- degrade honestly when only retrieval receipts are available and no topology snapshot exists.
R6.2 does **not** make DOC15 the owner of canonical graph truth, LlamaIndex corpora, or relationship-index storage.
---
## Part I — New non-negotiables for graph/topology consumption
### §1.8 Graph/topology is a derived read-model, not CIL truth
CIL may query relation/topology read seams and consume the results in ranking, planning, recommendation, and explanation. It may not:
- write canonical graph nodes or edges,
- silently promote inferred contradictions into authority,
- treat topology absence as proof of irrelevance,
- create a second durable graph substrate under `system/cil/`.
### §1.9 Relation-aware logic never outranks authority
Relation-aware ranking is subordinate to the existing precedence chain. Authority memory and protected native truth still beat learned graph signals.
### §1.10 Retrieval truth and topology truth are separate
Provider receipts tell CIL **how** something was found. Topology signals tell CIL **how it is related** to other things. CIL must not blur these together.
---
## Part II — ContextFacts and operating context extensions
### §2.5 ContextFacts enrichment for legal and workflow topology
R6.2 adds optional conditioning fields that CIL may consume when the source docs export them.
```ts
export const ContextFactsSchema = ContextFactsSchema.extend({
matter_id: z.string().max(200).optional(),
case_id: z.string().max(200).optional(),
workflow_stage: z.string().max(120).optional(),
motion_type: z.string().max(120).optional(),
document_role: z.string().max(120).optional(),
issue_labels: z.array(z.string().max(120)).max(20).default([]),
retrieval_corpus_hints: z.array(z.string().max(160)).max(12).default([]),
support_pack_policy: z.enum(["none", "allow_preview", "allow_apply"]).default("allow_preview"),
});
```
These fields are optional and must not block existing paths if absent.
---
## Part III — Relation-aware read contracts
### §3.6 Topology relation types consumed by CIL
```ts
export const TopologyRelationTypeSchema = z.enum([
"references",
"derived_from",
"supports",
"contradicts",
"supersedes",
"same_issue_as",
"same_matter_as",
"same_motion_type_as",
"used_with",
"successful_in_context",
]);
```
### §3.7 One-hop relation result contract
```ts
export const RelatedArtifactHitSchema = z.object({
related_ref_id: z.string().max(200),
related_node_id: z.string().max(200).optional(),
relation_type: TopologyRelationTypeSchema,
strength: z.number().min(0).max(1).optional(),
freshness_state: z.string().max(80).optional(),
scope_match: z.boolean().default(true),
metadata: z.record(z.string(), z.unknown()).default({}),
});
export const BoundedRelationQueryResultSchema = z.object({
source_ref_id: z.string().max(200),
degraded: z.boolean().default(false),
degraded_reason: z.string().max(160).optional(),
hits: z.array(RelatedArtifactHitSchema).max(12).default([]),
schema_version: z.literal(1),
});
```
### §3.8 Topology snapshot reference
```ts
export const TopologySnapshotRefSchema = z.object({
snapshot_id: z.string().max(200),
generated_at: z.string(),
freshness_state: z.enum(["fresh", "stale", "degraded", "missing"]),
read_model_alias: z.string().max(200).optional(),
});
```
---
## Part IV — MemorySearchService seam expansion
### §4.8 MemorySearchService now consumes relation-aware reranking
R6.1 preserved `MemorySearchService` as a seam note. R6.2 clarifies the minimum consumer behavior:
1. retrieve candidates from the selected retrieval lane,
2. optionally request bounded one-hop relation results,
3. rerank candidates using relation-aware signals,
4. suppress or demote contradiction/supersession conflicts when appropriate,
5. preserve retrieval/provider truth and topology reasons separately for advisor/UI use.
### §4.9 Relation-aware reranking rules
Recommended additive boosts/penalties:
- `same_issue_as`: modest positive boost
- `same_matter_as`: modest positive boost
- `used_with`: weak positive boost
- `successful_in_context`: moderate positive boost
- `supersedes`: candidate replacement or older-item demotion
- `contradicts`: comparison-only or explanation-required path
These are reranking signals only. They do not create durable confidence on their own.
### §4.10 Required function
```ts
export async function rerankWithTopology(input: {
contextFacts: z.infer<typeof ContextFactsSchema>;
candidateRefs: string[];
relationResults: z.infer<typeof BoundedRelationQueryResultSchema>[];
}): Promise<{
rankedRefs: string[];
suppressedRefs: Array<{ ref_id: string; reason: string }>;
reasonCodesByRef: Record<string, string[]>;
}>;
```
---
## Part V — ContextPlanner and document hinting amendments
### §5.10 Graph-aware document priority hints
R6.2 extends `DocumentPriorityHintSchema` consumption/emission to carry graph-aware reason codes and support-pack grouping hints.
```ts
export const DocumentPriorityHintSchema = DocumentPriorityHintSchema.extend({
reason_codes: z.array(z.string().max(120)).max(12).default([]),
relation_types: z.array(TopologyRelationTypeSchema).max(8).default([]),
relation_strength: z.number().min(0).max(1).optional(),
support_pack_id: z.string().max(160).optional(),
provider_receipt_refs: z.array(z.string().max(200)).max(12).default([]),
});
```
### §5.11 Support-pack suggestion payload
```ts
export const SupportPackSuggestionSchema = z.object({
support_pack_id: z.string().uuid(),
title: z.string().max(160),
doc_ids: z.array(z.string().max(200)).min(2).max(8),
reason_codes: z.array(z.string().max(120)).max(12).default([]),
provider_receipt_refs: z.array(z.string().max(200)).max(12).default([]),
source_node_ids: z.array(z.string().max(200)).max(12).default([]),
preview_only: z.boolean().default(true),
});
```
### §5.12 Planner behavior
When relation-aware document hints are present:
- active review target stays highest priority,
- same-issue / same-matter neighbors may be grouped into support-pack suggestions,
- superseding docs may replace older docs in the plan,
- contradiction docs may be marked comparison-only,
- if no topology data exists, planner falls back to retrieval/provider truth + ordinary relevance.
### §5.13 Support-pack policy
`support_pack_policy` in `ContextFacts` determines whether a plan may:
- emit no support-pack suggestion,
- emit preview-only support-pack suggestions,
- emit apply-capable support-pack suggestions for structured workflows.
Interactive chat defaults to `allow_preview`.
---
## Part VI — Learning pipeline amendments
### §6.7 Relation-aware passive telemetry
R6.2 adds passive telemetry families for relation-aware recommendation usage:
- `support_pack_previewed`
- `support_pack_applied`
- `support_pack_dismissed`
- `related_doc_loaded`
- `superseding_doc_chosen`
- `comparison_doc_opened`
These are usage signals only. They do not mutate graph truth directly.
### §6.8 Co-access learning with topology labels
When learning `workspace_default` document sets or document recommendations, CIL may preserve lightweight topology labels such as:
- same matter
- same issue
- same motion type
- support-pack member
These labels are hints for future grouping and explanation, not canonical legal truth.
---
## Part VII — Advisor amendments
### §7.12 Relation-aware explanation block
Add an advisor-facing explanation block that keeps provider truth separate from relation truth.
```ts
export const RelationAwareExplanationSchema = z.object({
provider_truth: z.array(z.object({
provider_kind: z.string().max(120),
search_lane: z.string().max(80),
corpus_id: z.string().max(200).optional(),
route_reason: z.string().max(200).optional(),
freshness_state: z.string().max(80).optional(),
degraded_reason: z.string().max(160).optional(),
})).max(12).default([]),
relation_truth: z.array(z.object({
related_ref_id: z.string().max(200),
relation_type: TopologyRelationTypeSchema,
explanation: z.string().max(240),
})).max(12).default([]),
topology_snapshot: TopologySnapshotRefSchema.optional(),
});
```
### §7.13 CILQueryContextPayload extension
```ts
export const CILQueryContextPayloadSchema = CILQueryContextPayloadSchema.extend({
referenced_support_pack_id: z.string().max(160).optional(),
referenced_provider_receipt_refs: z.array(z.string().max(200)).max(12).default([]),
referenced_topology_snapshot_id: z.string().max(200).optional(),
});
```
### §7.14 Advisor rule
If topology data was unavailable, the advisor must explicitly say the explanation is based on retrieval/provider truth and direct node evidence only, not relation-aware expansion.
---
## Part VIII — UI / product amendments
### §8.6 Suggestion cards
Suggestion cards may now include:
- support-pack recommendations,
- grouped document suggestions,
- relation reason pills (`same issue`, `same matter`, `supersedes older memo`),
- `Why this pack?` action.
### §8.7 Context Inspector
The Context Inspector should show for graph-aware docs:
- provider lane/provider kind,
- relation reasons,
- support-pack membership,
- whether the doc replaced a superseded candidate,
- whether topology fallback was used.
### §8.8 Knowledge Node Browser
For nodes that drive document recommendations, show a compact relation summary rather than pretending DOC15 owns the full graph browser.
States:
- no relation data
- relation-aware fallback
- full relation-aware explanation
---
## Part IX — Code implementation plan
### §9.1 New or amended files
```text
packages/contracts/src/cil/topology.ts
apps/ec-service/src/cil/memory-search-service.ts
apps/ec-service/src/cil/topology-consumer.ts
apps/ec-service/src/cil/support-pack-suggestions.ts
apps/ec-service/src/cil/advisors/relation-aware-explainer.ts
apps/ec-service/src/cil/context-planner.ts
apps/q-frontend/src/components/cil/SupportPackSuggestionCard.tsx
apps/q-frontend/src/components/cil/RelationReasonPills.tsx
apps/q-frontend/src/components/cil/RelationAwareInspectorSection.tsx
```
### §9.2 Required functions
```ts
export async function queryBoundedRelations(input: {
refIds: string[];
contextFacts: z.infer<typeof ContextFactsSchema>;
allowedRelationTypes?: z.infer<typeof TopologyRelationTypeSchema>[];
}): Promise<z.infer<typeof BoundedRelationQueryResultSchema>[]>;
export async function buildSupportPackSuggestions(input: {
contextFacts: z.infer<typeof ContextFactsSchema>;
documentHints: z.infer<typeof DocumentPriorityHintSchema>[];
}): Promise<z.infer<typeof SupportPackSuggestionSchema>[]>;
export function buildRelationAwareExplanation(input: {
providerReceipts: any[];
relationResults: z.infer<typeof BoundedRelationQueryResultSchema>[];
topologySnapshot?: z.infer<typeof TopologySnapshotRefSchema>;
}): z.infer<typeof RelationAwareExplanationSchema>;
```
### §9.3 Failure handling
- If bounded relation query fails, continue with receipt-only reasoning and set degraded flags.
- If support-pack generation fails validation, emit plain document hints instead.
- If legal/workflow context fields are absent, the system must continue without them.
---
## Part X — Acceptance scenarios
1. **Workspace default + same-issue pack**
CIL learns a workspace default doc set and later suggests a grouped pack of same-issue documents for a similar motion type.
2. **Superseding authority memo**
A newer memo is marked relation-wise as superseding an older one. CIL recommends the newer memo and explains the replacement in the advisor.
3. **Contradiction-aware comparison**
A contradictory document exists. CIL surfaces it only as comparison material, not as ordinary support.
4. **No topology snapshot**
CIL still offers a document recommendation based on provider receipts and direct evidence, with an explicit fallback note.
5. **Legal context conditioning**
`matter_id`, `motion_type`, and `issue_labels` are present. CIL uses them to improve document suggestions and support-pack grouping without requiring any new canonical graph storage.
---
## Part XI — Manifest reconciliation for this revision
R6.2 covers the Wave C DOC15 consumer obligations:
- graph/topology read-model consumption without ownership drift,
- relation-aware `MemorySearchService` seam expansion,
- graph-aware document hints and support-pack suggestions,
- legal/workflow context conditioning,
- relation-aware advisor explanations,
- explicit degraded fallback behavior,
- implementation-ready schemas, UI states, and code seams.
This revision preserves the existing DOC15 governance model: authority remains strong, graph inputs remain derived, and provider truth remains explicit.
---
# Part 2 — Merged Revision — DOC15 R7 Completion, Recommendation Expansion, and Authority-Scaling Hardening
# DOC15 Cognitive Infrastructure Layer (CIL) — R7
# ELNOR Suite Specification
**Date:** March 12, 2026
**Status:** Current operative draft for review
**Purpose of this merged revision:** fully incorporate the DOC15 R7 Amendment Brief into the operative CIL specification so that the next DOC15 version is a complete end-state spec rather than a planning note.
**Operating rule:** this merged revision governs over any overlapping or conflicting statement in Part 1.
---
## R7 Revision Summary
R7 is a completeness, seam-hardening, and recommendation-expansion revision.
It keeps the R6 architecture and its major design choices, but makes the following changes normative:
- authority memory now has explicit hot-path budgeting, lane assignment, compact/ref-only rendering, auditability, and mutation controls
- `ContextFactsSchema` grows legal/workflow fields and population-source tracing so matter/case/motion/doc-role logic is explainable instead of mysterious
- profile ties resolve deterministically via explicit `priority`
- `ConfigurationTupleRef` grouping must use deterministic hashing rather than raw object stringification
- `ContextPlanSchema` now carries `total_budget_tokens` and authority-selection details
- prompt fragments may never be truncated mid-text
- DOC15 now owns a full prompt-artifact recommendation layer rather than stopping at configuration/support-pack recommendations
- CIL UI surfaces gain complete response/error/degraded contracts
- advisor responses become typed instead of markdown-only
- topology/read-model consumption remains derived and bounded, and topology unavailability becomes visible rather than silently collapsing to ordinary retrieval
- the integration summary is updated to reflect the complete end-state companion obligations required by the finished product
This revision assumes the finished product, not a phase-limited subset.
---
## Part I — Governing R7 Additions
### §1.3C Authority Hot-Path Rule
CIL may not treat all matching authority as equal and may not inject all matching authority inline by default.
Every authority record participating in dispatch selection must be assigned to one of four render lanes:
- `core` — foundational or protected authority that must render inline for all matching dispatches
- `scoped` — scope-matched authority that renders inline when the scoped lane budget allows
- `ref_only` — durable authority that remains active and inspectable but is represented by compact reference form rather than full inline text
- `inspector_only` — durable authority that remains available for inspection, explanation, and future promotion but is not part of the hot-path prompt
Transient instructions never become durable authority implicitly. They outrank heuristic memory for the current dispatch only.
### §1.3D Recommendation Surface Rule
DOC15 owns three recommendation families:
1. `operation_configuration`
2. `support_pack`
3. `prompt_artifact`
Every recommendation family must share common envelope, evidence, degraded-state, and explanation contracts even if each family has its own payload shape.
---
## Part II — Schema and Logic Amendments
### §2.5A CILConfigSchema — add the following fields
```ts
max_authority_injection_tokens: z.number().int().positive().default(4000),
authority_overflow_behavior: z.enum([
"truncate_lowest_priority",
"fail_dispatch"
]).default("truncate_lowest_priority"),
authority_core_inline_limit: z.number().int().positive().default(8),
authority_scoped_inline_limit: z.number().int().positive().default(12),
authority_ref_only_limit: z.number().int().positive().default(24),
authority_compact_char_limit: z.number().int().positive().default(220),
authority_cold_after_skips: z.number().int().positive().default(12),
authority_audit_review_window_days: z.number().int().positive().default(30),
confidence_tier_thresholds: z.object({
very_limited_max: z.number().min(0).max(1).default(0.54),
limited_max: z.number().min(0).max(1).default(0.69),
moderate_max: z.number().min(0).max(1).default(0.84),
}).default({
very_limited_max: 0.54,
limited_max: 0.69,
moderate_max: 0.84,
}),
profile_bootstrap_defaults: z.array(z.string()).default([
"context_default_dispatch"
]),
graph_query_defaults: z.object({
max_traversal_depth: z.number().int().positive().default(2),
max_hits: z.number().int().positive().default(12),
}).default({
max_traversal_depth: 2,
max_hits: 12,
}),
strict_validator_policy: z.object({
semantic_field_failure: z.enum(["quarantine", "error"]).default("quarantine"),
non_semantic_field_failure: z.enum(["warn_and_default", "error"]).default("warn_and_default"),
}).default({
semantic_field_failure: "quarantine",
non_semantic_field_failure: "warn_and_default",
}),
```
### §3.3 ContextFactsSchema — bump schema version and add the following fields
Set `schema_version` to `7`.
```ts
matter_id: z.string().max(200).optional(),
case_id: z.string().max(200).optional(),
motion_type: z.string().max(120).optional(),
document_role: z.string().max(120).optional(),
workflow_stage: z.string().max(120).optional(),
issue_labels: z.array(z.string().max(120)).default([]),
retrieval_corpus_hints: z.array(z.string()).default([]),
context_fact_sources: z.record(z.enum([
"intent",
"panel_state",
"room_config",
"workspace_binding",
"docindex_metadata",
"derived",
"unknown",
])).default({}),
```
#### New rule: context-fact source truth
Every nontrivial legal/workflow field in `ContextFactsSchema` must have a visible population source in `context_fact_sources`. Missing fields may not be silently interpreted as “not applicable.”
### §3.7 ProfileDefinitionSchema — add deterministic tie-breaker
```ts
priority: z.number().int().default(0),
```
If multiple profiles tie after matcher scoring:
1. highest `priority` wins,
2. then most recent activation wins,
3. then `profile_id` ascending as the final deterministic tie-break.
### §3.8 Activation — add activation storage shape
```ts
export const ProfileActivationRecordStorageSchema = z.object({
profile_id: z.string(),
activated_at: z.string().datetime(),
source_surface: z.string().optional(),
operation_id: z.string().optional(),
});
```
### §4.2A Durable Authority Capture / Scope Filtering — add authority hot-path state
```ts
export const AuthorityRenderFormSchema = z.enum([
"inline_full",
"inline_compact",
"ref_only",
"inspector_only",
]);
export const AuthorityInjectionLaneSchema = z.enum([
"core",
"scoped",
"ref_only",
"inspector_only",
]);
export const AuthorityPersistenceClassSchema = z.enum([
"standard",
"protected",
"foundational",
]);
export const AuthorityHotPathStateSchema = z.object({
authority_id: z.string(),
lane: AuthorityInjectionLaneSchema,
render_form: AuthorityRenderFormSchema,
hot_path_salience: z.number().min(0).max(1),
recent_applied_count: z.number().int().nonnegative().default(0),
recent_skipped_count: z.number().int().nonnegative().default(0),
recent_trimmed_count: z.number().int().nonnegative().default(0),
recent_origin_view_count: z.number().int().nonnegative().default(0),
last_computed_at: z.string().datetime(),
});
export const AuthoritySelectionSummarySchema = z.object({
inline_core_ids: z.array(z.string()).default([]),
inline_scoped_ids: z.array(z.string()).default([]),
ref_only_ids: z.array(z.string()).default([]),
inspector_only_ids: z.array(z.string()).default([]),
trimmed_due_to_budget_ids: z.array(z.string()).default([]),
});
```
#### R7 authority ranking rule
Matching authority is ranked by:
1. persistence class,
2. scope specificity,
3. explicit user pin/protection,
4. hot-path salience,
5. recency of confirmed successful use,
6. creation order.
Foundational/protected authority may not fall below `inline_compact` unless explicitly overridden by the user.
#### R7 wrong-scope detection rule
If an authority record repeatedly matches by broad scope but is skipped, trimmed, or immediately overridden in narrower contexts, it must appear in the Authority Audit as a rescope candidate.
### §4.3 Knowledge Payloads and Nodes — add explicit conflict rule
A `workspace_default` node may not auto-suggest a document set, topology preference, or model preference when an explicit workspace config or standing order conflicts.
```ts
export function conflictsWithExplicitWorkspaceConfig(
node: KnowledgeNode,
workspaceConfig: WorkspaceConfig | null
): boolean;
```
### §4.4 Storage Layout — add the following files
```text
ELNOR_MEMORY/system/cil/profile_activation_records.jsonl
ELNOR_MEMORY/system/cil/authority_hot_path_state.json
ELNOR_MEMORY/system/cil/authority_audit_snapshot.json
```
`knowledge_nodes.jsonl` remains a canonical current-view file and must be rewritten atomically from in-memory snapshot state, never appended incrementally as if it were the signal spool.
### §4.5 Lifecycle, Decay, and Compaction — add authority recompute rule
Authority hot-path recompute is derived state. It may cool lane/render form without mutating the underlying authority record. Durable authority itself does not receive blind time decay.
### §5.3 ContextPlanSchema — add the following fields
```ts
total_budget_tokens: z.number().int().positive(),
authority_selection_summary: AuthoritySelectionSummarySchema.default({
inline_core_ids: [],
inline_scoped_ids: [],
ref_only_ids: [],
inspector_only_ids: [],
trimmed_due_to_budget_ids: [],
}),
```
### §5.7 Knowledge Node Injection Formatting — add whole-node-only truncation rule
Prompt fragments may be dropped whole when they do not fit, but may never be partially truncated.
```ts
if (node.payload.payload_kind === "prompt_fragment" && estimatedTokens > remainingBudget) {
dropNode(node.node_id, "prompt_fragment_exceeds_remaining_budget");
continue;
}
```
### §5.8 ConfigurationTupleRef and DispatchCheckpoint — deterministic tuple hashing
Replace any use of raw object stringification with deterministic hashing.
```ts
export function hashConfigurationTuple(tuple: ConfigurationTupleRef): string {
const ordered = Object.keys(tuple).sort().map((k) => {
const v = (tuple as any)[k];
return Array.isArray(v)
? `${k}=[${[...v].sort().join(",")}]`
: `${k}=${v ?? ""}`;
});
return ordered.join("|");
}
```
Extend `DispatchCheckpointSchema` with:
```ts
skipped_authority_ids: z.array(z.string()).default([]),
authority_lane_summary: AuthoritySelectionSummarySchema,
authority_overflow_reason: z.string().optional(),
```
### §6.3–§6.5 LearningPipeline — formalize signal keys
```ts
export const SignalTypeSchema = z.enum([
"suggestion_applied",
"suggestion_edited",
"suggestion_dismissed",
"suggestion_element_applied",
"suggestion_element_edited",
"suggestion_element_rejected",
"finding_starred",
"review_outcome_positive",
"review_outcome_negative",
"document_accessed",
"workspace_default_applied",
"friction_bridged:tool_error",
"friction_bridged:schema_error",
"friction_bridged:timeout",
"friction_bridged:context_overflow",
"friction_bridged:model_refusal",
"friction_bridged:quality_regression",
"friction_bridged:user_reported",
"friction_bridged:stale_context",
"prompt_artifact_recommendation_previewed",
"prompt_artifact_recommendation_applied",
"prompt_artifact_recommendation_dismissed",
"prompt_artifact_observation_ingested",
]);
```
All learning-signal declarations, validators, flusher logic, and nightly aggregators must consume `SignalTypeSchema` rather than open string space.
### §6.9 ReviewOutcomeSchema / §6.10 Prompt Lineage Tracking
Prompt-artifact recommendation evaluation is allowed to consume:
- review outcomes,
- prompt lineage,
- overlay/prompt recipe observations,
- replay/canary evidence routed through companion owners.
### §7.3 Signal Weights
Embed the full signal-weight table directly in the normative body. R7 may not rely on an implied or missing appendix for weight truth.
### §7.4 Context-Change Decay — add debounce
Apply one-hour debounce per change type. Highest severity wins. At most one application per node per nightly run per change type.
### §7.7 Nightly Beta Update Process — passive signals must not advance DR position
```ts
export function computeSignalPositionForDR(
priorSignalsInSession: LearningSignal[]
): number {
return priorSignalsInSession.filter(
(s) => (SIGNAL_WEIGHTS[s.signal_type]?.strength ?? 0) > 0
).length;
}
```
### §7.11 Normative Implementation Code — make tiering configurable and complete the math
Replace hard-coded confidence tiers with config-driven thresholds.
```ts
export function confidenceTier(
conf: BetaConfidence,
cfg: CILConfig
): "very_limited" | "limited" | "moderate" | "strong" {
const mean = betaMean(conf);
const t = cfg.confidence_tier_thresholds;
if (mean <= t.very_limited_max) return "very_limited";
if (mean <= t.limited_max) return "limited";
if (mean <= t.moderate_max) return "moderate";
return "strong";
}
```
Provide the full body for `evaluateRule()` in Annex C.
Provide a lightweight approximation for credible interval overlap so coders do not import a heavy native math library just to satisfy R7.
```ts
export function betaCredibleIntervalApprox(
dist: BetaConfidence,
zScore = 1.96
): { low: number; high: number } {
const mean = betaMean(dist);
const variance =
(dist.alpha * dist.beta_param) /
(((dist.alpha + dist.beta_param) ** 2) *
(dist.alpha + dist.beta_param + 1));
const sd = Math.sqrt(variance);
return {
low: Math.max(0, mean - zScore * sd),
high: Math.min(1, mean + zScore * sd),
};
}
```
### §8.4 Trusted Path for Execute Mode — add support-pack/prompt-artifact rule
Support-pack application and prompt-artifact application are never implicitly trusted merely because configuration execution is trusted. They must respect their own apply gates and runtime truth.
### §12.4 ResolvedSuggestionElementSchema / ResolvedOperationSchema — unify recommendations
```ts
export const RecommendationSurfaceKindSchema = z.enum([
"operation_configuration",
"support_pack",
"prompt_artifact",
]);
export const RecommendationEnvelopeSchema = z.object({
recommendation_id: z.string().uuid(),
kind: RecommendationSurfaceKindSchema,
evidence_grade: EvidenceGradeSchema,
confidence_mean: z.number().min(0).max(1).optional(),
confidence_tier: z.enum([
"very_limited",
"limited",
"moderate",
"strong",
]).optional(),
degraded_reason_code: z.string().optional(),
preview_only: z.boolean().default(false),
});
```
Extend `ResolvedOperationSchema` with:
```ts
suggested_overlay_ids: z.array(z.string()).default([]),
suggested_prompt_recipe_ids: z.array(z.string()).default([]),
prompt_improvement_hints: z.array(z.string()).default([]),
overlay_advice_state: z.enum([
"proposal_only",
"apply_allowed",
"blocked",
]).default("proposal_only"),
prompt_recipe_suggestion_state: z.enum([
"proposal_only",
"apply_allowed",
"blocked",
]).default("proposal_only"),
context_facts_matched: z.boolean().default(true),
```
#### New build rule for `buildResolvedOperationFromNodes()`
Recommendation selection must sort by weighted recommendation score. It may not simply take the first five nodes returned from an arbitrary array.
### §12.7A — add a new subsection: Prompt Artifact Recommendations
```ts
export const PromptArtifactRecommendationNodeSchema = z.object({
recommendation_id: z.string().uuid(),
matcher: ContextMatcherSchema,
prompt_artifact_kind: z.enum([
"room_role_prompt",
"overlay_template",
"prompt_recipe",
"prompt_advisor_rewrite",
]),
suggested_overlay_ids: z.array(z.string()).default([]),
suggested_prompt_recipe_ids: z.array(z.string()).default([]),
prompt_improvement_hints: z.array(z.string()).default([]),
overlay_advice_state: z.enum([
"proposal_only",
"apply_allowed",
"blocked",
]).default("proposal_only"),
prompt_recipe_suggestion_state: z.enum([
"proposal_only",
"apply_allowed",
"blocked",
]).default("proposal_only"),
evidence_grade: EvidenceGradeSchema,
confidence: BetaConfidenceSchema,
source_node_ids: z.array(z.string()).default([]),
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
export const PromptArtifactRecommendationQueryRequestSchema = z.object({
context_facts: ContextFactsSchema,
limit: z.number().int().positive().default(5),
});
export const PromptArtifactRecommendationQueryResponseSchema = z.object({
recommendations: z.array(PromptArtifactRecommendationNodeSchema).default([]),
degraded_reason_code: z.string().optional(),
});
```
Add route family:
```text
POST /api/cil/prompt-artifact-recommendations/query
POST /api/cil/prompt-artifact-recommendations/preview
POST /api/cil/prompt-artifact-recommendations/dismiss
```
### §13.2–§13.6 UI Surfaces — full state and payload contracts
#### Suggestion Card
Every suggestion-card surface must define:
- loading
- populated
- empty
- degraded
- blocked
- error
Allowed `empty_reason_code` values:
- `no_nodes`
- `heuristic_store_unavailable`
- `context_facts_incomplete`
- `cil_disabled`
Add support-pack preview tray, prompt-artifact preview tray, degraded chip, and a typed `Why?` drawer contract.
#### Knowledge Node Browser
Add the following routes and payloads:
```text
GET /api/cil/knowledge-nodes
POST /api/cil/knowledge-nodes/action
POST /api/cil/authority/action
GET /api/cil/authority/audit
```
```ts
export const AuthorityActionRequestSchema = z.object({
action: z.enum([
"rescope",
"revoke",
"set_expiry",
"set_review_after",
"mark_foundational",
"prefer_compact_render",
]),
authority_id: z.string(),
new_scope: z.string().optional(),
expires_at: z.string().datetime().optional(),
review_after_at: z.string().datetime().optional(),
rationale: z.string().optional(),
});
```
#### Context Inspector
Add explicit sections for:
- `applied_inline_authority`
- `ref_only_authority`
- `skipped_authority`
- `transient_instructions`
- `retrieval_receipts`
- `topology_fallback_reason`
- `contract_health_warnings`
#### What Changed? Nightly Cards
If a nightly run produced zero changes, no card may be rendered. Provide a typed detail payload for the populated case.
#### CIL Health Dashboard
Add three cards:
1. `ContractAdoptionHealthCard`
2. `AuthorityInjectionHealthCard`
3. `RecommendationHealthCard`
```ts
export const ContractAdoptionHealthSchema = z.object({
owner_doc: z.string(),
contract_item_id: z.string(),
status: z.enum(["exists", "partial", "missing", "future"]),
blocks_phase0_value: z.boolean().default(false),
last_checked_revision: z.string().optional(),
});
```
### §14.4 Shadow Gate — universal independent-session floor
Keep per-kind gate modes, but require:
```ts
min_independent_sessions: z.number().int().positive().default(2),
```
### §15.3 CILQueryContextPayload / §15.4 Advisor — typed advisor responses
```ts
export const CILAdvisorResponseSchema = z.object({
response_id: z.string().uuid(),
narrative_markdown: z.string(),
referenced_artifacts: z.array(z.object({
artifact_type: z.enum([
"knowledge_node",
"standing_order",
"signal",
"dispatch_checkpoint",
"profile",
"prompt_artifact_recommendation",
]),
artifact_id: z.string(),
ui_action: z.enum([
"open_browser",
"open_inspector",
"open_spool",
"open_profile",
"open_prompt_preview",
]),
display_label: z.string(),
})).default([]),
degraded_reason_code: z.string().optional(),
confidence_disclaimer: z.string().optional(),
failure: z.object({
code: z.string(),
message: z.string(),
retriable: z.boolean().default(false),
}).optional(),
});
```
### §16.2–§16.6 DocIndex and document access learning — add truth-separation rule
R7 must keep provider truth and topology truth separate in every explanation path. Retrieval receipts explain how something was found; relation/topology payloads explain how it is related.
### §17 Cross-Document Integration Summary — replace with end-state obligation summary
The R7 summary must state, as required for the fully built product:
- DOC1 authority schema adoption and mutation truth
- EC Core operation/compiler/context-plan acceptance
- DOC10 generic advisor transport and UI transport
- DOC11 runtime packet and prompt truth
- DOC12 room-close bundle, active review target, and prompt-plan truth
- DOC14 novelty, review outcome, and prompt-artifact observation export
- DOC8 prompt-artifact lifecycle/evaluation alignment
- DOC6 panel-close and durable-correction controls
- DOC7 hint/support-pack/topology-aware materialization consumption
- DOC18 retrieval receipt pass-through and lane truth
---
## Part III — Endpoint and UI Contract Appendix
### Operation Compiler
```text
POST /api/cil/operation
GET /api/cil/operation/:resolution_id
```
### Knowledge Node Browser / Authority Controls
```text
GET /api/cil/knowledge-nodes
POST /api/cil/knowledge-nodes/action
POST /api/cil/authority/action
GET /api/cil/authority/audit
```
### Advisor
```text
POST /api/cil/advisor/query
```
The transport still routes through the generic advisor dispatcher seam, but DOC15 now owns the typed payload/response family.
### Support Packs
```text
POST /api/cil/support-pack/preview
POST /api/cil/support-pack/apply
POST /api/cil/support-pack/dismiss
```
### Prompt Artifact Recommendations
```text
POST /api/cil/prompt-artifact-recommendations/query
POST /api/cil/prompt-artifact-recommendations/preview
POST /api/cil/prompt-artifact-recommendations/dismiss
```
---
## Part IV — Explicit “Do Not Regress” Notes
1. Do not collapse transient instructions into durable authority.
2. Do not merge retrieval truth and topology truth into one explanation stream.
3. Do not convert user-authored knowledge nodes into authority automatically.
4. Do not flatten all shadow-gate modes to AND.
5. Do not cut Part XV / Part XVI / training/evaluation text simply because companion adoption is still pending.
6. Do not let `prompt_fragment` truncation happen mid-text.
7. Do not silently hide missing companion seams; surface them in contract health and degraded reason codes.
---
## Part V — R7 Acceptance Additions
### Acceptance A — 300 authority records after one year
Given a workspace with 300 durable authority records, only the highest-ranked and scope-matched records may render inline, with the rest downgraded to compact/ref-only/inspector-only according to budget and salience. The inspector and authority audit must show the full set and why each item landed in its lane.
### Acceptance B — old but critical authority persists
A foundational rule created in week one must still appear inline or compact according to its persistence class even if it is old, provided it still matches current scope and has not been revoked.
### Acceptance C — one-off formatting instruction remains transient
An instruction such as “draft this in a Word document, not markdown” created during a single task may appear in `transient_instructions` for that dispatch, but may not create a durable authority record unless explicitly promoted through an approved durable-correction path.
### Acceptance D — prompt fragments are safe under budget pressure
If a `prompt_fragment` exceeds remaining budget, the planner must drop the whole node and log the reason. It may not truncate the text mid-fragment.
### Acceptance E — topology unavailable remains visible
If topology/read-model data is unavailable, support-pack and advisor surfaces must still work in degraded mode and show explicit `degraded_reason_code` values rather than silently pretending topology did not matter.
---
# Part 3 — Merged Revision — Authority Salience, Injection Governance, and Instruction Cooling (Fully Integrated)
## R7.1 Merge Rule
This Part 3 block is normative and governs over any earlier overlapping statement in Parts 1–2 on authority-memory laneing, salience, rendering, auditability, cooling, and related contract-facing output fields.
The goals of this merged revision are explicitly:
- durable authority may grow into the hundreds,
- some early instructions must persist indefinitely,
- one-off instructions must not be treated as globally important,
- relevance must be context-sensitive,
- memory injection must stay bounded,
- and the user must be able to inspect **why** something was injected, cooled, compacted, or skipped.
## §1.3C Authority Volume Must Not Become Prompt Sludge
Durable authority is strong, but strong does **not** mean “always injected in full.” CIL must distinguish between:
- **foundational authority** that is always materially important,
- **scope-matched active authority** that is important in the current context,
- **cold but still valid authority** that should remain inspectable and retrievable,
- and **transient instructions** that apply only to the current operation or session.
CIL must therefore rank and render durable authority for injection using:
- scope fit,
- operation relevance,
- protection/persistence class,
- recent applied/viewed signals,
- repeated skip/trim pressure,
- explicit user pin/foundational status,
- and the current memory-pressure context.
Pure age alone must **not** retire or invalidate durable authority. Age may affect render form, audit flags, and review recommendations, but not durable truth status.
## §2.5A CILConfigSchema — authority salience additions
Add the following fields to `CILConfigSchema` (or preserve the existing field if already present and merge semantics accordingly):
```ts
authority_core_inline_limit: z.number().int().positive().default(6),
authority_scoped_inline_limit: z.number().int().positive().default(8),
authority_ref_only_limit: z.number().int().positive().default(24),
authority_compact_char_limit: z.number().int().positive().default(140),
authority_foundational_inline_char_limit: z.number().int().positive().default(180),
authority_cold_after_days: z.number().int().positive().default(90),
authority_review_stale_after_days: z.number().int().positive().default(180),
authority_skip_cooling_threshold_30d: z.number().int().positive().default(6),
authority_trim_cooling_threshold_30d: z.number().int().positive().default(4),
max_authority_injection_tokens: z.number().int().positive().default(4000),
authority_overflow_behavior: z.enum(["truncate_lowest_priority", "fail_dispatch"]).default("truncate_lowest_priority"),
```
### Normative semantics
- `authority_cold_after_days` changes default render preference and audit state, not durable truth.
- `authority_review_stale_after_days` controls audit recommendations and default review badges.
- `authority_skip_cooling_threshold_30d` and `authority_trim_cooling_threshold_30d` contribute to salience penalties and lane transitions.
- `authority_foundational_inline_char_limit` caps inline render length for foundational records before they fall back to compact rendering.
## §3.3 ContextFactsSchema — additions
Add the following read-only derived hints to `ContextFactsSchema`:
```ts
memory_pressure_level: z.enum(["low", "medium", "high"]).optional(),
active_authority_count: z.number().int().nonnegative().optional(),
```
These are derived execution/planning hints. They are not user-truth facts.
## §4.2 Durable authority schemas — extensions and additions
### 4.2.1 Canonical enums
```ts
export const AuthorityPersistenceClassSchema = z.enum([
"standard",
"protected",
"foundational",
]);
export const AuthorityInjectionPreferenceSchema = z.enum([
"auto",
"inline_full",
"inline_compact",
"ref_preferred",
]);
export const AuthorityInjectionLaneSchema = z.enum([
"core",
"scoped",
"ref_only",
"inspector_only",
]);
export const AuthorityRenderFormSchema = z.enum([
"inline_full",
"inline_compact",
"ref_only",
"inspector_only",
]);
```
### 4.2.2 AuthorityMemoryRecordSchema — required governance fields
Extend `AuthorityMemoryRecordSchema` with:
```ts
display_label: z.string().optional(),
persistence_class: AuthorityPersistenceClassSchema.default("standard"),
injection_preference: AuthorityInjectionPreferenceSchema.default("auto"),
last_user_reconfirmed_at: z.string().datetime().optional(),
review_after_at: z.string().datetime().optional(),
```
#### Normative meaning
- `display_label` is the short human-readable audit/inspector label.
- `persistence_class` governs lane floors, render defaults, and audit expectations.
- `injection_preference` influences render-form choice but never bypasses scope or budget constraints.
- `last_user_reconfirmed_at` records the last explicit user reaffirmation of durable relevance.
- `review_after_at` schedules audit surfacing without downgrading durable truth.
### 4.2.3 AuthoritySalienceBreakdownSchema
```ts
export const AuthoritySalienceBreakdownSchema = z.object({
scope_fit: z.number(),
operation_fit: z.number(),
persistence_bonus: z.number(),
recent_apply_bonus: z.number(),
recent_view_bonus: z.number(),
skip_penalty: z.number(),
trim_penalty: z.number(),
inactivity_penalty: z.number(),
total: z.number().min(0).max(100),
});
```
`total` is a **ranking score**, not a truth score, confidence score, or probability.
### 4.2.4 AuthorityInjectionCandidateSchema
```ts
export const AuthorityInjectionCandidateSchema = z.object({
authority_id: z.string(),
display_label: z.string().optional(),
lane: AuthorityInjectionLaneSchema,
render_form: AuthorityRenderFormSchema,
salience: z.number().min(0).max(100),
salience_breakdown: AuthoritySalienceBreakdownSchema,
why_applies: z.array(z.string()).default([]),
skipped_reason: z.string().optional(),
trimmed_due_to_budget: z.boolean().default(false),
});
```
### 4.2.5 AuthorityHotPathStateSchema
```ts
export const AuthorityHotPathStateSchema = z.object({
authority_id: z.string(),
current_lane: AuthorityInjectionLaneSchema,
current_render_form: AuthorityRenderFormSchema,
hot_path_salience: z.number().min(0).max(100),
salience_breakdown: AuthoritySalienceBreakdownSchema,
recent_apply_count_30d: z.number().int().nonnegative().default(0),
recent_skip_count_30d: z.number().int().nonnegative().default(0),
recent_trim_count_30d: z.number().int().nonnegative().default(0),
recent_origin_view_count_30d: z.number().int().nonnegative().default(0),
cooling_reason: z.string().optional(),
last_recomputed_at: z.string().datetime(),
});
```
### 4.2.6 AuthoritySelectionSummarySchema — upgraded shape
If an earlier version of `AuthoritySelectionSummarySchema` exists, replace or extend it so it includes:
```ts
export const AuthoritySelectionSummarySchema = z.object({
inline_core_ids: z.array(z.string()).default([]),
inline_scoped_ids: z.array(z.string()).default([]),
ref_only_ids: z.array(z.string()).default([]),
inspector_only_ids: z.array(z.string()).default([]),
trimmed_due_to_budget_ids: z.array(z.string()).default([]),
total_candidates: z.number().int().nonnegative().default(0),
overflow_reason: z.string().optional(),
});
```
### 4.2.7 AuthorityApplicationTraceSchema — upgrade
Extend or replace `AuthorityApplicationTraceSchema` so it carries:
```ts
export const AuthorityApplicationTraceSchema = z.object({
authority_id: z.string(),
display_label: z.string().optional(),
scope: z.string(),
persistence_class: AuthorityPersistenceClassSchema,
lane: AuthorityInjectionLaneSchema,
render_form: AuthorityRenderFormSchema,
hot_path_salience: z.number().min(0).max(100),
salience_breakdown: AuthoritySalienceBreakdownSchema,
applied: z.boolean(),
skipped_reason: z.enum([
"scope_mismatch",
"budget_trim",
"cooled_to_ref_only",
"cooled_to_inspector_only",
"suppressed_by_stronger_scope",
"duplicate",
"revoked",
]).optional(),
});
```
### 4.2.8 AuthorityAuditRowSchema / AuthorityAuditSnapshotSchema
```ts
export const AuthorityAuditRowSchema = z.object({
authority_id: z.string(),
display_label: z.string().optional(),
scope: z.string(),
persistence_class: AuthorityPersistenceClassSchema,
injection_preference: AuthorityInjectionPreferenceSchema,
current_lane: AuthorityInjectionLaneSchema,
current_render_form: AuthorityRenderFormSchema,
hot_path_salience: z.number().min(0).max(100),
recent_apply_count_30d: z.number().int().nonnegative(),
recent_skip_count_30d: z.number().int().nonnegative(),
recent_trim_count_30d: z.number().int().nonnegative(),
recent_origin_view_count_30d: z.number().int().nonnegative(),
review_after_at: z.string().datetime().optional(),
suggested_actions: z.array(z.enum([
"rescope",
"set_review_after",
"set_persistence_class",
"set_injection_preference",
"mark_foundational",
"prefer_compact_render",
"set_expiry",
"revoke",
])).default([]),
warnings: z.array(z.string()).default([]),
});
export const AuthorityAuditSnapshotSchema = z.object({
generated_at: z.string().datetime(),
total_authority_records: z.number().int().nonnegative(),
core_count: z.number().int().nonnegative(),
scoped_count: z.number().int().nonnegative(),
ref_only_count: z.number().int().nonnegative(),
inspector_only_count: z.number().int().nonnegative(),
rows: z.array(AuthorityAuditRowSchema),
});
```
## §4.4 Storage layout / derived current views
Add or confirm the following derived-state artifacts:
```text
ELNOR_MEMORY/system/cil/
├── authority_hot_path_state.json
├── authority_audit_snapshot.json
├── authority_audit_history.jsonl
```
These files are **derived views** only. They never replace durable authority truth.
## §4.5 MemoryService / queryMemory() — lane-aware authority return shape
`queryMemory()` (or the equivalent memory-planning path) must return enough information to preserve explainability. At minimum it must expose:
- lane-aware authority records,
- `authority_selection_summary`,
- and `authority_candidates`.
Recommended shape:
```ts
export const MemoryQueryResultSchema = z.object({
authority_records: z.array(AuthorityMemoryRecordSchema).default([]),
authority_candidates: z.array(AuthorityInjectionCandidateSchema).default([]),
authority_selection_summary: AuthoritySelectionSummarySchema.default({}),
heuristic_nodes: z.array(KnowledgeNodeSchema).default([]),
});
```
## §5.3 ContextPlanSchema — authority planning artifacts
Add or confirm:
```ts
authority_selection_summary: AuthoritySelectionSummarySchema.default({}),
authority_candidates: z.array(AuthorityInjectionCandidateSchema).default([]),
```
## §5.8 DispatchCheckpointSchema — authority render plan
Add or confirm:
```ts
authority_selection_summary: AuthoritySelectionSummarySchema.default({}),
authority_render_plan: z.array(AuthorityInjectionCandidateSchema).default([]),
```
The render plan is required so later inspector/advisor flows can explain exactly what happened.
## §7.11 Normative helpers — authority hot-path salience and lane selection
```ts
export function computeAuthorityHotPathSalience(input: {
record: AuthorityMemoryRecord;
state?: z.infer<typeof AuthorityHotPathStateSchema>;
contextFacts: ContextFacts;
cfg: z.infer<typeof CILConfigSchema>;
now?: Date;
}): z.infer<typeof AuthoritySalienceBreakdownSchema> {
const scopeFit = computeScopeFit(input.record, input.contextFacts); // 0..30
const operationFit = computeOperationFit(input.record, input.contextFacts); // 0..20
const persistenceBonus = input.record.persistence_class === "foundational"
? 20
: input.record.persistence_class === "protected"
? 10
: 0;
const recentApplyBonus = Math.min(10, (input.state?.recent_apply_count_30d ?? 0) * 2);
const recentViewBonus = Math.min(5, input.state?.recent_origin_view_count_30d ?? 0);
const skipPenalty = Math.min(15, (input.state?.recent_skip_count_30d ?? 0) * 2);
const trimPenalty = Math.min(10, (input.state?.recent_trim_count_30d ?? 0) * 2);
const inactivityPenalty = computeInactivityPenalty(input.record, input.cfg, input.now);
const total = Math.max(
0,
Math.min(100, scopeFit + operationFit + persistenceBonus + recentApplyBonus + recentViewBonus - skipPenalty - trimPenalty - inactivityPenalty)
);
return {
scope_fit: scopeFit,
operation_fit: operationFit,
persistence_bonus: persistenceBonus,
recent_apply_bonus: recentApplyBonus,
recent_view_bonus: recentViewBonus,
skip_penalty: skipPenalty,
trim_penalty: trimPenalty,
inactivity_penalty: inactivityPenalty,
total,
};
}
export function chooseAuthorityLane(input: {
record: AuthorityMemoryRecord;
salience: z.infer<typeof AuthoritySalienceBreakdownSchema>;
contextFacts: ContextFacts;
}): z.infer<typeof AuthorityInjectionLaneSchema> {
if (input.record.persistence_class === "foundational" && input.salience.total >= 40) return "core";
if (input.salience.total >= 70) return "core";
if (input.salience.total >= 45) return "scoped";
if (input.salience.total >= 20) return "ref_only";
return "inspector_only";
}
export function chooseAuthorityRenderForm(input: {
record: AuthorityMemoryRecord;
lane: z.infer<typeof AuthorityInjectionLaneSchema>;
cfg: z.infer<typeof CILConfigSchema>;
}): z.infer<typeof AuthorityRenderFormSchema> {
if (input.record.injection_preference === "inline_full") return "inline_full";
if (input.record.injection_preference === "inline_compact") return "inline_compact";
if (input.record.injection_preference === "ref_preferred") return input.lane === "ref_only" ? "ref_only" : "inline_compact";
if (input.lane === "core" || input.lane === "scoped") return "inline_compact";
if (input.lane === "ref_only") return "ref_only";
return "inspector_only";
}
```
Cooling changes **lane and render form**, not durable truth.
## §8.1A Lane-Aware Authority Injection
Authority injection must occur in this order:
1. `transient_instructions`
2. foundational/core authority
3. hot scope-matched authority
4. ref-only authority metadata
5. heuristic nodes and remaining plan elements
Under memory pressure:
- trim lowest-salience scoped authority first,
- then demote to ref-only,
- then demote to inspector-only,
- and only fail dispatch if the configured `authority_overflow_behavior` is `fail_dispatch` and the required core/foundational set still cannot fit.
`prompt_fragment` nodes remain subject to the existing whole-node-only rule and may not be truncated mid-fragment.
## §12.4 Context Inspector — authority trace extensions
The inspector response must include four authority groups:
- `authority_inline`
- `authority_ref_only`
- `authority_skipped`
- `transient_instructions`
Each authority row must expose:
- `authority_id`
- `display_label`
- `scope`
- `persistence_class`
- `lane`
- `render_form`
- `hot_path_salience`
- `salience_breakdown`
- `applied`
- `skipped_reason`
- `why_applies`
### UI requirements
- rows show lane and render-form badges,
- a `Why?` affordance opens salience details,
- skipped rows show whether the reason was cooling, budget trim, duplicate suppression, or stronger-scope suppression,
- the inspector must visibly distinguish compact-rendered authority from ref-only authority.
## §13.5A Authority Audit
### API
```text
GET /api/cil/authority/audit
GET /api/cil/authority/audit/snapshot
POST /api/cil/authority/action
```
### Action request schema
```ts
export const AuthorityActionRequestSchema = z.object({
authority_id: z.string(),
action: z.enum([
"rescope",
"revoke",
"set_expiry",
"set_review_after",
"mark_foundational",
"prefer_compact_render",
"set_persistence_class",
"set_injection_preference",
]),
payload: z.record(z.unknown()).default({}),
});
```
### UI requirements
The Authority Audit surface must support:
- loading / empty / populated / degraded / blocked / error states,
- sortable rows,
- filters by scope, persistence class, lane, and warning state,
- row columns for salience, lane, render form, recent apply/skip/trim/view counts,
- row actions for `Why?`, `Rescope`, `Set Review Date`, `Mark Foundational`, `Prefer Compact Render`, `Set Expiry`, and `Revoke`,
- an explicit note that actions which broaden durable scope still follow approval-governed durable-memory write rules.
## §9.5.5 Nightly Phase 5.5 — Authority Hot-Path State Recompute & Audit Snapshot
Add a derived-state nightly step immediately after knowledge governance:
1. read durable authority records,
2. read assembly traces / dispatch checkpoints,
3. read authority telemetry and origin-view telemetry,
4. recompute `authority_hot_path_state.json`,
5. recompute `authority_audit_snapshot.json`,
6. append summary changes to `authority_audit_history.jsonl`.
This phase derives cooling state. It does **not** mutate durable authority truth unless a separate approved authority action is processed.
## §6.x Authority hot-path telemetry inputs
Add the following required telemetry inputs:
- authority selected for consideration
- authority injected inline
- authority rendered compact
- authority emitted as ref-only
- authority trimmed due to budget
- authority skipped because cooled
- authority origin viewed
- authority rescope / revoke / persistence-class / review-date changes
Recommended schema:
```ts
export const AuthorityHotPathTelemetryEventSchema = z.object({
event_id: z.string().uuid(),
authority_id: z.string(),
event_type: z.enum([
"selected",
"inline_full",
"inline_compact",
"ref_only",
"trimmed_due_to_budget",
"skipped_cooled",
"origin_viewed",
"rescope",
"revoke",
"set_persistence_class",
"set_review_after",
]),
dispatch_id: z.string().optional(),
occurred_at: z.string().datetime(),
});
```
## Acceptance additions
### Acceptance F — frequently skipped authority cools to ref-only
A durable authority record that repeatedly matches by broad scope but is skipped or trimmed in narrower contexts must remain valid while cooling to `ref_only` or `inspector_only` according to salience and lane rules.
### Acceptance G — user can repair buildup through Authority Audit
The Authority Audit must let the user identify stale or overbroad authority and submit proposal-path actions for rescope, compact-render preference, foundational promotion, review date, expiry, or revoke.
### Acceptance H — why a record moved lanes is inspectable
For any authority record shown as compact, ref-only, or skipped, the inspector and audit surfaces must be able to show the salience breakdown, lane, render form, and reason code that produced the current outcome.
### Acceptance I — foundational does not mean infinite inline text
A foundational authority record may remain durable indefinitely while still rendering as compact text when its full body exceeds configured inline limits.
### Acceptance J — cooling does not alter durable truth
Cooling may change salience, lane, render form, and audit recommendations. It may not revoke, expire, or change durable truth without a separate authority action.