@bilig/workbook 0.107.3 → 0.119.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.
Files changed (78) hide show
  1. package/README.md +39 -38
  2. package/dist/check.js +4 -1
  3. package/dist/check.js.map +1 -1
  4. package/dist/command-bundle.js +18 -3
  5. package/dist/command-bundle.js.map +1 -1
  6. package/dist/command-ops.js +257 -4
  7. package/dist/command-ops.js.map +1 -1
  8. package/dist/command-ranges.js +5 -1
  9. package/dist/command-ranges.js.map +1 -1
  10. package/dist/command-receipt-ranges.js +5 -1
  11. package/dist/command-receipt-ranges.js.map +1 -1
  12. package/dist/command-result-undo.js +6 -2
  13. package/dist/command-result-undo.js.map +1 -1
  14. package/dist/command-result.js +64 -3
  15. package/dist/command-result.js.map +1 -1
  16. package/dist/data-properties.d.ts +1 -0
  17. package/dist/data-properties.js +3 -0
  18. package/dist/data-properties.js.map +1 -1
  19. package/dist/describe.d.ts +6 -0
  20. package/dist/describe.js +10 -0
  21. package/dist/describe.js.map +1 -1
  22. package/dist/feature-plugin.d.ts +7 -1
  23. package/dist/feature-plugin.js +23 -1
  24. package/dist/feature-plugin.js.map +1 -1
  25. package/dist/features.d.ts +0 -6
  26. package/dist/features.js +17 -26
  27. package/dist/features.js.map +1 -1
  28. package/dist/find.js +94 -103
  29. package/dist/find.js.map +1 -1
  30. package/dist/formula.js +5 -2
  31. package/dist/formula.js.map +1 -1
  32. package/dist/index.d.ts +0 -1
  33. package/dist/index.js +0 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/model-action-validation.d.ts +2 -0
  36. package/dist/model-action-validation.js +71 -3
  37. package/dist/model-action-validation.js.map +1 -1
  38. package/dist/model.js +4 -1
  39. package/dist/model.js.map +1 -1
  40. package/dist/op-schema.d.ts +4 -0
  41. package/dist/op-schema.js +895 -0
  42. package/dist/op-schema.js.map +1 -0
  43. package/dist/plan-data.js +210 -55
  44. package/dist/plan-data.js.map +1 -1
  45. package/dist/readback.js +44 -1
  46. package/dist/readback.js.map +1 -1
  47. package/dist/requirements.js +41 -3
  48. package/dist/requirements.js.map +1 -1
  49. package/dist/result.d.ts +7 -0
  50. package/dist/result.js.map +1 -1
  51. package/dist/run-apply.js +5 -2
  52. package/dist/run-apply.js.map +1 -1
  53. package/dist/run-command-receipts.js +133 -3
  54. package/dist/run-command-receipts.js.map +1 -1
  55. package/dist/run-data.js +6 -2
  56. package/dist/run-data.js.map +1 -1
  57. package/dist/run-description-noop.d.ts +2 -0
  58. package/dist/run-description-noop.js +330 -0
  59. package/dist/run-description-noop.js.map +1 -0
  60. package/dist/run-description.js +23 -2
  61. package/dist/run-description.js.map +1 -1
  62. package/dist/run-runtime-boundary.js +39 -5
  63. package/dist/run-runtime-boundary.js.map +1 -1
  64. package/dist/run.d.ts +1 -0
  65. package/dist/run.js.map +1 -1
  66. package/dist/schema-fragments.d.ts +16 -0
  67. package/dist/schema-fragments.js +246 -0
  68. package/dist/schema-fragments.js.map +1 -0
  69. package/dist/schema-noop.d.ts +9 -0
  70. package/dist/schema-noop.js +128 -0
  71. package/dist/schema-noop.js.map +1 -0
  72. package/dist/schema.js +266 -145
  73. package/dist/schema.js.map +1 -1
  74. package/dist/testing.js +19 -4
  75. package/dist/testing.js.map +1 -1
  76. package/dist/verify.js +34 -0
  77. package/dist/verify.js.map +1 -1
  78. package/package.json +12 -5
package/dist/schema.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { workbookRunErrorCodes } from './result.js';
2
2
  import { workbookActionInputDescriptionKinds } from './input.js';
3
+ import { cellStylePatchSchema, literalInputSchema, workbookOpSchema } from './op-schema.js';
4
+ import { workbookActionCellStylePatchSchema, workbookCommandReceiptSchema, workbookRefDataDefSchemas, workbookRefDataSchemaRefs, workbookUndoRefSchema, } from './schema-fragments.js';
5
+ import { createNoopEffectSchema, noopEffectCommandKindConditions } from './schema-noop.js';
3
6
  export const workbookJsonSchemaVersion = 'bilig-workbook-json-schema-v1';
4
7
  export const workbookJsonSchemaNames = Object.freeze([
5
8
  'refData',
@@ -12,11 +15,22 @@ export const workbookJsonSchemaNames = Object.freeze([
12
15
  'readbackProof',
13
16
  ]);
14
17
  const jsonValue = Object.freeze({ $ref: '#/$defs/jsonValue' });
18
+ const literalInput = Object.freeze({ $ref: '#/$defs/literalInput' });
15
19
  const refData = Object.freeze({ $ref: '#/$defs/refData' });
16
20
  const cellRange = Object.freeze({ $ref: '#/$defs/cellRange' });
17
21
  const engineOp = Object.freeze({ $ref: '#/$defs/engineOp' });
18
22
  const actionInput = Object.freeze({ $ref: '#/$defs/actionInput' });
19
23
  const actionInputDescription = Object.freeze({ $ref: '#/$defs/actionInputDescription' });
24
+ const exactString = Object.freeze({
25
+ type: 'string',
26
+ minLength: 1,
27
+ pattern: '^(?!\\s)(?![\\s\\S]*\\s$)[\\s\\S]+$',
28
+ });
29
+ const nonNegativeSafeInteger = Object.freeze({
30
+ type: 'integer',
31
+ minimum: 0,
32
+ maximum: Number.MAX_SAFE_INTEGER,
33
+ });
20
34
  function defs(extra = {}) {
21
35
  return {
22
36
  jsonValue: {
@@ -29,6 +43,7 @@ function defs(extra = {}) {
29
43
  { type: 'object', additionalProperties: { $ref: '#/$defs/jsonValue' } },
30
44
  ],
31
45
  },
46
+ literalInput: literalInputSchema,
32
47
  actionInput: { $ref: '#/$defs/jsonValue' },
33
48
  actionInputDescription: {
34
49
  type: 'object',
@@ -47,11 +62,11 @@ function defs(extra = {}) {
47
62
  values: { type: 'array', minItems: 1, items: actionInput },
48
63
  min: { type: 'number' },
49
64
  max: { type: 'number' },
50
- minLength: { type: 'integer', minimum: 0 },
51
- maxLength: { type: 'integer', minimum: 0 },
65
+ minLength: nonNegativeSafeInteger,
66
+ maxLength: nonNegativeSafeInteger,
52
67
  pattern: { type: 'string' },
53
- minItems: { type: 'integer', minimum: 0 },
54
- maxItems: { type: 'integer', minimum: 0 },
68
+ minItems: nonNegativeSafeInteger,
69
+ maxItems: nonNegativeSafeInteger,
55
70
  additionalProperties: { type: 'boolean' },
56
71
  default: actionInput,
57
72
  examples: { type: 'array', minItems: 1, items: actionInput },
@@ -93,87 +108,21 @@ function defs(extra = {}) {
93
108
  endAddress: { type: 'string', minLength: 1 },
94
109
  },
95
110
  },
96
- engineOp: {
111
+ cellStylePatch: cellStylePatchSchema,
112
+ actionCellStylePatch: workbookActionCellStylePatchSchema,
113
+ formulaLabel: {
97
114
  type: 'object',
98
- required: ['kind'],
115
+ required: ['name', 'ref'],
116
+ additionalProperties: false,
99
117
  properties: {
100
- kind: { type: 'string', minLength: 1 },
118
+ name: { type: 'string', minLength: 1 },
119
+ ref: refData,
101
120
  },
102
- additionalProperties: true,
103
121
  },
122
+ engineOp: workbookOpSchema,
123
+ ...workbookRefDataDefSchemas,
104
124
  refData: {
105
- oneOf: [
106
- {
107
- type: 'object',
108
- required: ['kind', 'id', 'label', 'range'],
109
- additionalProperties: false,
110
- properties: {
111
- kind: { const: 'range' },
112
- id: { type: 'string', minLength: 1 },
113
- label: { type: 'string', minLength: 1 },
114
- range: { $ref: '#/$defs/cellRange' },
115
- },
116
- },
117
- {
118
- type: 'object',
119
- required: ['kind', 'id', 'label', 'name'],
120
- additionalProperties: false,
121
- properties: {
122
- kind: { const: 'name' },
123
- id: { type: 'string', minLength: 1 },
124
- label: { type: 'string', minLength: 1 },
125
- name: { type: 'string', minLength: 1 },
126
- },
127
- },
128
- {
129
- type: 'object',
130
- required: ['kind', 'id', 'label'],
131
- additionalProperties: false,
132
- properties: {
133
- kind: { const: 'table' },
134
- id: { type: 'string', minLength: 1 },
135
- label: { type: 'string', minLength: 1 },
136
- name: { type: 'string', minLength: 1 },
137
- sheetName: { type: 'string', minLength: 1 },
138
- headers: { type: 'array', items: { type: 'string' } },
139
- },
140
- },
141
- {
142
- type: 'object',
143
- required: ['kind', 'id', 'label', 'table', 'name'],
144
- additionalProperties: false,
145
- properties: {
146
- kind: { const: 'column' },
147
- id: { type: 'string', minLength: 1 },
148
- label: { type: 'string', minLength: 1 },
149
- table: { $ref: '#/$defs/refData' },
150
- rows: { $ref: '#/$defs/refData' },
151
- name: { type: 'string', minLength: 1 },
152
- },
153
- },
154
- {
155
- type: 'object',
156
- required: ['kind', 'id', 'label', 'where'],
157
- additionalProperties: false,
158
- properties: {
159
- kind: { const: 'rows' },
160
- id: { type: 'string', minLength: 1 },
161
- label: { type: 'string', minLength: 1 },
162
- sheetName: { type: 'string', minLength: 1 },
163
- table: { $ref: '#/$defs/refData' },
164
- where: {
165
- type: 'object',
166
- required: ['column', 'op', 'value'],
167
- additionalProperties: false,
168
- properties: {
169
- column: { type: 'string', minLength: 1 },
170
- op: { enum: ['eq', 'neq', 'contains', 'startsWith', 'gt', 'gte', 'lt', 'lte'] },
171
- value: { $ref: '#/$defs/jsonValue' },
172
- },
173
- },
174
- },
175
- },
176
- ],
125
+ oneOf: workbookRefDataSchemaRefs,
177
126
  },
178
127
  concreteRefData: {
179
128
  type: 'object',
@@ -217,11 +166,35 @@ const checkDataSchema = {
217
166
  target: refData,
218
167
  refs: { type: 'array', items: refData },
219
168
  message: { type: 'string', minLength: 1 },
220
- expectation: { type: 'object', additionalProperties: true },
169
+ expectation: { $ref: '#/$defs/checkExpectation' },
221
170
  proof: actionInput,
222
171
  },
223
172
  additionalProperties: false,
224
173
  };
174
+ const checkExpectationSchema = {
175
+ oneOf: [
176
+ {
177
+ type: 'object',
178
+ required: ['kind', 'value'],
179
+ additionalProperties: false,
180
+ properties: {
181
+ kind: { const: 'valueEquals' },
182
+ value: literalInput,
183
+ },
184
+ },
185
+ {
186
+ type: 'object',
187
+ required: ['kind', 'formula', 'inputs', 'labels'],
188
+ additionalProperties: false,
189
+ properties: {
190
+ kind: { const: 'formulaEquals' },
191
+ formula: { type: 'string' },
192
+ inputs: { type: 'array', items: refData },
193
+ labels: { type: 'array', items: { $ref: '#/$defs/formulaLabel' } },
194
+ },
195
+ },
196
+ ],
197
+ };
225
198
  const changeDataSchema = {
226
199
  type: 'object',
227
200
  required: ['kind', 'message'],
@@ -232,15 +205,6 @@ const changeDataSchema = {
232
205
  message: { type: 'string', minLength: 1 },
233
206
  },
234
207
  };
235
- const undoDataSchema = {
236
- type: 'object',
237
- required: ['id'],
238
- additionalProperties: false,
239
- properties: {
240
- id: { type: 'string', minLength: 1 },
241
- ops: { type: 'array', items: engineOp },
242
- },
243
- };
244
208
  const unverifiedDataSchema = {
245
209
  type: 'object',
246
210
  required: ['kind', 'message'],
@@ -257,9 +221,9 @@ const runtimeRequirementSchema = {
257
221
  properties: {
258
222
  kind: { enum: ['apply', 'read', 'verify'] },
259
223
  capability: { enum: ['writeFormula', 'writeValue', 'format', 'clear', 'applyOp', 'read', 'verifyCheck'] },
260
- commandIndex: { type: 'integer', minimum: 0 },
261
- checkIndex: { type: 'integer', minimum: 0 },
262
- opIndex: { type: 'integer', minimum: 0 },
224
+ commandIndex: nonNegativeSafeInteger,
225
+ checkIndex: nonNegativeSafeInteger,
226
+ opIndex: nonNegativeSafeInteger,
263
227
  opKind: { type: 'string', minLength: 1 },
264
228
  checkKind: { type: 'string', minLength: 1 },
265
229
  target: refData,
@@ -291,8 +255,8 @@ const applySummarySchema = {
291
255
  properties: {
292
256
  matched: { oneOf: [{ type: 'boolean' }, { type: 'null' }] },
293
257
  planId: { type: 'string', minLength: 1 },
294
- baseRevision: { type: 'integer', minimum: 0 },
295
- revision: { type: 'integer', minimum: 0 },
258
+ baseRevision: nonNegativeSafeInteger,
259
+ revision: nonNegativeSafeInteger,
296
260
  previewOps: { type: 'array', items: engineOp },
297
261
  appliedOps: { type: 'array', items: engineOp },
298
262
  commandReceipts: { type: 'array', items: { $ref: '#/$defs/applyCommandReceipt' } },
@@ -304,11 +268,33 @@ const applyCommandReceiptSchema = {
304
268
  required: ['commandIndex', 'commandKind', 'commandDigest', 'previewOps', 'appliedOps'],
305
269
  additionalProperties: false,
306
270
  properties: {
307
- commandIndex: { type: 'integer', minimum: 0 },
271
+ commandIndex: nonNegativeSafeInteger,
308
272
  commandKind: { type: 'string', minLength: 1 },
309
273
  commandDigest: { type: 'string', minLength: 1 },
310
274
  previewOps: { type: 'array', items: engineOp },
311
275
  appliedOps: { type: 'array', items: engineOp },
276
+ noop: {
277
+ type: 'object',
278
+ required: ['reason', 'proof'],
279
+ additionalProperties: false,
280
+ properties: {
281
+ reason: { enum: ['already_satisfied'] },
282
+ message: { type: 'string', minLength: 1 },
283
+ proof: {
284
+ type: 'object',
285
+ required: ['source', 'evidence', 'opCount', 'commandKind', 'commandDigest', 'effect'],
286
+ additionalProperties: true,
287
+ properties: {
288
+ source: exactString,
289
+ evidence: exactString,
290
+ opCount: { const: 0 },
291
+ commandKind: exactString,
292
+ commandDigest: exactString,
293
+ effect: { $ref: '#/$defs/noopEffect' },
294
+ },
295
+ },
296
+ },
297
+ },
312
298
  resolvedRefs: { $ref: '#/$defs/commandResolvedRefs' },
313
299
  formulaLabels: {
314
300
  type: 'array',
@@ -324,6 +310,19 @@ const applyCommandReceiptSchema = {
324
310
  },
325
311
  proof: actionInput,
326
312
  },
313
+ allOf: [
314
+ {
315
+ if: { required: ['noop'] },
316
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-thenable) -- JSON Schema conditional schemas use the standard "then" keyword.
317
+ then: {
318
+ properties: {
319
+ previewOps: { type: 'array', maxItems: 0, items: engineOp },
320
+ appliedOps: { type: 'array', maxItems: 0, items: engineOp },
321
+ },
322
+ },
323
+ },
324
+ ...noopEffectCommandKindConditions(),
325
+ ],
327
326
  };
328
327
  export const workbookJsonSchemas = deepFreeze({
329
328
  refData: schema('refData', {
@@ -356,15 +355,7 @@ export const workbookJsonSchemas = deepFreeze({
356
355
  }),
357
356
  planData: schema('planData', {
358
357
  $defs: defs({
359
- formulaLabel: {
360
- type: 'object',
361
- required: ['name', 'ref'],
362
- additionalProperties: false,
363
- properties: {
364
- name: { type: 'string', minLength: 1 },
365
- ref: refData,
366
- },
367
- },
358
+ checkExpectation: checkExpectationSchema,
368
359
  check: checkDataSchema,
369
360
  command: {
370
361
  oneOf: [
@@ -384,7 +375,7 @@ export const workbookJsonSchemas = deepFreeze({
384
375
  type: 'object',
385
376
  required: ['kind', 'target', 'value'],
386
377
  additionalProperties: false,
387
- properties: { kind: { const: 'writeValue' }, target: refData, value: jsonValue },
378
+ properties: { kind: { const: 'writeValue' }, target: refData, value: literalInput },
388
379
  },
389
380
  {
390
381
  type: 'object',
@@ -393,9 +384,10 @@ export const workbookJsonSchemas = deepFreeze({
393
384
  properties: {
394
385
  kind: { const: 'format' },
395
386
  target: refData,
396
- style: { type: 'object', additionalProperties: true },
387
+ style: { $ref: '#/$defs/actionCellStylePatch' },
397
388
  numberFormat: { oneOf: [{ type: 'string' }, { type: 'null' }] },
398
389
  },
390
+ anyOf: [{ required: ['style'] }, { required: ['numberFormat'] }],
399
391
  },
400
392
  {
401
393
  type: 'object',
@@ -452,8 +444,8 @@ export const workbookJsonSchemas = deepFreeze({
452
444
  required: ['featureId', 'commandId'],
453
445
  additionalProperties: false,
454
446
  properties: {
455
- featureId: { type: 'string', minLength: 1 },
456
- commandId: { type: 'string', minLength: 1 },
447
+ featureId: exactString,
448
+ commandId: exactString,
457
449
  category: { enum: ['command', 'operation', 'mutation'] },
458
450
  mode: { enum: ['preview', 'apply', 'applyAndVerify'] },
459
451
  input: actionInput,
@@ -467,21 +459,57 @@ export const workbookJsonSchemas = deepFreeze({
467
459
  additionalProperties: false,
468
460
  properties: {
469
461
  kind: { const: 'request' },
470
- id: { type: 'string', minLength: 1 },
462
+ id: exactString,
471
463
  touchedRanges: { type: 'array', items: cellRange },
472
464
  destructive: { type: 'boolean' },
473
465
  request: { $ref: '#/$defs/commandRequest' },
474
466
  },
467
+ allOf: [
468
+ {
469
+ if: {
470
+ properties: {
471
+ request: {
472
+ type: 'object',
473
+ properties: { category: { const: 'mutation' } },
474
+ required: ['category'],
475
+ },
476
+ },
477
+ required: ['request'],
478
+ },
479
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-thenable) -- JSON Schema conditional schemas use the standard "then" keyword.
480
+ then: {
481
+ required: ['destructive'],
482
+ properties: { destructive: { const: true } },
483
+ },
484
+ },
485
+ {
486
+ if: {
487
+ properties: {
488
+ request: {
489
+ type: 'object',
490
+ properties: { mode: { enum: ['apply', 'applyAndVerify'] } },
491
+ required: ['mode'],
492
+ },
493
+ },
494
+ required: ['request'],
495
+ },
496
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-thenable) -- JSON Schema conditional schemas use the standard "then" keyword.
497
+ then: {
498
+ required: ['destructive'],
499
+ properties: { destructive: { const: true } },
500
+ },
501
+ },
502
+ ],
475
503
  },
476
504
  {
477
505
  type: 'object',
478
- required: ['kind', 'op'],
506
+ required: ['kind', 'destructive', 'op'],
479
507
  additionalProperties: false,
480
508
  properties: {
481
509
  kind: { const: 'op' },
482
- id: { type: 'string', minLength: 1 },
510
+ id: exactString,
483
511
  touchedRanges: { type: 'array', items: cellRange },
484
- destructive: { type: 'boolean' },
512
+ destructive: { const: true },
485
513
  op: engineOp,
486
514
  },
487
515
  },
@@ -492,60 +520,152 @@ export const workbookJsonSchemas = deepFreeze({
492
520
  required: ['targetRevision', 'idempotencyKey', 'commands'],
493
521
  additionalProperties: false,
494
522
  properties: {
495
- id: { type: 'string', minLength: 1 },
496
- targetRevision: { type: 'integer', minimum: 0 },
497
- idempotencyKey: { type: 'string', minLength: 1 },
523
+ id: exactString,
524
+ targetRevision: nonNegativeSafeInteger,
525
+ idempotencyKey: exactString,
498
526
  scope: {
499
527
  type: 'object',
500
528
  additionalProperties: false,
501
- properties: { maxTouchedCells: { type: 'integer', minimum: 0 } },
529
+ properties: { maxTouchedCells: nonNegativeSafeInteger },
502
530
  },
503
531
  commands: { type: 'array', minItems: 1, items: { $ref: '#/$defs/bundleCommand' } },
504
532
  },
533
+ allOf: [
534
+ {
535
+ if: {
536
+ properties: {
537
+ scope: {
538
+ type: 'object',
539
+ required: ['maxTouchedCells'],
540
+ },
541
+ },
542
+ required: ['scope'],
543
+ },
544
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-thenable) -- JSON Schema conditional schemas use the standard "then" keyword.
545
+ then: {
546
+ properties: {
547
+ commands: {
548
+ items: {
549
+ allOf: [
550
+ {
551
+ if: {
552
+ properties: { kind: { const: 'op' } },
553
+ required: ['kind'],
554
+ },
555
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-thenable) -- JSON Schema conditional schemas use the standard "then" keyword.
556
+ then: {
557
+ required: ['touchedRanges'],
558
+ properties: { touchedRanges: { type: 'array', minItems: 1, items: cellRange } },
559
+ },
560
+ },
561
+ {
562
+ if: {
563
+ properties: {
564
+ kind: { const: 'request' },
565
+ request: {
566
+ type: 'object',
567
+ properties: { category: { const: 'mutation' } },
568
+ required: ['category'],
569
+ },
570
+ },
571
+ required: ['kind', 'request'],
572
+ },
573
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-thenable) -- JSON Schema conditional schemas use the standard "then" keyword.
574
+ then: {
575
+ required: ['touchedRanges'],
576
+ properties: { touchedRanges: { type: 'array', minItems: 1, items: cellRange } },
577
+ },
578
+ },
579
+ {
580
+ if: {
581
+ properties: {
582
+ kind: { const: 'request' },
583
+ request: {
584
+ type: 'object',
585
+ properties: { mode: { enum: ['apply', 'applyAndVerify'] } },
586
+ required: ['mode'],
587
+ },
588
+ },
589
+ required: ['kind', 'request'],
590
+ },
591
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-thenable) -- JSON Schema conditional schemas use the standard "then" keyword.
592
+ then: {
593
+ required: ['touchedRanges'],
594
+ properties: { touchedRanges: { type: 'array', minItems: 1, items: cellRange } },
595
+ },
596
+ },
597
+ ],
598
+ },
599
+ },
600
+ },
601
+ },
602
+ },
603
+ ],
505
604
  }),
506
605
  commandResult: schema('commandResult', {
507
606
  $defs: defs({
508
- receipt: {
509
- type: 'object',
510
- required: ['status', 'featureId', 'commandId', 'category'],
511
- properties: {
512
- status: { enum: ['previewed', 'applied', 'rejected', 'noop'] },
513
- featureId: { type: 'string', minLength: 1 },
514
- commandId: { type: 'string', minLength: 1 },
515
- category: { enum: ['command', 'operation', 'mutation'] },
516
- previewOps: { type: 'array', items: engineOp },
517
- appliedOps: { type: 'array', items: engineOp },
518
- undo: { type: 'object', additionalProperties: true },
519
- changedRanges: { type: 'array', items: cellRange },
520
- proof: actionInput,
521
- message: { type: 'string' },
522
- metadata: actionInput,
523
- errors: { type: 'array', items: { type: 'string' } },
524
- },
525
- additionalProperties: false,
526
- },
607
+ undo: workbookUndoRefSchema,
608
+ receipt: workbookCommandReceiptSchema,
527
609
  }),
528
610
  type: 'object',
529
611
  required: ['status', 'targetRevision', 'idempotencyKey', 'commandCount', 'touchedRanges', 'touchedCellCount'],
530
612
  additionalProperties: false,
531
613
  properties: {
532
614
  status: { enum: ['accepted', 'previewed', 'applied', 'rejected', 'noop'] },
533
- bundleId: { type: 'string', minLength: 1 },
534
- targetRevision: { type: 'integer', minimum: 0 },
535
- idempotencyKey: { type: 'string', minLength: 1 },
536
- commandCount: { type: 'integer', minimum: 0 },
615
+ bundleId: exactString,
616
+ targetRevision: nonNegativeSafeInteger,
617
+ idempotencyKey: exactString,
618
+ commandCount: nonNegativeSafeInteger,
537
619
  touchedRanges: { type: 'array', items: cellRange },
538
- touchedCellCount: { type: 'integer', minimum: 0 },
620
+ touchedCellCount: nonNegativeSafeInteger,
539
621
  receipts: { type: 'array', items: { $ref: '#/$defs/receipt' } },
540
622
  matched: { oneOf: [{ type: 'boolean' }, { type: 'null' }] },
541
623
  changedRanges: { type: 'array', items: cellRange },
542
- revision: { type: 'integer', minimum: 0 },
543
- undo: { type: 'object', additionalProperties: true },
544
- errors: { type: 'array', items: { type: 'string' } },
624
+ revision: nonNegativeSafeInteger,
625
+ undo: { $ref: '#/$defs/undo' },
626
+ errors: { type: 'array', items: exactString },
545
627
  },
628
+ allOf: [
629
+ {
630
+ if: { properties: { status: { const: 'accepted' } }, required: ['status'] },
631
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-thenable) -- JSON Schema conditional schemas use the standard "then" keyword.
632
+ then: {
633
+ not: {
634
+ anyOf: [
635
+ { required: ['receipts'] },
636
+ { required: ['matched'] },
637
+ { required: ['changedRanges'] },
638
+ { required: ['revision'] },
639
+ { required: ['undo'] },
640
+ { required: ['errors'] },
641
+ ],
642
+ },
643
+ },
644
+ },
645
+ {
646
+ if: {
647
+ properties: { status: { enum: ['previewed', 'applied', 'rejected', 'noop'] } },
648
+ required: ['status'],
649
+ },
650
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-thenable) -- JSON Schema conditional schemas use the standard "then" keyword.
651
+ then: {
652
+ required: ['receipts', 'matched', 'changedRanges'],
653
+ properties: {
654
+ receipts: { type: 'array', minItems: 1, items: { $ref: '#/$defs/receipt' } },
655
+ },
656
+ },
657
+ },
658
+ ],
546
659
  }),
547
660
  runResult: schema('runResult', {
548
661
  $defs: defs({
662
+ noopEffect: createNoopEffectSchema({
663
+ literalInput,
664
+ exactString,
665
+ engineOp,
666
+ actionCellStylePatch: { $ref: '#/$defs/actionCellStylePatch' },
667
+ }),
668
+ checkExpectation: checkExpectationSchema,
549
669
  apply: applySummarySchema,
550
670
  applyCommandReceipt: applyCommandReceiptSchema,
551
671
  check: checkDataSchema,
@@ -561,7 +681,7 @@ export const workbookJsonSchemas = deepFreeze({
561
681
  issueCode: { type: 'string' },
562
682
  },
563
683
  },
564
- undo: undoDataSchema,
684
+ undo: workbookUndoRefSchema,
565
685
  unverified: unverifiedDataSchema,
566
686
  }),
567
687
  oneOf: [
@@ -596,6 +716,7 @@ export const workbookJsonSchemas = deepFreeze({
596
716
  }),
597
717
  readbackProof: schema('readbackProof', {
598
718
  $defs: defs({
719
+ checkExpectation: checkExpectationSchema,
599
720
  check: checkDataSchema,
600
721
  readback: {
601
722
  type: 'object',