First public release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+239
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* PivotOps — decide whether a plan still holds when conditions move, and say
|
||||
* which assumption broke.
|
||||
*
|
||||
* The failure is not that conditions changed. It is that a plan does not
|
||||
* record what it assumed, so nobody can tell whether a change matters. Three
|
||||
* shapes, all seen here:
|
||||
*
|
||||
* 1. **A plan whose assumptions are implicit cannot be invalidated.** A
|
||||
* rollout assumed a host had disk headroom, a registry was reachable and
|
||||
* a maintenance window was open. None of that was written down, so when
|
||||
* the disk filled there was no mechanism to notice the plan was now
|
||||
* wrong — only the failure at the end of it.
|
||||
*
|
||||
* 2. **A stale reading is worse than no reading.** Replanning against a
|
||||
* condition nobody has re-measured produces a confident decision from
|
||||
* an old fact. A condition with no observation is NOT ASSESSED, and it
|
||||
* blocks — because "we did not look" is not "it is fine".
|
||||
*
|
||||
* 3. **Degradation without a floor is just optimism.** "Reduce concurrency
|
||||
* and continue" is a plan; "continue at reduced capacity" without saying
|
||||
* what capacity is unacceptable is a way of never stopping. A fallback
|
||||
* without a floor is reported as a fallback that cannot fail.
|
||||
*
|
||||
* Pure: no clock, no I/O. It executes nothing.
|
||||
*/
|
||||
|
||||
import {
|
||||
type CapabilityReport,
|
||||
type Finding,
|
||||
type FormatOptions,
|
||||
evidence,
|
||||
formatReport as renderReport,
|
||||
} from '@extant2000/evidence-record'
|
||||
|
||||
export type Comparison = 'at-least' | 'at-most' | 'equals'
|
||||
|
||||
export interface Assumption {
|
||||
id: string
|
||||
/** What the plan assumed, in a phrase. */
|
||||
description: string
|
||||
/** The condition being read, e.g. "disk free bytes". */
|
||||
condition: string
|
||||
comparison: Comparison
|
||||
/** The value the plan needs. */
|
||||
required: number | string
|
||||
/**
|
||||
* What was actually measured. `null` when the condition has not been
|
||||
* re-read — never treated as satisfied.
|
||||
*/
|
||||
observed: number | string | null
|
||||
/** Where the observation came from. */
|
||||
where: string
|
||||
/**
|
||||
* What to do if this assumption fails. A fallback without `floor` is a
|
||||
* fallback that cannot fail.
|
||||
*/
|
||||
fallback?: Fallback
|
||||
}
|
||||
|
||||
export interface Fallback {
|
||||
description: string
|
||||
/**
|
||||
* The point below which the fallback is no longer acceptable and the plan
|
||||
* must stop instead of degrading further.
|
||||
*/
|
||||
floor?: string
|
||||
}
|
||||
|
||||
function holds(a: Assumption): boolean | null {
|
||||
if (a.observed === null) return null
|
||||
if (a.comparison === 'equals') return a.observed === a.required
|
||||
if (typeof a.observed !== 'number' || typeof a.required !== 'number') {
|
||||
// A non-numeric value cannot be ordered. Reported as unknown rather than
|
||||
// guessed — a string comparison here would silently pass or fail on
|
||||
// lexical order, which is not what anyone meant.
|
||||
return null
|
||||
}
|
||||
return a.comparison === 'at-least' ? a.observed >= a.required : a.observed <= a.required
|
||||
}
|
||||
|
||||
function assumptionEvidence(a: Assumption) {
|
||||
return typeof a.observed === 'number'
|
||||
? evidence.measurement(a.condition, a.observed, 1, { note: a.where })
|
||||
: evidence.file(a.where, undefined, String(a.observed ?? 'not observed'), a.condition)
|
||||
}
|
||||
|
||||
function findingFor(a: Assumption): Finding {
|
||||
const ok = holds(a)
|
||||
const want = `${a.comparison.replace('-', ' ')} ${a.required}`
|
||||
|
||||
if (ok === null) {
|
||||
return {
|
||||
id: a.id,
|
||||
summary: `${a.description} — ${a.condition} was not re-measured`,
|
||||
determination: 'not-assessed',
|
||||
severity: 'high',
|
||||
detail: a.observed === null
|
||||
? 'Replanning against a condition nobody re-read produces a confident decision from an old fact. "We did not look" is not "it is fine".'
|
||||
: `"${a.observed}" cannot be ordered against "${a.required}". Reported as unknown rather than compared lexically, which is not what the plan meant.`,
|
||||
evidence: [assumptionEvidence(a)],
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
return {
|
||||
id: a.id,
|
||||
summary: `${a.description} — ${a.condition} is ${a.observed}, needs ${want}`,
|
||||
determination: 'pass',
|
||||
severity: 'info',
|
||||
evidence: [assumptionEvidence(a)],
|
||||
}
|
||||
}
|
||||
|
||||
if (a.fallback && !a.fallback.floor) {
|
||||
return {
|
||||
id: a.id,
|
||||
summary: `${a.description} BROKEN — fallback "${a.fallback.description}" has no floor`,
|
||||
determination: 'fail',
|
||||
severity: 'critical',
|
||||
detail: 'A fallback without a floor is a fallback that cannot fail. State the point below which degrading further is unacceptable, or this is a way of never stopping.',
|
||||
evidence: [assumptionEvidence(a)],
|
||||
}
|
||||
}
|
||||
|
||||
if (a.fallback) {
|
||||
return {
|
||||
id: a.id,
|
||||
summary: `${a.description} BROKEN — falling back: ${a.fallback.description}`,
|
||||
determination: 'fail',
|
||||
severity: 'high',
|
||||
detail: `${a.condition} is ${a.observed}, needs ${want}. Floor: ${a.fallback.floor}.`,
|
||||
evidence: [assumptionEvidence(a)],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: a.id,
|
||||
summary: `${a.description} BROKEN — no fallback defined`,
|
||||
determination: 'fail',
|
||||
severity: 'critical',
|
||||
detail: `${a.condition} is ${a.observed}, needs ${want}. The plan has no alternative for this, so it stops here rather than proceeding on an assumption known to be false.`,
|
||||
evidence: [assumptionEvidence(a)],
|
||||
}
|
||||
}
|
||||
|
||||
export interface Plan {
|
||||
name: string
|
||||
assumptions: Assumption[]
|
||||
}
|
||||
|
||||
export type Decision = 'proceed' | 'pivot' | 'stop' | 'hold'
|
||||
|
||||
export interface Replan {
|
||||
decision: Decision
|
||||
reason: string
|
||||
/** Assumptions that no longer hold. */
|
||||
broken: Assumption[]
|
||||
/** Assumptions nobody re-measured. */
|
||||
unverified: Assumption[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide what to do with a plan given the current readings.
|
||||
*
|
||||
* `hold` exists so that "we do not know" has an outcome distinct from both
|
||||
* "carry on" and "abandon". Collapsing it into either is the decision this
|
||||
* library is meant to prevent — proceeding on unread conditions, or stopping
|
||||
* a viable rollout because one probe was missing.
|
||||
*/
|
||||
export function replan(plan: Plan): Replan {
|
||||
const list = plan.assumptions ?? []
|
||||
const broken = list.filter(a => holds(a) === false)
|
||||
const unverified = list.filter(a => holds(a) === null)
|
||||
|
||||
if (list.length === 0) {
|
||||
return {
|
||||
decision: 'hold',
|
||||
reason: 'The plan records no assumptions, so there is nothing to re-check. A plan that cannot be invalidated cannot be trusted to still hold.',
|
||||
broken, unverified,
|
||||
}
|
||||
}
|
||||
|
||||
const unrecoverable = broken.filter(a => !a.fallback || !a.fallback.floor)
|
||||
if (unrecoverable.length > 0) {
|
||||
return {
|
||||
decision: 'stop',
|
||||
reason: `${unrecoverable.length} broken assumption(s) with no usable fallback: ${unrecoverable.map(a => a.id).join(', ')}.`,
|
||||
broken, unverified,
|
||||
}
|
||||
}
|
||||
|
||||
if (broken.length > 0) {
|
||||
return {
|
||||
decision: 'pivot',
|
||||
reason: `${broken.length} assumption(s) broken, each with a fallback and a floor: ${broken.map(a => `${a.id} → ${a.fallback!.description}`).join('; ')}.`,
|
||||
broken, unverified,
|
||||
}
|
||||
}
|
||||
|
||||
if (unverified.length > 0) {
|
||||
return {
|
||||
decision: 'hold',
|
||||
reason: `${unverified.length} assumption(s) unverified: ${unverified.map(a => a.id).join(', ')}. Proceeding would be a decision taken on unread conditions.`,
|
||||
broken, unverified,
|
||||
}
|
||||
}
|
||||
|
||||
return { decision: 'proceed', reason: `All ${list.length} assumption(s) re-checked and holding.`, broken, unverified }
|
||||
}
|
||||
|
||||
/** Report the plan's standing against current conditions. */
|
||||
export function toReport(plan: Plan): CapabilityReport {
|
||||
const list = plan.assumptions ?? []
|
||||
const d = replan(plan)
|
||||
|
||||
if (list.length === 0) {
|
||||
return {
|
||||
capability: 'PivotOps',
|
||||
scope: `${plan.name} — no assumptions recorded`,
|
||||
examined: 0,
|
||||
findings: [],
|
||||
notes: [d.reason],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
capability: 'PivotOps',
|
||||
scope: `${plan.name} — ${list.length} assumption(s)`,
|
||||
examined: list.length,
|
||||
findings: list.map(findingFor),
|
||||
notes: [`decision: ${d.decision.toUpperCase()} — ${d.reason}`],
|
||||
}
|
||||
}
|
||||
|
||||
/** Render a replan for a terminal, CI log or operations review. */
|
||||
export function formatReplan(plan: Plan, opts: FormatOptions = {}): string {
|
||||
return renderReport(toReport(plan), opts)
|
||||
}
|
||||
Reference in New Issue
Block a user