mod artifact;
mod checkpoint;
mod codec;
mod completion;
mod ledger;
mod model;
mod surface;
use std::fmt;
use lgwks_std::hash::{Digest, Hasher};
pub use artifact::{
ArtifactError, ArtifactKey, ArtifactStore, MAX_ARTIFACT_BYTES, MAX_ARTIFACTS_PER_TENANT,
WriteOutcome,
};
pub use checkpoint::{
Checkpoint, CheckpointError, Correction, CorrectionKind, EffectNote, EffectNoteKind,
MAX_CHECKPOINT_EVIDENCE, MAX_CHECKPOINT_NOTES, MAX_CHECKPOINT_STEPS,
};
pub use codec::{Declared, Decoder, Plan, PlanLimits, Wanted, payload_digest};
pub use completion::{Completion, CompletionKind, CompletionOutcome, Coverage, MAX_EVIDENCE_REFS};
pub use ledger::{Intervention, LedgerLimits, RepairLedger};
pub use model::StubModel;
pub use surface::{Operation, Surface, SurfaceBuilder, SurfaceError};
pub const MAX_FIELD_NAME_BYTES: usize = 32;
pub const MAX_PLAN_BYTES: usize = 64 * 1024;
pub const MAX_FIELD_BYTES: usize = 8 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Source {
Model,
ToolOutput,
Document,
}
impl Source {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Model => "model",
Self::ToolOutput => "tool-output",
Self::Document => "document",
}
}
}
impl fmt::Display for Source {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.label())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Provenance {
source: Source,
tenant: String,
digest: Digest,
bytes: usize,
}
impl Provenance {
#[must_use]
pub fn of(source: Source, tenant: &str, bytes: &[u8]) -> Self {
Self {
source,
tenant: tenant.to_owned(),
digest: lgwks_std::hash::blake3(bytes),
bytes: bytes.len(),
}
}
#[must_use]
pub fn for_model(tenant: &str, bytes: &[u8]) -> Self {
Self::of(Source::Model, tenant, bytes)
}
#[must_use]
pub const fn source(&self) -> Source {
self.source
}
#[must_use]
pub fn tenant(&self) -> &str {
&self.tenant
}
#[must_use]
pub const fn digest(&self) -> &Digest {
&self.digest
}
#[must_use]
pub fn digest_hex(&self) -> String {
self.digest.to_hex()
}
#[must_use]
pub const fn bytes(&self) -> usize {
self.bytes
}
}
impl fmt::Display for Provenance {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"{} from {}, {} bytes, digest {}",
self.source.label(),
self.tenant,
self.bytes,
self.digest.to_hex()
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Refusal {
Oversized {
got: usize,
limit: usize,
},
Malformed {
cause: &'static str,
at: usize,
},
Limit {
what: &'static str,
got: u64,
limit: u64,
},
Incomplete {
at: usize,
},
UnknownOperation {
name: String,
},
CapabilityNotHeld {
operation: String,
required: String,
},
InstallTool {
name: String,
},
CredentialRead {
what: String,
},
SandboxEscape {
field: &'static str,
target: String,
},
NotEvidenced {
named: Vec<String>,
missing: usize,
},
Empty,
}
impl Refusal {
#[must_use]
pub const fn is_privilege_attempt(&self) -> bool {
matches!(
self,
Self::InstallTool { .. }
| Self::CredentialRead { .. }
| Self::CapabilityNotHeld { .. }
| Self::UnknownOperation { .. }
| Self::SandboxEscape { .. }
)
}
#[must_use]
pub const fn label(&self) -> &'static str {
match *self {
Self::Oversized { .. } => "Oversized",
Self::Malformed { .. } => "Malformed",
Self::Limit { .. } => "Limit",
Self::Incomplete { .. } => "Incomplete",
Self::UnknownOperation { .. } => "UnknownOperation",
Self::CapabilityNotHeld { .. } => "CapabilityNotHeld",
Self::InstallTool { .. } => "InstallTool",
Self::CredentialRead { .. } => "CredentialRead",
Self::SandboxEscape { .. } => "SandboxEscape",
Self::NotEvidenced { .. } => "NotEvidenced",
Self::Empty => "Empty",
}
}
#[must_use]
pub fn install_tool(name: &str) -> Self {
Self::InstallTool {
name: name.to_owned(),
}
}
#[must_use]
pub fn credential_read(what: &str) -> Self {
Self::CredentialRead {
what: what.to_owned(),
}
}
#[must_use]
pub fn escape(field: &'static str, target: &str) -> Self {
Self::SandboxEscape {
field,
target: target.to_owned(),
}
}
}
impl fmt::Display for Refusal {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Oversized { ref got, ref limit } => {
write!(
formatter,
"Oversized: {got} bytes exceeds the {limit}-byte ceiling"
)
}
Self::Malformed { ref cause, ref at } => {
write!(formatter, "Malformed: {cause} at byte {at}")
}
Self::Limit {
ref what,
ref got,
ref limit,
} => {
write!(formatter, "Limit: {what} is {got}, outside 1..={limit}")
}
Self::Incomplete { ref at } => write!(
formatter,
"Incomplete: the payload was cut short at byte {at}, so it is not a complete document"
),
Self::UnknownOperation { ref name } => {
write!(formatter, "UnknownOperation: {name:?} is not registered")
}
Self::CapabilityNotHeld {
ref operation,
ref required,
} => write!(
formatter,
"CapabilityNotHeld: {operation:?} needs {required:?}, which this run does not hold"
),
Self::InstallTool { ref name } => {
write!(
formatter,
"InstallTool: a proposal may not install {name:?}"
)
}
Self::CredentialRead { ref what } => {
write!(
formatter,
"CredentialRead: a proposal may not read {what:?}"
)
}
Self::SandboxEscape {
ref field,
ref target,
} => write!(
formatter,
"SandboxEscape: {field} = {target:?} reaches outside this run's boundary"
),
Self::NotEvidenced {
ref named,
ref missing,
} => write!(
formatter,
"NotEvidenced: {} of the {} evidence references the claim named are absent",
missing,
named.len()
),
Self::Empty => formatter.write_str("Empty: the proposal names nothing to do"),
}
}
}
impl std::error::Error for Refusal {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Outcome {
Admitted {
plan: Plan,
provenance: Provenance,
},
Refused {
refusal: Box<Refusal>,
provenance: Provenance,
},
Intervention(Intervention),
}
impl Outcome {
#[must_use]
pub const fn is_admitted(&self) -> bool {
matches!(self, Self::Admitted { .. })
}
#[must_use]
pub const fn plan(&self) -> Option<&Plan> {
match *self {
Self::Admitted { ref plan, .. } => Some(plan),
Self::Refused { .. } | Self::Intervention(_) => None,
}
}
#[must_use]
pub fn refusal(&self) -> Option<&Refusal> {
match *self {
Self::Refused { ref refusal, .. } => Some(refusal.as_ref()),
Self::Admitted { .. } | Self::Intervention(_) => None,
}
}
#[must_use]
pub const fn provenance(&self) -> Option<&Provenance> {
match *self {
Self::Admitted { ref provenance, .. } | Self::Refused { ref provenance, .. } => {
Some(provenance)
}
Self::Intervention(_) => None,
}
}
#[must_use]
pub const fn intervention(&self) -> Option<&Intervention> {
match *self {
Self::Intervention(ref intervention) => Some(intervention),
Self::Admitted { .. } | Self::Refused { .. } => None,
}
}
fn refused(provenance: Provenance, refusal: Refusal) -> Self {
Self::Refused {
refusal: Box::new(refusal),
provenance,
}
}
}
impl fmt::Display for Outcome {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Admitted {
ref plan,
ref provenance,
} => write!(formatter, "admitted {plan} (from {provenance})"),
Self::Refused {
ref refusal,
ref provenance,
} => write!(formatter, "{refusal} (from {provenance})"),
Self::Intervention(ref intervention) => write!(formatter, "{intervention}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PlanBudget {
remaining: u32,
ceiling: u32,
}
impl PlanBudget {
#[must_use]
pub const fn new(ceiling: u32) -> Self {
Self {
remaining: ceiling,
ceiling,
}
}
#[must_use]
pub const fn remaining(&self) -> u32 {
self.remaining
}
#[must_use]
pub const fn ceiling(&self) -> u32 {
self.ceiling
}
#[must_use]
pub const fn is_spent(&self) -> bool {
self.remaining == 0
}
pub fn charge(&mut self) -> Result<(), Refusal> {
match self.remaining.checked_sub(1) {
Some(left) => {
self.remaining = left;
Ok(())
}
None => Err(Refusal::Limit {
what: "the plan budget for this step",
got: u64::from(self.ceiling).saturating_add(1),
limit: u64::from(self.ceiling),
}),
}
}
pub fn admit_once(
&mut self,
decoder: &Decoder,
surface: &Surface,
payload: &[u8],
source: Source,
) -> Result<Outcome, Refusal> {
self.charge()?;
Ok(decoder.decode(surface, payload, source))
}
}
fn framed_digest(label: &str, bytes: &[u8]) -> Digest {
let mut hasher = Hasher::new();
hasher.write_framed(label.as_bytes()).write_framed(bytes);
hasher.finalize()
}