@astram/guide 0.1.0 → 0.2.1
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 +166 -35
- package/FRAMEWORK_RULES.md +32 -21
- package/LICENSE +21 -0
- package/index.js +0 -1
- package/package.json +2 -2
package/CAPABILITIES.md
CHANGED
|
@@ -1,58 +1,189 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
@astram/guide — CAPABILITIES.md (framework-owned, version 0.
|
|
2
|
+
@astram/guide — CAPABILITIES.md (framework-owned, version 0.2.0)
|
|
3
3
|
|
|
4
4
|
What the Astram framework provides THIS version. Refreshed on upgrade (ADR-0025).
|
|
5
5
|
Use this to answer "does the framework already do X?" before building — and, if it
|
|
6
6
|
does NOT, that's a Rule-4 gap (STOP and flag in .astram/guide/gaps.md).
|
|
7
|
+
|
|
8
|
+
Matches the framework release: @astram/core 1.4 · guard 0.8 · engine 0.4 · identity 0.5 ·
|
|
9
|
+
frontend-core 0.5 · migrate 0.3 · sdk 0.2 · create-astram-app 0.6.
|
|
7
10
|
-->
|
|
8
11
|
|
|
9
|
-
# Astram Framework — Capabilities (v0.
|
|
12
|
+
# Astram Framework — Capabilities (v0.2.0)
|
|
13
|
+
|
|
14
|
+
The building blocks the framework provides. Consume these; do not reimplement them. Everything
|
|
15
|
+
below is wired by the two `forRoot` calls in your `app.module.ts`; you write no security code.
|
|
16
|
+
|
|
17
|
+
## Wiring (the ONE seam, plus the identity module)
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { ShieldModule } from '@astram/guard';
|
|
21
|
+
import {
|
|
22
|
+
IdentityFeatureModule, identityAuthActionKeys, identityControlPlaneKeys,
|
|
23
|
+
buildIdentityRecordAuthorization,
|
|
24
|
+
} from '@astram/identity';
|
|
25
|
+
// Raw-pool factory: composition root only (app.module.ts), never exported from it.
|
|
26
|
+
import { createIdentityAuthzPool } from '@astram/identity/internal';
|
|
27
|
+
|
|
28
|
+
const rootIds = ['<platform-admin user uuid>']; // from config, never from a request
|
|
29
|
+
const authzPool = createIdentityAuthzPool();
|
|
10
30
|
|
|
11
|
-
|
|
31
|
+
imports: [
|
|
32
|
+
ShieldModule.forRoot({
|
|
33
|
+
auth: true,
|
|
34
|
+
emailSender: myEmailSender, // or 'disabled' — REQUIRED outside dev/test
|
|
35
|
+
rootIds,
|
|
36
|
+
identityActionKeys: identityAuthActionKeys,
|
|
37
|
+
identityControlPlaneKeys,
|
|
38
|
+
recordAuthorization: buildIdentityRecordAuthorization(authzPool, [/* YourType */]),
|
|
39
|
+
}),
|
|
40
|
+
IdentityFeatureModule.forRoot({ rootIds, extraObjects: [/* YourType */] }),
|
|
41
|
+
],
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- **`ShieldModule.forRoot(options?)`** (`@astram/guard`; `forRootAsync` for DI-computed options).
|
|
45
|
+
Options: `auth?`, `recordAuthorization?` (or a pre-built `decisionAuthority?`, never both),
|
|
46
|
+
`emailSender?: EmailSender | 'disabled'`, `rootIds?`, `identityActionKeys?`,
|
|
47
|
+
`identityControlPlaneKeys?`, `config?`, and optional overrides `credentialResolver?`,
|
|
48
|
+
`profileResolver?`, `credentialWriter?`, `resetTokenStore?`, `recoveryCodeStore?`,
|
|
49
|
+
`profileWriter?`, `sessionSweeper?`, `auditSink?`, `mfaDeviceStore?`, `totpVerifier?`,
|
|
50
|
+
`sessionFactsProjector?`, `scopeSubtreeResolver?`, `membershipChecker?`. With `auth: true` every
|
|
51
|
+
override has a MariaDB/Redis default; leave them out unless you have a reason.
|
|
52
|
+
- **`IdentityFeatureModule.forRoot({ rootIds?, extraObjects? })`** (`@astram/identity`). Pass the
|
|
53
|
+
SAME `rootIds` as `ShieldModule`, and the SAME `extraObjects` as
|
|
54
|
+
`buildIdentityRecordAuthorization`. Without `rootIds` it logs a boot warning.
|
|
55
|
+
- `ShieldModule.forRoot()` with no options = enforcement only (no DB, no Redis, boots without
|
|
56
|
+
`.env`). Omitting `recordAuthorization` (and `decisionAuthority`) binds a stub that denies every
|
|
57
|
+
`@Classified` route.
|
|
12
58
|
|
|
13
59
|
## Enforcement (`@astram/guard`)
|
|
14
60
|
- **Global deny-by-default guard** — every route is denied unless `@Public()` or
|
|
15
|
-
`@Classified({ typeKey, action })`.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
61
|
+
`@Classified({ typeKey, action })`. With `IdentityFeatureModule` wired, an unclassified route
|
|
62
|
+
aborts boot (`verifyGatable`).
|
|
63
|
+
- **One decision authority** — a `@Classified` route resolves the bearer token → `Principal` →
|
|
64
|
+
`decide()`; `request.principal` is the caller. Identity, tenant, roles and root status come from
|
|
65
|
+
the session, never from the request.
|
|
66
|
+
- **Redis-only request path** — session, cached facts and the per-tenant permission version are
|
|
67
|
+
read from Redis; no DB query on a request with fresh facts. Redis unavailable ⇒ deny (fail
|
|
68
|
+
closed), bounded by `REDIS_COMMAND_TIMEOUT_MS` (default 2000).
|
|
69
|
+
- **Live roles** — a role posting change, a grant change or a user disable bumps the permission
|
|
70
|
+
version; the next request reloads facts and roles. No re-login needed.
|
|
71
|
+
- **Engine errors → HTTP** — `AuthorizationDeniedError` → 403, `ValidationError` → 400,
|
|
72
|
+
`EngineUnavailableError` → 503 for your controllers too (global filter; id-free messages).
|
|
73
|
+
|
|
74
|
+
## Authentication and sessions (`@astram/guard`, `auth: true`)
|
|
75
|
+
- `POST /auth/login` `{ identityHint, password }` → `{ session, tokens }`, or an MFA challenge.
|
|
76
|
+
`POST /auth/mfa` (TOTP or recovery code) · `POST /auth/refresh` · `POST /auth/logout` ·
|
|
77
|
+
`GET /auth/me` · `POST /auth/scope-switch` · `POST /auth/forgot-password` ·
|
|
78
|
+
`POST /auth/reset-password`.
|
|
79
|
+
- `/account/*`: change password, edit profile, MFA devices (enrol, confirm, rename, delete),
|
|
80
|
+
regenerate recovery codes. A recovery-code login is restricted until a new device is enrolled.
|
|
81
|
+
- Argon2id passwords; opaque access tokens bound in Redis (server-revocable, logout is immediate);
|
|
82
|
+
refresh-token rotation with reuse detection (a replay revokes the whole lineage and its live
|
|
83
|
+
access tokens). An access token lives at most the access TTL (15 min default).
|
|
84
|
+
- **MFA enforced**: any user with a confirmed TOTP device must pass `/auth/mfa` on every password
|
|
85
|
+
login. 5 wrong codes / 15 min per account; a used code cannot be replayed (90 s); the pending
|
|
86
|
+
challenge lives in Redis (works across instances, never holds the password).
|
|
87
|
+
- **Lockout and rate limits**: per-account login and forgot-password budgets (keyed on the
|
|
88
|
+
resolved account, so spelling variants share one counter); per-IP limits for login,
|
|
89
|
+
forgot-password and reset-password, active only when Express `trust proxy` is set explicitly
|
|
90
|
+
(`TRUST_PROXY` in the scaffold). Rate limiters fail open on a Redis error by design; the request
|
|
91
|
+
path and the TOTP replay guard fail closed.
|
|
92
|
+
- **Disabled accounts** cannot log in or refresh; disabling a user revokes their sessions and
|
|
93
|
+
drops their roles at once.
|
|
94
|
+
- **Silent recovery**: after a Redis loss, the next refresh rebuilds the session from MariaDB
|
|
95
|
+
(no forced re-login); a restricted (recovery-code) session rebuilds restricted.
|
|
96
|
+
- **Email**: reset links go through your `EmailSender` (`send({ to, subject, html, text })`).
|
|
97
|
+
Outside `NODE_ENV=development|test`, `auth: true` without `emailSender` fails boot;
|
|
98
|
+
`'disabled'` sends nothing and `/auth/forgot-password` still answers 204.
|
|
99
|
+
- **Service-account API keys** (`Authorization: Bearer ask_…`) authenticate agents and machine
|
|
100
|
+
clients through the same `decide()` path; scope, status and roles are read live; issuing a key
|
|
101
|
+
needs an explicit expiry (`expiresAtMs`) or `neverExpires: true`.
|
|
102
|
+
- Override contracts: the login lockout is keyed on the account the injected `credentialResolver`
|
|
103
|
+
returns for the hint as typed, so use that one resolver wherever login resolves accounts; a
|
|
104
|
+
custom `totpVerifier` must use a window of at most ±1 step and must not normalise the code, or
|
|
105
|
+
the 90 s replay claim no longer covers it.
|
|
106
|
+
|
|
107
|
+
## Identity and tenancy (`@astram/identity`)
|
|
108
|
+
- Admin API under `/identity/*`: companies (create, update name/status, move via
|
|
109
|
+
`PUT /identity/companies/:uuid/parent`, delete), users, roles, memberships and role postings,
|
|
110
|
+
access (role ↔ permission grants and access bundles), permission catalog, record policies
|
|
111
|
+
(guided rule builder, 14 operators, no raw expressions), record shares (owner shares),
|
|
112
|
+
cross-tenant grants (offer, accept, revoke), service accounts (roles, scope, keys), audit read.
|
|
113
|
+
`GET /identity/auth-facts` feeds the frontend's permission-gated navigation.
|
|
114
|
+
- **Scoped-hierarchy tenancy** (ADR-0027): a tenant is a company node; an admin's authority covers
|
|
115
|
+
its subtree. Reads, updates, deletes AND creates of subtree-scoped types are walled in the query
|
|
116
|
+
(ADR-0030, ADR-0034). Non-root admins cannot create top-level tenants.
|
|
117
|
+
- **No escalation**: you can grant, revoke or assign (via postings) only roles and keys you hold
|
|
118
|
+
(ADR-0029); a role used outside your subtree is root-only; changing another user's email or
|
|
119
|
+
status needs dominance over that user. Grant changes are audited in the same transaction.
|
|
120
|
+
- **Cross-tenant access** only through an explicit, consented, scoped, revocable grant (ADR-0009).
|
|
121
|
+
- **Root** = the `rootIds` allowlist only. Root administers the control plane and cannot read
|
|
122
|
+
customer business data.
|
|
123
|
+
- Migrations for `shield_identity`, `shield_authz`, `shield_app` identity tables ship in
|
|
124
|
+
`@astram/identity` and `@astram/guard` and run through `@astram/migrate` automatically. Dev seeds
|
|
125
|
+
(099/100) run only with `NODE_ENV=development` or `ASTRAM_SEED_DEV=1`.
|
|
28
126
|
|
|
29
127
|
## The record pipe (`@astram/engine`)
|
|
30
|
-
- **`RecordEngine`** (token `RECORD_ENGINE`) — create/
|
|
31
|
-
registered
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
|
|
128
|
+
- **`RecordEngine`** (token `RECORD_ENGINE`) / `RecordsService` — create/get/update/delete/list
|
|
129
|
+
over registered object types through one pipe: permission gate (`decide()`) → server-trusted
|
|
130
|
+
write context → validate → write → junction sync → audit (mandatory) → event.
|
|
131
|
+
- **Record decision inputs**: `get`, `update` and `delete` read the stored row first and pass its
|
|
132
|
+
fields and tenant to `decide()`, so record rules such as "deny update where status eq locked"
|
|
133
|
+
hold on single-record paths. An unreachable id (missing, other tenant, outside your subtree) is
|
|
134
|
+
a 403, identical for missing and foreign.
|
|
135
|
+
- **List scoping in the query** (ADR-0028): `list()` returns exactly the rows `decide()` allows
|
|
136
|
+
(record rules, owner shares, cross-tenant grants, subtree), filtered in SQL; `get()` uses the
|
|
137
|
+
same scope. Membership/subtree-scoped lists ("companies I belong to") are provided.
|
|
138
|
+
- Stable paging: every `list()` orders by the primary key last.
|
|
139
|
+
- `transaction(fn)` runs several pipe calls in one unit of work (rolls back on any failed write);
|
|
140
|
+
`lockForUpdate(typeKey, id)` serialises a check-then-write.
|
|
141
|
+
- `@Field({ immutable: true })` create-only fields; `trusted` fields are stamped server-side.
|
|
142
|
+
`readScope` declares the subtree wall; `create: 'unscoped'` opts a type out of the create wall.
|
|
143
|
+
- **Physical wall** — the raw DB pool is sealed in the engine. The scaffold's
|
|
144
|
+
`server/eslint.config.mjs` fails lint on driver imports, raw pools, `.query()` and the
|
|
145
|
+
framework's internal DB exports anywhere in `src/**` except `app.module.ts` and `main.ts`. A lint
|
|
146
|
+
rule is a backstop, not a sandbox: do not go around it.
|
|
147
|
+
- **Gap signal** — an unsupported field type or operation throws `CapabilityNotSupportedError`
|
|
148
|
+
(a Rule-4 STOP). Field types this version: `string`, `number`, `boolean`, `uuid`, `timestamp`,
|
|
149
|
+
`json`.
|
|
42
150
|
|
|
43
151
|
## Migrations (`@astram/migrate`)
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
152
|
+
- `migrate` / `migrate:revert` / `migrate:status` with the deploy-time DDL account (`DDL_DB_*`);
|
|
153
|
+
the running app never holds DDL. Framework migrations merge with yours (`APP_MIGRATION_DIR`).
|
|
154
|
+
- Numeric ordering, duplicate-id detection, a database lock so concurrent deploys never apply a
|
|
155
|
+
migration twice (`APP_MIGRATION_LOCK_TIMEOUT`, default 120 s), and drift detection (a changed
|
|
156
|
+
applied file is reported; `--strict` fails on it). Never edit a migration that already ran.
|
|
157
|
+
|
|
158
|
+
## Frontend (`@astram/frontend-core`, `@astram/sdk`, scaffolded `frontend/`)
|
|
159
|
+
- `@astram/frontend-core`: auth, account, identity, audit, record-policy, share, cross-tenant and
|
|
160
|
+
service-account services over an injected `HttpClient`; `createRefreshingHttp` refreshes an
|
|
161
|
+
expired access token transparently (single-flight, cross-tab lock, one retry; an outage keeps
|
|
162
|
+
the session). Clients that call `/auth/refresh` concurrently without single-flight can log the
|
|
163
|
+
user out (reuse detection).
|
|
164
|
+
- `@astram/sdk`: platform-agnostic auth-fact projection (`check` → allowed/deny/unavailable,
|
|
165
|
+
version-staleness refetch, retry/backoff). The UI never re-decides.
|
|
166
|
+
- The scaffolded frontend (`create-astram-app --with-frontend` or `npx -p create-astram-app
|
|
167
|
+
astram-add-frontend`) ships login, MFA, recovery, forgot/reset, account and admin pages, with a
|
|
168
|
+
strict Content-Security-Policy.
|
|
47
169
|
|
|
48
|
-
##
|
|
49
|
-
-
|
|
170
|
+
## Available, but NOT wired to a data path (do not assume they protect data yet)
|
|
171
|
+
- **Envelope encryption / per-tenant DEKs** (`createEncryptionService`, `LocalKekCrypto`): the
|
|
172
|
+
engine exists, but no record field is encrypted and no production `DekStore` ships.
|
|
173
|
+
- **Secrets provider** (`SecretsProvider`, `resolveSecretRefs`, `EncryptedFileSecretsProvider`):
|
|
174
|
+
the guard's runtime config does not resolve `secret-ref:` values at boot.
|
|
175
|
+
- **Risk engine** (`createRiskEngine`): advisory scoring with no request-path caller.
|
|
176
|
+
- **Hash-chained audit** (`createAuditRecorder`, `verifyChain`): the shipped audit writer is
|
|
177
|
+
append-only but does not store the chain.
|
|
50
178
|
|
|
51
|
-
## NOT provided yet
|
|
179
|
+
## NOT provided yet — these are Rule-4 gaps if you need them
|
|
52
180
|
- Runtime (no-code) object definition / admin-defined objects + runtime DDL.
|
|
53
181
|
- Formula / lookup / rollup computed fields (the pipe's compute stage is a no-op).
|
|
54
182
|
- Business-rule events (the pipe's emit-event stage is a no-op).
|
|
55
183
|
- Extension-field sidecar (runtime custom fields on a registered table).
|
|
56
|
-
-
|
|
184
|
+
- Per-principal column masking on reads (row-level scoping is provided; column-level is not).
|
|
185
|
+
- External identity providers (OIDC, SAML, LDAP, Entra ID, Cognito, Okta, Google Workspace) and
|
|
186
|
+
WebAuthn ceremonies.
|
|
187
|
+
- Tenant- or role-mandated MFA (MFA is enforced for users who enrolled a device).
|
|
57
188
|
|
|
58
189
|
If you need one of these, STOP and flag it in `.astram/guide/gaps.md` (Rule 4).
|
package/FRAMEWORK_RULES.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
@astram/guide — FRAMEWORK_RULES.md (framework-owned, version 0.
|
|
2
|
+
@astram/guide — FRAMEWORK_RULES.md (framework-owned, version 0.2.0)
|
|
3
3
|
|
|
4
4
|
This file is the FRAMEWORK layer of the two-layer AI guide (ADR-0025). It is
|
|
5
5
|
installed in node_modules and REPLACED WHOLESALE on `npm update` — never edit it
|
|
@@ -30,10 +30,12 @@ the current rules, not a stale memory of them. Your project's own rules in
|
|
|
30
30
|
## 1. NEVER edit or bypass the framework
|
|
31
31
|
|
|
32
32
|
The framework packages (`@astram/core`, `@astram/guard`, `@astram/engine`,
|
|
33
|
-
`@astram/migrate`, `@astram/
|
|
34
|
-
**immutable**. Do not edit them, patch
|
|
35
|
-
public API. You register your app through
|
|
36
|
-
|
|
33
|
+
`@astram/identity`, `@astram/migrate`, `@astram/frontend-core`, `@astram/sdk`,
|
|
34
|
+
`@astram/guide`) live in `node_modules` and are **immutable**. Do not edit them, patch
|
|
35
|
+
them, monkey-patch them, or reach around their public API. You register your app through
|
|
36
|
+
the ONE seam — `ShieldModule.forRoot(...)`, plus `IdentityFeatureModule.forRoot(...)` for
|
|
37
|
+
the identity surface — and consume the exported building blocks. That is the entire
|
|
38
|
+
contract.
|
|
37
39
|
|
|
38
40
|
- **`@astram/core` is especially sacred.** It is the pure security engine. It has no
|
|
39
41
|
reason to be imported directly by feature code and must never be modified.
|
|
@@ -48,8 +50,12 @@ the permission gate, validation, audit, and cross-tenant isolation on every writ
|
|
|
48
50
|
by construction.
|
|
49
51
|
|
|
50
52
|
This is enforced two ways: the raw DB pool is **sealed inside the engine module** (you
|
|
51
|
-
cannot inject what you are not given)
|
|
52
|
-
|
|
53
|
+
cannot inject what you are not given) and no package entry point exports a raw pool or
|
|
54
|
+
unit of work (they live on `@astram/*/internal` subpaths for `app.module.ts` wiring only);
|
|
55
|
+
and the app's `server/eslint.config.mjs` **fails lint** on off-road DB access anywhere in
|
|
56
|
+
`src/**` (only `app.module.ts` and `main.ts` may import `@astram/*/internal` and wire
|
|
57
|
+
infrastructure, and they may not re-export it). The lint wall is a backstop, not a
|
|
58
|
+
sandbox: code that slips past it is still a Rule 2 violation. If you find yourself
|
|
53
59
|
wanting a raw query, see Rule 4.
|
|
54
60
|
|
|
55
61
|
## 3. Classify every route (deny-by-default)
|
|
@@ -60,9 +66,10 @@ Every HTTP route is DENIED by the global enforcement guard unless you classify i
|
|
|
60
66
|
request's bearer token → `Principal` → `decide()` and allows only on `allow`. No
|
|
61
67
|
token → 401; denied → 403.
|
|
62
68
|
|
|
63
|
-
An unclassified route is a 403, by design
|
|
64
|
-
|
|
65
|
-
it
|
|
69
|
+
An unclassified route is a 403, by design, and once `IdentityFeatureModule` is wired the
|
|
70
|
+
app refuses to boot with one — a forgotten declaration locks the door instead of
|
|
71
|
+
silently exposing it. Never make a route public to "make it work"; classify it
|
|
72
|
+
correctly.
|
|
66
73
|
|
|
67
74
|
## 4. When the framework can't do it: STOP and FLAG the gap — do NOT work around it
|
|
68
75
|
|
|
@@ -87,17 +94,21 @@ signal, not something to catch-and-work-around.
|
|
|
87
94
|
Resolve internal ids to human-readable labels before they reach a UI or a response a
|
|
88
95
|
user sees. Never put a password, hash, token, or secret in a response body or a log.
|
|
89
96
|
|
|
90
|
-
## 6. Auth
|
|
91
|
-
|
|
92
|
-
You do not write login/session/token code
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
## 6. Auth and identity are the framework's; you provide only CONFIG + DATA
|
|
98
|
+
|
|
99
|
+
You do not write login/session/token/MFA code, user or role administration, or an
|
|
100
|
+
authorization policy engine — the framework owns all of it. `ShieldModule.forRoot({ auth:
|
|
101
|
+
true, recordAuthorization, … })` gives you `/auth/*` and `/account/*`, and
|
|
102
|
+
`IdentityFeatureModule.forRoot(...)` gives you `/identity/*` (companies, users, roles,
|
|
103
|
+
access, record policies, shares, cross-tenant grants, service accounts, audit). Your app
|
|
104
|
+
contributes configuration through the seam (`rootIds` from config, an `emailSender`, your
|
|
105
|
+
own object types as `extraObjects`) and its data; access policy is authored by
|
|
106
|
+
administrators as role grants and record policies, not in code. See `CAPABILITIES.md`.
|
|
107
|
+
|
|
108
|
+
Never reimplement session handling, token verification, password hashing, or an
|
|
109
|
+
allow/deny check in app code. Never take a tenant, company, role or user id for an access
|
|
110
|
+
decision from the request body — the framework takes them from the session. If the auth
|
|
111
|
+
or identity surface lacks something you need, that's Rule 4 (flag the gap).
|
|
101
112
|
|
|
102
113
|
## 7. Fail closed, always
|
|
103
114
|
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Astram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/index.js
CHANGED
|
@@ -18,7 +18,6 @@ const FRAMEWORK_RULES_PATH = join(__dirname, 'FRAMEWORK_RULES.md');
|
|
|
18
18
|
const CAPABILITIES_PATH = join(__dirname, 'CAPABILITIES.md');
|
|
19
19
|
|
|
20
20
|
/** The guide version (== the framework release it ships with). */
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
22
21
|
const { version } = require('./package.json');
|
|
23
22
|
|
|
24
23
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astram/guide",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
@@ -20,4 +20,4 @@
|
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
}
|
|
23
|
-
}
|
|
23
|
+
}
|