@jarenjs/linq 0.73.0 → 0.83.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ARCHITECTURE.md CHANGED
@@ -202,16 +202,14 @@ residual runs locally, and `explain()` reports the split.
202
202
  (`JL0005`) rather than silently materialised — except two providers
203
203
  sharing a `scope` (one store's entity sets), whose roots are two
204
204
  bindings of one multi-entity input.
205
- - **Federation is a door, not a default.** `federate({ sources,
206
- maxRows, maxBytes })` (`src/federate.js`, QUERY-PEN §12.1) hands back
207
- one provider source per name, sharing one scope, so the ordinary join
208
- is admitted and the federation executes it: each side's own packed
209
- document at its own source, the smaller side into a bounded hash
210
- table, the other probed against it, and the caller's document decided
211
- in the engine over the two reduced sets. It spells no join semantics
212
- of its own — the reduction bounds the FETCH, and a value it cannot
213
- key is kept rather than guessed at — and a budget is a refusal
214
- (`JL2008`), never a spill.
205
+ - **Federation is explicit.** `federate()` (QUERY-PEN §12.1) accepts a
206
+ connected equality graph across named providers. Source-local work runs at
207
+ its provider; estimates choose a connected fetch order, hash sets reduce
208
+ candidates, and the engine retains the original tuple semantics. Packed
209
+ joins run inside out under the same cumulative row/byte admission credits.
210
+ Per-side budgets also cap intermediates; buffered production and final
211
+ results have the explicit limits documented in §12.1. Aliases receive
212
+ independent input members, and array framing preserves array-valued rows.
215
213
  - **The engine result shape leaks nowhere.** Every surface — sync,
216
214
  async, provider — reproduces `undefined | item | items` exactly,
217
215
  which is why the window-wrapper trick exists at all.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @jarenjs/linq
2
2
 
3
+ The pens emit documents; `linq/db` supplies the declared optional database adapter edge. The [combined adoption recipe](../../docs/ADOPTION-EVIDENCE.md) composes mappings, receipts, ingestion and run adapters over one transaction owner, with host and retirement limits visible.
4
+
3
5
  **Write it once, in typed JavaScript. Keep it as data.**
4
6
 
5
7
  Every engine in this repository runs a JSON document. The validator runs
@@ -47,7 +49,7 @@ SQL — and nothing is hidden: the chain's `explain()` shows the document
47
49
  it sent, and the store's `explain(document)` shows what that became,
48
50
  `SELECT … FROM "User" WHERE "age" > ? ORDER BY "name"`.
49
51
 
50
- Coverage: <!--fact:coverage.pens-->14 public pen/client subpaths beside the chain; 69/69 owned schema keywords have dedicated emission routes.<!--/fact-->
52
+ Coverage: <!--fact:coverage.pens-->15 public pen/client subpaths beside the chain; 69/69 owned schema keywords have dedicated emission routes.<!--/fact-->
51
53
 
52
54
  ## What you gain
53
55
 
@@ -729,4 +731,16 @@ Every subpath a consumer can import, derived from the manifest by
729
731
  | `@jarenjs/linq/jtlt` | JavaScript | declared |
730
732
  | `@jarenjs/linq/messages` | JavaScript | declared |
731
733
  | `@jarenjs/linq/ai` | JavaScript | declared |
734
+ | `@jarenjs/linq/formula` | JavaScript | declared |
732
735
  <!--/fact-->
736
+
737
+ Explicit cross-provider joins use `federate({ sources, maxRows, maxBytes,
738
+ maxTotalRows, maxTotalBytes })`. Connected joins across three or more sources,
739
+ including successive fluent `.join()` calls, preserve the engine's result
740
+ order while sharing cumulative admission credits. See
741
+ [QUERY-PEN §12.1](docs/QUERY-PEN.md#121-the-federation-boundary-federate) for
742
+ planning, buffering limits and cleanup guarantees.
743
+
744
+ ## Saved formula documents
745
+
746
+ `@jarenjs/linq/formula` exports `defineFormula` for immutable JSON Query profiles with revision, bindings and schema/helper references. See [FORMULA-PEN](docs/FORMULA-PEN.md); execution remains in json/formula.
package/docs/AI-PEN.md CHANGED
@@ -94,5 +94,5 @@ sub-call budgets, storage and result interpretation remain runner concerns.
94
94
 
95
95
  ## 7. Cost
96
96
 
97
- The isolated AI program pen costs **<!--fact:bundle.ai-->16,904<!--/fact--> bytes**.
97
+ The isolated AI program pen costs **<!--fact:bundle.ai-->15,472<!--/fact--> bytes**.
98
98
  Its tree probe excludes AI, other target engines and the query chain.
package/docs/APP-PEN.md CHANGED
@@ -1123,7 +1123,7 @@ not look for them:
1123
1123
 
1124
1124
  ## 7. Cost
1125
1125
 
1126
- `@jarenjs/linq/app` builds to **<!--fact:bundle.app-->51,006<!--/fact--> bytes** as a minified,
1126
+ `@jarenjs/linq/app` builds to **<!--fact:bundle.app-->49,574<!--/fact--> bytes** as a minified,
1127
1127
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
1128
1128
  measures and `npm run test:tree-shaking` reports, published rounded
1129
1129
  beside the other nine subpath prices in
@@ -1133,8 +1133,8 @@ pen and the JSLT pen (state, and views), and no chain module, no
1133
1133
 
1134
1134
  It is the second-largest pen bundle after the client, and the two pens
1135
1135
  it carries are most of it. The three figures the same probe measures,
1136
- side by side: `@jarenjs/linq/schema` <!--fact:bundle.schema-->36,717<!--/fact--> bytes,
1137
- `@jarenjs/linq/jslt` <!--fact:bundle.jslt-->19,856<!--/fact-->, `@jarenjs/linq/app` <!--fact:bundle.app-->51,006<!--/fact-->. The subpath sums do not add — all
1136
+ side by side: `@jarenjs/linq/schema` <!--fact:bundle.schema-->35,285<!--/fact--> bytes,
1137
+ `@jarenjs/linq/jslt` <!--fact:bundle.jslt-->18,424<!--/fact-->, `@jarenjs/linq/app` <!--fact:bundle.app-->49,574<!--/fact-->. The subpath sums do not add — all
1138
1138
  three carry the capture, the expression lowering and the JSON boundary,
1139
1139
  which each bundle counts once — so what the app pen costs a consumer who
1140
1140
  already imports the schema pen is the difference the numbers do state:
@@ -89,6 +89,6 @@ render tooltips; those responsibilities remain in the chart engine.
89
89
 
90
90
  ## 7. Cost
91
91
 
92
- The isolated chart pen costs **<!--fact:bundle.charts-->17,014<!--/fact--> bytes**.
92
+ The isolated chart pen costs **<!--fact:bundle.charts-->15,582<!--/fact--> bytes**.
93
93
  The tree-shaking gate refuses chart engine, other target engine and chain
94
94
  modules in this bundle, and checks that the chain and schema pen do not import it.
@@ -1188,10 +1188,10 @@ to write, and reaching them means one import of `@jarenjs/contract` over
1188
1188
 
1189
1189
  ## 7. Cost
1190
1190
 
1191
- `@jarenjs/linq/contract` builds to **<!--fact:bundle.contract-->48,859<!--/fact--> bytes** as a minified,
1191
+ `@jarenjs/linq/contract` builds to **<!--fact:bundle.contract-->47,427<!--/fact--> bytes** as a minified,
1192
1192
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
1193
1193
  measures and `npm run test:tree-shaking` reports, published rounded
1194
- (<!--fact:bundle.contract.kb-->49<!--/fact--> kB) beside the other nine subpath prices in
1194
+ (<!--fact:bundle.contract.kb-->47<!--/fact--> kB) beside the other nine subpath prices in
1195
1195
  [docs/CONSUMING.md](../../../docs/CONSUMING.md).
1196
1196
 
1197
1197
  The probe is a gate, not a report: building a one-operation contract as a
@@ -1200,7 +1200,7 @@ them:
1200
1200
 
1201
1201
  - **the schema pen is included, and that is the ceiling.** A contract's
1202
1202
  inputs and outputs are schemas, so the two are measured together and
1203
- the bundle carries <!--fact:bundle.schema-->36,717<!--/fact--> of its <!--fact:bundle.contract-->48,859<!--/fact--> bytes as the schema pen's own.
1203
+ the bundle carries <!--fact:bundle.schema-->35,285<!--/fact--> of its <!--fact:bundle.contract-->47,427<!--/fact--> bytes as the schema pen's own.
1204
1204
  The contract pen's own share is the remaining ~12 kB, most of it the
1205
1205
  refusal messages §4 lists;
1206
1206
  - **no chain module** — none of `sequence.js`, `document.js`, `async.js`,
@@ -1216,6 +1216,6 @@ them:
1216
1216
  A consumer who writes a contract and also compiles it pays both prices
1217
1217
  and they add rather than overlap. That is the shape the separation is
1218
1218
  for: a browser bundle that only needs the TYPES a contract implies —
1219
- `typedClient` over an HTTP binding, say — ships the pen's <!--fact:bundle.contract.kb-->49<!--/fact--> kB and none
1219
+ `typedClient` over an HTTP binding, say — ships the pen's <!--fact:bundle.contract.kb-->47<!--/fact--> kB and none
1220
1220
  of the compiler, while the server that serves the contract imports
1221
1221
  `@jarenjs/contract` and does not need the pen at all.
package/docs/DB-CLIENT.md CHANGED
@@ -115,6 +115,11 @@ back, the durable ledger over what it opened, and replication document authoring
115
115
  | `defineReplication(header)` | a logical replication document builder — §2.7 | `ReplicationPen` |
116
116
  | `defaultValidator()` | `new JarenValidator({ collectErrors: true })` with `stringFormats` and `dateTimeFormats` registered | `JarenValidator` |
117
117
  | `createDbLedger(client, options?)` | the contract idempotency ledger (`claim`/`commit`/`fail`/`lookup`/`sweep`) over a declared collection of the client's store — §2.6 | `DbLedger`; structurally `@jarenjs/contract`'s `Ledger` |
118
+ | `createDbReceipts(client, options)` | permanent receipts and independent fenced leases; see durable mapped records | structural receipt repository |
119
+ | `createDbEffectStore(client, options)` | reviewed external intent and reconciliation under job fences | structural effect store |
120
+ | `createDbRunStore(client, options)` | mapped workflow checkpoints and bounded revision event pages | structural run store |
121
+ | `createDbIngestionStore(client, options)` | atomic page/checkpoint staging and complete snapshot publication; see [complete ingestion store](DB-CLIENT.md#complete-ingestion-store) | structural ingestion store |
122
+ | `createDbRangeProvider(store, entity, spec, options)` | a bounded structural range source over keyset pages and committed capture; also `handle.range(spec, options)` — [COLLECTION-PROVIDER.md](../../app/docs/COLLECTION-PROVIDER.md) | `Promise<DbRangeProvider>` |
118
123
 
119
124
  `open` is the only door, and it is deliberately not a coded refusal: a
120
125
  missing `options`, or a `validator` that is not a `JarenValidator`, is a
@@ -128,18 +133,18 @@ reconstruct the configuration by reading this paragraph.
128
133
 
129
134
  ### 2.3 The entity handle
130
135
 
131
- A handle is 59 members and no Proxy: 18 from the store's entity set, 40
132
- from the chain, one name in both (`explain`, resolved below), and two of
133
- the client's own.
136
+ A handle has no Proxy. It combines the store's entity set, the asynchronous
137
+ chain, and the client's graph, live and range adapters. `explain` is shared
138
+ and resolved below.
134
139
 
135
140
  | Group | Members |
136
141
  |---|---|
137
- | the unit of work | `create` `get` `update` `delete` `add` `put` `remove` `discard` `asNoTracking` |
142
+ | the unit of work | `create` `get` `update` `delete` `add` `put` `remove` `discard` `asNoTracking`; `mutate` executes an untracked [native column mutation](../../db/docs/NATIVE-PLANS.md) |
138
143
  | the store's reads | `load` `explainLoad` `execute` |
139
144
  | the provider seam | `root` `scope` `relations` |
140
145
  | membership | `link` `unlink` — the store's, behind §4.2's check |
141
146
  | the chain | every `AsyncSequence` operator and terminal: `where` `select` `selectMany` `orderBy` `orderByDescending` `thenBy` `thenByDescending` `groupBy` `aggregate` `join` `groupJoin` `skip` `take` `distinct` `reverse` `concat` `defaultIfEmpty` `ofType` `cast` `zip` `mapAsync` `params` `toDocument` `toArray` `first` `firstOrDefault` `single` `singleOrDefault` `last` `lastOrDefault` `elementAt` `elementAtOrDefault` `count` `sum` `average` `min` `max` `any` `all`, and `Symbol.asyncIterator` |
142
- | the client's own | `include` (§2.4) and `live` |
147
+ | the client's own | `include` (§2.4), `live`, and `range(spec, options)` |
143
148
  | in both | `explain` |
144
149
 
145
150
  **`explain` is the one name the store's set and the chain both carry, and
@@ -309,6 +314,18 @@ See the [replication format](../../db/docs/REPLICATION-FORMAT.md) for causality,
309
314
  conflict policy and bounded reset contracts. Authoring does not allocate a
310
315
  committed sequence; normal replication exports the Store's committed pages.
311
316
 
317
+ ### Lexical ranges
318
+
319
+ `createLexicalRangeProvider(source, text, request, options)` composes a
320
+ `createDbSearch` source from `@jarenjs/db/search` with the shared structural range
321
+ implementation. `maxMatches` and `maxSourceBytes` cap complete source membership;
322
+ `maxRows`, `maxBytes`, `maxPages` and `maxInFlight` cap range work and retained pages.
323
+ A truncated search refuses rather than presenting incomplete membership as a
324
+ complete collection. Filters/facets/order use the query provider's one meaning.
325
+ Source resets invalidate requests; `refresh()` rebuilds the bounded range under
326
+ the new source revision. Disposal drains the range and unsubscribes; set
327
+ `disposeSource` when the range also owns its source.
328
+
312
329
  ## 3. Worked examples
313
330
 
314
331
  The client's examples are not builder-to-document pairs, and this is
@@ -826,10 +843,10 @@ never builds one; the migration between two of them is
826
843
 
827
844
  ## 7. Cost
828
845
 
829
- `@jarenjs/linq/db` builds to **<!--fact:bundle.db-->627,769<!--/fact--> bytes** as a minified,
846
+ `@jarenjs/linq/db` builds to **<!--fact:bundle.db-->675,752<!--/fact--> bytes** as a minified,
830
847
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
831
848
  measures and `npm run test:tree-shaking` reports, published rounded
832
- (<!--fact:bundle.db.kb-->628<!--/fact--> kB) beside the other nine subpath prices in
849
+ (<!--fact:bundle.db.kb-->676<!--/fact--> kB) beside the other nine subpath prices in
833
850
  [docs/CONSUMING.md](../../../docs/CONSUMING.md).
834
851
 
835
852
  It is by far the largest of the ten, and the reason is §1.1's edge rather
@@ -840,6 +857,10 @@ planner, a unit of work, a live-maintenance engine and a JSON Schema
840
857
  validator, and the honest way to read the figure is as the price of the
841
858
  database, not of the front door.
842
859
 
860
+ Explicit physical codecs, catalog verification, invariant lowering and guarded
861
+ SQL also ride with the store. Their measured cost is included in this figure;
862
+ the structural bundle ceiling is 665 kB. The frozen consumer budgets are unchanged.
863
+
843
864
  What the probe asserts, and fails the build on:
844
865
 
845
866
  - **all three peers ride** — `@jarenjs/db`, `@jarenjs/validate` and
@@ -856,7 +877,7 @@ What the probe asserts, and fails the build on:
856
877
  asserts the same exclusion.
857
878
 
858
879
  A consumer who wants the model pen's types without the store pays
859
- `./model`'s <!--fact:bundle.model-->45,143<!--/fact--> bytes and installs no peer; one who wants to run
880
+ `./model`'s <!--fact:bundle.model-->44,143<!--/fact--> bytes and installs no peer; one who wants to run
860
881
  queries against an array rather than a database pays the chain's price
861
882
  (§17 of [QUERY-PEN.md](QUERY-PEN.md)) and installs no peer. `./db` is
862
883
  the one subpath whose `package.json` entry carries an optional peer at
@@ -891,3 +912,174 @@ survives is structural, not temporal** — the graph load's statement
891
912
  counts are printed beside its timings, and the client answers a
892
913
  two-level graph in ONE statement where the schema-first ORM takes three,
893
914
  whatever the corpus and whatever the clock says.
915
+
916
+ ## Trusted SQL during adoption
917
+
918
+ `open()` forwards `adopt: true` and physical model declarations to the store.
919
+ A transaction client exposes `tx.sql`, `tx.jobs`, and the store's optional
920
+ `tx.sync` surface. `tx.sql.prepare(text, { access: 'read' | 'write', affects? })`
921
+ returns `run(params)`, `get(params)`, `all(params)` and `close()`. Parameters are
922
+ positional arrays. These methods answer values on synchronous hosts and promises
923
+ on asynchronous hosts. Always await asynchronous operations inside the callback;
924
+ statement preparation rechecks scope ownership before execution, so a late
925
+ preparation cannot write after settlement.
926
+
927
+ ```js
928
+ await client.transaction(async tx => {
929
+ const insert = tx.sql.prepare('INSERT INTO legacy_note(body) VALUES (?)',
930
+ { access: 'write' });
931
+ await insert.run(['reviewed']);
932
+ await tx.entities.Setting.update('locale', { value: 'nl-NL' });
933
+ await tx.jobs.enqueue('publish', { key: 'locale' });
934
+ insert.close();
935
+ }, { mode: 'immediate' });
936
+ ```
937
+
938
+ The SQL capability accepts trusted application statements with explicit access.
939
+ It refuses multiple statements, transaction control, schema changes and connection
940
+ configuration. A retained statement or continuation refuses after scope settlement
941
+ or while a nested scope owns the connection. Closing a statement invalidates its
942
+ public handle; underlying cached/native storage belongs to the connection.
943
+
944
+ SQL writes conservatively invalidate clean tracked entities, including effects of
945
+ unknown triggers/cascades. Pending tracked changes must first be saved or discarded.
946
+ The optional `affects` declaration does not narrow this conservative policy.
947
+ SQL writes refuse capture/replication and store-only invariant configurations whose
948
+ coverage they cannot honor. This capability is not an untrusted-SQL sandbox;
949
+ application-supplied SQL and host functions remain trusted.
950
+
951
+ `store.sync.transaction(fn, { mode: 'immediate' })` uses the same writer and
952
+ savepoint owner. It rejects thenables and invalidates post-return work. Async-only
953
+ hosts have no synchronous transaction surface. The outbox infrastructure must
954
+ already have been provisioned through an explicit setup/migration before a
955
+ no-DDL adoption; opening with jobs enabled is a separate infrastructure operation.
956
+
957
+ ## Complete ingestion store
958
+
959
+ `createDbIngestionStore(client,{staging,checkpoints,publications,facts?,reconcile?})`
960
+ uses application-declared document collections through the root or transaction
961
+ client. Collection names must be distinct. The root adapter opens an immediate
962
+ transaction; a transaction adapter nests under that owner. It introduces no
963
+ second ledger, transaction engine or fixed provider schema.
964
+
965
+ ```js
966
+ import { open, createDbIngestionStore } from '@jarenjs/linq/db';
967
+ import { nodeDriver } from '@jarenjs/db/node';
968
+
969
+ const model = { $model: '0.1', collections: Object.fromEntries(
970
+ ['observations', 'pulls', 'snapshots', 'facts'].map((name) => [name, {
971
+ key: '/id', schema: { type: 'object' }, indexes: [],
972
+ }]),
973
+ ) };
974
+ const client = await open(model, { driver: nodeDriver(), path: 'ingestion.db' });
975
+ const store = createDbIngestionStore(client, {
976
+ staging: 'observations', checkpoints: 'pulls', publications: 'snapshots', facts: 'facts',
977
+ reconcile: (existing, incoming) => existing?.provenance === 'manual' ? existing : incoming,
978
+ });
979
+ // Pass store to createIngestion; close client after ingestion and workers drain.
980
+ ```
981
+
982
+ `begin(plan)` creates or resumes a generation, or returns `unchanged` when the
983
+ current published fingerprint matches. A fingerprint includes source version,
984
+ partitions, input, policy revision and consistency. Generations cannot be reused
985
+ with a changed plan. `stage(plan,partition,page)` commits exact raw page text,
986
+ parsed/transformed observations and continuation/completion evidence together.
987
+ An identical repeated page does no work; a conflicting page or stale cursor is
988
+ refused. `inspect(plan)` returns the checkpoint and retained observations.
989
+ `invalidate` preserves them while fencing the generation from publication.
990
+
991
+ `publish(plan,evidence)` requires completed coverage of every requested
992
+ partition, matching source evidence and the publication generation observed at
993
+ begin. Concurrent stale publication is refused. The pointer, completion record
994
+ and any reconciled facts commit together. If reconciliation or persistence
995
+ fails, all those changes roll back while staging remains available for recovery.
996
+ `current(source)` reads the published pointer and its completion evidence.
997
+
998
+ Facts use the collision-free JSON tuple `[source,partition,providerId]`; strings
999
+ retain leading zeros. `reconcile(existingValue,incomingRow,evidence)` is a pure
1000
+ synchronous policy and defaults to the incoming row. It must explicitly preserve
1001
+ manual provenance when required. Equal values do not write or increment the
1002
+ fact's revision. Reported `changes`, `writes` and `revisions` count effective
1003
+ fact changes; staging/checkpoint/manifest metadata is separate. An identical
1004
+ published pull performs no metadata writes either. A new policy or source
1005
+ version can publish a new manifest with zero fact changes.
1006
+
1007
+ The complete pointer defines source membership. The facts collection is a
1008
+ reconciled history of observed identities; missing identities are not implicitly
1009
+ deleted. Applications own retirement of absent facts, staging retention and
1010
+ business-history policy. Without `facts`, only raw staging and publication are
1011
+ maintained. No provider schema, receipt TTL or manual-edit rule is inferred.
1012
+
1013
+ ## Durable mapped records
1014
+
1015
+ `createDbReceipts`, `createDbEffectStore` and `createDbRunStore` from
1016
+ `@jarenjs/linq/db` adapt application-declared collections or entities. Each record option
1017
+ accepts its name, `{ collection, read, write }` or `{ entity, read, write, key? }`:
1018
+ synchronous, lossless mappings
1019
+ between application fields and canonical records with an `id`. The optional
1020
+ `key(id)` maps canonical identity to the declared physical key, including a
1021
+ composite key object. Entity writes use the existing direct create/update/delete
1022
+ API, read back the persisted result, and refuse lost identity before commit. A failed
1023
+ round-trip or changed identity refuses `JL2009`. Physical tables and column names
1024
+ remain the model's declarations; no adapter creates a second authoritative
1025
+ business ledger. Root operations use immediate transactions; transaction-client
1026
+ adapters use the caller's savepoint and commit with that caller.
1027
+
1028
+ `createDbReceipts(client, { receipts, leases?, runtime? })` stores permanent
1029
+ command identity, validated outcome, stable references and creation/revision
1030
+ metadata. Its trusted-host methods are `lookup`, `execute`, `claim`, `release`,
1031
+ `sweep`, `migrate` and `compact`. Authorization belongs to the command layer.
1032
+ Claim leases are independent execution capabilities: expiry/recovery replaces
1033
+ the token, while receipt lookup always wins before admitting any work. A hash
1034
+ or hash-version collision refuses; receipts never expire. Sweep accepts at most
1035
+ 1000 selected identities and deletes only expired leases. It never interprets
1036
+ replication acknowledgement as retention permission.
1037
+
1038
+ Migration takes at most 1000 explicit receipt outcomes with references. It
1039
+ preserves originals and later receipts; a conflicting identity/outcome/reference
1040
+ refuses the whole batch. Repeating it reports zero changes, writes and revisions,
1041
+ including after compaction/restart. Expired HTTP claims cannot be imported as
1042
+ business outcomes implicitly. Compaction requires `retainReplay: true`,
1043
+ `retainReferences: true`, `actor` and `reason`; it removes only auxiliary data,
1044
+ keeps collision identity/outcome/references, and becomes a zero-write no-op on
1045
+ repeat. Erasure is unsupported and refused.
1046
+
1047
+ `createDbEffectStore(client, { operations, maxLegs?, maxBytes? })` provides
1048
+ `prepare`, `get`, `begin`, `settle`, `recover` and `reconcile`. A reviewed plan
1049
+ contains `id`, `jobId`, `kind`, `actor`, `reason`, `hashVersion` and bounded `legs`.
1050
+ Each leg names `id`, a provider `request` and `maxAttempts`; arbitrary reviewed
1051
+ selection/payload declarations stay in the frozen plan. Preparation plus its
1052
+ optional local callback and enqueue co-commit. Repeated preparation is a no-op;
1053
+ changed reviewed bytes refuse. Each sending/settlement operation compares the
1054
+ operation revision and uses `tx.jobs.assertLease` against the plan's job ID.
1055
+ Remote I/O occurs outside these transactions. Confirmed/rejected/unresolved
1056
+ states and individual leg attempts survive lease takeover.
1057
+
1058
+ Reconciliation requires a unique decision `id`, `actor`, `reason`, `action`
1059
+ (`confirm`, `reject`, `retry`) and public `evidence`. Repeating identical evidence
1060
+ is a zero-write/revision no-op; a reused decision ID with different evidence
1061
+ refuses. Idempotent retries preserve the provider key and original attempt
1062
+ budget. Absence without an authoritative non-application guarantee is not retry
1063
+ permission. A single-send leg remains limited to one attempt; a proven negative
1064
+ may be rejected and followed by a separately reviewed operation. Compensation
1065
+ requires a separate plan with `compensationOf`, `compensationAuthorized: true`
1066
+ and its own authorized actor/reason. The trusted host must establish that
1067
+ permission; the boolean is recorded evidence, not authentication.
1068
+
1069
+ `createDbRunStore(client, { runs, events, statuses?, summary?, canReset?,
1070
+ maxPage?, maxBytes? })` attaches existing IDs and status vocabularies to workflow
1071
+ checkpoints. `attach` verifies workflow/schema/job identity, `load` checks it
1072
+ before reading a checkpoint, and `save` combines workflow generation CAS, the
1073
+ job fence, mapped status, summary and event in one transaction. The application
1074
+ `summary(snapshot)` projects only public JSON. Default summaries contain status
1075
+ and generation, not inputs or results. `get` is trusted/private;
1076
+ `page(id, { after, limit })` exposes bounded public events and current summary.
1077
+ Missing event history returns `reset-required` with the current durable cursor.
1078
+
1079
+ `requestCancel` compares the observed revision and records actor/reason;
1080
+ `finish` persists a final cancelled/failed observation after draining.
1081
+ `reset` changes only this run's checkpoint and requires current revision,
1082
+ inactive status, actor/reason and a transactional `canReset(tx, record)` policy.
1083
+ The default refuses; the policy must consult receipts and unresolved effects.
1084
+ Reset never deletes those tables. Incompatible workflow/schema identities refuse;
1085
+ a host must perform an explicitly reviewed migration before attachment.
package/docs/FLOW-PEN.md CHANGED
@@ -985,10 +985,10 @@ meaning is a `$return` that constructs an array explicitly.
985
985
 
986
986
  ## 7. Cost
987
987
 
988
- `@jarenjs/linq/flow` builds to **<!--fact:bundle.flow-->19,910<!--/fact--> bytes** as a minified,
988
+ `@jarenjs/linq/flow` builds to **<!--fact:bundle.flow-->18,478<!--/fact--> bytes** as a minified,
989
989
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
990
990
  measures and `npm run test:tree-shaking` reports, published rounded
991
- (<!--fact:bundle.flow.kb-->20<!--/fact--> kB) beside the other nine subpath prices in
991
+ (<!--fact:bundle.flow.kb-->18<!--/fact--> kB) beside the other nine subpath prices in
992
992
  [docs/CONSUMING.md](../../../docs/CONSUMING.md).
993
993
 
994
994
  The probe is a gate, not a report. Building a machine with a guard and
@@ -1015,7 +1015,7 @@ two effects as a consumer would — `defineFsm`, `state`, `on` and
1015
1015
  `packages/linq/src/flow/`.
1016
1016
 
1017
1017
  Two documents, two grammars, thirteen exported names — and 57 bytes more
1018
- than `./jslt`'s <!--fact:bundle.jslt-->19,856<!--/fact-->, which writes one. The reason is that most of
1018
+ than `./jslt`'s <!--fact:bundle.jslt-->18,424<!--/fact-->, which writes one. The reason is that most of
1019
1019
  both prices is the same shared machinery: the recording proxy
1020
1020
  (`expression.js`), the root capture (`capture-root.js`) and the JSON
1021
1021
  boundary (`json-boundary.js`). What this pen adds on top of them is 685
package/docs/FORMS-PEN.md CHANGED
@@ -925,7 +925,7 @@ for this pen at all.
925
925
 
926
926
  ## 7. Cost
927
927
 
928
- `@jarenjs/linq/forms` builds to **<!--fact:bundle.forms-->40,873<!--/fact--> bytes** as a minified,
928
+ `@jarenjs/linq/forms` builds to **<!--fact:bundle.forms-->39,441<!--/fact--> bytes** as a minified,
929
929
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
930
930
  measures and `npm run test:tree-shaking` reports, published rounded
931
931
  beside the other nine subpath prices in
@@ -934,7 +934,7 @@ pen it subclasses, and no chain module, no `@jarenjs/forms` byte and no
934
934
  model pen.
935
935
 
936
936
  Most of that figure is the schema pen: `@jarenjs/linq/schema` alone
937
- is <!--fact:bundle.schema-->36,717<!--/fact--> bytes, so the whole `x-form` vocabulary — the mixin, the rule
937
+ is <!--fact:bundle.schema-->35,285<!--/fact--> bytes, so the whole `x-form` vocabulary — the mixin, the rule
938
938
  capture, the submit transform and their refusal messages — is about 4 kB
939
939
  on top of a pen a form-shaped consumer usually already carries. A
940
940
  consumer importing both subpaths pays the schema pen once.
@@ -0,0 +1,41 @@
1
+ # Formula document authoring
2
+
3
+ > `./formula` — author versioned saved JSON Query profiles without executing them.
4
+
5
+ ## 1. What it writes
6
+
7
+ `defineFormula(id, expression, options?)` from `@jarenjs/linq/formula` emits a frozen
8
+ `$formula: "1"` JSON document. The default revision is `"1"`. Options declare
9
+ revision, immutable bindings, input/result schema references, helper versions and
10
+ result mode. The expression is an existing JSON Query document. The pen validates
11
+ the envelope and JSON boundary without executing a formula or importing a component.
12
+
13
+ ```js
14
+ import { defineFormula } from '@jarenjs/linq/formula';
15
+ const amount = defineFormula('amount', { $mul: ['$.price', '$.quantity'] }, {
16
+ revision: '2',
17
+ inputSchema: { id: 'row', version: '1' },
18
+ });
19
+ ```
20
+
21
+ Compile the document using `@jarenjs/json/formula` and a host schema registry/type
22
+ compiler. [The formula format](../../json/docs/FORMULA-FORMAT.md) owns execution,
23
+ missing/null arithmetic, explicit migration and reviewed-plan semantics.
24
+
25
+ ## 2. The mapping table
26
+
27
+ | Call / option | Emitted member |
28
+ |---|---|
29
+ | `defineFormula(id, expression)` | `$formula`, `id`, `expression`, default `revision` |
30
+ | `revision`, `bindings`, schema/helper references, result mode | Same named profile members |
31
+
32
+ ## 3. Refusals
33
+
34
+ Non-JSON authoring input is `JL0101`; invalid envelopes and unresolved runtime
35
+ capabilities use the owning JSON formula registry. The pen never executes a query.
36
+
37
+ ## 4. Cost
38
+
39
+ The isolated formula pen costs **<!--fact:bundle.formula-->14,850<!--/fact--> bytes**.
40
+ Its bundle contains envelope validation and JSON helpers, with no Query evaluator
41
+ or component. The tree-shaking gate enforces the separate authoring boundary.
package/docs/JSLT-PEN.md CHANGED
@@ -919,10 +919,10 @@ non-judgement is itself gated.
919
919
 
920
920
  ## 7. Cost
921
921
 
922
- `@jarenjs/linq/jslt` builds to **<!--fact:bundle.jslt-->19,856<!--/fact--> bytes** as a minified,
922
+ `@jarenjs/linq/jslt` builds to **<!--fact:bundle.jslt-->18,424<!--/fact--> bytes** as a minified,
923
923
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
924
924
  measures and `npm run test:tree-shaking` reports, published rounded
925
- (<!--fact:bundle.jslt.kb-->20<!--/fact--> kB) beside the other nine subpath prices in
925
+ (<!--fact:bundle.jslt.kb-->18<!--/fact--> kB) beside the other nine subpath prices in
926
926
  [docs/CONSUMING.md](../../../docs/CONSUMING.md).
927
927
 
928
928
  The probe is a gate, not a report. Building a stylesheet as a consumer
@@ -948,8 +948,8 @@ source. That makes it the SMALLEST of the nine pen bundles, and the
948
948
  reason is that a stylesheet is mostly bodies, and a body is the shared
949
949
  machine every pen already pays for.
950
950
 
951
- Two figures worth reading beside it: `./migration` (<!--fact:bundle.migration-->24,259<!--/fact--> bytes)
951
+ Two figures worth reading beside it: `./migration` (<!--fact:bundle.migration-->22,827<!--/fact--> bytes)
952
952
  carries this pen's `body()` and pays for it, which is why the two prices
953
- sit so close; and `./flow` (<!--fact:bundle.flow-->19,910<!--/fact--> bytes) is within 60 bytes of this one
953
+ sit so close; and `./flow` (<!--fact:bundle.flow-->18,478<!--/fact--> bytes) is within 60 bytes of this one
954
954
  despite writing two formats, because it shares the same capture and adds
955
955
  almost nothing but member checks and their messages.
package/docs/JTLT-PEN.md CHANGED
@@ -79,5 +79,5 @@ validate XML well-formedness, render text, or add a second dispatch language.
79
79
 
80
80
  ## 7. Cost
81
81
 
82
- The isolated JTLT pen costs **<!--fact:bundle.jtlt-->16,725<!--/fact--> bytes**.
82
+ The isolated JTLT pen costs **<!--fact:bundle.jtlt-->15,293<!--/fact--> bytes**.
83
83
  Its tree probe excludes target engines and the query chain.