Skip to content

recover.orphans

Walk the publish temps of every record store, the rename temps under the layer's local half and the tracked sessions section, and the site generator's staging directories at the repository root; give each a verdict by reading its age and then its content; and, unless the call is a check, remove the ones the verdict says nothing is holding. Nothing is deleted for being unrecognised: a temp holding the only copy of a record is reported as `incomplete` and left for the caller to publish, content this version cannot classify is `foreign` and left exactly where it is, a candidate whose age this platform cannot read is `unmeasurable` and never a candidate, and no directory is ever removed. The threshold is required and has no default, because a sweep measuring against an absent threshold would find every file stale.

Walk the publish temps of every record store, the rename temps under the layer's local half and the tracked sessions section, and the site generator's staging directories at the repository root; give each a verdict by reading its age and then its content; and, unless the call is a check, remove the ones the verdict says nothing is holding. Nothing is deleted for being unrecognised: a temp holding the only copy of a record is reported as `incomplete` and left for the caller to publish, content this version cannot classify is `foreign` and left exactly where it is, a candidate whose age this platform cannot read is `unmeasurable` and never a candidate, and no directory is ever removed. The threshold is required and has no default, because a sweep measuring against an absent threshold would find every file stale.

  • command
  • behaviorally_verified
  • module recover
  • #recover
  • #sessions
  • #maintenance
  • #lifecycle

Exposure

Where this one definition is reachable. Absence is explicit: a surface not listed does not carry it.

surfaceas
MCPtool majordomus_recover_orphans
HTTPPOST /api/v1/recover/orphans operationId recover.orphans
command line—

Schemas

The canonical input and output, from the Rust types; the MCP tool schema and the OpenAPI parameters and responses are derived from these.

input · RecoverOrphansInput

The input of recover.orphans: how old a stray must be, and whether to act.

use majordomus_cli::capability::builtin::recover::RecoverOrphansInput;

// The wire form the shell tool, an MCP client or an HTTP POST sends.
let act: RecoverOrphansInput =
    serde_json::from_str(r#"{"older_than_seconds":43200,"check":false}"#).unwrap();
assert_eq!(act.older_than_seconds, 43_200);
assert!(!act.check);

// `check` defaults to true, and that direction is deliberate: this capability deletes
// files, and a caller that forgets the field gets a plan rather than a sweep.
let forgot: RecoverOrphansInput =
    serde_json::from_str(r#"{"older_than_seconds":43200}"#).unwrap();
assert!(forgot.check, "the safe value is the default one");

// The threshold has no default at all. `session.stranded_after` is a policy value, and
// a reader that carried its own copy would be a second place the number lives.
assert!(serde_json::from_str::<RecoverOrphansInput>(r#"{"check":true}"#).is_err());

// Unknown fields are refused rather than ignored: a caller that misspells `check` is
// told so instead of having a sweep silently run.
assert!(serde_json::from_str::<RecoverOrphansInput>(
    r#"{"older_than_seconds":1,"chek":true}"#).is_err());
propertytyperequireddescription
check boolean no Classify and report, and write nothing. The default, because the alternative deletes files. default true
older_than_seconds integer yes How long a stray file must have been untouched before it is a candidate at all, in seconds. Required, and refused at zero: the shell tool resolves it from session.stranded_after with mj_pol_req, which fails closed, and nothing here invents one.
JSON Schema
{
  "additionalProperties": false,
  "description": "The input of `recover.orphans`: how old a stray must be, and whether to act.\n\n```\nuse majordomus_cli::capability::builtin::recover::RecoverOrphansInput;\n\n// The wire form the shell tool, an MCP client or an HTTP POST sends.\nlet act: RecoverOrphansInput =\n    serde_json::from_str(r#\"{\"older_than_seconds\":43200,\"check\":false}\"#).unwrap();\nassert_eq!(act.older_than_seconds, 43_200);\nassert!(!act.check);\n\n// `check` defaults to true, and that direction is deliberate: this capability deletes\n// files, and a caller that forgets the field gets a plan rather than a sweep.\nlet forgot: RecoverOrphansInput =\n    serde_json::from_str(r#\"{\"older_than_seconds\":43200}\"#).unwrap();\nassert!(forgot.check, \"the safe value is the default one\");\n\n// The threshold has no default at all. `session.stranded_after` is a policy value, and\n// a reader that carried its own copy would be a second place the number lives.\nassert!(serde_json::from_str::<RecoverOrphansInput>(r#\"{\"check\":true}\"#).is_err());\n\n// Unknown fields are refused rather than ignored: a caller that misspells `check` is\n// told so instead of having a sweep silently run.\nassert!(serde_json::from_str::<RecoverOrphansInput>(\n    r#\"{\"older_than_seconds\":1,\"chek\":true}\"#).is_err());\n```",
  "properties": {
    "check": {
      "default": true,
      "description": "Classify and report, and write nothing. The default, because the alternative deletes\nfiles.",
      "type": "boolean"
    },
    "older_than_seconds": {
      "description": "How long a stray file must have been untouched before it is a candidate at all, in\nseconds. Required, and refused at zero: the shell tool resolves it from\n`session.stranded_after` with `mj_pol_req`, which fails closed, and nothing here\ninvents one.",
      "format": "uint64",
      "minimum": 0,
      "type": "integer"
    }
  },
  "required": [
    "older_than_seconds"
  ],
  "title": "RecoverOrphansInput",
  "type": "object"
}

output · RecoverOrphansResult

What one sweep found and what it did about it.

The counts are derived from strays rather than accumulated beside it, so a reader can check the report against itself and a surface that renders the list cannot disagree with the surface that renders the total.

use majordomus_cli::capability::builtin::recover::RecoverOrphansResult;

let plan: RecoverOrphansResult = serde_json::from_str(r#"{
    "older_than_seconds": 43200, "check": true, "strays": [],
    "removable": 0, "removed": 0, "unmeasurable": 0
}"#).unwrap();
// a check never removes anything, whatever it found
assert_eq!(plan.removed, 0);
assert!(plan.check);
propertytyperequireddescription
check boolean yes Whether this was a check. true means nothing was written.
older_than_seconds integer yes The threshold this sweep measured against, echoed back so that a refusal or a verdict names the value the caller actually got rather than the one it believes it sent.
removable integer yes How many of them this call would remove, or did.
removed integer yes How many it actually removed. Always 0 on a check.
strays array yes Every stray found, in path order.
unmeasurable integer yes How many candidates could not be measured and were therefore left alone.
JSON Schema
{
  "$defs": {
    "Stray": {
      "description": "One stray file, its verdict, and the evidence that produced it.\n\nEvery field that decided something is carried, because the rule this command is shaped\naround requires the value that decided a verdict to be visible before the verdict acts:\na surface that printed \"orphan\" without the age, the episode and the file that already\nholds it would be asking to be trusted rather than read.\n\n```\nuse majordomus_cli::capability::builtin::recover::{Stray, StrayKind, StrayVerdict};\nuse serde_json::json;\n\nlet s: Stray = serde_json::from_value(json!({\n    \"path\": \".ai/repo/sessions/.tmp.a1b2c3\",\n    \"kind\": \"publish_temp\",\n    \"verdict\": \"orphan\",\n    \"age_seconds\": 90_000,\n    \"session_id\": \"s-0007\",\n    \"published_at\": \".ai/repo/sessions/2026-09-11--s-0007.md\",\n    \"removable\": true,\n    \"removed\": false,\n})).unwrap();\nassert_eq!(s.verdict, StrayVerdict::Orphan);\nassert_eq!(s.kind, StrayKind::PublishTemp);\n// removable and removed are different facts: a check run reports the first and never\n// the second, which is what makes `--check` readable as a plan rather than a report.\nassert!(s.removable && !s.removed);\n\n// and an unmeasurable one carries no age at all, rather than a zero that would read as\n// \"brand new\" to anything that compared it with a threshold\nlet u: Stray = serde_json::from_value(json!({\n    \"path\": \".mj-stage.xyz\", \"kind\": \"staging_dir\", \"verdict\": \"unmeasurable\",\n    \"removable\": false, \"removed\": false,\n})).unwrap();\nassert!(u.age_seconds.is_none());\n```",
      "properties": {
        "age_seconds": {
          "description": "How old it is, in seconds. Absent when the platform could not say, which is the\n`Unmeasurable` verdict and never a zero.",
          "format": "uint64",
          "minimum": 0,
          "type": [
            "integer",
            "null"
          ]
        },
        "kind": {
          "$ref": "#/$defs/StrayKind",
          "description": "Which of the three shapes this is."
        },
        "path": {
          "description": "The path, repository-relative. Never the absolute one: a finding that names a disk is\na finding nobody else can act on (`project.no-machine-paths`).",
          "type": "string"
        },
        "published_at": {
          "description": "The record that already holds that episode, when one does. Its presence is what turns\nan `Incomplete` publish temp into an `Orphan`.",
          "type": "string"
        },
        "removable": {
          "description": "Whether this call would remove it. Decided by the verdict and by\n`remove_stray`'s own pattern guard, never by the caller.",
          "type": "boolean"
        },
        "removed": {
          "description": "Whether this call did remove it. Always `false` on a check.",
          "type": "boolean"
        },
        "session_id": {
          "description": "The episode this publish temp carries, when it carries one.",
          "type": "string"
        },
        "target": {
          "description": "The file a rename temp was going to become. Its presence is what turns an\n`Incomplete` rename temp into an `Orphan`.",
          "type": "string"
        },
        "verdict": {
          "$ref": "#/$defs/StrayVerdict",
          "description": "What reading it decided."
        }
      },
      "required": [
        "path",
        "kind",
        "verdict",
        "removable",
        "removed"
      ],
      "type": "object"
    },
    "StrayKind": {
      "description": "What kind of stray a path is, decided by where it is and how it is named rather than by\nwhat is in it. The content decides the *verdict*; this decides which question to ask.\n\nThe word a kind is reported under is also the word it is read back from, and a word this\nversion does not know is refused rather than read as the nearest kind:\n\n```\nuse majordomus_cli::capability::builtin::recover::StrayKind;\nfor kind in [StrayKind::PublishTemp, StrayKind::RenameTemp, StrayKind::StagingDir] {\n    let word = serde_json::to_value(kind).unwrap();\n    assert_eq!(serde_json::from_value::<StrayKind>(word).unwrap(), kind);\n}\nassert!(serde_json::from_value::<StrayKind>(serde_json::json!(\"temp\")).is_err());\n```",
      "oneOf": [
        {
          "const": "publish_temp",
          "description": "A `.tmp.XXXXXX` in a record store: `mj_publish_record` makes them, and only a process\nthat died between the `mktemp` and the `rm` leaves one.",
          "type": "string"
        },
        {
          "const": "rename_temp",
          "description": "A `<target>.mj-tmp`: written and renamed in one `&&`, so one that exists means the\nwrite failed or the process died mid-write.",
          "type": "string"
        },
        {
          "const": "staging_dir",
          "description": "A `.mj-stage.XXXXXX` at the repository root: `scripts/generate-site-data` stages into\none. Always a directory, and therefore never removed here.",
          "type": "string"
        }
      ]
    },
    "StrayVerdict": {
      "description": "What this stray is, decided by reading it.\n\nThe five words are `lib/recover.sh`'s, deliberately: a person who has read the command's\noutput should not have to learn the same five facts twice because a second surface chose\nits own vocabulary.\n\nThat vocabulary is closed. Every verdict reads back as itself, and there is no sixth word:\na surface that invents one gets an error rather than a verdict nobody meant.\n\n```\nuse majordomus_cli::capability::builtin::recover::StrayVerdict::{self, *};\nlet all = [Live, Orphan, Incomplete, Foreign, Unmeasurable];\nlet words: Vec<&str> = all.iter().map(|v| v.as_str()).collect();\nassert_eq!(words, [\"live\", \"orphan\", \"incomplete\", \"foreign\", \"unmeasurable\"]);\nfor v in all {\n    let word = serde_json::json!(v.as_str());\n    assert_eq!(serde_json::from_value::<StrayVerdict>(word).unwrap(), v);\n}\nassert!(serde_json::from_value::<StrayVerdict>(serde_json::json!(\"stale\")).is_err());\n```",
      "oneOf": [
        {
          "const": "live",
          "description": "Younger than the threshold: a `session close` or a `scripts/derive` running right now\nowns it. Measured, reported, never touched.",
          "type": "string"
        },
        {
          "const": "orphan",
          "description": "Its work completed, or there was never anything in it. Removable — unless it is a\ndirectory, which this runtime never removes.",
          "type": "string"
        },
        {
          "const": "incomplete",
          "description": "It holds something no other file holds: a record nobody published, or a write whose\ntarget never arrived. Never removed here; the caller decides.",
          "type": "string"
        },
        {
          "const": "foreign",
          "description": "Not written by this tool at all, or written by a version that shaped it differently.\nNever touched; reported with what it is.",
          "type": "string"
        },
        {
          "const": "unmeasurable",
          "description": "Its age could not be read on this platform, so nothing here treats it as stale. The\nguard `project.destructive-sweeps-fail-closed` asks for: a predicate that cannot\nmeasure its input is false, never the most extreme value in the set.",
          "type": "string"
        }
      ]
    }
  },
  "description": "What one sweep found and what it did about it.\n\nThe counts are derived from `strays` rather than accumulated beside it, so a reader can\ncheck the report against itself and a surface that renders the list cannot disagree with\nthe surface that renders the total.\n\n```\nuse majordomus_cli::capability::builtin::recover::RecoverOrphansResult;\n\nlet plan: RecoverOrphansResult = serde_json::from_str(r#\"{\n    \"older_than_seconds\": 43200, \"check\": true, \"strays\": [],\n    \"removable\": 0, \"removed\": 0, \"unmeasurable\": 0\n}\"#).unwrap();\n// a check never removes anything, whatever it found\nassert_eq!(plan.removed, 0);\nassert!(plan.check);\n```",
  "properties": {
    "check": {
      "description": "Whether this was a check. `true` means nothing was written.",
      "type": "boolean"
    },
    "older_than_seconds": {
      "description": "The threshold this sweep measured against, echoed back so that a refusal or a verdict\nnames the value the caller actually got rather than the one it believes it sent.",
      "format": "uint64",
      "minimum": 0,
      "type": "integer"
    },
    "removable": {
      "description": "How many of them this call would remove, or did.",
      "format": "uint",
      "minimum": 0,
      "type": "integer"
    },
    "removed": {
      "description": "How many it actually removed. Always `0` on a check.",
      "format": "uint",
      "minimum": 0,
      "type": "integer"
    },
    "strays": {
      "description": "Every stray found, in path order.",
      "items": {
        "$ref": "#/$defs/Stray"
      },
      "type": "array"
    },
    "unmeasurable": {
      "description": "How many candidates could not be measured and were therefore left alone.",
      "format": "uint",
      "minimum": 0,
      "type": "integer"
    }
  },
  "required": [
    "older_than_seconds",
    "check",
    "strays",
    "removable",
    "removed",
    "unmeasurable"
  ],
  "title": "RecoverOrphansResult",
  "type": "object"
}

Policies

benchmark
required — a target on every transport the exposure declares; the cases are the input type's
cache
disabled — every call runs the handler

Benchmark targets

Derived from the registry for this repository: one requirement per transport, and the cases the input type provides. The whole matrix is on the benchmarks page.

Searches the titles and descriptions of every page this site publishes.