Přeskočit na obsah

Tato stránka byla automaticky přeložena z angličtiny, jediného jazyka, ve kterém se tento web píše. Číst originál.

lifecycle.recovery

Otevřené záznamy, které se už nemohou samy uzavřít, dočasné soubory, které zabité uzavření zanechalo ve sledované sekci relací, zda byl ukazatel migrován na rozvržení po providerech a aritmetika, která říká, zda je zohledněna každá epizoda, kterou ledger viděl začít. Každý nález nese příkaz, který jej odstraní.

Otevřené záznamy, které se už nemohou samy uzavřít, dočasné soubory, které zabité uzavření zanechalo ve sledované sekci relací, zda byl ukazatel migrován na rozvržení po providerech a aritmetika, která říká, zda je zohledněna každá epizoda, kterou ledger viděl začít. Každý nález nese příkaz, který jej odstraní.

Zpřístupnění

Kde je tato jedna definice dosažitelná. Absence je explicitní: povrch, který není uveden, ji nenese.

povrchjako
MCPnástroj majordomus_lifecycle_recovery
zdroj majordomus://lifecycle/recovery
HTTPGET /api/v1/lifecycle/recovery operationId lifecycle.recovery
příkazová řádka—

Schémata

Kanonický vstup a výstup z typů Rustu; schéma nástroje MCP a parametry a odpovědi OpenAPI se odvozují z nich.

vstup · Prázdné

Žádný vstup.

Žádné vlastnosti: schopnost nebere žádný vstup.

JSON Schema
{
  "additionalProperties": false,
  "description": "No input.",
  "title": "Empty",
  "type": "object"
}

output · Recovery

Co úložiště relací tohoto checkoutu potřebuje, aby někdo udělal.

findings je celý report řečený jednou a prázdné findings je verdikt: není co obnovovat. To je pole, podle kterého se větví — tři podrobné seznamy a aritmetika jsou to, co čtenář konzultuje poté, co se rozhodne, že je na co se dívat, a každý z nich může být prázdný z dobrého důvodu, zatímco jiný není.

use majordomus_cli::capability::builtin::lifecycle::Recovery;
use serde_json::json;

// a healthy checkout: the current layout, nothing stranded, the arithmetic agreeing
let clean: Recovery = serde_json::from_value(json!({
    "present": true,
    "pointer": {"layout": "pointer", "target": "sessions-open/c.yaml",
                "resolves": true, "session_id": "s-1", "note": "resolves"},
    "balance": {"started": 8, "closed": 7, "open": 1,
                "unaccounted": 0, "agrees": true, "note": "they agree"},
})).unwrap();

assert!(clean.findings.is_empty(), "an empty findings list is the good verdict");
assert!(clean.stranded.is_empty() && clean.orphans.is_empty());
assert!(clean.balance.agrees);

// a fresh clone has no local half at all, which is also nothing to recover
let fresh: Recovery = serde_json::from_value(json!({
    "present": false,
    "pointer": {"layout": "absent", "resolves": false, "note": "no episode is open here"},
    "balance": {"started": 0, "closed": 0, "open": 0,
                "unaccounted": 0, "agrees": true, "note": "nothing has happened yet"},
})).unwrap();
assert!(!fresh.present && fresh.findings.is_empty());
vlastnosttyppovinnépopis
bilance reference ano Aritmetika zahájených/uzavřených/otevřených.
zjištění array ne Vše výše, řečené jednou, pro čtenáře, který chce souhrn. Prázdné je dobrý případ a znamená, že není co obnovovat.
osiřelé array ne Dočasné soubory zbylé ve sledované sekci relací.
ukazatel reference ano Ukazatel a jeho rozvržení.
přítomno boolean ano Zda lokální polovina vůbec existuje.
uvízlé array ne Epizody, které se už nemohou uzavřít samy.
JSON Schema
{
  "$defs": {
    "Balance": {
      "description": "What the ledger says about episode lifecycle against what the store holds.\n\nThe invariant ADR 0052 asks for, stated as arithmetic a person can check: every episode\nthat started either closed or is still open. When `started` exceeds `closed + open`, the\ndifference is episodes whose close was never recorded — the shape of the outage that ADR\ndescribes, where events kept arriving and the records they should have produced did not.\n\nThe sign matters and the two directions mean opposite things. Positive `unaccounted` is\nthe outage: episodes that started, are not open, and produced no closed record — work\nthat happened and left nothing behind. Negative is ordinary bookkeeping: the ledger was\nrotated, or a record was committed from a checkout whose ledger lines are not in this\none, so there are more records than started lines to account for them.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::Balance;\nuse serde_json::json;\n\nlet of = |started, closed, open, unaccounted, agrees| -> Balance {\n    serde_json::from_value(json!({\n        \"started\": started, \"closed\": closed, \"open\": open,\n        \"unaccounted\": unaccounted, \"agrees\": agrees, \"note\": \"as measured\",\n    })).unwrap()\n};\n\n// the good case: every episode that started either closed or is still open\nlet sound = of(40, 37, 3, 0, true);\nassert_eq!(sound.started as i64 - sound.closed as i64 - sound.open as i64, sound.unaccounted);\nassert!(sound.agrees);\n\n// the outage ADR 0052 describes: four closes that were never recorded\nlet lost = of(40, 33, 3, 4, false);\nassert!(lost.unaccounted > 0 && !lost.agrees);\n\n// and a rotated ledger, which is not a fault in either half\nlet rotated = of(2, 37, 3, -38, false);\nassert!(rotated.unaccounted < 0, \"more records than lines to account for them\");\n```",
      "properties": {
        "agrees": {
          "description": "Whether the three agree.",
          "type": "boolean"
        },
        "closed": {
          "description": "`session.closed` lines.",
          "format": "uint",
          "minimum": 0,
          "type": "integer"
        },
        "note": {
          "description": "What the numbers mean, in words.",
          "type": "string"
        },
        "open": {
          "description": "Open records in the store.",
          "format": "uint",
          "minimum": 0,
          "type": "integer"
        },
        "started": {
          "description": "`session.started` lines in the ledger.",
          "format": "uint",
          "minimum": 0,
          "type": "integer"
        },
        "unaccounted": {
          "description": "`started - closed - open`. Zero is the good case; negative means the ledger was\nrotated under the records, which is ordinary.",
          "format": "int64",
          "type": "integer"
        }
      },
      "required": [
        "started",
        "closed",
        "open",
        "unaccounted",
        "agrees",
        "note"
      ],
      "type": "object"
    },
    "Orphan": {
      "description": "A temporary file left in a record directory by a close that did not finish.\n\nThe close path writes the durable record to `.tmp.XXXX` in the destination directory and\nrenames it, so that no reader ever sees half a record. A process killed between the two\nleaves the temporary file behind, where it is untracked, invisible to every reader of the\nsection, and — because the directory is tracked — offered to the next `git add .`.\n\n`bytes` is carried because it is the one thing that decides what to do with the file: a\nzero-length temporary file is a close that died before it wrote anything and can simply\ngo, while a large one is a record that was written and never renamed, and is worth\nreading before it is deleted.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::Orphan;\nuse majordomus_cli::order::canonical;\n\nlet mut orphans = vec![\n    Orphan { path: \".ai/repo/project/sessions/.tmp.b2\".into(), bytes: 4_812 },\n    Orphan { path: \".ai/repo/project/sessions/.tmp.a1\".into(), bytes: 0 },\n];\ncanonical(&mut orphans);\n\n// filed under the path, which names one file and so makes the list total\nlet paths: Vec<&str> = orphans.iter().map(|o| o.path.as_str()).collect();\nassert_eq!(paths, [\n    \".ai/repo/project/sessions/.tmp.a1\",\n    \".ai/repo/project/sessions/.tmp.b2\",\n]);\n\n// and the size is what tells a dead stub from a record that was written\nassert_eq!(orphans[0].bytes, 0, \"nothing was written before the close died\");\nassert!(orphans[1].bytes > 0, \"read this one before removing it\");\n```",
      "properties": {
        "bytes": {
          "description": "Its size in bytes, which is how a reader tells an empty stub from a lost record.",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "path": {
          "description": "Repository-relative path.",
          "type": "string"
        }
      },
      "required": [
        "path",
        "bytes"
      ],
      "type": "object"
    },
    "Pointer": {
      "description": "What the pointer is and whether it leads anywhere.\n\n`layout` and `resolves` are independent, and the interesting case is the pair\n`Pointer` + `resolves: false`: a symlink that survived the episode it named, which is\nwhat a killed close or a hand-removed record leaves behind. The layout is right, the\ndestination is gone, and `continuity.state` has nothing to report while the store may\nstill hold open episodes — so this is a dangling pointer, not an empty store.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::{Pointer, PointerLayout};\nuse serde_json::json;\n\n// the healthy case: the current layout, and it leads to an episode\nlet live: Pointer = serde_json::from_value(json!({\n    \"layout\": \"pointer\",\n    \"target\": \"sessions-open/claude-abc.yaml\",\n    \"resolves\": true,\n    \"session_id\": \"s-0002\",\n    \"note\": \"the pointer resolves to an open episode\",\n})).unwrap();\nassert_eq!(live.layout, PointerLayout::Pointer);\nassert!(live.resolves && !live.session_id.is_empty());\n\n// and the dangling one: the right layout, aimed at a record that is no longer there\nlet dangling: Pointer = serde_json::from_value(json!({\n    \"layout\": \"pointer\",\n    \"target\": \"sessions-open/claude-gone.yaml\",\n    \"resolves\": false,\n    \"note\": \"the symlink outlived the episode it named\",\n})).unwrap();\nassert_eq!(dangling.layout, PointerLayout::Pointer, \"the layout is not the fault\");\nassert!(!dangling.resolves);\nassert!(dangling.session_id.is_empty(), \"there is no episode to name\");\n```",
      "properties": {
        "layout": {
          "$ref": "#/$defs/PointerLayout",
          "description": "The layout."
        },
        "note": {
          "description": "What that means, in words.",
          "type": "string"
        },
        "resolves": {
          "description": "Whether reading through it yields an episode.",
          "type": "boolean"
        },
        "session_id": {
          "description": "The episode it resolves to.",
          "type": "string"
        },
        "target": {
          "description": "What the symlink names, as written, when it is one.",
          "type": "string"
        }
      },
      "required": [
        "layout",
        "resolves",
        "note"
      ],
      "type": "object"
    },
    "PointerLayout": {
      "description": "The layout of the pointer, which is how far the per-provider migration has got here.\n\nBefore the episode was keyed by its provider session, `session-current.yaml` *was* the\nopen record: one regular file, one episode per checkout. Seven concurrent sessions on\n2026-09-09 produced one record between them under that layout. A regular file here today\nis a checkout that has not run the migration, and its episodes are being overwritten.\n\nOnly one of the three is a finding, and it is the middle one. `Pointer` is the current\nlayout and `Absent` means no episode is open here — a fresh clone, or a worker between\nepisodes — while `Inline` is a checkout still on the pre-migration layout, where a\nsecond concurrent session silently overwrites the first one's record. So the question a\nreader asks of this value is not \"which layout\" but \"is it `Inline`\".\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::PointerLayout;\n\n// the two that are fine, and the one that loses records\nlet loses_records = |l: PointerLayout| l == PointerLayout::Inline;\nassert!(!loses_records(PointerLayout::Pointer));\nassert!(!loses_records(PointerLayout::Absent), \"no episode open is not a fault\");\nassert!(loses_records(PointerLayout::Inline));\n\n// serialised as the word `as_str` gives\nassert_eq!(\n    serde_json::to_value(PointerLayout::Absent).unwrap(),\n    serde_json::json!(PointerLayout::Absent.as_str()),\n);\n```",
      "oneOf": [
        {
          "const": "pointer",
          "description": "A symlink into `sessions-open/`: the per-provider layout.",
          "type": "string"
        },
        {
          "const": "inline",
          "description": "A regular file: the pre-migration layout, one episode per checkout.",
          "type": "string"
        },
        {
          "const": "absent",
          "description": "Nothing is there. No episode is open here, which is not a fault.",
          "type": "string"
        }
      ]
    },
    "Stranded": {
      "description": "An open episode that cannot close itself, and what to do about it.\n\nThe remedy here is the mechanical one — remove the file — because it is correct in every\ncheckout and needs nothing that is not already installed. `lib/recover.sh` on\n`feature/session-store-recovery` is the same three degradations seen from the writing\nside: it classifies each candidate by reading it, prints the evidence before acting, and\ncloses a stranded episode into a real record rather than deleting it. When that lands,\nthe remedy a finding carries should become `majordomus recover episodes` and the two\ndetections should become one — a reader and a writer that each decide for themselves\nwhat \"stranded\" means are two definitions, and this repository has been bitten by that\nshape often enough to name it.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::Stranded;\nuse serde_json::json;\n\nlet s: Stranded = serde_json::from_value(json!({\n    \"session_id\": \"s-0007\",\n    \"path\": \".ai/local/state/sessions-open/claude-7.yaml\",\n    \"reason\": \"the worktree it opened in is gone from disk\",\n    \"remedy\": \"rm .ai/local/state/sessions-open/claude-7.yaml\",\n})).unwrap();\n\n// the reason is about the world and the remedy is a command: a finding with no\n// remedy is a complaint, so both are required fields rather than optional ones\nassert!(!s.reason.is_empty() && !s.remedy.is_empty());\nassert!(s.remedy.contains(&s.path), \"the remedy acts on the file it names\");\n```",
      "properties": {
        "path": {
          "description": "The open record, repository-relative.",
          "type": "string"
        },
        "reason": {
          "description": "Why it is stranded.",
          "type": "string"
        },
        "remedy": {
          "description": "The command that clears it. A finding with no remedy is a complaint.",
          "type": "string"
        },
        "session_id": {
          "description": "The episode id.",
          "type": "string"
        }
      },
      "required": [
        "session_id",
        "path",
        "reason",
        "remedy"
      ],
      "type": "object"
    }
  },
  "description": "What this checkout's session store needs somebody to do.\n\n`findings` is the whole report said once, and an empty `findings` is the verdict: there\nis nothing to recover. That is the field to branch on — the three detailed lists and the\narithmetic are what a reader consults *after* deciding there is something to look at,\nand each of them can be empty for a good reason while another is not.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::Recovery;\nuse serde_json::json;\n\n// a healthy checkout: the current layout, nothing stranded, the arithmetic agreeing\nlet clean: Recovery = serde_json::from_value(json!({\n    \"present\": true,\n    \"pointer\": {\"layout\": \"pointer\", \"target\": \"sessions-open/c.yaml\",\n                \"resolves\": true, \"session_id\": \"s-1\", \"note\": \"resolves\"},\n    \"balance\": {\"started\": 8, \"closed\": 7, \"open\": 1,\n                \"unaccounted\": 0, \"agrees\": true, \"note\": \"they agree\"},\n})).unwrap();\n\nassert!(clean.findings.is_empty(), \"an empty findings list is the good verdict\");\nassert!(clean.stranded.is_empty() && clean.orphans.is_empty());\nassert!(clean.balance.agrees);\n\n// a fresh clone has no local half at all, which is also nothing to recover\nlet fresh: Recovery = serde_json::from_value(json!({\n    \"present\": false,\n    \"pointer\": {\"layout\": \"absent\", \"resolves\": false, \"note\": \"no episode is open here\"},\n    \"balance\": {\"started\": 0, \"closed\": 0, \"open\": 0,\n                \"unaccounted\": 0, \"agrees\": true, \"note\": \"nothing has happened yet\"},\n})).unwrap();\nassert!(!fresh.present && fresh.findings.is_empty());\n```",
  "properties": {
    "balance": {
      "$ref": "#/$defs/Balance",
      "description": "The started/closed/open arithmetic."
    },
    "findings": {
      "description": "Everything above, said once, for a reader that wants the summary. Empty is the good\ncase and means there is nothing to recover.",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "orphans": {
      "description": "Temporary files left in the tracked sessions section.",
      "items": {
        "$ref": "#/$defs/Orphan"
      },
      "type": "array"
    },
    "pointer": {
      "$ref": "#/$defs/Pointer",
      "description": "The pointer and its layout."
    },
    "present": {
      "description": "Whether the local half exists at all.",
      "type": "boolean"
    },
    "stranded": {
      "description": "Episodes that can no longer close themselves.",
      "items": {
        "$ref": "#/$defs/Stranded"
      },
      "type": "array"
    }
  },
  "required": [
    "present",
    "pointer",
    "balance"
  ],
  "title": "Recovery",
  "type": "object"
}

Politiky

benchmark
povinné — cíl na každém transportu, který expozice deklaruje; případy jsou ty ze vstupního typu
cache
process — až 2 záznamy v procesu, po 2 s každý, vymezené otiskem registru

Cíle benchmarku

Odvozeno z registru pro tento repozitář: jeden požadavek na transport a případy, které poskytuje typ vstupu. Celá matice je na stránce měření výkonu.

transportstatepřípadytargets
directpokryto1výchozí studený+teplý
mcppokryto1výchozí studený+teplý
httppokryto1výchozí studený+teplý

Tvrzení implementovaná vedle ní

Připojeno souborem, který každé tvrzení implementuje; každé odkazuje na svou stránku s implementací a testem, který je dokazuje.

Prohledává názvy a popisy všech stránek, které tento web publikuje.