@substrat-run/kernel 0.116.0 → 0.117.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/capability.d.ts +220 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +537 -0
- package/dist/capability.js.map +1 -0
- package/dist/check-key.d.ts +30 -0
- package/dist/check-key.d.ts.map +1 -0
- package/dist/check-key.js +37 -0
- package/dist/check-key.js.map +1 -0
- package/dist/denial-query.d.ts +32 -1
- package/dist/denial-query.d.ts.map +1 -1
- package/dist/denial-query.js +66 -28
- package/dist/denial-query.js.map +1 -1
- package/dist/index.d.ts +19 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/outbox-event.d.ts +131 -0
- package/dist/outbox-event.d.ts.map +1 -0
- package/dist/outbox-event.js +185 -0
- package/dist/outbox-event.js.map +1 -0
- package/dist/permission-checker.d.ts +8 -1
- package/dist/permission-checker.d.ts.map +1 -1
- package/dist/permission-checker.js +18 -0
- package/dist/permission-checker.js.map +1 -1
- package/dist/permission-eval.d.ts +8 -0
- package/dist/permission-eval.d.ts.map +1 -1
- package/dist/permission-eval.js +164 -91
- package/dist/permission-eval.js.map +1 -1
- package/dist/platform-request-query.d.ts +58 -1
- package/dist/platform-request-query.d.ts.map +1 -1
- package/dist/platform-request-query.js +61 -1
- package/dist/platform-request-query.js.map +1 -1
- package/dist/platform-sweep.d.ts +123 -8
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +158 -34
- package/dist/platform-sweep.js.map +1 -1
- package/dist/row-decode.d.ts +107 -0
- package/dist/row-decode.d.ts.map +1 -0
- package/dist/row-decode.js +93 -0
- package/dist/row-decode.js.map +1 -0
- package/dist/scope-host.d.ts +211 -9
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js +33 -0
- package/dist/scope-host.js.map +1 -1
- package/dist/scope-tuple-seat.d.ts +72 -0
- package/dist/scope-tuple-seat.d.ts.map +1 -0
- package/dist/scope-tuple-seat.js +93 -0
- package/dist/scope-tuple-seat.js.map +1 -0
- package/dist/subject-redaction.d.ts +160 -0
- package/dist/subject-redaction.d.ts.map +1 -0
- package/dist/subject-redaction.js +210 -0
- package/dist/subject-redaction.js.map +1 -0
- package/dist/system-switch.d.ts +108 -0
- package/dist/system-switch.d.ts.map +1 -0
- package/dist/system-switch.js +145 -0
- package/dist/system-switch.js.map +1 -0
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +109 -57
- package/dist/timeline.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { domainEvent, drainedEvent } from '@substrat-run/contracts';
|
|
2
|
+
import { issueOf, NOT_JSON } from './row-decode.js';
|
|
3
|
+
/** Contract field → the column a message should name, where the two differ. */
|
|
4
|
+
const COLUMN_OF = {
|
|
5
|
+
schemaVersion: 'schema_version',
|
|
6
|
+
occurredAt: 'occurred_at',
|
|
7
|
+
tenantId: 'tenant_id',
|
|
8
|
+
scopeId: 'scope_id',
|
|
9
|
+
piiClass: 'pii_class',
|
|
10
|
+
subjectId: 'subject_id',
|
|
11
|
+
causedBy: 'caused_by',
|
|
12
|
+
invocationId: 'invocation_id',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The envelope candidate a schema parse is run over, JSON columns parsed.
|
|
16
|
+
*
|
|
17
|
+
* Every optional column is tested for PRESENCE (`!= null`), never for truthiness (#1641
|
|
18
|
+
* review). A truthiness test read a stored `''` as "absent" and skipped validating it, so a
|
|
19
|
+
* restored row carrying an empty `authorization`, `impersonation`, `subject_id` or
|
|
20
|
+
* `operation` was delivered, executed or drained as though the column were null, instead of
|
|
21
|
+
* contained. The kernel never writes `''` to any of them — each is written from a validated
|
|
22
|
+
* value or as NULL — so a present value is always one to validate. `undefined` still reads
|
|
23
|
+
* as absent: it is the column missing from a legacy row, never a stored value.
|
|
24
|
+
*/
|
|
25
|
+
function envelopeCandidate(row, into) {
|
|
26
|
+
const json = (column, stored) => {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(stored);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
into.failed.push(`${column}: ${NOT_JSON}`);
|
|
32
|
+
into.named.add(column);
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
id: row.id,
|
|
38
|
+
type: row.type,
|
|
39
|
+
schemaVersion: row.schema_version,
|
|
40
|
+
occurredAt: row.occurred_at,
|
|
41
|
+
tenantId: row.tenant_id,
|
|
42
|
+
scopeId: row.scope_id,
|
|
43
|
+
actor: json('actor', row.actor),
|
|
44
|
+
entity: { entityType: row.entity_type, entityId: row.entity_id },
|
|
45
|
+
piiClass: row.pii_class,
|
|
46
|
+
...(row.subject_id != null ? { subjectId: row.subject_id } : {}),
|
|
47
|
+
...(row.authorization != null ? { authorization: json('authorization', row.authorization) } : {}),
|
|
48
|
+
// K-42: the stamp survives the read, so a consumer's event and an executor's
|
|
49
|
+
// are the same fact the stored row is. Absent rather than null when nobody
|
|
50
|
+
// was impersonating, because `DomainEvent.impersonation` is optional — the
|
|
51
|
+
// shape module code never sees is also the shape it cannot branch on.
|
|
52
|
+
...(row.impersonation != null ? { impersonation: json('impersonation', row.impersonation) } : {}),
|
|
53
|
+
// #1231: absent rather than null, the same shape rule as the stamp above.
|
|
54
|
+
...(row.operation != null ? { operation: row.operation } : {}),
|
|
55
|
+
payload: row.payload == null ? undefined : json('payload', row.payload),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `candidate` parsed by the PUBLISHED `schema`, returned only as that parse's own output — or
|
|
60
|
+
* a throw naming every column that did not decode. Nothing is copied onto the result after
|
|
61
|
+
* validation, so the value a caller gets is exactly what the schema accepted (#1641 review).
|
|
62
|
+
*/
|
|
63
|
+
function strictly(row, schema, candidate, { failed, named }) {
|
|
64
|
+
const parsed = schema.safeParse(candidate);
|
|
65
|
+
if (parsed.success && failed.length === 0)
|
|
66
|
+
return parsed.data;
|
|
67
|
+
if (!parsed.success) {
|
|
68
|
+
for (const issue of parsed.error.issues) {
|
|
69
|
+
const head = String(issue.path[0] ?? '');
|
|
70
|
+
const column = head === 'entity' ? (issue.path[1] === 'entityId' ? 'entity_id' : 'entity_type') : (COLUMN_OF[head] ?? head);
|
|
71
|
+
// A JSON column that did not parse is already named; its `Required` issue here says
|
|
72
|
+
// nothing new.
|
|
73
|
+
if (named.has(column))
|
|
74
|
+
continue;
|
|
75
|
+
named.add(column);
|
|
76
|
+
failed.push(issueOf(column, { issues: [{ ...issue, path: issue.path.slice(head === 'entity' ? 2 : 1) }] }));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw new Error(`event row ${JSON.stringify(row.id)} cannot be decoded — ${failed.join('; ')}`);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The stored row → the `DomainEvent` a consumer or executor is handed, or a throw naming
|
|
83
|
+
* EVERY column that did not decode.
|
|
84
|
+
*
|
|
85
|
+
* A row the kernel wrote decodes exactly as it always did — the same candidate into the same
|
|
86
|
+
* `domainEvent.parse`. The throw's message never quotes the stored text (see `rowDecoder`):
|
|
87
|
+
* it is written into a dead letter, which is stored beside the event and survives an erasure
|
|
88
|
+
* of its payload.
|
|
89
|
+
*/
|
|
90
|
+
export function domainEventOf(row) {
|
|
91
|
+
const collected = { failed: [], named: new Set() };
|
|
92
|
+
return strictly(row, domainEvent, envelopeCandidate(row, collected), collected);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The stored row → the `DrainedEvent` the Tier-2 drain ships, or a throw (`domainEventOf`'s).
|
|
96
|
+
*
|
|
97
|
+
* The WHOLE event is parsed by the published `drainedEvent` schema — the envelope and the
|
|
98
|
+
* columns lifted beside it (`operation`, `version`, `caused_by`, `invocation_id`), which the
|
|
99
|
+
* envelope does not carry. They used to be copied onto an already-validated envelope as
|
|
100
|
+
* stored, into a value merely TYPED as a `DrainedEvent` (#1641 review): a restored row with
|
|
101
|
+
* `version: ''` or a `caused_by` that is not an event id reached the lake, append-only, and
|
|
102
|
+
* was stamped as having left. Parsed here, it throws, and `readUndrainedOutbox` skips it and
|
|
103
|
+
* says so. A row the kernel wrote passes unchanged: each lifted column is written from a
|
|
104
|
+
* validated value or as NULL.
|
|
105
|
+
*/
|
|
106
|
+
export function drainedEventOf(row) {
|
|
107
|
+
const collected = { failed: [], named: new Set() };
|
|
108
|
+
return strictly(row, drainedEvent, {
|
|
109
|
+
...envelopeCandidate(row, collected),
|
|
110
|
+
operation: row.operation ?? null,
|
|
111
|
+
version: row.version ?? null,
|
|
112
|
+
// #1237 — lifted like the two above, and for the same reason: the column
|
|
113
|
+
// exists on the outbox but not on the envelope.
|
|
114
|
+
causedBy: row.caused_by ?? null,
|
|
115
|
+
// …and the invocation, for the same reason again: a lake that kept cause and
|
|
116
|
+
// dropped the call could say what set an event off and never which request did
|
|
117
|
+
// it, which is the grouping a trace is built on.
|
|
118
|
+
invocationId: row.invocation_id ?? null,
|
|
119
|
+
}, collected);
|
|
120
|
+
}
|
|
121
|
+
/** How many rows past `limit` one read may look through, as a multiple of `limit`. */
|
|
122
|
+
export const UNDRAINED_SCAN_FACTOR = 10;
|
|
123
|
+
/** How many skipped ids a read names; the count past it stays exact. */
|
|
124
|
+
export const UNDRAINED_SKIPPED_IDS = 50;
|
|
125
|
+
/**
|
|
126
|
+
* The Tier-2 drain's read over one scope's outbox (#1334), CONTAINED per row (#1636) — the
|
|
127
|
+
* one implementation both adapters run, so they cannot disagree about what is skipped.
|
|
128
|
+
*
|
|
129
|
+
* `page(offset, count)` is the adapter's
|
|
130
|
+
* `SELECT * … WHERE drained_at IS NULL ORDER BY id LIMIT count OFFSET offset`. Offset
|
|
131
|
+
* rather than an id cursor on purpose: the rows being stepped over are exactly the ones
|
|
132
|
+
* whose `id` may be the column that is broken.
|
|
133
|
+
*
|
|
134
|
+
* A row that does not decode is SKIPPED: not returned, so no sink ever receives an event
|
|
135
|
+
* built from stand-ins — the lake is append-only, and a wrong row there cannot be taken
|
|
136
|
+
* back — and not stamped, so `drained_at` stays an exact record of what left. The read
|
|
137
|
+
* keeps going past it, so the healthy rows behind it still ship. The cost is stated rather
|
|
138
|
+
* than hidden: a skipped row never reaches the lake, and every pass reads it again. It
|
|
139
|
+
* stays visible in Tier 1, where `readHistory` returns it with a `decodeError`.
|
|
140
|
+
*
|
|
141
|
+
* Bounded: at most `limit × UNDRAINED_SCAN_FACTOR` rows are looked at. A scope whose next
|
|
142
|
+
* that-many undrained rows ALL fail to decode ships nothing, pass after pass — at that
|
|
143
|
+
* point the spine is broken wholesale, which is a restore to repair rather than a row to
|
|
144
|
+
* step over, and the skipped count in the sweep report is what says so.
|
|
145
|
+
*/
|
|
146
|
+
export function readUndrainedOutbox(page, limit) {
|
|
147
|
+
const events = [];
|
|
148
|
+
const eventIds = [];
|
|
149
|
+
let count = 0;
|
|
150
|
+
let scanned = 0;
|
|
151
|
+
const ceiling = limit * UNDRAINED_SCAN_FACTOR;
|
|
152
|
+
while (events.length < limit && scanned < ceiling) {
|
|
153
|
+
const want = Math.min(limit, ceiling - scanned);
|
|
154
|
+
const rows = page(scanned, want);
|
|
155
|
+
for (const row of rows) {
|
|
156
|
+
if (events.length === limit)
|
|
157
|
+
break;
|
|
158
|
+
scanned += 1;
|
|
159
|
+
let event;
|
|
160
|
+
try {
|
|
161
|
+
event = drainedEventOf(row);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// `drainedEventOf` is a pure decode of text already in hand — nothing in it can
|
|
165
|
+
// fail transiently, so a throw here is a fact about the row, not about this pass.
|
|
166
|
+
count += 1;
|
|
167
|
+
if (eventIds.length < UNDRAINED_SKIPPED_IDS)
|
|
168
|
+
eventIds.push(String(row.id));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
events.push(event);
|
|
172
|
+
}
|
|
173
|
+
if (rows.length < want)
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
return count > 0 ? { events, skipped: { count, eventIds } } : { events };
|
|
177
|
+
}
|
|
178
|
+
/** An `UndrainedRead` → the in-process array shape, the skip attached only when there is one. */
|
|
179
|
+
export function undrainedEventsOf(read) {
|
|
180
|
+
const events = [...read.events];
|
|
181
|
+
if (read.skipped)
|
|
182
|
+
events.skipped = read.skipped;
|
|
183
|
+
return events;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=outbox-event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox-event.js","sourceRoot":"","sources":["../src/outbox-event.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SubstratError, type Coverage, type Decision, type EntityRef, type Node, type PermissionKey, type CheckSubject, type PrincipalId } from '@substrat-run/contracts';
|
|
1
|
+
import { SubstratError, type Actor, type Coverage, type Decision, type EntityRef, type Node, type PermissionKey, type CheckSubject, type PrincipalId } from '@substrat-run/contracts';
|
|
2
2
|
/**
|
|
3
3
|
* The evaluation seam (D-16): the MODEL is kernel-owned, the evaluation engine
|
|
4
4
|
* is an adapter — the built-in default is a constrained relationship-tuple
|
|
@@ -46,6 +46,13 @@ export interface PermissionChecker {
|
|
|
46
46
|
}
|
|
47
47
|
/** Convenience for the overwhelmingly common case. */
|
|
48
48
|
export declare const asPrincipal: (id: PrincipalId) => CheckSubject;
|
|
49
|
+
/**
|
|
50
|
+
* The spine ACTOR a check subject is recorded as — on an event, a denial, a platform
|
|
51
|
+
* intent. One mapping, so the four places that record "who" cannot disagree about it: a
|
|
52
|
+
* principal is its bare id, and every subject that is not a person is the object form that
|
|
53
|
+
* says what it is instead (#97, #383, #1672).
|
|
54
|
+
*/
|
|
55
|
+
export declare const actorOf: (subject: CheckSubject) => Actor;
|
|
49
56
|
/**
|
|
50
57
|
* A refused check. The message constructor stays the public surface modules use
|
|
51
58
|
* (`throw new PermissionDenied('…')` for their own policy denials). `assertAllowed`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-checker.d.ts","sourceRoot":"","sources":["../src/permission-checker.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,KAAK,CACH,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,aAAa,EACzB,IAAI,EAAE,IAAI,EACV,MAAM,CAAC,EAAE,SAAS,GACjB,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,MAAM,CACJ,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtB;AAED,sDAAsD;AACtD,eAAO,MAAM,WAAW,OAAQ,WAAW,KAAG,YAA2C,CAAC;AAE1F;;;;;;GAMG;AACH,qBAAa,gBAAiB,SAAQ,aAAa;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IACrB,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE;QAAE,UAAU,EAAE,aAAa,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,EAS9E;CACF;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAC5E,QAAQ,EACR;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,CAClB,CAOA;AAED,4FAA4F;AAC5F,eAAO,MAAM,cAAc,EAAE,iBAa5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAgBpC,CAAC"}
|
|
1
|
+
{"version":3,"file":"permission-checker.d.ts","sourceRoot":"","sources":["../src/permission-checker.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,KAAK,KAAK,EACV,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,KAAK,CACH,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,aAAa,EACzB,IAAI,EAAE,IAAI,EACV,MAAM,CAAC,EAAE,SAAS,GACjB,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,MAAM,CACJ,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtB;AAED,sDAAsD;AACtD,eAAO,MAAM,WAAW,OAAQ,WAAW,KAAG,YAA2C,CAAC;AAE1F;;;;;GAKG;AACH,eAAO,MAAM,OAAO,YAAa,YAAY,KAAG,KAW/C,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,gBAAiB,SAAQ,aAAa;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IACrB,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE;QAAE,UAAU,EAAE,aAAa,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,EAS9E;CACF;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAC5E,QAAQ,EACR;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,CAClB,CAOA;AAED,4FAA4F;AAC5F,eAAO,MAAM,cAAc,EAAE,iBAa5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAgBpC,CAAC"}
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { objectRef, SubstratError, } from '@substrat-run/contracts';
|
|
2
2
|
/** Convenience for the overwhelmingly common case. */
|
|
3
3
|
export const asPrincipal = (id) => ({ kind: 'principal', id });
|
|
4
|
+
/**
|
|
5
|
+
* The spine ACTOR a check subject is recorded as — on an event, a denial, a platform
|
|
6
|
+
* intent. One mapping, so the four places that record "who" cannot disagree about it: a
|
|
7
|
+
* principal is its bare id, and every subject that is not a person is the object form that
|
|
8
|
+
* says what it is instead (#97, #383, #1672).
|
|
9
|
+
*/
|
|
10
|
+
export const actorOf = (subject) => {
|
|
11
|
+
switch (subject.kind) {
|
|
12
|
+
case 'principal':
|
|
13
|
+
return subject.id;
|
|
14
|
+
case 'system':
|
|
15
|
+
return { system: subject.id };
|
|
16
|
+
case 'connection':
|
|
17
|
+
return { connection: subject.id };
|
|
18
|
+
case 'capability':
|
|
19
|
+
return { capability: subject.id };
|
|
20
|
+
}
|
|
21
|
+
};
|
|
4
22
|
/**
|
|
5
23
|
* A refused check. The message constructor stays the public surface modules use
|
|
6
24
|
* (`throw new PermissionDenied('…')` for their own policy denials). `assertAllowed`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-checker.js","sourceRoot":"","sources":["../src/permission-checker.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,aAAa,
|
|
1
|
+
{"version":3,"file":"permission-checker.js","sourceRoot":"","sources":["../src/permission-checker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Node, type RoleDefinition } from '@substrat-run/contracts';
|
|
2
2
|
import type { PermissionChecker } from './permission-checker.js';
|
|
3
|
+
import { type CapabilityRow } from './capability.js';
|
|
3
4
|
type MaybePromise<T> = T | Promise<T>;
|
|
4
5
|
/**
|
|
5
6
|
* One tuple row as the spine stores it — snake_case, because that is what both adapters
|
|
@@ -25,6 +26,13 @@ export interface ScopeTupleReader {
|
|
|
25
26
|
grant(subject: string, relation: string, object: string): MaybePromise<PermissionTupleRow | undefined>;
|
|
26
27
|
/** The declared `parent` edges out of `object`. */
|
|
27
28
|
parents(object: string): MaybePromise<PermissionTupleRow[]>;
|
|
29
|
+
/**
|
|
30
|
+
* The capability row a `{ kind: 'capability' }` subject names (#1672), read with
|
|
31
|
+
* `capabilityByIdQuery` — capabilities live in the scope's own spine beside the entities
|
|
32
|
+
* they reach. Optional so a reader written before capabilities still compiles; ABSENT
|
|
33
|
+
* means every capability check DENIES, never that one is waved through.
|
|
34
|
+
*/
|
|
35
|
+
capability?(id: string): MaybePromise<CapabilityRow | undefined>;
|
|
28
36
|
}
|
|
29
37
|
/**
|
|
30
38
|
* Everything the evaluator needs to know, and nothing about where it comes from.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-eval.d.ts","sourceRoot":"","sources":["../src/permission-eval.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,IAAI,EAIT,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"permission-eval.d.ts","sourceRoot":"","sources":["../src/permission-eval.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,IAAI,EAIT,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAyBxF,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oFAAoF;IACpF,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACpF,qEAAqE;IACrE,KAAK,CACH,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,YAAY,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAChD,mDAAmD;IACnD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC5D;;;;;OAKG;IACH,UAAU,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;CAClE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,GAAG,IAAI,MAAM,CAAC;IACd,qFAAqF;IACrF,YAAY,CACV,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,GACrB,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACtC,iGAAiG;IACjG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACjF,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,gBAAgB,GAAG,SAAS,CAAC;CACpD;AAsED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,qBAAqB,GAAG,iBAAiB,CAmQrF"}
|
package/dist/permission-eval.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { objectRef, subjectRef, } from '@substrat-run/contracts';
|
|
2
|
+
import { capabilityGrantOf, capabilityLive } from './capability.js';
|
|
2
3
|
/**
|
|
3
4
|
* The built-in constrained relationship-tuple evaluator (design doc §4.2, plan D-23),
|
|
4
5
|
* as ONE implementation both adapters call.
|
|
@@ -40,6 +41,45 @@ const nodeObjectsOf = (node) => node.scopeId
|
|
|
40
41
|
{ obj: `tenant:${node.tenantId}`, scoped: false },
|
|
41
42
|
]
|
|
42
43
|
: [{ obj: `tenant:${node.tenantId}`, scoped: false }];
|
|
44
|
+
/**
|
|
45
|
+
* Rule 3's walk, as ONE function: from `start`, look for a hit at each frontier object, then
|
|
46
|
+
* expand one level of declared `parent` edges, to `ENTITY_WALK_DEPTH`. `probe` decides what a
|
|
47
|
+
* hit is — a live entity-narrowed grant for a principal, the capability's own root for a
|
|
48
|
+
* capability subject — so both subjects travel exactly the same edges, to exactly the same
|
|
49
|
+
* depth, skipping exactly the same revoked edges. A capability that reached further than an
|
|
50
|
+
* entity grant (or less far) would be a second algebra.
|
|
51
|
+
*/
|
|
52
|
+
async function walkParents(scope, start, now, probe) {
|
|
53
|
+
let frontier = [{ ref: start, chain: [] }];
|
|
54
|
+
for (let depth = 0; depth <= ENTITY_WALK_DEPTH && frontier.length > 0; depth++) {
|
|
55
|
+
for (const candidate of frontier) {
|
|
56
|
+
const hit = await probe(candidate.ref, candidate.chain);
|
|
57
|
+
if (hit !== undefined)
|
|
58
|
+
return hit;
|
|
59
|
+
}
|
|
60
|
+
// Nothing consults the frontier past the last depth, so expanding it there is a
|
|
61
|
+
// read per candidate for an answer no one asks for.
|
|
62
|
+
if (depth === ENTITY_WALK_DEPTH)
|
|
63
|
+
break;
|
|
64
|
+
const next = [];
|
|
65
|
+
for (const candidate of frontier) {
|
|
66
|
+
for (const p of await scope.parents(candidate.ref)) {
|
|
67
|
+
// A revoked parent edge stops expanding. Without this the tombstone would work
|
|
68
|
+
// for grants and membership but silently NOT for entity edges — which is the
|
|
69
|
+
// case open question 15 is actually about (a facility moving management
|
|
70
|
+
// company must stop being reachable).
|
|
71
|
+
if (!live(p, now))
|
|
72
|
+
continue;
|
|
73
|
+
next.push({
|
|
74
|
+
ref: p.object,
|
|
75
|
+
chain: [...candidate.chain, t(p.subject, 'parent', p.object)],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
frontier = next;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
43
83
|
/**
|
|
44
84
|
* Build the evaluator over one adapter's reader. Stateless per call: everything it knows it
|
|
45
85
|
* reads at check time, which is what makes check-after-write consistent (the "no zookies"
|
|
@@ -85,6 +125,124 @@ export function createTupleEvaluator(reader) {
|
|
|
85
125
|
}
|
|
86
126
|
return out;
|
|
87
127
|
};
|
|
128
|
+
/**
|
|
129
|
+
* A CAPABILITY subject (#1672). Resolved against the capability's own directory row —
|
|
130
|
+
* never against tuples, so no `capability:` tuple anywhere could widen it — in four steps,
|
|
131
|
+
* each of which denies on its own:
|
|
132
|
+
*
|
|
133
|
+
* 1. **Usable.** The row exists, can act (`mode: 'act'`), and is neither revoked nor
|
|
134
|
+
* expired — `capabilityLive`, the predicate the session door and the exchange share.
|
|
135
|
+
* 2. **The key.** `permission` is one the capability carries.
|
|
136
|
+
* 3. **The subtree.** An entity is named, and it is the capability's root or lies beneath
|
|
137
|
+
* it along declared parent edges — `walkParents`, the walk entity grants take. A
|
|
138
|
+
* capability holds NO node-level authority: a check without an entity denies.
|
|
139
|
+
* 4. **The minter, now.** The principal who minted it must hold `permission` on this
|
|
140
|
+
* entity at this moment, by the ordinary check. This is what makes "a capability never
|
|
141
|
+
* grants more than its minter holds" true on every use rather than only at mint time:
|
|
142
|
+
* revoke the minter's access and their links stop granting it.
|
|
143
|
+
*
|
|
144
|
+
* The proof is the minter's own chain, a `minted-by` link, the parent chain, and last the
|
|
145
|
+
* capability's grant on its root — last so K-34's `grantRefFromProof` names the root.
|
|
146
|
+
*/
|
|
147
|
+
async function checkCapability(id, permission, node, entity) {
|
|
148
|
+
const deny = { allowed: false, checked: permission, node };
|
|
149
|
+
if (!entity || !node.scopeId)
|
|
150
|
+
return deny;
|
|
151
|
+
const scope = reader.scopeFor(node);
|
|
152
|
+
if (!scope?.capability)
|
|
153
|
+
return deny;
|
|
154
|
+
const now = reader.now();
|
|
155
|
+
const row = await scope.capability(id);
|
|
156
|
+
if (!row || !capabilityLive(row, now))
|
|
157
|
+
return deny;
|
|
158
|
+
const grant = capabilityGrantOf(row);
|
|
159
|
+
if (!grant || !grant.mintedBy || !grant.permissions.includes(permission))
|
|
160
|
+
return deny;
|
|
161
|
+
const root = `${grant.entity.entityType}:${grant.entity.entityId}`;
|
|
162
|
+
const self = `capability:${id}`;
|
|
163
|
+
const chain = await walkParents(scope, `${entity.entityType}:${entity.entityId}`, now, async (ref, walked) => ref === root ? [...walked, t(self, `granted:${permission}`, root)] : undefined);
|
|
164
|
+
if (!chain)
|
|
165
|
+
return deny;
|
|
166
|
+
const minter = await check({ kind: 'principal', id: grant.mintedBy }, permission, node, entity);
|
|
167
|
+
if (!minter.allowed)
|
|
168
|
+
return deny;
|
|
169
|
+
return {
|
|
170
|
+
allowed: true,
|
|
171
|
+
proof: [...minter.proof, t(self, 'minted-by', `principal:${grant.mintedBy}`), ...chain],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
async function check(subject, permission, node, entity) {
|
|
175
|
+
if (subject.kind === 'capability') {
|
|
176
|
+
return checkCapability(subject.id, permission, node, entity);
|
|
177
|
+
}
|
|
178
|
+
const now = reader.now();
|
|
179
|
+
const deny = { allowed: false, checked: permission, node };
|
|
180
|
+
const scope = reader.scopeFor(node);
|
|
181
|
+
const getRole = roleReaderFor(node.tenantId);
|
|
182
|
+
// Rule 4 — membership: the subject set is the caller plus its orgs. Shared with
|
|
183
|
+
// `covers` (§ `subjectsOf`).
|
|
184
|
+
const subjects = await subjectsOf(subject, node, now);
|
|
185
|
+
const tuplesFor = async (subjectRefValue, prefix, scoped) => scoped
|
|
186
|
+
? scope
|
|
187
|
+
? scope.tuples(subjectRefValue, prefix)
|
|
188
|
+
: []
|
|
189
|
+
: reader.tenantTuples(node.tenantId, subjectRefValue, prefix);
|
|
190
|
+
for (const nodeObj of nodeObjectsOf(node)) {
|
|
191
|
+
for (const s of subjects) {
|
|
192
|
+
// Rule 1 — role expansion.
|
|
193
|
+
for (const row of await tuplesFor(s.ref, 'role:', nodeObj.scoped)) {
|
|
194
|
+
if (row.object !== nodeObj.obj || !live(row, now))
|
|
195
|
+
continue;
|
|
196
|
+
const roleKey = row.relation.slice('role:'.length);
|
|
197
|
+
const role = await getRole(roleKey);
|
|
198
|
+
if (role?.permissions.includes(permission)) {
|
|
199
|
+
return {
|
|
200
|
+
allowed: true,
|
|
201
|
+
proof: [
|
|
202
|
+
...(s.via ? [s.via] : []),
|
|
203
|
+
t(row.subject, row.relation, row.object),
|
|
204
|
+
t(`role:${roleKey}`, `granted:${permission}`, nodeObj.obj),
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Direct grants at the node.
|
|
210
|
+
for (const row of await tuplesFor(s.ref, `granted:${permission}`, nodeObj.scoped)) {
|
|
211
|
+
if (row.object === nodeObj.obj &&
|
|
212
|
+
row.relation === `granted:${permission}` &&
|
|
213
|
+
live(row, now)) {
|
|
214
|
+
return {
|
|
215
|
+
allowed: true,
|
|
216
|
+
proof: [...(s.via ? [s.via] : []), t(row.subject, row.relation, row.object)],
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Rule 3 — entity walk along declared parent edges (entity grants are scope-local by
|
|
223
|
+
// construction, so no scope store means no walk).
|
|
224
|
+
if (entity && scope) {
|
|
225
|
+
const found = await walkParents(scope, `${entity.entityType}:${entity.entityId}`, now, async (ref, chain) => {
|
|
226
|
+
for (const s of subjects) {
|
|
227
|
+
const grant = await scope.grant(s.ref, `granted:${permission}`, ref);
|
|
228
|
+
if (grant && live(grant, now)) {
|
|
229
|
+
return {
|
|
230
|
+
allowed: true,
|
|
231
|
+
proof: [
|
|
232
|
+
...(s.via ? [s.via] : []),
|
|
233
|
+
...chain,
|
|
234
|
+
t(grant.subject, grant.relation, grant.object),
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return undefined;
|
|
240
|
+
});
|
|
241
|
+
if (found)
|
|
242
|
+
return found;
|
|
243
|
+
}
|
|
244
|
+
return deny;
|
|
245
|
+
}
|
|
88
246
|
return {
|
|
89
247
|
/**
|
|
90
248
|
* The subject's effective permission set at the node, compared against `required`
|
|
@@ -103,6 +261,11 @@ export function createTupleEvaluator(reader) {
|
|
|
103
261
|
// prove the empty set is a subset of anything.
|
|
104
262
|
if (required.length === 0)
|
|
105
263
|
return { covered: true, missing: [] };
|
|
264
|
+
// A capability holds no node-level authority by construction (#1672) — everything it
|
|
265
|
+
// carries is narrowed onto one entity — so it covers nothing and can confer nothing.
|
|
266
|
+
if (subject.kind === 'capability') {
|
|
267
|
+
return { covered: false, missing: [...new Set(required)] };
|
|
268
|
+
}
|
|
106
269
|
const now = reader.now();
|
|
107
270
|
const scope = reader.scopeFor(node);
|
|
108
271
|
const subjects = await subjectsOf(subject, node, now);
|
|
@@ -138,97 +301,7 @@ export function createTupleEvaluator(reader) {
|
|
|
138
301
|
}
|
|
139
302
|
return missing.length === 0 ? { covered: true, missing: [] } : { covered: false, missing };
|
|
140
303
|
},
|
|
141
|
-
|
|
142
|
-
const now = reader.now();
|
|
143
|
-
const deny = { allowed: false, checked: permission, node };
|
|
144
|
-
const scope = reader.scopeFor(node);
|
|
145
|
-
const getRole = roleReaderFor(node.tenantId);
|
|
146
|
-
// Rule 4 — membership: the subject set is the caller plus its orgs. Shared with
|
|
147
|
-
// `covers` (§ `subjectsOf`).
|
|
148
|
-
const subjects = await subjectsOf(subject, node, now);
|
|
149
|
-
const tuplesFor = async (subjectRefValue, prefix, scoped) => scoped
|
|
150
|
-
? scope
|
|
151
|
-
? scope.tuples(subjectRefValue, prefix)
|
|
152
|
-
: []
|
|
153
|
-
: reader.tenantTuples(node.tenantId, subjectRefValue, prefix);
|
|
154
|
-
for (const nodeObj of nodeObjectsOf(node)) {
|
|
155
|
-
for (const s of subjects) {
|
|
156
|
-
// Rule 1 — role expansion.
|
|
157
|
-
for (const row of await tuplesFor(s.ref, 'role:', nodeObj.scoped)) {
|
|
158
|
-
if (row.object !== nodeObj.obj || !live(row, now))
|
|
159
|
-
continue;
|
|
160
|
-
const roleKey = row.relation.slice('role:'.length);
|
|
161
|
-
const role = await getRole(roleKey);
|
|
162
|
-
if (role?.permissions.includes(permission)) {
|
|
163
|
-
return {
|
|
164
|
-
allowed: true,
|
|
165
|
-
proof: [
|
|
166
|
-
...(s.via ? [s.via] : []),
|
|
167
|
-
t(row.subject, row.relation, row.object),
|
|
168
|
-
t(`role:${roleKey}`, `granted:${permission}`, nodeObj.obj),
|
|
169
|
-
],
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// Direct grants at the node.
|
|
174
|
-
for (const row of await tuplesFor(s.ref, `granted:${permission}`, nodeObj.scoped)) {
|
|
175
|
-
if (row.object === nodeObj.obj &&
|
|
176
|
-
row.relation === `granted:${permission}` &&
|
|
177
|
-
live(row, now)) {
|
|
178
|
-
return {
|
|
179
|
-
allowed: true,
|
|
180
|
-
proof: [...(s.via ? [s.via] : []), t(row.subject, row.relation, row.object)],
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
// Rule 3 — entity walk along declared parent edges (entity grants are scope-local by
|
|
187
|
-
// construction, so no scope store means no walk).
|
|
188
|
-
if (entity && scope) {
|
|
189
|
-
let frontier = [{ ref: `${entity.entityType}:${entity.entityId}`, chain: [] }];
|
|
190
|
-
for (let depth = 0; depth <= ENTITY_WALK_DEPTH && frontier.length > 0; depth++) {
|
|
191
|
-
// grant lookup at current frontier objects
|
|
192
|
-
for (const candidate of frontier) {
|
|
193
|
-
for (const s of subjects) {
|
|
194
|
-
const grant = await scope.grant(s.ref, `granted:${permission}`, candidate.ref);
|
|
195
|
-
if (grant && live(grant, now)) {
|
|
196
|
-
return {
|
|
197
|
-
allowed: true,
|
|
198
|
-
proof: [
|
|
199
|
-
...(s.via ? [s.via] : []),
|
|
200
|
-
...candidate.chain,
|
|
201
|
-
t(grant.subject, grant.relation, grant.object),
|
|
202
|
-
],
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// Nothing consults the frontier past the last depth, so expanding it there is a
|
|
208
|
-
// read per candidate for an answer no one asks for.
|
|
209
|
-
if (depth === ENTITY_WALK_DEPTH)
|
|
210
|
-
break;
|
|
211
|
-
// expand one level of parents
|
|
212
|
-
const next = [];
|
|
213
|
-
for (const candidate of frontier) {
|
|
214
|
-
for (const p of await scope.parents(candidate.ref)) {
|
|
215
|
-
// A revoked parent edge stops expanding. Without this the tombstone would work
|
|
216
|
-
// for grants and membership but silently NOT for entity edges — which is the
|
|
217
|
-
// case open question 15 is actually about (a facility moving management
|
|
218
|
-
// company must stop being reachable).
|
|
219
|
-
if (!live(p, now))
|
|
220
|
-
continue;
|
|
221
|
-
next.push({
|
|
222
|
-
ref: p.object,
|
|
223
|
-
chain: [...candidate.chain, t(p.subject, 'parent', p.object)],
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
frontier = next;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return deny;
|
|
231
|
-
},
|
|
304
|
+
check,
|
|
232
305
|
};
|
|
233
306
|
}
|
|
234
307
|
//# sourceMappingURL=permission-eval.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-eval.js","sourceRoot":"","sources":["../src/permission-eval.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,UAAU,GASX,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"permission-eval.js","sourceRoot":"","sources":["../src/permission-eval.ts"],"names":[],"mappings":"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"}
|