@affordance/core 0.1.0

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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/engine/compute.d.ts +147 -0
  4. package/dist/engine/compute.js +152 -0
  5. package/dist/engine/compute.js.map +1 -0
  6. package/dist/engine/engine.d.ts +162 -0
  7. package/dist/engine/engine.js +115 -0
  8. package/dist/engine/engine.js.map +1 -0
  9. package/dist/engine/errors.d.ts +6 -0
  10. package/dist/engine/errors.js +11 -0
  11. package/dist/engine/errors.js.map +1 -0
  12. package/dist/engine/index.d.ts +16 -0
  13. package/dist/engine/index.js +15 -0
  14. package/dist/engine/index.js.map +1 -0
  15. package/dist/errors.d.ts +46 -0
  16. package/dist/errors.js +42 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/execution/delta.d.ts +42 -0
  19. package/dist/execution/delta.js +93 -0
  20. package/dist/execution/delta.js.map +1 -0
  21. package/dist/execution/errors.d.ts +94 -0
  22. package/dist/execution/errors.js +132 -0
  23. package/dist/execution/errors.js.map +1 -0
  24. package/dist/execution/execute.d.ts +179 -0
  25. package/dist/execution/execute.js +356 -0
  26. package/dist/execution/execute.js.map +1 -0
  27. package/dist/execution/index.d.ts +25 -0
  28. package/dist/execution/index.js +20 -0
  29. package/dist/execution/index.js.map +1 -0
  30. package/dist/execution/journal.d.ts +190 -0
  31. package/dist/execution/journal.js +192 -0
  32. package/dist/execution/journal.js.map +1 -0
  33. package/dist/execution/port.d.ts +81 -0
  34. package/dist/execution/port.js +101 -0
  35. package/dist/execution/port.js.map +1 -0
  36. package/dist/execution/replay.d.ts +58 -0
  37. package/dist/execution/replay.js +59 -0
  38. package/dist/execution/replay.js.map +1 -0
  39. package/dist/execution/timers.d.ts +21 -0
  40. package/dist/execution/timers.js +26 -0
  41. package/dist/execution/timers.js.map +1 -0
  42. package/dist/execution/transaction.d.ts +24 -0
  43. package/dist/execution/transaction.js +49 -0
  44. package/dist/execution/transaction.js.map +1 -0
  45. package/dist/guards/condition.d.ts +86 -0
  46. package/dist/guards/condition.js +11 -0
  47. package/dist/guards/condition.js.map +1 -0
  48. package/dist/guards/evaluate.d.ts +143 -0
  49. package/dist/guards/evaluate.js +167 -0
  50. package/dist/guards/evaluate.js.map +1 -0
  51. package/dist/guards/guard.d.ts +136 -0
  52. package/dist/guards/guard.js +118 -0
  53. package/dist/guards/guard.js.map +1 -0
  54. package/dist/guards/index.d.ts +20 -0
  55. package/dist/guards/index.js +17 -0
  56. package/dist/guards/index.js.map +1 -0
  57. package/dist/guards/time.d.ts +32 -0
  58. package/dist/guards/time.js +45 -0
  59. package/dist/guards/time.js.map +1 -0
  60. package/dist/index.d.ts +31 -0
  61. package/dist/index.js +20 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/ingestion/correlation.d.ts +54 -0
  64. package/dist/ingestion/correlation.js +78 -0
  65. package/dist/ingestion/correlation.js.map +1 -0
  66. package/dist/ingestion/index.d.ts +16 -0
  67. package/dist/ingestion/index.js +15 -0
  68. package/dist/ingestion/index.js.map +1 -0
  69. package/dist/ingestion/ingest.d.ts +196 -0
  70. package/dist/ingestion/ingest.js +275 -0
  71. package/dist/ingestion/ingest.js.map +1 -0
  72. package/dist/migration/index.d.ts +10 -0
  73. package/dist/migration/index.js +10 -0
  74. package/dist/migration/index.js.map +1 -0
  75. package/dist/migration/migrate.d.ts +119 -0
  76. package/dist/migration/migrate.js +195 -0
  77. package/dist/migration/migrate.js.map +1 -0
  78. package/dist/model/casetype.d.ts +53 -0
  79. package/dist/model/casetype.js +51 -0
  80. package/dist/model/casetype.js.map +1 -0
  81. package/dist/model/errors.d.ts +28 -0
  82. package/dist/model/errors.js +39 -0
  83. package/dist/model/errors.js.map +1 -0
  84. package/dist/model/handler.d.ts +120 -0
  85. package/dist/model/handler.js +16 -0
  86. package/dist/model/handler.js.map +1 -0
  87. package/dist/model/index.d.ts +24 -0
  88. package/dist/model/index.js +19 -0
  89. package/dist/model/index.js.map +1 -0
  90. package/dist/model/retry.d.ts +40 -0
  91. package/dist/model/retry.js +51 -0
  92. package/dist/model/retry.js.map +1 -0
  93. package/dist/model/scope.d.ts +67 -0
  94. package/dist/model/scope.js +19 -0
  95. package/dist/model/scope.js.map +1 -0
  96. package/dist/model/step.d.ts +232 -0
  97. package/dist/model/step.js +210 -0
  98. package/dist/model/step.js.map +1 -0
  99. package/dist/model/target.d.ts +175 -0
  100. package/dist/model/target.js +236 -0
  101. package/dist/model/target.js.map +1 -0
  102. package/dist/store/bootstrap.d.ts +57 -0
  103. package/dist/store/bootstrap.js +268 -0
  104. package/dist/store/bootstrap.js.map +1 -0
  105. package/dist/store/errors.d.ts +18 -0
  106. package/dist/store/errors.js +26 -0
  107. package/dist/store/errors.js.map +1 -0
  108. package/dist/store/ids.d.ts +15 -0
  109. package/dist/store/ids.js +12 -0
  110. package/dist/store/ids.js.map +1 -0
  111. package/dist/store/index.d.ts +12 -0
  112. package/dist/store/index.js +9 -0
  113. package/dist/store/index.js.map +1 -0
  114. package/dist/store/queryable.d.ts +60 -0
  115. package/dist/store/queryable.js +7 -0
  116. package/dist/store/queryable.js.map +1 -0
  117. package/dist/store/resolve.d.ts +66 -0
  118. package/dist/store/resolve.js +71 -0
  119. package/dist/store/resolve.js.map +1 -0
  120. package/dist/store/sql.d.ts +26 -0
  121. package/dist/store/sql.js +21 -0
  122. package/dist/store/sql.js.map +1 -0
  123. package/dist/store/store.d.ts +66 -0
  124. package/dist/store/store.js +108 -0
  125. package/dist/store/store.js.map +1 -0
  126. package/package.json +61 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The engine: the case-type registry bound to the case store.
3
+ *
4
+ * `createEngine({ db, caseTypes })` wires the halves together — persistence
5
+ * (`../store`), guard evaluation (`../guards`), the definition API
6
+ * (`../model`) and the execution lifecycle (`../execution`) — into the
7
+ * framework's public face: `affordances`, `explain`, `execute`, `journal`.
8
+ *
9
+ * The engine is where `asOf` defaults to now: conditions never read the
10
+ * clock, so `EngineOptions.now` — wall clock by default — is the one clock,
11
+ * threaded through the environment to everything below, and everything below
12
+ * it is pure and reconstructable.
13
+ */
14
+ import { DEFAULT_CLAIM_TTL_MS, DEFAULT_HEARTBEAT_MS, executeStep, readJournal, realTimers, withTransaction, } from '../execution/index.js';
15
+ import { correlationsFor, ingest, lookupCorrelation, normalizeIngestion, readDeadLetters, registerCorrelation, } from '../ingestion/index.js';
16
+ import { migrate } from '../migration/index.js';
17
+ import { insertCase, queryableOf, resolveCase } from '../store/index.js';
18
+ import { computeAffordances, computeExplanation, explainContext, } from './compute.js';
19
+ import { UnknownCaseTypeError } from './errors.js';
20
+ /**
21
+ * Build an engine from the app's database and its case type definitions.
22
+ * Throws at construction on duplicate case type names — the registry is
23
+ * keyed by name, which is all a case row records (definitions
24
+ * float; only the name is persisted).
25
+ */
26
+ export const createEngine = (options) => {
27
+ const registry = new Map();
28
+ for (const definition of options.caseTypes) {
29
+ if (registry.has(definition.name)) {
30
+ throw new TypeError(`createEngine: duplicate case type name '${definition.name}'`);
31
+ }
32
+ registry.set(definition.name, definition);
33
+ }
34
+ const caseTypeFor = (caseTypeName) => {
35
+ const definition = registry.get(caseTypeName);
36
+ if (definition === undefined) {
37
+ throw new UnknownCaseTypeError(caseTypeName, [...registry.keys()]);
38
+ }
39
+ return definition;
40
+ };
41
+ const now = options.now ?? (() => new Date());
42
+ // Single self-contained statements run against either arm alike; only
43
+ // transactions (and the lifecycle behind them) need the declaration itself.
44
+ const db = queryableOf(options.db);
45
+ // The widest environment any subsystem asks for (IngestionEnvironment ⊇
46
+ // ExecutionEnvironment), built once and handed to all of them.
47
+ const environment = {
48
+ db: options.db,
49
+ caseTypeFor,
50
+ claimTtlMs: options.claimTtlMs ?? DEFAULT_CLAIM_TTL_MS,
51
+ heartbeatMs: options.heartbeatMs ?? DEFAULT_HEARTBEAT_MS,
52
+ ingestion: normalizeIngestion(options.ingestion),
53
+ now,
54
+ timers: realTimers,
55
+ };
56
+ /** Load the case, resolve its type from the registry, validate state against the type's schema. */
57
+ const load = async (caseId) => {
58
+ const { definition, handle, state } = await resolveCase(db, caseTypeFor, caseId);
59
+ return {
60
+ definition,
61
+ snapshot: { id: handle.id, state, endedAt: handle.endedAt },
62
+ };
63
+ };
64
+ return {
65
+ createCase: async (caseTypeName, initialState) => {
66
+ const definition = caseTypeFor(caseTypeName);
67
+ return withTransaction(options.db, (tx) => insertCase(tx, caseTypeName, definition.state, initialState));
68
+ },
69
+ affordances: async (caseId, actor, asOf) => {
70
+ const { definition, snapshot } = await load(caseId);
71
+ return computeAffordances(definition, snapshot, {
72
+ actor,
73
+ asOf: asOf ?? now(),
74
+ });
75
+ },
76
+ affordancesOf: (handle, actor, asOf) => computeAffordances(caseTypeFor(handle.caseTypeName), { id: handle.id, state: handle.state, endedAt: handle.endedAt }, { actor, asOf: asOf ?? now() }),
77
+ explain: async (caseId, stepName, explainOptions = {}) => {
78
+ const { definition, snapshot } = await load(caseId);
79
+ // The boundary's one normalization (absent-vs-undefined actor, the
80
+ // asOf default) lives with the pure computation — see explainContext.
81
+ return computeExplanation(definition, snapshot, stepName, explainContext(explainOptions, now));
82
+ },
83
+ // Rebuilt field by field, not spread: whatever extra properties a
84
+ // caller's object drags along stop here, so the lifecycle only ever
85
+ // sees the options the public interface declares.
86
+ execute: (caseId, stepName, { actor, scopeKey, input, asOf, claimTtlMs, heartbeatMs, retry }) => executeStep(environment, caseId, stepName, {
87
+ actor,
88
+ scopeKey,
89
+ input,
90
+ asOf,
91
+ claimTtlMs,
92
+ heartbeatMs,
93
+ retry,
94
+ }),
95
+ journal: (caseId, filter) => readJournal(db, caseId, filter),
96
+ case: async (caseId) => {
97
+ const { handle, state } = await resolveCase(db, caseTypeFor, caseId);
98
+ return { ...handle, state };
99
+ },
100
+ correlationOf: (system, externalId) => lookupCorrelation(db, system, externalId),
101
+ inputSchemaFor: (caseTypeName, stepName) => caseTypeFor(caseTypeName).getStep(stepName)?.input ?? null,
102
+ stepMetadataFor: (caseTypeName, stepName) => {
103
+ const step = caseTypeFor(caseTypeName).getStep(stepName);
104
+ if (step === undefined)
105
+ return null;
106
+ return { title: step.title, description: step.description };
107
+ },
108
+ ingest: (event) => ingest(environment, event),
109
+ correlate: (registration) => registerCorrelation(db, registration),
110
+ correlations: (caseId, scopeKey) => correlationsFor(db, caseId, scopeKey),
111
+ deadLetters: (filter) => readDeadLetters(db, filter),
112
+ migrate: (caseTypeName, name, transform, migrationOptions) => migrate(environment, caseTypeName, name, transform, migrationOptions),
113
+ };
114
+ };
115
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/engine/engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The engine: the case-type registry bound to the case store.\n *\n * `createEngine({ db, caseTypes })` wires the halves together — persistence\n * (`../store`), guard evaluation (`../guards`), the definition API\n * (`../model`) and the execution lifecycle (`../execution`) — into the\n * framework's public face: `affordances`, `explain`, `execute`, `journal`.\n *\n * The engine is where `asOf` defaults to now: conditions never read the\n * clock, so `EngineOptions.now` — wall clock by default — is the one clock,\n * threaded through the environment to everything below, and everything below\n * it is pure and reconstructable.\n */\n\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n ExecuteOptions,\n ExecutionResult,\n JournalEntry,\n JournalFilter,\n} from '../execution/index.js'\nimport {\n DEFAULT_CLAIM_TTL_MS,\n DEFAULT_HEARTBEAT_MS,\n executeStep,\n readJournal,\n realTimers,\n withTransaction,\n} from '../execution/index.js'\nimport type { Instant } from '../guards/index.js'\nimport type {\n Correlation,\n CorrelationRegistration,\n DeadLetter,\n DeadLetterFilter,\n ExternalEvent,\n IngestionEnvironment,\n IngestionOptions,\n IngestionResult,\n} from '../ingestion/index.js'\nimport {\n correlationsFor,\n ingest,\n lookupCorrelation,\n normalizeIngestion,\n readDeadLetters,\n registerCorrelation,\n} from '../ingestion/index.js'\nimport type {\n MigrationOptions,\n MigrationReport,\n MigrationTransform,\n} from '../migration/index.js'\nimport { migrate } from '../migration/index.js'\nimport type { AnyCaseType, StepMetadata } from '../model/index.js'\nimport type { CaseHandle, DatabaseAccess } from '../store/index.js'\nimport { insertCase, queryableOf, resolveCase } from '../store/index.js'\nimport type {\n AffordanceExplanation,\n CaseAffordances,\n CaseSnapshot,\n ExplainRequest,\n} from './compute.js'\nimport {\n computeAffordances,\n computeExplanation,\n explainContext,\n} from './compute.js'\nimport { UnknownCaseTypeError } from './errors.js'\n\n/** Options for {@link createEngine}. */\nexport interface EngineOptions {\n /**\n * The app brings its database, declaring which kind of handle it has:\n * `{ pool }` for anything that checks out connections (`pg.Pool`, or any\n * wrapper declaring `connect()`), `{ client }` for a single connection\n * dedicated to the engine. The declaration is what lets `execute` run its\n * transactions on one connection without guessing — see\n * {@link DatabaseAccess}.\n */\n readonly db: DatabaseAccess\n /** Every case type this engine serves; a loaded case's `case_type` must name one of them. */\n readonly caseTypes: readonly AnyCaseType[]\n /**\n * How long an Execution's claim survives without a heartbeat (default 30s).\n * The floor on how long a crashed handler can hold a case.\n */\n readonly claimTtlMs?: number\n /** How often a running handler refreshes its claim (default 5s). */\n readonly heartbeatMs?: number\n /**\n * Event ingestion: how an external event's Actor is derived.\n * Correlation needs no configuration — it is a registry, not a policy.\n */\n readonly ingestion?: IngestionOptions\n /**\n * The clock — every process-side \"as of now\" below the boundary reads\n * through it: guard evaluation instants, journal timestamps, ingestion\n * receipt times. Defaults to the wall clock. Two \"nows\" it deliberately\n * does not govern: lease expiry is judged by the storage adapter's own\n * clock (the one clock all competing processes share), and the retry\n * delay / heartbeat cadence run on process timers (an internal seam the\n * lifecycle's own tests drive virtually).\n */\n readonly now?: () => Date\n}\n\n/**\n * Options for {@link Engine.explain}. Omit `actor` to probe `requires`\n * alone: `permits` conditions are then reported un-evaluated (failed, with\n * the stated reason `'not evaluated: no actor supplied'`) rather than run\n * against nothing. The normalization rule is `explainContext`, stated and\n * tested beside the pure computation it feeds.\n */\nexport type ExplainOptions = ExplainRequest\n\nexport interface Engine {\n /**\n * Create a case of a registered case type. The initial state is validated\n * against the type's schema.\n */\n createCase(\n caseTypeName: string,\n initialState: unknown,\n ): Promise<CaseHandle<unknown>>\n\n /**\n * Compute the affordances record for a case: load it, evaluate every\n * step's guard for `actor` (with scope fan-out), and return the\n * serializable available + blocked answer. Dormant (ended) cases compute\n * like any other — dormancy is annotation, never a freeze.\n */\n affordances(\n caseId: string,\n actor: unknown,\n asOf?: Instant,\n ): Promise<CaseAffordances>\n\n /**\n * {@link Engine.affordances} for a case already in hand — no second load,\n * no re-validation. Synchronous: a registry read plus the pure\n * computation. The handle must be one this engine issued\n * ({@link Engine.createCase}, {@link Engine.case}) — their `state` is the\n * validated document; a hand-built handle carries no such guarantee.\n * This is how a create route answers with the fresh case's affordances\n * without re-reading what it just wrote.\n */\n affordancesOf(\n handle: CaseHandle<unknown>,\n actor: unknown,\n asOf?: Instant,\n ): CaseAffordances\n\n /** The full per-condition breakdown for one step (× scope element) of a case. */\n explain(\n caseId: string,\n stepName: string,\n options?: ExplainOptions,\n ): Promise<AffordanceExplanation>\n\n /**\n * Execute a step on a case: claim → run → commit. The claim\n * re-evaluates the guard transactionally — the enforcement moment — so an\n * affordance that has since gone away rejects with `StepNotAvailableError`\n * carrying the current unmet conditions, and a case with an Execution\n * already in flight rejects with `CaseBusyError`.\n */\n execute(\n caseId: string,\n stepName: string,\n options: ExecuteOptions,\n ): Promise<ExecutionResult>\n\n /**\n * Read a case's journal, oldest first. Filter by `scopeKey` for\n * a per-track audit — \"everything that happened on buyer #7\".\n */\n journal(\n caseId: string,\n filter?: JournalFilter,\n ): Promise<readonly JournalEntry[]>\n\n /**\n * Read one case as persisted: the row plus its Case State validated\n * against the registered schema. Loud — an addressed read owes an answer,\n * so an unknown case or a state its schema no longer accepts throws.\n * The read ops surfaces, tests and consoles would otherwise each\n * hand-write in SQL.\n */\n case(caseId: string): Promise<CaseHandle<unknown>>\n\n /** Where an external identifier routes — the reverse of {@link Engine.correlations}. */\n correlationOf(system: string, externalId: string): Promise<Correlation | null>\n\n /**\n * The declared input schema of one step of a registered case type, or\n * `null` when the step takes no input. Synchronous — a registry read. The\n * engine owns the registry and a case names its type, so an adapter never\n * needs to be handed the case types a second time to describe inputs.\n */\n inputSchemaFor(\n caseTypeName: string,\n stepName: string,\n ): StandardSchemaV1 | null\n\n /**\n * The declared human metadata of one step of a registered case type —\n * `title` and `description`, each `null` when undeclared — or `null` for\n * an unknown step. Synchronous, a registry read like\n * {@link Engine.inputSchemaFor}, and for the same reason: adapters\n * serialize step metadata from here rather than being handed the case\n * types a second time.\n */\n stepMetadataFor(caseTypeName: string, stepName: string): StepMetadata | null\n\n /**\n * Ingest one external event: dedup, correlate, then an ordinary Execution\n * with the external system as the actor. Never throws for an\n * event's own sake — an event that could not be applied is dead-lettered\n * with a reason, so a webhook endpoint can acknowledge and move on.\n */\n ingest(event: ExternalEvent): Promise<IngestionResult>\n\n /**\n * Register an external identifier against a case out of band. Handlers\n * should prefer `ctx.correlate(...)`, which rides the same commit as the\n * state recording that the interaction was started.\n */\n correlate(registration: CorrelationRegistration): Promise<Correlation>\n\n /** Every external identifier registered against a case (× scope element). */\n correlations(\n caseId: string,\n scopeKey?: string,\n ): Promise<readonly Correlation[]>\n\n /** The dead-letter surface: events that arrived and changed nothing, with why. */\n deadLetters(filter?: DeadLetterFilter): Promise<readonly DeadLetter[]>\n\n /**\n * Restructure the state of every case of a case type, as journaled system\n * Executions (float's escape hatch). Idempotent: a case that\n * already carries the migration's marker is skipped, so re-running is a\n * no-op and an interrupted run resumes. Reach for it only when no total\n * condition can read the old shape — see `docs/migration.md`.\n */\n migrate(\n caseTypeName: string,\n name: string,\n transform: MigrationTransform,\n options?: MigrationOptions,\n ): Promise<MigrationReport>\n}\n\n/**\n * Build an engine from the app's database and its case type definitions.\n * Throws at construction on duplicate case type names — the registry is\n * keyed by name, which is all a case row records (definitions\n * float; only the name is persisted).\n */\nexport const createEngine = (options: EngineOptions): Engine => {\n const registry = new Map<string, AnyCaseType>()\n for (const definition of options.caseTypes) {\n if (registry.has(definition.name)) {\n throw new TypeError(\n `createEngine: duplicate case type name '${definition.name}'`,\n )\n }\n registry.set(definition.name, definition)\n }\n\n const caseTypeFor = (caseTypeName: string): AnyCaseType => {\n const definition = registry.get(caseTypeName)\n if (definition === undefined) {\n throw new UnknownCaseTypeError(caseTypeName, [...registry.keys()])\n }\n return definition\n }\n\n const now = options.now ?? (() => new Date())\n\n // Single self-contained statements run against either arm alike; only\n // transactions (and the lifecycle behind them) need the declaration itself.\n const db = queryableOf(options.db)\n\n // The widest environment any subsystem asks for (IngestionEnvironment ⊇\n // ExecutionEnvironment), built once and handed to all of them.\n const environment: IngestionEnvironment = {\n db: options.db,\n caseTypeFor,\n claimTtlMs: options.claimTtlMs ?? DEFAULT_CLAIM_TTL_MS,\n heartbeatMs: options.heartbeatMs ?? DEFAULT_HEARTBEAT_MS,\n ingestion: normalizeIngestion(options.ingestion),\n now,\n timers: realTimers,\n }\n\n /** Load the case, resolve its type from the registry, validate state against the type's schema. */\n const load = async (\n caseId: string,\n ): Promise<{ definition: AnyCaseType; snapshot: CaseSnapshot<unknown> }> => {\n const { definition, handle, state } = await resolveCase(\n db,\n caseTypeFor,\n caseId,\n )\n return {\n definition,\n snapshot: { id: handle.id, state, endedAt: handle.endedAt },\n }\n }\n\n return {\n createCase: async (caseTypeName, initialState) => {\n const definition = caseTypeFor(caseTypeName)\n return withTransaction(options.db, (tx) =>\n insertCase(tx, caseTypeName, definition.state, initialState),\n )\n },\n affordances: async (caseId, actor, asOf) => {\n const { definition, snapshot } = await load(caseId)\n return computeAffordances(definition, snapshot, {\n actor,\n asOf: asOf ?? now(),\n })\n },\n affordancesOf: (handle, actor, asOf) =>\n computeAffordances(\n caseTypeFor(handle.caseTypeName),\n { id: handle.id, state: handle.state, endedAt: handle.endedAt },\n { actor, asOf: asOf ?? now() },\n ),\n explain: async (caseId, stepName, explainOptions = {}) => {\n const { definition, snapshot } = await load(caseId)\n // The boundary's one normalization (absent-vs-undefined actor, the\n // asOf default) lives with the pure computation — see explainContext.\n return computeExplanation(\n definition,\n snapshot,\n stepName,\n explainContext(explainOptions, now),\n )\n },\n // Rebuilt field by field, not spread: whatever extra properties a\n // caller's object drags along stop here, so the lifecycle only ever\n // sees the options the public interface declares.\n execute: (\n caseId,\n stepName,\n { actor, scopeKey, input, asOf, claimTtlMs, heartbeatMs, retry },\n ) =>\n executeStep(environment, caseId, stepName, {\n actor,\n scopeKey,\n input,\n asOf,\n claimTtlMs,\n heartbeatMs,\n retry,\n }),\n journal: (caseId, filter) => readJournal(db, caseId, filter),\n case: async (caseId) => {\n const { handle, state } = await resolveCase(db, caseTypeFor, caseId)\n return { ...handle, state }\n },\n correlationOf: (system, externalId) =>\n lookupCorrelation(db, system, externalId),\n inputSchemaFor: (caseTypeName, stepName) =>\n caseTypeFor(caseTypeName).getStep(stepName)?.input ?? null,\n stepMetadataFor: (caseTypeName, stepName) => {\n const step = caseTypeFor(caseTypeName).getStep(stepName)\n if (step === undefined) return null\n return { title: step.title, description: step.description }\n },\n ingest: (event) => ingest(environment, event),\n correlate: (registration) => registerCorrelation(db, registration),\n correlations: (caseId, scopeKey) => correlationsFor(db, caseId, scopeKey),\n deadLetters: (filter) => readDeadLetters(db, filter),\n migrate: (caseTypeName, name, transform, migrationOptions) =>\n migrate(environment, caseTypeName, name, transform, migrationOptions),\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import { AffordanceError } from '../errors.js';
2
+ /** A loaded case's `case_type` names no case type registered with the engine. */
3
+ export declare class UnknownCaseTypeError extends AffordanceError {
4
+ readonly caseTypeName: string;
5
+ constructor(caseTypeName: string, registered: readonly string[]);
6
+ }
@@ -0,0 +1,11 @@
1
+ import { AffordanceError } from '../errors.js';
2
+ /** A loaded case's `case_type` names no case type registered with the engine. */
3
+ export class UnknownCaseTypeError extends AffordanceError {
4
+ caseTypeName;
5
+ constructor(caseTypeName, registered) {
6
+ super('not-found', `unknown case type '${caseTypeName}' — registered case types: ${registered.length > 0 ? registered.join(', ') : '(none)'}`);
7
+ this.name = 'UnknownCaseTypeError';
8
+ this.caseTypeName = caseTypeName;
9
+ }
10
+ }
11
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/engine/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE9C,iFAAiF;AACjF,MAAM,OAAO,oBAAqB,SAAQ,eAAe;IAC9C,YAAY,CAAQ;IAE7B,YAAY,YAAoB,EAAE,UAA6B;QAC7D,KAAK,CACH,WAAW,EACX,sBAAsB,YAAY,8BAChC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAClD,EAAE,CACH,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAA;QAClC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;IAClC,CAAC;CACF","sourcesContent":["import { AffordanceError } from '../errors.js'\n\n/** A loaded case's `case_type` names no case type registered with the engine. */\nexport class UnknownCaseTypeError extends AffordanceError {\n readonly caseTypeName: string\n\n constructor(caseTypeName: string, registered: readonly string[]) {\n super(\n 'not-found',\n `unknown case type '${caseTypeName}' — registered case types: ${\n registered.length > 0 ? registered.join(', ') : '(none)'\n }`,\n )\n this.name = 'UnknownCaseTypeError'\n this.caseTypeName = caseTypeName\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The affordance engine.
3
+ *
4
+ * An **affordance** is a step (× scope binding) currently available on a
5
+ * case for an actor — computed from guards over state, queryable,
6
+ * explainable (CONTEXT.md; spec §Core model). This module is the
7
+ * framework's public face: the pure computation (`computeAffordances`,
8
+ * `computeExplanation`) and the store-bound engine (`createEngine` →
9
+ * `affordances` / `explain` / `execute` / `journal`). Nothing in the
10
+ * computation ever invokes a handler — that is `../execution`'s job alone.
11
+ */
12
+ export type { Affordance, AffordanceExplanation, BlockedStep, CaseAffordances, CaseSnapshot, ExplainRequest, } from './compute.js';
13
+ export { computeAffordances, computeExplanation, explainContext, } from './compute.js';
14
+ export type { Engine, EngineOptions, ExplainOptions } from './engine.js';
15
+ export { createEngine } from './engine.js';
16
+ export { UnknownCaseTypeError } from './errors.js';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The affordance engine.
3
+ *
4
+ * An **affordance** is a step (× scope binding) currently available on a
5
+ * case for an actor — computed from guards over state, queryable,
6
+ * explainable (CONTEXT.md; spec §Core model). This module is the
7
+ * framework's public face: the pure computation (`computeAffordances`,
8
+ * `computeExplanation`) and the store-bound engine (`createEngine` →
9
+ * `affordances` / `explain` / `execute` / `journal`). Nothing in the
10
+ * computation ever invokes a handler — that is `../execution`'s job alone.
11
+ */
12
+ export { computeAffordances, computeExplanation, explainContext, } from './compute.js';
13
+ export { createEngine } from './engine.js';
14
+ export { UnknownCaseTypeError } from './errors.js';
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/engine/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,GACf,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA","sourcesContent":["/**\n * The affordance engine.\n *\n * An **affordance** is a step (× scope binding) currently available on a\n * case for an actor — computed from guards over state, queryable,\n * explainable (CONTEXT.md; spec §Core model). This module is the\n * framework's public face: the pure computation (`computeAffordances`,\n * `computeExplanation`) and the store-bound engine (`createEngine` →\n * `affordances` / `explain` / `execute` / `journal`). Nothing in the\n * computation ever invokes a handler — that is `../execution`'s job alone.\n */\n\nexport type {\n Affordance,\n AffordanceExplanation,\n BlockedStep,\n CaseAffordances,\n CaseSnapshot,\n ExplainRequest,\n} from './compute.js'\nexport {\n computeAffordances,\n computeExplanation,\n explainContext,\n} from './compute.js'\nexport type { Engine, EngineOptions, ExplainOptions } from './engine.js'\nexport { createEngine } from './engine.js'\nexport { UnknownCaseTypeError } from './errors.js'\n"]}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The framework's error taxonomy.
3
+ *
4
+ * Every way the framework refuses or fails carries a **code**: a stable,
5
+ * wire-safe name for the *kind* of refusal, distinct from the message (which
6
+ * is prose, and which an adapter may have to rewrite for the audience it is
7
+ * answering — see the HTTP contract's visibility rule).
8
+ *
9
+ * The code lives here, on the error, rather than in a mapping table at each
10
+ * edge. An adapter that turns framework errors into responses then translates
11
+ * a closed set of codes instead of enumerating error classes it has to be
12
+ * told about: adding an error class cannot silently produce a 500, because
13
+ * the class cannot be constructed without declaring what it is.
14
+ *
15
+ * Anything not an {@link AffordanceError} is not the framework's refusal —
16
+ * it is a bug or an infrastructure failure, and edges should let it through
17
+ * rather than dress it up as an answer.
18
+ */
19
+ import type { RefusalCode } from '@affordance/contract';
20
+ /**
21
+ * What kind of refusal or failure this is. Deliberately small and closed:
22
+ * these are the answers the affordance contract is specified in terms of, so
23
+ * a new code *is* a contract change — and it reads as one, because the set
24
+ * has exactly one declaration, in `@affordance/contract` (`REFUSAL_CODES`),
25
+ * from which this type derives.
26
+ */
27
+ export type AffordanceErrorCode = RefusalCode;
28
+ /**
29
+ * The base of every error the framework raises deliberately. Subclasses
30
+ * declare their {@link AffordanceErrorCode} at construction — there is no
31
+ * default, so the taxonomy cannot be extended by accident.
32
+ */
33
+ export declare class AffordanceError extends Error {
34
+ readonly code: AffordanceErrorCode;
35
+ constructor(code: AffordanceErrorCode, message: string, options?: ErrorOptions);
36
+ }
37
+ /** Narrow an unknown throw to a deliberate framework refusal. */
38
+ export declare const isAffordanceError: (error: unknown) => error is AffordanceError;
39
+ /**
40
+ * The human-readable message of whatever was thrown — Error or not. The one
41
+ * spelling of a conversion that guards, targets, and the lifecycle all need,
42
+ * because app code can throw anything.
43
+ */
44
+ export declare const thrownMessage: (error: unknown) => string;
45
+ /** Convert an unknown throw into an `Error`, preserving one that already is. */
46
+ export declare const toError: (error: unknown) => Error;
package/dist/errors.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The framework's error taxonomy.
3
+ *
4
+ * Every way the framework refuses or fails carries a **code**: a stable,
5
+ * wire-safe name for the *kind* of refusal, distinct from the message (which
6
+ * is prose, and which an adapter may have to rewrite for the audience it is
7
+ * answering — see the HTTP contract's visibility rule).
8
+ *
9
+ * The code lives here, on the error, rather than in a mapping table at each
10
+ * edge. An adapter that turns framework errors into responses then translates
11
+ * a closed set of codes instead of enumerating error classes it has to be
12
+ * told about: adding an error class cannot silently produce a 500, because
13
+ * the class cannot be constructed without declaring what it is.
14
+ *
15
+ * Anything not an {@link AffordanceError} is not the framework's refusal —
16
+ * it is a bug or an infrastructure failure, and edges should let it through
17
+ * rather than dress it up as an answer.
18
+ */
19
+ /**
20
+ * The base of every error the framework raises deliberately. Subclasses
21
+ * declare their {@link AffordanceErrorCode} at construction — there is no
22
+ * default, so the taxonomy cannot be extended by accident.
23
+ */
24
+ export class AffordanceError extends Error {
25
+ code;
26
+ constructor(code, message, options) {
27
+ super(message, options);
28
+ this.name = 'AffordanceError';
29
+ this.code = code;
30
+ }
31
+ }
32
+ /** Narrow an unknown throw to a deliberate framework refusal. */
33
+ export const isAffordanceError = (error) => error instanceof AffordanceError;
34
+ /**
35
+ * The human-readable message of whatever was thrown — Error or not. The one
36
+ * spelling of a conversion that guards, targets, and the lifecycle all need,
37
+ * because app code can throw anything.
38
+ */
39
+ export const thrownMessage = (error) => error instanceof Error ? error.message : String(error);
40
+ /** Convert an unknown throw into an `Error`, preserving one that already is. */
41
+ export const toError = (error) => error instanceof Error ? error : new Error(thrownMessage(error));
42
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAaH;;;;GAIG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,CAAqB;IAElC,YACE,IAAyB,EACzB,OAAe,EACf,OAAsB;QAEtB,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;CACF;AAED,iEAAiE;AACjE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAA4B,EAAE,CAC5E,KAAK,YAAY,eAAe,CAAA;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAc,EAAU,EAAE,CACtD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAExD,gFAAgF;AAChF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAc,EAAS,EAAE,CAC/C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA","sourcesContent":["/**\n * The framework's error taxonomy.\n *\n * Every way the framework refuses or fails carries a **code**: a stable,\n * wire-safe name for the *kind* of refusal, distinct from the message (which\n * is prose, and which an adapter may have to rewrite for the audience it is\n * answering — see the HTTP contract's visibility rule).\n *\n * The code lives here, on the error, rather than in a mapping table at each\n * edge. An adapter that turns framework errors into responses then translates\n * a closed set of codes instead of enumerating error classes it has to be\n * told about: adding an error class cannot silently produce a 500, because\n * the class cannot be constructed without declaring what it is.\n *\n * Anything not an {@link AffordanceError} is not the framework's refusal —\n * it is a bug or an infrastructure failure, and edges should let it through\n * rather than dress it up as an answer.\n */\n\nimport type { RefusalCode } from '@affordance/contract'\n\n/**\n * What kind of refusal or failure this is. Deliberately small and closed:\n * these are the answers the affordance contract is specified in terms of, so\n * a new code *is* a contract change — and it reads as one, because the set\n * has exactly one declaration, in `@affordance/contract` (`REFUSAL_CODES`),\n * from which this type derives.\n */\nexport type AffordanceErrorCode = RefusalCode\n\n/**\n * The base of every error the framework raises deliberately. Subclasses\n * declare their {@link AffordanceErrorCode} at construction — there is no\n * default, so the taxonomy cannot be extended by accident.\n */\nexport class AffordanceError extends Error {\n readonly code: AffordanceErrorCode\n\n constructor(\n code: AffordanceErrorCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options)\n this.name = 'AffordanceError'\n this.code = code\n }\n}\n\n/** Narrow an unknown throw to a deliberate framework refusal. */\nexport const isAffordanceError = (error: unknown): error is AffordanceError =>\n error instanceof AffordanceError\n\n/**\n * The human-readable message of whatever was thrown — Error or not. The one\n * spelling of a conversion that guards, targets, and the lifecycle all need,\n * because app code can throw anything.\n */\nexport const thrownMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\n/** Convert an unknown throw into an `Error`, preserving one that already is. */\nexport const toError = (error: unknown): Error =>\n error instanceof Error ? error : new Error(thrownMessage(error))\n"]}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * State deltas — what an Execution changed, as RFC 6902 JSON Patch.
3
+ *
4
+ * Every committed Execution journals the delta from the previous Case State
5
+ * to the next. A standard patch format is deliberate: the delta is
6
+ * an audit artifact read by people and machines that are not this library, so
7
+ * it should not need a bespoke decoder. Paths are RFC 6901 JSON Pointers.
8
+ *
9
+ * Pure and total over JSON values — no clock, no I/O, no schema knowledge.
10
+ */
11
+ /** One JSON Patch operation. */
12
+ export type PatchOp = {
13
+ readonly op: 'add';
14
+ readonly path: string;
15
+ readonly value: unknown;
16
+ } | {
17
+ readonly op: 'remove';
18
+ readonly path: string;
19
+ } | {
20
+ readonly op: 'replace';
21
+ readonly path: string;
22
+ readonly value: unknown;
23
+ };
24
+ /** An Execution's state delta: the ops taking the previous Case State to the next. */
25
+ export type StateDelta = readonly PatchOp[];
26
+ /**
27
+ * Structural equality over JSON values. `undefined` never appears in a
28
+ * document that round-tripped through jsonb, but a handler's return value has
29
+ * not round-tripped yet, so it is compared as-is.
30
+ */
31
+ export declare const jsonEqual: (a: unknown, b: unknown) => boolean;
32
+ /**
33
+ * The delta from one Case State document to the next. An Execution that
34
+ * changed nothing yields an empty delta — a real and unremarkable outcome
35
+ * (a handler whose only effect was external, or a no-op retry landing).
36
+ *
37
+ * Arrays are diffed positionally: element *i* against element *i*, then
38
+ * appends and trailing removals. Case State collections are keyed by the
39
+ * app's own identifiers (scope keys), so a positional diff of a reordered
40
+ * collection is verbose but never wrong.
41
+ */
42
+ export declare const diffState: (previous: unknown, next: unknown) => StateDelta;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * State deltas — what an Execution changed, as RFC 6902 JSON Patch.
3
+ *
4
+ * Every committed Execution journals the delta from the previous Case State
5
+ * to the next. A standard patch format is deliberate: the delta is
6
+ * an audit artifact read by people and machines that are not this library, so
7
+ * it should not need a bespoke decoder. Paths are RFC 6901 JSON Pointers.
8
+ *
9
+ * Pure and total over JSON values — no clock, no I/O, no schema knowledge.
10
+ */
11
+ /** RFC 6901 escaping: `~` → `~0`, `/` → `~1`. */
12
+ const escapeToken = (token) => token.replace(/~/g, '~0').replace(/\//g, '~1');
13
+ const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
14
+ /**
15
+ * Structural equality over JSON values. `undefined` never appears in a
16
+ * document that round-tripped through jsonb, but a handler's return value has
17
+ * not round-tripped yet, so it is compared as-is.
18
+ */
19
+ export const jsonEqual = (a, b) => {
20
+ if (a === b)
21
+ return true;
22
+ if (Array.isArray(a) && Array.isArray(b)) {
23
+ return (a.length === b.length &&
24
+ a.every((item, index) => jsonEqual(item, b[index])));
25
+ }
26
+ if (isPlainObject(a) && isPlainObject(b)) {
27
+ const keys = Object.keys(a);
28
+ if (keys.length !== Object.keys(b).length)
29
+ return false;
30
+ return keys.every((key) => Object.hasOwn(b, key) && jsonEqual(a[key], b[key]));
31
+ }
32
+ return false;
33
+ };
34
+ const diffInto = (ops, path, previous, next) => {
35
+ if (previous === next)
36
+ return;
37
+ if (Array.isArray(previous) && Array.isArray(next)) {
38
+ const shared = Math.min(previous.length, next.length);
39
+ for (let index = 0; index < shared; index += 1) {
40
+ diffInto(ops, `${path}/${index}`, previous[index], next[index]);
41
+ }
42
+ // Appends first, then trailing removals from the end backwards, so every
43
+ // index a `remove` names is still valid when that op is applied.
44
+ for (let index = previous.length; index < next.length; index += 1) {
45
+ ops.push({ op: 'add', path: `${path}/-`, value: next[index] });
46
+ }
47
+ for (let index = previous.length - 1; index >= next.length; index -= 1) {
48
+ ops.push({ op: 'remove', path: `${path}/${index}` });
49
+ }
50
+ return;
51
+ }
52
+ if (isPlainObject(previous) && isPlainObject(next)) {
53
+ for (const key of Object.keys(previous)) {
54
+ const child = `${path}/${escapeToken(key)}`;
55
+ if (!Object.hasOwn(next, key))
56
+ ops.push({ op: 'remove', path: child });
57
+ else
58
+ diffInto(ops, child, previous[key], next[key]);
59
+ }
60
+ for (const key of Object.keys(next)) {
61
+ if (!Object.hasOwn(previous, key)) {
62
+ ops.push({
63
+ op: 'add',
64
+ path: `${path}/${escapeToken(key)}`,
65
+ value: next[key],
66
+ });
67
+ }
68
+ }
69
+ return;
70
+ }
71
+ // Differing types, or two differing scalars: the whole node is replaced.
72
+ // An equal pair never lands here — identical references return at the top,
73
+ // equal scalars are identical, and matching containers recurse above
74
+ // (a deeply-equal subtree just emits no ops) — so no deep comparison is
75
+ // needed on the way down.
76
+ ops.push({ op: 'replace', path, value: next });
77
+ };
78
+ /**
79
+ * The delta from one Case State document to the next. An Execution that
80
+ * changed nothing yields an empty delta — a real and unremarkable outcome
81
+ * (a handler whose only effect was external, or a no-op retry landing).
82
+ *
83
+ * Arrays are diffed positionally: element *i* against element *i*, then
84
+ * appends and trailing removals. Case State collections are keyed by the
85
+ * app's own identifiers (scope keys), so a positional diff of a reordered
86
+ * collection is verbose but never wrong.
87
+ */
88
+ export const diffState = (previous, next) => {
89
+ const ops = [];
90
+ diffInto(ops, '', previous, next);
91
+ return ops;
92
+ };
93
+ //# sourceMappingURL=delta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/execution/delta.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * State deltas — what an Execution changed, as RFC 6902 JSON Patch.\n *\n * Every committed Execution journals the delta from the previous Case State\n * to the next. A standard patch format is deliberate: the delta is\n * an audit artifact read by people and machines that are not this library, so\n * it should not need a bespoke decoder. Paths are RFC 6901 JSON Pointers.\n *\n * Pure and total over JSON values — no clock, no I/O, no schema knowledge.\n */\n\n/** One JSON Patch operation. */\nexport type PatchOp =\n | { readonly op: 'add'; readonly path: string; readonly value: unknown }\n | { readonly op: 'remove'; readonly path: string }\n | { readonly op: 'replace'; readonly path: string; readonly value: unknown }\n\n/** An Execution's state delta: the ops taking the previous Case State to the next. */\nexport type StateDelta = readonly PatchOp[]\n\n/** RFC 6901 escaping: `~` → `~0`, `/` → `~1`. */\nconst escapeToken = (token: string): string =>\n token.replace(/~/g, '~0').replace(/\\//g, '~1')\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * Structural equality over JSON values. `undefined` never appears in a\n * document that round-tripped through jsonb, but a handler's return value has\n * not round-tripped yet, so it is compared as-is.\n */\nexport const jsonEqual = (a: unknown, b: unknown): boolean => {\n if (a === b) return true\n if (Array.isArray(a) && Array.isArray(b)) {\n return (\n a.length === b.length &&\n a.every((item, index) => jsonEqual(item, b[index]))\n )\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const keys = Object.keys(a)\n if (keys.length !== Object.keys(b).length) return false\n return keys.every(\n (key) => Object.hasOwn(b, key) && jsonEqual(a[key], b[key]),\n )\n }\n return false\n}\n\nconst diffInto = (\n ops: PatchOp[],\n path: string,\n previous: unknown,\n next: unknown,\n): void => {\n if (previous === next) return\n\n if (Array.isArray(previous) && Array.isArray(next)) {\n const shared = Math.min(previous.length, next.length)\n for (let index = 0; index < shared; index += 1) {\n diffInto(ops, `${path}/${index}`, previous[index], next[index])\n }\n // Appends first, then trailing removals from the end backwards, so every\n // index a `remove` names is still valid when that op is applied.\n for (let index = previous.length; index < next.length; index += 1) {\n ops.push({ op: 'add', path: `${path}/-`, value: next[index] })\n }\n for (let index = previous.length - 1; index >= next.length; index -= 1) {\n ops.push({ op: 'remove', path: `${path}/${index}` })\n }\n return\n }\n\n if (isPlainObject(previous) && isPlainObject(next)) {\n for (const key of Object.keys(previous)) {\n const child = `${path}/${escapeToken(key)}`\n if (!Object.hasOwn(next, key)) ops.push({ op: 'remove', path: child })\n else diffInto(ops, child, previous[key], next[key])\n }\n for (const key of Object.keys(next)) {\n if (!Object.hasOwn(previous, key)) {\n ops.push({\n op: 'add',\n path: `${path}/${escapeToken(key)}`,\n value: next[key],\n })\n }\n }\n return\n }\n\n // Differing types, or two differing scalars: the whole node is replaced.\n // An equal pair never lands here — identical references return at the top,\n // equal scalars are identical, and matching containers recurse above\n // (a deeply-equal subtree just emits no ops) — so no deep comparison is\n // needed on the way down.\n ops.push({ op: 'replace', path, value: next })\n}\n\n/**\n * The delta from one Case State document to the next. An Execution that\n * changed nothing yields an empty delta — a real and unremarkable outcome\n * (a handler whose only effect was external, or a no-op retry landing).\n *\n * Arrays are diffed positionally: element *i* against element *i*, then\n * appends and trailing removals. Case State collections are keyed by the\n * app's own identifiers (scope keys), so a positional diff of a reordered\n * collection is verbose but never wrong.\n */\nexport const diffState = (previous: unknown, next: unknown): StateDelta => {\n const ops: PatchOp[] = []\n diffInto(ops, '', previous, next)\n return ops\n}\n"]}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The ways an Execution can be refused or fail.
3
+ *
4
+ * Each is a distinct answer to "why didn't this run", and callers are
5
+ * expected to branch on them: {@link StepNotAvailableError} is a client-facing
6
+ * *no* with reasons attached; {@link CaseBusyError} is "not now, try again";
7
+ * {@link ClaimLostError} and {@link StepExecutionError} are failures of a run
8
+ * that did start.
9
+ */
10
+ import { AffordanceError } from '../errors.js';
11
+ import type { ConditionResult, GuardEvaluation } from '../guards/index.js';
12
+ /**
13
+ * How a step (× scope element) is named in prose: `'close-purchase'`, or
14
+ * `'request-re-sign' (buyer_7)` when scoped. One spelling, because refusal
15
+ * messages and adapter renderings must name the same affordance identically.
16
+ */
17
+ export declare const stepLabel: (stepName: string, scopeKey: string | null) => string;
18
+ /**
19
+ * The claim's transactional guard re-evaluation said no — the enforcement
20
+ * moment. Guards advise; handlers enforce: an affordance computed
21
+ * for a render is advice, and by the time the execute request arrives, state
22
+ * may have moved (another Execution committed) or the definitions may have
23
+ * (a deploy tightened the guard — definition drift is handled by the
24
+ * same mechanism as state races).
25
+ *
26
+ * The unmet conditions carried here are the *current* ones, evaluated inside
27
+ * the claim transaction, so a rejection is self-explaining: hand `unmet`
28
+ * straight back to the caller.
29
+ */
30
+ export declare class StepNotAvailableError extends AffordanceError {
31
+ readonly caseId: string;
32
+ readonly stepName: string;
33
+ readonly scopeKey: string | null;
34
+ /** False when a `requires` condition is unmet: not possible on this case, for anyone. */
35
+ readonly possible: boolean;
36
+ /** False when a `permits` condition is unmet: possible, but not for this actor. */
37
+ readonly permitted: boolean;
38
+ /** The failed condition results, verbatim from the claim-time evaluation. */
39
+ readonly unmet: readonly ConditionResult[];
40
+ /** The full evaluation record, for journaling or `explain`-style rendering. */
41
+ readonly evaluation: GuardEvaluation;
42
+ constructor(caseId: string, stepName: string, scopeKey: string | null, evaluation: GuardEvaluation);
43
+ }
44
+ /**
45
+ * Another Execution is already in flight on this case and its claim has not
46
+ * expired. Executions are serialized per case in v1 — cases advance at human
47
+ * pace, so one Execution at a time costs no real throughput — which makes
48
+ * this "not now", not "never": retry after
49
+ * {@link CaseBusyError.expiresAt} at the latest.
50
+ */
51
+ export declare class CaseBusyError extends AffordanceError {
52
+ readonly caseId: string;
53
+ /** The in-flight Execution holding the case. */
54
+ readonly executionId: string;
55
+ readonly stepName: string;
56
+ readonly scopeKey: string | null;
57
+ /** When the in-flight claim lapses if its handler stops heartbeating (ISO-8601 UTC). */
58
+ readonly expiresAt: string;
59
+ constructor(caseId: string, holder: {
60
+ executionId: string;
61
+ stepName: string;
62
+ scopeKey: string | null;
63
+ expiresAt: string;
64
+ });
65
+ }
66
+ /**
67
+ * The claim was gone (or belonged to someone else) when this Execution tried
68
+ * to commit: its lease expired mid-handler and another claimant took the case
69
+ * over. The handler's effects on the outside world already happened — they are
70
+ * at-least-once by contract and deduplicated on `executionId` — but
71
+ * its Case State write is refused, because the state it computed from is stale.
72
+ */
73
+ export declare class ClaimLostError extends AffordanceError {
74
+ readonly caseId: string;
75
+ readonly executionId: string;
76
+ /** The Execution now holding the case, if any. */
77
+ readonly heldBy: string | null;
78
+ constructor(caseId: string, executionId: string, heldBy: string | null);
79
+ }
80
+ /**
81
+ * The Execution ran and failed: the handler threw on every allowed attempt,
82
+ * or it returned a Case State the case type's schema rejects (a deterministic
83
+ * defect, failed without retry). A `failed` journal entry records it and the
84
+ * case is released.
85
+ */
86
+ export declare class StepExecutionError extends AffordanceError {
87
+ readonly caseId: string;
88
+ readonly executionId: string;
89
+ readonly stepName: string;
90
+ readonly scopeKey: string | null;
91
+ /** How many attempts ran before the Execution was given up on. */
92
+ readonly attempts: number;
93
+ constructor(caseId: string, executionId: string, stepName: string, scopeKey: string | null, attempts: number, cause: unknown);
94
+ }