@lotics/cli 0.181.0 → 0.181.1

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/AGENTS.md CHANGED
@@ -10,6 +10,7 @@ conventions are, and where the traps are.
10
10
  | `lotics docs` · `lotics docs <area>` | Every reference the packages installed beside the project actually ship — `@lotics/app-sdk`, `@lotics/ui` and the document engines each carry their own, and this index only covers THIS package. Discovered by looking, not by a list, so it reports the installed VERSION of each: a doc always describes the code that is really there. |
11
11
  | [docs/building_an_app.md](./docs/building_an_app.md) | The SEQUENCE — scaffold, model, types, queries, workflows, screens, ship — and the deploy-free inner loop. The other references describe contracts; this one is the order they go in and why. Read it once before starting an app. |
12
12
  | [docs/cli_reference.md](./docs/cli_reference.md) | Per-command contracts, flags, exit codes, and gotchas — the detail `--help` compresses. Read it before hand-building a `set_app_*` payload: several tools REPLACE rather than patch, and a CLI verb already owns the safe assembly. |
13
+ | [docs/data_model.md](./docs/data_model.md) | How tables RELATE — one entity per table and the NAME-OVERLAP probe that says when a split has broken, one vocabulary wherever values are copied between tables, a copy boundary that accounts for every source field, provenance as a link rather than a flag, a declared natural key so find-or-create never compares rendered text, and why derived DEPTH costs more than row count. Separate from building_an_app because every workspace starts with tables and many never get an app. The within-table half (one fact, one column) is stated at `create_table` / `update_table`, where you meet it while deciding. |
13
14
  | [docs/document_templates.md](./docs/document_templates.md) | Generating PDF/Excel/Word/email from reusable templates. |
14
15
  | [docs/knowledge_docs.md](./docs/knowledge_docs.md) | Authoring the workspace facts an agent can't guess; access-vs-activation; catalog-then-stage retrieval. |
15
16
  | [README.md](./README.md) | Install, auth, and worked examples. |
package/dist/src/cli.js CHANGED
@@ -45711,7 +45711,7 @@ function resultSideEffects(result) {
45711
45711
  }
45712
45712
 
45713
45713
  // src/version.ts
45714
- var VERSION = "0.181.0";
45714
+ var VERSION = "0.181.1";
45715
45715
 
45716
45716
  // src/timezone.ts
45717
45717
  function machineTimezone() {
@@ -57,6 +57,31 @@ images do not exist yet, generate them out of band and `lotics file upload` + `u
57
57
  on — and keep one style across the whole set, because a catalogue whose shots disagree about
58
58
  lighting and background reads worse than one with no pictures at all.
59
59
 
60
+ **One fact, one column — and check before you add one.** Read the table's existing fields before
61
+ adding any, because the fact is often already there in another shape: a place written as text
62
+ beside a `select_record_link` to the place record, a status word beside the select that decides it,
63
+ a total beside the formula that computes it. Two columns for one fact never stay equal — some
64
+ writer sets only one of them, and nothing reports the divergence because both rows look correct
65
+ alone. Prefer the link, the select, or the formula, and compose the text when you READ. Renaming or
66
+ re-pointing the existing field beats adding a second one; a field is addressed by key, so a rename
67
+ breaks nothing. Superseding a field means deleting it, not leaving it beside its replacement.
68
+
69
+ **Match on ids and option keys, never on rendered text.** A reader that compares display strings
70
+ treats "Acme" and "Acme Ltd" as different records, and a value spelled `Net 30` as different from
71
+ the option labelled `Net 30 days` — so an import creates a duplicate every time it runs, silently,
72
+ because each row looks right on its own. Resolve a name to its `rec_…` or `opt_…` once, at the boundary, and
73
+ compare those. Treat an unresolved name as UNKNOWN, never as a wildcard.
74
+
75
+ **How the tables RELATE is `lotics docs data_model`** — one entity per table and the overlap probe
76
+ that says when a split has broken, one vocabulary wherever values are copied between tables, a copy
77
+ boundary that accounts for every source field, provenance as a link, a declared natural key, and why
78
+ derived depth costs more than row count. Read it before designing a schema; those decisions outlive
79
+ any one app, and most of them are unfixable once a second screen depends on the copy.
80
+
81
+ **Empty is not the same as redundant.** A field nothing fills may still be the only home for a real
82
+ distinction, and a column whose values are all `1` may be the volume band nobody has needed yet.
83
+ Read what a field MEANS before you remove it; "unused in this data" is not evidence it is wrong.
84
+
60
85
  ## 4 — Typed field access
61
86
 
62
87
  ```
@@ -0,0 +1,92 @@
1
+ # The data model — how tables relate
2
+
3
+ The decisions here outlive any one app, and most become expensive the moment a second screen depends
4
+ on them. They are separate from `lotics docs building_an_app` on purpose: **every workspace starts
5
+ with tables and many never get a custom app**, so schema design is not a chapter of app building.
6
+
7
+ `ONE FACT, ONE COLUMN` — the rules governing a single table's own columns — is stated at the tools
8
+ that add a field, `create_table` and `update_table`, where you meet it while deciding. This doc is
9
+ the other half: how tables relate to each other.
10
+
11
+ Everything below shares one signature. **Both sides read correctly on their own**, so nothing reports
12
+ the problem — no error, no empty column, no failing query. Each is found by looking for it.
13
+
14
+ ## One entity, one table — and the test is measurable
15
+
16
+ Variation belongs in a column — a multi-select role, a kind, a stage — not in a second table. Two
17
+ tables for one kind of thing give the same real-world entity two rows, two ids and two halves of its
18
+ history, and each screen shows whichever half it happens to link to.
19
+
20
+ Split tables are often right. A supplier book beside a customer book is a normal shape, and merging
21
+ on suspicion is a large repoint bought for nothing. So do not argue it in the abstract:
22
+
23
+ > **List both tables' names and look for one that appears in both.**
24
+
25
+ None means the split is holding. One means it has broken — the usual cause is a party you begin to
26
+ invoice as well as buy from — and the fix is to merge before a second screen depends on the copy.
27
+
28
+ Worth writing as a test rather than a note, because a note about a condition nobody re-checks goes
29
+ stale in silence.
30
+
31
+ ## One vocabulary wherever values are COPIED between tables
32
+
33
+ Two `select` fields for one concept carry DIFFERENT option keys even when their labels match — keys
34
+ are minted per field. So anything moving a value between them needs a hand-written key map.
35
+
36
+ That map is code. Put it in one named module with a test; written inline at the copy it is invisible,
37
+ untested, and silently wrong the first time somebody renames an option, because a rename leaves the
38
+ key intact and the map still compiling. Prefer a link to a shared reference table where the set is
39
+ open or growing; keep a map only for a small closed set.
40
+
41
+ Where one side genuinely holds MORE values than the other, that is not drift — it is the model
42
+ telling the truth. The wider side must **refuse** what the narrower one cannot express rather than
43
+ quietly picking the nearest value.
44
+
45
+ ## A copy boundary accounts for EVERY source field
46
+
47
+ Each field on the source gets a column on the destination, a deliberate drop with the reason written
48
+ down, or a refusal.
49
+
50
+ A field with nowhere to land is data destroyed at the boundary, and it is invisible afterwards: the
51
+ destination is not empty and not obviously wrong — just a number that no longer agrees with where it
52
+ came from.
53
+
54
+ ## Provenance is a LINK, not a flag and not a copy
55
+
56
+ A row created BY another row carries a link to it.
57
+
58
+ That link is what makes "is this the estimate or the actual", "where did this come from" and "have we
59
+ already imported this" answerable at all. A boolean records that something was true once; a link
60
+ stays true, survives a rename, and lets the next write UPDATE the original instead of adding a second
61
+ row beside it.
62
+
63
+ ## Say what makes two rows the SAME row
64
+
65
+ Declare the natural key in the table's description.
66
+
67
+ Anything that imports, reconciles or de-duplicates has to decide identity, and with no declared key
68
+ it falls back to comparing displayed text — which is how one company arrives three times under three
69
+ spellings. Name the key: a reference number, a tax id, a link plus a period. Then match on `rec_…`
70
+ and `opt_…`, never on rendered labels.
71
+
72
+ ## Keep derived chains shallow
73
+
74
+ Formulas and rollups are computed and STORED when a row is written, and one that reads another
75
+ recomputes with it. A rollup over a formula over a formula is paid three times on every touch, and
76
+ again for every row upstream of it.
77
+
78
+ **Depth costs more than row count.** This is the optimisation lever that actually exists here; row
79
+ scanning is the platform's problem, chain depth is yours.
80
+
81
+ ## Changing a money formula on a live table
82
+
83
+ Formula edits recompute every row, so the only honest proof that one changed nothing it should not is
84
+ the numbers themselves:
85
+
86
+ 1. Snapshot the affected totals to a file.
87
+ 2. Make the change.
88
+ 3. Diff. Identical is the pass.
89
+
90
+ And when a formula gains a new field, **test that the value IS the one you want, never that it
91
+ differs from it** — an empty cell reads as `""`, which differs from every option key, so the inverted
92
+ spelling silently zeroes every row written before the field existed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.181.0",
3
+ "version": "0.181.1",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {