diff --git a/CHANGELOG.md b/CHANGELOG.md index cbe0fd0..59cbd6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Written by hand. Each release says what changed for someone running Rowbit, not every commit. +## Unreleased + +### Fixed + +- When an agent proposes the same statement on the same database again, its older held copy is withdrawn, with a `Superseded` note naming the new change. Its review page links the new change and won't approve the old one. A held change left over from an earlier run could be approved in its place: it applied, while the change the agent was watching stayed held, and approving that one too failed on drift. + ## 0.2.2 (2026-11-07) Set up on your database: `rowbit setup` checks your database's URL and Rowbit's grants, writes rules that hold every write, and connects your agents; TLS to the database; a container image and an MCP Registry listing. diff --git a/crates/rowbit-engine/src/lib.rs b/crates/rowbit-engine/src/lib.rs index b14d2be..0f7ab9b 100644 --- a/crates/rowbit-engine/src/lib.rs +++ b/crates/rowbit-engine/src/lib.rs @@ -359,6 +359,13 @@ impl Engine { }; store.transition_notifying(id, &event, &actor, None, ¬ify)?; } + if decision != Decision::Block || shadow { + // Only tidies up: the new change is recorded and decided, so a + // failure here must not tell the agent its proposal failed. + if let Err(e) = self.supersede(agent, database, &classified.canonical, id) { + eprintln!("rowbit: change {id}: couldn't withdraw older held copies: {e}"); + } + } if decision == Decision::Allow || shadow { self.apply(id).await?; } @@ -571,6 +578,37 @@ impl Engine { Ok(self.store().verify()?) } + /// Withdraws `agent`'s older held changes that are the same statement + /// on the same database as `newer`, so a person never approves a stale + /// copy of what the agent just proposed again. Each one gets a + /// `Superseded` note naming `newer`, in the same transaction as the + /// withdraw. A copy a person decided on in the meantime is left alone. + /// + /// Matches on the agent's name, not the connection: two projects + /// connected as the same agent replace each other's held copies. + fn supersede( + &self, + agent: &str, + database: &str, + canonical: &str, + newer: ChangeId, + ) -> Result<(), EngineError> { + let mut store = self.store(); + for id in store.list(State::Held)? { + if id == newer { + continue; + } + let old = store.get(id)?; + let same = old.agent == agent + && old.database == database + && classify(&old.sql).is_ok_and(|c| c.canonical == canonical); + if same { + store.withdraw_superseded(id, agent, &format!("{SUPERSEDED_PREFIX}{newer}"))?; + } + } + Ok(()) + } + /// Changes waiting for a person, oldest first. pub fn held(&self) -> Result, EngineError> { Ok(self.store().list(State::Held)?) @@ -695,8 +733,8 @@ impl Engine { Ok(_) | Err(StoreError::NotFound(_)) => return Err(EngineError::NotFound(id)), Err(e) => return Err(e.into()), }; - let reason = store - .events(id)? + let history = store.events(id)?; + let reason = history .iter() .rev() .find(|e| e.from.as_deref() != Some(e.to.as_str()) || e.event == "Proposed") @@ -706,7 +744,8 @@ impl Engine { "PolicyHeld" | "PolicyBlocked" | "Reject" | "ApplyFailed" ) }) - .and_then(|e| e.detail.clone()); + .and_then(|e| e.detail.clone()) + .or_else(|| superseded_by(&history).map(|n| format!("{SUPERSEDED_PREFIX}{n}"))); let (table, rows) = match &change.review { Some(Review::Rows(p)) => (Some(p.table.qualified_name()), Some(p.rows.len())), Some(Review::Ddl { .. }) | None => (None, None), @@ -921,6 +960,23 @@ pub const DEFAULT_REVIEW_ADDRESS: &str = "http://127.0.0.1:8080"; /// A change's page on the review UI at `address`, by way of sign-in if /// the browser isn't signed in. +/// How a `Superseded` note's detail starts; the newer change's id follows. +const SUPERSEDED_PREFIX: &str = "proposed again as #"; + +/// The change that replaced this one, from its `Superseded` note. +pub fn superseded_by(history: &[AuditEvent]) -> Option { + history + .iter() + .find(|e| e.event == "Superseded") + .and_then(|e| { + e.detail + .as_deref()? + .strip_prefix(SUPERSEDED_PREFIX)? + .parse() + .ok() + }) +} + pub fn change_link(address: &str, id: ChangeId) -> String { format!("{}/changes/{id}", address.trim_end_matches('/')) } diff --git a/crates/rowbit-engine/tests/propose.rs b/crates/rowbit-engine/tests/propose.rs index 5df592c..0da40fb 100644 --- a/crates/rowbit-engine/tests/propose.rs +++ b/crates/rowbit-engine/tests/propose.rs @@ -354,3 +354,63 @@ async fn rejected_and_drifted_changes_write_nothing(var: &str, table: &str) { assert_eq!(refunded, [4]); conn.disconnect().await.unwrap(); } + +// An agent that proposes the same statement again replaces its older held +// copy, so the person can't approve a stale one by mistake. +on_both!(proposing_again_withdraws_the_older_held_copy); +async fn proposing_again_withdraws_the_older_held_copy(var: &str, table: &str) { + let Some((engine, t)) = setup(var, &format!("{table}_w")).await else { + return; + }; + let sql = format!("UPDATE {t} SET status = 'refunded' WHERE status = 'approved'"); + let first = propose(&engine, "refund-bot", &sql).await; + assert_eq!(first.state, State::Held); + let other = propose( + &engine, + "refund-bot", + &format!("UPDATE {t} SET status = 'refunded' WHERE id = 3"), + ) + .await; + assert_eq!(other.state, State::Held, "{other:?}"); + + // Same statement, spelled differently. + let second = propose( + &engine, + "refund-bot", + &format!("update {t} set status = 'refunded' where status = 'approved'"), + ) + .await; + assert_eq!(second.state, State::Held); + assert_eq!(engine.held().unwrap(), [other.id, second.id]); + + let old = engine.status("refund-bot", first.id).unwrap(); + assert_eq!(old.state, State::Rejected); + assert_eq!(old.review_url, None); + // The agent that looks at the old copy learns why it was withdrawn. + let because = format!("proposed again as #{}", second.id); + assert_eq!(old.reason.as_deref(), Some(because.as_str())); + let history = engine.history(first.id).unwrap(); + let events: Vec<_> = history + .iter() + .rev() + .take(2) + .map(|e| e.event.as_str()) + .collect(); + assert_eq!( + events, + ["Superseded", "Withdraw"], + "noted after the withdraw" + ); + assert_eq!( + history.last().unwrap().detail.as_deref(), + Some(because.as_str()) + ); + assert_eq!(rowbit_engine::superseded_by(&history), Some(second.id)); + assert!(engine.verify().unwrap().is_ok()); + + let hash = engine.change(first.id).unwrap().review_hash.unwrap(); + assert!(engine.approve("philip", first.id, &hash).await.is_err()); + let hash = engine.change(second.id).unwrap().review_hash.unwrap(); + let change = engine.approve("philip", second.id, &hash).await.unwrap(); + assert_eq!(change.state, State::Applied); +} diff --git a/crates/rowbit-mcp/tests/attacks.rs b/crates/rowbit-mcp/tests/attacks.rs index 2e0a72d..2106a41 100644 --- a/crates/rowbit-mcp/tests/attacks.rs +++ b/crates/rowbit-mcp/tests/attacks.rs @@ -208,7 +208,9 @@ async fn self_approval(var: &str, table: &str) { ) .await; assert!(text.contains("\"held\""), "{text}"); - assert_eq!(w.engine.held().unwrap().len(), 2); + // The same statement again replaces the first copy; neither is approved. + let again: Value = serde_json::from_str(&text).unwrap(); + assert_eq!(w.engine.held().unwrap(), [again["id"].as_i64().unwrap()]); w.untouched().await; } diff --git a/crates/rowbit-store/src/lib.rs b/crates/rowbit-store/src/lib.rs index dd7dd0a..44f2528 100644 --- a/crates/rowbit-store/src/lib.rs +++ b/crates/rowbit-store/src/lib.rs @@ -456,7 +456,33 @@ impl Store { if matches!(event, Event::ApplySucceeded) { return Err(StoreError::ApplyNeedsRecord); } - self.transition_with(id, event, actor, reviewed, None, notify) + self.transition_with(id, event, actor, reviewed, None, notify, None) + } + + /// Withdraws a held change for `agent`, which proposed it, and logs a + /// `Superseded` note with `detail` in the same transaction, so the note + /// is never there without the withdraw. Returns false, and logs + /// nothing, if the change is no longer held; only the proposer may. + pub fn withdraw_superseded( + &mut self, + id: ChangeId, + agent: &str, + detail: &str, + ) -> Result { + let actor = Actor::Agent(agent.to_owned()); + match self.transition_with( + id, + &Event::Withdraw, + &actor, + None, + None, + &[], + Some(("Superseded", detail)), + ) { + Ok(_) => Ok(true), + Err(StoreError::Transition(TransitionError::InvalidTransition { .. })) => Ok(false), + Err(e) => Err(e), + } } /// Records a successful apply (`ApplySucceeded`, by the system) together @@ -469,9 +495,13 @@ impl Store { None, Some(applied.to_canonical_json()), &[], + None, ) } + // One private entry point for every transition; the callers above name + // what each argument is for. + #[allow(clippy::too_many_arguments)] fn transition_with( &mut self, id: ChangeId, @@ -480,6 +510,7 @@ impl Store { reviewed: Option<&str>, applied: Option>, notify: &[NewDelivery], + note: Option<(&str, &str)>, ) -> Result { let tx = self .conn @@ -536,6 +567,19 @@ impl Store { }, at, )?; + if let Some((event, detail)) = note { + append( + &tx, + id, + Some(to), + to, + event, + Some(detail), + &actor.to_string(), + None, + at, + )?; + } for d in notify { tx.execute( "INSERT INTO deliveries (change_id, target, delivery, payload, state, next_at_ms, created_at_ms) diff --git a/crates/rowbit-store/src/tests.rs b/crates/rowbit-store/src/tests.rs index a6e74cf..9004d27 100644 --- a/crates/rowbit-store/src/tests.rs +++ b/crates/rowbit-store/src/tests.rs @@ -641,3 +641,54 @@ fn settings_are_listed_by_prefix_and_deleted() { // Characters LIKE would treat as wildcards are literal. assert!(store.settings_with_prefix("agent_").unwrap().is_empty()); } + +#[test] +fn a_superseded_copy_is_withdrawn_and_noted_together() { + let mut store = Store::open_in_memory().unwrap(); + let (id, _) = previewed(&mut store); + store + .transition( + id, + &Event::PolicyHeld { reason: "r".into() }, + &Actor::Policy("default".into()), + None, + ) + .unwrap(); + // Only the proposer's name withdraws it. + assert!( + store + .withdraw_superseded(id, "other-bot", "proposed again as #9") + .is_err() + ); + assert_eq!(store.get(id).unwrap().state, State::Held); + assert!( + store + .events(id) + .unwrap() + .iter() + .all(|e| e.event != "Superseded") + ); + + assert!( + store + .withdraw_superseded(id, "refund-bot", "proposed again as #9") + .unwrap() + ); + assert_eq!(store.get(id).unwrap().state, State::Rejected); + let events = store.events(id).unwrap(); + let last: Vec<_> = events + .iter() + .rev() + .take(2) + .map(|e| e.event.as_str()) + .collect(); + assert_eq!(last, ["Superseded", "Withdraw"]); + // No longer held: nothing changes, nothing is noted. + assert!( + !store + .withdraw_superseded(id, "refund-bot", "proposed again as #10") + .unwrap() + ); + assert_eq!(store.events(id).unwrap().len(), events.len()); + assert!(store.verify().unwrap().is_ok()); +} diff --git a/crates/rowbit-web/dist/assets/app.js b/crates/rowbit-web/dist/assets/app.js index 744954b..43e7572 100644 --- a/crates/rowbit-web/dist/assets/app.js +++ b/crates/rowbit-web/dist/assets/app.js @@ -1,6 +1,6 @@ var e=Array.isArray,t=Array.prototype.indexOf,n=Array.prototype.includes,r=Array.from,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyDescriptors,s=Object.prototype,c=Array.prototype,l=Object.getPrototypeOf,u=Object.isExtensible,d=()=>{};function f(e){for(var t=0;t{e=n,t=r}),resolve:e,reject:t}}function m(e,t){if(Array.isArray(e))return e;if(t===void 0||!(Symbol.iterator in e))return Array.from(e);let n=[];for(let r of e)if(n.push(r),n.length===t)break;return n}var h=1024,g=2048,_=4096,v=8192,y=16384,b=32768,x=1<<25,S=65536,C=1<<19,ee=1<<20,te=1<<25,ne=1<<21,re=1<<22,ie=1<<23,ae=Symbol(`$state`),oe=Symbol(`component`),se=Symbol(``),ce=Symbol(`attributes`),le=Symbol(`class`),ue=Symbol(`style`),de=Symbol(`text`),fe=Symbol(`form reset`),pe=new class extends Error{name=`StaleReactionError`;message="The reaction that called `getAbortSignal()` was re-run or destroyed"},me=!!globalThis.document?.contentType&&globalThis.document.contentType.includes(`xml`),he={},w=Symbol(`uninitialized`),ge=`http://www.w3.org/1999/xhtml`;function _e(){console.warn(`https://svelte.dev/e/derived_inert`)}function ve(e){console.warn(`https://svelte.dev/e/hydration_mismatch`)}function ye(){console.warn(`https://svelte.dev/e/select_multiple_invalid_value`)}function be(){console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`)}var T=!1;function xe(e){T=e}var E;function D(e){if(e===null)throw ve(),he;return E=e}function Se(){return D(en(E))}function O(e){if(T){if(en(E)!==null)throw ve(),he;E=e}}function Ce(e=1){if(T){for(var t=e,n=E;t--;)n=en(n);E=n}}function we(e=!0){for(var t=0,n=E;;){if(n.nodeType===8){var r=n.data;if(r===`]`){if(t===0)return n;--t}else(r===`[`||r===`[!`||r[0]===`[`&&!isNaN(Number(r.slice(1))))&&(t+=1)}var i=en(n);e&&n.remove(),n=i}}function Te(e){if(!e||e.nodeType!==8)throw ve(),he;return e.data}function Ee(e){return e===this.v}function De(e,t){return e==e?e!==t||typeof e==`object`&&!!e||typeof e==`function`:t==t}function Oe(e){return!De(e,this.v)}function ke(e){throw Error(`https://svelte.dev/e/lifecycle_outside_component`)}function Ae(){throw Error(`https://svelte.dev/e/async_derived_orphan`)}function je(e,t,n){throw Error(`https://svelte.dev/e/each_key_duplicate`)}function Me(e){throw Error(`https://svelte.dev/e/effect_in_teardown`)}function Ne(){throw Error(`https://svelte.dev/e/effect_in_unowned_derived`)}function Pe(e){throw Error(`https://svelte.dev/e/effect_orphan`)}function Fe(){throw Error(`https://svelte.dev/e/effect_update_depth_exceeded`)}function Ie(){throw Error(`https://svelte.dev/e/state_descriptors_fixed`)}function Le(){throw Error(`https://svelte.dev/e/state_prototype_fixed`)}function Re(){throw Error(`https://svelte.dev/e/state_unsafe_mutation`)}function ze(){throw Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`)}var k=null;function Be(e){k=e}function Ve(e,t=!1,n){k={p:k,i:!1,c:null,e:null,s:e,x:null,r:W,l:null}}function He(e){var t=k,n=t.e;if(n!==null){t.e=null;for(var r of n)mn(r)}return e!==void 0&&(t.x=e),t.i=!0,k=t.p,Ue(e)}function Ue(e={}){return i(e,oe,{value:!0}),e}function We(){return!0}var Ge=[];function Ke(){var e=Ge;Ge=[],f(e)}function qe(e){if(Ge.length===0&&!bt){var t=Ge;queueMicrotask(()=>{t===Ge&&Ke()})}Ge.push(e)}function Je(){for(;Ge.length>0;)Ke()}var Ye=~(g|_|h);function A(e,t){e.f=e.f&Ye|t}function Xe(e){e.f&512||e.deps===null?A(e,h):A(e,_)}function Ze(e,t,n){e.f&2048?t.add(e):e.f&4096&&n.add(e),A(e,h)}var Qe=!1;function $e(){Qe||(Qe=!0,document.addEventListener(`reset`,e=>{Promise.resolve().then(()=>{if(!e.defaultPrevented)for(let t of e.target.elements)t[fe]?.()})},{capture:!0}))}function et(e){var t=H,n=W;U(null),Fn(null);try{return e()}finally{U(t),Fn(n)}}function tt(e,t,n,r=n){e.addEventListener(t,()=>et(n));let i=e[fe];e[fe]=i?()=>{i(),r(!0)}:()=>r(!0),$e()}function nt(e,t,n,r){let i=We()?ot:ut;var a=e.filter(e=>!e.settled),o=t.map(i);if(n.length===0&&a.length===0){r(o);return}var s=W,c=rt(),l=a.length===1?a[0].promise:a.length>1?Promise.all(a.map(e=>e.promise)):null;function u(e){if(!(s.f&16384)){c();try{r([...o,...e])}catch(e){sn(e,s)}it()}}var d=at();if(n.length===0){l.then(()=>u([])).finally(d);return}function f(){Promise.all(n.map(e=>ct(e))).then(u).catch(e=>sn(e,s)).finally(d)}l?l.then(()=>{c(),f(),it()}):f()}function rt(){var e=W,t=H,n=k,r=j;return function(i=!0){Fn(e),U(t),Be(n),i&&!(e.f&16384)&&(r?.activate(),r?.apply())}}function it(e=!0){Fn(null),U(null),Be(null),e&&j?.deactivate()}function at(){var e=W,t=e.b,n=j,r=!!t?.is_rendered();return t?.update_pending_count(1,n),n.increment(r,e),()=>{t?.update_pending_count(-1,n),n.decrement(r,e)}}function ot(e){var t=2|g;return W!==null&&(W.f|=C),{ctx:k,deps:null,effects:null,equals:Ee,f:t,fn:e,reactions:null,rv:0,v:w,wv:0,parent:W,ac:null}}var st=Symbol(`obsolete`);function ct(e,t,n){let r=W;r===null&&Ae();var i=void 0,a=Lt(w),o=!H,s=new Set;return _n(()=>{var t=W,n=p();i=n.promise;try{Promise.resolve(e()).then(n.resolve,e=>{e!==pe&&n.reject(e)}).finally(it)}catch(e){n.reject(e),it()}var c=j;if(o){if(t.f&32768)var l=at();if(r.b?.is_rendered())c.async_deriveds.get(t)?.reject(st);else for(let e of s.values())e.reject(st);s.add(n),c.async_deriveds.set(t,n)}let u=(e,t=void 0)=>{l?.(),s.delete(n),t!==st&&(c.activate(),t?(a.f|=ie,Vt(a,t)):(a.f&8388608&&(a.f^=ie),Vt(a,e)),c.deactivate())};n.promise.then(u,e=>u(null,e||`unknown`))}),fn(()=>{for(let e of s)e.reject(st)}),new Promise(e=>{function t(n){function r(){n===i?e(a):t(i)}n.then(r,r)}t(i)})}function lt(e){let t=ot(e);return Ln(t),t}function ut(e){let t=ot(e);return t.equals=Oe,t}function dt(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n{t.ac.abort(pe),t.ac=null}),t.fn!==null&&(t.teardown=d),Yn(t,0),xn(t))}function ht(e){if(e.effects!==null)for(let t of e.effects)t.teardown&&t.fn!==null&&Xn(t)}var gt=null,j=null,_t=null,vt=null,yt=null,bt=!1,xt=!1,St=null,Ct=null,wt=0,Tt=1,Et=class e{id=Tt++;#e=!1;linked=!0;#t=null;#n=null;async_deriveds=new Map;current=new Map;previous=new Map;#r=new Set;#i=new Set;#a=0;#o=new Map;#s=null;#c=[];#l=[];#u=new Set;#d=new Set;#f=new Map;#p=new Set;is_fork=!1;#m=!1;constructor(){gt===null?gt=this:(gt.#n=this,this.#t=gt),gt=this}#h(){if(this.is_fork)return!0;for(let n of this.#o.keys()){for(var e=n,t=!1;e.parent!==null;){if(this.#f.has(e)){t=!0;break}e=e.parent}if(!t)return!0}return!1}skip_effect(e){this.#f.has(e)||this.#f.set(e,{d:[],m:[]}),this.#p.delete(e)}unskip_effect(e,t=e=>this.schedule(e)){var n=this.#f.get(e);if(n){this.#f.delete(e);for(var r of n.d)A(r,g),t(r);for(r of n.m)A(r,_),t(r)}this.#p.add(e)}#g(){var e=[];for(let i of this.#c)if(!(i.f&16384||!(i.f&6144))){for(var t=i,n=!1;t.parent!==null;){t=t.parent;var r=t.f;if(r&96){if(!(r&1024)){n=!0;break}t.f^=h}}n||e.push(t)}return this.#c=[],e}#_(){this.#e=!0;for(let e of this.#u)this.#d.delete(e),A(e,g),this.schedule(e);for(let e of this.#d)A(e,_),this.schedule(e);this.apply();for(var t=St=[],n=[],r=Ct=[];this.#c.length>0;){wt++>1e3&&(this.#S(),Ot());for(let e of this.#g())try{this.#v(e,t,n)}catch(t){throw Nt(e),this.#h()||this.discard(),t}}if(j=null,r.length>0){var i=e.ensure();for(let e of r)i.schedule(e)}if(St=null,Ct=null,this.#h()){this.#x(n),this.#x(t);for(let[e,t]of this.#f)Mt(e,t);r.length>0&&j.#_();return}let a=this.#y();if(a)this.#x(n),this.#x(t),a.#b(this);else{this.#u.clear(),this.#d.clear();for(let e of this.#r)e(this);this.#r.clear(),_t=this,At(n),At(t),_t=null,this.#s?.resolve();var o=j;if(this.#a===0&&(this.#c.length===0||o!==null)&&this.#S(),this.#c.length>0){if(o!==null){for(let e of this.#c)o.#c.push(e);this.#c=[]}else o=this}o!==null&&(Ft.clear(),o.#_())}}#v(e,t,n){e.f^=h;for(var r=e.first;r!==null;){var i=r.f,a=!!(i&96);if(!(a&&i&1024||i&8192||this.#f.has(r))&&r.fn!==null){a?r.f^=h:i&4?t.push(r):Wn(r)&&(i&16&&this.#d.add(r),Xn(r));var o=r.first;if(o!==null){r=o;continue}}for(;r!==null;){var s=r.next;if(s!==null){r=s;break}r=r.parent}}}#y(){for(var e=this.#t;e!==null;){if(!e.is_fork){for(let[t,[,n]]of this.current)if(e.current.has(t)&&!n)return e}e=e.#t}return null}#b(e){for(let[t,n]of e.current)!this.previous.has(t)&&e.previous.has(t)&&this.previous.set(t,e.previous.get(t)),this.current.set(t,n);for(let[t,n]of e.async_deriveds){let e=this.async_deriveds.get(t);e&&n.promise.then(e.resolve).catch(e.reject)}e.async_deriveds.clear(),this.transfer_effects(e.#u,e.#d);let t=e=>{var n=e.reactions;if(n!==null&&!(e.f&2&&!(e.f&6144)))for(let e of n){var r=e.f;if(r&2)t(e);else{var i=e;r&4194320&&!this.async_deriveds.has(i)&&(this.#d.delete(i),A(i,g),this.schedule(i))}}};for(let e of this.current.keys())t(e);this.oncommit(()=>e.discard()),e.#S(),j=this,this.#_()}#x(e){for(var t=0;t{this.#m=!1,this.linked&&this.flush()}))}transfer_effects(e,t){for(let t of e)this.#u.add(t);for(let e of t)this.#d.add(e);e.clear(),t.clear()}oncommit(e){this.#r.add(e)}ondiscard(e){this.#i.add(e)}settled(){return(this.#s??=p()).promise}static ensure(){if(j===null){let t=j=new e;!xt&&!bt&&qe(()=>{t.#e||t.flush()})}return j}apply(){vt=null}schedule(e){yt=e,e.b?.is_pending&&e.f&16777228&&!(e.f&32768)?e.b.defer_effect(e):this.#c.push(e)}#S(){if(this.linked){var e=this.#t,t=this.#n;e===null||(e.#n=t),t===null?gt=e:t.#t=e,this.linked=!1}}};function Dt(e){var t=bt;bt=!0;try{var n;for(e&&(j!==null&&!j.is_fork&&j.flush(),n=e());;){if(Je(),j===null)return n;j.flush()}}finally{bt=t}}function Ot(){try{Fe()}catch(e){sn(e,yt)}}var kt=null;function At(e){var t=e.length;if(t!==0){for(var n=0;n0)){Ft.clear();for(let e of kt){if(e.f&24576)continue;let t=[e],n=e.parent;for(;n!==null;)kt.has(n)&&(kt.delete(n),t.push(n)),n=n.parent;for(let e=t.length-1;e>=0;e--){let n=t[e];n.f&24576||Xn(n)}}kt.clear()}}kt=null}}function jt(e){j.schedule(e)}function Mt(e,t){if(!(e.f&32&&e.f&1024)){e.f&2048?t.d.push(e):e.f&4096&&t.m.push(e),A(e,h);for(var n=e.first;n!==null;)Mt(n,t),n=n.next}}function Nt(e){A(e,h);for(var t=e.first;t!==null;)Nt(t),t=t.next}var Pt=new Set,Ft=new Map,It=!1;function Lt(e,t){return{f:0,v:e,reactions:null,equals:Ee,rv:0,wv:0}}function M(e,t){let n=Lt(e,t);return Ln(n),n}function Rt(e,t=!1,n=!0){let r=Lt(e);return t||(r.equals=Oe),r}function N(e,t,n=!1){return H!==null&&(!Pn||H.f&131072)&&We()&&H.f&4325394&&(In===null||!In.has(e))&&Re(),Vt(e,n?Gt(t):t,Ct)}var zt=null,Bt=0;function Vt(e,t,n=null){if(!e.equals(t)){Mn?Ft.set(e,t):Ft.has(e)||Ft.set(e,e.v);var r=Et.ensure();if(r.capture(e,t),e.f&2){let t=e;e.f&2048&&ft(t),vt===null&&Xe(t)}e.wv=Un(),zt=null,Bt=0,Wt(e,g,n),zt=null,We()&&W!==null&&W.f&1024&&!(W.f&96)&&(q===null?Rn([e]):q.push(e)),!r.is_fork&&Pt.size>0&&!It&&Ht()}return t}function Ht(){It=!1;for(let e of Pt){e.f&1024&&A(e,_);let t;try{t=Wn(e)}catch{t=!0}t&&Xn(e)}Pt.clear()}function Ut(e){N(e,e.v+1)}function Wt(e,t,n){var r=e.reactions;if(r!==null){var i=We(),a=r.length;if(Bt+=a,Bt>1e5&&zt===null&&(zt=new Set),zt!==null){if(zt.has(e))return;zt.add(e)}for(var o=0;o{if(Vn===d)return e();var t=H,n=Vn;U(null),Hn(d);var r=e();return U(t),Hn(n),r};return i&&r.set(`length`,M(t.length,u)),new Proxy(t,{defineProperty(e,t,n){`value`in n&&n.configurable!==!1&&n.enumerable!==!1&&n.writable!==!1||Ie();var i=r.get(t);return i===void 0?f(()=>{var e=M(n.value,u);return r.set(t,e),e}):N(i,n.value,!0),!0},deleteProperty(e,t){var n=r.get(t);if(n===void 0){if(t in e){let e=f(()=>M(w,u));r.set(t,e),Ut(o)}}else N(n,w),Ut(o);return!0},get(e,n,i){if(n===ae)return t;var o=r.get(n),s=n in e;if(o===void 0&&(!s||a(e,n)?.writable)&&(o=f(()=>M(Gt(s?e[n]:w),u)),r.set(n,o)),o!==void 0){var c=J(o);return c===w?void 0:c}return Reflect.get(e,n,i)},getOwnPropertyDescriptor(e,t){this.has?.(e,t);var n=Reflect.getOwnPropertyDescriptor(e,t),i=r.get(t);if(i!==void 0){var a=J(i);if(a===w)return;if(n&&`value`in n)n.value=a;else return{enumerable:!0,configurable:!0,value:a,writable:!0}}return n},has(e,t){if(t===ae)return!0;var n=r.get(t),i=n!==void 0&&n.v!==w||Reflect.has(e,t);return(n!==void 0||W!==null&&(!i||a(e,t)?.writable))&&(n===void 0&&(n=f(()=>M(i?Gt(e[t]):w,u)),r.set(t,n)),J(n)===w)?!1:i},set(e,t,n,s){var c=r.get(t),l=t in e;if(i&&t===`length`)for(var d=n;dM(w,u)),r.set(d+``,p)):N(p,w)}if(c===void 0)(!l||a(e,t)?.writable)&&(c=f(()=>M(void 0,u)),N(c,Gt(n)),r.set(t,c));else{l=c.v!==w;var m=f(()=>Gt(n));N(c,m)}var h=Reflect.getOwnPropertyDescriptor(e,t);if(h?.set&&h.set.call(s,n),!l){if(i&&typeof t==`string`){var g=r.get(`length`),_=Number(t);Number.isInteger(_)&&_>=g.v&&N(g,_+1)}Ut(o)}return!0},ownKeys(e){J(o);var t=Reflect.ownKeys(e).filter(e=>{var t=r.get(e);return t===void 0||t.v!==w});for(var[n,i]of r)i.v!==w&&!(n in e)&&t.push(n);return t},setPrototypeOf(){Le()}})}function Kt(e){try{if(typeof e==`object`&&e&&ae in e)return e[ae]}catch{}return e}function qt(e,t){return Object.is(Kt(e),Kt(t))}var Jt,Yt,Xt,Zt;function Qt(){if(Jt===void 0){Jt=window,Yt=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;Xt=a(t,`firstChild`).get,Zt=a(t,`nextSibling`).get,u(e)&&(e[le]=void 0,e[ce]=null,e[ue]=void 0,e.__e=void 0),u(n)&&(n[de]=void 0)}}function P(e=``){return document.createTextNode(e)}function $t(e){return Xt.call(e)}function en(e){return Zt.call(e)}function F(e,t){if(!T)return $t(e);var n=$t(E);if(n===null)n=E.appendChild(P());else if(t&&n.nodeType!==3){var r=P();return n?.before(r),D(r),r}return t&&an(n),D(n),n}function I(e,t=!1){if(!T){var n=$t(e);return n instanceof Comment&&n.data===``?en(n):n}if(t){if(E?.nodeType!==3){var r=P();return E?.before(r),D(r),r}an(E)}return E}function L(e,t=!1){if(!T)return $t(e);var n=F(e,t);return O(e),n}function R(e,t=1,n=!1){let r=T?E:e;for(var i;t--;)i=r,r=en(r);if(!T)return r;if(n){if(r?.nodeType!==3){var a=P();return r===null?i?.after(a):r.before(a),D(a),a}an(r)}return D(r),r}function tn(e){e.textContent=``}function nn(){return!1}function rn(e,t,n){return t==null||t===`http://www.w3.org/1999/xhtml`?n?document.createElement(e,{is:n}):document.createElement(e):n?document.createElementNS(t,e,{is:n}):document.createElementNS(t,e)}function an(e){if(e.nodeValue.length<65536)return;let t=e.nextSibling;for(;t!==null&&t.nodeType===3;)t.remove(),e.nodeValue+=t.nodeValue,t=e.nextSibling}function on(e){var t=W;if(t===null)return H.f|=ie,e;if(!(t.f&32768)&&!(t.f&4))throw e;sn(e,t)}function sn(e,t){if(!(t!==null&&t.f&16384)){for(;t!==null;){if(t.f&128&&!(t.f&33570816)){if(!(t.f&32768))throw e;try{t.b.error(e);return}catch(t){e=t}}t=t.parent}throw e}}function cn(e){W===null&&(H===null&&Pe(e),Ne()),Mn&&Me(e)}function ln(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function un(e,t){var n=W;n!==null&&n.f&8192&&(e|=v);var r={ctx:k,deps:null,nodes:null,f:e|g|512,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};j?.register_created_effect(r);var i=r;if(e&4)St===null?Et.ensure().schedule(r):St.push(r);else if(t!==null){try{Xn(r)}catch(e){throw V(r),e}i.deps===null&&i.teardown===null&&i.nodes===null&&i.first===i.last&&!(i.f&524288)&&(i=i.first,e&16&&e&65536&&i!==null&&(i.f|=S))}if(i!==null&&(i.parent=n,n!==null&&ln(i,n),H!==null&&H.f&2&&!(e&64))){var a=H;(a.effects??=[]).push(i)}return r}function dn(){return H!==null&&!Pn}function fn(e){let t=un(8,null);return A(t,h),t.teardown=e,t}function pn(e){cn(`$effect`);var t=W.f;if(!H&&t&32&&k!==null&&!k.i){var n=k;(n.e??=[]).push(e)}else return mn(e)}function mn(e){return un(4|ee,e)}function hn(e){Et.ensure();let t=un(64|C,e);return(e={})=>new Promise(n=>{e.outro?Tn(t,()=>{V(t),n(void 0)}):(V(t),n(void 0))})}function gn(e){return un(4,e)}function _n(e){return un(re|C,e)}function vn(e,t=0){return un(8|t,e)}function z(e,t=[],n=[],r=[]){nt(r,t,n,t=>{un(8,()=>{e(...t.map(J))})})}function yn(e,t=0){return un(16|t,e)}function B(e){return un(32|C,e)}function bn(e){var t=e.teardown;if(t!==null){let n=Mn,r=H;Nn(!0),U(null);try{t.call(null)}catch(t){sn(t,e.parent)}finally{Nn(n),U(r)}}}function xn(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){let e=n.ac;e!==null&&et(()=>{e.abort(pe)});var r=n.next;n.f&64?n.parent=null:V(n,t),n=r}}function Sn(e){for(var t=e.first;t!==null;){var n=t.next;t.f&32||V(t),t=n}}function V(e,t=!0){var n=!1;(t||e.f&262144)&&e.nodes!==null&&e.nodes.end!==null&&(Cn(e.nodes.start,e.nodes.end),n=!0),e.f|=x,xn(e,t&&!n),Yn(e,0);var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)e.stop();bn(e),e.f^=x,e.f|=y;var i=e.parent;i!==null&&i.first!==null&&wn(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function Cn(e,t){for(;e!==null;){var n=e===t?null:en(e);e.remove(),e=n}}function wn(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function Tn(e,t,n=!0){var r=[];e.f|=256,En(e,r,!0);var i=()=>{n&&V(e),t&&t()},a=r.length;if(a>0){var o=()=>--a||i();for(var s of r)s.out(o)}else i()}function En(e,t,n){if(!(e.f&8192)){e.f^=v;var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)(e.is_global||n)&&t.push(e);for(var i=e.first;i!==null;){var a=i.next;if(!(i.f&64)){var o=!!(i.f&65536)||!!(i.f&32)&&!!(e.f&16);En(i,t,o?n:!1)}i=a}}}function Dn(e){e.f&=-257,On(e,!0)}function On(e,t){if(!(e.f&256)&&e.f&8192){e.f^=v,e.f&1024||(A(e,g),Et.ensure().schedule(e));for(var n=e.first;n!==null;){var r=n.next,i=!!(n.f&65536)||!!(n.f&32);On(n,i?t:!1),n=r}var a=e.nodes&&e.nodes.t;if(a!==null)for(let e of a)(e.is_global||t)&&e.in()}}function kn(e,t){if(e.nodes)for(var n=e.nodes.start,r=e.nodes.end;n!==null;){var i=n===r?null:en(n);t.append(n),n=i}}var An=null,jn=!1,Mn=!1;function Nn(e){Mn=e}var H=null,Pn=!1;function U(e){H=e}var W=null;function Fn(e){W=e}var In=null;function Ln(e){H!==null&&(H.f&2097152||H.f&2)&&(In??=new Set).add(e)}var G=null,K=0,q=null;function Rn(e){q=e}var zn=1,Bn=0,Vn=Bn;function Hn(e){Vn=e}function Un(){return++zn}function Wn(e){var t=e.f;if(t&2048)return!0;if(t&4096){for(var n=e.deps,r=n.length,i=0;ie.wv)return!0}t&512&&vt===null&&A(e,h)}return!1}function Gn(e,t,n=!0){var r=e.reactions;if(r!==null&&!(In!==null&&In.has(e)))for(var i=0;i{e.ac.abort(pe)}),e.ac=null);try{e.f|=ne;var u=e.fn,d=u();e.f|=b;var f=qn(e);if(We()&&q!==null&&!Pn&&f!==null&&!(e.f&6146))for(var p=0;p0)for(t.length=K+G.length,r=0;r{s.ac.abort(pe),s.ac=null,A(s,g)}),mt(s),Yn(s,0)}}function Yn(e,t){var n=e.deps;if(n!==null)for(var r=t;rn?.call(this,e))}return e.startsWith(`pointer`)||e.startsWith(`touch`)||e===`wheel`?(i.__removed=!1,qe(()=>{i.__removed||t.addEventListener(e,i,r)})):t.addEventListener(e,i,r),i}function sr(e,t,n,r,i){var a={capture:r,passive:i},o=or(e,t,n,a);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&fn(()=>{o.__removed=!0,t.removeEventListener(e,o,a)})}function cr(e,t,n){(t[rr]??={})[e]=n}function lr(e){for(var t=0;t{dr=!1,ur=null}));var s=0,c=ur===e&&e[rr];if(c){var l=a.indexOf(c);if(l!==-1&&(t===document||t===window)){e[rr]=t;return}var u=a.indexOf(t);if(u===-1)return;l<=u&&(s=l)}if(o=a[s]||e.target,o!==t){i(e,`currentTarget`,{configurable:!0,get(){return o||n}});var d=H,f=W;U(null),Fn(null);try{for(var p,m=[];o!==null&&o!==t;){try{var h=o[rr]?.[r];h==null||o.disabled&&e.target!==o||h.call(o,e)}catch(e){p?m.push(e):p=e}if(e.cancelBubble)break;s++,o=s{throw e});throw p}}finally{e[rr]=t,delete e.currentTarget,U(d),Fn(f)}}}var pr=globalThis?.window?.trustedTypes&&globalThis.window.trustedTypes.createPolicy(`svelte-trusted-html`,{createHTML:e=>e});function mr(e){return pr?.createHTML(e)??e}function hr(e){var t=rn(`template`);return t.innerHTML=mr(e.replaceAll(``,``)),t.content}function gr(e,t){var n=W;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function Y(e,t){var n=!!(t&1),r=!!(t&2),i,a=!e.startsWith(``);return()=>{if(T)return gr(E,null),E;i===void 0&&(i=hr(a?e:``+e),n||(i=$t(i)));var t=r||Yt?document.importNode(i,!0):i.cloneNode(!0);if(n){var o=$t(t),s=t.lastChild;gr(o,s)}else gr(t,t);return t}}function _r(e=``){if(!T){var t=P(e+``);return gr(t,t),t}var n=E;return n.nodeType===3?an(n):(n.before(n=P()),D(n)),gr(n,n),n}function vr(){if(T)return gr(E,null),E;var e=document.createDocumentFragment(),t=document.createComment(``),n=P();return e.append(t,n),gr(t,n),e}function X(e,t){if(T){var n=W;n.f&32768&&n.nodes.end!==null||(n.nodes.end=E),Se()}else e!==null&&e.before(t)}function yr(e){let t=0,n=Lt(0),r;return()=>{dn()&&(J(n),vn(()=>(t===0&&(r=er(()=>e(()=>Ut(n)))),t+=1,()=>{qe(()=>{--t,t===0&&(r?.(),r=void 0,Ut(n))})})))}}var br=S|C;function xr(e,t,n,r){new Sr(e,t,n,r)}var Sr=class{parent;is_pending=!1;transform_error;#e;#t=T?E:null;#n;#r;#i;#a=null;#o=null;#s=null;#c=null;#l=0;#u=0;#d=!1;#f=new Set;#p=new Set;#m=null;#h=yr(()=>(this.#m=Lt(this.#l),()=>{this.#m=null}));constructor(e,t,n,r){this.#e=e,this.#n=t,this.#r=e=>{var t=W;t.b=this,t.f|=128,n(e)},this.parent=W.b,this.transform_error=r??this.parent?.transform_error??(e=>e),this.#i=yn(()=>{if(T){let e=this.#t;Se();let t=e.data===`[!`;if(e.data.startsWith(`[?`)){let t=JSON.parse(e.data.slice(2));this.#_(t)}else t?this.#y():this.#g()}else this.#b()},br),T&&(this.#e=E)}#g(){try{this.#a=B(()=>this.#r(this.#e))}catch(e){this.error(e)}}#_(e){let t=this.#n.failed,{reset:n,invoke_onerror:r}=this.#v(e);qe(r),t&&(this.#s=B(()=>{t(this.#e,()=>e,()=>n)}))}#v(e){var t=!1,n=!1;let r=()=>{t?be():(t=!0,n&&ze(),this.#s!==null&&Tn(this.#s,()=>{this.#s=null}),this.#S(()=>{this.#b()}))};return{reset:r,invoke_onerror:()=>{try{n=!0,this.#n.onerror?.(e,r),n=!1}catch(e){sn(e,this.#i&&this.#i.parent)}}}}#y(){let e=this.#n.pending;e&&(this.is_pending=!0,this.#o=B(()=>e(this.#e)),qe(()=>{var e=this.#c=document.createDocumentFragment(),t=P(),n=!1;e.append(t),this.#a=this.#S(()=>{try{return B(()=>this.#r(t))}catch(e){try{this.error(e),n=!0}catch(e){sn(e,this.#i.parent)}return null}}),this.#a===null?(this.#c=null,n&&this.#x(j)):this.#u===0&&(this.#e.before(e),this.#c=null,Tn(this.#o,()=>{this.#o=null}),this.#x(j))}))}#b(){try{if(this.is_pending=this.has_pending_snippet(),this.#u=0,this.#l=0,this.#a=B(()=>{this.#r(this.#e)}),this.#u>0){var e=this.#c=document.createDocumentFragment();kn(this.#a,e);let t=this.#n.pending;this.#o=B(()=>t(this.#e))}else this.#x(j)}catch(e){this.error(e)}}#x(e){this.is_pending=!1,e.transfer_effects(this.#f,this.#p)}defer_effect(e){Ze(e,this.#f,this.#p)}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!this.#n.pending}#S(e){var t=W,n=H,r=k;Fn(this.#i),U(this.#i),Be(this.#i.ctx);try{return Et.ensure(),e()}finally{Fn(t),U(n),Be(r)}}#C(e,t){this.has_pending_snippet()?(this.#u+=e,this.#u===0&&(this.#x(t),this.#o&&Tn(this.#o,()=>{this.#o=null}),this.#c&&=(this.#e.before(this.#c),null))):this.parent&&this.parent.#C(e,t)}update_pending_count(e,t){this.#C(e,t),this.#l+=e,!(!this.#m||this.#d)&&(this.#d=!0,qe(()=>{this.#d=!1,this.#m&&Vt(this.#m,this.#l)}))}get_effect_pending(){return this.#h(),J(this.#m)}error(e){if(!this.#n.onerror&&!this.#n.failed)throw e;j?.is_fork?(this.#a&&j.skip_effect(this.#a),this.#o&&j.skip_effect(this.#o),this.#s&&j.skip_effect(this.#s),j.oncommit(()=>{this.#w(e)})):this.#w(e)}#w(e){this.#a&&=(V(this.#a),null),this.#o&&=(V(this.#o),null),this.#s&&=(V(this.#s),null),T&&(D(this.#t),Ce(),D(we()));let t=this.#n.failed,n=e=>{let{reset:n,invoke_onerror:r}=this.#v(e);r(),t&&(this.#s=this.#S(()=>{try{return B(()=>{var r=W;r.b=this,r.f|=128,t(this.#e,()=>e,()=>n)})}catch(e){return sn(e,this.#i.parent),null}}))};qe(()=>{var t;try{t=this.transform_error(e)}catch(e){sn(e,this.#i&&this.#i.parent);return}typeof t==`object`&&t&&typeof t.then==`function`?t.then(n,e=>sn(e,this.#i&&this.#i.parent)):n(t)})}};function Z(e,t){var n=t==null?``:typeof t==`object`?`${t}`:t;n!==(e[de]??=e.nodeValue)&&(e[de]=n,e.nodeValue=`${n}`)}function Cr(e,t){return Tr(e,t)}var wr=new Map;function Tr(e,{target:t,anchor:n,props:i={},events:a,context:o,intro:s=!0,transformError:c}){Qt();var l=void 0,u=hn(()=>{var s=n??t.appendChild(P());xr(s,{pending:()=>{}},t=>{Ve({});var n=k;if(o&&(n.c=o),a&&(i.$$events=a),T&&gr(t,null),l=e(t,i)||Ue(),T&&(W.nodes.end=E,E===null||E.nodeType!==8||E.data!==`]`))throw ve(),he;He()},c);var u=new Set,d=e=>{for(var n=0;n{for(var e of u)for(let n of[t,document]){var r=wr.get(n),i=r.get(e);--i==0?(n.removeEventListener(e,fr),r.delete(e),r.size===0&&wr.delete(n)):r.set(e,i)}ar.delete(d),s!==n&&s.parentNode?.removeChild(s)}});return Er.set(l,u),l}var Er=new WeakMap,Dr=class{anchor;#e=new Map;#t=new Map;#n=new Map;#r=new Set;#i=!0;constructor(e,t=!0){this.anchor=e,this.#i=t}#a=e=>{if(this.#e.has(e)){var t=this.#e.get(e),n=this.#t.get(t);if(n)Dn(n),this.#r.delete(t);else{var r=this.#n.get(t);r&&(Dn(r.effect),this.#t.set(t,r.effect),this.#n.delete(t),r.fragment.lastChild.remove(),this.anchor.before(r.fragment),n=r.effect)}for(let[t,n]of this.#e){if(this.#e.delete(t),t===e)break;let r=this.#n.get(n);r&&(V(r.effect),this.#n.delete(n))}for(let[e,r]of this.#t){if(e===t||this.#r.has(e))continue;let i=()=>{if(Array.from(this.#e.values()).includes(e)){var t=document.createDocumentFragment();kn(r,t),t.append(P()),this.#n.set(e,{effect:r,fragment:t})}else V(r);this.#r.delete(e),this.#t.delete(e)};this.#i||!n?(this.#r.add(e),Tn(r,i,!1)):i()}}};#o=e=>{this.#e.delete(e);let t=Array.from(this.#e.values());for(let[e,n]of this.#n)t.includes(e)||(V(n.effect),this.#n.delete(e))};ensure(e,t){var n=j,r=nn();if(t&&!this.#t.has(e)&&!this.#n.has(e)){if(r){var i=document.createDocumentFragment(),a=P();i.append(a),this.#n.set(e,{effect:B(()=>t(a)),fragment:i})}else this.#t.set(e,B(()=>t(this.anchor)))}if(this.#e.set(n,e),r){for(let[t,r]of this.#t)t===e?n.unskip_effect(r):n.skip_effect(r);for(let[t,r]of this.#n)t===e?n.unskip_effect(r.effect):n.skip_effect(r.effect);n.oncommit(this.#a),n.ondiscard(this.#o)}else T&&(this.anchor=E),this.#a(n)}};function Q(e,t,n=!1){var r;T&&(r=E,Se());var i=new Dr(e),a=n?S:0;function o(e,t){if(T){var n=Te(r);if(e!==parseInt(n.substring(1))){var a=we();D(a),i.anchor=a,xe(!1),i.ensure(e,t),xe(!0);return}}i.ensure(e,t)}yn(()=>{var e=!1;t((t,n=0)=>{e=!0,o(n,t)}),e||o(-1,null)},a)}var Or=Symbol(`NaN`);function kr(e,t,n){T&&Se();var r=new Dr(e),i=!We();yn(()=>{var e=t();e!==e&&(e=Or),i&&typeof e==`object`&&e&&(e={}),r.ensure(e,n)})}function Ar(e,t){return t}function jr(e,t,n){for(var i=[],a=t.length,o,s=t.length,c=0;c{if(o){if(o.pending.delete(n),o.done.add(n),o.pending.size===0){var t=e.outrogroups;Mr(e,r(o.done)),t.delete(o),t.size===0&&(e.outrogroups=null)}}else--s},!1)}if(s===0){var l=i.length===0&&n!==null&&e.pending.size===0;if(l){var u=n,d=u.parentNode;tn(d),d.append(u),e.items.clear()}Mr(e,t,!l)}else o={pending:new Set(t),done:new Set},(e.outrogroups??=new Set).add(o)}function Mr(e,t,n=!0){var r;if(e.pending.size>0){r=new Set;for(let t of e.pending.values())for(let n of t)r.add(e.items.get(n).e)}for(var i=0;i{var t=i();return e(t)?t:t==null?[]:r(t)}),p,m=new Map,h=!0;function g(e){v.effect.f&16384||(v.pending.delete(e),v.fallback=d,Fr(v,p,c,n,a),d!==null&&(p.length===0?d.f&33554432?(d.f^=te,Lr(d,null,c)):Dn(d):Tn(d,()=>{d=null})))}function _(e){v.pending.delete(e)}var v={effect:yn(()=>{p=J(f);var e=p.length;let t=!1;T&&Te(c)===`[!`!=(e===0)&&(c=we(),D(c),xe(!1),t=!0);for(var r=new Set,u=j,v=nn(),y=0;ys(c)):(d=B(()=>s(Nr??=P())),d.f|=te)),e>r.size&&je(``,``,``),T&&e>0&&D(we()),!h){if(m.set(u,r),v){for(let[e,t]of l)r.has(e)||u.skip_effect(t.e);u.oncommit(g),u.ondiscard(_)}else g(u)}t&&xe(!0),J(f)}),flags:n,items:l,pending:m,outrogroups:null,fallback:d};h=!1,T&&(c=E)}function Pr(e){for(;e!==null&&!(e.f&32);)e=e.next;return e}function Fr(e,t,n,i,a){var o=!!(i&8),s=t.length,c=e.items,l=Pr(e.effect.first),u,d=null,f,p=[],m=[],h,g,_,v;if(o)for(v=0;v0){var re=i&4&&s===0?n:null;if(o){for(v=0;v{if(f!==void 0)for(_ of f)_.nodes?.a?.apply()})}function Ir(e,t,n,r,i,a,o,s){var c=o&1?o&16?Lt(n):Rt(n,!1,!1):null,l=o&2?Lt(i):null;return{v:c,i:l,e:B(()=>(a(t,c??n,l??i,s),()=>{e.delete(r)}))}}function Lr(e,t,n){if(e.nodes)for(var r=e.nodes.start,i=e.nodes.end,a=t&&!(t.f&33554432)?t.nodes.start:n;r!==null;){var o=en(r);if(a.before(r),r===i)return;r=o}}function Rr(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}var zr=[...` -\r\f\xA0\v`];function Br(e,t,n){var r=e==null?``:``+e;if(t&&(r=r?r+` `+t:t),n){for(var i of Object.keys(n))if(n[i])r=r?r+` `+i:i;else if(r.length)for(var a=i.length,o=0;(o=r.indexOf(i,o))>=0;){var s=o+a;(o===0||zr.includes(r[o-1]))&&(s===r.length||zr.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function Vr(e,t,n,r,i,a){var o=e[le];if(T||o!==n||o===void 0){var s=Br(n,r,a);T&&s===e.getAttribute(`class`)||(s==null?e.removeAttribute(`class`):t?e.className=s:e.setAttribute(`class`,s)),e[le]=n}else if(a&&i!==a)for(var c in a){var l=!!a[c];(i==null||l!==!!i[c])&&e.classList.toggle(c,l)}return a}function Hr(e,t){t?e.hasAttribute(`selected`)||e.setAttribute(`selected`,``):e.removeAttribute(`selected`)}function Ur(t,n){var r=t.__defaultValue,i=t.multiple,a=i?r??[]:null;if(!i||e(a)){var o=t.selectedIndex,s=n&&i?new Set(t.selectedOptions):null;for(var c of t.options){var l=qr(c);Hr(c,i?a.includes(l):qt(l,r))}if(n){if(s!==null)for(c of t.options){var u=s.has(c);c.selected!==u&&(c.selected=u)}else t.selectedIndex!==o&&(t.selectedIndex=o)}}}function Wr(t,n,r=!1){if(t.multiple){if(n==null)return;if(!e(n))return ye();for(var i of t.options)i.selected=n.includes(qr(i))}else{for(i of t.options)if(qt(qr(i),n)){i.selected=!0;return}r&&n===void 0||(t.selectedIndex=-1)}}function Gr(e){var t=new MutationObserver(t=>{t.every(Jr)||(`__defaultValue`in e&&Ur(e,!1),`__value`in e&&Wr(e,e.__value))});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`value`]}),fn(()=>{t.disconnect()})}function Kr(e,t,n=t){var r=new WeakSet,i=!0;tt(e,`change`,t=>{var i=t?`[selected]`:`:checked`,a;if(e.multiple)a=[].map.call(e.querySelectorAll(i),qr);else{var o=e.querySelector(i)??e.querySelector(`option:not([disabled])`);a=o&&qr(o)}n(a),e.__value=a,j!==null&&r.add(j)}),gn(()=>{var a=t();if(e===document.activeElement){var o=j;if(r.has(o))return}if(Wr(e,a,i),i&&a===void 0){var s=e.querySelector(`:checked`);s!==null&&(a=qr(s),n(a))}e.__value=a,i=!1})}function qr(e){return`__value`in e?e.__value:e.value}function Jr(e){if(e.target.closest(`selectedcontent`)!==null)return!0;if(e.type===`childList`){var t=[...e.addedNodes,...e.removedNodes];return t.length>0&&t.every(e=>e.nodeName===`SELECTEDCONTENT`)}return!1}var Yr=Symbol(`is custom element`),Xr=Symbol(`is html`),Zr=me?`link`:`LINK`;function Qr(e){if(T){var t=!1,n=()=>{if(!t){if(t=!0,e.hasAttribute(`value`)){var n=e.value;$r(e,`value`,null),e.value=n}if(e.hasAttribute(`checked`)){var r=e.checked;$r(e,`checked`,null),e.checked=r}}};e[fe]=n,qe(n),$e()}}function $r(e,t,n,r){var i=ei(e);T&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===Zr)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[se]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&ni(e).has(t)?e[t]=n:e.setAttribute(t,n))}function ei(e){return e[ce]??={[Yr]:e.nodeName.includes(`-`),[Xr]:e.namespaceURI===ge}}var ti=new Map;function ni(e){var t=e.getAttribute(`is`)||e.nodeName,n=ti.get(t);if(n)return n;ti.set(t,n=new Set);for(var r,i=e,a=Element.prototype;a!==i;){for(var s in r=o(i),r)r[s].set&&s!==`innerHTML`&&s!==`textContent`&&s!==`innerText`&&n.add(s);i=l(i)}return n}function ri(e,t,n=t){var r=new WeakSet;tt(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=ai(e)?oi(a):a,n(a),j!==null&&r.add(j),await Zn(),a!==(a=t())){var o=e.selectionStart,s=e.selectionEnd,c=e.value.length;if(e.value=a??``,s!==null){var l=e.value.length;o===s&&s===c&&l>c?(e.selectionStart=l,e.selectionEnd=l):(e.selectionStart=o,e.selectionEnd=Math.min(s,l))}}}),(T&&e.defaultValue!==e.value||er(t)==null&&e.value)&&(n(ai(e)?oi(e.value):e.value),j!==null&&r.add(j)),vn(()=>{var n=t();if(e===document.activeElement){var i=j;if(r.has(i))return}ai(e)&&n===oi(e.value)||(e.type!==`date`||n||e.value)&&n!==e.value&&(e.value=n??``)})}function ii(e,t,n=t){tt(e,`change`,t=>{n(t?e.defaultChecked:e.checked)}),(T&&e.defaultChecked!==e.checked||er(t)==null)&&n(e.checked),vn(()=>{e.checked=!!t()})}function ai(e){var t=e.type;return t===`number`||t===`range`}function oi(e){return e===``?null:+e}function si(e){k===null&&ke(`onMount`),pn(()=>{let t=er(e);if(typeof t==`function`)return t})}typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`);var ci=``,li=class extends Error{constructor(e,t){super(t),this.status=e}};async function ui(e,t,n){let r={Accept:`application/json`};e!==`GET`&&(r[`Content-Type`]=`application/json`,r[`X-Rowbit-Token`]=ci);let i=await fetch(t,{method:e,headers:r,credentials:`same-origin`,body:n===void 0?void 0:JSON.stringify(n)});if(i.status===401)throw window.location.assign(`/login`),new li(401,`Signed out`);let a=await i.json().catch(()=>({}));if(!i.ok)throw new li(i.status,a.error||`HTTP ${i.status}`);return a}async function di(){let e=await ui(`GET`,`/api/session`);return ci=e.token,e}var fi=(e={})=>ui(`GET`,`/api/changes?${new URLSearchParams(Object.entries(e).filter(([,e])=>e))}`),pi=(e,t=0)=>ui(`GET`,`/api/changes/${e}?offset=${t}`),mi=(e,t,n)=>ui(`POST`,`/api/changes/${e}/approve`,{review:t,rows:n}),hi=(e,t)=>ui(`POST`,`/api/changes/${e}/reject`,{reason:t}),gi=e=>ui(`POST`,`/api/changes/${e}/undo`,{}),_i=()=>ui(`GET`,`/api/verify`),vi=()=>ui(`GET`,`/api/deliveries`),yi=()=>ui(`GET`,`/api/setup`),bi=()=>ui(`POST`,`/api/logout`,{});function xi(e,t,n=`${t}s`){return`${e.toLocaleString(`en`)} ${e===1?t:n}`}function Si(e){let t=Math.max(0,Math.round((Date.now()-e)/1e3));return t<60?`${t}s ago`:t<3600?`${Math.round(t/60)} min ago`:t<86400?`${Math.round(t/3600)} h ago`:`${Math.round(t/86400)} d ago`}function Ci(e){return new Date(e).toLocaleString()}var wi={insert:`inserts`,update:`updates`,delete:`deletes`,ddl:`changes the schema`},Ti=Y(` `);function Ei(e,t){var n=Ti(),r=L(n,!0);z(()=>{Vr(n,1,`state ${t.state??``}`),Z(r,t.state)}),X(e,n)}var Di=Y(`-`),Oi=Y(`NULL`),ki=Y(` `),Ai=Y(` `),ji=Y(` `);function Mi(e,t){Ve(t,!0);var n=vr(),r=I(n),i=e=>{X(e,Di())},a=e=>{X(e,Oi())},o=e=>{var n=ki(),r=L(n,!0);z(()=>Z(r,t.value.text)),X(e,n)},s=e=>{var n=Ai(),r=L(n,!0);z(()=>Z(r,t.value.text)),X(e,n)},c=e=>{var n=ji(),r=L(n);z(()=>Z(r,`"${t.value.text??``}"`)),X(e,n)};Q(r,e=>{t.value==null?e(i):t.value.kind===`null`?e(a,1):t.value.kind===`number`?e(o,2):t.value.kind===`binary`?e(s,3):e(c,-1)}),X(e,n),He()}var Ni=Y(``),Pi=Y(`

`),Fi=Y(`

`),Ii=Y(`

Runs this schema change, which can't be undone:

 
`,1),Li=Y(``,1),Ri=Y(`new`),zi=Y(` `),Bi=Y(`

`),Vi=Y(`

in

RowColumnBeforeAfter
`,1),Hi=Y(``),Ui=Y(`

`,1),Wi=Y(`
Restores every row as it was, unless one changed since.
`),Gi=Y(` `),Ki=Y(`

 

History

WhenEventStateByDetail
`,1),qi=Y(`

Back to the queue

`,1);function Ji(e,t){Ve(t,!0);let n=M(null),r=M(``),i=M(!1),a=M(!1),o=M(``),s=M(``);async function c(e=0){try{N(n,await pi(t.id,e),!0),N(r,``)}catch(e){N(r,e.message,!0)}}pn(()=>{c()});async function l(e){N(i,!0);try{N(n,await e(),!0),N(r,``)}catch(e){N(r,e.message,!0)}finally{N(i,!1)}}let u=lt(()=>J(n)?.preview?.total_rows??0),d=lt(()=>J(n)?.preview?J(u)===1?`Approve and apply 1 row`:`Approve and apply all ${J(u).toLocaleString(`en`)} rows`:`Approve and run`),f=lt(()=>!J(n)?.confirm_rows||Number(J(o))===J(u)),p=lt(()=>J(n)?.preview?Math.floor(J(n).preview.offset/50):0),m=lt(()=>Math.ceil(J(u)/50));var h=qi(),g=R(I(h),2),_=e=>{var t=Ni(),n=L(t,!0);z(()=>Z(n,J(r))),X(e,t)};Q(g,e=>{J(r)&&e(_)});var v=R(g,2),y=e=>{var t=Ki(),r=I(t),h=F(r),g=F(h),_=L(g);Ei(R(g,2),{get state(){return J(n).state}}),O(h);var v=R(h,2),y=e=>{var t=Pi(),r=L(t,!0);z(()=>Z(r,J(n).reason)),X(e,t)};Q(v,e=>{J(n).reason&&e(y)});var b=R(v,2),x=e=>{var t=Fi(),r=L(t);z(()=>Z(r,`Shadow mode: ${J(n).shadowed.would??``} (${J(n).shadowed.by??``}). It was applied anyway.`)),X(e,t)};Q(b,e=>{J(n).shadowed&&e(x)});var S=R(b,2),C=L(S,!0),ee=R(S,2),te=e=>{var t=Ii(),r=L(R(I(t),2),!0);z(()=>Z(r,J(n).ddl)),X(e,t)},ne=e=>{var t=Vi(),r=I(t),i=F(r),o=R(i),s=L(o,!0),l=R(o,2),d=L(l,!0),f=R(l,2),h=F(f);Qr(h),Ce(),O(f),O(r);var g=R(r,2),_=F(g),v=R(F(_));$(v,21,()=>J(n).preview.rows,Ar,(e,t)=>{var n=vr();$(I(n),17,()=>J(t).cells.filter(e=>J(a)||e.changed),Ar,(e,n,r)=>{var i=zi();Vr(i,1,``,null,{},{first:r===0});var a=F(i),o=F(a),s=e=>{var n=vr(),r=I(n),i=e=>{var n=vr();$(I(n),17,()=>J(t).key,Ar,(e,t,n)=>{var r=Li(),i=I(r),a=e=>{X(e,_r(`,`))};Q(i,e=>{n>0&&e(a)}),Mi(R(i),{get value(){return J(t)}}),X(e,r)}),X(e,n)},a=e=>{X(e,Ri())};Q(r,e=>{J(t).key?e(i):e(a,-1)}),X(e,n)};Q(o,e=>{r===0&&e(s)}),O(a);var c=R(a),l=L(c,!0),u=R(c);let d;Mi(F(u),{get value(){return J(n).before}}),O(u);var f=R(u);let p;Mi(F(f),{get value(){return J(n).after}}),O(f),O(i),z(()=>{Z(l,J(n).column),d=Vr(u,1,`before`,null,d,{same:!J(n).changed}),p=Vr(f,1,`after`,null,p,{same:!J(n).changed})}),X(e,i)}),X(e,n)}),O(v),O(_),O(g);var y=R(g,2),b=e=>{var t=Bi(),r=F(t),i=R(r),a=R(i);O(t),z((e,t)=>{r.disabled=J(p)===0,Z(i,` Rows ${J(n).preview.offset+1} to ${e??``} of - ${t??``} `),a.disabled=J(p)+1>=J(m)},[()=>Math.min(J(n).preview.offset+50,J(u)),()=>J(u).toLocaleString(`en`)]),cr(`click`,r,()=>c((J(p)-1)*50)),cr(`click`,a,()=>c((J(p)+1)*50)),X(e,t)};Q(y,e=>{J(m)>1&&e(b)}),z(e=>{Z(i,`${wi[J(n).preview.kind]??``} `),Z(s,e),Z(d,J(n).preview.table)},[()=>xi(J(u),`row`)]),ii(h,()=>J(a),e=>N(a,e)),X(e,t)};Q(ee,e=>{J(n).ddl?e(te):J(n).preview&&e(ne,1)});var re=R(ee,2),ie=e=>{var t=Ui(),r=I(t),a=F(r),c=F(a),p=e=>{var t=Hi(),n=R(F(t));Qr(n),O(t),z(e=>$r(n,`placeholder`,e),[()=>String(J(u))]),ri(n,()=>J(o),e=>N(o,e)),X(e,t)};Q(c,e=>{J(n).confirm_rows&&e(p)});var m=R(c,2),h=L(m,!0);O(a);var g=R(a,2),_=F(g);Qr(_);var v=R(_,2);O(g),O(r);var y=L(R(r,2));z(e=>{m.disabled=J(i)||!J(f),Z(h,J(d)),v.disabled=J(i),Z(y,`Approving covers review ${e??``}: exactly the rows above.`)},[()=>J(n).review_hash?.slice(0,12)]),sr(`submit`,a,e=>{e.preventDefault(),l(()=>mi(J(n).id,J(n).review_hash,J(n).confirm_rows?Number(J(o)):void 0))}),sr(`submit`,g,e=>{e.preventDefault(),l(()=>hi(J(n).id,J(s)))}),ri(_,()=>J(s),e=>N(s,e)),X(e,t)};Q(re,e=>{J(n).state===`held`&&e(ie)});var ae=R(re,2),oe=e=>{var t=Wi(),r=F(t);Ce(2),O(t),z(()=>r.disabled=J(i)),cr(`click`,r,()=>l(()=>gi(J(n).id))),X(e,t)};Q(ae,e=>{J(n).undoable&&e(oe)}),O(r);var se=R(r,4),ce=R(F(se));$(ce,21,()=>J(n).history,e=>e.seq,(e,t)=>{var n=Gi(),r=F(n),i=L(r,!0),a=R(r),o=L(a,!0),s=R(a),c=L(s,!0),l=R(s),u=L(l,!0),d=L(R(l),!0);O(n),z(e=>{Z(i,e),Z(o,J(t).event),Z(c,J(t).to),Z(u,J(t).actor),Z(d,J(t).detail??``)},[()=>Ci(J(t).at_ms)]),X(e,n)}),O(ce),O(se),z(()=>{Z(_,`#${J(n).id??``} from ${J(n).agent??``}`),Z(C,J(n).statement)}),X(e,t)};Q(v,e=>{J(n)&&e(y)}),X(e,h),He()}lr([`click`]);var Yi=Y(`

`),Xi=Y(`

Nothing sent yet.

`),Zi=Y(` `),Qi=Y(`
ChangeTargetStateTriesLast errorQueued
`),$i=Y(`

Notifications

Held changes announced to the targets in rowbit-server.yml. A notification that - can't be delivered never affects the change; it stays held here.

`,1);function ea(e,t){Ve(t,!0);let n=M(null),r=M(``);async function i(){try{N(n,(await vi()).deliveries,!0),N(r,``)}catch(e){N(r,e.message,!0)}}si(()=>{i();let e=setInterval(i,1e4);return()=>clearInterval(e)});var a=$i(),o=R(I(a),4),s=e=>{var t=Yi(),n=L(t,!0);z(()=>Z(n,J(r))),X(e,t)};Q(o,e=>{J(r)&&e(s)});var c=R(o,2),l=e=>{X(e,Xi())};Q(c,e=>{J(n)&&J(n).length===0&&e(l)});var u=R(c,2),d=e=>{var t=Qi(),r=F(t),i=R(F(r));$(i,21,()=>J(n),e=>e.id,(e,t)=>{var n=Zi(),r=F(n),i=F(r),a=L(i);O(r);var o=R(r),s=L(o,!0),c=R(o),l=F(c),u=L(l,!0);O(c);var d=R(c),f=L(d,!0),p=R(d),m=L(p,!0),h=L(R(p),!0);O(n),z(e=>{$r(i,`href`,`#/changes/${J(t).change_id??``}`),Z(a,`#${J(t).change_id??``}`),Z(s,J(t).target),Vr(l,1,`state ${J(t).state===`delivered`?`applied`:J(t).state===`failed`?`failed`:`held`}`),Z(u,J(t).state),Z(f,J(t).attempts),Z(m,J(t).last_error??``),Z(h,e)},[()=>Si(J(t).created_at_ms)]),X(e,n)}),O(i),O(r),O(t),X(e,t)};Q(u,e=>{J(n)&&J(n).length&&e(d)}),X(e,a),He()}var ta=Y(`

The audit log's hash chain checks out.

`),na=Y(``),ra=Y(``),ia=Y(`

`),aa=Y(` `),oa=Y(` `),sa=Y(`

Log

#StateAgentWhatDecided byReasonWhen
`,1);function ca(e,t){Ve(t,!0);let n=[``,`proposed`,`held`,`approved`,`applied`,`rejected`,`failed`,`undone`],r=Gt({state:``,agent:``,table:``}),i=M(Gt([])),a=M(null),o=M(``);async function s(){try{N(i,(await fi(r)).changes,!0),N(o,``)}catch(e){N(o,e.message,!0)}}si(async()=>{s(),N(a,await _i().catch(e=>({ok:!1,problem:e.message})),!0)});var c=sa(),l=R(I(c),2),u=e=>{var t=vr(),n=I(t),r=e=>{X(e,ta())},i=e=>{var t=na(),n=L(t);z(()=>Z(n,`The audit log doesn't check out: ${J(a).problem??``}`)),X(e,t)};Q(n,e=>{J(a).ok?e(r):e(i,-1)}),X(e,t)};Q(l,e=>{J(a)&&e(u)});var d=R(l,2),f=F(d),p=R(F(f));$(p,20,()=>n,e=>e,(e,t)=>{var n=ra(),r=L(n,!0),i={};z(()=>{Z(r,t||`any`),i!==(i=t)&&(n.value=(n.__value=i)??``)}),X(e,n)}),O(p),Gr(p),O(f);var m=R(f,2),h=R(F(m));Qr(h),O(m);var g=R(m,2),_=R(F(g));Qr(_),O(g),Ce(2),O(d);var v=R(d,2),y=e=>{var t=ia(),n=L(t,!0);z(()=>Z(n,J(o))),X(e,t)};Q(v,e=>{J(o)&&e(y)});var b=R(v,2),x=F(b),S=R(F(x));$(S,21,()=>J(i),e=>e.id,(e,t)=>{var n=oa(),r=F(n),i=F(r),a=L(i,!0);O(r);var o=R(r);Ei(F(o),{get state(){return J(t).state}}),O(o);var s=R(o),c=L(s,!0),l=R(s),u=F(l),d=R(u),f=e=>{var n=_r();z(()=>Z(n,J(t).rows)),X(e,n)};Q(d,e=>{J(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=aa(),r=L(n,!0);z(()=>Z(r,J(t).table)),X(e,n)};Q(p,e=>{J(t).table&&e(m)}),O(l);var h=R(l),g=L(h,!0),_=R(h),v=L(_,!0),y=L(R(_),!0);O(n),z(e=>{$r(i,`href`,`#/changes/${J(t).id??``}`),Z(a,J(t).id),Z(c,J(t).agent),Z(u,`${wi[J(t).kind]??``??``} `),Z(g,J(t).decided_by??``),Z(v,J(t).reason??``),Z(y,e)},[()=>Si(J(t).created_at_ms)]),X(e,n)}),O(S),O(x),O(b),sr(`submit`,d,e=>{e.preventDefault(),s()}),Kr(p,()=>r.state,e=>r.state=e),ri(h,()=>r.agent,e=>r.agent=e),ri(_,()=>r.table,e=>r.table=e),X(e,c),He()}var la=Y(`

`),ua=Y(`

Nothing is waiting.

`),da=Y(` in`,1),fa=Y(` `),pa=Y(`
`),ma=Y(`
`),ha=Y(`

Waiting for a person

`,1);function ga(e,t){Ve(t,!0);let n=M(null),r=M(Gt([])),i=M(``);async function a(){try{let e=await fi({state:`held`});N(n,e.changes.reverse(),!0),N(r,e.interrupted,!0),N(i,``)}catch(e){N(i,e.message,!0)}}si(()=>{a();let e=setInterval(a,1e4);return()=>clearInterval(e)});var o=ha(),s=I(o),c=R(F(s)),l=e=>{var t=_r();z(()=>Z(t,`(${J(n).length??``})`)),X(e,t)};Q(c,e=>{J(n)&&e(l)}),O(s);var u=R(s,2),d=e=>{var t=la(),n=L(t);z(e=>Z(n,`Changes ${e??``} were interrupted while applying or undoing. Compare their rows - with the preview before anything else.`),[()=>J(r).join(`, `)]),X(e,t)};Q(u,e=>{J(r).length&&e(d)});var f=R(u,2),p=e=>{var t=la(),n=L(t,!0);z(()=>Z(n,J(i))),X(e,t)};Q(f,e=>{J(i)&&e(p)});var m=R(f,2),h=e=>{X(e,ua())};Q(m,e=>{J(n)&&J(n).length===0&&e(h)}),$(R(m,2),17,()=>J(n)??[],e=>e.id,(e,t)=>{var n=ma(),r=F(n),i=F(r),a=L(i),o=R(i,2),s=L(o,!0),c=L(R(o,2),!0);O(r);var l=R(r,2),u=F(l),d=R(u),f=e=>{var n=da(),r=L(I(n),!0);Ce(),z(e=>Z(r,e),[()=>xi(J(t).rows,`row`)]),X(e,n)};Q(d,e=>{J(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=fa(),r=L(n,!0);z(()=>Z(r,J(t).table)),X(e,n)};Q(p,e=>{J(t).table&&e(m)}),O(l);var h=R(l,2),g=e=>{var n=pa(),r=L(n);z(()=>Z(r,`Held: ${J(t).reason??``}`)),X(e,n)};Q(h,e=>{J(t).reason&&e(g)}),O(n),z(e=>{$r(n,`href`,`#/changes/${J(t).id??``}`),Z(a,`#${J(t).id??``}`),Z(s,J(t).agent),Z(c,e),Z(u,`${wi[J(t).kind]??`writes`??``} `)},[()=>Si(J(t).created_at_ms)]),X(e,n)}),X(e,o),He()}var _a=Y(`

`),va=Y(`
Database
Server
(MariaDB or MySQL)
Rowbit connects as
Connection
`),ya=Y(`

Unknown: the database URL isn't available to the review page.

`),ba=Y(`
Files
`,1),xa=Y(`
Notifications
`,1),Sa=Y(`

`),Ca=Y(` `),wa=Y(`
ConnectedIdSinceLast used

Sign one out:

`,1),Ta=Y(`

Not connected over HTTP. Connect a client from your project: rowbit connect claude-code

`),Ea=Y(`

`,1),Da=Y(`

Database

Rowbit

Review page
Sign-in
Agents over HTTP
Held now
Version

Agents

`,1),Oa=Y(`

Setup

What this Rowbit guards, and where its agents are connected. Passwords are never shown. rowbit status shows the same for every setup on this machine.

`,1);function ka(e,t){Ve(t,!0);let n=M(null),r=M(``);async function i(){try{N(n,await yi(),!0),N(r,``)}catch(e){N(r,e.message,!0)}}si(()=>{i();let e=setInterval(i,15e3);return()=>clearInterval(e)});function a(e){let t={allow:`allows`,block:`refuses`}[e.default]??`holds`,n=e.rules===0?`${t} every write`:`${e.rules} rule${e.rules===1?``:`s`}, else ${t}`;return e.mode===`shadow`&&(n+=` (shadow mode: logged, not enforced)`),e.scoped&&(n+=`, scoped`),n}var o=Oa(),s=R(I(o),4),c=e=>{var t=_a(),n=L(t,!0);z(()=>Z(n,J(r))),X(e,t)};Q(s,e=>{J(r)&&e(c)});var l=R(s,2),u=e=>{var t=Da(),r=I(t),i=R(F(r),2),o=e=>{var t=va(),r=R(F(t)),i=L(F(r),!0);O(r);var a=R(r,3),o=L(F(a));Ce(),O(a);var s=R(a,3),c=L(F(s),!0);O(s);var l=L(R(s,3),!0);O(t),z(()=>{Z(i,J(n).database.database),Z(o,`${J(n).database.host??``}:${J(n).database.port??``}`),Z(c,J(n).database.user),Z(l,J(n).database.tls)}),X(e,t)},s=e=>{X(e,ya())};Q(i,e=>{J(n).database?e(o):e(s,-1)}),O(r);var c=R(r,2),l=R(F(c),2),u=F(l),d=e=>{var t=ba(),r=R(I(t)),i=L(F(r),!0);O(r),z(()=>Z(i,J(n).dir)),X(e,t)};Q(u,e=>{J(n).dir&&e(d)});var f=R(u,3),p=L(f,!0),m=R(f,3),h=L(m,!0),g=R(m,3),_=L(g,!0),v=R(g,2),y=e=>{var t=xa(),r=L(R(I(t)),!0);z(e=>Z(r,e),[()=>J(n).notify.join(`, `)]),X(e,t)};Q(v,e=>{J(n).notify?.length&&e(y)});var b=R(v,3),x=L(F(b),!0);O(b);var S=L(R(b,3),!0);O(l),O(c);var C=R(c,2);$(R(F(C),2),17,()=>J(n).agents,e=>e.id,(e,t)=>{var n=Ea(),r=I(n),i=L(F(r),!0);O(r);var o=R(r,2),s=e=>{var n=Sa(),r=L(n,!0);z(()=>Z(r,J(t).description)),X(e,n)};Q(o,e=>{J(t).description&&e(s)});var c=R(o,2),l=L(c,!0),u=R(c,2),d=e=>{var n=wa(),r=I(n),i=F(r),a=R(F(i));$(a,21,()=>J(t).connections,e=>e.id,(e,t)=>{var n=Ca(),r=F(n),i=L(r,!0),a=R(r),o=L(F(a),!0);O(a);var s=R(a),c=L(s,!0),l=L(R(s),!0);O(n),z((e,n)=>{Z(i,J(t).label),Z(o,J(t).id),Z(c,e),Z(l,n)},[()=>Si(J(t).created_ms),()=>J(t).used_ms?Si(J(t).used_ms):`never`]),X(e,n)}),O(a),O(i),O(r);var o=R(r,2),s=L(R(F(o)));O(o),z(()=>Z(s,`rowbit agents revoke ${J(t).id??``} --connection `)),X(e,n)},f=e=>{X(e,Ta())};Q(u,e=>{J(t).connections.length?e(d):e(f,-1)}),z(e=>{Z(i,J(t).id),Z(l,e)},[()=>a(J(t))]),X(e,n)}),O(C),z(()=>{Z(p,J(n).review_page),Z(h,J(n).sign_in),Z(_,J(n).mcp??`off (agents start rowbit mcp themselves)`),Z(x,J(n).held),Z(S,J(n).version)}),X(e,t)};Q(l,e=>{J(n)&&e(u)}),X(e,o),He()}var Aa=Y(`

`),ja=Y(`

Nothing yet.

`),Ma=Y(` `),Na=Y(` `),Pa=Y(`

#StateAgentWhatWould haveWhen
`,1),Fa=Y(`

Shadow mode

Changes from agents in shadow mode that a rule would have held or blocked. They were applied, and - can be undone from their page.

`,1);function Ia(e,t){Ve(t,!0);let n=M(null),r=M(``);si(async()=>{try{let{changes:e}=await fi({shadowed:`true`}),t=new Map;for(let n of e){let e=n.shadowed.by;t.has(e)||t.set(e,[]),t.get(e).push(n)}N(n,[...t.entries()].sort((e,t)=>t[1].length-e[1].length),!0)}catch(e){N(r,e.message,!0)}});var i=Fa(),a=R(I(i),4),o=e=>{var t=Aa(),n=L(t,!0);z(()=>Z(n,J(r))),X(e,t)};Q(a,e=>{J(r)&&e(o)});var s=R(a,2),c=e=>{X(e,ja())};Q(s,e=>{J(n)&&J(n).length===0&&e(c)}),$(R(s,2),17,()=>J(n)??[],([e,t])=>e,(e,t)=>{var n=lt(()=>m(J(t),2));let r=()=>J(n)[0],i=()=>J(n)[1];var a=Pa(),o=I(a),s=F(o),c=L(R(s));O(o);var l=R(o,2),u=F(l),d=R(F(u));$(d,21,i,e=>e.id,(e,t)=>{var n=Na(),r=F(n),i=F(r),a=L(i,!0);O(r);var o=R(r);Ei(F(o),{get state(){return J(t).state}}),O(o);var s=R(o),c=L(s,!0),l=R(s),u=F(l),d=R(u),f=e=>{var n=_r();z(()=>Z(n,J(t).rows)),X(e,n)};Q(d,e=>{J(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=Ma(),r=L(n,!0);z(()=>Z(r,J(t).table)),X(e,n)};Q(p,e=>{J(t).table&&e(m)}),O(l);var h=R(l),g=L(h,!0),_=L(R(h),!0);O(n),z(e=>{$r(i,`href`,`#/changes/${J(t).id??``}`),Z(a,J(t).id),Z(c,J(t).agent),Z(u,`${wi[J(t).kind]??``??``} `),Z(g,J(t).shadowed.would??``),Z(_,e)},[()=>Si(J(t).created_at_ms)]),X(e,n)}),O(d),O(u),O(l),z(e=>{Z(s,`${r()??``} `),Z(c,`(${e??``})`)},[()=>xi(i().length,`change`)]),X(e,a)}),X(e,i),He()}var La=Y(`Guarding on as `),Ra=Y(` `,1),za=Y(`

Loading...

`),Ba=Y(`
Rowbit
`,1);function Va(e,t){Ve(t,!0);let n=M(``),r=M(null),i=M(Gt(location.hash));si(()=>{di().then(e=>{N(n,e.reviewer,!0),yi().then(e=>N(r,e.database,!0)).catch(()=>{})});let e=()=>N(i,location.hash,!0);return addEventListener(`hashchange`,e),()=>removeEventListener(`hashchange`,e)});let a=lt(()=>J(i).match(/^#\/changes\/(\d+)$/)?.[1]);async function o(){await bi().catch(()=>{}),location.assign(`/login`)}var s=Ba(),c=I(s),l=R(F(c),2),u=F(l);let d;var f=R(u,2);let p;var m=R(f,2);let h;var g=R(m,2);let _;var v=R(g,2);let y;O(l);var b=R(l,2),x=e=>{var t=La(),n=R(F(t)),i=L(n,!0),a=R(n,2),o=L(a),s=L(R(a,2),!0);O(t),z(()=>{Z(i,J(r).database),Z(o,`${J(r).host??``}:${J(r).port??``}`),Z(s,J(r).user)}),X(e,t)};Q(b,e=>{J(r)&&e(x)});var S=R(b,2),C=F(S),ee=e=>{var t=Ra(),r=I(t),i=R(r);z(()=>Z(r,`${J(n)??``} `)),cr(`click`,i,o),X(e,t)};Q(C,e=>{J(n)&&e(ee)}),O(S),O(c);var te=R(c,2),ne=F(te),re=e=>{X(e,za())},ie=e=>{var t=vr();kr(I(t),()=>J(a),e=>{Ji(e,{get id(){return J(a)}})}),X(e,t)},ae=e=>{ca(e,{})},oe=e=>{Ia(e,{})},se=e=>{ea(e,{})},ce=e=>{ka(e,{})},le=e=>{ga(e,{})};Q(ne,e=>{J(n)?J(a)?e(ie,1):J(i)===`#/log`?e(ae,2):J(i)===`#/shadow`?e(oe,3):J(i)===`#/notifications`?e(se,4):J(i)===`#/setup`?e(ce,5):e(le,-1):e(re)}),O(te),z(()=>{d=Vr(u,1,``,null,d,{active:J(i)===``||J(i)===`#/`}),p=Vr(f,1,``,null,p,{active:J(i)===`#/log`}),h=Vr(m,1,``,null,h,{active:J(i)===`#/shadow`}),_=Vr(g,1,``,null,_,{active:J(i)===`#/notifications`}),y=Vr(v,1,``,null,y,{active:J(i)===`#/setup`})}),X(e,s),He()}lr([`click`]),Cr(Va,{target:document.getElementById(`app`)}); \ No newline at end of file +\r\f\xA0\v`];function Br(e,t,n){var r=e==null?``:``+e;if(t&&(r=r?r+` `+t:t),n){for(var i of Object.keys(n))if(n[i])r=r?r+` `+i:i;else if(r.length)for(var a=i.length,o=0;(o=r.indexOf(i,o))>=0;){var s=o+a;(o===0||zr.includes(r[o-1]))&&(s===r.length||zr.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function Vr(e,t,n,r,i,a){var o=e[le];if(T||o!==n||o===void 0){var s=Br(n,r,a);T&&s===e.getAttribute(`class`)||(s==null?e.removeAttribute(`class`):t?e.className=s:e.setAttribute(`class`,s)),e[le]=n}else if(a&&i!==a)for(var c in a){var l=!!a[c];(i==null||l!==!!i[c])&&e.classList.toggle(c,l)}return a}function Hr(e,t){t?e.hasAttribute(`selected`)||e.setAttribute(`selected`,``):e.removeAttribute(`selected`)}function Ur(t,n){var r=t.__defaultValue,i=t.multiple,a=i?r??[]:null;if(!i||e(a)){var o=t.selectedIndex,s=n&&i?new Set(t.selectedOptions):null;for(var c of t.options){var l=qr(c);Hr(c,i?a.includes(l):qt(l,r))}if(n){if(s!==null)for(c of t.options){var u=s.has(c);c.selected!==u&&(c.selected=u)}else t.selectedIndex!==o&&(t.selectedIndex=o)}}}function Wr(t,n,r=!1){if(t.multiple){if(n==null)return;if(!e(n))return ye();for(var i of t.options)i.selected=n.includes(qr(i))}else{for(i of t.options)if(qt(qr(i),n)){i.selected=!0;return}r&&n===void 0||(t.selectedIndex=-1)}}function Gr(e){var t=new MutationObserver(t=>{t.every(Jr)||(`__defaultValue`in e&&Ur(e,!1),`__value`in e&&Wr(e,e.__value))});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`value`]}),fn(()=>{t.disconnect()})}function Kr(e,t,n=t){var r=new WeakSet,i=!0;tt(e,`change`,t=>{var i=t?`[selected]`:`:checked`,a;if(e.multiple)a=[].map.call(e.querySelectorAll(i),qr);else{var o=e.querySelector(i)??e.querySelector(`option:not([disabled])`);a=o&&qr(o)}n(a),e.__value=a,j!==null&&r.add(j)}),gn(()=>{var a=t();if(e===document.activeElement){var o=j;if(r.has(o))return}if(Wr(e,a,i),i&&a===void 0){var s=e.querySelector(`:checked`);s!==null&&(a=qr(s),n(a))}e.__value=a,i=!1})}function qr(e){return`__value`in e?e.__value:e.value}function Jr(e){if(e.target.closest(`selectedcontent`)!==null)return!0;if(e.type===`childList`){var t=[...e.addedNodes,...e.removedNodes];return t.length>0&&t.every(e=>e.nodeName===`SELECTEDCONTENT`)}return!1}var Yr=Symbol(`is custom element`),Xr=Symbol(`is html`),Zr=me?`link`:`LINK`;function Qr(e){if(T){var t=!1,n=()=>{if(!t){if(t=!0,e.hasAttribute(`value`)){var n=e.value;$r(e,`value`,null),e.value=n}if(e.hasAttribute(`checked`)){var r=e.checked;$r(e,`checked`,null),e.checked=r}}};e[fe]=n,qe(n),$e()}}function $r(e,t,n,r){var i=ei(e);T&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===Zr)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[se]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&ni(e).has(t)?e[t]=n:e.setAttribute(t,n))}function ei(e){return e[ce]??={[Yr]:e.nodeName.includes(`-`),[Xr]:e.namespaceURI===ge}}var ti=new Map;function ni(e){var t=e.getAttribute(`is`)||e.nodeName,n=ti.get(t);if(n)return n;ti.set(t,n=new Set);for(var r,i=e,a=Element.prototype;a!==i;){for(var s in r=o(i),r)r[s].set&&s!==`innerHTML`&&s!==`textContent`&&s!==`innerText`&&n.add(s);i=l(i)}return n}function ri(e,t,n=t){var r=new WeakSet;tt(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=ai(e)?oi(a):a,n(a),j!==null&&r.add(j),await Zn(),a!==(a=t())){var o=e.selectionStart,s=e.selectionEnd,c=e.value.length;if(e.value=a??``,s!==null){var l=e.value.length;o===s&&s===c&&l>c?(e.selectionStart=l,e.selectionEnd=l):(e.selectionStart=o,e.selectionEnd=Math.min(s,l))}}}),(T&&e.defaultValue!==e.value||er(t)==null&&e.value)&&(n(ai(e)?oi(e.value):e.value),j!==null&&r.add(j)),vn(()=>{var n=t();if(e===document.activeElement){var i=j;if(r.has(i))return}ai(e)&&n===oi(e.value)||(e.type!==`date`||n||e.value)&&n!==e.value&&(e.value=n??``)})}function ii(e,t,n=t){tt(e,`change`,t=>{n(t?e.defaultChecked:e.checked)}),(T&&e.defaultChecked!==e.checked||er(t)==null)&&n(e.checked),vn(()=>{e.checked=!!t()})}function ai(e){var t=e.type;return t===`number`||t===`range`}function oi(e){return e===``?null:+e}function si(e){k===null&&ke(`onMount`),pn(()=>{let t=er(e);if(typeof t==`function`)return t})}typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`);var ci=``,li=class extends Error{constructor(e,t){super(t),this.status=e}};async function ui(e,t,n){let r={Accept:`application/json`};e!==`GET`&&(r[`Content-Type`]=`application/json`,r[`X-Rowbit-Token`]=ci);let i=await fetch(t,{method:e,headers:r,credentials:`same-origin`,body:n===void 0?void 0:JSON.stringify(n)});if(i.status===401)throw window.location.assign(`/login`),new li(401,`Signed out`);let a=await i.json().catch(()=>({}));if(!i.ok)throw new li(i.status,a.error||`HTTP ${i.status}`);return a}async function di(){let e=await ui(`GET`,`/api/session`);return ci=e.token,e}var fi=(e={})=>ui(`GET`,`/api/changes?${new URLSearchParams(Object.entries(e).filter(([,e])=>e))}`),pi=(e,t=0)=>ui(`GET`,`/api/changes/${e}?offset=${t}`),mi=(e,t,n)=>ui(`POST`,`/api/changes/${e}/approve`,{review:t,rows:n}),hi=(e,t)=>ui(`POST`,`/api/changes/${e}/reject`,{reason:t}),gi=e=>ui(`POST`,`/api/changes/${e}/undo`,{}),_i=()=>ui(`GET`,`/api/verify`),vi=()=>ui(`GET`,`/api/deliveries`),yi=()=>ui(`GET`,`/api/setup`),bi=()=>ui(`POST`,`/api/logout`,{});function xi(e,t,n=`${t}s`){return`${e.toLocaleString(`en`)} ${e===1?t:n}`}function Si(e){let t=Math.max(0,Math.round((Date.now()-e)/1e3));return t<60?`${t}s ago`:t<3600?`${Math.round(t/60)} min ago`:t<86400?`${Math.round(t/3600)} h ago`:`${Math.round(t/86400)} d ago`}function Ci(e){return new Date(e).toLocaleString()}var wi={insert:`inserts`,update:`updates`,delete:`deletes`,ddl:`changes the schema`},Ti=Y(` `);function Ei(e,t){var n=Ti(),r=L(n,!0);z(()=>{Vr(n,1,`state ${t.state??``}`),Z(r,t.state)}),X(e,n)}var Di=Y(`-`),Oi=Y(`NULL`),ki=Y(` `),Ai=Y(` `),ji=Y(` `);function Mi(e,t){Ve(t,!0);var n=vr(),r=I(n),i=e=>{X(e,Di())},a=e=>{X(e,Oi())},o=e=>{var n=ki(),r=L(n,!0);z(()=>Z(r,t.value.text)),X(e,n)},s=e=>{var n=Ai(),r=L(n,!0);z(()=>Z(r,t.value.text)),X(e,n)},c=e=>{var n=ji(),r=L(n);z(()=>Z(r,`"${t.value.text??``}"`)),X(e,n)};Q(r,e=>{t.value==null?e(i):t.value.kind===`null`?e(a,1):t.value.kind===`number`?e(o,2):t.value.kind===`binary`?e(s,3):e(c,-1)}),X(e,n),He()}var Ni=Y(``),Pi=Y(`

`),Fi=Y(`

`),Ii=Y(`

`),Li=Y(`

Runs this schema change, which can't be undone:

 
`,1),Ri=Y(``,1),zi=Y(`new`),Bi=Y(` `),Vi=Y(`

`),Hi=Y(`

in

RowColumnBeforeAfter
`,1),Ui=Y(``),Wi=Y(`

`,1),Gi=Y(`
Restores every row as it was, unless one changed since.
`),Ki=Y(` `),qi=Y(`

 

History

WhenEventStateByDetail
`,1),Ji=Y(`

Back to the queue

`,1);function Yi(e,t){Ve(t,!0);let n=M(null),r=M(``),i=M(!1),a=M(!1),o=M(``),s=M(``);async function c(e=0){try{N(n,await pi(t.id,e),!0),N(r,``)}catch(e){N(r,e.message,!0)}}pn(()=>{c()});async function l(e){N(i,!0);try{N(n,await e(),!0),N(r,``)}catch(e){N(r,e.message,!0)}finally{N(i,!1)}}let u=lt(()=>J(n)?.preview?.total_rows??0),d=lt(()=>J(n)?.preview?J(u)===1?`Approve and apply 1 row`:`Approve and apply all ${J(u).toLocaleString(`en`)} rows`:`Approve and run`),f=lt(()=>!J(n)?.confirm_rows||Number(J(o))===J(u)),p=lt(()=>J(n)?.preview?Math.floor(J(n).preview.offset/50):0),m=lt(()=>Math.ceil(J(u)/50));var h=Ji(),g=R(I(h),2),_=e=>{var t=Ni(),n=L(t,!0);z(()=>Z(n,J(r))),X(e,t)};Q(g,e=>{J(r)&&e(_)});var v=R(g,2),y=e=>{var t=qi(),r=I(t),h=F(r),g=F(h),_=L(g);Ei(R(g,2),{get state(){return J(n).state}}),O(h);var v=R(h,2),y=e=>{var t=Pi(),r=L(t,!0);z(()=>Z(r,J(n).reason)),X(e,t)};Q(v,e=>{J(n).reason&&e(y)});var b=R(v,2),x=e=>{var t=Fi(),r=F(t),i=L(r);O(t),z(()=>{$r(r,`href`,`#/changes/${J(n).superseded_by??``}`),Z(i,`Review #${J(n).superseded_by??``} instead`)}),X(e,t)};Q(b,e=>{J(n).superseded_by&&e(x)});var S=R(b,2),C=e=>{var t=Ii(),r=L(t);z(()=>Z(r,`Shadow mode: ${J(n).shadowed.would??``} (${J(n).shadowed.by??``}). It was applied anyway.`)),X(e,t)};Q(S,e=>{J(n).shadowed&&e(C)});var ee=R(S,2),te=L(ee,!0),ne=R(ee,2),re=e=>{var t=Li(),r=L(R(I(t),2),!0);z(()=>Z(r,J(n).ddl)),X(e,t)},ie=e=>{var t=Hi(),r=I(t),i=F(r),o=R(i),s=L(o,!0),l=R(o,2),d=L(l,!0),f=R(l,2),h=F(f);Qr(h),Ce(),O(f),O(r);var g=R(r,2),_=F(g),v=R(F(_));$(v,21,()=>J(n).preview.rows,Ar,(e,t)=>{var n=vr();$(I(n),17,()=>J(t).cells.filter(e=>J(a)||e.changed),Ar,(e,n,r)=>{var i=Bi();Vr(i,1,``,null,{},{first:r===0});var a=F(i),o=F(a),s=e=>{var n=vr(),r=I(n),i=e=>{var n=vr();$(I(n),17,()=>J(t).key,Ar,(e,t,n)=>{var r=Ri(),i=I(r),a=e=>{X(e,_r(`,`))};Q(i,e=>{n>0&&e(a)}),Mi(R(i),{get value(){return J(t)}}),X(e,r)}),X(e,n)},a=e=>{X(e,zi())};Q(r,e=>{J(t).key?e(i):e(a,-1)}),X(e,n)};Q(o,e=>{r===0&&e(s)}),O(a);var c=R(a),l=L(c,!0),u=R(c);let d;Mi(F(u),{get value(){return J(n).before}}),O(u);var f=R(u);let p;Mi(F(f),{get value(){return J(n).after}}),O(f),O(i),z(()=>{Z(l,J(n).column),d=Vr(u,1,`before`,null,d,{same:!J(n).changed}),p=Vr(f,1,`after`,null,p,{same:!J(n).changed})}),X(e,i)}),X(e,n)}),O(v),O(_),O(g);var y=R(g,2),b=e=>{var t=Vi(),r=F(t),i=R(r),a=R(i);O(t),z((e,t)=>{r.disabled=J(p)===0,Z(i,` Rows ${J(n).preview.offset+1} to ${e??``} of + ${t??``} `),a.disabled=J(p)+1>=J(m)},[()=>Math.min(J(n).preview.offset+50,J(u)),()=>J(u).toLocaleString(`en`)]),cr(`click`,r,()=>c((J(p)-1)*50)),cr(`click`,a,()=>c((J(p)+1)*50)),X(e,t)};Q(y,e=>{J(m)>1&&e(b)}),z(e=>{Z(i,`${wi[J(n).preview.kind]??``} `),Z(s,e),Z(d,J(n).preview.table)},[()=>xi(J(u),`row`)]),ii(h,()=>J(a),e=>N(a,e)),X(e,t)};Q(ne,e=>{J(n).ddl?e(re):J(n).preview&&e(ie,1)});var ae=R(ne,2),oe=e=>{var t=Wi(),r=I(t),a=F(r),c=F(a),p=e=>{var t=Ui(),n=R(F(t));Qr(n),O(t),z(e=>$r(n,`placeholder`,e),[()=>String(J(u))]),ri(n,()=>J(o),e=>N(o,e)),X(e,t)};Q(c,e=>{J(n).confirm_rows&&e(p)});var m=R(c,2),h=L(m,!0);O(a);var g=R(a,2),_=F(g);Qr(_);var v=R(_,2);O(g),O(r);var y=L(R(r,2));z(e=>{m.disabled=J(i)||!J(f),Z(h,J(d)),v.disabled=J(i),Z(y,`Approving covers review ${e??``}: exactly the rows above.`)},[()=>J(n).review_hash?.slice(0,12)]),sr(`submit`,a,e=>{e.preventDefault(),l(()=>mi(J(n).id,J(n).review_hash,J(n).confirm_rows?Number(J(o)):void 0))}),sr(`submit`,g,e=>{e.preventDefault(),l(()=>hi(J(n).id,J(s)))}),ri(_,()=>J(s),e=>N(s,e)),X(e,t)};Q(ae,e=>{J(n).state===`held`&&e(oe)});var se=R(ae,2),ce=e=>{var t=Gi(),r=F(t);Ce(2),O(t),z(()=>r.disabled=J(i)),cr(`click`,r,()=>l(()=>gi(J(n).id))),X(e,t)};Q(se,e=>{J(n).undoable&&e(ce)}),O(r);var le=R(r,4),ue=R(F(le));$(ue,21,()=>J(n).history,e=>e.seq,(e,t)=>{var n=Ki(),r=F(n),i=L(r,!0),a=R(r),o=L(a,!0),s=R(a),c=L(s,!0),l=R(s),u=L(l,!0),d=L(R(l),!0);O(n),z(e=>{Z(i,e),Z(o,J(t).event),Z(c,J(t).to),Z(u,J(t).actor),Z(d,J(t).detail??``)},[()=>Ci(J(t).at_ms)]),X(e,n)}),O(ue),O(le),z(()=>{Z(_,`#${J(n).id??``} from ${J(n).agent??``}`),Z(te,J(n).statement)}),X(e,t)};Q(v,e=>{J(n)&&e(y)}),X(e,h),He()}lr([`click`]);var Xi=Y(`

`),Zi=Y(`

Nothing sent yet.

`),Qi=Y(` `),$i=Y(`
ChangeTargetStateTriesLast errorQueued
`),ea=Y(`

Notifications

Held changes announced to the targets in rowbit-server.yml. A notification that + can't be delivered never affects the change; it stays held here.

`,1);function ta(e,t){Ve(t,!0);let n=M(null),r=M(``);async function i(){try{N(n,(await vi()).deliveries,!0),N(r,``)}catch(e){N(r,e.message,!0)}}si(()=>{i();let e=setInterval(i,1e4);return()=>clearInterval(e)});var a=ea(),o=R(I(a),4),s=e=>{var t=Xi(),n=L(t,!0);z(()=>Z(n,J(r))),X(e,t)};Q(o,e=>{J(r)&&e(s)});var c=R(o,2),l=e=>{X(e,Zi())};Q(c,e=>{J(n)&&J(n).length===0&&e(l)});var u=R(c,2),d=e=>{var t=$i(),r=F(t),i=R(F(r));$(i,21,()=>J(n),e=>e.id,(e,t)=>{var n=Qi(),r=F(n),i=F(r),a=L(i);O(r);var o=R(r),s=L(o,!0),c=R(o),l=F(c),u=L(l,!0);O(c);var d=R(c),f=L(d,!0),p=R(d),m=L(p,!0),h=L(R(p),!0);O(n),z(e=>{$r(i,`href`,`#/changes/${J(t).change_id??``}`),Z(a,`#${J(t).change_id??``}`),Z(s,J(t).target),Vr(l,1,`state ${J(t).state===`delivered`?`applied`:J(t).state===`failed`?`failed`:`held`}`),Z(u,J(t).state),Z(f,J(t).attempts),Z(m,J(t).last_error??``),Z(h,e)},[()=>Si(J(t).created_at_ms)]),X(e,n)}),O(i),O(r),O(t),X(e,t)};Q(u,e=>{J(n)&&J(n).length&&e(d)}),X(e,a),He()}var na=Y(`

The audit log's hash chain checks out.

`),ra=Y(``),ia=Y(``),aa=Y(`

`),oa=Y(` `),sa=Y(` `),ca=Y(`

Log

#StateAgentWhatDecided byReasonWhen
`,1);function la(e,t){Ve(t,!0);let n=[``,`proposed`,`held`,`approved`,`applied`,`rejected`,`failed`,`undone`],r=Gt({state:``,agent:``,table:``}),i=M(Gt([])),a=M(null),o=M(``);async function s(){try{N(i,(await fi(r)).changes,!0),N(o,``)}catch(e){N(o,e.message,!0)}}si(async()=>{s(),N(a,await _i().catch(e=>({ok:!1,problem:e.message})),!0)});var c=ca(),l=R(I(c),2),u=e=>{var t=vr(),n=I(t),r=e=>{X(e,na())},i=e=>{var t=ra(),n=L(t);z(()=>Z(n,`The audit log doesn't check out: ${J(a).problem??``}`)),X(e,t)};Q(n,e=>{J(a).ok?e(r):e(i,-1)}),X(e,t)};Q(l,e=>{J(a)&&e(u)});var d=R(l,2),f=F(d),p=R(F(f));$(p,20,()=>n,e=>e,(e,t)=>{var n=ia(),r=L(n,!0),i={};z(()=>{Z(r,t||`any`),i!==(i=t)&&(n.value=(n.__value=i)??``)}),X(e,n)}),O(p),Gr(p),O(f);var m=R(f,2),h=R(F(m));Qr(h),O(m);var g=R(m,2),_=R(F(g));Qr(_),O(g),Ce(2),O(d);var v=R(d,2),y=e=>{var t=aa(),n=L(t,!0);z(()=>Z(n,J(o))),X(e,t)};Q(v,e=>{J(o)&&e(y)});var b=R(v,2),x=F(b),S=R(F(x));$(S,21,()=>J(i),e=>e.id,(e,t)=>{var n=sa(),r=F(n),i=F(r),a=L(i,!0);O(r);var o=R(r);Ei(F(o),{get state(){return J(t).state}}),O(o);var s=R(o),c=L(s,!0),l=R(s),u=F(l),d=R(u),f=e=>{var n=_r();z(()=>Z(n,J(t).rows)),X(e,n)};Q(d,e=>{J(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=oa(),r=L(n,!0);z(()=>Z(r,J(t).table)),X(e,n)};Q(p,e=>{J(t).table&&e(m)}),O(l);var h=R(l),g=L(h,!0),_=R(h),v=L(_,!0),y=L(R(_),!0);O(n),z(e=>{$r(i,`href`,`#/changes/${J(t).id??``}`),Z(a,J(t).id),Z(c,J(t).agent),Z(u,`${wi[J(t).kind]??``??``} `),Z(g,J(t).decided_by??``),Z(v,J(t).reason??``),Z(y,e)},[()=>Si(J(t).created_at_ms)]),X(e,n)}),O(S),O(x),O(b),sr(`submit`,d,e=>{e.preventDefault(),s()}),Kr(p,()=>r.state,e=>r.state=e),ri(h,()=>r.agent,e=>r.agent=e),ri(_,()=>r.table,e=>r.table=e),X(e,c),He()}var ua=Y(`

`),da=Y(`

Nothing is waiting.

`),fa=Y(` in`,1),pa=Y(` `),ma=Y(`
`),ha=Y(`
`),ga=Y(`

Waiting for a person

`,1);function _a(e,t){Ve(t,!0);let n=M(null),r=M(Gt([])),i=M(``);async function a(){try{let e=await fi({state:`held`});N(n,e.changes.reverse(),!0),N(r,e.interrupted,!0),N(i,``)}catch(e){N(i,e.message,!0)}}si(()=>{a();let e=setInterval(a,1e4);return()=>clearInterval(e)});var o=ga(),s=I(o),c=R(F(s)),l=e=>{var t=_r();z(()=>Z(t,`(${J(n).length??``})`)),X(e,t)};Q(c,e=>{J(n)&&e(l)}),O(s);var u=R(s,2),d=e=>{var t=ua(),n=L(t);z(e=>Z(n,`Changes ${e??``} were interrupted while applying or undoing. Compare their rows + with the preview before anything else.`),[()=>J(r).join(`, `)]),X(e,t)};Q(u,e=>{J(r).length&&e(d)});var f=R(u,2),p=e=>{var t=ua(),n=L(t,!0);z(()=>Z(n,J(i))),X(e,t)};Q(f,e=>{J(i)&&e(p)});var m=R(f,2),h=e=>{X(e,da())};Q(m,e=>{J(n)&&J(n).length===0&&e(h)}),$(R(m,2),17,()=>J(n)??[],e=>e.id,(e,t)=>{var n=ha(),r=F(n),i=F(r),a=L(i),o=R(i,2),s=L(o,!0),c=L(R(o,2),!0);O(r);var l=R(r,2),u=F(l),d=R(u),f=e=>{var n=fa(),r=L(I(n),!0);Ce(),z(e=>Z(r,e),[()=>xi(J(t).rows,`row`)]),X(e,n)};Q(d,e=>{J(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=pa(),r=L(n,!0);z(()=>Z(r,J(t).table)),X(e,n)};Q(p,e=>{J(t).table&&e(m)}),O(l);var h=R(l,2),g=e=>{var n=ma(),r=L(n);z(()=>Z(r,`Held: ${J(t).reason??``}`)),X(e,n)};Q(h,e=>{J(t).reason&&e(g)}),O(n),z(e=>{$r(n,`href`,`#/changes/${J(t).id??``}`),Z(a,`#${J(t).id??``}`),Z(s,J(t).agent),Z(c,e),Z(u,`${wi[J(t).kind]??`writes`??``} `)},[()=>Si(J(t).created_at_ms)]),X(e,n)}),X(e,o),He()}var va=Y(`

`),ya=Y(`
Database
Server
(MariaDB or MySQL)
Rowbit connects as
Connection
`),ba=Y(`

Unknown: the database URL isn't available to the review page.

`),xa=Y(`
Files
`,1),Sa=Y(`
Notifications
`,1),Ca=Y(`

`),wa=Y(` `),Ta=Y(`
ConnectedIdSinceLast used

Sign one out:

`,1),Ea=Y(`

Not connected over HTTP. Connect a client from your project: rowbit connect claude-code

`),Da=Y(`

`,1),Oa=Y(`

Database

Rowbit

Review page
Sign-in
Agents over HTTP
Held now
Version

Agents

`,1),ka=Y(`

Setup

What this Rowbit guards, and where its agents are connected. Passwords are never shown. rowbit status shows the same for every setup on this machine.

`,1);function Aa(e,t){Ve(t,!0);let n=M(null),r=M(``);async function i(){try{N(n,await yi(),!0),N(r,``)}catch(e){N(r,e.message,!0)}}si(()=>{i();let e=setInterval(i,15e3);return()=>clearInterval(e)});function a(e){let t={allow:`allows`,block:`refuses`}[e.default]??`holds`,n=e.rules===0?`${t} every write`:`${e.rules} rule${e.rules===1?``:`s`}, else ${t}`;return e.mode===`shadow`&&(n+=` (shadow mode: logged, not enforced)`),e.scoped&&(n+=`, scoped`),n}var o=ka(),s=R(I(o),4),c=e=>{var t=va(),n=L(t,!0);z(()=>Z(n,J(r))),X(e,t)};Q(s,e=>{J(r)&&e(c)});var l=R(s,2),u=e=>{var t=Oa(),r=I(t),i=R(F(r),2),o=e=>{var t=ya(),r=R(F(t)),i=L(F(r),!0);O(r);var a=R(r,3),o=L(F(a));Ce(),O(a);var s=R(a,3),c=L(F(s),!0);O(s);var l=L(R(s,3),!0);O(t),z(()=>{Z(i,J(n).database.database),Z(o,`${J(n).database.host??``}:${J(n).database.port??``}`),Z(c,J(n).database.user),Z(l,J(n).database.tls)}),X(e,t)},s=e=>{X(e,ba())};Q(i,e=>{J(n).database?e(o):e(s,-1)}),O(r);var c=R(r,2),l=R(F(c),2),u=F(l),d=e=>{var t=xa(),r=R(I(t)),i=L(F(r),!0);O(r),z(()=>Z(i,J(n).dir)),X(e,t)};Q(u,e=>{J(n).dir&&e(d)});var f=R(u,3),p=L(f,!0),m=R(f,3),h=L(m,!0),g=R(m,3),_=L(g,!0),v=R(g,2),y=e=>{var t=Sa(),r=L(R(I(t)),!0);z(e=>Z(r,e),[()=>J(n).notify.join(`, `)]),X(e,t)};Q(v,e=>{J(n).notify?.length&&e(y)});var b=R(v,3),x=L(F(b),!0);O(b);var S=L(R(b,3),!0);O(l),O(c);var C=R(c,2);$(R(F(C),2),17,()=>J(n).agents,e=>e.id,(e,t)=>{var n=Da(),r=I(n),i=L(F(r),!0);O(r);var o=R(r,2),s=e=>{var n=Ca(),r=L(n,!0);z(()=>Z(r,J(t).description)),X(e,n)};Q(o,e=>{J(t).description&&e(s)});var c=R(o,2),l=L(c,!0),u=R(c,2),d=e=>{var n=Ta(),r=I(n),i=F(r),a=R(F(i));$(a,21,()=>J(t).connections,e=>e.id,(e,t)=>{var n=wa(),r=F(n),i=L(r,!0),a=R(r),o=L(F(a),!0);O(a);var s=R(a),c=L(s,!0),l=L(R(s),!0);O(n),z((e,n)=>{Z(i,J(t).label),Z(o,J(t).id),Z(c,e),Z(l,n)},[()=>Si(J(t).created_ms),()=>J(t).used_ms?Si(J(t).used_ms):`never`]),X(e,n)}),O(a),O(i),O(r);var o=R(r,2),s=L(R(F(o)));O(o),z(()=>Z(s,`rowbit agents revoke ${J(t).id??``} --connection `)),X(e,n)},f=e=>{X(e,Ea())};Q(u,e=>{J(t).connections.length?e(d):e(f,-1)}),z(e=>{Z(i,J(t).id),Z(l,e)},[()=>a(J(t))]),X(e,n)}),O(C),z(()=>{Z(p,J(n).review_page),Z(h,J(n).sign_in),Z(_,J(n).mcp??`off (agents start rowbit mcp themselves)`),Z(x,J(n).held),Z(S,J(n).version)}),X(e,t)};Q(l,e=>{J(n)&&e(u)}),X(e,o),He()}var ja=Y(`

`),Ma=Y(`

Nothing yet.

`),Na=Y(` `),Pa=Y(` `),Fa=Y(`

#StateAgentWhatWould haveWhen
`,1),Ia=Y(`

Shadow mode

Changes from agents in shadow mode that a rule would have held or blocked. They were applied, and + can be undone from their page.

`,1);function La(e,t){Ve(t,!0);let n=M(null),r=M(``);si(async()=>{try{let{changes:e}=await fi({shadowed:`true`}),t=new Map;for(let n of e){let e=n.shadowed.by;t.has(e)||t.set(e,[]),t.get(e).push(n)}N(n,[...t.entries()].sort((e,t)=>t[1].length-e[1].length),!0)}catch(e){N(r,e.message,!0)}});var i=Ia(),a=R(I(i),4),o=e=>{var t=ja(),n=L(t,!0);z(()=>Z(n,J(r))),X(e,t)};Q(a,e=>{J(r)&&e(o)});var s=R(a,2),c=e=>{X(e,Ma())};Q(s,e=>{J(n)&&J(n).length===0&&e(c)}),$(R(s,2),17,()=>J(n)??[],([e,t])=>e,(e,t)=>{var n=lt(()=>m(J(t),2));let r=()=>J(n)[0],i=()=>J(n)[1];var a=Fa(),o=I(a),s=F(o),c=L(R(s));O(o);var l=R(o,2),u=F(l),d=R(F(u));$(d,21,i,e=>e.id,(e,t)=>{var n=Pa(),r=F(n),i=F(r),a=L(i,!0);O(r);var o=R(r);Ei(F(o),{get state(){return J(t).state}}),O(o);var s=R(o),c=L(s,!0),l=R(s),u=F(l),d=R(u),f=e=>{var n=_r();z(()=>Z(n,J(t).rows)),X(e,n)};Q(d,e=>{J(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=Na(),r=L(n,!0);z(()=>Z(r,J(t).table)),X(e,n)};Q(p,e=>{J(t).table&&e(m)}),O(l);var h=R(l),g=L(h,!0),_=L(R(h),!0);O(n),z(e=>{$r(i,`href`,`#/changes/${J(t).id??``}`),Z(a,J(t).id),Z(c,J(t).agent),Z(u,`${wi[J(t).kind]??``??``} `),Z(g,J(t).shadowed.would??``),Z(_,e)},[()=>Si(J(t).created_at_ms)]),X(e,n)}),O(d),O(u),O(l),z(e=>{Z(s,`${r()??``} `),Z(c,`(${e??``})`)},[()=>xi(i().length,`change`)]),X(e,a)}),X(e,i),He()}var Ra=Y(`Guarding on as `),za=Y(` `,1),Ba=Y(`

Loading...

`),Va=Y(`
Rowbit
`,1);function Ha(e,t){Ve(t,!0);let n=M(``),r=M(null),i=M(Gt(location.hash));si(()=>{di().then(e=>{N(n,e.reviewer,!0),yi().then(e=>N(r,e.database,!0)).catch(()=>{})});let e=()=>N(i,location.hash,!0);return addEventListener(`hashchange`,e),()=>removeEventListener(`hashchange`,e)});let a=lt(()=>J(i).match(/^#\/changes\/(\d+)$/)?.[1]);async function o(){await bi().catch(()=>{}),location.assign(`/login`)}var s=Va(),c=I(s),l=R(F(c),2),u=F(l);let d;var f=R(u,2);let p;var m=R(f,2);let h;var g=R(m,2);let _;var v=R(g,2);let y;O(l);var b=R(l,2),x=e=>{var t=Ra(),n=R(F(t)),i=L(n,!0),a=R(n,2),o=L(a),s=L(R(a,2),!0);O(t),z(()=>{Z(i,J(r).database),Z(o,`${J(r).host??``}:${J(r).port??``}`),Z(s,J(r).user)}),X(e,t)};Q(b,e=>{J(r)&&e(x)});var S=R(b,2),C=F(S),ee=e=>{var t=za(),r=I(t),i=R(r);z(()=>Z(r,`${J(n)??``} `)),cr(`click`,i,o),X(e,t)};Q(C,e=>{J(n)&&e(ee)}),O(S),O(c);var te=R(c,2),ne=F(te),re=e=>{X(e,Ba())},ie=e=>{var t=vr();kr(I(t),()=>J(a),e=>{Yi(e,{get id(){return J(a)}})}),X(e,t)},ae=e=>{la(e,{})},oe=e=>{La(e,{})},se=e=>{ta(e,{})},ce=e=>{Aa(e,{})},le=e=>{_a(e,{})};Q(ne,e=>{J(n)?J(a)?e(ie,1):J(i)===`#/log`?e(ae,2):J(i)===`#/shadow`?e(oe,3):J(i)===`#/notifications`?e(se,4):J(i)===`#/setup`?e(ce,5):e(le,-1):e(re)}),O(te),z(()=>{d=Vr(u,1,``,null,d,{active:J(i)===``||J(i)===`#/`}),p=Vr(f,1,``,null,p,{active:J(i)===`#/log`}),h=Vr(m,1,``,null,h,{active:J(i)===`#/shadow`}),_=Vr(g,1,``,null,_,{active:J(i)===`#/notifications`}),y=Vr(v,1,``,null,y,{active:J(i)===`#/setup`})}),X(e,s),He()}lr([`click`]),Cr(Ha,{target:document.getElementById(`app`)}); \ No newline at end of file diff --git a/crates/rowbit-web/src/api.rs b/crates/rowbit-web/src/api.rs index 0eba154..b691750 100644 --- a/crates/rowbit-web/src/api.rs +++ b/crates/rowbit-web/src/api.rs @@ -184,6 +184,11 @@ fn summary(app: &App, change: &Change) -> Value { ) }) .and_then(|e| e.detail.clone()); + let superseded_by = rowbit_engine::superseded_by(&history); + let reason = reason.or_else(|| { + superseded_by + .map(|n| format!("Replaced by #{n}: the agent proposed the same change again.")) + }); let shadowed = history.iter().find(|e| e.event == "PolicyShadowed"); json!({ "id": change.id, @@ -199,6 +204,8 @@ fn summary(app: &App, change: &Change) -> Value { "kind": kind, "rows": rows, "reason": reason.map(|r| visible(&r)), + // The change that replaced this one, when the agent proposed it again. + "superseded_by": superseded_by, "decided_by": decided.map(|e| visible(&e.actor)), "created_at_ms": change.created_at_ms, }) @@ -364,6 +371,14 @@ pub(crate) async fn approve( Ok(c) => c, Err(e) => return error(StatusCode::NOT_FOUND, &e.to_string()), }; + if change.state != ChangeState::Held + && let Some(n) = rowbit_engine::superseded_by(&app.engine.history(id).unwrap_or_default()) + { + return error( + StatusCode::CONFLICT, + &format!("#{id} was replaced by #{n}, the agent's newer copy; review #{n} instead"), + ); + } if let (_, _, Some(total)) = table_and_kind(&change) && total > CONFIRM_ROWS_ABOVE && body.rows != Some(total) diff --git a/crates/rowbit-web/tests/page.rs b/crates/rowbit-web/tests/page.rs index a024821..8a4e941 100644 --- a/crates/rowbit-web/tests/page.rs +++ b/crates/rowbit-web/tests/page.rs @@ -674,3 +674,53 @@ async fn shadowed_changes_are_listed_apart(var: &str, table: &str) { json!({ "by": "policy:default", "would": "would hold: no rule matched" }) ); } + +// A copy the agent replaced by proposing it again says so, links the new +// one, and can't be approved: the person is sent to the newer copy. +on_both!(a_replaced_copy_points_to_the_new_one); +async fn a_replaced_copy_points_to_the_new_one(var: &str, table: &str) { + let Some((page, url, t)) = setup(var, &format!("{table}_su")).await else { + return; + }; + let sql = format!("UPDATE {t} SET status = 'refunded'"); + let old = page + .engine + .propose("refund-bot", "rowbit_test", &sql) + .await + .unwrap() + .id; + let new = page + .engine + .propose("refund-bot", "rowbit_test", &sql) + .await + .unwrap() + .id; + + let (_, change) = page.api(&format!("/api/changes/{old}")).await; + assert_eq!(change["state"], "rejected", "{change}"); + assert_eq!(change["superseded_by"], new, "{change}"); + assert_eq!( + change["reason"], + format!("Replaced by #{new}: the agent proposed the same change again."), + ); + let review = change["review_hash"].as_str().unwrap().to_owned(); + let token = page.token().await; + let (status, body) = page + .act( + &format!("/api/changes/{old}/approve"), + json!({ "review": review }), + Some(&token), + None, + ) + .await; + assert_eq!(status, StatusCode::CONFLICT); + assert!( + body.to_string().contains(&format!("review #{new} instead")), + "{body}" + ); + assert_eq!(statuses(&url, &t).await, ["approved", "approved"]); + + let (_, change) = page.api(&format!("/api/changes/{new}")).await; + assert_eq!(change["state"], "held"); + assert_eq!(change["superseded_by"], Value::Null); +} diff --git a/docs/spec/change-lifecycle.md b/docs/spec/change-lifecycle.md index 419b77b..255dfab 100644 --- a/docs/spec/change-lifecycle.md +++ b/docs/spec/change-lifecycle.md @@ -60,6 +60,7 @@ stateDiagram-v2 Every other pair is an error. Notable consequences: - `Approve` is only accepted from `held`, and only from a person. A person cannot approve a change that has no preview yet, and a policy rule cannot approve a held change. +- When an agent proposes the same statement (after normalizing) on the same database while its earlier copy is still held, Rowbit withdraws the earlier copy on the agent's behalf (recorded as the agent, the only actor that may withdraw), with a `Superseded` note naming the new change in the same transaction, unless the new one is blocked. A person then sees one copy to decide on, the one the agent is waiting for. The old copy's page links the new one and refuses approval, and its status tells the agent `proposed again as #N`. If withdrawing fails, the new change still stands and the failure is logged. Matching is per agent name, not per connection: two projects connected as the same agent replace each other's held copies. - `Expire` rejects. A timeout never approves. Nothing sends `Expire` yet: held changes wait until a person decides or the agent withdraws. - An `approved` change cannot be rejected: it is applied as soon as it is approved, and a reject would race the write. A cool-down window, if one comes, will add its own state. - Undo is requested by a person. The system performs it and records `UndoSucceeded`. If undo is refused (see below) the change stays `applied` and the refusal is logged. diff --git a/web/src/Change.svelte b/web/src/Change.svelte index 104e29b..52c44f7 100644 --- a/web/src/Change.svelte +++ b/web/src/Change.svelte @@ -62,6 +62,7 @@ {#if c.reason}

{c.reason}

{/if} + {#if c.superseded_by}

Review #{c.superseded_by} instead

{/if} {#if c.shadowed}

Shadow mode: {c.shadowed.would} ({c.shadowed.by}). It was applied anyway.

{/if}
{c.statement}