# PivotOps Decide whether a plan still holds when conditions change, and name the assumption that broke. PivotOps is a pure function over a plan and its current readings. It has no clock and does no I/O, and it executes nothing. It tells you whether to proceed, pivot to a fallback, stop, or hold. ## What it checks and why Conditions changing is not the real failure. The failure is a plan that never wrote down what it assumed, so nobody can tell whether a change matters. PivotOps is built against three versions of that. 1. A plan whose assumptions are implicit cannot be invalidated. Say a rollout assumes a host has disk headroom, a registry is reachable and a maintenance window is open. If none of that is recorded, a full disk only shows up as the failure at the end. A plan with no recorded assumptions returns `hold`, never `proceed`. 2. A stale reading is worse than no reading. Replanning against a condition nobody re-measured gives a confident decision from an old fact. An assumption with `observed: null` is NOT ASSESSED and blocks. "We did not look" is not "it is fine". 3. Degradation without a floor is optimism. "Reduce concurrency and continue" is a plan. "Continue at reduced capacity" with no point at which capacity becomes unacceptable is a way of never stopping. A broken assumption whose fallback has no `floor` makes the decision `stop`, not `pivot`. ## Four decisions, not two | Decision | When | |---|---| | `proceed` | every assumption was re-checked and holds | | `pivot` | something broke, and each broken assumption has a fallback with a floor | | `stop` | something broke with no fallback, or with a fallback that has no floor | | `hold` | nothing broke, but at least one condition was never re-measured | `hold` gives "we do not know" its own outcome, separate from "carry on" and "abandon". Folding it into either one leads to the two mistakes this library exists to prevent: proceeding on unread conditions, or killing a viable rollout because one probe was missing. A broken assumption outranks an unread one, so `stop` wins over `hold`. Non-numeric values are never ordered. An `at-least` comparison of `"closing"` against `"open"` reports NOT ASSESSED instead of comparing the strings, which would pass or fail on alphabetical order. `equals` works on any value. ## Usage ```ts import { replan, formatReplan, type Plan } from '@extant2000/pivot-ops' const plan: Plan = { name: 'nightly rollout', assumptions: [ { id: 'disk-headroom', description: 'the host has room for the new image', condition: 'disk free bytes', comparison: 'at-least', required: 5e9, observed: 2e9, where: 'df -P /var/lib/docker', fallback: { description: 'reduce concurrency to 1', floor: 'stop below 500 MB free' }, }, { id: 'window', description: 'the maintenance window is open', condition: 'window state', comparison: 'equals', required: 'open', observed: 'open', where: 'change calendar', }, ], } const { decision, reason, broken, unverified } = replan(plan) console.log(decision) console.log(reason) // The full report, for a terminal or CI log console.log(formatReplan(plan)) ``` `toReport(plan)` returns the same result as a `CapabilityReport` from `@extant2000/evidence-record`, where every finding cites the reading it was based on. ## Sample output Produced by running the example above and printing `decision` and `reason`: ``` pivot 1 assumption(s) broken, each with a fallback and a floor: disk-headroom → reduce concurrency to 1. ``` ## Install The package is published to the GitLab package registry, so point the `@extant2000` scope there first: ``` npm config set @extant2000:registry https://gitlab.com/api/v4/packages/npm/ npm install @extant2000/pivot-ops ``` ## License MIT. Copyright (c) 2026 Extant 2000 LLC. See [LICENSE](LICENSE).