@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 @@
1
+ {"version":3,"file":"step.js","sourceRoot":"","sources":["../../src/model/step.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Step definitions.\n *\n * A **step** is an independently-defined unit of possible work on a case: a\n * guard plus a handler (CONTEXT.md). Steps never declare ordering —\n * sequencing is data dependencies between guards. `step()` validates the\n * definition loudly at construction time: a malformed step should fail the\n * deploy, not an evaluation.\n */\n\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { AffordanceError } from '../errors.js'\nimport type { ConditionMap, Guard, GuardSection } from '../guards/index.js'\nimport { guardEntries } from '../guards/index.js'\nimport type {\n ErasedStepHandler,\n ScopedStepHandler,\n StepHandler,\n} from './handler.js'\nimport type { RetryOptions, RetryPolicy } from './retry.js'\nimport { normalizeRetry } from './retry.js'\nimport type { ScopeDeclaration, ScopedConditionMap } from './scope.js'\nimport { eraseScopedConditionMap } from './scope.js'\n\n/** Options for an unscoped step. */\nexport interface StepOptions<TState, TActor = unknown, TInput = undefined> {\n /** The step's name — unique within its case type; half of a scoped affordance's identity. */\n readonly name: string\n /**\n * A short human label for the step (\"Issue the funding call\"). Definition\n * metadata, not identity: journal entries and refusals name the step by\n * `name`; adapters serialize the title so a client renders steps without\n * an out-of-band label table.\n */\n readonly title?: string\n /**\n * A sentence on what the step does and when to take it — what makes the\n * affordance contract a usable tool list for a caller (an agent included)\n * that has never seen this case type.\n */\n readonly description?: string\n /** Case conditions: when one fails the step is not possible on this case, for anyone. */\n readonly requires?: ConditionMap<TState, TActor>\n /** Actor conditions: when one fails the step is possible but not permitted for this actor. */\n readonly permits?: ConditionMap<TState, TActor>\n /**\n * Optional input schema (any Standard Schema — zod v4 qualifies). Validated\n * by `validateStepInput` before the handler runs.\n */\n readonly input?: StandardSchemaV1<unknown, TInput>\n /**\n * How many times a failed attempt is retried, and how long between\n * attempts. Defaults to three attempts with exponential backoff; set\n * `{ maxAttempts: 1 }` to disable retry for this step.\n */\n readonly retry?: RetryOptions\n /** The step's effect function — the only thing that mutates Case State. */\n readonly handler: StepHandler<TState, TActor, TInput>\n}\n\n/** Options for a scoped step: an unscoped step plus the scope declaration. */\nexport interface ScopedStepOptions<\n TState,\n TElement,\n TActor = unknown,\n TInput = undefined,\n> {\n readonly name: string\n readonly title?: string\n readonly description?: string\n /** The collection the step ranges over and how each element is identified. */\n readonly scope: ScopeDeclaration<TState, TElement>\n readonly requires?: ScopedConditionMap<TState, TElement, TActor>\n readonly permits?: ScopedConditionMap<TState, TElement, TActor>\n readonly input?: StandardSchemaV1<unknown, TInput>\n readonly retry?: RetryOptions\n readonly handler: ScopedStepHandler<TState, TElement, TActor, TInput>\n}\n\n/**\n * A step definition as held by a case type and consumed by the engine — the\n * authoring generics (scope element, input) erased to the case-state level.\n * The options types above carry the precise authoring shapes; this is the\n * machine-facing normal form.\n */\nexport interface StepDefinition<TState, TActor = unknown> {\n readonly name: string\n /** The declared human label, or `null` — clients fall back to `name`. */\n readonly title: string | null\n /** The declared what-and-when sentence, or `null`. */\n readonly description: string | null\n /**\n * The step's guard in the guards module's shape, ready for `evaluateGuard`.\n * For a scoped step the engine evaluates it once per selected element with\n * the element bound as `scope`.\n */\n readonly guard: Guard<TState, TActor>\n /** The scope declaration (element type erased), or `null` for an unscoped step. */\n readonly scope: {\n readonly select: (state: TState) => readonly unknown[]\n readonly key: (element: unknown) => string\n } | null\n /** The declared input schema, or `null` when the step takes no input. */\n readonly input: StandardSchemaV1 | null\n /** The normalized retry policy the execution lifecycle applies to this step. */\n readonly retry: RetryPolicy\n /** The step's handler — invoked only by the execution lifecycle. */\n readonly handler: ErasedStepHandler<TState, TActor>\n}\n\n/**\n * A step's declared human metadata, as `Engine.stepMetadataFor` answers it —\n * the serializable slice of a {@link StepDefinition} an adapter puts on the\n * wire.\n */\nexport interface StepMetadata {\n readonly title: string | null\n readonly description: string | null\n}\n\n/** Whether a value is a Standard-Schema instance — the one spelling of the check. */\nexport const isStandardSchema = (value: unknown): value is StandardSchemaV1 =>\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { '~standard'?: unknown })['~standard'] === 'object'\n\n/**\n * Whether a value is plausibly a {@link StepDefinition} — the structural\n * check `caseType()` applies to every step it is given.\n */\nexport const looksLikeStepDefinition = (\n value: unknown,\n): value is StepDefinition<unknown, unknown> =>\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { name?: unknown }).name === 'string' &&\n typeof (value as { handler?: unknown }).handler === 'function' &&\n typeof (value as { guard?: unknown }).guard === 'object'\n\n/**\n * Validate one guard section map at definition time: every entry must be a\n * condition function or an `anyOf(...)` group\n * (unscoped only — scoped maps admit conditions alone).\n *\n * The classification and the `requires.escrowReady` addressing both come from\n * `guardEntries`, so what a definition is allowed to contain is decided\n * against the same walk evaluation uses — a definition `step()` accepts is\n * one `evaluateGuard` can read.\n */\nconst validateConditionMap = (\n stepName: string,\n section: GuardSection,\n map: Readonly<Record<string, unknown>> | undefined,\n allowAnyOf: boolean,\n): void => {\n if (map === undefined) return\n if (typeof map !== 'object' || map === null || Array.isArray(map)) {\n throw new TypeError(\n `step '${stepName}': ${section} must be a plain object of named conditions`,\n )\n }\n const section_ = { [section]: map } as Guard<unknown, unknown>\n for (const entry of guardEntries(section_)) {\n if (entry.kind === 'condition') continue\n if (entry.kind === 'anyOf' && allowAnyOf) continue\n const allowed = allowAnyOf\n ? 'a condition function or an anyOf(...) group'\n : 'a condition function (anyOf is not part of the scoped guard surface)'\n throw new TypeError(\n `step '${stepName}': ${entry.address} must be ${allowed}`,\n )\n }\n}\n\nconst validateCommon = (options: {\n name: unknown\n title?: unknown\n description?: unknown\n input?: unknown\n handler: unknown\n}): string => {\n const { name } = options\n if (typeof name !== 'string' || name.trim() === '') {\n throw new TypeError('step: name must be a non-empty string')\n }\n if (typeof options.handler !== 'function') {\n throw new TypeError(`step '${name}': handler must be an async function`)\n }\n if (options.input !== undefined && !isStandardSchema(options.input)) {\n throw new TypeError(\n `step '${name}': input must be a Standard Schema (e.g. a zod schema)`,\n )\n }\n for (const field of ['title', 'description'] as const) {\n const value = options[field]\n if (\n value !== undefined &&\n (typeof value !== 'string' || value.trim() === '')\n ) {\n throw new TypeError(\n `step '${name}': ${field} must be a non-empty string when given`,\n )\n }\n }\n return name\n}\n\n/**\n * Define a step. Two shapes, discriminated by the presence of `scope`:\n *\n * ```ts\n * // Standalone form (internal — apps author through {@link stepsOf}, which\n * // binds the generics once and takes the same options annotation-free):\n * // type parameters are inferred from annotations in the options — annotate\n * // the state parameter of a condition (and the actor on a permits ctx).\n * step({\n * name: 'issue-funding-call',\n * requires: { escrowReady: (s: Purchase) => s.escrow?.status === 'open' },\n * permits: { isOrganizer: (_s: Purchase, ctx: ConditionContext<Ops>) => ctx.actor.roles.includes('organizer') },\n * handler: async (s, ctx) => s,\n * })\n *\n * // Scoped, standalone form: annotating scope.select anchors both the state\n * // and element types; conditions then read the bound element as ctx.scope,\n * // fully typed (through stepsOf, even the select annotation goes away):\n * step({\n * name: 'escalate-verification',\n * scope: { select: (s: Purchase) => s.buyers.filter(b => b.verification?.status === 'review'), key: b => b.id },\n * requires: { flagged: (s: Purchase, ctx) => (ctx.scope as Buyer).verification?.flaggedAt != null },\n * handler: async (s, ctx) => s,\n * })\n * ```\n *\n * Validates loudly at construction time — malformed guard entries, a\n * malformed scope declaration, a non-function handler, or a non-schema\n * `input` all throw `TypeError` (definition-time validation; a definition\n * bug should fail the deploy, not an evaluation).\n *\n * The scoped overload is declared first: type parameters are meant to be\n * inferred, and inference resolves each call shape against its own overload\n * (explicit type-argument lists interact badly with overloaded\n * context-sensitive options — annotate inside the options instead).\n */\nexport function step<TState, TElement, TActor = unknown, TInput = undefined>(\n options: ScopedStepOptions<TState, TElement, TActor, TInput>,\n): StepDefinition<TState, TActor>\nexport function step<TState, TActor = unknown, TInput = undefined>(\n options: StepOptions<TState, TActor, TInput>,\n): StepDefinition<TState, TActor>\nexport function step<TState, TActor>(\n options:\n | StepOptions<TState, TActor, unknown>\n | ScopedStepOptions<TState, unknown, TActor, unknown>,\n): StepDefinition<TState, TActor> {\n const name = validateCommon(options)\n const scoped = 'scope' in options && options.scope !== undefined\n let scope: StepDefinition<TState, TActor>['scope'] = null\n let guard: Guard<TState, TActor>\n\n if (scoped) {\n const declaration = (\n options as ScopedStepOptions<TState, unknown, TActor, unknown>\n ).scope\n if (\n typeof declaration !== 'object' ||\n declaration === null ||\n typeof declaration.select !== 'function' ||\n typeof declaration.key !== 'function'\n ) {\n throw new TypeError(\n `step '${name}': scope must be { select: state => elements, key: element => string }`,\n )\n }\n validateConditionMap(name, 'requires', options.requires, false)\n validateConditionMap(name, 'permits', options.permits, false)\n scope = { select: declaration.select, key: declaration.key }\n // Erasure, not conversion: scoped maps are runtime-identical to unscoped\n // ones; evaluation binds the scope element the scoped types promise.\n const scopedOptions = options as ScopedStepOptions<\n TState,\n unknown,\n TActor,\n unknown\n >\n guard = {\n ...(scopedOptions.requires !== undefined && {\n requires: eraseScopedConditionMap(scopedOptions.requires),\n }),\n ...(scopedOptions.permits !== undefined && {\n permits: eraseScopedConditionMap(scopedOptions.permits),\n }),\n }\n } else {\n const unscoped = options as StepOptions<TState, TActor, unknown>\n validateConditionMap(name, 'requires', unscoped.requires, true)\n validateConditionMap(name, 'permits', unscoped.permits, true)\n guard = {\n ...(unscoped.requires !== undefined && { requires: unscoped.requires }),\n ...(unscoped.permits !== undefined && { permits: unscoped.permits }),\n }\n }\n\n return {\n name,\n title: options.title ?? null,\n description: options.description ?? null,\n guard,\n scope,\n input: options.input ?? null,\n retry: normalizeRetry(name, options.retry),\n // The one erasure cast for handlers: the authored context (typed input,\n // typed scope element) is what the execution lifecycle constructs; see\n // ErasedStepHandler.\n handler: options.handler as unknown as ErasedStepHandler<TState, TActor>,\n }\n}\n\n/**\n * The `step()` authoring surface with the case's state and actor types fixed.\n *\n * Same two call shapes as `step()` — scoped first, discriminated by the\n * presence of `scope` — but `TState`/`TActor` are already substituted, so\n * only the per-step generics (scope element, input) remain to be inferred.\n * That is what makes annotation-free authoring work: conditions and handlers\n * no longer participate in inferring the state type, they just receive it\n * contextually, and a scoped step's element type anchors on `scope.select`\n * alone (so `ctx.scope` is the element, with no `undefined` to narrow away).\n */\nexport interface BoundStep<TState, TActor = unknown> {\n <TElement, TInput = undefined>(\n options: ScopedStepOptions<TState, TElement, TActor, TInput>,\n ): StepDefinition<TState, TActor>\n <TInput = undefined>(\n options: StepOptions<TState, TActor, TInput>,\n ): StepDefinition<TState, TActor>\n}\n\n/**\n * A value-level carrier for `stepsOf`'s actor type — nothing but the type.\n * Exists because `TActor` has no value to be inferred from (a case's actor\n * shape is app-defined and never materializes at definition time), and\n * spelling it as a type argument would force spelling the schema's type too\n * (TypeScript has no partial type-argument inference).\n */\nexport interface ActorMarker<TActor> {\n readonly __actor?: TActor\n}\n\nconst ACTOR_MARKER: ActorMarker<never> = Object.freeze({})\n\n/** Name the actor type of a `stepsOf` factory: `stepsOf(PurchaseState, actor<PurchaseActor>())`. */\nexport const actor = <TActor>(): ActorMarker<TActor> => ACTOR_MARKER\n\n/**\n * Bind `step()` to a case's state schema — the schema-anchored authoring\n * factory.\n *\n * ```ts\n * const purchaseStep = stepsOf(PurchaseState, actor<PurchaseActor>())\n *\n * purchaseStep({\n * name: 'issue-funding-call',\n * requires: { escrowReady: s => s.escrow.status === 'open' }, // s: inferred from the schema\n * permits: { isOrganizer: (_s, ctx) => hasRole(ctx.actor, 'organizer') },\n * handler: async s => s,\n * })\n *\n * purchaseStep({\n * name: 'escalate-verification',\n * scope: { select: s => s.buyers.filter(b => b.verification.status === 'review'), key: b => b.id },\n * requires: { flagged: (_s, ctx) => ctx.scope.verification.flaggedAt !== null }, // ctx.scope: Buyer\n * handler: async s => s,\n * })\n * ```\n *\n * The state type is derived from the schema *value* — the same\n * `InferOutput` derivation `caseType` performs — so the factory and the case\n * type are anchored to one declaration and cannot drift apart: the state a\n * condition sees is definitionally the state the engine validates against.\n * The per-condition annotations the bare `step()` needs\n * (`(s: Purchase) => …`) disappear, because `TState` is no longer inferred\n * from the options.\n *\n * The second argument exists only to name the actor type and carries no\n * runtime information; omit it for an untyped actor. Define one factory per\n * case type module, next to the schema, and author every step of that case\n * type through it.\n *\n * Returns `step` itself, re-typed — a step authored through the factory is\n * bit-for-bit an ordinary step definition. `step` is deliberately not part\n * of the package barrel: this factory is the public authoring surface, and\n * a helper that builds steps generically should accept a\n * {@link BoundStep} rather than reach for the unbound `step`.\n * Throws at definition time when `state` is not a Standard Schema, like\n * every other malformed-definition case in this module.\n */\nexport const stepsOf = <S extends StandardSchemaV1, TActor = unknown>(\n state: S,\n _actor?: ActorMarker<TActor>,\n): BoundStep<StandardSchemaV1.InferOutput<S>, TActor> => {\n if (!isStandardSchema(state)) {\n throw new TypeError(\n 'stepsOf: state must be a Standard Schema (e.g. a zod schema)',\n )\n }\n return step\n}\n\n/** A step's declared input failed validation against its input schema. */\nexport class StepInputValidationError extends AffordanceError {\n readonly stepName: string\n readonly issues: readonly StandardSchemaV1.Issue[]\n\n constructor(stepName: string, issues: readonly StandardSchemaV1.Issue[]) {\n super(\n 'invalid-input',\n `invalid input for step '${stepName}': ${issues.map((issue) => issue.message).join('; ')}`,\n )\n this.name = 'StepInputValidationError'\n this.stepName = stepName\n this.issues = issues\n }\n}\n\n/**\n * Validate a step's input against its declared input schema — the validation\n * plumbing the execution lifecycle runs before invoking the handler (\"validated before the\n * handler runs\"). Returns the schema *output* (defaults applied). A step\n * without an input schema accepts only `undefined` and yields `undefined`;\n * anything else is a caller bug and throws.\n */\nexport const validateStepInput = async <TState, TActor>(\n definition: StepDefinition<TState, TActor>,\n input: unknown,\n): Promise<unknown> => {\n if (definition.input === null) {\n if (input !== undefined) {\n throw new StepInputValidationError(definition.name, [\n { message: 'step declares no input schema, but input was provided' },\n ])\n }\n return undefined\n }\n const result = await definition.input['~standard'].validate(input)\n if (result.issues)\n throw new StepInputValidationError(definition.name, result.issues)\n return result.value\n}\n"]}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Step targeting: resolving "step X, of element K, on this state".
3
+ *
4
+ * A **step target** is a step definition plus its scope binding — the pair
5
+ * that an affordance's identity (step × scope key) names. Everything
6
+ * that acts on a step goes through here first: the execution lifecycle's
7
+ * claim, `explain`, audit replay, and the affordance listing itself.
8
+ *
9
+ * It lives in the model because it is a fact about a {@link StepDefinition}
10
+ * and a Case State, and nothing more — no store, no registry, no clock. Put
11
+ * anywhere higher it would drag every consumer's imports upward toward the
12
+ * engine, which is a facade none of them should need.
13
+ *
14
+ * ## One fan-out, filtered
15
+ *
16
+ * {@link selectTargets} is the only implementation of fan-out — expanding
17
+ * one scoped step into its per-element targets — and it makes the one
18
+ * distinction every consumer needs:
19
+ *
20
+ * - A **defective selector** — one that throws over historical state, or
21
+ * returns something other than an array — is a *selection failure*,
22
+ * reported as `failure` with no targets. Each caller decides what its
23
+ * audience deserves: the affordance listing renders it as a blocked
24
+ * `$scope` entry, a sweep skips the step, {@link resolveTarget} throws.
25
+ * - A **key integrity violation** — duplicate or malformed scope keys —
26
+ * corrupts affordance identity itself, so {@link ScopeKeyError} is loud
27
+ * through every path. No caller may absorb it into an empty selection.
28
+ *
29
+ * Addressing — "step X (of element K) on this state" — is likewise one
30
+ * implementation, {@link addressTarget}, answering with the target or a
31
+ * {@link TargetAddressFailure} that names its kind. Its filters:
32
+ * {@link resolveTarget} (loud: an addressed caller is owed the precise
33
+ * failure, so it throws), audit replay (lenient: a sweep over the Journal
34
+ * reports the failure as a value and keeps going), and `explain` (loud,
35
+ * with one deliberate exception — a `defective-selector` failure is
36
+ * *answered*, because the listing published that exact link).
37
+ */
38
+ import { SCOPE_FAILURE_CONDITION } from '@affordance/contract';
39
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
40
+ import type { GuardEvaluation, Instant } from '../guards/index.js';
41
+ import type { CaseTypeDefinition } from './casetype.js';
42
+ import { ScopeKeyError, UnknownStepError } from './errors.js';
43
+ import type { StepDefinition } from './step.js';
44
+ /**
45
+ * The synthetic condition name under which a throwing or malformed scope
46
+ * selector is reported. `$`-prefixed so it can never collide with an
47
+ * author's condition names. Declared by `@affordance/contract` — it reaches
48
+ * clients through `blocked[].unmet[].name`, so it is wire vocabulary, and
49
+ * the wire owns it; re-exported here for the engine's own consumers.
50
+ */
51
+ export { SCOPE_FAILURE_CONDITION };
52
+ /**
53
+ * A defective selection as a full evaluation record — the
54
+ * possible/permitted/available verdict stated once. The selection failed, so
55
+ * nothing about the step is possible
56
+ * on this case; `permits` were never reachable (they may read the element),
57
+ * reported vacuously satisfied — the requires-level `$scope` failure is the
58
+ * answer. The listing's blocked entry and `explain`'s answer for the same
59
+ * link both derive from this.
60
+ */
61
+ export declare const scopeFailureEvaluation: (asOf: string, failure: {
62
+ readonly reason: string;
63
+ }) => GuardEvaluation;
64
+ /** What a guard is evaluated against, beyond state: the actor and the instant. */
65
+ export interface ComputationContext<TActor = unknown> {
66
+ readonly actor: TActor;
67
+ /**
68
+ * The instant to evaluate as of — always explicit. Conditions cannot read
69
+ * the clock, so defaulting to now is the engine's job, done once at its
70
+ * boundary; everything below it is pure and reconstructable.
71
+ */
72
+ readonly asOf: Instant;
73
+ }
74
+ /** One element of a scoped step's selection, with the key that identifies it. */
75
+ export interface ScopeBinding {
76
+ readonly element: unknown;
77
+ readonly key: string;
78
+ }
79
+ /**
80
+ * A step addressed by name (× scope key, if scoped): the step definition and
81
+ * its scope binding, resolved against a given Case State.
82
+ */
83
+ export interface StepTarget<TState, TActor = unknown> {
84
+ readonly step: StepDefinition<TState, TActor>;
85
+ /**
86
+ * The Case State the target was resolved against — the document its guard
87
+ * is evaluated over. Carried on the target so a binding can never be
88
+ * evaluated against a different document than the one that produced it.
89
+ */
90
+ readonly state: TState;
91
+ /** The bound element and its key, or `null` for an unscoped step. */
92
+ readonly binding: ScopeBinding | null;
93
+ }
94
+ /** The outcome of scope fan-out: the step's targets, or why selection produced none. */
95
+ export interface TargetSelection<TState, TActor = unknown> {
96
+ readonly targets: readonly StepTarget<TState, TActor>[];
97
+ /** The selection failure — a defective selector — or `null` when selection succeeded. */
98
+ readonly failure: {
99
+ readonly reason: string;
100
+ } | null;
101
+ }
102
+ /**
103
+ * The one implementation of scope fan-out — every consumer (the affordance
104
+ * listing, read tracing, addressing)
105
+ * is a filter over this function.
106
+ *
107
+ * An unscoped step yields exactly one target; a scoped step yields one per
108
+ * selected element, or none with a `failure` naming why when the selector is
109
+ * defective. {@link ScopeKeyError} — identity corruption — propagates: it is
110
+ * never a selection failure, and no caller may absorb it into "no targets".
111
+ */
112
+ export declare const selectTargets: <TState, TActor>(step: StepDefinition<TState, TActor>, state: TState) => TargetSelection<TState, TActor>;
113
+ /**
114
+ * Why an address does not resolve, by kind. Each failure carries the error
115
+ * the loud filter would throw, so the diagnosis (including the
116
+ * currently-valid scope keys, where knowable) is constructed exactly once
117
+ * and reads identically whether it is thrown at an addressed caller or
118
+ * reported by a sweep. The kind is what lets a filter treat one failure
119
+ * differently without re-deriving how the address failed — `explain`
120
+ * *answers* a defective selector (the listing published that exact link)
121
+ * and throws everything else.
122
+ */
123
+ export type TargetAddressFailure = {
124
+ /** The named step is not declared on the case type. */
125
+ readonly kind: 'unknown-step';
126
+ readonly error: UnknownStepError;
127
+ } | {
128
+ /** The selector threw or returned a non-array over this Case State. */
129
+ readonly kind: 'defective-selector';
130
+ /** The selection failure's own words — what the listing's `$scope` entry reports. */
131
+ readonly reason: string;
132
+ readonly error: ScopeKeyError;
133
+ } | {
134
+ /**
135
+ * A scope-key problem on an otherwise healthy step: a key given for an
136
+ * unscoped step, a missing key on a scoped one, or a key no selected
137
+ * element carries.
138
+ */
139
+ readonly kind: 'unscoped-key' | 'missing-key' | 'unknown-key';
140
+ readonly error: ScopeKeyError;
141
+ };
142
+ /** The outcome of addressing a step: the target, or the precise failure. */
143
+ export type TargetAddress<TState, TActor = unknown> = {
144
+ readonly target: StepTarget<TState, TActor>;
145
+ readonly failure: null;
146
+ } | {
147
+ readonly target: null;
148
+ readonly failure: TargetAddressFailure;
149
+ };
150
+ /**
151
+ * The one implementation of addressing — "step X (of element K) on this
152
+ * state". Total over everything except key integrity: an undeclared step
153
+ * name, a missing/unknown scope key on a scoped step, a scope key on an
154
+ * unscoped step, and a defective scope selector all come back as `failure`.
155
+ * {@link ScopeKeyError} raised for duplicate or malformed keys (identity
156
+ * corruption, from {@link selectScope}) still propagates — no filter may
157
+ * absorb it.
158
+ */
159
+ export declare const addressTarget: <S extends StandardSchemaV1, TActor>(definition: CaseTypeDefinition<S, TActor>, state: StandardSchemaV1.InferOutput<S>, stepName: string, scopeKey?: string) => TargetAddress<StandardSchemaV1.InferOutput<S>, TActor>;
160
+ /**
161
+ * Resolve "step X (of element K) on this state" — the addressing shared by
162
+ * `explain` (a targeted probe) and the execution lifecycle's claim.
163
+ *
164
+ * The loud filter over {@link addressTarget}: an addressed caller named a
165
+ * case and a step and is owed an answer about *those*, so every way of
166
+ * failing to address throws with its precise message. Addressing a step you
167
+ * cannot name is a caller bug, not a blocked affordance.
168
+ */
169
+ export declare const resolveTarget: <S extends StandardSchemaV1, TActor>(definition: CaseTypeDefinition<S, TActor>, state: StandardSchemaV1.InferOutput<S>, stepName: string, scopeKey?: string) => StepTarget<StandardSchemaV1.InferOutput<S>, TActor>;
170
+ /**
171
+ * Evaluate one addressed step's guard against the state it was resolved on —
172
+ * the single evaluation shared by `explain` and the claim, so the enforcement
173
+ * moment and the explanation of it can never drift apart.
174
+ */
175
+ export declare const evaluateTarget: <TState, TActor>(target: StepTarget<TState, TActor>, ctx: ComputationContext<TActor>) => GuardEvaluation;
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Step targeting: resolving "step X, of element K, on this state".
3
+ *
4
+ * A **step target** is a step definition plus its scope binding — the pair
5
+ * that an affordance's identity (step × scope key) names. Everything
6
+ * that acts on a step goes through here first: the execution lifecycle's
7
+ * claim, `explain`, audit replay, and the affordance listing itself.
8
+ *
9
+ * It lives in the model because it is a fact about a {@link StepDefinition}
10
+ * and a Case State, and nothing more — no store, no registry, no clock. Put
11
+ * anywhere higher it would drag every consumer's imports upward toward the
12
+ * engine, which is a facade none of them should need.
13
+ *
14
+ * ## One fan-out, filtered
15
+ *
16
+ * {@link selectTargets} is the only implementation of fan-out — expanding
17
+ * one scoped step into its per-element targets — and it makes the one
18
+ * distinction every consumer needs:
19
+ *
20
+ * - A **defective selector** — one that throws over historical state, or
21
+ * returns something other than an array — is a *selection failure*,
22
+ * reported as `failure` with no targets. Each caller decides what its
23
+ * audience deserves: the affordance listing renders it as a blocked
24
+ * `$scope` entry, a sweep skips the step, {@link resolveTarget} throws.
25
+ * - A **key integrity violation** — duplicate or malformed scope keys —
26
+ * corrupts affordance identity itself, so {@link ScopeKeyError} is loud
27
+ * through every path. No caller may absorb it into an empty selection.
28
+ *
29
+ * Addressing — "step X (of element K) on this state" — is likewise one
30
+ * implementation, {@link addressTarget}, answering with the target or a
31
+ * {@link TargetAddressFailure} that names its kind. Its filters:
32
+ * {@link resolveTarget} (loud: an addressed caller is owed the precise
33
+ * failure, so it throws), audit replay (lenient: a sweep over the Journal
34
+ * reports the failure as a value and keeps going), and `explain` (loud,
35
+ * with one deliberate exception — a `defective-selector` failure is
36
+ * *answered*, because the listing published that exact link).
37
+ */
38
+ import { SCOPE_FAILURE_CONDITION } from '@affordance/contract';
39
+ import { thrownMessage } from '../errors.js';
40
+ import { evaluateGuard } from '../guards/index.js';
41
+ import { ScopeKeyError, UnknownStepError } from './errors.js';
42
+ /**
43
+ * The synthetic condition name under which a throwing or malformed scope
44
+ * selector is reported. `$`-prefixed so it can never collide with an
45
+ * author's condition names. Declared by `@affordance/contract` — it reaches
46
+ * clients through `blocked[].unmet[].name`, so it is wire vocabulary, and
47
+ * the wire owns it; re-exported here for the engine's own consumers.
48
+ */
49
+ export { SCOPE_FAILURE_CONDITION };
50
+ /**
51
+ * The synthetic `$scope` entry as a condition result — the one spelling of
52
+ * how a failed scope selection reports into an evaluation-shaped record: a
53
+ * failed condition carrying the selector's reason.
54
+ */
55
+ const scopeConditionResult = (failure) => ({
56
+ name: SCOPE_FAILURE_CONDITION,
57
+ section: 'requires',
58
+ kind: 'condition',
59
+ passed: false,
60
+ reason: failure.reason,
61
+ });
62
+ /**
63
+ * A defective selection as a full evaluation record — the
64
+ * possible/permitted/available verdict stated once. The selection failed, so
65
+ * nothing about the step is possible
66
+ * on this case; `permits` were never reachable (they may read the element),
67
+ * reported vacuously satisfied — the requires-level `$scope` failure is the
68
+ * answer. The listing's blocked entry and `explain`'s answer for the same
69
+ * link both derive from this.
70
+ */
71
+ export const scopeFailureEvaluation = (asOf, failure) => ({
72
+ asOf,
73
+ possible: false,
74
+ permitted: true,
75
+ available: false,
76
+ conditions: [scopeConditionResult(failure)],
77
+ });
78
+ /**
79
+ * Select a scoped step's elements and derive their keys, enforcing key
80
+ * integrity: every key a non-empty string, unique within the selection —
81
+ * scope keys are affordance identity, so violations throw
82
+ * {@link ScopeKeyError} instead of degrading. A `select` that throws
83
+ * (totality bug) is left to the caller to absorb or report.
84
+ */
85
+ const selectScope = (definition, scope, state) => {
86
+ const selected = scope.select(state);
87
+ if (!Array.isArray(selected)) {
88
+ throw new ScopeKeyError(definition.name, null, 'scope.select must return an array of elements');
89
+ }
90
+ const seen = new Set();
91
+ return selected.map((element) => {
92
+ let key;
93
+ try {
94
+ key = scope.key(element);
95
+ }
96
+ catch (err) {
97
+ throw new ScopeKeyError(definition.name, null, `scope.key threw: ${thrownMessage(err)}`);
98
+ }
99
+ if (typeof key !== 'string' || key === '') {
100
+ throw new ScopeKeyError(definition.name, null, 'scope.key must return a non-empty string for every element');
101
+ }
102
+ if (seen.has(key)) {
103
+ throw new ScopeKeyError(definition.name, key, `duplicate scope key '${key}' — scope keys are affordance identity and must be unique`);
104
+ }
105
+ seen.add(key);
106
+ return { element, key };
107
+ });
108
+ };
109
+ /**
110
+ * The one implementation of scope fan-out — every consumer (the affordance
111
+ * listing, read tracing, addressing)
112
+ * is a filter over this function.
113
+ *
114
+ * An unscoped step yields exactly one target; a scoped step yields one per
115
+ * selected element, or none with a `failure` naming why when the selector is
116
+ * defective. {@link ScopeKeyError} — identity corruption — propagates: it is
117
+ * never a selection failure, and no caller may absorb it into "no targets".
118
+ */
119
+ export const selectTargets = (step, state) => {
120
+ if (step.scope === null)
121
+ return { targets: [{ step, state, binding: null }], failure: null };
122
+ try {
123
+ return {
124
+ targets: selectScope(step, step.scope, state).map((binding) => ({
125
+ step,
126
+ state,
127
+ binding,
128
+ })),
129
+ failure: null,
130
+ };
131
+ }
132
+ catch (err) {
133
+ if (err instanceof ScopeKeyError)
134
+ throw err;
135
+ return {
136
+ targets: [],
137
+ failure: { reason: `scope selector threw: ${thrownMessage(err)}` },
138
+ };
139
+ }
140
+ };
141
+ /**
142
+ * The one implementation of addressing — "step X (of element K) on this
143
+ * state". Total over everything except key integrity: an undeclared step
144
+ * name, a missing/unknown scope key on a scoped step, a scope key on an
145
+ * unscoped step, and a defective scope selector all come back as `failure`.
146
+ * {@link ScopeKeyError} raised for duplicate or malformed keys (identity
147
+ * corruption, from {@link selectScope}) still propagates — no filter may
148
+ * absorb it.
149
+ */
150
+ export const addressTarget = (definition, state, stepName, scopeKey) => {
151
+ const stepDefinition = definition.getStep(stepName);
152
+ if (stepDefinition === undefined) {
153
+ return {
154
+ target: null,
155
+ failure: {
156
+ kind: 'unknown-step',
157
+ error: new UnknownStepError(definition.name, stepName, definition.steps.map((declared) => declared.name)),
158
+ },
159
+ };
160
+ }
161
+ if (stepDefinition.scope === null) {
162
+ if (scopeKey !== undefined) {
163
+ return {
164
+ target: null,
165
+ failure: {
166
+ kind: 'unscoped-key',
167
+ error: new ScopeKeyError(stepName, scopeKey, `scope key '${scopeKey}' given, but the step is not scoped`),
168
+ },
169
+ };
170
+ }
171
+ return {
172
+ target: { step: stepDefinition, state, binding: null },
173
+ failure: null,
174
+ };
175
+ }
176
+ const selection = selectTargets(stepDefinition, state);
177
+ if (selection.failure !== null) {
178
+ return {
179
+ target: null,
180
+ failure: {
181
+ kind: 'defective-selector',
182
+ reason: selection.failure.reason,
183
+ error: new ScopeKeyError(stepName, scopeKey ?? null, selection.failure.reason),
184
+ },
185
+ };
186
+ }
187
+ const known = selection.targets.map((target) => target.binding?.key ?? '');
188
+ const selected = known.length > 0 ? known.join(', ') : '(no elements in scope)';
189
+ if (scopeKey === undefined) {
190
+ return {
191
+ target: null,
192
+ failure: {
193
+ kind: 'missing-key',
194
+ error: new ScopeKeyError(stepName, null, `scoped step: a scopeKey is required — currently selected: ${selected}`),
195
+ },
196
+ };
197
+ }
198
+ const bound = selection.targets.find((target) => target.binding?.key === scopeKey);
199
+ if (bound === undefined) {
200
+ return {
201
+ target: null,
202
+ failure: {
203
+ kind: 'unknown-key',
204
+ error: new ScopeKeyError(stepName, scopeKey, `no element in scope has key '${scopeKey}' — currently selected: ${selected}`),
205
+ },
206
+ };
207
+ }
208
+ return { target: bound, failure: null };
209
+ };
210
+ /**
211
+ * Resolve "step X (of element K) on this state" — the addressing shared by
212
+ * `explain` (a targeted probe) and the execution lifecycle's claim.
213
+ *
214
+ * The loud filter over {@link addressTarget}: an addressed caller named a
215
+ * case and a step and is owed an answer about *those*, so every way of
216
+ * failing to address throws with its precise message. Addressing a step you
217
+ * cannot name is a caller bug, not a blocked affordance.
218
+ */
219
+ export const resolveTarget = (definition, state, stepName, scopeKey) => {
220
+ const address = addressTarget(definition, state, stepName, scopeKey);
221
+ if (address.failure !== null)
222
+ throw address.failure.error;
223
+ return address.target;
224
+ };
225
+ /**
226
+ * Evaluate one addressed step's guard against the state it was resolved on —
227
+ * the single evaluation shared by `explain` and the claim, so the enforcement
228
+ * moment and the explanation of it can never drift apart.
229
+ */
230
+ export const evaluateTarget = (target, ctx) => evaluateGuard(target.step.guard, {
231
+ state: target.state,
232
+ actor: ctx.actor,
233
+ asOf: ctx.asOf,
234
+ ...(target.binding !== null && { scope: target.binding.element }),
235
+ });
236
+ //# sourceMappingURL=target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.js","sourceRoot":"","sources":["../../src/model/target.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Step targeting: resolving \"step X, of element K, on this state\".\n *\n * A **step target** is a step definition plus its scope binding — the pair\n * that an affordance's identity (step × scope key) names. Everything\n * that acts on a step goes through here first: the execution lifecycle's\n * claim, `explain`, audit replay, and the affordance listing itself.\n *\n * It lives in the model because it is a fact about a {@link StepDefinition}\n * and a Case State, and nothing more — no store, no registry, no clock. Put\n * anywhere higher it would drag every consumer's imports upward toward the\n * engine, which is a facade none of them should need.\n *\n * ## One fan-out, filtered\n *\n * {@link selectTargets} is the only implementation of fan-out — expanding\n * one scoped step into its per-element targets — and it makes the one\n * distinction every consumer needs:\n *\n * - A **defective selector** — one that throws over historical state, or\n * returns something other than an array — is a *selection failure*,\n * reported as `failure` with no targets. Each caller decides what its\n * audience deserves: the affordance listing renders it as a blocked\n * `$scope` entry, a sweep skips the step, {@link resolveTarget} throws.\n * - A **key integrity violation** — duplicate or malformed scope keys —\n * corrupts affordance identity itself, so {@link ScopeKeyError} is loud\n * through every path. No caller may absorb it into an empty selection.\n *\n * Addressing — \"step X (of element K) on this state\" — is likewise one\n * implementation, {@link addressTarget}, answering with the target or a\n * {@link TargetAddressFailure} that names its kind. Its filters:\n * {@link resolveTarget} (loud: an addressed caller is owed the precise\n * failure, so it throws), audit replay (lenient: a sweep over the Journal\n * reports the failure as a value and keeps going), and `explain` (loud,\n * with one deliberate exception — a `defective-selector` failure is\n * *answered*, because the listing published that exact link).\n */\n\nimport { SCOPE_FAILURE_CONDITION } from '@affordance/contract'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { thrownMessage } from '../errors.js'\nimport type {\n GuardEvaluation,\n Instant,\n SingleConditionResult,\n} from '../guards/index.js'\nimport { evaluateGuard } from '../guards/index.js'\nimport type { CaseTypeDefinition } from './casetype.js'\nimport { ScopeKeyError, UnknownStepError } from './errors.js'\nimport type { StepDefinition } from './step.js'\n\n/**\n * The synthetic condition name under which a throwing or malformed scope\n * selector is reported. `$`-prefixed so it can never collide with an\n * author's condition names. Declared by `@affordance/contract` — it reaches\n * clients through `blocked[].unmet[].name`, so it is wire vocabulary, and\n * the wire owns it; re-exported here for the engine's own consumers.\n */\nexport { SCOPE_FAILURE_CONDITION }\n\n/**\n * The synthetic `$scope` entry as a condition result — the one spelling of\n * how a failed scope selection reports into an evaluation-shaped record: a\n * failed condition carrying the selector's reason.\n */\nconst scopeConditionResult = (failure: {\n readonly reason: string\n}): SingleConditionResult => ({\n name: SCOPE_FAILURE_CONDITION,\n section: 'requires',\n kind: 'condition',\n passed: false,\n reason: failure.reason,\n})\n\n/**\n * A defective selection as a full evaluation record — the\n * possible/permitted/available verdict stated once. The selection failed, so\n * nothing about the step is possible\n * on this case; `permits` were never reachable (they may read the element),\n * reported vacuously satisfied — the requires-level `$scope` failure is the\n * answer. The listing's blocked entry and `explain`'s answer for the same\n * link both derive from this.\n */\nexport const scopeFailureEvaluation = (\n asOf: string,\n failure: { readonly reason: string },\n): GuardEvaluation => ({\n asOf,\n possible: false,\n permitted: true,\n available: false,\n conditions: [scopeConditionResult(failure)],\n})\n\n/** What a guard is evaluated against, beyond state: the actor and the instant. */\nexport interface ComputationContext<TActor = unknown> {\n readonly actor: TActor\n /**\n * The instant to evaluate as of — always explicit. Conditions cannot read\n * the clock, so defaulting to now is the engine's job, done once at its\n * boundary; everything below it is pure and reconstructable.\n */\n readonly asOf: Instant\n}\n\n/** One element of a scoped step's selection, with the key that identifies it. */\nexport interface ScopeBinding {\n readonly element: unknown\n readonly key: string\n}\n\n/**\n * A step addressed by name (× scope key, if scoped): the step definition and\n * its scope binding, resolved against a given Case State.\n */\nexport interface StepTarget<TState, TActor = unknown> {\n readonly step: StepDefinition<TState, TActor>\n /**\n * The Case State the target was resolved against — the document its guard\n * is evaluated over. Carried on the target so a binding can never be\n * evaluated against a different document than the one that produced it.\n */\n readonly state: TState\n /** The bound element and its key, or `null` for an unscoped step. */\n readonly binding: ScopeBinding | null\n}\n\n/** The outcome of scope fan-out: the step's targets, or why selection produced none. */\nexport interface TargetSelection<TState, TActor = unknown> {\n readonly targets: readonly StepTarget<TState, TActor>[]\n /** The selection failure — a defective selector — or `null` when selection succeeded. */\n readonly failure: { readonly reason: string } | null\n}\n\n/**\n * Select a scoped step's elements and derive their keys, enforcing key\n * integrity: every key a non-empty string, unique within the selection —\n * scope keys are affordance identity, so violations throw\n * {@link ScopeKeyError} instead of degrading. A `select` that throws\n * (totality bug) is left to the caller to absorb or report.\n */\nconst selectScope = <TState, TActor>(\n definition: StepDefinition<TState, TActor>,\n scope: NonNullable<StepDefinition<TState, TActor>['scope']>,\n state: TState,\n): readonly ScopeBinding[] => {\n const selected = scope.select(state)\n if (!Array.isArray(selected)) {\n throw new ScopeKeyError(\n definition.name,\n null,\n 'scope.select must return an array of elements',\n )\n }\n const seen = new Set<string>()\n return selected.map((element) => {\n let key: unknown\n try {\n key = scope.key(element)\n } catch (err) {\n throw new ScopeKeyError(\n definition.name,\n null,\n `scope.key threw: ${thrownMessage(err)}`,\n )\n }\n if (typeof key !== 'string' || key === '') {\n throw new ScopeKeyError(\n definition.name,\n null,\n 'scope.key must return a non-empty string for every element',\n )\n }\n if (seen.has(key)) {\n throw new ScopeKeyError(\n definition.name,\n key,\n `duplicate scope key '${key}' — scope keys are affordance identity and must be unique`,\n )\n }\n seen.add(key)\n return { element, key }\n })\n}\n\n/**\n * The one implementation of scope fan-out — every consumer (the affordance\n * listing, read tracing, addressing)\n * is a filter over this function.\n *\n * An unscoped step yields exactly one target; a scoped step yields one per\n * selected element, or none with a `failure` naming why when the selector is\n * defective. {@link ScopeKeyError} — identity corruption — propagates: it is\n * never a selection failure, and no caller may absorb it into \"no targets\".\n */\nexport const selectTargets = <TState, TActor>(\n step: StepDefinition<TState, TActor>,\n state: TState,\n): TargetSelection<TState, TActor> => {\n if (step.scope === null)\n return { targets: [{ step, state, binding: null }], failure: null }\n try {\n return {\n targets: selectScope(step, step.scope, state).map((binding) => ({\n step,\n state,\n binding,\n })),\n failure: null,\n }\n } catch (err) {\n if (err instanceof ScopeKeyError) throw err\n return {\n targets: [],\n failure: { reason: `scope selector threw: ${thrownMessage(err)}` },\n }\n }\n}\n\n/**\n * Why an address does not resolve, by kind. Each failure carries the error\n * the loud filter would throw, so the diagnosis (including the\n * currently-valid scope keys, where knowable) is constructed exactly once\n * and reads identically whether it is thrown at an addressed caller or\n * reported by a sweep. The kind is what lets a filter treat one failure\n * differently without re-deriving how the address failed — `explain`\n * *answers* a defective selector (the listing published that exact link)\n * and throws everything else.\n */\nexport type TargetAddressFailure =\n | {\n /** The named step is not declared on the case type. */\n readonly kind: 'unknown-step'\n readonly error: UnknownStepError\n }\n | {\n /** The selector threw or returned a non-array over this Case State. */\n readonly kind: 'defective-selector'\n /** The selection failure's own words — what the listing's `$scope` entry reports. */\n readonly reason: string\n readonly error: ScopeKeyError\n }\n | {\n /**\n * A scope-key problem on an otherwise healthy step: a key given for an\n * unscoped step, a missing key on a scoped one, or a key no selected\n * element carries.\n */\n readonly kind: 'unscoped-key' | 'missing-key' | 'unknown-key'\n readonly error: ScopeKeyError\n }\n\n/** The outcome of addressing a step: the target, or the precise failure. */\nexport type TargetAddress<TState, TActor = unknown> =\n | { readonly target: StepTarget<TState, TActor>; readonly failure: null }\n | { readonly target: null; readonly failure: TargetAddressFailure }\n\n/**\n * The one implementation of addressing — \"step X (of element K) on this\n * state\". Total over everything except key integrity: an undeclared step\n * name, a missing/unknown scope key on a scoped step, a scope key on an\n * unscoped step, and a defective scope selector all come back as `failure`.\n * {@link ScopeKeyError} raised for duplicate or malformed keys (identity\n * corruption, from {@link selectScope}) still propagates — no filter may\n * absorb it.\n */\nexport const addressTarget = <S extends StandardSchemaV1, TActor>(\n definition: CaseTypeDefinition<S, TActor>,\n state: StandardSchemaV1.InferOutput<S>,\n stepName: string,\n scopeKey?: string,\n): TargetAddress<StandardSchemaV1.InferOutput<S>, TActor> => {\n const stepDefinition = definition.getStep(stepName)\n if (stepDefinition === undefined) {\n return {\n target: null,\n failure: {\n kind: 'unknown-step',\n error: new UnknownStepError(\n definition.name,\n stepName,\n definition.steps.map((declared) => declared.name),\n ),\n },\n }\n }\n\n if (stepDefinition.scope === null) {\n if (scopeKey !== undefined) {\n return {\n target: null,\n failure: {\n kind: 'unscoped-key',\n error: new ScopeKeyError(\n stepName,\n scopeKey,\n `scope key '${scopeKey}' given, but the step is not scoped`,\n ),\n },\n }\n }\n return {\n target: { step: stepDefinition, state, binding: null },\n failure: null,\n }\n }\n\n const selection = selectTargets(stepDefinition, state)\n if (selection.failure !== null) {\n return {\n target: null,\n failure: {\n kind: 'defective-selector',\n reason: selection.failure.reason,\n error: new ScopeKeyError(\n stepName,\n scopeKey ?? null,\n selection.failure.reason,\n ),\n },\n }\n }\n const known = selection.targets.map((target) => target.binding?.key ?? '')\n const selected =\n known.length > 0 ? known.join(', ') : '(no elements in scope)'\n if (scopeKey === undefined) {\n return {\n target: null,\n failure: {\n kind: 'missing-key',\n error: new ScopeKeyError(\n stepName,\n null,\n `scoped step: a scopeKey is required — currently selected: ${selected}`,\n ),\n },\n }\n }\n const bound = selection.targets.find(\n (target) => target.binding?.key === scopeKey,\n )\n if (bound === undefined) {\n return {\n target: null,\n failure: {\n kind: 'unknown-key',\n error: new ScopeKeyError(\n stepName,\n scopeKey,\n `no element in scope has key '${scopeKey}' — currently selected: ${selected}`,\n ),\n },\n }\n }\n return { target: bound, failure: null }\n}\n\n/**\n * Resolve \"step X (of element K) on this state\" — the addressing shared by\n * `explain` (a targeted probe) and the execution lifecycle's claim.\n *\n * The loud filter over {@link addressTarget}: an addressed caller named a\n * case and a step and is owed an answer about *those*, so every way of\n * failing to address throws with its precise message. Addressing a step you\n * cannot name is a caller bug, not a blocked affordance.\n */\nexport const resolveTarget = <S extends StandardSchemaV1, TActor>(\n definition: CaseTypeDefinition<S, TActor>,\n state: StandardSchemaV1.InferOutput<S>,\n stepName: string,\n scopeKey?: string,\n): StepTarget<StandardSchemaV1.InferOutput<S>, TActor> => {\n const address = addressTarget(definition, state, stepName, scopeKey)\n if (address.failure !== null) throw address.failure.error\n return address.target\n}\n\n/**\n * Evaluate one addressed step's guard against the state it was resolved on —\n * the single evaluation shared by `explain` and the claim, so the enforcement\n * moment and the explanation of it can never drift apart.\n */\nexport const evaluateTarget = <TState, TActor>(\n target: StepTarget<TState, TActor>,\n ctx: ComputationContext<TActor>,\n): GuardEvaluation =>\n evaluateGuard(target.step.guard, {\n state: target.state,\n actor: ctx.actor,\n asOf: ctx.asOf,\n ...(target.binding !== null && { scope: target.binding.element }),\n })\n"]}
@@ -0,0 +1,57 @@
1
+ import type { Queryable } from './queryable.js';
2
+ /**
3
+ * Dedicated Postgres schema owning all framework tables.
4
+ * Named `affordance` because `case` itself is a SQL reserved word.
5
+ */
6
+ export declare const FRAMEWORK_SCHEMA = "affordance";
7
+ /**
8
+ * The DDL revision below. Bump it whenever the DDL changes: a database
9
+ * already carrying this version skips the DDL entirely, which is what keeps
10
+ * a start-up from touching a busy database at all.
11
+ */
12
+ export declare const SCHEMA_VERSION = 4;
13
+ /**
14
+ * Every framework table that holds rows belonging to one case, with the
15
+ * column that names the case — listed in an order safe to delete from
16
+ * (children first; everything references `cases`). **The one answer to
17
+ * "which tables does the framework own"** outside the DDL above: a consumer
18
+ * that sweeps per-case rows (a dev console's case purge, a test harness's
19
+ * cleanup) iterates this instead of keeping a private copy that goes stale
20
+ * the release a table is added.
21
+ *
22
+ * `ingested_events.case_id` is nullable — an unrouted event belongs to no
23
+ * case and survives a per-case sweep, which is correct: it was never about
24
+ * the deleted case.
25
+ */
26
+ export declare const CASE_TABLES: readonly [{
27
+ readonly table: "journal";
28
+ readonly caseColumn: "case_id";
29
+ }, {
30
+ readonly table: "claims";
31
+ readonly caseColumn: "case_id";
32
+ }, {
33
+ readonly table: "correlations";
34
+ readonly caseColumn: "case_id";
35
+ }, {
36
+ readonly table: "ingested_events";
37
+ readonly caseColumn: "case_id";
38
+ }, {
39
+ readonly table: "cases";
40
+ readonly caseColumn: "id";
41
+ }];
42
+ /**
43
+ * Idempotent DDL bootstrap for the framework schema. Safe to call on every
44
+ * app start and from concurrent processes: the statements are sent as one
45
+ * multi-statement simple query, which Postgres runs on one connection inside
46
+ * a single implicit transaction, and the leading `pg_advisory_xact_lock`
47
+ * serializes racing bootstraps (concurrent `CREATE ... IF NOT EXISTS` can
48
+ * otherwise fail on catalog uniqueness).
49
+ *
50
+ * Also safe to call against a *busy* database, which is the harder promise,
51
+ * and is answered twice over. First, a bootstrap with nothing to do does
52
+ * nothing at all: {@link isCurrent} checks the version marker and returns
53
+ * before any DDL runs, so the common case takes no table locks whatsoever.
54
+ * Second, when there *is* work, the transaction bounds its own lock wait and
55
+ * this retries it — schema management yields to live work, never the reverse.
56
+ */
57
+ export declare const bootstrap: (db: Queryable, attempts?: number) => Promise<void>;