Identities for entities did.bot
agent llm did
didbot plan e-stop.md
7.0 kB
Markdown
at commit 18ba4fe0


id: e-stop title: An operator can halt the swarm when nothing else is working status: open crates: [didbot-serve, didbot-pds] dependsOn: [] exitCriterion: > With the owner's server unreachable and the web surface down, an operator halts token issuance and provisioning, and can see what is being refused. #

e-stop #

Two settings, because they answer different questions. Pause stops new tokens and new provisioning and leaves outstanding work alone. Revoke stops outstanding work too, which is not undone by releasing it.

The reason this is its own epic is availability, not mechanism. Policy lives in the owner's repository and arrives on a poll, so a policy-level stop waits for a network, a server somebody else runs, and an interval. That is correct for a rule and useless for an emergency. The stop is local state, and it is the only control that is.

  • One fact decides most of it. A halt narrower than "everything" must leave
    some provisioning permitted, and `bot.did.provisionAgent` authenticates a
    host and nothing narrower: the daemon on a host signs for every context
    on it, and the profile the request carries is the caller's word about
    itself. A halted agent that can still provision takes a fresh account
    under the same host and carries on. So a selector narrower than a host is
    not merely weaker than "everything" — it reads as a halt while being
    none.
    
    - **One node.** The value is the host a claim names, bound to a key that
      host alone holds and recorded as `Assurance::NodeCredential`.
      Provisioning refuses a host with a lock on its account, which is how
      one node is stopped; see `didbot_pds::lockout` and `estop.rs`'s own
      module doc.
    - **One lineage subtree.** An attested context's parent is its host, and
      a registration naming any other parent is refused
      (`ProvisionError::ParentDisagrees`). The daemon names the host as the
      parent of every context, a subagent's included, so below the host
      there is no parent link the server checked. A subtree narrower than a
      host is the harness's word. Refused.
    - **One app.** The only one of the three whose value is sound. A
      `client_id`'s origin is established by this server fetching a metadata
      document from it over TLS, the app is not the agent, and no agent can
      move its own requests to another origin. Note that the selector has to
      be the origin and not `ClientKey`, which a halted app changes by
      republishing its document at the same admitted host. Refused anyway,
      on other grounds: stopping one app is already a local, synchronous
      decision, consulted in process at every pushed authorization request,
      and [app-allowlist](app-allowlist.md) owns it and calls it "a policy
      edit, not a hunt for outstanding grants". It needs no emergency
      channel, and a second copy of it behind this socket would be a weaker
      control justified by an unreachability that does not apply to it.
      Reading "app" as the harness instead does not rescue it: `harness` and
      `agent_type` are the harness's word, written down unchecked, and
      `docs/trust-model.md` puts anything at that tier below a fact that
      selects policy.
    
    What is left is therefore not this epic's. A node is stopped by a lock
    on its host's account, and a lineage below a host needs a parent link
    the server checks.
    

Done #

Left open: scope selectors narrower than "everything". The open item above says which were examined and why each was refused.