pub struct Questions { /* private fields */ }Expand description
A question store on disk.
Implementations§
Source§impl Questions
impl Questions
Sourcepub fn at(root: PathBuf) -> Self
pub fn at(root: PathBuf) -> Self
A store at an explicit root. Tests use this, which is why none of them need the operator’s real home.
Sourcepub fn panel_dir(&self, id: &str) -> PathBuf
pub fn panel_dir(&self, id: &str) -> PathBuf
Directory holding one question’s panel, <root>/<id>.panel.
Sourcepub fn put_panel(
&self,
q: &mut Question,
html: &str,
assets: &[PathBuf],
) -> Result<()>
pub fn put_panel( &self, q: &mut Question, html: &str, assets: &[PathBuf], ) -> Result<()>
Store a panel: the html, plus assets copied in under their base
names. Updates q.panel and q.assets; the caller then puts the
question, or the record on disk will deny having a panel that exists.
The assets are copied, not referenced. An agent authors its panel
inside a candidate worktree and points at files there, and magi fold
deletes those worktrees; a question is the permanent record of a
decision the owner took, so a panel that referenced its own images
would render as broken boxes exactly when someone went back to ask why
the decision was made. Copying follows symlinks - std::fs::copy
does, and so does the std::fs::metadata the size is measured with,
so the bytes counted and the bytes written are the same target file’s -
which is the intent: storing a link would leave the panel pointing at
the worktree again, one indirection further away.
Everything that can be rejected is rejected before the first byte is written, and the panel is then assembled in a scratch directory and swapped in. So a refusal leaves the previous panel intact, and a success replaces it wholesale rather than merging: a re-asked question showing one attempt’s diff next to another attempt’s table would be a panel neither agent ever wrote.
Sourcepub fn panel_html(&self, id: &str) -> Option<String>
pub fn panel_html(&self, id: &str) -> Option<String>
The panel’s html, or None when the question has no panel.
None rather than an error for a missing panel because the caller is a
web handler whose answer is 404 either way, and an unreadable panel is
not a reason to fail the question it belongs to.
Sourcepub fn panel_asset(&self, id: &str, name: &str) -> Result<Option<Vec<u8>>>
pub fn panel_asset(&self, id: &str, name: &str) -> Result<Option<Vec<u8>>>
One file from a panel. Ok(None) is “no such file”; Err is “that is
not a name a panel file can have”.
Rejects a name failing valid_asset_name before touching the
filesystem, which is the whole point of the second check: the name
arrives from a URL, the directory is on disk where any process could
have dropped a file, and <root>/<id>.panel/../../id_rsa is a path the
operating system would resolve perfectly happily. The two callers’
distinct outcomes - 400 for a name, 404 for a file - are why this is
Result<Option<_>> rather than one flattened Option.
Sourcepub fn drop_panel(&self, id: &str) -> Result<()>
pub fn drop_panel(&self, id: &str) -> Result<()>
Delete a question’s panel, and any scratch a killed put_panel left.
Succeeds when there is nothing to delete, so a caller cleaning up does
not have to know whether a panel was ever written. The question record
is not touched: the caller clears panel and assets and puts it,
in the same order as everywhere else here.
Sourcepub fn put(&self, q: &mut Question) -> Result<()>
pub fn put(&self, q: &mut Question) -> Result<()>
Write a question, atomically, so a process killed mid-write leaves the previous state readable rather than a truncated file that would strand the run waiting on it.
Sourcepub fn get(&self, id: &str) -> Result<Question>
pub fn get(&self, id: &str) -> Result<Question>
Load a question by id or unambiguous id prefix.
Sourcepub fn list(&self) -> Vec<Question>
pub fn list(&self) -> Vec<Question>
Every question on disk: open first, then newest first.
Open first because that ordering is the product - the list exists to show the operator what has stopped, and an answered question is history underneath it. Unreadable files are skipped rather than fatal: one corrupt question must not take the web UI down, and must certainly not hide the open question the operator was looking for.
Sourcepub fn open_for(&self, run: &str) -> Vec<Question>
pub fn open_for(&self, run: &str) -> Vec<Question>
Open questions belonging to one run, newest first.
Used to decide whether a parked run can be resumed: while this is non-empty, nothing about the run has changed and no agent should be spawned for it.
Sourcepub fn abandon_for_run(&self, run: &str, why: &str) -> Result<usize>
pub fn abandon_for_run(&self, run: &str, why: &str) -> Result<usize>
Abandon every open question belonging to a run, and report how many.
Called when a run’s record is deleted. The agent that asked died with the run, so there is nobody left to hand an answer to, and a question left open would keep asking the operator for a decision that can no longer be delivered - the phone showed exactly that: “auth.rs というファ イルが見つかりません” with two buttons, for a run whose directory had been gone for two hours.
Abandoned rather than deleted, because Question::abandon already
means “this can no longer be answered” and the record of having asked
is worth keeping. Answered questions are left exactly as they are.
Sourcepub fn resolve_id(&self, prefix: &str) -> Result<String>
pub fn resolve_id(&self, prefix: &str) -> Result<String>
Expand an id prefix to exactly one question id. The short id the phone and the reports show is a suffix, so that is accepted too.
Sourcepub fn revision(&self) -> u64
pub fn revision(&self) -> u64
Newest modification time in the store, in milliseconds, for change
detection. The web UI compares this instead of re-reading every
question, so an idle phone on a slow link costs one stat per file.
Sourcepub fn count_open(&self) -> usize
pub fn count_open(&self) -> usize
How many questions are waiting on the owner. The badge on the phone, and the one number that says whether magi is blocked on a human.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Questions
impl RefUnwindSafe for Questions
impl Send for Questions
impl Sync for Questions
impl Unpin for Questions
impl UnsafeUnpin for Questions
impl UnwindSafe for Questions
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more