@fui-org/fui-cli 2.2.2 → 2.2.4

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.
@@ -1,278 +1,189 @@
1
- # Provisioning a New FUI Project
1
+ # Provisioning: new database, new FUI project, or both
2
2
 
3
- > Owns: **new-project infrastructure — SQL Server database, two SQL accounts (execute-only app vs db_owner dev), `acc` alias, FUI project, project admin rights**. Connection/schema/SP: [db-workflow.md](db-workflow.md); multi-module design: [system-design.md](system-design.md); table naming: [db-table-design.md](db-table-design.md); end-user rights: [permission-system.md](permission-system.md). Flags: `fui <command> --help`.
3
+ > Owns: **creating the infrastructure of a new system** — SQL Server database, its two SQL accounts, the SEC (acc) alias and its manager, the FUI project and its IDE rights. Connection/schema/SP: [db-workflow.md](db-workflow.md); multi-module design: [system-design.md](system-design.md); table naming: [db-table-design.md](db-table-design.md); end-user rights: [permission-system.md](permission-system.md). Flags: `fui <command> --help`.
4
4
 
5
- Use for **"new project"** / **"new DB + FUI project"**, or [system-design.md](system-design.md) Step 0 → _"build from scratch"_. Comes **before** all of `system-design.md`.
5
+ This file wins over any memory or note written earlier about provisioning. Every step below is a `fui` command; **never** call a `spAPI_*` of `acc`/`fp` through `fui query`/`fui exec`/HTTP, never pass `@sys_*` parameters (tAPI injects them from your token; `fui` blocks them), never `INSERT` into `tblModules`/`tblUserModule`/`tblA_UserProject`. A step with no command ⇒ **stop and tell the user** to do it in the SEC/FUI UI.
6
6
 
7
- **Do Step 0, then Steps 1–6 in order, skipping none** unless the user says otherwise. Two permission gates in two systems must both pass before anything is created.
7
+ ## The three requests — separate scopes
8
8
 
9
- ---
10
-
11
- ## 🛑 Step 0 — Check BOTH gates first
12
-
13
- | Gate | Needs | Check with |
14
- | ------------------------ | --------------------------------- | -------------------------- |
15
- | **A. FUI platform** | `SystemRight >= 3` on system `fp` | `fui project new` (Step 1) |
16
- | **B. Target SQL Server** | create database + create login | read-only `SELECT` below |
17
-
18
- **Check B BEFORE A.** B has no side effects; A only answers by actually creating the project. Reverse order ⇒ orphan FUI project if the DB can't be created. Late discovery leaves garbage fui can't clean up.
19
-
20
- ### Gate B — target SQL Server
21
-
22
- `CREATE DATABASE`/`CREATE LOGIN` are server-level; a normal dev account (`db_owner` on some DBs) can't. Run with `fui query` on a connection **to the exact target server** (`fui db alias list` shows alias → server):
23
-
24
- ```sql
25
- SELECT SUSER_NAME() AS LoginHienTai,
26
- IS_SRVROLEMEMBER('sysadmin') AS sysadmin,
27
- IS_SRVROLEMEMBER('dbcreator') AS dbcreator,
28
- IS_SRVROLEMEMBER('securityadmin') AS securityadmin,
29
- HAS_PERMS_BY_NAME(NULL, NULL, 'CREATE ANY DATABASE') AS taoDuocDB,
30
- HAS_PERMS_BY_NAME(NULL, NULL, 'ALTER ANY LOGIN') AS taoDuocLogin;
31
- ```
32
-
33
- Pass: `sysadmin = 1`, **or** `taoDuocDB = 1` **and** `taoDuocLogin = 1`.
34
-
35
- - `sa` not required — `dbcreator` + `securityadmin` also pass. Don't block a valid setup with "needs `sa`".
36
- - Need BOTH: `dbcreator` alone creates the DB but not the Step 3 login ⇒ fails midway.
37
- - `IS_SRVROLEMEMBER` returns `NULL` (not `0`) for a bad role name/invalid login ⇒ **unknown**, ask the user.
38
- - A connection borrowed from a sibling project may point at another server ⇒ green for the wrong server.
39
- - ⚠️ `HAS_PERMS_BY_NAME` answers only for the _connected principal_ — correct here. Never use it for another principal (e.g. `[public]` `EXECUTE`): always green for `db_owner`. Read `sys.database_permissions` instead.
40
-
41
- Fails ⇒ **stop; user must request rights on that server**. Never run `CREATE DATABASE` to probe; never grant `db_datareader` to the app account to avoid creating a login.
42
-
43
- ### Gate A — FUI platform
44
-
45
- Creating a project needs **`SystemRight >= 3`** on `fp`, enforced server-side in `spAPI_FUI_Dev_ProjectEdit` (direct API calls can't bypass):
46
-
47
- ```sql
48
- if @jID is null -- không có id ⇒ đang TẠO MỚI
49
- Begin
50
- if @sys_SystemRight < 3
51
- RAISERROR(N'Bạn không có quyền tạo dự án', 16, 1)
52
- ```
53
-
54
- **No right ⇒ STOP, report, do nothing else.** Project goes before the DB because `INSERT INTO tblA_Project` needs only `ProjectID`/`ProjectName`/`GroupName`/`Framework`/`pDomain`/`Router` — no database. DB-first then rejection leaves a real DB, two SQL logins and an orphan `acc` record; never run `DROP DATABASE` (fui has no guard for it), so cleanup is the user's manual job. `fui project new` _is_ the gate A check (no extra call); rejected ⇒ nothing created (if B was checked first).
55
-
56
- | Operation | SP | Condition |
57
- | ----------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------ |
58
- | **Create** project (`fui project new`) | `spAPI_FUI_Dev_ProjectEdit` (empty `id`) | `SystemRight >= 3` |
59
- | **Edit** project (`fui project update`) | `spAPI_FUI_Dev_ProjectEdit` (with `id`) | `SystemRight >= 2` **and** `UserRight > 2` |
60
- | Publish `project.json` (`fui project publish-config`) | `spAPI_FUI_Dev_EditorUpdate` (`Mode = 'project'`) | `SystemRight >= 2` **and** `UserRight > 2` |
61
- | Create/edit/publish **module** | `spAPI_FUI_Dev_ModuleEdit` · `spAPI_FUI_Dev_EditorUpdate` | `SystemRight >= 2` **and** `UserRight > 1` |
9
+ | The user asks for | Do | Do NOT |
10
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
11
+ | **a database** | database + app login (execute-only) + dev login as `db_owner` + alias on the SEC + the alias's **manager** on the SEC | FUI project, FUI rights |
12
+ | **a project** | FUI project + IDE rights (`fui user`) + basic `project.json` when the apiDomain/alias is known | database, SQL logins, SEC |
13
+ | **both** | both blocks; `project.json` `apiDomain` points at the new alias | — |
62
14
 
63
- `SystemRight` (whole `fp`) is necessary, `UserRight` (that project) sufficient — ANDed. Only _create_ skips `UserRight` (no `tblA_UserProject` row yet).
64
-
65
- > ⚠️ `pDomain` is checked **before** the permission gate (`CheckDomain`): a duplicate domain gives a domain error, not a permission error. Don't go request rights over it.
15
+ Unclear which ⇒ ask. Never widen the scope ("a database" does not bring a project, and the reverse).
66
16
 
67
17
  ---
68
18
 
69
- ## Index — phases of a new project
70
-
71
- This file owns **phase 1 only**.
19
+ ## Phase A — the result frame (no side effects)
72
20
 
73
- | # | Phase | Owner | Do |
74
- | ----- | ------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
75
- | 0 | Clarify scope | [system-design.md](system-design.md) §Step 0 | Users, process, main entities |
76
- | **1** | **Infrastructure ← HERE** | **this file** | 2 gates → `fui project new` → DB + 2 SQL accounts + `acc` alias → admin rights |
77
- | 2 | Split into modules | [system-design.md](system-design.md) §Step 1 | Module list, access roles |
78
- | 3 | Schema | [db-table-design.md](db-table-design.md) | Naming, PK/FK, audit columns. `CREATE TABLE` **run by the developer** |
79
- | 4 | **End-user** rights | [permission-system.md](permission-system.md) + [system-design.md](system-design.md) §Step 4b | `fui right system new` / `fui right function new` — BEFORE any module gates on them |
80
- | 5 | Runtime config | [project-config.md](project-config.md) | `project.json`: menu, menuLeft, navigation, domainSetting |
81
- | 6 | First module | `fui module new` → [fullstack-workflow.md](fullstack-workflow.md) | 6-step UI-first flow |
21
+ Before creating anything, build a **result frame**: a table of the end state that will exist when the work is done, with only the blocks of the request. Unknown value ⇒ ask; a value you propose is marked **(proposed)**; never fill a value from memory or from another project.
82
22
 
83
- Not in this file (don't look or copy here): table naming · menus · end-user rights · SP writing ([tapi-reference.md](tapi-reference.md)) · daily connection/schema/SP ([db-workflow.md](db-workflow.md)).
84
-
85
- ---
23
+ **Database block**
86
24
 
87
- ## 🔴 Two permission planes — don't mix
25
+ | Field | Example |
26
+ | --------------------------------------- | ----------------------------------------------------------------------------------------- |
27
+ | System (zone) → SEC | LHBS → SEC `tapi.lhbs.vn` |
28
+ | SQL Server (IP) | `192.168.100.22` |
29
+ | Database, file folder | `ISO`, `D:\Database\` — folder of the **most recently created** user database (query below) |
30
+ | App login | `ISO` (= database name), only `GRANT EXECUTE ON SCHEMA::dbo` |
31
+ | Dev login → `db_owner` | `iotdev` (existing login on that server) |
32
+ | SEC alias | apiName `iso` (≤ 20 chars), module name "Quản lý ISO" (≤ 50 chars) |
33
+ | SystemRight created on the new module | `9 = Project Admin` (required: the manager holds level 9) |
34
+ | Alias manager on the SEC | SEC UserID + name from `fui right user search`, level **9** by default |
88
35
 
89
- | | `SystemRight` (system `acc`) | `UserRight` (`tblA_UserProject`) |
90
- | --------- | ------------------------------------------------------------------ | --------------------------------------- |
91
- | Scope | **System-wide** — one number per user per app | **Per project** |
92
- | Answers | "What can this user do in app X?" | "What can this user do with project Y?" |
93
- | Stored in | `acc.tblSysRight` + `acc` assignment tables | `tblA_UserProject` (FUI platform DB) |
94
- | Commands | `fui right ...` (8) → [permission-system.md](permission-system.md) | `fui user ...` (5) → Step 6 |
36
+ **The database frame needs a working login to that server first.** Find or add a `fui` connection that reaches it and prove it is the right machine:
95
37
 
96
- Mixing them = granting in the wrong place, then "granted but no effect". The FUI IDE _is itself a FUI app_ (project `fp`), so `SystemRight` on `fp` = developer rights in the IDE:
97
-
98
- - `SystemRight` on `fp` = **may create projects** (≥ 3); necessary for every edit.
99
- - `UserRight` on project Y = **may touch Y**; sufficient.
100
- - ANDed: `UserRight = 3` with `SystemRight = 1` can't edit anything.
101
-
102
- `SystemRight`/`FunctionRight` on **your business project** (not `fp`) are end-user rights — later, per [system-design.md](system-design.md) Step 4b.
103
-
104
- ---
105
-
106
- ## Step 1 — Create the FUI project (gate A, before the DB)
107
-
108
- ```
109
- fui project new --data '{
110
- "ProjectID": "...", "ProjectName": "...", "GroupName": "...", "Framework": "V3",
111
- "pDomain": "...",
112
- "Router": ""
113
- }'
38
+ ```sql
39
+ SELECT @@SERVERNAME AS ServerName, CONNECTIONPROPERTY('local_net_address') AS LocalIP;
114
40
  ```
115
41
 
116
- `pDomain` optional (project's own domain); `Router` optional, default `""`.
117
-
118
- - **Only these SIX fields are read** from `@jsonObj`.
119
- - 🔴 **`title`/`login`/`apiDomain`/`fileDomain`/`userInfo`/`menu` are silently IGNORED.** The SP writes `projectData` from a hardcoded constant:
120
- ```sql
121
- declare @jProjectdata nvarchar(1000) = '{"title":"","apiDomain":"","login":"","menu":[]}'
122
- ```
123
- API still returns ✅, project created, config lost — silent failure.
124
- - Runtime config: edit `{projectId}/project.json` then `fui project publish-config` ([project-config.md](project-config.md)) — the **only** way `menu`/`apiDomain`/`login` get in.
125
- - `projectData` must be **FLAT** — not wrapped in `"data"`.
126
- - **`Framework`: `V3` for new projects** unless the user has a reason (usually matching older projects in the group). Expensive to change: decides import layer, component vocabulary, grid syntax — V2 = Vuetify 1.5 (`v-container/v-layout/v-flex`), V3 = Vuetify 3 (`v-container/v-row/v-col`), and `w` px thresholds differ. Most skill module.json examples are **V2**; for V3 cross-check [controls-patterns.md](controls-patterns.md) §6 (V2/V3 diff owner). State the choice to the user; never implicit.
127
-
128
- **Only the caller gets `UserRight = 3` on the new project** (SP `INSERT INTO tblA_UserProject ... 3`) — hence Step 6.
129
-
130
- Rejected with `Bạn không có quyền tạo dự án` ⇒ **stop; user must request `SystemRight >= 3` on `fp`**. Nothing created yet — keep it so.
131
-
132
- ---
133
-
134
- ## Step 2 — Create the database
135
-
136
- > Prerequisite: gate B green. Never run `CREATE DATABASE` to probe.
42
+ The IP must match the frame. No connection reaches it ⇒ **do not build the frame**: tell the user which server you cannot log in to and ask for a connection (`fui db add … --token-env VAR` / `--conn-env VAR`, values set by the user outside the chat). Never guess the server, never try passwords, never ask for a secret in chat.
137
43
 
138
- **Query where the server keeps other DB files; never hardcode paths** (wrong guess = misplaced files, unnoticed until backup):
44
+ Folder of the latest user database on that server (proposal for the new one):
139
45
 
140
46
  ```sql
141
- SELECT DISTINCT LEFT(physical_name, LEN(physical_name) - CHARINDEX('\', REVERSE(physical_name)))
142
- FROM sys.master_files WHERE database_id > 4;
47
+ SELECT TOP 1 d.name, d.create_date,
48
+ LEFT(f.physical_name, LEN(f.physical_name) - CHARINDEX('\', REVERSE(f.physical_name))) AS Folder
49
+ FROM sys.databases d JOIN sys.master_files f ON f.database_id = d.database_id AND f.type = 0
50
+ WHERE d.database_id > 4
51
+ ORDER BY d.create_date DESC;
143
52
  ```
144
53
 
145
- `database_id > 4` skips `master`/`model`/`msdb`/`tempdb` (install dir). Typical: `C:\Application\Database\`. Follow it:
54
+ **Project block**
146
55
 
147
- ```sql
148
- CREATE DATABASE [TenDB]
149
- ON PRIMARY (NAME = N'TenDB', FILENAME = N'C:\Application\Database\TenDB.mdf')
150
- LOG ON (NAME = N'TenDB_log', FILENAME = N'C:\Application\Database\TenDB_log.ldf');
151
- ```
56
+ | Field | Example |
57
+ | ---------------------------------- | --------------------------------------------------------------------- |
58
+ | ProjectID, ProjectName, GroupName | `lhbs-iso`, "Quản lý Hệ thống ISO", `LHBS` |
59
+ | Framework, Router | `V3` (proposed), `""` |
60
+ | pDomain | `iso.lhbs.vn` (list allowed) |
61
+ | `project.json` apiDomain / alias | `tapi.lhbs.vn` / `iso` (when known) |
62
+ | IDE rights | FUI UserID + name from `fui user search`, level 1/2/3 |
152
63
 
153
- Chicken-and-egg: the new DB has no connection — borrow an **existing connection on the same server** (`fui db alias list`, then `fui db add-by-name <apiName>`) and run `fui exec "<sql>" --confirm-write`.
64
+ Show the frame and let the user **adjust** it; after every change show the whole frame again. **No write runs before the user says the frame is right.** The approved frame is the to-do list; at the end, compare the real state with it line by line.
154
65
 
155
66
  ---
156
67
 
157
- ## Step 3 — APP account (execute-only)
158
-
159
- Login **named after the DB**, strong random password:
160
-
161
- ```bash
162
- openssl rand -base64 12
163
- ```
164
-
165
- ```sql
166
- CREATE LOGIN [TenDB] WITH PASSWORD = N'<mật khẩu vừa sinh>',
167
- DEFAULT_DATABASE = [TenDB], CHECK_POLICY = ON;
168
- USE [TenDB];
169
- CREATE USER [TenDB] FOR LOGIN [TenDB];
170
- GRANT EXECUTE ON SCHEMA::dbo TO [TenDB];
171
- ```
172
-
173
- **Only `public` + `GRANT EXECUTE ON SCHEMA::dbo`. NO `db_datareader`, NO `db_datawriter`, NO table grants.** tAPI only runs SPs; new SPs fall under `SCHEMA::dbo` automatically. Table rights don't help the app — they only open direct table access to an abused call.
68
+ ## Phase B — read-only checks (report before writing)
174
69
 
175
- The **only** account loaded into `acc` (Step 5).
70
+ Database block:
176
71
 
177
- ---
72
+ 1. Gate B — rights on the target server (through the connection proved above):
178
73
 
179
- ## Step 4 — DEV account (db_owner)
74
+ ```sql
75
+ SELECT SUSER_NAME() AS LoginHienTai, IS_SRVROLEMEMBER('sysadmin') AS sysadmin,
76
+ HAS_PERMS_BY_NAME(NULL, NULL, 'CREATE ANY DATABASE') AS taoDuocDB,
77
+ HAS_PERMS_BY_NAME(NULL, NULL, 'ALTER ANY LOGIN') AS taoDuocLogin;
78
+ ```
180
79
 
181
- **Default: reuse the existing shared dev login** (e.g. `xTuan`) — just add it to the new DB:
80
+ Pass: `sysadmin = 1`, or both others `= 1` (`dbcreator` alone fails midway at the login). `NULL` ⇒ unknown, ask.
81
+ 2. The database name is free (`SELECT DB_ID(N'ISO')` is `NULL`); the dev login exists (`SELECT name FROM sys.server_principals WHERE name = N'iotdev'`).
82
+ 3. The alias is free on **that** SEC: `fui db alias list --db <project/acc connection of that SEC> --search iso`. A project with connections on several tAPI hosts makes every SEC command ask for `--db`; that is the guard against writing to the wrong SEC.
83
+ 4. The project that holds the SEC connection also has a connection to that SEC's own database (apiName starting with `acc`, same apiDomain): `db alias new` takes `MAX(ModuleID)+1` from it and refuses one on another host.
182
84
 
183
- ```sql
184
- USE [TenDB];
185
- CREATE USER [xTuan] FOR LOGIN [xTuan];
186
- ALTER ROLE db_owner ADD MEMBER [xTuan];
187
- ```
85
+ Project block: `fui project list --filter <ProjectID>` and `--filter <domain>` find nothing.
188
86
 
189
- **NO new `CREATE LOGIN`** unless the user asks for a project-specific dev account — each extra login is another password, while the shared one exists in every sibling DB and is the credential `fui db add-by-name` borrows.
87
+ Any check fails ⇒ stop, report, write nothing.
190
88
 
191
89
  ---
192
90
 
193
- ## Step 5 — Register the DB as a tAPI alias
91
+ ## Phase C — run, in order, stop at the first failure
194
92
 
195
- ```
196
- fui db alias new tenalias \
197
- --module-name "Tên hệ thống" \
198
- --sql-server "..." \
199
- --database TenDB \
200
- --username TenDB \
201
- --password-env TENDB_PASSWORD
202
- ```
93
+ Run only the approved blocks. Both blocks: **project first** — `fui project new` is the only way to learn whether you may create projects (Gate A, `SystemRight >= 3` on `fp`), and a refusal then leaves no orphan database.
203
94
 
204
- `tenalias` → module.json calls `"/tenalias/TenAPI"`. Password only via `--password -` (stdin) or `--password-env VAR`.
95
+ ### Project block
205
96
 
206
- fui calls `SM_Modules_Update` **twice**: `UpdateConnection=0` creates the record, `UpdateConnection=1` sets the connection (record must exist first).
97
+ 1. **Create**
207
98
 
208
- - 🔴 **Credentials = Step 3 APP account, NEVER the dev account.** Dev account ⇒ every app API call runs as `db_owner`, with no visible error.
209
- - New `ModuleID` = `MAX(ModuleID)+1` over **all** `acc.tblModules` ⇒ needs an `acc` connection (`fui db add-by-name acc`). Can't use `fui db alias list`: it lists only modules **you** manage; MAX over a subset may collide.
210
- - Existing alias ⇒ hard error, no overwrite. An explicit `--module-id` that is already in `acc.tblModules` (or used by an alias you manage) ⇒ hard error too; without an `acc` connection it can only be checked against the aliases you manage, and fui says so.
99
+ ```
100
+ fui project new --data '{"ProjectID":"lhbs-iso","ProjectName":"Quản lý Hệ thống ISO","GroupName":"LHBS","Framework":"V3","pDomain":["iso.lhbs.vn"],"Router":""}'
101
+ ```
211
102
 
212
- Then `fui db add-by-name tenalias` works — [db-workflow.md](db-workflow.md) §2b.
103
+ - Only these six fields exist; `fui` refuses any other (`title`, `login`, `apiDomain`, `menu` … belong in `project.json`, step 3).
104
+ - `pDomain` is stored as a JSON-array string; give an array or a string, `fui` normalizes it.
105
+ - Refused with `Bạn không có quyền tạo dự án` ⇒ stop; the user must request `SystemRight >= 3` on `fp`.
106
+ - `pDomain` is checked before the right (`CheckDomain`): a duplicate domain is a domain error, not a permission error.
107
+ - Only the caller gets `UserRight = 3` on the new project.
108
+ 2. **IDE rights**: `fui user search "<name>"` → show the table, the user picks → `fui user add <UserID> --right 3`. `fui user list` to check.
109
+ 3. **Runtime config**: `fui project sync -p <ProjectID>`, edit `<ProjectID>/project.json` (`apiDomain`, `title`, `login`, `menu`), `fui project publish-config` — [project-config.md](project-config.md). Skipped ⇒ the project can call no API.
213
110
 
214
- ---
111
+ ### Database block
215
112
 
216
- ## Step 6 — Grant project admin to others
113
+ All SQL through `fui exec … --db <proved connection> --confirm-write` (server-level DDL and `GRANT` need the flag; each was approved through the frame).
217
114
 
218
- Order is **mandatory**:
115
+ 4. **Database**, in the folder from the frame:
219
116
 
220
- ```
221
- 1. fui user list → who has rights now
222
- 2. fui user search "tên/username" → get the real UserID
223
- 3. fui user add DEV001 --right 3
224
- ```
117
+ ```sql
118
+ CREATE DATABASE [ISO]
119
+ ON PRIMARY (NAME = N'ISO', FILENAME = N'D:\Database\ISO.mdf')
120
+ LOG ON (NAME = N'ISO_log', FILENAME = N'D:\Database\ISO_log.ldf');
121
+ ```
225
122
 
226
- **`fui user add`/`set`/`remove` take only the exact `UserID`** — a structural identity gate: you must run `fui user search` and see its `userId`, `username`, `fullName` columns. **Show that table and let the user pick before granting** — duplicate names are common; a wrong Admin grant can't be undone in one call.
123
+ 5. **App login** (execute-only), named after the database, random password nobody has to read. Shell state does not survive between commands, so keep it in a file in your scratch directory (outside the workspace), load it into a variable inside each command that needs it, never print it, and delete the file after step 7:
227
124
 
228
- | Level | Meaning |
229
- | ----- | -------------------------------------------------------------------------------------- |
230
- | 1 | View only |
231
- | 2 | Develop |
232
- | 3 | **Admin** — needed to manage the project (`fui project publish-config` requires `> 2`) |
125
+ ```bash
126
+ openssl rand -base64 18 | tr -d '\n' > "$SCRATCH/iso_app_pwd"
127
+ ISO_APP_PWD="$(cat "$SCRATCH/iso_app_pwd")" && fui exec "CREATE LOGIN [ISO] WITH PASSWORD = N'$ISO_APP_PWD', DEFAULT_DATABASE = [ISO], CHECK_POLICY = ON; USE [ISO]; CREATE USER [ISO] FOR LOGIN [ISO]; GRANT EXECUTE ON SCHEMA::dbo TO [ISO];" --db <proved connection> --confirm-write
128
+ ```
233
129
 
234
- - Create and edit are separate: `fui user add` hard-errors if rights exist (use `set`), and vice versa. No silent upsert.
235
- - Revoke = `fui user remove` — 🔴 summarize the consequence, wait for confirmation.
236
- - **Rights are ALWAYS project-level.** The SP has `@MID` and IDE module `fp/permission` passes it, but the body never reads it — all filtering is by `ProjectID`. Don't infer a module scope.
130
+ Only `public` + `GRANT EXECUTE ON SCHEMA::dbo`: **no** `db_datareader`/`db_datawriter`/table grants. tAPI only runs SPs.
131
+ 6. **Dev login**: reuse the existing shared login of the frame, no new login unless asked:
237
132
 
238
- **For YOU to grant** (`spAPI_FUI_Dev_UserRight_Update`): `SystemRight >= 2` **and** (`UserRight > 2` on the project **or** `SystemRight >= 4`) — `SystemRight >= 4` grants on any project. _Listing_ (`fui user list`): `SystemRight >= 2` and (`UserRight > 1` or `SystemRight >= 3`).
133
+ ```sql
134
+ USE [ISO];
135
+ CREATE USER [iotdev] FOR LOGIN [iotdev];
136
+ ALTER ROLE db_owner ADD MEMBER [iotdev];
137
+ ```
239
138
 
240
- ---
139
+ 7. **SEC alias** with the **app** login (never the dev login: every API call would run as `db_owner`, silently):
241
140
 
242
- ## After infrastructure
141
+ ```bash
142
+ ISO_APP_PWD="$(cat "$SCRATCH/iso_app_pwd")" fui db alias new iso --db <project>/<acc connection of that SEC> \
143
+ --module-name "Quản lý ISO" --sql-server 192.168.100.22 --database ISO \
144
+ --username ISO --password-env ISO_APP_PWD
145
+ ```
243
146
 
244
- Step 6 ends **phase 1 only**: the project is empty, runtime config still SP-seeded empty strings, no tables/modules/right codes. Don't report "done"; don't jump into module.json.
147
+ Without `--confirm-write` it sends nothing and prints the plan (SEC, ModuleID and where it came from, apiName, server, database, login). Check it against the frame, then rerun with `--confirm-write`. It calls `SM_Modules_Update` twice (record, then connection), refuses an existing alias or a taken `--module-id`, and post-checks.
148
+ 8. **Level 9 on the new module**: `fui right system new 9 --api-name iso --db <same connection>` (the server names it "Project Admin").
149
+ 9. **Alias manager**: `fui right user search <UserID or username prefix> --db <same connection>` → the user picks → `fui right user add <UserID> --sys-right 9 --api-name iso --db <same connection>`. A manager on SEC is exactly a user with level 9 on that module (`SM_Modules_ManagerSelect`).
150
+ 10. **Proof**: `fui db add-by-name iso -p <project> --db …` connects; as the app login, `SELECT TOP 1 * FROM <any table>` is **denied** while `EXEC` of an SP works.
245
151
 
246
- 1. `fui project sync -p <projectId>`, then work from `{workspace}/<projectId>` (or pass `-p <projectId>`) — pulls the project into the workspace; `fui use` needs a module.
247
- 2. **Runtime config (mandatory)**: edit `{projectId}/project.json` (`apiDomain` → new alias, `title`, `login`, `menu`), then `fui project publish-config` — [project-config.md](project-config.md). Skipped ⇒ the project can't call any API.
248
- 3. `fui db add-by-name <apiName>` ([db-workflow.md](db-workflow.md) §2c) — also **proves** Steps 3 and 5 (app account + `acc` record OK).
249
- 4. Resume [system-design.md](system-design.md) at **Step 1**, following the index.
152
+ ### End
250
153
 
251
- Only **one** simple module ⇒ skip `system-design.md`: `fui module new` → [fullstack-workflow.md](fullstack-workflow.md).
154
+ Compare every line of the frame with the real state (`fui project list`, `fui user list`, `fui db alias list --search`, `fui right user list`) and report any line that is not met. The project is still empty: no tables, modules or right codes. Next phase: [system-design.md](system-design.md) Step 1 (one simple module ⇒ `fui module new` → [fullstack-workflow.md](fullstack-workflow.md)).
252
155
 
253
156
  ---
254
157
 
255
- ## Checklist
158
+ ## Two permission planes — don't mix
256
159
 
257
- - [ ] Gate B: read-only `SELECT` on the **target server** — `sysadmin = 1`, or `CREATE ANY DATABASE` **and** `ALTER ANY LOGIN` = 1
258
- - [ ] Gate A: `fui project new` ran BEFORE the DB and succeeded — else stop, no DB
259
- - [ ] `projectData` FLAT, **only 6 fields**; `Framework` agreed with user
260
- - [ ] Physical folder from `sys.master_files`, not hardcoded; `CREATE DATABASE` `FILENAME` follows it
261
- - [ ] App account: login = DB name, random password, **only** `GRANT EXECUTE ON SCHEMA::dbo`
262
- - [ ] Dev account: **shared login reused**, only `CREATE USER` + `db_owner`
263
- - [ ] `fui db alias new` loaded the **app account**
264
- - [ ] `fui db add-by-name <apiName>` works
265
- - [ ] `fui user list` shows exactly the right people
266
- - [ ] `fui project sync` + `fui use` + **`fui project publish-config`** done
267
- - [ ] Told the user **what phase 2 is**
268
- - [ ] **Security check**: as the app account, `SELECT TOP 1 * FROM <any table>` is **denied** and `EXEC` of an SP works — otherwise the `GRANT` is unverified
160
+ | | SEC `SystemRight` / `FunctionRight` (`acc`) | FUI `UserRight` (`tblA_UserProject`) |
161
+ | --------- | -------------------------------------------------------------------------------- | ------------------------------------ |
162
+ | Scope | one level per user per **system** (alias) | per **FUI project** |
163
+ | Commands | `fui right system|function …` (definitions), `fui right user …` (who holds them) | `fui user …` |
164
+ | Grants | what a user may do in the running app; level 9 on a module = its SEC manager | who may edit the project in the IDE |
269
165
 
270
- ---
271
-
272
- ## Three common misreadings
166
+ The FUI IDE is itself the FUI app `fp`: `SystemRight` on `fp` ≥ 3 may create projects, ≥ 2 is needed for any edit, ANDed with `UserRight` on the project (> 1 modules, > 2 project metadata and `project.json`). `UserRight = 3` grants nothing in the running app, and SEC level 9 grants nothing in the IDE.
273
167
 
274
- **Server-level DDL ≠ business-schema DDL.** `CREATE DATABASE` / `CREATE LOGIN` / `GRANT` run via `fui exec "<sql>" --confirm-write`. The _"`CREATE TABLE`/`ALTER TABLE` run by the developer"_ rule ([system-design.md](system-design.md) Step 2) still stands for the **business schema** only — no conflict.
168
+ | Operation | Condition |
169
+ | ----------------------------------------------------- | ------------------------------------------ |
170
+ | `fui project new` | `SystemRight >= 3` on `fp` |
171
+ | `fui project update`, `fui project publish-config` | `SystemRight >= 2` **and** `UserRight > 2` |
172
+ | module create/edit/publish | `SystemRight >= 2` **and** `UserRight > 1` |
173
+ | `fui user add/set/remove` | `SystemRight >= 2` and (`UserRight > 2` or `SystemRight >= 4`) |
174
+ | `fui db alias new` | `SystemRight = 9` on the SEC itself |
175
+ | `fui right user add/set/remove` | SEC `SystemRight >= 3`, or level 9 on that module |
275
176
 
276
- **The app password is in the transcript verbatim** (Step 3 `CREATE LOGIN` goes through the fui command) — treat the provisioning transcript as sensitive. From Step 5 on nothing prints it (`fui db alias new`/`fui db alias list` omit `Password`).
177
+ ## Checklist
277
178
 
278
- **Step 6 `UserRight` ≠ `SystemRight` on the business project.** `UserRight=3` grants **nothing** in the running app, and vice versa.
179
+ - [ ] Request type stated (database / project / both); nothing outside it
180
+ - [ ] Database frame only after a proved login to the exact server IP
181
+ - [ ] Frame shown, adjusted, and approved before the first write
182
+ - [ ] Gate B passed on the target server; names free; SEC chosen with `--db`
183
+ - [ ] `project new`: six fields, `Framework` agreed; `publish-config` done
184
+ - [ ] Database folder = folder of the latest user database on that server
185
+ - [ ] App login = database name, only `GRANT EXECUTE ON SCHEMA::dbo`; dev login reused as `db_owner`
186
+ - [ ] `db alias new` plan checked against the frame, then `--confirm-write`; app login stored
187
+ - [ ] Level 9 defined on the module; the manager added with `fui right user add … --sys-right 9`
188
+ - [ ] `fui db add-by-name` works; app login denied `SELECT`, allowed `EXEC`
189
+ - [ ] Frame compared line by line with the real state
@@ -65,10 +65,11 @@ https://api.domain.vn/app/FunctionName/value1/value2
65
65
  | `@sys_SystemRight` | int | Tiered right (1=low, 9=high) |
66
66
  | `@sys_FunctionRight` | varchar(2000) | Function rights as `[AD][RPT][MGR]` |
67
67
  | `@sys_SessionID` | varchar(50) | Auth session ID |
68
+ | `@sys_RemoteIP` | varchar(15) | `Request.ServerVariables("REMOTE_ADDR")`; the address seen by the server |
68
69
  | `@sys_RawData` | nvarchar(max) | Full client JSON body |
69
- | `@sys_header_{HeaderName}` | varchar(n) | **A family, not one fixed name** — reads any HTTP header; `NULL` if client didn't send it. Naming rule below |
70
+ | `@sys_header_{HeaderName}` | varchar(n) | Request header except `Authorization`; missing header becomes `NULL`. Naming rule below |
70
71
 
71
- > **CRITICAL — never put `@sys_` in module.json `IN`.** tAPI fills all `@sys_*` from the session; client values are **ignored or overwritten**.
72
+ > **CRITICAL — never put recognized `@sys_*` params in module.json `IN`.** tAPI assigns them from authenticated user data, the HTTP request, or the JSON body; a same-named client field is ignored.
72
73
  >
73
74
  > ```json
74
75
  > // ❌ SAI — không đưa sys_ vào IN
@@ -78,9 +79,21 @@ https://api.domain.vn/app/FunctionName/value1/value2
78
79
  > "IN": { "ModuleID": "sModuleID" }
79
80
  > ```
80
81
 
82
+ To read the remote address in an API procedure, declare the parameter with this exact name and type. tAPI reads `REMOTE_ADDR`; do not send it in `IN`. `varchar(15)` fits IPv4 addresses, so check the deployment's IPv6 and proxy behavior before using it for a wider address range:
83
+
84
+ ```sql
85
+ CREATE PROCEDURE spAPI_AuditVisit
86
+ @sys_UserID varchar(9),
87
+ @sys_RemoteIP varchar(15)
88
+ AS
89
+ BEGIN
90
+ SELECT @sys_UserID AS UserID, @sys_RemoteIP AS RemoteIP;
91
+ END
92
+ ```
93
+
81
94
  #### `@sys_header_*` — read HTTP headers: replace each `-` with `_`
82
95
 
83
- Works for **any** header. `-` is invalid in SQL Server param names, so the literal name won't compile:
96
+ Works for request headers **except `Authorization`**. The suffix after `@sys_header_` must be nonempty. tAPI lowercases the SQL parameter name, takes the suffix, replaces every `_` with `-`, then looks it up in `Request.Headers`. `-` is invalid in SQL Server param names, so the literal name won't compile:
84
97
 
85
98
  ```sql
86
99
  -- ❌ SAI — lỗi cú pháp ngay lúc CREATE PROCEDURE, không phải lỗi runtime
@@ -88,7 +101,7 @@ Works for **any** header. `-` is invalid in SQL Server param names, so the liter
88
101
  ```
89
102
 
90
103
  - **No quoting works**: `@[sys_header_User-Agent]` and `@"sys_header_User-Agent"` are both invalid. Renaming is the **only** way.
91
- - **Each `-` → one `_`.** 1-to-1: don't drop characters, change case, or merge segments.
104
+ - **Each `-` → one `_` in the SP declaration.** tAPI reverses every `_` in the suffix to `-`; a header whose actual name contains `_` cannot be represented exactly by this convention.
92
105
 
93
106
  | HTTP header | SP param |
94
107
  | ------------------ | ---------------------------------------- |
@@ -99,26 +112,28 @@ Works for **any** header. `-` is invalid in SQL Server param names, so the liter
99
112
  | `Cookie` | `@sys_header_Cookie` — no `-`, unchanged |
100
113
 
101
114
  - Reverse (reading an existing SP): drop `@sys_header_`, turn remaining `_` into `-` — `@sys_header_Accept_Language` → `Accept-Language`.
102
- - Matching is **case-insensitive** (prefix and name): `@sys_header_User_Agent`, `@sys_Header_User_Agent`, `@sys_header_USER_AGENT` are the same header (so `@sys_Header_RequestID` works). Still use standard casing (`User-Agent`, not `USER_AGENT`). Only `_` for `-` is mandatory.
115
+ - Matching is **case-insensitive** (prefix and header lookup): `@sys_header_User_Agent`, `@sys_Header_User_Agent`, `@sys_header_USER_AGENT` request the same header. Use `_` for each `-`; tAPI does not insert separators, so `@sys_Header_RequestID` requests `RequestID`, not `Request-ID`.
116
+ - `@sys_header_Authorization` is **not exposed**: the code skips assignment for that header. It does not explicitly set the SQL parameter to `NULL` in this branch; do not rely on its value.
117
+ - A missing header is assigned `DBNull.Value`; exceptions during header lookup are caught without a reported error, so diagnose a missing value against the actual incoming request.
103
118
 
104
119
  ```sql
105
120
  CREATE PROCEDURE spAPI_AuditLog_Insert
106
121
  @Action nvarchar(100),
107
122
  @sys_UserID varchar(9),
123
+ @sys_RemoteIP varchar(15),
108
124
  @sys_header_User_Agent nvarchar(500),
109
125
  @sys_header_X_Forwarded_For varchar(200)
110
126
  AS
111
127
  BEGIN
112
- -- ISNULL vì client không gửi header thì tham số là NULL, không phải chuỗi rỗng
113
- INSERT INTO tblAuditLog (Action, UserID, UserAgent, ClientIP, CreateTime)
114
- VALUES (@Action, @sys_UserID,
115
- ISNULL(@sys_header_User_Agent, N'(không có)'),
116
- ISNULL(@sys_header_X_Forwarded_For, ''),
117
- GETDATE());
128
+ SELECT @Action AS ActionName,
129
+ @sys_UserID AS UserID,
130
+ @sys_RemoteIP AS RemoteIP,
131
+ @sys_header_User_Agent AS UserAgent,
132
+ @sys_header_X_Forwarded_For AS ForwardedFor;
118
133
  END
119
134
  ```
120
135
 
121
- - **Still `@sys_*`:** auto-filled, **not** in `IN`, **no default** (`= null` is a hard error; `fui sp deploy` blocks it, see below).
136
+ - **Still recognized `@sys_*`:** auto-filled, **not** in `IN`, **no default** (`= null` is a hard error; `fui sp deploy` blocks it, see below).
122
137
  - **Missing header → `NULL`** (not `''`) — normal, e.g. script/server-to-server calls often lack `User-Agent`. Wrap in `ISNULL(...)` at use: comparisons give UNKNOWN (`WHERE @sys_header_User_Agent <> 'bot'` **drops** the row), and `+` concatenation gives `NULL` (blanks the whole log string).
123
138
  - **Untrusted (client-supplied, spoofable).** Log/stats only, never authorization; take rights from `@sys_UserID`/`@sys_SystemRight` (from the verified token).
124
139
 
@@ -13,6 +13,8 @@ If the developer said **do not upload**, **local only**, **draft**, **preview fi
13
13
 
14
14
  **Strict mode (`FUI_STRICT=1`)**, for runs nobody reviews step by step: `module publish json|html|script|css`, `component new|update`, `project publish-config` and `sp deploy` are **blocked** until the skill was loaded this session (`fui skill get` / `fui skill ref`), and validator **errors block** publishing module.json. Without it both are warnings.
15
15
 
16
+ Project CDN files are managed separately from workspace files. `fui cdn info|list|url` read CKFinder; `fui cdn upload|mkdir|rename|move|copy` change the project file store; `fui cdn download` writes a local file. `fui cdn rm|rmdir` permanently delete remote data and require authorization for the exact target plus `--confirm-delete`. Read [cdn-files.md](cdn-files.md) for paths, token handling, allowed extensions and links.
17
+
16
18
  ## Sync before reading
17
19
 
18
20
  Every file fetched from the server (`module.json`, `header.html`, `body.html`, `script.js`, `_moduleInfo.json`, `uc-*.vue`, `project.json`, `_db/{connectionName}/*.sql`…) is a copy from the last checkout; someone may have changed the server since.
@@ -48,8 +50,8 @@ Users name commands loosely, often in Vietnamese.
48
50
  | "which module is active" / "đang làm module gì" | `fui use` |
49
51
  | "switch module" / "chọn module", a pasted module URL or `/project/module` route, or just a module name | `fui use <target>` |
50
52
  | "list projects" / "danh sách project" | `fui project list` |
51
- | "new project from scratch" / "tạo dự án mới", "tạo DB + project FUI mới" | [project-provisioning.md](project-provisioning.md), 6 steps; do not start with `fui project new` |
52
- | "new database", "register an alias" / "đăng ký alias", "thêm DB vào tAPI" | `fui db alias new` |
53
+ | "new project from scratch" / "tạo dự án mới", "tạo DB + project FUI mới" | [project-provisioning.md](project-provisioning.md): result frame approved by the user first, then Phase B/C |
54
+ | "new database", "register an alias" / "đăng ký alias", "thêm DB vào tAPI" | [project-provisioning.md](project-provisioning.md) database block (`fui db alias new` is one step of it) |
53
55
  | "let someone edit this project" / "cấp quyền admin project", "thêm người vào project" | `fui user search` → `fui user add` |
54
56
  | "who can edit this project" / "ai được sửa project này" | `fui user list` |
55
57
  | "update project.json", "edit the project menu" / "sửa menu project" | edit `{projectId}/project.json`, then `fui project publish-config` |
@@ -73,6 +75,7 @@ Users name commands loosely, often in Vietnamese.
73
75
  | "push the component" / "up component", "sửa component" | `fui component update` |
74
76
  | "sync components" / "sync component" | `fui component list` |
75
77
  | "list / sync import files" / "danh sách import file" | `fui import list` |
78
+ | "add a CDN / library / JS / CSS link" / "nhúng CDN", "thêm thư viện", "link css/js" | `fui import new` (never `<link>`/`<script>` in header/body) |
76
79
  | "new SP", "deploy the SP" / "tạo SP mới", "cập nhật SP" | `fui sp save` → `fui sp deploy` |
77
80
  | "add a function right" / "thêm quyền chức năng", "tạo FunctionRight" | `fui right function new` |
78
81
  | "save the user token" / "lưu user token", "token để gọi API" | `fui db user-token` |
@@ -92,7 +95,7 @@ Users name commands loosely, often in Vietnamese.
92
95
  | `use <target>`, `use --clear` | M | ✅ | session target only; no data changes |
93
96
  | `project list` | R | ✅ | |
94
97
  | `project sync` | M | ✅ | refreshes every project-level file and import content; removes local `.vue`/import files that were tracked and are gone on the server (unpublished new ones are kept) |
95
- | `project new` | M | ⚠️ | needs `SystemRight >= 3` on `fp`; the server reads **6 fields only** (`ProjectID`, `ProjectName`, `GroupName`, `Framework`, `pDomain`, `Router`). `title`/`login`/`apiDomain`/`menu` are ignored silently: runtime config goes through `project publish-config`. Full procedure: [project-provisioning.md](project-provisioning.md) |
98
+ | `project new` | M | ⚠️ | needs `SystemRight >= 3` on `fp`; the server reads **6 fields only** (`ProjectID`, `ProjectName`, `GroupName`, `Framework`, `pDomain`, `Router`). other keys (`title`/`login`/`apiDomain`/`menu`…) are refused: runtime config goes through `project publish-config`. `pDomain` (array or string) is normalized to the stored JSON-array string. `project update` merges onto the current record. Full procedure: [project-provisioning.md](project-provisioning.md) |
96
99
  | `project update` | M | ⚠️ | edits an existing project; `projectData.id` is required (without it the server would create) |
97
100
  | `project publish-config` | M | ⚠️ | pushes the local **file** `{projectId}/project.json`; edit the file first. A JSON syntax error is a hard stop (pushing `{}` would wipe menu and settings). Skips when unchanged |
98
101
  | `user search`, `user list` | R | ✅ | `user search` is the only source of the exact `UserID`: show its rows and let the user pick. `user list` = who may edit the project in the FUI IDE (`UserRight` 1/2/3), **not** the `acc` `SystemRight` ([permission-system.md](permission-system.md)) |
@@ -109,10 +112,10 @@ Users name commands loosely, often in Vietnamese.
109
112
  | `module new` | M | ⚠️ | creates an **empty** module on the server; confirm id and name first, then `fui module get`. An id already in `_modules.json` is a hard error |
110
113
  | `module update` | M | ⚠️ | metadata of an existing module. Changing `ModuleID` changes its URL (old links break) and renames the local folder: warn the user |
111
114
  | `module delete` | M | 🔴 | **permanent on the server, no history there**; archives the local folder and clears a matching session target |
112
- | `module validate`, `module outline`, `module preview` | R | ✅ | `validate` does **not** read `script.js`: an `EXE`/`FUN` calling an unwritten function only shows in `--render` as `... is not defined` ([advanced-techniques.md](advanced-techniques.md) §2). `[design]` warnings must be fixed unless the user asked for that style |
115
+ | `module validate`, `module outline`, `module preview` | R | ✅ | `validate` does **not** read `script.js`: an `EXE`/`FUN` calling an unwritten function only shows in `--render` as `... is not defined` ([advanced-techniques.md](advanced-techniques.md) §2). `[design]` warnings must be fixed unless the user asked for that style. It also reads `header.html`/`body.html` and lists CSS, library tags and old placeholders there as header/body warnings |
113
116
  | `module simulate`, `module check`, `component preview` | R | ✅ | write only `_preview/`, `_componentPreview/` and `_testdata/`; an unmocked read-only API may be called for real ([verification.md](verification.md)) |
114
117
  | `module publish json` | M | ⚠️ | **only** module.json; no preview needed |
115
- | `module publish script`, `module publish css` | M | ⚠️ | an **empty** file clears it on the server; a missing file is refused |
118
+ | `module publish script`, `module publish css` | M | ⚠️ | an **empty** file clears it on the server; a missing file is refused. These two files hold all page code and CSS, never header/body |
116
119
  | `module publish html` | M | ⚠️ | header/body/title **and** module.json; needs the token from `module preview`, which covers the whole payload (any file edited after the preview voids it). Also pushes non-empty `script.js`/`style.css`, and **reconciles `components/*.vue` with the server: a server component with no local file is DELETED** (the preview lists it as `deleted component <name>`) |
117
120
 
118
121
  ### Components
@@ -155,7 +158,7 @@ Scope, for every component command:
155
158
  | `db remove <project>/<connectionName>` | M | ⚠️ | removes only the named local connection and updates the default; server alias and cached `_db/<connectionName>/` files remain |
156
159
  | `db user-token`, `db default`, `db migrate` | M | ✅ | local only |
157
160
  | `db token-rebuild` | M | ⚠️ | overwrites the stored token; `--db` is required when the project has several connections |
158
- | `db alias new` | M | ⚠️ | registers an existing database as a tAPI alias on `acc`; the login must be the **application** account (execute-only), never a `db_owner` dev login. Ask the user first |
161
+ | `db alias new` | M | ⚠️ | registers an existing database as a tAPI alias on one SEC (`--db` picks it when the project spans several hosts); prints the plan and sends nothing until `--confirm-write`; ModuleID = MAX+1 from the acc database on the same apiDomain (`--acc-db`). The login must be the **application** account (execute-only), never a `db_owner` dev login |
159
162
  | `schema`, `sp list`, `sp path`, `sp help`, `sp verify` | R | ✅ | `schema` reads one connection: a "missing" table usually means the wrong connection. `sp list` covers every connection unless `--db`. `sp help` also clears the tAPI cache for that function |
160
163
  | `schema pull` | M | ✅ | rewrites `_db/{connectionName}/` of **one** connection, bodies included. Files of procedures dropped on the server are archived. Files in neither schema are only **listed** (maybe an undeployed draft): read that note and ask before `--prune-local-only` |
161
164
  | `sp get` | M | ✅ | overwrites `_db/{connectionName}/{name}.sql` (old copy to `_history/`). Not read-only |
@@ -168,6 +171,9 @@ Scope, for every component command:
168
171
  | `right system list`, `right function list` | R | ✅ | read `acc` and refresh `{projectId}/_db/rights.json`; **until they ran once, validate/simulate/render cannot check right codes** |
169
172
  | `right system new/update`, `right function new/update` | M | ⚠️ | create and edit are separate; `update` needs the id from `list` |
170
173
  | `right system delete`, `right function delete` | M | 🔴 | `function delete` also removes every user assignment of that code |
174
+ | `right user list`, `right user search` | R | ✅ | who holds which SystemRight on a system; exact SEC UserID (not the FUI `DEV…` id) |
175
+ | `right user add/set` | M | ⚠️ | add/set are separate; the level must be defined first; level 9 = the module's SEC manager |
176
+ | `right user remove` | M | 🔴 | also removes the user's FunctionRights there; needs `--confirm-write` |
171
177
 
172
178
  Guarding with a right code that is not defined fails silently: `rightTest()` is always false, the control vanishes with no JS error, and **the preview still shows it** ([permission-system.md](permission-system.md)).
173
179
 
@@ -191,7 +197,7 @@ Guarding with a right code that is not defined fails silently: `rightTest()` is
191
197
  - Only `module.json` → `fui module publish json` (no preview needed).
192
198
  - `header.html` / `body.html` / title (with or without module.json) → `fui module preview`, review the diff yourself, then `fui module publish html --approval <token>`. It pushes module.json too. A separate approval turn is unnecessary when this publish completes the requested module work; stop if the developer previously asked not to upload or to review first.
193
199
  - `script.js` (also to clear it) → `fui module publish script`. `module publish json` does not push it.
194
- - `style.css` (also to clear it) → `fui module publish css`.
200
+ - `style.css` (also to clear it) → `fui module publish css`. It is the only home for module CSS: never `<style>`/`style="…"` in `header.html`/`body.html`. JS/CSS libraries go in through `fui import new`, not `<link>`/`<script src>` ([module-structure.md](module-structure.md) §CSS and JS placement).
195
201
  - `uc-*.vue` → `fui component new <name>` the first time, `fui component update <name>` after. A **project-scope** component must be previewed and pushed **before** rendering a module that uses it: `--render` hard-blocks when the local file differs from the server.
196
202
  - Several files changed → one command per file, in turn. `fui file push <path...>` routes each path to its publisher.
197
203