Skip to main content

okf_core/
computation.rs

1//! Attested Computation concepts.
2//!
3//! An Attested Computation carries not just what a value *means* but a
4//! sanctioned way to *compute* it, so a consumer can confirm an agent ran the
5//! blessed computation instead of improvising its own. Provenance
6//! answers "where did this claim come from"; attestation answers "was this
7//! number produced the way we said it must be."
8//!
9//! ```markdown
10//! ---
11//! type: Attested Computation
12//! runtime: bigquery
13//! parameters:
14//!   - { name: year, type: integer, required: true }
15//! executor:
16//!   resource: references/skills/run-on-bq.md
17//!   receipt: [job_id, executed_sql, result]
18//! attester:
19//!   resource: references/attesters/revenue.py
20//! ---
21//!
22//! # Computation
23//!
24//!     SELECT SUM(amount) AS revenue
25//!     FROM finance.recognized_revenue
26//!     WHERE fiscal_year = @year
27//! ```
28//!
29//! **This crate records and checks the contract; it never executes anything.**
30//! Running the computation, producing a receipt, and running the attester over
31//! that receipt are consumer-side concerns, and the runtime artifacts they
32//! produce are explicitly *not* stored in the bundle. What
33//! [`AttestedComputation`] gives you is the contract in typed form: the
34//! `runtime` that defines what `parameters` mean, the computation itself
35//! (inline or by path), and the executor/attester interfaces.
36
37use crate::links;
38use crate::yaml::Value;
39use std::fmt;
40
41/// The `type` value that marks a concept as an Attested Computation.
42pub const ATTESTED_COMPUTATION_TYPE: &str = "Attested Computation";
43
44/// The conventional body heading that introduces an inline computation.
45pub const COMPUTATION_HEADING: &str = "Computation";
46
47/// One typed, named hole an agent may fill.
48///
49/// Binding semantics follow `runtime`: the same entry is a SQL bind variable, a
50/// dbt var, or a Python argument depending on it. An agent may supply only
51/// *values* for declared parameters; it must not author or edit the
52/// computation.
53#[derive(Clone, Debug, Default, PartialEq, Eq)]
54pub struct Parameter {
55    /// The parameter name.
56    pub name: Option<String>,
57    /// The parameter's declared type, e.g. `integer`, `string`.
58    pub type_: Option<String>,
59    /// Whether a value must be supplied.
60    pub required: Option<bool>,
61}
62
63impl Parameter {
64    /// Reads one `parameters` entry. Returns `None` when it is not a mapping.
65    pub fn from_value(value: &Value) -> Option<Self> {
66        let map = value.as_mapping()?;
67        Some(Self {
68            name: map.get("name").and_then(Value::as_display_string),
69            type_: map.get("type").and_then(Value::as_display_string),
70            required: map.get("required").and_then(Value::as_bool),
71        })
72    }
73
74    /// Reads the whole `parameters` list.
75    pub fn list_from_value(value: &Value) -> Vec<Self> {
76        match value {
77            Value::Sequence(items) => items.iter().filter_map(Self::from_value).collect(),
78            Value::Mapping(_) => Self::from_value(value).into_iter().collect(),
79            _ => Vec::new(),
80        }
81    }
82
83    /// `true` when the parameter is explicitly marked required.
84    #[must_use]
85    pub fn is_required(&self) -> bool {
86        self.required.unwrap_or(false)
87    }
88}
89
90impl fmt::Display for Parameter {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        write!(f, "{}", self.name.as_deref().unwrap_or("(unnamed)"))?;
93        if let Some(t) = &self.type_ {
94            write!(f, ": {t}")?;
95        }
96        if self.is_required() {
97            f.write_str(" (required)")?;
98        }
99        Ok(())
100    }
101}
102
103/// How the computation is run.
104///
105/// `resource` names run instructions or code that a runner (an agent, or
106/// deterministic consumer code) follows. `receipt` declares the fields a run
107/// must return: the evidence the attester inspects, for example a `BigQuery`
108/// `job_id` and the SQL the job actually executed.
109#[derive(Clone, Debug, Default, PartialEq, Eq)]
110pub struct Executor {
111    /// Path to run instructions or code.
112    pub resource: Option<String>,
113    /// The fields a run must return.
114    pub receipt: Vec<String>,
115}
116
117impl Executor {
118    /// Reads an `executor` mapping. Returns `None` when it is not a mapping.
119    pub fn from_value(value: &Value) -> Option<Self> {
120        let map = value.as_mapping()?;
121        Some(Self {
122            resource: map.get("resource").and_then(Value::as_display_string),
123            receipt: match map.get("receipt") {
124                Some(Value::Sequence(items)) => {
125                    items.iter().filter_map(Value::as_display_string).collect()
126                }
127                Some(other) => other.as_display_string().into_iter().collect(),
128                None => Vec::new(),
129            },
130        })
131    }
132}
133
134/// The deterministic check.
135///
136/// `resource` names code (no LLM) that takes a receipt and returns a verdict.
137/// It is meant to run consumer-side.
138#[derive(Clone, Debug, Default, PartialEq, Eq)]
139pub struct Attester {
140    /// Path to the attester code.
141    pub resource: Option<String>,
142}
143
144impl Attester {
145    /// Reads an `attester` mapping. Returns `None` when it is not a mapping.
146    pub fn from_value(value: &Value) -> Option<Self> {
147        let map = value.as_mapping()?;
148        Some(Self {
149            resource: map.get("resource").and_then(Value::as_display_string),
150        })
151    }
152}
153
154/// A computation held in the body under `# Computation`.
155#[derive(Clone, Debug, PartialEq, Eq)]
156pub struct InlineComputation {
157    /// The code, dedented, without the fence or indent.
158    pub code: String,
159    /// The fence info string (`sql`, `python`, …), when the block is fenced and
160    /// carries one.
161    pub language: Option<String>,
162    /// `true` for a ```` ``` ````/`~~~` fenced block, `false` for an indented
163    /// one. The spec's prose says "fenced"; its own examples are indented, so
164    /// both are read.
165    pub fenced: bool,
166}
167
168/// Where the sanctioned computation lives.
169#[derive(Clone, Debug, PartialEq, Eq)]
170pub enum ComputationSource {
171    /// A code block in the body under `# Computation`.
172    Inline(InlineComputation),
173    /// The path named by the `computation` frontmatter key.
174    File(String),
175    /// Neither form is present, so the contract is incomplete.
176    Missing,
177}
178
179impl ComputationSource {
180    /// The inline code, if the computation is inline.
181    #[must_use]
182    pub fn code(&self) -> Option<&str> {
183        match self {
184            Self::Inline(c) => Some(&c.code),
185            _ => None,
186        }
187    }
188
189    /// The path, if the computation is held in a file.
190    #[must_use]
191    pub fn path(&self) -> Option<&str> {
192        match self {
193            Self::File(p) => Some(p),
194            _ => None,
195        }
196    }
197
198    /// `true` when neither an inline block nor a `computation` path is present.
199    #[must_use]
200    pub fn is_missing(&self) -> bool {
201        *self == Self::Missing
202    }
203}
204
205impl fmt::Display for ComputationSource {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        match self {
208            Self::Inline(c) => {
209                write!(f, "inline ({} line(s))", c.code.lines().count())
210            }
211            Self::File(p) => write!(f, "file {p}"),
212            Self::Missing => f.write_str("(missing)"),
213        }
214    }
215}
216
217/// The contract of an `Attested Computation` concept: its top-level frontmatter
218/// plus the computation itself.
219#[derive(Clone, Debug, PartialEq, Eq)]
220pub struct AttestedComputation {
221    /// REQUIRED for this type: how to run the computation, and so how the
222    /// executor and attester interpret it and what `parameters` mean.
223    pub runtime: Option<String>,
224    /// The typed, named holes an agent may fill.
225    pub parameters: Vec<Parameter>,
226    /// The sanctioned computation, inline or by path.
227    pub computation: ComputationSource,
228    /// How the computation is run.
229    pub executor: Option<Executor>,
230    /// The deterministic check over a run's receipt.
231    pub attester: Option<Attester>,
232    /// `true` when the body carries a `# Computation` block *and* the
233    /// `computation` key names a file. The spec asks for one or the other, so this
234    /// flags a contract whose two halves may disagree.
235    pub has_redundant_inline: bool,
236}
237
238impl AttestedComputation {
239    /// Reads the contract from a concept's frontmatter and body.
240    ///
241    /// This does not check that the concept's `type` is
242    /// [`ATTESTED_COMPUTATION_TYPE`], since the computation keys are ordinary
243    /// frontmatter and a producer may use them on another type. Use
244    /// [`Frontmatter::is_attested_computation`](crate::Frontmatter::is_attested_computation)
245    /// to test the type.
246    pub fn from_parts(frontmatter: &crate::Frontmatter, body: &str) -> Self {
247        let inline = extract_inline_computation(body);
248        let path = frontmatter
249            .get("computation")
250            .and_then(Value::as_display_string)
251            .filter(|p| !p.trim().is_empty());
252
253        let (computation, has_redundant_inline) = match (path, inline) {
254            (Some(p), Some(_)) => (ComputationSource::File(p), true),
255            (Some(p), None) => (ComputationSource::File(p), false),
256            (None, Some(c)) => (ComputationSource::Inline(c), false),
257            (None, None) => (ComputationSource::Missing, false),
258        };
259
260        Self {
261            runtime: frontmatter
262                .get("runtime")
263                .and_then(Value::as_display_string)
264                .filter(|r| !r.trim().is_empty()),
265            parameters: frontmatter
266                .get("parameters")
267                .map(Parameter::list_from_value)
268                .unwrap_or_default(),
269            computation,
270            executor: frontmatter.get("executor").and_then(Executor::from_value),
271            attester: frontmatter.get("attester").and_then(Attester::from_value),
272            has_redundant_inline,
273        }
274    }
275
276    /// The parameters an agent must supply a value for.
277    pub fn required_parameters(&self) -> impl Iterator<Item = &Parameter> {
278        self.parameters.iter().filter(|p| p.is_required())
279    }
280
281    /// The path-valued fields of this contract, as `(field name, raw path)`
282    /// pairs, the inputs to [`links::field_path_candidates`] when checking
283    /// that a contract points at something real.
284    #[must_use]
285    pub fn path_fields(&self) -> Vec<(&'static str, &str)> {
286        let mut out = Vec::new();
287        if let Some(p) = self.computation.path() {
288            out.push(("computation", p));
289        }
290        if let Some(r) = self.executor.as_ref().and_then(|e| e.resource.as_deref()) {
291            out.push(("executor.resource", r));
292        }
293        if let Some(r) = self.attester.as_ref().and_then(|a| a.resource.as_deref()) {
294            out.push(("attester.resource", r));
295        }
296        out
297    }
298}
299
300/// Extracts the code block under a `# Computation` heading.
301///
302/// The section runs from the heading to the next heading of the same or a
303/// higher level. Within it, the first fenced block wins; failing that, the
304/// first indented block does, because the spec's own examples are written that
305/// way. Returns `None` when there is no `# Computation` section or it holds no
306/// code.
307#[must_use]
308pub fn extract_inline_computation(body: &str) -> Option<InlineComputation> {
309    let section = computation_section(body)?;
310    fenced_block(&section).or_else(|| indented_block(&section))
311}
312
313/// The lines of the `# Computation` section, heading excluded.
314fn computation_section(body: &str) -> Option<Vec<&str>> {
315    let mut lines = body.lines();
316    let mut level = 0;
317    for line in lines.by_ref() {
318        if let Some((l, title)) = heading(line)
319            && title.eq_ignore_ascii_case(COMPUTATION_HEADING)
320        {
321            level = l;
322            break;
323        }
324    }
325    if level == 0 {
326        return None;
327    }
328    let mut section = Vec::new();
329    for line in lines {
330        if let Some((l, _)) = heading(line)
331            && l <= level
332        {
333            break;
334        }
335        section.push(line);
336    }
337    Some(section)
338}
339
340/// Splits an ATX heading into its level and title.
341fn heading(line: &str) -> Option<(usize, &str)> {
342    let t = line.trim_start();
343    let hashes = t.len() - t.trim_start_matches('#').len();
344    if hashes == 0 || hashes > 6 {
345        return None;
346    }
347    let rest = &t[hashes..];
348    if !rest.is_empty() && !rest.starts_with([' ', '\t']) {
349        return None;
350    }
351    Some((hashes, rest.trim().trim_end_matches('#').trim()))
352}
353
354/// The first fenced code block in a section.
355fn fenced_block(section: &[&str]) -> Option<InlineComputation> {
356    for (i, line) in section.iter().enumerate() {
357        let t = line.trim_start();
358        for marker in ["```", "~~~"] {
359            if let Some(info) = t.strip_prefix(marker) {
360                let info = info.trim();
361                let language = (!info.is_empty()).then(|| info.to_string());
362                return finish_fenced(section, i, marker, language);
363            }
364        }
365    }
366    None
367}
368
369fn finish_fenced(
370    section: &[&str],
371    open: usize,
372    marker: &str,
373    language: Option<String>,
374) -> Option<InlineComputation> {
375    let indent = section[open].len() - section[open].trim_start().len();
376    let mut code: Vec<String> = Vec::new();
377    for line in &section[open + 1..] {
378        if line.trim_start().starts_with(marker) {
379            break;
380        }
381        code.push(dedent(line, indent));
382    }
383    let code = trim_blank_edges(code);
384    (!code.is_empty()).then(|| InlineComputation {
385        code: code.join("\n"),
386        language,
387        fenced: true,
388    })
389}
390
391/// The first indented (4-space or tab) code block in a section.
392fn indented_block(section: &[&str]) -> Option<InlineComputation> {
393    let mut code: Vec<String> = Vec::new();
394    let mut started = false;
395    for line in section {
396        let is_code = line.starts_with("    ") || line.starts_with('\t');
397        if is_code {
398            started = true;
399            code.push(dedent(line, 4));
400        } else if line.trim().is_empty() {
401            if started {
402                code.push(String::new());
403            }
404        } else if started {
405            break;
406        }
407    }
408    let code = trim_blank_edges(code);
409    (!code.is_empty()).then(|| InlineComputation {
410        code: code.join("\n"),
411        language: None,
412        fenced: false,
413    })
414}
415
416/// Removes up to `n` columns of leading whitespace (a tab counts as one level).
417fn dedent(line: &str, n: usize) -> String {
418    if let Some(rest) = line.strip_prefix('\t') {
419        return rest.to_string();
420    }
421    let strip = line.len() - line.trim_start_matches(' ').len();
422    line[strip.min(n)..].to_string()
423}
424
425fn trim_blank_edges(mut lines: Vec<String>) -> Vec<String> {
426    while lines.first().is_some_and(|l| l.trim().is_empty()) {
427        lines.remove(0);
428    }
429    while lines.last().is_some_and(|l| l.trim().is_empty()) {
430        lines.pop();
431    }
432    lines
433}
434
435/// Resolves a contract's path-valued fields to bundle-relative candidates,
436/// as `(field, raw, candidates)`.
437#[must_use]
438pub fn contract_path_candidates(
439    contract: &AttestedComputation,
440    from: &crate::ConceptId,
441) -> Vec<(&'static str, String, Vec<String>)> {
442    contract
443        .path_fields()
444        .into_iter()
445        .map(|(field, raw)| {
446            let candidates = links::field_path_candidates(raw, from);
447            (field, raw.to_string(), candidates)
448        })
449        .collect()
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455    use crate::Document;
456
457    const REVENUE: &str = "\
458---
459type: Attested Computation
460title: Revenue for fiscal year
461runtime: bigquery
462parameters:
463  - { name: year, type: integer, required: true }
464executor:
465  resource: references/skills/run-on-bq.md
466  receipt: [job_id, executed_sql, result]
467attester:
468  resource: references/attesters/revenue.py
469---
470
471# Computation
472
473    SELECT SUM(amount) AS revenue
474    FROM finance.recognized_revenue
475    WHERE fiscal_year = @year
476
477The computation binds only the declared `parameters`, per the recognition
478policy.[^rev-policy]
479
480[^rev-policy]: Revenue recognition policy
481";
482
483    #[test]
484    fn reads_the_spec_contract() {
485        let doc = Document::parse(REVENUE).unwrap();
486        assert!(doc.frontmatter.is_attested_computation());
487        let c = AttestedComputation::from_parts(&doc.frontmatter, &doc.body);
488
489        assert_eq!(c.runtime.as_deref(), Some("bigquery"));
490        assert_eq!(c.parameters.len(), 1);
491        assert_eq!(c.parameters[0].name.as_deref(), Some("year"));
492        assert_eq!(c.parameters[0].type_.as_deref(), Some("integer"));
493        assert!(c.parameters[0].is_required());
494        assert_eq!(c.required_parameters().count(), 1);
495
496        let executor = c.executor.as_ref().unwrap();
497        assert_eq!(
498            executor.resource.as_deref(),
499            Some("references/skills/run-on-bq.md")
500        );
501        assert_eq!(executor.receipt, vec!["job_id", "executed_sql", "result"]);
502        assert_eq!(
503            c.attester.as_ref().unwrap().resource.as_deref(),
504            Some("references/attesters/revenue.py")
505        );
506
507        let code = c.computation.code().unwrap();
508        assert!(code.starts_with("SELECT SUM(amount) AS revenue"));
509        assert!(code.ends_with("WHERE fiscal_year = @year"));
510        // Prose after the block is not part of the computation.
511        assert!(!code.contains("binds only"));
512        assert!(!c.has_redundant_inline);
513    }
514
515    #[test]
516    fn fenced_blocks_win_and_carry_a_language() {
517        let body = "# Computation\n\n```sql\nSELECT 1\n```\n\nProse.\n";
518        let c = extract_inline_computation(body).unwrap();
519        assert!(c.fenced);
520        assert_eq!(c.language.as_deref(), Some("sql"));
521        assert_eq!(c.code, "SELECT 1");
522    }
523
524    #[test]
525    fn file_form_replaces_the_body_block() {
526        let doc = Document::parse(
527            "---\ntype: Attested Computation\nruntime: bigquery\n\
528             computation: references/computations/lib/revenue.sql\n---\n\n# Definition\n\nProse.\n",
529        )
530        .unwrap();
531        let c = AttestedComputation::from_parts(&doc.frontmatter, &doc.body);
532        assert_eq!(
533            c.computation.path(),
534            Some("references/computations/lib/revenue.sql")
535        );
536        assert!(!c.has_redundant_inline);
537        assert_eq!(
538            c.path_fields(),
539            vec![("computation", "references/computations/lib/revenue.sql")]
540        );
541    }
542
543    #[test]
544    fn both_forms_present_is_flagged() {
545        let doc = Document::parse(
546            "---\ntype: Attested Computation\ncomputation: x.sql\n---\n\n# Computation\n\n    SELECT 1\n",
547        )
548        .unwrap();
549        let c = AttestedComputation::from_parts(&doc.frontmatter, &doc.body);
550        assert!(c.has_redundant_inline);
551        assert_eq!(c.computation.path(), Some("x.sql"));
552    }
553
554    #[test]
555    fn missing_computation_is_representable() {
556        let doc =
557            Document::parse("---\ntype: Attested Computation\n---\n\n# Definition\n").unwrap();
558        let c = AttestedComputation::from_parts(&doc.frontmatter, &doc.body);
559        assert!(c.computation.is_missing());
560        assert!(c.runtime.is_none());
561    }
562
563    #[test]
564    fn section_ends_at_the_next_same_level_heading() {
565        let body = "# Computation\n\n## Detail\n\n    SELECT 1\n\n# Notes\n\n    SELECT 2\n";
566        let c = extract_inline_computation(body).unwrap();
567        assert_eq!(c.code, "SELECT 1");
568    }
569
570    #[test]
571    fn dbt_template_syntax_survives() {
572        let body = "# Computation\n\n    SELECT gross_profit\n    FROM {{ ref('fct_income_statement') }}\n    WHERE fiscal_year = {{ var('year') }}\n";
573        let c = extract_inline_computation(body).unwrap();
574        assert!(c.code.contains("{{ ref('fct_income_statement') }}"));
575        assert_eq!(c.code.lines().count(), 3);
576    }
577}