@bilig/workbook 0.94.0 → 0.103.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 (62) hide show
  1. package/README.md +167 -438
  2. package/dist/command.d.ts +3 -0
  3. package/dist/command.js +4 -0
  4. package/dist/command.js.map +1 -0
  5. package/dist/features-public.d.ts +2 -0
  6. package/dist/features-public.js +3 -0
  7. package/dist/features-public.js.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +3 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/model-plan-result.d.ts +1 -0
  12. package/dist/model-plan-result.js +17 -0
  13. package/dist/model-plan-result.js.map +1 -1
  14. package/dist/model.d.ts +1 -0
  15. package/dist/model.js +21 -14
  16. package/dist/model.js.map +1 -1
  17. package/dist/prepare.d.ts +22 -0
  18. package/dist/prepare.js +41 -0
  19. package/dist/prepare.js.map +1 -0
  20. package/dist/readback.d.ts +16 -0
  21. package/dist/readback.js +57 -4
  22. package/dist/readback.js.map +1 -1
  23. package/dist/requirements.d.ts +4 -3
  24. package/dist/requirements.js +50 -7
  25. package/dist/requirements.js.map +1 -1
  26. package/dist/result.d.ts +2 -2
  27. package/dist/result.js +1 -0
  28. package/dist/result.js.map +1 -1
  29. package/dist/run-apply.d.ts +17 -0
  30. package/dist/run-apply.js +274 -0
  31. package/dist/run-apply.js.map +1 -0
  32. package/dist/run-check-verification.d.ts +8 -0
  33. package/dist/run-check-verification.js +174 -0
  34. package/dist/run-check-verification.js.map +1 -0
  35. package/dist/run-data.d.ts +6 -0
  36. package/dist/run-data.js +120 -0
  37. package/dist/run-data.js.map +1 -0
  38. package/dist/run-description.js +12 -9
  39. package/dist/run-description.js.map +1 -1
  40. package/dist/run-runtime-boundary.js +35 -12
  41. package/dist/run-runtime-boundary.js.map +1 -1
  42. package/dist/run.d.ts +2 -2
  43. package/dist/run.js +4 -560
  44. package/dist/run.js.map +1 -1
  45. package/dist/runtime.d.ts +7 -0
  46. package/dist/runtime.js +8 -0
  47. package/dist/runtime.js.map +1 -0
  48. package/dist/schema.d.ts +22 -0
  49. package/dist/schema.js +581 -0
  50. package/dist/schema.js.map +1 -0
  51. package/dist/testing.d.ts +34 -0
  52. package/dist/testing.js +85 -0
  53. package/dist/testing.js.map +1 -0
  54. package/fixtures/command-bundle.json +28 -0
  55. package/fixtures/command-result.json +58 -0
  56. package/fixtures/invalid-plan.json +9 -0
  57. package/fixtures/readback-proof.json +64 -0
  58. package/fixtures/runtime-requirements.json +72 -0
  59. package/fixtures/strict-run-failure.json +11 -0
  60. package/fixtures/strict-run-success.json +118 -0
  61. package/fixtures/valid-plan.json +145 -0
  62. package/package.json +60 -4
package/README.md CHANGED
@@ -7,280 +7,135 @@ predictable, inspectable, verifiable, and never dependent on hardcoded business
7
7
  models or human spreadsheet UI assumptions.
8
8
 
9
9
  Use this package when a consumer wants to define their own workbook model and
10
- hand a runtime a portable plan. The package does not import the engine, start a
11
- server, calculate formulas, ship revenue/quote/forecast models, or depend on
12
- `zod`, `effect`, `@bilig/core`, `@bilig/headless`, or `@bilig/agent-api`.
10
+ hand a runtime a portable plan. Bilig supplies the generic model API, selectors,
11
+ formula helpers, checks, JSON-safe transport data, validators, and run-result
12
+ proof shapes. It does not import an engine, start a server, calculate formulas,
13
+ ship business templates, or depend on `@bilig/core`, `@bilig/headless`,
14
+ `@bilig/agent-api`, `zod`, or `effect`.
13
15
 
14
16
  ```sh
15
17
  pnpm add @bilig/workbook
16
18
  ```
17
19
 
20
+ ## Use These First
21
+
22
+ Most consumers should start with only these names:
23
+
24
+ - `defineModel`
25
+ - `formula`
26
+ - `prepareWorkbookAction`
27
+ - `runWorkbookPlan`
28
+ - `describeModel`, `describePlan`, `describeRunResult`
29
+
30
+ That path lets an agent define intent, inspect it before execution, transport it
31
+ as plain data, run it through a runtime-owned adapter, and verify the returned
32
+ proof without knowing anything about a rendered spreadsheet UI.
33
+
18
34
  ## The Shape
19
35
 
20
36
  ```ts
21
- import {
22
- defineModel,
23
- describePlan,
24
- describeRunResult,
25
- describeRuntimeRequirements,
26
- formula,
27
- planWorkbookAction,
28
- runWorkbookPlan,
29
- toPlanData,
30
- verifyPlan,
31
- verifyPlanData,
32
- } from '@bilig/workbook'
37
+ import { defineModel, describeRunResult, formula, prepareWorkbookAction, runWorkbookPlan } from '@bilig/workbook'
33
38
 
34
39
  export const model = defineModel({
35
- name: 'generic-row-calculator',
40
+ name: 'named-range-formula',
36
41
 
37
42
  find(workbook) {
38
- const table = workbook.findTable({
39
- headers: ['Item', 'Quantity', 'Rate', 'Status', 'Total'],
40
- })
41
- const rows = workbook.findRows({
42
- table,
43
- where: { column: 'Status', op: 'eq', value: 'ready' },
44
- })
45
-
46
43
  return {
47
- table,
48
- rows,
49
- quantity: rows.column('Quantity'),
50
- rate: rows.column('Rate'),
51
- total: rows.column('Total'),
44
+ input: workbook.findName('input'),
45
+ factor: workbook.findName('factor'),
46
+ result: workbook.findName('result'),
52
47
  }
53
48
  },
54
49
 
55
50
  checks({ refs, workbook }) {
56
- return [workbook.check.exists(refs.table), workbook.check.noFormulaErrors(refs.total)]
51
+ return [workbook.check.exists(refs.result), workbook.check.noFormulaErrors(refs.result)]
57
52
  },
58
53
 
59
54
  actions: {
60
- recompute({ refs, workbook }) {
61
- const expected = formula.multiply(refs.quantity, refs.rate)
62
- workbook.writeFormula(refs.total, expected)
63
- workbook.check.formulaEquals(refs.total, expected)
55
+ calculate({ refs, workbook }) {
56
+ const expected = formula.multiply(refs.input, refs.factor)
57
+ workbook.writeFormula(refs.result, expected)
58
+ workbook.check.formulaEquals(refs.result, expected)
64
59
  },
65
60
  },
66
61
  })
67
62
 
68
- const planned = planWorkbookAction(model, 'recompute')
69
- if (planned.status === 'failed') throw new Error(planned.errors[0]?.message)
70
-
71
- const staticProof = verifyPlan(planned.plan)
72
- const requirements = describeRuntimeRequirements(planned.plan)
73
- const planForLogs = describePlan(planned.plan)
74
- const transportedPlan = JSON.parse(JSON.stringify(toPlanData(planned.plan)))
75
- const transportProof = verifyPlanData(transportedPlan)
63
+ const prepared = prepareWorkbookAction(model, 'calculate')
64
+ if (prepared.status === 'failed') throw new Error(prepared.errors[0]?.message)
76
65
 
77
- const result = await runWorkbookPlan(transportedPlan, adapter)
66
+ const result = await runWorkbookPlan(prepared.planData, adapter, { strict: true })
78
67
  const resultForLogs = describeRunResult(result)
79
68
  ```
80
69
 
81
- That is the core flow:
70
+ The core flow is deliberately boring:
82
71
 
83
72
  1. `defineModel` freezes a consumer-defined model.
84
73
  2. `find` returns generic refs.
85
74
  3. `checks` declares facts the runtime must prove.
86
75
  4. An action builds workbook intent.
87
- 5. `verifyPlan` checks the plan without running an engine.
88
- 6. `describeRuntimeRequirements` tells an adapter what it must apply, read, and prove.
89
- 7. `toPlanData` makes the plan JSON-safe for handoff.
90
- 8. `workbookPlanId` gives that exact plan a stable id for runtime proof.
91
- 9. `runWorkbookPlan` applies either the in-memory plan or transported plan data through a runtime-owned adapter and returns a boring result with check proof and apply proof when the adapter provides it.
92
-
93
- ## Public Contract
94
-
95
- The main API is intentionally small:
96
-
97
- - model: `defineModel`, `inspectModel`, `planWorkbookAction`, `buildWorkbookActionPlan`
98
- - selectors: `findTable`, `findColumn`, `findRange`, `findName`, `findRows`, `find`
99
- - checks: `check.exists`, `check.noFormulaErrors`, `check.valueEquals`, `check.formulaEquals`, `check.custom`
100
- - formulas: `formula.add`, `formula.subtract`, `formula.multiply`, `formula.divide`, `formula.sum`, `formula.call`, `formula.raw`, `formula.text`, `formula.labels`
101
- - input: `checkInput`, `normalizeWorkbookActionInputDescription`
102
- - proof: `verifyPlan`, `verifyModel`, `verifyWorkbookReadbacks`
103
- - descriptions: `describeModel`, `describeRef`, `describePlan`, `describePlanResult`, `describeRuntimeRequirements`, `checkRuntimeRequirements`, `checkRuntimeAdapter`, `describeRunResult`
104
- - transport data: `isWorkbookRefData`, `toWorkbookRefData`, `collectWorkbookRefData`, `hydrateWorkbookRef`, `hydrateWorkbookRefs`, `toPlanData`, `workbookPlanId`, `isPlanData`, `checkPlanData`, `hydratePlanData`, `verifyPlanData`
105
- - runtime handoff: `runWorkbookPlan`, `runWorkbookAction`, `WorkbookRunAdapter`
106
- - apply proof: `workbookPlanId`, `workbookActionCommandDigest`, command-level `commandReceipts`
107
- - feature handoff: `defineWorkbookFeaturePlugin`, `checkWorkbookFeaturePlugin`, `checkWorkbookCommandRequest`, `normalizeWorkbookCommandRequest`, `checkWorkbookCommandBundle`, `normalizeWorkbookCommandBundle`, `workbookCommandResultFor`, `workbookCommandResultForReceipts`, `workbookOpCommandReceiptIdentity`, `workbookOpCommandReceipt`, `checkWorkbookCommandResult`, `checkWorkbookCommandResultForBundle`, `normalizeWorkbookCommandResult`, `checkWorkbookCommandReceipt`, `normalizeWorkbookCommandReceipt`, `workbookCommandReceiptOpsMatch`
108
- - low-level language: `WorkbookOp`, `WorkbookTxn`, `EngineOp`, `EngineOpBatch`, `isEngineOpBatch`
109
-
110
- Stable data helpers are exported for generic tool builders:
111
-
112
- - `workbookRefKinds`, `isWorkbookRefKind`, `checkWorkbookRef`, `isWorkbookRef`
113
- - `checkWorkbookRefData`, `isWorkbookRefData`, `toWorkbookRefData`, `collectWorkbookRefData`, `hydrateWorkbookRef`, `hydrateWorkbookRefs`
114
- - `isPlanData`, `checkPlanData`, `workbookPlanId`
115
- - `workbookRowOperators`, `workbookRowOperatorValueTypes`, `isWorkbookRowOperator`, `isWorkbookRowValueCompatible`
116
- - `builtInWorkbookCheckKinds`, `isBuiltInWorkbookCheckKind`
117
- - `workbookActionInputDescriptionKinds`, `isWorkbookActionInputDescriptionKind`, `isWorkbookActionInputDescription`, `isWorkbookActionInput`, `checkInput`
118
- - `workbookRuntimeRequirementKinds`, `isWorkbookRuntimeRequirementKind`, `workbookRuntimeCapabilities`, `isWorkbookRuntimeCapability`, `checkRuntimeRequirements`
119
- - `workbookCommandCategories`, `isWorkbookCommandCategory`, `workbookCommandExecutionModes`, `isWorkbookCommandExecutionMode`, `workbookCommandReceiptStatuses`, `isWorkbookCommandReceiptStatus`, `workbookCommandResultStatuses`, `isWorkbookCommandResultStatus`
120
- - `workbookProjectionInterceptorPoints`, `isWorkbookProjectionInterceptorPoint`, `workbookUiContributionSlots`, `isWorkbookUiContributionSlot`, `checkWorkbookCommandRequest`
121
- - `workbookCommandBundleCommandKinds`, `isWorkbookCommandBundleCommandKind`, `checkWorkbookCommandBundle`, `isWorkbookCommandBundle`, `workbookCommandResultFor`, `workbookCommandResultForReceipts`, `workbookOpCommandFeatureId`, `workbookOpCommandReceiptIdentity`, `workbookOpCommandReceipt`, `checkWorkbookCommandResult`, `checkWorkbookCommandResultForBundle`, `isWorkbookCommandResult`, `isWorkbookCommandResultForBundle`
122
- - `workbookRunErrorCodes`, `isWorkbookRunErrorCode`, `checkWorkbookRunResultDescription`, `isWorkbookRunResultDescription`
123
-
124
- Model action manifests are frozen null-prototype maps. Consumers can use normal
125
- business-agnostic action names, including names such as `toString` or
126
- `constructor`, and `planWorkbookAction` only runs own actions from the manifest.
127
- Prototype-inherited actions are ignored, so an agent can treat the action list as
128
- the full executable surface.
129
- Public helper namespaces are frozen as well: `find`, `check`, and `formula`
130
- cannot be patched after import, and factory-created check/find helpers return
131
- frozen API objects too.
132
- Model config and action objects are read as data too: `defineModel` requires
133
- object-record model roots plus own data properties for `actions` entries and
134
- for action-object `run`, `description`, and `input`. Accessor-backed model
135
- metadata is rejected before any getter can run.
136
- `inspectModel` and `describeModel` use the same manifest boundary, so model
137
- names, descriptions, action maps, and action metadata can be inspected without
138
- triggering hidden getters. Class/custom-prototype model roots are rejected, while
139
- action maps and action objects keep their existing own-field-only prototype
140
- behavior. `inspectModel` returns a frozen manifest snapshot.
141
- The description layer is frozen too: `describeRef`, `describePlan`,
142
- `describePlanResult`, and `describeRunResult` return JSON-safe objects whose
143
- nested refs, commands, checks, apply proof, undo ops, and errors cannot be
144
- mutated after an agent has inspected them.
145
- Validation and proof results follow the same rule: `checkInput`,
146
- `checkPlanData`, `verifyPlan`, `verifyModel`, and `verifyWorkbookReadbacks`
147
- return frozen verdict containers, arrays, generated issues, and readback-derived
148
- checks.
149
- Ref, ref-data, feature, command, receipt, result, run-result-description, and
150
- runtime-adapter validators return frozen verdicts too, so every public
151
- `{ status, issues }` handoff has the same inspect-once behavior.
152
- Feature plugin manifests, nested command descriptors, projection interceptors,
153
- and UI contributions must be object-record data. Class/custom-prototype feature
154
- metadata is rejected before registration, and exported command descriptor
155
- normalization reads own data properties without invoking accessors.
156
- `runWorkbookPlan` and `runWorkbookAction` return frozen run results too,
157
- including changed summaries, checks, errors, apply proof, undo refs, and
158
- unverified proof notes.
159
- `planWorkbookAction` also validates that boundary before reading action metadata
160
- or running model code. Invalid manifests return a structured `invalid_model`
161
- failure instead of making the agent catch an accessor side effect. Planned and
162
- failed action-plan result wrappers are frozen before they are returned.
163
- Action helper calls fail closed during planning too: write, clear, format, and
164
- low-level-op helpers reject malformed targets, non-literal values, invalid
165
- format options, invalid add-op options, class/custom-prototype option roots, and
166
- accessor-backed op payloads before a plan is returned.
167
- Check helper calls use the same boundary: malformed targets, readback options,
168
- custom check options, class/custom-prototype option roots, sparse ref arrays, and
169
- accessor-backed check payloads are rejected before a check is recorded.
170
- Formula helpers also normalize as plain data: raw formula options, explicit
171
- inputs, labels, and function argument arrays reject sparse, accessor-backed, or
172
- class/custom-prototype payloads before formula intent is returned.
173
- Ref transport helpers return frozen plain data and frozen arrays, so a ref that
174
- an agent inspected cannot be mutated behind the same handoff object. Transported
175
- ref nodes and nested selector records must be object-record data; class/custom-
176
- prototype ref records are rejected before hydration or persisted proof use.
177
- `verifyPlan` also treats plans as data: malformed, sparse, or accessor-backed
178
- handoff objects return an `invalid_plan` issue instead of executing hidden
179
- properties or throwing at the caller.
180
- `verifyModel` keeps the same behavior at whole-model scope: invalid,
181
- array-backed, or accessor-backed manifests return an invalid verdict with an
182
- `invalid_model` error and no actions.
183
- Its `{ inputs }` option is data-only too: accessor-backed or
184
- class/custom-prototype option payloads and per-action inputs produce structured
185
- `invalid_action_input` results without running hidden consumer code.
186
-
187
- ## Selectors
188
-
189
- Selectors are not a human spreadsheet UI. They are stable intent for runtimes and
190
- agents.
191
-
192
- - `findTable({ headers })` means "find a table with all these headers." Header
193
- order is normalized, duplicate headers are rejected, and matching is
194
- case-sensitive after trimming.
195
- - `findRows({ table, where })` means "find rows in this table matching this
196
- predicate." `eq` and `neq` accept any JSON literal; `contains` and
197
- `startsWith` accept strings; ordered comparisons accept numbers or strings.
198
- - `findRange` is the escape hatch for an explicit range when the consumer really
199
- has one. It validates and canonicalizes addresses before runtime handoff.
200
-
201
- Refs are frozen data. Helpers such as `table.column("Total")` and
202
- `rows.column("Total")` are non-enumerable, so JSON descriptions stay data-first.
203
- Use `toWorkbookRefData` or `describeRef` when a ref must cross a JSON boundary.
204
- Use `hydrateWorkbookRef` or `hydrateWorkbookRefs` after transport to regain the
205
- local helpers. `verifyPlanData(describePlan(plan))` checks transported plan data
206
- without requiring the consumer's private `refs` object shape.
207
- Ref collection and ref hydration only inspect enumerable own data properties.
208
- Accessors are ignored instead of invoked, so hidden consumer getters cannot run
209
- while an agent is planning, verifying, logging, or hydrating workbook intent.
210
- Array entries follow the same rule, and ref cloning copies only known ref fields
211
- instead of spreading extra enumerable properties.
212
- Selector creation follows the same data boundary. `findTable`, `findColumn`,
213
- `findRange`, and `findRows` read option objects, row predicates, and header
214
- arrays through own data properties, rejecting accessor-backed fields before any
215
- getter can run.
216
- Transported row refs use that same selector contract too: ref-data guards,
217
- collection, cloning, and hydration reject operator/value pairs that `findRows`
218
- would reject. Transported ref data is an object-record boundary at every node:
219
- range payloads, row predicates, and nested table/rows refs cannot be class
220
- instances with hidden behavior.
221
-
222
- For full action handoff, use `toPlanData(plan)` before JSON transport. A runtime
223
- can call `checkPlanData(data)` to get structured path-based issues before
224
- hydration, call `hydratePlanData(data)` to regain frozen refs and helper
225
- methods, or pass the data directly to `describeRuntimeRequirements(data)` and
226
- `runWorkbookPlan(data, adapter)`. `runWorkbookPlan` returns a failed result with
227
- `invalid_plan_data` errors instead of throwing or calling `apply` when
228
- transported plan data is malformed. Invalid transported action input and check
229
- proof keep nested JSON paths such as `input.rows[1]` and
230
- `checks[0].proof.when`, so an agent can repair the exact payload field before
231
- hydration. Plan-data guards only trust own payload fields; inherited
232
- prototype fields never satisfy the transport contract. Transported plan arrays
233
- must contain own enumerable data entries too; holes, non-enumerable entries, or
234
- accessor-backed entries are rejected without running getters. The plan root and
235
- nested plan entries such as commands, changes, checks, formula labels, and
236
- expectations must be record-shaped payloads, not arrays with attached fields.
237
- The hydrated plan
238
- exposes `refs: { refsUsed }` instead of the consumer's private model-shaped
239
- `refs` object, so transported execution stays generic. A valid
240
- `checkPlanData(data)` result returns canonical plan data, stripping caller-owned
241
- scratch fields before ids, hydration, requirements, or execution inspect it.
242
-
243
- ## Action Input
244
-
245
- Action input is JSON-safe data, not a schema-framework object. Action metadata
246
- can describe generic input with `json`, `object`, `array`, `string`, `number`,
247
- `boolean`, and `null` kinds. `checkInput(description, value)` returns a frozen
248
- `{ status, input, issues }` result so an agent can reject malformed tool payloads
249
- before running workbook model code. Omitted input is valid unless the top-level
250
- description sets `required: true`, so agents can distinguish an optional payload
251
- from a malformed payload. `planWorkbookAction` uses the same check when an action
252
- declares input metadata and preserves each failed input issue as a run error
253
- with `path` and `issueCode`, so agents can branch without parsing messages.
254
- JSON-safety failures keep the nested offending path too, such as
255
- `input.items[2].amount`. Normalized payloads preserve consumer-owned JSON keys
256
- as data, including names like `__proto__` and `constructor`, instead of letting
257
- them affect object prototypes.
258
- Action input payloads and input-description metadata must be enumerable own data
259
- properties. Accessors are rejected without invoking them, so tool payload
260
- validation cannot run hidden consumer code while an agent is planning.
261
-
262
- ## Formulas
263
-
264
- `@bilig/workbook` creates formula expressions. `@bilig/formula` parses and
265
- normalizes formula text. `@bilig/core` or an app runtime calculates it.
266
-
267
- Formula helpers keep formula text, workbook dependencies, and formula labels
268
- separate. A planned formula write includes the formula string, the refs used to
269
- build it, and a `labels` array mapping each formula token to the workbook ref it
270
- represents. Runtime adapters use those labels to materialize table columns,
271
- filtered rows, names, and ranges without reverse-engineering hidden JS helpers.
272
- Plan verification parses formula text and checks labels against formula reference
273
- tokens, so substrings and quoted string literals do not count as dependency
274
- proof.
275
- For custom formula text, use `formula.raw(source, { inputs })`; pass
276
- `labels: [{ name, ref }]` when the raw formula uses custom tokens. For
277
- spreadsheet string literals, use `formula.text(value)`. Bare strings are not
278
- formula operands because agents should not guess whether a string is code, a
279
- label, a named range, or user text.
280
-
281
- ## Runtime Adapter
282
-
283
- `@bilig/workbook` does not execute plans. A runtime owns that:
76
+ 5. `prepareWorkbookAction` verifies the plan, computes requirements, emits
77
+ JSON-safe `planData`, and gives the exact plan a stable id.
78
+ 6. `runWorkbookPlan(..., { strict: true })` fails closed unless the adapter
79
+ returns plan-bound apply proof, revision proof, resolved refs, command
80
+ receipts, check proof, and no unverified apply facts.
81
+
82
+ ## Which Package
83
+
84
+ | Package | Choose when | Do not use for |
85
+ | ------------------ | --------------------------------------------------------------------------------------------- | -------------------------------------------------- |
86
+ | `@bilig/workbook` | Defining generic agent intent, refs, formulas, checks, plan data, schemas, and proof handoff. | Calculating formulas or owning workbook state. |
87
+ | `@bilig/workpaper` | Running workbook tools, MCP, or product workflows around persisted WorkPaper state. | Designing a reusable model API for other runtimes. |
88
+ | `@bilig/headless` | Owning workbook state inside Node with formula recalculation and import/export. | Publishing generic agent intent contracts. |
89
+ | `@bilig/core` | Implementing calculation or mutation internals. | Consumer-facing agent model definitions. |
90
+
91
+ The root export keeps the complete contract. Subpath exports are available when
92
+ an agent wants a smaller import map: `@bilig/workbook/model`,
93
+ `@bilig/workbook/prepare`, `@bilig/workbook/find`,
94
+ `@bilig/workbook/check`, `@bilig/workbook/formula`,
95
+ `@bilig/workbook/verify`, `@bilig/workbook/runtime`,
96
+ `@bilig/workbook/command`, `@bilig/workbook/features`,
97
+ `@bilig/workbook/testing`, and `@bilig/workbook/schema`.
98
+
99
+ ## Mental Model
100
+
101
+ Consumers define models. Bilig does not ship hardcoded business models in this
102
+ package.
103
+
104
+ Models are plain:
105
+
106
+ - `find(workbook)` binds the workbook parts the model needs.
107
+ - `checks({ refs, workbook })` declares proof the runtime must provide.
108
+ - `actions` write formulas, values, formatting, clears, or guarded low-level ops.
109
+ - `prepareWorkbookAction(model, action)` is the canonical preflight for agents.
110
+
111
+ Refs are generic:
112
+
113
+ - `findName(name)` binds a named workbook ref.
114
+ - `findTable({ name, sheetName, headers })` binds a table by stable traits.
115
+ - `findColumn({ table, name })` and `table.column(name)` bind columns.
116
+ - `findRows({ table, where })` binds filtered rows.
117
+ - `findRange(input)` exists for explicit ranges when a consumer truly has one.
118
+
119
+ Formulas stay symbolic until a runtime materializes them:
120
+
121
+ - `formula.multiply(refs.input, refs.factor)` builds formula intent.
122
+ - `formula.raw(source, { inputs, labels })` accepts custom formula text.
123
+ - `formula.text(value)` creates a spreadsheet string literal.
124
+ - `@bilig/formula` parses and normalizes the formula language.
125
+ - `@bilig/core` or an app runtime calculates formulas.
126
+
127
+ Checks are part of the plan, not comments:
128
+
129
+ - `check.exists(ref)` proves the ref resolved.
130
+ - `check.noFormulaErrors(ref)` proves a formula target is clean.
131
+ - `check.valueEquals(ref, value)` proves a runtime value.
132
+ - `check.formulaEquals(ref, formula)` proves the runtime formula matches intent.
133
+ - `check.custom(options)` carries a runtime-owned proof contract.
134
+
135
+ ## Agent-Safe Runtime
136
+
137
+ `@bilig/workbook` never mutates a workbook by itself. A runtime provides an
138
+ adapter:
284
139
 
285
140
  ```ts
286
141
  const adapter = {
@@ -288,122 +143,42 @@ const adapter = {
288
143
  const ops = materializeForThisRuntime(plan)
289
144
  return {
290
145
  status: 'applied',
146
+ planId: workbookPlanId(plan),
147
+ baseRevision: currentRevision,
148
+ revision: currentRevision + 1,
291
149
  previewOps: ops,
292
150
  appliedOps: ops,
293
- proof: { source: 'runtime', opCount: ops.length },
151
+ commandReceipts: receiptsFor(plan, ops),
294
152
  undo: { id: 'undo-1' },
295
153
  }
296
154
  },
297
155
  read(targets, plan) {
298
- return targets.map((target) => ({ target, value: 12 }))
156
+ return readTargetsFromRuntime(targets, plan)
299
157
  },
300
158
  verifyChecks(checks, plan) {
301
- return checks.map((entry) => ({ ...entry, status: 'passed' }))
159
+ return proveChecksFromRuntime(checks, plan)
302
160
  },
303
161
  }
304
162
  ```
305
163
 
306
- `runWorkbookPlan` accepts either a live plan or transported plan data and
307
- refuses to call `apply` if transported plan data is invalid, static plan
308
- verification fails, or the adapter is missing a required method. Use
309
- `checkRuntimeRequirements(data)` when runtime
310
- requirements crossed a JSON boundary and an agent needs path-based diagnostics
311
- before trusting the handoff. Runtime requirement arrays and nested ref arrays
312
- must be own enumerable data entries; holes, non-enumerable entries, or
313
- accessor-backed entries are rejected without running getters. Use
314
- `checkRuntimeAdapter(planOrRequirements, adapter)` when an agent wants to check
315
- `apply`, `read`, and `verifyChecks` coverage before calling the runtime.
316
- The requirements root object, every requirement entry, and runtime adapter
317
- objects must be record-shaped payloads, not arrays with attached fields.
318
- Runtime requirement descriptions are frozen normalized data too:
319
- `describeRuntimeRequirements` and `checkRuntimeRequirements` strip
320
- caller-owned extra fields and freeze the returned requirement tree, including
321
- nested refs.
322
- Check-only plans do not require `apply`; when runtime requirements
323
- contain only `read` or `verifyCheck`, `runWorkbookPlan` skips mutation and
324
- verifies the declared checks directly.
325
- Adapter methods are own data functions, not getters or inherited prototype
326
- methods. Accessor-backed `apply`, `read`, or `verifyChecks` entries are treated
327
- as missing capabilities without running hidden consumer code.
328
- If an adapter returns both `previewOps` and `appliedOps`, the result reports
329
- whether they matched. If the adapter returns neither, the run records an
330
- unverified apply fact. Use `runWorkbookPlan(plan, adapter, { requireApplyProof:
331
- true })` when an agent must fail closed instead of accepting an unproved apply.
332
- Use `workbookPlanId(plan)` when the runtime needs to bind apply evidence to the
333
- exact generic plan it received. If an adapter returns `planId`, `@bilig/workbook`
334
- rejects stale or mismatched ids; `{ requirePlanId: true }` fails closed when the
335
- adapter omits that binding. Apply summaries may also carry `baseRevision` and
336
- `revision`, so a later agent can inspect which workbook revision the proof
337
- claimed to apply against.
338
- Use `{ strict: true }` as the single agent-safe option when callers want
339
- agent-grade proof without remembering multiple flags. Strict mode requires
340
- at least one planned check before mutating plans apply, apply proof, plan-id
341
- proof, base and applied workbook revisions, no unverified apply facts, concrete
342
- applied ops for every planned command, non-empty resolved-ref proof for commands
343
- that target workbook refs, and proof on every passed check.
344
- Run options are data-only too: accessor-backed or non-boolean proof options
345
- return `invalid_run_options` before any adapter method is called. Optional
346
- `expectedBaseRevision` must be a non-negative safe integer and fails closed when
347
- the runtime applies against a different base revision.
348
- Use `workbookActionCommandDigest(command)` when a runtime needs to bind
349
- materialized ops to a specific planned command. Adapter apply results can return
350
- `commandReceipts`, one per planned command, with the command index, command kind,
351
- command digest, preview ops, applied ops, optional `resolvedRefs` proof, and
352
- optional `formulaLabels` proof for the parsed formula labels used during
353
- materialization.
354
- `@bilig/workbook` rejects stale digests, missing commands, duplicate command
355
- indexes, mismatched receipt ops, receipts whose ops do not match the planned
356
- command's concrete workbook op, or receipts whose flattened ops disagree with
357
- the apply-level ops. With `{ requireApplyProof: true }`, a plan with commands
358
- fails closed unless those command receipts are present. With `{ strict: true }`,
359
- empty per-command applied ops or missing resolved-ref proof fail closed too.
360
- The repository-owned `@bilig/core` adapter now supplies that strict proof for
361
- generic model actions: each command receipt includes materialized applied ops and
362
- the resolved target/input refs that produced them; single-cell formula receipts
363
- also include the generic label-to-reference replacements used to materialize the
364
- formula. Apply summaries include base/applied revisions, and core-owned
365
- `exists` / `noFormulaErrors` check verification attaches proof to passed checks.
366
- Formula readback proof uses the same parsed-label materialization, so agents can
367
- compare symbolic formula intent to runtime formula strings without substring
368
- replacement or UI-coordinate assumptions. `apps/bilig` can therefore accept
369
- transported `WorkbookPlanData` directly through its Zero mutation path, run it
370
- with `strict: true` and the current expected base revision, persist the original
371
- plan, the concrete applied ops, and the frozen run-result description, and roll
372
- back engine ops if post-apply readback or check proof fails.
373
- Runtime apply results, undo refs, apply errors, and check verifier output are
374
- validated from own fields only; prototype-inherited fields are ignored before
375
- they can become run proof. Apply-result objects and verifier check objects must
376
- be plain record-shaped payloads, not arrays with attached fields.
377
- Adapter-returned ops and verifier proof must be data properties, including
378
- non-enumerable guard fields such as `kind`. Runtime evidence arrays must contain
379
- own enumerable data entries; holes, non-enumerable entries, and accessors are
380
- rejected before any getter can run during validation, cloning, or preview/apply
381
- comparison.
382
- Readback checks attach proof to passed checks, such as
383
- `{ source: "readback", value: 12 }` or
384
- `{ source: "readback", formula: "Table[Quantity]*Table[Rate]" }`.
385
- Readback proof objects, check objects, expectations, and formula labels must be
386
- record-shaped payloads, not arrays with attached fields.
387
- Formula readback proof is parsed with `@bilig/formula` and stored in canonical
388
- no-leading-`=` form, so harmless runtime differences such as a leading equals
389
- sign, whitespace, or redundant parentheses do not make proof fail.
390
- Each requested target may appear only once in runtime readbacks; duplicate
391
- targets fail with `readback_duplicate` instead of being silently collapsed.
392
- Generic check verifiers may only change `status` or add JSON-safe `proof`; they
393
- cannot rewrite the check contract.
394
- Consumer `checks()` return values are treated as model-output data too: returned
395
- check arrays must contain own enumerable data entries, and returned check fields
396
- must be own data properties. Accessor-backed or sparse returned checks fail
397
- planning without running hidden getters.
398
- If runtime apply succeeds but readback or check proof fails, the failed result
399
- still carries `changed` and `undo` when the adapter returned applied ops or undo
400
- metadata. A failed result before apply, or a failed apply that reports
401
- `appliedOps: []` without undo metadata, uses `changed: []`.
402
- Returned run results are frozen before they cross the public boundary, so an
403
- agent can inspect `status`, `changed`, `checks`, `errors`, `apply`, `undo`, and
404
- `unverified` without another actor mutating the proof underneath it.
405
-
406
- The result is deliberately plain:
164
+ Use `runWorkbookPlan(planOrData, adapter, { strict: true })` when an agent needs
165
+ production proof. Strict mode requires:
166
+
167
+ - a valid plan before mutation
168
+ - at least one planned check before mutating actions
169
+ - adapter capabilities for the planned work
170
+ - plan id proof
171
+ - base and applied revision proof
172
+ - apply proof with no unverified apply facts
173
+ - concrete applied ops for every planned command
174
+ - command receipts bound to the planned command digests
175
+ - resolved-ref proof for ref-targeting commands
176
+ - proof on every passed check
177
+
178
+ Runtime authors can run the same contract with `checkWorkbookRunAdapter` or
179
+ `assertWorkbookRunAdapter` from `@bilig/workbook/testing`.
180
+
181
+ The returned `WorkbookRunResult` is intentionally plain:
407
182
 
408
183
  ```ts
409
184
  type WorkbookRunResult =
@@ -426,104 +201,58 @@ type WorkbookRunResult =
426
201
  }
427
202
  ```
428
203
 
429
- ## Feature Handoff
430
-
431
- Feature command requests are plain data for runtimes that expose workbook
432
- features to agents. Use `checkWorkbookFeaturePlugin(data)` before registering
433
- consumer-provided feature metadata. It returns stable path issues for commands,
434
- projection interceptors, UI contributions, dependencies, lifecycle hooks, and
435
- nested command input-description or UI metadata fields. Feature manifests and
436
- their nested extension records must be object-record data, so class instances
437
- and custom-prototype metadata cannot smuggle behavior into registration. Its
438
- verdict is frozen.
439
-
440
- Use `checkWorkbookCommandRequest(data)` before dispatching a transported
441
- request. It returns stable path issues such as `featureId`, `commandId`,
442
- `category`, `mode`, and nested input paths like `input.rows[1]`, and
443
- `normalizeWorkbookCommandRequest` returns the frozen request data for the
444
- runtime. The check verdict is frozen. The exported command category, execution-mode, receipt-status,
445
- projection-point, and UI-slot lists let tool builders present and validate
446
- command contracts without importing a schema framework.
447
-
448
- Use `checkWorkbookCommandBundle(data)` when an agent wants to hand a runtime a
449
- single ordered set of command requests and low-level ops. A bundle must include
450
- `targetRevision`, `idempotencyKey`, and non-empty `commands`. Each command uses
451
- plain `kind: "request"` or `kind: "op"` data, keeps declared `touchedRanges`
452
- canonical, preserves command order after normalization, and rejects duplicate
453
- command ids when ids are supplied. Mutation requests and ops must say
454
- `destructive: true`, so broad edits are never implied by a generic payload. When
455
- `scope.maxTouchedCells` is present, every destructive command must also declare
456
- `touchedRanges`; otherwise the scope limit would be unprovable. The validator
457
- returns a `WorkbookCommandResult` with normalized touched ranges and touched-cell
458
- count without importing `@bilig/core`. The bundle check verdict is frozen.
459
- `@bilig/agent-api` uses this same public handoff to validate its richer
460
- app-owned `WorkbookAgentCommandBundle` before preview and authoritative apply,
461
- without making `@bilig/workbook` depend on agent runtime code.
462
- `apps/bilig` then refuses to write the agent execution record unless
463
- authoritative apply returns a validated `WorkbookCommandResult` for the exact
464
- accepted bundle and applied revision, so later agents can inspect the generic
465
- proof without replaying human spreadsheet UI state.
466
-
467
- After a runtime has previewed or applied a bundle, call
468
- `workbookCommandResultForReceipts(bundle, receipts, { revision, undo })` to turn
469
- receipt evidence into the same boring public result shape. The helper validates
470
- receipt count and request identity, aggregates changed ranges, reports
471
- preview/apply `matched` proof when ops are present, and carries undo metadata
472
- without requiring `@bilig/core`. If a command declared `touchedRanges`, receipt
473
- `changedRanges` must stay inside that declared scope. Use
474
- `checkWorkbookCommandResult(data)` or
475
- `normalizeWorkbookCommandResult(data)` before trusting a transported result. An
476
- `"accepted"` result is only the pre-runtime handoff acknowledgement: it must not
477
- carry settled proof fields such as receipts, changed ranges, revision, undo, or
478
- errors. Those fields are valid only on receipt-backed runtime result statuses.
479
- Command-result check verdicts are frozen.
480
- Use `checkWorkbookCommandResultForBundle(bundle, data)` when a stored or
481
- transported result must be mechanically checked against the bundle it claims to
482
- settle. It compares bundle id, target revision, idempotency key, command count,
483
- touched ranges, request or low-level-op receipt identity, receipt changed-range
484
- scope, and final applied revision. It also recomputes result `status`,
485
- `matched`, `changedRanges`, and `errors` from receipts, so an adapter cannot
486
- smuggle a hand-edited summary past the public proof boundary. For low-level `op`
487
- commands, use
488
- `workbookOpCommandReceiptIdentity` or `workbookOpCommandReceipt` so adapters do
489
- not invent receipt ids.
490
-
491
- Use `checkWorkbookCommandReceipt(data)` before trusting runtime command evidence.
492
- It returns the same boring `{ status, issues }` shape for receipt fields such as
493
- `status`, `featureId`, `commandId`, `previewOps`, `appliedOps`, `undo`,
494
- `changedRanges`, `proof`, `metadata`, and `errors`. Feature manifests, command
495
- requests, and command receipts are validated from own payload fields only;
496
- prototype-inherited fields are ignored. Receipt verdicts are frozen. Receipt ops are frozen after
497
- normalization, changed ranges are canonicalized through the same workbook range
498
- normalizer used by command scopes, and manifest or receipt arrays must contain
499
- own enumerable data entries. Holes, non-enumerable entries, invalid range
500
- addresses, and accessor-backed ops, undo ops, ranges, or errors are rejected
501
- before any getter can run.
502
- Receipt statuses are semantic: `previewed` cannot include applied proof,
503
- `applied` cannot include errors and must carry applied evidence, `rejected`
504
- cannot claim changed workbook proof, and `noop` cannot claim changed ranges or
505
- ops.
506
- `workbookCommandReceiptOpsMatch` uses canonical op equality instead of object
507
- property order and refuses accessor-backed proof data.
204
+ ## Data Boundaries
205
+
206
+ Everything that crosses an agent/runtime boundary is inspectable data:
207
+
208
+ - `describeModel`, `describePlan`, `describePlanResult`, and
209
+ `describeRunResult` return JSON-safe descriptions.
210
+ - `toPlanData(plan)` emits executable plan data for transport.
211
+ - `checkPlanData(data)` reports path-based transport issues before hydration.
212
+ - `hydratePlanData(data)` restores frozen refs and helper methods locally.
213
+ - `verifyPlan`, `verifyPlanData`, and `verifyModel` return frozen verdicts.
214
+ - `checkWorkbookReadbackProof(data)` validates transported readback proof.
215
+ - `workbookJsonSchemas`, `workbookJsonSchemaHashes`, and `fixtures/` publish
216
+ checked plan, runtime-requirements, command, run-result, and readback artifacts
217
+ for non-TypeScript consumers.
218
+
219
+ Public validators read own data properties and reject malformed, sparse,
220
+ accessor-backed, or custom-prototype payloads before hidden consumer code can
221
+ run. Public results are frozen before they cross the package boundary.
222
+
223
+ ## Feature Commands
224
+
225
+ Runtimes can expose workbook extensions with the same data-first contract:
226
+
227
+ - `defineWorkbookFeaturePlugin`
228
+ - `checkWorkbookFeaturePlugin`
229
+ - `checkWorkbookCommandRequest`
230
+ - `checkWorkbookCommandBundle`
231
+ - `workbookCommandResultForReceipts`
232
+ - `checkWorkbookCommandResult`
233
+ - `checkWorkbookCommandResultForBundle`
234
+ - `checkWorkbookCommandReceipt`
235
+
236
+ Import these from `@bilig/workbook/features` when building runtime-owned
237
+ extensions. Ordinary models should prefer `writeFormula`, `writeValue`,
238
+ `format`, `clear`, and checks.
508
239
 
509
240
  ## Low-Level Ops
510
241
 
511
- Most models should use the small action API: `writeFormula`, `writeValue`,
512
- `format`, `clear`, and checks. If a consumer needs the existing workbook
513
- operation language directly, call `workbook.addOp(op, { target, message })`
514
- inside an action.
242
+ The existing workbook operation language remains public:
243
+
244
+ - `WorkbookOp`
245
+ - `WorkbookTxn`
246
+ - `EngineOp`
247
+ - `EngineOpBatch`
248
+ - guards such as `isEngineOpBatch`
515
249
 
516
- The op is guarded with `isWorkbookOp`, cloned into `plan.ops`, and kept in the
517
- command log. If a target is supplied and the op exposes a concrete range,
518
- `verifyPlan` checks that the target and op agree.
519
- Low-level op guards accept plain own-field payloads only. Prototype-inherited
520
- op fields, nested ranges, and batch clocks are ignored so transported ops cannot
521
- smuggle proof through object prototypes. Accessor-backed required fields,
522
- nested fields, and op-array entries are rejected from descriptors without
523
- running getters.
250
+ Most models should not start there. Use `workbook.addOp(op, { target, message })`
251
+ inside an action only when the generic action helpers cannot express the
252
+ required workbook intent.
524
253
 
525
254
  ## Example
526
255
 
527
256
  See [examples/workbook-agent-model](../../examples/workbook-agent-model) for a
528
- generic model that plans, verifies, describes, runs, and prints proof without
529
- depending on a hardcoded business model.
257
+ generic model that plans, verifies, describes, transports, runs, and prints proof
258
+ without depending on a hardcoded business model.