Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:02:15.056Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

pins.rs

328 lines · 11.6 KB · rust
1//! The component ledger: which harness versions the owner froze, and at which
2//! bytes.
3//!
4//! A pin that lives in memory is not a pin. Omega restarts, the registry
5//! document refreshes, and the next launch resolves whatever version the
6//! registry now advertises. So the ledger is a file, it is re-read on every
7//! maintenance decision rather than cached, and it is the *only* thing the
8//! decision consults. Nothing in the enforcement path can hold a stale copy,
9//! because nothing in the enforcement path holds a copy at all.
10//!
11//! A pin names two things:
12//!
13//! * a **version**, which is what the owner typed, and
14//! * a **digest**, which is what the bytes were when the pin was taken.
15//!
16//! Both are checked. A version alone would be satisfied by a re-tagged release
17//! — the exact substitution the falsifier on omega#81 describes — and a digest
18//! alone would produce a refusal message nobody could act on.
19
20use std::collections::BTreeMap;
21
22use serde::{Deserialize, Serialize};
23
24/// The schema every pin ledger on disk carries.
25pub const HARNESS_PIN_LEDGER_SCHEMA: &str = "openagents.omega.harness.pins.v1";
26
27/// The file name of the ledger, relative to the external agents directory.
28pub const HARNESS_PIN_LEDGER_FILE_NAME: &str = "omega-harness-pins.json";
29
30/// The most pins one ledger may hold. A bound, so a corrupt or hostile file
31/// cannot make the launch path do unbounded work.
32pub const MAX_HARNESS_PINS: usize = 128;
33
34/// One frozen harness.
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase", deny_unknown_fields)]
37pub struct HarnessPin {
38    /// The registry id of the harness, for example `codex-acp`.
39    pub harness_id: String,
40    /// The version the owner froze.
41    pub version: String,
42    /// The digest of the installed tree when the pin was taken.
43    ///
44    /// A `String` rather than a
45    /// [`MeasuredDigest`](crate::MeasuredDigest): a pin read back from disk is
46    /// a recorded claim about a past measurement, not a measurement this
47    /// process made, and the two must not share a type. It is only ever
48    /// compared against a live measurement, never written into a receipt as
49    /// one.
50    pub digest: String,
51}
52
53/// The owner's frozen set.
54#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct HarnessPinLedger {
56    pins: BTreeMap<String, HarnessPin>,
57}
58
59/// Every way a ledger file can fail to be a ledger.
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum HarnessPinLedgerError {
62    InvalidJson,
63    InvalidSchema,
64    PinBoundExceeded,
65    DuplicatePin,
66    InvalidPin,
67}
68
69impl std::fmt::Display for HarnessPinLedgerError {
70    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        formatter.write_str(match self {
72            Self::InvalidJson => "harness pin ledger is not valid contract JSON",
73            Self::InvalidSchema => "harness pin ledger schema is not supported",
74            Self::PinBoundExceeded => "harness pin ledger holds more pins than are admitted",
75            Self::DuplicatePin => "harness pin ledger pins one harness twice",
76            Self::InvalidPin => "harness pin ledger holds a pin that names no version or no bytes",
77        })
78    }
79}
80
81impl std::error::Error for HarnessPinLedgerError {}
82
83#[derive(Serialize, Deserialize)]
84#[serde(rename_all = "camelCase", deny_unknown_fields)]
85struct RawHarnessPinLedger {
86    schema: String,
87    pins: Vec<HarnessPin>,
88}
89
90impl HarnessPinLedger {
91    /// A ledger with nothing frozen. What a machine with no pin file has.
92    #[must_use]
93    pub fn empty() -> Self {
94        Self {
95            pins: BTreeMap::new(),
96        }
97    }
98
99    /// The pin covering one harness, if the owner froze it.
100    #[must_use]
101    pub fn pin(&self, harness_id: &str) -> Option<&HarnessPin> {
102        self.pins.get(harness_id)
103    }
104
105    /// Every pin, in a stable order.
106    pub fn pins(&self) -> impl Iterator<Item = &HarnessPin> {
107        self.pins.values()
108    }
109
110    #[must_use]
111    pub fn is_empty(&self) -> bool {
112        self.pins.is_empty()
113    }
114
115    /// Freeze one harness at the bytes the host just measured.
116    ///
117    /// Takes a [`MeasuredDigest`](crate::MeasuredDigest) rather than a string,
118    /// so a pin cannot be created against bytes nobody read. That is what makes
119    /// the pin worth checking later: it was a measurement once.
120    pub fn set_pin(&mut self, harness_id: &str, version: &str, digest: &crate::MeasuredDigest) {
121        self.pins.insert(
122            harness_id.to_string(),
123            HarnessPin {
124                harness_id: harness_id.to_string(),
125                version: version.to_string(),
126                digest: digest.as_str().to_string(),
127            },
128        );
129    }
130
131    /// Unfreeze one harness. Returns whether anything was frozen.
132    pub fn remove_pin(&mut self, harness_id: &str) -> bool {
133        self.pins.remove(harness_id).is_some()
134    }
135}
136
137/// Read a ledger file.
138///
139/// Fails closed on every shape it does not recognise. A ledger that cannot be
140/// read is not an empty ledger — see
141/// [`decide_maintenance`](crate::decide_maintenance) for what the caller does
142/// with the error, which is refuse rather than proceed unpinned.
143pub fn decode_harness_pin_ledger(input: &str) -> Result<HarnessPinLedger, HarnessPinLedgerError> {
144    let raw: RawHarnessPinLedger =
145        serde_json::from_str(input).map_err(|_| HarnessPinLedgerError::InvalidJson)?;
146    if raw.schema != HARNESS_PIN_LEDGER_SCHEMA {
147        return Err(HarnessPinLedgerError::InvalidSchema);
148    }
149    if raw.pins.len() > MAX_HARNESS_PINS {
150        return Err(HarnessPinLedgerError::PinBoundExceeded);
151    }
152
153    let mut pins = BTreeMap::new();
154    for pin in raw.pins {
155        if pin.harness_id.trim().is_empty()
156            || pin.version.trim().is_empty()
157            || pin.digest.trim().is_empty()
158        {
159            return Err(HarnessPinLedgerError::InvalidPin);
160        }
161        if pins.insert(pin.harness_id.clone(), pin).is_some() {
162            return Err(HarnessPinLedgerError::DuplicatePin);
163        }
164    }
165    Ok(HarnessPinLedger { pins })
166}
167
168/// Write a ledger file.
169///
170/// Routes back through [`decode_harness_pin_ledger`] before returning, for the
171/// same reason the receipt emitter does: a writer that can produce a file its
172/// own reader refuses turns the next restart into the moment the pins vanish.
173pub fn encode_harness_pin_ledger(
174    ledger: &HarnessPinLedger,
175) -> Result<String, HarnessPinLedgerError> {
176    let raw = RawHarnessPinLedger {
177        schema: HARNESS_PIN_LEDGER_SCHEMA.to_string(),
178        pins: ledger.pins.values().cloned().collect(),
179    };
180    let serialized =
181        serde_json::to_string_pretty(&raw).map_err(|_| HarnessPinLedgerError::InvalidJson)?;
182    decode_harness_pin_ledger(&serialized)?;
183    Ok(serialized)
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::MeasuredDigest;
190
191    fn ledger_with_codex() -> HarnessPinLedger {
192        let mut ledger = HarnessPinLedger::empty();
193        ledger.set_pin(
194            "codex-acp",
195            "0.9.4",
196            &MeasuredDigest::measure(b"codex-acp 0.9.4 tree"),
197        );
198        ledger
199    }
200
201    /// The restart law, expressed without a filesystem: whatever the process
202    /// held becomes bytes, and those bytes become the same thing again. The
203    /// on-disk half of this is
204    /// `a_pin_taken_before_a_restart_is_read_back_after_one` in
205    /// `crates/omega_harness/src/omega_harness.rs`.
206    #[test]
207    fn a_ledger_survives_a_round_trip_through_its_own_file_format() {
208        let ledger = ledger_with_codex();
209        let encoded = encode_harness_pin_ledger(&ledger).expect("a ledger encodes");
210        let decoded = decode_harness_pin_ledger(&encoded).expect("its own bytes decode");
211        assert_eq!(decoded, ledger);
212        assert_eq!(decoded.pin("codex-acp").expect("pinned").version, "0.9.4");
213    }
214
215    #[test]
216    fn an_unpinned_harness_has_no_pin() {
217        assert!(ledger_with_codex().pin("gemini-cli").is_none());
218    }
219
220    #[test]
221    fn a_pin_records_the_bytes_that_were_measured_not_a_supplied_string() {
222        let digest = MeasuredDigest::measure(b"codex-acp 0.9.4 tree");
223        let ledger = ledger_with_codex();
224        assert_eq!(
225            ledger.pin("codex-acp").expect("pinned").digest,
226            digest.as_str()
227        );
228    }
229
230    #[test]
231    fn an_unknown_schema_is_refused_rather_than_read_as_an_empty_ledger() {
232        let file = serde_json::json!({
233            "schema": "openagents.omega.harness.pins.v0",
234            "pins": [],
235        })
236        .to_string();
237        assert_eq!(
238            decode_harness_pin_ledger(&file),
239            Err(HarnessPinLedgerError::InvalidSchema)
240        );
241    }
242
243    /// A ledger that pins one harness twice has no single answer to
244    /// "what is `codex-acp` frozen at", and silently keeping the last one would
245    /// make the effective pin depend on file order.
246    #[test]
247    fn a_duplicated_harness_is_refused_rather_than_last_write_wins() {
248        let file = serde_json::json!({
249            "schema": HARNESS_PIN_LEDGER_SCHEMA,
250            "pins": [
251                { "harnessId": "codex-acp", "version": "0.9.4", "digest": "aa" },
252                { "harnessId": "codex-acp", "version": "0.9.5", "digest": "bb" },
253            ],
254        })
255        .to_string();
256        assert_eq!(
257            decode_harness_pin_ledger(&file),
258            Err(HarnessPinLedgerError::DuplicatePin)
259        );
260    }
261
262    #[test]
263    fn a_pin_that_names_no_bytes_is_refused() {
264        for (version, digest) in [("0.9.4", "   "), ("", "aa")] {
265            let file = serde_json::json!({
266                "schema": HARNESS_PIN_LEDGER_SCHEMA,
267                "pins": [
268                    { "harnessId": "codex-acp", "version": version, "digest": digest },
269                ],
270            })
271            .to_string();
272            assert_eq!(
273                decode_harness_pin_ledger(&file),
274                Err(HarnessPinLedgerError::InvalidPin)
275            );
276        }
277    }
278
279    #[test]
280    fn an_unadmitted_field_is_refused_rather_than_ignored() {
281        let file = serde_json::json!({
282            "schema": HARNESS_PIN_LEDGER_SCHEMA,
283            "pins": [
284                {
285                    "harnessId": "codex-acp",
286                    "version": "0.9.4",
287                    "digest": "aa",
288                    "allowUnpinnedUpdate": true,
289                },
290            ],
291        })
292        .to_string();
293        assert_eq!(
294            decode_harness_pin_ledger(&file),
295            Err(HarnessPinLedgerError::InvalidJson)
296        );
297    }
298
299    #[test]
300    fn the_pin_bound_holds() {
301        let pins: Vec<serde_json::Value> = (0..=MAX_HARNESS_PINS)
302            .map(|index| {
303                serde_json::json!({
304                    "harnessId": format!("harness-{index}"),
305                    "version": "1.0.0",
306                    "digest": "aa",
307                })
308            })
309            .collect();
310        let file = serde_json::json!({ "schema": HARNESS_PIN_LEDGER_SCHEMA, "pins": pins })
311            .to_string();
312        assert_eq!(
313            decode_harness_pin_ledger(&file),
314            Err(HarnessPinLedgerError::PinBoundExceeded)
315        );
316    }
317
318    #[test]
319    fn removing_a_pin_unfreezes_exactly_one_harness() {
320        let mut ledger = ledger_with_codex();
321        ledger.set_pin("gemini-cli", "1.2.3", &MeasuredDigest::measure(b"gemini"));
322        assert!(ledger.remove_pin("codex-acp"));
323        assert!(!ledger.remove_pin("codex-acp"));
324        assert!(ledger.pin("codex-acp").is_none());
325        assert!(ledger.pin("gemini-cli").is_some());
326    }
327}
328
Served at tenant.openagents/omega Member data and write actions are omitted.