monday-cli 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +249 -49
- package/README.md +87 -45
- package/dist/api/assets.d.ts +3 -3
- package/dist/api/column-types.d.ts +14 -7
- package/dist/api/column-types.d.ts.map +1 -1
- package/dist/api/column-types.js +14 -7
- package/dist/api/column-types.js.map +1 -1
- package/dist/api/error-decoration.d.ts +124 -0
- package/dist/api/error-decoration.d.ts.map +1 -0
- package/dist/api/error-decoration.js +161 -0
- package/dist/api/error-decoration.js.map +1 -0
- package/dist/api/fetch-transport-helpers.d.ts +97 -0
- package/dist/api/fetch-transport-helpers.d.ts.map +1 -0
- package/dist/api/fetch-transport-helpers.js +175 -0
- package/dist/api/fetch-transport-helpers.js.map +1 -0
- package/dist/api/file-column-set.d.ts +388 -82
- package/dist/api/file-column-set.d.ts.map +1 -1
- package/dist/api/file-column-set.js +466 -88
- package/dist/api/file-column-set.js.map +1 -1
- package/dist/api/multipart-transport.d.ts +95 -60
- package/dist/api/multipart-transport.d.ts.map +1 -1
- package/dist/api/multipart-transport.js +102 -120
- package/dist/api/multipart-transport.js.map +1 -1
- package/dist/api/transport.d.ts.map +1 -1
- package/dist/api/transport.js +2 -99
- package/dist/api/transport.js.map +1 -1
- package/dist/cli/program.js +1 -1
- package/dist/cli/program.js.map +1 -1
- package/dist/commands/auth/login.js +1 -1
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js +1 -1
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/board/column-create.d.ts +20 -2
- package/dist/commands/board/column-create.d.ts.map +1 -1
- package/dist/commands/board/column-create.js +191 -20
- package/dist/commands/board/column-create.js.map +1 -1
- package/dist/commands/completion.js +1 -1
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/dev/configure.js +1 -1
- package/dist/commands/dev/configure.js.map +1 -1
- package/dist/commands/dev/discover.js +1 -1
- package/dist/commands/dev/discover.js.map +1 -1
- package/dist/commands/dev/doctor.js +1 -1
- package/dist/commands/dev/doctor.js.map +1 -1
- package/dist/commands/dev/epic/items.js +2 -2
- package/dist/commands/dev/epic/items.js.map +1 -1
- package/dist/commands/dev/epic/list.js +2 -2
- package/dist/commands/dev/epic/list.js.map +1 -1
- package/dist/commands/dev/release/list.js +2 -2
- package/dist/commands/dev/release/list.js.map +1 -1
- package/dist/commands/dev/sprint/current.js +2 -2
- package/dist/commands/dev/sprint/current.js.map +1 -1
- package/dist/commands/dev/sprint/items.js +2 -2
- package/dist/commands/dev/sprint/items.js.map +1 -1
- package/dist/commands/dev/sprint/list.js +2 -2
- package/dist/commands/dev/sprint/list.js.map +1 -1
- package/dist/commands/dev/task/block.js +2 -2
- package/dist/commands/dev/task/block.js.map +1 -1
- package/dist/commands/dev/task/done.js +2 -2
- package/dist/commands/dev/task/done.js.map +1 -1
- package/dist/commands/dev/task/list.js +2 -2
- package/dist/commands/dev/task/list.js.map +1 -1
- package/dist/commands/dev/task/start.js +2 -2
- package/dist/commands/dev/task/start.js.map +1 -1
- package/dist/commands/doc/get.js +1 -1
- package/dist/commands/doc/get.js.map +1 -1
- package/dist/commands/doc/list.js +1 -1
- package/dist/commands/doc/list.js.map +1 -1
- package/dist/commands/item/clear.d.ts.map +1 -1
- package/dist/commands/item/clear.js +15 -41
- package/dist/commands/item/clear.js.map +1 -1
- package/dist/commands/item/create.d.ts +93 -1
- package/dist/commands/item/create.d.ts.map +1 -1
- package/dist/commands/item/create.js +474 -53
- package/dist/commands/item/create.js.map +1 -1
- package/dist/commands/item/search.js +7 -7
- package/dist/commands/item/search.js.map +1 -1
- package/dist/commands/item/set.d.ts +1 -0
- package/dist/commands/item/set.d.ts.map +1 -1
- package/dist/commands/item/set.js +94 -1
- package/dist/commands/item/set.js.map +1 -1
- package/dist/commands/item/time-track/start.js +2 -2
- package/dist/commands/item/time-track/start.js.map +1 -1
- package/dist/commands/item/time-track/stop.js +2 -2
- package/dist/commands/item/time-track/stop.js.map +1 -1
- package/dist/commands/item/update.d.ts +128 -11
- package/dist/commands/item/update.d.ts.map +1 -1
- package/dist/commands/item/update.js +784 -82
- package/dist/commands/item/update.js.map +1 -1
- package/dist/commands/item/upload.js +5 -5
- package/dist/commands/item/upload.js.map +1 -1
- package/dist/commands/item/watch.js +2 -2
- package/dist/commands/item/watch.js.map +1 -1
- package/dist/commands/notification/send.js +1 -1
- package/dist/commands/notification/send.js.map +1 -1
- package/dist/commands/update/body-source.d.ts +38 -0
- package/dist/commands/update/body-source.d.ts.map +1 -0
- package/dist/commands/update/body-source.js +80 -0
- package/dist/commands/update/body-source.js.map +1 -0
- package/dist/commands/update/upload.js +3 -3
- package/dist/commands/update/upload.js.map +1 -1
- package/dist/commands/user/team-add-members.js +1 -1
- package/dist/commands/user/team-add-members.js.map +1 -1
- package/dist/commands/user/team-create.js +1 -1
- package/dist/commands/user/team-create.js.map +1 -1
- package/dist/commands/user/team-remove-members.js +1 -1
- package/dist/commands/user/team-remove-members.js.map +1 -1
- package/dist/commands/webhook/create.js +1 -1
- package/dist/commands/webhook/create.js.map +1 -1
- package/dist/commands/webhook/delete.js +1 -1
- package/dist/commands/webhook/delete.js.map +1 -1
- package/dist/commands/webhook/list.js +1 -1
- package/dist/commands/webhook/list.js.map +1 -1
- package/dist/utils/file-source.d.ts +109 -0
- package/dist/utils/file-source.d.ts.map +1 -1
- package/dist/utils/file-source.js +123 -0
- package/dist/utils/file-source.js.map +1 -1
- package/dist/utils/output/table.d.ts +7 -6
- package/dist/utils/output/table.d.ts.map +1 -1
- package/dist/utils/output/table.js +32 -5
- package/dist/utils/output/table.js.map +1 -1
- package/package.json +2 -1
|
@@ -36,12 +36,25 @@
|
|
|
36
36
|
* 2. The atomicity contract differs — the 13 existing types
|
|
37
37
|
* bundle atomically via `change_multiple_column_values` when
|
|
38
38
|
* ≥2 columns target the same item. File-column writes are
|
|
39
|
-
* single-column-per-call only on Monday's wire
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* the existing atomicity
|
|
39
|
+
* single-column-per-wire-call only on Monday's wire (each
|
|
40
|
+
* `add_file_to_column` round-trip targets one file column);
|
|
41
|
+
* mixing a file-column `--set` with any value `--set` /
|
|
42
|
+
* `--set-raw` / `--name` in the same call would force a
|
|
43
|
+
* multi-leg dispatch across the multipart + JSON wire
|
|
44
|
+
* surfaces that breaks the existing atomicity guarantee
|
|
45
|
+
* (M38 enforces the mixed-rule mutex below — D2 closure;
|
|
46
|
+
* universal on non-create callShapes, SUPPRESSED on
|
|
47
|
+
* `'item_create'` per v0.7-M43 D6 asymmetry). Multi-file
|
|
48
|
+
* `--set` CARVED OUT at v0.8-M46 (D2 fold): the CLI now
|
|
49
|
+
* routes multi-leg fan-out for the 3 reachable callShapes
|
|
50
|
+
* (`'item_update_single'` / `'item_update_bulk'` /
|
|
51
|
+
* `'item_create'`), preserving the single-column-per-wire-
|
|
52
|
+
* call invariant by firing N sequential
|
|
53
|
+
* `add_file_to_column` calls per item. The existing
|
|
54
|
+
* atomicity contract on the JSON `column_values` bundle
|
|
55
|
+
* stays intact; the multi-file dispatch's atomicity
|
|
56
|
+
* envelope (partial-failure shape per D2 closure) is
|
|
57
|
+
* separately decorated.
|
|
45
58
|
* 3. The translator's input contract is "value-string-to-JSON-
|
|
46
59
|
* payload" — file columns take a file path, not a value
|
|
47
60
|
* string. Path validation + `fs.stat` + `fs.access(R_OK)` +
|
|
@@ -82,20 +95,36 @@
|
|
|
82
95
|
* after metadata loads). When any resolved column has `type ===
|
|
83
96
|
* 'file'`:
|
|
84
97
|
*
|
|
85
|
-
* -
|
|
86
|
-
*
|
|
87
|
-
* `
|
|
88
|
-
*
|
|
89
|
-
*
|
|
98
|
+
* - Multi-file `--set` per call — **CARVED OUT at v0.8-M46**
|
|
99
|
+
* (D2 fold). At v0.6-M38 + v0.7 this rejected universally
|
|
100
|
+
* with `'multi_file_set_unsupported'`; v0.8-M46 lifts the
|
|
101
|
+
* CLI mutex for the 3 reachable callShapes
|
|
102
|
+
* (`'item_update_single'` / `'item_update_bulk'` /
|
|
103
|
+
* `'item_create'`) and routes through new `'file_multi'` /
|
|
104
|
+
* `'file_bulk_multi'` / `'file_create_multi'` enforcement
|
|
105
|
+
* kinds for the action body's per-item multi-leg fan-out
|
|
106
|
+
* (sequential within an item × parallel across items for
|
|
107
|
+
* bulk). Monday wire stays single-column-per-wire-call
|
|
108
|
+
* (each `add_file_to_column` round-trip targets one file
|
|
109
|
+
* column); M46 fans N CLI legs per item rather than
|
|
110
|
+
* bundling them on the wire. The throw remains for the
|
|
111
|
+
* `'item_set'` callShape as a defensive type-system
|
|
112
|
+
* ceiling — `monday item set <iid> <col>=<value>` accepts
|
|
113
|
+
* exactly one positional and is argv-incapable of
|
|
114
|
+
* expressing 2+ file `--set` entries; the throw is
|
|
115
|
+
* unreachable from production argv but kept as defense-in-
|
|
116
|
+
* depth.
|
|
90
117
|
* - NO other value `--set` / `--set-raw` / `--name` flags
|
|
91
118
|
* allowed (mixing would force non-atomic multi-leg dispatch
|
|
92
|
-
* across the multipart + JSON wire surfaces
|
|
119
|
+
* across the multipart + JSON wire surfaces — universal on
|
|
120
|
+
* `'item_set'` / `'item_update_single'` / `'item_update_bulk'`,
|
|
121
|
+
* SUPPRESSED on `'item_create'` per v0.7-M43 D6 asymmetry).
|
|
93
122
|
* - Bulk `item update --where ... --set <file-col>=<path>` —
|
|
94
123
|
* **CARVED OUT at v0.7-M42** (D5 fold). At v0.6-M38 this was
|
|
95
124
|
* REJECTED with `'file_set_on_bulk_unsupported'`; v0.7-M42's
|
|
96
125
|
* pre-flight contract diff returns
|
|
97
126
|
* `{ kind: 'file_bulk', columnId, rawValue }` from
|
|
98
|
-
* {@link
|
|
127
|
+
* {@link routeFileColumnDispatch} on the clean dispatch
|
|
99
128
|
* path so the action body can branch into the per-item
|
|
100
129
|
* multipart fan-out. Multi-file / mixed mutex rules STILL
|
|
101
130
|
* apply on bulk (those are universal).
|
|
@@ -104,7 +133,7 @@
|
|
|
104
133
|
* `'file_set_on_create_unsupported'`; v0.7-M43's pre-flight
|
|
105
134
|
* contract diff returns
|
|
106
135
|
* `{ kind: 'file_create', columnId, rawValue }` from
|
|
107
|
-
* {@link
|
|
136
|
+
* {@link routeFileColumnDispatch} on the clean dispatch
|
|
108
137
|
* path so the action body can branch into the two-leg
|
|
109
138
|
* dispatch (`create_item` then `add_file_to_column`).
|
|
110
139
|
* Multi-file mutex rule STILL applies on create (universal).
|
|
@@ -122,9 +151,20 @@
|
|
|
122
151
|
* - `'mixed_file_and_value_sets'` — file `--set` + any value
|
|
123
152
|
* `--set` / `--set-raw` / `--name` in same call. Applies on
|
|
124
153
|
* single-item AND bulk call shapes (universal mutex rule).
|
|
125
|
-
* - `'multi_file_set_unsupported'` —
|
|
126
|
-
*
|
|
127
|
-
*
|
|
154
|
+
* - `'multi_file_set_unsupported'` — **NO LONGER SURFACES**
|
|
155
|
+
* from `'item_update_single'` / `'item_update_bulk'` /
|
|
156
|
+
* `'item_create'` callShapes at v0.8-M46 onwards (D2 carve-out
|
|
157
|
+
* fold; multi-file dispatch now routes through new `file_multi`
|
|
158
|
+
* / `file_bulk_multi` / `file_create_multi` enforcement kinds
|
|
159
|
+
* for the action body's per-item multi-leg fan-out). The
|
|
160
|
+
* discriminator literal stays RESERVED across the codebase
|
|
161
|
+
* (do not reuse for a different rejection reason; mirrors
|
|
162
|
+
* M42 / M43 reserved-literal discipline). The throw remains
|
|
163
|
+
* for the `'item_set'` callShape as a defensive type-system
|
|
164
|
+
* ceiling — the single-positional `monday item set <iid>
|
|
165
|
+
* <col>=<value>` shape is argv-incapable of expressing 2+
|
|
166
|
+
* file `--set` entries, so this rejection is argv-unreachable
|
|
167
|
+
* in practice; kept as defense-in-depth.
|
|
128
168
|
* - `'file_set_on_bulk_unsupported'` — **NO LONGER SURFACES**
|
|
129
169
|
* at v0.7-M42 onwards. Historical reference only; the
|
|
130
170
|
* discriminator literal stays reserved across the codebase
|
|
@@ -146,13 +186,33 @@
|
|
|
146
186
|
* rejection see the full set of working alternatives rather
|
|
147
187
|
* than just the M38 single-item form.
|
|
148
188
|
*
|
|
149
|
-
* **D7 closure — `<path>='-'` stdin support OUT
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* <
|
|
153
|
-
*
|
|
154
|
-
* `
|
|
155
|
-
*
|
|
189
|
+
* **D7 closure — `<path>='-'` stdin support CARVED OUT at
|
|
190
|
+
* v0.8-M47.** Deferred from v0.6-M38 (no `--filename` companion
|
|
191
|
+
* shape pinned then); v0.8-M47 pins the OPTIONAL `--filename
|
|
192
|
+
* <name>` companion (default `"blob"`; Monday accepts any non-empty
|
|
193
|
+
* `Asset.name` and `500`s only an empty one — probe-pinned) and
|
|
194
|
+
* accepts a bare `-` file `--set` value as a stdin source. Because
|
|
195
|
+
* stdin is a single non-replayable stream, the carve-out is scoped
|
|
196
|
+
* to single-file, single-target dispatch: exactly one
|
|
197
|
+
* `<file-col>=-` per call, as the SOLE file `--set` entry, on
|
|
198
|
+
* `item set` / single-item `item update` / `item create`.
|
|
199
|
+
* {@link routeFileColumnDispatch}'s stdin scope gate (mutex
|
|
200
|
+
* priority 0) enforces it — `'multiple_stdin_file_sets'` /
|
|
201
|
+
* `'stdin_file_set_not_sole_file'` / `'stdin_file_set_on_bulk_
|
|
202
|
+
* unsupported'` reject with `usage_error` (literals reserved). A
|
|
203
|
+
* clean stdin source routes through the same single-file
|
|
204
|
+
* `kind: 'file'` / `'file_create'` as a path source; the action
|
|
205
|
+
* body sources the Blob from `readStdinFileSource`
|
|
206
|
+
* (`src/utils/file-source.ts`) instead of {@link buildBlobFromPath},
|
|
207
|
+
* then dispatches it through M31's `addFileToColumn` fetcher directly
|
|
208
|
+
* (the path leg builds its Blob inside {@link executeFileColumnSet};
|
|
209
|
+
* the stdin Blob is already in hand). Live read + dispatch shipped at
|
|
210
|
+
* the v0.8-M47 IMPL alongside the size-less dry-run echo (D4 — a
|
|
211
|
+
* stream can't be `fs.stat`'d, so the echo omits `file_size_bytes`);
|
|
212
|
+
* an empty stdin payload rejects `usage_error` with
|
|
213
|
+
* `details.reason: 'stdin_file_empty'` before any wire activity. The
|
|
214
|
+
* verb-shaped `monday item upload` stays path-only (no stdin) —
|
|
215
|
+
* M47 carves out only the friendly `--set` path.
|
|
156
216
|
*
|
|
157
217
|
* **No new ERROR_CODE (D8 closure; registry stays at 29).** All
|
|
158
218
|
* M38-specific rejections route through existing `usage_error`
|
|
@@ -169,8 +229,8 @@
|
|
|
169
229
|
* canonical cross-link.
|
|
170
230
|
*/
|
|
171
231
|
import { z } from 'zod';
|
|
172
|
-
import { ApiError } from '../utils/errors.js';
|
|
173
|
-
import { buildBlobFromPath } from '../utils/file-source.js';
|
|
232
|
+
import { ApiError, MondayCliError } from '../utils/errors.js';
|
|
233
|
+
import { buildBlobFromPath, isStdinFileSetSource } from '../utils/file-source.js';
|
|
174
234
|
import { addFileToColumn, assetSchema } from './assets.js';
|
|
175
235
|
import { resolveColumnWithRefresh } from './columns.js';
|
|
176
236
|
import { foldResolverWarningsIntoError } from './resolver-error-fold.js';
|
|
@@ -199,6 +259,42 @@ export const fileColumnSetOutputSchema = z
|
|
|
199
259
|
asset: assetSchema,
|
|
200
260
|
})
|
|
201
261
|
.strict();
|
|
262
|
+
/**
|
|
263
|
+
* Output envelope shape for the v0.8-M46 multi-file `--set` dispatch
|
|
264
|
+
* leg on single-item `monday item update <iid> --set f1=p1 --set
|
|
265
|
+
* f2=p2 ...`. Discriminate from M38's single-file envelope via the
|
|
266
|
+
* new `operation: 'add_files_to_columns'` literal (plural) — agents
|
|
267
|
+
* branch on `data.operation` to handle single vs multi shapes
|
|
268
|
+
* uniformly.
|
|
269
|
+
*
|
|
270
|
+
* Per-file slot mirrors M31's `Asset` shape verbatim wrapped under
|
|
271
|
+
* a `column_id` + `filename` + `file_size_bytes` per-entry context.
|
|
272
|
+
* The `applied_file_columns` slot echoes the file columns that
|
|
273
|
+
* landed on success (length N; always present on multi-file success
|
|
274
|
+
* envelopes).
|
|
275
|
+
*
|
|
276
|
+
* **Status: schema landed at v0.8-M46 pre-flight contract diff;
|
|
277
|
+
* runtime emit shipped at v0.8-M46 IMPL.**
|
|
278
|
+
* `runItemUpdateSingleFileMultiDispatch` (commands/item/update.ts)
|
|
279
|
+
* emits against this schema on the single-item multi-file path.
|
|
280
|
+
*/
|
|
281
|
+
export const fileColumnSetMultiOutputSchema = z
|
|
282
|
+
.object({
|
|
283
|
+
operation: z.literal('add_files_to_columns'),
|
|
284
|
+
item_id: z.string().min(1),
|
|
285
|
+
assets: z
|
|
286
|
+
.array(z
|
|
287
|
+
.object({
|
|
288
|
+
column_id: z.string().min(1),
|
|
289
|
+
filename: z.string().min(1),
|
|
290
|
+
file_size_bytes: z.number().int().nonnegative(),
|
|
291
|
+
asset: assetSchema,
|
|
292
|
+
})
|
|
293
|
+
.strict())
|
|
294
|
+
.min(2),
|
|
295
|
+
applied_file_columns: z.array(z.string().min(1)).min(2),
|
|
296
|
+
})
|
|
297
|
+
.strict();
|
|
202
298
|
/**
|
|
203
299
|
* Reads the local file at `inputs.entry.filePath`, constructs a Blob
|
|
204
300
|
* via {@link buildBlobFromPath} (with `Content-Type` sniffed from the
|
|
@@ -219,7 +315,7 @@ export const fileColumnSetOutputSchema = z
|
|
|
219
315
|
* 1. Parsing argv + collecting `--set` / `--set-raw` / `--name`
|
|
220
316
|
* entries.
|
|
221
317
|
* 2. Resolving columns via `resolveColumnWithRefresh`.
|
|
222
|
-
* 3. Calling {@link
|
|
318
|
+
* 3. Calling {@link routeFileColumnDispatch} to detect the
|
|
223
319
|
* file-column dispatch leg + enforce the mutex rules.
|
|
224
320
|
* 4. Running {@link precheckLocalFile} from
|
|
225
321
|
* `src/utils/file-source.ts` on the agent-supplied path to
|
|
@@ -254,6 +350,79 @@ export const executeFileColumnSet = async (inputs) => {
|
|
|
254
350
|
complexity: result.complexity,
|
|
255
351
|
};
|
|
256
352
|
};
|
|
353
|
+
/**
|
|
354
|
+
* Sequential multi-leg `add_file_to_column` fan-out shared by the
|
|
355
|
+
* three v0.8-M46 multi-file callShape helpers
|
|
356
|
+
* (`runItemUpdateSingleFileMultiDispatch` /
|
|
357
|
+
* `runItemUpdateBulkFileMultiDispatch` per-item worker /
|
|
358
|
+
* `runItemCreateFileMultiDispatch`). R-v0.8-NEW-1 lift — the inner
|
|
359
|
+
* loop + partial-failure accumulator is byte-identical across all 3
|
|
360
|
+
* sites; only the envelope decoration diverges (kept in the caller).
|
|
361
|
+
*
|
|
362
|
+
* **Sequential within an item (D1 closure).** Legs fire in
|
|
363
|
+
* `inputs.entries` order so `appliedColumns` echoes the file columns
|
|
364
|
+
* that landed before a failure in dispatch order. The bulk path
|
|
365
|
+
* achieves cross-item parallelism by invoking this helper inside each
|
|
366
|
+
* per-item `dispatchParallel` worker — parallel ACROSS items ×
|
|
367
|
+
* sequential WITHIN each item.
|
|
368
|
+
*
|
|
369
|
+
* **Failure handling.** A `MondayCliError` from any leg stops the
|
|
370
|
+
* loop and returns the accumulator with `failure` populated (the
|
|
371
|
+
* caller decorates + optionally remaps). A non-`MondayCliError`
|
|
372
|
+
* (programmer bug in the wire layer) re-throws to the runner's
|
|
373
|
+
* catch-all so it surfaces as a whole-call `internal_error` rather
|
|
374
|
+
* than masquerading as a recoverable partial failure — mirrors the
|
|
375
|
+
* non-CliError re-throw arms in M42's bulk + M43's create-time
|
|
376
|
+
* single-file dispatch helpers.
|
|
377
|
+
*/
|
|
378
|
+
export const dispatchFileLegsSequentially = async (inputs) => {
|
|
379
|
+
const assets = [];
|
|
380
|
+
const appliedColumns = [];
|
|
381
|
+
let lastComplexity = null;
|
|
382
|
+
for (const entry of inputs.entries) {
|
|
383
|
+
try {
|
|
384
|
+
const result = await executeFileColumnSet({
|
|
385
|
+
client: inputs.client,
|
|
386
|
+
multipart: inputs.multipart,
|
|
387
|
+
itemId: inputs.itemId,
|
|
388
|
+
entry: {
|
|
389
|
+
columnId: entry.columnId,
|
|
390
|
+
columnType: 'file',
|
|
391
|
+
rawValue: entry.rawValue,
|
|
392
|
+
filePath: entry.filePath,
|
|
393
|
+
filename: entry.filename,
|
|
394
|
+
fileSizeBytes: entry.fileSizeBytes,
|
|
395
|
+
},
|
|
396
|
+
signal: inputs.signal,
|
|
397
|
+
retries: inputs.retries,
|
|
398
|
+
});
|
|
399
|
+
assets.push({
|
|
400
|
+
column_id: entry.columnId,
|
|
401
|
+
filename: entry.filename,
|
|
402
|
+
file_size_bytes: entry.fileSizeBytes,
|
|
403
|
+
asset: result.asset,
|
|
404
|
+
});
|
|
405
|
+
appliedColumns.push(entry.columnId);
|
|
406
|
+
lastComplexity = result.complexity;
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
if (err instanceof MondayCliError) {
|
|
410
|
+
return {
|
|
411
|
+
appliedColumns,
|
|
412
|
+
assets,
|
|
413
|
+
lastComplexity,
|
|
414
|
+
failure: { failedColumn: entry.columnId, cause: err },
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
// Non-CliError programmer bug — re-throw to the runner's
|
|
418
|
+
// catch-all (whole-call `internal_error`). Routing it through
|
|
419
|
+
// a partial-failure envelope would falsely promise a recovery
|
|
420
|
+
// handle for a broken contract.
|
|
421
|
+
throw err;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return { appliedColumns, assets, lastComplexity };
|
|
425
|
+
};
|
|
257
426
|
/**
|
|
258
427
|
* Iterates `inputs.setEntries`, identifies entries with
|
|
259
428
|
* `columnType === 'file'`, applies the mutex rules per D2 / D5 / D6
|
|
@@ -262,19 +431,39 @@ export const executeFileColumnSet = async (inputs) => {
|
|
|
262
431
|
* throws `ApiError('usage_error', ...)` on a mutex violation.
|
|
263
432
|
*
|
|
264
433
|
* Mutex priority (ratified at M38 pre-flight; updated at v0.7-M42
|
|
265
|
-
* + v0.7-M43 pre-flights to fold the D5 bulk + D6 create carve-outs
|
|
434
|
+
* + v0.7-M43 pre-flights to fold the D5 bulk + D6 create carve-outs;
|
|
435
|
+
* updated at v0.8-M46 pre-flight to fold the D2 multi-file carve-out;
|
|
436
|
+
* updated at v0.8-M47 pre-flight to add the stdin scope gate):
|
|
266
437
|
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
* the
|
|
277
|
-
*
|
|
438
|
+
* 0. **stdin scope gate (v0.8-M47 D7 fold).** When ≥1 file `--set`
|
|
439
|
+
* value is the bare `-` stdin sentinel, enforce stdin's single-
|
|
440
|
+
* file / single-target scope: `'multiple_stdin_file_sets'` (2+
|
|
441
|
+
* `=-`), `'stdin_file_set_not_sole_file'` (stdin + another file
|
|
442
|
+
* `--set`), `'stdin_file_set_on_bulk_unsupported'` (`=-` on the
|
|
443
|
+
* `'item_update_bulk'` callShape). All `usage_error`; literals
|
|
444
|
+
* reserved. A clean stdin source (exactly one `=-`, sole file,
|
|
445
|
+
* single-target) falls through to the clean single-file leg and
|
|
446
|
+
* returns `kind: 'file'` / `'file_create'` — identical routing to
|
|
447
|
+
* a path source; the action body sources the Blob from stdin. The
|
|
448
|
+
* mixed + duplicate gates below still apply to a stdin entry.
|
|
449
|
+
* 1. **callShape gate — ONLY `'item_set'` defensive throw remains**
|
|
450
|
+
* post-v0.8-M46. The v0.6-M38 `'item_update_bulk'` (D5 fold at
|
|
451
|
+
* v0.7-M42) and `'item_create'` (D6 fold at v0.7-M43) short-
|
|
452
|
+
* circuit-throws have been removed; the v0.6-M38 universal
|
|
453
|
+
* multi-file throw FOLDED at v0.8-M46 (D2) for the 3 reachable
|
|
454
|
+
* callShapes. The `'item_set'` callShape's multi-file throw
|
|
455
|
+
* stays as defensive type-system ceiling — the single-
|
|
456
|
+
* positional `monday item set <iid> <col>=<value>` verb is
|
|
457
|
+
* argv-incapable of expressing 2+ file `--set` entries, so
|
|
458
|
+
* this branch is argv-unreachable in practice; kept as
|
|
459
|
+
* defense-in-depth.
|
|
460
|
+
* 2. **multi-file leg (`'item_set'` only post-fold)** — 2+ file
|
|
461
|
+
* `--set` entries on `'item_set'` callShape surface
|
|
462
|
+
* `'multi_file_set_unsupported'` (argv-unreachable defensive).
|
|
463
|
+
* The other 3 reachable callShapes (`'item_update_single'` /
|
|
464
|
+
* `'item_update_bulk'` / `'item_create'`) fall through to the
|
|
465
|
+
* mixed gate + clean leg for the multi-file dispatch routing.
|
|
466
|
+
* 3. **mixed leg** — 1+ file `--set` + any value `--set` /
|
|
278
467
|
* `--set-raw` / `--name` surfaces `'mixed_file_and_value_sets'`
|
|
279
468
|
* on `'item_set'` / `'item_update_single'` / `'item_update_bulk'`.
|
|
280
469
|
* Universal rule on those callShapes: mixing forces non-atomic
|
|
@@ -283,29 +472,106 @@ export const executeFileColumnSet = async (inputs) => {
|
|
|
283
472
|
* asymmetry — `create_item` natively bundles non-file
|
|
284
473
|
* `column_values` atomically into leg-1, and `--name` is
|
|
285
474
|
* required on create.
|
|
286
|
-
* 4. **clean leg** — 1 file `--set`, no mutex violation
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
475
|
+
* 4. **clean leg** — ≥1 file `--set`, no mutex violation. Branches
|
|
476
|
+
* on N (single vs multi) then callShape:
|
|
477
|
+
* - N=1 (single-file path):
|
|
478
|
+
* - `'item_update_single'` / `'item_set'` → `{ kind: 'file',
|
|
479
|
+
* columnId, rawValue }` (M38 path; unchanged).
|
|
480
|
+
* - `'item_update_bulk'` → `{ kind: 'file_bulk', ... }`
|
|
481
|
+
* (v0.7-M42 D5 carve-out fold).
|
|
482
|
+
* - `'item_create'` → `{ kind: 'file_create', ... }`
|
|
483
|
+
* (v0.7-M43 D6 carve-out fold).
|
|
484
|
+
* - N>1 (multi-file path — v0.8-M46 D2 carve-out fold):
|
|
485
|
+
* - `'item_update_single'` → `{ kind: 'file_multi', entries }`
|
|
486
|
+
* for the single-item multi-leg fan-out helper.
|
|
487
|
+
* - `'item_update_bulk'` → `{ kind: 'file_bulk_multi',
|
|
488
|
+
* entries }` for the bulk per-item multi-leg fan-out.
|
|
489
|
+
* - `'item_create'` → `{ kind: 'file_create_multi', entries }`
|
|
490
|
+
* for the create-time two-leg-group multi-file helper.
|
|
298
491
|
*
|
|
299
492
|
* The function is sync + pure. No I/O. Path validation lives at a
|
|
300
493
|
* SEPARATE step (`precheckLocalFile` from `src/utils/file-source.ts`)
|
|
301
|
-
* that the caller runs AFTER this function returns a `kind: 'file'`
|
|
494
|
+
* that the caller runs AFTER this function returns a `kind: 'file*'`
|
|
302
495
|
* result.
|
|
303
496
|
*/
|
|
304
|
-
export const
|
|
497
|
+
export const routeFileColumnDispatch = (inputs) => {
|
|
305
498
|
const fileSetEntries = inputs.setEntries.filter((e) => e.columnType === 'file');
|
|
306
499
|
if (fileSetEntries.length === 0) {
|
|
307
500
|
return { kind: 'json' };
|
|
308
501
|
}
|
|
502
|
+
// v0.8-M47 stdin file `--set` scope gate (D7 fold). A file `--set`
|
|
503
|
+
// value of the bare `-` sentinel sources the file body from stdin.
|
|
504
|
+
// stdin is a single non-replayable stream, so the contract scopes it
|
|
505
|
+
// to single-file, single-target dispatch (D-list closures at
|
|
506
|
+
// v0.8-plan §3 M47): exactly one `<file-col>=-` per call, as the
|
|
507
|
+
// SOLE file entry, on a single-target callShape. The three
|
|
508
|
+
// violations reject with `usage_error` + a reserved `details.reason`
|
|
509
|
+
// discriminator (no new ERROR_CODE — registry stays 29). A clean
|
|
510
|
+
// stdin source falls through to the single-file clean leg below and
|
|
511
|
+
// returns `kind: 'file'` (single-item update) / `'file_create'`
|
|
512
|
+
// (create) just like a path source — the rawValue carries the `-`
|
|
513
|
+
// sentinel and the action body's dispatch helper sources from stdin
|
|
514
|
+
// (via `readStdinFileSource`) instead of `precheckLocalFile`. The
|
|
515
|
+
// mixed-rule + duplicate-column checks below still apply to a stdin
|
|
516
|
+
// entry (e.g. `--set f=- --set status=Done` rejects `mixed_file_and_
|
|
517
|
+
// value_sets`); stdin only changes the file SOURCE, not the mutex
|
|
518
|
+
// surface around it.
|
|
519
|
+
const stdinFileEntries = fileSetEntries.filter((e) => isStdinFileSetSource(e.rawValue));
|
|
520
|
+
if (stdinFileEntries.length > 0) {
|
|
521
|
+
const firstStdin = stdinFileEntries[0];
|
|
522
|
+
/* c8 ignore next 6 — defensive: length > 0 guarantees [0] is
|
|
523
|
+
defined; the guard exists for `noUncheckedIndexedAccess`
|
|
524
|
+
narrowing (mirrors the `fe === undefined` guards below). */
|
|
525
|
+
if (firstStdin === undefined) {
|
|
526
|
+
throw new ApiError('internal_error', 'routeFileColumnDispatch: stdin entry narrowing failed');
|
|
527
|
+
}
|
|
528
|
+
if (stdinFileEntries.length > 1) {
|
|
529
|
+
throw new ApiError('usage_error', `Only one \`--set <file-col>=-\` stdin source is allowed per call ` +
|
|
530
|
+
`(stdin is a single non-replayable stream — it can't be split ` +
|
|
531
|
+
`across multiple file columns). Pass at most one \`<file-col>=-\` ` +
|
|
532
|
+
`and use local file paths for the rest.`, {
|
|
533
|
+
details: {
|
|
534
|
+
reason: 'multiple_stdin_file_sets',
|
|
535
|
+
stdin_file_count: stdinFileEntries.length,
|
|
536
|
+
stdin_file_column_ids: stdinFileEntries.map((e) => e.columnId),
|
|
537
|
+
hint: 'at most one `<file-col>=-` per call; source the other file ' +
|
|
538
|
+
'columns from local paths.',
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
if (fileSetEntries.length > 1) {
|
|
543
|
+
throw new ApiError('usage_error', `A \`--set <file-col>=-\` stdin source must be the only file ` +
|
|
544
|
+
`\`--set\` entry in the call (stdin is a single stream — it ` +
|
|
545
|
+
`can't be combined with other file uploads in one invocation). ` +
|
|
546
|
+
`Run the stdin upload in its own call, or source every file ` +
|
|
547
|
+
`column from a local path.`, {
|
|
548
|
+
details: {
|
|
549
|
+
reason: 'stdin_file_set_not_sole_file',
|
|
550
|
+
file_count: fileSetEntries.length,
|
|
551
|
+
stdin_file_column_id: firstStdin.columnId,
|
|
552
|
+
file_column_ids: fileSetEntries.map((e) => e.columnId),
|
|
553
|
+
hint: 'use `<file-col>=-` alone (e.g. `cat f | monday item set ' +
|
|
554
|
+
'<iid> <file-col>=-`), or source all file columns from paths.',
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (inputs.callShape === 'item_update_bulk') {
|
|
559
|
+
throw new ApiError('usage_error', `\`--set <file-col>=-\` (stdin) is not supported on the bulk ` +
|
|
560
|
+
`\`monday item update --where ...\` path: stdin is a single ` +
|
|
561
|
+
`non-replayable stream and can't be fanned out across the ` +
|
|
562
|
+
`matched item set. Pipe to a temp file and \`--set ` +
|
|
563
|
+
`<file-col>=<path>\` for bulk, or target one item with ` +
|
|
564
|
+
`\`monday item update <iid> --set <file-col>=-\`.`, {
|
|
565
|
+
details: {
|
|
566
|
+
reason: 'stdin_file_set_on_bulk_unsupported',
|
|
567
|
+
column_id: firstStdin.columnId,
|
|
568
|
+
call_shape: inputs.callShape,
|
|
569
|
+
hint: 'stdin file `--set` is single-target only; use a local path ' +
|
|
570
|
+
'for bulk fan-out or target a single item id.',
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
309
575
|
// callShape gate — NO callShape short-circuits at this layer
|
|
310
576
|
// post v0.7-M43. The v0.6-M38 `'item_create'` short-circuit-throw
|
|
311
577
|
// (`'file_set_on_create_unsupported'`) FOLDED at v0.7-M43 (D6):
|
|
@@ -317,29 +583,41 @@ export const enforceSingleFileColumnSet = (inputs) => {
|
|
|
317
583
|
// runtime rejection without a fresh contract decision. (Parallels
|
|
318
584
|
// the v0.7-M42 fold of `'file_set_on_bulk_unsupported'` for
|
|
319
585
|
// `'item_update_bulk'`.)
|
|
320
|
-
// Multi-file leg (D2 multi).
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
586
|
+
// Multi-file leg (D2 multi). v0.8-M46 D2 carve-out fold: lifted
|
|
587
|
+
// on the 3 reachable callShapes (`'item_update_single'`,
|
|
588
|
+
// `'item_update_bulk'`, `'item_create'`); the action body routes
|
|
589
|
+
// the multi-file dispatch into the per-callShape multi-leg
|
|
590
|
+
// helper via the new `file_multi` / `file_bulk_multi` /
|
|
591
|
+
// `file_create_multi` enforcement kinds (see clean-leg block
|
|
592
|
+
// below).
|
|
593
|
+
//
|
|
594
|
+
// The `'item_set'` callShape's defensive throw stays in force —
|
|
595
|
+
// the single-positional `monday item set <iid> <col>=<value>`
|
|
596
|
+
// verb is argv-incapable of expressing 2+ file `--set` entries,
|
|
597
|
+
// so this branch is argv-unreachable in practice; kept as
|
|
598
|
+
// defense-in-depth + type-system ceiling. The
|
|
599
|
+
// `'multi_file_set_unsupported'` discriminator literal stays
|
|
600
|
+
// RESERVED across the codebase post-fold (mirrors v0.7-M42 D5
|
|
601
|
+
// fold of `'file_set_on_bulk_unsupported'` + v0.7-M43 D6 fold of
|
|
602
|
+
// `'file_set_on_create_unsupported'` reserved-literal discipline).
|
|
603
|
+
if (fileSetEntries.length > 1 && inputs.callShape === 'item_set') {
|
|
604
|
+
throw new ApiError('usage_error', `Multi-file \`--set <file-col>=<path>\` is not supported on the ` +
|
|
605
|
+
`\`monday item set\` single-positional verb (argv-unreachable ` +
|
|
606
|
+
`defensive guard — \`item set <iid> <col>=<value>\` accepts ` +
|
|
607
|
+
`exactly one positional). For multi-file dispatch, use ` +
|
|
608
|
+
`\`monday item update <iid> --set <file-col>=<path> --set ` +
|
|
609
|
+
`<file-col2>=<path2> ...\` (v0.8-M46 single-item multi-file ` +
|
|
610
|
+
`path) or \`monday item create --board <bid> --set ` +
|
|
611
|
+
`<file-col>=<path> --set <file-col2>=<path2> ...\` ` +
|
|
612
|
+
`(v0.8-M46 create-time multi-file path).`, {
|
|
336
613
|
details: {
|
|
337
614
|
reason: 'multi_file_set_unsupported',
|
|
338
615
|
file_count: fileSetEntries.length,
|
|
339
616
|
file_column_ids: fileSetEntries.map((e) => e.columnId),
|
|
340
|
-
|
|
341
|
-
hint: '
|
|
342
|
-
'
|
|
617
|
+
call_shape: inputs.callShape,
|
|
618
|
+
hint: 'use `monday item update <iid> --set ...` or `monday item ' +
|
|
619
|
+
'create --set ...` for multi-file dispatch (v0.8-M46 ' +
|
|
620
|
+
'carve-out fold).',
|
|
343
621
|
},
|
|
344
622
|
});
|
|
345
623
|
}
|
|
@@ -371,7 +649,7 @@ export const enforceSingleFileColumnSet = (inputs) => {
|
|
|
371
649
|
const fe = fileSetEntries[0];
|
|
372
650
|
/* c8 ignore next 3 */
|
|
373
651
|
if (fe === undefined) {
|
|
374
|
-
throw new ApiError('internal_error', '
|
|
652
|
+
throw new ApiError('internal_error', 'routeFileColumnDispatch: file entry narrowing failed (mixed)');
|
|
375
653
|
}
|
|
376
654
|
throw new ApiError('usage_error', `Mixing a file \`--set <file-col>=<path>\` with value \`--set\` / ` +
|
|
377
655
|
`\`--set-raw\` / \`--name\` in the same call is not supported at ` +
|
|
@@ -394,20 +672,88 @@ export const enforceSingleFileColumnSet = (inputs) => {
|
|
|
394
672
|
},
|
|
395
673
|
});
|
|
396
674
|
}
|
|
397
|
-
// Clean dispatch leg.
|
|
398
|
-
//
|
|
399
|
-
// -
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
675
|
+
// Clean dispatch leg. ≥1 file `--set`, no mutex violation.
|
|
676
|
+
// Branches on N (single vs multi) then on callShape:
|
|
677
|
+
// N=1 (single-file path):
|
|
678
|
+
// - `'item_update_single'` / `'item_set'` → `kind: 'file'`
|
|
679
|
+
// (M38 single-item path; unchanged).
|
|
680
|
+
// - `'item_update_bulk'` → `kind: 'file_bulk'` (v0.7-M42 D5
|
|
681
|
+
// carve-out fold; action body's per-item multipart fan-out).
|
|
682
|
+
// - `'item_create'` → `kind: 'file_create'` (v0.7-M43 D6
|
|
683
|
+
// carve-out fold; action body's two-leg `create_item` then
|
|
684
|
+
// `add_file_to_column` dispatch with orphan-warn atomicity
|
|
685
|
+
// envelope per D1 closure).
|
|
686
|
+
// N>1 (multi-file path — v0.8-M46 D2 carve-out fold):
|
|
687
|
+
// - `'item_update_single'` → `kind: 'file_multi'` (single-item
|
|
688
|
+
// N-leg fan-out helper `runItemUpdateSingleFileMultiDispatch`).
|
|
689
|
+
// - `'item_update_bulk'` → `kind: 'file_bulk_multi'` (per-item
|
|
690
|
+
// N-leg fan-out helper `runItemUpdateBulkFileMultiDispatch`;
|
|
691
|
+
// cross-item parallel × within-item sequential).
|
|
692
|
+
// - `'item_create'` → `kind: 'file_create_multi'` (two-leg-
|
|
693
|
+
// group helper `runItemCreateFileMultiDispatch`; leg-1
|
|
694
|
+
// `create_item` then N sequential `add_file_to_column`
|
|
695
|
+
// legs). The `'item_set'` callShape is argv-unreachable
|
|
696
|
+
// (single-positional; cannot express 2+ file `--set`) and
|
|
697
|
+
// throws the defensive `'multi_file_set_unsupported'` above.
|
|
698
|
+
if (fileSetEntries.length > 1) {
|
|
699
|
+
// v0.8-M46 Codex R1 P2-1 fix: reject duplicate resolved file-
|
|
700
|
+
// column IDs across multi-file entries (mirrors JSON path's
|
|
701
|
+
// existing cross-token duplicate-resolved-ID contract at
|
|
702
|
+
// `src/api/resolution-pass.ts` + `docs/output-shapes.md`).
|
|
703
|
+
// Without this guard, `--set attachments=/p/a --set
|
|
704
|
+
// id:attachments=/p/b` (two distinct argv tokens that resolve
|
|
705
|
+
// to the same file column ID) would either silently dispatch
|
|
706
|
+
// two `add_file_to_column` legs against the same column (the
|
|
707
|
+
// second leg's file replaces the first wire-side — surprising
|
|
708
|
+
// behavior) or downstream `projectedEntries` token-by-find
|
|
709
|
+
// logic in `preCheckM38FileDispatch` would lose token identity
|
|
710
|
+
// (map both entries to the first token's slot). Rejecting at
|
|
711
|
+
// the enforcement layer is the simplest contract that aligns
|
|
712
|
+
// with the JSON path's existing behavior + sidesteps the
|
|
713
|
+
// token-identity edge case.
|
|
714
|
+
const seenColumnIds = new Set();
|
|
715
|
+
for (const entry of fileSetEntries) {
|
|
716
|
+
if (seenColumnIds.has(entry.columnId)) {
|
|
717
|
+
throw new ApiError('usage_error', `Multiple \`--set\` entries resolve to the same file column ` +
|
|
718
|
+
`\`${entry.columnId}\` (v0.8-M46 multi-file dispatch requires ` +
|
|
719
|
+
`each file column to appear at most once per call). Monday's ` +
|
|
720
|
+
`\`add_file_to_column\` is single-column per call on the wire; ` +
|
|
721
|
+
`two legs against the same column would either silently ` +
|
|
722
|
+
`replace the first file or surface non-deterministic ` +
|
|
723
|
+
`wire-side ordering. Drop one of the duplicate entries or ` +
|
|
724
|
+
`target distinct file columns.`, {
|
|
725
|
+
details: {
|
|
726
|
+
reason: 'duplicate_resolved_file_columns',
|
|
727
|
+
column_id: entry.columnId,
|
|
728
|
+
file_count: fileSetEntries.length,
|
|
729
|
+
file_column_ids: fileSetEntries.map((e) => e.columnId),
|
|
730
|
+
hint: 'each file column may appear at most once across `--set` ' +
|
|
731
|
+
'entries; drop the duplicate or target distinct file ' +
|
|
732
|
+
'columns.',
|
|
733
|
+
},
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
seenColumnIds.add(entry.columnId);
|
|
737
|
+
}
|
|
738
|
+
const entries = fileSetEntries.map((e) => ({
|
|
739
|
+
columnId: e.columnId,
|
|
740
|
+
rawValue: e.rawValue,
|
|
741
|
+
}));
|
|
742
|
+
if (inputs.callShape === 'item_update_bulk') {
|
|
743
|
+
return { kind: 'file_bulk_multi', entries };
|
|
744
|
+
}
|
|
745
|
+
if (inputs.callShape === 'item_create') {
|
|
746
|
+
return { kind: 'file_create_multi', entries };
|
|
747
|
+
}
|
|
748
|
+
// `'item_update_single'` (and `'item_set'` defensively — the
|
|
749
|
+
// multi-file gate above already threw on `'item_set'`, so this
|
|
750
|
+
// branch only fires for `'item_update_single'` in practice).
|
|
751
|
+
return { kind: 'file_multi', entries };
|
|
752
|
+
}
|
|
407
753
|
const fe = fileSetEntries[0];
|
|
408
754
|
/* c8 ignore next 3 */
|
|
409
755
|
if (fe === undefined) {
|
|
410
|
-
throw new ApiError('internal_error', '
|
|
756
|
+
throw new ApiError('internal_error', 'routeFileColumnDispatch: file entry narrowing failed (clean)');
|
|
411
757
|
}
|
|
412
758
|
if (inputs.callShape === 'item_update_bulk') {
|
|
413
759
|
return { kind: 'file_bulk', columnId: fe.columnId, rawValue: fe.rawValue };
|
|
@@ -504,7 +850,7 @@ export const preCheckM38FileDispatch = async (inputs) => {
|
|
|
504
850
|
// matters for the mutex check; columnId / columnType slots are
|
|
505
851
|
// unused on the mixed-set discriminator path.
|
|
506
852
|
const setRawEntries = Array.from({ length: inputs.setRawCount }, () => ({ columnId: '', columnType: '' }));
|
|
507
|
-
const enforcement =
|
|
853
|
+
const enforcement = routeFileColumnDispatch({
|
|
508
854
|
callShape: inputs.callShape,
|
|
509
855
|
setEntries: resolved.map((r) => ({
|
|
510
856
|
columnId: r.columnId,
|
|
@@ -522,9 +868,41 @@ export const preCheckM38FileDispatch = async (inputs) => {
|
|
|
522
868
|
cacheAgeSeconds: aggregateCacheAge,
|
|
523
869
|
};
|
|
524
870
|
}
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
871
|
+
// v0.8-M46 multi-file branches (D2 closure). The enforcement
|
|
872
|
+
// result carries a list of resolved file-column entries (length
|
|
873
|
+
// ≥ 2, argv order). Map each entry back to its argv token by
|
|
874
|
+
// matching (columnId, rawValue) against the pre-check's
|
|
875
|
+
// `resolved` list — the token is the load-bearing handle for
|
|
876
|
+
// the dispatch envelope's `resolved_ids` slot.
|
|
877
|
+
if (enforcement.kind === 'file_multi' ||
|
|
878
|
+
enforcement.kind === 'file_bulk_multi' ||
|
|
879
|
+
enforcement.kind === 'file_create_multi') {
|
|
880
|
+
const projectedEntries = enforcement.entries.map((entry) => {
|
|
881
|
+
const fileResolved = resolved.find((r) => r.columnType === 'file' &&
|
|
882
|
+
r.columnId === entry.columnId &&
|
|
883
|
+
r.rawValue === entry.rawValue);
|
|
884
|
+
/* c8 ignore next 6 — defensive: enforcement returned entries
|
|
885
|
+
the pre-check passed in; every find must succeed. */
|
|
886
|
+
if (fileResolved === undefined) {
|
|
887
|
+
throw new ApiError('internal_error', 'preCheckM38FileDispatch: multi-file entry not found in resolved set after enforcement');
|
|
888
|
+
}
|
|
889
|
+
return {
|
|
890
|
+
columnId: entry.columnId,
|
|
891
|
+
rawValue: entry.rawValue,
|
|
892
|
+
token: fileResolved.token,
|
|
893
|
+
};
|
|
894
|
+
});
|
|
895
|
+
return {
|
|
896
|
+
kind: enforcement.kind,
|
|
897
|
+
entries: projectedEntries,
|
|
898
|
+
warnings,
|
|
899
|
+
source: aggregateSource,
|
|
900
|
+
cacheAgeSeconds: aggregateCacheAge,
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
// Single-file branches: `'file'` (M38) / `'file_bulk'` (M42) /
|
|
904
|
+
// `'file_create'` (M43). Find the matching resolved entry for
|
|
905
|
+
// the file-column token (echo into resolved_ids downstream).
|
|
528
906
|
const fileResolved = resolved.find((r) => r.columnType === 'file' &&
|
|
529
907
|
r.columnId === enforcement.columnId &&
|
|
530
908
|
r.rawValue === enforcement.rawValue);
|