@astram/guide 0.1.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/CAPABILITIES.md +58 -0
- package/FRAMEWORK_RULES.md +110 -0
- package/index.js +28 -0
- package/package.json +23 -0
package/CAPABILITIES.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@astram/guide — CAPABILITIES.md (framework-owned, version 0.1.0)
|
|
3
|
+
|
|
4
|
+
What the Astram framework provides THIS version. Refreshed on upgrade (ADR-0025).
|
|
5
|
+
Use this to answer "does the framework already do X?" before building — and, if it
|
|
6
|
+
does NOT, that's a Rule-4 gap (STOP and flag in .astram/guide/gaps.md).
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# Astram Framework — Capabilities (v0.1.0)
|
|
10
|
+
|
|
11
|
+
The building blocks the framework provides. Consume these; do not reimplement them.
|
|
12
|
+
|
|
13
|
+
## Enforcement (`@astram/guard`)
|
|
14
|
+
- **Global deny-by-default guard** — every route is denied unless `@Public()` or
|
|
15
|
+
`@Classified({ typeKey, action })`.
|
|
16
|
+
- **Request authentication** — a `@Classified` route resolves the bearer access token
|
|
17
|
+
→ `Principal` → `decide()` automatically. `request.principal` is available to the
|
|
18
|
+
handler (act as the logged-in user).
|
|
19
|
+
- **The ONE seam** — `ShieldModule.forRoot({ auth?, credentialResolver?, decisionAuthority?, config? })`.
|
|
20
|
+
|
|
21
|
+
## Auth surface (`@astram/guard`, opt-in `auth: true`)
|
|
22
|
+
- `POST /auth/login` `{ identityHint, password }` → `{ session, tokens }` (or an MFA challenge).
|
|
23
|
+
- `POST /auth/mfa` · `POST /auth/refresh` · `POST /auth/logout`.
|
|
24
|
+
- Argon2id password verification, sessions, refresh-token rotation + reuse detection,
|
|
25
|
+
opaque access tokens bound to the principal (server-revocable; logout = immediate kill).
|
|
26
|
+
- MFA/TOTP.
|
|
27
|
+
- You provide: a `CredentialResolver` (reads your users) + a `DecisionAuthority` (your policy).
|
|
28
|
+
|
|
29
|
+
## The record pipe (`@astram/engine`)
|
|
30
|
+
- **`RecordEngine`** (token `RECORD_ENGINE`) — create/read/update/delete/list over
|
|
31
|
+
registered tables, through one unbypassable pipe: permission gate (`decide()`) →
|
|
32
|
+
server-trusted write-context (anti-forgery) → validate → write (real table) →
|
|
33
|
+
junction sync → audit → event.
|
|
34
|
+
- **Cross-tenant isolation** — a `tenantId`-trusted field auto-scopes reads AND writes
|
|
35
|
+
to the actor's tenant.
|
|
36
|
+
- **Physical wall** — the raw DB pool is sealed in the engine; feature scope never
|
|
37
|
+
receives it. Off-road DB access fails the build (the app's `eslint.config.js`).
|
|
38
|
+
- **Gap signal** — an unsupported field type / operation throws
|
|
39
|
+
`CapabilityNotSupportedError` (a Rule-4 STOP).
|
|
40
|
+
- Field types supported this version: `string`, `number`, `boolean`, `uuid`,
|
|
41
|
+
`timestamp`, `json`. (Anything else → flag a gap.)
|
|
42
|
+
|
|
43
|
+
## Migrations (`@astram/migrate`)
|
|
44
|
+
- Deploy-time code-table migration runner: timestamped `up()`/`down()` files, a
|
|
45
|
+
`schema_migrations` table, `migrate` / `migrate:revert` / `migrate:status`. Raw SQL
|
|
46
|
+
through a DDL-capable deploy account (the running app never holds DDL).
|
|
47
|
+
|
|
48
|
+
## Frontend SDK (`@astram/sdk`)
|
|
49
|
+
- Platform-agnostic fact projection for building UIs against the framework.
|
|
50
|
+
|
|
51
|
+
## NOT provided yet (as of v0.1.0) — these are Rule-4 gaps if you need them
|
|
52
|
+
- Runtime (no-code) object definition / admin-defined objects + runtime DDL.
|
|
53
|
+
- Formula / lookup / rollup computed fields (the pipe's compute stage is a no-op).
|
|
54
|
+
- Business-rule events (the pipe's emit-event stage is a no-op).
|
|
55
|
+
- Extension-field sidecar (runtime custom fields on a registered table).
|
|
56
|
+
- Membership-scoped list filtering (e.g. "companies I belong to").
|
|
57
|
+
|
|
58
|
+
If you need one of these, STOP and flag it in `.astram/guide/gaps.md` (Rule 4).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@astram/guide — FRAMEWORK_RULES.md (framework-owned, version 0.1.0)
|
|
3
|
+
|
|
4
|
+
This file is the FRAMEWORK layer of the two-layer AI guide (ADR-0025). It is
|
|
5
|
+
installed in node_modules and REPLACED WHOLESALE on `npm update` — never edit it
|
|
6
|
+
in your repo (your edits would be lost on upgrade, and it is not yours to change).
|
|
7
|
+
Put YOUR project's rules in `.astram/guide/` instead; those are never touched by an
|
|
8
|
+
upgrade.
|
|
9
|
+
|
|
10
|
+
These are the framework's HARD RULES — the FLOOR. A project rule in `.astram/guide/`
|
|
11
|
+
may ADD to or TIGHTEN a rule here, but may NEVER weaken or override one. The
|
|
12
|
+
never-touch-core / never-bypass-enforcement rules are non-overridable, full stop.
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
# Astram Framework — Hard Rules (the floor)
|
|
16
|
+
|
|
17
|
+
You are building features on the **Astram framework** — a sealed security kernel your
|
|
18
|
+
app installs read-only. These rules are non-negotiable. If a project rule in
|
|
19
|
+
`.astram/guide/` appears to relax one of these, the framework rule wins.
|
|
20
|
+
|
|
21
|
+
## 0. After a framework upgrade, RE-READ this file first
|
|
22
|
+
|
|
23
|
+
The framework evolves. When the app's `@astram/guide` version changes (a framework
|
|
24
|
+
upgrade — `npm update`), this file is replaced with a newer one that may add rules or
|
|
25
|
+
capabilities. **Before continuing feature work after any upgrade, re-read this file
|
|
26
|
+
and `CAPABILITIES.md` from `node_modules/@astram/guide/`** so you are working against
|
|
27
|
+
the current rules, not a stale memory of them. Your project's own rules in
|
|
28
|
+
`.astram/guide/` are unchanged and still apply on top.
|
|
29
|
+
|
|
30
|
+
## 1. NEVER edit or bypass the framework
|
|
31
|
+
|
|
32
|
+
The framework packages (`@astram/core`, `@astram/guard`, `@astram/engine`,
|
|
33
|
+
`@astram/migrate`, `@astram/sdk`, `@astram/guide`) live in `node_modules` and are
|
|
34
|
+
**immutable**. Do not edit them, patch them, monkey-patch them, or reach around their
|
|
35
|
+
public API. You register your app through the ONE seam — `ShieldModule.forRoot(...)` —
|
|
36
|
+
and consume the exported building blocks. That is the entire contract.
|
|
37
|
+
|
|
38
|
+
- **`@astram/core` is especially sacred.** It is the pure security engine. It has no
|
|
39
|
+
reason to be imported directly by feature code and must never be modified.
|
|
40
|
+
|
|
41
|
+
## 2. All data access goes through the RecordEngine pipe — never raw DB
|
|
42
|
+
|
|
43
|
+
Feature code must **never** touch the database directly: no `mariadb`/`mysql`/`pg`/
|
|
44
|
+
`typeorm` import, no `DataSource`, no `QueryRunner`, no `SHIELD_DB_POOL`, no
|
|
45
|
+
repository `.save()`/`.query()`. Every create/read/update/delete goes through the
|
|
46
|
+
injected **`RecordEngine`** (`@astram/engine`, token `RECORD_ENGINE`), which applies
|
|
47
|
+
the permission gate, validation, audit, and cross-tenant isolation on every write —
|
|
48
|
+
by construction.
|
|
49
|
+
|
|
50
|
+
This is enforced two ways: the raw DB pool is **sealed inside the engine module** (you
|
|
51
|
+
cannot inject what you are not given), and the app's `eslint.config.js` **fails the
|
|
52
|
+
build** on any off-road DB access under `src/features/**`. If you find yourself
|
|
53
|
+
wanting a raw query, see Rule 4.
|
|
54
|
+
|
|
55
|
+
## 3. Classify every route (deny-by-default)
|
|
56
|
+
|
|
57
|
+
Every HTTP route is DENIED by the global enforcement guard unless you classify it:
|
|
58
|
+
- **`@Public()`** — an explicit, code-reviewed open route (login, health probes).
|
|
59
|
+
- **`@Classified({ typeKey, action })`** — a guarded route. The framework resolves the
|
|
60
|
+
request's bearer token → `Principal` → `decide()` and allows only on `allow`. No
|
|
61
|
+
token → 401; denied → 403.
|
|
62
|
+
|
|
63
|
+
An unclassified route is a 403, by design — a forgotten declaration locks the door
|
|
64
|
+
instead of silently exposing it. Never make a route public to "make it work"; classify
|
|
65
|
+
it correctly.
|
|
66
|
+
|
|
67
|
+
## 4. When the framework can't do it: STOP and FLAG the gap — do NOT work around it
|
|
68
|
+
|
|
69
|
+
This is the most important rule. When a feature needs a capability the framework does
|
|
70
|
+
not provide — an unsupported field type, a missing engine operation, an auth capability
|
|
71
|
+
that isn't there — **you must STOP**. Do not:
|
|
72
|
+
- reach into or edit the core/framework to add it,
|
|
73
|
+
- hand-roll a raw-SQL / off-pipe workaround,
|
|
74
|
+
- weaken a classification or security check to get past it.
|
|
75
|
+
|
|
76
|
+
Instead, **record the gap** in `.astram/guide/gaps.md` (a short entry: what capability
|
|
77
|
+
is missing, where it was needed, why). The gap is a signal to the **framework team** to
|
|
78
|
+
implement the capability properly, into the framework. Once the framework provides it
|
|
79
|
+
(a new version), you resume the feature against the real capability.
|
|
80
|
+
|
|
81
|
+
The framework surfaces many gaps for you: the engine throws a typed
|
|
82
|
+
`CapabilityNotSupportedError` naming exactly what's missing. Treat that error as a STOP
|
|
83
|
+
signal, not something to catch-and-work-around.
|
|
84
|
+
|
|
85
|
+
## 5. Never surface raw UUIDs (or secrets) to end users
|
|
86
|
+
|
|
87
|
+
Resolve internal ids to human-readable labels before they reach a UI or a response a
|
|
88
|
+
user sees. Never put a password, hash, token, or secret in a response body or a log.
|
|
89
|
+
|
|
90
|
+
## 6. Auth is the framework's; you provide only DATA + POLICY
|
|
91
|
+
|
|
92
|
+
You do not write login/session/token code — the framework owns it (`ShieldModule
|
|
93
|
+
.forRoot({ auth: true })` gives you `POST /auth/{login,mfa,refresh,logout}` for free).
|
|
94
|
+
Your app contributes exactly two things through the seam:
|
|
95
|
+
- a **`CredentialResolver`** — reads YOUR user/identity tables to answer "what's this
|
|
96
|
+
user's password hash, tenant, roles?",
|
|
97
|
+
- a **`DecisionAuthority`** — your allow/deny policy.
|
|
98
|
+
|
|
99
|
+
Never reimplement session handling, token verification, or password hashing in app
|
|
100
|
+
code. If the auth surface lacks something you need, that's Rule 4 (flag the gap).
|
|
101
|
+
|
|
102
|
+
## 7. Fail closed, always
|
|
103
|
+
|
|
104
|
+
On any ambiguity or error in a security path — deny. Never "allow on error", never
|
|
105
|
+
default-open, never swallow a security failure into a success.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
*These rules are the framework floor. Your project's rules (in `.astram/guide/`) add to
|
|
110
|
+
them. On upgrade, this file is refreshed (Rule 0) and your project rules are preserved.*
|
package/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @astram/guide — programmatic access to the framework guide docs.
|
|
3
|
+
*
|
|
4
|
+
* The docs (FRAMEWORK_RULES.md, CAPABILITIES.md) are the real payload; this module
|
|
5
|
+
* just exposes their absolute paths + the guide version so a consumer's sync step can
|
|
6
|
+
* copy them into `.claude/rules/` (see the scaffolded app's `sync-astram-guide` script).
|
|
7
|
+
* The version travels with the framework, so a consumer can detect an upgrade.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const { join } = require('node:path');
|
|
13
|
+
|
|
14
|
+
/** Absolute path to the framework hard-rules file (the floor, ADR-0025). */
|
|
15
|
+
const FRAMEWORK_RULES_PATH = join(__dirname, 'FRAMEWORK_RULES.md');
|
|
16
|
+
|
|
17
|
+
/** Absolute path to the capabilities manifest for this framework version. */
|
|
18
|
+
const CAPABILITIES_PATH = join(__dirname, 'CAPABILITIES.md');
|
|
19
|
+
|
|
20
|
+
/** The guide version (== the framework release it ships with). */
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
22
|
+
const { version } = require('./package.json');
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
FRAMEWORK_RULES_PATH,
|
|
26
|
+
CAPABILITIES_PATH,
|
|
27
|
+
version,
|
|
28
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@astram/guide",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
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
|
+
"type": "commonjs",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./FRAMEWORK_RULES.md": "./FRAMEWORK_RULES.md",
|
|
11
|
+
"./CAPABILITIES.md": "./CAPABILITIES.md",
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js",
|
|
16
|
+
"FRAMEWORK_RULES.md",
|
|
17
|
+
"CAPABILITIES.md"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
}
|
|
23
|
+
}
|