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