@smthrs/plan 1.0.0-rc.8 → 1.0.0-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Effects.d.ts +29 -11
- package/dist/cjs/Effects.d.ts.map +1 -1
- package/dist/cjs/Effects.js.map +2 -2
- package/dist/cjs/FileSet.d.ts +121 -51
- package/dist/cjs/FileSet.d.ts.map +1 -1
- package/dist/cjs/FileSet.js.map +2 -2
- package/dist/cjs/GraphBuildError.d.ts +2 -4
- package/dist/cjs/GraphBuildError.d.ts.map +1 -1
- package/dist/cjs/GraphBuildError.js.map +2 -2
- package/dist/cjs/KeyMaterial.d.ts +13 -8
- package/dist/cjs/KeyMaterial.d.ts.map +1 -1
- package/dist/cjs/KeyMaterial.js.map +2 -2
- package/dist/cjs/Plan.d.ts +72 -23
- package/dist/cjs/Plan.d.ts.map +1 -1
- package/dist/cjs/Plan.js +3 -0
- package/dist/cjs/Plan.js.map +2 -2
- package/dist/cjs/PlanDiff.d.ts +7 -3
- package/dist/cjs/PlanDiff.d.ts.map +1 -1
- package/dist/cjs/PlanDiff.js.map +2 -2
- package/dist/cjs/StepKey.d.ts +38 -17
- package/dist/cjs/StepKey.d.ts.map +1 -1
- package/dist/cjs/StepKey.js.map +2 -2
- package/dist/esm/Effects.d.ts +29 -11
- package/dist/esm/Effects.d.ts.map +1 -1
- package/dist/esm/Effects.js +18 -7
- package/dist/esm/Effects.js.map +1 -1
- package/dist/esm/FileSet.d.ts +121 -51
- package/dist/esm/FileSet.d.ts.map +1 -1
- package/dist/esm/FileSet.js +97 -35
- package/dist/esm/FileSet.js.map +1 -1
- package/dist/esm/GraphBuildError.d.ts +2 -4
- package/dist/esm/GraphBuildError.d.ts.map +1 -1
- package/dist/esm/GraphBuildError.js +2 -3
- package/dist/esm/GraphBuildError.js.map +1 -1
- package/dist/esm/KeyMaterial.d.ts +13 -8
- package/dist/esm/KeyMaterial.d.ts.map +1 -1
- package/dist/esm/KeyMaterial.js +11 -4
- package/dist/esm/KeyMaterial.js.map +1 -1
- package/dist/esm/Plan.d.ts +72 -23
- package/dist/esm/Plan.d.ts.map +1 -1
- package/dist/esm/Plan.js +61 -16
- package/dist/esm/Plan.js.map +1 -1
- package/dist/esm/PlanDiff.d.ts +7 -3
- package/dist/esm/PlanDiff.d.ts.map +1 -1
- package/dist/esm/PlanDiff.js +0 -1
- package/dist/esm/PlanDiff.js.map +1 -1
- package/dist/esm/StepKey.d.ts +38 -17
- package/dist/esm/StepKey.d.ts.map +1 -1
- package/dist/esm/StepKey.js +33 -11
- package/dist/esm/StepKey.js.map +1 -1
- package/docs/guides/diff-two-plans.md +78 -1
- package/package.json +4 -4
- package/src/Effects.ts +29 -11
- package/src/FileSet.ts +121 -51
- package/src/GraphBuildError.ts +2 -4
- package/src/KeyMaterial.ts +13 -8
- package/src/Plan.ts +66 -23
- package/src/PlanDiff.ts +7 -3
- package/src/StepKey.ts +38 -17
package/src/Plan.ts
CHANGED
|
@@ -34,12 +34,17 @@ import * as StepKey from "./StepKey.ts"
|
|
|
34
34
|
* What a node does to the world, declared. Paths only — measuring them is
|
|
35
35
|
* run-time work, so a digest here would break the no-I/O law.
|
|
36
36
|
*
|
|
37
|
+
* Decoding fails with a `SchemaError` when a path is not workspace-relative.
|
|
38
|
+
* {@link compile} also refuses a node whose `removes` overlaps its `writes`
|
|
39
|
+
* with an `invalid_effects` {@link PlanError}.
|
|
40
|
+
*
|
|
37
41
|
* @since 0.1.0
|
|
38
42
|
* @category schemas
|
|
39
|
-
* @slop
|
|
40
43
|
*/
|
|
41
44
|
export const NodeEffects = Schema.Struct({
|
|
45
|
+
/** Paths the node reads. A read orders the node after every writer of an overlapping path. */
|
|
42
46
|
reads: Schema.Array(FileSet.ReadDeclaration),
|
|
47
|
+
/** Paths the node writes. Two writers of an overlapping path form a conflict. */
|
|
43
48
|
writes: Schema.Array(FileSet.Declaration),
|
|
44
49
|
/**
|
|
45
50
|
* Paths the node declares it will DELETE. A removal mutates the world exactly
|
|
@@ -47,6 +52,7 @@ export const NodeEffects = Schema.Struct({
|
|
|
47
52
|
* conflict pass and the reader-after-writer pass see them as one set.
|
|
48
53
|
*/
|
|
49
54
|
removes: Schema.optional(Schema.Array(FileSet.Pattern)),
|
|
55
|
+
/** How strictly the sandbox enforces these declarations. */
|
|
50
56
|
boundaryMode: FileSet.BoundaryMode
|
|
51
57
|
})
|
|
52
58
|
|
|
@@ -55,16 +61,18 @@ export const NodeEffects = Schema.Struct({
|
|
|
55
61
|
*
|
|
56
62
|
* @since 0.1.0
|
|
57
63
|
* @category models
|
|
58
|
-
* @slop
|
|
59
64
|
*/
|
|
60
65
|
export type NodeEffects = typeof NodeEffects.Type
|
|
61
66
|
|
|
62
67
|
/**
|
|
63
|
-
* The plan-time
|
|
68
|
+
* The plan-time verdict for two writers of an overlapping path:
|
|
69
|
+
* `serialize` gives the later writer an ordering edge, `lane` records a lane
|
|
70
|
+
* annotation on both writers and adds no edge, and `fail` refuses the plan
|
|
71
|
+
* with `overlap_forbidden`. For a pair, `fail` dominates `lane`, which
|
|
72
|
+
* dominates `serialize`. The same schema as `Effects.ConflictStrategy`.
|
|
64
73
|
*
|
|
65
74
|
* @since 0.1.0
|
|
66
75
|
* @category schemas
|
|
67
|
-
* @slop
|
|
68
76
|
*/
|
|
69
77
|
export const PairStrategy = Effects.ConflictStrategy
|
|
70
78
|
|
|
@@ -73,18 +81,17 @@ export const PairStrategy = Effects.ConflictStrategy
|
|
|
73
81
|
*
|
|
74
82
|
* @since 0.1.0
|
|
75
83
|
* @category models
|
|
76
|
-
* @slop
|
|
77
84
|
*/
|
|
78
85
|
export type PairStrategy = typeof PairStrategy.Type
|
|
79
86
|
|
|
80
87
|
/**
|
|
81
88
|
* The runtime half of a conflict annotation: what the scheduler
|
|
82
89
|
* does when the overlap the plan predicted actually bites, or when a
|
|
83
|
-
* scheduled node's inputs are invalidated under it.
|
|
90
|
+
* scheduled node's inputs are invalidated under it. For a pair,
|
|
91
|
+
* `stop-merge` dominates `delay-rebase`.
|
|
84
92
|
*
|
|
85
93
|
* @since 0.1.0
|
|
86
94
|
* @category schemas
|
|
87
|
-
* @slop
|
|
88
95
|
*/
|
|
89
96
|
export const RuntimeStrategy = Schema.Literals(["delay-rebase", "stop-merge"])
|
|
90
97
|
|
|
@@ -93,7 +100,6 @@ export const RuntimeStrategy = Schema.Literals(["delay-rebase", "stop-merge"])
|
|
|
93
100
|
*
|
|
94
101
|
* @since 0.1.0
|
|
95
102
|
* @category models
|
|
96
|
-
* @slop
|
|
97
103
|
*/
|
|
98
104
|
export type RuntimeStrategy = typeof RuntimeStrategy.Type
|
|
99
105
|
|
|
@@ -104,9 +110,11 @@ const NodeKind = Schema.Literals(["step", "agent", "merge"])
|
|
|
104
110
|
* One resolved overlap between two writers that no dependency path already
|
|
105
111
|
* orders. Conflict is a property of the PAIR, not of one declaration.
|
|
106
112
|
*
|
|
113
|
+
* `with` names the other node, `paths` lists the overlapping declarations,
|
|
114
|
+
* and `strategy` and `runtime` are the verdicts resolved for the pair.
|
|
115
|
+
*
|
|
107
116
|
* @since 0.1.0
|
|
108
117
|
* @category schemas
|
|
109
|
-
* @slop
|
|
110
118
|
*/
|
|
111
119
|
export const ConflictAnnotation = Schema.Struct({
|
|
112
120
|
with: Schema.NonEmptyString,
|
|
@@ -120,7 +128,6 @@ export const ConflictAnnotation = Schema.Struct({
|
|
|
120
128
|
*
|
|
121
129
|
* @since 0.1.0
|
|
122
130
|
* @category models
|
|
123
|
-
* @slop
|
|
124
131
|
*/
|
|
125
132
|
export type ConflictAnnotation = typeof ConflictAnnotation.Type
|
|
126
133
|
|
|
@@ -138,9 +145,11 @@ export type ConflictAnnotation = typeof ConflictAnnotation.Type
|
|
|
138
145
|
* a later elaboration can resolve a pair against them without re-reading the
|
|
139
146
|
* flow source.
|
|
140
147
|
*
|
|
148
|
+
* `generation` is 0 for nodes from {@link compile} and grows by one with each
|
|
149
|
+
* {@link append}.
|
|
150
|
+
*
|
|
141
151
|
* @since 0.1.0
|
|
142
152
|
* @category schemas
|
|
143
|
-
* @slop
|
|
144
153
|
*/
|
|
145
154
|
export const PlanNode = Schema.Struct({
|
|
146
155
|
id: Schema.NonEmptyString,
|
|
@@ -161,7 +170,6 @@ export const PlanNode = Schema.Struct({
|
|
|
161
170
|
*
|
|
162
171
|
* @since 0.1.0
|
|
163
172
|
* @category models
|
|
164
|
-
* @slop
|
|
165
173
|
*/
|
|
166
174
|
export type PlanNode = typeof PlanNode.Type
|
|
167
175
|
|
|
@@ -175,9 +183,11 @@ export type PlanNode = typeof PlanNode.Type
|
|
|
175
183
|
* `baseDigest` is the digest at generation 0: what a human approved and what
|
|
176
184
|
* a `RUNNING` run pins. `digest` advances with every appended elaboration.
|
|
177
185
|
*
|
|
186
|
+
* Decoding checks only the shape. Use {@link verify} to check that keys,
|
|
187
|
+
* edges, and digests match the declarations.
|
|
188
|
+
*
|
|
178
189
|
* @since 0.1.0
|
|
179
190
|
* @category schemas
|
|
180
|
-
* @slop
|
|
181
191
|
*/
|
|
182
192
|
export const Plan = Schema.Struct({
|
|
183
193
|
planId: Schema.NonEmptyString,
|
|
@@ -193,7 +203,6 @@ export const Plan = Schema.Struct({
|
|
|
193
203
|
*
|
|
194
204
|
* @since 0.1.0
|
|
195
205
|
* @category models
|
|
196
|
-
* @slop
|
|
197
206
|
*/
|
|
198
207
|
export type Plan = typeof Plan.Type
|
|
199
208
|
|
|
@@ -202,13 +211,17 @@ export type Plan = typeof Plan.Type
|
|
|
202
211
|
*
|
|
203
212
|
* @since 0.1.0
|
|
204
213
|
* @category models
|
|
205
|
-
* @slop
|
|
206
214
|
*/
|
|
207
215
|
export interface NodeDraft {
|
|
216
|
+
/** A non-empty id, unique within the plan. */
|
|
208
217
|
readonly id: string
|
|
218
|
+
/** The key inputs. Its `Ref` inputs name the node's material dependencies. */
|
|
209
219
|
readonly material: KeyMaterial.KeyMaterial
|
|
220
|
+
/** The declared reads, writes, and removals. They replace `material.effects`. */
|
|
210
221
|
readonly effects: NodeEffects
|
|
222
|
+
/** `step`, `agent`, or `merge`. Defaults to `step`. */
|
|
211
223
|
readonly kind?: PlanNode["kind"] | undefined
|
|
224
|
+
/** A safe integer scheduling priority. Defaults to 0. */
|
|
212
225
|
readonly priority?: number | undefined
|
|
213
226
|
/** This declaration's preferred plan-time verdict for a detected overlap. */
|
|
214
227
|
readonly conflictStrategy?: PairStrategy | undefined
|
|
@@ -217,11 +230,22 @@ export interface NodeDraft {
|
|
|
217
230
|
}
|
|
218
231
|
|
|
219
232
|
/**
|
|
220
|
-
* A graph the compiler refuses.
|
|
233
|
+
* A graph the compiler refuses. `code` names the rule:
|
|
234
|
+
*
|
|
235
|
+
* - `cycle`: material or inferred edges form a cycle.
|
|
236
|
+
* - `unknown_dependency`: a `Ref` names a node that is not in the plan.
|
|
237
|
+
* - `duplicate_node`: two nodes share an id.
|
|
238
|
+
* - `overlap_forbidden`: two unordered writers overlap and the pair's
|
|
239
|
+
* strategy is `fail`.
|
|
240
|
+
* - `invalid_effects`: a node both writes and removes one path.
|
|
241
|
+
* - `invalid_node`: a draft field, the plan id, or the flow name is malformed.
|
|
242
|
+
* - `invalid_plan`: {@link verify} found a plan that does not match its
|
|
243
|
+
* declarations.
|
|
244
|
+
* - `graph_too_large`: the plan exceeds {@link maximumPlanNodes} or the
|
|
245
|
+
* effect-analysis work budget.
|
|
221
246
|
*
|
|
222
247
|
* @since 0.1.0
|
|
223
248
|
* @category errors
|
|
224
|
-
* @slop
|
|
225
249
|
*/
|
|
226
250
|
export class PlanError extends Schema.TaggedError<PlanError>()("@smthrs/plan/PlanError", {
|
|
227
251
|
code: Schema.Literals([
|
|
@@ -245,7 +269,6 @@ export class PlanError extends Schema.TaggedError<PlanError>()("@smthrs/plan/Pla
|
|
|
245
269
|
*
|
|
246
270
|
* @since 1.0.0
|
|
247
271
|
* @category limits
|
|
248
|
-
* @slop
|
|
249
272
|
*/
|
|
250
273
|
export const maximumPlanNodes = 10_000
|
|
251
274
|
|
|
@@ -671,9 +694,15 @@ const keyNodes = (
|
|
|
671
694
|
* {@link maximumPlanNodes} and fail with `graph_too_large` above that limit
|
|
672
695
|
* or the effect-analysis work budget. Analysis yields periodically.
|
|
673
696
|
*
|
|
697
|
+
* The options and drafts are copied when `compile` is called, so later
|
|
698
|
+
* caller mutation does not reach the plan. The result is deeply frozen.
|
|
699
|
+
*
|
|
700
|
+
* @param options - `planId` and `flow` name the plan; `nodes` are the drafts.
|
|
701
|
+
* @returns The generation-0 plan, whose `baseDigest` equals its `digest`.
|
|
702
|
+
* Fails with {@link PlanError} for a refused graph, `KeyMaterialError` or
|
|
703
|
+
* `SchemaError` when a key cannot be derived.
|
|
674
704
|
* @since 0.1.0
|
|
675
705
|
* @category constructors
|
|
676
|
-
* @slop
|
|
677
706
|
*/
|
|
678
707
|
export const compile = (options: {
|
|
679
708
|
readonly planId: string
|
|
@@ -710,9 +739,16 @@ export const compile = (options: {
|
|
|
710
739
|
* Re-ordering after a reconciliation happens by re-keying *future* steps,
|
|
711
740
|
* never by rewriting history.
|
|
712
741
|
*
|
|
742
|
+
* A plan not produced by this module is checked with {@link verify} first.
|
|
743
|
+
*
|
|
744
|
+
* @param plan - The plan to grow.
|
|
745
|
+
* @param drafts - At least one new node. Ids must not repeat existing ones;
|
|
746
|
+
* `Ref` inputs may name existing nodes.
|
|
747
|
+
* @returns The plan with one more generation and a new `digest`. Fails with
|
|
748
|
+
* {@link PlanError} (`invalid_node` for an empty `drafts`, `invalid_plan` for a
|
|
749
|
+
* plan that fails verification), `KeyMaterialError`, or `SchemaError`.
|
|
713
750
|
* @since 0.1.0
|
|
714
751
|
* @category constructors
|
|
715
|
-
* @slop
|
|
716
752
|
*/
|
|
717
753
|
export const append = (
|
|
718
754
|
plan: Plan,
|
|
@@ -832,9 +868,10 @@ export const verify = (
|
|
|
832
868
|
* The nodes added by the newest generation: what {@link module:PlanStore}
|
|
833
869
|
* appends and what the `subgraph-appended` journal record names.
|
|
834
870
|
*
|
|
871
|
+
* @param plan - A plan.
|
|
872
|
+
* @returns The nodes whose generation equals `plan.generation`, in order.
|
|
835
873
|
* @since 0.1.0
|
|
836
874
|
* @category accessors
|
|
837
|
-
* @slop
|
|
838
875
|
*/
|
|
839
876
|
export const generationNodes = (plan: Plan): ReadonlyArray<PlanNode> =>
|
|
840
877
|
plan.nodes.filter((node) => node.generation === plan.generation)
|
|
@@ -844,9 +881,11 @@ export const generationNodes = (plan: Plan): ReadonlyArray<PlanNode> =>
|
|
|
844
881
|
* {@link verify}: such values take verify's trusted fast path and need no
|
|
845
882
|
* second schema validation.
|
|
846
883
|
*
|
|
884
|
+
* @param input - Any value.
|
|
885
|
+
* @returns `true` only for a plan returned by {@link compile},
|
|
886
|
+
* {@link append}, or {@link verify}; a structurally equal copy returns `false`.
|
|
847
887
|
* @since 1.0.0
|
|
848
888
|
* @category guards
|
|
849
|
-
* @slop
|
|
850
889
|
*/
|
|
851
890
|
export const isVerified = (input: unknown): input is Plan =>
|
|
852
891
|
typeof input === "object" && input !== null && frozenPlans.has(input as Plan)
|
|
@@ -858,9 +897,13 @@ export const isVerified = (input: unknown): input is Plan =>
|
|
|
858
897
|
* plan's integrity from the already-verified prefix instead of decoding and
|
|
859
898
|
* re-verifying every stored row.
|
|
860
899
|
*
|
|
900
|
+
* For a generation-0 plan no generation precedes the newest, so this is the
|
|
901
|
+
* digest of an empty node list.
|
|
902
|
+
*
|
|
903
|
+
* @param plan - A plan.
|
|
904
|
+
* @returns The approval digest of the nodes older than `plan.generation`.
|
|
861
905
|
* @since 1.0.0
|
|
862
906
|
* @category accessors
|
|
863
|
-
* @slop
|
|
864
907
|
*/
|
|
865
908
|
export const prefixDigest = (
|
|
866
909
|
plan: Plan
|
package/src/PlanDiff.ts
CHANGED
|
@@ -23,11 +23,13 @@ import type * as Planned from "./Planned.ts"
|
|
|
23
23
|
*
|
|
24
24
|
* @since 0.1.0
|
|
25
25
|
* @category models
|
|
26
|
-
* @slop
|
|
27
26
|
*/
|
|
28
27
|
export interface Rekeyed {
|
|
28
|
+
/** The node's id in the newer plan. */
|
|
29
29
|
readonly id: string
|
|
30
|
+
/** The step key the paired node carried in the older plan. */
|
|
30
31
|
readonly from: string
|
|
32
|
+
/** The step key the node carries in the newer plan. */
|
|
31
33
|
readonly to: string
|
|
32
34
|
/**
|
|
33
35
|
* Field labels such as `"kind"`, `"body"`, `"layers"`, `"capabilities"`,
|
|
@@ -44,12 +46,15 @@ export interface Rekeyed {
|
|
|
44
46
|
*
|
|
45
47
|
* @since 0.1.0
|
|
46
48
|
* @category models
|
|
47
|
-
* @slop
|
|
48
49
|
*/
|
|
49
50
|
export interface PlanDiff {
|
|
51
|
+
/** Ids, in the newer plan, of nodes with no counterpart in the older plan. */
|
|
50
52
|
readonly added: ReadonlyArray<string>
|
|
53
|
+
/** Ids, in the older plan, of nodes with no counterpart in the newer plan. */
|
|
51
54
|
readonly removed: ReadonlyArray<string>
|
|
55
|
+
/** Nodes paired by id whose step key changed, with the fields that changed it. */
|
|
52
56
|
readonly rekeyed: ReadonlyArray<Rekeyed>
|
|
57
|
+
/** Ids, in the newer plan, of nodes whose step key is unchanged, including nodes whose id moved. */
|
|
53
58
|
readonly unchanged: ReadonlyArray<string>
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -174,7 +179,6 @@ const changedFields = (
|
|
|
174
179
|
* plan's node order.
|
|
175
180
|
* @since 0.1.0
|
|
176
181
|
* @category constructors
|
|
177
|
-
* @slop
|
|
178
182
|
*/
|
|
179
183
|
export const diff = (previous: Plan.Plan, next: Plan.Plan): PlanDiff => {
|
|
180
184
|
// pairs maps a next node to the previous node it is the same step as.
|
package/src/StepKey.ts
CHANGED
|
@@ -43,7 +43,6 @@ import type * as KeyMaterial from "./KeyMaterial.ts"
|
|
|
43
43
|
*
|
|
44
44
|
* @since 0.1.0
|
|
45
45
|
* @category models
|
|
46
|
-
* @slop
|
|
47
46
|
*/
|
|
48
47
|
export type StepKey = StoredKey
|
|
49
48
|
|
|
@@ -72,7 +71,6 @@ type GraphReference = "ref" | "pending" | "ref-projected"
|
|
|
72
71
|
*
|
|
73
72
|
* @since 0.1.0
|
|
74
73
|
* @category models
|
|
75
|
-
* @slop
|
|
76
74
|
*/
|
|
77
75
|
export interface DigestInput {
|
|
78
76
|
readonly [DigestInputTypeId]: typeof DigestInputTypeId
|
|
@@ -94,9 +92,12 @@ export interface DigestInput {
|
|
|
94
92
|
* Nominally tags a precomputed digest so it is hashed as a digest reference
|
|
95
93
|
* rather than a literal value.
|
|
96
94
|
*
|
|
95
|
+
* @param digest - The precomputed digest.
|
|
96
|
+
* @param reference - Which graph reference resolved to the digest, and the
|
|
97
|
+
* projection path for `ref-projected`. Omit for a plain digest.
|
|
98
|
+
* @returns The tagged digest input.
|
|
97
99
|
* @since 0.1.0
|
|
98
100
|
* @category constructors
|
|
99
|
-
* @slop
|
|
100
101
|
*/
|
|
101
102
|
export const digestInput = (
|
|
102
103
|
digest: string,
|
|
@@ -112,11 +113,13 @@ export const digestInput = (
|
|
|
112
113
|
})
|
|
113
114
|
|
|
114
115
|
/**
|
|
115
|
-
* Type guard for values produced by {@link digestInput}.
|
|
116
|
+
* Type guard for values produced by {@link digestInput}. A plain object
|
|
117
|
+
* shaped like `{ digest: "..." }` is not one.
|
|
116
118
|
*
|
|
119
|
+
* @param value - Any value.
|
|
120
|
+
* @returns `true` only for a value carrying the private digest-input brand.
|
|
117
121
|
* @since 0.1.0
|
|
118
122
|
* @category guards
|
|
119
|
-
* @slop
|
|
120
123
|
*/
|
|
121
124
|
export const isDigestInput = (value: unknown): value is DigestInput =>
|
|
122
125
|
typeof value === "object" && value !== null && DigestInputTypeId in value &&
|
|
@@ -146,7 +149,6 @@ export const isDigestInput = (value: unknown): value is DigestInput =>
|
|
|
146
149
|
*
|
|
147
150
|
* @since 0.1.0
|
|
148
151
|
* @category models
|
|
149
|
-
* @slop
|
|
150
152
|
*/
|
|
151
153
|
export type EnvironmentIdentity = typeof EnvironmentIdentity.Type
|
|
152
154
|
|
|
@@ -180,9 +182,13 @@ export const EnvironmentIdentity = Schema.Union([
|
|
|
180
182
|
* digest. Entries are sound only while each settled `from` value is immutable;
|
|
181
183
|
* callers must create a fresh memo when those values can change.
|
|
182
184
|
*
|
|
185
|
+
* `digest(from, path, compute)` runs `compute` once per address and shares its
|
|
186
|
+
* result with concurrent callers. A failed or interrupted computation is not
|
|
187
|
+
* kept: the next caller recomputes, and a waiter whose leader was interrupted
|
|
188
|
+
* becomes the new leader instead of inheriting the interrupt.
|
|
189
|
+
*
|
|
183
190
|
* @since 0.1.0
|
|
184
191
|
* @category models
|
|
185
|
-
* @slop
|
|
186
192
|
*/
|
|
187
193
|
export interface DigestMemo {
|
|
188
194
|
readonly digest: (
|
|
@@ -196,9 +202,9 @@ export interface DigestMemo {
|
|
|
196
202
|
* Creates an empty projected-value digest memo. Projection addresses are
|
|
197
203
|
* JSON-encoded `[from, path]` tuples so segment boundaries are unambiguous.
|
|
198
204
|
*
|
|
205
|
+
* @returns A memo with no entries.
|
|
199
206
|
* @since 0.1.0
|
|
200
207
|
* @category constructors
|
|
201
|
-
* @slop
|
|
202
208
|
*/
|
|
203
209
|
export const makeDigestMemo = (): DigestMemo => {
|
|
204
210
|
const entries = new Map<string, Deferred.Deferred<StepKey, Schema.SchemaError>>()
|
|
@@ -240,7 +246,6 @@ export const makeDigestMemo = (): DigestMemo => {
|
|
|
240
246
|
*
|
|
241
247
|
* @since 0.1.0
|
|
242
248
|
* @category models
|
|
243
|
-
* @slop
|
|
244
249
|
*/
|
|
245
250
|
export interface ContentIdentity {
|
|
246
251
|
readonly body: unknown
|
|
@@ -261,7 +266,6 @@ export interface ContentIdentity {
|
|
|
261
266
|
*
|
|
262
267
|
* @since 0.1.0
|
|
263
268
|
* @category models
|
|
264
|
-
* @slop
|
|
265
269
|
*/
|
|
266
270
|
export interface OrdinalIdentity {
|
|
267
271
|
readonly runId: string
|
|
@@ -271,11 +275,18 @@ export interface OrdinalIdentity {
|
|
|
271
275
|
}
|
|
272
276
|
|
|
273
277
|
/**
|
|
274
|
-
* Stable failures while
|
|
278
|
+
* Stable failures while deriving a key. `code` names the rule:
|
|
279
|
+
*
|
|
280
|
+
* - `invalid_environment`: an {@link EnvironmentIdentity} that is neither
|
|
281
|
+
* declared nor run-scoped.
|
|
282
|
+
* - `missing_dependency`: a `Ref` or `Pending` input names a node with no
|
|
283
|
+
* recorded digest, or, at dispatch, a `Ref` names one with no settled
|
|
284
|
+
* result.
|
|
285
|
+
* - `non_content_material`: a content-key constructor received material whose
|
|
286
|
+
* tier is not `sealed`.
|
|
275
287
|
*
|
|
276
288
|
* @since 0.1.0
|
|
277
289
|
* @category errors
|
|
278
|
-
* @slop
|
|
279
290
|
*/
|
|
280
291
|
export class KeyMaterialError extends Schema.TaggedError<KeyMaterialError>()("@smthrs/plan/KeyMaterialError", {
|
|
281
292
|
code: Schema.Literals(["invalid_environment", "missing_dependency", "non_content_material"]),
|
|
@@ -423,9 +434,11 @@ export const environmentIdentity = (
|
|
|
423
434
|
* declarations are normalized before serialization; write declarations remain
|
|
424
435
|
* part of the identity even when the step writes nothing.
|
|
425
436
|
*
|
|
437
|
+
* @param identity - The step's content material.
|
|
438
|
+
* @returns The content key. Fails with `invalid_environment` for a malformed
|
|
439
|
+
* environment, or `SchemaError` when the material cannot be serialized.
|
|
426
440
|
* @since 0.1.0
|
|
427
441
|
* @category constructors
|
|
428
|
-
* @slop
|
|
429
442
|
*/
|
|
430
443
|
export const content = (
|
|
431
444
|
identity: ContentIdentity
|
|
@@ -447,9 +460,10 @@ export const content = (
|
|
|
447
460
|
* Produces a run-local ordinal key for compensable, irreversible, or unsealed
|
|
448
461
|
* work. These keys intentionally cannot be reused across runs.
|
|
449
462
|
*
|
|
463
|
+
* @param identity - The run id, optional parent scope, ordinal, and tier.
|
|
464
|
+
* @returns The ordinal key.
|
|
450
465
|
* @since 0.1.0
|
|
451
466
|
* @category constructors
|
|
452
|
-
* @slop
|
|
453
467
|
*/
|
|
454
468
|
export const ordinal = (
|
|
455
469
|
identity: OrdinalIdentity
|
|
@@ -466,9 +480,13 @@ export const ordinal = (
|
|
|
466
480
|
* and never enter the hashed value — rename a node and nothing re-keys; change
|
|
467
481
|
* what a node consumes and everything downstream of it does.
|
|
468
482
|
*
|
|
483
|
+
* @param material - The node's key material; its tier must be `sealed`.
|
|
484
|
+
* @param dependencyDigests - The key of every node the material references,
|
|
485
|
+
* by node id.
|
|
486
|
+
* @returns The content key. Fails with `non_content_material` for a
|
|
487
|
+
* non-sealed tier and `missing_dependency` for an unresolved reference.
|
|
469
488
|
* @since 0.1.0
|
|
470
489
|
* @category constructors
|
|
471
|
-
* @slop
|
|
472
490
|
*/
|
|
473
491
|
export const fromKeyMaterial = (
|
|
474
492
|
material: KeyMaterial.KeyMaterial,
|
|
@@ -637,7 +655,6 @@ const orderingOnly = "ordering-only"
|
|
|
637
655
|
*
|
|
638
656
|
* @since 0.1.0
|
|
639
657
|
* @category utilities
|
|
640
|
-
* @slop
|
|
641
658
|
*/
|
|
642
659
|
export const project = (value: unknown, path: ReadonlyArray<string>): unknown => {
|
|
643
660
|
let current = value
|
|
@@ -672,9 +689,13 @@ export const project = (value: unknown, path: ReadonlyArray<string>): unknown =>
|
|
|
672
689
|
* halt rule guarantees it: a dependent of failed or skipped work never
|
|
673
690
|
* dispatches, so a `Ref` always resolves against a success.
|
|
674
691
|
*
|
|
692
|
+
* @param options - The material, the dependencies' settled results, the
|
|
693
|
+
* measured hermetic boundary, and an optional environment and memo.
|
|
694
|
+
* @returns The dispatch key. Fails with `non_content_material` for a
|
|
695
|
+
* non-sealed tier, `missing_dependency` for a `Ref` absent from `results`,
|
|
696
|
+
* or `invalid_environment` for a malformed environment.
|
|
675
697
|
* @since 0.1.0
|
|
676
698
|
* @category constructors
|
|
677
|
-
* @slop
|
|
678
699
|
*/
|
|
679
700
|
export const dispatchIdentity = (options: {
|
|
680
701
|
readonly material: KeyMaterial.KeyMaterial
|