docs: M6b implementation plan

Six closed tasks for consult: registry, CONSULT_WAIT, [consult]/[secrets],
ToolName and grants, Decision body, loopback HTTP. Reference is the git bundle
~/boxmaker-refs/m6b-ref.bundle at 5fb05f0. Gate after task 06 is 1611.

Co-Authored-By: Grok 4.6 <noreply@x.ai>
This commit is contained in:
2026-10-03 21:39:31 -07:00
co-authored by Grok 4.6
parent 09580c705a
commit 50e9cc2037
15 changed files with 2887 additions and 0 deletions
@@ -0,0 +1,84 @@
//! `[consult]` in `brokerd.toml`. Do not edit.
#[path = "support/tmp.rs"]
mod tmp;
use brokerd::config::{Config, ConfigError};
use brokerd::consult::{DEFAULT_TEMPERATURE_MILLI, DEFAULT_TIMEOUT_MS};
use tmp::TempDir;
fn load(text: &str) -> Result<Config, ConfigError> {
let dir = TempDir::new("cfg-consult");
let path = dir.write("brokerd.toml", text);
Config::load(&path)
}
const TABLE: &str = r#"
[consult]
url = "http://127.0.0.1:9/v1/chat/completions"
model = "test-model"
secret = "consult"
"#;
#[test]
fn no_consult_table_means_none() {
let c = load("").unwrap();
assert!(c.consult.is_none());
assert!(c.secrets.is_empty());
}
#[test]
fn a_consult_table_loads_defaults() {
let c = load(TABLE).unwrap();
let consult = c.consult.expect("consult");
assert_eq!(consult.url, "http://127.0.0.1:9/v1/chat/completions");
assert_eq!(consult.model, "test-model");
assert_eq!(consult.secret, "consult");
assert_eq!(consult.temperature_milli, DEFAULT_TEMPERATURE_MILLI);
assert_eq!(consult.timeout_ms, DEFAULT_TIMEOUT_MS);
assert!((consult.temperature() - 0.6).abs() < 1e-9);
}
#[test]
fn temperature_above_zero_point_six_is_invalid() {
let text = format!("{TABLE}temperature = 0.7\n");
match load(&text) {
Err(ConfigError::Parse(_, err)) => {
let msg = err.to_string();
assert!(msg.contains("0.6"), "{msg}");
}
other => panic!("{other:?}"),
}
}
#[test]
fn a_non_loopback_url_is_invalid() {
let text = r#"
[consult]
url = "https://example.com/v1/chat/completions"
model = "x"
secret = "consult"
"#;
match load(text) {
Err(ConfigError::Invalid(_, why)) => {
assert!(
why.contains("http://127.0.0.1") || why.contains("localhost"),
"{why}"
);
}
other => panic!("{other:?}"),
}
}
#[test]
fn unknown_consult_keys_are_errors() {
let text = format!("{TABLE}vendor = \"x\"\n");
assert!(matches!(load(&text), Err(ConfigError::Parse(_, _))));
}
#[test]
fn a_secret_spec_loads_under_secrets() {
let text = format!("{TABLE}\n[secrets.consult]\nenv = \"CONSULT_TOKEN\"\n");
let c = load(&text).unwrap();
assert!(c.secrets.contains_key("consult"));
}
@@ -0,0 +1,185 @@
//! An allowed `consult`: fake HTTP 200, audit body is the allowlisted JSON, the tool result is
//! the answer text. Do not edit.
#[path = "support/client.rs"]
mod client;
#[path = "support/rig.rs"]
mod rig;
#[path = "support/runtime.rs"]
mod runtime;
#[path = "support/sink.rs"]
mod sink;
#[path = "support/tmp.rs"]
mod tmp;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use brokerd::consult::{self, Consult, ConsultRuntime, ConsultTransport};
use bxcore::secrets::Secret;
use bxcore::{AuditEvent, DataClass, DecisionRecord, ResultStatus, ToolRequest, ToolResponse};
use client::call;
use rig::{Rig, request};
struct SharedFake {
reply: Vec<u8>,
seen: Arc<Mutex<Option<Vec<u8>>>>,
}
impl ConsultTransport for SharedFake {
fn post(
&self,
_url: &str,
_bearer: &str,
body: &[u8],
_timeout: Duration,
) -> Result<Vec<u8>, String> {
*self.seen.lock().unwrap() = Some(body.to_vec());
Ok(self.reply.clone())
}
}
fn consult_cfg() -> Consult {
Consult {
url: "http://127.0.0.1:9/v1/chat/completions".to_string(),
model: "test-model".to_string(),
temperature_milli: 600,
timeout_ms: 1_000,
secret: "consult".to_string(),
}
}
fn grant_consult(rig: &Rig) {
rig.grant(
"consult",
"tool = \"consult\"\nmode = \"auto\"\nmax_taint = \"private\"\n",
);
}
fn question_req(question: &str) -> ToolRequest {
request(
"s1",
1,
"consult",
&format!(
r#"{{"question":{}}}"#,
serde_json::to_string(question).unwrap()
),
)
}
fn broker_with_fake(
rig: &Rig,
cfg: Consult,
reply: Vec<u8>,
seen: Arc<Mutex<Option<Vec<u8>>>>,
) -> Arc<brokerd::broker::Broker> {
let runtime = ConsultRuntime::new(
cfg,
Secret::new("token".to_string()),
SharedFake { reply, seen },
);
Arc::new(brokerd::broker::Broker::new(
rig.cfg.clone(),
rig.ledger(),
Box::new(runtime),
rig.lines.sink(),
))
}
#[test]
fn an_allowed_consult_posts_only_the_question_and_returns_the_answer() {
let mut rig = Rig::new("consult-allow");
let cfg = consult_cfg();
rig.cfg.consult = Some(cfg.clone());
grant_consult(&rig);
let tag = concat!("<", "think", ">");
let raw_answer = format!("blue {tag} sky");
let reply = serde_json::json!({
"id": "ignored",
"choices": [{"index": 0, "message": {"role": "assistant", "content": raw_answer}, "finish_reason": "stop"}],
"usage": {"prompt_tokens": 1}
})
.to_string()
.into_bytes();
let recorded = Arc::new(Mutex::new(None));
let broker = broker_with_fake(&rig, cfg.clone(), reply, Arc::clone(&recorded));
let question = "what colour is the sky?";
let frames = call(&broker, question_req(question));
let want_body = consult::wire_body(Some(&cfg), &format!(r#"{{"question":"{question}"}}"#));
match &frames[0].msg {
bxcore::Message::ToolResponse(ToolResponse::Result {
content,
class,
untrusted,
truncated,
}) => {
assert!(content.contains("blue"), "{content}");
assert!(content.contains("sky"), "{content}");
assert!(!content.contains(tag), "{content}");
assert!(content.contains("&lt;"), "{content}");
assert_eq!(*class, DataClass::Private);
assert!(*untrusted);
assert!(!*truncated);
}
other => panic!("{other:?}"),
}
let posted = recorded.lock().unwrap().clone().expect("posted");
assert_eq!(posted, want_body.as_bytes());
match rig.events().as_slice() {
[
AuditEvent::Decision {
outcome,
body: Some(body),
tool,
..
},
AuditEvent::Result {
status: ResultStatus::Result,
..
},
] => {
assert_eq!(*outcome, DecisionRecord::Allowed {});
assert_eq!(tool, "consult");
assert_eq!(body, &want_body);
let v: serde_json::Value = serde_json::from_str(body).unwrap();
assert_eq!(v["model"], "test-model");
assert_eq!(v["temperature"], 0.6);
assert_eq!(v["messages"].as_array().unwrap().len(), 1);
assert_eq!(v["messages"][0]["role"], "user");
assert_eq!(v["messages"][0]["content"], question);
assert!(v.get("tools").is_none());
}
other => panic!("{other:?}"),
}
}
/// The body is only `question`, even when the caller had earlier user text in its own session.
#[test]
fn the_consult_body_is_only_the_question() {
let mut rig = Rig::new("consult-nohist");
let cfg = consult_cfg();
rig.cfg.consult = Some(cfg.clone());
grant_consult(&rig);
let reply = serde_json::json!({
"choices": [{"message": {"content": "ok"}}]
})
.to_string()
.into_bytes();
let recorded = Arc::new(Mutex::new(None));
let broker = broker_with_fake(&rig, cfg, reply, Arc::clone(&recorded));
let arguments = r#"{"question":"only this"}"#;
let _ = call(&broker, request("s1", 4, "consult", arguments));
let posted = recorded.lock().unwrap().clone().expect("posted");
let v: serde_json::Value = serde_json::from_slice(&posted).unwrap();
assert_eq!(v["messages"].as_array().unwrap().len(), 1);
assert_eq!(v["messages"][0]["content"], "only this");
match &rig.events()[0] {
AuditEvent::Decision {
body: Some(body), ..
} => {
assert_eq!(body.as_bytes(), posted.as_slice());
}
other => panic!("{other:?}"),
}
}
@@ -0,0 +1,168 @@
//! Consult denials: not configured, taint too high, unknown keys. Each records the body. Do not edit.
#[path = "support/client.rs"]
mod client;
#[path = "support/rig.rs"]
mod rig;
#[path = "support/runtime.rs"]
mod runtime;
#[path = "support/sink.rs"]
mod sink;
#[path = "support/tmp.rs"]
mod tmp;
use std::sync::Arc;
use brokerd::consult::{self, Consult};
use brokerd::policy::{Label, SessionState};
use bxcore::{
AuditEvent, DataClass, DecisionRecord, DenyReason, SessionId, ToolRequest, ToolResponse,
};
use client::{Serve, call};
use rig::{Rig, request};
use runtime::Recording;
fn consult_req(session: &str, call: u64, arguments: &str) -> ToolRequest {
request(session, call, "consult", arguments)
}
fn consult_cfg() -> Consult {
Consult {
url: "http://127.0.0.1:9/v1/chat/completions".to_string(),
model: "test-model".to_string(),
temperature_milli: 600,
timeout_ms: 1_000,
secret: "consult".to_string(),
}
}
fn grant_consult(rig: &Rig, max_taint: &str) {
rig.grant(
"consult",
&format!("tool = \"consult\"\nmode = \"auto\"\nmax_taint = \"{max_taint}\"\n"),
);
}
fn decision_body(events: &[AuditEvent]) -> Option<&str> {
match events.first() {
Some(AuditEvent::Decision { body, .. }) => body.as_deref(),
_ => None,
}
}
#[test]
fn no_consult_table_is_denied_with_the_runbook_and_records_the_body() {
let rig = Rig::new("consult-noconfig");
let rt = Recording::answering("no");
let broker = rig.broker(&rt);
let question = r#"{"question":"what colour is the sky?"}"#;
let frames = call(&broker, consult_req("s1", 1, question));
assert_eq!(rt.count(), 0);
match &frames[0].msg {
bxcore::Message::ToolResponse(ToolResponse::Denied { reason, detail }) => {
assert_eq!(*reason, DenyReason::InvalidArguments);
let detail = detail.as_deref().unwrap_or("");
assert!(detail.contains("consult-not-configured"), "{detail}");
assert!(detail.contains("docs/runbook.md"), "{detail}");
}
other => panic!("{other:?}"),
}
let events = rig.events();
let body = decision_body(&events).expect("body");
let want = consult::wire_body(None, question);
assert_eq!(body, want);
assert!(body.contains("what colour is the sky?"), "{body}");
assert!(!body.contains("tools"), "{body}");
}
#[test]
fn a_secret_session_is_taint_too_high_and_records_the_body() {
let mut rig = Rig::new("consult-taint");
rig.cfg.consult = Some(consult_cfg());
grant_consult(&rig, "private");
let sid = SessionId::new("s1").unwrap();
rig.state()
.raise(
&sid,
SessionState {
taint: DataClass::Secret,
untrusted: false,
},
Label {
class: DataClass::Secret,
untrusted: false,
},
)
.unwrap();
let rt = Recording::answering("no");
let broker = Arc::new(brokerd::broker::Broker::new(
rig.cfg.clone(),
rig.ledger(),
Box::new(runtime::Shared(Arc::clone(&rt))),
rig.lines.sink(),
));
let question = r#"{"question":"summarise the notes"}"#;
let frames = call(&broker, consult_req("s1", 2, question));
assert_eq!(rt.count(), 0);
match &frames[0].msg {
bxcore::Message::ToolResponse(ToolResponse::Denied { reason, detail }) => {
assert_eq!(*reason, DenyReason::TaintTooHigh);
assert_eq!(*detail, None);
}
other => panic!("{other:?}"),
}
match rig.events().as_slice() {
[
AuditEvent::Decision {
outcome,
body: Some(body),
taint,
..
},
] => {
assert_eq!(
*outcome,
DecisionRecord::Denied {
reason: DenyReason::TaintTooHigh
}
);
assert_eq!(*taint, DataClass::Secret);
let want = consult::wire_body(rig.cfg.consult.as_ref(), question);
assert_eq!(body, &want);
assert!(body.contains("\"model\":\"test-model\""), "{body}");
assert_eq!(
serde_json::from_str::<serde_json::Value>(body).unwrap()["messages"]
.as_array()
.unwrap()
.len(),
1
);
}
other => panic!("{other:?}"),
}
}
#[test]
fn an_unknown_field_is_invalid_arguments() {
let mut rig = Rig::new("consult-unknown");
rig.cfg.consult = Some(consult_cfg());
grant_consult(&rig, "secret");
let rt = Recording::answering("no");
let broker = Arc::new(brokerd::broker::Broker::new(
rig.cfg.clone(),
rig.ledger(),
Box::new(runtime::Shared(Arc::clone(&rt))),
rig.lines.sink(),
));
let frames = call(
&broker,
consult_req("s1", 3, r#"{"question":"hi","history":["old"]}"#),
);
assert_eq!(rt.count(), 0);
match &frames[0].msg {
bxcore::Message::ToolResponse(ToolResponse::Denied { reason, .. }) => {
assert_eq!(*reason, DenyReason::InvalidArguments);
}
other => panic!("{other:?}"),
}
}
@@ -0,0 +1,82 @@
//! `[consult]` without a readable secret: `brokerd` does not start. Do not edit.
#[path = "support/tmp.rs"]
mod tmp;
use std::ffi::OsString;
use brokerd::config::Config;
use brokerd::consult::{self, SECRET_POINTER};
use tmp::TempDir;
#[test]
fn a_missing_secrets_entry_is_consult_secret_unreadable() {
let dir = TempDir::new("consult-secret-missing");
let path = dir.write(
"brokerd.toml",
r#"
[consult]
url = "http://127.0.0.1:9/v1/chat/completions"
model = "test-model"
secret = "consult"
"#,
);
let cfg = Config::load(&path).unwrap();
let err = consult::load_secret(&cfg, &|_| None).unwrap_err();
let text = err.to_string();
assert!(text.contains(SECRET_POINTER), "{text}");
assert!(text.contains("consult"), "{text}");
}
#[test]
fn an_unreadable_env_secret_names_both_pointers() {
let dir = TempDir::new("consult-secret-env");
let path = dir.write(
"brokerd.toml",
r#"
[consult]
url = "http://127.0.0.1:9/v1/chat/completions"
model = "test-model"
secret = "consult"
[secrets.consult]
env = "CONSULT_TOKEN_FOR_THIS_TEST_UNSET"
"#,
);
let cfg = Config::load(&path).unwrap();
let err = consult::load_secret(&cfg, &|_| None).unwrap_err();
let text = err.to_string();
assert!(text.contains(SECRET_POINTER), "{text}");
assert!(
text.contains("docs/runbook.md#secret-unavailable"),
"{text}"
);
}
#[test]
fn a_present_env_secret_loads() {
let dir = TempDir::new("consult-secret-ok");
let path = dir.write(
"brokerd.toml",
r#"
[consult]
url = "http://127.0.0.1:9/v1/chat/completions"
model = "test-model"
secret = "consult"
[secrets.consult]
env = "CONSULT_TOKEN_FOR_THIS_TEST_SET"
"#,
);
let cfg = Config::load(&path).unwrap();
let loaded = consult::load_secret(&cfg, &|k| {
if k == "CONSULT_TOKEN_FOR_THIS_TEST_SET" {
Some(OsString::from("bearer-value"))
} else {
None
}
})
.unwrap()
.expect("loaded");
assert_eq!(loaded.secret.expose(), "bearer-value");
}
@@ -0,0 +1,76 @@
//! A `consult` grant: empty constraints, no paths or hosts. Do not edit.
#[path = "support/tmp.rs"]
mod tmp;
use brokerd::args::{ToolArgs, ToolName, parse};
use brokerd::grants::load;
use tmp::TempDir;
fn body(rest: &str) -> String {
format!("tool = \"consult\"\nmode = \"auto\"\nmax_taint = \"private\"\n{rest}")
}
#[test]
fn consult_parses_and_is_not_a_container_tool() {
assert_eq!(ToolName::parse("consult"), Some(ToolName::Consult));
assert_eq!(ToolName::Consult.as_str(), "consult");
assert!(!ToolName::Consult.is_memory());
assert!(
!ToolName::ALL.contains(&ToolName::Consult),
"ALL is the container tools"
);
let args = parse(ToolName::Consult, r#"{"question":"hello"}"#).unwrap();
assert_eq!(args.tool(), ToolName::Consult);
assert_eq!(
args,
ToolArgs::Consult {
question: "hello".to_string()
}
);
assert_eq!(args.canonical_json(), r#"{"question":"hello"}"#);
}
#[test]
fn unknown_keys_are_invalid_arguments() {
let err = parse(ToolName::Consult, r#"{"question":"hello","history":[]}"#).unwrap_err();
let detail = err.detail();
assert!(
detail.contains("unknown") || detail.contains("history"),
"{detail}"
);
}
#[test]
fn a_consult_grant_with_empty_constraints_loads() {
let dir = TempDir::new("grants-consult");
dir.write("consult.toml", &body(""));
let set = load(dir.path()).unwrap();
assert_eq!(set.grants()[0].grant.tool, "consult");
assert!(set.grants()[0].grant.untrusted);
}
#[test]
fn consult_does_not_take_paths_or_hosts() {
let dir = TempDir::new("grants-consult-paths");
dir.write("consult.toml", &body("[constraints]\npaths = [\"/tmp\"]\n"));
let problems = load(dir.path()).expect_err("paths");
assert!(
problems
.iter()
.any(|p| p.problem.contains("does not take paths")),
"{problems:?}"
);
let dir = TempDir::new("grants-consult-hosts");
dir.write(
"consult.toml",
&body("[constraints]\nhosts = [\"example.com\"]\n"),
);
let problems = load(dir.path()).expect_err("hosts");
assert!(
problems
.iter()
.any(|p| p.problem.contains("does not take hosts")),
"{problems:?}"
);
}
@@ -0,0 +1,22 @@
//! How long `BrokerPort` waits for `consult`: 180 s, or `[broker] timeout_ms` if that is longer.
//! Do not edit.
use std::time::Duration;
use loopd::broker_port::{BrokerPort, CONSULT_WAIT};
fn quiet(timeout: Duration) -> BrokerPort {
BrokerPort::with_log("unused.sock".into(), timeout, Box::new(|_| {}))
}
#[test]
fn consult_waits_one_hundred_and_eighty_seconds() {
assert_eq!(CONSULT_WAIT, Duration::from_secs(180));
let port = BrokerPort::new("unused.sock".into(), Duration::from_secs(120));
assert_eq!(port.timeout_for("consult"), CONSULT_WAIT);
assert_eq!(port.timeout_for("recall"), Duration::from_secs(120));
let port = BrokerPort::new("unused.sock".into(), Duration::from_secs(300));
assert_eq!(port.timeout_for("consult"), Duration::from_secs(300));
let port = quiet(Duration::from_secs(120));
assert_eq!(port.timeout_for("consult"), CONSULT_WAIT, "with_log as new");
}
@@ -0,0 +1,58 @@
//! `consult` in the M5c registry, not core. Do not edit.
mod support;
use loopd::baseline::Baseline;
use loopd::consult::{self, CONSULT};
use loopd::tools::Registry;
use std::path::Path;
use support::Home;
fn core_names(registry: &Registry) -> Vec<String> {
registry
.core_schemas()
.into_iter()
.map(|s| s.name)
.collect()
}
#[test]
fn consult_is_in_m5c_and_is_not_core() {
let r = Registry::m5c();
let entry = r.get(CONSULT).expect("consult");
assert!(!entry.core);
assert_eq!(entry.schema, consult::schema());
assert_eq!(
core_names(&r),
[
"clock",
"recall",
"memory",
"ask_memory",
"find_tool",
"call_tool"
]
);
assert!(r.find("consult").iter().any(|s| s.name == CONSULT));
assert!(r.find("remote").iter().any(|s| s.name == CONSULT));
assert!(r.get("subagent").is_some());
}
#[test]
fn a_baseline_from_m5c_does_not_put_consult_in_the_tools_array() {
let home = Home::new();
let cfg = home.config(Path::new("/tmp/unused.sock"));
let baseline = Baseline::assemble(&cfg, &Registry::m5c()).unwrap();
let names: Vec<&str> = baseline.tools.iter().map(|t| t.name.as_str()).collect();
assert_eq!(
names,
[
"clock",
"recall",
"memory",
"ask_memory",
"find_tool",
"call_tool"
]
);
}
+44
View File
@@ -0,0 +1,44 @@
## consult-not-configured
**What you see.** A `consult` call is denied as invalid-arguments. The tool result names this
entry:
```text
Denied: the arguments are not valid for this tool: consult is not configured; see docs/runbook.md#consult-not-configured.
```
**Why.** `brokerd.toml` has no `[consult]` table. Every `consult` call is denied until the owner
writes one. The audit line still holds the JSON body that would have been sent.
**Confirm.**
```sh
grep -n '\[consult\]' "$BOXMAKER_HOME/../brokerd.toml" /etc/boxmaker/brokerd.toml 2>/dev/null
```
**Fix.** Add a `[consult]` table with `url`, `model` and `secret`, a matching `[secrets.<name>]`
entry, and a grant whose `tool` is `consult`. Restart `brokerd`. M6's URL is loopback only.
**Check.** A `consult` from a session whose taint is at or below the grant's `max_taint` returns
an answer, and the audit line's `body` matches the bytes posted.
## consult-secret-unreadable
**What you see.** `brokerd` does not start. The line names the secret and this entry, and may
also name `secret-unavailable` or `secret-in-a-file`.
**Why.** `[consult]` names a secret that is missing from `[secrets]`, whose spec is not exactly
one of credential/env/file, or that the secret store cannot read (missing, too open, or not
UTF-8).
**Confirm.**
```sh
# the name in [consult] secret = "…"
ls -l <the file or credential path>
```
**Fix.** Put the bearer in the store the spec names, mode `0600` for a file, owned by the
user `brokerd` runs as. Restart `brokerd`.
**Check.** `brokerd serve --config <path>` prints the serving lines and no secret error.