From b14b50605f4b9c4f7a0e3eba847f1ca238c1e02f Mon Sep 17 00:00:00 2001 From: Ermag Date: Sat, 7 Nov 2026 14:44:37 +0200 Subject: [PATCH 1/2] Proposing the same write again withdraws the older held copy Signed-off-by: Ermag --- CHANGELOG.md | 6 ++++ crates/rowbit-engine/src/lib.rs | 42 ++++++++++++++++++++++++ crates/rowbit-engine/tests/propose.rs | 47 +++++++++++++++++++++++++++ crates/rowbit-mcp/tests/attacks.rs | 4 ++- docs/spec/change-lifecycle.md | 1 + 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbe0fd0..e525a83 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. 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 a2976f9..cb48803 100644 --- a/crates/rowbit-engine/src/lib.rs +++ b/crates/rowbit-engine/src/lib.rs @@ -357,6 +357,9 @@ impl Engine { }; store.transition_notifying(id, &event, &actor, None, ¬ify)?; } + if decision != Decision::Block || shadow { + self.supersede(agent, database, &classified.canonical, id)?; + } if decision == Decision::Allow || shadow { self.apply(id).await?; } @@ -563,6 +566,45 @@ 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`. A copy a person decided on in the + /// meantime is left as it is. + 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 { + continue; + } + let actor = Actor::Agent(agent.to_owned()); + store.note( + id, + &actor, + "Superseded", + &format!("proposed again as #{newer}"), + )?; + match store.transition(id, &Event::Withdraw, &actor, None) { + Ok(_) | Err(StoreError::Transition(_)) => {} + Err(e) => return Err(e.into()), + } + } + Ok(()) + } + /// Changes waiting for a person, oldest first. pub fn held(&self) -> Result, EngineError> { Ok(self.store().list(State::Held)?) diff --git a/crates/rowbit-engine/tests/propose.rs b/crates/rowbit-engine/tests/propose.rs index 5df592c..9da30ce 100644 --- a/crates/rowbit-engine/tests/propose.rs +++ b/crates/rowbit-engine/tests/propose.rs @@ -354,3 +354,50 @@ 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); + let history = engine.history(first.id).unwrap(); + let note = history.iter().find(|e| e.event == "Superseded").unwrap(); + assert_eq!( + note.detail.as_deref(), + Some(format!("proposed again as #{}", second.id).as_str()) + ); + assert_eq!(history.last().unwrap().event, "Withdraw"); + 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 4cf8c39..2c32fd1 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/docs/spec/change-lifecycle.md b/docs/spec/change-lifecycle.md index 419b77b..ea56448 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, with a `Superseded` note naming the new change, unless the new one is blocked. A person then sees one copy to decide on, the one the agent is waiting for. - `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. From 5df849c7c0bbd5cb964b3329f708c10f35ef74ed Mon Sep 17 00:00:00 2001 From: Ermag Date: Sat, 7 Nov 2026 23:04:39 +0200 Subject: [PATCH 2/2] Supersede review fixes: withdraw and note together, never fail the proposal, link the new copy Signed-off-by: Ermag --- CHANGELOG.md | 2 +- crates/rowbit-engine/src/lib.rs | 52 +++++++++++++++++---------- crates/rowbit-engine/tests/propose.rs | 21 ++++++++--- crates/rowbit-store/src/lib.rs | 46 +++++++++++++++++++++++- crates/rowbit-store/src/tests.rs | 51 ++++++++++++++++++++++++++ crates/rowbit-web/dist/assets/app.js | 10 +++--- crates/rowbit-web/src/api.rs | 15 ++++++++ crates/rowbit-web/tests/page.rs | 50 ++++++++++++++++++++++++++ docs/spec/change-lifecycle.md | 2 +- web/src/Change.svelte | 1 + 10 files changed, 219 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e525a83..59cbd6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Written by hand. Each release says what changed for someone running Rowbit, not ### 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. 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. +- 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) diff --git a/crates/rowbit-engine/src/lib.rs b/crates/rowbit-engine/src/lib.rs index cb48803..7e49d95 100644 --- a/crates/rowbit-engine/src/lib.rs +++ b/crates/rowbit-engine/src/lib.rs @@ -358,7 +358,11 @@ impl Engine { store.transition_notifying(id, &event, &actor, None, ¬ify)?; } if decision != Decision::Block || shadow { - self.supersede(agent, database, &classified.canonical, id)?; + // 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?; @@ -569,8 +573,11 @@ impl Engine { /// 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`. A copy a person decided on in the - /// meantime is left as it is. + /// `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, @@ -587,19 +594,8 @@ impl Engine { let same = old.agent == agent && old.database == database && classify(&old.sql).is_ok_and(|c| c.canonical == canonical); - if !same { - continue; - } - let actor = Actor::Agent(agent.to_owned()); - store.note( - id, - &actor, - "Superseded", - &format!("proposed again as #{newer}"), - )?; - match store.transition(id, &Event::Withdraw, &actor, None) { - Ok(_) | Err(StoreError::Transition(_)) => {} - Err(e) => return Err(e.into()), + if same { + store.withdraw_superseded(id, agent, &format!("{SUPERSEDED_PREFIX}{newer}"))?; } } Ok(()) @@ -729,8 +725,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") @@ -740,7 +736,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), @@ -955,6 +952,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 9da30ce..0da40fb 100644 --- a/crates/rowbit-engine/tests/propose.rs +++ b/crates/rowbit-engine/tests/propose.rs @@ -386,13 +386,26 @@ async fn proposing_again_withdraws_the_older_held_copy(var: &str, table: &str) { 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 note = history.iter().find(|e| e.event == "Superseded").unwrap(); + let events: Vec<_> = history + .iter() + .rev() + .take(2) + .map(|e| e.event.as_str()) + .collect(); assert_eq!( - note.detail.as_deref(), - Some(format!("proposed again as #{}", second.id).as_str()) + events, + ["Superseded", "Withdraw"], + "noted after the withdraw" ); - assert_eq!(history.last().unwrap().event, "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(); diff --git a/crates/rowbit-store/src/lib.rs b/crates/rowbit-store/src/lib.rs index cee3a09..f42e01d 100644 --- a/crates/rowbit-store/src/lib.rs +++ b/crates/rowbit-store/src/lib.rs @@ -388,7 +388,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 @@ -401,9 +427,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, @@ -412,6 +442,7 @@ impl Store { reviewed: Option<&str>, applied: Option>, notify: &[NewDelivery], + note: Option<(&str, &str)>, ) -> Result { let tx = self .conn @@ -468,6 +499,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 ee901e7..1856689 100644 --- a/crates/rowbit-store/src/tests.rs +++ b/crates/rowbit-store/src/tests.rs @@ -566,3 +566,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 4fb2e41..989a4a7 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,ee=1<<19,te=1<<20,ne=1<<25,re=1<<21,ie=1<<22,ae=1<<23,oe=Symbol(`$state`),se=Symbol(`component`),ce=Symbol(``),le=Symbol(`attributes`),ue=Symbol(`class`),de=Symbol(`style`),fe=Symbol(`text`),pe=Symbol(`form reset`),me=new class extends Error{name=`StaleReactionError`;message="The reaction that called `getAbortSignal()` was re-run or destroyed"},he=!!globalThis.document?.contentType&&globalThis.document.contentType.includes(`xml`),ge={},C=Symbol(`uninitialized`),_e=`http://www.w3.org/1999/xhtml`;function ve(){console.warn(`https://svelte.dev/e/derived_inert`)}function ye(e){console.warn(`https://svelte.dev/e/hydration_mismatch`)}function be(){console.warn(`https://svelte.dev/e/select_multiple_invalid_value`)}function xe(){console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`)}var w=!1;function Se(e){w=e}var T;function E(e){if(e===null)throw ye(),ge;return T=e}function Ce(){return E(en(T))}function D(e){if(w){if(en(T)!==null)throw ye(),ge;T=e}}function we(e=1){if(w){for(var t=e,n=T;t--;)n=en(n);T=n}}function Te(e=!0){for(var t=0,n=T;;){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 Ee(e){if(!e||e.nodeType!==8)throw ye(),ge;return e.data}function De(e){return e===this.v}function Oe(e,t){return e==e?e!==t||typeof e==`object`&&!!e||typeof e==`function`:t==t}function ke(e){return!Oe(e,this.v)}function Ae(e){throw Error(`https://svelte.dev/e/lifecycle_outside_component`)}function je(){throw Error(`https://svelte.dev/e/async_derived_orphan`)}function Me(e,t,n){throw Error(`https://svelte.dev/e/each_key_duplicate`)}function Ne(e){throw Error(`https://svelte.dev/e/effect_in_teardown`)}function Pe(){throw Error(`https://svelte.dev/e/effect_in_unowned_derived`)}function Fe(e){throw Error(`https://svelte.dev/e/effect_orphan`)}function Ie(){throw Error(`https://svelte.dev/e/effect_update_depth_exceeded`)}function Le(){throw Error(`https://svelte.dev/e/state_descriptors_fixed`)}function Re(){throw Error(`https://svelte.dev/e/state_prototype_fixed`)}function ze(){throw Error(`https://svelte.dev/e/state_unsafe_mutation`)}function Be(){throw Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`)}var O=null;function Ve(e){O=e}function He(e,t=!1,n){O={p:O,i:!1,c:null,e:null,s:e,x:null,r:G,l:null}}function Ue(e){var t=O,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,O=t.p,We(e)}function We(e={}){return i(e,se,{value:!0}),e}function Ge(){return!0}var Ke=[];function qe(){var e=Ke;Ke=[],f(e)}function Je(e){if(Ke.length===0&&!bt){var t=Ke;queueMicrotask(()=>{t===Ke&&qe()})}Ke.push(e)}function Ye(){for(;Ke.length>0;)qe()}var Xe=~(g|_|h);function k(e,t){e.f=e.f&Xe|t}function Ze(e){e.f&512||e.deps===null?k(e,h):k(e,_)}function Qe(e,t,n){e.f&2048?t.add(e):e.f&4096&&n.add(e),k(e,h)}var $e=!1;function et(){$e||($e=!0,document.addEventListener(`reset`,e=>{Promise.resolve().then(()=>{if(!e.defaultPrevented)for(let t of e.target.elements)t[pe]?.()})},{capture:!0}))}function tt(e){var t=H,n=G;W(null),Pn(null);try{return e()}finally{W(t),Pn(n)}}function nt(e,t,n,r=n){e.addEventListener(t,()=>tt(n));let i=e[pe];e[pe]=i?()=>{i(),r(!0)}:()=>r(!0),et()}function rt(e,t,n,r){let i=Ge()?st:dt;var a=e.filter(e=>!e.settled),o=t.map(i);if(n.length===0&&a.length===0){r(o);return}var s=G,c=it(),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)}at()}}var d=ot();if(n.length===0){l.then(()=>u([])).finally(d);return}function f(){Promise.all(n.map(e=>lt(e))).then(u).catch(e=>sn(e,s)).finally(d)}l?l.then(()=>{c(),f(),at()}):f()}function it(){var e=G,t=H,n=O,r=A;return function(i=!0){Pn(e),W(t),Ve(n),i&&!(e.f&16384)&&(r?.activate(),r?.apply())}}function at(e=!0){Pn(null),W(null),Ve(null),e&&A?.deactivate()}function ot(){var e=G,t=e.b,n=A,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 st(e){var t=2|g;return G!==null&&(G.f|=ee),{ctx:O,deps:null,effects:null,equals:De,f:t,fn:e,reactions:null,rv:0,v:C,wv:0,parent:G,ac:null}}var ct=Symbol(`obsolete`);function lt(e,t,n){let r=G;r===null&&je();var i=void 0,a=Lt(C),o=!H,s=new Set;return _n(()=>{var t=G,n=p();i=n.promise;try{Promise.resolve(e()).then(n.resolve,e=>{e!==me&&n.reject(e)}).finally(at)}catch(e){n.reject(e),at()}var c=A;if(o){if(t.f&32768)var l=ot();if(r.b?.is_rendered())c.async_deriveds.get(t)?.reject(ct);else for(let e of s.values())e.reject(ct);s.add(n),c.async_deriveds.set(t,n)}let u=(e,t=void 0)=>{l?.(),s.delete(n),t!==ct&&(c.activate(),t?(a.f|=ae,Vt(a,t)):(a.f&8388608&&(a.f^=ae),Vt(a,e)),c.deactivate())};n.promise.then(u,e=>u(null,e||`unknown`))}),fn(()=>{for(let e of s)e.reject(ct)}),new Promise(e=>{function t(n){function r(){n===i?e(a):t(i)}n.then(r,r)}t(i)})}function ut(e){let t=st(e);return In(t),t}function dt(e){let t=st(e);return t.equals=ke,t}function ft(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n{t.ac.abort(me),t.ac=null}),t.fn!==null&&(t.teardown=d),Jn(t,0),xn(t))}function gt(e){if(e.effects!==null)for(let t of e.effects)t.teardown&&t.fn!==null&&Yn(t)}var _t=null,A=null,vt=null,j=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(){_t===null?_t=this:(_t.#n=this,this.#t=_t),_t=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)k(r,g),t(r);for(r of n.m)k(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),k(e,g),this.schedule(e);for(let e of this.#d)k(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(A=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&&A.#_();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(),vt=this,At(n),At(t),vt=null,this.#s?.resolve();var o=A;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):Un(r)&&(i&16&&this.#d.add(r),Yn(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),k(i,g),this.schedule(i))}}};for(let e of this.current.keys())t(e);this.oncommit(()=>e.discard()),e.#S(),A=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(A===null){let t=A=new e;!xt&&!bt&&Je(()=>{t.#e||t.flush()})}return A}apply(){j=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?_t=e:t.#t=e,this.linked=!1}}};function Dt(e){var t=bt;bt=!0;try{var n;for(e&&(A!==null&&!A.is_fork&&A.flush(),n=e());;){if(Ye(),A===null)return n;A.flush()}}finally{bt=t}}function Ot(){try{Ie()}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||Yn(n)}}kt.clear()}}kt=null}}function jt(e){A.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),k(e,h);for(var n=e.first;n!==null;)Mt(n,t),n=n.next}}function Nt(e){k(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:De,rv:0,wv:0}}function M(e,t){let n=Lt(e,t);return In(n),n}function Rt(e,t=!1,n=!0){let r=Lt(e);return t||(r.equals=ke),r}function N(e,t,n=!1){return H!==null&&(!U||H.f&131072)&&Ge()&&H.f&4325394&&(Fn===null||!Fn.has(e))&&ze(),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&&pt(t),j===null&&Ze(t)}e.wv=Hn(),zt=null,Bt=0,Wt(e,g,n),zt=null,Ge()&&G!==null&&G.f&1024&&!(G.f&96)&&(J===null?Ln([e]):J.push(e)),!r.is_fork&&Pt.size>0&&!It&&Ht()}return t}function Ht(){It=!1;for(let e of Pt){e.f&1024&&k(e,_);let t;try{t=Un(e)}catch{t=!0}t&&Yn(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=Ge(),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(Bn===d)return e();var t=H,n=Bn;W(null),Vn(d);var r=e();return W(t),Vn(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||Le();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(C,u));r.set(t,e),Ut(o)}}else N(n,C),Ut(o);return!0},get(e,n,i){if(n===oe)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]:C),u)),r.set(n,o)),o!==void 0){var c=Y(o);return c===C?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=Y(i);if(a===C)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===oe)return!0;var n=r.get(t),i=n!==void 0&&n.v!==C||Reflect.has(e,t);return(n!==void 0||G!==null&&(!i||a(e,t)?.writable))&&(n===void 0&&(n=f(()=>M(i?Gt(e[t]):C,u)),r.set(t,n)),Y(n)===C)?!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(C,u)),r.set(d+``,p)):N(p,C)}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!==C;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){Y(o);var t=Reflect.ownKeys(e).filter(e=>{var t=r.get(e);return t===void 0||t.v!==C});for(var[n,i]of r)i.v!==C&&!(n in e)&&t.push(n);return t},setPrototypeOf(){Re()}})}function Kt(e){try{if(typeof e==`object`&&e&&oe in e)return e[oe]}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[ue]=void 0,e[le]=null,e[de]=void 0,e.__e=void 0),u(n)&&(n[fe]=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(!w)return $t(e);var n=$t(T);if(n===null)n=T.appendChild(P());else if(t&&n.nodeType!==3){var r=P();return n?.before(r),E(r),r}return t&&an(n),E(n),n}function I(e,t=!1){if(!w){var n=$t(e);return n instanceof Comment&&n.data===``?en(n):n}if(t){if(T?.nodeType!==3){var r=P();return T?.before(r),E(r),r}an(T)}return T}function L(e,t=!1){if(!w)return $t(e);var n=F(e,t);return D(e),n}function R(e,t=1,n=!1){let r=w?T:e;for(var i;t--;)i=r,r=en(r);if(!w)return r;if(n){if(r?.nodeType!==3){var a=P();return r===null?i?.after(a):r.before(a),E(a),a}an(r)}return E(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=G;if(t===null)return H.f|=ae,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){G===null&&(H===null&&Fe(e),Pe()),Mn&&Ne(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=G;n!==null&&n.f&8192&&(e|=v);var r={ctx:O,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};A?.register_created_effect(r);var i=r;if(e&4)St===null?Et.ensure().schedule(r):St.push(r);else if(t!==null){try{Yn(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&&!U}function fn(e){let t=un(8,null);return k(t,h),t.teardown=e,t}function pn(e){cn(`$effect`);var t=G.f;if(!H&&t&32&&O!==null&&!O.i){var n=O;(n.e??=[]).push(e)}else return mn(e)}function mn(e){return un(4|te,e)}function hn(e){Et.ensure();let t=un(64|ee,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(ie|ee,e)}function vn(e,t=0){return un(8|t,e)}function z(e,t=[],n=[],r=[]){rt(r,t,n,t=>{un(8,()=>{e(...t.map(Y))})})}function yn(e,t=0){return un(16|t,e)}function B(e){return un(32|ee,e)}function bn(e){var t=e.teardown;if(t!==null){let n=Mn,r=H;Nn(!0),W(null);try{t.call(null)}catch(t){sn(t,e.parent)}finally{Nn(n),W(r)}}}function xn(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){let e=n.ac;e!==null&&tt(()=>{e.abort(me)});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),Jn(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||(k(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,U=!1;function W(e){H=e}var G=null;function Pn(e){G=e}var Fn=null;function In(e){H!==null&&(H.f&2097152||H.f&2)&&(Fn??=new Set).add(e)}var K=null,q=0,J=null;function Ln(e){J=e}var Rn=1,zn=0,Bn=zn;function Vn(e){Bn=e}function Hn(){return++Rn}function Un(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&&j===null&&k(e,h)}return!1}function Wn(e,t,n=!0){var r=e.reactions;if(r!==null&&!(Fn!==null&&Fn.has(e)))for(var i=0;i{e.ac.abort(me)}),e.ac=null);try{e.f|=re;var u=e.fn,d=u();e.f|=b;var f=Kn(e);if(Ge()&&J!==null&&!U&&f!==null&&!(e.f&6146))for(var p=0;p0)for(t.length=q+K.length,r=0;r{s.ac.abort(me),s.ac=null,k(s,g)}),ht(s),Jn(s,0)}}function Jn(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,Je(()=>{i.__removed||t.addEventListener(e,i,r)})):t.addEventListener(e,i,r),i}function or(e,t,n,r,i){var a={capture:r,passive:i},o=ar(e,t,n,a);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&fn(()=>{o.__removed=!0,t.removeEventListener(e,o,a)})}function sr(e,t,n){(t[nr]??={})[e]=n}function cr(e){for(var t=0;t{ur=!1,lr=null}));var s=0,c=lr===e&&e[nr];if(c){var l=a.indexOf(c);if(l!==-1&&(t===document||t===window)){e[nr]=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=G;W(null),Pn(null);try{for(var p,m=[];o!==null&&o!==t;){try{var h=o[nr]?.[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[nr]=t,delete e.currentTarget,W(d),Pn(f)}}}var fr=globalThis?.window?.trustedTypes&&globalThis.window.trustedTypes.createPolicy(`svelte-trusted-html`,{createHTML:e=>e});function pr(e){return fr?.createHTML(e)??e}function mr(e){var t=rn(`template`);return t.innerHTML=pr(e.replaceAll(``,``)),t.content}function hr(e,t){var n=G;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function X(e,t){var n=!!(t&1),r=!!(t&2),i,a=!e.startsWith(``);return()=>{if(w)return hr(T,null),T;i===void 0&&(i=mr(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;hr(o,s)}else hr(t,t);return t}}function gr(e=``){if(!w){var t=P(e+``);return hr(t,t),t}var n=T;return n.nodeType===3?an(n):(n.before(n=P()),E(n)),hr(n,n),n}function _r(){if(w)return hr(T,null),T;var e=document.createDocumentFragment(),t=document.createComment(``),n=P();return e.append(t,n),hr(t,n),e}function Z(e,t){if(w){var n=G;n.f&32768&&n.nodes.end!==null||(n.nodes.end=T),Ce()}else e!==null&&e.before(t)}function vr(e){let t=0,n=Lt(0),r;return()=>{dn()&&(Y(n),vn(()=>(t===0&&(r=$n(()=>e(()=>Ut(n)))),t+=1,()=>{Je(()=>{--t,t===0&&(r?.(),r=void 0,Ut(n))})})))}}var yr=S|ee;function br(e,t,n,r){new xr(e,t,n,r)}var xr=class{parent;is_pending=!1;transform_error;#e;#t=w?T: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=vr(()=>(this.#m=Lt(this.#l),()=>{this.#m=null}));constructor(e,t,n,r){this.#e=e,this.#n=t,this.#r=e=>{var t=G;t.b=this,t.f|=128,n(e)},this.parent=G.b,this.transform_error=r??this.parent?.transform_error??(e=>e),this.#i=yn(()=>{if(w){let e=this.#t;Ce();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()},yr),w&&(this.#e=T)}#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);Je(r),t&&(this.#s=B(()=>{t(this.#e,()=>e,()=>n)}))}#v(e){var t=!1,n=!1;let r=()=>{t?xe():(t=!0,n&&Be(),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)),Je(()=>{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(A)):this.#u===0&&(this.#e.before(e),this.#c=null,Tn(this.#o,()=>{this.#o=null}),this.#x(A))}))}#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(A)}catch(e){this.error(e)}}#x(e){this.is_pending=!1,e.transfer_effects(this.#f,this.#p)}defer_effect(e){Qe(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=G,n=H,r=O;Pn(this.#i),W(this.#i),Ve(this.#i.ctx);try{return Et.ensure(),e()}finally{Pn(t),W(n),Ve(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,Je(()=>{this.#d=!1,this.#m&&Vt(this.#m,this.#l)}))}get_effect_pending(){return this.#h(),Y(this.#m)}error(e){if(!this.#n.onerror&&!this.#n.failed)throw e;A?.is_fork?(this.#a&&A.skip_effect(this.#a),this.#o&&A.skip_effect(this.#o),this.#s&&A.skip_effect(this.#s),A.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),w&&(E(this.#t),we(),E(Te()));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=G;r.b=this,r.f|=128,t(this.#e,()=>e,()=>n)})}catch(e){return sn(e,this.#i.parent),null}}))};Je(()=>{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 Q(e,t){var n=t==null?``:typeof t==`object`?`${t}`:t;n!==(e[fe]??=e.nodeValue)&&(e[fe]=n,e.nodeValue=`${n}`)}function Sr(e,t){return wr(e,t)}var Cr=new Map;function wr(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());br(s,{pending:()=>{}},t=>{He({});var n=O;if(o&&(n.c=o),a&&(i.$$events=a),w&&hr(t,null),l=e(t,i)||We(),w&&(G.nodes.end=T,T===null||T.nodeType!==8||T.data!==`]`))throw ye(),ge;Ue()},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=Cr.get(n),i=r.get(e);--i==0?(n.removeEventListener(e,dr),r.delete(e),r.size===0&&Cr.delete(n)):r.set(e,i)}ir.delete(d),s!==n&&s.parentNode?.removeChild(s)}});return Tr.set(l,u),l}var Tr=new WeakMap,Er=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=A,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 w&&(this.anchor=T),this.#a(n)}};function $(e,t,n=!1){var r;w&&(r=T,Ce());var i=new Er(e),a=n?S:0;function o(e,t){if(w){var n=Ee(r);if(e!==parseInt(n.substring(1))){var a=Te();E(a),i.anchor=a,Se(!1),i.ensure(e,t),Se(!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 Dr=Symbol(`NaN`);function Or(e,t,n){w&&Ce();var r=new Er(e),i=!Ge();yn(()=>{var e=t();e!==e&&(e=Dr),i&&typeof e==`object`&&e&&(e={}),r.ensure(e,n)})}function kr(e,t){return t}function Ar(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;jr(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()}jr(e,t,!l)}else o={pending:new Set(t),done:new Set},(e.outrogroups??=new Set).add(o)}function jr(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^=ne,Lr(d,null,c)):Dn(d):Tn(d,()=>{d=null})))}function _(e){v.pending.delete(e)}var v={effect:yn(()=>{p=Y(f);var e=p.length;let t=!1;w&&Ee(c)===`[!`!=(e===0)&&(c=Te(),E(c),Se(!1),t=!0);for(var r=new Set,u=A,v=nn(),y=0;ys(c)):(d=B(()=>s(Mr??=P())),d.f|=ne)),e>r.size&&Me(``,``,``),w&&e>0&&E(Te()),!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&&Se(!0),Y(f)}),flags:n,items:l,pending:m,outrogroups:null,fallback:d};h=!1,w&&(c=T)}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 ie=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[ue];if(w||o!==n||o===void 0){var s=Br(n,r,a);w&&s===e.getAttribute(`class`)||(s==null?e.removeAttribute(`class`):t?e.className=s:e.setAttribute(`class`,s)),e[ue]=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 be();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;nt(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,A!==null&&r.add(A)}),gn(()=>{var a=t();if(e===document.activeElement){var o=A;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=he?`link`:`LINK`;function Qr(e){if(w){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[pe]=n,Je(n),et()}}function $r(e,t,n,r){var i=ei(e);w&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===Zr)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[ce]=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[le]??={[Yr]:e.nodeName.includes(`-`),[Xr]:e.namespaceURI===_e}}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;nt(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=ai(e)?oi(a):a,n(a),A!==null&&r.add(A),await Xn(),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))}}}),(w&&e.defaultValue!==e.value||$n(t)==null&&e.value)&&(n(ai(e)?oi(e.value):e.value),A!==null&&r.add(A)),vn(()=>{var n=t();if(e===document.activeElement){var i=A;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){nt(e,`change`,t=>{n(t?e.defaultChecked:e.checked)}),(w&&e.defaultChecked!==e.checked||$n(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){O===null&&Ae(`onMount`),pn(()=>{let t=$n(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(`POST`,`/api/logout`,{});function bi(e,t,n=`${t}s`){return`${e.toLocaleString(`en`)} ${e===1?t:n}`}function xi(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 Si(e){return new Date(e).toLocaleString()}var Ci={insert:`inserts`,update:`updates`,delete:`deletes`,ddl:`changes the schema`},wi=X(` `);function Ti(e,t){var n=wi(),r=L(n,!0);z(()=>{Vr(n,1,`state ${t.state??``}`),Q(r,t.state)}),Z(e,n)}var Ei=X(`-`),Di=X(`NULL`),Oi=X(` `),ki=X(` `),Ai=X(` `);function ji(e,t){He(t,!0);var n=_r(),r=I(n),i=e=>{Z(e,Ei())},a=e=>{Z(e,Di())},o=e=>{var n=Oi(),r=L(n,!0);z(()=>Q(r,t.value.text)),Z(e,n)},s=e=>{var n=ki(),r=L(n,!0);z(()=>Q(r,t.value.text)),Z(e,n)},c=e=>{var n=Ai(),r=L(n);z(()=>Q(r,`"${t.value.text??``}"`)),Z(e,n)};$(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)}),Z(e,n),Ue()}var Mi=X(``),Ni=X(`

`),Pi=X(`

`),Fi=X(`

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

 
`,1),Ii=X(``,1),Li=X(`new`),Ri=X(` `),zi=X(`

`),Bi=X(`

in

RowColumnBeforeAfter
`,1),Vi=X(``),Hi=X(`

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

 

History

WhenEventStateByDetail
`,1),Ki=X(`

Back to the queue

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

`),Yi=X(`

Nothing sent yet.

`),Xi=X(` `),Zi=X(`
ChangeTargetStateTriesLast errorQueued
`),Qi=X(`

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 $i(e,t){He(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=Qi(),o=R(I(a),4),s=e=>{var t=Ji(),n=L(t,!0);z(()=>Q(n,Y(r))),Z(e,t)};$(o,e=>{Y(r)&&e(s)});var c=R(o,2),l=e=>{Z(e,Yi())};$(c,e=>{Y(n)&&Y(n).length===0&&e(l)});var u=R(c,2),d=e=>{var t=Zi(),r=F(t),i=R(F(r));Nr(i,21,()=>Y(n),e=>e.id,(e,t)=>{var n=Xi(),r=F(n),i=F(r),a=L(i);D(r);var o=R(r),s=L(o,!0),c=R(o),l=F(c),u=L(l,!0);D(c);var d=R(c),f=L(d,!0),p=R(d),m=L(p,!0),h=L(R(p),!0);D(n),z(e=>{$r(i,`href`,`#/changes/${Y(t).change_id??``}`),Q(a,`#${Y(t).change_id??``}`),Q(s,Y(t).target),Vr(l,1,`state ${Y(t).state===`delivered`?`applied`:Y(t).state===`failed`?`failed`:`held`}`),Q(u,Y(t).state),Q(f,Y(t).attempts),Q(m,Y(t).last_error??``),Q(h,e)},[()=>xi(Y(t).created_at_ms)]),Z(e,n)}),D(i),D(r),D(t),Z(e,t)};$(u,e=>{Y(n)&&Y(n).length&&e(d)}),Z(e,a),Ue()}var ea=X(`

The audit log's hash chain checks out.

`),ta=X(``),na=X(``),ra=X(`

`),ia=X(` `),aa=X(` `),oa=X(`

Log

#StateAgentWhatDecided byReasonWhen
`,1);function sa(e,t){He(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=oa(),l=R(I(c),2),u=e=>{var t=_r(),n=I(t),r=e=>{Z(e,ea())},i=e=>{var t=ta(),n=L(t);z(()=>Q(n,`The audit log doesn't check out: ${Y(a).problem??``}`)),Z(e,t)};$(n,e=>{Y(a).ok?e(r):e(i,-1)}),Z(e,t)};$(l,e=>{Y(a)&&e(u)});var d=R(l,2),f=F(d),p=R(F(f));Nr(p,20,()=>n,e=>e,(e,t)=>{var n=na(),r=L(n,!0),i={};z(()=>{Q(r,t||`any`),i!==(i=t)&&(n.value=(n.__value=i)??``)}),Z(e,n)}),D(p),Gr(p),D(f);var m=R(f,2),h=R(F(m));Qr(h),D(m);var g=R(m,2),_=R(F(g));Qr(_),D(g),we(2),D(d);var v=R(d,2),y=e=>{var t=ra(),n=L(t,!0);z(()=>Q(n,Y(o))),Z(e,t)};$(v,e=>{Y(o)&&e(y)});var b=R(v,2),x=F(b),S=R(F(x));Nr(S,21,()=>Y(i),e=>e.id,(e,t)=>{var n=aa(),r=F(n),i=F(r),a=L(i,!0);D(r);var o=R(r);Ti(F(o),{get state(){return Y(t).state}}),D(o);var s=R(o),c=L(s,!0),l=R(s),u=F(l),d=R(u),f=e=>{var n=gr();z(()=>Q(n,Y(t).rows)),Z(e,n)};$(d,e=>{Y(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=ia(),r=L(n,!0);z(()=>Q(r,Y(t).table)),Z(e,n)};$(p,e=>{Y(t).table&&e(m)}),D(l);var h=R(l),g=L(h,!0),_=R(h),v=L(_,!0),y=L(R(_),!0);D(n),z(e=>{$r(i,`href`,`#/changes/${Y(t).id??``}`),Q(a,Y(t).id),Q(c,Y(t).agent),Q(u,`${Ci[Y(t).kind]??``??``} `),Q(g,Y(t).decided_by??``),Q(v,Y(t).reason??``),Q(y,e)},[()=>xi(Y(t).created_at_ms)]),Z(e,n)}),D(S),D(x),D(b),or(`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),Z(e,c),Ue()}var ca=X(`

`),la=X(`

Nothing is waiting.

`),ua=X(` in`,1),da=X(` `),fa=X(`
`),pa=X(`
`),ma=X(`

Waiting for a person

`,1);function ha(e,t){He(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=ma(),s=I(o),c=R(F(s)),l=e=>{var t=gr();z(()=>Q(t,`(${Y(n).length??``})`)),Z(e,t)};$(c,e=>{Y(n)&&e(l)}),D(s);var u=R(s,2),d=e=>{var t=ca(),n=L(t);z(e=>Q(n,`Changes ${e??``} were interrupted while applying or undoing. Compare their rows - with the preview before anything else.`),[()=>Y(r).join(`, `)]),Z(e,t)};$(u,e=>{Y(r).length&&e(d)});var f=R(u,2),p=e=>{var t=ca(),n=L(t,!0);z(()=>Q(n,Y(i))),Z(e,t)};$(f,e=>{Y(i)&&e(p)});var m=R(f,2),h=e=>{Z(e,la())};$(m,e=>{Y(n)&&Y(n).length===0&&e(h)}),Nr(R(m,2),17,()=>Y(n)??[],e=>e.id,(e,t)=>{var n=pa(),r=F(n),i=F(r),a=L(i),o=R(i,2),s=L(o,!0),c=L(R(o,2),!0);D(r);var l=R(r,2),u=F(l),d=R(u),f=e=>{var n=ua(),r=L(I(n),!0);we(),z(e=>Q(r,e),[()=>bi(Y(t).rows,`row`)]),Z(e,n)};$(d,e=>{Y(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=da(),r=L(n,!0);z(()=>Q(r,Y(t).table)),Z(e,n)};$(p,e=>{Y(t).table&&e(m)}),D(l);var h=R(l,2),g=e=>{var n=fa(),r=L(n);z(()=>Q(r,`Held: ${Y(t).reason??``}`)),Z(e,n)};$(h,e=>{Y(t).reason&&e(g)}),D(n),z(e=>{$r(n,`href`,`#/changes/${Y(t).id??``}`),Q(a,`#${Y(t).id??``}`),Q(s,Y(t).agent),Q(c,e),Q(u,`${Ci[Y(t).kind]??`writes`??``} `)},[()=>xi(Y(t).created_at_ms)]),Z(e,n)}),Z(e,o),Ue()}var ga=X(`

`),_a=X(`

Nothing yet.

`),va=X(` `),ya=X(` `),ba=X(`

#StateAgentWhatWould haveWhen
`,1),xa=X(`

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 Sa(e,t){He(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=xa(),a=R(I(i),4),o=e=>{var t=ga(),n=L(t,!0);z(()=>Q(n,Y(r))),Z(e,t)};$(a,e=>{Y(r)&&e(o)});var s=R(a,2),c=e=>{Z(e,_a())};$(s,e=>{Y(n)&&Y(n).length===0&&e(c)}),Nr(R(s,2),17,()=>Y(n)??[],([e,t])=>e,(e,t)=>{var n=ut(()=>m(Y(t),2));let r=()=>Y(n)[0],i=()=>Y(n)[1];var a=ba(),o=I(a),s=F(o),c=L(R(s));D(o);var l=R(o,2),u=F(l),d=R(F(u));Nr(d,21,i,e=>e.id,(e,t)=>{var n=ya(),r=F(n),i=F(r),a=L(i,!0);D(r);var o=R(r);Ti(F(o),{get state(){return Y(t).state}}),D(o);var s=R(o),c=L(s,!0),l=R(s),u=F(l),d=R(u),f=e=>{var n=gr();z(()=>Q(n,Y(t).rows)),Z(e,n)};$(d,e=>{Y(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=va(),r=L(n,!0);z(()=>Q(r,Y(t).table)),Z(e,n)};$(p,e=>{Y(t).table&&e(m)}),D(l);var h=R(l),g=L(h,!0),_=L(R(h),!0);D(n),z(e=>{$r(i,`href`,`#/changes/${Y(t).id??``}`),Q(a,Y(t).id),Q(c,Y(t).agent),Q(u,`${Ci[Y(t).kind]??``??``} `),Q(g,Y(t).shadowed.would??``),Q(_,e)},[()=>xi(Y(t).created_at_ms)]),Z(e,n)}),D(d),D(u),D(l),z(e=>{Q(s,`${r()??``} `),Q(c,`(${e??``})`)},[()=>bi(i().length,`change`)]),Z(e,a)}),Z(e,i),Ue()}var Ca=X(` `,1),wa=X(`

Loading...

`),Ta=X(`
Rowbit
`,1);function Ea(e,t){He(t,!0);let n=M(``),r=M(Gt(location.hash));si(()=>{di().then(e=>N(n,e.reviewer,!0));let e=()=>N(r,location.hash,!0);return addEventListener(`hashchange`,e),()=>removeEventListener(`hashchange`,e)});let i=ut(()=>Y(r).match(/^#\/changes\/(\d+)$/)?.[1]);async function a(){await yi().catch(()=>{}),location.assign(`/login`)}var o=Ta(),s=I(o),c=R(F(s),2),l=F(c);let u;var d=R(l,2);let f;var p=R(d,2);let m;var h=R(p,2);let g;D(c);var _=R(c,2),v=F(_),y=e=>{var t=Ca(),r=I(t),i=R(r);z(()=>Q(r,`${Y(n)??``} `)),sr(`click`,i,a),Z(e,t)};$(v,e=>{Y(n)&&e(y)}),D(_),D(s);var b=R(s,2),x=F(b),S=e=>{Z(e,wa())},ee=e=>{var t=_r();Or(I(t),()=>Y(i),e=>{qi(e,{get id(){return Y(i)}})}),Z(e,t)},te=e=>{sa(e,{})},ne=e=>{Sa(e,{})},re=e=>{$i(e,{})},ie=e=>{ha(e,{})};$(x,e=>{Y(n)?Y(i)?e(ee,1):Y(r)===`#/log`?e(te,2):Y(r)===`#/shadow`?e(ne,3):Y(r)===`#/notifications`?e(re,4):e(ie,-1):e(S)}),D(b),z(()=>{u=Vr(l,1,``,null,u,{active:Y(r)===``||Y(r)===`#/`}),f=Vr(d,1,``,null,f,{active:Y(r)===`#/log`}),m=Vr(p,1,``,null,m,{active:Y(r)===`#/shadow`}),g=Vr(h,1,``,null,g,{active:Y(r)===`#/notifications`})}),Z(e,o),Ue()}cr([`click`]),Sr(Ea,{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[ue];if(w||o!==n||o===void 0){var s=Br(n,r,a);w&&s===e.getAttribute(`class`)||(s==null?e.removeAttribute(`class`):t?e.className=s:e.setAttribute(`class`,s)),e[ue]=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 be();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;nt(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,A!==null&&r.add(A)}),gn(()=>{var a=t();if(e===document.activeElement){var o=A;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=he?`link`:`LINK`;function Qr(e){if(w){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[pe]=n,Je(n),et()}}function $r(e,t,n,r){var i=ei(e);w&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===Zr)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[ce]=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[le]??={[Yr]:e.nodeName.includes(`-`),[Xr]:e.namespaceURI===_e}}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;nt(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=ai(e)?oi(a):a,n(a),A!==null&&r.add(A),await Xn(),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))}}}),(w&&e.defaultValue!==e.value||$n(t)==null&&e.value)&&(n(ai(e)?oi(e.value):e.value),A!==null&&r.add(A)),vn(()=>{var n=t();if(e===document.activeElement){var i=A;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){nt(e,`change`,t=>{n(t?e.defaultChecked:e.checked)}),(w&&e.defaultChecked!==e.checked||$n(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){O===null&&Ae(`onMount`),pn(()=>{let t=$n(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(`POST`,`/api/logout`,{});function bi(e,t,n=`${t}s`){return`${e.toLocaleString(`en`)} ${e===1?t:n}`}function xi(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 Si(e){return new Date(e).toLocaleString()}var Ci={insert:`inserts`,update:`updates`,delete:`deletes`,ddl:`changes the schema`},wi=X(` `);function Ti(e,t){var n=wi(),r=L(n,!0);z(()=>{Vr(n,1,`state ${t.state??``}`),Q(r,t.state)}),Z(e,n)}var Ei=X(`-`),Di=X(`NULL`),Oi=X(` `),ki=X(` `),Ai=X(` `);function ji(e,t){He(t,!0);var n=_r(),r=I(n),i=e=>{Z(e,Ei())},a=e=>{Z(e,Di())},o=e=>{var n=Oi(),r=L(n,!0);z(()=>Q(r,t.value.text)),Z(e,n)},s=e=>{var n=ki(),r=L(n,!0);z(()=>Q(r,t.value.text)),Z(e,n)},c=e=>{var n=Ai(),r=L(n);z(()=>Q(r,`"${t.value.text??``}"`)),Z(e,n)};$(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)}),Z(e,n),Ue()}var Mi=X(``),Ni=X(`

`),Pi=X(`

`),Fi=X(`

`),Ii=X(`

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

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

`),Vi=X(`

in

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

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

 

History

WhenEventStateByDetail
`,1),qi=X(`

Back to the queue

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

`),Xi=X(`

Nothing sent yet.

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

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){He(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(()=>Q(n,Y(r))),Z(e,t)};$(o,e=>{Y(r)&&e(s)});var c=R(o,2),l=e=>{Z(e,Xi())};$(c,e=>{Y(n)&&Y(n).length===0&&e(l)});var u=R(c,2),d=e=>{var t=Qi(),r=F(t),i=R(F(r));Nr(i,21,()=>Y(n),e=>e.id,(e,t)=>{var n=Zi(),r=F(n),i=F(r),a=L(i);D(r);var o=R(r),s=L(o,!0),c=R(o),l=F(c),u=L(l,!0);D(c);var d=R(c),f=L(d,!0),p=R(d),m=L(p,!0),h=L(R(p),!0);D(n),z(e=>{$r(i,`href`,`#/changes/${Y(t).change_id??``}`),Q(a,`#${Y(t).change_id??``}`),Q(s,Y(t).target),Vr(l,1,`state ${Y(t).state===`delivered`?`applied`:Y(t).state===`failed`?`failed`:`held`}`),Q(u,Y(t).state),Q(f,Y(t).attempts),Q(m,Y(t).last_error??``),Q(h,e)},[()=>xi(Y(t).created_at_ms)]),Z(e,n)}),D(i),D(r),D(t),Z(e,t)};$(u,e=>{Y(n)&&Y(n).length&&e(d)}),Z(e,a),Ue()}var ta=X(`

The audit log's hash chain checks out.

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

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

Log

#StateAgentWhatDecided byReasonWhen
`,1);function ca(e,t){He(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=_r(),n=I(t),r=e=>{Z(e,ta())},i=e=>{var t=na(),n=L(t);z(()=>Q(n,`The audit log doesn't check out: ${Y(a).problem??``}`)),Z(e,t)};$(n,e=>{Y(a).ok?e(r):e(i,-1)}),Z(e,t)};$(l,e=>{Y(a)&&e(u)});var d=R(l,2),f=F(d),p=R(F(f));Nr(p,20,()=>n,e=>e,(e,t)=>{var n=ra(),r=L(n,!0),i={};z(()=>{Q(r,t||`any`),i!==(i=t)&&(n.value=(n.__value=i)??``)}),Z(e,n)}),D(p),Gr(p),D(f);var m=R(f,2),h=R(F(m));Qr(h),D(m);var g=R(m,2),_=R(F(g));Qr(_),D(g),we(2),D(d);var v=R(d,2),y=e=>{var t=ia(),n=L(t,!0);z(()=>Q(n,Y(o))),Z(e,t)};$(v,e=>{Y(o)&&e(y)});var b=R(v,2),x=F(b),S=R(F(x));Nr(S,21,()=>Y(i),e=>e.id,(e,t)=>{var n=oa(),r=F(n),i=F(r),a=L(i,!0);D(r);var o=R(r);Ti(F(o),{get state(){return Y(t).state}}),D(o);var s=R(o),c=L(s,!0),l=R(s),u=F(l),d=R(u),f=e=>{var n=gr();z(()=>Q(n,Y(t).rows)),Z(e,n)};$(d,e=>{Y(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=aa(),r=L(n,!0);z(()=>Q(r,Y(t).table)),Z(e,n)};$(p,e=>{Y(t).table&&e(m)}),D(l);var h=R(l),g=L(h,!0),_=R(h),v=L(_,!0),y=L(R(_),!0);D(n),z(e=>{$r(i,`href`,`#/changes/${Y(t).id??``}`),Q(a,Y(t).id),Q(c,Y(t).agent),Q(u,`${Ci[Y(t).kind]??``??``} `),Q(g,Y(t).decided_by??``),Q(v,Y(t).reason??``),Q(y,e)},[()=>xi(Y(t).created_at_ms)]),Z(e,n)}),D(S),D(x),D(b),or(`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),Z(e,c),Ue()}var la=X(`

`),ua=X(`

Nothing is waiting.

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

Waiting for a person

`,1);function ga(e,t){He(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=gr();z(()=>Q(t,`(${Y(n).length??``})`)),Z(e,t)};$(c,e=>{Y(n)&&e(l)}),D(s);var u=R(s,2),d=e=>{var t=la(),n=L(t);z(e=>Q(n,`Changes ${e??``} were interrupted while applying or undoing. Compare their rows + with the preview before anything else.`),[()=>Y(r).join(`, `)]),Z(e,t)};$(u,e=>{Y(r).length&&e(d)});var f=R(u,2),p=e=>{var t=la(),n=L(t,!0);z(()=>Q(n,Y(i))),Z(e,t)};$(f,e=>{Y(i)&&e(p)});var m=R(f,2),h=e=>{Z(e,ua())};$(m,e=>{Y(n)&&Y(n).length===0&&e(h)}),Nr(R(m,2),17,()=>Y(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);D(r);var l=R(r,2),u=F(l),d=R(u),f=e=>{var n=da(),r=L(I(n),!0);we(),z(e=>Q(r,e),[()=>bi(Y(t).rows,`row`)]),Z(e,n)};$(d,e=>{Y(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=fa(),r=L(n,!0);z(()=>Q(r,Y(t).table)),Z(e,n)};$(p,e=>{Y(t).table&&e(m)}),D(l);var h=R(l,2),g=e=>{var n=pa(),r=L(n);z(()=>Q(r,`Held: ${Y(t).reason??``}`)),Z(e,n)};$(h,e=>{Y(t).reason&&e(g)}),D(n),z(e=>{$r(n,`href`,`#/changes/${Y(t).id??``}`),Q(a,`#${Y(t).id??``}`),Q(s,Y(t).agent),Q(c,e),Q(u,`${Ci[Y(t).kind]??`writes`??``} `)},[()=>xi(Y(t).created_at_ms)]),Z(e,n)}),Z(e,o),Ue()}var _a=X(`

`),va=X(`

Nothing yet.

`),ya=X(` `),ba=X(` `),xa=X(`

#StateAgentWhatWould haveWhen
`,1),Sa=X(`

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 Ca(e,t){He(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=Sa(),a=R(I(i),4),o=e=>{var t=_a(),n=L(t,!0);z(()=>Q(n,Y(r))),Z(e,t)};$(a,e=>{Y(r)&&e(o)});var s=R(a,2),c=e=>{Z(e,va())};$(s,e=>{Y(n)&&Y(n).length===0&&e(c)}),Nr(R(s,2),17,()=>Y(n)??[],([e,t])=>e,(e,t)=>{var n=ut(()=>m(Y(t),2));let r=()=>Y(n)[0],i=()=>Y(n)[1];var a=xa(),o=I(a),s=F(o),c=L(R(s));D(o);var l=R(o,2),u=F(l),d=R(F(u));Nr(d,21,i,e=>e.id,(e,t)=>{var n=ba(),r=F(n),i=F(r),a=L(i,!0);D(r);var o=R(r);Ti(F(o),{get state(){return Y(t).state}}),D(o);var s=R(o),c=L(s,!0),l=R(s),u=F(l),d=R(u),f=e=>{var n=gr();z(()=>Q(n,Y(t).rows)),Z(e,n)};$(d,e=>{Y(t).rows!=null&&e(f)});var p=R(d,2),m=e=>{var n=ya(),r=L(n,!0);z(()=>Q(r,Y(t).table)),Z(e,n)};$(p,e=>{Y(t).table&&e(m)}),D(l);var h=R(l),g=L(h,!0),_=L(R(h),!0);D(n),z(e=>{$r(i,`href`,`#/changes/${Y(t).id??``}`),Q(a,Y(t).id),Q(c,Y(t).agent),Q(u,`${Ci[Y(t).kind]??``??``} `),Q(g,Y(t).shadowed.would??``),Q(_,e)},[()=>xi(Y(t).created_at_ms)]),Z(e,n)}),D(d),D(u),D(l),z(e=>{Q(s,`${r()??``} `),Q(c,`(${e??``})`)},[()=>bi(i().length,`change`)]),Z(e,a)}),Z(e,i),Ue()}var wa=X(` `,1),Ta=X(`

Loading...

`),Ea=X(`
Rowbit
`,1);function Da(e,t){He(t,!0);let n=M(``),r=M(Gt(location.hash));si(()=>{di().then(e=>N(n,e.reviewer,!0));let e=()=>N(r,location.hash,!0);return addEventListener(`hashchange`,e),()=>removeEventListener(`hashchange`,e)});let i=ut(()=>Y(r).match(/^#\/changes\/(\d+)$/)?.[1]);async function a(){await yi().catch(()=>{}),location.assign(`/login`)}var o=Ea(),s=I(o),c=R(F(s),2),l=F(c);let u;var d=R(l,2);let f;var p=R(d,2);let m;var h=R(p,2);let g;D(c);var _=R(c,2),v=F(_),y=e=>{var t=wa(),r=I(t),i=R(r);z(()=>Q(r,`${Y(n)??``} `)),sr(`click`,i,a),Z(e,t)};$(v,e=>{Y(n)&&e(y)}),D(_),D(s);var b=R(s,2),x=F(b),S=e=>{Z(e,Ta())},ee=e=>{var t=_r();Or(I(t),()=>Y(i),e=>{Ji(e,{get id(){return Y(i)}})}),Z(e,t)},te=e=>{ca(e,{})},ne=e=>{Ca(e,{})},re=e=>{ea(e,{})},ie=e=>{ga(e,{})};$(x,e=>{Y(n)?Y(i)?e(ee,1):Y(r)===`#/log`?e(te,2):Y(r)===`#/shadow`?e(ne,3):Y(r)===`#/notifications`?e(re,4):e(ie,-1):e(S)}),D(b),z(()=>{u=Vr(l,1,``,null,u,{active:Y(r)===``||Y(r)===`#/`}),f=Vr(d,1,``,null,f,{active:Y(r)===`#/log`}),m=Vr(p,1,``,null,m,{active:Y(r)===`#/shadow`}),g=Vr(h,1,``,null,g,{active:Y(r)===`#/notifications`})}),Z(e,o),Ue()}cr([`click`]),Sr(Da,{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 7f12be6..26532bf 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 511e2ac..c614680 100644 --- a/crates/rowbit-web/tests/page.rs +++ b/crates/rowbit-web/tests/page.rs @@ -663,3 +663,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 ea56448..255dfab 100644 --- a/docs/spec/change-lifecycle.md +++ b/docs/spec/change-lifecycle.md @@ -60,7 +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, with a `Superseded` note naming the new change, unless the new one is blocked. A person then sees one copy to decide on, the one the agent is waiting for. +- 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}