use assert_cmd::Command;
use rusqlite::{params, Connection};
use sha2::{Digest, Sha256};
use std::fs;
use std::io::{BufRead, BufReader, ErrorKind, Read, Write};
use std::net::{Shutdown, TcpStream};
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use std::path::{Path, PathBuf};
use std::process::{Command as StdCommand, Stdio};
use tempfile::tempdir;
fn root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn run_zynk(args: &[&str]) -> std::process::Output {
let cwd = tempdir().unwrap();
Command::cargo_bin("zynk")
.unwrap()
.current_dir(cwd.path())
.args(args)
.output()
.unwrap()
}
fn run_zynk_in(args: &[&str], cwd: &Path) -> std::process::Output {
let mut command = Command::cargo_bin("zynk").unwrap();
command.current_dir(cwd).args(args).output().unwrap()
}
fn stdout(output: &std::process::Output) -> String {
String::from_utf8_lossy(&output.stdout).to_string()
}
fn stderr(output: &std::process::Output) -> String {
String::from_utf8_lossy(&output.stderr).to_string()
}
fn fake_herdr(dir: &Path, body: &str) -> PathBuf {
let path = dir.join("fake-herdr");
fs::write(&path, format!("#!/bin/sh\n{body}")).unwrap();
let mut perms = fs::metadata(&path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&path, perms).unwrap();
path
}
#[test]
fn top_level_help_lists_subcommands() {
let output = run_zynk(&["--help"]);
assert!(output.status.success(), "{}", stderr(&output));
let out = stdout(&output);
assert!(out.contains("compose"));
assert!(out.contains("send"));
assert!(out.contains("status"));
assert!(out.contains("audit"));
assert!(out.contains("dashboard"));
assert!(out.contains("db"));
}
#[test]
fn send_help_lists_herdr_transport() {
let output = run_zynk(&["send", "--help"]);
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains("herdr"));
}
#[test]
fn send_herdr_help_says_session_id_replaces_manual_audit() {
let out = stdout(&run_zynk(&["send", "herdr", "--help"]));
let flat = out.split_whitespace().collect::<Vec<_>>().join(" ");
assert!(
flat.contains("records the sender audit + message corpus automatically"),
"send herdr --help must say --session-id auto-records: {flat}"
);
assert!(
flat.contains("do not also run zynk audit for the same sent message"),
"send herdr --help must warn against a separate audit: {flat}"
);
}
#[test]
fn audit_help_says_when_manual_audit_is_appropriate() {
let out = stdout(&run_zynk(&["audit", "--help"]));
let flat = out.split_whitespace().collect::<Vec<_>>().join(" ");
assert!(
flat.contains("manual proof, corrections, recovery"),
"audit --help must state its manual/proof/correction/recovery purpose: {flat}"
);
assert!(
flat.contains("not for a message already sent with zynk send herdr --session-id"),
"audit --help must say it is not for already-audited sends: {flat}"
);
}
#[test]
fn compose_subcommand_outputs_protocol_message() {
let output = run_zynk(&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"request-review",
"--ref",
"tools/status-update",
"--mode",
"review",
"--body",
"Please review status-update.",
]);
assert!(output.status.success(), "{}", stderr(&output));
assert_eq!(
stdout(&output).trim(),
"[from-codex via herdr] [herdr from=codex:w652dc9b3ded432-2 to=claude:w652dc9b3ded432-1 mid=abc123 type=request-review ref=tools/status-update mode=review] BODY: Please review status-update."
);
}
#[test]
fn send_herdr_subcommand_dry_run() {
let output = run_zynk(&[
"send",
"herdr",
"--pane",
"w652dc9b3ded432-1",
"--dry-run",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"request-review",
"--ref",
"tools/status-update",
"--body",
"Please review status-update.",
]);
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains("[from-codex via herdr]"));
assert!(stdout(&output).contains("type=request-review"));
}
#[test]
fn status_subcommand_writes_under_outputs_root() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"status",
"--root",
&root,
"--session-id",
"session-a",
"--timestamp",
"2026-05-28T22:30:00+07:00",
"--phase",
"tooling",
"--mode",
"validate",
"--artifact-ref",
"outputs/sessions/session-a/status.md",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"compose verified",
"--in-progress",
"cli packaging",
"--next-action",
"append audit",
]);
let status_path = tmp.path().join("sessions/session-a/status.md");
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains(status_path.to_str().unwrap()));
}
#[test]
fn audit_subcommand_writes_under_outputs_root() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"abc123",
"--timestamp",
"2026-05-28T22:35:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"def456",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"audit payload",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
let audit_path = tmp.path().join("sessions/session-a/audit.md");
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains(audit_path.to_str().unwrap()));
}
fn run_audit_with_optional_due(root: &str, due: Option<&str>) -> std::process::Output {
let mut args = vec![
"audit",
"--root",
root,
"--session-id",
"session-due",
"--audit-id",
"due001",
"--timestamp",
"2026-05-29T00:00:00Z",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"mdue",
"--type",
"request-action",
"--command-origin",
"agent",
"--payload",
"due payload",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
];
if let Some(due) = due {
args.push("--due");
args.push(due);
}
run_zynk(&args)
}
#[test]
fn audit_due_offset_is_canonicalized_to_utc_z() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_audit_with_optional_due(&root, Some("2026-06-01T19:00:00+07:00"));
assert!(output.status.success(), "{}", stderr(&output));
let content = fs::read_to_string(tmp.path().join("sessions/session-due/audit.md")).unwrap();
assert!(
content.contains("due=2026-06-01T12:00:00Z"),
"due must be canonicalized to UTC Z; got:\n{content}"
);
}
#[test]
fn audit_due_invalid_is_rejected() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_audit_with_optional_due(&root, Some("not-a-timestamp"));
assert_eq!(
output.status.code(),
Some(2),
"invalid --due must be a usage error; stderr:\n{}",
stderr(&output)
);
assert!(stderr(&output).to_lowercase().contains("due"));
}
#[test]
fn audit_without_due_writes_no_due_line() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_audit_with_optional_due(&root, None);
assert!(output.status.success(), "{}", stderr(&output));
let content = fs::read_to_string(tmp.path().join("sessions/session-due/audit.md")).unwrap();
assert!(
!content.contains("due="),
"no due= line when --due omitted; got:\n{content}"
);
}
fn run_status_due(
root: &str,
db: &[&str],
session: &str,
phase: &str,
status: &str,
lead: &str,
artifact: &str,
) -> std::process::Output {
let mut args = vec![
"status",
"--root",
root,
"--session-id",
session,
"--phase",
phase,
"--mode",
"build",
"--artifact-ref",
artifact,
"--lead-agent",
lead,
"--status",
status,
"--completed",
"c",
"--in-progress",
"ip",
"--next-action",
"na",
"--event",
"ev",
];
args.extend_from_slice(db);
run_zynk(&args)
}
#[test]
fn status_db_projects_and_updates_session_current_state() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let first = run_status_due(root, &["--db", db], "sd", "p1", "working", "claude", "a1");
assert!(first.status.success(), "{}", stderr(&first));
let second = run_status_due(root, &["--db", db], "sd", "p2", "blocked", "codex", "a2");
assert!(second.status.success(), "{}", stderr(&second));
let conn = Connection::open(db).unwrap();
let (phase, status, lead, artifact): (String, String, Option<String>, Option<String>) = conn
.query_row(
"SELECT phase, workflow_status, lead_agent_id, artifact_ref FROM sessions WHERE session_id='sd'",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)
.unwrap();
assert_eq!(
phase, "p2",
"second status write must UPDATE the session row"
);
assert_eq!(status, "blocked");
assert_eq!(lead.as_deref(), Some("codex"));
assert_eq!(artifact.as_deref(), Some("a2"));
let events: i64 = conn
.query_row(
"SELECT COUNT(*) FROM status_events WHERE session_id='sd'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(events, 2);
}
#[test]
fn status_no_db_flag_writes_file_only() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_status_due(
root.to_str().unwrap(),
&["--db", db.to_str().unwrap(), "--no-db"],
"s",
"p",
"working",
"claude",
"a",
);
assert!(out.status.success(), "{}", stderr(&out));
assert!(!db.exists(), "--no-db must not create the DB");
assert!(root.join("sessions/s/status.md").exists());
}
#[test]
fn status_auto_creates_default_db_and_projects() {
let tmp = tempdir().unwrap();
let out = run_zynk_in(
&[
"status",
"--root",
"outputs",
"--session-id",
"s",
"--phase",
"p",
"--mode",
"build",
"--artifact-ref",
"a",
"--lead-agent",
"claude",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"ip",
"--next-action",
"na",
"--event",
"ev",
],
tmp.path(),
);
assert!(out.status.success(), "{}", stderr(&out));
let db = tmp.path().join(".zynk/zynk.db");
assert!(
db.exists(),
"status with no flags must auto-create .zynk/zynk.db (ADR 028)"
);
let gitignore = tmp.path().join(".zynk/.gitignore");
assert!(
gitignore.exists() && fs::read_to_string(&gitignore).unwrap().contains('*'),
".zynk/.gitignore must be written with `*`"
);
let conn = Connection::open(&db).unwrap();
let phase: String = conn
.query_row(
"SELECT phase FROM sessions WHERE session_id='s'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(phase, "p");
assert!(
stderr(&out).contains(".zynk/zynk.db"),
"first-run notice expected on auto-create; stderr:\n{}",
stderr(&out)
);
assert!(tmp.path().join("outputs/sessions/s/status.md").exists());
}
#[test]
fn audit_db_projects_file_chain_identity() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let head = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"sx",
"--audit-id",
"aaa111",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"status-update",
"--command-origin",
"agent",
"--payload",
"p1",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(head.status.success(), "{}", stderr(&head));
let child = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"sx",
"--audit-id",
"bbb222",
"--source-agent",
"codex",
"--source-address",
"w-2",
"--target-agent",
"claude",
"--target-address",
"w-1",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m2",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"p2",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(child.status.success(), "{}", stderr(&child));
let conn = Connection::open(db).unwrap();
let previous: Option<String> = conn
.query_row(
"SELECT previous_audit_id FROM audit_records WHERE audit_id='bbb222'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
previous.as_deref(),
Some("aaa111"),
"DB must carry the file-rendered chain identity verbatim"
);
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE session_id='sx'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(count, 2);
}
#[test]
fn audit_db_projects_canonical_due_value() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"sd",
"--audit-id",
"due777",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"md",
"--type",
"request-action",
"--command-origin",
"agent",
"--payload",
"p",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
"--due",
"2026-06-01T19:00:00+07:00",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(db).unwrap();
let due: Option<String> = conn
.query_row(
"SELECT due FROM audit_records WHERE audit_id='due777'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
due.as_deref(),
Some("2026-06-01T12:00:00Z"),
"due must reach the DB canonicalized to UTC Z"
);
}
#[test]
fn audit_full_policy_projects_payload_excerpt_into_messages() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"s",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"status-update",
"--command-origin",
"agent",
"--payload",
"the full design rationale goes here",
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(db).unwrap();
let excerpt: Option<String> = conn
.query_row(
"SELECT payload_excerpt FROM messages WHERE session_id='s' AND mid='m1'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
excerpt.as_deref(),
Some("the full design rationale goes here"),
"policy=full must carry the full payload into the live messages corpus"
);
}
#[test]
fn audit_excerpt_policy_projects_truncated_excerpt_into_messages() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"s",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"status-update",
"--command-origin",
"agent",
"--payload",
"abcdefghijklmnopqrstuvwxyz",
"--payload-redaction-policy",
"excerpt",
"--excerpt-chars",
"5",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(db).unwrap();
let excerpt: Option<String> = conn
.query_row(
"SELECT payload_excerpt FROM messages WHERE session_id='s' AND mid='m1'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
excerpt.as_deref(),
Some("abcde...vwxyz"),
"policy=excerpt must carry the head...tail excerpt into the corpus"
);
}
#[test]
fn audit_hash_only_policy_leaves_messages_payload_excerpt_null() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"s",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"status-update",
"--command-origin",
"agent",
"--payload",
"secret payload that must not leak",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(db).unwrap();
let excerpt: Option<String> = conn
.query_row(
"SELECT payload_excerpt FROM messages WHERE session_id='s' AND mid='m1'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
excerpt, None,
"hash-only must leave the corpus excerpt NULL — redacted content must not leak"
);
}
#[test]
fn db_import_carries_payload_excerpt_into_messages() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("zynk.db");
let session_dir = root.join("sessions/sx");
fs::create_dir_all(&session_dir).unwrap();
fs::write(
session_dir.join("status.md"),
"session_id: sx\nlast_update: 2026-05-29T00:00:00Z\nlead_agent: codex\nstatus: working\n\
- phase: x\n- mode: review\n- artifact_ref: a\n- completed_since_last_update: c\n\
- in_progress: p\n- next_action: n\n- blockers: none\n- asks_for_Zevs: none\n\
- risk_or_residual_uncertainty: none\n- expected_wait: unknown\n\n\
## Rolling Events\n\n1. 2026-05-29T00:00:00Z - x\n",
)
.unwrap();
fs::write(
session_dir.join("audit.md"),
"# Audit Trail: sx\n\nsession_id: sx\n\n## Records\n\n\
```text\naudit_id=a1\nprevious_audit_id=none\ntimestamp=2026-05-29T01:00:00Z\n\
source_agent=claude\nsource_address=w-1\ntarget_agent=codex\ntarget_address=w-2\n\
transport=herdr\nworkspace_id=w\nsession_id=sx\nmid=m1\ntype=status-update\n\
command_origin=agent\npayload_hash=sha256:x\npayload_redaction_policy=full\n\
content_size=24\ndelivery_status=observed\nobserved_by=codex\nverified_by=helper-tool\n\
payload_excerpt=the full imported payload\n```\n",
)
.unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let conn = Connection::open(&db_path).unwrap();
let excerpt: Option<String> = conn
.query_row(
"SELECT payload_excerpt FROM messages WHERE session_id='sx' AND mid='m1'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
excerpt.as_deref(),
Some("the full imported payload"),
"db import must carry the file's payload_excerpt into the live corpus"
);
}
#[test]
fn db_import_hash_only_payload_excerpt_stays_null() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("zynk.db");
let session_dir = root.join("sessions/sx");
fs::create_dir_all(&session_dir).unwrap();
fs::write(
session_dir.join("status.md"),
"session_id: sx\nlast_update: 2026-05-29T00:00:00Z\nlead_agent: codex\nstatus: working\n\
- phase: x\n- mode: review\n- artifact_ref: a\n- completed_since_last_update: c\n\
- in_progress: p\n- next_action: n\n- blockers: none\n- asks_for_Zevs: none\n\
- risk_or_residual_uncertainty: none\n- expected_wait: unknown\n\n\
## Rolling Events\n\n1. 2026-05-29T00:00:00Z - x\n",
)
.unwrap();
fs::write(
session_dir.join("audit.md"),
"# Audit Trail: sx\n\nsession_id: sx\n\n## Records\n\n\
```text\naudit_id=a1\nprevious_audit_id=none\ntimestamp=2026-05-29T01:00:00Z\n\
source_agent=claude\nsource_address=w-1\ntarget_agent=codex\ntarget_address=w-2\n\
transport=herdr\nworkspace_id=w\nsession_id=sx\nmid=m1\ntype=status-update\n\
command_origin=agent\npayload_hash=sha256:x\npayload_redaction_policy=hash-only\n\
content_size=0\ndelivery_status=observed\nobserved_by=codex\nverified_by=helper-tool\n\
payload_excerpt=SHOULD_NOT_IMPORT\n```\n",
)
.unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let conn = Connection::open(&db_path).unwrap();
let excerpt: Option<String> = conn
.query_row(
"SELECT payload_excerpt FROM messages WHERE session_id='sx' AND mid='m1'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
excerpt, None,
"a hash-only record must not leak content into the corpus on import (C6)"
);
}
#[test]
fn db_import_rebuilds_operator_decisions_for_decision() {
let cwd = tempdir().unwrap();
let db_rel = ".zynk/zynk.db";
let report = run_zynk_in(
&[
"report",
"gate",
"--root",
"outputs",
"--db",
db_rel,
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--action",
"Approve",
],
cwd.path(),
);
assert!(report.status.success(), "{}", stderr(&report));
let decide = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--no-db",
"--session-id",
"s1",
"--ref",
"1",
"--verdict",
"approve",
"--timestamp",
"2026-05-31T00:01:00Z",
],
cwd.path(),
);
assert!(decide.status.success(), "{}", stderr(&decide));
let db_path = cwd.path().join(db_rel);
let before: i64 = Connection::open(&db_path)
.unwrap()
.query_row("SELECT count(*) FROM operator_decisions", [], |row| {
row.get(0)
})
.unwrap();
assert_eq!(
before, 0,
"precondition: a --no-db decision leaves no typed row until import"
);
let import = run_zynk_in(
&[
"db", "--db", db_rel, "import", "outputs", "--root", "outputs",
],
cwd.path(),
);
assert!(import.status.success(), "{}", stderr(&import));
let conn = Connection::open(&db_path).unwrap();
let (count, decision_type, target, verdict, notify): (i64, String, i64, String, String) = conn
.query_row(
"SELECT count(*), decision_type, target_work_event_id, verdict, notification_status \
FROM operator_decisions",
[],
|row| {
Ok((
row.get(0)?,
row.get(1)?,
row.get(2)?,
row.get(3)?,
row.get(4)?,
))
},
)
.unwrap();
assert_eq!(
count, 1,
"import must rebuild exactly one operator_decisions row"
);
assert_eq!(decision_type, "gate-decision");
assert_eq!(target, 1, "the gate binds to work_event id 1 via --ref 1");
assert_eq!(verdict, "approve");
assert_eq!(
notify, "not-requested",
"a file-first decision never had a successful notify"
);
let import_again = run_zynk_in(
&[
"db", "--db", db_rel, "import", "outputs", "--root", "outputs",
],
cwd.path(),
);
assert!(import_again.status.success(), "{}", stderr(&import_again));
let after: i64 = conn
.query_row("SELECT count(*) FROM operator_decisions", [], |row| {
row.get(0)
})
.unwrap();
assert_eq!(
after, 1,
"re-import stays exactly one row (INSERT OR IGNORE)"
);
}
#[test]
fn db_import_carries_multiline_decision_payload() {
let cwd = tempdir().unwrap();
let db_rel = ".zynk/zynk.db";
let report = run_zynk_in(
&[
"report",
"gate",
"--root",
"outputs",
"--db",
db_rel,
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--action",
"Approve",
],
cwd.path(),
);
assert!(report.status.success(), "{}", stderr(&report));
let decide = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--no-db",
"--session-id",
"s1",
"--ref",
"1",
"--verdict",
"approve",
"--timestamp",
"2026-05-31T00:01:00Z",
],
cwd.path(),
);
assert!(decide.status.success(), "{}", stderr(&decide));
let import = run_zynk_in(
&[
"db", "--db", db_rel, "import", "outputs", "--root", "outputs",
],
cwd.path(),
);
assert!(import.status.success(), "{}", stderr(&import));
let conn = Connection::open(cwd.path().join(db_rel)).unwrap();
let excerpt: Option<String> = conn
.query_row(
"SELECT payload_excerpt FROM messages \
WHERE session_id='s1' AND message_type='gate-decision'",
[],
|row| row.get(0),
)
.unwrap();
let excerpt = excerpt.expect("decision message carries a corpus payload (full redaction)");
assert!(
excerpt.contains("verdict: approve"),
"the multi-line decision payload must round-trip intact, not truncate to its first \
line; got: {excerpt:?}"
);
assert!(
excerpt.contains("decision: gate") && excerpt.contains("target_work_event_id: 1"),
"all payload lines present; got: {excerpt:?}"
);
}
#[test]
fn db_import_carries_multiline_message_payload_excerpt() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("zynk.db");
let session_dir = root.join("sessions/sx");
fs::create_dir_all(&session_dir).unwrap();
fs::write(
session_dir.join("audit.md"),
"# Audit Trail: sx\n\nsession_id: sx\n\n## Records\n\n\
```text\naudit_id=a1\nprevious_audit_id=none\ntimestamp=2026-05-29T01:00:00Z\n\
source_agent=claude\nsource_address=w-1\ntarget_agent=codex\ntarget_address=w-2\n\
transport=herdr\nworkspace_id=w\nsession_id=sx\nmid=m1\ntype=answer\n\
command_origin=agent\npayload_hash=sha256:x\npayload_redaction_policy=full\n\
content_size=24\ndelivery_status=observed\nobserved_by=codex\nverified_by=helper-tool\n\
payload_excerpt=line one\nline two\nline three\n```\n",
)
.unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let conn = Connection::open(&db_path).unwrap();
let excerpt: Option<String> = conn
.query_row(
"SELECT payload_excerpt FROM messages WHERE session_id='sx' AND mid='m1'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
excerpt.as_deref(),
Some("line one\nline two\nline three"),
"a multi-line payload_excerpt must round-trip in full through db import, not \
truncate to its first line"
);
}
#[test]
fn db_import_fully_file_first_gate_recovers_all() {
let cwd = tempdir().unwrap();
let db_rel = ".zynk/zynk.db";
let report = run_zynk_in(
&[
"report",
"gate",
"--no-db",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--summary",
"ready",
"--proposer",
"claude",
"--action",
"Approve",
"--action",
"Request changes",
],
cwd.path(),
);
assert!(report.status.success(), "{}", stderr(&report));
let decide = run_zynk_in(
&[
"decide",
"gate",
"--no-db",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:01:00Z",
"--ref",
"1",
"--verdict",
"approve",
],
cwd.path(),
);
assert!(decide.status.success(), "{}", stderr(&decide));
let import = run_zynk_in(
&[
"db", "--db", db_rel, "import", "outputs", "--root", "outputs",
],
cwd.path(),
);
assert!(
import.status.success(),
"fully file-first import must not FK-fail: {}",
stderr(&import)
);
let conn = Connection::open(cwd.path().join(db_rel)).unwrap();
let work_events: i64 = conn
.query_row(
"SELECT count(*) FROM work_events WHERE kind='gate'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(work_events, 1, "the gate work_event must import");
let audit: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE record_type='gate-decision'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(audit, 1, "the decision audit must import");
let (decisions, decision_type, target, verdict): (i64, String, i64, String) = conn
.query_row(
"SELECT count(*), decision_type, target_work_event_id, verdict \
FROM operator_decisions",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)
.unwrap();
assert_eq!(
decisions, 1,
"the typed operator_decisions row must rebuild now that work_events exist first"
);
assert_eq!(decision_type, "gate-decision");
assert_eq!(target, 1, "the rebuilt row binds work_event id 1");
assert_eq!(verdict, "approve");
}
#[test]
fn db_import_decision_with_missing_or_wrongkind_ref_warnskips() {
let cwd = tempdir().unwrap();
let db_rel = ".zynk/zynk.db";
let gate = run_zynk_in(
&[
"report",
"gate",
"--no-db",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--action",
"Approve",
],
cwd.path(),
);
assert!(gate.status.success(), "{}", stderr(&gate));
let think = run_zynk_in(
&[
"report",
"think",
"--no-db",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:30Z",
"--text",
"thinking",
],
cwd.path(),
);
assert!(think.status.success(), "{}", stderr(&think));
let dangling = run_zynk_in(
&[
"decide",
"gate",
"--no-db",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:01:00Z",
"--ref",
"99",
"--verdict",
"approve",
],
cwd.path(),
);
assert!(dangling.status.success(), "{}", stderr(&dangling));
let wrongkind = run_zynk_in(
&[
"decide",
"gate",
"--no-db",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:02:00Z",
"--ref",
"2",
"--verdict",
"approve",
],
cwd.path(),
);
assert!(wrongkind.status.success(), "{}", stderr(&wrongkind));
let import = run_zynk_in(
&[
"db", "--db", db_rel, "import", "outputs", "--root", "outputs",
],
cwd.path(),
);
assert!(
import.status.success(),
"a dangling/wrong-kind ref must warn+skip, not abort the import: {}",
stderr(&import)
);
let conn = Connection::open(cwd.path().join(db_rel)).unwrap();
let work_events: i64 = conn
.query_row("SELECT count(*) FROM work_events", [], |row| row.get(0))
.unwrap();
assert_eq!(
work_events, 2,
"both valid work_events must import (no rollback)"
);
let audit: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE record_type='gate-decision'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(audit, 2, "both decision audits must import (no rollback)");
let decisions: i64 = conn
.query_row("SELECT count(*) FROM operator_decisions", [], |row| {
row.get(0)
})
.unwrap();
assert_eq!(
decisions, 0,
"neither dangling nor wrong-kind ref may produce a typed operator_decisions row"
);
}
#[test]
fn audit_rejects_calendar_invalid_timestamp_before_writing() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--session-id",
"s",
"--timestamp",
"2026-99-99T99:99:99Z",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"p",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert_eq!(
out.status.code(),
Some(2),
"calendar-invalid timestamp (GLOB-shaped but unreal) must be rejected; stderr:\n{}",
stderr(&out)
);
assert!(stderr(&out).to_lowercase().contains("timestamp"));
assert!(
!root.join("sessions/s/audit.md").exists(),
"no audit file may be written when the timestamp is rejected"
);
}
#[test]
fn status_rejects_malformed_timestamp_before_writing() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"status",
"--root",
root.to_str().unwrap(),
"--session-id",
"s",
"--timestamp",
"not-a-timestamp",
"--phase",
"p",
"--mode",
"build",
"--artifact-ref",
"a",
"--lead-agent",
"claude",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"ip",
"--next-action",
"na",
"--event",
"ev",
]);
assert_eq!(
out.status.code(),
Some(2),
"malformed timestamp must be rejected; stderr:\n{}",
stderr(&out)
);
assert!(stderr(&out).to_lowercase().contains("timestamp"));
assert!(
!root.join("sessions/s/status.md").exists(),
"no status file may be written when the timestamp is rejected"
);
}
#[test]
fn db_import_skips_audit_record_with_invalid_due() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("zynk.db");
let session_dir = root.join("sessions/sdue");
fs::create_dir_all(&session_dir).unwrap();
fs::write(
session_dir.join("status.md"),
"session_id: sdue\nlast_update: 2026-05-29T00:00:00Z\nlead_agent: codex\nstatus: working\n\
- phase: x\n- mode: review\n- artifact_ref: a\n- completed_since_last_update: c\n\
- in_progress: p\n- next_action: n\n- blockers: none\n- asks_for_Zevs: none\n\
- risk_or_residual_uncertainty: none\n- expected_wait: unknown\n\n\
## Rolling Events\n\n1. 2026-05-29T00:00:00Z - x\n",
)
.unwrap();
fs::write(
session_dir.join("audit.md"),
"# Audit Trail: sdue\n\nsession_id: sdue\n\n## Records\n\n\
```text\naudit_id=baddue\nprevious_audit_id=none\ntimestamp=2026-05-29T01:00:00Z\n\
source_agent=claude\nsource_address=w-1\ntarget_agent=codex\ntarget_address=w-2\n\
transport=herdr\nworkspace_id=w\nsession_id=sdue\nmid=m1\ntype=ack\ncommand_origin=agent\n\
payload_hash=sha256:x\npayload_redaction_policy=hash-only\ncontent_size=0\n\
delivery_status=observed\nobserved_by=codex\nverified_by=helper-tool\n\
due=not-a-timestamp\n```\n",
)
.unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(
import.status.success(),
"import should skip the bad-due record, not hard-fail: {}",
stderr(&import)
);
let conn = Connection::open(&db_path).unwrap();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE audit_id='baddue'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
count, 0,
"a record with malformed due must be skipped, not imported with due=NULL"
);
let warning: String = conn
.query_row(
"SELECT warning_summary FROM imports ORDER BY import_id DESC LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert!(
warning.to_lowercase().contains("due"),
"import warning must name the skipped due; got: {warning}"
);
}
#[test]
fn db_export_preserves_audit_due() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("zynk.db");
let session_dir = root.join("sessions/sx");
fs::create_dir_all(&session_dir).unwrap();
fs::write(
session_dir.join("status.md"),
"session_id: sx\nlast_update: 2026-05-29T00:00:00Z\nlead_agent: codex\nstatus: working\n\
- phase: x\n- mode: review\n- artifact_ref: a\n- completed_since_last_update: c\n\
- in_progress: p\n- next_action: n\n- blockers: none\n- asks_for_Zevs: none\n\
- risk_or_residual_uncertainty: none\n- expected_wait: unknown\n\n\
## Rolling Events\n\n1. 2026-05-29T00:00:00Z - x\n",
)
.unwrap();
fs::write(
session_dir.join("audit.md"),
"# Audit Trail: sx\n\nsession_id: sx\n\n## Records\n\n\
```text\naudit_id=due001\nprevious_audit_id=none\ntimestamp=2026-05-29T01:00:00Z\n\
source_agent=claude\nsource_address=w-1\ntarget_agent=codex\ntarget_address=w-2\n\
transport=herdr\nworkspace_id=w\nsession_id=sx\nmid=m1\ntype=request-action\n\
command_origin=agent\npayload_hash=sha256:x\npayload_redaction_policy=hash-only\n\
content_size=0\ndelivery_status=observed\nobserved_by=codex\nverified_by=helper-tool\n\
due=2026-06-01T12:00:00Z\n```\n",
)
.unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let export_root = tmp.path().join("exported");
let export = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"export",
"outputs",
"--root",
export_root.to_str().unwrap(),
"--session-id",
"sx",
]);
assert!(export.status.success(), "{}", stderr(&export));
let exported = fs::read_to_string(export_root.join("sessions/sx/audit.md")).unwrap();
assert!(
exported.contains("due=2026-06-01T12:00:00Z"),
"export must preserve due across the round trip; got:\n{exported}"
);
}
#[test]
fn dashboard_shows_latest_status_for_reimported_session() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("zynk.db");
let session_dir = root.join("sessions/sx");
fs::create_dir_all(&session_dir).unwrap();
let write_status = |last_update: &str, status: &str, phase: &str| {
fs::write(
session_dir.join("status.md"),
format!(
"session_id: sx\nlast_update: {last_update}\nlead_agent: codex\nstatus: {status}\n\
- phase: {phase}\n- mode: review\n- artifact_ref: a\n- completed_since_last_update: c\n\
- in_progress: p\n- next_action: n\n- blockers: none\n- asks_for_Zevs: none\n\
- risk_or_residual_uncertainty: none\n- expected_wait: unknown\n\n\
## Rolling Events\n\n1. {last_update} - {phase}\n"
),
)
.unwrap();
};
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
write_status("2026-05-29T06:51:27Z", "working", "apply");
let imp1 = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T06:52:00Z",
]);
assert!(imp1.status.success(), "{}", stderr(&imp1));
write_status("2026-05-29T07:32:48Z", "done", "shipped");
let imp2 = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T07:33:00Z",
]);
assert!(imp2.status.success(), "{}", stderr(&imp2));
let html = fetch_dashboard_with_serve_args(&db_path, "/?session=sx", &[]);
assert!(
html.contains("<dt>State</dt><dd>done</dd>"),
"detail must show the latest status (done), not the stale sessions row (working); html:\n{html}"
);
assert!(
html.contains("/ shipped /"),
"timeline header must show the latest phase (shipped), not stale (apply); html:\n{html}"
);
}
#[test]
fn db_init_at_default_path_writes_gitignore() {
let tmp = tempdir().unwrap();
let out = run_zynk_in(&["db", "init"], tmp.path());
assert!(out.status.success(), "{}", stderr(&out));
assert!(tmp.path().join(".zynk/zynk.db").exists());
let gitignore = tmp.path().join(".zynk/.gitignore");
assert!(
gitignore.exists() && fs::read_to_string(&gitignore).unwrap().contains('*'),
"db init at the default path must write .zynk/.gitignore=* (ADR 028 C1)"
);
}
#[test]
fn audit_explicit_db_unreachable_writes_file_then_nonzero() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let blocker = tmp.path().join("blocker");
fs::write(&blocker, "x").unwrap();
let db = blocker.join("zynk.db"); let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"p",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(
!out.status.success(),
"explicit --db to an unreachable path must hard-fail (nonzero); stderr:\n{}",
stderr(&out)
);
assert!(
root.join("sessions/s/audit.md").exists(),
"file must be written before the explicit-db hard-fail (file-first, C3)"
);
}
#[test]
fn status_default_db_failure_soft_degrades() {
let tmp = tempdir().unwrap();
fs::write(tmp.path().join(".zynk"), "x").unwrap();
let out = run_zynk_in(
&[
"status",
"--root",
"outputs",
"--session-id",
"s",
"--phase",
"p",
"--mode",
"build",
"--artifact-ref",
"a",
"--lead-agent",
"claude",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"ip",
"--next-action",
"na",
"--event",
"ev",
],
tmp.path(),
);
assert!(
out.status.success(),
"default-DB failure must soft-degrade (exit 0); stderr:\n{}",
stderr(&out)
);
assert!(
tmp.path().join("outputs/sessions/s/status.md").exists(),
"the status file must be written despite the DB failure"
);
assert!(
stderr(&out).to_lowercase().contains("warning"),
"soft-degrade must warn; stderr:\n{}",
stderr(&out)
);
}
#[test]
fn status_adds_star_to_preexisting_nonprotective_gitignore() {
let tmp = tempdir().unwrap();
fs::create_dir_all(tmp.path().join(".zynk")).unwrap();
fs::write(tmp.path().join(".zynk/.gitignore"), "# notes\nfoo\n").unwrap();
let out = run_zynk_in(
&[
"status",
"--root",
"outputs",
"--session-id",
"s",
"--phase",
"p",
"--mode",
"build",
"--artifact-ref",
"a",
"--lead-agent",
"claude",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"ip",
"--next-action",
"na",
"--event",
"ev",
],
tmp.path(),
);
assert!(out.status.success(), "{}", stderr(&out));
let gitignore = fs::read_to_string(tmp.path().join(".zynk/.gitignore")).unwrap();
assert!(
gitignore.lines().any(|line| line.trim() == "*"),
"must add a `*` rule to a pre-existing non-protective .gitignore (ADR 028 P1); got:\n{gitignore}"
);
assert!(
gitignore.contains("foo"),
"pre-existing content must be preserved"
);
}
#[test]
fn status_audit_db_help_reflects_auto_create() {
for sub in ["status", "audit"] {
let out = run_zynk(&[sub, "--help"]);
assert!(out.status.success(), "{}", stderr(&out));
let help = stdout(&out);
assert!(
!help.contains("when set (or .zynk/zynk.db exists)"),
"{sub} --help must not document v0.3 presence-gating; got:\n{help}"
);
assert!(
help.contains("auto-create"),
"{sub} --help should mention auto-create (ADR 028); got:\n{help}"
);
}
}
#[test]
fn audit_explicit_nondefault_zynk_dir_self_protects() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("proj/.zynk/zynk.db");
let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"p",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(out.status.success(), "{}", stderr(&out));
let gitignore = tmp.path().join("proj/.zynk/.gitignore");
assert!(
gitignore.exists() && fs::read_to_string(&gitignore).unwrap().contains('*'),
"any .zynk runtime dir self-protects (ADR 028 C7), including an explicit --db"
);
}
#[test]
fn status_default_soft_degrades_when_gitignore_unensurable() {
let tmp = tempdir().unwrap();
fs::create_dir_all(tmp.path().join(".zynk/.gitignore")).unwrap(); let out = run_zynk_in(
&[
"status",
"--root",
"outputs",
"--session-id",
"s",
"--phase",
"p",
"--mode",
"build",
"--artifact-ref",
"a",
"--lead-agent",
"claude",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"ip",
"--next-action",
"na",
"--event",
"ev",
],
tmp.path(),
);
assert!(
out.status.success(),
"default must soft-degrade (exit 0) when the self-ignore can't be ensured; stderr:\n{}",
stderr(&out)
);
assert!(
tmp.path().join("outputs/sessions/s/status.md").exists(),
"the status file must still be written (file-first)"
);
assert!(
!tmp.path().join(".zynk/zynk.db").exists(),
"must NOT create the DB when it cannot be self-ignored (ADR 028 P4)"
);
assert!(
!stderr(&out).contains("created .zynk/zynk.db"),
"must NOT print the created/self-ignore notice when the DB was not safely created"
);
assert!(
stderr(&out).to_lowercase().contains("warning"),
"soft-degrade must warn; stderr:\n{}",
stderr(&out)
);
}
#[test]
fn audit_explicit_zynk_db_hard_fails_when_gitignore_unensurable() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let zynk_dir = tmp.path().join(".zynk");
fs::create_dir_all(zynk_dir.join(".gitignore")).unwrap(); let db = zynk_dir.join("zynk.db");
let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"p",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(
!out.status.success(),
"explicit --db into a .zynk dir must hard-fail when the self-ignore can't be ensured; stderr:\n{}",
stderr(&out)
);
assert!(
root.join("sessions/s/audit.md").exists(),
"file must be written before the hard-fail (file-first)"
);
}
#[test]
fn cargo_declares_zynk_package() {
let cargo = fs::read_to_string(root().join("Cargo.toml")).unwrap();
assert!(cargo.contains("name = \"zynk\""));
assert!(cargo.contains("edition = \"2021\""));
}
#[test]
fn protocol_profile_remains_top_level_tool_config() {
let profile = fs::read_to_string(root().join("tools/message-profile.yaml")).unwrap();
assert!(profile.contains("profile_name: portable-multi-agent-collab"));
assert!(profile.contains("delivery_status_enum:"));
}
#[test]
fn db_init_creates_default_zynk_database_under_cwd() {
let tmp = tempdir().unwrap();
let output = run_zynk_in(&["db", "init"], tmp.path());
let db_path = tmp.path().join(".zynk/zynk.db");
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains(db_path.to_str().unwrap()));
assert!(db_path.exists());
}
#[test]
fn db_init_accepts_explicit_db_path() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/custom-zynk.db");
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains(db_path.to_str().unwrap()));
assert!(db_path.exists());
}
#[test]
fn db_init_applies_first_schema_and_wal_mode() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(output.status.success(), "{}", stderr(&output));
let result = std::process::Command::new("sqlite3")
.arg(&db_path)
.arg(
"PRAGMA user_version; PRAGMA journal_mode; SELECT version FROM schema_migrations ORDER BY version;",
)
.output();
let Ok(result) = result else {
eprintln!("sqlite3 CLI unavailable; DB file creation already covered");
return;
};
assert!(
result.status.success(),
"{}",
String::from_utf8_lossy(&result.stderr)
);
let out = String::from_utf8_lossy(&result.stdout);
assert!(out.contains("1"), "{out}");
assert!(out.to_ascii_lowercase().contains("wal"), "{out}");
}
#[test]
fn db_init_rejects_newer_schema_version() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/future.db");
fs::create_dir_all(db_path.parent().unwrap()).unwrap();
let connection = Connection::open(&db_path).unwrap();
connection.pragma_update(None, "user_version", 999).unwrap();
drop(connection);
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(!output.status.success());
assert!(stderr(&output).contains("newer than this zynk binary supports"));
}
#[test]
fn db_init_creates_initial_live_state_tables() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(output.status.success(), "{}", stderr(&output));
let connection = Connection::open(&db_path).unwrap();
for table in [
"schema_migrations",
"projects",
"sessions",
"agents",
"agent_addresses",
"session_agents",
"messages",
"status_events",
"audit_records",
"artifacts",
"imports",
] {
let count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
[table],
|row| row.get(0),
)
.unwrap();
assert_eq!(count, 1, "missing table {table}");
}
}
#[test]
fn v7_operator_decisions_table_present() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(output.status.success(), "{}", stderr(&output));
let connection = Connection::open(&db_path).unwrap();
let version: i64 = connection
.pragma_query_value(None, "user_version", |row| row.get(0))
.unwrap();
assert!(version >= 7, "schema must be at least v7 after db init");
let count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
["operator_decisions"],
|row| row.get(0),
)
.unwrap();
assert_eq!(count, 1, "operator_decisions table must exist at schema v7");
}
#[test]
fn fresh_init_reaches_head_with_custody_vault_and_nontransport_trigger() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(output.status.success(), "{}", stderr(&output));
let connection = Connection::open(&db_path).unwrap();
let version: i64 = connection
.pragma_query_value(None, "user_version", |row| row.get(0))
.unwrap();
assert_eq!(version, 10, "schema must be at v10 after db init");
let count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
["custody_vault"],
|row| row.get(0),
)
.unwrap();
assert_eq!(count, 1, "custody_vault table must exist at head");
let trigger: i64 = connection
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'trigger' AND name = ?1",
["audit_records_no_sent_nontransport_proof_insert"],
|row| row.get(0),
)
.unwrap();
assert_eq!(
trigger, 1,
"the non-transport-proof sent-guard trigger must exist after a fresh db init"
);
}
#[test]
fn db_init_separates_workflow_status_from_herdr_agent_status() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(output.status.success(), "{}", stderr(&output));
let connection = Connection::open(&db_path).unwrap();
let session_columns = table_columns(&connection, "sessions");
let session_agent_columns = table_columns(&connection, "session_agents");
assert!(session_columns.contains(&"workflow_status".to_string()));
assert!(!session_columns.contains(&"agent_status".to_string()));
assert!(session_agent_columns.contains(&"agent_status".to_string()));
assert!(!session_agent_columns.contains(&"workflow_status".to_string()));
}
#[test]
fn db_audit_append_links_records_and_updates_latest_message() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(output.status.success(), "{}", stderr(&output));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
drop(connection);
let first = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"aud001",
"--timestamp",
"2026-05-29T01:00:00Z",
"--source-address",
"codex-pane",
"--target-address",
"claude-pane",
"--transport",
"herdr",
"--workspace-id",
"workspace-a",
"--mid",
"mid001",
"--type",
"request-review",
"--command-origin",
"agent",
"--payload",
"hello from codex",
"--payload-redaction-policy",
"excerpt",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(first.status.success(), "{}", stderr(&first));
assert_eq!(stdout(&first).trim(), "aud001");
let second = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"aud002",
"--timestamp",
"2026-05-29T01:01:00Z",
"--source-address",
"claude-pane",
"--target-address",
"codex-pane",
"--transport",
"herdr",
"--workspace-id",
"workspace-a",
"--mid",
"mid001",
"--type",
"request-review",
"--command-origin",
"agent",
"--payload",
"hello from codex",
"--payload-redaction-policy",
"excerpt",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"agent",
]);
assert!(second.status.success(), "{}", stderr(&second));
assert_eq!(stdout(&second).trim(), "aud002");
let connection = Connection::open(&db_path).unwrap();
let links = audit_links(&connection);
assert_eq!(
links,
vec![
("aud001".to_string(), None),
("aud002".to_string(), Some("aud001".to_string())),
]
);
let expected_hash = format!("sha256:{:x}", Sha256::digest("hello from codex".as_bytes()));
let latest: (String, String, String) = connection
.query_row(
"SELECT latest_delivery_status, latest_audit_id, payload_hash
FROM messages
WHERE session_id = 'session-a' AND mid = 'mid001'",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
)
.unwrap();
assert_eq!(
latest,
("observed".to_string(), "aud002".to_string(), expected_hash)
);
let command_origin: String = connection
.query_row(
"SELECT command_origin FROM audit_records WHERE audit_id = 'aud001'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(command_origin, "agent");
}
#[test]
fn db_audit_records_reject_sent_verified_by_agent_at_database_layer() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(output.status.success(), "{}", stderr(&output));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
let result = insert_raw_audit_record(&connection, "aud001", "sent", "agent");
assert!(
result.is_err(),
"database accepted delivery_status=sent with verified_by=agent"
);
}
#[test]
fn db_audit_records_are_append_only_at_database_layer() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let output = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(output.status.success(), "{}", stderr(&output));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
insert_raw_audit_record(&connection, "aud001", "sent", "helper-tool").unwrap();
let update = connection.execute(
"UPDATE audit_records SET mid = 'changed' WHERE audit_id = 'aud001'",
[],
);
assert!(update.is_err(), "database allowed audit record update");
let delete = connection.execute("DELETE FROM audit_records WHERE audit_id = 'aud001'", []);
assert!(delete.is_err(), "database allowed audit record delete");
}
#[test]
fn db_import_outputs_imports_status_and_audit_artifacts() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"tooling",
"review",
"working",
"tools/import",
);
write_audit_artifact(
&root,
"session-a",
&[
test_audit_record("aud001", "none", "mid001", "sent", "helper-tool"),
test_audit_record("aud002", "aud001", "mid001", "observed", "agent"),
],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
assert!(stdout(&import).contains("sessions imported: 1"));
let connection = Connection::open(&db_path).unwrap();
let session: (String, String, String, String) = connection
.query_row(
"SELECT phase, mode, workflow_status, artifact_ref
FROM sessions
WHERE session_id = 'session-a'",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)
.unwrap();
assert_eq!(
session,
(
"tooling".to_string(),
"review".to_string(),
"working".to_string(),
"tools/import".to_string(),
)
);
let status_events: i64 = connection
.query_row(
"SELECT COUNT(*) FROM status_events WHERE session_id = 'session-a'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(status_events, 1);
let links = audit_links(&connection);
assert_eq!(
links,
vec![
("aud001".to_string(), None),
("aud002".to_string(), Some("aud001".to_string())),
]
);
let latest: (String, String) = connection
.query_row(
"SELECT latest_delivery_status, latest_audit_id
FROM messages
WHERE session_id = 'session-a' AND mid = 'mid001'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap();
assert_eq!(latest, ("observed".to_string(), "aud002".to_string()));
let import_row: (String, String, String) = connection
.query_row(
"SELECT source_kind, result, warning_summary FROM imports",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
)
.unwrap();
assert_eq!(
import_row,
(
"outputs".to_string(),
"imported".to_string(),
"none".to_string(),
)
);
}
#[test]
fn db_import_outputs_does_not_update_existing_live_rows() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"tooling",
"review",
"working",
"tools/import",
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let first = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(first.status.success(), "{}", stderr(&first));
write_status_artifact(
&root,
"session-a",
"changed",
"validate",
"done",
"tools/import-v2",
);
let second = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:01:00Z",
]);
assert!(second.status.success(), "{}", stderr(&second));
let connection = Connection::open(&db_path).unwrap();
let session: (String, String, String) = connection
.query_row(
"SELECT phase, mode, workflow_status
FROM sessions
WHERE session_id = 'session-a'",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
)
.unwrap();
assert_eq!(
session,
(
"tooling".to_string(),
"review".to_string(),
"working".to_string()
)
);
let imports: (i64, String) = connection
.query_row(
"SELECT COUNT(*), MAX(warning_summary) FROM imports",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap();
assert_eq!(imports.0, 2);
assert!(imports.1.contains("skipped existing session session-a"));
}
#[test]
fn db_import_outputs_rebases_forked_audit_child_and_records_warning() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"tooling",
"review",
"working",
"tools/import",
);
write_audit_artifact(
&root,
"session-a",
&[
test_audit_record("aud001", "none", "mid001", "sent", "helper-tool"),
test_audit_record("aud002", "aud001", "mid002", "observed", "agent"),
test_audit_record("aud003", "aud001", "mid003", "observed", "agent"),
],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let audit_count: i64 = connection
.query_row("SELECT COUNT(*) FROM audit_records", [], |row| row.get(0))
.unwrap();
assert_eq!(audit_count, 3);
let links = audit_links(&connection);
assert_eq!(
links,
vec![
("aud001".to_string(), None),
("aud002".to_string(), Some("aud001".to_string())),
("aud003".to_string(), None),
]
);
let warning: String = connection
.query_row("SELECT warning_summary FROM imports", [], |row| row.get(0))
.unwrap();
assert!(warning.contains("rebased audit_id aud003 to chain root"));
}
#[test]
fn db_import_outputs_rebases_conflicting_audit_branch_to_preserve_descendants() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let export_root = tmp.path().join("exported-outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"export",
"review",
"working",
"seam-5-export",
);
write_audit_artifact(
&root,
"session-a",
&[
test_audit_record("aud001", "none", "mid001", "sent", "helper-tool"),
test_audit_record("aud002", "aud001", "mid002", "observed", "agent"),
test_audit_record("aud003", "aud001", "mid003", "observed", "agent"),
test_audit_record("aud004", "aud003", "mid004", "observed", "agent"),
],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let links = audit_links(&connection);
assert_eq!(
links,
vec![
("aud001".to_string(), None),
("aud002".to_string(), Some("aud001".to_string())),
("aud003".to_string(), None),
("aud004".to_string(), Some("aud003".to_string())),
]
);
let warning: String = connection
.query_row("SELECT warning_summary FROM imports", [], |row| row.get(0))
.unwrap();
assert!(warning.contains("rebased audit_id aud003 to chain root"));
let export = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"export",
"outputs",
"--root",
export_root.to_str().unwrap(),
"--session-id",
"session-a",
]);
assert!(export.status.success(), "{}", stderr(&export));
let audit = fs::read_to_string(export_root.join("sessions/session-a/audit.md")).unwrap();
assert_eq!(audit.matches("```text").count(), 4);
assert!(audit.contains("audit_id=aud003\nprevious_audit_id=none"));
assert!(audit.contains("audit_id=aud004\nprevious_audit_id=aud003"));
}
#[test]
fn db_export_outputs_writes_status_and_audit_artifacts() {
let tmp = tempdir().unwrap();
let source_root = tmp.path().join("source-outputs");
let export_root = tmp.path().join("exported-outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&source_root,
"session-a",
"export",
"review",
"working",
"seam-5-export",
);
write_audit_artifact(
&source_root,
"session-a",
&[
test_audit_record("aud001", "none", "mid001", "sent", "helper-tool"),
test_audit_record("aud002", "aud001", "mid001", "observed", "agent"),
],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
source_root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let export = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"export",
"outputs",
"--root",
export_root.to_str().unwrap(),
"--session-id",
"session-a",
]);
assert!(export.status.success(), "{}", stderr(&export));
assert!(stdout(&export).contains("written: 2"));
let status = fs::read_to_string(export_root.join("sessions/session-a/status.md")).unwrap();
assert!(status.contains("# Session Status: session-a"));
assert!(status.contains("session_id: session-a"));
assert!(status.contains("lead_agent: codex"));
assert!(status.contains("status: working"));
assert!(status.contains("- phase: export"));
assert!(status.contains("- mode: review"));
assert!(status.contains("- artifact_ref: seam-5-export"));
assert!(status.contains("- next_action: import outputs"));
let audit = fs::read_to_string(export_root.join("sessions/session-a/audit.md")).unwrap();
assert!(audit.contains("# Audit Trail: session-a"));
assert_eq!(audit.matches("```text").count(), 2);
assert!(audit.contains("audit_id=aud001"));
assert!(audit.contains("previous_audit_id=none"));
assert!(audit.contains("audit_id=aud002"));
assert!(audit.contains("previous_audit_id=aud001"));
assert!(audit.contains("delivery_status=sent"));
assert!(audit.contains("verified_by=helper-tool"));
assert!(audit.contains("delivery_status=observed"));
assert!(audit.contains("verified_by=agent"));
assert!(audit.contains("payload_redaction_policy=hash-only"));
assert!(!audit.contains("payload_excerpt="));
}
#[test]
fn db_export_outputs_preserves_imported_rolling_event_text() {
let tmp = tempdir().unwrap();
let source_root = tmp.path().join("source-outputs");
let export_root = tmp.path().join("exported-outputs");
let db_path = tmp.path().join("state/zynk.db");
let first = run_zynk(&[
"status",
"--root",
source_root.to_str().unwrap(),
"--session-id",
"session-a",
"--timestamp",
"2026-05-29T01:00:00Z",
"--phase",
"export",
"--mode",
"review",
"--artifact-ref",
"seam-5-export",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"status artifact written",
"--in-progress",
"import test",
"--next-action",
"import outputs",
"--event",
"first operator-authored event",
]);
assert!(first.status.success(), "{}", stderr(&first));
let second = run_zynk(&[
"status",
"--root",
source_root.to_str().unwrap(),
"--session-id",
"session-a",
"--timestamp",
"2026-05-29T01:01:00Z",
"--phase",
"export",
"--mode",
"review",
"--artifact-ref",
"seam-5-export",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"status artifact written",
"--in-progress",
"import test",
"--next-action",
"import outputs",
"--event",
"second operator-authored event",
]);
assert!(second.status.success(), "{}", stderr(&second));
write_audit_artifact(
&source_root,
"session-a",
&[test_audit_record(
"aud001",
"none",
"mid001",
"sent",
"helper-tool",
)],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
source_root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let export = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"export",
"outputs",
"--root",
export_root.to_str().unwrap(),
"--session-id",
"session-a",
]);
assert!(export.status.success(), "{}", stderr(&export));
let status = fs::read_to_string(export_root.join("sessions/session-a/status.md")).unwrap();
assert!(status.contains("second operator-authored event"));
assert!(status.contains("first operator-authored event"));
assert_eq!(status.matches("operator-authored event").count(), 2);
}
#[test]
fn db_export_outputs_does_not_rewrite_unchanged_artifacts() {
let tmp = tempdir().unwrap();
let source_root = tmp.path().join("source-outputs");
let export_root = tmp.path().join("exported-outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&source_root,
"session-a",
"export",
"review",
"working",
"seam-5-export",
);
write_audit_artifact(
&source_root,
"session-a",
&[test_audit_record(
"aud001",
"none",
"mid001",
"sent",
"helper-tool",
)],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
source_root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let first = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"export",
"outputs",
"--root",
export_root.to_str().unwrap(),
"--session-id",
"session-a",
]);
assert!(first.status.success(), "{}", stderr(&first));
let status_path = export_root.join("sessions/session-a/status.md");
let audit_path = export_root.join("sessions/session-a/audit.md");
fs::set_permissions(&status_path, fs::Permissions::from_mode(0o444)).unwrap();
fs::set_permissions(&audit_path, fs::Permissions::from_mode(0o444)).unwrap();
let second = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"export",
"outputs",
"--root",
export_root.to_str().unwrap(),
"--session-id",
"session-a",
]);
assert!(second.status.success(), "{}", stderr(&second));
assert!(stdout(&second).contains("written: 0"));
assert!(stdout(&second).contains("unchanged: 2"));
}
#[test]
fn db_export_outputs_rejects_missing_session() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let export_root = tmp.path().join("outputs");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let export = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"export",
"outputs",
"--root",
export_root.to_str().unwrap(),
"--session-id",
"missing",
]);
assert!(!export.status.success());
assert!(stderr(&export).contains("session not found: missing"));
}
#[test]
fn db_serve_once_renders_html_dashboard_from_sqlite() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"dashboard",
"review",
"working",
"seam-4-dashboard",
);
write_audit_artifact(
&root,
"session-a",
&[
test_audit_record("aud001", "none", "mid001", "sent", "helper-tool"),
test_audit_record("aud002", "aud001", "mid001", "observed", "agent"),
],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let response = fetch_db_dashboard_once(&db_path, "/");
assert!(response.contains("HTTP/1.1 200 OK"), "{response}");
assert!(response.contains("<!doctype html>"));
assert!(response.contains("session-a"));
assert!(response.contains("working"));
assert!(response.contains("seam-4-dashboard"));
assert!(response.contains("mid001"));
assert!(response.contains("observed / agent"));
assert!(response.contains("class=\"app-shell\""));
assert!(response.contains("class=\"timeline\""));
assert!(response.contains("class=\"context-panel\""));
let linked_response = fetch_db_dashboard_once(&db_path, "/?session=session-a");
assert!(
linked_response.contains("HTTP/1.1 200 OK"),
"{linked_response}"
);
assert!(linked_response.contains("session-a"));
}
#[test]
fn db_serve_once_defaults_to_loopback_and_returns_404_for_unknown_route() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let response = fetch_db_dashboard_once(&db_path, "/missing");
assert!(response.contains("HTTP/1.1 404 Not Found"), "{response}");
}
#[test]
fn db_serve_interleaves_status_events_and_messages_chronologically() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"dashboard",
"review",
"working",
"seam-4-dashboard",
);
write_audit_artifact(
&root,
"session-a",
&[test_audit_record_at(
"aud001",
"none",
"newer-mid",
"sent",
"helper-tool",
"2026-05-29T01:05:00Z",
)],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let response = fetch_db_dashboard_once(&db_path, "/");
let message_index = response.find("newer-mid").unwrap();
let status_index = response.find("import outputs").unwrap();
assert!(
status_index < message_index,
"older status event must appear before newer message (oldest-first): {response}"
);
}
#[test]
fn db_serve_rejects_non_loopback_host() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let output = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"serve",
"--host",
"0.0.0.0",
"--once",
]);
assert!(!output.status.success());
assert!(stderr(&output).contains("binds only to 127.0.0.1 in v0.2"));
}
#[test]
fn db_serve_rejects_non_get_methods() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let response = fetch_db_dashboard_once_with_method(&db_path, "POST", "/");
assert!(
response.contains("HTTP/1.1 405 Method Not Allowed"),
"{response}"
);
}
#[test]
fn dashboard_feed_renders_message_body_proof_and_permalink() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("zynk.db");
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
"m1",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"hello body",
"--payload-redaction-policy",
"full",
"--delivery-status",
"sent",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(out.status.success(), "{}", stderr(&out));
let html = fetch_db_dashboard_once(&db, "/?session=s");
assert!(html.contains("hello body"), "full body rendered: {html}");
assert!(html.contains("acp://w1/s#a1"), "permalink rendered: {html}");
assert!(
html.contains("w1-2") && html.contains("w1-1"),
"source/target addresses rendered: {html}"
);
assert!(html.contains("helper-tool"), "proof strip rendered: {html}");
}
#[test]
fn dashboard_feed_redacts_hash_only_message() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("zynk.db");
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
"m1",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"TOPSECRETPAYLOAD",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"sent",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(out.status.success(), "{}", stderr(&out));
let html = fetch_db_dashboard_once(&db, "/?session=s");
assert!(
!html.contains("TOPSECRETPAYLOAD"),
"hash-only body must not render: {html}"
);
assert!(html.contains("redacted"), "shows a redacted marker: {html}");
}
#[test]
fn dashboard_audit_view_verifies_chain_readonly() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("zynk.db");
let root = tmp.path().join("outputs");
for (aid, prev, mid) in [("a1", "none", "m1"), ("a2", "a1", "m2")] {
let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--audit-id",
aid,
"--previous-audit-id",
prev,
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
mid,
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"x",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(out.status.success(), "{}", stderr(&out));
}
let html = fetch_db_dashboard_once(&db, "/audit?session=s");
assert!(html.contains("HTTP/1.1 200 OK"), "{html}");
assert!(html.contains("chain intact"), "verify result shown: {html}");
assert!(
html.contains("a1") && html.contains("a2"),
"records listed: {html}"
);
}
#[test]
fn dashboard_audit_view_rejects_cross_session_chain() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("zynk.db");
let root = tmp.path().join("outputs");
let mk = |session: &str, aid: &str, prev: &str, mid: &str| {
run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
session,
"--audit-id",
aid,
"--previous-audit-id",
prev,
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
mid,
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"x",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
])
};
assert!(mk("s1", "a1", "none", "m1").status.success());
assert!(mk("s2", "b1", "none", "mb1").status.success());
let cross = mk("s2", "b2", "a1", "mb2");
assert!(cross.status.success(), "{}", stderr(&cross));
let html = fetch_db_dashboard_once(&db, "/audit?session=s2");
assert!(
!html.contains("chain intact"),
"must NOT falsely report a cross-session chain as intact: {html}"
);
assert!(
html.contains("chain anomaly"),
"reports the chain anomaly: {html}"
);
}
#[test]
fn dashboard_has_inline_audit_panel_and_toggle() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("zynk.db");
let root = tmp.path().join("outputs");
for (aid, prev, mid) in [("a1", "none", "m1"), ("a2", "a1", "m2")] {
let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--audit-id",
aid,
"--previous-audit-id",
prev,
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
mid,
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"x",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(out.status.success(), "{}", stderr(&out));
}
let html = fetch_db_dashboard_once(&db, "/?session=s");
assert!(html.contains("HTTP/1.1 200 OK"), "{html}");
assert!(
html.contains("id=\"feed\""),
"feed container present: {html}"
);
assert!(
html.contains("id=\"audit-view\""),
"inline audit panel present: {html}"
);
assert!(
html.contains("a1") && html.contains("a2"),
"audit records render inside the inline panel: {html}"
);
assert!(
html.contains("id=\"view-toggle\"") || html.contains("data-view-toggle"),
"view toggle control present: {html}"
);
let audit_at = html
.find("id=\"audit-view\"")
.unwrap_or_else(|| panic!("audit-view present: {html}"));
let tag_start = html[..audit_at].rfind('<').unwrap();
let tag_end = audit_at + html[audit_at..].find('>').unwrap();
let audit_tag = &html[tag_start..=tag_end];
assert!(
audit_tag.contains(" hidden")
|| audit_tag.contains("display:none")
|| audit_tag.contains("display: none"),
"the inline audit panel is hidden by default: {audit_tag}"
);
assert!(
!html.contains("href=\"/audit"),
"the toggle is a button, NOT an <a href=\"/audit\"> navigation: {html}"
);
}
#[test]
fn dashboard_feed_reset_targets_feed_only() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("zynk.db");
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--audit-id",
"a1",
"--previous-audit-id",
"none",
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
"m1",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"x",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(out.status.success(), "{}", stderr(&out));
let html = fetch_db_dashboard_once(&db, "/?session=s");
assert!(html.contains("HTTP/1.1 200 OK"), "{html}");
assert!(
html.contains("getElementById('feed')"),
"the SSE client resolves #feed by id: {html}"
);
assert!(
html.contains("feed.innerHTML=e.data"),
"feed-reset writes the feed fragment to #feed.innerHTML ONLY: {html}"
);
assert!(
html.contains("feed.insertAdjacentHTML('beforeend',e.data)"),
"feed-append appends to #feed ONLY: {html}"
);
assert!(
!html.contains("document.body.innerHTML=e.data"),
"feed-reset must NOT replace document.body: {html}"
);
assert!(
!html.contains("querySelector('main').innerHTML=e.data")
&& !html.contains("getElementsByTagName('main')"),
"feed-reset must NOT replace <main>: {html}"
);
}
#[test]
fn db_vertical_slice_import_append_dashboard_and_export_round_trip() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("source-outputs");
let export_root = tmp.path().join("exported-outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_event_artifact(
&root,
"session-a",
"2026-05-29T01:00:00Z",
"vertical slice opened",
);
write_status_event_artifact(
&root,
"session-a",
"2026-05-29T01:01:00Z",
"vertical slice imported",
);
append_audit_artifact_via_cli(
&root,
"session-a",
TestAuditAppend {
audit_id: "aud001",
timestamp: "2026-05-29T01:02:00Z",
mid: "mid001",
delivery_status: "sent",
verified_by: "helper-tool",
payload: "initial transport send",
},
);
append_audit_artifact_via_cli(
&root,
"session-a",
TestAuditAppend {
audit_id: "aud002",
timestamp: "2026-05-29T01:03:00Z",
mid: "mid001",
delivery_status: "observed",
verified_by: "agent",
payload: "observed by receiver",
},
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let append = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"aud003",
"--timestamp",
"2026-05-29T01:04:00Z",
"--source-address",
"codex-pane",
"--target-address",
"claude-pane",
"--transport",
"herdr",
"--workspace-id",
"workspace-a",
"--mid",
"mid002",
"--type",
"status-update",
"--mode",
"review",
"--ref",
"seam-6-vertical-slice",
"--command-origin",
"agent",
"--payload",
"db-backed status update",
"--payload-redaction-policy",
"excerpt",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(append.status.success(), "{}", stderr(&append));
let dashboard = fetch_db_dashboard_once(&db_path, "/?session=session-a");
assert!(dashboard.contains("HTTP/1.1 200 OK"), "{dashboard}");
assert!(dashboard.contains("session-a"));
assert!(dashboard.contains("mid001"));
assert!(dashboard.contains("mid002"));
assert!(dashboard.contains("observed / agent"));
assert!(dashboard.contains("sent / helper-tool"));
assert!(dashboard.contains("seam-6-vertical-slice"));
let export = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"export",
"outputs",
"--root",
export_root.to_str().unwrap(),
"--session-id",
"session-a",
]);
assert!(export.status.success(), "{}", stderr(&export));
let exported_status =
fs::read_to_string(export_root.join("sessions/session-a/status.md")).unwrap();
assert!(exported_status.contains("vertical slice imported"));
assert!(exported_status.contains("vertical slice opened"));
let exported_audit =
fs::read_to_string(export_root.join("sessions/session-a/audit.md")).unwrap();
assert!(exported_audit.contains("audit_id=aud001\nprevious_audit_id=none"));
assert!(exported_audit.contains("audit_id=aud002\nprevious_audit_id=aud001"));
assert!(exported_audit.contains("audit_id=aud003\nprevious_audit_id=aud002"));
assert!(exported_audit.contains("mid=mid002"));
assert!(exported_audit.contains("payload_redaction_policy=excerpt"));
}
fn table_columns(connection: &Connection, table: &str) -> Vec<String> {
let mut statement = connection
.prepare(&format!("PRAGMA table_info({table})"))
.unwrap();
statement
.query_map([], |row| row.get::<_, String>(1))
.unwrap()
.map(Result::unwrap)
.collect()
}
fn write_status_artifact(
root: &Path,
session_id: &str,
phase: &str,
mode: &str,
status: &str,
artifact_ref: &str,
) {
let output = run_zynk(&[
"status",
"--root",
root.to_str().unwrap(),
"--session-id",
session_id,
"--timestamp",
"2026-05-29T01:00:00Z",
"--phase",
phase,
"--mode",
mode,
"--artifact-ref",
artifact_ref,
"--lead-agent",
"codex",
"--status",
status,
"--completed",
"status artifact written",
"--in-progress",
"import test",
"--next-action",
"import outputs",
"--blockers",
"none",
"--asks-for-zevs",
"none",
"--risk",
"none",
"--expected-wait",
"unknown",
]);
assert!(output.status.success(), "{}", stderr(&output));
}
fn write_status_event_artifact(root: &Path, session_id: &str, timestamp: &str, event: &str) {
let output = run_zynk(&[
"status",
"--root",
root.to_str().unwrap(),
"--session-id",
session_id,
"--timestamp",
timestamp,
"--phase",
"vertical-slice",
"--mode",
"review",
"--artifact-ref",
"seam-6-vertical-slice",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"status artifact written",
"--in-progress",
"vertical slice test",
"--next-action",
"export outputs",
"--blockers",
"none",
"--asks-for-zevs",
"none",
"--risk",
"none",
"--expected-wait",
"unknown",
"--event",
event,
]);
assert!(output.status.success(), "{}", stderr(&output));
}
struct TestAuditAppend<'a> {
audit_id: &'a str,
timestamp: &'a str,
mid: &'a str,
delivery_status: &'a str,
verified_by: &'a str,
payload: &'a str,
}
fn append_audit_artifact_via_cli(root: &Path, session_id: &str, input: TestAuditAppend<'_>) {
let output = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--session-id",
session_id,
"--audit-id",
input.audit_id,
"--timestamp",
input.timestamp,
"--source-agent",
"codex",
"--source-address",
"codex-pane",
"--target-agent",
"claude",
"--target-address",
"claude-pane",
"--transport",
"herdr",
"--workspace-id",
"workspace-a",
"--mid",
input.mid,
"--type",
"request-review",
"--mode",
"review",
"--ref",
"seam-6-vertical-slice",
"--command-origin",
"agent",
"--payload",
input.payload,
"--payload-redaction-policy",
"excerpt",
"--delivery-status",
input.delivery_status,
"--observed-by",
"codex",
"--verified-by",
input.verified_by,
]);
assert!(output.status.success(), "{}", stderr(&output));
}
fn write_audit_artifact(root: &Path, session_id: &str, records: &[String]) {
let session_dir = root.join("sessions").join(session_id);
fs::create_dir_all(&session_dir).unwrap();
fs::write(
session_dir.join("audit.md"),
format!(
"# Audit Trail: {session_id}\n\nsession_id: {session_id}\n\n## Records\n\n{}\n",
records.join("\n")
),
)
.unwrap();
}
fn test_audit_record(
audit_id: &str,
previous_audit_id: &str,
mid: &str,
delivery_status: &str,
verified_by: &str,
) -> String {
test_audit_record_at(
audit_id,
previous_audit_id,
mid,
delivery_status,
verified_by,
"2026-05-29T01:00:00Z",
)
}
fn test_audit_record_at(
audit_id: &str,
previous_audit_id: &str,
mid: &str,
delivery_status: &str,
verified_by: &str,
timestamp: &str,
) -> String {
format!(
"```text\n\
audit_id={audit_id}\n\
previous_audit_id={previous_audit_id}\n\
timestamp={timestamp}\n\
source_agent=codex\n\
source_address=codex-pane\n\
target_agent=claude\n\
target_address=claude-pane\n\
transport=herdr\n\
workspace_id=workspace-a\n\
session_id=session-a\n\
mid={mid}\n\
type=request-review\n\
command_origin=agent\n\
payload_hash=sha256:test-{audit_id}\n\
payload_redaction_policy=hash-only\n\
content_size=12\n\
delivery_status={delivery_status}\n\
observed_by=codex\n\
verified_by={verified_by}\n\
mode=review\n\
ref=tools/import\n\
```"
)
}
fn fetch_db_dashboard_once(db_path: &Path, route: &str) -> String {
fetch_db_dashboard_once_with_method(db_path, "GET", route)
}
fn fetch_db_dashboard_once_with_method(db_path: &Path, method: &str, route: &str) -> String {
let mut child = StdCommand::new(env!("CARGO_BIN_EXE_zynk"))
.args([
"db",
"--db",
db_path.to_str().unwrap(),
"serve",
"--port",
"0",
"--once",
])
.stdout(Stdio::piped())
.spawn()
.unwrap();
let stdout = child.stdout.take().unwrap();
let mut reader = BufReader::new(stdout);
let mut line = String::new();
reader.read_line(&mut line).unwrap();
assert!(line.starts_with("listening on http://127.0.0.1:"), "{line}");
let host_port = line
.trim()
.strip_prefix("listening on http://")
.unwrap()
.trim_end_matches('/');
let mut stream = TcpStream::connect(host_port).unwrap();
write!(
stream,
"{method} {route} HTTP/1.1\r\nHost: {host_port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
stream.shutdown(Shutdown::Write).unwrap();
let mut response_bytes = Vec::new();
let mut buffer = [0_u8; 4096];
loop {
match stream.read(&mut buffer) {
Ok(0) => break,
Ok(count) => response_bytes.extend_from_slice(&buffer[..count]),
Err(error) if error.kind() == ErrorKind::ConnectionReset => break,
Err(error) => panic!("failed to read dashboard response: {error}"),
}
}
let response = String::from_utf8_lossy(&response_bytes).to_string();
let status = child.wait().unwrap();
assert!(status.success());
response
}
#[test]
fn dashboard_composer_send_records_operator_audit() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&to=codex%3Aw1-1&type=status-update&body=hello+from+operator";
let resp = post_dashboard(port, "/send", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 303"),
"PRG redirect on success: {resp}"
);
let conn = Connection::open(&db).unwrap();
let (origin, verified): (String, String) = conn
.query_row(
"SELECT command_origin, verified_by FROM audit_records WHERE session_id='s1' AND command_origin='operator' ORDER BY timestamp DESC LIMIT 1",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(origin, "operator");
assert_eq!(verified, "helper-tool");
}
#[test]
fn db_serve_handles_multiple_connections() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (_token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let one = post_dashboard_raw(
port,
&format!(
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
),
);
let two = post_dashboard_raw(
port,
&format!(
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
),
);
child.kill().ok();
child.wait().ok();
assert!(one.contains("HTTP/1.1 200"), "first served: {one}");
assert!(two.contains("HTTP/1.1 200"), "second served: {two}");
}
#[test]
fn db_serve_rejects_foreign_host_on_reads() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let evil = post_dashboard_raw(
port,
"GET /?session=s1 HTTP/1.1\r\nHost: evil.test\r\nConnection: close\r\n\r\n",
);
let ok = post_dashboard_raw(
port,
&format!(
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
),
);
child.kill().ok();
child.wait().ok();
assert!(
evil.contains("HTTP/1.1 403"),
"foreign Host rejected: {evil}"
);
assert!(ok.contains("HTTP/1.1 200"), "served Host ok: {ok}");
}
#[test]
fn db_serve_rejects_duplicate_host_header() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let auth_then_evil = post_dashboard_raw(
port,
&format!(
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nHost: evil.test\r\nConnection: close\r\n\r\n"
),
);
let evil_then_auth = post_dashboard_raw(
port,
&format!(
"GET /?session=s1 HTTP/1.1\r\nHost: evil.test\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
),
);
let single = post_dashboard_raw(
port,
&format!(
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
),
);
child.kill().ok();
child.wait().ok();
assert!(
auth_then_evil.contains("HTTP/1.1 403"),
"duplicate Host (authority first) rejected: {auth_then_evil}"
);
assert!(
evil_then_auth.contains("HTTP/1.1 403"),
"duplicate Host (evil first) rejected: {evil_then_auth}"
);
assert!(
single.contains("HTTP/1.1 200"),
"single correct Host still served: {single}"
);
}
fn start_writable_serve(
db: &Path,
root: &Path,
herdr: &Path,
) -> (String, u16, std::process::Child) {
let mut child = StdCommand::new(env!("CARGO_BIN_EXE_zynk"))
.args([
"db",
"--db",
db.to_str().unwrap(),
"serve",
"--port",
"0",
"--allow-writes",
"--root",
root.to_str().unwrap(),
"--herdr-bin",
herdr.to_str().unwrap(),
])
.env_remove("HERDR_ENV")
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut line = String::new();
BufReader::new(child.stdout.take().unwrap())
.read_line(&mut line)
.unwrap();
let host_port = line
.trim()
.strip_prefix("listening on http://")
.unwrap()
.trim_end_matches('/');
let port: u16 = host_port.rsplit(':').next().unwrap().parse().unwrap();
let page = {
let mut stream = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(
stream,
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
stream.shutdown(Shutdown::Write).unwrap();
let mut body = String::new();
stream.read_to_string(&mut body).unwrap();
body
};
let token = page
.split("name=\"csrf\" value=\"")
.nth(1)
.unwrap_or_else(|| panic!("no csrf token in rendered page:\n{page}"))
.split('"')
.next()
.unwrap()
.to_string();
(token, port, child)
}
fn post_dashboard(port: u16, route: &str, token: &str, form: &str) -> String {
post_dashboard_raw(
port,
&format!(
"POST {route} HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nOrigin: http://127.0.0.1:{port}\r\nX-Zynk-CSRF: {token}\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{form}",
form.len()
),
)
}
fn post_dashboard_raw(port: u16, raw: &str) -> String {
let mut stream = TcpStream::connect(("127.0.0.1", port)).unwrap();
stream.write_all(raw.as_bytes()).unwrap();
stream.shutdown(Shutdown::Write).unwrap();
let mut response = String::new();
stream.read_to_string(&mut response).unwrap();
response
}
fn seed_writable_session(root: &Path, db: &Path) {
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let seed = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--audit-id",
"seed0",
"--timestamp",
"2026-05-29T01:00:00Z",
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
"seedm0",
"--type",
"status-update",
"--command-origin",
"agent",
"--payload",
"seed",
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(seed.status.success(), "{}", stderr(&seed));
}
fn read_sse_snapshot(port: u16, session: &str) -> String {
use std::time::Duration;
let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(s, "GET /events?session={session} HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n").unwrap();
s.set_read_timeout(Some(Duration::from_millis(1500)))
.unwrap();
let mut out = Vec::new();
let mut chunk = [0u8; 1024];
loop {
match s.read(&mut chunk) {
Ok(0) => break,
Ok(n) => {
out.extend_from_slice(&chunk[..n]);
if out.windows(2).filter(|w| *w == b"\n\n").count() >= 1 {
break;
}
}
Err(_) => break,
}
}
String::from_utf8_lossy(&out).to_string()
}
fn read_sse_snapshot_multi(port: u16, session: &str) -> String {
use std::time::{Duration, Instant};
let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(s, "GET /events?session={session} HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n").unwrap();
s.set_read_timeout(Some(Duration::from_millis(400)))
.unwrap();
let deadline = Instant::now() + Duration::from_millis(1600);
let mut out = Vec::new();
let mut chunk = [0u8; 2048];
while Instant::now() < deadline {
match s.read(&mut chunk) {
Ok(0) => break,
Ok(n) => out.extend_from_slice(&chunk[..n]),
Err(_) => {}
}
}
String::from_utf8_lossy(&out).to_string()
}
#[test]
fn db_serve_sse_roster_falls_back_outside_herdr() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let snap = read_sse_snapshot_multi(port, "s1");
child.kill().ok();
child.wait().ok();
assert!(
snap.contains("event: roster"),
"roster event present: {snap}"
);
assert!(
snap.contains("db-fallback"),
"db-fallback provenance outside herdr: {snap}"
);
assert!(
snap.contains("codex"),
"known participant in roster: {snap}"
);
}
#[test]
fn db_serve_sse_roster_includes_lead_agent_for_status_only_session() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
let seed = run_zynk(&[
"status",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--timestamp",
"2026-05-29T01:00:00Z",
"--phase",
"tooling",
"--mode",
"review",
"--artifact-ref",
"tools/status-only",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"seed",
"--in-progress",
"seed",
"--next-action",
"next",
]);
assert!(seed.status.success(), "{}", stderr(&seed));
let connection = Connection::open(&db).unwrap();
let lead: String = connection
.query_row(
"SELECT lead_agent_id FROM sessions WHERE session_id = 's1'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(lead, "codex");
let agent_count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM agents WHERE agent_id = 'codex'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(agent_count, 1, "the lead agent row exists");
let audit_count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE session_id = 's1'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(audit_count, 0, "no audit rows for a status-only session");
drop(connection);
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let snap = read_sse_snapshot_multi(port, "s1");
child.kill().ok();
child.wait().ok();
assert!(
snap.contains("event: roster"),
"roster event present: {snap}"
);
assert!(
snap.contains("codex") && snap.contains("db-fallback"),
"status-only roster includes the lead agent with db-fallback: {snap}"
);
}
#[test]
fn roster_shows_assigned_actor_kind_and_traits() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let kind = run_zynk(&[
"assign",
"actor-kind",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--timestamp",
"2026-05-29T02:00:00Z",
"--subject",
"codex",
"--kind",
"agent",
]);
assert!(kind.status.success(), "{}", stderr(&kind));
let trait_ = run_zynk(&[
"assign",
"trait",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--timestamp",
"2026-05-29T02:01:00Z",
"--subject",
"codex",
"--trait",
"independent",
]);
assert!(trait_.status.success(), "{}", stderr(&trait_));
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let snap = read_sse_snapshot_multi(port, "s1");
child.kill().ok();
child.wait().ok();
assert!(
snap.contains("event: roster"),
"roster event present: {snap}"
);
assert!(
snap.contains("roster-actor-agent"),
"roster shows codex's assigned actor-kind marker (agent): {snap}"
);
assert!(
snap.contains("roster-traits") && snap.contains("independent"),
"roster shows codex's assigned `independent` trait badge: {snap}"
);
}
#[test]
fn operator_subject_appears_in_roster_not_sendable() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let kind = run_zynk(&[
"assign",
"actor-kind",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--timestamp",
"2026-05-29T02:00:00Z",
"--subject",
"operator",
"--kind",
"human",
]);
assert!(kind.status.success(), "{}", stderr(&kind));
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let snap = read_sse_snapshot_multi(port, "s1");
child.kill().ok();
child.wait().ok();
assert!(
snap.contains("event: roster"),
"roster event present: {snap}"
);
assert!(
snap.contains("operator"),
"operator subject appears in the display roster: {snap}"
);
assert!(
snap.contains("roster-actor-human"),
"operator renders a human actor-kind marker in the roster: {snap}"
);
let html = fetch_dashboard_with_serve_args(
&db,
"/?session=s1",
&["--allow-writes", "--root", root.to_str().unwrap()],
);
assert!(
html.contains("<option value=\"codex:w1-1\">"),
"the composer still offers the real sendable target codex:w1-1: {html}"
);
assert!(
!html.contains("<option value=\"operator:"),
"operator (overlay-only subject) is NEVER a sendable composer target: {html}"
);
}
#[test]
fn db_serve_sse_emits_initial_feed_reset_snapshot() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db); let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let snap = read_sse_snapshot(port, "s1");
child.kill().ok();
child.wait().ok();
assert!(snap.contains("HTTP/1.1 200"), "{snap}");
assert!(
snap.contains("text/event-stream"),
"SSE content-type: {snap}"
);
assert!(
!snap.to_lowercase().contains("access-control-allow"),
"no CORS"
);
assert!(snap.contains("event: feed-reset"), "initial reset: {snap}");
assert!(
snap.contains("seedm0"),
"feed snapshot carries the seeded message: {snap}"
);
}
#[test]
fn db_serve_sse_emits_session_state() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let snap = read_sse_snapshot_multi(port, "s1");
child.kill().ok();
child.wait().ok();
let state_block = snap
.split("event: session-state")
.nth(1)
.and_then(|tail| tail.split("\n\n").next())
.unwrap_or_else(|| panic!("no session-state event in stream: {snap}"));
assert!(
state_block.contains("<dt>State</dt><dd>idle</dd>"),
"session-state detail carries the current workflow_status: {state_block}"
);
assert!(
state_block.contains("s1"),
"session-state payload carries the session id: {state_block}"
);
}
#[test]
fn dashboard_streams_usage_event_on_change() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let usage = run_zynk(&[
"report",
"usage",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-29T02:00:00Z",
"--agent",
"claude",
"--tokens",
"4700",
]);
assert!(usage.status.success(), "{}", stderr(&usage));
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let snap = read_sse_snapshot_multi(port, "s1");
child.kill().ok();
child.wait().ok();
assert!(snap.contains("event: usage"), "usage event present: {snap}");
let usage_block = snap
.split("event: usage")
.nth(1)
.and_then(|tail| tail.split("\n\n").next())
.unwrap_or_else(|| panic!("no usage event in stream: {snap}"));
assert!(
usage_block.contains("4700"),
"usage event payload carries the aggregate token count: {usage_block}"
);
}
#[test]
fn dashboard_usage_empty_state_renders() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db); let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let page = {
let mut stream = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(
stream,
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
stream.shutdown(Shutdown::Write).unwrap();
let mut body = String::new();
stream.read_to_string(&mut body).unwrap();
body
};
child.kill().ok();
child.wait().ok();
assert!(
page.contains("data-usage"),
"the static page carries a [data-usage] usage mount: {page}"
);
let usage_mount = page
.split("data-usage")
.nth(1)
.and_then(|tail| tail.split("</div>").next())
.unwrap_or_else(|| panic!("no [data-usage] mount in page: {page}"));
assert!(
usage_mount.contains("0 tokens"),
"empty-state usage renders 0 tokens: {usage_mount}"
);
assert!(
usage_mount.contains('\u{2014}'),
"empty-state cost is the em dash placeholder: {usage_mount}"
);
assert!(
!usage_mount.contains("$0.00"),
"empty-state cost is NEVER a fabricated $0.00: {usage_mount}"
);
}
#[test]
fn dashboard_topbar_has_usage_toggle_mode() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
seed_context_panel_session(&root, &db, false);
let response = fetch_db_dashboard_once(&db, "/?session=rail1");
assert!(response.contains("HTTP/1.1 200 OK"), "{response}");
let html = response
.split("\r\n\r\n")
.nth(1)
.unwrap_or_else(|| panic!("no body in response: {response}"));
assert!(
html.contains("class=\"topbar\""),
"a topbar header is present: {html}"
);
let topbar_at = html
.find("class=\"topbar\"")
.unwrap_or_else(|| panic!("no topbar in page: {html}"));
let shell_at = html
.find("class=\"app-shell\"")
.unwrap_or_else(|| panic!("no app-shell in page: {html}"));
assert!(
topbar_at < shell_at,
"the topbar is rendered ABOVE the .app-shell: {html}"
);
let topbar = &html[topbar_at..shell_at];
assert!(
topbar.contains("rail1"),
"the topbar carries the session label: {topbar}"
);
assert!(
topbar.contains("8200") && topbar.contains("tokens"),
"the topbar carries the usage aggregate (tokens): {topbar}"
);
assert!(
topbar.contains("id=\"view-toggle\""),
"the #view-toggle control lives in the topbar: {topbar}"
);
assert!(
topbar.contains("review"),
"the topbar shows the current mode: {topbar}"
);
assert_eq!(
html.matches("id=\"view-toggle\"").count(),
1,
"the #view-toggle is relocated into the topbar, not duplicated: {html}"
);
}
#[test]
fn mode_rail_highlights_current_mode() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
seed_context_panel_session(&root, &db, false);
let response = fetch_db_dashboard_once(&db, "/?session=rail1");
assert!(response.contains("HTTP/1.1 200 OK"), "{response}");
let html = response
.split("\r\n\r\n")
.nth(1)
.unwrap_or_else(|| panic!("no body in response: {response}"));
let rail_at = html
.find("class=\"mode-rail\"")
.unwrap_or_else(|| panic!("no mode-rail in page: {html}"));
let rail_end = html[rail_at..]
.find("</nav>")
.map(|off| rail_at + off)
.unwrap_or(html.len());
let rail = &html[rail_at..rail_end];
for mode in ["brainstorm", "decide", "review", "validate", "debug"] {
assert!(
rail.contains(mode),
"the mode-rail lists the canonical mode `{mode}`: {rail}"
);
}
assert!(
rail.contains("mode active\">review")
|| rail.contains("class=\"mode active\">review")
|| rail.contains("active\">review"),
"the mode-rail marks the current mode `review` active: {rail}"
);
assert!(
!rail.contains("active\">decide"),
"the mode-rail does NOT mark a non-current mode (`decide`) active: {rail}"
);
}
#[test]
fn usage_event_updates_topbar_and_budget() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
seed_context_panel_session(&root, &db, false);
let response = fetch_db_dashboard_once(&db, "/?session=rail1");
assert!(response.contains("HTTP/1.1 200 OK"), "{response}");
let html = response
.split("\r\n\r\n")
.nth(1)
.unwrap_or_else(|| panic!("no body in response: {response}"));
assert!(
html.matches("data-usage").count() >= 2,
"the page has >=2 [data-usage] usage mounts (topbar + budget): {html}"
);
assert!(
html.matches("8200 tokens").count() >= 2,
"both usage mounts render the token total at load: {html}"
);
assert_eq!(
html.matches("id=\"usage\"").count(),
0,
"render_usage_html must NOT hardcode id=\"usage\" (no duplicate id; class-only fragment): {html}"
);
assert!(
html.contains("querySelectorAll('[data-usage]')")
|| html.contains("querySelectorAll(\"[data-usage]\")"),
"the usage SSE handler targets all [data-usage] mounts via querySelectorAll: {html}"
);
assert!(
!html.contains("getElementById('usage')"),
"the usage SSE handler must NOT target a single getElementById('usage'): {html}"
);
}
fn seed_context_panel_session(root: &Path, db: &Path, with_artifact: bool) {
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let status = run_zynk(&[
"status",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"rail1",
"--timestamp",
"2026-05-29T01:00:00Z",
"--phase",
"tooling",
"--mode",
"review",
"--artifact-ref",
"ref-rail",
"--lead-agent",
"claude",
"--status",
"working",
"--completed",
"seeded",
"--in-progress",
"context panel",
"--next-action",
"ship",
"--blockers",
"none",
"--asks-for-zevs",
"none",
"--risk",
"none",
"--expected-wait",
"unknown",
]);
assert!(status.status.success(), "{}", stderr(&status));
let audit_one = run_zynk(&[
"audit",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"rail1",
"--audit-id",
"seed0",
"--timestamp",
"2026-05-29T01:01:00Z",
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
"seedm0",
"--type",
"status-update",
"--command-origin",
"agent",
"--payload",
"first",
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(audit_one.status.success(), "{}", stderr(&audit_one));
let audit_two = run_zynk(&[
"audit",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"rail1",
"--audit-id",
"seed1",
"--previous-audit-id",
"seed0",
"--timestamp",
"2026-05-29T01:02:00Z",
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
"seedm1",
"--type",
"status-update",
"--command-origin",
"agent",
"--payload",
"second",
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(audit_two.status.success(), "{}", stderr(&audit_two));
let usage = run_zynk(&[
"report",
"usage",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"rail1",
"--actor",
"claude",
"--timestamp",
"2026-05-29T01:03:00Z",
"--agent",
"claude",
"--tokens",
"8200",
]);
assert!(usage.status.success(), "{}", stderr(&usage));
if with_artifact {
let artifact = run_zynk(&[
"report",
"artifact",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"rail1",
"--actor",
"claude",
"--timestamp",
"2026-05-29T01:04:00Z",
"--file",
"src/context_panel.rs:42:1",
]);
assert!(artifact.status.success(), "{}", stderr(&artifact));
}
}
#[test]
fn right_rail_has_all_sections() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
seed_context_panel_session(&root, &db, true);
let response = fetch_db_dashboard_once(&db, "/?session=rail1");
assert!(response.contains("HTTP/1.1 200 OK"), "{response}");
assert!(
response.contains("class=\"context-panel\""),
"right aside is a .context-panel: {response}"
);
let rail = response
.split("id=\"detail\"")
.nth(1)
.and_then(|tail| tail.split("</aside>").next())
.unwrap_or_else(|| panic!("no #detail aside in page: {response}"));
assert!(
rail.contains("Status") && rail.contains("working"),
"Status section keeps the M1 workflow status: {rail}"
);
assert!(
rail.contains("chain-summary"),
"an audit-chain summary section is present: {rail}"
);
assert!(
rail.contains("intact") && rail.contains('2'),
"chain summary shows intact + the 2-record count: {rail}"
);
assert!(
rail.contains("src/context_panel.rs"),
"Artifacts section lists the real artifact file path: {rail}"
);
assert!(
rail.contains("8200 tokens"),
"Context budget section renders the usage token total: {rail}"
);
assert!(
rail.to_lowercase().contains("controls"),
"an operator controls section is present: {rail}"
);
assert!(
rail.to_lowercase().contains("transport") || rail.to_lowercase().contains("connection"),
"a transport/connection sub-panel is present: {rail}"
);
}
#[test]
fn right_rail_artifacts_empty_state() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
seed_context_panel_session(&root, &db, false);
let response = fetch_db_dashboard_once(&db, "/?session=rail1");
assert!(response.contains("HTTP/1.1 200 OK"), "{response}");
let rail = response
.split("id=\"detail\"")
.nth(1)
.and_then(|tail| tail.split("</aside>").next())
.unwrap_or_else(|| panic!("no #detail aside in page: {response}"));
let artifacts = rail
.split("artifacts-section")
.nth(1)
.unwrap_or_else(|| panic!("no artifacts-section in rail: {rail}"));
assert!(
artifacts.contains("No artifacts"),
"Artifacts section renders an empty-state when none exist: {artifacts}"
);
assert!(
!rail.contains(".rs:"),
"the empty Artifacts section must NOT fabricate an artifact row: {rail}"
);
}
#[test]
fn m4b_absent_class_renders_empty_state_not_mock() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("state/zynk.db");
let root = tmp.path().join("outputs");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let base = [
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"empty1",
];
let st = run_zynk(
&[
&[
"status",
"--timestamp",
"2026-05-31T00:00:00Z",
"--phase",
"plan",
"--mode",
"brainstorm",
"--artifact-ref",
"ref-1",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"i",
"--next-action",
"n",
"--blockers",
"none",
"--asks-for-zevs",
"none",
"--risk",
"none",
"--expected-wait",
"unknown",
],
base.as_slice(),
]
.concat(),
);
assert!(st.status.success(), "{}", stderr(&st));
let page = fetch_db_dashboard_once(&db, "/?session=empty1");
assert!(page.contains("HTTP/1.1 200"), "{page}");
assert!(
page.contains("<p class=\"empty-state\">No artifacts.</p>"),
"absent artifacts render an empty-state: {page}"
);
for mock in ["mock", "sample-card", "placeholder-card", "lorem"] {
assert!(
!page.to_lowercase().contains(mock),
"no mock card sentinel `{mock}`: {page}"
);
}
}
#[test]
fn transport_panel_has_no_fake_latency() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
seed_context_panel_session(&root, &db, true);
let response = fetch_db_dashboard_once(&db, "/?session=rail1");
assert!(response.contains("HTTP/1.1 200 OK"), "{response}");
let rail = response
.split("id=\"detail\"")
.nth(1)
.and_then(|tail| tail.split("</aside>").next())
.unwrap_or_else(|| panic!("no #detail aside in page: {response}"));
let transport = rail
.split("transport-panel")
.nth(1)
.unwrap_or_else(|| panic!("no transport-panel in rail: {rail}"));
assert!(
transport.contains("loopback"),
"transport panel names the loopback connection: {transport}"
);
assert!(
transport.contains("db-fallback") || transport.contains("live-herdr"),
"transport panel names the roster provenance source: {transport}"
);
assert!(
!transport.contains("ms"),
"transport panel must NOT carry a fake latency metric: {transport}"
);
}
#[test]
fn db_serve_sse_emits_feed_append_on_post_connect_write() {
use std::time::Duration;
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let mut sse = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(
sse,
"GET /events?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
sse.set_read_timeout(Some(Duration::from_millis(1500)))
.unwrap();
let mut buf = Vec::new();
let mut chunk = [0u8; 2048];
if let Ok(n) = sse.read(&mut chunk) {
buf.extend_from_slice(&chunk[..n]);
} let form = "session=s1&to=codex%3Aw1-1&type=status-update&body=live+ping";
let resp = post_dashboard(port, "/send", &token, form);
assert!(resp.contains("HTTP/1.1 303"), "send ok: {resp}");
let deadline = std::time::Instant::now() + Duration::from_secs(4);
let mut saw_append = false;
while std::time::Instant::now() < deadline {
match sse.read(&mut chunk) {
Ok(0) => break,
Ok(n) => {
buf.extend_from_slice(&chunk[..n]);
if String::from_utf8_lossy(&buf).contains("event: feed-append") {
saw_append = true;
break;
}
}
Err(_) => break,
}
}
child.kill().ok();
child.wait().ok();
let text = String::from_utf8_lossy(&buf);
assert!(
saw_append,
"a post-connect write emits feed-append (not only reset): {text}"
);
assert!(
text.contains("live ping") || text.contains("live+ping") || text.contains("status-update"),
"the appended event carries the new message: {text}"
);
}
#[test]
fn sse_emits_decision_overlay_after_post_connect_gate_decision() {
use std::time::Duration;
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session_with_gate(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let mut sse = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(
sse,
"GET /events?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
sse.set_read_timeout(Some(Duration::from_millis(1500)))
.unwrap();
let mut buf = Vec::new();
let mut chunk = [0u8; 4096];
if let Ok(n) = sse.read(&mut chunk) {
buf.extend_from_slice(&chunk[..n]);
} assert!(
!String::from_utf8_lossy(&buf).contains("decision-verdict"),
"the gate card must NOT carry a verdict overlay BEFORE the decision: {}",
String::from_utf8_lossy(&buf)
);
let form = "session=s1&ref=1&verdict=approve";
let resp = post_dashboard(port, "/decide/gate", &token, form);
assert!(resp.contains("HTTP/1.1 303"), "decide ok: {resp}");
let deadline = std::time::Instant::now() + Duration::from_secs(4);
let mut saw_overlay = false;
while std::time::Instant::now() < deadline {
match sse.read(&mut chunk) {
Ok(0) => break,
Ok(n) => {
buf.extend_from_slice(&chunk[..n]);
if String::from_utf8_lossy(&buf).contains("decision-verdict") {
saw_overlay = true;
break;
}
}
Err(_) => break,
}
}
child.kill().ok();
child.wait().ok();
let text = String::from_utf8_lossy(&buf);
assert!(
saw_overlay,
"a post-connect gate decision must push the decision-verdict overlay over SSE \
(feed-reset, not just a heartbeat): {text}"
);
assert!(
text.contains("event: feed-reset")
&& text
.split("event: feed-reset")
.last()
.unwrap_or("")
.contains("decision-verdict"),
"the overlay rode in on a feed-reset (overlay change -> non-prefix -> Reset): {text}"
);
}
#[test]
fn db_serve_sse_caps_concurrent_connections() {
use std::time::Duration;
const SSE_CONNECTION_CAP: usize = 8;
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let mut held: Vec<TcpStream> = Vec::new();
for i in 0..SSE_CONNECTION_CAP {
let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(
s,
"GET /events?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
s.set_read_timeout(Some(Duration::from_millis(1500)))
.unwrap();
let mut buf = Vec::new();
let mut chunk = [0u8; 2048];
let deadline = std::time::Instant::now() + Duration::from_secs(3);
loop {
if String::from_utf8_lossy(&buf).contains("event: feed-reset") {
break;
}
if std::time::Instant::now() >= deadline {
break;
}
match s.read(&mut chunk) {
Ok(0) => break,
Ok(n) => buf.extend_from_slice(&chunk[..n]),
Err(_) => break,
}
}
assert!(
String::from_utf8_lossy(&buf).contains("event: feed-reset"),
"held SSE stream {i} produced an initial feed-reset: {}",
String::from_utf8_lossy(&buf)
);
held.push(s);
}
let over = {
let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(
s,
"GET /events?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
s.set_read_timeout(Some(Duration::from_millis(2000)))
.unwrap();
let mut buf = Vec::new();
let mut chunk = [0u8; 1024];
loop {
match s.read(&mut chunk) {
Ok(0) => break,
Ok(n) => {
buf.extend_from_slice(&chunk[..n]);
if buf.windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
}
Err(_) => break,
}
}
String::from_utf8_lossy(&buf).to_string()
};
drop(held);
child.kill().ok();
child.wait().ok();
assert!(
over.contains("HTTP/1.1 503"),
"the connection over the cap is refused with 503: {over}"
);
assert!(
over.contains("too many live connections"),
"the 503 carries the cap message: {over}"
);
}
#[test]
fn dashboard_page_wires_sse_client_and_roster() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(
s,
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
s.shutdown(Shutdown::Write).unwrap();
let mut page = String::new();
s.read_to_string(&mut page).unwrap();
child.kill().ok();
child.wait().ok();
assert!(
page.contains("new EventSource('/events?session="),
"SSE client wired: {page}"
);
assert!(
page.contains("id=\"feed\""),
"feed container has a stable id"
);
assert!(page.contains("id=\"roster\""), "roster container present");
let composer_at = page
.find("class=\"composer\"")
.unwrap_or_else(|| panic!("composer present: {page}"));
let feed_at = page
.find("id=\"feed\"")
.unwrap_or_else(|| panic!("feed container present: {page}"));
assert!(
composer_at < feed_at,
"composer is outside (before) the #feed container so feed-reset cannot wipe it: {page}"
);
assert!(
!page.contains("var sid=\""),
"no raw session id interpolated into the script body: {page}"
);
assert!(
page.contains("data-sid="),
"session id rides in an escaped data attribute: {page}"
);
assert!(
page.contains("dataset.sid"),
"script reads the session id from the data attribute: {page}"
);
}
#[test]
fn dashboard_send_rejects_unauthorized_post() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&to=codex%3Aw1-1&type=status-update&body=x";
let bad_token = post_dashboard_raw(port, &format!(
"POST /send HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nOrigin: http://127.0.0.1:{port}\r\nX-Zynk-CSRF: wrong\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{form}",
form.len()));
let no_origin = post_dashboard_raw(port, &format!(
"POST /send HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nX-Zynk-CSRF: {token}\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{form}",
form.len()));
let options = post_dashboard_raw(
port,
&format!("OPTIONS /send HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"),
);
child.kill().ok();
child.wait().ok();
assert!(
bad_token.contains("HTTP/1.1 403"),
"wrong token rejected: {bad_token}"
);
assert!(
no_origin.contains("HTTP/1.1 403"),
"missing Origin rejected: {no_origin}"
);
assert!(options.contains("405"), "OPTIONS rejected: {options}");
assert!(
!bad_token.to_lowercase().contains("access-control-allow"),
"no CORS allow headers: {bad_token}"
);
let conn = Connection::open(&db).unwrap();
let n: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE command_origin='operator'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(n, 0, "rejected POSTs dispatched no send/audit");
}
#[test]
fn dashboard_send_escapes_hostile_body_in_output() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo \"$@\" >&2\nexit 1\n");
seed_writable_session(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form =
"session=s1&to=codex%3Aw1-1&type=status-update&body=%3Cscript%3Ealert(1)%3C%2Fscript%3E";
let resp = post_dashboard(port, "/send", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
!resp.contains("<script>alert(1)</script>"),
"hostile body must be escaped, never surfaced raw: {resp}"
);
assert!(
resp.contains("<script>"),
"shows the escaped form: {resp}"
);
}
#[test]
fn dashboard_get_send_does_not_write() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (_token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let resp = post_dashboard_raw(
port,
&format!("GET /send HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"),
);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("404") || resp.contains("405"),
"GET /send is not a write: {resp}"
);
let conn = Connection::open(&db).unwrap();
let n: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE command_origin='operator'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(n, 0, "GET dispatched no send/audit");
}
#[test]
fn dashboard_writes_off_by_default() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let resp = fetch_db_dashboard_once_with_method(&db, "POST", "/send");
assert!(
resp.contains("HTTP/1.1 405"),
"writes disabled by default: {resp}"
);
}
#[test]
fn dashboard_send_rejects_unknown_session() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=ghostsession&to=codex%3Aw1-1&type=status-update&body=x";
let resp = post_dashboard(port, "/send", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 404") || resp.contains("HTTP/1.1 400"),
"unknown session rejected before spawn: {resp}"
);
let conn = Connection::open(&db).unwrap();
let sessions: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sessions WHERE session_id='ghostsession'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(sessions, 0, "no browser-originated session created");
let audits: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE session_id='ghostsession'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(audits, 0, "no audit rows for the rejected session");
}
#[test]
fn dashboard_send_rejects_unknown_target() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&to=ghost%3Az9-9&type=status-update&body=x";
let resp = post_dashboard(port, "/send", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 400") || resp.contains("HTTP/1.1 404"),
"unknown target rejected before spawn: {resp}"
);
let conn = Connection::open(&db).unwrap();
let operator: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE command_origin='operator'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(operator, 0, "no operator audit for an unknown target");
}
#[test]
fn dashboard_composer_renders_known_targets_not_free_input() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (_token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let mut stream = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(
stream,
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
stream.shutdown(Shutdown::Write).unwrap();
let mut page = String::new();
stream.read_to_string(&mut page).unwrap();
child.kill().ok();
child.wait().ok();
assert!(
page.contains("codex:w1-1"),
"offers the known target: {page}"
);
assert!(
page.contains("<select name=\"to\""),
"renders a select of known targets, not a free input"
);
assert!(
!page.contains("<input name=\"to\""),
"no free-text target input"
);
}
#[test]
fn composer_dropdown_only_offers_sendable_targets() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
{
let conn = Connection::open(&db).unwrap();
conn.execute(
"INSERT INTO projects (project_id, name, root_path, created_at, updated_at)
VALUES ('p1', 'P1', '/tmp/p1', '2026-05-29T00:00:00Z', '2026-05-29T00:00:00Z')",
[],
)
.unwrap();
conn.execute(
"INSERT INTO sessions (
session_id, project_id, title, phase, mode, workflow_status,
created_at, updated_at
)
VALUES (
's1', 'p1', 'S1', 'implementation', 'review', 'working',
'2026-05-29T00:00:00Z', '2026-05-29T00:00:00Z'
)",
[],
)
.unwrap();
conn.execute(
"INSERT INTO audit_records (
audit_id, previous_audit_id, session_id, source_agent_id, target_agent_id,
source_address, target_address, transport, workspace_id, mid, record_type,
command_origin, payload_hash, payload_redaction_policy, content_size,
delivery_status, observed_by, verified_by, timestamp
)
VALUES (
'aud-herdr', NULL, 's1', 'claude', 'codex', 'w-1', 'w-2', 'herdr', 'w',
'm1', 'note', 'agent', 'sha256:test', 'hash-only', 12,
'observed', 'codex', 'agent', '2026-05-29T01:00:00Z'
)",
[],
)
.unwrap();
conn.execute(
"INSERT INTO audit_records (
audit_id, previous_audit_id, session_id, source_agent_id, target_agent_id,
source_address, target_address, transport, workspace_id, mid, record_type,
command_origin, payload_hash, payload_redaction_policy, content_size,
delivery_status, observed_by, verified_by, timestamp
)
VALUES (
'aud-decide', NULL, 's1', 'operator', 'none', 'cli', 'none', 'none', 'none',
'm2', 'gate-decision', 'operator', 'sha256:test', 'full', 12,
'observed', 'operator', 'operator', '2026-05-29T01:01:00Z'
)",
[],
)
.unwrap();
conn.execute(
"INSERT INTO audit_records (
audit_id, previous_audit_id, session_id, source_agent_id, target_agent_id,
source_address, target_address, transport, workspace_id, mid, record_type,
command_origin, payload_hash, payload_redaction_policy, content_size,
delivery_status, observed_by, verified_by, timestamp
)
VALUES (
'aud-reveal', NULL, 's1', 'operator', 'none', 'cli', 'none', 'none', 'none',
'm3', 'reveal', 'operator', 'sha256:test', 'full', 12,
'observed', 'operator', 'operator', '2026-05-29T01:02:00Z'
)",
[],
)
.unwrap();
}
let html = fetch_dashboard_with_serve_args(
&db,
"/?session=s1",
&["--allow-writes", "--root", root.to_str().unwrap()],
);
assert!(
html.contains("<option value=\"codex:w-2\">"),
"the write dropdowns must offer the real herdr target codex:w-2: {html}"
);
assert!(
!html.contains("<option value=\"operator:cli\">"),
"the write dropdowns must not offer operator:cli (decide/reveal source): {html}"
);
assert!(
!html.contains("<option value=\"none:none\">"),
"the write dropdowns must not offer none:none (decide/reveal target): {html}"
);
}
fn seed_two_sendable_targets(db: &Path) {
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let conn = Connection::open(db).unwrap();
conn.execute(
"INSERT INTO projects (project_id, name, root_path, created_at, updated_at)
VALUES ('p1', 'P1', '/tmp/p1', '2026-05-29T00:00:00Z', '2026-05-29T00:00:00Z')",
[],
)
.unwrap();
conn.execute(
"INSERT INTO sessions (
session_id, project_id, title, phase, mode, workflow_status,
created_at, updated_at
)
VALUES (
's1', 'p1', 'S1', 'implementation', 'review', 'working',
'2026-05-29T00:00:00Z', '2026-05-29T00:00:00Z'
)",
[],
)
.unwrap();
for (audit_id, mid, target_agent, target_addr, ts) in [
("aud-c", "mc", "claude", "w-1", "2026-05-29T01:00:00Z"),
("aud-x", "mx", "codex", "w-2", "2026-05-29T01:01:00Z"),
] {
conn.execute(
"INSERT INTO audit_records (
audit_id, previous_audit_id, session_id, source_agent_id, target_agent_id,
source_address, target_address, transport, workspace_id, mid, record_type,
command_origin, payload_hash, payload_redaction_policy, content_size,
delivery_status, observed_by, verified_by, timestamp
)
VALUES (
?1, NULL, 's1', 'operator', ?2, 'cli', ?3, 'herdr', 'w',
?4, 'note', 'agent', 'sha256:test', 'hash-only', 12,
'observed', ?2, 'agent', ?5
)",
params![audit_id, target_agent, target_addr, mid, ts],
)
.unwrap();
}
}
#[test]
fn composer_both_fans_out_to_n_real_sends() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_two_sendable_targets(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let page = post_dashboard_raw(
port,
&format!(
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
),
);
assert!(
page.contains("<option value=\"__all__\">All sendable targets (2)</option>"),
"composer offers the __all__ fan-out sentinel labeled with the target count: {page}"
);
assert!(
page.contains("data-sendable-targets="),
"composer emits the sendable targets for the Both fan-out JS: {page}"
);
assert!(
page.contains("claude:w-1") && page.contains("codex:w-2"),
"both real sendable targets are present in the page: {page}"
);
let sendable_attr = page
.split("data-sendable-targets=\"")
.nth(1)
.unwrap_or_else(|| panic!("no data-sendable-targets attribute:\n{page}"))
.split('"')
.next()
.unwrap();
assert!(
!sendable_attr.contains("__all__") && !sendable_attr.contains("__both__"),
"the fan-out target list must not carry the __all__/__both__ sentinel: {sendable_attr}"
);
let r1 = post_dashboard(
port,
"/send",
&token,
"session=s1&to=claude%3Aw-1&type=status-update&body=both+fanout&mid=both-0",
);
let r2 = post_dashboard(
port,
"/send",
&token,
"session=s1&to=codex%3Aw-2&type=status-update&body=both+fanout&mid=both-1",
);
child.kill().ok();
child.wait().ok();
assert!(
r1.contains("HTTP/1.1 303"),
"first fan-out send succeeded: {r1}"
);
assert!(
r2.contains("HTTP/1.1 303"),
"second fan-out send succeeded: {r2}"
);
let conn = Connection::open(&db).unwrap();
let send_rows: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records
WHERE session_id='s1' AND command_origin='operator' AND delivery_status='sent'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
send_rows, 2,
"Both fanned out to exactly two real audited sends"
);
let to_claude: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records
WHERE command_origin='operator' AND target_agent_id='claude' AND target_address='w-1'",
[],
|r| r.get(0),
)
.unwrap();
let to_codex: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records
WHERE command_origin='operator' AND target_agent_id='codex' AND target_address='w-2'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(to_claude, 1, "one send went to claude:w-1");
assert_eq!(to_codex, 1, "one send went to codex:w-2");
let distinct_mids: i64 = conn
.query_row(
"SELECT COUNT(DISTINCT mid) FROM audit_records
WHERE command_origin='operator' AND delivery_status='sent'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
distinct_mids, 2,
"two distinct mids — two independent sends"
);
let distinct_proofs: i64 = conn
.query_row(
"SELECT COUNT(DISTINCT payload_hash) FROM audit_records
WHERE command_origin='operator' AND delivery_status='sent'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(distinct_proofs, 2, "two distinct proofs — two real sends");
let pseudo: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records
WHERE target_agent_id IN ('both', 'all', '__both__', '__all__')
OR target_address IN ('both', 'all', '__both__', '__all__')",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(pseudo, 0, "no audit row carries a both/all pseudo-target");
}
#[test]
fn composer_both_partial_failure_surfaced() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(
tmp.path(),
"if [ \"$3\" = \"w-2\" ]; then echo boom >&2; exit 1; fi\necho sent\n",
);
seed_two_sendable_targets(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let good = post_dashboard(
port,
"/send",
&token,
"session=s1&to=claude%3Aw-1&type=status-update&body=both+fanout&mid=both-0",
);
let bad = post_dashboard(
port,
"/send",
&token,
"session=s1&to=codex%3Aw-2&type=status-update&body=both+fanout&mid=both-1",
);
child.kill().ok();
child.wait().ok();
assert!(
good.contains("HTTP/1.1 303"),
"the good target's send succeeded independently: {good}"
);
assert!(
bad.contains("HTTP/1.1 502") || bad.contains("HTTP/1.1 500"),
"the failing target's error is surfaced per-target: {bad}"
);
let conn = Connection::open(&db).unwrap();
let good_sends: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records
WHERE command_origin='operator' AND delivery_status='sent'
AND target_agent_id='claude' AND target_address='w-1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
good_sends, 1,
"the good fan-out target recorded its send audit"
);
let bad_sends: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records
WHERE command_origin='operator' AND delivery_status='sent'
AND target_agent_id='codex' AND target_address='w-2'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(bad_sends, 0, "the failing target wrote no sent audit");
}
fn seed_three_sendable_targets(db: &Path) {
seed_two_sendable_targets(db); let conn = Connection::open(db).unwrap();
conn.execute(
"INSERT INTO audit_records (
audit_id, previous_audit_id, session_id, source_agent_id, target_agent_id,
source_address, target_address, transport, workspace_id, mid, record_type,
command_origin, payload_hash, payload_redaction_policy, content_size,
delivery_status, observed_by, verified_by, timestamp
)
VALUES (
'aud-p', NULL, 's1', 'operator', 'pi', 'cli', 'w-3', 'herdr', 'w',
'mp', 'note', 'agent', 'sha256:test', 'hash-only', 12,
'observed', 'pi', 'agent', '2026-05-29T01:02:00Z'
)",
[],
)
.unwrap();
}
#[test]
fn composer_all_fanout_partial_failure_n3() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(
tmp.path(),
"if [ \"$3\" = \"w-3\" ]; then echo boom >&2; exit 1; fi\necho sent\n",
);
seed_three_sendable_targets(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let g1 = post_dashboard(
port,
"/send",
&token,
"session=s1&to=claude%3Aw-1&type=status-update&body=all+fanout&mid=all-0",
);
let g2 = post_dashboard(
port,
"/send",
&token,
"session=s1&to=codex%3Aw-2&type=status-update&body=all+fanout&mid=all-1",
);
let bad = post_dashboard(
port,
"/send",
&token,
"session=s1&to=pi%3Aw-3&type=status-update&body=all+fanout&mid=all-2",
);
child.kill().ok();
child.wait().ok();
assert!(
g1.contains("HTTP/1.1 303"),
"good leg 1 (claude:w-1) succeeds independently: {g1}"
);
assert!(
g2.contains("HTTP/1.1 303"),
"good leg 2 (codex:w-2) succeeds independently: {g2}"
);
assert!(
bad.contains("HTTP/1.1 502") || bad.contains("HTTP/1.1 500"),
"the failing leg (pi:w-3) surfaces its error per-target, not aborting the others: {bad}"
);
let conn = Connection::open(&db).unwrap();
let good_sends: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records
WHERE command_origin='operator' AND delivery_status='sent'
AND ((target_agent_id='claude' AND target_address='w-1')
OR (target_agent_id='codex' AND target_address='w-2'))",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(good_sends, 2, "both good legs recorded a real sent proof");
let bad_sends: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records
WHERE command_origin='operator' AND delivery_status='sent'
AND target_agent_id='pi' AND target_address='w-3'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(bad_sends, 0, "the failed leg wrote NO sent (ADR 024)");
let sentinel_targets: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records
WHERE target_agent_id IN ('__all__','__both__','all','both')
OR target_address IN ('__all__','__both__','all','both')",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
sentinel_targets, 0,
"no fan-out leg ever stored a sentinel target"
);
}
#[test]
fn composer_both_is_client_side_no_server_both_target() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_two_sendable_targets(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let both = post_dashboard(
port,
"/send",
&token,
"session=s1&to=__both__&type=status-update&body=should+not+send",
);
let plain_both = post_dashboard(
port,
"/send",
&token,
"session=s1&to=both&type=status-update&body=should+not+send",
);
let all_sentinel = post_dashboard(
port,
"/send",
&token,
"session=s1&to=__all__&type=status-update&body=should+not+send",
);
child.kill().ok();
child.wait().ok();
assert!(
both.contains("HTTP/1.1 400") || both.contains("HTTP/1.1 404"),
"to=__both__ is rejected by sendable_targets validation: {both}"
);
assert!(
plain_both.contains("HTTP/1.1 400") || plain_both.contains("HTTP/1.1 404"),
"to=both is rejected by sendable_targets validation: {plain_both}"
);
assert!(
all_sentinel.contains("HTTP/1.1 400") || all_sentinel.contains("HTTP/1.1 404"),
"to=__all__ is rejected by sendable_targets validation: {all_sentinel}"
);
let conn = Connection::open(&db).unwrap();
let operator: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE command_origin='operator'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
operator, 0,
"no operator audit for a both/__both__ pseudo-target"
);
}
fn seed_db_session(connection: &Connection) {
connection
.execute(
"INSERT INTO projects (project_id, name, root_path, created_at, updated_at)
VALUES ('project-a', 'Project A', '/tmp/project-a', '2026-05-29T00:00:00Z', '2026-05-29T00:00:00Z')",
[],
)
.unwrap();
connection
.execute(
"INSERT INTO sessions (
session_id, project_id, title, phase, mode, workflow_status, created_at, updated_at
)
VALUES (
'session-a', 'project-a', 'Session A', 'implementation', 'review', 'working',
'2026-05-29T00:00:00Z', '2026-05-29T00:00:00Z'
)",
[],
)
.unwrap();
}
fn seed_db_agent(connection: &Connection, agent_id: &str) {
connection
.execute(
"INSERT OR IGNORE INTO agents (
agent_id, display_name, agent_kind, created_at, updated_at
)
VALUES (?1, ?1, 'test', '2026-05-29T00:00:00Z', '2026-05-29T00:00:00Z')",
params![agent_id],
)
.unwrap();
}
fn insert_raw_audit_record(
connection: &Connection,
audit_id: &str,
delivery_status: &str,
verified_by: &str,
) -> rusqlite::Result<usize> {
connection.execute(
"INSERT INTO audit_records (
audit_id, previous_audit_id, session_id, source_address, target_address,
transport, workspace_id, mid, record_type, command_origin, payload_hash,
payload_redaction_policy, content_size, delivery_status, observed_by,
verified_by, timestamp
)
VALUES (
?1, NULL, 'session-a', 'codex-pane', 'claude-pane', 'herdr', 'workspace-a',
'mid001', 'request-review', 'agent', 'sha256:test', 'hash-only', 12, ?2,
'codex', ?3, '2026-05-29T01:00:00Z'
)",
params![audit_id, delivery_status, verified_by],
)
}
fn insert_raw_audit_record_typed(
connection: &Connection,
audit_id: &str,
record_type: &str,
delivery_status: &str,
verified_by: &str,
) -> rusqlite::Result<usize> {
connection.execute(
"INSERT INTO audit_records (
audit_id, previous_audit_id, session_id, source_address, target_address,
transport, workspace_id, mid, record_type, command_origin, payload_hash,
payload_redaction_policy, content_size, delivery_status, observed_by,
verified_by, timestamp
)
VALUES (
?1, NULL, 'session-a', 'operator', 'none', 'none', 'none',
?2, ?3, 'operator', 'sha256:test', 'full', 12, ?4,
'operator', ?5, '2026-05-29T01:00:00Z'
)",
params![
audit_id,
audit_id,
record_type,
delivery_status,
verified_by
],
)
}
fn audit_links(connection: &Connection) -> Vec<(String, Option<String>)> {
let mut statement = connection
.prepare(
"SELECT audit_id, previous_audit_id
FROM audit_records
ORDER BY timestamp, audit_id",
)
.unwrap();
statement
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
.unwrap()
.map(Result::unwrap)
.collect()
}
#[test]
fn dashboard_subcommand_writes_under_outputs_root() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let status = run_zynk(&[
"status",
"--root",
&root,
"--session-id",
"session-a",
"--timestamp",
"2026-05-28T23:30:00+07:00",
"--phase",
"dashboard",
"--mode",
"validate",
"--artifact-ref",
"zynk-dashboard",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"status ready",
"--in-progress",
"dashboard render",
"--next-action",
"review dashboard",
]);
assert!(status.status.success(), "{}", stderr(&status));
let cwd = tempdir().unwrap();
let dashboard = run_zynk_in(
&[
"dashboard",
"--root",
&root,
"--timestamp",
"2026-05-28T23:31:00+07:00",
],
cwd.path(),
);
let dashboard_path = tmp.path().join("dashboard.md");
assert!(dashboard.status.success(), "{}", stderr(&dashboard));
assert!(stdout(&dashboard).contains(dashboard_path.to_str().unwrap()));
}
#[test]
fn dashboard_renders_active_sessions_and_operator_attention() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
for (session_id, status, asks) in [
("session-a", "working", "none"),
("session-b", "waiting-for-operator", "choose release scope"),
] {
let result = run_zynk(&[
"status",
"--root",
&root,
"--session-id",
session_id,
"--timestamp",
"2026-05-28T23:30:00+07:00",
"--phase",
"dashboard",
"--mode",
"validate",
"--artifact-ref",
"zynk-dashboard",
"--lead-agent",
"codex",
"--status",
status,
"--completed",
"status ready",
"--in-progress",
"dashboard render",
"--next-action",
"review dashboard",
"--asks-for-zevs",
asks,
]);
assert!(result.status.success(), "{}", stderr(&result));
}
let cwd = tempdir().unwrap();
let dashboard = run_zynk_in(
&[
"dashboard",
"--root",
&root,
"--timestamp",
"2026-05-28T23:31:00+07:00",
],
cwd.path(),
);
let content = fs::read_to_string(tmp.path().join("dashboard.md")).unwrap();
assert!(dashboard.status.success(), "{}", stderr(&dashboard));
assert!(content.contains("last_update: 2026-05-28T23:31:00+07:00"));
assert!(content.contains("| session-a | codex | working | dashboard | validate | zynk-dashboard | review dashboard | 2026-05-28T23:30:00+07:00 |"));
assert!(content.contains("| session-b | codex | waiting-for-operator | dashboard | validate | zynk-dashboard | review dashboard | 2026-05-28T23:30:00+07:00 |"));
assert!(
content.contains("- `session-b`: status=waiting-for-operator, ask=choose release scope")
);
assert!(!content.contains("- `session-a`: status=working"));
assert!(content.contains("[status](sessions/session-a/status.md)"));
assert!(content.contains("[summary](sessions/session-a/summary.md)"));
assert!(content.contains("[audit](sessions/session-a/audit.md)"));
}
#[test]
fn dashboard_handles_empty_outputs_root() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let cwd = tempdir().unwrap();
let output = run_zynk_in(
&[
"dashboard",
"--root",
&root,
"--timestamp",
"2026-05-28T23:31:00+07:00",
],
cwd.path(),
);
let content = fs::read_to_string(tmp.path().join("dashboard.md")).unwrap();
assert!(output.status.success(), "{}", stderr(&output));
assert!(content.contains("No active session status files found."));
assert!(content.contains("## Operator Attention\n\n- none"));
}
#[test]
fn markdown_dashboard_is_db_backed_when_db_exists() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join(".zynk/zynk.db");
run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
let seed = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"send-only",
"--audit-id",
"a0",
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
"m0",
"--type",
"status-update",
"--command-origin",
"agent",
"--payload",
"x",
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
]);
assert!(seed.status.success(), "{}", stderr(&seed));
let out = run_zynk_in(&["dashboard", "--root", root.to_str().unwrap()], tmp.path());
assert!(out.status.success(), "{}", stderr(&out));
let md = fs::read_to_string(root.join("dashboard.md")).unwrap();
assert!(
md.contains("send-only"),
"send-only DB session appears: {md}"
);
assert!(
md.to_lowercase().contains("snapshot"),
"labeled a point-in-time snapshot: {md}"
);
}
#[test]
fn markdown_dashboard_db_reflects_latest_status_event() {
let tmp = tempdir().unwrap();
let db = tmp.path().join(".zynk/zynk.db");
run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
let seed_root = tmp.path().join("seed-outputs");
let seed = run_zynk(&[
"status",
"--root",
seed_root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"drift",
"--timestamp",
"2026-05-29T01:00:00Z",
"--phase",
"tooling",
"--mode",
"review",
"--artifact-ref",
"tools/drift",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"seed",
"--in-progress",
"seed",
"--next-action",
"stale-next",
]);
assert!(seed.status.success(), "{}", stderr(&seed));
let import_root = tmp.path().join("import-outputs");
let newer = run_zynk(&[
"status",
"--root",
import_root.to_str().unwrap(),
"--no-db",
"--session-id",
"drift",
"--timestamp",
"2026-05-29T05:00:00Z",
"--phase",
"implementation",
"--mode",
"execute",
"--artifact-ref",
"tools/drift",
"--lead-agent",
"codex",
"--status",
"blocked",
"--completed",
"newer",
"--in-progress",
"newer",
"--next-action",
"newer-next",
]);
assert!(newer.status.success(), "{}", stderr(&newer));
let import = run_zynk(&[
"db",
"--db",
db.to_str().unwrap(),
"import",
"outputs",
"--root",
import_root.to_str().unwrap(),
"--timestamp",
"2026-05-29T05:01:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let render_root = tmp.path().join("render-outputs");
let out = run_zynk_in(
&["dashboard", "--root", render_root.to_str().unwrap()],
tmp.path(),
);
assert!(out.status.success(), "{}", stderr(&out));
let md = fs::read_to_string(render_root.join("dashboard.md")).unwrap();
assert!(
md.contains("blocked") && md.contains("newer-next"),
"snapshot must reflect the NEWER status_event (blocked/newer-next): {md}"
);
assert!(
!md.contains("working") && !md.contains("stale-next"),
"snapshot must not show the stale sessions-row value (working/stale-next): {md}"
);
}
#[test]
fn zynk_dashboard_snapshot_mode_reflects_latest_mode_decision() {
let tmp = tempdir().unwrap();
let db = tmp.path().join(".zynk/zynk.db");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let seed_root = tmp.path().join("seed-outputs");
let base = [
"--root",
seed_root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
];
let status = run_zynk(
&[
&[
"status",
"--timestamp",
"2026-05-31T00:00:00Z",
"--phase",
"plan",
"--mode",
"brainstorm",
"--artifact-ref",
"ref-1",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"i",
"--next-action",
"n",
],
base.as_slice(),
]
.concat(),
);
assert!(status.status.success(), "{}", stderr(&status));
let decide_mode = run_zynk(
&[
&[
"decide",
"mode",
"--timestamp",
"2026-05-31T01:00:00Z",
"--to",
"review",
],
base.as_slice(),
]
.concat(),
);
assert!(decide_mode.status.success(), "{}", stderr(&decide_mode));
let render_root = tmp.path().join("render-outputs");
let out = run_zynk_in(
&["dashboard", "--root", render_root.to_str().unwrap()],
tmp.path(),
);
assert!(out.status.success(), "{}", stderr(&out));
let md = fs::read_to_string(render_root.join("dashboard.md")).unwrap();
assert!(
md.contains("| review |"),
"snapshot mode column must be the newer mode-switch decision (review): {md}"
);
assert!(
!md.contains("| brainstorm |"),
"snapshot mode column must NOT be the older status mode (brainstorm): {md}"
);
}
#[test]
fn composes_dual_audience_request_review_message() {
let output = run_zynk(&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"request-review",
"--ref",
"outputs/decisions/010-brainstorm.md",
"--mode",
"review",
"--body",
"Please review the brainstorm ADR.",
]);
assert!(output.status.success(), "{}", stderr(&output));
assert_eq!(
stdout(&output),
"[from-codex via herdr] [herdr from=codex:w652dc9b3ded432-2 to=claude:w652dc9b3ded432-1 mid=abc123 type=request-review ref=outputs/decisions/010-brainstorm.md mode=review] BODY: Please review the brainstorm ADR.\n",
);
}
#[test]
fn rejects_request_review_without_ref() {
let output = run_zynk(&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"request-review",
"--body",
"Please review this.",
]);
assert_eq!(output.status.code(), Some(2));
assert!(stderr(&output).contains("request-review requires ref"));
}
#[test]
fn expands_ack_shorthand_body_template() {
let output = run_zynk(&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"def456",
"--shorthand",
"ack",
"--re",
"q4j8k1",
"--var",
"summary=Prototype review received.",
"--var",
"remaining_action=none",
]);
assert!(output.status.success(), "{}", stderr(&output));
assert_eq!(
stdout(&output),
"[from-codex via herdr] [herdr from=codex:w652dc9b3ded432-2 to=claude:w652dc9b3ded432-1 mid=def456 type=ack re=q4j8k1] BODY: ACK. Prototype review received. Remaining action: none.\n",
);
}
#[test]
fn default_profile_path_works_outside_package_root() {
let tmp = tempdir().unwrap();
let output = run_zynk_in(
&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"ack",
"--re",
"q4j8k1",
"--body",
"Profile loaded from embedded default.",
],
tmp.path(),
);
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains("[from-codex via herdr]"));
assert!(stdout(&output).contains("type=ack re=q4j8k1"));
}
#[test]
fn approve_accepts_ref_or_re_required_field() {
let ref_result = run_zynk(&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"app123",
"--type",
"approve",
"--ref",
"outputs/decisions/023-cross-pane-audit-trail.md",
"--body",
"APPROVE.",
]);
let re_result = run_zynk(&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"app124",
"--type",
"approve",
"--re",
"q4j8k1",
"--body",
"APPROVE.",
]);
let missing_result = run_zynk(&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"app125",
"--type",
"approve",
"--body",
"APPROVE.",
]);
assert!(ref_result.status.success(), "{}", stderr(&ref_result));
assert!(re_result.status.success(), "{}", stderr(&re_result));
assert_eq!(missing_result.status.code(), Some(2));
assert!(stderr(&missing_result).contains("approve requires one of: ref, re"));
}
#[test]
fn reads_body_file_and_flattens_newlines() {
let tmp = tempdir().unwrap();
let body_file = tmp.path().join("body.txt");
fs::write(&body_file, "Line one.\nLine two.").unwrap();
let output = run_zynk(&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"file01",
"--type",
"ack",
"--re",
"q4j8k1",
"--body-file",
body_file.to_str().unwrap(),
]);
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).ends_with("BODY: Line one. Line two.\n"));
}
#[test]
fn rejects_malformed_field_value() {
let output = run_zynk(&[
"compose",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"bad001",
"--type",
"ack",
"--re",
"q4j8k1",
"--field",
"malformed",
"--body",
"Bad field.",
]);
assert_eq!(output.status.code(), Some(2));
assert!(stderr(&output).contains("--field expects key=value"));
}
#[test]
fn dry_run_prints_composed_message_without_sending() {
let output = run_zynk(&[
"send",
"herdr",
"--pane",
"w652dc9b3ded432-1",
"--dry-run",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"ack",
"--re",
"q4j8k1",
"--body",
"Dry run only.",
]);
assert!(output.status.success(), "{}", stderr(&output));
assert_eq!(
stdout(&output),
"[from-codex via herdr] [herdr from=codex:w652dc9b3ded432-2 to=claude:w652dc9b3ded432-1 mid=abc123 type=ack re=q4j8k1] BODY: Dry run only.\n",
);
assert!(stderr(&output).contains("DRY RUN: message was not sent"));
}
#[test]
fn send_default_profile_path_works_outside_package_root() {
let tmp = tempdir().unwrap();
let output = run_zynk_in(
&[
"send",
"herdr",
"--pane",
"w652dc9b3ded432-1",
"--dry-run",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"ack",
"--re",
"q4j8k1",
"--body",
"Dry run outside root.",
],
tmp.path(),
);
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains("BODY: Dry run outside root."));
}
#[cfg(unix)]
#[test]
fn send_invokes_herdr_pane_run_with_composed_message() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempdir().unwrap();
let capture_file = tmp.path().join("argv.txt");
let fake_herdr = tmp.path().join("fake-herdr");
fs::write(
&fake_herdr,
"#!/bin/sh\nfor arg in \"$@\"; do printf '%s\\n' \"$arg\"; done > \"$CAPTURE_FILE\"\necho sent\n",
)
.unwrap();
let mut permissions = fs::metadata(&fake_herdr).unwrap().permissions();
permissions.set_mode(0o755);
fs::set_permissions(&fake_herdr, permissions).unwrap();
let mut command = Command::cargo_bin("zynk").unwrap();
let output = command
.env("CAPTURE_FILE", &capture_file)
.args([
"send",
"herdr",
"--pane",
"w652dc9b3ded432-1",
"--herdr-bin",
fake_herdr.to_str().unwrap(),
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"ack",
"--re",
"q4j8k1",
"--body",
"Send through fake herdr.",
])
.output()
.unwrap();
let argv = fs::read_to_string(capture_file).unwrap();
assert!(output.status.success(), "{}", stderr(&output));
assert_eq!(stdout(&output), "sent\n");
let lines = argv.lines().collect::<Vec<_>>();
assert_eq!(&lines[..3], ["pane", "run", "w652dc9b3ded432-1"]);
assert_eq!(
lines[3],
"[from-codex via herdr] [herdr from=codex:w652dc9b3ded432-2 to=claude:w652dc9b3ded432-1 mid=abc123 type=ack re=q4j8k1] BODY: Send through fake herdr."
);
}
#[test]
fn audited_send_writes_sender_audit_and_corpus_content() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w652dc9b3ded432-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"ack",
"--re",
"q4j8k1",
"--command-origin",
"agent",
"--body",
"Send through fake herdr.",
]);
assert!(out.status.success(), "{}", stderr(&out));
let audit_md = fs::read_to_string(root.join("sessions/s/audit.md")).unwrap();
assert!(
audit_md.contains("delivery_status=sent"),
"audit.md must record delivery_status=sent: {audit_md}"
);
assert!(
audit_md.contains("verified_by=helper-tool"),
"ADR 024: zynk dispatched transport, so verified_by=helper-tool: {audit_md}"
);
let conn = Connection::open(&db).unwrap();
let (status, verified, excerpt): (String, String, Option<String>) = conn
.query_row(
"SELECT latest_delivery_status, latest_verified_by, payload_excerpt
FROM messages WHERE session_id='s' AND mid='abc123'",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
)
.unwrap();
assert_eq!(status, "sent");
assert_eq!(verified, "helper-tool");
assert_eq!(
excerpt.as_deref(),
Some("[from-codex via herdr] [herdr from=codex:w652dc9b3ded432-2 to=claude:w652dc9b3ded432-1 mid=abc123 type=ack re=q4j8k1] BODY: Send through fake herdr."),
"corpus must store the exact rendered wire message (C4, default full)"
);
}
#[test]
fn audited_send_no_audit_writes_nothing_but_still_sends() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--no-audit",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"hi",
]);
assert!(out.status.success(), "{}", stderr(&out));
assert_eq!(
stdout(&out),
"sent\n",
"the message must still be sent under --no-audit"
);
assert!(
!root.join("sessions/s/audit.md").exists(),
"--no-audit must write no audit file"
);
assert!(!db.exists(), "--no-audit must write no DB record");
assert!(
stderr(&out).contains("--db has no effect"),
"--db + --no-audit must warn the DB had no effect (hard part 6): {}",
stderr(&out)
);
}
#[test]
fn audited_send_rejects_to_address_mismatched_with_pane() {
let tmp = tempdir().unwrap();
let marker = tmp.path().join("herdr-ran");
let herdr = fake_herdr(
tmp.path(),
&format!("touch '{}'\necho sent\n", marker.display()),
);
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-9",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"hi",
]);
assert_eq!(
out.status.code(),
Some(2),
"C2 mismatch is a pre-send usage error; stderr:\n{}",
stderr(&out)
);
assert!(
stderr(&out).contains("--pane"),
"error must explain the --to/--pane mismatch: {}",
stderr(&out)
);
assert!(
!marker.exists(),
"C2 must reject BEFORE the irreversible send (nothing sent)"
);
}
#[test]
fn audited_send_freeform_due_sends_with_null_audit_due() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--due",
"after-drafting-adr-029",
"--body",
"hi",
]);
assert!(
out.status.success(),
"a free-form header --due must NEVER be rejected (C1); stderr:\n{}",
stderr(&out)
);
let conn = Connection::open(&db).unwrap();
let due: Option<String> = conn
.query_row(
"SELECT due FROM audit_records WHERE session_id='s'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
due, None,
"a non-RFC3339 header due leaves audit_records.due NULL (C1)"
);
let excerpt: Option<String> = conn
.query_row(
"SELECT payload_excerpt FROM messages WHERE session_id='s' AND mid='m1'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
excerpt.unwrap().contains("due=after-drafting-adr-029"),
"the free-form due must ride in the rendered payload"
);
}
#[test]
fn audited_send_rfc3339_due_populates_audit_due() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--due",
"2026-06-01T19:00:00+07:00",
"--body",
"hi",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(&db).unwrap();
let due: Option<String> = conn
.query_row(
"SELECT due FROM audit_records WHERE session_id='s'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
due.as_deref(),
Some("2026-06-01T12:00:00Z"),
"an RFC3339 header due canonicalizes into audit_records.due"
);
}
#[test]
fn audited_send_transport_failure_writes_no_audit() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo boom >&2\nexit 1\n");
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"hi",
]);
assert!(!out.status.success(), "transport failure must exit nonzero");
assert!(
!root.join("sessions/s/audit.md").exists(),
"no audit when the send failed (ADR 029 hard part 2)"
);
assert!(!db.exists(), "no corpus record when the send failed");
}
#[test]
fn send_retain_writes_vault_and_reveals() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--retain-custody",
"--payload-redaction-policy",
"hash-only",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--command-origin",
"agent",
"--body",
"retained secret body",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(&db).unwrap();
let audit_id: String = conn
.query_row(
"SELECT audit_id FROM audit_records WHERE session_id='s1' AND mid='m1'",
[],
|row| row.get(0),
)
.expect("the audited send must project an audit_records row");
let ciphertext: Vec<u8> = conn
.query_row(
"SELECT ciphertext FROM custody_vault WHERE audit_id=?1",
params![audit_id],
|row| row.get(0),
)
.expect("custody_vault must have a row for the retained sent record");
assert!(
!ciphertext.is_empty(),
"retained ciphertext must not be empty"
);
let reveal = run_zynk(&[
"reveal",
&audit_id,
"--db",
db.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
]);
assert!(
reveal.status.success(),
"reveal of the retained sent record must succeed: {}",
stderr(&reveal)
);
assert!(
stdout(&reveal).contains("BODY: retained secret body"),
"reveal must round-trip the exact sent wire message: {:?}",
stdout(&reveal)
);
}
#[test]
fn send_retain_failure_is_loud_after_send() {
let tmp = tempdir().unwrap();
let marker = tmp.path().join("herdr-ran");
let herdr = fake_herdr(
tmp.path(),
&format!("touch '{}'\necho sent\n", marker.display()),
);
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let keydir = tmp.path().join("keydir");
fs::create_dir(&keydir).unwrap();
fs::set_permissions(&keydir, fs::Permissions::from_mode(0o700)).unwrap();
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--retain-custody",
"--custody-key-file",
keydir.to_str().unwrap(),
"--payload-redaction-policy",
"hash-only",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--command-origin",
"agent",
"--body",
"retained secret body",
]);
assert_ne!(
out.status.code(),
Some(0),
"a retain failure after a successful send must exit nonzero (loud): {}",
stderr(&out)
);
assert!(
stderr(&out).contains("record written but custody NOT retained"),
"the failure must be loud + explicit the record is durable but custody not retained: {}",
stderr(&out)
);
assert!(
marker.exists(),
"the message must already be sent (transport runs before the retain): {}",
stderr(&out)
);
let conn = Connection::open(&db).unwrap();
let (audit_id, delivery_status): (String, String) = conn
.query_row(
"SELECT audit_id, delivery_status FROM audit_records WHERE session_id='s1' AND mid='m1'",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.expect("the sender audit row must be durable even when custody fails");
assert_eq!(
delivery_status, "sent",
"the durable sender audit row records the send (delivery_status=sent)"
);
let vault_rows: i64 = conn
.query_row(
"SELECT count(*) FROM custody_vault WHERE audit_id=?1",
[&audit_id],
|r| r.get(0),
)
.unwrap();
assert_eq!(
vault_rows, 0,
"a failed retention must leave NO custody_vault row (no silent no-retain)"
);
}
fn seed_revealable_record(
root: &Path,
db: &Path,
herdr: &Path,
session: &str,
mid: &str,
body: &str,
) -> String {
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
session,
"--retain-custody",
"--payload-redaction-policy",
"hash-only",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
mid,
"--type",
"ack",
"--re",
"r1",
"--command-origin",
"agent",
"--body",
body,
]);
assert!(
out.status.success(),
"seed_revealable_record: {}",
stderr(&out)
);
let conn = Connection::open(db).unwrap();
conn.query_row(
"SELECT audit_id FROM audit_records WHERE session_id=?1 AND mid=?2",
params![session, mid],
|row| row.get(0),
)
.expect("the retained audited send must project an audit_records row")
}
fn seed_revealable_record_with_id(
root: &Path,
db: &Path,
session: &str,
audit_id: &str,
mid: &str,
secret: &str,
) {
let out = run_zynk(&[
"audit",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
session,
&format!("--audit-id={audit_id}"),
"--timestamp",
"2026-05-29T01:00:00Z",
"--source-agent",
"claude",
"--source-address",
"w1-2",
"--target-agent",
"codex",
"--target-address",
"w1-1",
"--transport",
"herdr",
"--workspace-id",
"w1",
"--mid",
mid,
"--type",
"status-update",
"--command-origin",
"agent",
"--payload",
secret,
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"claude",
"--verified-by",
"helper-tool",
"--retain-custody",
]);
assert!(
out.status.success(),
"seed_revealable_record_with_id({audit_id}): {}",
stderr(&out)
);
}
fn reveal_proof_count(db: &Path) -> i64 {
let conn = Connection::open(db).unwrap();
conn.query_row(
"SELECT COUNT(*) FROM audit_records WHERE record_type='reveal'",
[],
|r| r.get(0),
)
.unwrap()
}
#[test]
fn reveal_requires_allow_writes() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let resp = fetch_db_dashboard_once_with_method(&db, "POST", "/reveal");
assert!(
resp.contains("HTTP/1.1 405"),
"POST /reveal disabled without --allow-writes: {resp}"
);
}
#[test]
fn reveal_requires_csrf_and_exact_host() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let audit_id = seed_revealable_record(&root, &db, &herdr, "s1", "m1", "top secret plaintext");
let before = reveal_proof_count(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = format!("session=s1&audit_id={audit_id}");
let bad_token = post_dashboard_raw(port, &format!(
"POST /reveal HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nOrigin: http://127.0.0.1:{port}\r\nX-Zynk-CSRF: wrong\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{form}",
form.len()));
let no_origin = post_dashboard_raw(port, &format!(
"POST /reveal HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nX-Zynk-CSRF: {token}\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{form}",
form.len()));
let bad_host = post_dashboard_raw(port, &format!(
"POST /reveal HTTP/1.1\r\nHost: evil.test\r\nOrigin: http://127.0.0.1:{port}\r\nX-Zynk-CSRF: {token}\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{form}",
form.len()));
child.kill().ok();
child.wait().ok();
assert!(
bad_token.contains("HTTP/1.1 403"),
"wrong CSRF rejected: {bad_token}"
);
assert!(
no_origin.contains("HTTP/1.1 403"),
"missing Origin rejected: {no_origin}"
);
assert!(
bad_host.contains("HTTP/1.1 403"),
"wrong Host rejected: {bad_host}"
);
assert!(
!bad_token.contains("top secret plaintext"),
"no plaintext in a rejected reveal: {bad_token}"
);
assert_eq!(
reveal_proof_count(&db),
before,
"an unauthorized reveal must spawn nothing — no new reveal proof"
);
}
#[test]
fn reveal_cross_session_rejected() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db); let s2_audit_id =
seed_revealable_record(&root, &db, &herdr, "s2", "m2", "cross session secret");
let before = reveal_proof_count(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = format!("session=s1&audit_id={s2_audit_id}");
let resp = post_dashboard(port, "/reveal", &token, &form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 404"),
"cross-session reveal is 404: {resp}"
);
assert!(
resp.contains("not revealable"),
"the rejection says not revealable: {resp}"
);
assert!(
!resp.contains("cross session secret"),
"NO plaintext for a cross-session reveal: {resp}"
);
assert_eq!(
reveal_proof_count(&db),
before,
"a cross-session reveal must write NO reveal proof (rejected pre-spawn)"
);
}
#[test]
fn reveal_non_revealable_rejected() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let before = reveal_proof_count(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&audit_id=seed0";
let resp = post_dashboard(port, "/reveal", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 404"),
"non-revealable reveal is 404: {resp}"
);
assert!(
resp.contains("not revealable"),
"the rejection says not revealable: {resp}"
);
assert_eq!(
reveal_proof_count(&db),
before,
"a non-revealable reveal must write NO reveal proof (rejected pre-spawn)"
);
}
#[test]
fn reveal_nonzero_child_surfaces_error_not_plaintext() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let audit_id = seed_revealable_record(&root, &db, &herdr, "s1", "m1", "do-not-leak plaintext");
let before = reveal_proof_count(&db);
let bad_root = tmp.path().join("not-a-dir");
fs::write(&bad_root, b"x").unwrap();
let (token, port, mut child) = start_writable_serve(&db, &bad_root, &herdr);
let form = format!("session=s1&audit_id={audit_id}");
let resp = post_dashboard(port, "/reveal", &token, &form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 502") || resp.contains("HTTP/1.1 500"),
"a nonzero reveal child surfaces an error status: {resp}"
);
assert!(
!resp.contains("do-not-leak plaintext"),
"a FAILED reveal must NOT disclose the plaintext: {resp}"
);
assert_eq!(
reveal_proof_count(&db),
before,
"a failed reveal writes no reveal proof (proof-before-disclosure)"
);
}
#[test]
fn reveal_success_renders_inline_no_store_and_writes_proof() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let audit_id = seed_revealable_record(&root, &db, &herdr, "s1", "m1", "the revealed secret");
let before = reveal_proof_count(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = format!("session=s1&audit_id={audit_id}");
let resp = post_dashboard(port, "/reveal", &token, &form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 200"),
"a valid reveal is 200: {resp}"
);
assert!(
resp.contains("the revealed secret"),
"the plaintext renders inline in the 200 body: {resp}"
);
let lower = resp.to_lowercase();
assert!(
lower.contains("cache-control: no-store"),
"the reveal response carries Cache-Control: no-store: {resp}"
);
assert!(
lower.contains("pragma: no-cache"),
"the reveal response carries Pragma: no-cache: {resp}"
);
assert!(
lower.contains("x-content-type-options: nosniff"),
"the reveal response carries X-Content-Type-Options: nosniff: {resp}"
);
assert!(
!resp.contains("HTTP/1.1 303"),
"reveal must never 303-PRG to the redacted read view: {resp}"
);
assert_eq!(
reveal_proof_count(&db),
before + 1,
"a successful reveal writes exactly one new operator-verified reveal proof"
);
}
#[test]
fn reveal_dash_prefixed_audit_id_reveals_not_help() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let secret = "DASH-PREFIX-REVEAL-SECRET-7f3a";
seed_revealable_record_with_id(&root, &db, "s1", "-h", "m-dash", secret);
{
let conn = Connection::open(&db).unwrap();
let (stored_id, policy): (String, String) = conn
.query_row(
"SELECT a.audit_id, a.payload_redaction_policy
FROM audit_records a
JOIN custody_vault v ON v.audit_id = a.audit_id
WHERE a.session_id = ?1 AND a.mid = ?2",
params!["s1", "m-dash"],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.expect("the seed must store audit_id='-h' with a custody_vault row");
assert_eq!(
stored_id, "-h",
"the seeded audit_id is the literal dash flag"
);
assert_eq!(
policy, "hash-only",
"a non-full policy keeps the record revealable"
);
}
let before = reveal_proof_count(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&audit_id=-h";
let resp = post_dashboard(port, "/reveal", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 200"),
"a valid dash-prefixed reveal is 200: {resp}"
);
assert!(
resp.contains(secret),
"the dash-prefixed audit_id reveals the real secret inline: {resp}"
);
assert!(
!resp.contains("Usage:"),
"the body is the reveal, not clap help (no `Usage:`): {resp}"
);
assert!(
!resp.contains("proof-before-disclosure un-redaction"),
"the body is the reveal, not the reveal command's about-string: {resp}"
);
assert_eq!(
reveal_proof_count(&db),
before + 1,
"a dash-prefixed reveal still writes exactly one new reveal proof"
);
}
#[test]
fn reveal_route_rejects_full_redaction_record() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let full_id = seed_retained_record_with_policy(
&root,
&db,
&herdr,
"s1",
"mfull",
"plainly visible secret",
"full",
);
{
let conn = Connection::open(&db).unwrap();
let policy: String = conn
.query_row(
"SELECT a.payload_redaction_policy
FROM audit_records a
JOIN custody_vault v ON v.audit_id = a.audit_id
WHERE a.audit_id = ?1",
params![full_id],
|row| row.get(0),
)
.expect("a full-redaction --retain-custody send must still write a custody_vault row");
assert_eq!(
policy, "full",
"the retained record's policy must be full (the case the route must now reject)"
);
}
let before = reveal_proof_count(&db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = format!("session=s1&audit_id={full_id}");
let resp = post_dashboard(port, "/reveal", &token, &form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 404"),
"a full-redaction record is not revealable -> 404: {resp}"
);
assert!(
resp.contains("not revealable"),
"the rejection says not revealable: {resp}"
);
assert!(
!resp.contains("plainly visible secret"),
"no plaintext disclosed for a full-redaction reveal: {resp}"
);
assert_eq!(
reveal_proof_count(&db),
before,
"a full-redaction reveal writes NO new reveal proof (rejected pre-spawn)"
);
}
#[test]
fn reveal_refresh_writes_second_proof() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let audit_id = seed_revealable_record(&root, &db, &herdr, "s1", "m1", "repeatable secret");
assert_eq!(
reveal_proof_count(&db),
0,
"no reveal proofs before any reveal"
);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = format!("session=s1&audit_id={audit_id}");
let first = post_dashboard(port, "/reveal", &token, &form);
assert!(
first.contains("HTTP/1.1 200"),
"the first reveal is 200: {first}"
);
assert_eq!(
reveal_proof_count(&db),
1,
"the first reveal writes exactly one operator-verified reveal proof"
);
let second = post_dashboard(port, "/reveal", &token, &form);
child.kill().ok();
child.wait().ok();
assert!(
second.contains("HTTP/1.1 200"),
"the re-POST reveal is also 200 (repeatable): {second}"
);
assert_eq!(
reveal_proof_count(&db),
2,
"the re-POST writes a SECOND audited reveal proof (repeatable-reveal, never silent)"
);
}
#[test]
fn reveal_does_not_unredact_read_views() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let sentinel = "sentinel-plaintext-d6-zzqx";
let audit_id = seed_revealable_record(&root, &db, &herdr, "s1", "m1", sentinel);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = format!("session=s1&audit_id={audit_id}");
let reveal = post_dashboard(port, "/reveal", &token, &form);
assert!(
reveal.contains("HTTP/1.1 200"),
"the reveal is 200: {reveal}"
);
assert!(
reveal.contains(sentinel),
"the plaintext sentinel renders inline in the /reveal 200 body: {reveal}"
);
let feed = get_dashboard_page(port, "/?session=s1");
let audit = get_dashboard_page(port, "/audit?session=s1");
child.kill().ok();
child.wait().ok();
assert!(
feed.contains("HTTP/1.1 200"),
"the read feed still renders after a reveal: {feed}"
);
assert!(
audit.contains("HTTP/1.1 200"),
"the /audit view still renders after a reveal: {audit}"
);
assert!(
!feed.contains(sentinel),
"ADR 029 / D6: the plaintext sentinel must be ABSENT from the read feed: {feed}"
);
assert!(
!audit.contains(sentinel),
"ADR 029 / D6: the plaintext sentinel must be ABSENT from the /audit view: {audit}"
);
}
fn seed_retained_record_with_policy(
root: &Path,
db: &Path,
herdr: &Path,
session: &str,
mid: &str,
body: &str,
policy: &str,
) -> String {
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
session,
"--retain-custody",
"--payload-redaction-policy",
policy,
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
mid,
"--type",
"ack",
"--re",
"r1",
"--command-origin",
"agent",
"--body",
body,
]);
assert!(
out.status.success(),
"seed_retained_record_with_policy({policy}): {}",
stderr(&out)
);
let conn = Connection::open(db).unwrap();
conn.query_row(
"SELECT audit_id FROM audit_records WHERE session_id=?1 AND mid=?2",
params![session, mid],
|row| row.get(0),
)
.expect("the retained audited send must project an audit_records row")
}
fn reveal_form_count(html: &str) -> usize {
html.matches("action=\"/reveal\"").count()
}
#[test]
fn reveal_button_only_for_revealable_under_allow_writes() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db); let revealable_id = seed_retained_record_with_policy(
&root,
&db,
&herdr,
"s1",
"mhash",
"hidden secret",
"hash-only",
);
let full_id = seed_retained_record_with_policy(
&root,
&db,
&herdr,
"s1",
"mfull",
"plainly visible",
"full",
);
let status = run_zynk(&[
"status",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--timestamp",
"2026-05-29T02:00:00Z",
"--phase",
"tooling",
"--mode",
"review",
"--artifact-ref",
"outputs/sessions/s1/status.md",
"--lead-agent",
"claude",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"p",
"--next-action",
"n",
]);
assert!(status.status.success(), "{}", stderr(&status));
let html = fetch_dashboard_with_serve_args(
&db,
"/?session=s1",
&["--allow-writes", "--root", root.to_str().unwrap()],
);
assert!(
html.contains(&format!("name=\"audit_id\" value=\"{revealable_id}\"")),
"a reveal control must render for the hash-only retained record ({revealable_id}): {html}"
);
assert!(
!html.contains(&format!("name=\"audit_id\" value=\"{full_id}\"")),
"NO reveal control for a full-redaction retained record ({full_id}): {html}"
);
assert_eq!(
reveal_form_count(&html),
1,
"exactly one reveal control (the hash-only retained record); none for full/work/status: {html}"
);
}
#[test]
fn reveal_button_absent_without_allow_writes() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let _revealable_id = seed_retained_record_with_policy(
&root,
&db,
&herdr,
"s1",
"mhash",
"hidden secret",
"hash-only",
);
let html = fetch_db_dashboard_once(&db, "/?session=s1");
assert_eq!(
reveal_form_count(&html),
0,
"a read-only serve renders NO reveal control anywhere: {html}"
);
}
#[test]
fn send_no_audit_with_retain_is_usage_error() {
let tmp = tempdir().unwrap();
let marker = tmp.path().join("herdr-ran");
let herdr = fake_herdr(
tmp.path(),
&format!("touch '{}'\necho sent\n", marker.display()),
);
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--session-id",
"s1",
"--no-audit",
"--retain-custody",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"hi",
]);
assert_eq!(
out.status.code(),
Some(2),
"--no-audit + --retain-custody must be a usage error (exit 2): {}",
stderr(&out)
);
assert!(
stderr(&out).contains("--retain-custody"),
"the usage error must name --retain-custody: {}",
stderr(&out)
);
assert!(
!marker.exists(),
"a retain-without-audit usage error must reject BEFORE the send (nothing sent)"
);
}
#[test]
fn send_no_db_with_retain_is_usage_error() {
let tmp = tempdir().unwrap();
let marker = tmp.path().join("herdr-ran");
let herdr = fake_herdr(
tmp.path(),
&format!("touch '{}'\necho sent\n", marker.display()),
);
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--no-db",
"--session-id",
"s1",
"--retain-custody",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"hi",
]);
assert_eq!(
out.status.code(),
Some(2),
"--no-db + --retain-custody must be a usage error (exit 2): {}",
stderr(&out)
);
assert!(
stderr(&out).contains("--retain-custody") && stderr(&out).contains("--no-db"),
"the usage error must name both flags: {}",
stderr(&out)
);
assert!(
!marker.exists(),
"C5(2) must reject BEFORE transport (no message sent)"
);
}
#[test]
fn send_dry_run_with_retain_does_not_retain() {
let tmp = tempdir().unwrap();
let marker = tmp.path().join("herdr-ran");
let herdr = fake_herdr(
tmp.path(),
&format!("touch '{}'\necho sent\n", marker.display()),
);
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--retain-custody",
"--dry-run",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"retained secret body",
]);
assert!(out.status.success(), "{}", stderr(&out));
assert!(
stdout(&out).contains("BODY: retained secret body"),
"dry-run must still compose + print the message: {}",
stdout(&out)
);
assert!(
!marker.exists(),
"dry-run must NOT send (nothing was transported)"
);
assert!(
!db.exists(),
"dry-run must write no DB record and no custody_vault row"
);
}
#[test]
fn audited_send_integrity_gap_is_loud_and_recoverable() {
let tmp = tempdir().unwrap();
let cwd = tmp.path().join("cwd");
fs::create_dir_all(&cwd).unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
fs::create_dir_all(&root).unwrap();
fs::write(root.join("sessions"), "x").unwrap();
let out = Command::cargo_bin("zynk")
.unwrap()
.current_dir(&cwd)
.args([
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--no-db",
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"critical message",
])
.output()
.unwrap();
assert!(!out.status.success(), "an integrity gap must exit nonzero");
let err = stderr(&out);
assert!(err.contains("INTEGRITY GAP"), "the gap must be loud: {err}");
assert!(
err.contains("zynk audit"),
"it must reprint the reconcile command: {err}"
);
assert!(
err.contains("--root"),
"the reconcile command must carry --root (C3): {err}"
);
let recovery = cwd.join("zynk-recovery-s-m1.txt");
assert!(
recovery.exists(),
"the rendered message must be preserved to a recovery file"
);
let saved = fs::read_to_string(&recovery).unwrap();
assert!(
saved.contains("BODY: critical message"),
"the recovery file holds the exact wire message: {saved}"
);
}
#[test]
fn audited_send_integrity_gap_recovery_command_is_shell_safe() {
let tmp = tempdir().unwrap();
let base = tmp.path().join("with space");
let cwd = base.join("cwd");
let root = base.join("outputs");
fs::create_dir_all(&cwd).unwrap();
fs::create_dir_all(&root).unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
fs::write(root.join("sessions"), "x").unwrap();
let out = Command::cargo_bin("zynk")
.unwrap()
.current_dir(&cwd)
.args([
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--no-db",
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"critical message",
])
.output()
.unwrap();
assert!(!out.status.success(), "the integrity gap must be nonzero");
let reconcile = stderr(&out)
.lines()
.map(str::trim)
.find(|line| line.starts_with("zynk audit"))
.expect("a reconcile `zynk audit ...` command must be printed")
.to_string();
fs::remove_file(root.join("sessions")).unwrap();
let bin = assert_cmd::cargo::cargo_bin("zynk");
let runnable = reconcile.replacen("zynk ", &format!("'{}' ", bin.display()), 1);
let status = StdCommand::new("sh")
.arg("-c")
.arg(&runnable)
.current_dir(&cwd)
.status()
.unwrap();
assert!(
status.success(),
"the printed reconcile command must be copy/paste runnable: {runnable}"
);
let audit_md = fs::read_to_string(root.join("sessions/s/audit.md")).unwrap();
assert!(
audit_md.contains("BODY: critical message"),
"reconcile must recover the exact escaped message: {audit_md}"
);
}
#[test]
fn audited_send_integrity_gap_recovery_preserves_no_db() {
let tmp = tempdir().unwrap();
let cwd = tmp.path().join("cwd");
fs::create_dir_all(&cwd).unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
fs::create_dir_all(&root).unwrap();
fs::write(root.join("sessions"), "x").unwrap();
let out = Command::cargo_bin("zynk")
.unwrap()
.current_dir(&cwd)
.args([
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--no-db",
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"critical message",
])
.output()
.unwrap();
assert!(!out.status.success(), "integrity gap must be nonzero");
let reconcile = stderr(&out)
.lines()
.map(str::trim)
.find(|line| line.starts_with("zynk audit"))
.expect("a reconcile command must be printed")
.to_string();
fs::remove_file(root.join("sessions")).unwrap();
let bin = assert_cmd::cargo::cargo_bin("zynk");
let runnable = reconcile.replacen("zynk ", &format!("'{}' ", bin.display()), 1);
let status = StdCommand::new("sh")
.arg("-c")
.arg(&runnable)
.current_dir(&cwd)
.status()
.unwrap();
assert!(status.success(), "reconcile must run: {runnable}");
assert!(
root.join("sessions/s/audit.md").exists(),
"reconcile wrote the audit file"
);
assert!(
!cwd.join(".zynk").exists(),
"a --no-db send's reconcile must stay file-only — no default DB (P3)"
);
}
#[test]
fn audited_send_integrity_gap_recovery_preserves_explicit_db() {
let tmp = tempdir().unwrap();
let cwd = tmp.path().join("cwd");
fs::create_dir_all(&cwd).unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
fs::create_dir_all(&root).unwrap();
fs::write(root.join("sessions"), "x").unwrap(); let custom_db = tmp.path().join("custom.db");
let out = Command::cargo_bin("zynk")
.unwrap()
.current_dir(&cwd)
.args([
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
custom_db.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"critical message",
])
.output()
.unwrap();
assert!(!out.status.success(), "integrity gap must be nonzero");
let reconcile = stderr(&out)
.lines()
.map(str::trim)
.find(|line| line.starts_with("zynk audit"))
.expect("a reconcile command must be printed")
.to_string();
fs::remove_file(root.join("sessions")).unwrap();
let bin = assert_cmd::cargo::cargo_bin("zynk");
let runnable = reconcile.replacen("zynk ", &format!("'{}' ", bin.display()), 1);
let status = StdCommand::new("sh")
.arg("-c")
.arg(&runnable)
.current_dir(&cwd)
.status()
.unwrap();
assert!(status.success(), "reconcile must run: {runnable}");
let n: i64 = Connection::open(&custom_db)
.unwrap()
.query_row(
"SELECT COUNT(*) FROM messages WHERE session_id='s' AND mid='m1'",
[],
|row| row.get(0),
)
.unwrap_or(0);
assert_eq!(
n, 1,
"explicit --db recovery must project into the named DB (P3)"
);
assert!(
!cwd.join(".zynk").exists(),
"explicit --db recovery must not fall back to the default DB (P3)"
);
}
#[test]
fn audited_send_no_db_writes_file_only() {
let tmp = tempdir().unwrap();
let cwd = tmp.path().join("cwd");
fs::create_dir_all(&cwd).unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let out = Command::cargo_bin("zynk")
.unwrap()
.current_dir(&cwd)
.args([
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--no-db",
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"hi",
])
.output()
.unwrap();
assert!(out.status.success(), "{}", stderr(&out));
assert!(
root.join("sessions/s/audit.md").exists(),
"--no-db still writes the audit file"
);
assert!(
!cwd.join(".zynk").exists(),
"--no-db must not auto-create the default DB"
);
}
#[test]
fn audited_send_explicit_db_projection_failure_hard_fails_after_file() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let blocker = tmp.path().join("blocker");
fs::write(&blocker, "x").unwrap();
let db = blocker.join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"hi",
]);
assert!(
!out.status.success(),
"an explicit --db projection failure must hard-fail; stderr:\n{}",
stderr(&out)
);
assert!(
root.join("sessions/s/audit.md").exists(),
"the file must be written before the explicit-db hard-fail (file-first)"
);
}
#[test]
fn audited_send_sensitive_category_forces_hash_only() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--payload-redaction-policy",
"full",
"--sensitive-category",
"secrets",
"--body",
"TOP SECRET body",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(&db).unwrap();
let (policy, excerpt): (String, Option<String>) = conn
.query_row(
"SELECT payload_redaction_policy, payload_excerpt
FROM messages WHERE session_id='s' AND mid='m1'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap();
assert_eq!(
policy, "hash-only",
"a force_hash_only category must override --payload-redaction-policy full"
);
assert_eq!(
excerpt, None,
"forced hash-only must not leak content into the corpus"
);
let audit_md = fs::read_to_string(root.join("sessions/s/audit.md")).unwrap();
assert!(
!audit_md.contains("TOP SECRET body"),
"forced hash-only must not write the body to the file: {audit_md}"
);
}
#[test]
fn audited_send_writes_exactly_one_record() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let out = run_zynk(&[
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"hi",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(&db).unwrap();
let audits: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE session_id='s'",
[],
|row| row.get(0),
)
.unwrap();
let msgs: i64 = conn
.query_row(
"SELECT COUNT(*) FROM messages WHERE session_id='s'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
audits, 1,
"one audited send writes exactly one audit record"
);
assert_eq!(
msgs, 1,
"one audited send writes exactly one corpus message"
);
let audit_md = fs::read_to_string(root.join("sessions/s/audit.md")).unwrap();
assert_eq!(
audit_md.matches("\naudit_id=").count(),
1,
"exactly one record block in the file"
);
}
#[test]
fn audited_send_default_db_auto_creates_and_projects() {
let tmp = tempdir().unwrap();
let cwd = tmp.path().join("cwd");
fs::create_dir_all(&cwd).unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let root = tmp.path().join("outputs");
let out = Command::cargo_bin("zynk")
.unwrap()
.current_dir(&cwd)
.args([
"send",
"herdr",
"--pane",
"w1-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--root",
root.to_str().unwrap(),
"--session-id",
"s",
"--from",
"codex:w1-2",
"--to",
"claude:w1-1",
"--mid",
"m1",
"--type",
"ack",
"--re",
"r1",
"--body",
"hi",
])
.output()
.unwrap();
assert!(out.status.success(), "{}", stderr(&out));
let db = cwd.join(".zynk/zynk.db");
assert!(
db.exists(),
"an audited send with no --db auto-creates the default .zynk/zynk.db (ADR 028)"
);
let conn = Connection::open(&db).unwrap();
let status: String = conn
.query_row(
"SELECT latest_delivery_status FROM messages WHERE session_id='s' AND mid='m1'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(status, "sent");
assert!(
cwd.join(".zynk/.gitignore").exists(),
"the default DB dir self-ignores (ADR 028)"
);
}
#[test]
fn compose_validation_failure_prevents_send() {
let output = run_zynk(&[
"send",
"herdr",
"--pane",
"w652dc9b3ded432-1",
"--dry-run",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"request-review",
"--body",
"Missing ref.",
]);
assert_eq!(output.status.code(), Some(2));
assert!(stderr(&output).contains("request-review requires ref"));
}
#[test]
fn missing_herdr_binary_returns_clean_error() {
let output = run_zynk(&[
"send",
"herdr",
"--pane",
"w652dc9b3ded432-1",
"--herdr-bin",
"/tmp/zynk-missing-herdr",
"--from",
"codex:w652dc9b3ded432-2",
"--to",
"claude:w652dc9b3ded432-1",
"--mid",
"abc123",
"--type",
"ack",
"--re",
"q4j8k1",
"--body",
"Missing herdr binary.",
]);
assert_eq!(output.status.code(), Some(127));
assert!(stderr(&output).contains("herdr CLI not found at"));
assert!(!stderr(&output).contains("panicked"));
}
#[test]
fn help_clarifies_status_root_is_outputs_root() {
let output = run_zynk(&["status", "--help"]);
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains("runtime outputs root; pass <project>/outputs"));
}
#[test]
fn creates_status_file_with_adr022_fields() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"status",
"--root",
&root,
"--session-id",
"session-a",
"--timestamp",
"2026-05-28T21:00:00+07:00",
"--phase",
"tooling",
"--mode",
"validate",
"--artifact-ref",
"tools/status-update",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"message-compose and transport-send-herdr committed",
"--in-progress",
"status-update helper",
"--next-action",
"request Claude review",
"--blockers",
"none",
"--asks-for-zevs",
"none",
"--risk",
"format drift",
"--expected-wait",
"2m",
"--event",
"Initialized status helper work.",
]);
let status_path = tmp.path().join("sessions/session-a/status.md");
let content = fs::read_to_string(status_path).unwrap();
assert!(output.status.success(), "{}", stderr(&output));
assert!(content.contains("session_id: session-a"));
assert!(content.contains("last_update: 2026-05-28T21:00:00+07:00"));
assert!(content.contains("lead_agent: codex"));
assert!(content.contains("status: working"));
assert!(content.contains("- phase: tooling"));
assert!(content.contains("- mode: validate"));
assert!(content.contains("- artifact_ref: tools/status-update"));
assert!(content.contains(
"- completed_since_last_update: message-compose and transport-send-herdr committed"
));
assert!(content.contains("- in_progress: status-update helper"));
assert!(content.contains("- next_action: request Claude review"));
assert!(content.contains("- blockers: none"));
assert!(content.contains("- asks_for_Zevs: none"));
assert!(content.contains("- risk_or_residual_uncertainty: format drift"));
assert!(content.contains("- expected_wait: 2m"));
assert!(content.contains("1. 2026-05-28T21:00:00+07:00 - Initialized status helper work."));
}
#[test]
fn preserves_latest_rolling_events_with_limit() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
for index in 0..12 {
let output = run_zynk(&[
"status",
"--root",
&root,
"--session-id",
"session-a",
"--timestamp",
&format!("2026-05-28T21:{index:02}:00+07:00"),
"--phase",
"tooling",
"--mode",
"validate",
"--artifact-ref",
"tools/status-update",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
&format!("step {index}"),
"--in-progress",
"status-update helper",
"--next-action",
"continue",
"--event",
&format!("event {index}"),
"--max-events",
"3",
]);
assert!(output.status.success(), "{}", stderr(&output));
}
let content = fs::read_to_string(tmp.path().join("sessions/session-a/status.md")).unwrap();
assert!(content.contains("1. 2026-05-28T21:11:00+07:00 - event 11"));
assert!(content.contains("2. 2026-05-28T21:10:00+07:00 - event 10"));
assert!(content.contains("3. 2026-05-28T21:09:00+07:00 - event 9"));
assert!(!content.contains("event 8"));
}
#[test]
fn rejects_unknown_workflow_status() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"status",
"--root",
&root,
"--session-id",
"session-a",
"--phase",
"tooling",
"--mode",
"validate",
"--artifact-ref",
"tools/status-update",
"--lead-agent",
"codex",
"--status",
"thinking",
"--completed",
"nothing",
"--in-progress",
"status-update helper",
"--next-action",
"fix status",
]);
assert_eq!(output.status.code(), Some(2));
assert!(stderr(&output).contains("invalid choice"));
}
#[test]
fn rejects_zero_max_events() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"status",
"--root",
&root,
"--session-id",
"session-a",
"--phase",
"tooling",
"--mode",
"validate",
"--artifact-ref",
"tools/status-update",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"nothing",
"--in-progress",
"status-update helper",
"--next-action",
"fix max events",
"--max-events",
"0",
]);
assert_eq!(output.status.code(), Some(2));
assert!(stderr(&output).contains("--max-events must be >= 1"));
}
#[test]
fn generates_fallback_event_when_event_omitted() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"status",
"--root",
&root,
"--session-id",
"session-a",
"--timestamp",
"2026-05-28T21:30:00+07:00",
"--phase",
"tooling",
"--mode",
"validate",
"--artifact-ref",
"tools/status-update",
"--lead-agent",
"codex",
"--status",
"waiting-for-operator",
"--completed",
"status file written",
"--in-progress",
"operator decision",
"--next-action",
"choose next helper",
]);
let content = fs::read_to_string(tmp.path().join("sessions/session-a/status.md")).unwrap();
assert!(output.status.success(), "{}", stderr(&output));
assert!(content.contains(
"1. 2026-05-28T21:30:00+07:00 - status=waiting-for-operator; next=choose next helper"
));
}
#[test]
fn status_enum_is_loaded_from_profile() {
let tmp = tempdir().unwrap();
let profile = tmp.path().join("profile.yaml");
fs::write(
&profile,
"operator_interface:\n workflow_status_enum:\n - custom-status\n",
)
.unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"status",
"--profile",
profile.to_str().unwrap(),
"--root",
&root,
"--session-id",
"session-a",
"--phase",
"tooling",
"--mode",
"validate",
"--artifact-ref",
"tools/status-update",
"--lead-agent",
"codex",
"--status",
"custom-status",
"--completed",
"profile status accepted",
"--in-progress",
"status-update helper",
"--next-action",
"continue",
]);
assert!(output.status.success(), "{}", stderr(&output));
}
#[test]
fn help_clarifies_audit_root_is_outputs_root() {
let output = run_zynk(&["audit", "--help"]);
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains("runtime outputs root; pass <project>/outputs"));
}
#[test]
fn appends_first_hash_only_record_with_computed_hash_and_size() {
let tmp = tempdir().unwrap();
let payload = "First audited payload.";
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--transport-thread-id",
"thread-a",
"--mid",
"abc123",
"--type",
"request-review",
"--mode",
"review",
"--ref",
"tools/audit-append",
"--command-origin",
"agent",
"--payload",
payload,
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
let audit_path = tmp.path().join("sessions/session-a/audit.md");
let content = fs::read_to_string(&audit_path).unwrap();
assert!(output.status.success(), "{}", stderr(&output));
assert!(stdout(&output).contains(audit_path.to_str().unwrap()));
assert!(content.contains("audit_id=a1b2c3"));
assert!(content.contains("previous_audit_id=none"));
assert!(content.contains("payload_hash=sha256:"));
assert!(content.contains(&format!("content_size={}", payload.len())));
assert!(content.contains("payload_redaction_policy=hash-only"));
assert!(!content.contains("payload_excerpt="));
}
#[test]
fn appends_second_record_linked_to_previous_audit_id() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let first = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"abc123",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"first",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
let second = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"d4e5f6",
"--timestamp",
"2026-05-28T22:01:00+07:00",
"--source-agent",
"claude",
"--source-address",
"w652dc9b3ded432-1",
"--target-agent",
"codex",
"--target-address",
"w652dc9b3ded432-2",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"def456",
"--type",
"approve",
"--re",
"abc123",
"--command-origin",
"agent",
"--payload",
"second",
"--delivery-status",
"observed",
"--observed-by",
"claude,codex",
"--verified-by",
"agent",
]);
let content = fs::read_to_string(tmp.path().join("sessions/session-a/audit.md")).unwrap();
assert!(first.status.success(), "{}", stderr(&first));
assert!(second.status.success(), "{}", stderr(&second));
assert!(content.contains("audit_id=a1b2c3"));
assert!(content.contains("audit_id=d4e5f6"));
assert!(content.contains("previous_audit_id=a1b2c3"));
}
#[test]
fn excerpt_policy_writes_inline_excerpt() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"abc123",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"abcdefghijklmnopqrstuvwxyz",
"--payload-redaction-policy",
"excerpt",
"--excerpt-chars",
"5",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
let content = fs::read_to_string(tmp.path().join("sessions/session-a/audit.md")).unwrap();
assert!(output.status.success(), "{}", stderr(&output));
assert!(content.contains("payload_redaction_policy=excerpt"));
assert!(content.contains("payload_excerpt=abcde...vwxyz"));
}
#[test]
fn sensitive_category_forces_hash_only() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"abc123",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"secret value",
"--payload-redaction-policy",
"full",
"--sensitive-category",
"tokens",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
let content = fs::read_to_string(tmp.path().join("sessions/session-a/audit.md")).unwrap();
assert!(output.status.success(), "{}", stderr(&output));
assert!(content.contains("payload_redaction_policy=hash-only"));
assert!(!content.contains("payload_excerpt=secret value"));
}
#[test]
fn rejects_unknown_redaction_policy_from_profile() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"abc123",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"payload",
"--payload-redaction-policy",
"summary",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert_eq!(output.status.code(), Some(2));
assert!(stderr(&output).contains("invalid redaction policy"));
}
#[test]
fn accepts_drafted_delivery_status_for_message_shaped_drafts() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"claude",
"--source-address",
"w652dc9b3ded432-1",
"--target-agent",
"codex",
"--target-address",
"w652dc9b3ded432-2",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"abc123",
"--type",
"propose",
"--command-origin",
"agent",
"--payload",
"Message-shaped draft in sender pane.",
"--delivery-status",
"drafted",
"--observed-by",
"claude",
"--verified-by",
"agent",
]);
let content = fs::read_to_string(tmp.path().join("sessions/session-a/audit.md")).unwrap();
assert!(output.status.success(), "{}", stderr(&output));
assert!(content.contains("delivery_status=drafted"));
assert!(content.contains("verified_by=agent"));
}
#[test]
fn rejects_sent_delivery_self_verified_by_agent() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"claude",
"--source-address",
"w652dc9b3ded432-1",
"--target-agent",
"codex",
"--target-address",
"w652dc9b3ded432-2",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"abc123",
"--type",
"propose",
"--command-origin",
"agent",
"--payload",
"Message-shaped draft in sender pane.",
"--delivery-status",
"sent",
"--observed-by",
"claude",
"--verified-by",
"agent",
]);
assert_eq!(output.status.code(), Some(2));
assert!(stderr(&output).contains(
"delivery_status=sent requires transport, helper-tool, or operator verification"
));
}
#[test]
fn payload_file_without_inline_payload_is_accepted() {
let tmp = tempdir().unwrap();
let payload_file = tmp.path().join("payload.txt");
fs::write(&payload_file, "Payload loaded from file.").unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"abc123",
"--type",
"ack",
"--command-origin",
"agent",
"--payload-file",
payload_file.to_str().unwrap(),
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
let content = fs::read_to_string(tmp.path().join("sessions/session-a/audit.md")).unwrap();
assert!(output.status.success(), "{}", stderr(&output));
assert!(content.contains("payload_hash=sha256:"));
}
#[test]
fn rejects_missing_or_duplicate_payload_inputs() {
let tmp = tempdir().unwrap();
let payload_file = tmp.path().join("payload.txt");
fs::write(&payload_file, "payload").unwrap();
let root = tmp.path().to_string_lossy();
let common = [
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"abc123",
"--type",
"ack",
"--command-origin",
"agent",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
];
let missing = run_zynk(&common);
let mut duplicate_args = common.to_vec();
duplicate_args.extend([
"--payload",
"inline",
"--payload-file",
payload_file.to_str().unwrap(),
]);
let duplicate = run_zynk(&duplicate_args);
assert_eq!(missing.status.code(), Some(2));
assert!(stderr(&missing).contains("exactly one of --payload or --payload-file is required"));
assert_eq!(duplicate.status.code(), Some(2));
assert!(stderr(&duplicate).contains("exactly one of --payload or --payload-file is required"));
}
#[test]
fn rejects_duplicate_audit_id() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let first = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:00:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"abc123",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"first",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
let duplicate = run_zynk(&[
"audit",
"--root",
&root,
"--session-id",
"session-a",
"--audit-id",
"a1b2c3",
"--timestamp",
"2026-05-28T22:01:00+07:00",
"--source-agent",
"codex",
"--source-address",
"w652dc9b3ded432-2",
"--target-agent",
"claude",
"--target-address",
"w652dc9b3ded432-1",
"--transport",
"herdr",
"--workspace-id",
"w652dc9b3ded432",
"--mid",
"def456",
"--type",
"ack",
"--command-origin",
"agent",
"--payload",
"second",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(first.status.success(), "{}", stderr(&first));
assert_eq!(duplicate.status.code(), Some(2));
assert!(stderr(&duplicate).contains("duplicate audit_id: a1b2c3"));
}
fn is_canonical_z(value: &str) -> bool {
let b = value.as_bytes();
value.len() == 20
&& b[4] == b'-'
&& b[7] == b'-'
&& b[10] == b'T'
&& b[13] == b':'
&& b[16] == b':'
&& b[19] == b'Z'
&& value[..4].chars().all(|c| c.is_ascii_digit())
}
#[test]
fn db_import_normalizes_offset_timestamps_to_utc_z() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"tooling",
"review",
"working",
"tools/import",
);
write_audit_artifact(
&root,
"session-a",
&[test_audit_record_at(
"aud001",
"none",
"mid001",
"observed",
"agent",
"2026-05-28T18:00:00+07:00",
)],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let ts: String = connection
.query_row(
"SELECT timestamp FROM audit_records WHERE audit_id='aud001'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
ts, "2026-05-28T11:00:00Z",
"offset timestamp not normalized to UTC Z"
);
}
#[test]
fn db_import_mixed_offset_records_sort_chronologically() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"tooling",
"review",
"working",
"tools/import",
);
write_audit_artifact(
&root,
"session-a",
&[
test_audit_record_at(
"audearly",
"none",
"m1",
"observed",
"agent",
"2026-05-28T18:00:00+07:00",
),
test_audit_record_at(
"audlate",
"audearly",
"m2",
"observed",
"agent",
"2026-05-28T18:00:00+00:00",
),
],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let mut statement = connection
.prepare(
"SELECT audit_id FROM audit_records WHERE session_id='session-a' ORDER BY timestamp",
)
.unwrap();
let order: Vec<String> = statement
.query_map([], |r| r.get::<_, String>(0))
.unwrap()
.map(Result::unwrap)
.collect();
assert_eq!(
order,
vec!["audearly".to_string(), "audlate".to_string()],
"ORDER BY timestamp must be chronological after UTC-Z normalization"
);
}
#[test]
fn db_v4_rejects_noncanonical_timestamp_on_raw_insert() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
connection
.execute(
"INSERT INTO projects (project_id, name, root_path, created_at, updated_at)
VALUES ('p', 'p', '/p', '2026-05-29T00:00:00Z', '2026-05-29T00:00:00Z')",
[],
)
.unwrap();
connection
.execute(
"INSERT INTO sessions (session_id, project_id, title, phase, mode, workflow_status, created_at, updated_at)
VALUES ('s', 'p', 's', 'x', 'review', 'working', '2026-05-29T00:00:00Z', '2026-05-29T00:00:00Z')",
[],
)
.unwrap();
let bad = connection.execute(
"INSERT INTO status_events (session_id, timestamp, phase, mode, workflow_status,
completed_since_last_update, in_progress, next_action, blockers, asks_for_zevs,
risk_or_residual_uncertainty, expected_wait)
VALUES ('s', '2026-05-28T18:00:00+07:00', 'x', 'review', 'working', '-', '-', '-', '-', '-', '-', '-')",
[],
);
assert!(
bad.is_err(),
"non-canonical timestamp should be rejected on insert"
);
}
#[test]
fn db_v4_rejects_noncanonical_messages_timestamp_on_raw_update() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"tooling",
"review",
"working",
"tools/import",
);
write_audit_artifact(
&root,
"session-a",
&[test_audit_record_at(
"aud001",
"none",
"mid001",
"observed",
"agent",
"2026-05-28T18:00:00Z",
)],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let bad = connection.execute(
"UPDATE messages SET timestamp = '2026-05-28T18:00:00+07:00' WHERE mid = 'mid001'",
[],
);
assert!(
bad.is_err(),
"non-canonical timestamp should be rejected on messages update"
);
}
#[test]
fn db_import_skips_audit_record_missing_verified_by() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"tooling",
"review",
"working",
"tools/import",
);
let session_dir = root.join("sessions").join("session-a");
fs::create_dir_all(&session_dir).unwrap();
let record =
"```text\naudit_id=audnov\nprevious_audit_id=none\ntimestamp=2026-05-28T18:00:00Z\n\
source_agent=codex\nsource_address=cp\ntarget_agent=claude\ntarget_address=clp\ntransport=herdr\n\
workspace_id=w\nsession_id=session-a\nmid=mid001\ntype=request-review\ncommand_origin=agent\n\
payload_hash=sha256:test\npayload_redaction_policy=hash-only\ncontent_size=12\n\
delivery_status=observed\nobserved_by=codex\n```";
fs::write(
session_dir.join("audit.md"),
format!("# Audit Trail: session-a\n\nsession_id: session-a\n\n## Records\n\n{record}\n"),
)
.unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE audit_id='audnov'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
count, 0,
"record missing verified_by must be skipped, not stored as 'agent'"
);
let warning: String = connection
.query_row("SELECT warning_summary FROM imports", [], |r| r.get(0))
.unwrap();
assert!(
warning.contains("audnov"),
"skip must be recorded in warning_summary"
);
}
#[test]
fn db_import_preserves_distinct_status_events_same_timestamp() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
let status_call = |next: &str| {
run_zynk(&[
"status",
"--root",
root.to_str().unwrap(),
"--session-id",
"session-a",
"--timestamp",
"2026-05-29T01:00:00Z",
"--phase",
"tooling",
"--mode",
"review",
"--artifact-ref",
"tools/import",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"p",
"--next-action",
next,
"--event",
next,
])
};
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
assert!(status_call("first-action").status.success());
let imp1 = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(imp1.status.success(), "{}", stderr(&imp1));
assert!(status_call("second-action").status.success());
let imp2 = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:01:00Z",
]);
assert!(imp2.status.success(), "{}", stderr(&imp2));
let connection = Connection::open(&db_path).unwrap();
let count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM status_events WHERE session_id='session-a' AND timestamp='2026-05-29T01:00:00Z'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
count, 2,
"distinct status events sharing the 5-tuple must both be preserved"
);
}
#[test]
fn db_reimport_does_not_duplicate_status_events() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"tooling",
"review",
"working",
"tools/import",
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
for _ in 0..2 {
let imp = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(imp.status.success(), "{}", stderr(&imp));
}
let connection = Connection::open(&db_path).unwrap();
let count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM status_events WHERE session_id='session-a'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
count, 1,
"re-import of identical status must not duplicate the event"
);
}
#[test]
fn status_command_emits_utc_z_timestamp() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"status",
"--root",
root.to_str().unwrap(),
"--session-id",
"session-a",
"--phase",
"tooling",
"--mode",
"review",
"--artifact-ref",
"tools/x",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"p",
"--next-action",
"n",
]);
assert!(out.status.success(), "{}", stderr(&out));
let status = fs::read_to_string(root.join("sessions/session-a/status.md")).unwrap();
let line = status
.lines()
.find(|l| l.starts_with("last_update: "))
.unwrap();
let ts = line.trim_start_matches("last_update: ");
assert!(
is_canonical_z(ts),
"status last_update must be UTC Z seconds form, got {ts}"
);
}
#[test]
fn db_audit_append_normalizes_offset_timestamp() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
drop(connection);
let out = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"aud001",
"--timestamp",
"2026-05-28T18:00:00+07:00",
"--source-address",
"cp",
"--target-address",
"clp",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"mid001",
"--type",
"request-review",
"--command-origin",
"agent",
"--payload",
"p",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(
out.status.success(),
"valid offset timestamp must not fail: {}",
stderr(&out)
);
let connection = Connection::open(&db_path).unwrap();
let ts: String = connection
.query_row(
"SELECT timestamp FROM audit_records WHERE audit_id='aud001'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
ts, "2026-05-28T11:00:00Z",
"append --timestamp offset must normalize to Z"
);
}
#[test]
fn db_audit_append_rejects_invalid_timestamp() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
drop(connection);
let out = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"aud001",
"--timestamp",
"not-a-timestamp",
"--source-address",
"cp",
"--target-address",
"clp",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"mid001",
"--type",
"request-review",
"--command-origin",
"agent",
"--payload",
"p",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert_eq!(
out.status.code(),
Some(2),
"invalid timestamp must be a clean usage error"
);
assert!(
stderr(&out).contains("RFC3339"),
"error must name RFC3339: {}",
stderr(&out)
);
}
#[test]
fn db_import_timestamp_offset_normalized_for_fallback_session() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
fs::create_dir_all(root.join("sessions").join("empty-sess")).unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-28T18:00:00+07:00",
]);
assert!(
import.status.success(),
"offset import timestamp must not fail: {}",
stderr(&import)
);
let connection = Connection::open(&db_path).unwrap();
let created: String = connection
.query_row(
"SELECT created_at FROM sessions WHERE session_id='empty-sess'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
created, "2026-05-28T11:00:00Z",
"fallback session created_at must normalize to Z"
);
}
#[test]
fn db_import_help_mentions_recovery_recipe() {
let out = run_zynk(&["db", "import", "outputs", "--help"]);
assert!(out.status.success(), "{}", stderr(&out));
let help = stdout(&out);
assert!(
help.contains("zynk db init") && help.contains("import outputs"),
"import help must document the legacy-DB recovery recipe"
);
}
#[test]
fn db_import_skips_invalid_audit_timestamp_without_polluting_agents() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_audit_artifact(
&root,
"session-a",
&[test_audit_record_at(
"audbad",
"none",
"mid001",
"observed",
"agent",
"not-a-timestamp",
)],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let audits: i64 = connection
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE audit_id='audbad'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(audits, 0, "invalid-timestamp audit record must be skipped");
let agents: i64 = connection
.query_row("SELECT COUNT(*) FROM agents", [], |r| r.get(0))
.unwrap();
assert_eq!(
agents, 0,
"invalid-timestamp record must not pollute agents before validation"
);
let warning: String = connection
.query_row("SELECT warning_summary FROM imports", [], |r| r.get(0))
.unwrap();
assert!(warning.contains("audbad"), "skip must be warned: {warning}");
}
#[test]
fn db_import_skips_calendar_invalid_audit_timestamp() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_audit_artifact(
&root,
"session-a",
&[test_audit_record_at(
"audcal",
"none",
"mid001",
"observed",
"agent",
"2026-99-99T99:99:99Z",
)],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let audits: i64 = connection
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE audit_id='audcal'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
audits, 0,
"calendar-invalid timestamp must not enter audit_records"
);
let messages: i64 = connection
.query_row(
"SELECT COUNT(*) FROM messages WHERE mid='mid001'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
messages, 0,
"calendar-invalid timestamp must not enter messages"
);
}
#[test]
fn db_import_fails_on_invalid_status_timestamp() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
let session_dir = root.join("sessions/session-a");
fs::create_dir_all(&session_dir).unwrap();
fs::write(
session_dir.join("status.md"),
"# Session Status: session-a\n\n\
session_id: session-a\n\
last_update: 2026-99-99T99:99:99Z\n\
lead_agent: codex\n\
status: working\n\n\
## Current State\n\n\
- phase: tooling\n\
- mode: review\n\
- artifact_ref: tools/x\n\
- completed_since_last_update: c\n\
- in_progress: p\n\
- next_action: n\n\
- blockers: none\n\
- asks_for_Zevs: none\n\
- risk_or_residual_uncertainty: none\n\
- expected_wait: unknown\n\n\
## Rolling Events\n\n\
1. 2026-99-99T99:99:99Z - created\n",
)
.unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(
!import.status.success(),
"invalid status timestamp must fail the import"
);
assert!(
stderr(&import).to_lowercase().contains("timestamp"),
"import error must name the timestamp problem: {}",
stderr(&import)
);
}
#[test]
fn db_one_child_per_previous_unique_index_rejects_second_child() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
let insert = |id: &str, prev: Option<&str>| {
connection.execute(
"INSERT INTO audit_records (audit_id, previous_audit_id, session_id, source_address,
target_address, transport, workspace_id, mid, record_type, command_origin,
payload_hash, payload_redaction_policy, content_size, delivery_status,
observed_by, verified_by, timestamp)
VALUES (?1, ?2, 'session-a', 'cp', 'clp', 'herdr', 'w', 'm', 'request-review',
'agent', 'sha256:x', 'hash-only', 1, 'observed', 'codex', 'helper-tool',
'2026-05-29T01:00:00Z')",
params![id, prev],
)
};
insert("head", None).unwrap();
insert("childA", Some("head")).unwrap();
let second = insert("childB", Some("head"));
assert!(
second.is_err(),
"a second child of the same previous_audit_id must violate the one-child unique index"
);
}
#[test]
fn db_import_skips_adr024_violating_source_record() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_audit_artifact(
&root,
"session-a",
&[test_audit_record_at(
"audv",
"none",
"mid001",
"sent",
"agent",
"2026-05-29T01:00:00Z",
)],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE audit_id='audv'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(count, 0, "sent+agent record must be rejected, not stored");
let messages: i64 = connection
.query_row(
"SELECT COUNT(*) FROM messages WHERE mid='mid001'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(messages, 0, "rejected record must not create a message");
let agents: i64 = connection
.query_row("SELECT COUNT(*) FROM agents", [], |r| r.get(0))
.unwrap();
assert_eq!(
agents, 0,
"rejected record must not create agents (no side effects before accept)"
);
let warning: String = connection
.query_row("SELECT warning_summary FROM imports", [], |r| r.get(0))
.unwrap();
assert!(
warning.contains("audv"),
"rejection must be warned: {warning}"
);
}
#[test]
fn db_import_preserves_payload_hash_verbatim() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"session-a",
"tooling",
"review",
"working",
"tools/import",
);
write_audit_artifact(
&root,
"session-a",
&[test_audit_record_at(
"aud1",
"none",
"mid001",
"observed",
"agent",
"2026-05-29T01:00:00Z",
)],
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let hash: String = connection
.query_row(
"SELECT payload_hash FROM audit_records WHERE audit_id='aud1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
hash, "sha256:test-aud1",
"payload_hash must round-trip verbatim from source"
);
}
#[test]
fn db_multi_session_sidebar_filter_and_export_isolation() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let export_root = tmp.path().join("exported");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(&root, "sess-a", "phase-a", "review", "working", "ref-a");
write_status_artifact(&root, "sess-b", "phase-b", "validate", "blocked", "ref-b");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let home = fetch_db_dashboard_once(&db_path, "/");
assert!(
home.contains("sess-a") && home.contains("sess-b"),
"sidebar must list both sessions"
);
let a = fetch_db_dashboard_once(&db_path, "/?session=sess-a");
assert!(
a.contains("phase-a"),
"filtered view must show the selected session"
);
assert!(
!a.contains("phase-b"),
"filtered view must not render the other session's timeline"
);
let export = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"export",
"outputs",
"--root",
export_root.to_str().unwrap(),
"--session-id",
"sess-a",
]);
assert!(export.status.success(), "{}", stderr(&export));
let status_a = fs::read_to_string(export_root.join("sessions/sess-a/status.md")).unwrap();
assert!(
status_a.contains("phase-a"),
"exported session must contain its own data"
);
assert!(
!status_a.contains("phase-b"),
"export must not bleed another session's data"
);
assert!(
!export_root.join("sessions/sess-b").exists(),
"export must not write the unselected session"
);
}
fn fetch_dashboard_with_serve_args(db_path: &Path, route: &str, extra: &[&str]) -> String {
let mut args: Vec<String> = vec![
"db".into(),
"--db".into(),
db_path.to_str().unwrap().into(),
"serve".into(),
"--port".into(),
"0".into(),
"--once".into(),
];
args.extend(extra.iter().map(|s| s.to_string()));
let mut child = StdCommand::new(env!("CARGO_BIN_EXE_zynk"))
.args(&args)
.stdout(Stdio::piped())
.spawn()
.unwrap();
let stdout = child.stdout.take().unwrap();
let mut reader = BufReader::new(stdout);
let mut line = String::new();
reader.read_line(&mut line).unwrap();
assert!(line.starts_with("listening on http://127.0.0.1:"), "{line}");
let host_port = line
.trim()
.strip_prefix("listening on http://")
.unwrap()
.trim_end_matches('/');
let mut stream = TcpStream::connect(host_port).unwrap();
write!(
stream,
"GET {route} HTTP/1.1\r\nHost: {host_port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
stream.shutdown(Shutdown::Write).unwrap();
let mut bytes = Vec::new();
let mut buf = [0_u8; 4096];
loop {
match stream.read(&mut buf) {
Ok(0) => break,
Ok(n) => bytes.extend_from_slice(&buf[..n]),
Err(e) if e.kind() == ErrorKind::ConnectionReset => break,
Err(e) => panic!("read failed: {e}"),
}
}
let response = String::from_utf8_lossy(&bytes).to_string();
assert!(child.wait().unwrap().success());
response
}
#[test]
fn db_serve_auto_import_renders_file_only_session() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"live-sess",
"tooling",
"review",
"working",
"ref-live",
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let response = fetch_dashboard_with_serve_args(
&db_path,
"/",
&["--auto-import", "--root", root.to_str().unwrap()],
);
assert!(response.contains("HTTP/1.1 200 OK"), "{response}");
assert!(
response.contains("live-sess"),
"--auto-import must import file artifacts before render so the session is live"
);
}
#[test]
fn db_serve_default_does_not_auto_import() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
write_status_artifact(
&root,
"live-sess",
"tooling",
"review",
"working",
"ref-live",
);
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let response = fetch_dashboard_with_serve_args(&db_path, "/", &[]);
assert!(response.contains("HTTP/1.1 200 OK"), "{response}");
assert!(
response.contains("No sessions in this database"),
"default serve must NOT import (preserves ADR 025 DB-read-only default)"
);
assert!(
!response.contains("live-sess"),
"default serve must not surface un-imported sessions"
);
}
fn http_get(host_port: &str, route: &str) -> String {
let mut stream = TcpStream::connect(host_port).unwrap();
write!(
stream,
"GET {route} HTTP/1.1\r\nHost: {host_port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
stream.shutdown(Shutdown::Write).unwrap();
let mut bytes = Vec::new();
let mut buf = [0_u8; 4096];
loop {
match stream.read(&mut buf) {
Ok(0) => break,
Ok(n) => bytes.extend_from_slice(&buf[..n]),
Err(e) if e.kind() == ErrorKind::ConnectionReset => break,
Err(e) => panic!("read failed: {e}"),
}
}
String::from_utf8_lossy(&bytes).to_string()
}
#[test]
fn db_serve_auto_import_reflects_writes_on_immediate_refresh() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
fs::create_dir_all(&root).unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let mut child = StdCommand::new(env!("CARGO_BIN_EXE_zynk"))
.args([
"db",
"--db",
db_path.to_str().unwrap(),
"serve",
"--auto-import",
"--root",
root.to_str().unwrap(),
"--host",
"127.0.0.1",
"--port",
"0",
])
.stdout(Stdio::piped())
.spawn()
.unwrap();
let stdout = child.stdout.take().unwrap();
let mut reader = BufReader::new(stdout);
let mut line = String::new();
reader.read_line(&mut line).unwrap();
let host_port = line
.trim()
.strip_prefix("listening on http://")
.unwrap()
.trim_end_matches('/')
.to_string();
let first = http_get(&host_port, "/");
let wrote = run_zynk(&[
"status",
"--root",
root.to_str().unwrap(),
"--session-id",
"imm-sess",
"--lead-agent",
"codex",
"--status",
"working",
"--phase",
"p",
"--mode",
"review",
"--artifact-ref",
"x",
"--completed",
"c",
"--in-progress",
"ip",
"--next-action",
"na",
]);
let second = http_get(&host_port, "/");
let _ = child.kill();
let _ = child.wait();
assert!(wrote.status.success(), "{}", stderr(&wrote));
assert!(
first.contains("No sessions in this database"),
"first render should be empty: {first}"
);
assert!(
second.contains("imm-sess"),
"a status written after startup must appear on the very next refresh (no debounce staleness): {second}"
);
}
#[test]
fn report_think_projects_and_writes_artifact() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join(".zynk/zynk.db");
let out = run_zynk(&[
"report",
"think",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
"two viable strategies; token bucket absorbs bursts",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(&db).unwrap();
let (kind, payload): (String, String) = conn
.query_row(
"SELECT kind, payload FROM work_events WHERE session_id='s1'",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(kind, "think");
assert!(payload.contains("token bucket"));
let work_md = fs::read_to_string(root.join("sessions/s1/work.md")).unwrap();
assert!(work_md.contains("think") && work_md.contains("token bucket"));
}
#[test]
fn report_default_db_writes_gitignore() {
let tmp = tempdir().unwrap();
let out = run_zynk_in(
&[
"report",
"think",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
"weighing token bucket vs leaky bucket",
],
tmp.path(),
);
assert!(out.status.success(), "{}", stderr(&out));
let db = tmp.path().join(".zynk/zynk.db");
assert!(
db.exists(),
"report with no --db must auto-create .zynk/zynk.db (ADR 028)"
);
let gitignore = tmp.path().join(".zynk/.gitignore");
assert!(
gitignore.exists() && fs::read_to_string(&gitignore).unwrap().contains('*'),
"report default-DB auto-create must write .zynk/.gitignore with `*` (ADR 028)"
);
let conn = Connection::open(&db).unwrap();
let kind: String = conn
.query_row(
"SELECT kind FROM work_events WHERE session_id='s1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(kind, "think");
}
#[test]
fn report_rejects_empty_payload() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join(".zynk/zynk.db");
let out = run_zynk(&[
"report",
"think",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--actor",
"claude",
"--text",
" ",
]);
assert!(
!out.status.success(),
"empty --text must exit nonzero: {}",
stderr(&out)
);
assert!(
!root.join("sessions/s1/work.md").exists(),
"validate-before-write: no work.md may be written on a rejected payload"
);
assert!(
!db.exists(),
"validate-before-write: no DB may be created on a rejected payload"
);
}
#[test]
fn report_diff_rejects_unknown_hunk_op() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join(".zynk/zynk.db");
let out = run_zynk(&[
"report",
"diff",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--actor",
"claude",
"--file",
"src/x.rs",
"--added",
"1",
"--removed",
"0",
"--hunk",
"frobnicate:hello",
]);
assert!(
!out.status.success(),
"unknown hunk op must exit nonzero: {}",
stderr(&out)
);
assert!(
!root.join("sessions/s1/work.md").exists(),
"validate-before-write: no work.md may be written on a rejected hunk op"
);
assert!(
!db.exists(),
"validate-before-write: no DB may be created on a rejected hunk op"
);
}
#[test]
fn report_artifact_dual_projects_and_diff_gate_conflict() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join(".zynk/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let base: Vec<&str> = vec![
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:00Z",
];
let art = run_zynk(
&[
["report", "artifact"].as_slice(),
&base,
&["--file", "src/mid.rs:96:1", "--file", "src/lib.rs:3:0"],
]
.concat(),
);
assert!(art.status.success(), "{}", stderr(&art));
let conn = Connection::open(&db).unwrap();
let n: i64 = conn
.query_row(
"SELECT COUNT(*) FROM work_events WHERE kind='artifact' AND session_id='s1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(n, 1);
let arts: i64 = conn
.query_row(
"SELECT COUNT(*) FROM artifacts WHERE session_id='s1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(arts, 2);
let path: String = conn
.query_row(
"SELECT path FROM artifacts WHERE session_id='s1' ORDER BY path LIMIT 1",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(path, "src/lib.rs");
let d = run_zynk(
&[
["report", "diff"].as_slice(),
&base,
&[
"--file",
"src/x.rs",
"--added",
"3",
"--removed",
"1",
"--hunk",
"add:fn x() {}",
"--hunk",
"rem:old line",
],
]
.concat(),
);
assert!(d.status.success(), "{}", stderr(&d));
let dk: i64 = conn
.query_row(
"SELECT COUNT(*) FROM work_events WHERE kind='diff'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(dk, 1);
let g = run_zynk(
&[
["report", "gate"].as_slice(),
&base,
&[
"--title",
"Approve plan",
"--action",
"Approve",
"--action",
"Request changes",
"--summary",
"ready",
"--proposer",
"claude",
],
]
.concat(),
);
assert!(g.status.success(), "{}", stderr(&g));
let gk: i64 = conn
.query_row(
"SELECT COUNT(*) FROM work_events WHERE kind='gate'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(gk, 1);
let c = run_zynk(
&[
["report", "conflict"].as_slice(),
&base,
&[
"--topic",
"rate limiter",
"--position",
"claude:for:token bucket:more memory",
"--position",
"codex:against:leaky bucket:smoother",
"--recommended",
"token bucket",
"--option",
"token bucket",
"--option",
"leaky bucket",
],
]
.concat(),
);
assert!(c.status.success(), "{}", stderr(&c));
let ck: i64 = conn
.query_row(
"SELECT COUNT(*) FROM work_events WHERE kind='conflict'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(ck, 1);
let payload: String = conn
.query_row(
"SELECT payload FROM work_events WHERE kind='conflict'",
[],
|r| r.get(0),
)
.unwrap();
assert!(
payload.contains("token bucket") && payload.contains("smoother"),
"conflict payload must round-trip the typed positions: {payload}"
);
}
#[test]
fn report_rejects_invalid_timestamp() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let out = run_zynk(&[
"report",
"think",
"--root",
root.to_str().unwrap(),
"--session-id",
"badts",
"--actor",
"codex",
"--timestamp",
"not-a-timestamp",
"--text",
"hello",
]);
assert!(
!out.status.success(),
"invalid --timestamp must exit nonzero: {}",
stderr(&out)
);
assert!(
!root.join("sessions/badts/work.md").exists(),
"validate-before-write: no work.md may be written on an invalid timestamp"
);
assert!(
!root.parent().unwrap().join(".zynk/zynk.db").exists(),
"validate-before-write: no DB may be created on an invalid timestamp"
);
}
#[test]
fn report_canonicalizes_offset_timestamp() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join(".zynk/zynk.db");
let out = run_zynk(&[
"report",
"think",
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"off",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:00+07:00",
"--text",
"hello",
]);
assert!(out.status.success(), "{}", stderr(&out));
let work = fs::read_to_string(root.join("sessions/off/work.md")).unwrap();
assert!(
work.contains("timestamp=2026-05-30T17:00:00Z"),
"work.md must store the canonical UTC-Z timestamp: {work}"
);
assert!(
!work.contains("+07:00"),
"work.md must not store the raw offset timestamp: {work}"
);
let conn = Connection::open(&db).unwrap();
let ts: String = conn
.query_row(
"SELECT timestamp FROM work_events WHERE session_id='off'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(ts, "2026-05-30T17:00:00Z");
}
#[test]
fn report_repeated_same_payload_distinct_timestamps_both_kept() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join(".zynk/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
for ts in ["2026-05-31T00:00:00Z", "2026-05-31T00:00:01Z"] {
let out = run_zynk(&[
"report",
"think",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"log",
"--actor",
"codex",
"--timestamp",
ts,
"--text",
"same",
]);
assert!(out.status.success(), "{}", stderr(&out));
}
let conn = Connection::open(&db).unwrap();
let n: i64 = conn
.query_row(
"SELECT COUNT(*) FROM work_events WHERE session_id='log' AND kind='think'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
n, 2,
"distinct-timestamp events with the same payload must stay distinct rows"
);
}
#[test]
fn report_same_actor_second_payload_distinct_rows() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join(".zynk/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let args: Vec<&str> = vec![
"report",
"think",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"idem",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
"same",
];
for _ in 0..2 {
let out = run_zynk(&args);
assert!(out.status.success(), "{}", stderr(&out));
}
let count_rows = |conn: &Connection| -> i64 {
conn.query_row(
"SELECT COUNT(*) FROM work_events WHERE session_id='idem' AND kind='think'",
[],
|r| r.get(0),
)
.unwrap()
};
let conn = Connection::open(&db).unwrap();
assert_eq!(
count_rows(&conn),
2,
"two same-second identical events by the same actor must stay distinct rows (file-rendered seq)"
);
let reimport = run_zynk(&[
"db",
"--db",
&db_str,
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T02:00:00Z",
]);
assert!(reimport.status.success(), "{}", stderr(&reimport));
assert_eq!(
count_rows(&conn),
2,
"re-importing the same work.md must be a no-op (stored seq → stable content_hash)"
);
}
#[test]
fn report_concurrent_same_session_no_seq_collision() {
const N: usize = 8;
const ROUNDS: usize = 5;
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let bin = assert_cmd::cargo::cargo_bin("zynk");
for r in 0..ROUNDS {
let session = format!("concurrent-r{r}");
let mut children: Vec<std::process::Child> = Vec::with_capacity(N);
for _ in 0..N {
let child = StdCommand::new(&bin)
.args([
"report",
"think",
"--root",
&root_str,
"--no-db",
"--session-id",
&session,
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
"same payload for every concurrent producer",
])
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.unwrap();
children.push(child);
}
for child in children {
let out = child.wait_with_output().unwrap();
assert!(
out.status.success(),
"round {r}: concurrent report failed: {}",
String::from_utf8_lossy(&out.stderr)
);
}
let work_md = fs::read_to_string(root.join(format!("sessions/{session}/work.md"))).unwrap();
let block_count = work_md.matches("```work-event").count();
assert_eq!(
block_count, N,
"round {r}: every concurrent producer must append exactly one block ({N} expected); work.md:\n{work_md}"
);
let mut seqs: Vec<usize> = work_md
.lines()
.filter_map(|l| l.strip_prefix("seq="))
.map(|s| s.trim().parse::<usize>().unwrap())
.collect();
assert_eq!(
seqs.len(),
N,
"round {r}: expected {N} seq= header lines; got {}",
seqs.len()
);
seqs.sort_unstable();
seqs.dedup();
assert_eq!(
seqs.len(),
N,
"round {r}: seq ordinals must be distinct (no duplicate seq from a count+append race); got {seqs:?}"
);
assert_eq!(
seqs,
(0..N).collect::<Vec<_>>(),
"round {r}: seq ordinals must be exactly 0..{N} (dense, no gaps); got {seqs:?}"
);
}
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db.to_str().unwrap(),
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let conn = Connection::open(&db).unwrap();
let total_rows: i64 = conn
.query_row(
"SELECT COUNT(*) FROM work_events WHERE kind='think'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
total_rows,
(ROUNDS * N) as i64,
"every concurrent producer's event across all rounds must import (no seq-collision dedup loss): \
expected {} work_events rows, got {total_rows}",
ROUNDS * N
);
for r in 0..ROUNDS {
let session = format!("concurrent-r{r}");
let rows: i64 = conn
.query_row(
"SELECT COUNT(*) FROM work_events WHERE session_id=?1 AND kind='think'",
[&session],
|row| row.get(0),
)
.unwrap();
assert_eq!(
rows, N as i64,
"round {r} session {session}: expected {N} imported work_events rows, got {rows}"
);
}
}
#[test]
fn m1_seed_scenario_renders_every_work_card() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
write_status_artifact(&root, "s1", "exec", "build", "working", "ref-1");
let import = run_zynk(&[
"db",
"--db",
db.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let base = [
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
"--actor",
"claude",
];
for args in [
vec!["report", "think", "--text", "weighing options"],
vec![
"report",
"tool",
"--name",
"run_tests",
"--arg",
"cargo test",
"--output",
"12 passing",
"--ok",
],
vec![
"report",
"plan",
"--title",
"Token-bucket limiter",
"--item",
"atomic Lua",
"--item",
"tiered limits",
],
vec!["report", "artifact", "--file", "src/rateLimit.js:96:1"],
vec!["report", "usage", "--agent", "claude", "--tokens", "4700"],
vec!["report", "system", "--text", "merged · CI green"],
vec![
"report",
"diff",
"--file",
"src/rateLimit.js",
"--added",
"12",
"--removed",
"3",
"--hunk",
"add:+ const bucket = new TokenBucket()",
],
vec![
"report",
"gate",
"--title",
"Approve plan",
"--summary",
"ready",
"--proposer",
"claude",
"--action",
"Approve",
"--action",
"Request changes",
],
vec![
"report",
"conflict",
"--topic",
"Limiter algorithm",
"--position",
"claude:token-bucket:absorbs bursts:tunable refill",
"--recommended",
"token-bucket",
"--option",
"token-bucket",
"--option",
"fixed-window",
],
] {
let o = run_zynk(&[args.as_slice(), &base].concat());
assert!(o.status.success(), "{}", stderr(&o));
}
let page = fetch_db_dashboard_once(&db, "/?session=s1");
assert!(page.contains("HTTP/1.1 200"), "{page}");
for needle in [
"kind-tool",
"run_tests",
"kind-plan",
"Token-bucket limiter",
"kind-artifact",
"rateLimit.js",
"kind-usage",
"4,700",
"kind-think",
"kind-system",
"CI green",
"kind-diff",
"src/rateLimit.js",
"+12",
"\u{2212}3",
"kind-gate",
"Approve plan",
"Approve",
"Request changes",
"kind-conflict",
"Limiter algorithm",
"claude",
"token-bucket",
] {
assert!(
page.contains(needle),
"feed missing real card content {needle:?}"
);
}
}
#[test]
fn dashboard_renders_decision_overlay_and_feed_item() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
write_status_artifact(&root, "s1", "exec", "build", "working", "ref-1");
let import = run_zynk(&[
"db",
"--db",
db.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let base = [
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
];
let gate = run_zynk(
&[
&[
"report",
"gate",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--summary",
"ready",
"--proposer",
"claude",
"--action",
"Approve",
],
base.as_slice(),
]
.concat(),
);
assert!(gate.status.success(), "{}", stderr(&gate));
let decide_gate = run_zynk(
&[
&[
"decide",
"gate",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--verdict",
"approve",
],
base.as_slice(),
]
.concat(),
);
assert!(decide_gate.status.success(), "{}", stderr(&decide_gate));
let decide_mode = run_zynk(
&[
&[
"decide",
"mode",
"--timestamp",
"2026-05-31T02:00:00Z",
"--to",
"review",
],
base.as_slice(),
]
.concat(),
);
assert!(decide_mode.status.success(), "{}", stderr(&decide_mode));
let page = fetch_db_dashboard_once(&db, "/?session=s1");
assert!(page.contains("HTTP/1.1 200"), "{page}");
assert!(
page.contains("decision-verdict"),
"gate card must carry a decision-verdict overlay marker: {page}"
);
assert!(
page.contains("approve"),
"the overlay must render the verdict 'approve': {page}"
);
assert!(
page.contains("operator"),
"the overlay must name the operator actor: {page}"
);
assert!(
page.contains("decision-mode"),
"a mode-switch decision must render a decision-mode feed item: {page}"
);
assert!(
page.contains("review"),
"the decision-mode feed item must render the target mode 'review': {page}"
);
}
#[test]
fn current_state_mode_is_latest_writer_across_status_and_decision() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let base = [
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
];
let seed_status = |timestamp: &str, mode: &str, phase: &str| {
let out = run_zynk(
&[
&[
"status",
"--timestamp",
timestamp,
"--phase",
phase,
"--mode",
mode,
"--artifact-ref",
"ref-1",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"i",
"--next-action",
"n",
"--blockers",
"none",
"--asks-for-zevs",
"none",
"--risk",
"none",
"--expected-wait",
"unknown",
],
base.as_slice(),
]
.concat(),
);
assert!(out.status.success(), "{}", stderr(&out));
};
seed_status("2026-05-31T00:00:00Z", "brainstorm", "plan");
let decide_mode = run_zynk(
&[
&[
"decide",
"mode",
"--timestamp",
"2026-05-31T01:00:00Z",
"--to",
"review",
],
base.as_slice(),
]
.concat(),
);
assert!(decide_mode.status.success(), "{}", stderr(&decide_mode));
let page = fetch_db_dashboard_once(&db, "/?session=s1");
assert!(page.contains("HTTP/1.1 200"), "{page}");
assert!(
page.contains("<p>s1 / plan / review</p>"),
"current-state mode must be the newer mode-switch decision (review), not the \
older status mode (brainstorm): {page}"
);
seed_status("2026-05-31T02:00:00Z", "validate", "build");
let page = fetch_db_dashboard_once(&db, "/?session=s1");
assert!(page.contains("HTTP/1.1 200"), "{page}");
assert!(
page.contains("<p>s1 / build / validate</p>"),
"current-state mode must be the newer status_event (validate) once it is the \
latest writer across status + decision: {page}"
);
}
#[test]
fn m2_seed_scenario_surfaces_every_decision() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let base = [
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
];
let seed_status = run_zynk(
&[
&[
"status",
"--timestamp",
"2026-05-31T00:00:00Z",
"--phase",
"plan",
"--mode",
"brainstorm",
"--artifact-ref",
"ref-1",
"--lead-agent",
"codex",
"--status",
"working",
"--completed",
"c",
"--in-progress",
"i",
"--next-action",
"n",
"--blockers",
"none",
"--asks-for-zevs",
"none",
"--risk",
"none",
"--expected-wait",
"unknown",
],
base.as_slice(),
]
.concat(),
);
assert!(seed_status.status.success(), "{}", stderr(&seed_status));
let gate = run_zynk(
&[
&[
"report",
"gate",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:10:00Z",
"--title",
"Approve plan",
"--summary",
"ready",
"--proposer",
"claude",
"--action",
"Approve",
"--action",
"Request changes",
],
base.as_slice(),
]
.concat(),
);
assert!(gate.status.success(), "{}", stderr(&gate));
let conflict = run_zynk(
&[
&[
"report",
"conflict",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:20:00Z",
"--topic",
"lock strategy",
"--position",
"claude:for:use mutex:simpler",
"--position",
"codex:against:use channel:faster",
"--recommended",
"use mutex",
"--option",
"use mutex",
"--option",
"use channel",
],
base.as_slice(),
]
.concat(),
);
assert!(conflict.status.success(), "{}", stderr(&conflict));
let decide_gate = run_zynk(
&[
&[
"decide",
"gate",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--verdict",
"approve",
],
base.as_slice(),
]
.concat(),
);
assert!(decide_gate.status.success(), "{}", stderr(&decide_gate));
let decide_conflict = run_zynk(
&[
&[
"decide",
"conflict",
"--timestamp",
"2026-05-31T01:10:00Z",
"--ref",
"2",
"--resolution",
"use mutex",
],
base.as_slice(),
]
.concat(),
);
assert!(
decide_conflict.status.success(),
"{}",
stderr(&decide_conflict)
);
let decide_mode = run_zynk(
&[
&[
"decide",
"mode",
"--timestamp",
"2026-05-31T02:00:00Z",
"--to",
"review",
],
base.as_slice(),
]
.concat(),
);
assert!(decide_mode.status.success(), "{}", stderr(&decide_mode));
let decide_interrupt = run_zynk(
&[
&[
"decide",
"interrupt",
"--timestamp",
"2026-05-31T02:10:00Z",
"--reason",
"pause for review",
],
base.as_slice(),
]
.concat(),
);
assert!(
decide_interrupt.status.success(),
"{}",
stderr(&decide_interrupt)
);
let decide_redirect = run_zynk(
&[
&[
"decide",
"redirect",
"--timestamp",
"2026-05-31T02:20:00Z",
"--to",
"codex",
"--reason",
"handoff",
],
base.as_slice(),
]
.concat(),
);
assert!(
decide_redirect.status.success(),
"{}",
stderr(&decide_redirect)
);
let conn = Connection::open(&db).unwrap();
let mut rows: Vec<(String, Option<i64>)> = conn
.prepare("SELECT decision_type, target_work_event_id FROM operator_decisions WHERE session_id = 's1' ORDER BY decision_type")
.unwrap()
.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))
.unwrap()
.map(|r| r.unwrap())
.collect();
rows.sort();
assert_eq!(
rows,
vec![
("conflict-resolve".to_string(), Some(2)),
("gate-decision".to_string(), Some(1)),
("interrupt".to_string(), None),
("mode-switch".to_string(), None),
("redirect".to_string(), None),
],
"every decision type must be present, gate/conflict bound to their work_event ids"
);
let page = fetch_db_dashboard_once(&db, "/?session=s1");
assert!(page.contains("HTTP/1.1 200"), "{page}");
assert!(
page.contains(r#"class="decision-overlay decision-verdict""#),
"the decided gate must overlay its work_event card (decision-verdict overlay): {page}"
);
assert!(
page.contains("Approve plan"),
"the gate work_event card must render (its title): {page}"
);
assert!(
page.contains("approve"),
"the gate overlay must render the verdict 'approve': {page}"
);
assert!(
page.contains(r#"class="decision-overlay decision-resolution""#),
"the decided conflict must overlay its work_event card (decision-resolution overlay): \
{page}"
);
assert!(
page.contains("lock strategy"),
"the conflict work_event card must render (its topic): {page}"
);
assert!(
page.contains("use mutex"),
"the conflict overlay must render the resolution 'use mutex': {page}"
);
assert!(
page.contains(r#"class="feed-item decision decision-mode""#),
"the mode-switch must render a standalone decision-mode feed item: {page}"
);
assert!(
page.contains(r#"class="feed-item decision decision-interrupt""#),
"the interrupt must render a standalone decision-interrupt feed item: {page}"
);
assert!(
page.contains("pause for review"),
"the interrupt feed item must render its reason: {page}"
);
assert!(
page.contains(r#"class="feed-item decision decision-redirect""#),
"the redirect must render a standalone decision-redirect feed item: {page}"
);
assert!(
page.contains("codex"),
"the redirect feed item must render its target agent 'codex': {page}"
);
assert!(
page.contains("<p>s1 / plan / review</p>"),
"current-state mode must be the newer mode-switch decision (review), not the \
older status mode (brainstorm): {page}"
);
assert!(
!page.contains("<p>s1 / plan / brainstorm</p>"),
"the stale status mode (brainstorm) must NOT be the current-state header: {page}"
);
}
fn seed_writable_session_with_gate(root: &Path, db: &Path) {
let init = run_zynk(&["db", "--db", db.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
write_status_artifact(root, "s1", "exec", "build", "working", "ref-1");
let import = run_zynk(&[
"db",
"--db",
db.to_str().unwrap(),
"import",
"outputs",
"--root",
root.to_str().unwrap(),
"--timestamp",
"2026-05-29T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let base = [
"--root",
root.to_str().unwrap(),
"--db",
db.to_str().unwrap(),
"--session-id",
"s1",
];
let gate = run_zynk(
&[
&[
"report",
"gate",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--summary",
"ready",
"--proposer",
"claude",
"--action",
"Approve",
],
base.as_slice(),
]
.concat(),
);
assert!(gate.status.success(), "{}", stderr(&gate));
let think = run_zynk(
&[
&[
"report",
"think",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:30:00Z",
"--text",
"just thinking",
],
base.as_slice(),
]
.concat(),
);
assert!(think.status.success(), "{}", stderr(&think));
}
fn operator_decision_count(db: &Path) -> i64 {
let conn = Connection::open(db).unwrap();
conn.query_row("SELECT COUNT(*) FROM operator_decisions", [], |r| r.get(0))
.unwrap()
}
#[test]
fn decide_route_writes_decision() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session_with_gate(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&ref=1&verdict=approve";
let resp = post_dashboard(port, "/decide/gate", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 303"),
"PRG redirect on a successful decide: {resp}"
);
let conn = Connection::open(&db).unwrap();
let (dtype, target, origin, verified): (String, i64, String, String) = conn
.query_row(
"SELECT od.decision_type, od.target_work_event_id, a.command_origin, a.verified_by
FROM operator_decisions od
JOIN audit_records a ON a.audit_id = od.audit_id
WHERE od.session_id = 's1'
ORDER BY od.created_at DESC LIMIT 1",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
)
.unwrap();
assert_eq!(dtype, "gate-decision");
assert_eq!(target, 1, "the decision overlays the gate work_event id 1");
assert_eq!(
origin, "operator",
"decide hardcodes command_origin=operator"
);
assert_eq!(verified, "operator", "a decision is operator-verified");
}
#[test]
fn decide_route_rejects_wrong_kind_ref() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session_with_gate(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&ref=2&verdict=approve";
let resp = post_dashboard(port, "/decide/gate", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 400") || resp.contains("HTTP/1.1 404"),
"wrong-kind ref rejected before spawn: {resp}"
);
assert_eq!(
operator_decision_count(&db),
0,
"a wrong-kind ref must write no decision"
);
}
#[test]
fn decide_route_rejects_offenum_mode() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session_with_gate(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&to=banana";
let resp = post_dashboard(port, "/decide/mode", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 400") || resp.contains("HTTP/1.1 404"),
"off-enum mode rejected: {resp}"
);
assert_eq!(
operator_decision_count(&db),
0,
"an off-enum mode must write no decision"
);
}
#[test]
fn decide_route_rejects_unknown_type() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session_with_gate(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&ref=1&verdict=approve";
let resp = post_dashboard(port, "/decide/bogus", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 400"),
"an unknown decision type is rejected with 400: {resp}"
);
assert_eq!(
operator_decision_count(&db),
0,
"an unknown decision type must write no decision"
);
}
#[test]
fn decide_route_requires_csrf_and_host() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session_with_gate(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&ref=1&verdict=approve";
let bad_token = post_dashboard_raw(port, &format!(
"POST /decide/gate HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nOrigin: http://127.0.0.1:{port}\r\nX-Zynk-CSRF: wrong\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{form}",
form.len()));
let no_origin = post_dashboard_raw(port, &format!(
"POST /decide/gate HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nX-Zynk-CSRF: {token}\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{form}",
form.len()));
let wrong_host = post_dashboard_raw(port, &format!(
"POST /decide/gate HTTP/1.1\r\nHost: evil.test\r\nOrigin: http://127.0.0.1:{port}\r\nX-Zynk-CSRF: {token}\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{form}",
form.len()));
child.kill().ok();
child.wait().ok();
assert!(
bad_token.contains("HTTP/1.1 403"),
"forged CSRF rejected: {bad_token}"
);
assert!(
no_origin.contains("HTTP/1.1 403"),
"missing Origin rejected: {no_origin}"
);
assert!(
wrong_host.contains("HTTP/1.1 403"),
"wrong Host rejected: {wrong_host}"
);
assert_eq!(
operator_decision_count(&db),
0,
"rejected POSTs wrote no decision"
);
}
#[test]
fn decide_route_disabled_without_allow_writes() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
seed_writable_session_with_gate(&root, &db);
let resp = fetch_db_dashboard_once_with_method(&db, "POST", "/decide/gate");
assert!(
resp.contains("HTTP/1.1 405"),
"writes disabled by default: {resp}"
);
assert_eq!(operator_decision_count(&db), 0, "a 405 wrote no decision");
}
fn get_dashboard_page(port: u16, route: &str) -> String {
let mut stream = TcpStream::connect(("127.0.0.1", port)).unwrap();
write!(
stream,
"GET {route} HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
)
.unwrap();
stream.shutdown(Shutdown::Write).unwrap();
let mut body = String::new();
stream.read_to_string(&mut body).unwrap();
body
}
#[test]
fn dashboard_renders_decision_controls_with_allow_writes() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session_with_gate(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let page = get_dashboard_page(port, "/?session=s1");
child.kill().ok();
child.wait().ok();
assert!(page.contains("HTTP/1.1 200"), "{page}");
assert!(
page.contains("action=\"/decide/gate\""),
"the gate card must carry a /decide/gate form under --allow-writes: {page}"
);
assert!(
page.contains(&format!("value=\"{token}\"")),
"the decide form must carry the per-serve CSRF token in a hidden field: {page}"
);
assert!(
page.contains("approve") && page.contains("request-changes"),
"the gate control must offer approve + request-changes: {page}"
);
assert!(
page.contains("action=\"/decide/mode\""),
"the page must carry a /decide/mode control under --allow-writes: {page}"
);
let readonly = fetch_db_dashboard_once(&db, "/?session=s1");
assert!(readonly.contains("HTTP/1.1 200"), "{readonly}");
assert!(
!readonly.contains("/decide/"),
"read-only dashboard renders NO decide form: {readonly}"
);
}
#[test]
fn decide_route_notify_allow_list() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session_with_gate(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&ref=1&verdict=approve¬ify=codex%3Aw1-9";
let resp = post_dashboard(port, "/decide/gate", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 400") || resp.contains("HTTP/1.1 404"),
"a free-typed notify target (not in the allow-list) is rejected: {resp}"
);
assert_eq!(
operator_decision_count(&db),
0,
"a rejected free-typed notify must write no decision (rejected pre-spawn)"
);
}
#[test]
fn decide_route_rejects_forged_redirect_target() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session_with_gate(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&to=evil%3Aw999&reason=handoff";
let resp = post_dashboard(port, "/decide/redirect", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 400"),
"a forged free-typed redirect target (not in the allow-list) is rejected: {resp}"
);
assert_eq!(
operator_decision_count(&db),
0,
"a rejected free-typed redirect must write no decision (rejected pre-spawn)"
);
}
#[test]
fn decide_route_accepts_known_redirect_target() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db);
let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&to=codex%3Aw1-1&reason=handoff";
let resp = post_dashboard(port, "/decide/redirect", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 303"),
"a known-target redirect is accepted (303-PRG): {resp}"
);
let conn = Connection::open(&db).unwrap();
let (dtype, target_agent): (String, String) = conn
.query_row(
"SELECT decision_type, target_agent FROM operator_decisions
WHERE session_id = 's1' ORDER BY created_at DESC LIMIT 1",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(dtype, "redirect", "a redirect decision row landed");
assert_eq!(
target_agent, "codex:w1-1",
"the redirect decision targets the allow-listed agent:address"
);
}
fn add_non_sendable_sentinels(db: &Path) {
let conn = Connection::open(db).unwrap();
conn.execute(
"INSERT INTO audit_records (
audit_id, previous_audit_id, session_id, source_agent_id, target_agent_id,
source_address, target_address, transport, workspace_id, mid, record_type,
command_origin, payload_hash, payload_redaction_policy, content_size,
delivery_status, observed_by, verified_by, timestamp
)
VALUES (
'aud-decide', NULL, 's1', 'operator', 'none', 'cli', 'none', 'none', 'none',
'm-decide', 'gate-decision', 'operator', 'sha256:test', 'full', 12,
'observed', 'operator', 'operator', '2026-05-29T01:01:00Z'
)",
[],
)
.unwrap();
conn.execute(
"INSERT INTO audit_records (
audit_id, previous_audit_id, session_id, source_agent_id, target_agent_id,
source_address, target_address, transport, workspace_id, mid, record_type,
command_origin, payload_hash, payload_redaction_policy, content_size,
delivery_status, observed_by, verified_by, timestamp
)
VALUES (
'aud-reveal', NULL, 's1', 'operator', 'none', 'cli', 'none', 'none', 'none',
'm-reveal', 'reveal', 'operator', 'sha256:test', 'full', 12,
'observed', 'operator', 'operator', '2026-05-29T01:02:00Z'
)",
[],
)
.unwrap();
}
#[test]
fn dashboard_send_rejects_non_sendable_target() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db); add_non_sendable_sentinels(&db); let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form_none = "session=s1&to=none%3Anone&type=status-update&body=x";
let resp_none = post_dashboard(port, "/send", &token, form_none);
let form_op = "session=s1&to=operator%3Acli&type=status-update&body=x";
let resp_op = post_dashboard(port, "/send", &token, form_op);
child.kill().ok();
child.wait().ok();
assert!(
resp_none.contains("HTTP/1.1 400") || resp_none.contains("HTTP/1.1 404"),
"to=none:none rejected before spawn: {resp_none}"
);
assert!(
resp_op.contains("HTTP/1.1 400") || resp_op.contains("HTTP/1.1 404"),
"to=operator:cli rejected before spawn: {resp_op}"
);
let conn = Connection::open(&db).unwrap();
let operator: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE command_origin='operator' AND verified_by='helper-tool'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
operator, 0,
"no operator send audit for non-sendable targets"
);
}
#[test]
fn dashboard_send_accepts_real_herdr_target() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db); add_non_sendable_sentinels(&db); let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form = "session=s1&to=codex%3Aw1-1&type=status-update&body=x";
let resp = post_dashboard(port, "/send", &token, form);
child.kill().ok();
child.wait().ok();
assert!(
resp.contains("HTTP/1.1 303"),
"a real herdr target is accepted (303-PRG) despite the sentinels present: {resp}"
);
}
#[test]
fn dashboard_decide_redirect_rejects_non_sendable_target() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
seed_writable_session(&root, &db); add_non_sendable_sentinels(&db); let (token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let form_none = "session=s1&to=none%3Anone&reason=handoff";
let resp_none = post_dashboard(port, "/decide/redirect", &token, form_none);
let form_op = "session=s1&to=operator%3Acli&reason=handoff";
let resp_op = post_dashboard(port, "/decide/redirect", &token, form_op);
child.kill().ok();
child.wait().ok();
assert!(
resp_none.contains("HTTP/1.1 400"),
"redirect to=none:none rejected before spawn: {resp_none}"
);
assert!(
resp_op.contains("HTTP/1.1 400"),
"redirect to=operator:cli rejected before spawn: {resp_op}"
);
assert_eq!(
operator_decision_count(&db),
0,
"a rejected non-sendable redirect must write no decision (rejected pre-spawn)"
);
}
#[test]
fn db_import_outputs_imports_work_events() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let think = run_zynk(&[
"report",
"think",
"--root",
&root_str,
"--no-db",
"--session-id",
"fileonly",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
"hello",
]);
assert!(think.status.success(), "{}", stderr(&think));
let art = run_zynk(&[
"report",
"artifact",
"--root",
&root_str,
"--no-db",
"--session-id",
"fileonly",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:01Z",
"--file",
"src/mid.rs:96:1",
"--file",
"src/lib.rs:3:0",
]);
assert!(art.status.success(), "{}", stderr(&art));
assert!(
root.join("sessions/fileonly/work.md").exists(),
"report --no-db must still write work.md"
);
assert!(!db_path.exists(), "report --no-db must not create any DB");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let total: i64 = connection
.query_row(
"SELECT COUNT(*) FROM work_events WHERE session_id = 'fileonly'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(total, 2, "both file-only work events must be imported");
let kinds: Vec<(String, String)> = {
let mut stmt = connection
.prepare(
"SELECT kind, timestamp FROM work_events
WHERE session_id = 'fileonly' ORDER BY timestamp",
)
.unwrap();
stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
.unwrap()
.map(Result::unwrap)
.collect()
};
assert_eq!(
kinds,
vec![
("think".to_string(), "2026-05-31T00:00:00Z".to_string()),
("artifact".to_string(), "2026-05-31T00:00:01Z".to_string()),
]
);
let actor: String = connection
.query_row(
"SELECT actor_agent_id FROM work_events
WHERE session_id = 'fileonly' AND kind = 'think'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(actor, "codex", "the work.md actor header must be preserved");
let arts: i64 = connection
.query_row(
"SELECT COUNT(*) FROM artifacts WHERE session_id = 'fileonly'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(arts, 2, "artifact import must dual-project into artifacts");
let first_path: String = connection
.query_row(
"SELECT path FROM artifacts WHERE session_id = 'fileonly'
ORDER BY path LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(first_path, "src/lib.rs");
}
#[test]
fn db_import_outputs_work_events_idempotent() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let think = run_zynk(&[
"report",
"think",
"--root",
&root_str,
"--no-db",
"--session-id",
"idem",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
"hello",
]);
assert!(think.status.success(), "{}", stderr(&think));
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let count_rows = |connection: &Connection| -> i64 {
connection
.query_row(
"SELECT COUNT(*) FROM work_events WHERE session_id = 'idem'",
[],
|row| row.get(0),
)
.unwrap()
};
for ts in ["2026-05-31T02:00:00Z", "2026-05-31T03:00:00Z"] {
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
ts,
]);
assert!(import.status.success(), "{}", stderr(&import));
}
let connection = Connection::open(&db_path).unwrap();
assert_eq!(
count_rows(&connection),
1,
"re-import must not duplicate work_events (content_hash INSERT OR IGNORE)"
);
}
#[test]
fn db_import_outputs_skips_malformed_work_block() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let think = run_zynk(&[
"report",
"think",
"--root",
&root_str,
"--no-db",
"--session-id",
"mixed",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
"good event",
]);
assert!(think.status.success(), "{}", stderr(&think));
let work_md = root.join("sessions/mixed/work.md");
let mut existing = fs::read_to_string(&work_md).unwrap();
existing.push_str(
"\n```work-event\nkind=think\nactor=codex\ntimestamp=2026-05-31T00:00:05Z\n\
this: is: not: valid: yaml: for: a: typed: payload\n```\n",
);
fs::write(&work_md, &existing).unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let rows: Vec<(String, String)> = {
let mut stmt = connection
.prepare(
"SELECT kind, timestamp FROM work_events
WHERE session_id = 'mixed' ORDER BY timestamp",
)
.unwrap();
stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
.unwrap()
.map(Result::unwrap)
.collect()
};
assert_eq!(
rows,
vec![("think".to_string(), "2026-05-31T00:00:00Z".to_string())],
"malformed work block must be skipped, valid one kept"
);
}
#[test]
fn db_import_outputs_work_event_payload_with_equals_in_field() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let tool = run_zynk(&[
"report",
"tool",
"--root",
&root_str,
"--no-db",
"--session-id",
"eq",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:00Z",
"--name",
"run_tests",
"--arg",
"a=b c",
"--output",
"ok=yes: done",
]);
assert!(tool.status.success(), "{}", stderr(&tool));
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let (kind, payload): (String, String) = connection
.query_row(
"SELECT kind, payload FROM work_events WHERE session_id = 'eq'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap();
assert_eq!(kind, "tool");
assert!(
payload.contains("arg: a=b c") && payload.contains("ok=yes: done"),
"payload field values with `=` must round-trip: {payload}"
);
}
#[test]
fn db_import_outputs_skips_missing_header_work_block() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let think = run_zynk(&[
"report",
"think",
"--root",
&root_str,
"--no-db",
"--session-id",
"attackd",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
"good event",
]);
assert!(think.status.success(), "{}", stderr(&think));
let work_md = root.join("sessions/attackd/work.md");
let mut existing = fs::read_to_string(&work_md).unwrap();
existing.push_str(
"\n```work-event\nkind=think\nactor=codex\nkind: think\ntext: |-\n \
timestamp=1999-01-01T00:00:00Z\n real thought text\n```\n",
);
fs::write(&work_md, &existing).unwrap();
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let connection = Connection::open(&db_path).unwrap();
let rows: Vec<(String, String)> = {
let mut stmt = connection
.prepare(
"SELECT kind, timestamp FROM work_events
WHERE session_id = 'attackd' ORDER BY timestamp",
)
.unwrap();
stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
.unwrap()
.map(Result::unwrap)
.collect()
};
assert_eq!(
rows,
vec![("think".to_string(), "2026-05-31T00:00:00Z".to_string())],
"missing-header work block must be skipped, not silently imported corrupted"
);
let stolen: i64 = connection
.query_row(
"SELECT COUNT(*) FROM work_events
WHERE session_id = 'attackd' AND timestamp LIKE '1999-%'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
stolen, 0,
"no row may carry a timestamp stolen from corrupted payload content"
);
}
#[test]
fn db_import_outputs_preserves_fenced_code_in_payload() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db_path = tmp.path().join("state/zynk.db");
let root_str = root.to_str().unwrap().to_string();
let fenced_text = "before\n```\ncode\n```\nafter";
let think = run_zynk(&[
"report",
"think",
"--root",
&root_str,
"--no-db",
"--session-id",
"fence",
"--actor",
"codex",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
fenced_text,
]);
assert!(think.status.success(), "{}", stderr(&think));
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
assert!(
stdout(&import).contains("warnings: 0"),
"fenced payload must import cleanly: {}",
stdout(&import)
);
let connection = Connection::open(&db_path).unwrap();
let payload: String = connection
.query_row(
"SELECT payload FROM work_events WHERE session_id = 'fence' AND kind = 'think'",
[],
|row| row.get(0),
)
.unwrap();
let restored = reconstruct_think_text(&payload);
assert_eq!(
restored, fenced_text,
"fenced think payload must round-trip in full, not truncate at `before`: {payload:?}"
);
}
fn reconstruct_think_text(payload: &str) -> String {
let mut lines = payload.lines();
let mut value: Option<String> = None;
while let Some(line) = lines.next() {
if let Some(rest) = line.strip_prefix("text: ") {
if rest == "|-" || rest == "|" {
let mut body: Vec<String> = Vec::new();
for body_line in lines.by_ref() {
match body_line.strip_prefix(" ") {
Some(content) => body.push(content.to_string()),
None if body_line.is_empty() => body.push(String::new()),
None => break,
}
}
value = Some(body.join("\n"));
} else {
value = Some(rest.to_string());
}
break;
}
}
value.unwrap_or_default()
}
#[test]
fn decide_gate_writes_proof_and_typed_row() {
let tmp = tempdir().unwrap();
let seed = run_zynk_in(
&[
"report",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--summary",
"ready to merge",
"--proposer",
"claude",
"--action",
"Approve",
"--action",
"Request changes",
],
tmp.path(),
);
assert!(seed.status.success(), "{}", stderr(&seed));
let out = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--verdict",
"approve",
],
tmp.path(),
);
assert!(out.status.success(), "{}", stderr(&out));
let db = tmp.path().join(".zynk/zynk.db");
let conn = Connection::open(&db).unwrap();
let (delivery_status, verified_by, command_origin, record_type): (
String,
String,
String,
String,
) = conn
.query_row(
"SELECT delivery_status, verified_by, command_origin, record_type
FROM audit_records WHERE record_type='gate-decision'",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
)
.unwrap();
assert_eq!(delivery_status, "observed");
assert_eq!(verified_by, "operator");
assert_eq!(command_origin, "operator");
assert_eq!(record_type, "gate-decision");
let (twe, verdict, notification_status, decision_status): (i64, String, String, String) = conn
.query_row(
"SELECT target_work_event_id, verdict, notification_status, decision_status
FROM operator_decisions",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
)
.unwrap();
assert_eq!(twe, 1, "the gate decision binds the seeded gate work_event");
assert_eq!(verdict, "approve");
assert_eq!(notification_status, "not-requested");
assert_eq!(decision_status, "decided");
}
#[test]
fn decide_created_at_is_canonical() {
let tmp = tempdir().unwrap();
let seed = run_zynk_in(
&[
"report",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--summary",
"ready to merge",
"--proposer",
"claude",
"--action",
"Approve",
"--action",
"Request changes",
],
tmp.path(),
);
assert!(seed.status.success(), "{}", stderr(&seed));
let out = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00+07:00",
"--ref",
"1",
"--verdict",
"approve",
],
tmp.path(),
);
assert!(out.status.success(), "{}", stderr(&out));
let db = tmp.path().join(".zynk/zynk.db");
let conn = Connection::open(&db).unwrap();
let (audit_id, created_at): (String, String) = conn
.query_row(
"SELECT audit_id, created_at FROM operator_decisions",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(
created_at, "2026-05-30T17:00:00Z",
"operator_decisions.created_at must be canonical UTC-Z, not the raw +07:00 offset"
);
let audit_timestamp: String = conn
.query_row(
"SELECT timestamp FROM audit_records WHERE audit_id = ?1",
[&audit_id],
|r| r.get(0),
)
.unwrap();
assert_eq!(
audit_timestamp, "2026-05-30T17:00:00Z",
"the decision audit row timestamp must match the canonical created_at"
);
assert_eq!(
created_at, audit_timestamp,
"created_at and the audit timestamp must agree"
);
}
#[test]
fn decide_gate_rejects_wrong_kind_ref_before_write() {
let tmp = tempdir().unwrap();
let seed = run_zynk_in(
&[
"report",
"think",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--text",
"weighing token bucket vs leaky bucket",
],
tmp.path(),
);
assert!(seed.status.success(), "{}", stderr(&seed));
let out = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--verdict",
"approve",
],
tmp.path(),
);
assert!(
!out.status.success(),
"decide gate against a non-gate ref must exit nonzero: {}",
stderr(&out)
);
let db = tmp.path().join(".zynk/zynk.db");
let conn = Connection::open(&db).unwrap();
let decisions: i64 = conn
.query_row("SELECT COUNT(*) FROM operator_decisions", [], |r| r.get(0))
.unwrap();
assert_eq!(
decisions, 0,
"a wrong-kind ref must be rejected BEFORE any decision row is written"
);
}
#[test]
fn decide_notify_failure_records_failed_never_sent() {
let tmp = tempdir().unwrap();
let seed = run_zynk_in(
&[
"report",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--summary",
"ready to merge",
"--proposer",
"claude",
"--action",
"Approve",
"--action",
"Request changes",
],
tmp.path(),
);
assert!(seed.status.success(), "{}", stderr(&seed));
let out = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--verdict",
"approve",
"--notify-pane",
"w-bad-1",
"--notify-to",
"claude:w-bad-1",
"--herdr-bin",
"/usr/bin/false",
],
tmp.path(),
);
assert!(
!out.status.success(),
"a failed notify must exit nonzero (decision durable, notify failed): {}",
stderr(&out)
);
let db = tmp.path().join(".zynk/zynk.db");
let conn = Connection::open(&db).unwrap();
let delivery_status: String = conn
.query_row(
"SELECT delivery_status FROM audit_records WHERE record_type='gate-decision'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
delivery_status, "observed",
"the decision audit row stays observed/durable even though the notify failed"
);
let (ns, err, audit_id): (String, Option<String>, Option<String>) = conn
.query_row(
"SELECT notification_status, notification_error, notification_audit_id
FROM operator_decisions",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)),
)
.unwrap();
assert_eq!(
ns, "failed",
"a failed notify records notification_status=failed"
);
assert!(
err.is_some_and(|e| !e.trim().is_empty()),
"a failed notify records a non-null notification_error"
);
assert!(
audit_id.is_none(),
"a failed notify links NO notification audit_id (there is no sent proof)"
);
let sent_rows: i64 = conn
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE delivery_status='sent'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
sent_rows, 0,
"a failed notify must NEVER write a delivery_status=sent audit (ADR 024)"
);
}
#[test]
fn decide_notify_success_links_sent_audit() {
let tmp = tempdir().unwrap();
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let seed = run_zynk_in(
&[
"report",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--summary",
"ready to merge",
"--proposer",
"claude",
"--action",
"Approve",
"--action",
"Request changes",
],
tmp.path(),
);
assert!(seed.status.success(), "{}", stderr(&seed));
let out = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--verdict",
"approve",
"--notify-pane",
"w-good-1",
"--notify-to",
"claude:w-good-1",
"--herdr-bin",
herdr.to_str().unwrap(),
],
tmp.path(),
);
assert!(out.status.success(), "{}", stderr(&out));
let db = tmp.path().join(".zynk/zynk.db");
let conn = Connection::open(&db).unwrap();
let (ns, audit_id, notif_mid): (String, Option<String>, Option<String>) = conn
.query_row(
"SELECT notification_status, notification_audit_id, notification_mid
FROM operator_decisions",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)),
)
.unwrap();
assert_eq!(
ns, "sent",
"a successful notify records notification_status=sent"
);
let audit_id = audit_id.expect("a successful notify links a notification_audit_id");
let notif_mid = notif_mid.expect("a successful notify records the notification mid");
let (delivery_status, verified_by, mid): (String, String, String) = conn
.query_row(
"SELECT delivery_status, verified_by, mid FROM audit_records WHERE audit_id = ?1",
[&audit_id],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)),
)
.unwrap();
assert_eq!(
delivery_status, "sent",
"the linked notification audit row is the real delivery_status=sent proof"
);
assert_eq!(
verified_by, "helper-tool",
"ADR 024: zynk dispatched the notification transport, so verified_by=helper-tool"
);
assert_eq!(
mid, notif_mid,
"the linked audit row is keyed by the notification mid the send used"
);
}
#[test]
fn decide_notify_rejects_no_db() {
let tmp = tempdir().unwrap();
let out = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--no-db",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--verdict",
"approve",
"--notify-pane",
"w-1",
"--notify-to",
"claude:w-1",
],
tmp.path(),
);
assert!(
!out.status.success(),
"--notify-* with --no-db must be a usage error (exit nonzero): {}",
stderr(&out)
);
assert!(
stderr(&out).contains("--no-db"),
"the rejection explains the --no-db conflict: {}",
stderr(&out)
);
let audit_path = tmp.path().join("outputs/sessions/s1/audit.md");
assert!(
!audit_path.exists(),
"--notify + --no-db is rejected BEFORE the file write — no audit.md"
);
}
#[test]
fn decide_notify_softdegrade_fails_loud() {
let tmp = tempdir().unwrap();
fs::write(tmp.path().join(".zynk"), "not a directory").unwrap();
let out = run_zynk_in(
&[
"decide",
"interrupt",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--reason",
"pause",
"--notify-pane",
"w-1",
"--notify-to",
"claude:w-1",
"--herdr-bin",
"/usr/bin/false",
],
tmp.path(),
);
assert!(
!out.status.success(),
"a soft-degraded default projection + --notify must FAIL LOUD (exit nonzero): {}",
stderr(&out)
);
assert!(
stderr(&out).contains("notification NOT attempted"),
"the failure clearly states the notify was not attempted: {}",
stderr(&out)
);
let audit_path = tmp.path().join("outputs/sessions/s1/audit.md");
assert!(
audit_path.exists(),
"the decision stays durable on a soft-degrade — audit.md is written"
);
}
#[test]
fn decide_retain_writes_vault() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let seed = run_zynk_in(
&[
"report",
"gate",
"--root",
"outputs",
"--db",
db,
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--summary",
"ready to merge",
"--proposer",
"claude",
"--action",
"Approve",
"--action",
"Request changes",
],
tmp.path(),
);
assert!(seed.status.success(), "{}", stderr(&seed));
let out = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--db",
db,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--verdict",
"approve",
"--retain-custody",
],
tmp.path(),
);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(db).unwrap();
let audit_id: String = conn
.query_row(
"SELECT audit_id FROM audit_records WHERE record_type='gate-decision'",
[],
|r| r.get(0),
)
.expect("the durable decision proof row must exist");
let ciphertext: Vec<u8> = conn
.query_row(
"SELECT ciphertext FROM custody_vault WHERE audit_id=?1",
[&audit_id],
|r| r.get(0),
)
.expect("custody_vault must have a row for the retained decision record");
assert!(
!ciphertext.is_empty(),
"retained decision ciphertext must not be empty"
);
assert!(
!ciphertext
.windows(b"approve".len())
.any(|w| w == b"approve"),
"the stored vault ciphertext must NOT contain the literal decision plaintext"
);
}
#[test]
fn decide_retain_failure_skips_notify() {
let cwd = tempdir().unwrap();
let herdr = fake_herdr(cwd.path(), "echo sent\n");
let keydir = cwd.path().join("keydir");
fs::create_dir(&keydir).unwrap();
fs::set_permissions(&keydir, fs::Permissions::from_mode(0o700)).unwrap();
let seed = run_zynk_in(
&[
"report",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--title",
"Approve plan",
"--summary",
"ready to merge",
"--proposer",
"claude",
"--action",
"Approve",
"--action",
"Request changes",
],
cwd.path(),
);
assert!(seed.status.success(), "{}", stderr(&seed));
let out = run_zynk_in(
&[
"decide",
"gate",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--verdict",
"approve",
"--notify-pane",
"w-good-1",
"--notify-to",
"claude:w-good-1",
"--herdr-bin",
herdr.to_str().unwrap(),
"--retain-custody",
"--custody-key-file",
keydir.to_str().unwrap(),
],
cwd.path(),
);
assert_ne!(
out.status.code(),
Some(0),
"a retain failure must exit nonzero (loud): {}",
stderr(&out)
);
assert!(
stderr(&out).contains("record written but custody NOT retained"),
"the failure must be loud + explicit the record is durable but custody not retained: {}",
stderr(&out)
);
let db_path = cwd.path().join(".zynk/zynk.db");
let conn = Connection::open(&db_path).unwrap();
let (audit_id, delivery_status): (String, String) = conn
.query_row(
"SELECT audit_id, delivery_status FROM audit_records WHERE record_type='gate-decision'",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.expect("the decision proof row must be durable even when custody fails");
assert_eq!(
delivery_status, "observed",
"the decision audit row stays observed/durable"
);
let vault_rows: i64 = conn
.query_row(
"SELECT count(*) FROM custody_vault WHERE audit_id=?1",
[&audit_id],
|r| r.get(0),
)
.unwrap();
assert_eq!(
vault_rows, 0,
"a failed retention must leave NO custody_vault row (no silent no-retain)"
);
let notification_status: String = conn
.query_row(
"SELECT notification_status FROM operator_decisions WHERE audit_id=?1",
[&audit_id],
|r| r.get(0),
)
.unwrap();
assert_eq!(
notification_status, "not-requested",
"retain failure must SKIP the notify hop — notification_status stays not-requested"
);
let sent_rows: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE delivery_status='sent'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
sent_rows, 0,
"retain failure must SKIP notify — no delivery_status=sent notify audit is written"
);
}
#[test]
fn decide_no_db_with_retain_is_usage_error() {
let tmp = tempdir().unwrap();
let out = run_zynk_in(
&[
"decide",
"interrupt",
"--root",
"outputs",
"--no-db",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--reason",
"pause",
"--retain-custody",
],
tmp.path(),
);
assert_eq!(
out.status.code(),
Some(2),
"--no-db + --retain-custody must be a usage error (exit 2): {}",
stderr(&out)
);
assert!(
stderr(&out).contains("--retain-custody") && stderr(&out).contains("--no-db"),
"the usage error must name both flags: {}",
stderr(&out)
);
let audit_path = tmp.path().join("outputs/sessions/s1/audit.md");
assert!(
!audit_path.exists(),
"--no-db + --retain-custody is rejected BEFORE the file write — no audit.md"
);
}
#[test]
fn decide_mode_interrupt_redirect_conflict_round_trip() {
let tmp = tempdir().unwrap();
let seed = run_zynk_in(
&[
"report",
"conflict",
"--root",
"outputs",
"--session-id",
"s1",
"--actor",
"claude",
"--timestamp",
"2026-05-31T00:00:00Z",
"--topic",
"Limiter",
"--position",
"claude:token-bucket:fast:mem",
"--recommended",
"token-bucket",
],
tmp.path(),
);
assert!(seed.status.success(), "{}", stderr(&seed));
let conflict = run_zynk_in(
&[
"decide",
"conflict",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--ref",
"1",
"--resolution",
"token-bucket",
],
tmp.path(),
);
assert!(conflict.status.success(), "{}", stderr(&conflict));
let mode = run_zynk_in(
&[
"decide",
"mode",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T02:00:00Z",
"--to",
"review",
],
tmp.path(),
);
assert!(mode.status.success(), "{}", stderr(&mode));
let interrupt = run_zynk_in(
&[
"decide",
"interrupt",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T03:00:00Z",
"--reason",
"stop",
],
tmp.path(),
);
assert!(interrupt.status.success(), "{}", stderr(&interrupt));
let redirect = run_zynk_in(
&[
"decide",
"redirect",
"--root",
"outputs",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T04:00:00Z",
"--to",
"codex",
"--reason",
"handoff",
],
tmp.path(),
);
assert!(redirect.status.success(), "{}", stderr(&redirect));
let db = tmp.path().join(".zynk/zynk.db");
let conn = Connection::open(&db).unwrap();
let total: i64 = conn
.query_row("SELECT COUNT(*) FROM operator_decisions", [], |r| r.get(0))
.unwrap();
assert_eq!(total, 4, "all four decisions project typed rows");
let (mode_count, mode_to): (i64, String) = conn
.query_row(
"SELECT COUNT(*), MAX(mode_to) FROM operator_decisions
WHERE decision_type='mode-switch'",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(mode_count, 1, "exactly one mode-switch decision");
assert_eq!(mode_to, "review", "the mode-switch row records mode_to");
let (twe, resolution): (i64, String) = conn
.query_row(
"SELECT target_work_event_id, resolution FROM operator_decisions
WHERE decision_type='conflict-resolve'",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(twe, 1, "the conflict resolution binds the seeded conflict");
assert_eq!(resolution, "token-bucket");
let target_agent: String = conn
.query_row(
"SELECT target_agent FROM operator_decisions WHERE decision_type='redirect'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
target_agent, "codex",
"the redirect row records target_agent"
);
let reason: String = conn
.query_row(
"SELECT reason FROM operator_decisions WHERE decision_type='interrupt'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(reason, "stop", "the interrupt row records its reason");
}
#[test]
fn decide_mode_rejects_offenum_to() {
let tmp = tempdir().unwrap();
let out = run_zynk_in(
&[
"decide",
"mode",
"--no-db",
"--session-id",
"s1",
"--to",
"banana",
"--timestamp",
"2026-05-31T00:00:00Z",
],
tmp.path(),
);
assert!(
!out.status.success(),
"an off-enum mode must exit nonzero: {}",
stderr(&out)
);
let audit_path = tmp.path().join("outputs/sessions/s1/audit.md");
assert!(
!audit_path.exists(),
"an off-enum mode must be rejected BEFORE the file write — no audit.md"
);
}
#[test]
fn decide_mode_accepts_decide_and_debug() {
for mode in ["decide", "debug"] {
let tmp = tempdir().unwrap();
let out = run_zynk_in(
&[
"decide",
"mode",
"--no-db",
"--session-id",
"s1",
"--to",
mode,
"--timestamp",
"2026-05-31T00:00:00Z",
],
tmp.path(),
);
assert!(
out.status.success(),
"ADR 020 canonical mode `{mode}` must be accepted (exit 0): {}",
stderr(&out)
);
let audit_path = tmp.path().join("outputs/sessions/s1/audit.md");
assert!(
audit_path.exists(),
"an accepted `decide mode --to {mode}` writes the decision audit.md"
);
}
}
#[test]
fn decide_mode_rejects_execute_and_plan() {
for mode in ["execute", "plan"] {
let tmp = tempdir().unwrap();
let out = run_zynk_in(
&[
"decide",
"mode",
"--no-db",
"--session-id",
"s1",
"--to",
mode,
"--timestamp",
"2026-05-31T00:00:00Z",
],
tmp.path(),
);
assert!(
!out.status.success(),
"non-protocol mode `{mode}` must be rejected (exit nonzero): {}",
stderr(&out)
);
let audit_path = tmp.path().join("outputs/sessions/s1/audit.md");
assert!(
!audit_path.exists(),
"a rejected `decide mode --to {mode}` writes NO audit.md (rejected before write)"
);
}
}
#[test]
fn audit_rejects_sent_decision_record_type() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--no-db",
"--session-id",
"s1",
"--source-agent",
"operator",
"--source-address",
"cli",
"--target-agent",
"none",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"m1",
"--type",
"gate-decision",
"--command-origin",
"operator",
"--payload",
"x",
"--delivery-status",
"sent",
"--observed-by",
"operator",
"--verified-by",
"operator",
]);
assert!(
!output.status.success(),
"a decision record_type with delivery_status=sent must be rejected (forged 'sent decision' proof): {}",
stderr(&output)
);
let audit_path = tmp.path().join("sessions/s1/audit.md");
assert!(
!audit_path.exists(),
"a sent decision must be rejected BEFORE the file write — no audit.md"
);
}
#[test]
fn audit_accepts_sent_non_decision_record_type() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--no-db",
"--session-id",
"s1",
"--source-agent",
"codex",
"--source-address",
"w-2",
"--target-agent",
"claude",
"--target-address",
"w-1",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"ack",
"--command-origin",
"helper-tool",
"--payload",
"x",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
]);
assert!(
output.status.success(),
"a non-decision (message) record_type with delivery_status=sent is the audited-send shape and must still validate: {}",
stderr(&output)
);
let audit_path = tmp.path().join("sessions/s1/audit.md");
assert!(
audit_path.exists(),
"an accepted sent message audit must write its audit.md"
);
}
#[test]
fn audit_rejects_sent_reveal_record_type() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let db = tmp.path().join("zynk.db");
let db = db.to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--db",
&db,
"--session-id",
"s1",
"--audit-id",
"forged-reveal",
"--source-agent",
"operator",
"--source-address",
"cli",
"--target-agent",
"none",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"forged-reveal",
"--type",
"reveal",
"--command-origin",
"operator",
"--payload",
"revealed a1",
"--payload-redaction-policy",
"full",
"--delivery-status",
"sent",
"--observed-by",
"operator",
"--verified-by",
"helper-tool",
"--ref",
"a1",
]);
assert!(
!output.status.success(),
"a reveal record_type with delivery_status=sent must be rejected (forged 'sent reveal' proof): {}",
stderr(&output)
);
let err = stderr(&output);
assert!(
err.contains("reveal") && err.contains("delivery_status=sent"),
"the usage error must name reveal + delivery_status=sent: {err}"
);
let audit_path = tmp.path().join("sessions/s1/audit.md");
assert!(
!audit_path.exists(),
"a sent reveal must be rejected BEFORE the file write — no audit.md"
);
if std::path::Path::new(&*db).exists() {
let conn = Connection::open(&*db).unwrap();
let forged: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id = 'forged-reveal'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
forged, 0,
"a rejected sent reveal must leave NO audit_records row for forged-reveal"
);
}
}
#[test]
fn audit_rejects_sent_participant_overlay_record_type() {
let tmp = tempdir().unwrap();
let root = tmp.path().to_string_lossy();
let db = tmp.path().join("zynk.db");
let db = db.to_string_lossy();
let output = run_zynk(&[
"audit",
"--root",
&root,
"--db",
&db,
"--session-id",
"s1",
"--audit-id",
"forged-overlay",
"--source-agent",
"operator",
"--source-address",
"cli",
"--target-agent",
"none",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"forged-overlay",
"--type",
"participant-overlay",
"--command-origin",
"operator",
"--payload",
"assigned codex independent",
"--payload-redaction-policy",
"full",
"--delivery-status",
"sent",
"--observed-by",
"operator",
"--verified-by",
"helper-tool",
"--ref",
"a1",
]);
assert!(
!output.status.success(),
"a participant-overlay record_type with delivery_status=sent must be rejected (forged 'sent overlay' proof): {}",
stderr(&output)
);
let err = stderr(&output);
assert!(
err.contains("participant-overlay") && err.contains("delivery_status=sent"),
"the usage error must name participant-overlay + delivery_status=sent: {err}"
);
let audit_path = tmp.path().join("sessions/s1/audit.md");
assert!(
!audit_path.exists(),
"a sent participant-overlay must be rejected BEFORE the file write — no audit.md"
);
if std::path::Path::new(&*db).exists() {
let conn = Connection::open(&*db).unwrap();
let forged: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id = 'forged-overlay'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
forged, 0,
"a rejected sent participant-overlay must leave NO audit_records row for forged-overlay"
);
}
}
#[test]
fn db_audit_append_rejects_sent_reveal_record_type() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
seed_db_agent(&connection, "operator");
drop(connection);
let output = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"forged-db-reveal",
"--source-agent-id",
"operator",
"--source-address",
"cli",
"--target-agent-id",
"operator",
"--target-address",
"cli",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"forged-db-reveal",
"--type",
"reveal",
"--command-origin",
"operator",
"--payload",
"revealed a1",
"--payload-redaction-policy",
"full",
"--delivery-status",
"sent",
"--observed-by",
"operator",
"--verified-by",
"helper-tool",
"--timestamp",
"2026-05-29T01:00:00Z",
]);
assert!(
!output.status.success(),
"db audit append of a reveal record_type with delivery_status=sent must be rejected (forged 'sent reveal' proof): {}",
stderr(&output)
);
let err = stderr(&output);
assert!(
err.contains("reveal") || err.contains("decision record_type"),
"the usage error must name the non-transport proof (reveal): {err}"
);
assert!(
err.contains("sent"),
"the usage error must name delivery_status=sent: {err}"
);
let connection = Connection::open(&db_path).unwrap();
let forged: i64 = connection
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id = 'forged-db-reveal'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
forged, 0,
"a rejected db-append sent reveal must leave NO audit_records row for forged-db-reveal"
);
}
#[test]
fn db_audit_append_rejects_sent_decision_record_type() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
seed_db_agent(&connection, "operator");
drop(connection);
let output = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"forged-db-decision",
"--source-agent-id",
"operator",
"--source-address",
"cli",
"--target-agent-id",
"operator",
"--target-address",
"cli",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"forged-db-decision",
"--type",
"gate-decision",
"--command-origin",
"operator",
"--payload",
"approved",
"--payload-redaction-policy",
"full",
"--delivery-status",
"sent",
"--observed-by",
"operator",
"--verified-by",
"helper-tool",
"--timestamp",
"2026-05-29T01:00:00Z",
]);
assert!(
!output.status.success(),
"db audit append of a decision record_type with delivery_status=sent must be rejected (forged 'sent decision' proof): {}",
stderr(&output)
);
let connection = Connection::open(&db_path).unwrap();
let forged: i64 = connection
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id = 'forged-db-decision'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
forged, 0,
"a rejected db-append sent decision must leave NO audit_records row for forged-db-decision"
);
}
#[test]
fn db_audit_append_accepts_sent_message_type() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
seed_db_agent(&connection, "codex");
seed_db_agent(&connection, "claude");
drop(connection);
let output = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"real-db-ack",
"--source-agent-id",
"codex",
"--source-address",
"codex-pane",
"--target-agent-id",
"claude",
"--target-address",
"claude-pane",
"--transport",
"herdr",
"--workspace-id",
"workspace-a",
"--mid",
"real-db-ack",
"--type",
"ack",
"--command-origin",
"helper-tool",
"--payload",
"ack",
"--payload-redaction-policy",
"full",
"--delivery-status",
"sent",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
"--timestamp",
"2026-05-29T01:00:00Z",
]);
assert!(
output.status.success(),
"a sent ack (real transported message_type) must still validate and insert: {}",
stderr(&output)
);
let connection = Connection::open(&db_path).unwrap();
let present: i64 = connection
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id = 'real-db-ack'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
present, 1,
"an accepted sent ack must persist its audit_records row"
);
}
#[test]
fn db_audit_append_overlay_no_leak_no_orphan() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
seed_db_agent(&connection, "operator");
drop(connection);
let overlay_payload = "overlay: trait\nsubject: codex\nasserter: operator\nasserter_kind: operator\ntrait_id: independent\nvalue: true\nsupersedes: null\n";
let output = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"db-overlay-ok",
"--source-agent-id",
"operator",
"--source-address",
"cli",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"db-overlay-ok",
"--type",
"participant-overlay",
"--command-origin",
"operator",
"--payload",
overlay_payload,
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"operator",
"--verified-by",
"operator",
"--timestamp",
"2026-05-29T01:00:00Z",
]);
assert!(
output.status.success(),
"a valid operator-grade overlay append must succeed (routed): {}",
stderr(&output)
);
let connection = Connection::open(&db_path).unwrap();
let messages: i64 = connection
.query_row(
"SELECT count(*) FROM messages WHERE session_id = 'session-a'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
messages, 0,
"a participant-overlay append must NOT leak a messages chat-feed row (ADR 036 D7)"
);
let typed: i64 = connection
.query_row(
"SELECT count(*) FROM participant_overlay WHERE audit_id = 'db-overlay-ok'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
typed, 1,
"a participant-overlay append must ROUTE through the typed projection (no orphan proof)"
);
let proof: i64 = connection
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id = 'db-overlay-ok'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
proof, 1,
"the routed append still writes the audit proof row"
);
}
#[test]
fn db_audit_append_self_granted_overlay_rejected() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
seed_db_agent(&connection, "operator");
drop(connection);
let overlay_payload = "overlay: trait\nsubject: operator\nasserter: operator\nasserter_kind: operator\ntrait_id: independent\nvalue: true\nsupersedes: null\n";
let output = run_zynk(&[
"db",
"--db",
db_path.to_str().unwrap(),
"audit",
"append",
"--session-id",
"session-a",
"--audit-id",
"db-overlay-selfgrant",
"--source-agent-id",
"operator",
"--source-address",
"cli",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"db-overlay-selfgrant",
"--type",
"participant-overlay",
"--command-origin",
"operator",
"--payload",
overlay_payload,
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"operator",
"--verified-by",
"operator",
"--timestamp",
"2026-05-29T01:00:00Z",
]);
assert!(
!output.status.success(),
"a self-granted overlay append must be rejected: {}",
stderr(&output)
);
let connection = Connection::open(&db_path).unwrap();
let typed: i64 = connection
.query_row(
"SELECT count(*) FROM participant_overlay WHERE audit_id = 'db-overlay-selfgrant'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(typed, 0, "a rejected self-grant leaves NO typed row");
let proof: i64 = connection
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id = 'db-overlay-selfgrant'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
proof, 0,
"the no-self-grant trigger rolls back the whole append (no orphan audit row)"
);
let messages: i64 = connection
.query_row(
"SELECT count(*) FROM messages WHERE session_id = 'session-a'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(messages, 0, "a rejected self-grant leaves NO messages leak");
}
#[test]
fn db_trigger_blocks_direct_sent_reveal_insert() {
let tmp = tempdir().unwrap();
let db_path = tmp.path().join("state/zynk.db");
let init = run_zynk(&["db", "--db", db_path.to_str().unwrap(), "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let connection = Connection::open(&db_path).unwrap();
seed_db_session(&connection);
insert_raw_audit_record_typed(&connection, "ok-msg", "ack", "sent", "helper-tool")
.expect("a sent ack via direct insert must be accepted (only the proof guard blocks)");
let forged = insert_raw_audit_record_typed(
&connection,
"forged-direct-reveal",
"reveal",
"sent",
"helper-tool",
);
assert!(
forged.is_err(),
"the v9 trigger must ABORT a direct INSERT of record_type=reveal, delivery_status=sent"
);
let present: i64 = connection
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id = 'forged-direct-reveal'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
present, 0,
"the aborted direct sent-reveal insert must leave NO audit_records row"
);
}
#[test]
fn audit_retain_custody_writes_vault_row() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"s1",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"note",
"--command-origin",
"agent",
"--payload",
"secret",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
"--retain-custody",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(db).unwrap();
let ciphertext: Vec<u8> = conn
.query_row(
"SELECT ciphertext FROM custody_vault WHERE audit_id='a1'",
[],
|row| row.get(0),
)
.expect("custody_vault must have a row for the retained record");
assert!(
!ciphertext.is_empty(),
"retained ciphertext must not be empty"
);
assert!(
!ciphertext.windows(b"secret".len()).any(|w| w == b"secret"),
"the stored vault ciphertext must NOT contain the literal plaintext bytes"
);
}
#[test]
fn audit_no_db_with_retain_is_usage_error() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let out = run_zynk(&[
"audit",
"--root",
root,
"--no-db",
"--session-id",
"s1",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"note",
"--command-origin",
"agent",
"--payload",
"secret",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
"--retain-custody",
]);
assert_eq!(
out.status.code(),
Some(2),
"--no-db + --retain-custody must be a usage error (exit 2): {}",
stderr(&out)
);
assert!(
stderr(&out).contains("--retain-custody") && stderr(&out).contains("--no-db"),
"the usage error must name both flags: {}",
stderr(&out)
);
let audit_path = tmp.path().join("outputs/sessions/s1/audit.md");
assert!(
!audit_path.exists(),
"a pre-write usage error must NOT have written any audit.md"
);
}
#[test]
fn audit_retain_no_plaintext_in_corpus() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"s1",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"note",
"--command-origin",
"agent",
"--payload",
"topsecret",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
"--retain-custody",
]);
assert!(out.status.success(), "{}", stderr(&out));
let audit_md = fs::read_to_string(tmp.path().join("outputs/sessions/s1/audit.md")).unwrap();
assert!(
!audit_md.contains("topsecret"),
"the audit.md artifact must redact the plaintext (hash-only), not leak it"
);
let conn = Connection::open(db).unwrap();
let audit_hits: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records
WHERE payload_hash LIKE '%topsecret%' OR payload_redaction_policy LIKE '%topsecret%'
OR coalesce(re,'') LIKE '%topsecret%' OR coalesce(ref,'') LIKE '%topsecret%'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
audit_hits, 0,
"no audit_records column may carry the plaintext"
);
let message_hits: i64 = conn
.query_row(
"SELECT count(*) FROM messages WHERE coalesce(payload_excerpt,'') LIKE '%topsecret%'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
message_hits, 0,
"messages.payload_excerpt must be NULL under hash-only — never the plaintext"
);
let ciphertext: Vec<u8> = conn
.query_row(
"SELECT ciphertext FROM custody_vault WHERE audit_id='a1'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
!ciphertext
.windows(b"topsecret".len())
.any(|w| w == b"topsecret"),
"even the vault must store CIPHERTEXT, never the plaintext bytes"
);
}
fn write_audit_record(
root: &str,
db: &str,
audit_id: &str,
payload: &str,
retain: bool,
) -> std::process::Output {
let mut args: Vec<&str> = vec![
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"s1",
"--audit-id",
audit_id,
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"note",
"--command-origin",
"agent",
"--payload",
payload,
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
];
if retain {
args.push("--retain-custody");
}
run_zynk(&args)
}
#[test]
fn reveal_round_trips_and_writes_proof() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = write_audit_record(root, db, "a1", "secret", true);
assert!(out.status.success(), "{}", stderr(&out));
let out = run_zynk(&["reveal", "a1", "--db", db, "--root", root]);
assert!(
out.status.success(),
"reveal of a retained record must succeed: {}",
stderr(&out)
);
assert!(
stdout(&out).contains("secret"),
"reveal must emit the retained plaintext to stdout: {:?}",
stdout(&out)
);
let conn = Connection::open(db).unwrap();
let proof: (String, String, String, String, Option<String>) = conn
.query_row(
"SELECT record_type, command_origin, verified_by, delivery_status, ref
FROM audit_records WHERE record_type='reveal'",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?, r.get(4)?)),
)
.expect("a reveal proof must be recorded");
assert_eq!(proof.0, "reveal", "record_type");
assert_eq!(proof.1, "operator", "command_origin");
assert_eq!(proof.2, "operator", "verified_by");
assert_eq!(
proof.3, "observed",
"delivery_status (never sent — ADR 024)"
);
assert_eq!(
proof.4.as_deref(),
Some("a1"),
"ref must point at the revealed audit_id"
);
let proof_excerpt: i64 = conn
.query_row(
"SELECT count(*) FROM messages WHERE coalesce(payload_excerpt,'') LIKE '%secret%'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
proof_excerpt, 0,
"the reveal proof must store the descriptor, NEVER the plaintext"
);
let audit_plaintext: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records
WHERE payload_hash LIKE '%secret%' OR coalesce(ref,'') LIKE '%secret%'
OR coalesce(re,'') LIKE '%secret%' OR coalesce(mode,'') LIKE '%secret%'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
audit_plaintext, 0,
"no audit_records column may carry the plaintext"
);
let audit_md = fs::read_to_string(tmp.path().join("outputs/sessions/s1/audit.md")).unwrap();
assert!(
!audit_md.contains("secret"),
"neither the original record nor the reveal proof may leak the plaintext into audit.md"
);
}
#[test]
fn reveal_proof_uses_nontransport_sentinels() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = write_audit_record(root, db, "a1", "secret", true);
assert!(out.status.success(), "{}", stderr(&out));
let out = run_zynk(&["reveal", "a1", "--db", db, "--root", root]);
assert!(
out.status.success(),
"reveal of a retained record must succeed: {}",
stderr(&out)
);
let conn = Connection::open(db).unwrap();
let blank_agents: i64 = conn
.query_row("SELECT count(*) FROM agents WHERE agent_id = ''", [], |r| {
r.get(0)
})
.unwrap();
assert_eq!(
blank_agents, 0,
"no agents row may have a blank '' agent_id (reveal must use the 'none' sentinel)"
);
let target_agent_id: String = conn
.query_row(
"SELECT target_agent_id FROM audit_records WHERE record_type='reveal'",
[],
|r| r.get(0),
)
.expect("a reveal proof row must exist");
assert_eq!(
target_agent_id, "none",
"the reveal proof target_agent_id must be the 'none' non-transport sentinel, not ''"
);
let mut statement = conn
.prepare(
"SELECT DISTINCT agent_id || ':' || address AS target FROM (
SELECT target_agent_id AS agent_id, target_address AS address
FROM audit_records WHERE session_id = ?1
UNION
SELECT source_agent_id AS agent_id, source_address AS address
FROM audit_records WHERE session_id = ?1
)
WHERE agent_id IS NOT NULL AND address IS NOT NULL
ORDER BY target",
)
.unwrap();
let targets: Vec<String> = statement
.query_map(params!["s1"], |row| row.get::<_, String>(0))
.unwrap()
.map(|r| r.unwrap())
.collect();
assert!(
!targets.iter().any(|t| t == ":"),
"the session known targets must not contain a bogus ':' entry: {targets:?}"
);
}
#[test]
fn reveal_round_trips_payload_file_retain() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let body = "top-secret-file-body";
let payload_path = tmp.path().join("payload.txt");
fs::write(&payload_path, body).unwrap();
let out = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"s1",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"note",
"--command-origin",
"agent",
"--payload-file",
payload_path.to_str().unwrap(),
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
"--retain-custody",
]);
assert!(
out.status.success(),
"audit --payload-file --retain-custody must succeed: {}",
stderr(&out)
);
let out = run_zynk(&["reveal", "a1", "--db", db, "--root", root]);
assert!(
out.status.success(),
"reveal of a --payload-file retained record must succeed: {}",
stderr(&out)
);
assert_eq!(
stdout(&out),
body,
"reveal must emit the EXACT file body to stdout: {:?}",
stdout(&out)
);
let conn = Connection::open(db).unwrap();
let proof: (String, String, String, Option<String>) = conn
.query_row(
"SELECT record_type, verified_by, delivery_status, ref
FROM audit_records WHERE record_type='reveal'",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
)
.expect("a reveal proof must be recorded for a file-sourced retain");
assert_eq!(proof.0, "reveal", "record_type");
assert_eq!(proof.1, "operator", "verified_by");
assert_eq!(
proof.2, "observed",
"delivery_status (never sent — ADR 024)"
);
assert_eq!(
proof.3.as_deref(),
Some("a1"),
"ref must point at the audit_id"
);
let in_messages: i64 = conn
.query_row(
"SELECT count(*) FROM messages WHERE coalesce(payload_excerpt,'') LIKE '%top-secret-file-body%'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(in_messages, 0, "the file body must NOT appear in messages");
let in_audit_records: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records
WHERE payload_hash LIKE '%top-secret-file-body%'
OR coalesce(ref,'') LIKE '%top-secret-file-body%'
OR coalesce(re,'') LIKE '%top-secret-file-body%'
OR coalesce(mode,'') LIKE '%top-secret-file-body%'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
in_audit_records, 0,
"the file body must NOT appear in any audit_records column"
);
let ciphertext: Vec<u8> = conn
.query_row(
"SELECT ciphertext FROM custody_vault WHERE audit_id='a1'",
[],
|row| row.get(0),
)
.expect("custody_vault must have a row for the file-sourced retained record");
assert!(
!ciphertext.windows(body.len()).any(|w| w == body.as_bytes()),
"the stored vault ciphertext must NOT contain the literal file body bytes"
);
drop(conn);
let audit_md = fs::read_to_string(tmp.path().join("outputs/sessions/s1/audit.md")).unwrap();
assert!(
!audit_md.contains("top-secret-file-body"),
"neither the original record nor the reveal proof may leak the file body into audit.md"
);
}
#[cfg(target_os = "linux")]
#[test]
fn payload_file_retain_single_read_no_divergence() {
const O_NONBLOCK: i32 = 0o4000;
const ENXIO: i32 = 6;
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let fifo = tmp.path().join("payload.fifo");
let mkfifo = StdCommand::new("mkfifo")
.arg(&fifo)
.status()
.expect("mkfifo must run");
assert!(mkfifo.success(), "mkfifo must create the FIFO");
let fifo_writer = fifo.clone();
std::thread::spawn(move || {
if let Ok(mut file) = std::fs::OpenOptions::new().write(true).open(&fifo_writer) {
let _ = file.write_all(b"decoy");
}
loop {
match std::fs::OpenOptions::new()
.write(true)
.custom_flags(O_NONBLOCK)
.open(&fifo_writer)
{
Ok(handle) => {
drop(handle);
std::thread::sleep(std::time::Duration::from_millis(2));
}
Err(error) if error.raw_os_error() == Some(ENXIO) => break,
Err(_) => return,
}
}
if let Ok(mut file) = std::fs::OpenOptions::new().write(true).open(&fifo_writer) {
let _ = file.write_all(b"secret");
}
});
let out = run_zynk(&[
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"s1",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"note",
"--command-origin",
"agent",
"--payload-file",
fifo.to_str().unwrap(),
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
"--retain-custody",
]);
assert!(
out.status.success(),
"audit --payload-file --retain-custody must exit 0: {}",
stderr(&out)
);
let out = run_zynk(&["reveal", "a1", "--db", db, "--root", root]);
assert!(
out.status.success(),
"reveal must succeed — hash and ciphertext must bind identical single-read bytes: {}",
stderr(&out)
);
assert_eq!(
stdout(&out),
"decoy",
"reveal must emit the single-read bytes (the hash and ciphertext both bound `decoy`): {:?}",
stdout(&out)
);
}
#[test]
fn reveal_legacy_no_retain_not_revealable() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = write_audit_record(root, db, "a1", "secret", false);
assert!(out.status.success(), "{}", stderr(&out));
let out = run_zynk(&["reveal", "a1", "--db", db, "--root", root]);
assert_ne!(
out.status.code(),
Some(0),
"reveal of a non-retained record must fail: {}",
stderr(&out)
);
assert!(
stderr(&out).contains("not revealable"),
"the error must say the record is not revealable: {}",
stderr(&out)
);
assert!(
!stdout(&out).contains("secret"),
"no plaintext may be emitted for a non-revealable record"
);
let conn = Connection::open(db).unwrap();
let proofs: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE record_type='reveal'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(proofs, 0, "a failed reveal must NOT write a reveal proof");
}
#[test]
fn reveal_rejects_hash_mismatch() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = write_audit_record(root, db, "a1", "decoy", true);
assert!(out.status.success(), "{}", stderr(&out));
let key_path = tmp.path().join("custody.key");
let key_bytes = fs::read(&key_path).expect("the retained write must have created the key file");
assert_eq!(key_bytes.len(), 32, "custody key must be 32 bytes");
let recorded_hash = format!("sha256:{:x}", Sha256::digest(b"decoy"));
assert_ne!(
recorded_hash,
format!("sha256:{:x}", Sha256::digest(b"secret")),
"the recorded hash must differ from sha256 of the plaintext we hide"
);
let (ct, nonce) = custody_encrypt(&key_bytes, "a1", &recorded_hash, b"secret");
let conn = Connection::open(db).unwrap();
conn.execute(
"UPDATE custody_vault SET ciphertext=?1, nonce=?2 WHERE audit_id='a1'",
params![ct, nonce],
)
.unwrap();
drop(conn);
let out = run_zynk(&["reveal", "a1", "--db", db, "--root", root]);
assert_ne!(
out.status.code(),
Some(0),
"a post-decrypt hash mismatch must abort reveal: {}",
stderr(&out)
);
assert!(
!stdout(&out).contains("secret"),
"no plaintext may be emitted when the recomputed hash mismatches"
);
let conn = Connection::open(db).unwrap();
let proofs: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE record_type='reveal'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
proofs, 0,
"a hash-mismatch reveal must NOT write a reveal proof"
);
}
fn custody_encrypt(
key: &[u8],
audit_id: &str,
payload_hash: &str,
plaintext: &[u8],
) -> (Vec<u8>, Vec<u8>) {
use chacha20poly1305::aead::{Aead, KeyInit, Payload};
use chacha20poly1305::{XChaCha20Poly1305, XNonce};
let mut aad = Vec::new();
aad.extend_from_slice(b"zynk-custody-v1");
aad.extend_from_slice(&(audit_id.len() as u32).to_le_bytes());
aad.extend_from_slice(audit_id.as_bytes());
aad.extend_from_slice(&(payload_hash.len() as u32).to_le_bytes());
aad.extend_from_slice(payload_hash.as_bytes());
let key_arr: [u8; 32] = key.try_into().expect("32-byte key");
let cipher = XChaCha20Poly1305::new((&key_arr).into());
let nonce_bytes = [9u8; 24];
let nonce = XNonce::from_slice(&nonce_bytes);
let ct = cipher
.encrypt(
nonce,
Payload {
msg: plaintext,
aad: &aad,
},
)
.expect("encrypt");
(ct, nonce_bytes.to_vec())
}
fn seed_audit_record(
root: &str,
db: &str,
audit_id: &str,
mid: &str,
payload: &str,
retain: bool,
) -> std::process::Output {
let mut args: Vec<&str> = vec![
"audit",
"--root",
root,
"--db",
db,
"--session-id",
"s1",
"--audit-id",
audit_id,
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
mid,
"--type",
"note",
"--command-origin",
"agent",
"--payload",
payload,
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
];
if retain {
args.push("--retain-custody");
}
run_zynk(&args)
}
#[test]
fn reveal_aad_move_rejected_at_route() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = seed_audit_record(root, db, "aud-1", "m1", "secretone", true);
assert!(out.status.success(), "aud-1 seed: {}", stderr(&out));
let out = seed_audit_record(root, db, "aud-2", "m2", "secrettwo", false);
assert!(out.status.success(), "aud-2 seed: {}", stderr(&out));
let conn = Connection::open(db).unwrap();
let (ciphertext, nonce, cipher_id, key_version): (Vec<u8>, Vec<u8>, String, i64) = conn
.query_row(
"SELECT ciphertext, nonce, cipher_id, key_version FROM custody_vault WHERE audit_id='aud-1'",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
)
.expect("aud-1 must have a retained vault row");
conn.execute(
"INSERT INTO custody_vault (audit_id, ciphertext, nonce, cipher_id, key_version, created_at)
VALUES ('aud-2', ?1, ?2, ?3, ?4, '2026-05-31T00:00:00Z')",
params![ciphertext, nonce, cipher_id, key_version],
)
.unwrap();
drop(conn);
let out = run_zynk(&["reveal", "aud-2", "--db", db, "--root", root]);
assert_ne!(
out.status.code(),
Some(0),
"a relabeled (AAD-bound-to-aud-1) ciphertext must NOT reveal as aud-2: {}",
stderr(&out)
);
let err = stderr(&out);
assert!(
err.contains("custody decryption failed"),
"must fail at the AAD-decrypt step: {err}"
);
assert!(
!err.contains("not revealable"),
"must NOT be the no-custody branch (a vault row exists): {err}"
);
assert!(
!err.contains("does not match the recorded record"),
"must NOT reach the post-decrypt hash re-verify (decrypt never succeeds): {err}"
);
assert!(
!stdout(&out).contains("secret"),
"no plaintext may be emitted on an AAD-move reject"
);
let conn = Connection::open(db).unwrap();
let proofs: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE record_type='reveal' AND ref='aud-2'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
proofs, 0,
"an AAD-move reject must NOT write a reveal proof"
);
}
#[test]
fn custody_failure_after_file_first_is_loud() {
let cwd = tempdir().unwrap();
let keydir = cwd.path().join("keydir");
fs::create_dir(&keydir).unwrap();
fs::set_permissions(&keydir, fs::Permissions::from_mode(0o700)).unwrap();
let out = run_zynk_in(
&[
"audit",
"--root",
"outputs",
"--session-id",
"s1",
"--audit-id",
"a1",
"--source-agent",
"claude",
"--source-address",
"w-1",
"--target-agent",
"codex",
"--target-address",
"w-2",
"--transport",
"herdr",
"--workspace-id",
"w",
"--mid",
"m1",
"--type",
"note",
"--command-origin",
"agent",
"--payload",
"secret",
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"codex",
"--verified-by",
"helper-tool",
"--retain-custody",
"--custody-key-file",
keydir.to_str().unwrap(),
],
cwd.path(),
);
assert_ne!(
out.status.code(),
Some(0),
"a retention failure on the DEFAULT target must exit nonzero (loud): {}",
stderr(&out)
);
assert!(
stderr(&out).contains("record written but custody NOT retained"),
"the failure must be loud + explicit that the record is durable but not revealable: {}",
stderr(&out)
);
assert!(
cwd.path().join("outputs/sessions/s1/audit.md").exists(),
"the audit.md artifact must be written (durable) even when custody fails"
);
let db_path = cwd.path().join(".zynk/zynk.db");
assert!(db_path.exists(), "the default db must have been created");
let conn = Connection::open(&db_path).unwrap();
let vault_rows: i64 = conn
.query_row(
"SELECT count(*) FROM custody_vault WHERE audit_id='a1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
vault_rows, 0,
"a failed retention must leave NO custody_vault row (no silent no-retain)"
);
}
#[test]
fn proof_before_output_no_plaintext_on_proof_failure() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root = root.to_str().unwrap();
let db = tmp.path().join("zynk.db");
let db = db.to_str().unwrap();
let out = write_audit_record(root, db, "a1", "topsecret", true);
assert!(out.status.success(), "{}", stderr(&out));
let rootfile = tmp.path().join("rootfile");
fs::write(&rootfile, b"x").unwrap();
let out = run_zynk(&[
"reveal",
"a1",
"--db",
db,
"--root",
rootfile.to_str().unwrap(),
]);
assert_ne!(
out.status.code(),
Some(0),
"a forced reveal-proof-write failure must abort reveal: {}",
stderr(&out)
);
assert!(
!stdout(&out).contains("topsecret"),
"proof-before-disclosure: NO plaintext on stdout when the proof write fails: {:?}",
stdout(&out)
);
let conn = Connection::open(db).unwrap();
let proofs: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE record_type='reveal'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
proofs, 0,
"a failed proof write must leave NO reveal proof (and emitted no plaintext)"
);
}
fn run_seed_scenario(db: &Path, root: &Path, custody_key: &Path) -> String {
let dir = db.parent().unwrap();
fs::create_dir_all(dir).unwrap();
let herdr = fake_herdr(dir, "echo sent\n");
let script = concat!(env!("CARGO_MANIFEST_DIR"), "/scripts/seed-scenario.sh");
let out = StdCommand::new("bash")
.arg(script)
.env("ZYNK_BIN", env!("CARGO_BIN_EXE_zynk"))
.env("DB", db)
.env("ROOT", root)
.env("SESSION_ID", "s1")
.env("WORKSPACE_ID", "w-test")
.env("HERDR_BIN", &herdr)
.env("CUSTODY_KEY_FILE", custody_key)
.output()
.expect("spawn seed-scenario.sh");
let combined = format!(
"STDOUT:\n{}\nSTDERR:\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(out.status.success(), "seed-scenario.sh failed:\n{combined}");
combined
}
#[test]
fn m4b_seed_scenario_populates_every_core_class() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("state/zynk.db");
let root = tmp.path().join("outputs");
let key = tmp.path().join("custody.key");
run_seed_scenario(&db, &root, &key);
let conn = Connection::open(&db).unwrap();
let mut kinds: Vec<String> = conn
.prepare("SELECT DISTINCT kind FROM work_events WHERE session_id='s1'")
.unwrap()
.query_map([], |r| r.get(0))
.unwrap()
.map(|r| r.unwrap())
.collect();
kinds.sort();
assert_eq!(
kinds,
vec!["artifact", "conflict", "diff", "gate", "plan", "system", "think", "tool", "usage"],
"seed must produce every work-event kind via real `zynk report`"
);
let mut dtypes: Vec<String> = conn
.prepare("SELECT DISTINCT decision_type FROM operator_decisions WHERE session_id='s1'")
.unwrap()
.query_map([], |r| r.get(0))
.unwrap()
.map(|r| r.unwrap())
.collect();
dtypes.sort();
assert_eq!(
dtypes,
vec![
"conflict-resolve",
"gate-decision",
"interrupt",
"mode-switch",
"redirect"
],
"seed must produce every decision type via real `zynk decide`"
);
let full_visible_sends: i64 = conn
.query_row(
"SELECT COUNT(*) FROM messages WHERE session_id='s1' \
AND mid IN ('task01', 'disc01') AND payload_excerpt IS NOT NULL",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
full_visible_sends, 2,
"both full-visible send messages (task01, disc01) must be present and visible"
);
let retained: i64 = conn
.query_row("SELECT COUNT(*) FROM custody_vault", [], |r| r.get(0))
.unwrap();
assert!(
retained >= 1,
"seed must include a retained (custodied) revealable record"
);
assert!(
reveal_proof_count(&db) >= 1,
"seed must run a real `zynk reveal` producing a reveal proof"
);
}
#[test]
fn m4b_html_shell_topbar_rail_roster() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("state/zynk.db");
let root = tmp.path().join("outputs");
let key = tmp.path().join("custody.key");
run_seed_scenario(&db, &root, &key);
let page = fetch_db_dashboard_once(&db, "/?session=s1");
assert!(page.contains("HTTP/1.1 200"), "{page}");
assert!(page.contains("class=\"topbar\""), "topbar present: {page}");
assert!(page.contains("mode-rail"), "mode rail present: {page}");
assert!(page.contains("id=\"roster\""), "roster present: {page}");
assert!(
page.contains("view-toggle"),
"view toggle control present: {page}"
);
assert!(
page.contains("4700 tokens"),
"the topbar/budget usage strip shows the real seeded token total: {page}"
);
assert!(!page.contains("$0.00"), "no fabricated $0.00 cost: {page}");
for mode in ["brainstorm", "decide", "review", "validate", "debug"] {
assert!(page.contains(mode), "mode rail renders `{mode}`: {page}");
}
assert!(
page.contains("<span class=\"mode active\">review</span>"),
"the mode rail marks the current mode (review) active: {page}"
);
}
#[test]
fn m4b_html_feed_renders_every_class() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("state/zynk.db");
let root = tmp.path().join("outputs");
let key = tmp.path().join("custody.key");
run_seed_scenario(&db, &root, &key);
let page = fetch_db_dashboard_once(&db, "/?session=s1");
assert!(page.contains("HTTP/1.1 200"), "{page}");
for needle in [
"kind-think",
"kind-tool",
"run_tests",
"kind-plan",
"Token-bucket limiter",
"kind-artifact",
"rateLimit.js",
"kind-usage",
"4,700",
"kind-system",
"CI green",
"kind-diff",
"src/rateLimit.js",
"+12",
"\u{2212}3",
"kind-gate",
"Approve plan",
"Request changes",
"kind-conflict",
"Limiter algorithm",
] {
assert!(page.contains(needle), "feed must render `{needle}`: {page}");
}
assert!(
page.contains(r#"class="decision-overlay decision-verdict""#),
"gate verdict overlay: {page}"
);
assert!(
page.contains(r#"class="feed-item decision decision-mode""#),
"mode-switch feed item: {page}"
);
assert!(
page.contains("pause for review"),
"interrupt reason rendered: {page}"
);
assert!(
page.contains("Implement the token-bucket limiter")
|| page.contains("Limiter drafted; please review"),
"a full-visible message bubble renders: {page}"
);
assert!(
page.contains("id=\"audit-view\""),
"inline audit pane present: {page}"
);
}
#[test]
fn m4b_html_context_panel_sections() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("state/zynk.db");
let root = tmp.path().join("outputs");
let key = tmp.path().join("custody.key");
run_seed_scenario(&db, &root, &key);
let page = fetch_db_dashboard_once(&db, "/?session=s1");
assert!(page.contains("HTTP/1.1 200"), "{page}");
assert!(
page.contains("context-panel"),
"context panel present: {page}"
);
let section_slice = |name: &str| -> String {
let open = format!("class=\"panel-section {name}\"");
let start = page
.find(&open)
.unwrap_or_else(|| panic!("panel-section `{name}` present: {page}"));
let rest = &page[start..];
let end = rest[open.len()..]
.find("class=\"panel-section ")
.map(|i| open.len() + i)
.unwrap_or(rest.len());
rest[..end].to_string()
};
let status_section = section_slice("status-section");
assert!(
status_section.contains("<dt>Artifact</dt>"),
"status section renders the Artifact field: {status_section}"
);
assert!(
status_section.contains("ref-1"),
"status section shows the real seeded artifact ref `ref-1`: {status_section}"
);
let chain_section = section_slice("chain-section");
assert!(
chain_section.contains("class=\"chain-summary intact\""),
"chain summary badge is intact: {chain_section}"
);
assert!(
chain_section.contains("chain intact \u{00b7} ") && chain_section.contains(" verified"),
"chain summary shows intact + verified-count over the seeded chain: {chain_section}"
);
let artifacts_section = section_slice("artifacts-section");
assert!(
artifacts_section.contains("class=\"artifact-list\""),
"artifacts section renders the artifact list: {artifacts_section}"
);
assert!(
artifacts_section.contains("src/rateLimit.js"),
"artifacts section lists the real seeded file `src/rateLimit.js`: {artifacts_section}"
);
let budget_section = section_slice("budget-section");
assert!(
budget_section.contains("4700 tokens"),
"context budget shows the real seeded token total UNGROUPED: {budget_section}"
);
assert!(
!budget_section.contains("$0.00"),
"no fabricated cost in the budget section: {budget_section}"
);
let transport_section = section_slice("transport-panel");
assert!(
transport_section.contains("loopback (127.0.0.1, read-only)"),
"transport shows the loopback connection: {transport_section}"
);
assert!(
!transport_section.contains(" ms<")
&& !transport_section.contains(" ms ")
&& !transport_section.contains("latency"),
"transport shows no fabricated latency metric: {transport_section}"
);
}
#[test]
fn m4b_html_reveal_state_and_composer_targets() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("state/zynk.db");
let root = tmp.path().join("outputs");
let key = tmp.path().join("custody.key");
run_seed_scenario(&db, &root, &key);
let conn = Connection::open(&db).unwrap();
let revealable_ids: Vec<String> = conn
.prepare(
"SELECT a.audit_id FROM audit_records a \
JOIN custody_vault v ON v.audit_id = a.audit_id \
WHERE a.session_id = 's1' AND a.payload_redaction_policy <> 'full' \
ORDER BY a.audit_id",
)
.unwrap()
.query_map([], |r| r.get(0))
.unwrap()
.map(|r| r.unwrap())
.collect();
assert_eq!(
revealable_ids.len(),
1,
"seed produces exactly one custodied/revealable record: {revealable_ids:?}"
);
let revealable_id = &revealable_ids[0];
assert_ne!(
revealable_id, "rvl01",
"the revealable audit_id is auto-minted, not the literal mid: {revealable_id}"
);
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let (_token, port, mut child) = start_writable_serve(&db, &root, &herdr);
let page = post_dashboard_raw(
port,
&format!(
"GET /?session=s1 HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n"
),
);
child.kill().ok();
child.wait().ok();
assert!(page.contains("HTTP/1.1 200"), "{page}");
assert!(
page.contains("class=\"reveal-form\""),
"the custodied record exposes a reveal affordance: {page}"
);
let revealable_field = format!("name=\"audit_id\" value=\"{revealable_id}\"");
assert!(
page.contains(&revealable_field),
"the reveal affordance carries the real custodied audit_id `{revealable_id}`: {page}"
);
for non_revealable in ["legacy01", "noretain01"] {
assert!(
page.contains(non_revealable),
"the non-revealable fixture `{non_revealable}` is present on the page (as a \
feed/permalink record id): {page}"
);
let reveal_field = format!("name=\"audit_id\" value=\"{non_revealable}\"");
assert!(
!page.contains(&reveal_field),
"`{non_revealable}` must NOT carry a reveal affordance (ADR 035 D5): {page}"
);
}
assert_eq!(
page.matches("class=\"reveal-form\"").count(),
1,
"exactly one reveal affordance renders (only the custodied record): {page}"
);
assert!(
page.contains("<option value=\"__all__\">"),
"composer offers the All-targets fan-out sentinel: {page}"
);
assert!(
page.contains("data-sendable-targets="),
"composer emits the sendable targets: {page}"
);
}
#[test]
fn assign_trait_writes_proof_and_typed_row() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let out = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(&db).unwrap();
let n: i64 = conn
.query_row("SELECT COUNT(*) FROM participant_overlay", [], |r| r.get(0))
.unwrap();
assert_eq!(n, 1, "exactly one participant_overlay row");
let (session, subject, trait_id, trait_value, asserter): (String, String, String, i64, String) =
conn.query_row(
"SELECT session_id, subject_actor_id, trait_id, trait_value, asserter_actor_id
FROM participant_overlay",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?, r.get(4)?)),
)
.unwrap();
assert_eq!(session, "s1");
assert_eq!(subject, "codex");
assert_eq!(trait_id, "independent");
assert_eq!(trait_value, 1, "a set trait is value 1");
assert_eq!(asserter, "operator");
let (record_type, verified_by, delivery_status, command_origin): (
String,
String,
String,
String,
) = conn
.query_row(
"SELECT record_type, verified_by, delivery_status, command_origin
FROM audit_records WHERE record_type='participant-overlay'",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
)
.unwrap();
assert_eq!(record_type, "participant-overlay");
assert_eq!(verified_by, "operator");
assert_eq!(delivery_status, "observed");
assert_eq!(command_origin, "operator");
}
#[test]
fn assign_actor_kind_and_role_happy() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let ak = run_zynk(&[
"assign",
"actor-kind",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00Z",
"--subject",
"codex",
"--kind",
"agent",
]);
assert!(ak.status.success(), "{}", stderr(&ak));
let role = run_zynk(&[
"assign",
"role",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:01:00Z",
"--subject",
"codex",
"--role-id",
"reviewer",
"--role-label",
"Gate-2 reviewer",
]);
assert!(role.status.success(), "{}", stderr(&role));
let conn = Connection::open(&db).unwrap();
let actor_kind: String = conn
.query_row(
"SELECT actor_kind FROM participant_overlay WHERE overlay_kind='actor-kind'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(actor_kind, "agent");
let (role_id, role_label): (String, String) = conn
.query_row(
"SELECT role_id, role_label FROM participant_overlay WHERE overlay_kind='role'",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(role_id, "reviewer");
assert_eq!(role_label, "Gate-2 reviewer");
}
#[test]
fn assign_self_granted_trait_exits_nonzero() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let out = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00Z",
"--subject",
"operator",
"--trait",
"independent",
]);
assert!(
!out.status.success(),
"a self-granted integrity trait must fail loud: {}",
stdout(&out)
);
let conn = Connection::open(&db).unwrap();
let n: i64 = conn
.query_row("SELECT COUNT(*) FROM participant_overlay", [], |r| r.get(0))
.unwrap();
assert_eq!(n, 0, "a rejected self-grant produces NO typed row");
}
#[test]
fn assign_trait_unset_supersedes() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let first = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
]);
assert!(first.status.success(), "{}", stderr(&first));
let unset = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
"--unset",
]);
assert!(unset.status.success(), "{}", stderr(&unset));
let conn = Connection::open(&db).unwrap();
let n: i64 = conn
.query_row("SELECT COUNT(*) FROM participant_overlay", [], |r| r.get(0))
.unwrap();
assert_eq!(n, 2, "re-assign keeps the prior row and adds the new one");
let (current_audit_id, current_value): (String, i64) = conn
.query_row(
"SELECT audit_id, trait_value FROM participant_overlay
WHERE superseded_by_audit_id IS NULL",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(current_value, 0, "the current trait row is unset (value 0)");
let first_superseded_by: String = conn
.query_row(
"SELECT superseded_by_audit_id FROM participant_overlay
WHERE superseded_by_audit_id IS NOT NULL",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
first_superseded_by, current_audit_id,
"the first row's superseded_by points at the unset row's audit_id"
);
}
#[test]
fn assign_creates_no_message_row() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let out = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(&db).unwrap();
let messages: i64 = conn
.query_row(
"SELECT COUNT(*) FROM messages WHERE session_id='s1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
messages, 0,
"an overlay proof must NOT upsert a messages row (no chat leak, D7)"
);
}
#[test]
fn assign_creates_no_feed_item() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let out = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
]);
assert!(out.status.success(), "{}", stderr(&out));
let page = fetch_db_dashboard_once(&db, "/?session=s1");
let feed_start = page
.find("<div id=\"feed\">")
.expect("served page must contain the feed region");
let feed_region = &page[feed_start..];
let feed_region = feed_region
.split("<section id=\"audit-view\"")
.next()
.unwrap_or(feed_region);
assert!(
!feed_region.contains("participant-overlay"),
"the overlay record_type must not surface as a feed item: {feed_region}"
);
assert!(
!feed_region.contains("independent"),
"the overlay payload value must not render as a feed message bubble: {feed_region}"
);
assert!(
!feed_region.contains("<article"),
"the overlay must not create any feed article at all (feed stays empty): {feed_region}"
);
}
#[test]
fn import_of_overlay_proof_creates_no_message() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let assign = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--no-db",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
]);
assert!(assign.status.success(), "{}", stderr(&assign));
assert!(
root.join("sessions/s1/audit.md").exists(),
"assign --no-db must still write the file-first overlay proof"
);
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
&db_str,
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T01:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let conn = Connection::open(&db).unwrap();
let messages: i64 = conn
.query_row(
"SELECT COUNT(*) FROM messages WHERE session_id='s1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
messages, 0,
"the IMPORT path must also skip the messages upsert for participant-overlay (D7)"
);
}
#[test]
fn import_reconstructs_current_overlay() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let src_db = tmp.path().join("src.zynk.db");
let root_str = root.to_str().unwrap().to_string();
let src_db_str = src_db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &src_db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let first = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--db",
&src_db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
]);
assert!(first.status.success(), "{}", stderr(&first));
let unset = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--db",
&src_db_str,
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
"--unset",
]);
assert!(unset.status.success(), "{}", stderr(&unset));
let audit_md = fs::read_to_string(root.join("sessions/s1/audit.md")).unwrap();
assert!(
audit_md.contains("supersedes:"),
"the --unset proof payload must carry an explicit supersedes pointer (D10 determinism \
source): {audit_md}"
);
let dst_db = tmp.path().join("dst.zynk.db");
let dst_db_str = dst_db.to_str().unwrap().to_string();
let init_dst = run_zynk(&["db", "--db", &dst_db_str, "init"]);
assert!(init_dst.status.success(), "{}", stderr(&init_dst));
let import = run_zynk(&[
"db",
"--db",
&dst_db_str,
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T02:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let conn = Connection::open(&dst_db).unwrap();
let n: i64 = conn
.query_row("SELECT COUNT(*) FROM participant_overlay", [], |r| r.get(0))
.unwrap();
assert_eq!(
n, 2,
"import must reconstruct BOTH proof rows (the superseded + the current)"
);
let current: Vec<(String, i64)> = {
let mut stmt = conn
.prepare(
"SELECT audit_id, trait_value FROM participant_overlay
WHERE superseded_by_audit_id IS NULL",
)
.unwrap();
let rows = stmt
.query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, i64>(1)?)))
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
rows
};
assert_eq!(
current.len(),
1,
"exactly one CURRENT participant_overlay row after reconstruction"
);
let (current_audit_id, current_value) = ¤t[0];
assert_eq!(
*current_value, 0,
"the reconstructed current trait row is the unset (value 0)"
);
let first_superseded_by: String = conn
.query_row(
"SELECT superseded_by_audit_id FROM participant_overlay
WHERE superseded_by_audit_id IS NOT NULL",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
&first_superseded_by, current_audit_id,
"reconstruction follows the explicit supersede chain: the first row's superseded_by \
points at the current (unset) row"
);
}
#[test]
fn import_double_current_fails_loud() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let root_str = root.to_str().unwrap().to_string();
let first = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--no-db",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T00:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
]);
assert!(first.status.success(), "{}", stderr(&first));
let second = run_zynk(&[
"assign",
"trait",
"--root",
&root_str,
"--no-db",
"--session-id",
"s1",
"--timestamp",
"2026-05-31T01:00:00Z",
"--subject",
"codex",
"--trait",
"independent",
"--unset",
]);
assert!(second.status.success(), "{}", stderr(&second));
let db = tmp.path().join("dst.zynk.db");
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
&db_str,
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T02:00:00Z",
]);
assert!(
!import.status.success(),
"import of two un-superseded current overlay proofs in one slot must FAIL LOUD, not \
silently accept two current rows: stdout={} stderr={}",
stdout(&import),
stderr(&import)
);
let conn = Connection::open(&db).unwrap();
let n: i64 = conn
.query_row("SELECT COUNT(*) FROM participant_overlay", [], |r| r.get(0))
.unwrap();
assert_eq!(
n, 0,
"a fail-loud double-current import must not leave any partial participant_overlay row"
);
}
#[test]
fn audit_overlay_creates_typed_row_no_orphan() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let overlay_payload = "overlay: trait\nsubject: codex\nasserter: operator\nasserter_kind: operator\ntrait_id: independent\nvalue: true\nsupersedes: null\n";
let out = run_zynk(&[
"audit",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--audit-id",
"audit-overlay-ok",
"--timestamp",
"2026-05-31T00:00:00Z",
"--source-agent",
"operator",
"--source-address",
"cli",
"--target-agent",
"none",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"audit-overlay-ok",
"--type",
"participant-overlay",
"--command-origin",
"operator",
"--payload",
overlay_payload,
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"operator",
"--verified-by",
"operator",
]);
assert!(out.status.success(), "{}", stderr(&out));
let conn = Connection::open(&db).unwrap();
let audit_rows: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id='audit-overlay-ok'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(audit_rows, 1, "the overlay audit proof row must be present");
let typed: i64 = conn
.query_row(
"SELECT count(*) FROM participant_overlay WHERE audit_id='audit-overlay-ok'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
typed, 1,
"zynk audit of a participant-overlay must create the typed row (no orphan, D7)"
);
let messages: i64 = conn
.query_row(
"SELECT count(*) FROM messages WHERE session_id='s1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
messages, 0,
"an overlay proof must NOT leak a messages chat-feed row (D7)"
);
}
#[test]
fn audit_overlay_self_granted_rejected() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let overlay_payload = "overlay: trait\nsubject: operator\nasserter: operator\nasserter_kind: operator\ntrait_id: independent\nvalue: true\nsupersedes: null\n";
let out = run_zynk(&[
"audit",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--audit-id",
"audit-overlay-selfgrant",
"--timestamp",
"2026-05-31T00:00:00Z",
"--source-agent",
"operator",
"--source-address",
"cli",
"--target-agent",
"none",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"audit-overlay-selfgrant",
"--type",
"participant-overlay",
"--command-origin",
"operator",
"--payload",
overlay_payload,
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"operator",
"--verified-by",
"operator",
]);
assert!(
!out.status.success(),
"a self-granted overlay via `zynk audit --db` must hard-fail: {}",
stdout(&out)
);
let conn = Connection::open(&db).unwrap();
let audit_rows: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id='audit-overlay-selfgrant'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
audit_rows, 0,
"a rejected self-grant must leave NO audit_records row (transaction rolled back)"
);
let typed: i64 = conn
.query_row(
"SELECT count(*) FROM participant_overlay WHERE audit_id='audit-overlay-selfgrant'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(typed, 0, "a rejected self-grant produces NO typed row");
}
#[test]
fn audit_overlay_hash_only_rejected_pre_write() {
let tmp = tempdir().unwrap();
let overlay_payload = "overlay: trait\nsubject: codex\nasserter: operator\nasserter_kind: operator\ntrait_id: independent\nvalue: true\nsupersedes: null\n";
let out = run_zynk_in(
&[
"audit",
"--root",
"outputs",
"--db",
"./e.db",
"--session-id",
"s1",
"--audit-id",
"audit-overlay-hashonly",
"--timestamp",
"2026-05-31T00:00:00Z",
"--source-agent",
"operator",
"--source-address",
"cli",
"--target-agent",
"none",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"audit-overlay-hashonly",
"--type",
"participant-overlay",
"--command-origin",
"operator",
"--payload",
overlay_payload,
"--payload-redaction-policy",
"hash-only",
"--delivery-status",
"observed",
"--observed-by",
"operator",
"--verified-by",
"operator",
],
tmp.path(),
);
assert!(
!out.status.success(),
"a hash-only participant-overlay must be rejected (its typed row cannot be reconstructed): {}",
stdout(&out)
);
let err = stderr(&out);
assert!(
err.contains("participant-overlay") && err.contains("full"),
"the usage error must name participant-overlay + require full redaction: {err}"
);
assert!(
!tmp.path().join("outputs").exists(),
"a rejected hash-only overlay must leave NO audit.md (rejected BEFORE the file write)"
);
let db = tmp.path().join("e.db");
if db.exists() {
let conn = Connection::open(&db).unwrap();
let audit_rows: i64 = conn
.query_row("SELECT count(*) FROM audit_records", [], |r| r.get(0))
.unwrap_or(0);
assert_eq!(
audit_rows, 0,
"a rejected hash-only overlay must leave NO audit_records row"
);
let typed: i64 = conn
.query_row("SELECT count(*) FROM participant_overlay", [], |r| r.get(0))
.unwrap_or(0);
assert_eq!(
typed, 0,
"a rejected hash-only overlay must leave NO participant_overlay row"
);
}
}
#[test]
fn audit_overlay_invalid_payload_rejected_pre_write() {
let tmp = tempdir().unwrap();
let overlay_payload = "overlay: trait\nsubject: operator\nasserter: operator\nasserter_kind: operator\ntrait_id: independent\nvalue: true\nsupersedes: null\n";
let out = run_zynk_in(
&[
"audit",
"--root",
"outputs",
"--db",
"./e.db",
"--session-id",
"s1",
"--audit-id",
"audit-overlay-badpayload",
"--timestamp",
"2026-05-31T00:00:00Z",
"--source-agent",
"operator",
"--source-address",
"cli",
"--target-agent",
"none",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"audit-overlay-badpayload",
"--type",
"participant-overlay",
"--command-origin",
"operator",
"--payload",
overlay_payload,
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"operator",
"--verified-by",
"operator",
],
tmp.path(),
);
assert!(
!out.status.success(),
"a self-granted (invalid) overlay via `zynk audit` must be rejected: {}",
stdout(&out)
);
assert!(
!tmp.path().join("outputs").exists(),
"an invalid overlay must leave NO audit.md (rejected BEFORE the file write)"
);
let db = tmp.path().join("e.db");
if db.exists() {
let conn = Connection::open(&db).unwrap();
let audit_rows: i64 = conn
.query_row("SELECT count(*) FROM audit_records", [], |r| r.get(0))
.unwrap_or(0);
assert_eq!(
audit_rows, 0,
"a rejected invalid overlay must leave NO audit_records row"
);
let typed: i64 = conn
.query_row("SELECT count(*) FROM participant_overlay", [], |r| r.get(0))
.unwrap_or(0);
assert_eq!(
typed, 0,
"a rejected invalid overlay must leave NO participant_overlay row"
);
}
}
#[test]
fn audit_overlay_payload_file_valid_projects() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let overlay_payload = "overlay: trait\nsubject: codex\nasserter: operator\nasserter_kind: operator\ntrait_id: independent\nvalue: true\nsupersedes: null\n";
let payload_file = tmp.path().join("overlay.yml");
fs::write(&payload_file, overlay_payload).unwrap();
let payload_file_str = payload_file.to_str().unwrap().to_string();
let out = run_zynk(&[
"audit",
"--root",
&root_str,
"--db",
&db_str,
"--session-id",
"s1",
"--audit-id",
"audit-overlay-file-ok",
"--timestamp",
"2026-05-31T00:00:00Z",
"--source-agent",
"operator",
"--source-address",
"cli",
"--target-agent",
"none",
"--target-address",
"none",
"--transport",
"none",
"--workspace-id",
"none",
"--mid",
"audit-overlay-file-ok",
"--type",
"participant-overlay",
"--command-origin",
"operator",
"--payload-file",
&payload_file_str,
"--payload-redaction-policy",
"full",
"--delivery-status",
"observed",
"--observed-by",
"operator",
"--verified-by",
"operator",
]);
assert!(
out.status.success(),
"a valid full-redaction overlay via --payload-file must succeed: {}",
stderr(&out)
);
let conn = Connection::open(&db).unwrap();
let audit_rows: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id='audit-overlay-file-ok'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
audit_rows, 1,
"the file-supplied overlay audit proof row must be present"
);
let typed: i64 = conn
.query_row(
"SELECT count(*) FROM participant_overlay WHERE audit_id='audit-overlay-file-ok'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
typed, 1,
"a valid --payload-file overlay must create the typed row (no false reject)"
);
let messages: i64 = conn
.query_row(
"SELECT count(*) FROM messages WHERE session_id='s1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(messages, 0, "an overlay proof must NOT leak a messages row");
}
#[test]
fn import_malformed_overlay_no_orphan() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("zynk.db");
let root_str = root.to_str().unwrap().to_string();
let db_str = db.to_str().unwrap().to_string();
let session_dir = root.join("sessions/s1");
fs::create_dir_all(&session_dir).unwrap();
fs::write(
session_dir.join("audit.md"),
"# Audit Trail: s1\n\nsession_id: s1\n\n## Records\n\n\
```text\naudit_id=import-overlay-bad\nprevious_audit_id=none\n\
timestamp=2026-05-31T00:00:00Z\nsource_agent=operator\nsource_address=cli\n\
target_agent=none\ntarget_address=none\ntransport=none\nworkspace_id=none\n\
session_id=s1\nmid=import-overlay-bad\ntype=participant-overlay\n\
command_origin=operator\npayload_hash=sha256:x\npayload_redaction_policy=full\n\
content_size=12\ndelivery_status=observed\nobserved_by=operator\n\
verified_by=operator\npayload_excerpt=not: overlay\n```\n",
)
.unwrap();
assert!(
root.join("sessions/s1/audit.md").exists(),
"the malformed overlay artifact must be present on disk for the import test"
);
let init = run_zynk(&["db", "--db", &db_str, "init"]);
assert!(init.status.success(), "{}", stderr(&init));
let import = run_zynk(&[
"db",
"--db",
&db_str,
"import",
"outputs",
"--root",
&root_str,
"--timestamp",
"2026-05-31T01:00:00Z",
]);
assert!(import.status.success(), "{}", stderr(&import));
let conn = Connection::open(&db).unwrap();
let audit_rows: i64 = conn
.query_row(
"SELECT count(*) FROM audit_records WHERE audit_id='import-overlay-bad'",
[],
|r| r.get(0),
)
.unwrap();
let typed: i64 = conn
.query_row(
"SELECT count(*) FROM participant_overlay WHERE audit_id='import-overlay-bad'",
[],
|r| r.get(0),
)
.unwrap();
assert!(
!(audit_rows == 1 && typed == 0),
"a malformed overlay import must not leave an orphan audit row \
(audit_rows={audit_rows}, participant_overlay={typed})"
);
assert_eq!(
audit_rows, 0,
"the malformed overlay proof's audit row must be skipped (warned), not inserted"
);
}
#[test]
fn assign_invalid_input_no_side_effects() {
let cwd = tempdir().unwrap();
let out = run_zynk_in(
&[
"assign",
"trait",
"--session-id",
"s1",
"--subject",
"codex",
"--trait",
"bogus",
],
cwd.path(),
);
assert!(
!out.status.success(),
"an unknown trait must reject: {}",
stdout(&out)
);
assert!(
!cwd.path().join(".zynk").exists(),
"a rejected invalid input must leave NO .zynk/ (validate before the DB pre-read)"
);
assert!(
!cwd.path().join("outputs").exists(),
"a rejected invalid input must leave NO outputs/ (validate before any write)"
);
}
#[test]
fn track_c_workflow_profile_smscode_roster_proof() {
let tmp = tempdir().unwrap();
let root = tmp.path().join("outputs");
let db = tmp.path().join("state/zynk.db");
let bin = env!("CARGO_BIN_EXE_zynk");
let herdr = fake_herdr(tmp.path(), "echo sent\n");
let script = concat!(
env!("CARGO_MANIFEST_DIR"),
"/scripts/workflow-profile-smscode.sh"
);
seed_writable_session(&root, &db);
let conn0 = Connection::open(&db).unwrap();
let messages_before: i64 = conn0
.query_row("SELECT COUNT(*) FROM messages", [], |r| r.get(0))
.unwrap();
drop(conn0);
let out = StdCommand::new("bash")
.arg(script)
.env("ZYNK_BIN", bin)
.env("DB", &db)
.env("ROOT", &root)
.env("SESSION_ID", "s1")
.output()
.expect("run smscode workflow-profile script");
assert!(
out.status.success(),
"reference script must exit 0:\nSTDOUT:\n{}\nSTDERR:\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let (_t, port, mut child) = start_writable_serve(&db, &root, &herdr);
let snap = read_sse_snapshot_multi(port, "s1");
child.kill().ok();
child.wait().ok();
assert!(
snap.contains("event: roster"),
"roster event present: {snap}"
);
assert!(
snap.contains("roster-actor-human"),
"operator renders a human actor-kind marker in the roster: {snap}"
);
for role_label in [
"Operator",
"Implementer",
"Collaborative Reviewer",
"Independent Verifier",
] {
assert!(
snap.contains(&format!("<span class=\"roster-role\">{role_label}</span>")),
"role label {role_label:?} renders in the roster: {snap}"
);
}
assert!(
snap.contains("title=\"by operator @ "),
"trait badges carry bound operator provenance (asserter @ asserted_at): {snap}"
);
for trait_name in [
"can_edit_source",
"can_verify_gate",
"independent",
"non_iterating",
] {
assert!(
snap.contains("class=\"trait-badge trait-on\" title=\"by operator @ ")
&& snap.contains(&format!(">{trait_name}</span>")),
"trait {trait_name:?} renders as an operator-provenanced badge: {snap}"
);
}
assert!(
!snap.contains(">can_merge_approve</span>"),
"can_merge_approve is NOT assigned in SMSCode and must not render: {snap}"
);
let html = fetch_dashboard_with_serve_args(
&db,
"/?session=s1",
&["--allow-writes", "--root", root.to_str().unwrap()],
);
assert!(
html.contains("<option value=\"codex:w1-1\">"),
"the composer still offers the real sendable target codex:w1-1: {html}"
);
for subject in [
"operator",
"implementer",
"collaborative-reviewer",
"independent-verifier",
] {
assert!(
!html.contains(&format!("<option value=\"{subject}:")),
"overlay-only subject {subject:?} is NEVER a sendable composer target: {html}"
);
}
let conn = Connection::open(&db).unwrap();
let mut stmt = conn
.prepare(
"SELECT subject_actor_id, trait_id, asserter_actor_id, created_at
FROM participant_overlay
WHERE overlay_kind='trait' AND trait_value=1
AND superseded_by_audit_id IS NULL
ORDER BY subject_actor_id, trait_id",
)
.unwrap();
let traits: Vec<(String, String, String, String)> = stmt
.query_map([], |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)))
.unwrap()
.map(|x| x.unwrap())
.collect();
let expected = vec![
("collaborative-reviewer", "can_verify_gate"),
("implementer", "can_edit_source"),
("independent-verifier", "can_verify_gate"),
("independent-verifier", "independent"),
("independent-verifier", "non_iterating"),
];
let got: Vec<(&str, &str)> = traits
.iter()
.map(|(s, t, _, _)| (s.as_str(), t.as_str()))
.collect();
assert_eq!(
got, expected,
"SMSCode trait mapping (no can_merge_approve)"
);
for (subject, _t, asserter, created_at) in &traits {
assert_ne!(asserter, subject, "no self-grant: asserter != subject");
assert_eq!(asserter, "operator", "traits are operator-asserted");
assert!(!created_at.is_empty(), "trait has bound asserted_at");
}
let messages_after: i64 = conn
.query_row("SELECT COUNT(*) FROM messages", [], |r| r.get(0))
.unwrap();
assert_eq!(
messages_after, messages_before,
"overlays are roster-only: they add zero feed/message rows"
);
}