diff --git a/PLAN.md b/PLAN.md index 05a58389..608d78fa 100644 --- a/PLAN.md +++ b/PLAN.md @@ -68,7 +68,7 @@ Landed so far: |---|---|---| | 0 | #69 | Nothing | | 1 | #71 | Nothing | -| 2 | #73 | An attempt key on every operation; an uncertain external effect blocking reassignment; waiting on an outside event as a state; the record's remaining fields (`parent`, `scores`, `implements`, `reversibility`, `artifacts_touched`, `policy_context`) | +| 2 | #73, and attempt keys for a claim and a completion in the pull request after #74 | An attempt key on the other operations; an uncertain external effect blocking reassignment; waiting on an outside event as a state; the record's remaining fields (`parent`, `scores`, `implements`, `reversibility`, `artifacts_touched`, `policy_context`) | | 3 | the pull request after #73 | A suite that gates a version of a definition; the data scope enforced; a definition required by default, once the system's own filings name one; tools offered a few at boot and the rest by search | What stays unbuilt after these, and stays on README's list: federation across domains, authentication of people, logprob-based drift, and accelerator time, which only a gateway can count. So do the items on that list that need another machine or an outside service more than they need code: the limits on re-enactment, harness settings and decomposition templates in the platform domain, bytes stored and resource limits on the sandbox, the authoring of agent definitions, the search tool against its live provider, a harness under Seatbelt on a Mac, and the Gofannon bridge against a live deployment, whose agents make model calls that the egress does not meter. diff --git a/README.md b/README.md index 2b102984..f704619b 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,8 @@ two weeks. It is also the reason that none of it is the production shape. - CPU or memory limits on the sandbox - **The task record, in part.** A claim has a generation, and the ledger refuses a holder that gives an earlier one. A task can be `blocked` on a task that it depends on. - These parts are not built: an attempt key for each operation, a block on a new claim + A claim and a completion can carry an attempt key. These parts are not built: an + attempt key for the other operations, a block on a new claim while an external effect is uncertain, and a `blocked` state for a task that waits on an outside event. The generation does not prove who the caller is. - **Task definitions, in part, and autonomy levels.** A task can name a task definition. diff --git a/crates/ledger/src/lib.rs b/crates/ledger/src/lib.rs index caf4410b..e5c635d9 100644 --- a/crates/ledger/src/lib.rs +++ b/crates/ledger/src/lib.rs @@ -83,6 +83,14 @@ pub enum Error { /// The generation of the claim in force. current: u64, }, + /// The attempt key was used before, and that attempt is over or was for something + /// else: the same key does not claim or complete a second time. The refusal names + /// the key and nothing of what it was used for. + #[error("the attempt key {key} was used before, and that attempt is over")] + AttemptSpent { + /// The key. + key: String, + }, /// Another connection holds the write-ahead log, so text just removed may still be /// in it. Run the operation again. #[error("ledger: another connection holds the log: {0}")] @@ -290,6 +298,21 @@ pub struct Claim { pub generation: u64, } +/// Who claims, and the attempt key it gave: a retry of the same claim carries the same +/// key, so it is the same claim and not a second one. +#[derive(Clone, Copy, Debug)] +pub(crate) struct Claimant<'a> { + pub(crate) holder: &'a str, + pub(crate) attempt: Option<&'a str>, +} + +/// What a backend's `claim_next` did: claimed a task, or found the claim the caller's +/// attempt key made before, with its generation. +pub(crate) enum Taken { + New(String), + Again { id: String, generation: u64 }, +} + /// A holder and the generation of the claim it acts under. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Held<'a> { @@ -381,6 +404,12 @@ enum Event { holder: String, #[serde(with = "time::serde::rfc3339")] until: OffsetDateTime, + /// The generation this claim got. Absent from a claim of an older ledger. + #[serde(default)] + generation: u64, + /// The attempt key the caller gave, when it gave one. + #[serde(default, skip_serializing_if = "Option::is_none")] + attempt: Option, }, /// A claim of a named task the ledger refused, and why. Changes nothing; it is the /// record that someone tried. @@ -406,6 +435,9 @@ enum Event { }, Completed { handoff: String, + /// The attempt key the caller gave, when it gave one. + #[serde(default, skip_serializing_if = "Option::is_none")] + attempt: Option, }, Invalidated { reason: String, @@ -498,8 +530,9 @@ fn redact_event(id: &str, payload: &str, redaction: &Redaction) -> Result