/** * Decision docket: a persistent queue of decisions an agent would otherwise * route to a human, plus the lifecycle that carries each one from filing to a * reviewed, graduated resolution. * * State layout (mirrors councils): * - `.harnery/decisions/.json` — one manifest per decision * - `.harnery/decisions//` — long-form bodies (brief, options, * evidence write-up) as markdown * - `.harnery/decisions/archive/` — graduated / terminal decisions move here * * This module is the engine only. It stores `tier` (0/1/2) and `stakes` as * opaque typed fields; what those *mean* — which decisions belong to which * tier — is host policy, applied by the filing agent, never encoded here. That * keeps the docket generic across host projects. * * Every function takes `coordRoot` explicitly (the council pattern) so the * state machine is trivially testable against a tmpdir. The command layer * resolves the root once via `monorepoRoot()` and threads it down. * * Concurrency: one file per decision (no shared index to contend on) + atomic * temp→rename writes. `claim` is last-writer-wins — deliberating the same * decision twice wastes tokens, not correctness. */ export declare const DECISION_SCHEMA_VERSION: 1; /** Tier of human-involvement. Meaning is host policy; the engine only stores it. */ export declare const DECISION_TIERS: readonly [0, 1, 2]; export type DecisionTier = (typeof DECISION_TIERS)[number]; export declare const DECISION_STAKES: readonly ["small", "medium", "high"]; export type DecisionStakes = (typeof DECISION_STAKES)[number]; export declare const DECISION_STATUSES: readonly ["filed", "triaged", "deliberating", "resolved", "enacted", "reviewed", "archived", "superseded", "wontfix"]; export type DecisionStatus = (typeof DECISION_STATUSES)[number]; export declare const REVIEW_VERDICTS: readonly ["ratified", "overridden", "wrong-tier-high", "wrong-tier-low"]; export type ReviewVerdict = (typeof REVIEW_VERDICTS)[number]; export declare const TERMINAL_STATUSES: readonly DecisionStatus[]; /** * Legal status transitions. `superseded` is reachable from any non-terminal * state (a decision can be obsoleted at any point); `wontfix` closes an * un-deliberated decision. `deliberating → triaged` allows the sweeper to * re-triage a decision's tier on first touch (the self-triage safeguard). */ export declare const LEGAL_TRANSITIONS: Record; export interface DecisionResolution { recommendation: string; confidence?: string; reversal_cost?: string; /** What would make this resolution wrong (the pre-mortem). */ wrong_if?: string; /** When this should be revisited (a trigger, not a date). */ revisit_when?: string; /** Citations: queries run, files read, costs computed. Required (≥1). */ evidence: string[]; resolved_by: string; resolved_at: string; } export interface DecisionReview { verdict: ReviewVerdict; note?: string; reviewed_at: string; } export interface DecisionManifest { schema_version: typeof DECISION_SCHEMA_VERSION; decision_id: string; status: DecisionStatus; /** Provisional at file time; the sweeper may re-check on claim. */ tier: DecisionTier; stakes: DecisionStakes; question: string; context?: string; /** What the filer proceeded with under always-proceed (tier 0/1), or null. */ default_taken?: string | null; /** Agent name (e.g. "agent-Quill"), for display. */ filed_by?: string; /** Filer instance_id. */ filed_by_id?: string; filed_at: string; /** Deliberator (agent instance_id or a sweeper session), or null. */ claimed_by?: string | null; /** Set when escalated to a council. */ council_id?: string | null; resolution?: DecisionResolution | null; review?: DecisionReview | null; /** Where the resolved output graduated (e.g. "docs/decisions.md#…"), or null. */ graduated_to?: string | null; /** Set when superseded by another decision. */ superseded_by?: string | null; /** Reason recorded on wontfix. */ wontfix_reason?: string | null; updated_at?: string; [extra: string]: unknown; } export interface DecisionOpResult { ok: boolean; reason?: string; manifest?: DecisionManifest; } export declare function isTier(n: unknown): n is DecisionTier; export declare function isStakes(s: unknown): s is DecisionStakes; export declare function isStatus(s: unknown): s is DecisionStatus; export declare function isVerdict(v: unknown): v is ReviewVerdict; export declare function isTerminal(status: DecisionStatus): boolean; export declare function canTransition(from: DecisionStatus, to: DecisionStatus): boolean; export declare function decisionsDir(coordRoot: string): string; export declare function archiveDir(coordRoot: string): string; export declare function manifestPath(coordRoot: string, id: string): string; export declare function archivedManifestPath(coordRoot: string, id: string): string; export declare function decisionBodyDir(coordRoot: string, id: string): string; /** * Kebab-case slug from question text: first 5 words, lowercased, * non-alphanumerics stripped. Local (not imported from council) so the module * stays standalone. */ export declare function deriveSlug(question: string): string; /** * Build a decision_id: `--<4hex>`. The hex suffix is * crypto-random (not a hash of the question) so two decisions sharing a * slug + date don't collide. */ export declare function buildDecisionId(question: string, now?: Date): string; /** * Read a manifest by id, checking the active dir then the archive. Returns null * if absent or unparseable. Throws on an unsupported schema_version (fail loud * on a real-but-incompatible manifest, the way councils do). */ export declare function readManifest(coordRoot: string, id: string): DecisionManifest | null; export declare function writeManifest(coordRoot: string, manifest: DecisionManifest): void; export interface FileDecisionInput { question: string; tier: DecisionTier; stakes: DecisionStakes; context?: string; defaultTaken?: string; filedBy?: string; filedById?: string; /** Long-form brief written to `/brief.md`. */ brief?: string; now?: Date; } export declare function fileDecision(coordRoot: string, input: FileDecisionInput): DecisionOpResult; export declare function triageDecision(coordRoot: string, id: string, opts: { tier?: DecisionTier; stakes?: DecisionStakes; }): DecisionOpResult; export declare function claimDecision(coordRoot: string, id: string, owner: string): DecisionOpResult; export declare function escalateToCouncil(coordRoot: string, id: string, councilId: string): DecisionOpResult; /** * Resolve a decision. Evidence is required (≥1 citation): a resolution with no * cited evidence is structurally incomplete and bounced here — the same guard * the sweeper enforces. */ export declare function resolveDecision(coordRoot: string, id: string, resolution: Omit & { resolved_at?: string; }): DecisionOpResult; export declare function enactDecision(coordRoot: string, id: string): DecisionOpResult; export declare function reviewDecision(coordRoot: string, id: string, opts: { verdict: ReviewVerdict; note?: string; }): DecisionOpResult; export declare function supersedeDecision(coordRoot: string, id: string, bySupersedingId?: string): DecisionOpResult; export declare function wontfixDecision(coordRoot: string, id: string, reason?: string): DecisionOpResult; /** * Archive a decision (terminal). Records where its output graduated, then moves * manifest + body dir into `archive/`. Idempotent-ish: safe to re-run. */ export declare function archiveDecision(coordRoot: string, id: string, graduatedTo?: string): DecisionOpResult; /** * Reopen an archived decision back to `reviewed` — the inverse of `archive`, * and the one sanctioned way out of the (otherwise terminal) archived state. * Since `archived` has no legal outgoing transition, this deliberately bypasses * the `transition` guard, the same way `archive` does its file moves outside it. * Moves the manifest + body dir back from `archive/` into the active dir and * clears `graduated_to` (a re-archive sets it fresh). Only `archived` decisions * reopen; `superseded`/`wontfix` stay terminal. * * Ordering is write-active-then-remove-archived so an interrupted call leaves * the decision reopened (active copy wins in `readManifest`) rather than lost. */ export declare function reopenDecision(coordRoot: string, id: string): DecisionOpResult; export interface ListFilter { status?: DecisionStatus; tier?: DecisionTier; stakes?: DecisionStakes; /** Only non-terminal decisions (the live queue). */ openOnly?: boolean; /** Include the archive dir in the scan (default: active only). */ includeArchived?: boolean; } export declare function listDecisions(coordRoot: string, filter?: ListFilter): DecisionManifest[]; export interface DecisionDetail { manifest: DecisionManifest; bodies: { name: string; content: string; }[]; archived: boolean; } export declare function showDecision(coordRoot: string, id: string): DecisionDetail | null; export interface SearchHit { manifest: DecisionManifest; /** A short surrounding snippet of the first match. */ snippet: string; /** Where the match landed. */ where: "question" | "context" | "resolution" | "body"; } /** * Case-insensitive substring search over manifests + bodies. Deliberately * dumb: precedent recall depends on the host's decision skill running this before * filing, not on ranking sophistication. Includes the archive (precedent * lives there). */ export declare function searchDecisions(coordRoot: string, query: string): SearchHit[]; //# sourceMappingURL=index.d.ts.map