@astram/guide 0.2.10 → 0.2.12
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/CAPABILITIES.md +51 -9
- package/package.json +1 -1
package/CAPABILITIES.md
CHANGED
|
@@ -187,10 +187,23 @@ imports: [
|
|
|
187
187
|
service is built without `IdentityFeatureModule.forRoot`) when no credential was made.
|
|
188
188
|
Once your `EmailSender` works, `POST /auth/forgot-password` reaches the user. A user whose email
|
|
189
189
|
is set later stays locked until the next `bootstrap:org` run gives them a credential.
|
|
190
|
-
- **
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
- **Roles belong to one company** (G-013, ADR-0047). A role is assigned only in its own company
|
|
191
|
+
(postings and service accounts; 400 otherwise, root included), read and managed through the owner
|
|
192
|
+
wall (a parent company's admins manage their subsidiaries' roles; out of reach ⇒ 404), and its name
|
|
193
|
+
is unique within its company only (409 names the existing role). `GET|POST
|
|
194
|
+
/identity/companies/:companyUuid/roles`; `POST /identity/roles` creates the role in the caller's
|
|
195
|
+
active company (400 "Choose a company for the role." without one). Role routes need
|
|
196
|
+
`identity:company:read` (the `access-admin` bundle grants it). An ownerless role (seeded without
|
|
197
|
+
`r_owner_company_uuid`) is root-only and assignable nowhere.
|
|
198
|
+
- **Departments and branches belong to one company** (G-012). `POST
|
|
199
|
+
/identity/companies/:companyUuid/departments` and `/branches` (`{ name }`), `PUT` (rename) and
|
|
200
|
+
`DELETE` on `/identity/departments/:uuid` and `/identity/branches/:uuid`; the list routes are walled
|
|
201
|
+
and return `{ rows: [{ uuid, name, usedBy? }], total, page, limit }` (`?page&limit&search`). Names
|
|
202
|
+
are unique per company ignoring case and accents (409); delete is refused (409) while any posting
|
|
203
|
+
uses the row; at most 1000 of each per company. Access bundle `org-structure`.
|
|
204
|
+
- **The active membership is always `active`** (G-011): `PUT /identity/memberships/:uuid` refuses
|
|
205
|
+
activating a non-active membership or moving the active one off `active` (409), and a body that
|
|
206
|
+
sets `isActive: true` with another status (400).
|
|
194
207
|
- **Security policies (mandatory 2FA, ADR-0045).** `GET|POST /identity/security-policies`,
|
|
195
208
|
`GET|PUT|DELETE /identity/security-policies/:uuid` (permissions
|
|
196
209
|
`identity:security_policy:{read,create,update,delete}`; no access bundle carries them). Any holder
|
|
@@ -233,7 +246,8 @@ imports: [
|
|
|
233
246
|
`ASTRAM_BOOTSTRAP_OPERATOR`. Root passwords: `ASTRAM_BOOTSTRAP_ROOT_PASSWORD_<USERNAME>` (a
|
|
234
247
|
`secret-ref:` outside development/test, so a `SECRETS_PROVIDER` is needed) or `--interactive`.
|
|
235
248
|
Other flags: `--reissue-links`, `--link-ttl-hours <1..168>` (default 72), `--links-to-stdout`,
|
|
236
|
-
`--claim-unowned-roles
|
|
249
|
+
`--claim-unowned-roles` (a no-op notice since roles belong to one company). Runs hold a database
|
|
250
|
+
lock.
|
|
237
251
|
- **First sign-in.** Non-root users get a locked credential and a one-time set-password link,
|
|
238
252
|
printed once, to a terminal only (`--links-to-stdout` sends them to a pipe, where logs can keep
|
|
239
253
|
them). The operator delivers each link; the user redeems it at the reset page. Outside
|
|
@@ -241,9 +255,11 @@ imports: [
|
|
|
241
255
|
- **Roots.** The run prints each root's uuid and a `SHIELD_ROOT_IDS=…` line: set it and restart
|
|
242
256
|
(`rootIdsFromEnv`, above).
|
|
243
257
|
- **Re-runs and new tenants.** Additive and idempotent: a second `--apply` writes nothing. A file
|
|
244
|
-
covers only the tenants it declares, so onboard a new tenant with a new file.
|
|
245
|
-
|
|
246
|
-
|
|
258
|
+
covers only the tenants it declares, so onboard a new tenant with a new file. Roles, departments
|
|
259
|
+
and branches are created in the company that declares them, and the same name may exist in
|
|
260
|
+
another company. A posting must use a role declared on its own company (a role declared only on a
|
|
261
|
+
parent is a file error). After renaming a department or branch in the app, rename it in the file
|
|
262
|
+
too, or a re-run creates the old name again.
|
|
247
263
|
- **What it does NOT do.** No deletes, renames, moves or revokes (differences are reported only);
|
|
248
264
|
it never activates an inactive membership or updates a changed email. No upload from the UI.
|
|
249
265
|
Department and branch admin routes are separate. With `emailSender` configured, `--email-links`
|
|
@@ -308,7 +324,11 @@ imports: [
|
|
|
308
324
|
joins the outer transaction.
|
|
309
325
|
- `transaction(fn)` runs several pipe calls in one unit of work (rolls back on any failed write);
|
|
310
326
|
`lockForUpdate(typeKey, id)` serialises a check-then-write.
|
|
311
|
-
- `@Field({ immutable: true })` create-only fields; `
|
|
327
|
+
- `@Field({ immutable: true })` create-only fields; `@Field({ nullable: true })` fields that
|
|
328
|
+
create, update and `createMany` may set to NULL (`json` included: SQL NULL, not `"null"`;
|
|
329
|
+
the audit row lists the cleared keys, never values; the column must be NULL-able in your
|
|
330
|
+
migration; refused on trusted, primary-key, `readScope` and `required` fields). Without
|
|
331
|
+
`nullable`, `null` is a validation error. `trusted` fields are stamped server-side.
|
|
312
332
|
`readScope` declares the subtree wall; `create: 'unscoped'` opts a type out of the create wall.
|
|
313
333
|
- **Physical wall** — the raw DB pool is sealed in the engine. The scaffold's
|
|
314
334
|
`server/eslint.config.mjs` fails lint on driver imports, raw pools, `.query()` and the
|
|
@@ -321,6 +341,28 @@ imports: [
|
|
|
321
341
|
(a Rule-4 STOP). Field types this version: `string`, `number`, `boolean`, `uuid`, `timestamp`,
|
|
322
342
|
`json`.
|
|
323
343
|
|
|
344
|
+
## Shared reference data: global types (ADR-0046; engine 0.11, guard 0.20)
|
|
345
|
+
- **Declare** `@ObjectType({ ..., access: { plane, global: true } })` for rows every tenant reads the
|
|
346
|
+
same (code lists, catalogues, units). No tenant field, no `readScope`. Registration refuses
|
|
347
|
+
`global` with `tenantScoped`, `owned`, `system`, a `readScope`, a `trusted: 'tenantId'` field,
|
|
348
|
+
`crudActions: true` or any action other than `read`, naming the type.
|
|
349
|
+
- **Reads** (list, get, `aggregate()`, search) carry no tenant clause, but the type's `read` grant
|
|
350
|
+
and deny policies still apply: a caller without `read` sees nothing. Root follows the plane:
|
|
351
|
+
`plane: 'data'` keeps root denied, `plane: 'control'` lets root read.
|
|
352
|
+
- **Writes** through the engine (create, update, delete, `createMany`) are refused for everyone,
|
|
353
|
+
root included, before any SQL. Seed and change rows only in an app migration, idempotent and
|
|
354
|
+
select-before-insert on the business key.
|
|
355
|
+
- **Policies do not cascade.** A record policy on a global type applies only to callers whose
|
|
356
|
+
active tenant is the policy's tenant, plus platform policies; a parent's deny does not reach its
|
|
357
|
+
child companies' users. To hide a row across a group, author the policy in each tenant or as a
|
|
358
|
+
platform policy.
|
|
359
|
+
- **No sharing, no cross-tenant reach**: a global type is never `owned`, so never a share target.
|
|
360
|
+
- **Tenant-less data types are now refused writes.** A `plane: 'data'` type with no tenant field,
|
|
361
|
+
no `readScope` and not global refuses non-root create, update, delete and `createMany`
|
|
362
|
+
(`AuthorizationDeniedError('tenantless_data_write_denied')`). Boot logs the global types once and
|
|
363
|
+
warns once per such type: make it global, give it a tenant field, or add a `readScope`.
|
|
364
|
+
- Never fake shared data with a match-all `readScope`, and never put tenant data in a global type.
|
|
365
|
+
|
|
324
366
|
## Custom object actions (ADR-0038; engine 0.7, guard 0.14, identity 0.11)
|
|
325
367
|
- **Declare domain verbs on the type**: `@ObjectType({ ..., actions: ['activate', 'review'] })`.
|
|
326
368
|
The type's vocabulary is `read, create, update, delete` plus the declared actions, and each one
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astram/guide",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Astram framework — the AI/dev GUIDE layer (ADR-0025). Ships the framework's hard rules + capabilities manifest as installed, upgrade-refreshed docs. A consumer app's CLAUDE.md references FRAMEWORK_RULES.md from this package; `npm update` replaces it wholesale so new rules arrive cleanly, while the app's own .astram/guide/ layer is never touched. This is the framework-owned FLOOR: an app rule may tighten it, never weaken it.",
|
|
6
6
|
"type": "commonjs",
|