From 0442c43c6563b8173387811b2290dcb03bc5b176 Mon Sep 17 00:00:00 2001 From: sprooty Date: Sat, 17 Oct 2026 05:49:46 +0000 Subject: [PATCH 1/3] feat(core): port the decision service and its Inbox row (WI-1069) Ports decision.ask, decision.answer, decision.list and decision.withdraw from application/services/decisions.py. A person answers; an agent asks, and a pending decision past its expiry reads `expired` with no implied answer. The Inbox shows each pending decision as a decision.pending entry. Adds the Decision entity, its storage (migration 0023_decisions) and the ported test_decisions.py cases in application/services/decisions_test.rs. SequentialIds in the test is persisted, and each action opens its own context, because an in-memory counter over a shared directory collides. Co-Authored-By: Claude Haiku 5.5 Signed-off-by: sprooty --- docs/PYTHON_TEST_DISPOSITION.md | 2 +- engine/core/src/application/grants.rs | 2 +- .../src/application/services/decisions.rs | 447 ++++++++++++++++++ .../application/services/decisions_test.rs | 360 ++++++++++++++ engine/core/src/application/services/inbox.rs | 101 ++++ engine/core/src/application/services/mod.rs | 3 + engine/core/src/core.rs | 64 +++ engine/core/src/registry.rs | 4 + engine/core/src/storage/interface.rs | 13 +- engine/core/src/storage/sqlite/declared.rs | 131 ++++- 10 files changed, 1123 insertions(+), 4 deletions(-) create mode 100644 engine/core/src/application/services/decisions.rs create mode 100644 engine/core/src/application/services/decisions_test.rs diff --git a/docs/PYTHON_TEST_DISPOSITION.md b/docs/PYTHON_TEST_DISPOSITION.md index 83868add9..2cd43b004 100644 --- a/docs/PYTHON_TEST_DISPOSITION.md +++ b/docs/PYTHON_TEST_DISPOSITION.md @@ -52,7 +52,7 @@ gain tests; it does not move down. | test_contract_config.py | ported | Contract sourced from configuration, in `config.rs` and `collectors/contract_checker.rs`. | | test_contracts.py | ported | `collectors/contract_checker.rs`. | | test_core.py | ported | `core.rs` (ids, time, digests, principals). | -| test_decisions.py | unported | Decision entity and `decision.*` operations (WI-1321, migration 0023). Owner WI-1069. | +| test_decisions.py | ported | Decision entity and `decision.*` operations (WI-1321, migration 0023): `application/services/decisions_test.rs`. | | test_delivery.py | ported | `delivery.rs`. | | test_demo_delivery.py | retired | Browser-only public demo delivery. Not the core. | | test_diagnostics.py | ported | Instance diagnostics service. | diff --git a/engine/core/src/application/grants.rs b/engine/core/src/application/grants.rs index 7ac024353..958a4641a 100644 --- a/engine/core/src/application/grants.rs +++ b/engine/core/src/application/grants.rs @@ -392,7 +392,7 @@ fn check_requester( } /// The engine session the calling agent runs in, from its identity. -fn own_engine_session( +pub(crate) fn own_engine_session( ctx: &AppContext, ) -> Result, VogtError> { let reference = &ctx.principal.identity_ref; diff --git a/engine/core/src/application/services/decisions.rs b/engine/core/src/application/services/decisions.rs new file mode 100644 index 000000000..5a9dbb72b --- /dev/null +++ b/engine/core/src/application/services/decisions.rs @@ -0,0 +1,447 @@ +//! Decisions a person makes for an agent (WI-1321). Ports +//! `application/services/decisions.py`. +//! +//! An agent — usually an overseer — puts a question to a person with two to +//! four options, one recommended; a person answers it from the Inbox. The +//! record is shaped like a session grant, so the Inbox and the person-only rule +//! are reused. +//! +//! Three rules shape this module: +//! +//! - Only a person answers. `answer` refuses every agent principal before +//! anything else. +//! - Nothing is applied. A decision is not a grant and not a gate: the asker +//! reads the answer and acts through the op that checks the action. +//! - Expiry implies no answer. A pending decision past `expires_at` reads +//! `expired`, computed on read; it cannot be answered, and there is no +//! background job. + +use std::sync::Arc; + +use serde_json::{json, Value}; + +use crate::application::context::{write_of, AppContext, Built}; +use crate::application::grants::own_engine_session; +use crate::application::services::now_of; +use crate::application::writes::{audited_write, validate_reason, WriteOutcome}; +use crate::core::{ + Clock, Decision, DecisionAnswer, DecisionOption, DecisionState, IdFactory, Moment, +}; +use crate::errors::VogtError; +use crate::storage::interface::{DeclaredStore, ReadView, WriteTxn}; + +const DECISION_ASKED_EVENT: &str = "decision.asked"; +const DECISION_ANSWERED_EVENT: &str = "decision.answered"; +const DECISION_WITHDRAWN_EVENT: &str = "decision.withdrawn"; +const DEFAULT_TTL_SECONDS: i64 = 86_400; +/// Python's `ReadView.list_decisions` default: the Inbox reads at most this many. +const INBOX_PENDING_LIMIT: i64 = 200; + +pub fn ask_decision_op(built: &Built, params: Value) -> Result { + crate::with_ctx!(built, |ctx| ask(ctx, ¶ms)) +} + +pub fn answer_decision_op(built: &Built, params: Value) -> Result { + crate::with_ctx!(built, |ctx| answer(ctx, ¶ms)) +} + +pub fn withdraw_decision_op(built: &Built, params: Value) -> Result { + crate::with_ctx!(built, |ctx| withdraw(ctx, ¶ms)) +} + +pub fn list_decisions_op(built: &Built, params: Value) -> Result { + crate::with_ctx!(built, |ctx| list(ctx, ¶ms)) +} + +/// Record a question for a person; the Inbox shows it until it is answered. +fn ask( + ctx: &AppContext, + params: &Value, +) -> Result { + let reason = validate_reason(&required(params, "decision.ask", "reason")?)?; + let question = required(params, "decision.ask", "question")? + .trim() + .to_string(); + if question.is_empty() { + return Err(VogtError::InvalidRequest( + "a decision asks a question".to_string(), + )); + } + let options = options_of(params)?; + let context = text(params, "context").trim().to_string(); + let ttl_seconds = params + .get("ttl_seconds") + .and_then(Value::as_i64) + .unwrap_or(DEFAULT_TTL_SECONDS); + let work_item_id = match text_opt(params, "work_item") { + Some(reference) => Some( + ctx.declared + .read()? + .work_item_by_ref(reference.trim())? + .ok_or_else(|| { + VogtError::NotFound(format!( + "no work item {}", + crate::core::py_repr(&reference) + )) + })? + .id, + ), + None => None, + }; + // The asker's session is the token's, never a parameter. + let asker_session = if ctx.principal.kind == crate::core::ActorKind::Agent { + own_engine_session(ctx)? + } else { + None + }; + + let mut write = write_of(ctx); + let clock = Arc::clone(write.clock()); + let ids = Arc::clone(write.ids()); + audited_write(&mut write, "decision.ask", &reason, |txn, actor| { + let now = now_of(&clock); + let decision = Decision { + id: ids + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .next("dec"), + asker_actor_id: actor.id.clone(), + asker_session: asker_session.clone(), + work_item_id: work_item_id.clone(), + question: question.clone(), + options: options.clone(), + context: context.clone(), + state: DecisionState::Pending, + answer: None, + answered_by_actor_id: None, + created_at: now, + answered_at: None, + expires_at: plus_seconds(now, ttl_seconds), + reason: reason.clone(), + }; + txn.insert_decision(&decision)?; + outcome(&*txn, &decision, DECISION_ASKED_EVENT, now) + }) +} + +/// Answer a pending decision with one of its options. A person only. +fn answer( + ctx: &AppContext, + params: &Value, +) -> Result { + let reason = validate_reason(&required(params, "decision.answer", "reason")?)?; + if ctx.principal.kind == crate::core::ActorKind::Agent { + return Err(VogtError::PersonRequired(format!( + "only a person answers a decision, not an agent ({}); leave it for the Inbox", + ctx.principal.identity_ref + ))); + } + let id = required(params, "decision.answer", "id")?; + let decision = load(ctx, &id)?; + let label = required(params, "decision.answer", "label")? + .trim() + .to_string(); + if !decision.options.iter().any(|option| option.label == label) { + let offered = decision + .options + .iter() + .map(|option| crate::core::py_repr(&option.label)) + .collect::>() + .join(", "); + return Err(VogtError::InvalidRequest(format!( + "{} is not an option of {}; it offers {offered}", + crate::core::py_repr(&label), + decision.id + ))); + } + let note = text(params, "note").trim().to_string(); + + let mut write = write_of(ctx); + let clock = Arc::clone(write.clock()); + audited_write(&mut write, "decision.answer", &reason, |txn, actor| { + let now = now_of(&clock); + let current = pending(&*txn, &decision.id, now)?; + let answered = Decision { + state: DecisionState::Answered, + answer: Some(DecisionAnswer { + label: label.clone(), + note: note.clone(), + }), + answered_by_actor_id: Some(actor.id.clone()), + answered_at: Some(now), + ..current + }; + txn.update_decision(&answered)?; + outcome(&*txn, &answered, DECISION_ANSWERED_EVENT, now) + }) +} + +/// Withdraw a pending decision: a person, or the session that asked. +fn withdraw( + ctx: &AppContext, + params: &Value, +) -> Result { + let reason = validate_reason(&required(params, "decision.withdraw", "reason")?)?; + let id = required(params, "decision.withdraw", "id")?; + let decision = load(ctx, &id)?; + if ctx.principal.kind == crate::core::ActorKind::Agent && !is_asker(ctx, &decision)? { + return Err(VogtError::GrantRefused(format!( + "only a person, or the session that asked, withdraws a decision ({} did neither)", + ctx.principal.identity_ref + ))); + } + + let mut write = write_of(ctx); + let clock = Arc::clone(write.clock()); + audited_write(&mut write, "decision.withdraw", &reason, |txn, _actor| { + let now = now_of(&clock); + let current = pending(&*txn, &decision.id, now)?; + let withdrawn = Decision { + state: DecisionState::Withdrawn, + ..current + }; + txn.update_decision(&withdrawn)?; + outcome(&*txn, &withdrawn, DECISION_WITHDRAWN_EVENT, now) + }) +} + +/// Decisions, newest first, by state, work item or asker. +fn list( + ctx: &AppContext, + params: &Value, +) -> Result { + let state = text_opt(params, "state"); + let limit = params.get("limit").and_then(Value::as_i64).unwrap_or(50); + // `expired` is not stored: it is a pending row past its expiry. + let stored_state = match state.as_deref() { + Some("expired") => Some("pending"), + other => other, + }; + let view = ctx.declared.read()?; + let work_item_id = match text_opt(params, "work_item") { + Some(reference) => Some( + view.work_item_by_ref(reference.trim())? + .ok_or_else(|| { + VogtError::NotFound(format!( + "no work item {}", + crate::core::py_repr(&reference) + )) + })? + .id, + ), + None => None, + }; + let asker_id = match text_opt(params, "asker") { + Some(asker) => match view.actor_by_identity(asker.trim())? { + Some(actor) => Some(actor.id), + None => return Ok(json!({ "decisions": [] })), + }, + None => None, + }; + let now = now_of(&ctx.clock); + let rows = view.list_decisions( + stored_state, + work_item_id.as_deref(), + asker_id.as_deref(), + limit, + )?; + let mut views = Vec::new(); + for row in rows { + if matches!(state.as_deref(), Some("pending" | "expired")) + && Some(row.effective_state(now).as_str()) != state.as_deref() + { + continue; + } + views.push(view_of(&view, &row, now)?); + } + Ok(json!({ "decisions": views })) +} + +/// What the Inbox shows: every decision still waiting for a person, newest +/// first. Ports `pending_decisions` in the Python service. +pub fn pending_decisions(view: &impl ReadView, now: Moment) -> Result, VogtError> { + Ok(view + .list_decisions(Some("pending"), None, None, INBOX_PENDING_LIMIT)? + .into_iter() + .filter(|decision| decision.effective_state(now) == "pending") + .collect()) +} + +// -- helpers ----------------------------------------------------------------- + +fn is_asker( + ctx: &AppContext, + decision: &Decision, +) -> Result { + let caller = ctx + .declared + .read()? + .actor_by_identity(&ctx.principal.identity_ref)?; + if caller.is_some_and(|actor| actor.id == decision.asker_actor_id) { + return Ok(true); + } + let own = own_engine_session(ctx)?; + Ok(own.is_some() && own == decision.asker_session) +} + +fn load(ctx: &AppContext, id: &str) -> Result { + let found = ctx.declared.read()?.decision(id.trim())?; + found.ok_or_else(|| VogtError::NotFound(format!("no decision {}", crate::core::py_repr(id)))) +} + +/// The stored decision, if it is still pending at `now`. Anything else is a +/// conflict, and the message names the state the decision is in. +fn pending(txn: &impl ReadView, id: &str, now: Moment) -> Result { + let current = txn + .decision(id)? + .ok_or_else(|| VogtError::NotFound(format!("no decision {}", crate::core::py_repr(id))))?; + let state = current.effective_state(now); + if state != "pending" { + return Err(VogtError::Conflict(format!( + "decision {} is {state}, not pending", + current.id + ))); + } + Ok(current) +} + +fn outcome( + txn: &impl ReadView, + decision: &Decision, + event: &str, + now: Moment, +) -> Result, VogtError> { + Ok(WriteOutcome { + result: json!({ "decision": view_of(txn, decision, now)? }), + entity_kind: "decision".to_string(), + entity_id: decision.id.clone(), + payload: serde_json::to_value(decision).unwrap_or(Value::Null), + event_kind: event.to_string(), + summary: summary_of(txn, decision)?, + }) +} + +/// A decision as callers read it. Ports `_view` in the Python service. +fn view_of(view: &impl ReadView, decision: &Decision, now: Moment) -> Result { + Ok(json!({ + "id": decision.id, + "asker": identity_of(view, &decision.asker_actor_id)?, + "asker_session": decision.asker_session, + "work_item": work_ref_of(view, decision.work_item_id.as_deref())?, + "question": decision.question, + "options": decision.options.iter().map(option_json).collect::>(), + "context": decision.context, + "state": decision.effective_state(now), + "answer": decision.answer.as_ref().map(|answer| json!({ + "label": answer.label, + "note": answer.note, + })), + "answered_by": match decision.answered_by_actor_id.as_deref() { + Some(id) => Some(identity_of(view, id)?), + None => None, + }, + "created_at": decision.created_at.to_json(), + "answered_at": decision.answered_at.map(|moment| moment.to_json()), + "expires_at": decision.expires_at.to_json(), + "reason": decision.reason, + })) +} + +fn option_json(option: &DecisionOption) -> Value { + json!({ + "label": option.label, + "description": option.description, + "recommended": option.recommended, + }) +} + +fn summary_of(view: &impl ReadView, decision: &Decision) -> Result { + Ok(json!({ + "decision_id": decision.id, + "asker_session": decision.asker_session, + "work_item": work_ref_of(view, decision.work_item_id.as_deref())?, + "question": decision.question.chars().take(200).collect::(), + "state": decision.state.to_string(), + "answer": decision.answer.as_ref().map(|answer| answer.label.clone()), + })) +} + +fn identity_of(view: &impl ReadView, actor_id: &str) -> Result { + Ok(view + .actor_by_id(actor_id)? + .map_or_else(|| actor_id.to_string(), |actor| actor.identity_ref)) +} + +fn work_ref_of( + view: &impl ReadView, + work_item_id: Option<&str>, +) -> Result, VogtError> { + match work_item_id { + None => Ok(None), + Some(id) => Ok(Some( + view.work_item_by_id(id)? + .map_or_else(|| id.to_string(), |item| item.reference), + )), + } +} + +/// `options` as the caller wrote them: two to four, labelled, exactly one +/// recommended, labels distinct. +fn options_of(params: &Value) -> Result, VogtError> { + let raw = params + .get("options") + .cloned() + .ok_or_else(|| VogtError::InvalidRequest("decision.ask needs options".to_string()))?; + let parsed: Vec = serde_json::from_value(raw) + .map_err(|err| VogtError::InvalidRequest(format!("options: {err}")))?; + let options: Vec = parsed + .into_iter() + .map(|option| DecisionOption { + label: option.label.trim().to_string(), + description: option.description.trim().to_string(), + recommended: option.recommended, + }) + .collect(); + if options.iter().any(|option| option.label.is_empty()) { + return Err(VogtError::InvalidRequest( + "every option has a label".to_string(), + )); + } + let mut labels: Vec<&str> = options.iter().map(|option| option.label.as_str()).collect(); + labels.sort_unstable(); + labels.dedup(); + if labels.len() != options.len() { + return Err(VogtError::InvalidRequest( + "option labels must differ".to_string(), + )); + } + if options.iter().filter(|option| option.recommended).count() != 1 { + return Err(VogtError::InvalidRequest( + "exactly one option is recommended".to_string(), + )); + } + Ok(options) +} + +fn plus_seconds(moment: Moment, seconds: i64) -> Moment { + Moment::from_unix( + moment.unix_seconds().saturating_add(seconds), + moment.nanos(), + ) +} + +fn required(params: &Value, operation: &str, name: &str) -> Result { + params + .get(name) + .and_then(Value::as_str) + .map(str::to_string) + .ok_or_else(|| VogtError::InvalidRequest(format!("{operation} needs a {name}"))) +} + +fn text(params: &Value, name: &str) -> String { + text_opt(params, name).unwrap_or_default() +} + +fn text_opt(params: &Value, name: &str) -> Option { + params.get(name).and_then(Value::as_str).map(str::to_string) +} diff --git a/engine/core/src/application/services/decisions_test.rs b/engine/core/src/application/services/decisions_test.rs new file mode 100644 index 000000000..20d1cb7b4 --- /dev/null +++ b/engine/core/src/application/services/decisions_test.rs @@ -0,0 +1,360 @@ +//! The decision service, ported from `tests/test_decisions.py` (WI-1321). +//! +//! Each test opens one data directory as a person, then reopens it as the +//! principal it needs: an agent session, a second agent, or a later clock. +//! Nothing reaches an engine, so no stand-in is needed. + +use std::path::{Path, PathBuf}; + +use serde_json::{json, Value}; + +use crate::application::context::{build_context, Built}; +use crate::application::services::{decisions, history, inbox}; +use crate::config::VogtConfig; +use crate::core::{ActorKind, Decision, Moment, Principal, SequentialIds, StepClock}; +use crate::errors::VogtError; +use crate::storage::interface::{DeclaredStore, ObservedStore, ReadView}; + +const SESSION: &str = "agent:engine:eng-overseer"; +const WORKER: &str = "agent:engine:eng-worker"; +const POD: &str = "agent:pod:vogt"; +const SESSION_REF: &str = "agent:session:ses_x"; +const WHY: &str = "decision test"; + +fn start() -> Moment { + Moment::from_unix(1_700_000_000, 0) +} + +fn unique_dir() -> PathBuf { + use std::sync::atomic::{AtomicU64, Ordering}; + static COUNTER: AtomicU64 = AtomicU64::new(0); + let n = COUNTER.fetch_add(1, Ordering::Relaxed); + let dir = std::env::temp_dir().join(format!("vogt-decisions-{}-{n}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + dir +} + +fn person() -> Principal { + Principal::new("local:test-user", ActorKind::Human, "Test").unwrap() +} + +fn agent(identity: &str) -> Principal { + Principal::new(identity, ActorKind::Agent, identity).unwrap() +} + +fn open(dir: &Path, principal: Principal, at: Moment) -> Built { + let config = VogtConfig { + data_dir: dir.to_path_buf(), + ..VogtConfig::default() + }; + build_context( + config, + Some(principal), + Some(StepClock::new(at)), + // Persisted, so a reopened directory keeps counting from its last id. + Some(SequentialIds::new(Some(dir.join("ids.json"))).unwrap()), + None, + None, + None, + None, + ) + .unwrap() +} + +/// A migrated, bootstrapped store in a fresh directory, opened as a person. +fn store() -> (PathBuf, Built) { + let dir = unique_dir(); + let built = open(&dir, person(), start()); + match &built { + Built::StepSequential(ctx) => { + ctx.declared.migrate().unwrap(); + ctx.declared.bootstrap(&person()).unwrap(); + ctx.observed.migrate().unwrap(); + } + _other => panic!("expected a step clock"), + } + (dir, built) +} + +/// A context for one principal, opened on the directory at its start clock. +/// Each action opens its own: a context keeps its id counter in memory, so a +/// second context over the same directory would draw ids the first has used. +fn me(dir: &Path) -> Built { + open(dir, person(), start()) +} + +fn as_agent(dir: &Path, identity: &str) -> Built { + open(dir, agent(identity), start()) +} + +fn options() -> Value { + json!([ + {"label": "Merge", "description": "land it now", "recommended": true}, + {"label": "Hold", "description": "wait for the review"}, + ]) +} + +fn with(mut base: Value, extra: Value) -> Value { + if let (Some(base), Some(extra)) = (base.as_object_mut(), extra.as_object()) { + for (key, value) in extra { + base.insert(key.clone(), value.clone()); + } + } + base +} + +fn ask_with(built: &Built, extra: Value) -> Result { + let params = with( + json!({ + "question": "Merge the branch?", + "options": options(), + "context": "CI is green.", + "reason": "the branch is reviewed", + }), + extra, + ); + let result = decisions::ask_decision_op(built, params)?; + Ok(result["decision"]["id"].as_str().unwrap().to_string()) +} + +fn ask(built: &Built) -> String { + ask_with(built, json!({})).unwrap() +} + +fn answer_as(built: &Built, id: &str, label: &str) -> Result { + decisions::answer_decision_op( + built, + json!({"id": id, "label": label, "note": "ok", "reason": WHY}), + ) +} + +fn withdraw_as(built: &Built, id: &str) -> Result { + decisions::withdraw_decision_op(built, json!({"id": id, "reason": WHY})) +} + +fn list(built: &Built, extra: Value) -> Vec { + decisions::list_decisions_op(built, extra).unwrap()["decisions"] + .as_array() + .unwrap() + .clone() +} + +/// The stored row, read straight from the store. +fn stored(built: &Built, id: &str) -> Decision { + let Built::StepSequential(ctx) = built else { + unreachable!("store() builds a step clock"); + }; + ctx.declared.read().unwrap().decision(id).unwrap().unwrap() +} + +fn inbox_entries(built: &Built) -> Vec { + inbox::inbox_list_op(built, json!({})).unwrap()["entries"] + .as_array() + .unwrap() + .clone() +} + +fn pending_entries(built: &Built) -> Vec { + inbox_entries(built) + .into_iter() + .filter(|entry| entry["kind"] == "decision.pending") + .collect() +} + +#[test] +fn an_agent_asks_and_its_session_comes_from_the_token() { + let (dir, _person) = store(); + let asker = as_agent(&dir, SESSION); + let id = ask_with( + &asker, + json!({"question": "Merge the branch?", "options": options(), "reason": "reviewed"}), + ) + .unwrap(); + assert!(id.starts_with("dec"), "{id}"); + let row = stored(&asker, &id); + assert_eq!(row.asker_session.as_deref(), Some("eng-overseer")); + assert_eq!( + row.expires_at.unix_seconds() - row.created_at.unix_seconds(), + 86_400 + ); + assert!(row.answer.is_none()); + let view = decisions::list_decisions_op(&asker, json!({})).unwrap(); + assert_eq!(view["decisions"][0]["state"], "pending"); + assert_eq!(view["decisions"][0]["asker"], SESSION); +} + +#[test] +fn no_agent_answers_only_a_person() { + let (dir, _person) = store(); + let id = ask(&as_agent(&dir, SESSION)); + for identity in [SESSION, SESSION_REF, POD] { + let refused = answer_as(&as_agent(&dir, identity), &id, "Merge"); + assert!( + matches!(&refused, Err(VogtError::PersonRequired(message)) if message.starts_with("only a person answers")), + "{identity}: {refused:?}" + ); + } + assert_eq!(list(&me(&dir), json!({"state": "pending"})).len(), 1); + answer_as(&me(&dir), &id, "Merge").unwrap(); + let answered = list(&me(&dir), json!({"state": "answered"})); + assert_eq!(answered.len(), 1); + assert_eq!(answered[0]["answer"]["label"], "Merge"); + assert_eq!(answered[0]["answer"]["note"], "ok"); + assert_eq!(answered[0]["answered_by"], "local:test-user"); +} + +#[test] +fn an_answer_must_be_an_offered_option() { + let (dir, _person) = store(); + let id = ask(&me(&dir)); + let refused = answer_as(&me(&dir), &id, "Deploy"); + assert!( + matches!(&refused, Err(VogtError::InvalidRequest(message)) if message.contains("not an option")), + "{refused:?}" + ); + answer_as(&me(&dir), &id, "Hold").unwrap(); + let again = answer_as(&me(&dir), &id, "Merge"); + assert!( + matches!(&again, Err(VogtError::Conflict(message)) if message.contains("answered, not pending")), + "{again:?}" + ); +} + +#[test] +fn options_are_labelled_distinct_and_exactly_one_recommended() { + let (dir, _person) = store(); + let refusal = |extra: Value| match ask_with(&me(&dir), extra) { + Err(VogtError::InvalidRequest(message)) => message, + other => panic!("expected an invalid request, got {other:?}"), + }; + assert!(refusal(json!({"options": [{"label": "a"}, {"label": "b"}]})).contains("exactly one")); + assert!(refusal(json!({"options": [ + {"label": "a", "recommended": true}, + {"label": "b", "recommended": true}, + ]})) + .contains("exactly one")); + assert!(refusal(json!({"options": [ + {"label": "Merge", "recommended": true}, + {"label": "Merge"}, + ]})) + .contains("differ")); + assert!(refusal(json!({"options": [ + {"label": " ", "recommended": true}, + {"label": "Hold"}, + ]})) + .contains("every option has a label")); + assert!(refusal(json!({"question": " "})).contains("asks a question")); +} + +#[test] +fn a_pending_decision_expires_on_read_and_cannot_be_answered() { + let (dir, _person) = store(); + let id = ask_with(&as_agent(&dir, SESSION), json!({"ttl_seconds": 60})).unwrap(); + let later = open(&dir, person(), Moment::from_unix(1_700_000_120, 0)); + let expired = list(&later, json!({"state": "expired"})); + assert_eq!(expired.len(), 1); + assert_eq!(expired[0]["id"], id.as_str()); + assert!(expired[0]["answer"].is_null()); + assert!(list(&later, json!({"state": "pending"})).is_empty()); + let refused = answer_as(&later, &id, "Merge"); + assert!( + matches!(&refused, Err(VogtError::Conflict(message)) if message.contains("expired")), + "{refused:?}" + ); + assert!(pending_entries(&later).is_empty()); +} + +#[test] +fn the_asker_or_a_person_withdraws() { + let (dir, _person) = store(); + let first = ask(&as_agent(&dir, SESSION)); + let second = ask(&as_agent(&dir, SESSION)); + let refused = withdraw_as(&as_agent(&dir, WORKER), &first); + assert!( + matches!(&refused, Err(VogtError::GrantRefused(message)) if message.contains("only a person, or the session")), + "{refused:?}" + ); + let withdrawn = withdraw_as(&as_agent(&dir, SESSION), &first).unwrap(); + assert_eq!(withdrawn["decision"]["state"], "withdrawn"); + assert!(matches!( + withdraw_as(&as_agent(&dir, SESSION), &first), + Err(VogtError::Conflict(_)) + )); + let answer_after = answer_as(&me(&dir), &first, "Merge"); + assert!( + matches!(&answer_after, Err(VogtError::Conflict(message)) if message.contains("withdrawn")), + "{answer_after:?}" + ); + let by_person = withdraw_as(&me(&dir), &second).unwrap(); + assert_eq!(by_person["decision"]["state"], "withdrawn"); +} + +#[test] +fn list_filters_by_asker() { + let (dir, _person) = store(); + let on_asker = ask(&as_agent(&dir, SESSION)); + ask(&me(&dir)); + let by_asker = list(&me(&dir), json!({"asker": SESSION})); + assert_eq!(by_asker.len(), 1); + assert_eq!(by_asker[0]["id"], on_asker.as_str()); + assert!(list(&me(&dir), json!({"asker": "nobody"})).is_empty()); +} + +#[test] +fn a_pending_decision_is_an_inbox_entry_until_answered() { + let (dir, _person) = store(); + let id = ask(&as_agent(&dir, SESSION)); + let entries = pending_entries(&me(&dir)); + assert_eq!(entries.len(), 1, "{entries:?}"); + let entry = &entries[0]; + assert_eq!(entry["action"]["kind"], "decision"); + assert_eq!(entry["action"]["decision_id"], id.as_str()); + assert_eq!(entry["session_id"], "eng-overseer"); + assert_eq!(entry["source_subject_key"], id.as_str()); + assert!(entry["title"] + .as_str() + .unwrap() + .contains("Merge the branch?")); + let summary = entry["summary"].as_str().unwrap(); + assert!( + summary.contains("Merge (recommended)") && summary.contains("Hold"), + "{summary}" + ); + assert_eq!(entry["actor_login"], SESSION); + assert_eq!(entry["actor_kind"], "bot"); + let snapshot = &entry["evidence_snapshot"]; + assert_eq!(snapshot["recommended"], "Merge"); + assert_eq!(snapshot["context"], "CI is green."); + assert_eq!(snapshot["options"][0]["label"], "Merge"); + assert_eq!(snapshot["options"][1]["label"], "Hold"); + answer_as(&me(&dir), &id, "Merge").unwrap(); + assert!(pending_entries(&me(&dir)).is_empty()); +} + +#[test] +fn every_step_is_audited() { + let (dir, _person) = store(); + let decided = ask(&me(&dir)); + answer_as(&me(&dir), &decided, "Merge").unwrap(); + let other = ask(&me(&dir)); + withdraw_as(&me(&dir), &other).unwrap(); + let audited = history::audit_list_op(&me(&dir), json!({"limit": 50})).unwrap(); + let mut operations: Vec = audited["records"] + .as_array() + .unwrap() + .iter() + .filter(|row| row["entity_kind"] == "decision") + .map(|row| row["operation"].as_str().unwrap().to_string()) + .collect(); + operations.sort(); + assert_eq!( + operations, + [ + "decision.answer", + "decision.ask", + "decision.ask", + "decision.withdraw" + ] + ); +} diff --git a/engine/core/src/application/services/inbox.rs b/engine/core/src/application/services/inbox.rs index 0112137b9..50b94bf5b 100644 --- a/engine/core/src/application/services/inbox.rs +++ b/engine/core/src/application/services/inbox.rs @@ -645,6 +645,13 @@ where } } + // Decisions waiting for a person (WI-1321): one entry per question. + for decision in + crate::application::services::decisions::pending_decisions(view, now_of(&ctx.clock))? + { + entries.push(decision_entry(view, &decision)?); + } + for session in &live { let declared = view.session_by_engine_id(&session.id)?; if let Some(blocked) = &session.blocked { @@ -1167,6 +1174,100 @@ fn grant_entry( })) } +/// A question an agent asked and a person has yet to answer (WI-1321). The +/// question, options and context are the asking agent's text: untrusted, shown +/// verbatim, never acted on. Nothing is applied when the person answers. +fn decision_entry( + view: &impl ReadView, + decision: &crate::core::Decision, +) -> Result { + let recommended = decision + .options + .iter() + .find(|option| option.recommended) + .or(decision.options.first()) + .map_or("", |option| option.label.as_str()); + let options = decision + .options + .iter() + .map(|option| { + if option.recommended { + format!("{} (recommended)", option.label) + } else { + option.label.clone() + } + }) + .collect::>() + .join(" | "); + let asker_actor = view.actor_by_id(&decision.asker_actor_id)?; + let asker = asker_actor.as_ref().map_or_else( + || decision.asker_actor_id.clone(), + |actor| actor.identity_ref.clone(), + ); + let summary = truncate( + &format!("{asker} asks: {} Options: {options}.", decision.question), + 1000, + ); + let item = match decision.work_item_id.as_deref() { + Some(id) => view.work_item_by_id(id)?, + None => None, + }; + let project = match item.as_ref().and_then(|item| item.project_id.as_deref()) { + Some(id) => view.project_by_id(id)?, + None => None, + }; + let work_item_ref = item.as_ref().map(|item| item.reference.clone()); + let actor_kind = match view.actor_by_identity(&asker)? { + Some(actor) if actor.kind == crate::core::ActorKind::Human => actors::ActorKind::Human, + _ => actors::ActorKind::Bot, + }; + let actor = ActorClass { + login: Some(asker.clone()), + kind: Some(actor_kind), + relation: actors::ActorRelation::OrgMember, + }; + let title = format!("Decision: {}", truncate(&decision.question, 120)); + Ok(entry(EntrySpec { + entry_key: format!("agent:decision:{}", decision.id), + source: "agent", + kind: "decision.pending", + occurred_at: Some(decision.created_at), + observed_at: None, + title: &title, + summary: &summary, + project_slug: project.as_ref().map(|project| project.slug.as_str()), + work_item_ref: work_item_ref.as_deref(), + source_subject_key: &decision.id, + source_url: None, + trust_state: "unverified".to_string(), + freshness: "live", + action: action_of( + "decision", + json!({"decision_id": decision.id, "session_id": decision.asker_session}), + ), + actor: &actor, + extra: json!({ + "session_id": decision.asker_session, + "provisional": true, + "evidence_snapshot": { + "decision_id": decision.id, + "asker": asker, + "asker_session": decision.asker_session, + "work_item": work_item_ref, + "question": decision.question, + "options": decision.options.iter().map(|option| json!({ + "label": option.label, + "description": option.description, + "recommended": option.recommended, + })).collect::>(), + "recommended": recommended, + "context": decision.context, + "expires_at": crate::core::to_iso(decision.expires_at), + }, + }), + })) +} + /// Python renders a missing optional as the literal `None`. fn py_none(value: Option<&str>) -> &str { value.unwrap_or("None") diff --git a/engine/core/src/application/services/mod.rs b/engine/core/src/application/services/mod.rs index 2dce2e99f..aaf5e3938 100644 --- a/engine/core/src/application/services/mod.rs +++ b/engine/core/src/application/services/mod.rs @@ -24,6 +24,7 @@ pub mod history; pub mod install; pub mod work; +pub mod decisions; pub mod import_merge; pub mod inbox; pub mod initiatives; @@ -56,6 +57,8 @@ use crate::core::{Clock, IdFactory, Moment, Principal}; use crate::storage::sqlite::declared::SqliteDeclaredStore; use std::sync::Arc; +#[cfg(test)] +mod decisions_test; #[cfg(test)] mod services_test; diff --git a/engine/core/src/core.rs b/engine/core/src/core.rs index de51bdab0..230513c7e 100644 --- a/engine/core/src/core.rs +++ b/engine/core/src/core.rs @@ -1611,6 +1611,70 @@ impl SessionGrant { } } +vocab!(DecisionState { + Pending, + Answered, + Withdrawn +}); + +/// One answer a decision offers. Exactly one per decision is recommended. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] +pub struct DecisionOption { + pub label: String, + #[serde(default)] + pub description: String, + #[serde(default)] + pub recommended: bool, +} + +/// The option a person chose, and what they added. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] +pub struct DecisionAnswer { + pub label: String, + #[serde(default)] + pub note: String, +} + +/// A question an agent put to a person, with options (WI-1321). Shaped like +/// `SessionGrant`. `expired` is not a state: it is a pending decision past +/// `expires_at`, computed on read, and it implies no answer. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Decision { + pub id: String, + pub asker_actor_id: String, + pub asker_session: Option, + pub work_item_id: Option, + pub question: String, + pub options: Vec, + #[serde(default)] + pub context: String, + #[serde(default = "decision_state_default")] + pub state: DecisionState, + pub answer: Option, + pub answered_by_actor_id: Option, + pub created_at: Moment, + pub answered_at: Option, + pub expires_at: Moment, + pub reason: String, +} + +impl Decision { + /// `expired` for a pending decision past its expiry, else the stored state. + pub fn effective_state(&self, now: Moment) -> String { + if self.state == DecisionState::Pending && self.expires_at <= now { + return "expired".to_string(); + } + self.state.to_string() + } +} + +fn decision_state_default() -> DecisionState { + DecisionState::Pending +} + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Label { diff --git a/engine/core/src/registry.rs b/engine/core/src/registry.rs index a5d4ca014..90843d84c 100644 --- a/engine/core/src/registry.rs +++ b/engine/core/src/registry.rs @@ -250,6 +250,10 @@ pub fn service_for(name: &str) -> Option { "initiative.publish" => { Some(crate::application::services::initiatives::initiative_publish_op) } + "decision.ask" => Some(crate::application::services::decisions::ask_decision_op), + "decision.answer" => Some(crate::application::services::decisions::answer_decision_op), + "decision.list" => Some(crate::application::services::decisions::list_decisions_op), + "decision.withdraw" => Some(crate::application::services::decisions::withdraw_decision_op), "inbox.list" => Some(crate::application::services::inbox::inbox_list_op), "inbox.archive" => Some(crate::application::services::inbox::inbox_archive_op), "inbox.snooze" => Some(crate::application::services::inbox::inbox_snooze_op), diff --git a/engine/core/src/storage/interface.rs b/engine/core/src/storage/interface.rs index c5b46b2b9..e31a86a39 100644 --- a/engine/core/src/storage/interface.rs +++ b/engine/core/src/storage/interface.rs @@ -14,7 +14,7 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::core::{ Actor, ActorPreference, AuditRecord, AuthDecision, CodingSession, Comment, ContractExemption, - DepRef, DriftProposal, Event, ForgeAccount, InboxTriage, Initiative, Label, Moment, + Decision, DepRef, DriftProposal, Event, ForgeAccount, InboxTriage, Initiative, Label, Moment, Observation, PasswordCredential, Principal, Project, RelationKind, SessionGrant, Suppression, Sweep, SweepOutcome, Token, WorkItem, WorkLink, WorkOverlay, Workflow, WriteBackRecord, }; @@ -436,6 +436,15 @@ pub trait ReadView { target_engine_session_id: Option<&str>, limit: i64, ) -> Result, crate::errors::VogtError>; + fn decision(&self, decision_id: &str) -> Result, crate::errors::VogtError>; + /// Newest first, by `created_at` then id, up to `limit`. + fn list_decisions( + &self, + state: Option<&str>, + work_item_id: Option<&str>, + asker_actor_id: Option<&str>, + limit: i64, + ) -> Result, crate::errors::VogtError>; fn list_sessions( &self, project_id: Option<&str>, @@ -606,6 +615,8 @@ pub trait WriteTxn: ReadView { &mut self, grant: &SessionGrant, ) -> Result<(), crate::errors::VogtError>; + fn insert_decision(&mut self, decision: &Decision) -> Result<(), crate::errors::VogtError>; + fn update_decision(&mut self, decision: &Decision) -> Result<(), crate::errors::VogtError>; fn set_session_work_item( &mut self, session_id: &str, diff --git a/engine/core/src/storage/sqlite/declared.rs b/engine/core/src/storage/sqlite/declared.rs index b45870034..45c2d6aed 100644 --- a/engine/core/src/storage/sqlite/declared.rs +++ b/engine/core/src/storage/sqlite/declared.rs @@ -10,7 +10,7 @@ use rusqlite::{params, Connection, OptionalExtension, Row}; use crate::core::{ default_workflow, from_iso, to_iso, Actor, ActorKind, ActorPreference, AuditRecord, - AuthDecision, Clock, CodingSession, Comment, ContractExemption, DriftProposal, Event, + AuthDecision, Clock, CodingSession, Comment, ContractExemption, Decision, DriftProposal, Event, ForgeAccount, IdFactory, InboxTriage, Initiative, InitiativeState, Label, Moment, PasswordCredential, Principal, Project, RelationKind, SessionGrant, Suppression, Token, WorkItem, WorkLink, WorkOverlay, Workflow, WriteBackAction, WriteBackOutcome, WriteBackRecord, @@ -1332,6 +1332,49 @@ impl ReadView for SqliteReadView { row_session_grant, ) } + fn decision(&self, decision_id: &str) -> Result, VogtError> { + one( + &self.conn, + "SELECT * FROM decisions WHERE id = ?", + [decision_id], + row_decision, + ) + } + fn list_decisions( + &self, + state: Option<&str>, + work_item_id: Option<&str>, + asker_actor_id: Option<&str>, + limit: i64, + ) -> Result, VogtError> { + let mut clauses: Vec = Vec::new(); + let mut params: Vec> = Vec::new(); + for (column, value) in [ + ("state", state), + ("work_item_id", work_item_id), + ("asker_actor_id", asker_actor_id), + ] { + if let Some(value) = value { + clauses.push(format!("{column} = ?")); + params.push(Box::new(value.to_string())); + } + } + let where_sql = if clauses.is_empty() { + String::new() + } else { + format!("WHERE {} ", clauses.join(" AND ")) + }; + params.push(Box::new(limit)); + let refs: Vec<&dyn rusqlite::ToSql> = params.iter().map(|v| v.as_ref()).collect(); + many( + &self.conn, + &format!( + "SELECT * FROM decisions {where_sql}ORDER BY created_at DESC, id DESC LIMIT ?" + ), + refs.as_slice(), + row_decision, + ) + } fn list_sessions( &self, project_id: Option<&str>, @@ -1636,6 +1679,18 @@ impl ReadView for SqliteWrite { ) -> Result, VogtError> { self.view.list_session_grants(a, b, c) } + fn decision(&self, a: &str) -> Result, VogtError> { + self.view.decision(a) + } + fn list_decisions( + &self, + a: Option<&str>, + b: Option<&str>, + c: Option<&str>, + d: i64, + ) -> Result, VogtError> { + self.view.list_decisions(a, b, c, d) + } fn list_sessions( &self, a: Option<&str>, @@ -2244,6 +2299,27 @@ impl WriteTxn for SqliteWrite { .map(|_| ()) .map_err(sql_err) } + fn insert_decision(&mut self, decision: &Decision) -> Result<(), VogtError> { + self.view.conn.execute( + "INSERT INTO decisions (id, asker_actor_id, asker_session, work_item_id, question, options_json, context, state, answer_json, answered_by_actor_id, created_at, answered_at, expires_at, reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + params![ + decision.id, decision.asker_actor_id, decision.asker_session, decision.work_item_id, + decision.question, decision_options_json(decision), decision.context, + vocab_text(decision.state), decision_answer_json(decision), + decision.answered_by_actor_id, to_iso(decision.created_at), + decision.answered_at.map(to_iso), to_iso(decision.expires_at), decision.reason, + ], + ).map(|_| ()).map_err(sql_err) + } + fn update_decision(&mut self, decision: &Decision) -> Result<(), VogtError> { + self.view.conn.execute( + "UPDATE decisions SET state = ?, answer_json = ?, answered_by_actor_id = ?, answered_at = ? WHERE id = ?", + params![ + vocab_text(decision.state), decision_answer_json(decision), + decision.answered_by_actor_id, decision.answered_at.map(to_iso), decision.id, + ], + ).map(|_| ()).map_err(sql_err) + } fn insert_session_grant(&mut self, grant: &SessionGrant) -> Result<(), VogtError> { self.view.conn.execute( "INSERT INTO session_grants (id, target_engine_session_id, kind, var, project_id, secret_name, capability, uses, ttl_seconds, reason, requested_by, requested_at, state, decided_by, decided_at, decision_reason, expires_at, revoked_by, revoked_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", @@ -2693,6 +2769,59 @@ fn row_session(row: &Row<'_>) -> rusqlite::Result { }) } +fn row_decision(row: &Row<'_>) -> rusqlite::Result { + let options_text: String = row.get("options_json")?; + let options = serde_json::from_str(&options_text).map_err(|err| { + rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(err)) + })?; + let answer = row + .get::<_, Option>("answer_json")? + .map(|text| { + serde_json::from_str(&text).map_err(|err| { + rusqlite::Error::FromSqlConversionFailure( + 0, + rusqlite::types::Type::Text, + Box::new(err), + ) + }) + }) + .transpose()?; + Ok(Decision { + id: row.get("id")?, + asker_actor_id: row.get("asker_actor_id")?, + asker_session: row.get("asker_session")?, + work_item_id: row.get("work_item_id")?, + question: row.get("question")?, + options, + context: row.get("context")?, + state: vocab_cell(row, "state")?, + answer, + answered_by_actor_id: row.get("answered_by_actor_id")?, + created_at: moment(row, "created_at")?, + answered_at: opt_moment(row, "answered_at")?, + expires_at: moment(row, "expires_at")?, + reason: row.get("reason")?, + }) +} + +/// The options as Python's `json.dumps` writes them, in field order. +fn decision_options_json(decision: &Decision) -> String { + crate::decisions::python_json_dumps( + &serde_json::to_value(&decision.options).expect("options are plain data"), + false, + ) +} + +/// The answer as Python's `json.dumps` writes it, or NULL while unanswered. +fn decision_answer_json(decision: &Decision) -> Option { + decision.answer.as_ref().map(|answer| { + crate::decisions::python_json_dumps( + &serde_json::to_value(answer).expect("an answer is plain data"), + false, + ) + }) +} + fn row_session_grant(row: &Row<'_>) -> rusqlite::Result { Ok(SessionGrant { id: row.get("id")?, From 1544c26bfb474713ba64d3bfa2067547dd9cae59 Mon Sep 17 00:00:00 2001 From: sprooty Date: Sat, 17 Oct 2026 05:58:44 +0000 Subject: [PATCH 2/3] test(parity): decision.* steps and a golden re-recorded from Python (WI-1069) Adds ten decision.ask/list/answer/withdraw steps to tests/parity/script.json and records tests/parity/golden/21c95f6-s4 (cli, http, mcp) from the Python implementation, the authority. The Rust core agrees on every step. The decision service now reads the step clock as Python does: once per row when it lists, twice in the answer body, and once for each write's view. The MCP driver shares one process across steps, so the read count shifts every later timestamp when it is wrong. Co-Authored-By: Claude Haiku 5.5 Signed-off-by: sprooty --- .../src/application/services/decisions.rs | 54 +- engine/core/src/application/services/inbox.rs | 4 +- tests/parity/golden/21c95f6-s4/cli.json | 3252 +++++++++++++++++ tests/parity/golden/21c95f6-s4/http.json | 2044 +++++++++++ tests/parity/golden/21c95f6-s4/mcp.json | 2902 +++++++++++++++ tests/parity/golden/21c95f6-s4/meta.json | 5 + tests/parity/script.json | 127 + 7 files changed, 8361 insertions(+), 27 deletions(-) create mode 100644 tests/parity/golden/21c95f6-s4/cli.json create mode 100644 tests/parity/golden/21c95f6-s4/http.json create mode 100644 tests/parity/golden/21c95f6-s4/mcp.json create mode 100644 tests/parity/golden/21c95f6-s4/meta.json diff --git a/engine/core/src/application/services/decisions.rs b/engine/core/src/application/services/decisions.rs index 5a9dbb72b..e93eb1d35 100644 --- a/engine/core/src/application/services/decisions.rs +++ b/engine/core/src/application/services/decisions.rs @@ -16,7 +16,7 @@ //! `expired`, computed on read; it cannot be answered, and there is no //! background job. -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use serde_json::{json, Value}; @@ -120,7 +120,7 @@ fn ask( reason: reason.clone(), }; txn.insert_decision(&decision)?; - outcome(&*txn, &decision, DECISION_ASKED_EVENT, now) + outcome(&*txn, &decision, DECISION_ASKED_EVENT, &clock) }) } @@ -159,8 +159,10 @@ fn answer( let mut write = write_of(ctx); let clock = Arc::clone(write.clock()); audited_write(&mut write, "decision.answer", &reason, |txn, actor| { + // Two reads, as Python makes: one to test pending, one to stamp the + // answer. The step clock hands out a fresh moment per read. + let current = pending(&*txn, &decision.id, now_of(&clock))?; let now = now_of(&clock); - let current = pending(&*txn, &decision.id, now)?; let answered = Decision { state: DecisionState::Answered, answer: Some(DecisionAnswer { @@ -172,7 +174,7 @@ fn answer( ..current }; txn.update_decision(&answered)?; - outcome(&*txn, &answered, DECISION_ANSWERED_EVENT, now) + outcome(&*txn, &answered, DECISION_ANSWERED_EVENT, &clock) }) } @@ -194,14 +196,13 @@ fn withdraw( let mut write = write_of(ctx); let clock = Arc::clone(write.clock()); audited_write(&mut write, "decision.withdraw", &reason, |txn, _actor| { - let now = now_of(&clock); - let current = pending(&*txn, &decision.id, now)?; + let current = pending(&*txn, &decision.id, now_of(&clock))?; let withdrawn = Decision { state: DecisionState::Withdrawn, ..current }; txn.update_decision(&withdrawn)?; - outcome(&*txn, &withdrawn, DECISION_WITHDRAWN_EVENT, now) + outcome(&*txn, &withdrawn, DECISION_WITHDRAWN_EVENT, &clock) }) } @@ -238,33 +239,36 @@ fn list( }, None => None, }; - let now = now_of(&ctx.clock); let rows = view.list_decisions( stored_state, work_item_id.as_deref(), asker_id.as_deref(), limit, )?; + // One clock read per row, before the state filter, as Python's list. let mut views = Vec::new(); - for row in rows { - if matches!(state.as_deref(), Some("pending" | "expired")) - && Some(row.effective_state(now).as_str()) != state.as_deref() - { - continue; - } - views.push(view_of(&view, &row, now)?); + for row in &rows { + views.push(view_of(&view, row, now_of(&ctx.clock))?); + } + if matches!(state.as_deref(), Some("pending" | "expired")) { + views.retain(|found| found["state"].as_str() == state.as_deref()); } Ok(json!({ "decisions": views })) } /// What the Inbox shows: every decision still waiting for a person, newest /// first. Ports `pending_decisions` in the Python service. -pub fn pending_decisions(view: &impl ReadView, now: Moment) -> Result, VogtError> { - Ok(view - .list_decisions(Some("pending"), None, None, INBOX_PENDING_LIMIT)? - .into_iter() - .filter(|decision| decision.effective_state(now) == "pending") - .collect()) +pub fn pending_decisions( + view: &impl ReadView, + clock: &Arc>, +) -> Result, VogtError> { + let mut pending = Vec::new(); + for decision in view.list_decisions(Some("pending"), None, None, INBOX_PENDING_LIMIT)? { + if decision.effective_state(now_of(clock)) == "pending" { + pending.push(decision); + } + } + Ok(pending) } // -- helpers ----------------------------------------------------------------- @@ -305,14 +309,16 @@ fn pending(txn: &impl ReadView, id: &str, now: Moment) -> Result( txn: &impl ReadView, decision: &Decision, event: &str, - now: Moment, + clock: &Arc>, ) -> Result, VogtError> { Ok(WriteOutcome { - result: json!({ "decision": view_of(txn, decision, now)? }), + result: json!({ "decision": view_of(txn, decision, now_of(clock))? }), entity_kind: "decision".to_string(), entity_id: decision.id.clone(), payload: serde_json::to_value(decision).unwrap_or(Value::Null), diff --git a/engine/core/src/application/services/inbox.rs b/engine/core/src/application/services/inbox.rs index 50b94bf5b..f9a7d406d 100644 --- a/engine/core/src/application/services/inbox.rs +++ b/engine/core/src/application/services/inbox.rs @@ -646,9 +646,7 @@ where } // Decisions waiting for a person (WI-1321): one entry per question. - for decision in - crate::application::services::decisions::pending_decisions(view, now_of(&ctx.clock))? - { + for decision in crate::application::services::decisions::pending_decisions(view, &ctx.clock)? { entries.push(decision_entry(view, &decision)?); } diff --git a/tests/parity/golden/21c95f6-s4/cli.json b/tests/parity/golden/21c95f6-s4/cli.json new file mode 100644 index 000000000..f14707eef --- /dev/null +++ b/tests/parity/golden/21c95f6-s4/cli.json @@ -0,0 +1,3252 @@ +[ + { + "body": { + "declared": { + "actors": [ + { + "created_at": "2026-01-02T03:04:07+00:00", + "disabled": 0, + "display_name": "parity", + "id": "act_0001", + "identity_ref": "local:parity", + "kind": "human" + }, + { + "created_at": "2026-01-02T03:04:09+00:00", + "disabled": 0, + "display_name": "agent:vogt-engine", + "id": "act_0002", + "identity_ref": "agent:vogt-engine", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:13+00:00", + "disabled": 0, + "display_name": "agent:vogt-sessions", + "id": "act_0003", + "identity_ref": "agent:vogt-sessions", + "kind": "agent" + } + ], + "audit": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "ins_0001", + "entity_kind": "instance", + "id": "aud_0001", + "operation": "instance.init", + "payload_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "reason": "instance bootstrap", + "revision": 0, + "txn_id": "txn_0001" + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12+00:00", + "entity_id": "tok_0001", + "entity_kind": "token", + "id": "aud_0002", + "operation": "token.adopt", + "payload_digest": "sha256:59549779aa0cda6825e03092b441cacac09d0003597ed7fd262fc918fdedf350", + "reason": "adopting the operator-supplied core token at init", + "revision": 1, + "txn_id": "txn_0002" + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16+00:00", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0003", + "operation": "token.adopt", + "payload_digest": "sha256:f1942d83b32c6907f41a7053a5e2b379b085799a1b1a2bb7dd9e2a327080cefc", + "reason": "adopting the operator-supplied agent token at init", + "revision": 2, + "txn_id": "txn_0003" + } + ], + "events": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12+00:00", + "audit_id": "aud_0002", + "entity_id": "tok_0001", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 1, + "summary": "{\"actor\": \"agent:vogt-engine\", \"scopes\": [\"read\", \"work.write\", \"project.write\"]}" + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16+00:00", + "audit_id": "aud_0003", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 2, + "summary": "{\"actor\": \"agent:vogt-sessions\", \"scopes\": [\"read\", \"work.write\", \"project.write\", \"writeback\"]}" + } + ], + "meta": [ + { + "key": "instance_id", + "value": "ins_0001" + }, + { + "key": "revision", + "value": "2" + }, + { + "key": "work_ref_seq", + "value": "0" + }, + { + "key": "created_at", + "value": "2026-01-02T03:04:07+00:00" + } + ], + "migration_lock": [ + { + "acquired_at": null, + "holder": null, + "id": 1 + } + ], + "migrations": [ + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "83cf77f4abe04e89c2efa22271c6d9275532f1dd37a270b7667226b3748ae643", + "id": "0001_foundation" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "f690b73e4b9523cd85b818ac11c6fecf086c020683324dccbc03ec3d7b59627d", + "id": "0002_work" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "9032649fc02951fe5d6340a6f9576a45b8d7197057990c68dca834a1d26f48b8", + "id": "0003_observed_first" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "cc588f67774949d31caf0305200e557cc666617b8f5a2469f958f511944ee3f4", + "id": "0004_drift" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "0e2adf0935fc90a6810710297ab3490edb430b47d9d169a2166ae189c24223b2", + "id": "0005_tokens" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "37b95c74c33d9d7c5ee649ab72b265b4de517b41a40cb3a2c768099f210b0f58", + "id": "0006_writeback" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "47c82ee427931ca7c0a3a8d047298ffe5c2b2ad49ff06f83b00acf59306a1ff6", + "id": "0007_sessions" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "f533369dd3625830341933caac68f4611a02d0e4055b9d37a62db9c59f731ce3", + "id": "0008_superseded_drift" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "b0ca1559346b05c6ef818b3b65c2ae798f05c755c7a0dd326c73227da770c226", + "id": "0009_inbox_triage" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "de3f70faf7ccb5fd276455a9a56eb7a731c8334fb6964ea6d5eb2407dbcb03f5", + "id": "0010_session_model" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "7b4ab541ce8b97e31f1871373c6400e7a6390bdc70356fe703f3aa5e29211582", + "id": "0011_contract_adoption" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "c819361c246e6c76035ed27c1a296d723235259c449a2ee2ce192163b8cb8a3a", + "id": "0012_forge_accounts" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "fa8e380cd0761fe49bf3a46fd7e9305799fe8f142fdc331c71d2a0836fa00c3f", + "id": "0013_upstream_truth" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "2fef3fae64abd522ef65b21eef63f5a93d2b44ded81e5836da1288639a59a70d", + "id": "0014_native_migration" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "6f60bd5cae2d90f79cfb72228c88b21927958bdeedc7f00da59835c3e18a928c", + "id": "0015_work_overlay_branches" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "e9d6f101a8d95929b51b6dd9a4d1ccc0e3a732e49fd605c60de3ee849be7a113", + "id": "0016_perf_indexes" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "3131bbedaed1c58d3997598f18e8f30d103ec6f1447d38dcaa2a2846f1b7d8f8", + "id": "0017_password_credentials" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "a04732a0b920af03fc8731b513260caf81d25216533efb8c8eac030185371c00", + "id": "0018_actor_preferences" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "2c307b06be247b5b5739230750785d9209dee953a22da0354952ebe1cf45811d", + "id": "0019_session_grants" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "e5fd2b7bd062d50c9d21a923df3176e9e5b69e9fb06b0dd6bf75b295d694b761", + "id": "0020_install_latch" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "9f11063cfd66c2940b21819453fa18f16123b56f59ce77f2bad74d50523d5127", + "id": "0021_agent_policy" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "1a86e121ab2879671fd44ee5a9276e05d4af19bb4b98e71e3426634958be7078", + "id": "0022_comment_kind" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "8fb4506bf7517137f5e20b26525268bda7af4bcd45057731547564d5428f615e", + "id": "0023_decisions" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "806ade321a5da9c91bbbbff49927587f48cb214cadca672c1b5a705d60662cf9", + "id": "0024_session_started_by" + } + ], + "tokens": [ + { + "actor_id": "act_0002", + "created_at": "2026-01-02T03:04:11+00:00", + "expires_at": null, + "id": "tok_0001", + "kind": "api", + "last_used_at": null, + "name": "bootstrap core token", + "revoked_at": null, + "revoked_reason": null, + "scopes": "[\"read\", \"work.write\", \"project.write\"]", + "token_hash": "9545b721f3788049180e1ccb546015bce1287aeb505e54d04a01344bb38fe4f6" + }, + { + "actor_id": "act_0003", + "created_at": "2026-01-02T03:04:15+00:00", + "expires_at": null, + "id": "tok_0002", + "kind": "api", + "last_used_at": null, + "name": "bootstrap agent token", + "revoked_at": null, + "revoked_reason": null, + "scopes": "[\"read\", \"work.write\", \"project.write\", \"writeback\"]", + "token_hash": "d3ff8dd34b248fd9895c7ca25e883f4bc79b2321c11f6ebee6a619c6e702ce71" + } + ], + "workflow_defs": [ + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "feature", + "updated_at": "2026-01-02T03:04:05+00:00" + }, + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "bug", + "updated_at": "2026-01-02T03:04:05+00:00" + }, + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "chore", + "updated_at": "2026-01-02T03:04:05+00:00" + }, + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "question", + "updated_at": "2026-01-02T03:04:05+00:00" + } + ] + }, + "observed": { + "meta": [ + { + "key": "instance_id", + "value": "ins_0001" + }, + { + "key": "created_at", + "value": "2026-01-02T03:04:08+00:00" + } + ], + "migration_lock": [ + { + "acquired_at": null, + "holder": null, + "id": 1 + } + ], + "migrations": [ + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "c050f9e4dba983119f045b8fb4ccbe8b1ee97473170eead7d28279e99468de4b", + "id": "0001_foundation" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "9cdc9c9179740af96606bab0b557c7b35639eaabc78faf16630c7705f1d368f5", + "id": "0002_evidence" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "8bf0b8533774a156762fddc2e56bbb9656ba3b6bf0e38ec85a66c9dc9c325ed1", + "id": "0003_inherited_dep_refs" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "8d5cffbb2acb6ff18099ad73384eaa4ea5ebca7a6f45afb79d353fb4cbbcaee6", + "id": "0004_forge_sync" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "e6902e6616ab9948f9d222b2763ce9bc421a136b6acb1422240e5957683e5e45", + "id": "0005_perf_indexes" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "e0e959f4bf4f39db47d5e41ac11391d5f80a0b73bcce57d36168572062b1ab66", + "id": "0006_agent_activity" + } + ] + } + }, + "exit": 0, + "operation": "dump.after_init" + }, + { + "body": { + "clone": null, + "counts": { + "actors": 3, + "audit": 3, + "events": 2, + "initiatives": 0, + "projects": 0, + "work_items": 0 + }, + "data_dir": "", + "declared_schema_version": 24, + "disk": null, + "instance_id": "ins_0001", + "observed_schema_version": 6, + "principal": "local:parity", + "revision": 2, + "vogt_version": "0.7.17" + }, + "exit": 0, + "operation": "status" + }, + { + "body": { + "data_dir": "", + "declared_schema_expected": 24, + "declared_schema_version": 24, + "migrations_applied": [], + "observed_schema_expected": 6, + "observed_schema_version": 6 + }, + "exit": 0, + "operation": "migrate" + }, + { + "body": { + "workflows": [ + { + "initial_state": "open", + "kind": "feature", + "states": [ + "blocked", + "done", + "in_progress", + "open", + "review", + "wont_do" + ], + "transitions": { + "blocked": [ + "open", + "in_progress", + "wont_do" + ], + "done": [ + "open" + ], + "in_progress": [ + "review", + "blocked", + "open", + "wont_do" + ], + "open": [ + "in_progress", + "blocked", + "wont_do" + ], + "review": [ + "done", + "in_progress", + "blocked", + "wont_do" + ], + "wont_do": [ + "open" + ] + } + }, + { + "initial_state": "open", + "kind": "bug", + "states": [ + "blocked", + "done", + "in_progress", + "open", + "review", + "wont_do" + ], + "transitions": { + "blocked": [ + "open", + "in_progress", + "wont_do" + ], + "done": [ + "open" + ], + "in_progress": [ + "review", + "blocked", + "open", + "wont_do" + ], + "open": [ + "in_progress", + "blocked", + "wont_do" + ], + "review": [ + "done", + "in_progress", + "blocked", + "wont_do" + ], + "wont_do": [ + "open" + ] + } + }, + { + "initial_state": "open", + "kind": "chore", + "states": [ + "blocked", + "done", + "in_progress", + "open", + "review", + "wont_do" + ], + "transitions": { + "blocked": [ + "open", + "in_progress", + "wont_do" + ], + "done": [ + "open" + ], + "in_progress": [ + "review", + "blocked", + "open", + "wont_do" + ], + "open": [ + "in_progress", + "blocked", + "wont_do" + ], + "review": [ + "done", + "in_progress", + "blocked", + "wont_do" + ], + "wont_do": [ + "open" + ] + } + }, + { + "initial_state": "open", + "kind": "question", + "states": [ + "blocked", + "done", + "in_progress", + "open", + "review", + "wont_do" + ], + "transitions": { + "blocked": [ + "open", + "in_progress", + "wont_do" + ], + "done": [ + "open" + ], + "in_progress": [ + "review", + "blocked", + "open", + "wont_do" + ], + "open": [ + "in_progress", + "blocked", + "wont_do" + ], + "review": [ + "done", + "in_progress", + "blocked", + "wont_do" + ], + "wont_do": [ + "open" + ] + } + } + ] + }, + "exit": 0, + "operation": "workflow.list" + }, + { + "body": { + "actor": { + "created_at": "2026-01-02T03:04:05Z", + "disabled": false, + "display_name": "Parity Agent", + "id": "act_0004", + "identity_ref": "agent:parity", + "kind": "agent" + } + }, + "exit": 0, + "operation": "actor.create" + }, + { + "body": { + "actors": [ + { + "created_at": "2026-01-02T03:04:05Z", + "disabled": false, + "display_name": "Parity Agent", + "id": "act_0004", + "identity_ref": "agent:parity", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:09Z", + "disabled": false, + "display_name": "agent:vogt-engine", + "id": "act_0002", + "identity_ref": "agent:vogt-engine", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:13Z", + "disabled": false, + "display_name": "agent:vogt-sessions", + "id": "act_0003", + "identity_ref": "agent:vogt-sessions", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:07Z", + "disabled": false, + "display_name": "parity", + "id": "act_0001", + "identity_ref": "local:parity", + "kind": "human" + } + ] + }, + "exit": 0, + "operation": "actor.list" + }, + { + "body": { + "label": { + "color": "#d73a4a", + "created_at": "2026-01-02T03:04:05Z", + "id": "lbl_0001", + "name": "parity" + } + }, + "exit": 0, + "operation": "label.create" + }, + { + "body": { + "labels": [ + { + "color": "#d73a4a", + "created_at": "2026-01-02T03:04:05Z", + "id": "lbl_0001", + "name": "parity" + } + ] + }, + "exit": 0, + "operation": "label.list" + }, + { + "body": { + "checked_at": null, + "contract_version": "v1", + "criteria": [ + { + "applicable": true, + "detail": "directory exists", + "rule": "path.exists", + "satisfied": true, + "target": "", + "tracked": null + }, + { + "applicable": true, + "detail": "AGENTS.md is missing", + "rule": "required_file", + "satisfied": false, + "target": "AGENTS.md", + "tracked": null + }, + { + "applicable": true, + "detail": "README.md is missing", + "rule": "required_file", + "satisfied": false, + "target": "README.md", + "tracked": null + }, + { + "applicable": true, + "detail": "LICENSE is missing", + "rule": "required_file", + "satisfied": false, + "target": "LICENSE", + "tracked": null + }, + { + "applicable": true, + "detail": "docs/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "docs", + "tracked": null + }, + { + "applicable": true, + "detail": "design/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "design", + "tracked": null + }, + { + "applicable": true, + "detail": "src/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "src", + "tracked": null + } + ], + "detail": null, + "failing": [ + { + "applicable": true, + "detail": "AGENTS.md is missing", + "rule": "required_file", + "satisfied": false, + "target": "AGENTS.md", + "tracked": null + }, + { + "applicable": true, + "detail": "README.md is missing", + "rule": "required_file", + "satisfied": false, + "target": "README.md", + "tracked": null + }, + { + "applicable": true, + "detail": "LICENSE is missing", + "rule": "required_file", + "satisfied": false, + "target": "LICENSE", + "tracked": null + }, + { + "applicable": true, + "detail": "docs/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "docs", + "tracked": null + }, + { + "applicable": true, + "detail": "design/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "design", + "tracked": null + }, + { + "applicable": true, + "detail": "src/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "src", + "tracked": null + } + ], + "inapplicable": [], + "path": "", + "project": null, + "recommendations": [ + { + "instruction": "`project scaffold` writes AGENTS.md into this project without overwriting anything that is already there.", + "remedy": "scaffold", + "rule": "required_file", + "target": "AGENTS.md" + }, + { + "instruction": "`project scaffold` writes README.md into this project without overwriting anything that is already there.", + "remedy": "scaffold", + "rule": "required_file", + "target": "README.md" + }, + { + "instruction": "choose a licence for this project and write its full text to LICENSE. Vogt deliberately does not pick one: a scaffold that silently writes MIT makes that decision for the owner.", + "remedy": "judgement", + "rule": "required_file", + "target": "LICENSE" + }, + { + "instruction": "`project scaffold` writes docs/ into this project without overwriting anything that is already there.", + "remedy": "scaffold", + "rule": "required_dir", + "target": "docs" + }, + { + "instruction": "decide what this project's design record is \u2014 diagrams, mockups, exploratory notes \u2014 and put it in design/. An empty directory satisfies the letter of the criterion and none of its point, and git cannot carry one anyway.", + "remedy": "judgement", + "rule": "required_dir", + "target": "design" + }, + { + "instruction": "`project scaffold` writes src/ into this project without overwriting anything that is already there.", + "remedy": "scaffold", + "rule": "required_dir", + "target": "src" + } + ], + "recorded": false, + "status": "non_compliant" + }, + "exit": 0, + "operation": "contract.evaluate" + }, + { + "body": { + "checked_at": null, + "contract_version": "v1", + "criteria": [ + { + "applicable": true, + "detail": "the path does not exist or is not a directory, so no criterion below was evaluated \u2014 this is 'not checked', not 'does not comply'", + "rule": "path.exists", + "satisfied": false, + "target": "/does-not-exist", + "tracked": null + } + ], + "detail": null, + "failing": [ + { + "applicable": true, + "detail": "the path does not exist or is not a directory, so no criterion below was evaluated \u2014 this is 'not checked', not 'does not comply'", + "rule": "path.exists", + "satisfied": false, + "target": "/does-not-exist", + "tracked": null + } + ], + "inapplicable": [], + "path": "/does-not-exist", + "project": null, + "recommendations": [ + { + "instruction": "decide what /does-not-exist should contain for this project and add it. Nothing here can write it for you.", + "remedy": "judgement", + "rule": "path.exists", + "target": "/does-not-exist" + } + ], + "recorded": false, + "status": "not_checked" + }, + "exit": 0, + "operation": "contract.evaluate" + }, + { + "body": { + "created_paths": [ + "/proj", + "/proj/AGENTS.md", + "/proj/LICENSE", + "/proj/README.md", + "/proj/design", + "/proj/docs", + "/proj/src" + ], + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "landing": null, + "main_branches": [], + "merge_deploys_prod": false, + "review": null, + "rules": [], + "session_wall_clock_minutes": null, + "test_recipe": "", + "verification": null + }, + "compliance_checked_at": null, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:05Z", + "contract_version": null, + "created_at": "2026-01-02T03:04:05Z", + "current_version": null, + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "incubating", + "link_state": "unlinked", + "name": "Parity", + "repo_url": null, + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z", + "write_back": "none" + }, + "skipped_paths": [] + }, + "exit": 0, + "operation": "project.create" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "landing": null, + "main_branches": [], + "merge_deploys_prod": false, + "review": null, + "rules": [], + "session_wall_clock_minutes": null, + "test_recipe": "", + "verification": null + }, + "compliance_checked_at": null, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:05Z", + "contract_version": null, + "created_at": "2026-01-02T03:04:05Z", + "current_version": null, + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "incubating", + "link_state": "unlinked", + "name": "Parity", + "repo_url": null, + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z", + "write_back": "none" + } + }, + "exit": 0, + "operation": "project.get" + }, + { + "body": { + "projects": [ + { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "landing": null, + "main_branches": [], + "merge_deploys_prod": false, + "review": null, + "rules": [], + "session_wall_clock_minutes": null, + "test_recipe": "", + "verification": null + }, + "compliance_checked_at": null, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:05Z", + "contract_version": null, + "created_at": "2026-01-02T03:04:05Z", + "current_version": null, + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "incubating", + "link_state": "unlinked", + "name": "Parity", + "repo_url": null, + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z", + "writable": false, + "writable_reason": "not forge-linked: work.create refuses with project_not_linked. Pass local_only=true for a local record, or link (`forge link`) or publish (`forge publish`) the project.", + "write_back": "none" + } + ], + "total": 1 + }, + "exit": 0, + "operation": "project.list" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "landing": null, + "main_branches": [], + "merge_deploys_prod": false, + "review": null, + "rules": [], + "session_wall_clock_minutes": null, + "test_recipe": "", + "verification": null + }, + "compliance_checked_at": null, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:05Z", + "contract_version": null, + "created_at": "2026-01-02T03:04:05Z", + "current_version": null, + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "incubating", + "link_state": "unlinked", + "name": "Parity", + "repo_url": "https://example.invalid/parity.git", + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z", + "write_back": "none" + } + }, + "exit": 0, + "operation": "project.update" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "landing": null, + "main_branches": [], + "merge_deploys_prod": false, + "review": null, + "rules": [], + "session_wall_clock_minutes": null, + "test_recipe": "", + "verification": null + }, + "compliance_checked_at": null, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:05Z", + "contract_version": null, + "created_at": "2026-01-02T03:04:05Z", + "current_version": null, + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "active", + "link_state": "unlinked", + "name": "Parity", + "repo_url": "https://example.invalid/parity.git", + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z", + "write_back": "none" + } + }, + "exit": 0, + "operation": "project.transition" + }, + { + "body": { + "created": [], + "detail": "0 written, 6 already there and left exactly as they were", + "project": "parity", + "root_path": "/proj", + "skipped": [ + "/proj/AGENTS.md", + "/proj/LICENSE", + "/proj/README.md", + "/proj/design", + "/proj/docs", + "/proj/src" + ] + }, + "exit": 0, + "operation": "project.scaffold" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "landing": null, + "main_branches": [], + "merge_deploys_prod": false, + "review": null, + "rules": [], + "session_wall_clock_minutes": null, + "test_recipe": "", + "verification": null + }, + "compliance_checked_at": null, + "compliance_status": "not_checked", + "contract_adopted_at": null, + "contract_version": null, + "created_at": "2026-01-02T03:04:05Z", + "current_version": null, + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0002", + "lifecycle_state": "active", + "link_state": "unlinked", + "name": "Second", + "repo_url": null, + "root_path": "/second", + "slug": "second", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z", + "write_back": "none" + } + }, + "exit": 0, + "operation": "project.register" + }, + { + "body": { + "initiative": { + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "id": "ini_0001", + "slug": "platform", + "state": "open", + "title": "Platform", + "updated_at": "2026-01-02T03:04:05Z", + "weight": 0 + } + }, + "exit": 0, + "operation": "initiative.create" + }, + { + "body": { + "initiatives": [ + { + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "id": "ini_0001", + "slug": "platform", + "state": "open", + "title": "Platform", + "updated_at": "2026-01-02T03:04:05Z", + "weight": 0 + } + ] + }, + "exit": 0, + "operation": "initiative.list" + }, + { + "body": { + "initiative": { + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "id": "ini_0001", + "slug": "platform", + "state": "open", + "title": "Platform work", + "updated_at": "2026-01-02T03:04:05Z", + "weight": 0 + } + }, + "exit": 0, + "operation": "initiative.update" + }, + { + "body": { + "slug": "platform", + "state": "open", + "tracking_issues": [] + }, + "exit": 0, + "operation": "initiative.publish" + }, + { + "body": null, + "exit": 1, + "operation": "initiative.publish" + }, + { + "body": { + "preferences": [] + }, + "exit": 0, + "operation": "preference.get" + }, + { + "body": { + "preference": { + "key": "inbox.filter", + "updated_at": "2026-01-02T03:04:05Z", + "value": { + "actor": "any", + "sources": [ + "ci" + ], + "triage_states": [ + "active" + ] + }, + "version": 1 + } + }, + "exit": 0, + "operation": "preference.set" + }, + { + "body": null, + "exit": 1, + "operation": "preference.set" + }, + { + "body": { + "by_reason": {}, + "detail": "nothing collected \u2014 either there is nothing to say, or the configured token cannot read notifications; `coverage` distinguishes the two", + "freshness": { + "age_seconds": null, + "collectors": "", + "detail": "no collector has completed a sweep yet", + "oldest_relevant_sweep": null, + "status": "never_swept" + }, + "notifications": [], + "scope": "the GitHub account whose token this instance is configured with; notifications are instance-scoped, not per-actor", + "total": 0, + "unread": 0 + }, + "exit": 0, + "operation": "notifications" + }, + { + "body": { + "actor_unknown_hidden": 0, + "counts": { + "active": 0, + "archived": 0, + "snoozed": 0 + }, + "coverage": { + "agent": { + "count": 0, + "detail": "no session engine is configured", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "agent", + "status": "unconfigured" + }, + "ci": { + "count": 0, + "detail": "this collector has not completed a sweep", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "ci", + "status": "unswept" + }, + "drift": { + "count": 0, + "detail": "open proposals in the declared store", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "drift", + "status": "current" + }, + "github": { + "count": 0, + "detail": "this collector has not completed a sweep", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "github", + "status": "unswept" + } + }, + "engine_available": false, + "engine_detail": "no session engine is configured", + "engine_status": "not_configured", + "entries": [], + "github_scope": "registered projects only", + "high_water": { + "agent": null, + "ci": null, + "drift": null, + "github": null + }, + "instance_scope": "registered projects only", + "next_cursor": null, + "snapshot_at": "2026-01-02T03:04:05Z" + }, + "exit": 0, + "operation": "inbox.list" + }, + { + "body": null, + "exit": 1, + "operation": "inbox.archive" + }, + { + "body": null, + "exit": 1, + "operation": "inbox.snooze" + }, + { + "body": null, + "exit": 1, + "operation": "inbox.restore" + }, + { + "body": { + "branches": [], + "comments": [], + "git": null, + "item": { + "assignee_actor_id": null, + "assignee_identity_ref": null, + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "effort": null, + "id": "wrk_0001", + "initiative_id": null, + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p2", + "project_id": null, + "project_slug": null, + "ref": "WI-1", + "relations": [], + "state": "open", + "superseded_by": null, + "title": "parity item", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "exit": 0, + "operation": "work.create" + }, + { + "body": { + "branches": [], + "comments": [], + "git": null, + "item": { + "assignee_actor_id": null, + "assignee_identity_ref": null, + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "effort": null, + "id": "wrk_0001", + "initiative_id": null, + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "project_id": null, + "project_slug": null, + "ref": "WI-1", + "relations": [], + "state": "open", + "superseded_by": null, + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "exit": 0, + "operation": "work.update" + }, + { + "body": { + "branches": [], + "comments": [], + "git": null, + "item": { + "assignee_actor_id": null, + "assignee_identity_ref": null, + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "effort": null, + "id": "wrk_0002", + "initiative_id": null, + "kind": "chore", + "labels": [ + "parity" + ], + "origin": "created", + "priority": "p2", + "project_id": null, + "project_slug": null, + "ref": "WI-2", + "relations": [], + "state": "open", + "superseded_by": null, + "title": "labelled item", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "exit": 0, + "operation": "work.create" + }, + { + "body": null, + "exit": 1, + "operation": "work.create" + }, + { + "body": { + "detail": null, + "items": [ + { + "kind": "chore", + "priority": "p1", + "project_slug": null, + "ref": "WI-1", + "state": "open", + "title": "parity item renamed" + }, + { + "kind": "chore", + "priority": "p2", + "project_slug": null, + "ref": "WI-2", + "state": "open", + "title": "labelled item" + } + ], + "link_state": null, + "mode": "summary", + "next_offset": null, + "total": 2 + }, + "exit": 0, + "operation": "work.list" + }, + { + "body": null, + "exit": 1, + "operation": "work.list" + }, + { + "body": { + "branches": [], + "comments": [], + "git": null, + "item": { + "assignee_actor_id": null, + "assignee_identity_ref": null, + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "effort": null, + "id": "wrk_0001", + "initiative_id": null, + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "project_id": null, + "project_slug": null, + "ref": "WI-1", + "relations": [], + "state": "open", + "superseded_by": null, + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "exit": 0, + "operation": "work.get" + }, + { + "body": { + "comment": { + "actor_display_name": "parity", + "actor_id": "act_0001", + "body": "parity note", + "created_at": "2026-01-02T03:04:05Z", + "handoff": null, + "id": "cmt_0001", + "kind": "note", + "work_item_id": "wrk_0001" + }, + "write_back": "skipped" + }, + "exit": 0, + "operation": "work.comment" + }, + { + "body": { + "branches": [], + "comments": [], + "git": null, + "item": { + "assignee_actor_id": null, + "assignee_identity_ref": null, + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "effort": null, + "id": "wrk_0001", + "initiative_id": null, + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "project_id": null, + "project_slug": null, + "ref": "WI-1", + "relations": [], + "state": "in_progress", + "superseded_by": null, + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "exit": 0, + "operation": "work.transition" + }, + { + "body": { + "branches": [], + "comments": [], + "git": null, + "item": { + "assignee_actor_id": null, + "assignee_identity_ref": null, + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "effort": null, + "id": "wrk_0001", + "initiative_id": null, + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "project_id": null, + "project_slug": null, + "ref": "WI-1", + "relations": [ + { + "kind": "relates_to", + "related_id": "wrk_0002", + "related_ref": "WI-2", + "related_state": "open", + "related_title": "labelled item" + } + ], + "state": "in_progress", + "superseded_by": null, + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "exit": 0, + "operation": "work.relate" + }, + { + "body": { + "branches": [], + "comments": [], + "git": null, + "item": { + "assignee_actor_id": null, + "assignee_identity_ref": null, + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "effort": null, + "id": "wrk_0001", + "initiative_id": null, + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "project_id": null, + "project_slug": null, + "ref": "WI-1", + "relations": [], + "state": "in_progress", + "superseded_by": null, + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "exit": 0, + "operation": "work.unrelate" + }, + { + "body": { + "backlog_candidates": 2, + "cells": [ + { + "items": [ + { + "assignee_actor_id": null, + "assignee_identity_ref": null, + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "effort": null, + "id": "wrk_0002", + "initiative_id": null, + "kind": "chore", + "labels": [ + "parity" + ], + "origin": "created", + "priority": "p2", + "project_id": null, + "project_slug": null, + "ref": "WI-2", + "relations": [], + "state": "open", + "superseded_by": null, + "title": "labelled item", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + } + ], + "lane_key": "", + "next_cursor": null, + "state": "open", + "total": 1 + } + ], + "column_totals": { + "in_progress": 1, + "open": 1 + }, + "declared_total": 2, + "excluded_unlinked": 0, + "lane_totals": { + "": 2 + }, + "link_state": null, + "revision": 20, + "snapshot": "eyJmaW5nZXJwcmludCI6InNoYTI1Njo2N2JkMGQ3MTUxN2JlYThkM2VkNmJjYWE0ZTFmZTY2YzYyNTM5MGRkNjg2ODI3MjM1Njk4MTUxMDg2ZDc0MDYzIiwiaGlnaF93YXRlciI6WyIyMDI2LTAxLTAyVDAzOjA0OjA1KzAwOjAwIiwiV0ktMiJdLCJraW5kIjoic25hcHNob3QiLCJyZXZpc2lvbiI6MjAsInNuYXBzaG90X2F0IjoiMjAyNi0wMS0wMlQwMzowNDowNSswMDowMCJ9", + "snapshot_at": "2026-01-02T03:04:05Z", + "total": 2 + }, + "exit": 0, + "operation": "board.list" + }, + { + "body": { + "closed_upstream": 0, + "declared": 2, + "excluded_unlinked": 0, + "freshness": { + "age_seconds": null, + "collectors": "", + "detail": "no collector has completed a sweep yet", + "oldest_relevant_sweep": null, + "status": "never_swept" + }, + "items": [ + { + "adopted_as": null, + "classified": true, + "item": null, + "kind": "chore", + "labels": [], + "observation_kind": null, + "observed_at": null, + "origin": "declared", + "priority": "p1", + "project_slug": null, + "ref": "WI-1", + "score": 52.0, + "source_url": null, + "state": "in_progress", + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + }, + { + "adopted_as": null, + "classified": true, + "item": null, + "kind": "chore", + "labels": [ + "parity" + ], + "observation_kind": null, + "observed_at": null, + "origin": "declared", + "priority": "p2", + "project_slug": null, + "ref": "WI-2", + "score": 22.0, + "source_url": null, + "state": "open", + "title": "labelled item", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + } + ], + "link_state": null, + "next_offset": null, + "observed": 0, + "scope": "global", + "suppressed": 0, + "total_considered": 2 + }, + "exit": 0, + "operation": "backlog" + }, + { + "body": null, + "exit": 1, + "operation": "backlog" + }, + { + "body": { + "closed_upstream": 0, + "declared": 0, + "excluded_unlinked": 0, + "freshness": { + "age_seconds": null, + "collectors": "", + "detail": "no collector has completed a sweep yet", + "oldest_relevant_sweep": null, + "status": "never_swept" + }, + "items": [], + "link_state": null, + "next_offset": null, + "observed": 0, + "scope": "global", + "suppressed": 0, + "total_considered": 0 + }, + "exit": 0, + "operation": "bugs" + }, + { + "body": null, + "exit": 1, + "operation": "bugs" + }, + { + "body": null, + "exit": 1, + "operation": "deps" + }, + { + "body": { + "comments": 1, + "export_format_version": 2, + "path": "/export.json", + "project": null, + "projects": 2, + "work_items": 2 + }, + "exit": 0, + "operation": "export" + }, + { + "body": { + "applied": false, + "base": "2026-01-02T03:04:05Z", + "base_source": "an export of this same instance: its exported_at", + "by_entity": { + "actor": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 4, + "updated": 0 + }, + "comment": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 1, + "updated": 0 + }, + "initiative": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 1, + "updated": 0 + }, + "label": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 1, + "updated": 0 + }, + "project": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 2, + "updated": 0 + }, + "work_item": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 2, + "updated": 0 + } + }, + "changes": [], + "conflicted": 0, + "created": 0, + "detail": "Dry run: nothing was written. Pass --apply --confirm to merge.", + "export_format_version": 2, + "instance_id": "ins_0001", + "project": null, + "projects": 2, + "skipped": 0, + "source": "/export.json", + "unchanged": 11, + "updated": 0, + "work_items": 2 + }, + "exit": 0, + "operation": "import" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "landing": null, + "main_branches": [], + "merge_deploys_prod": false, + "review": null, + "rules": [], + "session_wall_clock_minutes": null, + "test_recipe": "", + "verification": null + }, + "compliance_checked_at": null, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:05Z", + "contract_version": null, + "created_at": "2026-01-02T03:04:05Z", + "current_version": null, + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "active", + "link_state": "unlinked", + "name": "Parity", + "repo_url": "https://example.invalid/parity.git", + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z", + "write_back": "full" + } + }, + "exit": 0, + "operation": "forge.writeback" + }, + { + "body": { + "actions": [ + { + "action": "comment", + "at": "2026-01-02T03:04:06Z", + "detail": "write-back is off for this project", + "id": "wbk_0001", + "outcome": "skipped", + "policy": "none", + "reason": "parity", + "source_url": null, + "subject_key": null + } + ] + }, + "exit": 0, + "operation": "forge.actions" + }, + { + "body": { + "actions": [ + { + "action": "comment", + "at": "2026-01-02T03:04:06Z", + "detail": "write-back is off for this project", + "id": "wbk_0001", + "outcome": "skipped", + "policy": "none", + "reason": "parity", + "source_url": null, + "subject_key": null + } + ] + }, + "exit": 0, + "operation": "forge.actions" + }, + { + "body": null, + "exit": 1, + "operation": "forge.repos" + }, + { + "body": { + "accounts": [] + }, + "exit": 0, + "operation": "forge.account_status" + }, + { + "body": null, + "exit": 1, + "operation": "forge.account_link" + }, + { + "body": { + "contributions": [ + { + "contribution": 55.0, + "detail": "p1", + "input": "priority", + "value": 1.0, + "weight": 55.0 + }, + { + "contribution": 0.0, + "detail": "0.0 days since last change", + "input": "staleness", + "value": 0.0, + "weight": 0.5 + }, + { + "contribution": 0.0, + "detail": "0 item(s) depend on this one", + "input": "blocking_fan_out", + "value": 0.0, + "weight": 8.0 + }, + { + "contribution": 0.0, + "detail": "no initiative", + "input": "initiative_weight", + "value": 0.0, + "weight": 0.25 + }, + { + "contribution": -3.0, + "detail": "trust state unverified", + "input": "trust_penalty", + "value": 1.0, + "weight": -3.0 + }, + { + "contribution": 0.0, + "detail": "no open pull request", + "input": "open_pr", + "value": 0.0, + "weight": 12.0 + }, + { + "contribution": 0.0, + "detail": "no branch observed", + "input": "branch_activity", + "value": 0.0, + "weight": 8.0 + } + ], + "inputs_not_yet_available": { + "ci_red_boost": "arrives with the CI check observations it reads" + }, + "ref": "WI-1", + "title": "parity item renamed", + "total": 52.0 + }, + "exit": 0, + "operation": "why" + }, + { + "body": null, + "exit": 1, + "operation": "why" + }, + { + "body": { + "detail": null, + "items": [ + { + "assignee_actor_id": null, + "assignee_identity_ref": null, + "body": "", + "created_at": "2026-01-02T03:04:05Z", + "effort": null, + "id": "wrk_0001", + "initiative_id": null, + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "project_id": null, + "project_slug": null, + "ref": "WI-1", + "relations": [], + "state": "in_progress", + "superseded_by": null, + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:05Z" + } + ], + "link_state": null, + "mode": "full", + "next_offset": 1, + "total": 2 + }, + "exit": 0, + "operation": "work.list" + }, + { + "body": { + "detail": null, + "items": [ + { + "kind": "chore", + "priority": "p2", + "project_slug": null, + "ref": "WI-2", + "state": "open", + "title": "labelled item" + } + ], + "link_state": null, + "mode": "summary", + "next_offset": null, + "total": 2 + }, + "exit": 0, + "operation": "work.list" + }, + { + "body": { + "items": [ + { + "kind": "chore", + "priority": "p1", + "project_slug": null, + "ref": "WI-1", + "state": "in_progress", + "title": "parity item renamed" + }, + { + "kind": "chore", + "priority": "p2", + "project_slug": null, + "ref": "WI-2", + "state": "open", + "title": "labelled item" + } + ], + "missing": [ + "WI-404" + ] + }, + "exit": 0, + "operation": "work.get_many" + }, + { + "body": { + "records": [ + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "prj_0001", + "entity_kind": "project", + "id": "aud_0022", + "operation": "project.writeback", + "payload_digest": "sha256:5160587a6bd4fd6e1c8cfdfbe9e7b71a0aff7cb411b75e8ad277bea16adfdf65", + "reason": "parity harness", + "revision": 21, + "txn_id": "txn_0023" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:05Z", + "entity_id": "wrk_0001", + "entity_kind": "work_item", + "id": "aud_0021", + "operation": "work.unrelate", + "payload_digest": "sha256:919ba7f4a526f308300142a25947bcafa3b9df9267bbef6a486125710e1f95b4", + "reason": "parity", + "revision": 20, + "txn_id": "txn_0022" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "wrk_0001", + "entity_kind": "work_item", + "id": "aud_0020", + "operation": "work.relate", + "payload_digest": "sha256:919ba7f4a526f308300142a25947bcafa3b9df9267bbef6a486125710e1f95b4", + "reason": "parity", + "revision": 19, + "txn_id": "txn_0021" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "wrk_0001", + "entity_kind": "work_item", + "id": "aud_0019", + "operation": "work.transition", + "payload_digest": "sha256:e29def8eb3987143cc71db472521260af60e6205bd7f2d4cb0cad1c34d95484e", + "reason": "parity", + "revision": 18, + "txn_id": "txn_0020" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:07Z", + "entity_id": "cmt_0001", + "entity_kind": "comment", + "id": "aud_0018", + "operation": "work.comment", + "payload_digest": "sha256:c0759a6538411595d62b368f304a00fa97626be794ec4750d566dfebaf313e68", + "reason": "parity", + "revision": 17, + "txn_id": "txn_0019" + } + ], + "total": 22 + }, + "exit": 0, + "operation": "audit.list" + }, + { + "body": null, + "exit": 1, + "operation": "work.bind_branch" + }, + { + "body": null, + "exit": 1, + "operation": "work.adopt" + }, + { + "body": { + "digests": [], + "engine": "no session engine is configured (VOGT_ENGINE_URL is unset)", + "hidden_count": 0, + "sessions": [], + "total": 0, + "truncated": false + }, + "exit": 0, + "operation": "session.list" + }, + { + "body": { + "counts": { + "needs_you": 0, + "total": 0 + }, + "engine": "no session engine is configured (VOGT_ENGINE_URL is unset)", + "rows": [], + "swept_at": "2026-01-02T03:04:05Z" + }, + "exit": 0, + "operation": "session.sweep" + }, + { + "body": { + "bytes": 0, + "engine": "no session engine is configured (VOGT_ENGINE_URL is unset)", + "session_id": null, + "text": "", + "total_bytes": 0, + "truncated": false + }, + "exit": 0, + "operation": "session.log_tail" + }, + { + "body": null, + "exit": 1, + "operation": "session.start" + }, + { + "body": null, + "exit": 1, + "operation": "session.input" + }, + { + "body": null, + "exit": 1, + "operation": "session.screen" + }, + { + "body": null, + "exit": 1, + "operation": "session.wait" + }, + { + "body": null, + "exit": 1, + "operation": "session.answer" + }, + { + "body": null, + "exit": 1, + "operation": "session.hibernate" + }, + { + "body": null, + "exit": 1, + "operation": "session.keep_awake" + }, + { + "body": null, + "exit": 1, + "operation": "session.set_role" + }, + { + "body": null, + "exit": 1, + "operation": "session.wake" + }, + { + "body": null, + "exit": 1, + "operation": "session.stop" + }, + { + "body": null, + "error": "error: engine_unavailable: no session engine is configured, and chats run on it (set VOGT_ENGINE_URL)", + "exit": 1, + "operation": "chat.list" + }, + { + "body": null, + "error": "error: invalid_request: 'nope' is not a chat id (a UUID, from chat.list or chat.create)", + "exit": 1, + "operation": "chat.get" + }, + { + "body": { + "user": { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:05Z", + "scopes": [ + "read", + "work.write", + "project.write" + ], + "updated_at": "2026-01-02T03:04:05Z", + "username": "ada" + } + }, + "exit": 0, + "operation": "user.create" + }, + { + "body": { + "users": [ + { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:05Z", + "scopes": [ + "read", + "work.write", + "project.write" + ], + "updated_at": "2026-01-02T03:04:05Z", + "username": "ada" + } + ] + }, + "exit": 0, + "operation": "user.list" + }, + { + "body": { + "user": { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:05Z", + "scopes": [ + "read", + "work.write", + "project.write" + ], + "updated_at": "2026-01-02T03:04:05Z", + "username": "ada" + } + }, + "exit": 0, + "operation": "user.set_password" + }, + { + "body": { + "tokens": [ + { + "actor_id": "act_0003", + "actor_identity_ref": "agent:vogt-sessions", + "created_at": "2026-01-02T03:04:15Z", + "expires_at": null, + "id": "tok_0002", + "kind": "api", + "last_used_at": null, + "name": "bootstrap agent token", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + }, + { + "actor_id": "act_0002", + "actor_identity_ref": "agent:vogt-engine", + "created_at": "2026-01-02T03:04:11Z", + "expires_at": null, + "id": "tok_0001", + "kind": "api", + "last_used_at": null, + "name": "bootstrap core token", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "read", + "work.write", + "project.write" + ] + } + ] + }, + "exit": 0, + "operation": "token.list" + }, + { + "body": { + "token": { + "actor_id": "act_0003", + "actor_identity_ref": "agent:vogt-sessions", + "created_at": "2026-01-02T03:04:15Z", + "expires_at": null, + "id": "tok_0002", + "kind": "api", + "last_used_at": null, + "name": "bootstrap agent token", + "revoked_at": "2026-01-02T03:04:05Z", + "revoked_reason": "parity harness", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + } + }, + "exit": 0, + "operation": "token.revoke" + }, + { + "body": { + "display_name": "parity", + "identity_ref": "local:parity", + "kind": "human", + "scopes": [ + "admin", + "project.write", + "read", + "work.write", + "writeback" + ], + "token": null + }, + "exit": 0, + "operation": "auth.whoami" + }, + { + "body": { + "declared_schema_version": 24, + "engine_state": "not configured", + "import_root": "/instance/repos", + "instance_id": "ins_0001", + "observed_schema_version": 6, + "path": "/backup", + "taken_at": "2026-01-02T03:04:05Z" + }, + "exit": 0, + "operation": "backup" + }, + { + "body": { + "declared_schema_version": 24, + "engine_state": "not in this backup", + "import_root_now": "/instance/repos", + "import_root_then": "/instance/repos", + "instance_id": "ins_0001", + "migrations_applied": [], + "restored_from": "2026-01-02T03:04:05Z", + "source": "/backup" + }, + "exit": 0, + "operation": "restore" + }, + { + "body": { + "sessions_revoked": 0, + "username": "ada" + }, + "exit": 0, + "operation": "user.remove" + }, + { + "body": { + "events": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12Z", + "audit_id": "aud_0002", + "entity_id": "tok_0001", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 1, + "summary": { + "actor": "agent:vogt-engine", + "scopes": [ + "read", + "work.write", + "project.write" + ] + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16Z", + "audit_id": "aud_0003", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 2, + "summary": { + "actor": "agent:vogt-sessions", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:06Z", + "audit_id": "aud_0004", + "entity_id": "act_0004", + "entity_kind": "actor", + "kind": "actor.created", + "seq": 3, + "summary": { + "identity_ref": "agent:parity", + "kind": "agent" + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:06Z", + "audit_id": "aud_0005", + "entity_id": "lbl_0001", + "entity_kind": "label", + "kind": "label.created", + "seq": 4, + "summary": { + "name": "parity" + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:06Z", + "audit_id": "aud_0006", + "entity_id": "prj_0001", + "entity_kind": "project", + "kind": "project.registered", + "seq": 5, + "summary": { + "name": "Parity", + "slug": "parity" + } + } + ], + "next_cursor": 5 + }, + "exit": 0, + "operation": "events.list" + }, + { + "body": { + "events": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16Z", + "audit_id": "aud_0003", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 2, + "summary": { + "actor": "agent:vogt-sessions", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:06Z", + "audit_id": "aud_0004", + "entity_id": "act_0004", + "entity_kind": "actor", + "kind": "actor.created", + "seq": 3, + "summary": { + "identity_ref": "agent:parity", + "kind": "agent" + } + } + ], + "next_cursor": 3 + }, + "exit": 0, + "operation": "events.list" + }, + { + "body": { + "records": [ + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0026", + "operation": "user.remove", + "payload_digest": "sha256:6cb1b570d1a756565af0191ec35d1fed1dc103ed9b82afa000b68a0172b6f711", + "reason": "parity harness", + "revision": 25, + "txn_id": "txn_0029" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0025", + "operation": "token.revoke", + "payload_digest": "sha256:5b5962b852bf72720c110fabfda9bea2589305bb33f47f25d1361ce815fa78c9", + "reason": "parity harness", + "revision": 24, + "txn_id": "txn_0027" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0024", + "operation": "user.set_password", + "payload_digest": "sha256:6782e2b5fa4d26772e8b2eeacbf82bb2c48960ddb25c736b28001d387114acf5", + "reason": "parity harness", + "revision": 23, + "txn_id": "txn_0026" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0023", + "operation": "user.create", + "payload_digest": "sha256:67e02676f1f6d286e8b3624b34c5496ed548e0ac92c3485f85c350f4c54dcce7", + "reason": "parity harness", + "revision": 22, + "txn_id": "txn_0025" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "prj_0001", + "entity_kind": "project", + "id": "aud_0022", + "operation": "project.writeback", + "payload_digest": "sha256:5160587a6bd4fd6e1c8cfdfbe9e7b71a0aff7cb411b75e8ad277bea16adfdf65", + "reason": "parity harness", + "revision": 21, + "txn_id": "txn_0023" + } + ], + "total": 26 + }, + "exit": 0, + "operation": "audit.list" + }, + { + "body": { + "records": [ + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0025", + "operation": "token.revoke", + "payload_digest": "sha256:5b5962b852bf72720c110fabfda9bea2589305bb33f47f25d1361ce815fa78c9", + "reason": "parity harness", + "revision": 24, + "txn_id": "txn_0027" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:06Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0024", + "operation": "user.set_password", + "payload_digest": "sha256:6782e2b5fa4d26772e8b2eeacbf82bb2c48960ddb25c736b28001d387114acf5", + "reason": "parity harness", + "revision": 23, + "txn_id": "txn_0026" + } + ], + "total": 26 + }, + "exit": 0, + "operation": "audit.list" + }, + { + "body": null, + "exit": 1, + "operation": "clone" + }, + { + "body": null, + "exit": 1, + "operation": "clone" + }, + { + "body": null, + "exit": 1, + "operation": "clone" + }, + { + "body": null, + "exit": 1, + "operation": "project.import" + }, + { + "body": null, + "exit": 1, + "operation": "project.import" + }, + { + "body": { + "decision": { + "answer": null, + "answered_at": null, + "answered_by": null, + "asker": "local:parity", + "asker_session": null, + "context": "CI is green.", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-01-03T03:04:05Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "pending", + "work_item": null + } + }, + "exit": 0, + "operation": "decision.ask" + }, + { + "body": { + "decision": { + "answer": null, + "answered_at": null, + "answered_by": null, + "asker": "local:parity", + "asker_session": null, + "context": "", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-01-03T03:04:05Z", + "id": "dec_0002", + "options": [ + { + "description": "", + "label": "Ship", + "recommended": true + }, + { + "description": "", + "label": "Wait", + "recommended": false + } + ], + "question": "Ship the release?", + "reason": "parity harness", + "state": "pending", + "work_item": null + } + }, + "exit": 0, + "operation": "decision.ask" + }, + { + "body": { + "decisions": [ + { + "answer": null, + "answered_at": null, + "answered_by": null, + "asker": "local:parity", + "asker_session": null, + "context": "", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-01-03T03:04:05Z", + "id": "dec_0002", + "options": [ + { + "description": "", + "label": "Ship", + "recommended": true + }, + { + "description": "", + "label": "Wait", + "recommended": false + } + ], + "question": "Ship the release?", + "reason": "parity harness", + "state": "pending", + "work_item": null + }, + { + "answer": null, + "answered_at": null, + "answered_by": null, + "asker": "local:parity", + "asker_session": null, + "context": "CI is green.", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-01-03T03:04:05Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "pending", + "work_item": null + } + ] + }, + "exit": 0, + "operation": "decision.list" + }, + { + "body": { + "decisions": [ + { + "answer": null, + "answered_at": null, + "answered_by": null, + "asker": "local:parity", + "asker_session": null, + "context": "", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-01-03T03:04:05Z", + "id": "dec_0002", + "options": [ + { + "description": "", + "label": "Ship", + "recommended": true + }, + { + "description": "", + "label": "Wait", + "recommended": false + } + ], + "question": "Ship the release?", + "reason": "parity harness", + "state": "pending", + "work_item": null + }, + { + "answer": null, + "answered_at": null, + "answered_by": null, + "asker": "local:parity", + "asker_session": null, + "context": "CI is green.", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-01-03T03:04:05Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "pending", + "work_item": null + } + ] + }, + "exit": 0, + "operation": "decision.list" + }, + { + "body": { + "actor_unknown_hidden": 0, + "counts": { + "active": 2, + "archived": 0, + "snoozed": 0 + }, + "coverage": { + "agent": { + "count": 2, + "detail": "no session engine is configured", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "agent", + "status": "unconfigured" + }, + "ci": { + "count": 0, + "detail": "this collector has not completed a sweep", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "ci", + "status": "unswept" + }, + "drift": { + "count": 0, + "detail": "open proposals in the declared store", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "drift", + "status": "current" + }, + "github": { + "count": 0, + "detail": "this collector has not completed a sweep", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "github", + "status": "unswept" + } + }, + "engine_available": false, + "engine_detail": "no session engine is configured", + "engine_status": "not_configured", + "entries": [ + { + "action": { + "decision_id": "dec_0002", + "drift_id": null, + "grant_id": null, + "kind": "decision", + "session_id": null, + "subject_key": null + }, + "actor_kind": "human", + "actor_login": "local:parity", + "actor_relation": "org_member", + "entry_key": "agent:decision:dec_0002", + "evidence_snapshot": "", + "freshness": "live", + "kind": "decision.pending", + "observed_at": null, + "occurred_at": "2026-01-02T03:04:05Z", + "project_slug": null, + "proposed_change": null, + "provisional": true, + "session_id": null, + "snooze_until": null, + "source": "agent", + "source_subject_key": "dec_0002", + "source_url": null, + "summary": "local:parity asks: Ship the release? Options: Ship (recommended) | Wait.", + "title": "Decision: Ship the release?", + "triage_state": "active", + "trust_state": "unverified", + "work_item_ref": null + }, + { + "action": { + "decision_id": "dec_0001", + "drift_id": null, + "grant_id": null, + "kind": "decision", + "session_id": null, + "subject_key": null + }, + "actor_kind": "human", + "actor_login": "local:parity", + "actor_relation": "org_member", + "entry_key": "agent:decision:dec_0001", + "evidence_snapshot": "", + "freshness": "live", + "kind": "decision.pending", + "observed_at": null, + "occurred_at": "2026-01-02T03:04:05Z", + "project_slug": null, + "proposed_change": null, + "provisional": true, + "session_id": null, + "snooze_until": null, + "source": "agent", + "source_subject_key": "dec_0001", + "source_url": null, + "summary": "local:parity asks: Merge the branch? Options: Merge (recommended) | Hold.", + "title": "Decision: Merge the branch?", + "triage_state": "active", + "trust_state": "unverified", + "work_item_ref": null + } + ], + "github_scope": "registered projects only", + "high_water": { + "agent": "2026-01-02T03:04:05Z", + "ci": null, + "drift": null, + "github": null + }, + "instance_scope": "registered projects only", + "next_cursor": null, + "snapshot_at": "2026-01-02T03:04:05Z" + }, + "exit": 0, + "operation": "inbox.list" + }, + { + "body": { + "decision": { + "answer": { + "label": "Merge", + "note": "reviewed" + }, + "answered_at": "2026-01-02T03:04:06Z", + "answered_by": "local:parity", + "asker": "local:parity", + "asker_session": null, + "context": "CI is green.", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-01-03T03:04:05Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "answered", + "work_item": null + } + }, + "exit": 0, + "operation": "decision.answer" + }, + { + "body": null, + "exit": 1, + "operation": "decision.answer" + }, + { + "body": null, + "exit": 1, + "operation": "decision.answer" + }, + { + "body": { + "decision": { + "answer": null, + "answered_at": null, + "answered_by": null, + "asker": "local:parity", + "asker_session": null, + "context": "", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-01-03T03:04:05Z", + "id": "dec_0002", + "options": [ + { + "description": "", + "label": "Ship", + "recommended": true + }, + { + "description": "", + "label": "Wait", + "recommended": false + } + ], + "question": "Ship the release?", + "reason": "parity harness", + "state": "withdrawn", + "work_item": null + } + }, + "exit": 0, + "operation": "decision.withdraw" + }, + { + "body": { + "decisions": [ + { + "answer": { + "label": "Merge", + "note": "reviewed" + }, + "answered_at": "2026-01-02T03:04:06Z", + "answered_by": "local:parity", + "asker": "local:parity", + "asker_session": null, + "context": "CI is green.", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-01-03T03:04:05Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "answered", + "work_item": null + } + ] + }, + "exit": 0, + "operation": "decision.list" + }, + { + "body": { + "actor_unknown_hidden": 0, + "counts": { + "active": 0, + "archived": 0, + "snoozed": 0 + }, + "coverage": { + "agent": { + "count": 0, + "detail": "no session engine is configured", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "agent", + "status": "unconfigured" + }, + "ci": { + "count": 0, + "detail": "this collector has not completed a sweep", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "ci", + "status": "unswept" + }, + "drift": { + "count": 0, + "detail": "open proposals in the declared store", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "drift", + "status": "current" + }, + "github": { + "count": 0, + "detail": "this collector has not completed a sweep", + "observed_at": null, + "projects": 0, + "registered": 2, + "source": "github", + "status": "unswept" + } + }, + "engine_available": false, + "engine_detail": "no session engine is configured", + "engine_status": "not_configured", + "entries": [], + "github_scope": "registered projects only", + "high_water": { + "agent": null, + "ci": null, + "drift": null, + "github": null + }, + "instance_scope": "registered projects only", + "next_cursor": null, + "snapshot_at": "2026-01-02T03:04:05Z" + }, + "exit": 0, + "operation": "inbox.list" + } +] diff --git a/tests/parity/golden/21c95f6-s4/http.json b/tests/parity/golden/21c95f6-s4/http.json new file mode 100644 index 000000000..f8b73a743 --- /dev/null +++ b/tests/parity/golden/21c95f6-s4/http.json @@ -0,0 +1,2044 @@ +[ + { + "operation": "http.health.live", + "result": { + "status": "ok", + "version": "" + }, + "status": 200 + }, + { + "operation": "http.health.ready", + "result": { + "declared_schema_expected": 24, + "declared_schema_version": 24, + "detail": null, + "instance_id": "ins_0001", + "observed_schema_expected": 6, + "observed_schema_version": 6, + "status": "ready" + }, + "status": 200 + }, + { + "operation": "http.version", + "result": { + "name": "vogt", + "version": "" + }, + "status": 200 + }, + { + "operation": "http.connection_info", + "result": { + "api_path": "/api", + "authentication": "bearer token", + "health_path": "/health/ready", + "mcp_path": "/mcp", + "name": "vogt", + "supported_mcp_protocol_versions": [ + "2025-06-18", + "2025-03-26", + "2024-11-05" + ], + "url": null, + "version": "", + "writes_enabled": true + }, + "status": 200 + }, + { + "operation": "install.bootstrap", + "result": { + "actor": { + "created_at": "2026-01-02T03:04:06Z", + "disabled": false, + "display_name": "Ada Lovelace", + "id": "act_0004", + "identity_ref": "human:ada-lovelace", + "kind": "human" + }, + "secret": "", + "token": { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": null, + "id": "tok_0003", + "kind": "api", + "last_used_at": null, + "name": "first-run browser token", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "admin" + ] + }, + "username": null, + "warning": "This is the only time the secret is shown. It is not stored and cannot be recovered \u2014 losing it means issuing another over the loopback surface." + }, + "status": 200 + }, + { + "operation": "why", + "result": { + "error": { + "code": "not_found", + "message": "no work item or observed subject 'WI-1'" + } + }, + "status": 404 + }, + { + "operation": "why", + "result": { + "error": { + "code": "not_found", + "message": "no work item or observed subject 'WI-404'" + } + }, + "status": 404 + }, + { + "operation": "work.list", + "result": { + "detail": null, + "items": [], + "link_state": null, + "mode": "full", + "next_offset": null, + "total": 0 + }, + "status": 200 + }, + { + "operation": "work.list", + "result": { + "detail": null, + "items": [], + "link_state": null, + "mode": "summary", + "next_offset": null, + "total": 0 + }, + "status": 200 + }, + { + "operation": "work.get_many", + "result": { + "items": [], + "missing": [ + "WI-1", + "WI-404", + "WI-2" + ] + }, + "status": 200 + }, + { + "operation": "audit.list", + "result": { + "records": [ + { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "at": "2026-01-02T03:04:07Z", + "entity_id": "tok_0003", + "entity_kind": "token", + "id": "aud_0005", + "operation": "install.bootstrap", + "payload_digest": "sha256:b19d0ae7d972de7a7d247d64fc4db90184619cd1db18a41a4d0e6b2a31a72d32", + "reason": "first-run install bootstrap for human:ada-lovelace", + "revision": 3, + "txn_id": "txn_0004" + }, + { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "at": "2026-01-02T03:04:06Z", + "entity_id": "act_0004", + "entity_kind": "actor", + "id": "aud_0004", + "operation": "actor.auto_register", + "payload_digest": "sha256:87af525aec71a14661415e1c50dcf914634e2707473b601ae5fe8254bca3cf4d", + "reason": "first authenticated use by human:ada-lovelace; auto-registered so the write it accompanies can be attributed", + "revision": 3, + "txn_id": "txn_0004" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:16Z", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0003", + "operation": "token.adopt", + "payload_digest": "sha256:f1942d83b32c6907f41a7053a5e2b379b085799a1b1a2bb7dd9e2a327080cefc", + "reason": "adopting the operator-supplied agent token at init", + "revision": 2, + "txn_id": "txn_0003" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:12Z", + "entity_id": "tok_0001", + "entity_kind": "token", + "id": "aud_0002", + "operation": "token.adopt", + "payload_digest": "sha256:59549779aa0cda6825e03092b441cacac09d0003597ed7fd262fc918fdedf350", + "reason": "adopting the operator-supplied core token at init", + "revision": 1, + "txn_id": "txn_0002" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:07Z", + "entity_id": "ins_0001", + "entity_kind": "instance", + "id": "aud_0001", + "operation": "instance.init", + "payload_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "reason": "instance bootstrap", + "revision": 0, + "txn_id": "txn_0001" + } + ], + "total": 5 + }, + "status": 200 + }, + { + "operation": "work.bind_branch", + "result": { + "error": { + "code": "not_found", + "message": "no work item 'WI-1'" + } + }, + "status": 404 + }, + { + "operation": "work.adopt", + "result": { + "error": { + "code": "not_found", + "message": "no observed subject 'gh:parity-org/parity#404'" + } + }, + "status": 404 + }, + { + "operation": "session.list", + "result": { + "digests": [], + "engine": "no session engine is configured (VOGT_ENGINE_URL is unset)", + "hidden_count": 0, + "sessions": [], + "total": 0, + "truncated": false + }, + "status": 200 + }, + { + "operation": "session.sweep", + "result": { + "counts": { + "needs_you": 0, + "total": 0 + }, + "engine": "no session engine is configured (VOGT_ENGINE_URL is unset)", + "rows": [], + "swept_at": "2026-01-02T03:04:06Z" + }, + "status": 200 + }, + { + "operation": "session.log_tail", + "result": { + "bytes": 0, + "engine": "no session engine is configured (VOGT_ENGINE_URL is unset)", + "session_id": null, + "text": "", + "total_bytes": 0, + "truncated": false + }, + "status": 200 + }, + { + "operation": "session.start", + "result": { + "error": { + "code": "engine_unavailable", + "message": "no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + } + }, + "status": 502 + }, + { + "operation": "session.input", + "result": { + "error": { + "code": "engine_unavailable", + "message": "no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + } + }, + "status": 502 + }, + { + "operation": "session.screen", + "result": { + "error": { + "code": "engine_unavailable", + "message": "no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + } + }, + "status": 502 + }, + { + "operation": "session.wait", + "result": { + "error": { + "code": "engine_unavailable", + "message": "no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + } + }, + "status": 502 + }, + { + "operation": "session.answer", + "result": { + "error": { + "code": "engine_unavailable", + "message": "no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + } + }, + "status": 502 + }, + { + "operation": "session.hibernate", + "result": { + "error": { + "code": "engine_unavailable", + "message": "no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + } + }, + "status": 502 + }, + { + "operation": "session.keep_awake", + "result": { + "error": { + "code": "engine_unavailable", + "message": "no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + } + }, + "status": 502 + }, + { + "operation": "session.set_role", + "result": { + "error": { + "code": "engine_unavailable", + "message": "no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + } + }, + "status": 502 + }, + { + "operation": "session.wake", + "result": { + "error": { + "code": "engine_unavailable", + "message": "no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + } + }, + "status": 502 + }, + { + "operation": "session.stop", + "result": { + "error": { + "code": "not_found", + "message": "no session 'ses_00000000000000000000000000'" + } + }, + "status": 404 + }, + { + "operation": "label.create", + "result": { + "label": { + "color": null, + "created_at": "2026-01-02T03:04:06Z", + "id": "lbl_0001", + "name": "bug" + } + }, + "status": 200 + }, + { + "operation": "token.issue", + "result": { + "secret": "", + "token": { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "created_at": "2026-01-02T03:04:06Z", + "expires_at": null, + "id": "tok_0004", + "kind": "api", + "last_used_at": null, + "name": "reader", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "read" + ] + }, + "warning": "This is the only time the secret is shown. Store it in a file and point VOGT_TOKEN_FILE at it \u2014 never in argv or a URL." + }, + "status": 200 + }, + { + "operation": "label.create", + "result": { + "error": { + "code": "forbidden", + "message": "label.create requires the 'work.write' scope; this token holds read" + } + }, + "status": 403 + }, + { + "operation": "auth.logout", + "result": { + "revoked": true, + "token": { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "created_at": "2026-01-02T03:04:06Z", + "expires_at": null, + "id": "tok_0004", + "kind": "api", + "last_used_at": "2026-01-02T03:04:06Z", + "name": "reader", + "revoked_at": "2026-01-02T03:04:06Z", + "revoked_reason": "done", + "scopes": [ + "read" + ] + } + }, + "status": 200 + }, + { + "operation": "user.create", + "result": { + "user": { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:06Z", + "scopes": [ + "read", + "work.write", + "project.write" + ], + "updated_at": "2026-01-02T03:04:06Z", + "username": "ada" + } + }, + "status": 200 + }, + { + "operation": "user.list", + "result": { + "users": [ + { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:06Z", + "scopes": [ + "read", + "work.write", + "project.write" + ], + "updated_at": "2026-01-02T03:04:06Z", + "username": "ada" + } + ] + }, + "status": 200 + }, + { + "operation": "user.set_password", + "result": { + "user": { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:06Z", + "scopes": [ + "read", + "work.write", + "project.write" + ], + "updated_at": "2026-01-02T03:04:06Z", + "username": "ada" + } + }, + "status": 200 + }, + { + "operation": "token.list", + "result": { + "tokens": [ + { + "actor_id": "act_0003", + "actor_identity_ref": "agent:vogt-sessions", + "created_at": "2026-01-02T03:04:15Z", + "expires_at": null, + "id": "tok_0002", + "kind": "api", + "last_used_at": null, + "name": "bootstrap agent token", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + }, + { + "actor_id": "act_0002", + "actor_identity_ref": "agent:vogt-engine", + "created_at": "2026-01-02T03:04:11Z", + "expires_at": null, + "id": "tok_0001", + "kind": "api", + "last_used_at": null, + "name": "bootstrap core token", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "read", + "work.write", + "project.write" + ] + }, + { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "created_at": "2026-01-02T03:04:06Z", + "expires_at": null, + "id": "tok_0004", + "kind": "api", + "last_used_at": "2026-01-02T03:04:06Z", + "name": "reader", + "revoked_at": "2026-01-02T03:04:06Z", + "revoked_reason": "done", + "scopes": [ + "read" + ] + }, + { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": null, + "id": "tok_0003", + "kind": "api", + "last_used_at": "2026-01-02T03:04:06Z", + "name": "first-run browser token", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "admin" + ] + } + ] + }, + "status": 200 + }, + { + "operation": "token.revoke", + "result": { + "token": { + "actor_id": "act_0003", + "actor_identity_ref": "agent:vogt-sessions", + "created_at": "2026-01-02T03:04:15Z", + "expires_at": null, + "id": "tok_0002", + "kind": "api", + "last_used_at": null, + "name": "bootstrap agent token", + "revoked_at": "2026-01-02T03:04:06Z", + "revoked_reason": "parity harness", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + } + }, + "status": 200 + }, + { + "operation": "auth.whoami", + "result": { + "display_name": "Ada Lovelace", + "identity_ref": "human:ada-lovelace", + "kind": "human", + "scopes": [ + "admin", + "project.write", + "read", + "work.write", + "writeback" + ], + "token": { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": null, + "id": "tok_0003", + "kind": "api", + "last_used_at": "2026-01-02T03:04:06Z", + "name": "first-run browser token", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "admin" + ] + } + }, + "status": 200 + }, + { + "operation": "auth.login", + "result": { + "actor": { + "created_at": "2026-01-02T03:04:06Z", + "disabled": false, + "display_name": "Ada Lovelace", + "id": "act_0005", + "identity_ref": "human:ada", + "kind": "human" + }, + "secret": "", + "token": { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-02-01T03:04:05Z", + "id": "tok_0005", + "kind": "session", + "last_used_at": null, + "name": "browser session", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "read", + "work.write", + "project.write" + ] + } + }, + "status": 200 + }, + { + "operation": "auth.whoami", + "result": { + "display_name": "Ada Lovelace", + "identity_ref": "human:ada", + "kind": "human", + "scopes": [ + "project.write", + "read", + "work.write" + ], + "token": { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:05Z", + "expires_at": "2026-02-01T03:04:05Z", + "id": "tok_0005", + "kind": "session", + "last_used_at": null, + "name": "browser session", + "revoked_at": null, + "revoked_reason": null, + "scopes": [ + "read", + "work.write", + "project.write" + ] + } + }, + "status": 200 + }, + { + "operation": "user.remove", + "result": { + "sessions_revoked": 1, + "username": "ada" + }, + "status": 200 + }, + { + "operation": "events.list", + "result": { + "events": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12Z", + "audit_id": "aud_0002", + "entity_id": "tok_0001", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 1, + "summary": { + "actor": "agent:vogt-engine", + "scopes": [ + "read", + "work.write", + "project.write" + ] + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16Z", + "audit_id": "aud_0003", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 2, + "summary": { + "actor": "agent:vogt-sessions", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + } + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:06Z", + "audit_id": "aud_0004", + "entity_id": "act_0004", + "entity_kind": "actor", + "kind": "actor.auto_register", + "seq": 3, + "summary": { + "identity_ref": "human:ada-lovelace", + "kind": "human" + } + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07Z", + "audit_id": "aud_0005", + "entity_id": "tok_0003", + "entity_kind": "token", + "kind": "install.bootstrapped", + "seq": 4, + "summary": { + "actor": "human:ada-lovelace", + "scopes": [ + "admin" + ] + } + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07Z", + "audit_id": "aud_0006", + "entity_id": "lbl_0001", + "entity_kind": "label", + "kind": "label.created", + "seq": 5, + "summary": { + "name": "bug" + } + } + ], + "next_cursor": 5 + }, + "status": 200 + }, + { + "operation": "events.list", + "result": { + "events": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16Z", + "audit_id": "aud_0003", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 2, + "summary": { + "actor": "agent:vogt-sessions", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + } + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:06Z", + "audit_id": "aud_0004", + "entity_id": "act_0004", + "entity_kind": "actor", + "kind": "actor.auto_register", + "seq": 3, + "summary": { + "identity_ref": "human:ada-lovelace", + "kind": "human" + } + } + ], + "next_cursor": 3 + }, + "status": 200 + }, + { + "operation": "audit.list", + "result": { + "records": [ + { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "at": "2026-01-02T03:04:07Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0013", + "operation": "user.remove", + "payload_digest": "sha256:c641563a061b879b4b32211c6b39a7a2eeb71a7a114359133e079bc16f1b4bfa", + "reason": "parity harness", + "revision": 11, + "txn_id": "txn_0013" + }, + { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "at": "2026-01-02T03:04:06Z", + "entity_id": "tok_0005", + "entity_kind": "token", + "id": "aud_0012", + "operation": "auth.login", + "payload_digest": "sha256:079f75e3b73e507a9203394b2f453386de6362973c6d3e9dfa6ddfee0201f931", + "reason": "password login by ada", + "revision": 10, + "txn_id": "txn_0012" + }, + { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "at": "2026-01-02T03:04:07Z", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0011", + "operation": "token.revoke", + "payload_digest": "sha256:d331a6f9ffa337f5bbea02b91117f85c4a54fe0e8550538ef0ccbe9757a74c7a", + "reason": "parity harness", + "revision": 9, + "txn_id": "txn_0011" + }, + { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "at": "2026-01-02T03:04:07Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0010", + "operation": "user.set_password", + "payload_digest": "sha256:6782e2b5fa4d26772e8b2eeacbf82bb2c48960ddb25c736b28001d387114acf5", + "reason": "parity harness", + "revision": 8, + "txn_id": "txn_0010" + }, + { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "at": "2026-01-02T03:04:07Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0009", + "operation": "user.create", + "payload_digest": "sha256:67e02676f1f6d286e8b3624b34c5496ed548e0ac92c3485f85c350f4c54dcce7", + "reason": "parity harness", + "revision": 7, + "txn_id": "txn_0009" + } + ], + "total": 13 + }, + "status": 200 + }, + { + "operation": "audit.list", + "result": { + "records": [ + { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "at": "2026-01-02T03:04:06Z", + "entity_id": "tok_0005", + "entity_kind": "token", + "id": "aud_0012", + "operation": "auth.login", + "payload_digest": "sha256:079f75e3b73e507a9203394b2f453386de6362973c6d3e9dfa6ddfee0201f931", + "reason": "password login by ada", + "revision": 10, + "txn_id": "txn_0012" + }, + { + "actor_id": "act_0004", + "actor_identity_ref": "human:ada-lovelace", + "at": "2026-01-02T03:04:07Z", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0011", + "operation": "token.revoke", + "payload_digest": "sha256:d331a6f9ffa337f5bbea02b91117f85c4a54fe0e8550538ef0ccbe9757a74c7a", + "reason": "parity harness", + "revision": 9, + "txn_id": "txn_0011" + } + ], + "total": 13 + }, + "status": 200 + }, + { + "operation": "project.import", + "result": { + "error": { + "code": "invalid_request", + "message": "'not a repo!' does not name a GitHub repository or a configured Forgejo repository; give owner/name, host/owner/name, or a repository URL" + } + }, + "status": 400 + }, + { + "operation": "project.import", + "result": { + "error": { + "code": "invalid_request", + "message": "'not a repo!' does not name a GitHub repository or a configured Forgejo repository; give owner/name, host/owner/name, or a repository URL" + } + }, + "status": 400 + }, + { + "operation": "dump", + "result": { + "actors": [ + { + "created_at": "2026-01-02T03:04:07+00:00", + "disabled": 0, + "display_name": "parity", + "id": "act_0001", + "identity_ref": "local:parity", + "kind": "human" + }, + { + "created_at": "2026-01-02T03:04:09+00:00", + "disabled": 0, + "display_name": "agent:vogt-engine", + "id": "act_0002", + "identity_ref": "agent:vogt-engine", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:13+00:00", + "disabled": 0, + "display_name": "agent:vogt-sessions", + "id": "act_0003", + "identity_ref": "agent:vogt-sessions", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:06+00:00", + "disabled": 0, + "display_name": "Ada Lovelace", + "id": "act_0004", + "identity_ref": "human:ada-lovelace", + "kind": "human" + }, + { + "created_at": "2026-01-02T03:04:06+00:00", + "disabled": 0, + "display_name": "Ada Lovelace", + "id": "act_0005", + "identity_ref": "human:ada", + "kind": "human" + } + ], + "audit": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "ins_0001", + "entity_kind": "instance", + "id": "aud_0001", + "operation": "instance.init", + "payload_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "reason": "instance bootstrap", + "revision": 0, + "txn_id": "txn_0001" + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12+00:00", + "entity_id": "tok_0001", + "entity_kind": "token", + "id": "aud_0002", + "operation": "token.adopt", + "payload_digest": "sha256:59549779aa0cda6825e03092b441cacac09d0003597ed7fd262fc918fdedf350", + "reason": "adopting the operator-supplied core token at init", + "revision": 1, + "txn_id": "txn_0002" + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16+00:00", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0003", + "operation": "token.adopt", + "payload_digest": "sha256:f1942d83b32c6907f41a7053a5e2b379b085799a1b1a2bb7dd9e2a327080cefc", + "reason": "adopting the operator-supplied agent token at init", + "revision": 2, + "txn_id": "txn_0003" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:06+00:00", + "entity_id": "act_0004", + "entity_kind": "actor", + "id": "aud_0004", + "operation": "actor.auto_register", + "payload_digest": "sha256:87af525aec71a14661415e1c50dcf914634e2707473b601ae5fe8254bca3cf4d", + "reason": "first authenticated use by human:ada-lovelace; auto-registered so the write it accompanies can be attributed", + "revision": 3, + "txn_id": "txn_0004" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "tok_0003", + "entity_kind": "token", + "id": "aud_0005", + "operation": "install.bootstrap", + "payload_digest": "sha256:b19d0ae7d972de7a7d247d64fc4db90184619cd1db18a41a4d0e6b2a31a72d32", + "reason": "first-run install bootstrap for human:ada-lovelace", + "revision": 3, + "txn_id": "txn_0004" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "lbl_0001", + "entity_kind": "label", + "id": "aud_0006", + "operation": "label.create", + "payload_digest": "sha256:1b0a972c9944e759355152203a12fb36104f692b1f1911217107dcd4f75b8ccd", + "reason": "seed", + "revision": 4, + "txn_id": "txn_0006" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "tok_0004", + "entity_kind": "token", + "id": "aud_0007", + "operation": "token.issue", + "payload_digest": "sha256:1efafbf3c435f850162b7fdb5f1a1f97b97eb7bae0490f291c1731c7a390ee5a", + "reason": "a read token", + "revision": 5, + "txn_id": "txn_0007" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "tok_0004", + "entity_kind": "token", + "id": "aud_0008", + "operation": "auth.logout", + "payload_digest": "sha256:77f2ce31482b0061e1a20b86d2de25c0b43a6eaff6c025be826208b0dec15fe3", + "reason": "done", + "revision": 6, + "txn_id": "txn_0008" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0009", + "operation": "user.create", + "payload_digest": "sha256:67e02676f1f6d286e8b3624b34c5496ed548e0ac92c3485f85c350f4c54dcce7", + "reason": "parity harness", + "revision": 7, + "txn_id": "txn_0009" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0010", + "operation": "user.set_password", + "payload_digest": "sha256:6782e2b5fa4d26772e8b2eeacbf82bb2c48960ddb25c736b28001d387114acf5", + "reason": "parity harness", + "revision": 8, + "txn_id": "txn_0010" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0011", + "operation": "token.revoke", + "payload_digest": "sha256:d331a6f9ffa337f5bbea02b91117f85c4a54fe0e8550538ef0ccbe9757a74c7a", + "reason": "parity harness", + "revision": 9, + "txn_id": "txn_0011" + }, + { + "actor_id": "act_0005", + "at": "2026-01-02T03:04:06+00:00", + "entity_id": "tok_0005", + "entity_kind": "token", + "id": "aud_0012", + "operation": "auth.login", + "payload_digest": "sha256:079f75e3b73e507a9203394b2f453386de6362973c6d3e9dfa6ddfee0201f931", + "reason": "password login by ada", + "revision": 10, + "txn_id": "txn_0012" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0013", + "operation": "user.remove", + "payload_digest": "sha256:c641563a061b879b4b32211c6b39a7a2eeb71a7a114359133e079bc16f1b4bfa", + "reason": "parity harness", + "revision": 11, + "txn_id": "txn_0013" + } + ], + "auth_decisions": [ + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0001", + "identity_ref": "human:ada-lovelace", + "operation": "why", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0002", + "identity_ref": "human:ada-lovelace", + "operation": "why", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0003", + "identity_ref": "human:ada-lovelace", + "operation": "work.list", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0004", + "identity_ref": "human:ada-lovelace", + "operation": "work.list", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0005", + "identity_ref": "human:ada-lovelace", + "operation": "work.get_many", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0006", + "identity_ref": "human:ada-lovelace", + "operation": "audit.list", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0007", + "identity_ref": "human:ada-lovelace", + "operation": "work.bind_branch", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0008", + "identity_ref": "human:ada-lovelace", + "operation": "work.adopt", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0009", + "identity_ref": "human:ada-lovelace", + "operation": "session.list", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0010", + "identity_ref": "human:ada-lovelace", + "operation": "session.sweep", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0011", + "identity_ref": "human:ada-lovelace", + "operation": "session.log_tail", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0012", + "identity_ref": "human:ada-lovelace", + "operation": "session.start", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0013", + "identity_ref": "human:ada-lovelace", + "operation": "session.input", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0014", + "identity_ref": "human:ada-lovelace", + "operation": "session.screen", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0015", + "identity_ref": "human:ada-lovelace", + "operation": "session.wait", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0016", + "identity_ref": "human:ada-lovelace", + "operation": "session.answer", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0017", + "identity_ref": "human:ada-lovelace", + "operation": "session.hibernate", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0018", + "identity_ref": "human:ada-lovelace", + "operation": "session.keep_awake", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0019", + "identity_ref": "human:ada-lovelace", + "operation": "session.set_role", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0020", + "identity_ref": "human:ada-lovelace", + "operation": "session.wake", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0021", + "identity_ref": "human:ada-lovelace", + "operation": "session.stop", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0022", + "identity_ref": "human:ada-lovelace", + "operation": "label.create", + "reason_code": "token_valid", + "scope": "work.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0023", + "identity_ref": "human:ada-lovelace", + "operation": "token.issue", + "reason_code": "token_valid", + "scope": "admin", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "deny", + "detail": null, + "id": "aut_0024", + "identity_ref": "human:ada-lovelace", + "operation": "label.create", + "reason_code": "missing_scope", + "scope": "work.write", + "token_id": "tok_0004", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0025", + "identity_ref": "human:ada-lovelace", + "operation": "auth.logout", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0004", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0026", + "identity_ref": "human:ada-lovelace", + "operation": "user.create", + "reason_code": "token_valid", + "scope": "admin", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0027", + "identity_ref": "human:ada-lovelace", + "operation": "user.list", + "reason_code": "token_valid", + "scope": "admin", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0028", + "identity_ref": "human:ada-lovelace", + "operation": "user.set_password", + "reason_code": "token_valid", + "scope": "admin", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0029", + "identity_ref": "human:ada-lovelace", + "operation": "token.list", + "reason_code": "token_valid", + "scope": "admin", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0030", + "identity_ref": "human:ada-lovelace", + "operation": "token.revoke", + "reason_code": "token_valid", + "scope": "admin", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0031", + "identity_ref": "human:ada-lovelace", + "operation": "auth.whoami", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0005", + "at": "2026-01-02T03:04:07+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0032", + "identity_ref": "human:ada", + "operation": "auth.login", + "reason_code": "login_ok", + "scope": null, + "token_id": "tok_0005", + "transport": "http" + }, + { + "actor_id": "act_0005", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0033", + "identity_ref": "human:ada", + "operation": "auth.whoami", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0005", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0034", + "identity_ref": "human:ada-lovelace", + "operation": "user.remove", + "reason_code": "token_valid", + "scope": "admin", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0035", + "identity_ref": "human:ada-lovelace", + "operation": "events.list", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0036", + "identity_ref": "human:ada-lovelace", + "operation": "events.list", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0037", + "identity_ref": "human:ada-lovelace", + "operation": "audit.list", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0038", + "identity_ref": "human:ada-lovelace", + "operation": "audit.list", + "reason_code": "token_valid", + "scope": "read", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0039", + "identity_ref": "human:ada-lovelace", + "operation": "project.import", + "reason_code": "token_valid", + "scope": "project.write", + "token_id": "tok_0003", + "transport": "http" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:05+00:00", + "decision": "allow", + "detail": null, + "id": "aut_0040", + "identity_ref": "human:ada-lovelace", + "operation": "project.import", + "reason_code": "token_valid", + "scope": "project.write", + "token_id": "tok_0003", + "transport": "http" + } + ], + "events": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12+00:00", + "audit_id": "aud_0002", + "entity_id": "tok_0001", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 1, + "summary": "{\"actor\": \"agent:vogt-engine\", \"scopes\": [\"read\", \"work.write\", \"project.write\"]}" + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16+00:00", + "audit_id": "aud_0003", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 2, + "summary": "{\"actor\": \"agent:vogt-sessions\", \"scopes\": [\"read\", \"work.write\", \"project.write\", \"writeback\"]}" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:06+00:00", + "audit_id": "aud_0004", + "entity_id": "act_0004", + "entity_kind": "actor", + "kind": "actor.auto_register", + "seq": 3, + "summary": "{\"identity_ref\": \"human:ada-lovelace\", \"kind\": \"human\"}" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "audit_id": "aud_0005", + "entity_id": "tok_0003", + "entity_kind": "token", + "kind": "install.bootstrapped", + "seq": 4, + "summary": "{\"actor\": \"human:ada-lovelace\", \"scopes\": [\"admin\"]}" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "audit_id": "aud_0006", + "entity_id": "lbl_0001", + "entity_kind": "label", + "kind": "label.created", + "seq": 5, + "summary": "{\"name\": \"bug\"}" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "audit_id": "aud_0007", + "entity_id": "tok_0004", + "entity_kind": "token", + "kind": "token.issued", + "seq": 6, + "summary": "{\"actor\": \"human:ada-lovelace\", \"scopes\": [\"read\"]}" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "audit_id": "aud_0008", + "entity_id": "tok_0004", + "entity_kind": "token", + "kind": "auth.session_closed", + "seq": 7, + "summary": "{\"actor\": \"human:ada-lovelace\"}" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "audit_id": "aud_0009", + "entity_id": "act_0005", + "entity_kind": "user", + "kind": "user.created", + "seq": 8, + "summary": "{\"actor\": \"human:ada\", \"username\": \"ada\"}" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "audit_id": "aud_0010", + "entity_id": "act_0005", + "entity_kind": "user", + "kind": "user.password_set", + "seq": 9, + "summary": "{\"username\": \"ada\"}" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "audit_id": "aud_0011", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.revoked", + "seq": 10, + "summary": "{\"actor\": \"agent:vogt-sessions\"}" + }, + { + "actor_id": "act_0005", + "at": "2026-01-02T03:04:06+00:00", + "audit_id": "aud_0012", + "entity_id": "tok_0005", + "entity_kind": "token", + "kind": "auth.session_opened", + "seq": 11, + "summary": "{\"actor\": \"human:ada\"}" + }, + { + "actor_id": "act_0004", + "at": "2026-01-02T03:04:07+00:00", + "audit_id": "aud_0013", + "entity_id": "act_0005", + "entity_kind": "user", + "kind": "user.removed", + "seq": 12, + "summary": "{\"username\": \"ada\"}" + } + ], + "install_latch": [ + { + "closed_at": "2026-01-02T03:04:05+00:00", + "id": 1, + "reason": "a person holds a credential" + } + ], + "labels": [ + { + "color": null, + "created_at": "2026-01-02T03:04:06+00:00", + "id": "lbl_0001", + "name": "bug" + } + ], + "meta": [ + { + "key": "instance_id", + "value": "ins_0001" + }, + { + "key": "revision", + "value": "11" + }, + { + "key": "work_ref_seq", + "value": "0" + }, + { + "key": "created_at", + "value": "2026-01-02T03:04:07+00:00" + } + ], + "migration_lock": [ + { + "acquired_at": null, + "holder": null, + "id": 1 + } + ], + "migrations": [ + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "83cf77f4abe04e89c2efa22271c6d9275532f1dd37a270b7667226b3748ae643", + "id": "0001_foundation" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "f690b73e4b9523cd85b818ac11c6fecf086c020683324dccbc03ec3d7b59627d", + "id": "0002_work" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "9032649fc02951fe5d6340a6f9576a45b8d7197057990c68dca834a1d26f48b8", + "id": "0003_observed_first" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "cc588f67774949d31caf0305200e557cc666617b8f5a2469f958f511944ee3f4", + "id": "0004_drift" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "0e2adf0935fc90a6810710297ab3490edb430b47d9d169a2166ae189c24223b2", + "id": "0005_tokens" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "37b95c74c33d9d7c5ee649ab72b265b4de517b41a40cb3a2c768099f210b0f58", + "id": "0006_writeback" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "47c82ee427931ca7c0a3a8d047298ffe5c2b2ad49ff06f83b00acf59306a1ff6", + "id": "0007_sessions" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "f533369dd3625830341933caac68f4611a02d0e4055b9d37a62db9c59f731ce3", + "id": "0008_superseded_drift" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "b0ca1559346b05c6ef818b3b65c2ae798f05c755c7a0dd326c73227da770c226", + "id": "0009_inbox_triage" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "de3f70faf7ccb5fd276455a9a56eb7a731c8334fb6964ea6d5eb2407dbcb03f5", + "id": "0010_session_model" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "7b4ab541ce8b97e31f1871373c6400e7a6390bdc70356fe703f3aa5e29211582", + "id": "0011_contract_adoption" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "c819361c246e6c76035ed27c1a296d723235259c449a2ee2ce192163b8cb8a3a", + "id": "0012_forge_accounts" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "fa8e380cd0761fe49bf3a46fd7e9305799fe8f142fdc331c71d2a0836fa00c3f", + "id": "0013_upstream_truth" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "2fef3fae64abd522ef65b21eef63f5a93d2b44ded81e5836da1288639a59a70d", + "id": "0014_native_migration" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "6f60bd5cae2d90f79cfb72228c88b21927958bdeedc7f00da59835c3e18a928c", + "id": "0015_work_overlay_branches" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "e9d6f101a8d95929b51b6dd9a4d1ccc0e3a732e49fd605c60de3ee849be7a113", + "id": "0016_perf_indexes" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "3131bbedaed1c58d3997598f18e8f30d103ec6f1447d38dcaa2a2846f1b7d8f8", + "id": "0017_password_credentials" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "a04732a0b920af03fc8731b513260caf81d25216533efb8c8eac030185371c00", + "id": "0018_actor_preferences" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "2c307b06be247b5b5739230750785d9209dee953a22da0354952ebe1cf45811d", + "id": "0019_session_grants" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "e5fd2b7bd062d50c9d21a923df3176e9e5b69e9fb06b0dd6bf75b295d694b761", + "id": "0020_install_latch" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "9f11063cfd66c2940b21819453fa18f16123b56f59ce77f2bad74d50523d5127", + "id": "0021_agent_policy" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "1a86e121ab2879671fd44ee5a9276e05d4af19bb4b98e71e3426634958be7078", + "id": "0022_comment_kind" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "8fb4506bf7517137f5e20b26525268bda7af4bcd45057731547564d5428f615e", + "id": "0023_decisions" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "806ade321a5da9c91bbbbff49927587f48cb214cadca672c1b5a705d60662cf9", + "id": "0024_session_started_by" + } + ], + "tokens": [ + { + "actor_id": "act_0002", + "created_at": "2026-01-02T03:04:11+00:00", + "expires_at": null, + "id": "tok_0001", + "kind": "api", + "last_used_at": null, + "name": "bootstrap core token", + "revoked_at": null, + "revoked_reason": null, + "scopes": "[\"read\", \"work.write\", \"project.write\"]", + "token_hash": "9545b721f3788049180e1ccb546015bce1287aeb505e54d04a01344bb38fe4f6" + }, + { + "actor_id": "act_0003", + "created_at": "2026-01-02T03:04:15+00:00", + "expires_at": null, + "id": "tok_0002", + "kind": "api", + "last_used_at": null, + "name": "bootstrap agent token", + "revoked_at": "2026-01-02T03:04:06+00:00", + "revoked_reason": "parity harness", + "scopes": "[\"read\", \"work.write\", \"project.write\", \"writeback\"]", + "token_hash": "d3ff8dd34b248fd9895c7ca25e883f4bc79b2321c11f6ebee6a619c6e702ce71" + }, + { + "actor_id": "act_0004", + "created_at": "2026-01-02T03:04:05+00:00", + "expires_at": null, + "id": "tok_0003", + "kind": "api", + "last_used_at": "2026-01-02T03:04:06+00:00", + "name": "first-run browser token", + "revoked_at": null, + "revoked_reason": null, + "scopes": "[\"admin\"]", + "token_hash": "" + }, + { + "actor_id": "act_0004", + "created_at": "2026-01-02T03:04:06+00:00", + "expires_at": null, + "id": "tok_0004", + "kind": "api", + "last_used_at": "2026-01-02T03:04:06+00:00", + "name": "reader", + "revoked_at": "2026-01-02T03:04:06+00:00", + "revoked_reason": "done", + "scopes": "[\"read\"]", + "token_hash": "" + }, + { + "actor_id": "act_0005", + "created_at": "2026-01-02T03:04:05+00:00", + "expires_at": "2026-02-01T03:04:05+00:00", + "id": "tok_0005", + "kind": "session", + "last_used_at": "2026-01-02T03:04:06+00:00", + "name": "browser session", + "revoked_at": "2026-01-02T03:04:06+00:00", + "revoked_reason": "parity harness", + "scopes": "[\"read\", \"work.write\", \"project.write\"]", + "token_hash": "" + } + ], + "workflow_defs": [ + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "feature", + "updated_at": "2026-01-02T03:04:05+00:00" + }, + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "bug", + "updated_at": "2026-01-02T03:04:05+00:00" + }, + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "chore", + "updated_at": "2026-01-02T03:04:05+00:00" + }, + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "question", + "updated_at": "2026-01-02T03:04:05+00:00" + } + ] + } + } +] diff --git a/tests/parity/golden/21c95f6-s4/mcp.json b/tests/parity/golden/21c95f6-s4/mcp.json new file mode 100644 index 000000000..930aa66b0 --- /dev/null +++ b/tests/parity/golden/21c95f6-s4/mcp.json @@ -0,0 +1,2902 @@ +[ + { + "body": { + "declared": { + "actors": [ + { + "created_at": "2026-01-02T03:04:07+00:00", + "disabled": 0, + "display_name": "parity", + "id": "act_0001", + "identity_ref": "local:parity", + "kind": "human" + }, + { + "created_at": "2026-01-02T03:04:09+00:00", + "disabled": 0, + "display_name": "agent:vogt-engine", + "id": "act_0002", + "identity_ref": "agent:vogt-engine", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:13+00:00", + "disabled": 0, + "display_name": "agent:vogt-sessions", + "id": "act_0003", + "identity_ref": "agent:vogt-sessions", + "kind": "agent" + } + ], + "audit": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:07+00:00", + "entity_id": "ins_0001", + "entity_kind": "instance", + "id": "aud_0001", + "operation": "instance.init", + "payload_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "reason": "instance bootstrap", + "revision": 0, + "txn_id": "txn_0001" + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12+00:00", + "entity_id": "tok_0001", + "entity_kind": "token", + "id": "aud_0002", + "operation": "token.adopt", + "payload_digest": "sha256:59549779aa0cda6825e03092b441cacac09d0003597ed7fd262fc918fdedf350", + "reason": "adopting the operator-supplied core token at init", + "revision": 1, + "txn_id": "txn_0002" + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16+00:00", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0003", + "operation": "token.adopt", + "payload_digest": "sha256:f1942d83b32c6907f41a7053a5e2b379b085799a1b1a2bb7dd9e2a327080cefc", + "reason": "adopting the operator-supplied agent token at init", + "revision": 2, + "txn_id": "txn_0003" + } + ], + "events": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12+00:00", + "audit_id": "aud_0002", + "entity_id": "tok_0001", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 1, + "summary": "{\"actor\": \"agent:vogt-engine\", \"scopes\": [\"read\", \"work.write\", \"project.write\"]}" + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16+00:00", + "audit_id": "aud_0003", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 2, + "summary": "{\"actor\": \"agent:vogt-sessions\", \"scopes\": [\"read\", \"work.write\", \"project.write\", \"writeback\"]}" + } + ], + "meta": [ + { + "key": "instance_id", + "value": "ins_0001" + }, + { + "key": "revision", + "value": "2" + }, + { + "key": "work_ref_seq", + "value": "0" + }, + { + "key": "created_at", + "value": "2026-01-02T03:04:07+00:00" + } + ], + "migration_lock": [ + { + "acquired_at": null, + "holder": null, + "id": 1 + } + ], + "migrations": [ + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "83cf77f4abe04e89c2efa22271c6d9275532f1dd37a270b7667226b3748ae643", + "id": "0001_foundation" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "f690b73e4b9523cd85b818ac11c6fecf086c020683324dccbc03ec3d7b59627d", + "id": "0002_work" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "9032649fc02951fe5d6340a6f9576a45b8d7197057990c68dca834a1d26f48b8", + "id": "0003_observed_first" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "cc588f67774949d31caf0305200e557cc666617b8f5a2469f958f511944ee3f4", + "id": "0004_drift" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "0e2adf0935fc90a6810710297ab3490edb430b47d9d169a2166ae189c24223b2", + "id": "0005_tokens" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "37b95c74c33d9d7c5ee649ab72b265b4de517b41a40cb3a2c768099f210b0f58", + "id": "0006_writeback" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "47c82ee427931ca7c0a3a8d047298ffe5c2b2ad49ff06f83b00acf59306a1ff6", + "id": "0007_sessions" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "f533369dd3625830341933caac68f4611a02d0e4055b9d37a62db9c59f731ce3", + "id": "0008_superseded_drift" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "b0ca1559346b05c6ef818b3b65c2ae798f05c755c7a0dd326c73227da770c226", + "id": "0009_inbox_triage" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "de3f70faf7ccb5fd276455a9a56eb7a731c8334fb6964ea6d5eb2407dbcb03f5", + "id": "0010_session_model" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "7b4ab541ce8b97e31f1871373c6400e7a6390bdc70356fe703f3aa5e29211582", + "id": "0011_contract_adoption" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "c819361c246e6c76035ed27c1a296d723235259c449a2ee2ce192163b8cb8a3a", + "id": "0012_forge_accounts" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "fa8e380cd0761fe49bf3a46fd7e9305799fe8f142fdc331c71d2a0836fa00c3f", + "id": "0013_upstream_truth" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "2fef3fae64abd522ef65b21eef63f5a93d2b44ded81e5836da1288639a59a70d", + "id": "0014_native_migration" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "6f60bd5cae2d90f79cfb72228c88b21927958bdeedc7f00da59835c3e18a928c", + "id": "0015_work_overlay_branches" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "e9d6f101a8d95929b51b6dd9a4d1ccc0e3a732e49fd605c60de3ee849be7a113", + "id": "0016_perf_indexes" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "3131bbedaed1c58d3997598f18e8f30d103ec6f1447d38dcaa2a2846f1b7d8f8", + "id": "0017_password_credentials" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "a04732a0b920af03fc8731b513260caf81d25216533efb8c8eac030185371c00", + "id": "0018_actor_preferences" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "2c307b06be247b5b5739230750785d9209dee953a22da0354952ebe1cf45811d", + "id": "0019_session_grants" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "e5fd2b7bd062d50c9d21a923df3176e9e5b69e9fb06b0dd6bf75b295d694b761", + "id": "0020_install_latch" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "9f11063cfd66c2940b21819453fa18f16123b56f59ce77f2bad74d50523d5127", + "id": "0021_agent_policy" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "1a86e121ab2879671fd44ee5a9276e05d4af19bb4b98e71e3426634958be7078", + "id": "0022_comment_kind" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "8fb4506bf7517137f5e20b26525268bda7af4bcd45057731547564d5428f615e", + "id": "0023_decisions" + }, + { + "applied_at": "2026-01-02T03:04:05+00:00", + "checksum": "806ade321a5da9c91bbbbff49927587f48cb214cadca672c1b5a705d60662cf9", + "id": "0024_session_started_by" + } + ], + "tokens": [ + { + "actor_id": "act_0002", + "created_at": "2026-01-02T03:04:11+00:00", + "expires_at": null, + "id": "tok_0001", + "kind": "api", + "last_used_at": null, + "name": "bootstrap core token", + "revoked_at": null, + "revoked_reason": null, + "scopes": "[\"read\", \"work.write\", \"project.write\"]", + "token_hash": "9545b721f3788049180e1ccb546015bce1287aeb505e54d04a01344bb38fe4f6" + }, + { + "actor_id": "act_0003", + "created_at": "2026-01-02T03:04:15+00:00", + "expires_at": null, + "id": "tok_0002", + "kind": "api", + "last_used_at": null, + "name": "bootstrap agent token", + "revoked_at": null, + "revoked_reason": null, + "scopes": "[\"read\", \"work.write\", \"project.write\", \"writeback\"]", + "token_hash": "d3ff8dd34b248fd9895c7ca25e883f4bc79b2321c11f6ebee6a619c6e702ce71" + } + ], + "workflow_defs": [ + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "feature", + "updated_at": "2026-01-02T03:04:05+00:00" + }, + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "bug", + "updated_at": "2026-01-02T03:04:05+00:00" + }, + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "chore", + "updated_at": "2026-01-02T03:04:05+00:00" + }, + { + "definition": "{\"initial_state\": \"open\", \"transitions\": {\"blocked\": [\"open\", \"in_progress\", \"wont_do\"], \"done\": [\"open\"], \"in_progress\": [\"review\", \"blocked\", \"open\", \"wont_do\"], \"open\": [\"in_progress\", \"blocked\", \"wont_do\"], \"review\": [\"done\", \"in_progress\", \"blocked\", \"wont_do\"], \"wont_do\": [\"open\"]}}", + "kind": "question", + "updated_at": "2026-01-02T03:04:05+00:00" + } + ] + }, + "observed": { + "meta": [ + { + "key": "instance_id", + "value": "ins_0001" + }, + { + "key": "created_at", + "value": "2026-01-02T03:04:08+00:00" + } + ], + "migration_lock": [ + { + "acquired_at": null, + "holder": null, + "id": 1 + } + ], + "migrations": [ + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "c050f9e4dba983119f045b8fb4ccbe8b1ee97473170eead7d28279e99468de4b", + "id": "0001_foundation" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "9cdc9c9179740af96606bab0b557c7b35639eaabc78faf16630c7705f1d368f5", + "id": "0002_evidence" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "8bf0b8533774a156762fddc2e56bbb9656ba3b6bf0e38ec85a66c9dc9c325ed1", + "id": "0003_inherited_dep_refs" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "8d5cffbb2acb6ff18099ad73384eaa4ea5ebca7a6f45afb79d353fb4cbbcaee6", + "id": "0004_forge_sync" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "e6902e6616ab9948f9d222b2763ce9bc421a136b6acb1422240e5957683e5e45", + "id": "0005_perf_indexes" + }, + { + "applied_at": "2026-01-02T03:04:06+00:00", + "checksum": "e0e959f4bf4f39db47d5e41ac11391d5f80a0b73bcce57d36168572062b1ab66", + "id": "0006_agent_activity" + } + ] + } + }, + "operation": "dump.after_init" + }, + { + "body": { + "counts": { + "actors": 3, + "audit": 3, + "events": 2, + "initiatives": 0, + "projects": 0, + "work_items": 0 + }, + "data_dir": "", + "declared_schema_version": 24, + "instance_id": "ins_0001", + "observed_schema_version": 6, + "principal": "local:parity", + "revision": 2, + "vogt_version": "0.7.17" + }, + "is_error": false, + "operation": "status" + }, + { + "body": { + "data_dir": "", + "declared_schema_expected": 24, + "declared_schema_version": 24, + "migrations_applied": [], + "observed_schema_expected": 6, + "observed_schema_version": 6 + }, + "is_error": false, + "operation": "migrate" + }, + { + "body": { + "workflows": [ + { + "initial_state": "open", + "kind": "feature", + "states": [ + "blocked", + "done", + "in_progress", + "open", + "review", + "wont_do" + ], + "transitions": { + "blocked": [ + "open", + "in_progress", + "wont_do" + ], + "done": [ + "open" + ], + "in_progress": [ + "review", + "blocked", + "open", + "wont_do" + ], + "open": [ + "in_progress", + "blocked", + "wont_do" + ], + "review": [ + "done", + "in_progress", + "blocked", + "wont_do" + ], + "wont_do": [ + "open" + ] + } + }, + { + "initial_state": "open", + "kind": "bug", + "states": [ + "blocked", + "done", + "in_progress", + "open", + "review", + "wont_do" + ], + "transitions": { + "blocked": [ + "open", + "in_progress", + "wont_do" + ], + "done": [ + "open" + ], + "in_progress": [ + "review", + "blocked", + "open", + "wont_do" + ], + "open": [ + "in_progress", + "blocked", + "wont_do" + ], + "review": [ + "done", + "in_progress", + "blocked", + "wont_do" + ], + "wont_do": [ + "open" + ] + } + }, + { + "initial_state": "open", + "kind": "chore", + "states": [ + "blocked", + "done", + "in_progress", + "open", + "review", + "wont_do" + ], + "transitions": { + "blocked": [ + "open", + "in_progress", + "wont_do" + ], + "done": [ + "open" + ], + "in_progress": [ + "review", + "blocked", + "open", + "wont_do" + ], + "open": [ + "in_progress", + "blocked", + "wont_do" + ], + "review": [ + "done", + "in_progress", + "blocked", + "wont_do" + ], + "wont_do": [ + "open" + ] + } + }, + { + "initial_state": "open", + "kind": "question", + "states": [ + "blocked", + "done", + "in_progress", + "open", + "review", + "wont_do" + ], + "transitions": { + "blocked": [ + "open", + "in_progress", + "wont_do" + ], + "done": [ + "open" + ], + "in_progress": [ + "review", + "blocked", + "open", + "wont_do" + ], + "open": [ + "in_progress", + "blocked", + "wont_do" + ], + "review": [ + "done", + "in_progress", + "blocked", + "wont_do" + ], + "wont_do": [ + "open" + ] + } + } + ] + }, + "is_error": false, + "operation": "workflow.list" + }, + { + "body": { + "actor": { + "created_at": "2026-01-02T03:04:07Z", + "disabled": false, + "display_name": "Parity Agent", + "id": "act_0004", + "identity_ref": "agent:parity", + "kind": "agent" + } + }, + "is_error": false, + "operation": "actor.create" + }, + { + "body": { + "actors": [ + { + "created_at": "2026-01-02T03:04:07Z", + "disabled": false, + "display_name": "Parity Agent", + "id": "act_0004", + "identity_ref": "agent:parity", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:09Z", + "disabled": false, + "display_name": "agent:vogt-engine", + "id": "act_0002", + "identity_ref": "agent:vogt-engine", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:13Z", + "disabled": false, + "display_name": "agent:vogt-sessions", + "id": "act_0003", + "identity_ref": "agent:vogt-sessions", + "kind": "agent" + }, + { + "created_at": "2026-01-02T03:04:07Z", + "disabled": false, + "display_name": "parity", + "id": "act_0001", + "identity_ref": "local:parity", + "kind": "human" + } + ] + }, + "is_error": false, + "operation": "actor.list" + }, + { + "body": { + "label": { + "color": "#d73a4a", + "created_at": "2026-01-02T03:04:09Z", + "id": "lbl_0001", + "name": "parity" + } + }, + "is_error": false, + "operation": "label.create" + }, + { + "body": { + "labels": [ + { + "color": "#d73a4a", + "created_at": "2026-01-02T03:04:09Z", + "id": "lbl_0001", + "name": "parity" + } + ] + }, + "is_error": false, + "operation": "label.list" + }, + { + "body": { + "contract_version": "v1", + "criteria": [ + { + "applicable": true, + "detail": "directory exists", + "rule": "path.exists", + "satisfied": true, + "target": "" + }, + { + "applicable": true, + "detail": "AGENTS.md is missing", + "rule": "required_file", + "satisfied": false, + "target": "AGENTS.md" + }, + { + "applicable": true, + "detail": "README.md is missing", + "rule": "required_file", + "satisfied": false, + "target": "README.md" + }, + { + "applicable": true, + "detail": "LICENSE is missing", + "rule": "required_file", + "satisfied": false, + "target": "LICENSE" + }, + { + "applicable": true, + "detail": "docs/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "docs" + }, + { + "applicable": true, + "detail": "design/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "design" + }, + { + "applicable": true, + "detail": "src/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "src" + } + ], + "failing": [ + { + "applicable": true, + "detail": "AGENTS.md is missing", + "rule": "required_file", + "satisfied": false, + "target": "AGENTS.md" + }, + { + "applicable": true, + "detail": "README.md is missing", + "rule": "required_file", + "satisfied": false, + "target": "README.md" + }, + { + "applicable": true, + "detail": "LICENSE is missing", + "rule": "required_file", + "satisfied": false, + "target": "LICENSE" + }, + { + "applicable": true, + "detail": "docs/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "docs" + }, + { + "applicable": true, + "detail": "design/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "design" + }, + { + "applicable": true, + "detail": "src/ is missing", + "rule": "required_dir", + "satisfied": false, + "target": "src" + } + ], + "inapplicable": [], + "path": "", + "recommendations": [ + { + "instruction": "`project scaffold` writes AGENTS.md into this project without overwriting anything that is already there.", + "remedy": "scaffold", + "rule": "required_file", + "target": "AGENTS.md" + }, + { + "instruction": "`project scaffold` writes README.md into this project without overwriting anything that is already there.", + "remedy": "scaffold", + "rule": "required_file", + "target": "README.md" + }, + { + "instruction": "choose a licence for this project and write its full text to LICENSE. Vogt deliberately does not pick one: a scaffold that silently writes MIT makes that decision for the owner.", + "remedy": "judgement", + "rule": "required_file", + "target": "LICENSE" + }, + { + "instruction": "`project scaffold` writes docs/ into this project without overwriting anything that is already there.", + "remedy": "scaffold", + "rule": "required_dir", + "target": "docs" + }, + { + "instruction": "decide what this project's design record is \u2014 diagrams, mockups, exploratory notes \u2014 and put it in design/. An empty directory satisfies the letter of the criterion and none of its point, and git cannot carry one anyway.", + "remedy": "judgement", + "rule": "required_dir", + "target": "design" + }, + { + "instruction": "`project scaffold` writes src/ into this project without overwriting anything that is already there.", + "remedy": "scaffold", + "rule": "required_dir", + "target": "src" + } + ], + "recorded": false, + "status": "non_compliant" + }, + "is_error": false, + "operation": "contract.evaluate" + }, + { + "body": { + "contract_version": "v1", + "criteria": [ + { + "applicable": true, + "detail": "the path does not exist or is not a directory, so no criterion below was evaluated \u2014 this is 'not checked', not 'does not comply'", + "rule": "path.exists", + "satisfied": false, + "target": "/does-not-exist" + } + ], + "failing": [ + { + "applicable": true, + "detail": "the path does not exist or is not a directory, so no criterion below was evaluated \u2014 this is 'not checked', not 'does not comply'", + "rule": "path.exists", + "satisfied": false, + "target": "/does-not-exist" + } + ], + "inapplicable": [], + "path": "/does-not-exist", + "recommendations": [ + { + "instruction": "decide what /does-not-exist should contain for this project and add it. Nothing here can write it for you.", + "remedy": "judgement", + "rule": "path.exists", + "target": "/does-not-exist" + } + ], + "recorded": false, + "status": "not_checked" + }, + "is_error": false, + "operation": "contract.evaluate" + }, + { + "body": { + "created_paths": [ + "/proj", + "/proj/AGENTS.md", + "/proj/LICENSE", + "/proj/README.md", + "/proj/design", + "/proj/docs", + "/proj/src" + ], + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "main_branches": [], + "merge_deploys_prod": false, + "rules": [], + "test_recipe": "" + }, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:11Z", + "created_at": "2026-01-02T03:04:11Z", + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "incubating", + "link_state": "unlinked", + "name": "Parity", + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:11Z", + "write_back": "none" + }, + "skipped_paths": [] + }, + "is_error": false, + "operation": "project.create" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "main_branches": [], + "merge_deploys_prod": false, + "rules": [], + "test_recipe": "" + }, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:11Z", + "created_at": "2026-01-02T03:04:11Z", + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "incubating", + "link_state": "unlinked", + "name": "Parity", + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:11Z", + "write_back": "none" + } + }, + "is_error": false, + "operation": "project.get" + }, + { + "body": { + "projects": [ + { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "main_branches": [], + "merge_deploys_prod": false, + "rules": [], + "test_recipe": "" + }, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:11Z", + "created_at": "2026-01-02T03:04:11Z", + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "incubating", + "link_state": "unlinked", + "name": "Parity", + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:11Z", + "writable": false, + "writable_reason": "not forge-linked: work.create refuses with project_not_linked. Pass local_only=true for a local record, or link (`forge link`) or publish (`forge publish`) the project.", + "write_back": "none" + } + ], + "total": 1 + }, + "is_error": false, + "operation": "project.list" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "main_branches": [], + "merge_deploys_prod": false, + "rules": [], + "test_recipe": "" + }, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:11Z", + "created_at": "2026-01-02T03:04:11Z", + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "incubating", + "link_state": "unlinked", + "name": "Parity", + "repo_url": "https://example.invalid/parity.git", + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:13Z", + "write_back": "none" + } + }, + "is_error": false, + "operation": "project.update" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "main_branches": [], + "merge_deploys_prod": false, + "rules": [], + "test_recipe": "" + }, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:11Z", + "created_at": "2026-01-02T03:04:11Z", + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "active", + "link_state": "unlinked", + "name": "Parity", + "repo_url": "https://example.invalid/parity.git", + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:15Z", + "write_back": "none" + } + }, + "is_error": false, + "operation": "project.transition" + }, + { + "body": { + "created": [], + "detail": "0 written, 6 already there and left exactly as they were", + "project": "parity", + "root_path": "/proj", + "skipped": [ + "/proj/AGENTS.md", + "/proj/LICENSE", + "/proj/README.md", + "/proj/design", + "/proj/docs", + "/proj/src" + ] + }, + "is_error": false, + "operation": "project.scaffold" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "main_branches": [], + "merge_deploys_prod": false, + "rules": [], + "test_recipe": "" + }, + "compliance_status": "not_checked", + "created_at": "2026-01-02T03:04:18Z", + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0002", + "lifecycle_state": "active", + "link_state": "unlinked", + "name": "Second", + "root_path": "/second", + "slug": "second", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:18Z", + "write_back": "none" + } + }, + "is_error": false, + "operation": "project.register" + }, + { + "body": { + "initiative": { + "body": "", + "created_at": "2026-01-02T03:04:20Z", + "id": "ini_0001", + "slug": "platform", + "state": "open", + "title": "Platform", + "updated_at": "2026-01-02T03:04:20Z", + "weight": 0 + } + }, + "is_error": false, + "operation": "initiative.create" + }, + { + "body": { + "initiatives": [ + { + "body": "", + "created_at": "2026-01-02T03:04:20Z", + "id": "ini_0001", + "slug": "platform", + "state": "open", + "title": "Platform", + "updated_at": "2026-01-02T03:04:20Z", + "weight": 0 + } + ] + }, + "is_error": false, + "operation": "initiative.list" + }, + { + "body": { + "initiative": { + "body": "", + "created_at": "2026-01-02T03:04:20Z", + "id": "ini_0001", + "slug": "platform", + "state": "open", + "title": "Platform work", + "updated_at": "2026-01-02T03:04:22Z", + "weight": 0 + } + }, + "is_error": false, + "operation": "initiative.update" + }, + { + "body": { + "slug": "platform", + "state": "open", + "tracking_issues": [] + }, + "is_error": false, + "operation": "initiative.publish" + }, + { + "body": { + "text": "not_found: no initiative with slug 'missing'" + }, + "is_error": true, + "operation": "initiative.publish" + }, + { + "body": { + "preferences": [] + }, + "is_error": false, + "operation": "preference.get" + }, + { + "body": { + "preference": { + "key": "inbox.filter", + "updated_at": "2026-01-02T03:04:25Z", + "value": { + "actor": "any", + "sources": [ + "ci" + ], + "triage_states": [ + "active" + ] + }, + "version": 1 + } + }, + "is_error": false, + "operation": "preference.set" + }, + { + "body": { + "text": "preference_version_conflict: preference 'inbox.filter' is at version 1, not 0; re-read it and apply the change on top of the current value" + }, + "is_error": true, + "operation": "preference.set" + }, + { + "body": { + "by_reason": {}, + "detail": "nothing collected \u2014 either there is nothing to say, or the configured token cannot read notifications; `coverage` distinguishes the two", + "freshness": { + "collectors": "", + "detail": "no collector has completed a sweep yet", + "status": "never_swept" + }, + "notifications": [], + "scope": "the GitHub account whose token this instance is configured with; notifications are instance-scoped, not per-actor", + "total": 0, + "unread": 0 + }, + "is_error": false, + "operation": "notifications" + }, + { + "body": { + "actor_unknown_hidden": 0, + "counts": { + "active": 0, + "archived": 0, + "snoozed": 0 + }, + "coverage": { + "agent": { + "count": 0, + "detail": "no session engine is configured", + "projects": 0, + "registered": 2, + "source": "agent", + "status": "unconfigured" + }, + "ci": { + "count": 0, + "detail": "this collector has not completed a sweep", + "projects": 0, + "registered": 2, + "source": "ci", + "status": "unswept" + }, + "drift": { + "count": 0, + "detail": "open proposals in the declared store", + "projects": 0, + "registered": 2, + "source": "drift", + "status": "current" + }, + "github": { + "count": 0, + "detail": "this collector has not completed a sweep", + "projects": 0, + "registered": 2, + "source": "github", + "status": "unswept" + } + }, + "engine_available": false, + "engine_detail": "no session engine is configured", + "engine_status": "not_configured", + "entries": [], + "github_scope": "registered projects only", + "high_water": {}, + "instance_scope": "registered projects only", + "snapshot_at": "2026-01-02T03:04:27Z" + }, + "is_error": false, + "operation": "inbox.list" + }, + { + "body": { + "text": "inbox_entry_not_found: no current Inbox entry 'nope'" + }, + "is_error": true, + "operation": "inbox.archive" + }, + { + "body": { + "text": "invalid_snooze: snooze deadline must be in the future" + }, + "is_error": true, + "operation": "inbox.snooze" + }, + { + "body": { + "text": "inbox_entry_not_found: no current Inbox entry 'nope'" + }, + "is_error": true, + "operation": "inbox.restore" + }, + { + "body": { + "branches": [], + "comments": [], + "item": { + "body": "", + "created_at": "2026-01-02T03:04:29Z", + "id": "wrk_0001", + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p2", + "ref": "WI-1", + "relations": [], + "state": "open", + "title": "parity item", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:29Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "is_error": false, + "operation": "work.create" + }, + { + "body": { + "branches": [], + "comments": [], + "item": { + "body": "", + "created_at": "2026-01-02T03:04:29Z", + "id": "wrk_0001", + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "ref": "WI-1", + "relations": [], + "state": "open", + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:31Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "is_error": false, + "operation": "work.update" + }, + { + "body": { + "text": "invalid_params: labels: Input should be a valid list. work_create takes required: kind, title, reason; optional: body, priority, effort, project, initiative, assignee, labels, local_only" + }, + "is_error": true, + "operation": "work.create" + }, + { + "body": { + "text": "project_not_linked: work.create needs a forge-linked project, and 'parity' is not linked: link it (`forge link`, or re-import through `project import`) or publish it (`forge publish`) first; or pass `local_only: true` to keep a native item in this project. Native items (WI-n) on this project still take comments, transitions (including to done) and field edits by ref." + }, + "is_error": true, + "operation": "work.create" + }, + { + "body": { + "items": [ + { + "kind": "chore", + "priority": "p1", + "ref": "WI-1", + "state": "open", + "title": "parity item renamed" + } + ], + "mode": "summary", + "total": 1 + }, + "is_error": false, + "operation": "work.list" + }, + { + "body": { + "text": "not_found: no project with slug 'missing'" + }, + "is_error": true, + "operation": "work.list" + }, + { + "body": { + "branches": [], + "comments": [], + "item": { + "body": "", + "created_at": "2026-01-02T03:04:29Z", + "id": "wrk_0001", + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "ref": "WI-1", + "relations": [], + "state": "open", + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:31Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "is_error": false, + "operation": "work.get" + }, + { + "body": { + "comment": { + "actor_display_name": "parity", + "actor_id": "act_0001", + "body": "parity note", + "created_at": "2026-01-02T03:04:36Z", + "id": "cmt_0001", + "kind": "note", + "work_item_id": "wrk_0001" + }, + "write_back": "skipped" + }, + "is_error": false, + "operation": "work.comment" + }, + { + "body": { + "branches": [], + "comments": [], + "item": { + "body": "", + "created_at": "2026-01-02T03:04:29Z", + "id": "wrk_0001", + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "ref": "WI-1", + "relations": [], + "state": "in_progress", + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:39Z" + }, + "live_sessions": [], + "sessions": [], + "walked": [] + }, + "is_error": false, + "operation": "work.transition" + }, + { + "body": { + "text": "not_found: no work item 'WI-2'" + }, + "is_error": true, + "operation": "work.relate" + }, + { + "body": { + "text": "not_found: no work item 'WI-2'" + }, + "is_error": true, + "operation": "work.unrelate" + }, + { + "body": { + "backlog_candidates": 1, + "cells": [ + { + "items": [], + "lane_key": "", + "state": "open", + "total": 0 + } + ], + "column_totals": { + "in_progress": 1 + }, + "declared_total": 1, + "excluded_unlinked": 0, + "lane_totals": { + "": 1 + }, + "revision": 17, + "snapshot": "eyJmaW5nZXJwcmludCI6InNoYTI1Njo2N2JkMGQ3MTUxN2JlYThkM2VkNmJjYWE0ZTFmZTY2YzYyNTM5MGRkNjg2ODI3MjM1Njk4MTUxMDg2ZDc0MDYzIiwiaGlnaF93YXRlciI6WyIyMDI2LTAxLTAyVDAzOjA0OjI5KzAwOjAwIiwiV0ktMSJdLCJraW5kIjoic25hcHNob3QiLCJyZXZpc2lvbiI6MTcsInNuYXBzaG90X2F0IjoiMjAyNi0wMS0wMlQwMzowNDo0MSswMDowMCJ9", + "snapshot_at": "2026-01-02T03:04:41Z", + "total": 1 + }, + "is_error": false, + "operation": "board.list" + }, + { + "body": { + "closed_upstream": 0, + "declared": 1, + "excluded_unlinked": 0, + "freshness": { + "collectors": "", + "detail": "no collector has completed a sweep yet", + "status": "never_swept" + }, + "items": [ + { + "classified": true, + "kind": "chore", + "labels": [], + "origin": "declared", + "priority": "p1", + "ref": "WI-1", + "score": 52.0, + "state": "in_progress", + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:39Z" + } + ], + "observed": 0, + "scope": "global", + "suppressed": 0, + "total_considered": 1 + }, + "is_error": false, + "operation": "backlog" + }, + { + "body": { + "text": "not_found: no project with slug 'missing'" + }, + "is_error": true, + "operation": "backlog" + }, + { + "body": { + "closed_upstream": 0, + "declared": 0, + "excluded_unlinked": 0, + "freshness": { + "collectors": "", + "detail": "no collector has completed a sweep yet", + "status": "never_swept" + }, + "items": [], + "observed": 0, + "scope": "global", + "suppressed": 0, + "total_considered": 0 + }, + "is_error": false, + "operation": "bugs" + }, + { + "body": { + "text": "not_found: no project with slug 'missing'" + }, + "is_error": true, + "operation": "bugs" + }, + { + "body": { + "text": "not_found: no project with slug 'missing'" + }, + "is_error": true, + "operation": "deps" + }, + { + "body": { + "comments": 1, + "export_format_version": 2, + "path": "/export.json", + "projects": 2, + "work_items": 1 + }, + "is_error": false, + "operation": "export" + }, + { + "body": { + "applied": false, + "base": "2026-01-02T03:04:48Z", + "base_source": "an export of this same instance: its exported_at", + "by_entity": { + "actor": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 4, + "updated": 0 + }, + "comment": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 1, + "updated": 0 + }, + "initiative": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 1, + "updated": 0 + }, + "label": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 1, + "updated": 0 + }, + "project": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 2, + "updated": 0 + }, + "work_item": { + "conflict": 0, + "created": 0, + "skipped": 0, + "unchanged": 1, + "updated": 0 + } + }, + "changes": [], + "conflicted": 0, + "created": 0, + "detail": "Dry run: nothing was written. Pass --apply --confirm to merge.", + "export_format_version": 2, + "instance_id": "ins_0001", + "projects": 2, + "skipped": 0, + "source": "/export.json", + "unchanged": 10, + "updated": 0, + "work_items": 1 + }, + "is_error": false, + "operation": "import" + }, + { + "body": { + "project": { + "agent_policy": { + "base_branch": "", + "commit_identity": "", + "deployed_setup": "", + "forbidden_text": [], + "main_branches": [], + "merge_deploys_prod": false, + "rules": [], + "test_recipe": "" + }, + "compliance_status": "not_checked", + "contract_adopted_at": "2026-01-02T03:04:11Z", + "created_at": "2026-01-02T03:04:11Z", + "exclusions": [ + ".venv/", + "node_modules/", + "target/", + "dist/", + "build/", + ".git/", + ".claude/" + ], + "id": "prj_0001", + "lifecycle_state": "active", + "link_state": "unlinked", + "name": "Parity", + "repo_url": "https://example.invalid/parity.git", + "root_path": "/proj", + "slug": "parity", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:50Z", + "write_back": "full" + } + }, + "is_error": false, + "operation": "forge.writeback" + }, + { + "body": { + "actions": [ + { + "action": "comment", + "at": "2026-01-02T03:04:37Z", + "detail": "write-back is off for this project", + "id": "wbk_0001", + "outcome": "skipped", + "policy": "none", + "reason": "parity" + } + ] + }, + "is_error": false, + "operation": "forge.actions" + }, + { + "body": { + "actions": [ + { + "action": "comment", + "at": "2026-01-02T03:04:37Z", + "detail": "write-back is off for this project", + "id": "wbk_0001", + "outcome": "skipped", + "policy": "none", + "reason": "parity" + } + ] + }, + "is_error": false, + "operation": "forge.actions" + }, + { + "body": { + "text": "invalid_request: 'no.such.host' is not a configured forge host; add it to forge_token_files before listing repositories" + }, + "is_error": true, + "operation": "forge.repos" + }, + { + "body": { + "accounts": [] + }, + "is_error": false, + "operation": "forge.account_status" + }, + { + "body": { + "text": "invalid_request: 'no.such.host' has no provider to validate a token against; configure the host under forge_token_files first" + }, + "is_error": true, + "operation": "forge.account_link" + }, + { + "body": { + "contributions": [ + { + "contribution": 55.0, + "detail": "p1", + "input": "priority", + "value": 1.0, + "weight": 55.0 + }, + { + "contribution": 0.0, + "detail": "0.0 days since last change", + "input": "staleness", + "value": 0.0, + "weight": 0.5 + }, + { + "contribution": 0.0, + "detail": "0 item(s) depend on this one", + "input": "blocking_fan_out", + "value": 0.0, + "weight": 8.0 + }, + { + "contribution": 0.0, + "detail": "no initiative", + "input": "initiative_weight", + "value": 0.0, + "weight": 0.25 + }, + { + "contribution": -3.0, + "detail": "trust state unverified", + "input": "trust_penalty", + "value": 1.0, + "weight": -3.0 + }, + { + "contribution": 0.0, + "detail": "no open pull request", + "input": "open_pr", + "value": 0.0, + "weight": 12.0 + }, + { + "contribution": 0.0, + "detail": "no branch observed", + "input": "branch_activity", + "value": 0.0, + "weight": 8.0 + } + ], + "inputs_not_yet_available": { + "ci_red_boost": "arrives with the CI check observations it reads" + }, + "ref": "WI-1", + "title": "parity item renamed", + "total": 52.0 + }, + "is_error": false, + "operation": "why" + }, + { + "body": { + "text": "not_found: no work item or observed subject 'WI-404'" + }, + "is_error": true, + "operation": "why" + }, + { + "body": { + "items": [ + { + "body": "", + "created_at": "2026-01-02T03:04:29Z", + "id": "wrk_0001", + "kind": "chore", + "labels": [], + "origin": "created", + "priority": "p1", + "ref": "WI-1", + "relations": [], + "state": "in_progress", + "title": "parity item renamed", + "trust_state": "unverified", + "updated_at": "2026-01-02T03:04:39Z" + } + ], + "mode": "full", + "total": 1 + }, + "is_error": false, + "operation": "work.list" + }, + { + "body": { + "items": [], + "mode": "summary", + "total": 1 + }, + "is_error": false, + "operation": "work.list" + }, + { + "body": { + "items": [ + { + "kind": "chore", + "priority": "p1", + "ref": "WI-1", + "state": "in_progress", + "title": "parity item renamed" + } + ], + "missing": [ + "WI-404", + "WI-2" + ] + }, + "is_error": false, + "operation": "work.get_many" + }, + { + "body": { + "records": [ + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:51Z", + "entity_id": "prj_0001", + "entity_kind": "project", + "id": "aud_0019", + "operation": "project.writeback", + "payload_digest": "sha256:5160587a6bd4fd6e1c8cfdfbe9e7b71a0aff7cb411b75e8ad277bea16adfdf65", + "reason": "parity harness", + "revision": 18, + "txn_id": "txn_0022" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:40Z", + "entity_id": "wrk_0001", + "entity_kind": "work_item", + "id": "aud_0018", + "operation": "work.transition", + "payload_digest": "sha256:3e2a6091aa3749e51f7d31a8e33b394a832c09657e63217835f679d979d60c0e", + "reason": "parity", + "revision": 17, + "txn_id": "txn_0019" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:38Z", + "entity_id": "cmt_0001", + "entity_kind": "comment", + "id": "aud_0017", + "operation": "work.comment", + "payload_digest": "sha256:4b2f8d9eeac24ac39262aff03628f2c1fd35cf5d69ee46e22cb9297504bac702", + "reason": "parity", + "revision": 16, + "txn_id": "txn_0018" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:32Z", + "entity_id": "wrk_0001", + "entity_kind": "work_item", + "id": "aud_0016", + "operation": "work.update", + "payload_digest": "sha256:1b7bbf5cf7c7f958e503144a57ead6668ed52a60ad818983f2b53baccbe0ff89", + "reason": "parity", + "revision": 15, + "txn_id": "txn_0017" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:30Z", + "entity_id": "wrk_0001", + "entity_kind": "work_item", + "id": "aud_0015", + "operation": "work.create", + "payload_digest": "sha256:575870b9df83c702df7cfd73d439289a2e52b7dc3f37236710df812b0664db5b", + "reason": "parity", + "revision": 14, + "txn_id": "txn_0016" + } + ], + "total": 19 + }, + "is_error": false, + "operation": "audit.list" + }, + { + "body": { + "text": "invalid_request: WI-1 belongs to no project, so there is no branch to bind it to" + }, + "is_error": true, + "operation": "work.bind_branch" + }, + { + "body": { + "text": "not_found: no observed subject 'gh:parity-org/parity#404'" + }, + "is_error": true, + "operation": "work.adopt" + }, + { + "body": { + "digests": [], + "engine": "no session engine is configured (VOGT_ENGINE_URL is unset)", + "hidden_count": 0, + "sessions": [], + "total": 0, + "truncated": false + }, + "is_error": false, + "operation": "session.list" + }, + { + "body": { + "counts": { + "needs_you": 0, + "total": 0 + }, + "engine": "no session engine is configured (VOGT_ENGINE_URL is unset)", + "rows": [], + "swept_at": "2026-01-02T03:04:55Z" + }, + "is_error": false, + "operation": "session.sweep" + }, + { + "body": { + "bytes": 0, + "engine": "no session engine is configured (VOGT_ENGINE_URL is unset)", + "text": "", + "total_bytes": 0, + "truncated": false + }, + "is_error": false, + "operation": "session.log_tail" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "session.start" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "session.input" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "session.screen" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "session.wait" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "session.answer" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "session.hibernate" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "session.keep_awake" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "session.set_role" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, so there is nothing to open a terminal on (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "session.wake" + }, + { + "body": { + "text": "not_found: no session 'ses_00000000000000000000000000'" + }, + "is_error": true, + "operation": "session.stop" + }, + { + "body": { + "text": "engine_unavailable: no session engine is configured, and chats run on it (set VOGT_ENGINE_URL)" + }, + "is_error": true, + "operation": "chat.list" + }, + { + "body": { + "text": "invalid_request: 'nope' is not a chat id (a UUID, from chat.list or chat.create)" + }, + "is_error": true, + "operation": "chat.get" + }, + { + "body": { + "user": { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:56Z", + "scopes": [ + "read", + "work.write", + "project.write" + ], + "updated_at": "2026-01-02T03:04:56Z", + "username": "ada" + } + }, + "is_error": false, + "operation": "user.create" + }, + { + "body": { + "users": [ + { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:56Z", + "scopes": [ + "read", + "work.write", + "project.write" + ], + "updated_at": "2026-01-02T03:04:56Z", + "username": "ada" + } + ] + }, + "is_error": false, + "operation": "user.list" + }, + { + "body": { + "user": { + "actor_id": "act_0005", + "actor_identity_ref": "human:ada", + "created_at": "2026-01-02T03:04:56Z", + "scopes": [ + "read", + "work.write", + "project.write" + ], + "updated_at": "2026-01-02T03:04:58Z", + "username": "ada" + } + }, + "is_error": false, + "operation": "user.set_password" + }, + { + "body": { + "tokens": [ + { + "actor_id": "act_0003", + "actor_identity_ref": "agent:vogt-sessions", + "created_at": "2026-01-02T03:04:15Z", + "id": "tok_0002", + "kind": "api", + "name": "bootstrap agent token", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + }, + { + "actor_id": "act_0002", + "actor_identity_ref": "agent:vogt-engine", + "created_at": "2026-01-02T03:04:11Z", + "id": "tok_0001", + "kind": "api", + "name": "bootstrap core token", + "scopes": [ + "read", + "work.write", + "project.write" + ] + } + ] + }, + "is_error": false, + "operation": "token.list" + }, + { + "body": { + "token": { + "actor_id": "act_0003", + "actor_identity_ref": "agent:vogt-sessions", + "created_at": "2026-01-02T03:04:15Z", + "id": "tok_0002", + "kind": "api", + "name": "bootstrap agent token", + "revoked_at": "2026-01-02T03:05:00Z", + "revoked_reason": "parity harness", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + } + }, + "is_error": false, + "operation": "token.revoke" + }, + { + "body": { + "display_name": "parity", + "identity_ref": "local:parity", + "kind": "human", + "scopes": [ + "admin", + "project.write", + "read", + "work.write", + "writeback" + ] + }, + "is_error": false, + "operation": "auth.whoami" + }, + { + "body": { + "declared_schema_version": 24, + "engine_state": "not configured", + "import_root": "/instance/repos", + "instance_id": "ins_0001", + "observed_schema_version": 6, + "path": "/backup", + "taken_at": "2026-01-02T03:05:02Z" + }, + "is_error": false, + "operation": "backup" + }, + { + "body": { + "declared_schema_version": 24, + "engine_state": "not in this backup", + "import_root_now": "/instance/repos", + "import_root_then": "/instance/repos", + "instance_id": "ins_0001", + "migrations_applied": [], + "restored_from": "2026-01-02T03:05:02Z", + "source": "/backup" + }, + "is_error": false, + "operation": "restore" + }, + { + "body": { + "sessions_revoked": 0, + "username": "ada" + }, + "is_error": false, + "operation": "user.remove" + }, + { + "body": { + "events": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12Z", + "audit_id": "aud_0002", + "entity_id": "tok_0001", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 1, + "summary": { + "actor": "agent:vogt-engine", + "scopes": [ + "read", + "work.write", + "project.write" + ] + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16Z", + "audit_id": "aud_0003", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 2, + "summary": { + "actor": "agent:vogt-sessions", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:08Z", + "audit_id": "aud_0004", + "entity_id": "act_0004", + "entity_kind": "actor", + "kind": "actor.created", + "seq": 3, + "summary": { + "identity_ref": "agent:parity", + "kind": "agent" + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:10Z", + "audit_id": "aud_0005", + "entity_id": "lbl_0001", + "entity_kind": "label", + "kind": "label.created", + "seq": 4, + "summary": { + "name": "parity" + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:12Z", + "audit_id": "aud_0006", + "entity_id": "prj_0001", + "entity_kind": "project", + "kind": "project.registered", + "seq": 5, + "summary": { + "name": "Parity", + "slug": "parity" + } + } + ], + "next_cursor": 5 + }, + "is_error": false, + "operation": "events.list" + }, + { + "body": { + "events": [ + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:16Z", + "audit_id": "aud_0003", + "entity_id": "tok_0002", + "entity_kind": "token", + "kind": "token.adopted", + "seq": 2, + "summary": { + "actor": "agent:vogt-sessions", + "scopes": [ + "read", + "work.write", + "project.write", + "writeback" + ] + } + }, + { + "actor_id": "act_0001", + "at": "2026-01-02T03:04:08Z", + "audit_id": "aud_0004", + "entity_id": "act_0004", + "entity_kind": "actor", + "kind": "actor.created", + "seq": 3, + "summary": { + "identity_ref": "agent:parity", + "kind": "agent" + } + } + ], + "next_cursor": 3 + }, + "is_error": false, + "operation": "events.list" + }, + { + "body": { + "records": [ + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:05:07Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0023", + "operation": "user.remove", + "payload_digest": "sha256:6cb1b570d1a756565af0191ec35d1fed1dc103ed9b82afa000b68a0172b6f711", + "reason": "parity harness", + "revision": 22, + "txn_id": "txn_0028" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:05:01Z", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0022", + "operation": "token.revoke", + "payload_digest": "sha256:8fa240315d52246f71e2fb88d07d05ccf98a13728f41fe384e7bdfc092b145bd", + "reason": "parity harness", + "revision": 21, + "txn_id": "txn_0026" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:59Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0021", + "operation": "user.set_password", + "payload_digest": "sha256:6782e2b5fa4d26772e8b2eeacbf82bb2c48960ddb25c736b28001d387114acf5", + "reason": "parity harness", + "revision": 20, + "txn_id": "txn_0025" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:57Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0020", + "operation": "user.create", + "payload_digest": "sha256:67e02676f1f6d286e8b3624b34c5496ed548e0ac92c3485f85c350f4c54dcce7", + "reason": "parity harness", + "revision": 19, + "txn_id": "txn_0024" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:51Z", + "entity_id": "prj_0001", + "entity_kind": "project", + "id": "aud_0019", + "operation": "project.writeback", + "payload_digest": "sha256:5160587a6bd4fd6e1c8cfdfbe9e7b71a0aff7cb411b75e8ad277bea16adfdf65", + "reason": "parity harness", + "revision": 18, + "txn_id": "txn_0022" + } + ], + "total": 23 + }, + "is_error": false, + "operation": "audit.list" + }, + { + "body": { + "records": [ + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:05:01Z", + "entity_id": "tok_0002", + "entity_kind": "token", + "id": "aud_0022", + "operation": "token.revoke", + "payload_digest": "sha256:8fa240315d52246f71e2fb88d07d05ccf98a13728f41fe384e7bdfc092b145bd", + "reason": "parity harness", + "revision": 21, + "txn_id": "txn_0026" + }, + { + "actor_id": "act_0001", + "actor_identity_ref": "local:parity", + "at": "2026-01-02T03:04:59Z", + "entity_id": "act_0005", + "entity_kind": "user", + "id": "aud_0021", + "operation": "user.set_password", + "payload_digest": "sha256:6782e2b5fa4d26772e8b2eeacbf82bb2c48960ddb25c736b28001d387114acf5", + "reason": "parity harness", + "revision": 20, + "txn_id": "txn_0025" + } + ], + "total": 23 + }, + "is_error": false, + "operation": "audit.list" + }, + { + "body": { + "text": "invalid_request: /backup is a backup of this instance (ins_0001); a clone copies another instance. Use `restore` to put this one back." + }, + "is_error": true, + "operation": "clone" + }, + { + "body": { + "text": "invalid_request: /backup is a backup of this instance (ins_0001); a clone copies another instance. Use `restore` to put this one back." + }, + "is_error": true, + "operation": "clone" + }, + { + "body": { + "text": "not_found: /missing has no manifest.json; it is not a Vogt backup" + }, + "is_error": true, + "operation": "clone" + }, + { + "body": { + "text": "invalid_request: 'not a repo!' does not name a GitHub repository or a configured Forgejo repository; give owner/name, host/owner/name, or a repository URL" + }, + "is_error": true, + "operation": "project.import" + }, + { + "body": { + "text": "invalid_request: 'not a repo!' does not name a GitHub repository or a configured Forgejo repository; give owner/name, host/owner/name, or a repository URL" + }, + "is_error": true, + "operation": "project.import" + }, + { + "body": { + "decision": { + "asker": "local:parity", + "context": "CI is green.", + "created_at": "2026-01-02T03:05:08Z", + "expires_at": "2026-01-03T03:05:08Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "pending" + } + }, + "is_error": false, + "operation": "decision.ask" + }, + { + "body": { + "decision": { + "asker": "local:parity", + "context": "", + "created_at": "2026-01-02T03:05:11Z", + "expires_at": "2026-01-03T03:05:11Z", + "id": "dec_0002", + "options": [ + { + "description": "", + "label": "Ship", + "recommended": true + }, + { + "description": "", + "label": "Wait", + "recommended": false + } + ], + "question": "Ship the release?", + "reason": "parity harness", + "state": "pending" + } + }, + "is_error": false, + "operation": "decision.ask" + }, + { + "body": { + "decisions": [ + { + "asker": "local:parity", + "context": "", + "created_at": "2026-01-02T03:05:11Z", + "expires_at": "2026-01-03T03:05:11Z", + "id": "dec_0002", + "options": [ + { + "description": "", + "label": "Ship", + "recommended": true + }, + { + "description": "", + "label": "Wait", + "recommended": false + } + ], + "question": "Ship the release?", + "reason": "parity harness", + "state": "pending" + }, + { + "asker": "local:parity", + "context": "CI is green.", + "created_at": "2026-01-02T03:05:08Z", + "expires_at": "2026-01-03T03:05:08Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "pending" + } + ] + }, + "is_error": false, + "operation": "decision.list" + }, + { + "body": { + "decisions": [ + { + "asker": "local:parity", + "context": "", + "created_at": "2026-01-02T03:05:11Z", + "expires_at": "2026-01-03T03:05:11Z", + "id": "dec_0002", + "options": [ + { + "description": "", + "label": "Ship", + "recommended": true + }, + { + "description": "", + "label": "Wait", + "recommended": false + } + ], + "question": "Ship the release?", + "reason": "parity harness", + "state": "pending" + }, + { + "asker": "local:parity", + "context": "CI is green.", + "created_at": "2026-01-02T03:05:08Z", + "expires_at": "2026-01-03T03:05:08Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "pending" + } + ] + }, + "is_error": false, + "operation": "decision.list" + }, + { + "body": { + "actor_unknown_hidden": 0, + "counts": { + "active": 2, + "archived": 0, + "snoozed": 0 + }, + "coverage": { + "agent": { + "count": 2, + "detail": "no session engine is configured", + "projects": 0, + "registered": 2, + "source": "agent", + "status": "unconfigured" + }, + "ci": { + "count": 0, + "detail": "this collector has not completed a sweep", + "projects": 0, + "registered": 2, + "source": "ci", + "status": "unswept" + }, + "drift": { + "count": 0, + "detail": "open proposals in the declared store", + "projects": 0, + "registered": 2, + "source": "drift", + "status": "current" + }, + "github": { + "count": 0, + "detail": "this collector has not completed a sweep", + "projects": 0, + "registered": 2, + "source": "github", + "status": "unswept" + } + }, + "engine_available": false, + "engine_detail": "no session engine is configured", + "engine_status": "not_configured", + "entries": [ + { + "action": { + "decision_id": "dec_0002", + "kind": "decision" + }, + "actor_kind": "human", + "actor_login": "local:parity", + "actor_relation": "org_member", + "entry_key": "agent:decision:dec_0002", + "evidence_snapshot": "", + "freshness": "live", + "kind": "decision.pending", + "occurred_at": "2026-01-02T03:05:11Z", + "provisional": true, + "source": "agent", + "source_subject_key": "dec_0002", + "summary": "local:parity asks: Ship the release? Options: Ship (recommended) | Wait.", + "title": "Decision: Ship the release?", + "triage_state": "active", + "trust_state": "unverified" + }, + { + "action": { + "decision_id": "dec_0001", + "kind": "decision" + }, + "actor_kind": "human", + "actor_login": "local:parity", + "actor_relation": "org_member", + "entry_key": "agent:decision:dec_0001", + "evidence_snapshot": "", + "freshness": "live", + "kind": "decision.pending", + "occurred_at": "2026-01-02T03:05:08Z", + "provisional": true, + "source": "agent", + "source_subject_key": "dec_0001", + "summary": "local:parity asks: Merge the branch? Options: Merge (recommended) | Hold.", + "title": "Decision: Merge the branch?", + "triage_state": "active", + "trust_state": "unverified" + } + ], + "github_scope": "registered projects only", + "high_water": { + "agent": "2026-01-02T03:05:11Z" + }, + "instance_scope": "registered projects only", + "snapshot_at": "2026-01-02T03:05:18Z" + }, + "is_error": false, + "operation": "inbox.list" + }, + { + "body": { + "decision": { + "answer": { + "label": "Merge", + "note": "reviewed" + }, + "answered_at": "2026-01-02T03:05:24Z", + "answered_by": "local:parity", + "asker": "local:parity", + "context": "CI is green.", + "created_at": "2026-01-02T03:05:08Z", + "expires_at": "2026-01-03T03:05:08Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "answered" + } + }, + "is_error": false, + "operation": "decision.answer" + }, + { + "body": { + "text": "conflict: decision dec_0001 is answered, not pending" + }, + "is_error": true, + "operation": "decision.answer" + }, + { + "body": { + "text": "invalid_request: 'Deploy' is not an option of dec_0002; it offers 'Ship', 'Wait'" + }, + "is_error": true, + "operation": "decision.answer" + }, + { + "body": { + "decision": { + "asker": "local:parity", + "context": "", + "created_at": "2026-01-02T03:05:11Z", + "expires_at": "2026-01-03T03:05:11Z", + "id": "dec_0002", + "options": [ + { + "description": "", + "label": "Ship", + "recommended": true + }, + { + "description": "", + "label": "Wait", + "recommended": false + } + ], + "question": "Ship the release?", + "reason": "parity harness", + "state": "withdrawn" + } + }, + "is_error": false, + "operation": "decision.withdraw" + }, + { + "body": { + "decisions": [ + { + "answer": { + "label": "Merge", + "note": "reviewed" + }, + "answered_at": "2026-01-02T03:05:24Z", + "answered_by": "local:parity", + "asker": "local:parity", + "context": "CI is green.", + "created_at": "2026-01-02T03:05:08Z", + "expires_at": "2026-01-03T03:05:08Z", + "id": "dec_0001", + "options": [ + { + "description": "land it now", + "label": "Merge", + "recommended": true + }, + { + "description": "wait for the review", + "label": "Hold", + "recommended": false + } + ], + "question": "Merge the branch?", + "reason": "parity harness", + "state": "answered" + } + ] + }, + "is_error": false, + "operation": "decision.list" + }, + { + "body": { + "actor_unknown_hidden": 0, + "counts": { + "active": 0, + "archived": 0, + "snoozed": 0 + }, + "coverage": { + "agent": { + "count": 0, + "detail": "no session engine is configured", + "projects": 0, + "registered": 2, + "source": "agent", + "status": "unconfigured" + }, + "ci": { + "count": 0, + "detail": "this collector has not completed a sweep", + "projects": 0, + "registered": 2, + "source": "ci", + "status": "unswept" + }, + "drift": { + "count": 0, + "detail": "open proposals in the declared store", + "projects": 0, + "registered": 2, + "source": "drift", + "status": "current" + }, + "github": { + "count": 0, + "detail": "this collector has not completed a sweep", + "projects": 0, + "registered": 2, + "source": "github", + "status": "unswept" + } + }, + "engine_available": false, + "engine_detail": "no session engine is configured", + "engine_status": "not_configured", + "entries": [], + "github_scope": "registered projects only", + "high_water": {}, + "instance_scope": "registered projects only", + "snapshot_at": "2026-01-02T03:05:32Z" + }, + "is_error": false, + "operation": "inbox.list" + } +] diff --git a/tests/parity/golden/21c95f6-s4/meta.json b/tests/parity/golden/21c95f6-s4/meta.json new file mode 100644 index 000000000..2bbb7cd8e --- /dev/null +++ b/tests/parity/golden/21c95f6-s4/meta.json @@ -0,0 +1,5 @@ +{ + "main_sha": "21c95f674e1b03ee177394f605abb1abfac41cdd", + "recorded_at": "2026-10-17T05:54:29Z", + "recorder": "scripts/parity.py" +} diff --git a/tests/parity/script.json b/tests/parity/script.json index fccafd43b..34c93e55c 100644 --- a/tests/parity/script.json +++ b/tests/parity/script.json @@ -1338,6 +1338,133 @@ "consolidate": true } } + }, + { + "operation": "decision.ask", + "params": { + "question": "Merge the branch?", + "options": [ + { + "label": "Merge", + "description": "land it now", + "recommended": true + }, + { + "label": "Hold", + "description": "wait for the review" + } + ], + "context": "CI is green.", + "reason": "parity harness" + }, + "expect": { + "exit": 0 + }, + "why": "a person-run ask records the asker as the person; the id is the first dec_ issued" + }, + { + "operation": "decision.ask", + "params": { + "question": "Ship the release?", + "options": [ + { + "label": "Ship", + "recommended": true + }, + { + "label": "Wait" + } + ], + "reason": "parity harness" + }, + "expect": { + "exit": 0 + } + }, + { + "operation": "decision.list", + "params": {}, + "expect": { + "exit": 0 + } + }, + { + "operation": "decision.list", + "params": { + "state": "pending" + }, + "expect": { + "exit": 0 + } + }, + { + "operation": "inbox.list", + "params": {}, + "expect": { + "exit": 0 + } + }, + { + "operation": "decision.answer", + "params": { + "id": "dec_0001", + "label": "Merge", + "note": "reviewed", + "reason": "parity harness" + }, + "expect": { + "exit": 0 + } + }, + { + "operation": "decision.answer", + "params": { + "id": "dec_0001", + "label": "Hold", + "reason": "parity harness" + }, + "expect": { + "exit": 1 + }, + "why": "an answered decision is no longer pending" + }, + { + "operation": "decision.answer", + "params": { + "id": "dec_0002", + "label": "Deploy", + "reason": "parity harness" + }, + "expect": { + "exit": 1 + }, + "why": "the label must be one the decision offers" + }, + { + "operation": "decision.withdraw", + "params": { + "id": "dec_0002", + "reason": "parity harness" + }, + "expect": { + "exit": 0 + } + }, + { + "operation": "decision.list", + "params": { + "state": "answered" + }, + "expect": { + "exit": 0 + } + }, + { + "operation": "inbox.list", + "params": {}, + "expect": { + "exit": 0 + } } ] } From 0085db10f8d69547b6773ee1db4814051f5b365c Mon Sep 17 00:00:00 2001 From: sprooty Date: Sat, 17 Oct 2026 06:01:30 +0000 Subject: [PATCH 3/3] test(parity): refuse a bad inbox.filter and read back the stored filter (WI-1069) Two refusal steps for preference.set inbox.filter (an unknown source, an unknown field) and a read-back, so the B5 blocker on refused filter values is held by the parity harness. Golden 21c95f6-s4 re-recorded from Python for cli, http and mcp; the Rust core agrees on every step. Co-Authored-By: Claude Haiku 5.5 Signed-off-by: sprooty --- tests/parity/golden/21c95f6-s4/cli.json | 32 +++++++++++++++++++++ tests/parity/golden/21c95f6-s4/mcp.json | 36 ++++++++++++++++++++++++ tests/parity/golden/21c95f6-s4/meta.json | 2 +- tests/parity/script.json | 34 ++++++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/tests/parity/golden/21c95f6-s4/cli.json b/tests/parity/golden/21c95f6-s4/cli.json index f14707eef..ff0455e1a 100644 --- a/tests/parity/golden/21c95f6-s4/cli.json +++ b/tests/parity/golden/21c95f6-s4/cli.json @@ -3248,5 +3248,37 @@ }, "exit": 0, "operation": "inbox.list" + }, + { + "body": null, + "exit": 1, + "operation": "preference.set" + }, + { + "body": null, + "exit": 1, + "operation": "preference.set" + }, + { + "body": { + "preferences": [ + { + "key": "inbox.filter", + "updated_at": "2026-01-02T03:04:05Z", + "value": { + "actor": "any", + "sources": [ + "ci" + ], + "triage_states": [ + "active" + ] + }, + "version": 1 + } + ] + }, + "exit": 0, + "operation": "preference.get" } ] diff --git a/tests/parity/golden/21c95f6-s4/mcp.json b/tests/parity/golden/21c95f6-s4/mcp.json index 930aa66b0..cc1b613c8 100644 --- a/tests/parity/golden/21c95f6-s4/mcp.json +++ b/tests/parity/golden/21c95f6-s4/mcp.json @@ -2898,5 +2898,41 @@ }, "is_error": false, "operation": "inbox.list" + }, + { + "body": { + "text": "invalid_preference: invalid inbox.filter value \u2014 sources.0: Input should be 'github', 'drift', 'ci' or 'agent'" + }, + "is_error": true, + "operation": "preference.set" + }, + { + "body": { + "text": "invalid_preference: invalid inbox.filter value \u2014 unknown: Extra inputs are not permitted" + }, + "is_error": true, + "operation": "preference.set" + }, + { + "body": { + "preferences": [ + { + "key": "inbox.filter", + "updated_at": "2026-01-02T03:04:25Z", + "value": { + "actor": "any", + "sources": [ + "ci" + ], + "triage_states": [ + "active" + ] + }, + "version": 1 + } + ] + }, + "is_error": false, + "operation": "preference.get" } ] diff --git a/tests/parity/golden/21c95f6-s4/meta.json b/tests/parity/golden/21c95f6-s4/meta.json index 2bbb7cd8e..395a25f76 100644 --- a/tests/parity/golden/21c95f6-s4/meta.json +++ b/tests/parity/golden/21c95f6-s4/meta.json @@ -1,5 +1,5 @@ { "main_sha": "21c95f674e1b03ee177394f605abb1abfac41cdd", - "recorded_at": "2026-10-17T05:54:29Z", + "recorded_at": "2026-10-17T06:01:06Z", "recorder": "scripts/parity.py" } diff --git a/tests/parity/script.json b/tests/parity/script.json index 34c93e55c..5534497b8 100644 --- a/tests/parity/script.json +++ b/tests/parity/script.json @@ -1465,6 +1465,40 @@ "expect": { "exit": 0 } + }, + { + "operation": "preference.set", + "params": { + "key": "inbox.filter", + "value": "{\"sources\": [\"bogus\"]}", + "reason": "parity harness" + }, + "expect": { + "exit": 1 + }, + "why": "B5: an inbox.filter value the Inbox model refuses is refused, not stored" + }, + { + "operation": "preference.set", + "params": { + "key": "inbox.filter", + "value": "{\"unknown\": 1}", + "reason": "parity harness" + }, + "expect": { + "exit": 1 + }, + "why": "B5: the saved filter forbids unknown fields" + }, + { + "operation": "preference.get", + "params": { + "key": "inbox.filter" + }, + "expect": { + "exit": 0 + }, + "why": "B5: a refused write leaves the stored filter as it was" } ] }