speccoder_runtime/substrate/provenance.rs
1//! Where content came from.
2//!
3//! agentplane's gates — what may go to which model, which arguments may
4//! drive a mutating tool — decide on labels. So every value speccoder hands
5//! the runtime says where it came from: speccoder's own instructions and the
6//! person's committed spec and decisions are trusted; a check's output, the
7//! worktree's content, an MCP server's words and the model's answers are not.
8//!
9//! Labels are given where content enters — a skill input is assembled from
10//! labelled parts — and read back with `label_at`, never inferred from what
11//! the content looks like: agentplane journals the field labels a run was
12//! admitted with (0.50), live and in replay.
13
14use agentplane::core::{Label, SourceId, Tainted};
15use serde_json::Value;
16
17/// Check output quoted in a retry digest: the verifier's facts.
18pub const VERIFIER: &str = "speccoder://verifier";
19
20/// Worktree content speccoder inlines into a prompt (a patch, files, a manifest).
21pub const WORKTREE: &str = "speccoder://worktree";
22
23/// A person's answer to a question: their input, not speccoder's;
24/// who they were is on the decision's record, not in the label.
25pub const PERSON: &str = "speccoder://person";
26
27/// The source of a tool's results and of an MCP tool's description.
28#[must_use]
29pub fn tool(server: &str, name: &str) -> SourceId {
30 agentplane::tools::ToolId::new(server, name).source()
31}
32
33/// An untrusted label from `source`.
34#[must_use]
35pub fn from(source: &str) -> Label {
36 Label::untrusted(SourceId::new(source))
37}
38
39/// A prompt whose `/system` is speccoder's constant instruction (trusted)
40/// and whose every other part carries `label`.
41#[must_use]
42pub fn labelled_prompt(prompt: Value, label: &Label) -> Tainted<Value> {
43 let Value::Object(parts) = prompt else {
44 return Tainted::with_label(prompt, label.clone());
45 };
46 Tainted::object(parts.into_iter().map(|(k, v)| {
47 let part = if k == "system" {
48 Tainted::trusted(v)
49 } else {
50 Tainted::with_label(v, label.clone())
51 };
52 (k, part)
53 }))
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use serde_json::json;
60
61 #[test]
62 fn the_instruction_stays_trusted_and_the_rest_carries_its_sources() {
63 let p = labelled_prompt(
64 json!({ "system": "be careful", "messages": [{ "role": "user", "content": "x" }] }),
65 &from(VERIFIER),
66 );
67 assert!(!p.label_at("/system").is_some_and(Label::is_untrusted));
68 assert!(p.label_at("/messages").is_some_and(Label::is_untrusted));
69 assert!(p.label().is_untrusted());
70 }
71}