@lotics/cli 0.253.0 → 0.255.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.
@@ -44,7 +44,7 @@ Per-command syntax, flags, contracts, and gotchas for the public `lotics` CLI. S
44
44
  | `lotics knowledge tag <id...> [--add <a,b>] [--remove <c,d>]` | `PATCH /v1/knowledge_docs` with `{ knowledge_doc_ids, add_tags?, remove_tags? }` — one transaction over the whole set. A **DIFF applied to each doc's own labels**, never a replacement: the docs named on one command line carry different labels, so one array across them would strip whatever the others were filed under. Removal matches case-insensitively; adding a label a doc already carries writes nothing. Ids may be separate arguments or comma-separated. At least one of --add/--remove required. |
45
45
  | `lotics knowledge hide <id...>` / `lotics knowledge unhide <id...>` | `PATCH /v1/knowledge_docs` with `{ knowledge_doc_ids, hidden }`. Hiding takes docs out of every **listing** — the Library's list, `list_knowledge`, and the corpus `grep_knowledge` searches — while leaving IAM untouched and keeping them readable **by id** (`read_knowledge` with an id, a code run staging one, an app agent's declared set). So it can never silently break an app that depends on a doc, and unhiding costs nothing. Refuses the no-argument form rather than reading it as "everything". |
46
46
  | `lotics knowledge rm <id>` | Archive the doc via `delete_knowledge` (`{ knowledge_doc_id }`). The REST execute path does not gate `needsApproval`, so this runs unattended. |
47
- | `lotics app create <name> [path] [--api]` | Scaffold a Vite+React+TS custom-code app project; POST /v1/apps; npm install; vite build; upload as v1. **`--api`** scaffolds an app with NO screens instead: the app's declared queries and workflows are the whole of what it offers, called over HTTP by the customer's own site, server or agent (`POST /v1/apps/{app_id}/queries/{alias}` and `/workflows/{alias}/execute`). It writes `package.json` (the `lotics` block, `typescript` as its only devDependency, `typecheck` as its only script), `tsconfig.json`, the brief in `src/workflows/`, a CI workflow and the two READMEs — no `index.html`, no `src/App.tsx`, no `vite.config.ts`, no kit. Nothing is built and nothing is deployed, so `current_version_id` stays null: `lotics app query set` and `lotics app workflow set` publish each declaration on their own, and `lotics app api publish` snapshots what they promise. The npm registry is not consulted (there is no `@lotics/app-runtime` range to resolve), `npm install` still runs for `typescript` (which `app workflow check` loads), and, for want of a bundle, `app dev` and `app check --screens` (no `vite.config.*`) and `app deploy` (no `build` script) are refused on such a project in one sentence, before a binding is pushed; `app pull` says its project lives in version control, where `app codegen` rebuilds `.lotics/`. `--api` beside `--from` is refused before anything is written — a model's app describes screens. The generated `package.json#name` is the app name FOLDED to ASCII (`Đơn hàng` → `don-hang`), never stripped of it — dropping the marks would treat each accented vowel as a separator and can slug a name away to nothing. The app's display name is unaffected; this is the npm field only. **`--from <model.json>#<app>` builds the app a MODEL states, and it is JSON.** The file is checked as `scaffold check` checks it, the named app (or the only one) is taken, and every entity the app lists, opens or picks is found as the live table this WORKSPACE remembers the alias became — and BY LABEL only for an alias nothing has bound, which is a new entity or a workspace nothing has applied this model to — so a table or a column relabelled since the apply is still the same one, while a bound id the workspace no longer serves is refused by name rather than re-bound to its namesake. A table the workspace lacks is refused first, naming every missing one and `scaffold apply`; then a field a table lacks, a label two tables or two fields share, or a field whose live type is not the model's — all before anything is created. **The app is BOUND to the model's app alias** (`PUT /v1/workspaces/model/binding/apps`) the moment its row exists; an alias already bound to another app is refused before the row is made — `lotics app pull <app_id>` works on that one, `lotics scaffold unbind app <alias>` first makes a new one the model's — and a bound app the workspace no longer holds is refused the same way, never replaced in silence. **What it writes is `app.json`** (version 3): the register — its columns, filters, order, layout and add — the record its rows open — door, sections (each its stage, fields, blocks and the acts at its foot), comment thread — the acts and the checks, and every entity those name with its fields, its `records` statement and the reads and writes over it. Every id in it is live (`tbl_`, `fld_`, `opt_`). Beside it: `src/main.tsx`, which mounts `@lotics/app-runtime` over the spec (and imports `@lotics/app-runtime/sheets` where the register exports), `src/components/index.ts` seeded empty (a `component` block names one there), one `src/workflows/<alias>.ts` per write, and the README. There is no screen source: **the runtime draws the spec**, so a kit correction reaches the app with its next `npm install` rather than with a regeneration. The manifest declares the reads — `<entity>_list` (param `search`), `<entity>_record` (param `<entity>_id`), `<child>_by_<via>` (the parent's id) for each rows or timeline block — `_<field>_<option>` after it for each option a rows block's `where` narrows to — and `<entity>_<field>_pick` where `write_rules` narrow what a link may point at — each carrying the entity's `read_scope` unless the app states `reads: "shared"`; and the writes — `create_<entity>`, `update_<entity>` (only the changed fields; a many-link or files field as `<alias>_added`/`<alias>_removed`), `remove_<entity>`, and `act_<alias>` per act. **Every write re-checks its gates on the server**: required fields, `min`/`max`, `options_where`, `read_scope`, a `natural_key` match reused rather than duplicated, `default_from`, an act's `when` and `requires`, the checks blocking it, and the status `history` row a move appends. An act that names its own `workflow` gets that file with a generated guard region at its top, between `// <lotics:guards>` and `// </lotics:guards>`; the code below is yours. **And it declares what the app WRITES** — `package.json#lotics.writes`, table alias → the field aliases its workflows change — **and what it DELETES**, `package.json#lotics.deletes`. Seeded once: from then on the declaration is the app's, and `app check` refuses a body that writes or deletes outside it. |
47
+ | `lotics app create <name> [path] [--api]` | Scaffold a Vite+React+TS custom-code app project; POST /v1/apps; npm install; vite build; upload as v1. **`--api`** scaffolds an app with NO screens instead: the app's declared queries and workflows are the whole of what it offers, called over HTTP by the customer's own site, server or agent (`POST /v1/apps/{app_id}/queries/{alias}` and `/workflows/{alias}/execute`). It writes `package.json` (the `lotics` block, `typescript` as its only devDependency, `typecheck` as its only script), `tsconfig.json`, the brief in `src/workflows/`, a CI workflow and the two READMEs — no `index.html`, no `src/App.tsx`, no `vite.config.ts`, no kit. Nothing is built and nothing is deployed, so `current_version_id` stays null: `lotics app query set` and `lotics app workflow set` publish each declaration on their own, and `lotics app api publish` snapshots what they promise. The npm registry is not consulted (there is no `@lotics/app-runtime` range to resolve), `npm install` still runs for `typescript` (which `app workflow check` loads), and, for want of a bundle, `app dev` and `app check --screens` (no `vite.config.*`) and `app deploy` (no `build` script) are refused on such a project in one sentence, before a binding is pushed; `app pull` says its project lives in version control, where `app codegen` rebuilds `.lotics/`. `--api` beside `--from` is refused before anything is written — a model's app describes screens. The generated `package.json#name` is the app name FOLDED to ASCII (`Đơn hàng` → `don-hang`), never stripped of it — dropping the marks would treat each accented vowel as a separator and can slug a name away to nothing. The app's display name is unaffected; this is the npm field only. **`--from <model.json>#<app>` builds the app a MODEL states, and it is JSON.** The file is checked as `scaffold check` checks it, the named app (or the only one) is taken, and every entity the app lists, opens or picks is found as the live table this WORKSPACE remembers the alias became — and BY LABEL only for an alias nothing has bound, which is a new entity or a workspace nothing has applied this model to — so a table or a column relabelled since the apply is still the same one, while a bound id the workspace no longer serves is refused by name rather than re-bound to its namesake. A table the workspace lacks is refused first, naming every missing one and `scaffold apply`; then a field a table lacks, a label two tables or two fields share, or a field whose live type is not the model's — all before anything is created. **The app is BOUND to the model's app alias** (`PUT /v1/workspaces/model/binding/apps`) the moment its row exists; an alias already bound to another app is refused before the row is made — `lotics app pull <app_id>` works on that one, `lotics scaffold unbind app <alias>` first makes a new one the model's — and a bound app the workspace no longer holds is refused the same way, never replaced in silence. **What it writes is `app.json`** (version 4): the register — its columns, filters, order, layout and add — the record its rows open — door, sections (each its stage, fields, blocks and the acts at its foot), comment thread — the acts and the checks, and every entity those name with its fields, its `records` statement and the reads and writes over it. Every id in it is live (`tbl_`, `fld_`, `opt_`). Beside it: `src/main.tsx`, which mounts `@lotics/app-runtime` over the spec (and imports `@lotics/app-runtime/sheets` where the register exports), `src/components/index.ts` seeded empty (a `component` block names one there), one `src/workflows/<alias>.ts` per write, and the README. There is no screen source: **the runtime draws the spec**, so a kit correction reaches the app with its next `npm install` rather than with a regeneration. The manifest declares the reads — `<entity>_list` (param `search`), `<entity>_record` (param `<entity>_id`), `<child>_by_<via>` (the parent's id) for each rows or timeline block — `_<field>_<option>` after it for each option a rows block's `where` narrows to — and `<entity>_<field>_pick` where `write_rules` narrow what a link may point at — each carrying the entity's `read_scope` unless the app states `reads: "shared"`; and the writes — `create_<entity>`, `update_<entity>` (only the changed fields; a many-link or files field as `<alias>_added`/`<alias>_removed`), `remove_<entity>`, and `act_<alias>` per act. **Every write re-checks its gates on the server**: required fields, `min`/`max`, `options_where`, `read_scope`, a `natural_key` match reused rather than duplicated, `default_from`, an act's `when` and `requires`, the checks blocking it, and the status `history` row a move appends. An act that names its own `workflow` gets that file with a generated guard region at its top, between `// <lotics:guards>` and `// </lotics:guards>`; the code below is yours. **And it declares what the app WRITES** — `package.json#lotics.writes`, table alias → the field aliases its workflows change — **and what it DELETES**, `package.json#lotics.deletes`. Seeded once: from then on the declaration is the app's, and `app check` refuses a body that writes or deletes outside it. |
48
48
  | `lotics app regenerate [--from <model.json>#<app>] [--dry-run] [--bind-new] [--screens]` | **Recompile an app that already exists from its model, and rewrite what the generator owns.** Run inside the app directory. The model is whatever `package.json#lotics.plan` remembers — written there by `app create --from` and by this command — unless `--from` names another, which is then remembered in its place; no model and no flag is a refusal, as is a directory with no `lotics.app_id`, a model that does not check (every finding printed), and a model that names no such app. It resolves against the LIVE workspace through the same function `app create --from` resolves with, so the refusals and the output are that command's. **Files. The generator owns what it emits.** `app.json` is compiled from the model and rewritten whole — there is nothing in a spec to merge — the entry beside it never varies, and a generated workflow body is the generator's too: what it replaced goes into `.lotics/regenerate-dropped.patch`, file by file, rather than into a three-way merge, because a conflict marker inside a body is a file the server has to parse. A body is compared as its BODY, since `app codegen` wraps every one on disk in the header and `__workflow` envelope the server verifies against, so comparing bytes would read that wrapper as your edit on every app. **Two things are the author's.** `src/components/` is seeded where it is absent, named back where you have changed it, and never rewritten. An act's own `workflow` keeps your code: only its guard region, between `// <lotics:guards>` and `// </lotics:guards>`, is rewritten, and a body carrying those markers is never deleted as retired. A body whose alias the generator has retired is deleted. **Manifest.** `.lotics/generated/manifest.json` records the alias sets each generation declared, and that is what the reconciliation reads: queries and workflow declarations the generator emits replace their counterparts (each `workflow_id` carried over — the server minted it), aliases the last generation emitted and this one does not are removed and listed, and aliases you added by hand are kept. `lotics.writes` gains the generator's columns and `lotics.deletes` its tables, and each loses an entry on two facts only, each named in the summary: a column the live table no longer carries, and one nothing in the app WRITES any more (a delete retires on the second rule alone — it names no column for a rename to strand) (the bodies it holds after the run, plus this generation's own declaration — a column a screen only READS is not a write, and `app check` can never find one, because a declaration covering more than the bodies write refuses nothing). A body the subset refuses, or one calling a tool this CLI's registry does not know, suspends that second rule for the run: nothing is dropped on a guess. **It pushes NOTHING.** What the live app RUNS changes at `lotics app deploy` and nowhere else, because the bundle production serves was built against the bindings it has: a regeneration that replaced a live workflow body or a live picker query left a deployed create dialog posting inputs its workflow no longer declared, and a picker answering nothing. Instead the summary NAMES what a deploy will do to the live app — `add` for an alias it does not have, `change` for one this tree is ahead of, `remove` for one the generator retired that this checkout bound and the app still serves (the next deploy unbinds it; the retirement is recorded in `.lotics/generated/manifest.json` until one does) — read through the same detector `app check` reports from and `app deploy` pushes from, so the preview cannot disagree with the deploy. **`--bind-new`** is the one live effect left: it binds the aliases the app does not have YET and refuses, naming them, to touch one that already exists, because `lotics app dev` forwards its queries to production and a new alias cannot be exercised until something binds it, while adding one the deployed bundle never calls cannot change what that bundle does. **The runtime follows the spec.** The spec is written for the `@lotics/app-runtime` line this CLI generates for, so the directory's `@lotics/app-runtime` moves to the runtime's latest line and a `@lotics/ui` the app lists to the range that runtime depends on, in one install, and the summary names each move; a range already on its line is left as written, an app still listing `@lotics/app-sdk` is migrated as `lotics app kit --published` migrates it, and a runtime installed from a checkout (`lotics app kit`) is left and named. The model app's icon and colour are compared with the live app's and a difference is named — `workspace build` sets it, since this command changes nothing live. Then `app codegen` runs, the summary prints — files written / kept / deleted, what a deploy will change, what was bound ahead, and `lotics app deploy` as the next command — and the fast `app check` runs (`--screens` passes through), with the bindings this run deliberately left ahead reported by the summary rather than failed by the check. `--dry-run` decides the whole run, prints it, and writes nothing: not a file, not a binding; it cannot be combined with `--bind-new`. |
49
49
  | `lotics app pull <app_id> [path]` | Download source archive from R2 (presigned), extract, install dependencies (`npm ci --ignore-scripts` when a lockfile is present, else `npm install --ignore-scripts`), stamp package.json's `lotics` field. With no `[path]`: refresh the cwd IN PLACE when it's already this app's own project (its manifest `app_id` matches — the documented `cd <app> && lotics app pull` flow), else clone into an `<name>/` subdir. **A pull never overwrites a file that differs from what it is about to write** — it writes only what is ABSENT or already identical, keeps the rest, and reports which files it kept plus the commands that close the gap. The same rule covers `src/workflows/<alias>.ts` and `src/agents/<alias>.md`, so an unpushed body or prompt survives too. Those two are written from the LIVE App row (`apps.workflows` / `apps.agents`), which owns them, and the archive's own copy of them is deliberately SKIPPED on extract: a deploy tars the whole source directory, so the tarball holds a deploy-time snapshot that is stale for anything authored since. The comparison is against the app's own content, not git, so it holds for a project that was never a repo. `--force` takes the app's copy and DISCARDS local edits; there is no other way to lose them. **For a KEPT workflow body or agent prompt the pull records the server's fingerprint only when the server has not moved** — that token is `set_app_workflow`/`set_app_agent`'s lost-update precondition, so recording one for text the author has not seen would clear the next push's refusal by disarming the guard, and silently overwrite whoever edited it. When the live text HAS moved, the pull writes it beside the checkout (`.lotics/agents/<alias>.live.md`, `.lotics/workflows/<alias>.live.ts`), names it, and leaves the token stale: the next deploy is refused, which is correct, and the text to merge is now on disk. A file whose prose/body already reads back AS the live text is never "kept" at all — the baselines are healed from it, so a checkout whose prose was pushed out of band (chat, `lotics run set_app_agent`) converges instead of latching. **A pull also REPORTS the files it restored** when refreshing a tree that already claimed a version: a pull mirrors the last DEPLOYED source, so a file deleted locally comes back until the deletion itself ships, and saying so is the only honest fix — nothing can read a deletion off the disk. **When a pull ACROSS versions keeps files, the manifest is left on the OLDER of the two versions** — the tree is then part one and part the other, and claiming the newer would make `deploy`'s `prev_version_id` check pass and ship a half-and-half bundle. Older, not "the one it had": `--from-version` pulls a deliberately old revision, so the version it had is the NEWER side, and holding that would match what the server serves and let the old source ship. Either way a deploy from that tree is refused until you reconcile the listed files by hand and pull again, or take the app's copy with `--force`. The report says which version each side is on, because a kept file is your unshipped work when the project was already current and merely the OLD version when it was behind — and nothing in a byte comparison can tell those apart. **`--from-version <apv_…>`** pulls an OLDER revision instead of the current one (`lotics app versions` lists the ids) — point it at a NEW path to read a previous revision without disturbing the project you are in. The manifest records the version actually written, never the live pointer, so a deploy from that checkout is refused by the version guard rather than shipping old source over newer. — `workflows` and `agents` are sourced from the live App row (NOT the archived manifest), so `set_app_workflow` / `set_app_agent` authoring survives the pull. Regenerates `.lotics/app_{workflows,queries,agents}.d.ts` so `useWorkflow` / `useQuery` / `useAgentRun` stay typed, AND — AFTER `npm install`, so `node_modules/@lotics/ui` actually exists to read — `.lotics/tsconfig.link.json`'s peer pins and, for a kit old enough to ship one, its `react-native` augmentation (a kit that ships none has the previously-written copy deleted); a pulled project's own `tsc` used to fail until `app codegen` was run by hand, because nothing had regenerated either one after install populated node_modules. AND the runtime `.lotics/app_fields.ts` (the same generation `app codegen` runs, off the app row already fetched). That one is not optional: `app deploy` tars source with `--exclude=.lotics`, so no archive can carry it, and a pulled project whose `src/` imports `F`/`OPT` would fail to build with `Could not resolve "../../.lotics/app_fields"` until `app codegen` was run by hand. Skipped under `--view-as` (the schema is read as that member and silently drops tables they cannot see — a narrowed `F` map compiles and then throws at runtime, worse than the missing module). A schema fetch failure is non-fatal and names the right recovery for what is on disk: an existing file is kept, an ABSENT one warns about the build error and points at `app codegen`. Pull GENERATES but never RECONCILES `.lotics/` — deleting a companion whose alias the manifest no longer declares is `app codegen`'s alone, since pull's authority is the server's alias set and a declared-but-not-yet-`set` alias is supported. Also writes one `src/workflows/<alias>.ts` per bound workflow (faithful body from `get_app_workflow`) and one `src/agents/<alias>.md` per bound agent (its instructions, straight off the live row) — so the prose an author actually edits lives in a file. A pull writes it from live UNLESS the local file holds unpushed work, in which case it is kept and the live text is parked beside the checkout — the same rule the rest of this row describes. A legacy workflow alias with no rendered source, or an agent with no instructions, warns and is skipped. The stamped `lotics.agents` map carries the TYPED half only (`inputs`/`outputs`/`tool_names`/`model_tier`/…) — an agent's prose lives solely in its `.md`, so there is never a second local copy to desync; a stale `instructions` left by an older CLI is inert and disappears on the next pull |
50
50
  | `lotics app deploy [--prune] [--prune-invoked <alias>] [-m <message>] [--acknowledge-breaking-api]` | `-m` is OPTIONAL — omitted, the deploy derives the version message from what it pushed. Runs the app's `npm run typecheck` and `npm run build`, tars source + dist, POST /v1/apps/{id}/versions multipart. **One command ships everything.** Before the bundle moves it pushes every binding the project has ahead of the app — an edited workflow body or declaration, edited agent prose, a changed query — through `set_app_query`, then `set_app_workflow`, then `set_app_agent`, and FAILS the release if any push is refused. **Every query and workflow in that set is asked before the first one moves** — a query through the server's own query gate over the fields `get_table` serves, a body through `set_app_workflow` with `verify_only` — and one that would be refused refuses the whole push, naming each refusal in the server's words: the bindings replace aliases the running bundle calls, so a push stopped half way leaves the live app reading a mix. What cannot be asked (a table unreadable to this credential, a server without `verify_only`) is warned and pushed. That order is required: an agent declares the query and workflow aliases it may call, so pushing it before its own new query is refused. A workflow's `description` rides that push and is compared against the recorded baseline, not the live app — it lives on the workflow ROW, which `getApp` does not carry. Editing `lotics.agents.<alias>.inputs`/`outputs` is pushed the same way, and only those two fields (`set_app_agent` merges, so anything the manifest does not model is left untouched). The deploy never AUTHORS a binding itself, and each push carries the fingerprint the project last saw live (`lotics.synced`), so a stale checkout is refused rather than overwriting another author's edit. It regenerates the `.lotics/*.d.ts` companions and `.lotics/app_fields.ts` before building — the build INLINES the latter — and then typechecks against them; a `package.json` with no `typecheck` script is warned about, never passed in silence. `lotics app check` reports the same set without pushing; neither has a `--strict`. The aliases the version RECORDS as called — the set `remove_app_workflow` / `remove_app_query` / `remove_app_agent` consult to refuse unbinding one the served version still reaches — are read by the SERVER out of the uploaded source archive, never reported by the client that is also what unbinds. **After the ship it unbinds what the GENERATOR retired, unasked**: an alias `.lotics/generated/manifest.json` records as retired by `app regenerate` that this checkout bound and no longer declares — the same transition rule as below, so an alias another checkout bound, or one declared again by hand, is never touched. Every unbind — this one and `--prune`'s — carries the fingerprint the project last saw live, so an alias rewritten since (by chat, by another checkout) is refused and left bound, and a retired one is then dropped from the record, named. Beside that fingerprint alone the invocation guard is lifted, since the recorded runs are then the generated app's own calls to a body nobody rewrote; one that will not unbind for any other reason stays recorded and the next deploy retries it. **Beyond those it reports two things and removes nothing.** Aliases the source CALLS that nothing bound. And bindings this project has RETIRED, which is two transitions, each with its own evidence: an alias the previous bundle called and this one does not (`package.json#lotics.bundle_calls`, recorded by each deploy), and an alias still bound live that this checkout holds a `lotics.synced.<kind>.<alias>` baseline for and no longer DECLARES. Neither piece of evidence present is an alias this checkout has never seen — bound by chat, by another operator, or after this tree was pulled — which is not a removal and is never a prune target. With no `bundle_calls` the first transition reports nothing; that deploy records it and the next can compare. The `bundle_calls` baseline is STICKY: it advances only once the call-site half is settled, so the `--prune` a warning names still finds the transition on a later run. **`--prune` unbinds them, and only when passed.** It runs AFTER the version is live, because the removal tools refuse an alias the SERVED version still declares. When the source computes an alias at run time, the call-site half is left in place with a warning (the scan cannot tell which binding that call reaches); the declaration-removed half is unbound anyway, since deleting a declaration here states the removal outright. A removal DELETES the local declaration too — `package.json#lotics.<kind>.<alias>` and its `synced` baseline — or the next plain deploy would push it straight back; what it deleted is written to `.lotics/pruned/<kind>/<alias>.json` and the ✓ names that file plus the `set` verb that re-binds it. (These trees are never committed, so `lotics app pull --from-version <apv_…>` is the only other route back.) The generated companions are then regenerated from the narrowed manifest; a table named ONLY by a pruned query leaves `F`/`OPT`, which is reported — a workflow that still writes it keeps it, since the codegen set is the queries' tables plus every bound workflow's own `table_ids`. A binding that will not unbind is reported and never fails the release, and neither does a local write that fails: the version is already live, and the report names which aliases were unbound server-side. The server refuses to unbind a WORKFLOW this workspace has actually run — a recorded execution means a caller the source cannot name — printed as `✗ could not unbind …` with the date it last ran. **`--prune-invoked <alias>` lifts that guard for the alias you name** (repeatable, comma-separated; needs `--prune`, and is refused as a no-op without it), keeping the prune's report, undo file and manifest cleanup that a raw `lotics run remove_app_workflow` loses. Finally it refreshes `.lotics/workflows/<alias>.globals.d.ts` for any alias whose `// lotics:declaration` stamp says this deploy moved its declaration — from the manifest, re-wrapping the SAME on-disk body, so local edits survive. Non-fatal: the release has shipped, and stale types never fail it. **`--acknowledge-breaking-api`** carries this write out even though it breaks what the app's published API promises, snapshotting the broken contract as a new version; without it such a write is refused and every breaking change is named (see `app api`). It rides every write the release makes — the bindings pushed ahead of the bundle, the version itself, and a `--prune`'s unbinds — because the answer is about the RELEASE. |
package/docs/migration.md CHANGED
@@ -4,6 +4,19 @@ What an app author has to DO when a release changes the shape of a project the C
4
4
  other contract is `docs/cli_reference.md`; this file is the one a reader opens once, because
5
5
  something already on disk no longer matches what the CLI writes.
6
6
 
7
+ ## Opt-in history and starts, `app.json` version 4
8
+
9
+ A record's status history and an add's starting values are stated, never inferred. The history is still
10
+ written on every move; it is drawn only where the app asks.
11
+
12
+ 1. **`record.history: true`** on each app whose record should show its status history beside it (a
13
+ shipment, a gate in and out, an order and its logistics). Without it the history is drawn nowhere. A
14
+ block of the history entity in a section is refused: `record.history` draws it.
15
+ 2. **`records.<e>.starts: { <field>: "today" | "me" }`** where an add should start a date at today (a
16
+ moment at now) or a member at the reader. Without it such a field starts empty.
17
+ 3. **`lotics app regenerate`** writes `app.json` version 4; a version 3 spec stops at its first render with
18
+ `app.json is version 3; this runtime reads version 4`.
19
+
7
20
  ## A record's `sections`, `app.json` version 3
8
21
 
9
22
  A record is its `sections`, stacked in the order the work reaches them, replacing `record.facts` and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.253.0",
3
+ "version": "0.255.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {