Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ The IR knows nothing about specific labs, hosts, or operators. `InferSurface::Na

`StandardRuntimeLowerer` is the default lowerer. Applications may implement the `Lowerer` trait for substrate-specific targets without forking this crate.

## LIP-0008 — LLM tier and dossier discipline

The runtime can represent and validate LLM ingress tier × grammar discipline:
Mini/Operator/Translator/Frontier tiers and Operational/Strong/Dossier grammars.

Current support is intentionally narrow: the runtime validates declared ingress
legitimacy and manifest acceptance. It does not call LLMs, dispatch work, or
close evidence.

See the [LIP-0008 spec](https://github.com/LogLine-Foundation/governance/blob/main/lips/LIP-0008-llm-tier-discipline-and-dossier-discipline.md) in the governance repo for the constitutional rule the runtime obeys.

## Where it sits in the LogLine ecosystem

```
Expand Down
7 changes: 5 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
//! Execution is not sovereign: material actions must be semantically admissible,
//! policy-permitted, capability-realizable, and evidentially accountable.
//!
//! LIP-0008 support: represents LLM ingress tiers, grammar kinds, dossiers,
//! and validates declared tier × grammar legitimacy during admissibility.
//!
//! See `docs/runtime/constitutional-runtime.md` for the full definition.

pub mod act_identity;
Expand Down Expand Up @@ -77,8 +80,8 @@ pub use strong_grammar::{
};
pub use tier::{GrammarKind, LlmTier};
pub use validation::{
check_capability, validate_admissibility, validate_capability, validate_policy,
validate_structure, AdmissibilityContext, AdmissibleNode, ValidationError,
check_capability, validate_admissibility, validate_capability, validate_ingress_context,
validate_policy, validate_structure, AdmissibilityContext, AdmissibleNode, ValidationError,
MAX_ROUTE_NESTING_DEPTH,
};

Expand Down
1 change: 1 addition & 0 deletions src/plan_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ mod tests {
runtime_permitted: true,
at_execution_boundary: true,
require_evidence_closure: true,
..Default::default()
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/planning_compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,29 @@ fn validation_error_to_runtime_failure(
reason_code: "capability_unsatisfied".into(),
detail: msg.clone(),
},
// LIP-0008 ingress errors fold into the Validation stage with
// ingress-specific reason codes. Detailed mapping into a dedicated
// RuntimeFailure variant is left for a later PR; preserving the
// back-compat shape keeps this patch minimal.
ValidationError::IncompleteIngressContext { .. } => RuntimeFailure::Validation {
at: "ingress".into(),
field: None,
detail: e.to_string(),
reason_code: "ingress_incomplete".into(),
},
ValidationError::TierGrammarIllegitimate { .. } => RuntimeFailure::Validation {
at: "ingress".into(),
field: None,
detail: e.to_string(),
reason_code: "tier_grammar_illegitimate".into(),
},
ValidationError::NoCapabilityForIngress { .. } => RuntimeFailure::Capability {
primitive: format!("{:?}", PrimitiveName::from_primitive(&node.body)),
kind: primitive_kind(&node.body).map(str::to_owned),
attempted_substrate: None,
reason_code: "no_capability_for_ingress".into(),
detail: e.to_string(),
},
}
}

Expand Down Expand Up @@ -745,6 +768,7 @@ mod tests {
runtime_permitted: true,
at_execution_boundary: true,
require_evidence_closure: true,
..Default::default()
}
}

Expand Down
115 changes: 115 additions & 0 deletions src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::capability::{primitive_kind, CapabilityManifest, PrimitiveName};
use crate::ir::{IRPrimitive, IrNode};
use crate::policy::PolicyClass;
use crate::tier::{GrammarKind, LlmTier};
use serde::{Deserialize, Serialize};
use thiserror::Error;

Expand All @@ -19,6 +20,16 @@ pub struct AdmissibilityContext {
pub at_execution_boundary: bool,
/// If true, substrates must declare `evidence.write` in [`CapabilityManifest::declared_guarantees`].
pub require_evidence_closure: bool,
/// LIP-0008: which LLM tier emitted the ingress that produced this node.
/// `None` together with `ingress_grammar = None` means legacy caller —
/// LIP-0008 checks are skipped. `Some` requires `ingress_grammar` to also
/// be `Some`; half-context is rejected.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ingress_tier: Option<LlmTier>,
/// LIP-0008: which grammar carried the ingress. Must be `Some` whenever
/// `ingress_tier` is `Some` (and vice versa).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ingress_grammar: Option<GrammarKind>,
}

impl Default for AdmissibilityContext {
Expand All @@ -28,6 +39,8 @@ impl Default for AdmissibilityContext {
runtime_permitted: true,
at_execution_boundary: true,
require_evidence_closure: true,
ingress_tier: None,
ingress_grammar: None,
}
}
}
Expand All @@ -46,6 +59,32 @@ pub enum ValidationError {
Policy(String),
#[error("capability: {0}")]
Capability(String),
/// LIP-0008: ingress context carries one side of (tier, grammar) but not
/// the other. Legacy callers leave both `None`; LIP-0008-aware callers
/// must set both.
#[error(
"ingress: incomplete LIP-0008 context (tier_present={ingress_tier_present}, grammar_present={ingress_grammar_present}); set both or neither"
)]
IncompleteIngressContext {
ingress_tier_present: bool,
ingress_grammar_present: bool,
},
/// LIP-0008: constitutional rule violated — this grammar does not admit
/// this tier per the matrix in `GrammarKind::admits`.
#[error("ingress: grammar {grammar:?} does not admit tier {tier:?} per LIP-0008")]
TierGrammarIllegitimate { tier: LlmTier, grammar: GrammarKind },
/// LIP-0008: no capability manifest accepts the declared ingress
/// (tier, grammar) for this primitive. At least one manifest must allow
/// the tier (or have `allowed_ingress_tiers = None`, treated as legacy
/// permissive) AND allow the grammar (same legacy rule for `None`).
#[error(
"ingress: no capability manifest accepts ingress tier={tier:?} grammar={grammar:?} for primitive {primitive:?}"
)]
NoCapabilityForIngress {
primitive: PrimitiveName,
tier: Option<LlmTier>,
grammar: Option<GrammarKind>,
},
}

fn max_nesting_depth(prim: &IRPrimitive) -> usize {
Expand Down Expand Up @@ -239,6 +278,7 @@ pub fn validate_capability(
));
}
let mut any = false;
let mut any_realizable = false;
for m in manifests {
if !m.can_realize(&node.body) {
continue;
Expand All @@ -249,10 +289,25 @@ pub fn validate_capability(
if !m.evidence_realizable(ctx.require_evidence_closure) {
continue;
}
any_realizable = true;
if !manifest_accepts_ingress(m, ctx) {
continue;
}
any = true;
break;
}
if !any {
// If at least one manifest could realize the primitive but none
// accepted the declared ingress, surface the LIP-0008-specific
// error so callers can distinguish "wrong substrate" from
// "no substrate" and from "ingress mismatch".
if any_realizable && (ctx.ingress_tier.is_some() || ctx.ingress_grammar.is_some()) {
return Err(ValidationError::NoCapabilityForIngress {
primitive: PrimitiveName::from_primitive(&node.body),
tier: ctx.ingress_tier,
grammar: ctx.ingress_grammar,
});
}
return Err(ValidationError::Capability(format!(
"no substrate can realize {:?} with kind={:?} and evidence requirements (manifests checked: {})",
PrimitiveName::from_primitive(&node.body),
Expand All @@ -271,10 +326,70 @@ pub fn validate_admissibility(
) -> Result<AdmissibleNode, ValidationError> {
validate_structure(node)?;
validate_policy(node, ctx)?;
validate_ingress_context(ctx)?;
validate_capability(node, manifests, ctx)?;
Ok(AdmissibleNode { node: node.clone() })
}

/// LIP-0008: enforce the constitutional matrix on ingress tier × grammar.
///
/// - `(None, None)` is the legacy / unspecified path; LIP-0008 checks are
/// skipped entirely.
/// - `(Some, Some)` triggers `GrammarKind::admits`.
/// - `(Some, None)` and `(None, Some)` are rejected as half-context.
pub fn validate_ingress_context(ctx: &AdmissibilityContext) -> Result<(), ValidationError> {
match (&ctx.ingress_tier, &ctx.ingress_grammar) {
(None, None) => Ok(()),
(Some(tier), Some(grammar)) => {
if grammar.admits(tier) {
Ok(())
} else {
Err(ValidationError::TierGrammarIllegitimate {
tier: *tier,
grammar: *grammar,
})
}
}
(tier, grammar) => Err(ValidationError::IncompleteIngressContext {
ingress_tier_present: tier.is_some(),
ingress_grammar_present: grammar.is_some(),
}),
}
}

/// LIP-0008 per-manifest ingress filter.
///
/// A manifest **accepts** the ingress pair if, for each of `ingress_tier`
/// and `ingress_grammar` that the context declares, either (a) the manifest
/// declared `None` (legacy, treated as permissive) or (b) the manifest's
/// declared set contains the value. Context with `None` on a side never
/// constrains the manifest on that side.
///
/// DOCTRINAL TODO (resolved when LIP-0008 transitions Proposed → Accepted):
/// while the LIP is Proposed, `allowed_*: None` on a manifest is treated
/// as legacy permissive — it accepts any ingress so callers can migrate.
/// When LIP-0008 is Accepted, the foundation must decide whether `None`
/// stays permissive (formal migration window) or becomes a Ghost /
/// validation error requiring explicit declaration. Legacy permissiveness
/// is a bridge, not a final constitution.
fn manifest_accepts_ingress(m: &CapabilityManifest, ctx: &AdmissibilityContext) -> bool {
if let Some(tier) = &ctx.ingress_tier {
if let Some(allowed) = &m.allowed_ingress_tiers {
if !allowed.contains(tier) {
return false;
}
}
}
if let Some(grammar) = &ctx.ingress_grammar {
if let Some(allowed) = &m.allowed_grammars {
if !allowed.contains(grammar) {
return false;
}
}
}
true
}

/// Back-compat: single-manifest capability check.
pub fn check_capability(
manifest: &CapabilityManifest,
Expand Down
Loading