@smthrs/plan 0.0.0-stage → 1.0.0-rc.4

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 (199) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/LICENSE +21 -0
  3. package/README.md +228 -2
  4. package/dist/cjs/CachePolicy.d.ts +131 -0
  5. package/dist/cjs/CachePolicy.d.ts.map +1 -0
  6. package/dist/cjs/CachePolicy.js +50 -0
  7. package/dist/cjs/CachePolicy.js.map +7 -0
  8. package/dist/cjs/Effects.d.ts +240 -0
  9. package/dist/cjs/Effects.d.ts.map +1 -0
  10. package/dist/cjs/Effects.js +76 -0
  11. package/dist/cjs/Effects.js.map +7 -0
  12. package/dist/cjs/FileSet.d.ts +288 -0
  13. package/dist/cjs/FileSet.d.ts.map +1 -0
  14. package/dist/cjs/FileSet.js +191 -0
  15. package/dist/cjs/FileSet.js.map +7 -0
  16. package/dist/cjs/GraphBuildError.d.ts +122 -0
  17. package/dist/cjs/GraphBuildError.d.ts.map +1 -0
  18. package/dist/cjs/GraphBuildError.js +71 -0
  19. package/dist/cjs/GraphBuildError.js.map +7 -0
  20. package/dist/cjs/KeyMaterial.d.ts +102 -0
  21. package/dist/cjs/KeyMaterial.d.ts.map +1 -0
  22. package/dist/cjs/KeyMaterial.js +64 -0
  23. package/dist/cjs/KeyMaterial.js.map +7 -0
  24. package/dist/cjs/Node.d.ts +606 -0
  25. package/dist/cjs/Node.d.ts.map +1 -0
  26. package/dist/cjs/Node.js +207 -0
  27. package/dist/cjs/Node.js.map +7 -0
  28. package/dist/cjs/Placement.d.ts +117 -0
  29. package/dist/cjs/Placement.d.ts.map +1 -0
  30. package/dist/cjs/Placement.js +46 -0
  31. package/dist/cjs/Placement.js.map +7 -0
  32. package/dist/cjs/Plan.d.ts +440 -0
  33. package/dist/cjs/Plan.d.ts.map +1 -0
  34. package/dist/cjs/Plan.js +508 -0
  35. package/dist/cjs/Plan.js.map +7 -0
  36. package/dist/cjs/PlanDiff.d.ts +58 -0
  37. package/dist/cjs/PlanDiff.d.ts.map +1 -0
  38. package/dist/cjs/PlanDiff.js +124 -0
  39. package/dist/cjs/PlanDiff.js.map +7 -0
  40. package/dist/cjs/Planned.d.ts +121 -0
  41. package/dist/cjs/Planned.d.ts.map +1 -0
  42. package/dist/cjs/Planned.js +72 -0
  43. package/dist/cjs/Planned.js.map +7 -0
  44. package/dist/cjs/Repetition.d.ts +31 -0
  45. package/dist/cjs/Repetition.d.ts.map +1 -0
  46. package/dist/cjs/Repetition.js +36 -0
  47. package/dist/cjs/Repetition.js.map +7 -0
  48. package/dist/cjs/Scheduling.d.ts +89 -0
  49. package/dist/cjs/Scheduling.d.ts.map +1 -0
  50. package/dist/cjs/Scheduling.js +76 -0
  51. package/dist/cjs/Scheduling.js.map +7 -0
  52. package/dist/cjs/StepKey.d.ts +333 -0
  53. package/dist/cjs/StepKey.d.ts.map +1 -0
  54. package/dist/cjs/StepKey.js +311 -0
  55. package/dist/cjs/StepKey.js.map +7 -0
  56. package/dist/cjs/index.d.ts +91 -0
  57. package/dist/cjs/index.d.ts.map +1 -0
  58. package/dist/cjs/index.js +59 -0
  59. package/dist/cjs/index.js.map +7 -0
  60. package/dist/cjs/internal/ConflictAnnotation.d.ts +59 -0
  61. package/dist/cjs/internal/ConflictAnnotation.d.ts.map +1 -0
  62. package/dist/cjs/internal/ConflictAnnotation.js +247 -0
  63. package/dist/cjs/internal/ConflictAnnotation.js.map +7 -0
  64. package/dist/cjs/internal/EffectCandidates.d.ts +19 -0
  65. package/dist/cjs/internal/EffectCandidates.d.ts.map +1 -0
  66. package/dist/cjs/internal/EffectCandidates.js +92 -0
  67. package/dist/cjs/internal/EffectCandidates.js.map +7 -0
  68. package/dist/cjs/internal/JsonMirror.d.ts +34 -0
  69. package/dist/cjs/internal/JsonMirror.d.ts.map +1 -0
  70. package/dist/cjs/internal/JsonMirror.js +195 -0
  71. package/dist/cjs/internal/JsonMirror.js.map +7 -0
  72. package/dist/cjs/internal/effects.d.ts +245 -0
  73. package/dist/cjs/internal/effects.d.ts.map +1 -0
  74. package/dist/cjs/internal/effects.js +290 -0
  75. package/dist/cjs/internal/effects.js.map +7 -0
  76. package/dist/cjs/internal/node.d.ts +526 -0
  77. package/dist/cjs/internal/node.d.ts.map +1 -0
  78. package/dist/cjs/internal/node.js +312 -0
  79. package/dist/cjs/internal/node.js.map +7 -0
  80. package/dist/cjs/package.json +1 -0
  81. package/dist/cjs/test/PlanFixtures.d.ts +127 -0
  82. package/dist/cjs/test/PlanFixtures.d.ts.map +1 -0
  83. package/dist/cjs/test/PlanFixtures.js +59 -0
  84. package/dist/cjs/test/PlanFixtures.js.map +7 -0
  85. package/dist/esm/CachePolicy.d.ts +131 -0
  86. package/dist/esm/CachePolicy.d.ts.map +1 -0
  87. package/dist/esm/CachePolicy.js +117 -0
  88. package/dist/esm/CachePolicy.js.map +1 -0
  89. package/dist/esm/Effects.d.ts +240 -0
  90. package/dist/esm/Effects.d.ts.map +1 -0
  91. package/dist/esm/Effects.js +167 -0
  92. package/dist/esm/Effects.js.map +1 -0
  93. package/dist/esm/FileSet.d.ts +288 -0
  94. package/dist/esm/FileSet.d.ts.map +1 -0
  95. package/dist/esm/FileSet.js +324 -0
  96. package/dist/esm/FileSet.js.map +1 -0
  97. package/dist/esm/GraphBuildError.d.ts +122 -0
  98. package/dist/esm/GraphBuildError.d.ts.map +1 -0
  99. package/dist/esm/GraphBuildError.js +146 -0
  100. package/dist/esm/GraphBuildError.js.map +1 -0
  101. package/dist/esm/KeyMaterial.d.ts +102 -0
  102. package/dist/esm/KeyMaterial.d.ts.map +1 -0
  103. package/dist/esm/KeyMaterial.js +85 -0
  104. package/dist/esm/KeyMaterial.js.map +1 -0
  105. package/dist/esm/Node.d.ts +606 -0
  106. package/dist/esm/Node.d.ts.map +1 -0
  107. package/dist/esm/Node.js +521 -0
  108. package/dist/esm/Node.js.map +1 -0
  109. package/dist/esm/Placement.d.ts +117 -0
  110. package/dist/esm/Placement.d.ts.map +1 -0
  111. package/dist/esm/Placement.js +65 -0
  112. package/dist/esm/Placement.js.map +1 -0
  113. package/dist/esm/Plan.d.ts +440 -0
  114. package/dist/esm/Plan.d.ts.map +1 -0
  115. package/dist/esm/Plan.js +673 -0
  116. package/dist/esm/Plan.js.map +1 -0
  117. package/dist/esm/PlanDiff.d.ts +58 -0
  118. package/dist/esm/PlanDiff.d.ts.map +1 -0
  119. package/dist/esm/PlanDiff.js +159 -0
  120. package/dist/esm/PlanDiff.js.map +1 -0
  121. package/dist/esm/Planned.d.ts +121 -0
  122. package/dist/esm/Planned.d.ts.map +1 -0
  123. package/dist/esm/Planned.js +147 -0
  124. package/dist/esm/Planned.js.map +1 -0
  125. package/dist/esm/Repetition.d.ts +31 -0
  126. package/dist/esm/Repetition.d.ts.map +1 -0
  127. package/dist/esm/Repetition.js +24 -0
  128. package/dist/esm/Repetition.js.map +1 -0
  129. package/dist/esm/Scheduling.d.ts +89 -0
  130. package/dist/esm/Scheduling.d.ts.map +1 -0
  131. package/dist/esm/Scheduling.js +82 -0
  132. package/dist/esm/Scheduling.js.map +1 -0
  133. package/dist/esm/StepKey.d.ts +333 -0
  134. package/dist/esm/StepKey.d.ts.map +1 -0
  135. package/dist/esm/StepKey.js +478 -0
  136. package/dist/esm/StepKey.js.map +1 -0
  137. package/dist/esm/index.d.ts +91 -0
  138. package/dist/esm/index.d.ts.map +1 -0
  139. package/dist/esm/index.js +91 -0
  140. package/dist/esm/index.js.map +1 -0
  141. package/dist/esm/internal/ConflictAnnotation.d.ts +59 -0
  142. package/dist/esm/internal/ConflictAnnotation.d.ts.map +1 -0
  143. package/dist/esm/internal/ConflictAnnotation.js +332 -0
  144. package/dist/esm/internal/ConflictAnnotation.js.map +1 -0
  145. package/dist/esm/internal/EffectCandidates.d.ts +19 -0
  146. package/dist/esm/internal/EffectCandidates.d.ts.map +1 -0
  147. package/dist/esm/internal/EffectCandidates.js +86 -0
  148. package/dist/esm/internal/EffectCandidates.js.map +1 -0
  149. package/dist/esm/internal/JsonMirror.d.ts +34 -0
  150. package/dist/esm/internal/JsonMirror.d.ts.map +1 -0
  151. package/dist/esm/internal/JsonMirror.js +204 -0
  152. package/dist/esm/internal/JsonMirror.js.map +1 -0
  153. package/dist/esm/internal/effects.d.ts +245 -0
  154. package/dist/esm/internal/effects.d.ts.map +1 -0
  155. package/dist/esm/internal/effects.js +459 -0
  156. package/dist/esm/internal/effects.js.map +1 -0
  157. package/dist/esm/internal/node.d.ts +526 -0
  158. package/dist/esm/internal/node.d.ts.map +1 -0
  159. package/dist/esm/internal/node.js +564 -0
  160. package/dist/esm/internal/node.js.map +1 -0
  161. package/dist/esm/test/PlanFixtures.d.ts +127 -0
  162. package/dist/esm/test/PlanFixtures.d.ts.map +1 -0
  163. package/dist/esm/test/PlanFixtures.js +60 -0
  164. package/dist/esm/test/PlanFixtures.js.map +1 -0
  165. package/docs/README.md +165 -0
  166. package/docs/api.md +1475 -0
  167. package/docs/concepts/authoring-ast.md +203 -0
  168. package/docs/concepts/effects-and-conflicts.md +135 -0
  169. package/docs/concepts/plan-value.md +168 -0
  170. package/docs/concepts/step-keys.md +179 -0
  171. package/docs/guides/author-a-node-graph.md +182 -0
  172. package/docs/guides/compile-a-plan.md +146 -0
  173. package/docs/guides/declare-file-effects.md +143 -0
  174. package/docs/guides/diff-two-plans.md +101 -0
  175. package/docs/installation.md +71 -0
  176. package/docs/quickstart.md +128 -0
  177. package/docs/testing.md +73 -0
  178. package/docs/troubleshooting.md +314 -0
  179. package/package.json +254 -3
  180. package/src/CachePolicy.ts +143 -0
  181. package/src/Effects.ts +284 -0
  182. package/src/FileSet.ts +411 -0
  183. package/src/GraphBuildError.ts +158 -0
  184. package/src/KeyMaterial.ts +106 -0
  185. package/src/Node.ts +871 -0
  186. package/src/Placement.ts +99 -0
  187. package/src/Plan.ts +868 -0
  188. package/src/PlanDiff.ts +199 -0
  189. package/src/Planned.ts +208 -0
  190. package/src/Repetition.ts +33 -0
  191. package/src/Scheduling.ts +154 -0
  192. package/src/StepKey.ts +710 -0
  193. package/src/index.ts +103 -0
  194. package/src/internal/ConflictAnnotation.ts +374 -0
  195. package/src/internal/EffectCandidates.ts +89 -0
  196. package/src/internal/JsonMirror.ts +255 -0
  197. package/src/internal/effects.ts +543 -0
  198. package/src/internal/node.ts +858 -0
  199. package/src/test/PlanFixtures.ts +76 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,183 @@
1
+ # @smthrs/plan
2
+
3
+ ## Unreleased
4
+
5
+ ### Fixed
6
+
7
+ - Keep live executable bindings and planned-reference ownership across compatible package copies; serialized ASTs remain inert. ([#1991](https://github.com/smithersai/smithers/issues/1991)).
8
+
9
+ ## [Unreleased]
10
+
11
+ ### Added
12
+
13
+ - `Scheduling.make` exposes the pure ready-work priority, aging and capacity
14
+ policy used by the durable plan scheduler. Its exact integer comparison does
15
+ not round away priority differences after aging. Conditional readiness and
16
+ effect execution remain the coordinator's responsibilities.
17
+
18
+ ### Fixed
19
+
20
+ - `StepKey.EnvironmentIdentity` now also supplies the runtime schema. Identity
21
+ constructors reject malformed/unknown environment fields with typed errors.
22
+ `StepKey.environmentIdentity` fingerprints a runtime environment for durable
23
+ recovery binding, using content-key normalization in a separate namespace.
24
+ Existing valid content and dispatch keys are unchanged.
25
+
26
+ - Explicit `Ref`/`Pending` read-before-write sequences compile and verify,
27
+ including transitive paths and appended generations. Conflicting inferred
28
+ producer/serialize orders still fail with `cycle`.
29
+
30
+ - `Plan.compile`, `append`, and `verify` accept compensable and irreversible
31
+ declarations. New `StepKey.planIdentity` separates their approval fingerprints
32
+ from cache eligibility. Existing sealed keys are unchanged;
33
+ `fromKeyMaterial` and `dispatchIdentity` remain sealed-only.
34
+
35
+ ## [1.0.0-rc.0] - 2026-08-31
36
+
37
+ ### Added
38
+
39
+ - The persisted plan: `Plan` (compile, append, conflict annotation),
40
+ `PlanStore` (append-only SQL, migration block `4000`), `PlanDiff`, and the
41
+ `KeyMaterial` to `StepKey` compiler revived from the deleted
42
+ `packages/smithers/flows/keys` modules, now producing `@smthrs/keys` `Key`
43
+ values rather than a second digest format.
44
+ - Added `Node.priority`, which attaches a scheduling priority to a node, and
45
+ `Node.declaredPriority`, which reads one back. The value is a plain JSON
46
+ field on the AST rather than a `Context` annotation, so a stored plan keeps
47
+ it. `Plan.compile` copies it from `NodeDraft.priority` onto the plan node and
48
+ folds it into the plan digest a human approves; it never enters a node's key
49
+ material, so raising a priority reorders work without re-keying a step. A
50
+ priority that is not a safe integer is a `GraphBuildError` with the new
51
+ `invalid_priority` code.
52
+ - Added package-owned documentation. `packages/plan/docs` and the public JSDoc
53
+ in `src` are the single source for the published API page.
54
+
55
+ ### Breaking Changes
56
+
57
+ - Bumped key material to `flows/key-material/v2` and captured-function
58
+ identity to `sha256-source-captures/v4`. Existing persisted step keys are
59
+ intentionally invalidated rather than being reused across changed identity
60
+ semantics.
61
+
62
+ - Plan approval digests now include each node's conflict and runtime
63
+ strategies. This moves plan digests without moving step keys. A running run
64
+ pinned to the old digest needs a new approval before it can use the changed
65
+ plan.
66
+ - A node payload is now cloned as its JSON mirror, so a payload carrying a
67
+ `toJSON`, such as a `Date` or a `URL`, keys as the value it serializes to
68
+ rather than as an empty object. Payloads of that shape re-key once.
69
+ - A flow call's `KeyMaterial.body` no longer has a body-less shape. `Flow.make`
70
+ requires `body`, so `@smthrs/flow` never records `declaration.body:
71
+ undefined` for a call whose declaration survived beside its AST.
72
+ `StepKey.content` therefore folds a body digest into every such node, and the
73
+ keys `Plan.compile` derives for the flow calls that previously carried none
74
+ move with it.
75
+ - The raw migration steps moved beneath `src/internal/migrations`, so
76
+ `@smthrs/plan/migrations/0001_initial` and its siblings no longer resolve.
77
+ `Migrations.set` is the supported surface; a step imported on its own ran
78
+ outside the namespaced ordering `Migrator` relies on.
79
+ - `Node.isNode` no longer accepts the public `TypeId` marker on its own. It
80
+ recognizes a node this package built by registration at construction, and a
81
+ rehydrated node, an object sharing the node prototype whose own `ast` is a
82
+ well-formed AST, by that shape, which is what `@smthrs/flow` hands back for
83
+ an AST that crossed a serialization boundary. `Node.all`, `andThen`,
84
+ `branch`, and `catch` therefore refuse a forged object carrying the marker,
85
+ one inheriting it from a node, and one whose `ast` is missing, malformed, or
86
+ cyclic with their usual `GraphBuildError` instead of storing an `ast` the
87
+ package never built.
88
+ - `StepKey.EnvironmentIdentity` is now a discriminated union. A declared
89
+ environment may not carry a `runScope` and an undeclared one must, which
90
+ `StepKey.dispatchIdentity` also enforces at run time with the new
91
+ `invalid_environment` code.
92
+ - A plan may no longer contain an unbounded number of nodes. `Plan.compile` and
93
+ `Plan.append` refuse a plan above `Plan.maximumPlanNodes` with the new
94
+ `graph_too_large` code, before any pair comparison runs. The conflict pass
95
+ compares node pairs and adds a reachability walk per overlapping pair, so an
96
+ unbounded plan had an unbounded planning cost; a graph larger than the bound
97
+ now has to be split.
98
+ - `NodeDraft.effects` is now the single derivation point for effect identity.
99
+ `Plan.compile` and `Plan.append` fold the decoded declaration into the hashed
100
+ material, replacing any `material.effects` a caller supplied. Every plan
101
+ re-keys once, and editing `reads`, `writes`, `removes`, or `boundaryMode`
102
+ now re-keys the node and is attributed by `PlanDiff` as `effects`. Before
103
+ this, the two channels were independent: a draft could change what a node
104
+ writes without moving its key, and `PlanDiff` reported it `unchanged`.
105
+ - Compiled material is stored as its deeply frozen JSON mirror. A `Date`, a
106
+ `URL`, or any value with a data-valued callable `toJSON` is stored as the
107
+ value it serializes to, which is what its key already covered. A material
108
+ accessor or a prototype with no JSON representation is now refused with
109
+ `invalid_node` rather than passed through by reference; the message names the
110
+ node and the payload path and never the value.
111
+ - A payload whose `toJSON` returns its own receiver now fails with the
112
+ `cyclic_payload` `GraphBuildError` instead of being silently omitted from the
113
+ clone. The clone and the input now agree: canonical serialization refused
114
+ that shape all along, while the clone quietly keyed as `{}`.
115
+
116
+ ### Fixed
117
+
118
+ - The migration steps under `internal/migrations` are named exports, and
119
+ `Migrations` imports them by name. The CommonJS build compiled each
120
+ `export default` step to `exports.default`, and Node's default-import interop
121
+ then handed the importer the whole exports object instead of the Effect, so
122
+ `require("@smthrs/plan/Migrations")` produced a set whose entries had no
123
+ `pipe`.
124
+ - Plan payload capture now refuses accessors and unsupported prototypes with
125
+ `invalid_payload` instead of executing getters or collapsing `Map`, `Set`,
126
+ `RegExp`, and class instances onto the same `{}` identity.
127
+ - `StepKey.content` and `dispatchIdentity` now share one environment validator;
128
+ undeclared environments require a non-empty run scope on both paths.
129
+
130
+ - `PlanStore.append` now advances the stored plan row with a compare-and-swap
131
+ on the previous generation, so appending a skipped generation is refused
132
+ instead of persisting node rows whose dependencies are missing and which the
133
+ append-only triggers then forbid removing. Node ordinals are derived from the
134
+ store rather than from the caller's array, `PlanStore.record` refuses
135
+ anything that is not generation 0, and an append with no new nodes is
136
+ refused rather than advancing the generation for nothing.
137
+ - A node payload no longer keeps live functions, and different `Date`, `URL`
138
+ or other `toJSON` payloads no longer share one step key.
139
+ - `StepKey.fromKeyMaterial` resolves dependency digests as own properties, so a
140
+ dependency named `toString` or `constructor` is a `missing_dependency`
141
+ refusal rather than a colliding key.
142
+ - A `StepKey.DigestMemo` waiter no longer inherits the leader's interruption;
143
+ it recomputes its own digest instead.
144
+ - `StepKey.project` resolves only own data properties, so a projection path
145
+ through `toString`, `constructor` or `__proto__` yields `undefined` as
146
+ documented and no getter runs during key derivation.
147
+ - The conflict pass now decides orderedness against the live edge set, so a
148
+ writer pair a dependency path already orders is neither annotated, nor given
149
+ a redundant edge, nor refused with `overlap_forbidden`.
150
+ - `Plan.compile` now fails with `cycle` when a reader-after-writer edge would
151
+ close a cycle, because a declared dependency or a `serialize` edge already
152
+ orders the producer behind its reader. The edge was dropped before, which
153
+ left the reader ahead of its producer to measure and cache pre-producer
154
+ bytes as a legitimate execution. The message names the reader, the producer,
155
+ the overlapping paths, and the dependency chain.
156
+ - `Plan.compile` no longer recurses per dependency edge and no longer
157
+ materializes a transitive closure, so a large graph compiles in bounded
158
+ memory instead of raising an untyped `RangeError`.
159
+ - `Plan.compile` and `Plan.append` validate drafts before keying, with the new
160
+ `invalid_node` code, so an empty plan id, an unsafe-integer priority, or a
161
+ key material version this release does not know is refused at the plan rather
162
+ than at the store.
163
+ - `PlanDiff` attributes a key move caused by `nondeterministic` or `placement`,
164
+ and `Node.capture` refuses a capture nested past its documented limit with a
165
+ path-bearing error rather than overflowing the stack.
166
+ - `Plan.compile` and `Plan.append` decode `NodeDraft.effects` through
167
+ `NodeEffects` before keying, so a malformed declaration is a typed
168
+ `invalid_node` naming the node and the failing path instead of an untyped
169
+ defect. A filesystem declaration containing C0 controls or DEL, or one that is
170
+ not workspace-relative, is refused there rather than reaching a host
171
+ filesystem, and path comparison is Unicode NFC normalized, so two spellings of
172
+ one name are detected as an overlap. `invalid_effects` is now reserved for the
173
+ cross-field rule that one path cannot be both a write and a removal.
174
+ - `Planned.reference` validates the reference shape, so a forged carrier can no
175
+ longer inject a fabricated projection path into a payload.
176
+ - Compiled and appended plans now keep the exact material and filesystem
177
+ authority that their keys and approval digest cover, even if caller-owned
178
+ draft objects are later mutated.
179
+ - Persisted plans can no longer be deleted or have their plan id, flow, or
180
+ creation time rewritten, and node order is now unique and deterministic within
181
+ each plan. Migration `4003` adds the plan-id pin the forward-only trigger was
182
+ missing, which a rename could otherwise use to strand a plan's immortal node
183
+ rows under an id nothing can record again.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Cory and the Smithers Flows contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,229 @@
1
- # Temporary Holding Version
1
+ # @smthrs/plan
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Release candidate scope, host requirements and compatibility review are defined in the [library support policy](https://github.com/smithersai/smithers/blob/main/RELEASE_SUPPORT.md).
4
+
5
+ This package declares `effect` as an exact
6
+ `4.0.0-rc.115` peer dependency. Keep the application on that version so
7
+ all Smithers packages share one Effect runtime.
8
+
9
+ **Documentation:** https://plan.smithers.sh
10
+
11
+ The persisted plan: a keyed action graph, its append-only store, its diff, and
12
+ the step-key compiler that gives every node its identity.
13
+
14
+ `@smthrs/plan` turns a description of work into one durable value. It compiles
15
+ declared steps into a graph in which every node carries a content key derived
16
+ from what that node declares, records that graph in a SQLite store that only
17
+ ever grows, and compares two graphs to say exactly what changed. It runs
18
+ nothing itself: it performs no I/O beyond the database, and driving a compiled
19
+ plan is a scheduler's job, such as
20
+ [`@smthrs/engine-store`](https://engine-store.smithers.sh)'s `PlanScheduler`.
21
+
22
+ Reach for it when you are building a scheduler, a cache, or an approval flow
23
+ over declared work and you need step identity, declared file effects, and an
24
+ auditable history as values you can hold, hash, and store.
25
+
26
+ ## Install
27
+
28
+ Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
29
+
30
+ Compiling asks Effect for its `Crypto` service, which a platform package
31
+ supplies:
32
+
33
+ ```bash
34
+ pnpm add @effect/platform-node@4.0.0-rc.115 @effect/platform-node-shared@4.0.0-rc.115
35
+ ```
36
+
37
+ Node.js 26.4.0 or later, and `effect` 4.0.0-rc.115 as a peer. Recording a plan
38
+ is [`@smthrs/plan-store`](https://plan-store.smithers.sh), which owns the SQL
39
+ and brings [`@smthrs/database`](https://database.smithers.sh) with it.
40
+
41
+ ## Compile a plan
42
+
43
+ Two steps, where the second consumes the first's result and reads the file the
44
+ first writes:
45
+
46
+ ```ts
47
+ import * as NodeCrypto from "@effect/platform-node/NodeCrypto"
48
+ import * as KeyMaterial from "@smthrs/plan/KeyMaterial"
49
+ import * as Plan from "@smthrs/plan/Plan"
50
+ import * as Effect from "effect/Effect"
51
+
52
+ /** Everything hashed about one node. `sealed` is work whose result may be reused across runs. */
53
+ const sealed = (body: unknown, inputs: ReadonlyArray<KeyMaterial.InputRef> = []): KeyMaterial.KeyMaterial => ({
54
+ version: KeyMaterial.version,
55
+ kind: "sealed",
56
+ body,
57
+ inputs,
58
+ layers: [],
59
+ capabilities: []
60
+ })
61
+
62
+ const drafts: ReadonlyArray<Plan.NodeDraft> = [
63
+ {
64
+ id: "run-tests",
65
+ material: sealed({ action: "run-tests" }, [{ _tag: "Ref", from: "read-pr", path: [] }]),
66
+ effects: { reads: ["pr.json"], writes: ["report.json"], boundaryMode: "hard" }
67
+ },
68
+ {
69
+ id: "read-pr",
70
+ material: sealed({ action: "read-pr", pr: 4821 }),
71
+ effects: { reads: [], writes: ["pr.json"], boundaryMode: "hard" }
72
+ }
73
+ ]
74
+
75
+ const plan = await Effect.runPromise(
76
+ Plan.compile({ planId: "review-4821", flow: "example/Review", nodes: drafts }).pipe(
77
+ Effect.provide(NodeCrypto.layer),
78
+ Effect.orDie
79
+ )
80
+ )
81
+
82
+ console.log(plan.digest)
83
+ for (const node of plan.nodes) console.log(node.id, node.key, node.dependsOn)
84
+ ```
85
+
86
+ The digests on your machine match these, because a plan is a pure function of
87
+ its declarations. Compiling reads no file, no clock, and no network:
88
+
89
+ ```text
90
+ key1_61edccc875ff69b798e0c3874d167f65ff0a250b9fe67d27558b854fa2832302
91
+ read-pr key1_6353326ab2a03804d6acfe916debc4f04f09f9deb8f0e93c1ebea3cdd983db18 []
92
+ run-tests key1_e58229e3be2f6abf6f818baf8577fe7045edba6f5e88097b633898478da93985 [ 'read-pr' ]
93
+ ```
94
+
95
+ The drafts went in out of order and came back topologically sorted.
96
+ `run-tests` names `read-pr` once, in its key material, and that single
97
+ declaration became both the hashed dependency and the graph edge, so an edge
98
+ and a key can never disagree about what a node consumes. Change `pr: 4821` to
99
+ another number and both keys move; rename `read-pr` to `fetch-pr` and neither
100
+ does.
101
+
102
+ Recording that plan is
103
+ [`@smthrs/plan-store`](https://plan-store.smithers.sh), whose
104
+ [quickstart](https://plan-store.smithers.sh/quickstart/) builds the whole
105
+ composition against an in-memory database.
106
+
107
+ ## What is in here
108
+
109
+ | Module | Role |
110
+ | ----------------- | --------------------------------------------------------------------------------------------------- |
111
+ | `Node` | The pure, pipeable authoring AST: `succeed`, `all`, `map`, `andThen`, `branch`, `catch`, `priority` |
112
+ | `Planned` | The strict placeholder a body sees where a step result will be, and the reference it records |
113
+ | `GraphBuildError` | The refusals a plan-time build raises instead of producing a wrong plan |
114
+ | `FileSet` | The static filesystem declaration vocabulary: patterns, globs, tree artifacts, filegroups, overlap |
115
+ | `KeyMaterial` | What a planner declares about a node: body, tagged input references, layers, capabilities, effects |
116
+ | `StepKey` | The compiler that turns material plus resolved dependency digests into a `@smthrs/keys` `Key` |
117
+ | `Plan` | `compile`, `append`, the node/edge/conflict schemas, and the digest an approval binds to |
118
+ | `PlanDiff` | A comparison of two plans as a value: added, removed, re-keyed with attribution, unchanged |
119
+ | `Placement` | Where a node runs: the four directives, their host-selection detail, and the annotation key |
120
+
121
+ Every module is its own entry point, so
122
+ `import * as Plan from "@smthrs/plan/Plan"` keeps a bundle to what you reach
123
+ for. The root entry re-exports all of them as namespaces.
124
+
125
+ ## The four rules this package exists to keep
126
+
127
+ **Planning demands nothing.** A `Node` carries Effect's requirement channel,
128
+ `R`, and carries it as a phantom: no combinator here reads it, and the AST, the
129
+ graph, the key material, and every digest are identical whatever it says.
130
+ Building a plan therefore asks for no service at all. What fills the channel is
131
+ a call to something whose code lives elsewhere, an action, so a plan's type
132
+ states which implementations running it will need, and the place that runs it
133
+ is where the compiler asks for them. Each combinator unions its parts: `all`
134
+ over its members, `map` and `andThen` along the chain, `branch` over BOTH arms
135
+ and `catch` over its failure arm, because both arms of a decision are topology
136
+ the plan carries and a run has to be able to take either.
137
+
138
+ **Planning performs no I/O.** Nothing here reads a file, a clock, or a network.
139
+ A node's declared `effects` carry read and write _paths_, never digests, since
140
+ measuring them is the scheduler's run-time job.
141
+
142
+ **Invalidation is re-keying.** A node's key is a function of what it consumes,
143
+ so an edited declaration re-keys that node and its dependent cone and nothing
144
+ else. There is deliberately **no reverse-dependency index and no invalidating
145
+ node visitor**: content addressing subsumes both.
146
+
147
+ **A plan grows; it is never rewritten.** `append` adds a pre-keyed subgraph at
148
+ the next generation. Recorded nodes keep their id, key, edges, and generation
149
+ byte for byte, and the SQL raises rather than letting a caller update or delete
150
+ one. Re-ordering after a reconciliation happens by re-keying _future_ steps.
151
+ Appending to a plan that was never recorded, or over a skipped generation, is a
152
+ `constraint` refusal, because the alternative is node rows for a plan that does
153
+ not exist and that the append-only triggers then forbid removing.
154
+
155
+ ## Conflict annotations
156
+
157
+ Declared write sets make overlap detectable at plan time. `compile` annotates
158
+ both members of every overlapping pair that no dependency path already orders:
159
+ `serialize` is the default and gives the later writer an ordering edge that is
160
+ deliberately not key material, so a serialized node keeps its cache hit; `lane`
161
+ gives both writers lane annotations and no edge; `fail` refuses the compile,
162
+ for flows that promise disjointness. Each annotation also carries the runtime
163
+ strategy, `delay-rebase` or `stop-merge`, and both strategies are folded into
164
+ the plan digest a human approves.
165
+
166
+ A node that reads a path another node writes is not a conflict but a missing
167
+ edge: `compile` puts the reader behind its producer by growing `dependsOn`, and
168
+ fails with `cycle` when no edge set can satisfy both that and the declared
169
+ order.
170
+
171
+ [Declared effects and conflicts](https://plan.smithers.sh/concepts/effects-and-conflicts/)
172
+ covers both passes in full.
173
+
174
+ ## When it refuses
175
+
176
+ `Plan.compile` and `Plan.append` fail with a `PlanError` carrying one of seven
177
+ stable codes: `cycle`, `unknown_dependency`, `duplicate_node`,
178
+ `overlap_forbidden`, `invalid_effects`, `invalid_node`, and `graph_too_large`.
179
+ `Plan.verify`, which every store read and write runs, adds an eighth,
180
+ `invalid_plan`: the plan decoded but its generations, approval digest, keys,
181
+ effects, or ordering do not match its content. A plan-time build refuses
182
+ through `GraphBuildError`, whose closed code set names the site and the fix;
183
+ `unstable_callback` in that set is raised by `@smthrs/flow` when a stable build
184
+ meets a callback without a `Node.capture` declaration. Persistence has its own
185
+ refusals, all of them
186
+ [`@smthrs/plan-store`](https://plan-store.smithers.sh)'s.
187
+
188
+ [Troubleshooting](https://plan.smithers.sh/troubleshooting/) lists every code
189
+ with the change that clears it.
190
+
191
+ ## Paths, payloads, and immutability
192
+
193
+ Declared paths are workspace-relative and compared in one spelling:
194
+ `FileSet.canonical` rewrites every separator to `/` and normalizes to Unicode
195
+ NFC, and `FileSet.workspaceRelative` refuses absolute paths, drive letters,
196
+ `..` and `.` segments, empty segments, the C0 control range, and DEL.
197
+
198
+ A node payload is stored as its inert JSON mirror, so a data-valued `toJSON` is
199
+ honoured while an accessor or an unsupported prototype fails with
200
+ `invalid_payload` rather than executing author code or collapsing distinct
201
+ values onto `{}`. That mirror is identity material: it is hashed into the step
202
+ key and persisted verbatim as plaintext in `node_json` and in the approval card,
203
+ and this package never redacts it. Keep credentials out of payloads. A secret
204
+ belongs in a layer, a capability, or the environment, resolved at dispatch. `Node.succeed(input)` returns `Node<Succeed<typeof input>>`:
205
+ the success type describes that projection. Dates become `string | null`, URLs
206
+ become strings, and callable object members disappear. Planned references
207
+ resolve to their result types. Reconstruct domain objects explicitly in
208
+ `Node.map` when needed. A compiled plan is deep-frozen: mutating the draft objects
209
+ you passed in cannot change the plan, its keys, or its digest.
210
+
211
+ `Plan.maximumPlanNodes` bounds compilation. The conflict and
212
+ reader-after-writer passes compare node pairs, so a plan above that bound is
213
+ refused with `graph_too_large` before any pair is compared.
214
+
215
+ ## Browser support
216
+
217
+ Browser-safe: the package resolves no `node:` built-in and binds no database,
218
+ so compiling, diffing, and building node graphs all work in a browser.
219
+ Persistence is a separate package, which is where a platform choice enters.
220
+
221
+ ## Links
222
+
223
+ - [Documentation](https://plan.smithers.sh)
224
+ - [API reference](https://plan.smithers.sh/reference/api/)
225
+ - [`@smthrs/plan-store`](https://plan-store.smithers.sh), the append-only store
226
+ a compiled plan is recorded in
227
+ - [`@smthrs/flows`](https://flows.smithers.sh), the whole engine as a single
228
+ dependency, which re-exports this package as its `Plan` namespace
229
+ - [License: MIT](./LICENSE)
@@ -0,0 +1,131 @@
1
+ /**
2
+ * The caller-declared cache policy: how far a recorded sealed result may
3
+ * travel, and how long it stays servable.
4
+ *
5
+ * This is the ONE cache-policy model. `@smthrs/flow` publishes it as
6
+ * `CacheEnvironment.CachePolicy` and the engine reads it off a dispatched
7
+ * action; `@smthrs/patterns` declares it over a flow as `WithCache.Policy`. It
8
+ * used to be two declarations in those two packages, tied together by nothing
9
+ * but a re-typed identifier string, so a field added on one side was invisible
10
+ * to the other. It lives here because `@smthrs/plan` is the package both
11
+ * already depend on.
12
+ *
13
+ * The annotation key still reads `"@smthrs/flow/Action/CachePolicy"`.
14
+ * `@smthrs/engine-store`'s `ActionPersistence` reads the policy off stored and
15
+ * dispatched actions by that identifier, so retagging it would make every
16
+ * policy already written invisible at dispatch. The identifier is a wire
17
+ * value, not a location.
18
+ *
19
+ * @since 1.0.0-rc.0
20
+ */
21
+ import * as Context from "effect/Context";
22
+ import * as Schema from "effect/Schema";
23
+ /**
24
+ * How far a recorded sealed result may travel.
25
+ *
26
+ * `shared` is the content-addressed default: the key names the inputs and the
27
+ * environment, so any run on any host that would have produced the same bytes
28
+ * may reuse the row. `run` and `flow` narrow that on purpose — a step whose
29
+ * result is only meaningful inside one execution, or inside one flow, folds
30
+ * that identity into its key so a sibling never reads it.
31
+ *
32
+ * The old scope vocabulary was `run | workflow | global`; `flow` and `shared`
33
+ * are the same three levels named after the current concepts.
34
+ *
35
+ * @category models
36
+ * @since 1.0.0-rc.0
37
+ */
38
+ export declare const CacheScope: Schema.Literals<readonly ["run", "flow", "shared"]>;
39
+ /**
40
+ * The value form of {@link CacheScope}.
41
+ *
42
+ * @category models
43
+ * @since 1.0.0-rc.0
44
+ */
45
+ export type CacheScope = typeof CacheScope.Type;
46
+ /**
47
+ * The caller's declaration about the decay and the reach of a sealed result.
48
+ *
49
+ * `ttlMs` bounds the age of a row the engine may serve: past it the dispatch
50
+ * executes again and journals `cache-expired`, so the refusal is durable
51
+ * evidence a replay reads rather than a fresh clock reading. `scope` decides
52
+ * what the key names besides the inputs.
53
+ *
54
+ * Both fields are optional and both defaults are the pre-policy behavior: no
55
+ * age bound, and the reach the composition's cache environment already
56
+ * granted.
57
+ *
58
+ * @category schemas
59
+ * @since 1.0.0-rc.0
60
+ */
61
+ export declare const CachePolicy: Schema.Struct<{
62
+ readonly ttlMs: Schema.optionalKey<Schema.Int>;
63
+ readonly scope: Schema.optionalKey<Schema.Literals<readonly ["run", "flow", "shared"]>>;
64
+ }>;
65
+ /**
66
+ * The value form of {@link CachePolicy}.
67
+ *
68
+ * @category models
69
+ * @since 1.0.0-rc.0
70
+ */
71
+ export type CachePolicy = typeof CachePolicy.Type;
72
+ /**
73
+ * Annotation key carrying a declaration's {@link CachePolicy}.
74
+ *
75
+ * It is an annotation, not a field on the declaration, for the same reason
76
+ * placement and effects are: the policy is data that travels with the
77
+ * declaration and is read by whoever executes it, and adding it changes no
78
+ * existing call site.
79
+ *
80
+ * @category annotations
81
+ * @since 1.0.0-rc.0
82
+ */
83
+ export declare const CachePolicyAnnotation: Context.Service<{
84
+ readonly ttlMs?: number;
85
+ readonly scope?: "run" | "flow" | "shared";
86
+ }, {
87
+ readonly ttlMs?: number;
88
+ readonly scope?: "run" | "flow" | "shared";
89
+ }>;
90
+ /**
91
+ * Reads the cache policy an annotation bag carries, or `undefined` when it
92
+ * carries none.
93
+ *
94
+ * @category getters
95
+ * @since 1.0.0-rc.0
96
+ */
97
+ export declare const cachePolicyOf: (annotations: Context.Context<never>) => CachePolicy | undefined;
98
+ /**
99
+ * Attaches a cache policy to a declaration that can carry annotations.
100
+ *
101
+ * The declaration is not mutated: `annotate` answers a new one carrying the
102
+ * policy, which is what a plan captures. It is written against the `annotate`
103
+ * method rather than against an action or a flow type so the one declaration
104
+ * serves both, which is what `@smthrs/flow` publishes as
105
+ * `CacheEnvironment.withCache`.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * import { CachePolicy } from "@smthrs/plan"
110
+ * import { Action } from "@smthrs/flow"
111
+ * import * as Effect from "effect/Effect"
112
+ * import * as Schema from "effect/Schema"
113
+ *
114
+ * const compile = CachePolicy.annotate(
115
+ * Action.make({
116
+ * name: "build/compile",
117
+ * success: Schema.String,
118
+ * tier: "sealed",
119
+ * execute: Effect.succeed("dist/server.js")
120
+ * }),
121
+ * { ttlMs: 60_000, scope: "shared" }
122
+ * )
123
+ * ```
124
+ *
125
+ * @category combinators
126
+ * @since 1.0.0-rc.0
127
+ */
128
+ export declare const annotate: <A extends {
129
+ annotate: (key: typeof CachePolicyAnnotation, value: CachePolicy) => A;
130
+ }>(declaration: A, policy: CachePolicy) => A;
131
+ //# sourceMappingURL=CachePolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CachePolicy.d.ts","sourceRoot":"","sources":["../../src/CachePolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,qDAA6C,CAAA;AAEpE;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA;AAU/C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW;;;EAGtB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;AAEjD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB;;;;;;EAAkE,CAAA;AAEpG;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAG,WAAW,GAAG,SACJ,CAAA;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS;IAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,qBAAqB,EAAE,KAAK,EAAE,WAAW,KAAK,CAAC,CAAA;CAAE,EAC3G,aAAa,CAAC,EACd,QAAQ,WAAW,KAClB,CAAwD,CAAA"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var CachePolicy_exports = {};
30
+ __export(CachePolicy_exports, {
31
+ CachePolicy: () => CachePolicy,
32
+ CachePolicyAnnotation: () => CachePolicyAnnotation,
33
+ CacheScope: () => CacheScope,
34
+ annotate: () => annotate,
35
+ cachePolicyOf: () => cachePolicyOf
36
+ });
37
+ module.exports = __toCommonJS(CachePolicy_exports);
38
+ var Context = __toESM(require("effect/Context"), 1);
39
+ var Option = __toESM(require("effect/Option"), 1);
40
+ var Schema = __toESM(require("effect/Schema"), 1);
41
+ const CacheScope = Schema.Literals(["run", "flow", "shared"]);
42
+ const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
43
+ const CachePolicy = Schema.Struct({
44
+ ttlMs: Schema.optionalKey(PositiveMillis),
45
+ scope: Schema.optionalKey(CacheScope)
46
+ });
47
+ const CachePolicyAnnotation = Context.Service("@smthrs/flow/Action/CachePolicy");
48
+ const cachePolicyOf = (annotations) => Option.getOrUndefined(Context.getOption(annotations, CachePolicyAnnotation));
49
+ const annotate = (declaration, policy) => declaration.annotate(CachePolicyAnnotation, policy);
50
+ //# sourceMappingURL=CachePolicy.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/CachePolicy.ts"],
4
+ "sourcesContent": ["/**\n * The caller-declared cache policy: how far a recorded sealed result may\n * travel, and how long it stays servable.\n *\n * This is the ONE cache-policy model. `@smthrs/flow` publishes it as\n * `CacheEnvironment.CachePolicy` and the engine reads it off a dispatched\n * action; `@smthrs/patterns` declares it over a flow as `WithCache.Policy`. It\n * used to be two declarations in those two packages, tied together by nothing\n * but a re-typed identifier string, so a field added on one side was invisible\n * to the other. It lives here because `@smthrs/plan` is the package both\n * already depend on.\n *\n * The annotation key still reads `\"@smthrs/flow/Action/CachePolicy\"`.\n * `@smthrs/engine-store`'s `ActionPersistence` reads the policy off stored and\n * dispatched actions by that identifier, so retagging it would make every\n * policy already written invisible at dispatch. The identifier is a wire\n * value, not a location.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Context from \"effect/Context\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\n\n/**\n * How far a recorded sealed result may travel.\n *\n * `shared` is the content-addressed default: the key names the inputs and the\n * environment, so any run on any host that would have produced the same bytes\n * may reuse the row. `run` and `flow` narrow that on purpose \u2014 a step whose\n * result is only meaningful inside one execution, or inside one flow, folds\n * that identity into its key so a sibling never reads it.\n *\n * The old scope vocabulary was `run | workflow | global`; `flow` and `shared`\n * are the same three levels named after the current concepts.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const CacheScope = Schema.Literals([\"run\", \"flow\", \"shared\"])\n\n/**\n * The value form of {@link CacheScope}.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type CacheScope = typeof CacheScope.Type\n\n/**\n * A positive whole number of milliseconds.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nconst PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0))\n\n/**\n * The caller's declaration about the decay and the reach of a sealed result.\n *\n * `ttlMs` bounds the age of a row the engine may serve: past it the dispatch\n * executes again and journals `cache-expired`, so the refusal is durable\n * evidence a replay reads rather than a fresh clock reading. `scope` decides\n * what the key names besides the inputs.\n *\n * Both fields are optional and both defaults are the pre-policy behavior: no\n * age bound, and the reach the composition's cache environment already\n * granted.\n *\n * @category schemas\n * @since 1.0.0-rc.0\n */\nexport const CachePolicy = Schema.Struct({\n ttlMs: Schema.optionalKey(PositiveMillis),\n scope: Schema.optionalKey(CacheScope)\n})\n\n/**\n * The value form of {@link CachePolicy}.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type CachePolicy = typeof CachePolicy.Type\n\n/**\n * Annotation key carrying a declaration's {@link CachePolicy}.\n *\n * It is an annotation, not a field on the declaration, for the same reason\n * placement and effects are: the policy is data that travels with the\n * declaration and is read by whoever executes it, and adding it changes no\n * existing call site.\n *\n * @category annotations\n * @since 1.0.0-rc.0\n */\nexport const CachePolicyAnnotation = Context.Service<CachePolicy>(\"@smthrs/flow/Action/CachePolicy\")\n\n/**\n * Reads the cache policy an annotation bag carries, or `undefined` when it\n * carries none.\n *\n * @category getters\n * @since 1.0.0-rc.0\n */\nexport const cachePolicyOf = (annotations: Context.Context<never>): CachePolicy | undefined =>\n Option.getOrUndefined(Context.getOption(annotations, CachePolicyAnnotation))\n\n/**\n * Attaches a cache policy to a declaration that can carry annotations.\n *\n * The declaration is not mutated: `annotate` answers a new one carrying the\n * policy, which is what a plan captures. It is written against the `annotate`\n * method rather than against an action or a flow type so the one declaration\n * serves both, which is what `@smthrs/flow` publishes as\n * `CacheEnvironment.withCache`.\n *\n * @example\n * ```ts\n * import { CachePolicy } from \"@smthrs/plan\"\n * import { Action } from \"@smthrs/flow\"\n * import * as Effect from \"effect/Effect\"\n * import * as Schema from \"effect/Schema\"\n *\n * const compile = CachePolicy.annotate(\n * Action.make({\n * name: \"build/compile\",\n * success: Schema.String,\n * tier: \"sealed\",\n * execute: Effect.succeed(\"dist/server.js\")\n * }),\n * { ttlMs: 60_000, scope: \"shared\" }\n * )\n * ```\n *\n * @category combinators\n * @since 1.0.0-rc.0\n */\nexport const annotate = <A extends { annotate: (key: typeof CachePolicyAnnotation, value: CachePolicy) => A }>(\n declaration: A,\n policy: CachePolicy\n): A => declaration.annotate(CachePolicyAnnotation, policy)\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBA,cAAyB;AACzB,aAAwB;AACxB,aAAwB;AAiBjB,MAAM,aAAa,OAAO,SAAS,CAAC,OAAO,QAAQ,QAAQ,CAAC;AAgBnE,MAAM,iBAAiB,OAAO,IAAI,MAAM,OAAO,cAAc,CAAC,CAAC;AAiBxD,MAAM,cAAc,OAAO,OAAO;AAAA,EACvC,OAAO,OAAO,YAAY,cAAc;AAAA,EACxC,OAAO,OAAO,YAAY,UAAU;AACtC,CAAC;AAqBM,MAAM,wBAAwB,QAAQ,QAAqB,iCAAiC;AAS5F,MAAM,gBAAgB,CAAC,gBAC5B,OAAO,eAAe,QAAQ,UAAU,aAAa,qBAAqB,CAAC;AAgCtE,MAAM,WAAW,CACtB,aACA,WACM,YAAY,SAAS,uBAAuB,MAAM;",
6
+ "names": []
7
+ }