Skip to main content

formal_ai/
knowledge.rs

1//! External knowledge oracles, treated as cached APIs (issue #412).
2//!
3//! The universal solver should not depend on a fixed, hand-written catalogue of
4//! coding answers. Instead it treats public knowledge bases — Rosetta Code,
5//! Wikifunctions, the Hello World Collection, and Stack Overflow — as external
6//! APIs, even when they expose no machine API: a reviewed snippet plus its
7//! deterministic output and source attribution is kept as a *cached* example so
8//! the answer is served offline, exactly the offline-first contract the
9//! Wikidata/Wiktionary caches provide. This cache of popular cases is embedded
10//! (`ORACLE_SNAPSHOTS`) so it compiles into both the native binary and the
11//! Rust→WASM worker without a runtime fetch; the cache holds only the *popular*
12//! cases so offline tests stay fast and the repository stays light, and it
13//! never mirrors a whole source. A gated live-refresh path (below) is what
14//! would materialise a per-source `data/cache/<source-slug>/` bucket from the
15//! live pages; until it runs, the embedded snapshots are the cache of record.
16//!
17//! Two concerns live here:
18//!
19//! 1. [`cache_capacity`] — the shared cap policy: never cache more than 1% of a
20//!    source, or [`KNOWLEDGE_CACHE_FLOOR`] items when 1% is smaller
21//!    (issue #412, R8). The same number bounds every per-source/per-topic cache
22//!    so no single external corpus can bloat the merged views.
23//! 2. [`CodingOracle`] — an offline-first lookup that resolves a
24//!    `(task, language)` coding request to a reviewed snippet plus its source
25//!    attribution, generalising the static `crate::coding` catalogue beyond
26//!    its built-in languages (R6). The committed snapshots are the popular-case
27//!    cache; a gated live-refresh path (mirroring the existing
28//!    `FORMAL_AI_LIVE_API` discipline) repopulates them from the live sources.
29//!
30//! The data is plain Rust so it compiles into both the native binary and the
31//! Rust→WASM browser worker without a runtime fetch, and is mirrored verbatim in
32//! `src/web/formal_ai_worker.js` so every reasoning surface agrees byte-for-byte.
33
34/// Lower bound on the local cache: even for a small source we keep up to this
35/// many popular items before the 1% ceiling takes over. Issue #412, R8.
36pub const KNOWLEDGE_CACHE_FLOOR: usize = 512;
37
38/// Maximum number of items we may cache locally for a source that publishes
39/// `source_total` items.
40///
41/// The policy from issue #412 is "never cache more than 1% — or 512 items when
42/// 1% is smaller than 512". So the cap is 1% of the source, rounded up, raised
43/// to the [`KNOWLEDGE_CACHE_FLOOR`] floor, and finally clamped to the source's
44/// own size (you can never cache more rows than exist).
45///
46/// The crate's Rust 1.77 baseline supports `div_ceil`, so the 1% calculation
47/// stays explicit without carrying a manual rounding formula.
48#[must_use]
49pub const fn cache_capacity(source_total: usize) -> usize {
50    let one_percent = source_total.div_ceil(100);
51    let floored = if one_percent > KNOWLEDGE_CACHE_FLOOR {
52        one_percent
53    } else {
54        KNOWLEDGE_CACHE_FLOOR
55    };
56    if floored < source_total {
57        floored
58    } else {
59        source_total
60    }
61}
62
63/// Whether keeping `cached` items from a source of `source_total` rows stays
64/// within the [`cache_capacity`] cap.
65///
66/// Used by the ratchet test that guards the committed snapshot set from
67/// silently growing into a mirror.
68#[must_use]
69pub const fn within_cache_capacity(cached: usize, source_total: usize) -> bool {
70    cached <= cache_capacity(source_total)
71}
72
73/// Public knowledge sources the coding oracle draws on.
74///
75/// Each is a public, reviewable corpus we treat as an external API even when it
76/// exposes none: a fetched page is parsed into a snippet and cached locally.
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum KnowledgeSource {
79    /// <https://rosettacode.org> — the same task implemented in hundreds of
80    /// languages; our primary code corpus for idioms we do not template.
81    RosettaCode,
82    /// <https://www.wikifunctions.org> — Abstract Wikipedia's function library;
83    /// evaluates `Z7` calls and returns a `Z22` result, so it doubles as a
84    /// *result oracle* that can cross-check an in-solver computation.
85    Wikifunctions,
86    /// <http://helloworldcollection.de> — "Hello, World!" in ~600 languages; the
87    /// canonical first program for any language we do not yet template.
88    HelloWorldCollection,
89    /// <https://stackoverflow.com> — community answers, treated read-only and
90    /// only for snippets under a compatible licence.
91    StackOverflow,
92}
93
94impl KnowledgeSource {
95    /// Stable, filesystem-safe identifier used as the `data/cache/<slug>/`
96    /// bucket name and in trace evidence.
97    #[must_use]
98    pub const fn slug(self) -> &'static str {
99        match self {
100            Self::RosettaCode => "rosetta-code",
101            Self::Wikifunctions => "wikifunctions",
102            Self::HelloWorldCollection => "hello-world-collection",
103            Self::StackOverflow => "stack-overflow",
104        }
105    }
106
107    /// Human-readable name for attribution in answers and the case study.
108    #[must_use]
109    pub const fn display_name(self) -> &'static str {
110        match self {
111            Self::RosettaCode => "Rosetta Code",
112            Self::Wikifunctions => "Wikifunctions",
113            Self::HelloWorldCollection => "Hello World Collection",
114            Self::StackOverflow => "Stack Overflow",
115        }
116    }
117
118    /// Landing URL for the source.
119    #[must_use]
120    pub const fn base_url(self) -> &'static str {
121        match self {
122            Self::RosettaCode => "https://rosettacode.org",
123            Self::Wikifunctions => "https://www.wikifunctions.org",
124            Self::HelloWorldCollection => "http://helloworldcollection.de",
125            Self::StackOverflow => "https://stackoverflow.com",
126        }
127    }
128
129    /// Conservative published size of the source, used only by
130    /// [`cache_capacity`] to bound how much we may cache. The exact figure does
131    /// not need to be precise — it just feeds the 1% ceiling — and is refreshed
132    /// by the gated live-refresh tool. Figures are public approximations:
133    /// Rosetta Code lists ~1,300 tasks, Wikifunctions a few thousand functions,
134    /// and the Hello World Collection ~600 languages.
135    #[must_use]
136    pub const fn approximate_catalog_size(self) -> usize {
137        match self {
138            Self::RosettaCode => 1_300,
139            Self::Wikifunctions => 3_000,
140            Self::HelloWorldCollection => 600,
141            Self::StackOverflow => 24_000_000,
142        }
143    }
144}
145
146/// A reviewed code snippet discovered from an external knowledge source.
147///
148/// `language_slug` is the lowercase identifier a prompt uses (`kotlin`),
149/// `language_label` is the display form (`Kotlin`). `expected_output` is the
150/// deterministic stdout the snippet prints, so the solver can show "code + the
151/// result" exactly as the built-in catalogue does.
152#[derive(Clone, Copy, Debug)]
153pub struct OracleSnippet {
154    pub task_slug: &'static str,
155    pub language_slug: &'static str,
156    pub language_label: &'static str,
157    pub source: KnowledgeSource,
158    pub source_url: &'static str,
159    pub code: &'static str,
160    pub expected_output: &'static str,
161}
162
163/// The committed popular-case cache for the coding oracle.
164///
165/// These are the "Hello, World!" programs for languages the built-in
166/// [`crate::coding::catalog`] does not template (Kotlin, Swift, PHP, Bash, Lua,
167/// Haskell), plus a Rosetta-Code factorial in Kotlin to exercise a non-trivial
168/// task. The set is intentionally tiny — well under [`cache_capacity`] for every
169/// source — and is the offline accelerator a live refresh would repopulate.
170const ORACLE_SNAPSHOTS: &[OracleSnippet] = &[
171    OracleSnippet {
172        task_slug: "hello_world",
173        language_slug: "kotlin",
174        language_label: "Kotlin",
175        source: KnowledgeSource::HelloWorldCollection,
176        source_url: "http://helloworldcollection.de/#Kotlin",
177        code: "fun main() {\n    println(\"Hello, World!\")\n}",
178        expected_output: "Hello, World!",
179    },
180    OracleSnippet {
181        task_slug: "hello_world",
182        language_slug: "swift",
183        language_label: "Swift",
184        source: KnowledgeSource::HelloWorldCollection,
185        source_url: "http://helloworldcollection.de/#Swift",
186        code: "print(\"Hello, World!\")",
187        expected_output: "Hello, World!",
188    },
189    OracleSnippet {
190        task_slug: "hello_world",
191        language_slug: "php",
192        language_label: "PHP",
193        source: KnowledgeSource::HelloWorldCollection,
194        source_url: "http://helloworldcollection.de/#PHP",
195        code: "<?php\necho \"Hello, World!\\n\";",
196        expected_output: "Hello, World!",
197    },
198    OracleSnippet {
199        task_slug: "hello_world",
200        language_slug: "bash",
201        language_label: "Bash",
202        source: KnowledgeSource::HelloWorldCollection,
203        source_url: "http://helloworldcollection.de/#Bash",
204        code: "echo \"Hello, World!\"",
205        expected_output: "Hello, World!",
206    },
207    OracleSnippet {
208        task_slug: "hello_world",
209        language_slug: "lua",
210        language_label: "Lua",
211        source: KnowledgeSource::HelloWorldCollection,
212        source_url: "http://helloworldcollection.de/#Lua",
213        code: "print(\"Hello, World!\")",
214        expected_output: "Hello, World!",
215    },
216    OracleSnippet {
217        task_slug: "hello_world",
218        language_slug: "haskell",
219        language_label: "Haskell",
220        source: KnowledgeSource::HelloWorldCollection,
221        source_url: "http://helloworldcollection.de/#Haskell",
222        code: "main :: IO ()\nmain = putStrLn \"Hello, World!\"",
223        expected_output: "Hello, World!",
224    },
225    OracleSnippet {
226        task_slug: "factorial",
227        language_slug: "kotlin",
228        language_label: "Kotlin",
229        source: KnowledgeSource::RosettaCode,
230        source_url: "https://rosettacode.org/wiki/Factorial#Kotlin",
231        code: "fun factorial(n: Int): Long =\n    if (n <= 1) 1L else n * factorial(n - 1)\n\nfun main() {\n    println(factorial(5))\n}",
232        expected_output: "120",
233    },
234];
235
236/// Offline-first lookup that generalises the built-in coding catalogue using
237/// the external knowledge sources' cached snapshots.
238pub struct CodingOracle;
239
240impl CodingOracle {
241    /// Every committed snapshot.
242    #[must_use]
243    pub const fn snapshots() -> &'static [OracleSnippet] {
244        ORACLE_SNAPSHOTS
245    }
246
247    /// Resolve a `(task, language)` request to a cached snippet.
248    ///
249    /// The language is matched by slug or case-insensitive display label so a
250    /// bare `kotlin` / `Kotlin` both resolve. Returns `None` when the oracle has
251    /// no cached answer — the caller then stays on its existing path (the static
252    /// catalogue or, ultimately, the `unknown` opener), so this is purely
253    /// additive.
254    #[must_use]
255    pub fn lookup(task_slug: &str, language: &str) -> Option<&'static OracleSnippet> {
256        let needle = language.trim().to_ascii_lowercase();
257        ORACLE_SNAPSHOTS.iter().find(|snippet| {
258            snippet.task_slug == task_slug
259                && (snippet.language_slug == needle
260                    || snippet.language_label.to_ascii_lowercase() == needle)
261        })
262    }
263
264    /// Whether the oracle can answer for `language` (any task), used to decide
265    /// when to generalise beyond the static catalogue.
266    #[must_use]
267    pub fn knows_language(language: &str) -> bool {
268        let needle = language.trim().to_ascii_lowercase();
269        ORACLE_SNAPSHOTS.iter().any(|snippet| {
270            snippet.language_slug == needle || snippet.language_label.to_ascii_lowercase() == needle
271        })
272    }
273
274    /// Distinct language labels the oracle covers that the built-in catalogue
275    /// does not, in committed order, for diagnostics and the case study.
276    #[must_use]
277    pub fn languages() -> Vec<&'static str> {
278        let mut labels: Vec<&'static str> = Vec::new();
279        for snippet in ORACLE_SNAPSHOTS {
280            if !labels.contains(&snippet.language_label) {
281                labels.push(snippet.language_label);
282            }
283        }
284        labels
285    }
286
287    /// Number of snapshots cached for `source`, for the cache-cap ratchet test.
288    #[must_use]
289    pub fn cached_count(source: KnowledgeSource) -> usize {
290        ORACLE_SNAPSHOTS
291            .iter()
292            .filter(|snippet| snippet.source == source)
293            .count()
294    }
295}