soustack 0.4.5 → 0.4.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soustack",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "The logic engine for computational recipes - validation, scaling, parsing, and Schema.org conversion",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -1,7 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://soustack.spec/stacks/compute.schema.json",
4
- "title": "Compute Claim Stack",
5
- "type": "object",
6
- "additionalProperties": false
7
- }
@@ -1,45 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://soustack.spec/stacks/dietary.schema.json",
4
- "title": "Dietary Stack",
5
- "type": "object",
6
- "properties": {
7
- "dietary": {
8
- "type": "object",
9
- "properties": {
10
- "basis": { "type": "string", "enum": ["perServing", "perRecipe"] },
11
- "calories": { "type": "number", "minimum": 0 },
12
- "macros": {
13
- "type": "object",
14
- "properties": {
15
- "protein": { "type": "number", "minimum": 0 },
16
- "fat": { "type": "number", "minimum": 0 },
17
- "carbohydrates": { "type": "number", "minimum": 0 },
18
- "metadata": { "type": "object", "additionalProperties": true }
19
- },
20
- "minProperties": 1,
21
- "additionalProperties": false,
22
- "patternProperties": {
23
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
24
- }
25
- },
26
- "diets": { "type": "array", "items": { "type": "string" } },
27
- "allergens": { "type": "array", "items": { "type": "string" } },
28
- "metadata": { "type": "object", "additionalProperties": true }
29
- },
30
- "required": ["basis"],
31
- "additionalProperties": false,
32
- "anyOf": [
33
- { "required": ["calories"] },
34
- { "required": ["macros"] },
35
- { "required": ["diets"] },
36
- { "required": ["allergens"] }
37
- ],
38
- "patternProperties": {
39
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
40
- }
41
- }
42
- },
43
- "required": ["dietary"],
44
- "additionalProperties": false
45
- }
@@ -1,24 +0,0 @@
1
- # dietary@1
2
-
3
- ## Purpose
4
- The `dietary@1` stack enables recipes to declare nutritional information, dietary classifications, and allergen information.
5
-
6
- ## Adds
7
- - Top-level `dietary` object with `basis` field (perServing or perRecipe).
8
- - Optional `calories`, `macros` (protein, fat, carbohydrates), `diets`, and `allergens` fields.
9
- - At least one of calories, macros, diets, or allergens must be present.
10
-
11
- ## Requires
12
- - None
13
-
14
- ## Semantics
15
- - MUST: The `dietary` object must include a `basis` field.
16
- - MUST: At least one of `calories`, `macros`, `diets`, or `allergens` must be present.
17
- - NOTE: Dietary tags and allergen lists are freeform strings in v1 (no controlled vocabulary).
18
-
19
- ## Composition Notes
20
- - This stack is monotonic: it adds requirements or fields without removing expressiveness.
21
- - Interaction: No dependencies on other stacks. Can be combined with any other stack.
22
-
23
-
24
-
@@ -1,24 +0,0 @@
1
- # illustrated@1
2
-
3
- ## Purpose
4
- The `illustrated@1` stack enables recipes to include media (images and videos) at the recipe and step levels for visual guidance.
5
-
6
- ## Adds
7
- - Top-level `images` and `videos` arrays (optional).
8
- - Step objects may include `images` and `videos` arrays.
9
- - Steps are structured objects with `id` fields.
10
-
11
- ## Requires
12
- - None
13
-
14
- ## Semantics
15
- - MUST: At least one media item (image or video) must be present at the recipe level or in at least one step.
16
- - MUST: Step objects must include `id` fields (steps are structured).
17
- - NOTE: Media URIs must be valid according to the URI schema definition.
18
-
19
- ## Composition Notes
20
- - This stack is monotonic: it adds requirements or fields without removing expressiveness.
21
- - Interaction: Works with `structured@1` for step-level media. No dependencies on other stacks.
22
-
23
-
24
-
@@ -1,76 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://soustack.spec/stacks/prep.schema.json",
4
- "title": "Prep Stack",
5
- "type": "object",
6
- "properties": {
7
- "miseEnPlace": {
8
- "type": "array",
9
- "minItems": 1,
10
- "items": {
11
- "$ref": "#/$defs/miseEnPlaceTask"
12
- }
13
- }
14
- },
15
- "required": ["miseEnPlace"],
16
- "$defs": {
17
- "miseEnPlaceTask": {
18
- "type": "object",
19
- "properties": {
20
- "id": {
21
- "type": "string",
22
- "pattern": "^[A-Za-z0-9._-]+$"
23
- },
24
- "text": {
25
- "type": "string",
26
- "minLength": 1
27
- },
28
- "inputs": {
29
- "type": "array",
30
- "items": {
31
- "type": "string"
32
- },
33
- "uniqueItems": true,
34
- "minItems": 1
35
- },
36
- "usesEquipment": {
37
- "type": "array",
38
- "items": {
39
- "type": "string",
40
- "pattern": "^[A-Za-z0-9._-]+$"
41
- },
42
- "uniqueItems": true,
43
- "minItems": 1
44
- }
45
- },
46
- "required": ["text"],
47
- "additionalProperties": false,
48
- "patternProperties": {
49
- "^x-": {
50
- "$ref": "../defs/common.schema.json#/properties/extensionLaneValue"
51
- }
52
- }
53
- },
54
- "prepItem": {
55
- "type": "object",
56
- "properties": {
57
- "verb": {
58
- "type": "string",
59
- "minLength": 1
60
- },
61
- "detail": {
62
- "type": "string"
63
- }
64
- },
65
- "required": ["verb"],
66
- "additionalProperties": false,
67
- "patternProperties": {
68
- "^x-": {
69
- "$ref": "../defs/common.schema.json#/properties/extensionLaneValue"
70
- }
71
- }
72
- }
73
- },
74
- "additionalProperties": false
75
- }
76
-
@@ -1,276 +0,0 @@
1
- #prep@1
2
-
3
- ---
4
-
5
- ## Purpose
6
-
7
- The `prep@1` stack enables recipes to include preparation guidance at both the ingredient level and as explicit mise en place tasks. This makes recipes more operational by providing clear prep instructions and planning checklists.
8
-
9
- This stack is adoption-first: ingredient prep can be simple strings for minimal friction, or structured objects for more detailed guidance.
10
-
11
- ---
12
-
13
- ## Requirements
14
-
15
- A document that declares `prep@1`:
16
-
17
- • MUST satisfy all structural rules enabled by this stack.
18
- • MUST satisfy the semantic rules described below.
19
-
20
- ---
21
-
22
- ## Data Model
23
-
24
- When `prep@1` is declared, the document MUST include a top-level `miseEnPlace` array.
25
-
26
- ### Mise En Place Array
27
-
28
- The `miseEnPlace` array contains one or more tasks. Each task is a structured object that describes a prep step.
29
-
30
- ### Ingredient-Level Prep
31
-
32
- Ingredient objects MAY include an optional `prep` field that describes how the ingredient should be prepared. The `prep` field supports multiple formats:
33
-
34
- • A single string (simple prep phrase)
35
- • An array of strings (multiple prep notes)
36
- • An array of structured prep items
37
-
38
- ---
39
-
40
- ## Ingredient Prep Formats
41
-
42
- ### String Format
43
-
44
- A simple string describing the prep:
45
-
46
- ```json
47
- {
48
- "id": "onion",
49
- "name": "Onion",
50
- "prep": "finely diced"
51
- }
52
- ```
53
-
54
- ### Array of Strings
55
-
56
- Multiple prep notes:
57
-
58
- ```json
59
- {
60
- "id": "garlic",
61
- "name": "Garlic",
62
- "prep": ["peeled", "minced"]
63
- }
64
- ```
65
-
66
- ### Structured Prep Items
67
-
68
- More detailed prep instructions with verbs and details:
69
-
70
- ```json
71
- {
72
- "id": "tomato",
73
- "name": "Tomato",
74
- "prep": [
75
- { "verb": "dice", "detail": "fine" },
76
- { "verb": "reserve", "detail": "half for garnish" }
77
- ]
78
- }
79
- ```
80
-
81
- Each prep item object includes:
82
-
83
- • `verb` (required): string describing the action (freeform; no controlled vocabulary in v1)
84
- • `detail` (optional): string providing additional context
85
-
86
- ---
87
-
88
- ## Mise En Place Tasks
89
-
90
- ### Minimal Task
91
-
92
- A task with only required text:
93
-
94
- ```json
95
- {
96
- "miseEnPlace": [
97
- { "text": "Finely dice the onion" },
98
- { "text": "Mince the garlic" }
99
- ]
100
- }
101
- ```
102
-
103
- ### Task with ID
104
-
105
- Tasks may include an optional `id` for cross-referencing:
106
-
107
- ```json
108
- {
109
- "miseEnPlace": [
110
- { "id": "dice-onion", "text": "Finely dice the onion" },
111
- { "id": "mince-garlic", "text": "Mince the garlic" }
112
- ]
113
- }
114
- ```
115
-
116
- Task IDs MUST be unique within the `miseEnPlace` array (semantic validation).
117
-
118
- ### Task with Ingredient References
119
-
120
- When `referenced@1` is present, tasks may reference ingredient IDs:
121
-
122
- ```json
123
- {
124
- "miseEnPlace": [
125
- {
126
- "text": "Prepare the vegetables",
127
- "inputs": ["onion", "garlic"]
128
- }
129
- ]
130
- }
131
- ```
132
-
133
- Each id in `inputs` MUST exist in the ingredients array (semantic validation when both stacks are present).
134
-
135
- ### Task with Equipment References
136
-
137
- When `equipment@1` is present, tasks may reference equipment IDs:
138
-
139
- ```json
140
- {
141
- "miseEnPlace": [
142
- {
143
- "text": "Sharpen the knife",
144
- "usesEquipment": ["knife"]
145
- }
146
- ]
147
- }
148
- ```
149
-
150
- Each id in `usesEquipment` MUST exist in the equipment array (semantic validation when both stacks are present).
151
-
152
- ### Combined References
153
-
154
- Tasks may include both `inputs` and `usesEquipment`:
155
-
156
- ```json
157
- {
158
- "miseEnPlace": [
159
- {
160
- "text": "Dice the onion with a sharp knife",
161
- "inputs": ["onion"],
162
- "usesEquipment": ["knife"]
163
- }
164
- ]
165
- }
166
- ```
167
-
168
- ---
169
-
170
- ## Semantics
171
-
172
- ### Prep Field
173
-
174
- The ingredient `prep` field is descriptive and freeform. In v1, there is no controlled vocabulary for prep verbs or details. Tools may interpret these as hints for display or planning, but validation does not enforce specific values.
175
-
176
- ### Mise En Place
177
-
178
- The `miseEnPlace` array is an explicit checklist of prep tasks. Tools may render this as a prep plan, separate from the main cooking instructions. Tasks are ordered and may be presented as a sequential checklist.
179
-
180
- ---
181
-
182
- ## Composition
183
-
184
- The prep stack is composable with other stacks:
185
-
186
- • No hard dependency on `referenced@1` or `equipment@1` (references are optional and only validated when those stacks are present)
187
- • Works with `structured@1` to enable ingredient object definitions
188
- • Remains monotonic: does not close objects needed by other stacks
189
- • Ingredient `prep` is optional even when the stack is present
190
-
191
- ---
192
-
193
- ## Semantic Validation Rules (Normative)
194
-
195
- Validators MUST enforce the following rules:
196
-
197
- 1. Mise en place task ID uniqueness
198
- If tasks include `id` values, all task IDs MUST be unique within the `miseEnPlace` array.
199
-
200
- 2. Ingredient reference resolution
201
- If both `prep@1` and `referenced@1` are present, and a task includes `inputs`, all referenced ingredient IDs MUST exist in the ingredients array.
202
-
203
- 3. Equipment reference resolution
204
- If both `prep@1` and `equipment@1` are present, and a task includes `usesEquipment`, all referenced equipment IDs MUST exist in the equipment array (as object ids, not string items).
205
-
206
- ---
207
-
208
- ## Examples
209
-
210
- Minimal mise en place
211
-
212
- ```json
213
- {
214
- "miseEnPlace": [
215
- { "text": "Finely dice the onion" },
216
- { "text": "Mince the garlic" }
217
- ]
218
- }
219
- ```
220
-
221
- Ingredient prep with strings
222
-
223
- ```json
224
- {
225
- "ingredients": [
226
- {
227
- "id": "onion",
228
- "name": "Onion",
229
- "prep": "finely diced"
230
- },
231
- {
232
- "id": "garlic",
233
- "name": "Garlic",
234
- "prep": ["peeled", "minced"]
235
- }
236
- ]
237
- }
238
- ```
239
-
240
- Structured prep items
241
-
242
- ```json
243
- {
244
- "ingredients": [
245
- {
246
- "id": "tomato",
247
- "name": "Tomato",
248
- "prep": [
249
- { "verb": "dice", "detail": "fine" },
250
- { "verb": "reserve", "detail": "half for garnish" }
251
- ]
252
- }
253
- ]
254
- }
255
- ```
256
-
257
- Mise en place with references
258
-
259
- ```json
260
- {
261
- "equipment": [
262
- { "id": "knife", "name": "Chef's knife" }
263
- ],
264
- "ingredients": [
265
- { "id": "onion", "name": "Onion" }
266
- ],
267
- "miseEnPlace": [
268
- {
269
- "text": "Dice the onion with a sharp knife",
270
- "inputs": ["onion"],
271
- "usesEquipment": ["knife"]
272
- }
273
- ]
274
- }
275
- ```
276
-
@@ -1,96 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://soustack.spec/stacks/referenced.schema.json",
4
- "title": "Referenced Stack",
5
- "type": "object",
6
- "properties": {
7
- "ingredients": {
8
- "type": "array",
9
- "items": {
10
- "anyOf": [
11
- { "$ref": "#/$defs/ingredient" },
12
- { "$ref": "#/$defs/ingredientSection" }
13
- ]
14
- }
15
- },
16
- "instructions": {
17
- "type": "array",
18
- "items": {
19
- "anyOf": [
20
- { "$ref": "#/$defs/referencedStep" },
21
- { "$ref": "#/$defs/referencedSection" }
22
- ]
23
- }
24
- }
25
- },
26
- "required": ["ingredients", "instructions"],
27
- "allOf": [
28
- { "$ref": "./structured.schema.json" }
29
- ],
30
- "$defs": {
31
- "ingredient": {
32
- "allOf": [
33
- { "$ref": "../defs/entities.schema.json#/$defs/IngredientBase" },
34
- { "required": ["id"] }
35
- ]
36
- },
37
- "ingredientSection": {
38
- "type": "object",
39
- "properties": {
40
- "section": { "type": "string" },
41
- "ingredients": {
42
- "type": "array",
43
- "items": {
44
- "anyOf": [
45
- { "$ref": "#/$defs/ingredient" },
46
- { "$ref": "#/$defs/ingredientSection" }
47
- ]
48
- }
49
- },
50
- "metadata": { "type": "object", "additionalProperties": true }
51
- },
52
- "required": ["section", "ingredients"],
53
- "additionalProperties": false,
54
- "patternProperties": {
55
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
56
- }
57
- },
58
- "referencedStep": {
59
- "allOf": [
60
- { "$ref": "../defs/entities.schema.json#/$defs/StepBase" },
61
- {
62
- "properties": {
63
- "inputs": {
64
- "allOf": [
65
- { "$ref": "../defs/entities.schema.json#/$defs/StepBase/properties/inputs" },
66
- { "minItems": 1 }
67
- ]
68
- }
69
- },
70
- "required": ["id", "inputs"]
71
- }
72
- ]
73
- },
74
- "referencedSection": {
75
- "type": "object",
76
- "properties": {
77
- "section": { "type": "string" },
78
- "steps": {
79
- "type": "array",
80
- "items": {
81
- "anyOf": [
82
- { "$ref": "#/$defs/referencedStep" },
83
- { "$ref": "#/$defs/referencedSection" }
84
- ]
85
- }
86
- },
87
- "metadata": { "type": "object", "additionalProperties": true }
88
- },
89
- "required": ["section", "steps"],
90
- "additionalProperties": false,
91
- "patternProperties": {
92
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
93
- }
94
- }
95
- }
96
- }
@@ -1,99 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://soustack.spec/stacks/scaling.schema.json",
4
- "title": "Scaling Stack",
5
- "type": "object",
6
- "properties": {
7
- "ingredients": {
8
- "type": "array",
9
- "items": {
10
- "anyOf": [
11
- { "$ref": "#/$defs/ingredient" },
12
- { "$ref": "#/$defs/ingredientSection" }
13
- ]
14
- }
15
- },
16
- "scaling": {
17
- "type": "object",
18
- "properties": {
19
- "discrete": {
20
- "type": "object",
21
- "properties": {
22
- "min": { "type": "integer", "minimum": 1 },
23
- "max": { "type": "integer", "minimum": 1 },
24
- "step": { "type": "integer", "minimum": 1 },
25
- "metadata": { "type": "object", "additionalProperties": true }
26
- },
27
- "required": ["min", "max"],
28
- "additionalProperties": false,
29
- "patternProperties": {
30
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
31
- }
32
- },
33
- "metadata": { "type": "object", "additionalProperties": true }
34
- },
35
- "required": ["discrete"],
36
- "additionalProperties": false,
37
- "patternProperties": {
38
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
39
- }
40
- }
41
- },
42
- "required": ["ingredients", "scaling"],
43
- "$defs": {
44
- "ingredient": {
45
- "type": "object",
46
- "properties": {
47
- "id": { "type": "string" },
48
- "name": { "type": "string" },
49
- "quantity": { "$ref": "../defs/quantity.schema.json" },
50
- "temperature": { "$ref": "../defs/temperature.schema.json" },
51
- "notes": { "type": "string" },
52
- "prep": {
53
- "oneOf": [
54
- { "type": "string", "minLength": 1 },
55
- {
56
- "type": "array",
57
- "minItems": 1,
58
- "items": {
59
- "anyOf": [
60
- { "type": "string", "minLength": 1 },
61
- { "$ref": "../stacks/prep.schema.json#/$defs/prepItem" }
62
- ]
63
- }
64
- }
65
- ]
66
- },
67
- "metadata": { "type": "object", "additionalProperties": true },
68
- "scaling": { "$ref": "../defs/scalingRule.schema.json" }
69
- },
70
- "required": ["id", "name", "quantity"],
71
- "additionalProperties": false,
72
- "patternProperties": {
73
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
74
- }
75
- },
76
- "ingredientSection": {
77
- "type": "object",
78
- "properties": {
79
- "section": { "type": "string" },
80
- "ingredients": {
81
- "type": "array",
82
- "items": {
83
- "anyOf": [
84
- { "$ref": "#/$defs/ingredient" },
85
- { "$ref": "#/$defs/ingredientSection" }
86
- ]
87
- }
88
- },
89
- "metadata": { "type": "object", "additionalProperties": true }
90
- },
91
- "required": ["section", "ingredients"],
92
- "additionalProperties": false,
93
- "patternProperties": {
94
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
95
- }
96
- }
97
- },
98
- "additionalProperties": false
99
- }
@@ -1,238 +0,0 @@
1
- #scaling@1
2
-
3
- ---
4
-
5
- ## Purpose
6
-
7
- The `scaling@1` stack standardizes how ingredient quantities change when a recipe is scaled, so independent implementations produce consistent results.
8
-
9
- This stack is intended to support the **Scalable** profile.
10
-
11
- ---
12
-
13
- ## Requirements
14
-
15
- A document that declares `scaling@1`:
16
-
17
- • MUST declare `quantified@1`.
18
- • SHOULD declare `structured@1` (recommended for stable ingredient IDs).
19
- • MUST satisfy all structural rules enabled by this stack.
20
- • MUST satisfy the semantic rules described below.
21
-
22
- ---
23
-
24
- ## Data Model
25
-
26
- When `scaling@1` is declared, quantified ingredients MAY include an optional `scaling` field.
27
-
28
- Field
29
- ingredient.scaling : ScalingRule
30
-
31
- ScalingRule is defined in `defs/scalingRule.schema.json` and is a closed object with a required `mode`.
32
-
33
- ---
34
-
35
- ## Scaling Modes
36
-
37
- ### linear
38
-
39
- Multiply the ingredient quantity by the scale factor.
40
-
41
- mode = "linear"
42
-
43
- If `scaling` is omitted, implementations MUST treat the ingredient as linear by default.
44
-
45
- ---
46
-
47
- ### fixed
48
-
49
- Do not scale this ingredient quantity.
50
-
51
- Examples:
52
- • 1 bay leaf
53
- • 1 cinnamon stick
54
- • 1 pan
55
-
56
- mode = "fixed"
57
-
58
- ---
59
-
60
- ### discrete
61
-
62
- Scale using discrete increments (for example, whole eggs), with optional rounding and bounds.
63
-
64
- Fields
65
- • mode = "discrete"
66
- • step (optional, > 0): discrete increment size
67
- • rounding (optional): nearest | ceil | floor
68
- • min (optional): minimum allowed result
69
- • max (optional): maximum allowed result
70
-
71
- Semantic defaults
72
- • If step is not present, default is 1
73
- • If rounding is not present, default is nearest
74
-
75
- ---
76
-
77
- ### toTaste
78
-
79
- Quantity is informational and MUST NOT be mechanically scaled.
80
-
81
- Examples:
82
- • salt to taste
83
- • pepper to taste
84
-
85
- mode = "toTaste"
86
-
87
- ---
88
-
89
- ### bakersPercent
90
-
91
- Represents the ingredient quantity as a baker’s percentage of a base ingredient (typically flour).
92
-
93
- Fields
94
- • mode = "bakersPercent"
95
- • percent (> 0): percentage value (for example, 70 for 70%)
96
- • of (string): ingredient id of the base ingredient
97
-
98
- ---
99
-
100
- ## Scale Factor
101
-
102
- Scaling is applied using a scale factor F.
103
-
104
- How F is selected depends on the consumer implementation:
105
-
106
- • Some consumers may accept an explicit factor (for example, “2x”).
107
- • Some consumers may compute F from yield changes (for example, “scale from 4 servings to 6 servings”).
108
-
109
- This stack standardizes how ingredient rules respond to F, not how F is chosen.
110
-
111
- ---
112
-
113
- ## Semantic Validation Rules (Normative)
114
-
115
- Validators MUST enforce the following rules:
116
-
117
- 1. Prerequisite
118
- If `scaling@1` is declared, `quantified@1` MUST also be declared.
119
-
120
- 2. Baker’s percent reference
121
- For mode = "bakersPercent", the `of` field MUST resolve to an existing ingredient id.
122
-
123
- 3. Discrete sanity
124
- For mode = "discrete", if both `min` and `max` are present, min MUST be less than or equal to max.
125
-
126
- 4. Closed rule objects
127
- ScalingRule MUST NOT contain unspecified fields. This is enforced by schema.
128
-
129
- Consumers MUST apply the following semantic defaults:
130
-
131
- • Missing `scaling` ⇒ treat as linear
132
- • discrete.step default = 1
133
- • discrete.rounding default = nearest
134
-
135
- ---
136
-
137
- ## Scaling Behavior (Normative)
138
-
139
- Given an ingredient with base quantity amount A and scale factor F:
140
-
141
- • linear
142
- scaled amount = A × F
143
-
144
- • fixed
145
- scaled amount = A
146
-
147
- • toTaste
148
- scaled amount = A (informational only)
149
- Consumers MAY hide numeric scaling UI for this item.
150
-
151
- • discrete
152
-
153
- 1. raw = A × F
154
- 2. if step is present, units = raw ÷ step
155
- 3. apply rounding to units
156
- 4. scaled = units × step
157
- 5. clamp to min / max if present
158
-
159
- • bakersPercent
160
-
161
- 1. let B be the amount of the ingredient referenced by `of`
162
- 2. scaled amount = B × (percent ÷ 100)
163
-
164
- ---
165
-
166
- ## Examples
167
-
168
- Linear (default)
169
-
170
- Ingredient
171
- id: i_sugar
172
- name: Sugar
173
- quantity: 100 g
174
- (no scaling field → treated as linear)
175
-
176
- ---
177
-
178
- Fixed
179
-
180
- Ingredient
181
- id: i_bayleaf
182
- name: Bay leaf
183
- quantity: 1 leaf
184
- scaling: mode = fixed
185
-
186
- ---
187
-
188
- Discrete (eggs)
189
-
190
- Ingredient
191
- id: i_eggs
192
- name: Eggs
193
- quantity: 2 egg
194
- scaling: mode = discrete, rounding = ceil
195
-
196
- ---
197
-
198
- To taste
199
-
200
- Ingredient
201
- id: i_salt
202
- name: Salt
203
- quantity: 1 tsp
204
- scaling: mode = toTaste
205
-
206
- ---
207
-
208
- Baker’s percent
209
-
210
- Ingredient A
211
- id: i_flour
212
- name: Flour
213
- quantity: 500 g
214
-
215
- Ingredient B
216
- id: i_water
217
- name: Water
218
- quantity: 350 g
219
- scaling: mode = bakersPercent, percent = 70, of = i_flour
220
-
221
- ---
222
-
223
- ## Fixtures
224
-
225
- Implementations SHOULD include fixtures covering:
226
-
227
- Valid cases
228
- • linear default (no scaling rule)
229
- • discrete with rounding
230
- • fixed
231
- • toTaste
232
- • bakersPercent with resolvable `of`
233
-
234
- Invalid cases
235
- • scaling declared without quantified
236
- • bakersPercent with missing or invalid `of`
237
- • invalid rounding value
238
- • discrete with min greater than max
@@ -1,132 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://soustack.spec/stacks/storage.schema.json",
4
- "title": "Storage Stack",
5
- "type": "object",
6
- "properties": {
7
- "storage": {
8
- "type": "object",
9
- "properties": {
10
- "roomTemp": { "$ref": "#/$defs/storageMethod" },
11
- "refrigerated": { "$ref": "#/$defs/storageMethod" },
12
- "frozen": { "$ref": "#/$defs/storageMethod" },
13
- "leftovers": { "$ref": "#/$defs/leftovers" },
14
- "metadata": { "type": "object", "additionalProperties": true }
15
- },
16
- "anyOf": [
17
- { "required": ["roomTemp"] },
18
- { "required": ["refrigerated"] },
19
- { "required": ["frozen"] }
20
- ],
21
- "additionalProperties": false,
22
- "patternProperties": {
23
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
24
- }
25
- }
26
- },
27
- "required": ["storage"],
28
- "$defs": {
29
- "storageMethod": {
30
- "type": "object",
31
- "properties": {
32
- "duration": { "$ref": "../defs/duration.schema.json#/properties/StorageDuration" },
33
- "notes": { "type": "string" },
34
- "metadata": { "type": "object", "additionalProperties": true }
35
- },
36
- "required": ["duration"],
37
- "additionalProperties": false,
38
- "patternProperties": {
39
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
40
- }
41
- },
42
- "reheatDuration": {
43
- "type": "object",
44
- "properties": {
45
- "minMinutes": { "type": "integer", "minimum": 0 },
46
- "maxMinutes": { "type": "integer", "minimum": 0 }
47
- },
48
- "anyOf": [
49
- { "required": ["minMinutes"] },
50
- { "required": ["maxMinutes"] }
51
- ],
52
- "additionalProperties": false,
53
- "patternProperties": {
54
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
55
- }
56
- },
57
- "reheatInstruction": {
58
- "type": "object",
59
- "properties": {
60
- "method": { "type": "string", "minLength": 1 },
61
- "temp": {
62
- "type": "object",
63
- "properties": {
64
- "value": { "type": "number" },
65
- "unit": { "type": "string", "enum": ["F", "C"] }
66
- },
67
- "required": ["value", "unit"],
68
- "additionalProperties": false,
69
- "patternProperties": {
70
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
71
- }
72
- },
73
- "duration": { "$ref": "#/$defs/reheatDuration" },
74
- "notes": { "type": "string" }
75
- },
76
- "required": ["method"],
77
- "additionalProperties": false,
78
- "patternProperties": {
79
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
80
- }
81
- },
82
- "portioning": {
83
- "type": "object",
84
- "properties": {
85
- "notes": { "type": "string" },
86
- "recommendedPortion": {
87
- "type": "object",
88
- "properties": {
89
- "quantity": { "type": "number" },
90
- "unit": { "type": "string" }
91
- },
92
- "required": ["quantity", "unit"],
93
- "additionalProperties": false,
94
- "patternProperties": {
95
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
96
- }
97
- }
98
- },
99
- "required": ["notes"],
100
- "additionalProperties": false,
101
- "patternProperties": {
102
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
103
- }
104
- },
105
- "leftovers": {
106
- "type": "object",
107
- "properties": {
108
- "notes": { "type": "string" },
109
- "reheat": {
110
- "oneOf": [
111
- {
112
- "type": "array",
113
- "items": { "type": "string" },
114
- "minItems": 1
115
- },
116
- {
117
- "type": "array",
118
- "items": { "$ref": "#/$defs/reheatInstruction" },
119
- "minItems": 1
120
- }
121
- ]
122
- },
123
- "portioning": { "$ref": "#/$defs/portioning" }
124
- },
125
- "additionalProperties": false,
126
- "patternProperties": {
127
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
128
- }
129
- }
130
- },
131
- "additionalProperties": false
132
- }
@@ -1,48 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://soustack.spec/stacks/structured.schema.json",
4
- "title": "Structured Stack",
5
- "type": "object",
6
- "properties": {
7
- "instructions": {
8
- "type": "array",
9
- "items": {
10
- "anyOf": [
11
- { "$ref": "#/$defs/step" },
12
- { "$ref": "#/$defs/stepSection" }
13
- ]
14
- }
15
- }
16
- },
17
- "required": ["instructions"],
18
- "$defs": {
19
- "step": {
20
- "allOf": [
21
- { "$ref": "../defs/entities.schema.json#/$defs/StepBase" },
22
- { "required": ["id"] }
23
- ]
24
- },
25
- "stepSection": {
26
- "type": "object",
27
- "properties": {
28
- "section": { "type": "string" },
29
- "steps": {
30
- "type": "array",
31
- "items": {
32
- "anyOf": [
33
- { "$ref": "#/$defs/step" },
34
- { "$ref": "#/$defs/stepSection" }
35
- ]
36
- }
37
- },
38
- "metadata": { "type": "object", "additionalProperties": true }
39
- },
40
- "required": ["section", "steps"],
41
- "additionalProperties": false,
42
- "patternProperties": {
43
- "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
44
- }
45
- }
46
- },
47
- "additionalProperties": false
48
- }
@@ -1,24 +0,0 @@
1
- # substitutions@1
2
-
3
- ## Purpose
4
- The `substitutions@1` stack enables recipes to declare ingredient substitutions with alternatives and ratios.
5
-
6
- ## Adds
7
- - Top-level `substitutions` array with substitution objects.
8
- - Each substitution includes `for` (ingredient ID) and `alternatives` array.
9
- - Each alternative includes `name` and `ratio` fields.
10
-
11
- ## Requires
12
- - `referenced@1`
13
-
14
- ## Semantics
15
- - MUST: The `for` field must reference an ingredient ID that exists in the ingredients array.
16
- - MUST: Each substitution must include at least one alternative.
17
- - NOTE: This stack requires `referenced@1` to ensure ingredient IDs are available for reference.
18
-
19
- ## Composition Notes
20
- - This stack is monotonic: it adds requirements or fields without removing expressiveness.
21
- - Interaction: Requires `referenced@1` for ingredient ID resolution. Substitutions are informational and do not affect recipe structure.
22
-
23
-
24
-
@@ -1,23 +0,0 @@
1
- # timed@1
2
-
3
- ## Purpose
4
- The `timed@1` stack enables recipes to include timing information for steps, supporting scheduling and time-based planning.
5
-
6
- ## Adds
7
- - Step objects must include a `timing` field with `activity` (active/passive) and either `duration` or `completionCue`.
8
- - Timing may include duration ranges (minMinutes/maxMinutes) or completion cues.
9
-
10
- ## Requires
11
- - `structured@1`
12
-
13
- ## Semantics
14
- - MUST: Each step must include a `timing` object with `activity` field.
15
- - MUST: Each timing object must include either `duration` or `completionCue`.
16
- - NOTE: This stack implies `structured@1` (steps are objects with IDs).
17
-
18
- ## Composition Notes
19
- - This stack is monotonic: it adds requirements or fields without removing expressiveness.
20
- - Interaction: Required by `compute@1` for deterministic scheduling. Works with `quantified@1` for computational recipe planning.
21
-
22
-
23
-