@decidio/sdk 0.3.0 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/resume.d.ts CHANGED
@@ -129,11 +129,12 @@ export interface ExecutionRecord {
129
129
  at: string;
130
130
  workerStopped?: boolean;
131
131
  };
132
- /** DERIVED AT READ TIME, never persisted: set when a DENIED decision also carries an execution
133
- * marker. That pair is the single most alarming state the machine can hold — a human refused it
134
- * and something ran anyway — so it must never resolve to silence. Only the file store can hold
135
- * it (the memory store keeps one record per decision); a crash between writing a marker and
136
- * sweeping the weaker ones, or a 0.1.x report migrated beside a tombstone, both produce it. */
132
+ /** Set when a DENIED decision also carries an execution record. That pair is the single most
133
+ * alarming state the machine can hold — a human refused it and something ran anyway — so it must
134
+ * never resolve to silence. The revision chain cannot produce it (no transition publishes over
135
+ * a denial); it is DERIVED by the legacy-marker reader, and PERSISTED only when a 0.2/0.3
136
+ * directory that held a denial marker beside an execution marker is migrated onto the chain —
137
+ * there it is the record of what that layout held. */
137
138
  executionDespiteDenial?: {
138
139
  state: ExecutionState;
139
140
  at: string;
@@ -346,19 +347,37 @@ export declare class FilePendingStore implements PendingStore {
346
347
  constructor(dir: string);
347
348
  private key;
348
349
  private path;
349
- /** One marker file per state. The FILENAME carries the state so a scan never has to parse,
350
- * and the content carries the record. */
351
- private statePath;
352
- /** Durable write: temp file in the SAME directory → fsync → atomic rename. A crash can then
353
- * only leave the old complete file or the new complete file, never a truncated one. The
354
- * 0.1.x journal wrote straight to its final path with O_TRUNC, so a crash mid-write
355
- * destroyed the record that proved an action had already run. */
350
+ private revPath;
351
+ /** Legacy (0.2/0.3) marker path, read for migration and never written. */
352
+ private markerPath;
353
+ private listRevs;
354
+ private legacyMarkers;
355
+ /** Write `text` to a fresh temp file beside `target`, fsynced. Returns the temp path. */
356
+ private writeTemp;
357
+ /** Durable write for the PAYLOAD file (not the chain): temp → fsync → atomic rename. */
356
358
  private writeAtomic;
357
- /** The authoritative record: the STRONGEST state present on disk. A crash between "write the
358
- * new marker" and "remove the old one" leaves two markers; taking the strongest means an
359
- * executed action can never read back as parked, so a partial transition is safe rather than
360
- * merely unlikely. Corruption is reported, never silently treated as absent. */
361
- private resolve;
359
+ /** The head of the chain, or the migrated reading of a legacy layout that has not been
360
+ * upgraded. Corruption is REPORTED, never treated as absent: an unreadable record is the one
361
+ * that proves an action already ran. */
362
+ private readHead;
363
+ /** TEST SEAM, a no-op in production. Called with the operation name and the state about to be
364
+ * published, AFTER validation and BEFORE the publish — the exact window a concurrent writer
365
+ * can land in. The deterministic two-process suites subclass the store and block here so the
366
+ * race the retest found is reproduced on purpose, every run, rather than by luck. */
367
+ protected _barrier(_op: string, _id: string, _next: ExecutionState): void;
368
+ /** ONE TRANSITION = read head → compute successor → publish with link(). `compute` receives the
369
+ * current record (null if none) and returns the successor, or null for "nothing to write" (a
370
+ * refusal that is a return value, like a lost claim, rather than an error). Every rule runs
371
+ * INSIDE `compute`, against the revision that is about to be superseded; if a rival publishes
372
+ * first, `compute` runs again against the rival's record. */
373
+ private transition;
374
+ /** The 0.2/0.3 reading of a marker layout — strongest marker wins, a denial marker wins at any
375
+ * rank, and a denial beside an execution marker is reported as both. Used ONLY to migrate an
376
+ * older directory and to report one that could not be migrated; nothing writes markers. */
377
+ private resolveLegacyMarkers;
378
+ /** The generic ADVANCE, with every fence evaluated against the head the publish will supersede.
379
+ * `pre` is a per-method check (markInvoking's claim requirement) that must ALSO run inside the
380
+ * transition — a check that runs before it is a check against a snapshot. */
362
381
  private setState;
363
382
  put(p: PendingAction): Promise<void>;
364
383
  get(id: string): Promise<PendingAction | null>;
@@ -374,10 +393,11 @@ export declare class FilePendingStore implements PendingStore {
374
393
  markDiscarded(id: string, verdict: "rejected" | "blocked", reason?: string): Promise<void>;
375
394
  getExecution(id: string): Promise<ExecutionRecord | null>;
376
395
  listExecutions(): Promise<ExecutionRecord[]>;
377
- /** OPERATOR RECONCILIATION — see MemoryPendingStore.settle. Writes the new marker first and
378
- * then clears every other state file for this decision, including the STRONGER ones: this is
379
- * the single place where a state legitimately moves backwards, so leaving a stronger marker
380
- * behind would let resolve()'s strongest-wins rule undo the operator's decision. */
396
+ /** OPERATOR RECONCILIATION — see MemoryPendingStore.settle. The one legal backwards move, and
397
+ * now a transition like any other: two conflicting settlements both validate, both try to
398
+ * publish the next revision, exactly one wins, and the loser re-validates against the winner's
399
+ * record — where `assertSettlable` refuses it (captured evidence protects a `committed`; a
400
+ * `parked` record refuses `committed`). Before this, both won and the second erased the first. */
381
401
  settle(id: string, outcome: SettleOutcome, opts?: {
382
402
  evidence?: unknown;
383
403
  reason?: string;
@@ -385,14 +405,20 @@ export declare class FilePendingStore implements PendingStore {
385
405
  workerStopped?: boolean;
386
406
  }): Promise<void>;
387
407
  releasePreinvoke(id: string, attemptId: string): Promise<void>;
388
- /** ONE-WAY MIGRATION from the 0.1.x layout, run once at construction. `<id>.json` → parked;
389
- * `<id>.claimed` → INDETERMINATE, never parked: a claimed 0.1.x entry may have been mid-flight
390
- * when the process died, and assuming it did not run is the assumption that duplicates a
391
- * payment. Nothing ambiguous is made executable by an upgrade. */
408
+ /** ONE-WAY MIGRATION of older layouts, run at construction.
409
+ * 0.2/0.3 one-marker-per-state → the head of a fresh revision chain (markers removed)
410
+ * 0.1.x `<id>.json` with no state → parked
411
+ * 0.1.x `<id>.claimed` → INDETERMINATE, never parked: a claimed 0.1.x entry may have
412
+ * been mid-flight when the process died, and assuming it did
413
+ * not run is the assumption that duplicates a payment
414
+ * 0.1.x `reports/<id>.json` → executed_unreported (the report can be re-sent)
415
+ * Nothing ambiguous is made executable by an upgrade, and nothing unreadable is silently
416
+ * dropped: a key whose markers cannot be parsed is left exactly as found and reads as corrupt. */
392
417
  migrateLegacy(): {
393
418
  migrated: number;
394
419
  indeterminate: number;
395
420
  reports: number;
421
+ chained: number;
396
422
  };
397
423
  /** 0.1.x wrote execution reports to `<pendingDir>/reports/<id>.json` — evidence that an action
398
424
  * ALREADY RAN and still owes Decidio a report. 0.2.0 keeps that in the ledger, so without this
@@ -424,7 +450,10 @@ export declare class DecidioSuspendedError extends Error {
424
450
  /** `indeterminate` (0.2.0): the handler was INVOKED and then threw. Whether the downstream
425
451
  * effect committed is not knowable from here, so the action is never retried automatically —
426
452
  * it waits for reconcile(). Callers that switch exhaustively on this union must handle it. */
427
- export type ResumeStatus = "executed" | "rejected" | "blocked" | "no_handler" | "unknown" | "indeterminate";
453
+ /** `already_resolved` (0.3.5): this resume signal LOST the transition race to an earlier, final answer
454
+ * (a denial, or an execution that already ran) — `reason` names which. It used to read `unknown`,
455
+ * which is the word for "not ours"; safety was right and the diagnosis was not (prodsec V033-06). */
456
+ export type ResumeStatus = "executed" | "rejected" | "blocked" | "no_handler" | "unknown" | "indeterminate" | "already_resolved";
428
457
  /** The states a durable action can actually be in — every one of them nameable, including the
429
458
  * one nobody likes. */
430
459
  export type RecoveryState =
@@ -446,7 +475,9 @@ export type RecoveryState =
446
475
  * and must never be retried automatically. */
447
476
  | "indeterminate"
448
477
  /** Executed, result captured, report not yet accepted by Decidio. Safe to `reconfirm`. */
449
- | "executed_unreported";
478
+ | "executed_unreported"
479
+ /** a parked row whose payload is gone: cannot execute, not awaiting anyone (0.3.5) */
480
+ | "orphaned_state";
450
481
  export interface RecoveryItem {
451
482
  decisionId: string;
452
483
  action?: string;