Plan IR v1.1 Specification
1. **Policy Hashing**: Replace inline policy configs with pre-computed hash references 2. **Compile-Time Type Checking**: Enforce binding type compatibility during compilation 3. **ForEachAnchor**: Atlas generation via anchor iteration 4. **SelectAnchor**: Single anchor extraction from sets 5. **Evidence Authority Gating**: Prevent lifecycle promotions from simulated evidence 6. **Slice Provenance Chain**: Propagate source_slice_id through downstream bindings
Full Public Reader
Plan IR v1.1 Specification
Version: 1.1.0
Status: Provisional
Effective Date: 2026-01-01
Supersedes: Plan IR v1.0
---
1. Overview
Plan IR v1.1 extends the base Plan Intermediate Representation with six critical enhancements:
1. Policy Hashing: Replace inline policy configs with pre-computed hash references
2. Compile-Time Type Checking: Enforce binding type compatibility during compilation
3. ForEachAnchor: Atlas generation via anchor iteration
4. SelectAnchor: Single anchor extraction from sets
5. Evidence Authority Gating: Prevent lifecycle promotions from simulated evidence
6. Slice Provenance Chain: Propagate source_slice_id through downstream bindings
These changes transform the Plan IR from a narrative description into a compilable, fingerprintable, replayable, auditable execution contract.
---
2. Motivation
2.1 Float Serialization Instability
Problem: Policy configurations contain floating-point values (e.g., `distance_decay: 0.9`). JSON serialization of floats can produce different representations across platforms, causing non-deterministic plan fingerprints.
Solution: Pre-compute policy hashes at registration time. Plans reference policies by `(policy_id, params_hash)` instead of embedding the full config.
2.2 Type Safety Gap
Problem: v1.0 allowed connecting incompatible step outputs to inputs (e.g., feeding Atoms to a step expecting Slice). Errors only surfaced at runtime.
Solution: Introduce `BindingType` enum and compile-time validation of producer-consumer relationships.
2.3 Atlas Generation
Problem: No native way to iterate over anchors and generate per-anchor artifacts (slices, atoms, realizations).
Solution: Add `ForEachAnchor` step that executes a sub-plan for each anchor, bundling results.
2.4 Evidence Authority
Problem: Simulated evidence could accidentally trigger lifecycle promotions (Proto→Provisional→Canonical), corrupting the vocabulary ledger.
Solution: Add `EvidenceAuthority` gating to block promotions unless evidence is `KernelMeasured`.
2.5 Provenance Gaps
Problem: Downstream bindings (Atoms, Proposals, Observations, Realizations) lost connection to their originating slice, making audit trails incomplete.
Solution: Propagate `source_slice_id` through the entire pipeline.
---
3. Binding Type System
3.1 BindingType Enum
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BindingType {
Snapshot, // Graph state capture
Anchors, // List of anchor IDs
Anchor, // Single anchor ID
Slice, // Context slice
Atoms, // Extracted atoms
Proposals, // Operator sequence proposals
Observations, // Evidence observations
Realizations, // Realized N'Ko forms
Bundle, // Artifact bundle
Custom, // Extensible type
}3.2 Type Compatibility Matrix
| Step Kind | Input Type Required | Output Type Produced |
|---|---|---|
| Snapshot | — | Snapshot |
| SelectAnchors | Snapshot | Anchors |
| SelectAnchor | Anchors | Anchor |
| Slice | Anchor (or Anchors*) | Slice |
| Atomize | Slice | Atoms |
| Propose | Atoms | Proposals |
| Observe | Proposals | Observations |
| Realize | Proposals | Realizations |
| Bundle | Any | Bundle |
| SubPlan | Varies | Varies |
| ForEachAnchor | Anchors | Bundle |
*Slice step accepts Anchors for backward compatibility (uses first anchor).
3.3 Compile-Time Validation
During `Plan::compile()`:
for step in &self.steps {
for (ref_name, expected_type) in step.kind.input_type_requirements() {
if let Some(producer_id) = binding_producers.get(ref_name) {
if let Some(producer) = self.get_step(producer_id) {
let actual_type = producer.kind.output_type();
if actual_type != Some(expected_type) {
return Err(CompileError::TypeMismatch {
step: step.id.clone(),
binding: ref_name.to_string(),
expected: expected_type,
actual: actual_type,
});
}
}
}
}
}---
4. Policy Reference System
4.1 PolicyRef Structure
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PolicyRef {
/// Policy type identifier (e.g., "slice_policy_v1")
pub policy_id: String,
/// xxHash64 of canonical policy JSON
pub params_hash: String,
}4.2 Policy Registration
Policies are registered once and referenced by hash:
impl PolicyRef {
pub fn from_policy<P: Serialize>(policy_id: &str, policy: &P) -> Self {
Self {
policy_id: policy_id.to_string(),
params_hash: canonical_hash_hex(policy),
}
}
pub fn from_slice_policy(policy: &SlicePolicyV1) -> Self {
Self::from_policy("slice_policy_v1", policy)
}
pub fn from_anchor_policy(policy: &AnchorSelectorConfig) -> Self {
Self::from_policy("anchor_selector_v1", policy)
}
}4.3 Step Kind Updates
Steps that use policies include optional `policy_ref`:
StepKind::SelectAnchors {
snapshot_ref: String,
policy: AnchorSelectorConfig,
policy_ref: Option<PolicyRef>, // v1.1
output_binding: String,
}
StepKind::Slice {
anchor_ref: String,
policy: SlicePolicyV1,
policy_ref: Option<PolicyRef>, // v1.1
output_binding: String,
}4.4 Determinism Guarantee
When `policy_ref` is present, the plan fingerprint uses the hash instead of serializing the policy, ensuring cross-platform determinism.
---
5. New Step Kinds
5.1 SelectAnchor
Select a single anchor from an anchors set.
StepKind::SelectAnchor {
/// Reference to anchors binding
anchors_ref: String,
/// How to select the anchor
selector: AnchorSelector,
/// Name for the anchor binding
output_binding: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnchorSelector {
Index(usize), // Select by 0-based index
First, // Select first anchor
Last, // Select last anchor
Random { seed: u64 }, // Deterministic pseudo-random
HighestSalience, // Query store for salience
}Type Signature: `Anchors → Anchor`
5.2 ForEachAnchor
Execute a sub-plan for each anchor in a set (atlas generation).
StepKind::ForEachAnchor {
/// Reference to anchors binding
anchors_ref: String,
/// Sub-plan to execute for each anchor
/// Receives "current_anchor" as Anchor type
body: Box<Plan>,
/// Name for the bundle output
output_binding: String,
}Type Signature: `Anchors → Bundle`
Execution Model:
1. For each `anchor_id` in anchors:
a. Create isolated context with `current_anchor` binding
b. Execute body plan recursively
c. Collect per-anchor outputs
2. Bundle all outputs with iteration index metadata
Namespace Isolation: Each iteration gets a fresh binding namespace. The body plan cannot see parent bindings except those explicitly passed.
---
6. Evidence Authority Gating
6.1 EvidenceAuthority Enum
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EvidenceAuthority {
/// Allow all operations regardless of evidence source.
/// Use for development, testing, or exploratory runs.
Permissive,
/// Allow observations but block lifecycle promotions from simulation.
/// Realize steps that would cause Proto→Provisional or Provisional→Canonical
/// transitions will fail unless evidence is KernelMeasured.
PromotionGated,
/// Block any lifecycle-affecting operations unless evidence is KernelMeasured.
/// Most restrictive mode for production runs.
Strict,
}6.2 ExecutorConfig Integration
pub struct ExecutorConfig {
pub batch_id: String,
pub evidence_mode: EvidenceSource,
pub continue_on_failure: bool,
pub evidence_authority: EvidenceAuthority, // v1.1
}6.3 Authority Checking
impl EvidenceAuthority {
pub fn allows_promotion(&self, evidence_source: &EvidenceSource) -> bool {
match self {
EvidenceAuthority::Permissive => true,
EvidenceAuthority::PromotionGated | EvidenceAuthority::Strict => {
matches!(evidence_source, EvidenceSource::KernelMeasured)
}
}
}
pub fn allows_observation(&self, evidence_source: &EvidenceSource) -> bool {
match self {
EvidenceAuthority::Permissive | EvidenceAuthority::PromotionGated => true,
EvidenceAuthority::Strict => {
matches!(evidence_source, EvidenceSource::KernelMeasured)
}
}
}
}6.4 Gating Points
| Step | Check Required |
|---|---|
| Observe | `allows_observation()` |
| Realize (with promotion mode) | `allows_promotion()` |
---
7. Slice Provenance Chain
7.1 Updated BindingValue Variants
pub enum BindingValue {
// ... existing variants ...
/// Atomization result (v1.1: includes provenance).
Atoms {
atom_ids: Vec<String>,
atom_count: usize,
source_slice_id: String, // v1.1
},
/// Operator proposals (v1.1: includes provenance).
Proposals {
proposal_ids: Vec<String>,
proposal_count: usize,
source_slice_id: String, // v1.1
},
/// Observation results (v1.1: includes provenance).
Observations {
request_ids: Vec<String>,
observation_count: usize,
source_slice_id: String, // v1.1
},
/// Realized forms (v1.1: includes provenance).
Realizations {
form_ids: Vec<String>,
realization_count: usize,
source_slice_id: String, // v1.1
},
}7.2 Provenance Accessor
impl BindingValue {
pub fn source_slice_id(&self) -> Option<&str> {
match self {
BindingValue::Slice { slice_id, .. } => Some(slice_id),
BindingValue::Atoms { source_slice_id, .. } => Some(source_slice_id),
BindingValue::Proposals { source_slice_id, .. } => Some(source_slice_id),
BindingValue::Observations { source_slice_id, .. } => Some(source_slice_id),
BindingValue::Realizations { source_slice_id, .. } => Some(source_slice_id),
_ => None,
}
}
}7.3 Propagation Flow
Slice{slice_id: "abc123"}
↓ Atomize
Atoms{source_slice_id: "abc123"}
↓ Propose
Proposals{source_slice_id: "abc123"}
↓ Observe / Realize
Observations{source_slice_id: "abc123"}
Realizations{source_slice_id: "abc123"}---
8. Compile Errors
8.1 New Error Variants
#[derive(Debug, Error)]
pub enum CompileError {
// ... existing variants ...
/// v1.1: Type mismatch between step input and producer output.
#[error("Step '{step}' expects binding '{binding}' to be {expected}, got {actual:?}")]
TypeMismatch {
step: StepId,
binding: String,
expected: BindingType,
actual: Option<BindingType>,
},
/// v1.1: Binding collision in SubPlan namespace.
#[error("Binding collision in SubPlan: '{binding}' exists in both parent and child")]
BindingCollision { binding: String },
}---
9. Migration Guide
9.1 From v1.0 to v1.1
1. Add policy_ref to policy-using steps (optional but recommended):
// Before
StepKind::Slice { anchor_ref, policy, output_binding }
// After
StepKind::Slice {
anchor_ref,
policy,
policy_ref: Some(PolicyRef::from_slice_policy(&policy)),
output_binding
}2. Update BindingValue consumers to handle new `source_slice_id` field:
// Before
BindingValue::Atoms { atom_ids, atom_count }
// After
BindingValue::Atoms { atom_ids, atom_count, source_slice_id }3. Add EvidenceAuthority to ExecutorConfig:
// Before
ExecutorConfig::with_batch_id("BATCH-001")
// After (production)
ExecutorConfig::production("BATCH-001")
// or
ExecutorConfig::with_batch_id("BATCH-001")
.with_evidence_authority(EvidenceAuthority::PromotionGated)9.2 Breaking Changes
- `BindingValue::Atoms`, `Proposals`, `Observations`, `Realizations` require `source_slice_id`
- Plans using type-incompatible bindings will fail at compile time
9.3 Backward Compatibility
- Plans without `policy_ref` continue to work (hash computed at execution time)
- `SelectAnchor` and `ForEachAnchor` are additive (new step kinds)
- Default `EvidenceAuthority` is `PromotionGated` (safe default)
---
10. Examples
10.1 Atlas Generation Plan
let atlas_plan = Plan::new("atlas", "Generate atlas for all anchors")
.add_step(Step::new("snapshot", "Capture graph", StepKind::Snapshot {
output_binding: "graph_snapshot".to_string(),
}))
.add_step(Step::new("select", "Select anchors", StepKind::SelectAnchors {
snapshot_ref: "graph_snapshot".to_string(),
policy: AnchorSelectorConfig::default(),
policy_ref: Some(PolicyRef::from_anchor_policy(&AnchorSelectorConfig::default())),
output_binding: "all_anchors".to_string(),
}))
.add_step(Step::new("atlas", "Generate per-anchor slices", StepKind::ForEachAnchor {
anchors_ref: "all_anchors".to_string(),
body: Box::new(
Plan::new("per_anchor", "Per-anchor processing")
.add_step(Step::new("slice", "Slice context", StepKind::Slice {
anchor_ref: "current_anchor".to_string(),
policy: SlicePolicyV1::default(),
policy_ref: Some(PolicyRef::from_slice_policy(&SlicePolicyV1::default())),
output_binding: "context_slice".to_string(),
}))
.add_step(Step::new("atomize", "Extract atoms", StepKind::Atomize {
slice_ref: "context_slice".to_string(),
output_binding: "atoms".to_string(),
}))
),
output_binding: "atlas_bundle".to_string(),
}))
.compile()
.expect("Should compile");10.2 Production Execution with Authority Gating
let config = ExecutorConfig::production("PROD-2026-001");
// evidence_mode: KernelMeasured
// evidence_authority: PromotionGated
let mut executor = PlanExecutor::new(store, content_provider, config);
let ctx = executor.execute(&plan)?;
// Realize steps with promotion will succeed because evidence is KernelMeasured10.3 Type-Safe Plan
let plan = Plan::new("typed", "Type-checked plan")
.add_step(Step::new("s1", "Snapshot", StepKind::Snapshot {
output_binding: "snap".to_string(),
}))
.add_step(Step::new("s2", "Select", StepKind::SelectAnchors {
snapshot_ref: "snap".to_string(), // Expects Snapshot ✓
policy: AnchorSelectorConfig::default(),
policy_ref: None,
output_binding: "anchors".to_string(),
}))
.add_step(Step::new("s3", "Pick one", StepKind::SelectAnchor {
anchors_ref: "anchors".to_string(), // Expects Anchors ✓
selector: AnchorSelector::First,
output_binding: "anchor".to_string(),
}))
.add_step(Step::new("s4", "Slice", StepKind::Slice {
anchor_ref: "anchor".to_string(), // Expects Anchor ✓
policy: SlicePolicyV1::default(),
policy_ref: None,
output_binding: "slice".to_string(),
}))
.compile()
.expect("Type checking passes");---
11. Invariants
11.1 Type Safety Invariant
INVARIANT: ∀ step ∈ compiled_plan:
∀ (ref, expected_type) ∈ step.input_type_requirements():
producer(ref).output_type() == Some(expected_type)11.2 Provenance Invariant
INVARIANT: ∀ binding ∈ {Atoms, Proposals, Observations, Realizations}:
binding.source_slice_id ≠ ""11.3 Authority Invariant
INVARIANT: evidence_authority ∈ {PromotionGated, Strict} ∧
evidence_mode == BridgeSimulation ⟹
realize_with_promotion() fails11.4 ForEachAnchor Isolation Invariant
INVARIANT: ∀ iteration ∈ ForEachAnchor:
iteration.bindings ∩ parent.bindings == ∅
(except explicitly passed inputs)---
12. Schema Version
pub const PLAN_SCHEMA_VERSION: &str = "1.1.0";Plans created with v1.1 will have:
{
"schema_version": "1.1.0",
...
}---
13. Validation Checklist
Before executing a v1.1 plan:
- [ ] `schema_version == "1.1.0"`
- [ ] All binding references resolve to valid producers
- [ ] All input types match producer output types
- [ ] `policy_ref.params_hash` matches registered policy (if present)
- [ ] `evidence_authority` is appropriate for the use case
- [ ] ForEachAnchor body plans have no external binding dependencies
---
14. Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.1.0 | 2026-01-01 | Agent | Initial v1.1 specification |
---
15. Related Documents
- [BRIDGE_CHARTER.md](BRIDGE_CHARTER.md) - Authority boundary specification
- [SLICE_POLICY_V1_SPEC.md](../../cc-graph-kernel/docs/SLICE_POLICY_V1_SPEC.md) - Slice policy specification
- [PROJECT_CHARTER.md](../../cc-semantic-language/docs/PROJECT_CHARTER.md) - Semantic kernel charter
- [INVARIANTS.md](../../cc-semantic-language/docs/INVARIANTS.md) - System invariants
Promotion Decision
Attach run IDs, datasets, metrics, and reproduction commands.
Source Anchor
Comp-Core/core/semantic/cc-cognitivetwin-bridge/docs/PLAN_IR_V1_1_SPEC.md
Detected Structure
Evaluation · References · Architecture