Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:00:21.155Z 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

thread_run_link.rs

744 lines · 30.1 KB · rust
1//! What a thread may say about the engine run it is linked to.
2//! `OMEGA-DELTA-0030`.
3//!
4//! omega#77 gave every thread an executor line, so a thread executed by an
5//! `omega-effectd` lane already names its run. Naming a run is not showing
6//! one: the reader is told a reference and left to go and look somewhere else
7//! for whether the work was verified, refused, or merely claimed. omega#80
8//! closes that — the linked run renders its state and its receipt chain in the
9//! thread, through the same inspector grammar the receipt pane uses.
10//!
11//! # The engine stays the sole run authority
12//!
13//! Nothing here holds run state. [`project_thread_run_link`] is a pure
14//! function of (the thread's disclosure record, the engine's own records, the
15//! clock), so the thread *projects* a run and can never become a second
16//! opinion about one. Three consequences, each of them checked below:
17//!
18//! - The reference comes from [`ExecutorDisclosure::run_ref`], the typed
19//!   record omega#77 stores, not from a string a surface assembled.
20//! - The engine's answer expires. Past [`THREAD_RUN_LINK_MAX_AGE_MS`] the link
21//!   renders `host_unavailable` instead of the last chain it saw, so a thread
22//!   cannot outlive the authority it is projecting. A stale complete chain
23//!   held on screen is precisely a panel entity becoming the source of truth.
24//! - A state Omega does not recognise is not translated into the nearest one.
25//!   An acknowledgement is not a completion, and a run that says `acknowledged`
26//!   reports no state at all rather than being read as finished.
27//!
28//! # A broken chain is shown, not hidden
29//!
30//! The chain is produced by
31//! [`workroom_receipts::project_issue31_evidence_pair`] — the same producer
32//! that builds the phone's projection, so the desktop and the phone cannot
33//! hold two opinions about one run. When it refuses, the refusal is rendered
34//! with its reason, never as an absence and never as completion. A single
35//! unshowable run must not blank the surface: one malformed record stopping
36//! every device is a bug this workspace has already shipped once.
37
38use serde_json::Value;
39use workroom_receipts::{
40    InspectorField, Issue31EvidenceChain, Issue31EvidenceUnavailableReason,
41    Issue31FullAutoLifecycle, PublicRef, project_issue31_evidence_pair,
42};
43
44use omega_front_door::{ExecutorClass, ExecutorDisclosure};
45
46/// How long the engine's answer about a run stays showable.
47///
48/// The panel re-reads every three seconds, so five missed reads is a host that
49/// has stopped answering rather than one that is briefly busy. Rendering the
50/// last known chain past that point would be the thread asserting a run state
51/// on the engine's behalf.
52pub const THREAD_RUN_LINK_MAX_AGE_MS: u64 = 15_000;
53
54/// One reading of the engine's records for a linked run.
55///
56/// Raw host answers plus the instant they were read. Deliberately not a
57/// projected view: caching the *conclusion* is what lets a surface disagree
58/// with the engine, whereas caching the engine's own words and re-deriving
59/// cannot.
60#[derive(Clone, Debug, Default, PartialEq, Eq)]
61pub struct ThreadRunRecords {
62    /// When the engine answered, on the same clock as `now_ms`.
63    pub read_at_ms: u64,
64    /// The `get_run` record, when the engine answered.
65    pub run: Option<Value>,
66    /// The `get_report` record, when the engine answered.
67    pub report: Option<Value>,
68    /// The `get_receipt` record, when the engine answered.
69    pub receipt: Option<Value>,
70}
71
72/// A thread's projection of the engine run that executed it.
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct ThreadRunLink {
75    /// The run, named by the thread's own disclosure record.
76    pub run_ref: PublicRef,
77    /// The agent the run delegated the work to, kept from the disclosure.
78    pub agent_id: String,
79    /// The run's lifecycle, when the engine reported one this contract models.
80    ///
81    /// `None` says the engine has not told this surface where the run stands.
82    /// It is never a stand-in for "queued" or "running": showing a state the
83    /// host did not report is the failure the whole surface exists to avoid.
84    pub state: Option<Issue31FullAutoLifecycle>,
85    /// The receipt chain, complete or unavailable-with-a-reason.
86    pub chain: Issue31EvidenceChain,
87}
88
89impl ThreadRunLink {
90    /// Whether this run's completion is backed by a resolvable receipt chain.
91    ///
92    /// omega#80's falsifier is "a completed run is claimed without its receipt
93    /// refs resolvable". This is the predicate that makes such a claim
94    /// impossible to make by accident: a terminal state is not enough, and an
95    /// authority decision that refused is not a completion either.
96    #[must_use]
97    pub fn is_receipted(&self) -> bool {
98        matches!(
99            self.chain,
100            Issue31EvidenceChain::Complete {
101                authority_allowed: true,
102                ..
103            }
104        )
105    }
106
107    /// Why the chain is not showable, when it is not.
108    #[must_use]
109    pub fn unavailable_reason(&self) -> Option<Issue31EvidenceUnavailableReason> {
110        match self.chain {
111            Issue31EvidenceChain::Unavailable { reason, .. } => Some(reason),
112            Issue31EvidenceChain::Complete { .. } => None,
113        }
114    }
115
116    /// The inspector rows this link renders.
117    ///
118    /// Same label/value grammar as `workroom_receipts::render_receipt_detail`,
119    /// so the thread's chain reads like the receipt pane's rather than like a
120    /// second format. The unavailable case still produces rows — `chain:
121    /// unavailable` and `chain_reason: …` — because a surface that renders
122    /// nothing has told the reader nothing, and silence reads as "no run".
123    #[must_use]
124    pub fn fields(&self) -> Vec<InspectorField> {
125        let mut fields = vec![
126            InspectorField::new("run_ref", self.run_ref.as_str()),
127            InspectorField::new("executed_by", self.agent_id.as_str()),
128            InspectorField::new(
129                "run_state",
130                match self.state {
131                    Some(state) => lifecycle_token(state),
132                    None => "not reported by the engine",
133                },
134            ),
135        ];
136        match &self.chain {
137            Issue31EvidenceChain::Complete {
138                authority_allowed,
139                hops,
140                ..
141            } => {
142                fields.push(InspectorField::new("chain", "complete"));
143                fields.push(InspectorField::new(
144                    "authority_allowed",
145                    if *authority_allowed { "true" } else { "false" },
146                ));
147                for hop in hops {
148                    fields.push(InspectorField::new(
149                        hop.kind.token(),
150                        match &hop.detail {
151                            Some(detail) => format!("{} · {detail}", hop.reference.as_str()),
152                            None => hop.reference.as_str().to_owned(),
153                        },
154                    ));
155                }
156            }
157            Issue31EvidenceChain::Unavailable {
158                reason, broken_at, ..
159            } => {
160                fields.push(InspectorField::new("chain", "unavailable"));
161                fields.push(InspectorField::new("chain_reason", reason.token()));
162                if let Some(broken_at) = broken_at {
163                    fields.push(InspectorField::new("chain_broken_at", broken_at.token()));
164                }
165            }
166        }
167        fields
168    }
169}
170
171/// The wire token for a lifecycle, for rendering.
172fn lifecycle_token(state: Issue31FullAutoLifecycle) -> &'static str {
173    match state {
174        Issue31FullAutoLifecycle::Queued => "queued",
175        Issue31FullAutoLifecycle::Running => "running",
176        Issue31FullAutoLifecycle::Pausing => "pausing",
177        Issue31FullAutoLifecycle::Paused => "paused",
178        Issue31FullAutoLifecycle::Stopping => "stopping",
179        Issue31FullAutoLifecycle::Retrying => "retrying",
180        Issue31FullAutoLifecycle::Stalled => "stalled",
181        Issue31FullAutoLifecycle::Succeeded => "succeeded",
182        Issue31FullAutoLifecycle::Failed => "failed",
183        Issue31FullAutoLifecycle::Stopped => "stopped",
184        Issue31FullAutoLifecycle::Expired => "expired",
185    }
186}
187
188fn lifecycle_from_host(state: &str) -> Option<Issue31FullAutoLifecycle> {
189    let token = crate::issue31_adjunct::lifecycle_for_host_state(state)?;
190    serde_json::from_value(Value::String(token.to_owned())).ok()
191}
192
193fn record_run_ref(record: &Value) -> Option<&str> {
194    record.get("runRef")?.as_str()
195}
196
197/// Project a thread's linked run from the engine's own records.
198///
199/// Returns `None` when the thread is not engine-lane work — a native or
200/// external ACP thread has no run authority to project, and inventing an empty
201/// run panel for it would suggest one exists.
202///
203/// An incoherent disclosure also returns `None`. A `native_loop` record
204/// carrying a run reference is a routed result wearing the wrong name, and
205/// `OMEGA-AGENT-AC-05` exists to stop exactly that from being rendered as a
206/// run this thread owns.
207#[must_use]
208pub fn project_thread_run_link(
209    disclosure: &ExecutorDisclosure,
210    records: Option<&ThreadRunRecords>,
211    now_ms: u64,
212) -> Option<ThreadRunLink> {
213    if disclosure.class != ExecutorClass::EngineLane || !disclosure.is_coherent() {
214        return None;
215    }
216    // The reference is the one the typed disclosure record holds. Nothing here
217    // parses it back out of the rendered line.
218    let run_ref = PublicRef::new(disclosure.run_ref.as_ref()?)?;
219
220    let fresh = records
221        .filter(|records| now_ms.saturating_sub(records.read_at_ms) <= THREAD_RUN_LINK_MAX_AGE_MS);
222
223    // A stale or absent reading is the host not answering, whatever it said
224    // last time. `state` follows the chain into `None` rather than surviving
225    // it: half a stale reading is still a stale reading.
226    let Some(records) = fresh else {
227        return Some(ThreadRunLink {
228            run_ref: run_ref.clone(),
229            agent_id: disclosure.agent_id.clone(),
230            state: None,
231            chain: Issue31EvidenceChain::Unavailable {
232                run_ref,
233                reason: Issue31EvidenceUnavailableReason::HostUnavailable,
234                broken_at: None,
235            },
236        });
237    };
238
239    let state = records
240        .run
241        .as_ref()
242        .filter(|run| record_run_ref(run) == Some(run_ref.as_str()))
243        .and_then(|run| run.get("state"))
244        .and_then(Value::as_str)
245        .and_then(lifecycle_from_host);
246
247    let chain = project_chain(&run_ref, records);
248
249    Some(ThreadRunLink {
250        run_ref,
251        agent_id: disclosure.agent_id.clone(),
252        state,
253        chain,
254    })
255}
256
257fn project_chain(run_ref: &PublicRef, records: &ThreadRunRecords) -> Issue31EvidenceChain {
258    let unavailable = |reason| Issue31EvidenceChain::Unavailable {
259        run_ref: run_ref.clone(),
260        reason,
261        broken_at: None,
262    };
263
264    let (Some(report), Some(receipt)) = (records.report.as_ref(), records.receipt.as_ref()) else {
265        return unavailable(Issue31EvidenceUnavailableReason::HostUnavailable);
266    };
267
268    // A pair belonging to another run is not this run's proof. Checked before
269    // the producer, because the producer is entitled to assume it was handed
270    // one run's records and would otherwise report a foreign but internally
271    // consistent chain as this thread's.
272    if record_run_ref(report) != Some(run_ref.as_str())
273        || record_run_ref(receipt) != Some(run_ref.as_str())
274    {
275        return unavailable(Issue31EvidenceUnavailableReason::HopMismatched);
276    }
277
278    match project_issue31_evidence_pair(report, receipt, Some(run_ref.as_str())) {
279        Ok(chain) if chain.run_ref() == run_ref => chain,
280        // The producer refuses to relabel a complete chain, so a chain naming
281        // another run reaching here means the records contradict themselves.
282        Ok(_) => unavailable(Issue31EvidenceUnavailableReason::HopMismatched),
283        // The two reachable decoder errors are both "the host produced this and
284        // this surface may not carry it": a hop reference outside the
285        // public-reference character class, and a detail past the carried
286        // bound. `hop_private` is the reason for exactly that situation.
287        Err(_) => unavailable(Issue31EvidenceUnavailableReason::HopPrivate),
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294    use serde_json::json;
295    use workroom_receipts::{ISSUE31_EVIDENCE_HOPS, Issue31EvidenceHopKind};
296
297    const NOW: u64 = 1_784_894_400_000;
298
299    fn engine_lane() -> ExecutorDisclosure {
300        ExecutorDisclosure {
301            class: ExecutorClass::EngineLane,
302            agent_id: "codex-acp".into(),
303            provider: None,
304            model: None,
305            run_ref: Some("run.fa.80".into()),
306            // A thread restored from before the router landed carries no
307            // recorded route. omega#78 says that is "not routed", not a route,
308            // and it must not stop the run behind it from being inspectable.
309            route: None,
310        }
311    }
312
313    fn run_record(state: &str) -> Value {
314        json!({
315            "runRef": "run.fa.80",
316            "title": "Receipts in thread",
317            "state": state,
318        })
319    }
320
321    fn evidence_pair() -> (Value, Value) {
322        (
323            json!({
324                "runRef": "run.fa.80",
325                "evidence": {
326                    "objectiveRef": "objective.fa.80",
327                    "turnRef": "turn.fa.80.11",
328                    "changeRef": "change.fa.80.11",
329                    "projectGeneration": "project.generation.9",
330                    "diffSummary": "3 files changed, 210 insertions",
331                    "testCommand": "cargo test -p full_auto_ui",
332                    "testOutcome": "passed",
333                    "verificationRef": "verification.host.fa.80.11",
334                    "hostExecuted": true
335                }
336            }),
337            json!({
338                "runRef": "run.fa.80",
339                "objectiveRef": "objective.fa.80",
340                "turnRef": "turn.fa.80.11",
341                "changeRef": "change.fa.80.11",
342                "verificationRef": "verification.host.fa.80.11",
343                "decisionRef": "decision.authority.fa.80.11",
344                "authorityReceiptRef": "receipt.authority.fa.80.11",
345                "allowed": true
346            }),
347        )
348    }
349
350    fn records(state: &str) -> ThreadRunRecords {
351        let (report, receipt) = evidence_pair();
352        ThreadRunRecords {
353            read_at_ms: NOW,
354            run: Some(run_record(state)),
355            report: Some(report),
356            receipt: Some(receipt),
357        }
358    }
359
360    fn link(records: Option<&ThreadRunRecords>, now_ms: u64) -> ThreadRunLink {
361        project_thread_run_link(&engine_lane(), records, now_ms).expect("an engine lane links")
362    }
363
364    #[test]
365    fn a_finished_run_renders_its_chain_in_the_normative_order() {
366        let records = records("completed");
367        let link = link(Some(&records), NOW);
368        assert_eq!(link.state, Some(Issue31FullAutoLifecycle::Succeeded));
369        assert!(link.is_receipted());
370
371        match &link.chain {
372            Issue31EvidenceChain::Complete { hops, .. } => {
373                let kinds: Vec<Issue31EvidenceHopKind> = hops.iter().map(|hop| hop.kind).collect();
374                assert_eq!(kinds, ISSUE31_EVIDENCE_HOPS.to_vec());
375            }
376            Issue31EvidenceChain::Unavailable { .. } => panic!("expected a complete chain"),
377        }
378
379        let lines: Vec<String> = link.fields().iter().map(InspectorField::line).collect();
380        let text = lines.join("\n");
381        assert!(text.contains("run_ref: run.fa.80"), "{text}");
382        assert!(text.contains("executed_by: codex-acp"), "{text}");
383        assert!(text.contains("run_state: succeeded"), "{text}");
384        assert!(text.contains("chain: complete"), "{text}");
385        assert!(text.contains("authority_allowed: true"), "{text}");
386        assert!(
387            text.contains("host_verification: verification.host.fa.80.11"),
388            "{text}"
389        );
390        assert!(
391            text.contains("receipt: receipt.authority.fa.80.11"),
392            "{text}"
393        );
394        assert!(
395            text.contains("test: verification.host.fa.80.11 · cargo test -p full_auto_ui"),
396            "the hop detail is the command the host ran: {text}"
397        );
398        assert!(!text.contains("/Users/"), "{text}");
399    }
400
401    /// Only engine-lane work has a run to project.
402    #[test]
403    fn a_thread_with_no_run_authority_has_no_run_link() {
404        for class in [ExecutorClass::NativeLoop, ExecutorClass::ExternalAcp] {
405            let disclosure = ExecutorDisclosure {
406                class,
407                run_ref: None,
408                ..engine_lane()
409            };
410            assert!(disclosure.is_coherent());
411            assert!(
412                project_thread_run_link(&disclosure, Some(&records("running")), NOW).is_none(),
413                "{class:?} threads own no run"
414            );
415        }
416    }
417
418    /// A routed result wearing the first-party name projects nothing.
419    ///
420    /// The incoherent record is the one omega#77 already refuses to call
421    /// coherent; this proves the run surface refuses it too, rather than
422    /// rendering a run panel on a thread that never had run authority.
423    #[test]
424    fn an_incoherent_disclosure_never_becomes_a_run_link() {
425        let native_claiming_a_run = ExecutorDisclosure {
426            class: ExecutorClass::NativeLoop,
427            run_ref: Some("run.fa.80".into()),
428            ..engine_lane()
429        };
430        assert!(!native_claiming_a_run.is_coherent());
431        assert!(
432            project_thread_run_link(&native_claiming_a_run, Some(&records("running")), NOW)
433                .is_none()
434        );
435
436        let engine_without_a_run = ExecutorDisclosure {
437            run_ref: None,
438            ..engine_lane()
439        };
440        assert!(!engine_without_a_run.is_coherent());
441        assert!(
442            project_thread_run_link(&engine_without_a_run, Some(&records("running")), NOW)
443                .is_none()
444        );
445
446        // omega#78: a fallback route means the router could not place the
447        // turn and it ran on the native loop. A record claiming both a
448        // fallback and an engine lane is two stories about one thread.
449        let fallback_claiming_a_lane = ExecutorDisclosure {
450            route: Some(omega_front_door::RouteReason::EngineUnreachable),
451            ..engine_lane()
452        };
453        assert!(!fallback_claiming_a_lane.is_coherent());
454        assert!(
455            project_thread_run_link(&fallback_claiming_a_lane, Some(&records("running")), NOW)
456                .is_none()
457        );
458    }
459
460    /// A run reference this surface may not repeat is not repeated.
461    #[test]
462    fn a_private_run_reference_is_never_rendered() {
463        let disclosure = ExecutorDisclosure {
464            run_ref: Some("/Users/owner/.omega/runs/80".into()),
465            ..engine_lane()
466        };
467        assert!(disclosure.is_coherent());
468        assert!(project_thread_run_link(&disclosure, Some(&records("running")), NOW).is_none());
469    }
470
471    /// The engine's silence is rendered as silence, not as its last answer.
472    ///
473    /// This is the "engine is the sole run authority" property with teeth: a
474    /// complete chain seen once must not stay on screen after the host stops
475    /// answering, or the thread has become the authority for it.
476    #[test]
477    fn a_stale_reading_stops_being_shown_as_the_runs_state() {
478        let records = records("completed");
479        assert!(link(Some(&records), NOW + THREAD_RUN_LINK_MAX_AGE_MS).is_receipted());
480
481        let stale = link(Some(&records), NOW + THREAD_RUN_LINK_MAX_AGE_MS + 1);
482        assert!(!stale.is_receipted());
483        assert_eq!(stale.state, None);
484        assert_eq!(
485            stale.unavailable_reason(),
486            Some(Issue31EvidenceUnavailableReason::HostUnavailable)
487        );
488        // The reference still shows: the thread knows which run it belongs to
489        // even when the engine is not answering about it.
490        assert_eq!(stale.run_ref.as_str(), "run.fa.80");
491    }
492
493    /// A host that has not answered at all is named as such.
494    #[test]
495    fn an_unanswered_host_renders_a_reason_rather_than_an_absence() {
496        let never_read = link(None, NOW);
497        assert_eq!(
498            never_read.unavailable_reason(),
499            Some(Issue31EvidenceUnavailableReason::HostUnavailable)
500        );
501        assert_eq!(never_read.state, None);
502
503        let no_receipt = ThreadRunRecords {
504            receipt: None,
505            ..records("running")
506        };
507        let partial = link(Some(&no_receipt), NOW);
508        assert_eq!(
509            partial.unavailable_reason(),
510            Some(Issue31EvidenceUnavailableReason::HostUnavailable)
511        );
512        // The half the host did answer is still shown. A missing receipt does
513        // not blank the run's state.
514        assert_eq!(partial.state, Some(Issue31FullAutoLifecycle::Running));
515    }
516
517    /// Every refusal path, watched refusing, and none of them silent.
518    #[test]
519    fn every_broken_chain_is_shown_with_its_reason() {
520        let reason = |mutate: &dyn Fn(&mut Value, &mut Value)| {
521            let (mut report, mut receipt) = evidence_pair();
522            mutate(&mut report, &mut receipt);
523            let records = ThreadRunRecords {
524                read_at_ms: NOW,
525                run: Some(run_record("completed")),
526                report: Some(report),
527                receipt: Some(receipt),
528            };
529            let link = link(Some(&records), NOW);
530            assert!(
531                !link.is_receipted(),
532                "a broken chain must never be claimed as receipted"
533            );
534            assert!(
535                !link.fields().is_empty(),
536                "a broken chain must still render rows"
537            );
538            link.unavailable_reason().expect("a named refusal")
539        };
540
541        assert_eq!(
542            reason(&|report, _| {
543                report["evidence"]
544                    .as_object_mut()
545                    .expect("evidence object")
546                    .remove("projectGeneration");
547            }),
548            Issue31EvidenceUnavailableReason::HopMissing
549        );
550        assert_eq!(
551            reason(&|_, receipt| receipt["changeRef"] = json!("change.someone-elses-work")),
552            Issue31EvidenceUnavailableReason::HopMismatched
553        );
554        assert_eq!(
555            reason(&|report, _| {
556                report["evidence"]["testCommand"] = json!("cat /Users/owner/.codex/auth.json");
557            }),
558            Issue31EvidenceUnavailableReason::HopPrivate
559        );
560        assert_eq!(
561            reason(&|report, _| report["evidence"]["hostExecuted"] = json!(false)),
562            Issue31EvidenceUnavailableReason::SelfReported
563        );
564        // A chain the host holds but this surface's bound refuses to carry.
565        assert_eq!(
566            reason(&|report, _| {
567                report["evidence"]["testCommand"] =
568                    json!(format!("cargo test -p {}", "x".repeat(300)));
569            }),
570            Issue31EvidenceUnavailableReason::HopPrivate
571        );
572    }
573
574    /// Another run's proof is not this run's proof.
575    ///
576    /// Both shapes are checked. A *complete* foreign chain is caught because
577    /// the producer refuses to relabel it. A *broken* foreign chain is not:
578    /// its refusal would arrive already wearing this run's reference, and
579    /// without the pair check the reader would be told this run has a missing
580    /// hop when in fact they are looking at somebody else's records.
581    #[test]
582    fn a_chain_belonging_to_another_run_is_refused_as_mismatched() {
583        let foreign = |mutate: &dyn Fn(&mut Value, &mut Value)| {
584            let (mut report, mut receipt) = evidence_pair();
585            report["runRef"] = json!("run.fa.81");
586            receipt["runRef"] = json!("run.fa.81");
587            mutate(&mut report, &mut receipt);
588            let records = ThreadRunRecords {
589                read_at_ms: NOW,
590                run: Some(run_record("completed")),
591                report: Some(report),
592                receipt: Some(receipt),
593            };
594            let link = link(Some(&records), NOW);
595            assert!(!link.is_receipted());
596            assert_eq!(link.run_ref.as_str(), "run.fa.80");
597            link.unavailable_reason()
598        };
599
600        assert_eq!(
601            foreign(&|_, _| {}),
602            Some(Issue31EvidenceUnavailableReason::HopMismatched),
603            "a complete chain proving another run is not this run's proof"
604        );
605        assert_eq!(
606            foreign(&|report, _| {
607                report["evidence"]
608                    .as_object_mut()
609                    .expect("evidence object")
610                    .remove("turnRef");
611            }),
612            Some(Issue31EvidenceUnavailableReason::HopMismatched),
613            "another run's broken records are a mismatch, not this run's \
614             missing hop"
615        );
616    }
617
618    /// A run record about a different run tells this thread nothing.
619    #[test]
620    fn a_state_from_another_runs_record_is_not_this_runs_state() {
621        let records = ThreadRunRecords {
622            run: Some(json!({"runRef": "run.fa.81", "state": "completed"})),
623            ..records("running")
624        };
625        assert_eq!(link(Some(&records), NOW).state, None);
626    }
627
628    /// An acknowledgement is not a completed command.
629    ///
630    /// A relay or a UI can report that it accepted a request. That is a
631    /// statement about a message, not about work, and it must not be
632    /// translated into the nearest lifecycle this contract does model.
633    #[test]
634    fn an_acknowledgement_is_never_read_as_a_run_state() {
635        for claimed in [
636            "acknowledged",
637            "accepted",
638            "sent",
639            "dispatched",
640            "delivered",
641            "ok",
642            "done",
643            "complete",
644        ] {
645            let records = ThreadRunRecords {
646                run: Some(json!({
647                    "runRef": "run.fa.80",
648                    "state": claimed,
649                    "acknowledged": true,
650                    "acknowledgedAtMs": NOW,
651                })),
652                ..records("running")
653            };
654            assert_eq!(
655                link(Some(&records), NOW).state,
656                None,
657                "{claimed:?} is not a lifecycle this contract models, and \
658                 guessing the nearest one is how a stalled run reads as done"
659            );
660        }
661    }
662
663    /// A terminal state is not a receipt.
664    ///
665    /// omega#80's falsifier: "a completed run is claimed without its receipt
666    /// refs resolvable". A succeeded run whose chain is unavailable renders
667    /// both facts, and `is_receipted` stays false.
668    #[test]
669    fn a_succeeded_run_without_a_chain_is_not_claimed_as_receipted() {
670        let records = ThreadRunRecords {
671            report: None,
672            receipt: None,
673            ..records("completed")
674        };
675        let link = link(Some(&records), NOW);
676        assert_eq!(link.state, Some(Issue31FullAutoLifecycle::Succeeded));
677        assert!(!link.is_receipted());
678
679        let text: Vec<String> = link.fields().iter().map(InspectorField::line).collect();
680        let text = text.join("\n");
681        assert!(text.contains("run_state: succeeded"), "{text}");
682        assert!(text.contains("chain: unavailable"), "{text}");
683        assert!(text.contains("chain_reason: host_unavailable"), "{text}");
684    }
685
686    /// A complete chain whose authority decision refused is not a receipt.
687    ///
688    /// The chain resolves — every hop is there and host-verified — and the
689    /// answer at the end is "no". Reading that as a receipted completion would
690    /// turn a refusal into an approval, which is the most consequential
691    /// misreading available on this surface.
692    #[test]
693    fn a_refused_authority_decision_is_complete_but_not_receipted() {
694        let (report, mut receipt) = evidence_pair();
695        receipt["allowed"] = json!(false);
696        let records = ThreadRunRecords {
697            read_at_ms: NOW,
698            run: Some(run_record("completed")),
699            report: Some(report),
700            receipt: Some(receipt),
701        };
702        let link = link(Some(&records), NOW);
703        assert!(matches!(link.chain, Issue31EvidenceChain::Complete { .. }));
704        assert!(link.unavailable_reason().is_none());
705        assert!(
706            !link.is_receipted(),
707            "an allowed:false decision is a resolvable chain and a refusal"
708        );
709
710        let text: Vec<String> = link.fields().iter().map(InspectorField::line).collect();
711        assert!(
712            text.join("\n").contains("authority_allowed: false"),
713            "{text:?}"
714        );
715    }
716
717    /// The link is a function of its inputs, which is what keeps the engine
718    /// the authority.
719    ///
720    /// Two projections from one reading are identical, and the record holds no
721    /// field a caller could set to change what a run appears to be doing.
722    #[test]
723    fn the_link_is_a_projection_and_holds_no_authority_of_its_own() {
724        let records = records("running");
725        assert_eq!(link(Some(&records), NOW), link(Some(&records), NOW));
726
727        let dumped = format!("{:?}", link(Some(&records), NOW));
728        for absent in ["cached", "override", "assumed", "last_known"] {
729            assert!(!dumped.contains(absent), "{dumped}");
730        }
731
732        // Changing only the engine's record changes the projection. Nothing in
733        // between remembers the old answer.
734        let stopped = ThreadRunRecords {
735            run: Some(run_record("stopped")),
736            ..records
737        };
738        assert_eq!(
739            link(Some(&stopped), NOW).state,
740            Some(Issue31FullAutoLifecycle::Stopped)
741        );
742    }
743}
744
Served at tenant.openagents/omega Member data and write actions are omitted.