@astram/guard 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/account.controller.d.ts +245 -0
- package/dist/auth/account.controller.d.ts.map +1 -0
- package/dist/auth/account.controller.js +673 -0
- package/dist/auth/account.controller.js.map +1 -0
- package/dist/auth/auth.controller.d.ts +219 -5
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +790 -12
- package/dist/auth/auth.controller.js.map +1 -0
- package/dist/auth/auth.module.d.ts +100 -2
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +299 -7
- package/dist/auth/auth.module.js.map +1 -0
- package/dist/auth/connections.js.map +1 -0
- package/dist/auth/dto.d.ts +16 -6
- package/dist/auth/dto.d.ts.map +1 -1
- package/dist/auth/dto.js.map +1 -0
- package/dist/auth/pending-mfa-store.js.map +1 -0
- package/dist/auth/persistence.module.js.map +1 -0
- package/dist/auth/ports/audit-sink.d.ts +47 -0
- package/dist/auth/ports/audit-sink.d.ts.map +1 -0
- package/dist/auth/ports/audit-sink.js +20 -0
- package/dist/auth/ports/audit-sink.js.map +1 -0
- package/dist/auth/ports/credential-writer.d.ts +23 -0
- package/dist/auth/ports/credential-writer.d.ts.map +1 -0
- package/dist/auth/ports/credential-writer.js +2 -0
- package/dist/auth/ports/credential-writer.js.map +1 -0
- package/dist/auth/ports/email-sender.d.ts +14 -0
- package/dist/auth/ports/email-sender.d.ts.map +1 -0
- package/dist/auth/ports/email-sender.js +2 -0
- package/dist/auth/ports/email-sender.js.map +1 -0
- package/dist/auth/ports/mfa-device-store.d.ts +45 -0
- package/dist/auth/ports/mfa-device-store.d.ts.map +1 -0
- package/dist/auth/ports/mfa-device-store.js +13 -0
- package/dist/auth/ports/mfa-device-store.js.map +1 -0
- package/dist/auth/ports/profile-writer.d.ts +12 -0
- package/dist/auth/ports/profile-writer.d.ts.map +1 -0
- package/dist/auth/ports/profile-writer.js +2 -0
- package/dist/auth/ports/profile-writer.js.map +1 -0
- package/dist/auth/ports/recovery-code-store.d.ts +10 -0
- package/dist/auth/ports/recovery-code-store.d.ts.map +1 -0
- package/dist/auth/ports/recovery-code-store.js +2 -0
- package/dist/auth/ports/recovery-code-store.js.map +1 -0
- package/dist/auth/ports/reset-token-store.d.ts +12 -0
- package/dist/auth/ports/reset-token-store.d.ts.map +1 -0
- package/dist/auth/ports/reset-token-store.js +2 -0
- package/dist/auth/ports/reset-token-store.js.map +1 -0
- package/dist/auth/ports/session-facts-projector.d.ts +44 -0
- package/dist/auth/ports/session-facts-projector.d.ts.map +1 -0
- package/dist/auth/ports/session-facts-projector.js +30 -0
- package/dist/auth/ports/session-facts-projector.js.map +1 -0
- package/dist/auth/ports/session-sweeper.d.ts +12 -0
- package/dist/auth/ports/session-sweeper.d.ts.map +1 -0
- package/dist/auth/ports/session-sweeper.js +2 -0
- package/dist/auth/ports/session-sweeper.js.map +1 -0
- package/dist/auth/profile-resolver.d.ts +73 -0
- package/dist/auth/profile-resolver.d.ts.map +1 -0
- package/dist/auth/profile-resolver.js +28 -0
- package/dist/auth/profile-resolver.js.map +1 -0
- package/dist/auth/rate-limit/attempt-limiter.d.ts +91 -0
- package/dist/auth/rate-limit/attempt-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limit/attempt-limiter.js +133 -0
- package/dist/auth/rate-limit/attempt-limiter.js.map +1 -0
- package/dist/auth/tokens.d.ts +174 -0
- package/dist/auth/tokens.d.ts.map +1 -1
- package/dist/auth/tokens.js +190 -1
- package/dist/auth/tokens.js.map +1 -0
- package/dist/auth/totp-enrollment.d.ts +42 -0
- package/dist/auth/totp-enrollment.d.ts.map +1 -0
- package/dist/auth/totp-enrollment.js +96 -0
- package/dist/auth/totp-enrollment.js.map +1 -0
- package/dist/authentication/argon2id-password-hasher.js.map +1 -0
- package/dist/authentication/mariadb-credential-resolver.d.ts +38 -24
- package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
- package/dist/authentication/mariadb-credential-resolver.js +42 -28
- package/dist/authentication/mariadb-credential-resolver.js.map +1 -0
- package/dist/authz/cross-tenant-grant-writer.d.ts +259 -0
- package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -0
- package/dist/authz/cross-tenant-grant-writer.js +397 -0
- package/dist/authz/cross-tenant-grant-writer.js.map +1 -0
- package/dist/authz/manifest-operation-authority-adapter.d.ts +65 -0
- package/dist/authz/manifest-operation-authority-adapter.d.ts.map +1 -0
- package/dist/authz/manifest-operation-authority-adapter.js +108 -0
- package/dist/authz/manifest-operation-authority-adapter.js.map +1 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts +90 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.js +211 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.js.map +1 -0
- package/dist/authz/mariadb-record-rule-adapter.d.ts +60 -0
- package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -0
- package/dist/authz/mariadb-record-rule-adapter.js +154 -0
- package/dist/authz/mariadb-record-rule-adapter.js.map +1 -0
- package/dist/authz/mariadb-record-share-adapter.d.ts +75 -0
- package/dist/authz/mariadb-record-share-adapter.d.ts.map +1 -0
- package/dist/authz/mariadb-record-share-adapter.js +106 -0
- package/dist/authz/mariadb-record-share-adapter.js.map +1 -0
- package/dist/authz/record-share-cascade.d.ts +52 -0
- package/dist/authz/record-share-cascade.d.ts.map +1 -0
- package/dist/authz/record-share-cascade.js +49 -0
- package/dist/authz/record-share-cascade.js.map +1 -0
- package/dist/authz/record-share-writer.d.ts +175 -0
- package/dist/authz/record-share-writer.d.ts.map +1 -0
- package/dist/authz/record-share-writer.js +225 -0
- package/dist/authz/record-share-writer.js.map +1 -0
- package/dist/authz/wire-record-authorization.d.ts +66 -0
- package/dist/authz/wire-record-authorization.d.ts.map +1 -0
- package/dist/authz/wire-record-authorization.js +34 -0
- package/dist/authz/wire-record-authorization.js.map +1 -0
- package/dist/boot/boot-sequence.js.map +1 -0
- package/dist/boot/boot.module.js.map +1 -0
- package/dist/completeness/catalog-reconciler.d.ts +21 -0
- package/dist/completeness/catalog-reconciler.d.ts.map +1 -0
- package/dist/completeness/catalog-reconciler.js +74 -0
- package/dist/completeness/catalog-reconciler.js.map +1 -0
- package/dist/completeness/operation-inventory-scanner.d.ts +17 -0
- package/dist/completeness/operation-inventory-scanner.d.ts.map +1 -0
- package/dist/completeness/operation-inventory-scanner.js +79 -0
- package/dist/completeness/operation-inventory-scanner.js.map +1 -0
- package/dist/completeness/ports/catalog-store.d.ts +33 -0
- package/dist/completeness/ports/catalog-store.d.ts.map +1 -0
- package/dist/completeness/ports/catalog-store.js +2 -0
- package/dist/completeness/ports/catalog-store.js.map +1 -0
- package/dist/config/app-config.js.map +1 -0
- package/dist/config/config.module.js.map +1 -0
- package/dist/enforcement/classification.decorator.d.ts +33 -14
- package/dist/enforcement/classification.decorator.d.ts.map +1 -1
- package/dist/enforcement/classification.decorator.js +34 -14
- package/dist/enforcement/classification.decorator.js.map +1 -0
- package/dist/enforcement/core-seam.js.map +1 -0
- package/dist/enforcement/enforcement.module.js.map +1 -0
- package/dist/enforcement/global-enforcement.guard.d.ts +29 -19
- package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -1
- package/dist/enforcement/global-enforcement.guard.js +108 -25
- package/dist/enforcement/global-enforcement.guard.js.map +1 -0
- package/dist/enforcement/reenroll-allowlist.d.ts +49 -0
- package/dist/enforcement/reenroll-allowlist.d.ts.map +1 -0
- package/dist/enforcement/reenroll-allowlist.js +77 -0
- package/dist/enforcement/reenroll-allowlist.js.map +1 -0
- package/dist/index.d.ts +35 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +159 -7
- package/dist/index.js.map +1 -0
- package/dist/module/shield-tokens.d.ts +15 -0
- package/dist/module/shield-tokens.d.ts.map +1 -0
- package/dist/module/shield-tokens.js +17 -0
- package/dist/module/shield-tokens.js.map +1 -0
- package/dist/module/shield.module.d.ts +153 -9
- package/dist/module/shield.module.d.ts.map +1 -1
- package/dist/module/shield.module.js +181 -24
- package/dist/module/shield.module.js.map +1 -0
- package/dist/request-auth/request-principal.d.ts +42 -0
- package/dist/request-auth/request-principal.d.ts.map +1 -0
- package/dist/request-auth/request-principal.js +87 -0
- package/dist/request-auth/request-principal.js.map +1 -0
- package/dist/request-auth/session-facts-als.d.ts +65 -0
- package/dist/request-auth/session-facts-als.d.ts.map +1 -0
- package/dist/request-auth/session-facts-als.js +57 -0
- package/dist/request-auth/session-facts-als.js.map +1 -0
- package/dist/request-auth/session-token-binder.d.ts +130 -0
- package/dist/request-auth/session-token-binder.d.ts.map +1 -0
- package/dist/request-auth/session-token-binder.js +277 -0
- package/dist/request-auth/session-token-binder.js.map +1 -0
- package/dist/request-auth/tokens.d.ts +14 -0
- package/dist/request-auth/tokens.d.ts.map +1 -0
- package/dist/request-auth/tokens.js +16 -0
- package/dist/request-auth/tokens.js.map +1 -0
- package/dist/session/crypto-adapters.js.map +1 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/mariadb-session-record-store.js.map +1 -0
- package/dist/session/redis-hot-session-store.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `RecordShareWriter` — the framework-owned WHO-MAY-SHARE write path (design §6.2, the
|
|
4
|
+
* KEY NEW INVARIANT of Phase 3). Share CREATION — not just evaluation — is framework-
|
|
5
|
+
* governed here so a share can NEVER expose data the sharer does not own.
|
|
6
|
+
*
|
|
7
|
+
* ⛔ THIS IS NOT A CORE PORT. Share evaluation is a core port ({@link RecordSharePort});
|
|
8
|
+
* share CREATION is a guard WRITE path (it needs the `mariadb` driver + the type's
|
|
9
|
+
* physical schema, neither of which core may touch — ADR-0003). It lives in guard and
|
|
10
|
+
* is exposed as a provider the consumer app calls from its share/unshare surface.
|
|
11
|
+
*
|
|
12
|
+
* ── THE BOUND (enforced fail-closed, IN ORDER, BEFORE any INSERT):
|
|
13
|
+
* 1. The type MUST be `owned`. A non-owned type (system / tenant-scoped-only) has no
|
|
14
|
+
* single owner, so there is no "sharer owns it" notion — sharing it is meaningless
|
|
15
|
+
* and REJECTED. The writer resolves the type's access posture via the injected
|
|
16
|
+
* {@link ShareTypeResolver}; an unknown type / non-owned type ⇒ reject.
|
|
17
|
+
* 2. The record's OWNER is FETCHED from the DB, TENANT-SCOPED
|
|
18
|
+
* (`SELECT owner_col FROM table WHERE pk = ? AND tenant_col = ?`). A row that does
|
|
19
|
+
* not exist in the SHARER'S tenant ⇒ reject (no cross-tenant reach, and no existence
|
|
20
|
+
* oracle beyond "you can't share it"). ⛔ THE OWNER IS ALWAYS FETCHED — never taken
|
|
21
|
+
* from caller input (fetch-then-gate discipline, mirrors the engine's fetch-owner
|
|
22
|
+
* stage).
|
|
23
|
+
* 3. The sharer MUST BE THE OWNER: `fetchedOwner === sharerId` (STRICT, non-empty). A
|
|
24
|
+
* non-owner — INCLUDING a grantee who merely received a share — can NEVER share.
|
|
25
|
+
* Because only the LITERAL owner passes, re-share by grantees is forbidden BY
|
|
26
|
+
* CONSTRUCTION (a grantee's id is not the owner id).
|
|
27
|
+
* ⛔ The `sharerId` is the AUTHENTICATED PRINCIPAL'S id — the caller MUST supply it from
|
|
28
|
+
* the request principal, NOT from a user-controlled body field. The writer never
|
|
29
|
+
* trusts an owner id in `input`; it fetches the owner and compares against `sharerId`.
|
|
30
|
+
*
|
|
31
|
+
* ── ON PASS: an IDEMPOTENT INSERT into `record_share` (the UNIQUE index on
|
|
32
|
+
* (tenant, type, record, grantee_kind, grantee_uuid, action) makes a repeat share of the
|
|
33
|
+
* same tuple a no-op via `INSERT ... ON DUPLICATE KEY UPDATE`). `unshare` DELETEs the
|
|
34
|
+
* matching row(s). Every share/unshare emits an audit event via the injected
|
|
35
|
+
* {@link ShareAuditSink} (best-effort; a sink error never fails the operation). NO raw
|
|
36
|
+
* uuid appears in a human-readable audit MESSAGE — ids ride in structured fields only.
|
|
37
|
+
*
|
|
38
|
+
* ── TENANT: every write is stamped with, and scoped to, the SHARER'S tenant. A share for
|
|
39
|
+
* a foreign tenant's record is impossible — the owner fetch is tenant-scoped, so a
|
|
40
|
+
* record outside the sharer's tenant is "not found" ⇒ rejected at step 2.
|
|
41
|
+
*/
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.RecordShareWriter = exports.ShareStoreUnavailableError = exports.ShareNotPermittedError = exports.SHIELD_RECORD_SHARE_WRITER = void 0;
|
|
44
|
+
const node_crypto_1 = require("node:crypto");
|
|
45
|
+
/** Schema the `record_share` table lives in (astram3 ships the migration). */
|
|
46
|
+
const DEFAULT_SCHEMA = 'shield_authz';
|
|
47
|
+
/** The DI token the writer is bound under. */
|
|
48
|
+
exports.SHIELD_RECORD_SHARE_WRITER = Symbol('SHIELD_RECORD_SHARE_WRITER');
|
|
49
|
+
/** Thrown when the who-may-share bound rejects a share/unshare (fail-closed). */
|
|
50
|
+
class ShareNotPermittedError extends Error {
|
|
51
|
+
constructor(message) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = 'ShareNotPermittedError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.ShareNotPermittedError = ShareNotPermittedError;
|
|
57
|
+
/** Thrown when the share store genuinely cannot answer/write (transient). */
|
|
58
|
+
class ShareStoreUnavailableError extends Error {
|
|
59
|
+
constructor(message) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.name = 'ShareStoreUnavailableError';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.ShareStoreUnavailableError = ShareStoreUnavailableError;
|
|
65
|
+
class RecordShareWriter {
|
|
66
|
+
db;
|
|
67
|
+
types;
|
|
68
|
+
audit;
|
|
69
|
+
schema;
|
|
70
|
+
now;
|
|
71
|
+
constructor(deps) {
|
|
72
|
+
this.db = deps.db;
|
|
73
|
+
this.types = deps.types;
|
|
74
|
+
this.audit = deps.audit;
|
|
75
|
+
this.schema = deps.schema ?? DEFAULT_SCHEMA;
|
|
76
|
+
this.now = deps.nowMs ?? (() => Date.now());
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create a share, enforcing the §6.2 who-may-share bound. Idempotent: re-sharing the
|
|
80
|
+
* same (tenant, type, record, grantee, action) tuple is a no-op (UNIQUE index +
|
|
81
|
+
* ON DUPLICATE KEY UPDATE). Emits a `share.created` audit event on success.
|
|
82
|
+
*
|
|
83
|
+
* @throws {@link ShareNotPermittedError} if the bound rejects (non-owned type, record
|
|
84
|
+
* not in the sharer's tenant, or the sharer is not the owner).
|
|
85
|
+
* @throws {@link ShareStoreUnavailableError} if the store cannot be reached (transient).
|
|
86
|
+
*/
|
|
87
|
+
async share(input) {
|
|
88
|
+
await this.assertMayShare(input);
|
|
89
|
+
try {
|
|
90
|
+
// Idempotent insert — the UNIQUE index makes a repeat tuple a no-op. rs_granted_by
|
|
91
|
+
// records the sharer (for the who-may-share audit trail).
|
|
92
|
+
await this.db.query(`INSERT INTO \`${this.schema}\`.\`record_share\`` +
|
|
93
|
+
` (rs_uuid, rs_tenant_uuid, rs_type_key, rs_record_uuid,` +
|
|
94
|
+
` rs_grantee_kind, rs_grantee_uuid, rs_action, rs_granted_by)` +
|
|
95
|
+
` VALUES (?, ?, ?, ?, ?, ?, ?, ?)` +
|
|
96
|
+
` ON DUPLICATE KEY UPDATE rs_granted_by = VALUES(rs_granted_by)`, [
|
|
97
|
+
(0, node_crypto_1.randomUUID)(),
|
|
98
|
+
input.tenantId,
|
|
99
|
+
input.typeKey,
|
|
100
|
+
input.recordId,
|
|
101
|
+
input.granteeKind,
|
|
102
|
+
input.granteeUuid,
|
|
103
|
+
input.action,
|
|
104
|
+
input.sharerId,
|
|
105
|
+
]);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
throw new ShareStoreUnavailableError(`record_share write failed for ${input.typeKey}: ${err.message}`);
|
|
109
|
+
}
|
|
110
|
+
await this.emit('share.created', input);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Remove a share. The SAME owner bound applies — only the owner of the record may revoke
|
|
114
|
+
* a share on it (a grantee cannot unshare a record they don't own). DELETEs the matching
|
|
115
|
+
* row(s); a no-match delete is a benign no-op. Emits `share.removed` on success.
|
|
116
|
+
*/
|
|
117
|
+
async unshare(input) {
|
|
118
|
+
await this.assertMayShare(input);
|
|
119
|
+
try {
|
|
120
|
+
await this.db.query(`DELETE FROM \`${this.schema}\`.\`record_share\`` +
|
|
121
|
+
` WHERE rs_tenant_uuid = ?` +
|
|
122
|
+
` AND rs_type_key = ?` +
|
|
123
|
+
` AND rs_record_uuid = ?` +
|
|
124
|
+
` AND rs_grantee_kind = ?` +
|
|
125
|
+
` AND rs_grantee_uuid = ?` +
|
|
126
|
+
` AND rs_action = ?`, [
|
|
127
|
+
input.tenantId,
|
|
128
|
+
input.typeKey,
|
|
129
|
+
input.recordId,
|
|
130
|
+
input.granteeKind,
|
|
131
|
+
input.granteeUuid,
|
|
132
|
+
input.action,
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
throw new ShareStoreUnavailableError(`record_share delete failed for ${input.typeKey}: ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
await this.emit('share.removed', input);
|
|
139
|
+
}
|
|
140
|
+
// --- the bound -------------------------------------------------------------
|
|
141
|
+
/**
|
|
142
|
+
* Enforce the §6.2 who-may-share bound (steps 1–3) and return the resolved type info.
|
|
143
|
+
* Fetches the record owner tenant-scoped and proves `sharerId` owns it. Throws
|
|
144
|
+
* {@link ShareNotPermittedError} on any failure (fail-closed).
|
|
145
|
+
*/
|
|
146
|
+
async assertMayShare(input) {
|
|
147
|
+
// Step 1 — the type must be `owned` (and known).
|
|
148
|
+
const info = this.types.resolve(input.typeKey);
|
|
149
|
+
if (!info) {
|
|
150
|
+
throw new ShareNotPermittedError(`type '${input.typeKey}' is not registered — cannot share.`);
|
|
151
|
+
}
|
|
152
|
+
if (info.owned !== true) {
|
|
153
|
+
throw new ShareNotPermittedError(`type '${input.typeKey}' is not an owned type — only owned records are shareable.`);
|
|
154
|
+
}
|
|
155
|
+
// A sharerId is mandatory + non-empty (the authenticated principal).
|
|
156
|
+
if (typeof input.sharerId !== 'string' || input.sharerId.length === 0) {
|
|
157
|
+
throw new ShareNotPermittedError(`a non-empty sharerId (the authenticated principal) is required.`);
|
|
158
|
+
}
|
|
159
|
+
// Step 2 — FETCH the owner tenant-scoped (never trust caller input). A record absent
|
|
160
|
+
// from the sharer's tenant ⇒ reject (no cross-tenant, no existence oracle).
|
|
161
|
+
const owner = await this.fetchOwnerTenantScoped(info, input.recordId, input.tenantId);
|
|
162
|
+
if (owner === undefined) {
|
|
163
|
+
throw new ShareNotPermittedError(`record not found in tenant — cannot share.`);
|
|
164
|
+
}
|
|
165
|
+
// Step 3 — the sharer MUST BE the owner (strict, non-empty). Grantees never pass.
|
|
166
|
+
if (owner !== input.sharerId) {
|
|
167
|
+
throw new ShareNotPermittedError(`only the record owner may share it.`);
|
|
168
|
+
}
|
|
169
|
+
return info;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* SELECT the owner column for `recordId`, tenant-scoped by `tenantId`. Returns the owner
|
|
173
|
+
* id string, or `undefined` when the row does not exist in that tenant / the owner is not
|
|
174
|
+
* a non-empty string (fail-closed — a null/non-string owner never matches a sharerId).
|
|
175
|
+
*/
|
|
176
|
+
async fetchOwnerTenantScoped(info, recordId, tenantId) {
|
|
177
|
+
const qualified = this.qualifiedTable(info);
|
|
178
|
+
let where = `\`${info.pkColumn}\` = ?`;
|
|
179
|
+
const params = [recordId];
|
|
180
|
+
if (info.tenantColumn) {
|
|
181
|
+
where += ` AND \`${info.tenantColumn}\` = ?`;
|
|
182
|
+
params.push(tenantId);
|
|
183
|
+
}
|
|
184
|
+
let rows;
|
|
185
|
+
try {
|
|
186
|
+
rows = (await this.db.query(`SELECT \`${info.ownerColumn}\` AS owner_id FROM ${qualified} WHERE ${where} LIMIT 1`, params));
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
throw new ShareStoreUnavailableError(`owner fetch failed for ${info.table}: ${err.message}`);
|
|
190
|
+
}
|
|
191
|
+
const first = rows[0];
|
|
192
|
+
return first && typeof first.owner_id === 'string' && first.owner_id.length > 0
|
|
193
|
+
? first.owner_id
|
|
194
|
+
: undefined;
|
|
195
|
+
}
|
|
196
|
+
qualifiedTable(info) {
|
|
197
|
+
return info.schema ? `\`${info.schema}\`.\`${info.table}\`` : `\`${info.table}\``;
|
|
198
|
+
}
|
|
199
|
+
/** Best-effort audit emission — a sink error is swallowed (never fails the write). */
|
|
200
|
+
async emit(action, input) {
|
|
201
|
+
if (!this.audit)
|
|
202
|
+
return;
|
|
203
|
+
const event = {
|
|
204
|
+
type: 'record_share',
|
|
205
|
+
action,
|
|
206
|
+
occurredAtMs: this.now(),
|
|
207
|
+
actorId: input.sharerId,
|
|
208
|
+
tenantId: input.tenantId,
|
|
209
|
+
attributes: {
|
|
210
|
+
typeKey: input.typeKey,
|
|
211
|
+
recordId: input.recordId,
|
|
212
|
+
granteeKind: input.granteeKind,
|
|
213
|
+
granteeUuid: input.granteeUuid,
|
|
214
|
+
shareAction: input.action,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
try {
|
|
218
|
+
await this.audit.record(event);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Best-effort: an audit failure must not fail a legitimate share/unshare.
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.RecordShareWriter = RecordShareWriter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-share-writer.js","sourceRoot":"","sources":["../../src/authz/record-share-writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `buildRecordAuthorization` — the ONE place the four framework-owned record-tier
|
|
3
|
+
* guard adapters are constructed and handed to core's `createWiredDecisionAuthority`
|
|
4
|
+
* (design §9, Phase 5 — "the framework pre-wires").
|
|
5
|
+
*
|
|
6
|
+
* ── WHY THIS FILE EXISTS (the record-tier ownership invariant, ADR-0003 / the
|
|
7
|
+
* "framework-owns-the-data-layer" initiative): before Phase 5, the CONSUMER app
|
|
8
|
+
* hand-constructed `MariaDbRecordPermissionRuleAdapter` / `MariaDbRecordShareAdapter`
|
|
9
|
+
* / `MariaDbCrossTenantGrantAdapter` / `ManifestOperationAuthorityAdapter` and called
|
|
10
|
+
* core's `createWiredDecisionAuthority` itself. That meant the app COULD swap in a
|
|
11
|
+
* fail-OPEN record adapter — the exact hole the initiative closes. Phase 5 moves that
|
|
12
|
+
* construction HERE, inside the framework: the app supplies ONLY the DB pool + the two
|
|
13
|
+
* app-owned ports (`permissionFacts`, `sessionFacts`) + the module registry + the
|
|
14
|
+
* operation-manifest DATA. The four record-tier read adapters are framework-built and
|
|
15
|
+
* UN-overridable — a planted fail-open adapter is now impossible.
|
|
16
|
+
*
|
|
17
|
+
* ── WHY IT IS NOT STATICALLY IMPORTED BY shield.module.ts (the enforcement-only
|
|
18
|
+
* invariant): this file value-imports `@astram/core` (`createWiredDecisionAuthority`)
|
|
19
|
+
* and the adapter classes (which themselves value-import core). `shield.module.ts` is
|
|
20
|
+
* deliberately kept core-free in its OWN compiled output (see its header) so the
|
|
21
|
+
* enforcement-only path is provably free of a top-level `require('@astram/core')`.
|
|
22
|
+
* So `shield.module.ts` reaches `buildRecordAuthorization` ONLY through a LAZY dynamic
|
|
23
|
+
* import inside the `SHIELD_DECISION_AUTHORITY` async factory — which runs ONLY when a
|
|
24
|
+
* consumer opts into `recordAuthorization`. This file (and thus core) loads only then.
|
|
25
|
+
*/
|
|
26
|
+
import type { DecisionAuthority, ModuleRegistry, PermissionFactsPort, SessionFactsReader } from '@astram/core';
|
|
27
|
+
import { type RuleQueryable } from './mariadb-record-rule-adapter.js';
|
|
28
|
+
import { type OperationManifestEntry } from './manifest-operation-authority-adapter.js';
|
|
29
|
+
/**
|
|
30
|
+
* The pieces a consumer supplies for the framework to build the real record-tier
|
|
31
|
+
* DecisionAuthority. The app owns ONLY these — the pool, the two app-owned ports, the
|
|
32
|
+
* module registry, and the operation-manifest DATA. The four record-tier read adapters
|
|
33
|
+
* are framework-constructed from `pool` and are DELIBERATELY NOT part of this shape
|
|
34
|
+
* (they cannot be overridden — the Phase-5 acceptance criterion).
|
|
35
|
+
*/
|
|
36
|
+
export interface RecordAuthorizationInputs {
|
|
37
|
+
/**
|
|
38
|
+
* The MariaDB pool the three DB-backed record-tier adapters read through. Typed
|
|
39
|
+
* structurally (`RuleQueryable`: `{ query(sql, params?) }`) — a `mariadb` `Pool`
|
|
40
|
+
* satisfies it — so the guard module signature does not force a hard `mariadb` type
|
|
41
|
+
* on consumers that inject a custom queryable. The framework builds the adapters over
|
|
42
|
+
* THIS pool; the app never constructs them.
|
|
43
|
+
*/
|
|
44
|
+
readonly pool: RuleQueryable;
|
|
45
|
+
/** APP-OWNED port: the principal's coarse grants for the object-type gate (gate 5). */
|
|
46
|
+
readonly permissionFacts: PermissionFactsPort;
|
|
47
|
+
/** APP-OWNED port: per-request cached session facts (null-slot until the Redis hot path). */
|
|
48
|
+
readonly sessionFacts: SessionFactsReader;
|
|
49
|
+
/** APP-OWNED: the module registry (object types + actions + tiers), bridged from the app's registrations. */
|
|
50
|
+
readonly registry: ModuleRegistry;
|
|
51
|
+
/**
|
|
52
|
+
* APP-SUPPLIED DATA (not an adapter): the declared trusted-operation manifest entries.
|
|
53
|
+
* The framework constructs the `ManifestOperationAuthorityAdapter` over them. Empty
|
|
54
|
+
* (default) ⇒ every delegation lookup is pass-through (undefined). The app supplies the
|
|
55
|
+
* ENTRIES; the framework owns the ADAPTER.
|
|
56
|
+
*/
|
|
57
|
+
readonly operationManifest?: readonly OperationManifestEntry[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Construct the four framework-owned record-tier adapters over the supplied pool +
|
|
61
|
+
* manifest, and return the fully-wired core {@link DecisionAuthority}. Behavior-identical
|
|
62
|
+
* to what the consumer's `buildDecisionAuthority` did before Phase 5 (same adapters, same
|
|
63
|
+
* pool, same registry, same ports) — this is a wiring MOVE, not a logic change.
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildRecordAuthorization(inputs: RecordAuthorizationInputs): DecisionAuthority;
|
|
66
|
+
//# sourceMappingURL=wire-record-authorization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-record-authorization.d.ts","sourceRoot":"","sources":["../../src/authz/wire-record-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,2CAA2C,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,eAAe,EAAE,mBAAmB,CAAC;IAC9C,6FAA6F;IAC7F,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,6GAA6G;IAC7G,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;CAChE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,yBAAyB,GAAG,iBAAiB,CAmB7F"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildRecordAuthorization = buildRecordAuthorization;
|
|
4
|
+
const core_1 = require("@astram/core");
|
|
5
|
+
const mariadb_record_rule_adapter_js_1 = require("./mariadb-record-rule-adapter.js");
|
|
6
|
+
const mariadb_record_share_adapter_js_1 = require("./mariadb-record-share-adapter.js");
|
|
7
|
+
const mariadb_cross_tenant_grant_adapter_js_1 = require("./mariadb-cross-tenant-grant-adapter.js");
|
|
8
|
+
const manifest_operation_authority_adapter_js_1 = require("./manifest-operation-authority-adapter.js");
|
|
9
|
+
/**
|
|
10
|
+
* Construct the four framework-owned record-tier adapters over the supplied pool +
|
|
11
|
+
* manifest, and return the fully-wired core {@link DecisionAuthority}. Behavior-identical
|
|
12
|
+
* to what the consumer's `buildDecisionAuthority` did before Phase 5 (same adapters, same
|
|
13
|
+
* pool, same registry, same ports) — this is a wiring MOVE, not a logic change.
|
|
14
|
+
*/
|
|
15
|
+
function buildRecordAuthorization(inputs) {
|
|
16
|
+
return (0, core_1.createWiredDecisionAuthority)({
|
|
17
|
+
registry: inputs.registry,
|
|
18
|
+
permissionFacts: inputs.permissionFacts,
|
|
19
|
+
sessionFacts: inputs.sessionFacts,
|
|
20
|
+
// ── The FOUR framework-owned record-tier adapters — constructed HERE, and ONLY here.
|
|
21
|
+
// Record-permit ADMIN rules over `shield_authz.record_permission_rule` (widen the
|
|
22
|
+
// core-owned owner baseline, deny-wins). Empty table ⇒ owner-only (NOT a lockout).
|
|
23
|
+
recordRule: new mariadb_record_rule_adapter_js_1.MariaDbRecordPermissionRuleAdapter(inputs.pool),
|
|
24
|
+
// Per-record SHARE source over `shield_authz.record_share` (widening permit, union
|
|
25
|
+
// with baseline under deny-wins). No share ⇒ false (baseline still governs).
|
|
26
|
+
recordShare: new mariadb_record_share_adapter_js_1.MariaDbRecordShareAdapter(inputs.pool),
|
|
27
|
+
// Cross-tenant consented grants over `shield_authz.cross_tenant_grant`. No active
|
|
28
|
+
// grant ⇒ null (deny-by-default for the cross-tenant reach).
|
|
29
|
+
crossTenantGrant: new mariadb_cross_tenant_grant_adapter_js_1.MariaDbCrossTenantGrantAdapter(inputs.pool),
|
|
30
|
+
// Delegation / trusted-operation authority over the DECLARED manifest (no DB).
|
|
31
|
+
// Empty ⇒ pass-through. The app supplies the entries; the framework owns the adapter.
|
|
32
|
+
operationAuthority: new manifest_operation_authority_adapter_js_1.ManifestOperationAuthorityAdapter(inputs.operationManifest ?? []),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-record-authorization.js","sourceRoot":"","sources":["../../src/authz/wire-record-authorization.ts"],"names":[],"mappings":";;AAgFA,4DAmBC;AApED,uCAA4D;AAC5D,qFAG0C;AAC1C,uFAA8E;AAC9E,mGAAyF;AACzF,uGAGmD;AAiCnD;;;;;GAKG;AACH,SAAgB,wBAAwB,CAAC,MAAiC;IACxE,OAAO,IAAA,mCAA4B,EAAC;QAClC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,sFAAsF;QACtF,kFAAkF;QAClF,mFAAmF;QACnF,UAAU,EAAE,IAAI,mEAAkC,CAAC,MAAM,CAAC,IAAI,CAAC;QAC/D,mFAAmF;QACnF,6EAA6E;QAC7E,WAAW,EAAE,IAAI,2DAAyB,CAAC,MAAM,CAAC,IAAI,CAAC;QACvD,kFAAkF;QAClF,6DAA6D;QAC7D,gBAAgB,EAAE,IAAI,sEAA8B,CAAC,MAAM,CAAC,IAAI,CAAC;QACjE,+EAA+E;QAC/E,sFAAsF;QACtF,kBAAkB,EAAE,IAAI,2EAAiC,CAAC,MAAM,CAAC,iBAAiB,IAAI,EAAE,CAAC;KAC1F,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot-sequence.js","sourceRoot":"","sources":["../../src/boot/boot-sequence.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAoDA,4CAuBC;AA3ED,2CAAiF;AACjF,iEAAwD;AACxD,2DAAoG;AAEpG;;;;;;;;;;;;;;GAcG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAY;IAC0B;IAAjD,YAAiD,MAAiB;QAAjB,WAAM,GAAN,MAAM,CAAW;IAAG,CAAC;IAEtE,KAAK,CAAC,sBAAsB;QAC1B,MAAM,IAAI,CAAC,yBAAyB,EAAE,CAAC;QACvC,8DAA8D;IAChE,CAAC;IAED;;;;;;;;;OASG;IACK,KAAK,CAAC,yBAAyB;QACrC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;CACF,CAAA;AAtBY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;IAEE,WAAA,IAAA,eAAM,EAAC,6BAAU,CAAC,CAAA;;GADpB,YAAY,CAsBxB;AAED;;;;;;;GAOG;AACH,SAAgB,gBAAgB,CAAC,GAAqB;IACpD,MAAM,CAAC,GAAG,IAAA,kCAAkB,EAAC,GAAG,CAAC,CAAC;IAElC,IAAI,CAAC,CAAC,CAAC,sBAAsB,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,6DAA6D;YAC3D,iCAAiC,GAAG,CAAC,aAAa,CAAC,iBAAiB,IAAI;YACxE,uEAAuE;YACvE,4CAA4C,CAC/C,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,sEAAsE;YACpE,eAAe,CAAC,CAAC,mBAAmB,0BAA0B;YAC9D,GAAG,GAAG,CAAC,cAAc,sBAAsB,GAAG,CAAC,OAAO,MAAM;YAC5D,2BAA2B,CAAC,CAAC,oBAAoB,eAAe;YAChE,GAAG,GAAG,CAAC,QAAQ,OAAO,CAAC,CAAC,mBAAmB,MAAM,GAAG,CAAC,cAAc,IAAI;YACvE,4DAA4D;YAC5D,6DAA6D,CAChE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot.module.js","sourceRoot":"","sources":["../../src/boot/boot.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,yDAAkD;AAElD;;;;GAIG;AAII,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAHtB,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,+BAAY,CAAC;KAC1B,CAAC;GACW,UAAU,CAAG"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OperationInventory, ModuleRegistry } from '@astram/core';
|
|
2
|
+
import type { CatalogStore, ReconcileReport } from './ports/catalog-store.js';
|
|
3
|
+
/** A @Classified route could not be gated (stranded or key-not-registered). Boot must
|
|
4
|
+
* abort — an ungatable route may never serve traffic (ADR-0002 fail-closed). */
|
|
5
|
+
export declare class CatalogGatabilityError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class CatalogReconciler {
|
|
9
|
+
/**
|
|
10
|
+
* BOOT — read-only, fail-closed. Throws if any non-public route is stranded (no key)
|
|
11
|
+
* or unclassified (declares a key the registry doesn't know). No DB, no writes.
|
|
12
|
+
*/
|
|
13
|
+
verifyGatable(inventory: OperationInventory, registry: ModuleRegistry): void;
|
|
14
|
+
/**
|
|
15
|
+
* CRON — mutating. Auto-adds an ACTIVE catalog row per registered non-public action,
|
|
16
|
+
* marks orphaned any stored row with no registered action, reactivates revived ones.
|
|
17
|
+
* Idempotent. Returns a report for logging + /health/catalog.
|
|
18
|
+
*/
|
|
19
|
+
reconcile(registry: ModuleRegistry, store: CatalogStore, nowIso: string): Promise<ReconcileReport>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=catalog-reconciler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-reconciler.d.ts","sourceRoot":"","sources":["../../src/completeness/catalog-reconciler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE9E;iFACiF;AACjF,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,iBAAiB;IAC5B;;;OAGG;IACH,aAAa,CAAC,SAAS,EAAE,kBAAkB,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAgB5E;;;;OAIG;IACG,SAAS,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAoCzG"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CatalogReconciler = exports.CatalogGatabilityError = void 0;
|
|
4
|
+
// astram-framework/packages/guard/src/completeness/catalog-reconciler.ts
|
|
5
|
+
const core_1 = require("@astram/core");
|
|
6
|
+
/** A @Classified route could not be gated (stranded or key-not-registered). Boot must
|
|
7
|
+
* abort — an ungatable route may never serve traffic (ADR-0002 fail-closed). */
|
|
8
|
+
class CatalogGatabilityError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'CatalogGatabilityError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.CatalogGatabilityError = CatalogGatabilityError;
|
|
15
|
+
class CatalogReconciler {
|
|
16
|
+
/**
|
|
17
|
+
* BOOT — read-only, fail-closed. Throws if any non-public route is stranded (no key)
|
|
18
|
+
* or unclassified (declares a key the registry doesn't know). No DB, no writes.
|
|
19
|
+
*/
|
|
20
|
+
verifyGatable(inventory, registry) {
|
|
21
|
+
const result = (0, core_1.checkCompleteness)(inventory, registry);
|
|
22
|
+
if (result.strandedOperations.length > 0 || result.unclassifiedOperations.length > 0) {
|
|
23
|
+
const stranded = result.strandedOperations.map((o) => o.operationId);
|
|
24
|
+
const unclassified = result.unclassifiedOperations.map((o) => `${o.operationId}${o.declaredPermissionKey ? ` (${o.declaredPermissionKey})` : ''}`);
|
|
25
|
+
throw new CatalogGatabilityError(`Shield: ${stranded.length} stranded + ${unclassified.length} unclassified route(s) — cannot boot. ` +
|
|
26
|
+
`Stranded (no @Classified): ${stranded.join(', ') || 'none'}. ` +
|
|
27
|
+
`Unclassified (key not registered): ${unclassified.join(', ') || 'none'}. ` +
|
|
28
|
+
`Classify the route or register the action.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* CRON — mutating. Auto-adds an ACTIVE catalog row per registered non-public action,
|
|
33
|
+
* marks orphaned any stored row with no registered action, reactivates revived ones.
|
|
34
|
+
* Idempotent. Returns a report for logging + /health/catalog.
|
|
35
|
+
*/
|
|
36
|
+
async reconcile(registry, store, nowIso) {
|
|
37
|
+
const stored = await store.listPermissions();
|
|
38
|
+
const storedByKey = new Map(stored.map((r) => [r.permissionKey, r]));
|
|
39
|
+
const registered = registry.listActions().filter((a) => a.tier !== 'public');
|
|
40
|
+
const registeredKeys = new Set(registered.map((a) => a.permissionKey));
|
|
41
|
+
// code → catalog: add missing rows; revive orphaned-but-now-registered.
|
|
42
|
+
const added = [];
|
|
43
|
+
const reactivated = [];
|
|
44
|
+
for (const a of registered) {
|
|
45
|
+
const key = a.permissionKey;
|
|
46
|
+
const existing = storedByKey.get(key);
|
|
47
|
+
if (!existing) {
|
|
48
|
+
const inserted = await store.upsertPermission({
|
|
49
|
+
permissionKey: key,
|
|
50
|
+
moduleKey: a.moduleKey,
|
|
51
|
+
typeKey: a.action.typeKey,
|
|
52
|
+
action: a.action.actionKey,
|
|
53
|
+
label: `${a.action.actionKey} ${a.action.typeKey}`,
|
|
54
|
+
tier: a.tier,
|
|
55
|
+
});
|
|
56
|
+
if (inserted)
|
|
57
|
+
added.push(key);
|
|
58
|
+
}
|
|
59
|
+
else if (existing.status === 'orphaned') {
|
|
60
|
+
reactivated.push(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (reactivated.length)
|
|
64
|
+
await store.markActive(reactivated);
|
|
65
|
+
// catalog → code: mark orphaned any active stored row with no registered action.
|
|
66
|
+
const orphaned = stored
|
|
67
|
+
.filter((r) => r.status === 'active' && !registeredKeys.has(r.permissionKey))
|
|
68
|
+
.map((r) => r.permissionKey);
|
|
69
|
+
if (orphaned.length)
|
|
70
|
+
await store.markOrphaned(orphaned);
|
|
71
|
+
return { added, orphaned, reactivated, at: nowIso };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.CatalogReconciler = CatalogReconciler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-reconciler.js","sourceRoot":"","sources":["../../src/completeness/catalog-reconciler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core';
|
|
2
|
+
import type { OperationInventory } from '@astram/core';
|
|
3
|
+
/**
|
|
4
|
+
* Reflects every controller handler at boot and emits an OperationInventory for core's
|
|
5
|
+
* checkCompleteness. operationId = `${ControllerClass.name}#${method}` (core's contract).
|
|
6
|
+
* A handler's permissionKey is `${moduleKey}:${typeKey}:${action}` from its @Classified;
|
|
7
|
+
* @Public handlers carry isPublic:true; an unclassified non-public handler carries no
|
|
8
|
+
* permissionKey (→ core reports it stranded).
|
|
9
|
+
*/
|
|
10
|
+
export declare class OperationInventoryScanner {
|
|
11
|
+
private readonly discovery;
|
|
12
|
+
private readonly scanner;
|
|
13
|
+
private readonly reflector;
|
|
14
|
+
constructor(discovery: DiscoveryService, scanner: MetadataScanner, reflector: Reflector);
|
|
15
|
+
scan(moduleKey: string): OperationInventory;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=operation-inventory-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-inventory-scanner.d.ts","sourceRoot":"","sources":["../../src/completeness/operation-inventory-scanner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE5E,OAAO,KAAK,EAAE,kBAAkB,EAAuB,MAAM,cAAc,CAAC;AAO5E;;;;;;GAMG;AACH,qBACa,yBAAyB;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,SAAS;IAGvC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB;CAyC5C"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.OperationInventoryScanner = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const constants_1 = require("@nestjs/common/constants");
|
|
15
|
+
const core_1 = require("@nestjs/core");
|
|
16
|
+
const core_2 = require("@astram/core");
|
|
17
|
+
const classification_decorator_js_1 = require("../enforcement/classification.decorator.js");
|
|
18
|
+
/**
|
|
19
|
+
* Reflects every controller handler at boot and emits an OperationInventory for core's
|
|
20
|
+
* checkCompleteness. operationId = `${ControllerClass.name}#${method}` (core's contract).
|
|
21
|
+
* A handler's permissionKey is `${moduleKey}:${typeKey}:${action}` from its @Classified;
|
|
22
|
+
* @Public handlers carry isPublic:true; an unclassified non-public handler carries no
|
|
23
|
+
* permissionKey (→ core reports it stranded).
|
|
24
|
+
*/
|
|
25
|
+
let OperationInventoryScanner = class OperationInventoryScanner {
|
|
26
|
+
discovery;
|
|
27
|
+
scanner;
|
|
28
|
+
reflector;
|
|
29
|
+
constructor(discovery, scanner, reflector) {
|
|
30
|
+
this.discovery = discovery;
|
|
31
|
+
this.scanner = scanner;
|
|
32
|
+
this.reflector = reflector;
|
|
33
|
+
}
|
|
34
|
+
scan(moduleKey) {
|
|
35
|
+
const operations = [];
|
|
36
|
+
for (const wrapper of this.discovery.getControllers()) {
|
|
37
|
+
const { instance, metatype } = wrapper;
|
|
38
|
+
if (!instance || !metatype)
|
|
39
|
+
continue;
|
|
40
|
+
const controllerName = metatype.name;
|
|
41
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
42
|
+
for (const method of this.scanner.getAllMethodNames(prototype)) {
|
|
43
|
+
const handler = prototype[method];
|
|
44
|
+
// Only real HTTP route handlers carry route metadata (set by @Get/@Post/etc.).
|
|
45
|
+
// Private/helper methods (e.g. buildQuery()) are emitted by getAllMethodNames but
|
|
46
|
+
// carry no PATH_METADATA — skipping them prevents core from stranding them and
|
|
47
|
+
// aborting boot. This matches what the guard actually enforces at runtime, which
|
|
48
|
+
// only ever runs on mapped handlers via context.getHandler().
|
|
49
|
+
if (Reflect.getMetadata(constants_1.PATH_METADATA, handler) === undefined)
|
|
50
|
+
continue;
|
|
51
|
+
// Mirror the guard's precedence: method metadata overrides class-level.
|
|
52
|
+
const isPublic = this.reflector.getAllAndOverride(classification_decorator_js_1.CLASSIFICATION_METADATA.PUBLIC, [
|
|
53
|
+
handler,
|
|
54
|
+
metatype,
|
|
55
|
+
]) === true;
|
|
56
|
+
const raw = this.reflector.getAllAndOverride(classification_decorator_js_1.CLASSIFICATION_METADATA.CLASSIFIED, [handler, metatype]);
|
|
57
|
+
const classification = (0, classification_decorator_js_1.normalizeClassification)(raw);
|
|
58
|
+
// Single-source the key format via core's derivePermissionKey (returns the
|
|
59
|
+
// branded PermissionKey) rather than a hand-built template string.
|
|
60
|
+
const permissionKey = !isPublic && classification
|
|
61
|
+
? (0, core_2.derivePermissionKey)(moduleKey, classification.typeKey, classification.action)
|
|
62
|
+
: undefined;
|
|
63
|
+
operations.push({
|
|
64
|
+
operationId: `${controllerName}#${method}`,
|
|
65
|
+
isPublic,
|
|
66
|
+
...(permissionKey ? { permissionKey } : {}),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { operations };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
exports.OperationInventoryScanner = OperationInventoryScanner;
|
|
74
|
+
exports.OperationInventoryScanner = OperationInventoryScanner = __decorate([
|
|
75
|
+
(0, common_1.Injectable)(),
|
|
76
|
+
__metadata("design:paramtypes", [core_1.DiscoveryService,
|
|
77
|
+
core_1.MetadataScanner,
|
|
78
|
+
core_1.Reflector])
|
|
79
|
+
], OperationInventoryScanner);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-inventory-scanner.js","sourceRoot":"","sources":["../../src/completeness/operation-inventory-scanner.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,wDAAyD;AACzD,uCAA4E;AAC5E,uCAAmD;AAEnD,4FAIoD;AAEpD;;;;;;GAMG;AAEI,IAAM,yBAAyB,GAA/B,MAAM,yBAAyB;IAEjB;IACA;IACA;IAHnB,YACmB,SAA2B,EAC3B,OAAwB,EACxB,SAAoB;QAFpB,cAAS,GAAT,SAAS,CAAkB;QAC3B,YAAO,GAAP,OAAO,CAAiB;QACxB,cAAS,GAAT,SAAS,CAAW;IACpC,CAAC;IAEJ,IAAI,CAAC,SAAiB;QACpB,MAAM,UAAU,GAA0B,EAAE,CAAC;QAC7C,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,EAAE,CAAC;YACtD,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;YACvC,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ;gBAAE,SAAS;YACrC,MAAM,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC;YACrC,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;YAClD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC/D,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;gBAClC,+EAA+E;gBAC/E,kFAAkF;gBAClF,+EAA+E;gBAC/E,iFAAiF;gBACjF,8DAA8D;gBAC9D,IAAI,OAAO,CAAC,WAAW,CAAC,yBAAa,EAAE,OAAO,CAAC,KAAK,SAAS;oBAAE,SAAS;gBACxE,wEAAwE;gBACxE,MAAM,QAAQ,GACZ,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAU,qDAAuB,CAAC,MAAM,EAAE;oBACxE,OAAO;oBACP,QAAQ;iBACT,CAAC,KAAK,IAAI,CAAC;gBACd,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAC1C,qDAAuB,CAAC,UAAU,EAClC,CAAC,OAAO,EAAE,QAAQ,CAAC,CACpB,CAAC;gBACF,MAAM,cAAc,GAAG,IAAA,qDAAuB,EAAC,GAAG,CAAC,CAAC;gBACpD,2EAA2E;gBAC3E,mEAAmE;gBACnE,MAAM,aAAa,GACjB,CAAC,QAAQ,IAAI,cAAc;oBACzB,CAAC,CAAC,IAAA,0BAAmB,EAAC,SAAS,EAAE,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC;oBAC/E,CAAC,CAAC,SAAS,CAAC;gBAChB,UAAU,CAAC,IAAI,CAAC;oBACd,WAAW,EAAE,GAAG,cAAc,IAAI,MAAM,EAAE;oBAC1C,QAAQ;oBACR,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC5C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,EAAE,UAAU,EAAE,CAAC;IACxB,CAAC;CACF,CAAA;AAhDY,8DAAyB;oCAAzB,yBAAyB;IADrC,IAAA,mBAAU,GAAE;qCAGmB,uBAAgB;QAClB,sBAAe;QACb,gBAAS;GAJ5B,yBAAyB,CAgDrC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DB seam for the catalog reconciler. The framework defines the interface only;
|
|
3
|
+
* the consumer app supplies the implementation (ADR-0003 — guard does no DB). A row is
|
|
4
|
+
* keyed by its permission key (`moduleKey:typeKey:actionKey`); `status` is the soft
|
|
5
|
+
* active/orphaned marker (never hard-delete).
|
|
6
|
+
*/
|
|
7
|
+
export interface CatalogPermissionRow {
|
|
8
|
+
readonly permissionKey: string;
|
|
9
|
+
readonly moduleKey: string;
|
|
10
|
+
readonly typeKey: string;
|
|
11
|
+
readonly action: string;
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly tier: string;
|
|
14
|
+
readonly status: 'active' | 'orphaned';
|
|
15
|
+
}
|
|
16
|
+
/** The result of one mutating reconcile pass — surfaced via /health/catalog + logged. */
|
|
17
|
+
export interface ReconcileReport {
|
|
18
|
+
readonly added: readonly string[];
|
|
19
|
+
readonly orphaned: readonly string[];
|
|
20
|
+
readonly reactivated: readonly string[];
|
|
21
|
+
readonly at: string;
|
|
22
|
+
}
|
|
23
|
+
export interface CatalogStore {
|
|
24
|
+
/** All catalog rows (both active + orphaned). */
|
|
25
|
+
listPermissions(): Promise<CatalogPermissionRow[]>;
|
|
26
|
+
/** Idempotent insert of an ACTIVE row (no-op if the key already exists as active). */
|
|
27
|
+
upsertPermission(row: Omit<CatalogPermissionRow, 'status'>): Promise<boolean>;
|
|
28
|
+
/** Mark keys orphaned (soft). No-op for keys already orphaned. */
|
|
29
|
+
markOrphaned(keys: readonly string[]): Promise<void>;
|
|
30
|
+
/** Flip keys orphaned → active (a route returned). */
|
|
31
|
+
markActive(keys: readonly string[]): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=catalog-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-store.d.ts","sourceRoot":"","sources":["../../../src/completeness/ports/catalog-store.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;CACxC;AAED,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,eAAe,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACnD,sFAAsF;IACtF,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,kEAAkE;IAClE,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,sDAAsD;IACtD,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-store.js","sourceRoot":"","sources":["../../../src/completeness/ports/catalog-store.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-config.js","sourceRoot":"","sources":["../../src/config/app-config.ts"],"names":[],"mappings":"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"}
|