@hatua/model 0.0.0 → 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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import { Manifest, WorkflowExecution } from '@hatua/schema';
2
+ /**
3
+ * Run metadata is derived, not reported.
4
+ *
5
+ * A component declares each metadata key as a `measure` (summable — tokens,
6
+ * cost, retries) or a `dimension` (groupable — model, region). Given only
7
+ * per-step values, that is enough for Hatua to compute run totals and pivots
8
+ * like "tokens per model" itself.
9
+ *
10
+ * The alternative — runners reporting their own run-level summaries with their
11
+ * own schemas — would mean every runner shapes the Runs view differently, and
12
+ * Hatua could render none of them generically.
13
+ */
14
+ export interface MetadataDescriptor {
15
+ k: string;
16
+ label: string;
17
+ role: 'measure' | 'dimension';
18
+ unit?: string;
19
+ }
20
+ export interface MeasureTotal {
21
+ key: string;
22
+ label: string;
23
+ unit?: string;
24
+ total: number;
25
+ }
26
+ export interface Pivot {
27
+ measure: string;
28
+ measureLabel: string;
29
+ dimension: string;
30
+ dimensionLabel: string;
31
+ unit?: string;
32
+ rows: {
33
+ value: string;
34
+ total: number;
35
+ }[];
36
+ }
37
+ /** Every metadata key any manifest declares, keyed by component `use`. */
38
+ export declare function descriptorsByUse(manifests: readonly Manifest[]): Map<string, MetadataDescriptor[]>;
39
+ /** Sum every declared measure across the run. */
40
+ export declare function totals(execution: WorkflowExecution, descriptors: Map<string, MetadataDescriptor[]>, useOf: (stepId: string) => string | undefined): MeasureTotal[];
41
+ /**
42
+ * Sum a measure grouped by a dimension — "tokens per model". Both must be
43
+ * declared by the same component, since only then do they co-occur on a sample.
44
+ */
45
+ export declare function pivot(execution: WorkflowExecution, descriptors: Map<string, MetadataDescriptor[]>, useOf: (stepId: string) => string | undefined, measureKey: string, dimensionKey: string): Pivot | null;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,162 @@
1
+ import { TypeNode } from '@hatua/expressions';
2
+ import { Block, ContextKey, Manifest, Step, WorkflowDefinition } from '@hatua/schema';
3
+ import { BoardId, StepRef } from './tree';
4
+ /**
5
+ * What a step may reference. The reference tree is built from this, which is
6
+ * what makes a broken mapping unexpressible rather than merely discouraged.
7
+ */
8
+ export interface ScopeEntry {
9
+ /**
10
+ * The whole addressable path, e.g. `steps.s2`, `triggers.nightly`,
11
+ * `var.digest_to`, `params.entry`, `run.tenant`, `TRIGGER`.
12
+ *
13
+ * Always below a root, never at one — which is what lets a Step be called
14
+ * `run` and a parameter be called `steps` (ADR-0014).
15
+ */
16
+ path: string;
17
+ /**
18
+ * Where the value comes from, which is what the reference tree groups by and
19
+ * what decides a row's icon.
20
+ *
21
+ * `context` is the Host's Run Context — ambient values it supplies to every
22
+ * execution. It sits beside `trigger` and `var` rather than under them
23
+ * because it is neither: nothing in the document declares it, and unlike a
24
+ * variable it cannot be edited from the builder at all.
25
+ */
26
+ kind: 'step' | 'trigger' | 'param' | 'var' | 'context' | 'builtin';
27
+ label: string;
28
+ /**
29
+ * One sentence about the value, shown under the focused row in the completion
30
+ * list. Only the Host's Run Context declares one today — a manifest output
31
+ * has nowhere to put a sentence — so absent is the ordinary case and a row
32
+ * without one simply shows nothing rather than a placeholder.
33
+ */
34
+ description?: string;
35
+ /**
36
+ * The shape of what it yields.
37
+ *
38
+ * This is what makes an entry usable by `@hatua/expressions`' type checker,
39
+ * which takes `ScopeEntry[]` as an argument precisely so it can stay ignorant
40
+ * of manifests and of this package. One scope, two readers: the reference
41
+ * tree reads `label` and `kind`, the checker reads `path` and `type`.
42
+ */
43
+ type: TypeNode;
44
+ }
45
+ /**
46
+ * The steps a given step may reference: its ancestors and the earlier siblings
47
+ * of every ancestor. Sibling branches are deliberately out of scope, so a user
48
+ * cannot express a mapping that could not resolve at run time.
49
+ *
50
+ * The walk is rooted at the Step's own Board and never leaves it. A Block's
51
+ * steps do not see the call site's ancestry, which is the whole reason a call is
52
+ * a cross-link with a contract and a jump is not (ADR-0013).
53
+ */
54
+ export declare function upstreamOf(doc: WorkflowDefinition, ref: StepRef): Step[];
55
+ /**
56
+ * Everything a Board offers with no position in its tree.
57
+ *
58
+ * **Never a Step's output.** A variable's value has no position — it is not
59
+ * reached by running anything — so no Step is guaranteed to have run by the
60
+ * time it is evaluated, and offering one would express a mapping that cannot
61
+ * resolve. Everything here is available unconditionally for the mirror-image
62
+ * reason: a workflow cannot run without a Trigger firing, the Host supplies Run
63
+ * Context to every execution, a parameter is filled by the caller before the
64
+ * Block starts, and a variable is Board-scoped rather than positional.
65
+ *
66
+ * The two Boards offer different things, and the difference IS the contract:
67
+ *
68
+ * | | root Board | a Block's |
69
+ * | --- | --- | --- |
70
+ * | `run.*` | Run Context | the same — the one thing that crosses |
71
+ * | `triggers.*`, `TRIGGER` | the parameter contract | absent |
72
+ * | `params.*` | absent | the parameter contract |
73
+ * | `var.*` | the workflow's | the Block's, rebuilt per call |
74
+ *
75
+ * A Block cannot read the workflow's variables or ask which Trigger fired.
76
+ * Run Context is the single exception because nothing in the document declares
77
+ * it: the Host supplies it to every execution, so it is exact on every path of
78
+ * every Board with no intersection to compute (ADR-0013).
79
+ *
80
+ * It exists as its own function because the Board panel needs scope without a
81
+ * Step to ask about. `scopeFor` is this plus the upstream Steps, so the two
82
+ * readers share one definition of the unpositioned half rather than drifting.
83
+ */
84
+ export declare function boardScope(doc: WorkflowDefinition, board?: BoardId, manifests?: readonly Manifest[], context?: readonly ContextKey[]): ScopeEntry[];
85
+ /**
86
+ * Everything addressable from a step: the unpositioned scope above, plus the
87
+ * upstream steps.
88
+ *
89
+ * Only steps are constrained by tree position, because only a step can fail to
90
+ * have run.
91
+ */
92
+ export declare function scopeFor(doc: WorkflowDefinition, ref: StepRef, manifests?: readonly Manifest[], context?: readonly ContextKey[], memo?: ScopeMemo): ScopeEntry[];
93
+ /**
94
+ * What one walk has already worked out, so a second walk over the same document
95
+ * does not work it out again.
96
+ *
97
+ * A scope is *positional*, so asking for every Step's is asking n times — and
98
+ * each answer names every Step upstream of it, so the answers together hold
99
+ * n²/2 entries. Recomputing each one costs a fresh type tree per upstream Step
100
+ * per Step, which is what makes a whole-document pass superlinear: a thousand
101
+ * Steps take a second, and `validateDefinition` runs on every keystroke.
102
+ *
103
+ * Shared by passing one of these to every `scopeFor` in the pass. Created per
104
+ * pass and never held in the module: the document is an argument, and a cache
105
+ * outliving the call would be answering about a document that has since been
106
+ * edited.
107
+ */
108
+ export interface ScopeMemo {
109
+ /** One loop's element type, which is what makes nested loops linear. */
110
+ readonly elements: ElementMemo;
111
+ /** One upstream Step's entry, keyed by `stepKey`. */
112
+ readonly entries: Map<string, ScopeEntry>;
113
+ /** One Board's unpositioned scope: its Triggers, its contract, its variables. */
114
+ readonly boards: Map<string, ScopeEntry[]>;
115
+ }
116
+ export declare const newScopeMemo: () => ScopeMemo;
117
+ /**
118
+ * The type one path names, read out of a scope, or null when nothing declares
119
+ * it.
120
+ *
121
+ * Longest prefix first, because a scope path is dotted and is one entry rather
122
+ * than two: `steps.s2` is an entry and `steps` is not, so `steps.s2.messages`
123
+ * has to try three segments before two. That is the same rule `validate.ts`
124
+ * walks a Member chain by, restated here for a caller that has a path and no
125
+ * expression — reading a *declared* type is not checking one, and reaching for
126
+ * the checker would mean manufacturing diagnostics nobody asked for in order to
127
+ * throw them away.
128
+ */
129
+ export declare function typeAtPath(scope: readonly ScopeEntry[], path: string): TypeNode | null;
130
+ /**
131
+ * What one element of a `core.for_each`'s list is, or null when the document
132
+ * does not say.
133
+ *
134
+ * This is the whole of `t: item`. The loop's `list` is a `ref` field, so its
135
+ * declared type is `unknown` and the ordinary Slot check learns nothing from
136
+ * it; the shape is one level below whatever it points at, which is exactly the
137
+ * `of:` the source output declared. Null when `list` is missing, is not a plain
138
+ * Reference, names nothing, or names something that is not a list — and null
139
+ * means `item` stays `item`, which the checker treats as matching anything.
140
+ * Guessing `object` instead would be a shape nothing declared.
141
+ */
142
+ export declare function loopElementType(doc: WorkflowDefinition, board: BoardId, step: Step, manifests?: readonly Manifest[], context?: readonly ContextKey[], resolving?: ReadonlySet<string>, elements?: ElementMemo): TypeNode | null;
143
+ /**
144
+ * What one loop's element resolves to, for the length of one top-level walk.
145
+ *
146
+ * A Step's element type is a property of the document, so one walk asking for
147
+ * the same loop twice is asking the same question twice — and the walk asks for
148
+ * it once per path that reaches it, which is what makes the cost exponential
149
+ * without this. The map is created per `scopeFor` rather than held in the
150
+ * module: the document is an argument, and a cache outliving the call would be
151
+ * answering about a document that has since been edited.
152
+ */
153
+ type ElementMemo = Map<string, TypeNode | null>;
154
+ /**
155
+ * What a Block's outputs are, as a scope entry's type at the call site.
156
+ *
157
+ * Read where the Block is declared rather than from a manifest, which is what
158
+ * makes a call type-check before its body is written: the declaration is the
159
+ * contract, and `core.return` only binds values to it.
160
+ */
161
+ export declare function blockOutputType(block: Block | undefined): TypeNode;
162
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,107 @@
1
+ import { Step, WorkflowDefinition } from '@hatua/schema';
2
+ import { Board, BoardId, StepRef } from './tree';
3
+ /**
4
+ * A contiguous stretch of sibling **Steps** in one region of one **Board**.
5
+ *
6
+ * The shape a selection takes and the shape extraction consumes (ADR-0018,
7
+ * ADR-0020). A Segment of one is a Segment: a single container together with
8
+ * its whole body is the flattening case a Block exists for.
9
+ *
10
+ * ## Named by Steps, never by positions
11
+ *
12
+ * `{ board, steps }` and not a start index and a length. A Segment is held
13
+ * across edits, and an index range means a Step added above it silently changes
14
+ * which Steps are in it — the argument `RegionRef` makes about `branchIndex`,
15
+ * which it accepts only because a Branch has no id and a Step has one.
16
+ *
17
+ * So contiguity is *derived* rather than stored, by `segmentSteps` against the
18
+ * Board being drawn. What the shape does carry structurally is the **one
19
+ * Board**: a `readonly StepRef[]` can express a selection spanning two Boards,
20
+ * which is not a Segment and never can be, and hoisting the Board out makes
21
+ * that unrepresentable rather than something every reader has to filter for.
22
+ */
23
+ export interface Segment {
24
+ readonly board: BoardId;
25
+ /** The Steps, by id, in document order. Never empty. */
26
+ readonly steps: readonly string[];
27
+ }
28
+ /** One Step's place among its siblings: the list it sits in, and where. */
29
+ export interface Siblings {
30
+ readonly steps: readonly Step[];
31
+ readonly index: number;
32
+ }
33
+ /**
34
+ * The sibling list holding one Step, and its position in it.
35
+ *
36
+ * The whole of "are these two Steps siblings": two ids resolve to the same
37
+ * `steps` array or they do not. Identity of the array is the test rather than a
38
+ * path, because `regionsOf` hands the region's own list over and a Board holds
39
+ * exactly one array per region.
40
+ */
41
+ export declare function siblingsOf(board: Board, id: string): Siblings | undefined;
42
+ /**
43
+ * The Segment reaching from one Step to another, or `undefined` when the two
44
+ * are not siblings.
45
+ *
46
+ * The *only* way a Segment of more than one Step is built, which is what makes
47
+ * "a selection is always extractable" true by construction rather than by a
48
+ * check somebody has to remember to run (ADR-0020). Order of the arguments does
49
+ * not matter: a user may extend a selection upwards, and the Steps come back in
50
+ * document order either way.
51
+ */
52
+ export declare function segmentBetween(board: Board, anchorId: string, headId: string): Segment | undefined;
53
+ /** A Segment holding one Step. */
54
+ export declare const segmentOf: ({ board, id }: StepRef) => Segment;
55
+ /**
56
+ * The Steps a Segment actually names on a Board, in document order.
57
+ *
58
+ * Every consumer's way in, and the place a Segment held across an edit is
59
+ * reconciled with the document as it is now: a Step that has been removed drops
60
+ * out, and what is left is still contiguous because removal closes the gap.
61
+ *
62
+ * Empty means the Segment names nothing on this Board — a Board that is gone, a
63
+ * Block that was deleted, or a Segment whose every Step has been removed. A
64
+ * caller showing a selection draws nothing for it; a caller acting on one has
65
+ * nothing to act on.
66
+ */
67
+ export declare function segmentSteps(doc: WorkflowDefinition, segment: Segment): readonly Step[];
68
+ /**
69
+ * Whether a Segment names this Step, for a surface deciding whether to draw a
70
+ * card as selected.
71
+ *
72
+ * A Board comparison and not only an id: ids are Board-local, so two Blocks may
73
+ * each hold a Step called `ret` and a bare id highlights both (ADR-0013).
74
+ */
75
+ export declare const segmentHolds: (segment: Segment | undefined, ref: StepRef) => boolean;
76
+ /**
77
+ * The Step one place from the Segment's head, in the direction given, or
78
+ * `undefined` at the end of the sibling list.
79
+ *
80
+ * What `Shift`+`↑`/`↓` moves. The *head* moves and the anchor stays, so the
81
+ * same keystroke grows a Segment and shrinks it from the other end.
82
+ */
83
+ export declare function siblingFrom(board: Board, id: string, step: 1 | -1): string | undefined;
84
+ /**
85
+ * Whether a Segment holds a `core.return`, anywhere inside it.
86
+ *
87
+ * What extraction refuses (ADR-0018). Moved onto a new Board, a return binds to
88
+ * the *new* Block's `outputs:` and ends a Block the author did not mean it to
89
+ * end — behaviour the move silently changes, with nothing malformed for a rule
90
+ * to report. So the gesture is not offered rather than repaired.
91
+ *
92
+ * Nested and not only the Segment's own Steps: a return inside a Fork branch
93
+ * inside the Segment moves with it and binds exactly the same way.
94
+ *
95
+ * Takes the Steps a Segment resolves to rather than the Segment, so a caller
96
+ * that has already asked `segmentSteps` — which is every caller, because the
97
+ * count beside the action comes from the same answer — does not resolve twice.
98
+ *
99
+ * **`extractBlock` cannot call this, and asks the same question its own way.**
100
+ * A command runs against a document that does not project (ADR-0001), so it has
101
+ * no `Step[]` to hand over and walks the YAML AST instead. The two agree because
102
+ * `regionsOf` here and `stepEntriesIn` there enumerate the same regions — a
103
+ * Branch's steps, a body, a handler — which is a coupling neither file states on
104
+ * its own. A region kind added to one and not the other makes the canvas offer
105
+ * an enabled action on the one selection the command refuses.
106
+ */
107
+ export declare const segmentReturns: (steps: readonly Step[]) => boolean;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,161 @@
1
+ import { Slot, ValueType } from '@hatua/expressions';
2
+ import { Manifest, Step, Variable, WorkflowDefinition, MAPPABLE_FIELD_KINDS } from '@hatua/schema';
3
+ import { BoardId } from './tree';
4
+ /**
5
+ * The bridge between a Component Manifest and the expression language.
6
+ *
7
+ * `@hatua/expressions` deliberately knows nothing about manifests: it takes a
8
+ * `Slot` and a `ScopeEntry[]` as arguments, which is what keeps it depending on
9
+ * `@hatua/schema` alone and stops a cycle forming with this package. The cost is
10
+ * that *something* has to turn a step and its manifest into those arguments, and
11
+ * this is it — once per language, so a runner never restates the field-kind to
12
+ * type mapping and cannot get it subtly different from the builder.
13
+ */
14
+ /** The kinds whose value is a Template. Everything else holds a literal. */
15
+ export type MappableFieldKind = (typeof MAPPABLE_FIELD_KINDS)[number];
16
+ /**
17
+ * What each mappable field kind's value must produce.
18
+ *
19
+ * Keyed by `MappableFieldKind` rather than by `string`, so the compiler refuses
20
+ * both a missing kind and an invented one. A loose `Record<string, ValueType>`
21
+ * accepts entries for `bool`, `enum`, `secret` and `conn` — kinds `isMappable`
22
+ * rejects before this is ever read, so they are unreachable — while silently
23
+ * tolerating the omission of `map`, which is mappable. That is the same "two
24
+ * definitions of one thing" failure a Reference regex would be, and the key
25
+ * type is what stops it.
26
+ *
27
+ * `mono` and `textarea` are text that renders differently. `ref` is `unknown` on
28
+ * purpose: a ref field holds whatever it points at, and the check belongs at the
29
+ * far end. `map` has no single type at all — each of its entries declares its
30
+ * own, which is why `slotsFor` never reads this for one.
31
+ */
32
+ export declare const FIELD_KIND_TYPES: Readonly<Record<MappableFieldKind, ValueType>>;
33
+ /** One entry of a `map` field: a name, a Template, and the type it must produce. */
34
+ export interface MapEntry {
35
+ key: string;
36
+ value: string;
37
+ type: ValueType;
38
+ }
39
+ /**
40
+ * The Slots a step's `with:` map resolves into.
41
+ *
42
+ * A `map` field contributes one Slot per entry, named `<field>.<key>`, because
43
+ * each entry is separately typed and separately wrong.
44
+ */
45
+ export declare function slotsFor(step: Step, manifest: Manifest): Slot[];
46
+ /**
47
+ * The Slot a branch's `when` resolves into.
48
+ *
49
+ * Separate from `slotsFor` because a branch is not a step and has no manifest —
50
+ * and because its type is not declared anywhere: a condition is a boolean, and
51
+ * that is the whole reason `when: "{{steps.s2.count}} > 0"` can be refused at design
52
+ * time rather than misread at run time.
53
+ */
54
+ export declare const whenSlot: (when: string) => Slot;
55
+ /** The verb whose outputs come from its own configuration. */
56
+ export declare const MAPPING_VERB = "core.map";
57
+ /** The verb that iterates a collection. */
58
+ export declare const FOR_EACH_VERB = "core.for_each";
59
+ /** The verb that branches. */
60
+ export declare const FORK_VERB = "core.fork";
61
+ /** The verb that writes one of its Board's variables. */
62
+ export declare const SET_VAR_VERB = "core.set_var";
63
+ /**
64
+ * The field a `core.for_each` iterates, and the one `item` is resolved through.
65
+ *
66
+ * A constant rather than a literal at three call sites, because it is a name two
67
+ * languages and one manifest have to agree on: `item` means "one element of
68
+ * whatever THIS key points at", so a reader looking under a different key
69
+ * resolves `item` to nothing and reports no type at all.
70
+ */
71
+ export declare const FOR_EACH_LIST_FIELD = "list";
72
+ /**
73
+ * The output key a container binds for the children it owns.
74
+ *
75
+ * Both are ordinary manifest outputs of the container Step, read as
76
+ * `{{steps.<container id>.<key>}}`. That is the whole binding mechanism, and it
77
+ * is one mechanism rather than two: ADR-0014 closed the path roots so that a
78
+ * structural idea could not take a bare word away from users, and a Step id is
79
+ * already one segment below `steps.`. Two nested loops cannot shadow each other,
80
+ * because two Steps cannot share an id on one Board.
81
+ */
82
+ export declare const ITEM_BINDING = "item";
83
+ /**
84
+ * The Slot a `core.repeat`'s `until` resolves into.
85
+ *
86
+ * The mirror of `whenSlot`, and for the same reason: a condition is a boolean,
87
+ * and no manifest field can say so — `FIELD_KIND_TYPES` has no mappable boolean
88
+ * at all, because `bool` holds a literal rather than a Template. That is why
89
+ * `until` is a structural key beside `steps:` rather than a field under `with:`.
90
+ * Under `with:` it would type-check as text, so `{{ steps.s2.count }}` would
91
+ * pass as a termination condition.
92
+ *
93
+ * A repeat tests this AFTER its body, so the body always runs at least once.
94
+ */
95
+ export declare const repeatSlot: (until: string) => Slot;
96
+ /**
97
+ * The type a variable's `{{ var.<key> }}`, its initial `value` and every
98
+ * `core.set_var` writing it are all checked against.
99
+ *
100
+ * Declared rather than read off the value. A var is the one addressable thing
101
+ * whose content changes while the document does not, so inferring its type from
102
+ * the literal in the file would make the marking a lie the moment a
103
+ * `core.set_var` wrote something else — and every downstream check was answered
104
+ * against it (ADR-0013).
105
+ *
106
+ * `unknown` for a var carrying no `t` at all — absent or empty alike, matching
107
+ * the Go SDK, because a hand-edit is exactly what reaches here and `t: ""` is as
108
+ * plausible a one as a missing key. The schema requires a type, so refusing to
109
+ * check is the honest answer where guessing `text` would refuse a document over
110
+ * a type nothing declared.
111
+ */
112
+ export declare const variableType: (variable: Variable) => ValueType;
113
+ /**
114
+ * The Slot a `core.set_var`'s `value` resolves into, typed by the variable it
115
+ * names.
116
+ *
117
+ * The third verb a manifest cannot describe, alongside a call and a
118
+ * `core.return`, and for the same reason: what its field must produce is
119
+ * declared elsewhere in the document. Here it is the Board's `vars`, which is
120
+ * also why a `core.set_var` inside a Block can only ever name that Block's —
121
+ * `vars` is read from the Board the Step sits on, so there is no reaching out.
122
+ *
123
+ * Takes the Board rather than a list of variables, so the caller cannot supply
124
+ * the wrong one: the Go SDK's `SetVarSlot` has the same signature, and a runner
125
+ * handed a list would be the one deciding whether a Block falls back to the
126
+ * workflow's variables — which is the rule this verb exists inside.
127
+ *
128
+ * Null when the step names no variable, or names one the Board does not
129
+ * declare: both have their own diagnostic, and resolving a Template against a
130
+ * type nothing declared would report a mismatch the user cannot act on.
131
+ */
132
+ export declare function setVarSlot(doc: WorkflowDefinition, board: BoardId, step: Step): Slot | null;
133
+ /**
134
+ * The Slot a variable's initial value resolves into.
135
+ *
136
+ * A var's `value` may hold `{{ … }}`, and until `t` was declared there was
137
+ * nothing to check it against. Null for a literal: only a Template is a Slot.
138
+ */
139
+ export declare function variableSlot(variable: Variable): Slot | null;
140
+ /** The `{key, value, type}` entries of a `map` field, ignoring anything malformed. */
141
+ export declare function mapEntries(value: unknown): MapEntry[];
142
+ /**
143
+ * The Slots a Step's `with:` map resolves into, whichever kind of Step it is.
144
+ *
145
+ * One entry point, so a caller never has to know that a call, a `core.return`
146
+ * and a `core.set_var` are the three verbs a Component Manifest cannot
147
+ * describe: what each of their fields must produce is declared elsewhere in the
148
+ * document — in a Block's `params`, in a Block's `outputs`, in the Board's
149
+ * `vars` — and a caller that only asked `slotsFor` would check nothing at a
150
+ * call site at all. `sdk/go`'s `SlotsForStep` is the same dispatch.
151
+ *
152
+ * Empty where the document does not say: a call naming a Block that is not
153
+ * declared, a return on the root Board, a `core.set_var` naming no variable.
154
+ * Each of those has its own diagnostic, and resolving a Template against a
155
+ * contract nothing declares would report a second problem about the first one.
156
+ *
157
+ * A branch's `when:` and a `core.repeat`'s `until:` are NOT here. They are
158
+ * structural keys beside `steps:` rather than fields under `with:`, which is
159
+ * exactly what lets them be boolean — `whenSlot` and `repeatSlot` are theirs.
160
+ */
161
+ export declare function slotsForStep(doc: WorkflowDefinition, board: BoardId, step: Step, manifest: Manifest | undefined): Slot[];
@@ -0,0 +1 @@
1
+ export {};