@lotics/cli 0.221.0 → 0.223.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.
- package/dist/src/cli.js +1244 -196
- package/docs/cli_reference.md +2 -2
- package/package.json +1 -1
package/docs/cli_reference.md
CHANGED
|
@@ -44,8 +44,8 @@ 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/ui` or `@lotics/app-sdk` 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 plan 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 PLAN describes, and it is JSON.** The file is checked as `scaffold check` checks it, the named app (or the only one) is taken, every screen's entity 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; then every field the model declares on it — the record shows the ones the list leaves out — and, for each child entity a record section is over, its table and the fields that section draws, and, for each one-row link the record's facts let a reader RE-POINT, the target entity's table and the column its rows are picked by (`display_field_aliases`, else the target's `identity`) — 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. **What it writes is `app.json`**: the bound plan, whole — every screen with its registry shape and the fields its slots read, its strip, lenses, summary and acts; the RECORD that shape opens, off the same roles (`recordSections`), with its header, its band, its facts in bands and its sections in the archetype's job order — a progress over the lifecycle, an expected set per required set, a children register per child entity, a files pile per files field; and the create panels, each with the inputs its workflow declares. Every id in it is live (`tbl_`, `fld_`, `opt_`). Beside it: a five-line `src/main.tsx` that mounts `@lotics/app-runtime` over the spec, `src/components/index.ts` seeded empty (the one hatch — a screen or a section the plan has no word for names a component 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, and `@lotics/app-runtime` is installed as a dependency for a plan-built app only. The manifest declares one `project` query per screen (every column the screen and its record read, a files cell whole, a dated book newest first) plus one per child entity, filtered to the record through whichever of those links names it and taking it as a declared `{{params.<entity>_id}}`, plus one per re-pointable link — the target's rows under their naming column alone, sorted by it, carrying the target's own `read_scope` and a `{{params.search}}` the picker narrows by server-side; the `.lotics` companions and `app_fields.ts` are written before the first build, and the deploy pushes the queries as it pushes any. **And it declares what the app WRITES** — `package.json#lotics.writes`, table alias → the field aliases each record surface's editor changes, derived from the same `update_<entity>` declarations it emits beside the bodies. Seeded once: from then on the declaration is the app's, and `app check` refuses a body that writes outside it. A screen the plan marks `writes: false` contributes none. |
|
|
48
|
-
| `lotics app regenerate [--from <model.json>#<app>] [--dry-run] [--bind-new] [--screens]` | **Re-run the plan over an app that already exists, and rewrite its spec.** Run inside the app directory. The plan 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 plan 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 plan 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 derived from the plan and rewritten whole — there is nothing in a spec to merge — the entry beside it never varies, and a 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. **`src/components/` is the exception and the only one**: seeded where it is absent, named back where you have changed it, never rewritten — it is the hatch, so it is the app's code. 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 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 (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 bound live that this tree declares nowhere (a plain deploy leaves those; only `app deploy --prune` unbinds them) — 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. 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`. |
|
|
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/ui` or `@lotics/app-sdk` 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 plan 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 PLAN describes, and it is JSON.** The file is checked as `scaffold check` checks it, the named app (or the only one) is taken, every screen's entity 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; then every field the model declares on it — the record shows the ones the list leaves out — and, for each child entity a record section is over, its table and the fields that section draws, and, for each one-row link the record's facts let a reader RE-POINT, the target entity's table and the column its rows are picked by (`display_field_aliases`, else the target's `identity`) — 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. **What it writes is `app.json`**: the bound plan, whole — every screen with its registry shape and the fields its slots read, its strip, lenses, summary and acts; the RECORD that shape opens, off the same roles (`recordSections`), with its header, its band, its facts in bands and its sections in the archetype's job order — a progress over the lifecycle, an expected set per required set, a children register per child entity, a files pile per files field; and the create panels, each with the inputs its workflow declares. Every id in it is live (`tbl_`, `fld_`, `opt_`). Beside it: a five-line `src/main.tsx` that mounts `@lotics/app-runtime` over the spec, `src/components/index.ts` seeded empty (the one hatch — a screen or a section the plan has no word for names a component 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, and `@lotics/app-runtime` is installed as a dependency for a plan-built app only. The manifest declares one `project` query per screen (every column the screen and its record read, a files cell whole, a dated book newest first) plus one per child entity, filtered to the record through whichever of those links names it and taking it as a declared `{{params.<entity>_id}}`, plus one per re-pointable link — the target's rows under their naming column alone, sorted by it, carrying the target's own `read_scope` and a `{{params.search}}` the picker narrows by server-side; the `.lotics` companions and `app_fields.ts` are written before the first build, and the deploy pushes the queries as it pushes any. **And it declares what the app WRITES** — `package.json#lotics.writes`, table alias → the field aliases each record surface's editor changes, derived from the same `update_<entity>` declarations it emits beside the bodies — **and what it DELETES**, `package.json#lotics.deletes`, the table aliases whose ROWS its bodies take (a delete names no column, so no field entry could carry it; today that is the publish desk's child, whose withdrawal is the one generated body that deletes). Seeded once: from then on the declaration is the app's, and `app check` refuses a body that writes or deletes outside it. A screen the plan marks `writes: false` contributes none. |
|
|
48
|
+
| `lotics app regenerate [--from <model.json>#<app>] [--dry-run] [--bind-new] [--screens]` | **Re-run the plan over an app that already exists, and rewrite its spec.** Run inside the app directory. The plan 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 plan 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 plan 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 derived from the plan and rewritten whole — there is nothing in a spec to merge — the entry beside it never varies, and a 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. **`src/components/` is the exception and the only one**: seeded where it is absent, named back where you have changed it, never rewritten — it is the hatch, so it is the app's code. 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 bound live that this tree declares nowhere (a plain deploy leaves those; only `app deploy --prune` unbinds them) — 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. 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 eject <screen\|<section key>\|<act label>>` | **Hand ONE part of a JSON app's spec to the app — one-way, per part.** Run inside the app directory; it is local and offline, and touches no workspace. It writes `src/components/<Name>.tsx` whose whole body renders what `@lotics/app-runtime` was rendering for that part — `ScreenView` over the screen the spec still states, `RecordSectionView` over the node the section WAS, `ActView` over the act — points `app.json` at it (`component` on the screen, the section node replaced by `{"kind": "custom", …}`, or `component` on the act), and adds the import and the map entry to `src/components/index.ts`, which is what the app's entry reads its components from. The node a section was travels INTO the file, because the spec no longer states it — which also takes that section out of what `app check` proves, since the spec no longer names the columns it reads; the plan still declares the query behind it, so the manifest keeps the alias. The name is derived (`<Screen>Register`, `<Entity><Key>`, `<Address>Act`), so two ejects can never collide. **Targets**: `screen` (or the register's alias) for the register; a section by its `key`, or `<entity>.<key>` where two records carry the same one; an ACT by its label, its key, or its address (`screen#<key>` for the row's ⋯, the selection bar or the export, `<entity>#<key>` for a record's own header menu, `<entity>.<section>#<key>` for a verb on a section — the `#` is what keeps an act's address off a section's). **An act's eject ADDS a surface rather than taking one over**: a paper act's default is to make the document on the press, so the component is the PANEL it now opens instead — the runtime owns the dialog, the file starts at the runtime's own confirm-and-run body, and `props.run({ … })` makes the paper with whatever the panel asked for beside the rows it was pressed on. An AGENT act is refused: the panel a run is reviewed in is the kit's — started once, reviewed before it is applied, cancelled by closing — so there is nothing to hand over without handing those over too. **Refusals, all before anything is written**: a directory that is not a JSON app; an `app.json` that does not read (every finding printed); a part already ejected, naming the component it points at; a `src/components/<Name>.tsx` that already exists, because that file IS the ejected part and rewriting it would be the generator taking your screen away; a word that reaches two parts, naming every address; a name something under `src/components/` already exports; and a `src/components/index.ts` that no longer states `export const components: RuntimeComponents = { … }`, which names the two lines to add by hand rather than guessing at the author's own file. **It is one-way because the file is.** Dropping the clause by hand does not put the component back — delete the file too. `lotics app regenerate` keeps both: `app.json` is rewritten whole, and every `component` clause an eject wrote is folded back onto the fresh spec, so the parts you did NOT eject go on taking every ruling the runtime makes. A part whose key the plan later retires has no node left to point at, and its clause goes with it; the file stays. |
|
|
50
50
|
| `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 |
|
|
51
51
|
| `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. 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 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. |
|