@substrat-run/kernel 0.136.0 → 0.138.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 +2 -0
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +2 -0
- package/dist/capability.js.map +1 -1
- package/dist/delivery-refusal.d.ts +21 -0
- package/dist/delivery-refusal.d.ts.map +1 -0
- package/dist/delivery-refusal.js +21 -0
- package/dist/delivery-refusal.js.map +1 -0
- package/dist/index.d.ts +23 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -12
- package/dist/index.js.map +1 -1
- package/dist/invocation-log.d.ts +25 -1
- package/dist/invocation-log.d.ts.map +1 -1
- package/dist/invocation-log.js +45 -6
- package/dist/invocation-log.js.map +1 -1
- package/dist/job-run.d.ts +370 -47
- package/dist/job-run.d.ts.map +1 -1
- package/dist/job-run.js +472 -81
- package/dist/job-run.js.map +1 -1
- package/dist/lifecycle-flow.d.ts.map +1 -1
- package/dist/lifecycle-flow.js +2 -1
- package/dist/lifecycle-flow.js.map +1 -1
- package/dist/membership-executor.d.ts +97 -0
- package/dist/membership-executor.d.ts.map +1 -0
- package/dist/membership-executor.js +246 -0
- package/dist/membership-executor.js.map +1 -0
- package/dist/membership-fence.d.ts +57 -0
- package/dist/membership-fence.d.ts.map +1 -0
- package/dist/membership-fence.js +90 -0
- package/dist/membership-fence.js.map +1 -0
- package/dist/operation-series.d.ts +58 -0
- package/dist/operation-series.d.ts.map +1 -0
- package/dist/operation-series.js +63 -0
- package/dist/operation-series.js.map +1 -0
- package/dist/peer.d.ts +13 -8
- package/dist/peer.d.ts.map +1 -1
- package/dist/peer.js +13 -14
- package/dist/peer.js.map +1 -1
- package/dist/permission-eval.d.ts +48 -1
- package/dist/permission-eval.d.ts.map +1 -1
- package/dist/permission-eval.js +137 -18
- package/dist/permission-eval.js.map +1 -1
- package/dist/read-only-sql.d.ts +2 -26
- package/dist/read-only-sql.d.ts.map +1 -1
- package/dist/read-only-sql.js +13 -9
- package/dist/read-only-sql.js.map +1 -1
- package/dist/refusal-query.d.ts +4 -1
- package/dist/refusal-query.d.ts.map +1 -1
- package/dist/refusal-query.js +6 -4
- package/dist/refusal-query.js.map +1 -1
- package/dist/refusals.d.ts +80 -8
- package/dist/refusals.d.ts.map +1 -1
- package/dist/refusals.js +141 -22
- package/dist/refusals.js.map +1 -1
- package/dist/scope-copy.d.ts +18 -0
- package/dist/scope-copy.d.ts.map +1 -1
- package/dist/scope-copy.js +18 -0
- package/dist/scope-copy.js.map +1 -1
- package/dist/scope-host.d.ts +266 -15
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js +41 -0
- package/dist/scope-host.js.map +1 -1
- package/dist/spine-restore.d.ts +7 -2
- package/dist/spine-restore.d.ts.map +1 -1
- package/dist/spine-restore.js +13 -5
- package/dist/spine-restore.js.map +1 -1
- package/dist/system-switch-record.d.ts +290 -74
- package/dist/system-switch-record.d.ts.map +1 -1
- package/dist/system-switch-record.js +353 -101
- package/dist/system-switch-record.js.map +1 -1
- package/dist/system-switch.d.ts +146 -20
- package/dist/system-switch.d.ts.map +1 -1
- package/dist/system-switch.js +135 -27
- package/dist/system-switch.js.map +1 -1
- package/dist/timeline.d.ts +23 -0
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +30 -0
- package/dist/timeline.js.map +1 -1
- package/dist/ulid.d.ts +5 -0
- package/dist/ulid.d.ts.map +1 -1
- package/dist/ulid.js +9 -0
- package/dist/ulid.js.map +1 -1
- package/dist/vertical-events.d.ts +7 -1
- package/dist/vertical-events.d.ts.map +1 -1
- package/dist/vertical-events.js +14 -0
- package/dist/vertical-events.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { instant } from '@substrat-run/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* The removal fence (#1184): one row per (tenant, principal), stamped with the latest moment
|
|
4
|
+
* that person was removed at the tenant level. The membership executor's add reads it inside
|
|
5
|
+
* the directory unit that assigns the role, and applies nothing when it stands at or after
|
|
6
|
+
* the request's `occurredAt - MEMBERSHIP_REMOVAL_SKEW_MS`.
|
|
7
|
+
*
|
|
8
|
+
* A table of its own rather than a read of the admin log, because K-21's audit contract
|
|
9
|
+
* writes no row for a removal that took nothing — and a removal of someone whose add is still
|
|
10
|
+
* on its way takes nothing, yet must still win. Every tenant-level `unassignRole` and every
|
|
11
|
+
* `removeMember` raises it in the SAME unit as its revoke, staff's and the executor's alike,
|
|
12
|
+
* a no-op included. Never lowered: a later removal moves it forward, an earlier one does not.
|
|
13
|
+
*
|
|
14
|
+
* The admin log is still the history. This is only the latest position.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The table. Interpolated into both adapters' directory DDL, so `lint:spine-ddl` sees the one
|
|
18
|
+
* spelling on each side, and a directory dump carries it like every other directory table.
|
|
19
|
+
*/
|
|
20
|
+
export const MEMBERSHIP_FENCES_DDL = `
|
|
21
|
+
CREATE TABLE IF NOT EXISTS _substrat_membership_fences (
|
|
22
|
+
tenant_id TEXT NOT NULL,
|
|
23
|
+
principal TEXT NOT NULL,
|
|
24
|
+
removed_at TEXT NOT NULL,
|
|
25
|
+
PRIMARY KEY (tenant_id, principal)
|
|
26
|
+
);
|
|
27
|
+
`;
|
|
28
|
+
/** Raise `principal`'s fence to `removed_at`, never lower it. Binds tenant, principal, at. */
|
|
29
|
+
export const RAISE_MEMBERSHIP_FENCE_SQL = `
|
|
30
|
+
INSERT INTO _substrat_membership_fences (tenant_id, principal, removed_at) VALUES (?, ?, ?)
|
|
31
|
+
ON CONFLICT (tenant_id, principal) DO UPDATE SET removed_at = MAX(removed_at, excluded.removed_at)
|
|
32
|
+
`;
|
|
33
|
+
/** The fence, if it stands at or after an instant. Binds tenant, principal, since. */
|
|
34
|
+
export const MEMBERSHIP_FENCE_SINCE_SQL = `
|
|
35
|
+
SELECT removed_at FROM _substrat_membership_fences
|
|
36
|
+
WHERE tenant_id = ? AND principal = ? AND removed_at >= ?
|
|
37
|
+
`;
|
|
38
|
+
/** The fence's table name, as the DDL above spells it. */
|
|
39
|
+
export const MEMBERSHIP_FENCES_TABLE = '_substrat_membership_fences';
|
|
40
|
+
/**
|
|
41
|
+
* The one-time backfill from the admin log: everyone removed at the tenant level before the
|
|
42
|
+
* fence existed. Without it, an add requested back then and never effected — the backlog a host
|
|
43
|
+
* that mounts the executor for the first time drains — would re-admit someone removed by hand
|
|
44
|
+
* since, because the fence would have nothing to say about them.
|
|
45
|
+
*
|
|
46
|
+
* It reads who those removals named: an `unassignRole` row at the tenant node (`scope_id` NULL)
|
|
47
|
+
* carries the assignment as `before.principalId`, a `removeMember` row the membership as
|
|
48
|
+
* `before.principal`. A no-op removal wrote no row and is not recovered — nothing before the
|
|
49
|
+
* fence recorded one.
|
|
50
|
+
*
|
|
51
|
+
* **Every backfilled fence stands at `at`, the adapter's clock at the moment it runs — never the
|
|
52
|
+
* audit row's own time** (Codex round 4). Those rows were stamped by the wall clock, while a
|
|
53
|
+
* request still queued from back then was stamped by the host's clock, which need not agree: a
|
|
54
|
+
* host clock running ahead would put the copied time below the request's cutoff and the backlog
|
|
55
|
+
* would re-admit the person. Stamping at backfill time is conservative instead: every request
|
|
56
|
+
* emitted before it, for anyone ever removed, is refused. That takes nothing legitimate away —
|
|
57
|
+
* anyone re-added before the executor existed got their role inline, by hand, and holds it
|
|
58
|
+
* still — and an invite accepted more than `MEMBERSHIP_REMOVAL_SKEW_MS` after it is unaffected.
|
|
59
|
+
*
|
|
60
|
+
* `INSERT OR IGNORE`, so a fence the live path raised is never lowered by history. The adapters
|
|
61
|
+
* run it on the construction that creates the table, and a directory restore runs it when the
|
|
62
|
+
* dump did not carry the table, so it runs once per directory that never had it.
|
|
63
|
+
*/
|
|
64
|
+
export function membershipFencesBackfillSql(at) {
|
|
65
|
+
// Interpolated, not bound: the adapters run it through `exec`. `instant` holds it to an
|
|
66
|
+
// ISO-8601 UTC timestamp, which carries no quote, or throws.
|
|
67
|
+
return `
|
|
68
|
+
INSERT OR IGNORE INTO _substrat_membership_fences (tenant_id, principal, removed_at)
|
|
69
|
+
SELECT DISTINCT tenant_id, principal, '${instant.parse(at)}'
|
|
70
|
+
FROM (
|
|
71
|
+
SELECT tenant_id, json_extract(before, '$.principalId') AS principal
|
|
72
|
+
FROM _substrat_admin_log
|
|
73
|
+
WHERE action = 'unassignRole' AND tenant_id IS NOT NULL AND scope_id IS NULL
|
|
74
|
+
UNION ALL
|
|
75
|
+
SELECT tenant_id, json_extract(before, '$.principal') AS principal
|
|
76
|
+
FROM _substrat_admin_log
|
|
77
|
+
WHERE action = 'removeMember' AND tenant_id IS NOT NULL
|
|
78
|
+
)
|
|
79
|
+
WHERE principal IS NOT NULL
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
/** "Does the fence table exist yet?" — asked BEFORE the DDL, so the backfill runs once. */
|
|
83
|
+
export function membershipFencesTableExists(db) {
|
|
84
|
+
return db.all(`SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = ?`, MEMBERSHIP_FENCES_TABLE).length > 0;
|
|
85
|
+
}
|
|
86
|
+
/** Does a dump carry the fence table? Compared without case, as SQLite resolves a name. */
|
|
87
|
+
export function dumpCarriesMembershipFences(names) {
|
|
88
|
+
return names.some((n) => n.toLowerCase() === MEMBERSHIP_FENCES_TABLE);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=membership-fence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"membership-fence.js","sourceRoot":"","sources":["../src/membership-fence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAElD;;;;;;;;;;;;;GAaG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;CAOpC,CAAC;AAEF,8FAA8F;AAC9F,MAAM,CAAC,MAAM,0BAA0B,GAAG;;;CAGzC,CAAC;AAEF,sFAAsF;AACtF,MAAM,CAAC,MAAM,0BAA0B,GAAG;;;CAGzC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,CAAC,MAAM,uBAAuB,GAAG,6BAA6B,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,2BAA2B,CAAC,EAAU;IACpD,wFAAwF;IACxF,6DAA6D;IAC7D,OAAO;;2CAEkC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;;;;;;;;;;;CAW3D,CAAC;AACF,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,2BAA2B,CAAC,EAAwD;IAClG,OAAO,EAAE,CAAC,GAAG,CAAC,0EAA0E,EAAE,uBAAuB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAChI,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,2BAA2B,CAAC,KAAwB;IAClE,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,uBAAuB,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business volumes on a clock (#1750): calls per `(entityType, operation)` per time bucket,
|
|
3
|
+
* counted over a scope's outbox. Which pairs are worth counting is the caller's, derived
|
|
4
|
+
* from the model by the dashboard's `lifecycleMovesOf` (see `@substrat-run/contracts`'s
|
|
5
|
+
* `operation-series.ts` for why those counts are exact).
|
|
6
|
+
*
|
|
7
|
+
* ## Bounded, and index-backed with no new index
|
|
8
|
+
*
|
|
9
|
+
* The window is capped at seven days and the answer at `OPERATION_SERIES_MAX_BUCKETS` per
|
|
10
|
+
* pair, and it is ONE aggregate statement: no row leaves SQLite, so there is no per-row
|
|
11
|
+
* work on this side of it. The rows it scans are a CLOSED range of the primary key, so the
|
|
12
|
+
* work is the window's rows whatever lies after it — a window years ago visits only its own.
|
|
13
|
+
* An event id is minted from the instant the operation ran (#956) by a mint whose floor only
|
|
14
|
+
* rises, so an id never carries a time BEFORE its own `occurred_at`: every event at or after
|
|
15
|
+
* `since` has an id at or above `ulidFloor(since)`. The key range ends at
|
|
16
|
+
* `ulidFloor(until + OPERATION_SERIES_ID_SLACK_MS)`, a fixed sliver past the window.
|
|
17
|
+
* `occurred_at` stays the exact filter on both ends; the key range only bounds the scan.
|
|
18
|
+
*
|
|
19
|
+
* The slack is for ids that run AHEAD of their instants. `createUlid` (ulid.ts) holds a
|
|
20
|
+
* clock that is at or behind its floor at `lastTime` — and bumps a millisecond on the
|
|
21
|
+
* (astronomically rare) random overflow — and `seedFrom` raises that floor to the outbox's
|
|
22
|
+
* `MAX(id)` when a writer wakes. So an event in the window's last moments can carry an id
|
|
23
|
+
* past `until`, by as much as the clock stepped back. The mint itself sets no bound on
|
|
24
|
+
* that; the wall clock does: the Durable Object host reads the platform's NTP-disciplined
|
|
25
|
+
* clock, whose corrections are sub-second. `OPERATION_SERIES_ID_SLACK_MS` covers that with
|
|
26
|
+
* two orders of margin, at the cost of scanning one more minute of rows. `occurred_at`
|
|
27
|
+
* stays the exact filter, so the slack admits rows to the scan, never to the count.
|
|
28
|
+
*
|
|
29
|
+
* Past the slack, stated: a step back larger than a minute (a pure host's injected clock
|
|
30
|
+
* rewound by hand, a floor seeded from an id planted in the future) leaves an id beyond
|
|
31
|
+
* the range, and that event is not counted by a window ending before its id.
|
|
32
|
+
*
|
|
33
|
+
* The bucket arithmetic casts its parameters: a driver may bind a JS number as REAL, and
|
|
34
|
+
* then the division would place an event at its own instant instead of its bucket's start.
|
|
35
|
+
*
|
|
36
|
+
* The pairs go in as ONE bound JSON array, matched with `json_each`, rather than a
|
|
37
|
+
* placeholder each: 64 pairs as two `IN` lists is 128 parameters, past the Durable
|
|
38
|
+
* Object's 100.
|
|
39
|
+
*
|
|
40
|
+
* Same permission posture as every read in `timeline.ts`: the caller checks, this does not.
|
|
41
|
+
*/
|
|
42
|
+
import type { OperationSeriesInput, OperationSeriesResult } from '@substrat-run/contracts';
|
|
43
|
+
import type { TimelineReader } from './timeline.js';
|
|
44
|
+
/**
|
|
45
|
+
* How far past `until` the key range reaches, for an id held at the mint's floor while its
|
|
46
|
+
* instant was inside the window (see the header for where the size comes from).
|
|
47
|
+
*/
|
|
48
|
+
export declare const OPERATION_SERIES_ID_SLACK_MS = 60000;
|
|
49
|
+
/**
|
|
50
|
+
* The read's one statement and its parameters — exported so each adapter's suite can ask
|
|
51
|
+
* its own SQLite for the plan of exactly what runs, not a copy of it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function operationSeriesQuery(input: OperationSeriesInput): {
|
|
54
|
+
sql: string;
|
|
55
|
+
params: Array<string | number>;
|
|
56
|
+
};
|
|
57
|
+
export declare function readOperationSeries(ctx: TimelineReader, input: OperationSeriesInput): OperationSeriesResult;
|
|
58
|
+
//# sourceMappingURL=operation-series.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-series.d.ts","sourceRoot":"","sources":["../src/operation-series.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAKnD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,oBAAoB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;CAAE,CA2BjH;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,oBAAoB,GAAG,qBAAqB,CAqB3G"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ulidFloor } from './ulid.js';
|
|
2
|
+
/**
|
|
3
|
+
* How far past `until` the key range reaches, for an id held at the mint's floor while its
|
|
4
|
+
* instant was inside the window (see the header for where the size comes from).
|
|
5
|
+
*/
|
|
6
|
+
export const OPERATION_SERIES_ID_SLACK_MS = 60_000;
|
|
7
|
+
/** Joins a pair into one key — a control character no entity or operation name carries. */
|
|
8
|
+
const SEP = '\u001f';
|
|
9
|
+
/**
|
|
10
|
+
* The read's one statement and its parameters — exported so each adapter's suite can ask
|
|
11
|
+
* its own SQLite for the plan of exactly what runs, not a copy of it.
|
|
12
|
+
*/
|
|
13
|
+
export function operationSeriesQuery(input) {
|
|
14
|
+
const sinceMs = Date.parse(input.since);
|
|
15
|
+
const untilMs = Date.parse(input.until);
|
|
16
|
+
const keys = [...new Set(input.moves.map((m) => `${m.entityType}${SEP}${m.operation}`))];
|
|
17
|
+
// A distinct (entity, call) is one move: a call that emitted three events about one
|
|
18
|
+
// record moved it once. A row from before invocation ids falls back to its own id.
|
|
19
|
+
return {
|
|
20
|
+
sql: `SELECT entity_type, operation,
|
|
21
|
+
(CAST(strftime('%s', occurred_at) AS INTEGER) - CAST(? AS INTEGER)) / CAST(? AS INTEGER) AS b,
|
|
22
|
+
COUNT(DISTINCT entity_id || char(31) || COALESCE(invocation_id, id)) AS n
|
|
23
|
+
FROM _substrat_outbox
|
|
24
|
+
WHERE id >= ? AND id < ? AND occurred_at >= ? AND occurred_at < ?
|
|
25
|
+
AND entity_type || char(31) || operation IN (SELECT value FROM json_each(?))
|
|
26
|
+
GROUP BY entity_type, operation, b
|
|
27
|
+
ORDER BY b`,
|
|
28
|
+
params: [
|
|
29
|
+
sinceMs / 1000,
|
|
30
|
+
input.bucketMinutes * 60,
|
|
31
|
+
ulidFloor(sinceMs),
|
|
32
|
+
// `until` is at most the end of 9999 (the input schema's bound), so a minute past it is
|
|
33
|
+
// far inside what a ULID encodes: `ulidFloor` cannot throw here.
|
|
34
|
+
ulidFloor(untilMs + OPERATION_SERIES_ID_SLACK_MS),
|
|
35
|
+
new Date(sinceMs).toISOString(),
|
|
36
|
+
new Date(untilMs).toISOString(),
|
|
37
|
+
JSON.stringify(keys),
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function readOperationSeries(ctx, input) {
|
|
42
|
+
const sinceMs = Date.parse(input.since);
|
|
43
|
+
const step = input.bucketMinutes * 60;
|
|
44
|
+
const q = operationSeriesQuery(input);
|
|
45
|
+
const rows = ctx.sql.query(q.sql, q.params);
|
|
46
|
+
const counted = new Map();
|
|
47
|
+
for (const r of rows) {
|
|
48
|
+
const key = `${r.entity_type}${SEP}${r.operation}`;
|
|
49
|
+
const list = counted.get(key) ?? [];
|
|
50
|
+
list.push({ start: new Date(sinceMs + r.b * step * 1000).toISOString(), count: r.n });
|
|
51
|
+
counted.set(key, list);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
since: input.since,
|
|
55
|
+
until: input.until,
|
|
56
|
+
bucketMinutes: input.bucketMinutes,
|
|
57
|
+
series: input.moves.map((m) => {
|
|
58
|
+
const buckets = counted.get(`${m.entityType}${SEP}${m.operation}`) ?? [];
|
|
59
|
+
return { entityType: m.entityType, operation: m.operation, total: buckets.reduce((n, b) => n + b.count, 0), buckets };
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=operation-series.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-series.js","sourceRoot":"","sources":["../src/operation-series.ts"],"names":[],"mappings":"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"}
|
package/dist/peer.d.ts
CHANGED
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
* so a switched-off peer holds nothing to ANY check — `covers` included — without the checker
|
|
23
23
|
* knowing the marker exists; and `seatScopeTuple` seats nothing for a subject whose marker is
|
|
24
24
|
* live, so no re-provision can hand the grants back. `restoreToPeer` is the only way back.
|
|
25
|
+
* The switch is also recorded in the directory (#2029, `system-switch-record.ts`, kind
|
|
26
|
+
* `peer`), so a scope whose storage loses the marker — a wipe, a restore, a PITR rewind — gets
|
|
27
|
+
* it back from every carry's re-assert, exactly as a module's does.
|
|
25
28
|
* - **`peerGrantsStatus`** — the read half of that switch: every peer this scope holds or has
|
|
26
29
|
* held grants for, and where each stands. The SAME predicate `admitPeer` gates on, so the
|
|
27
30
|
* status a tenant reads and the answer a call gets cannot disagree.
|
|
@@ -34,11 +37,8 @@
|
|
|
34
37
|
* guarantees are the ones above; the caller's liveness and tenancy are the platform's gate.
|
|
35
38
|
*/
|
|
36
39
|
import { type CheckSubject, type PeerSpec, type Scope, type TenantId, type VerticalCaller, type VerticalResolution } from '@substrat-run/contracts';
|
|
37
|
-
import { type SwitchOutcome, type SwitchSql } from './system-switch.js';
|
|
38
|
-
|
|
39
|
-
export declare const PEER_SUBJECT_PREFIX = "vertical:";
|
|
40
|
-
/** The tuple subject a peer's grants are seated under. The slug only, never the instance. */
|
|
41
|
-
export declare const peerSubjectRef: (vertical: string) => string;
|
|
40
|
+
import { PEER_SUBJECT_PREFIX, peerSubjectRef, type SwitchOutcome, type SwitchSql } from './system-switch.js';
|
|
41
|
+
export { PEER_SUBJECT_PREFIX, peerSubjectRef };
|
|
42
42
|
/** One peer vertical as every registered module declares it, together. */
|
|
43
43
|
export interface PeerDeclaration {
|
|
44
44
|
readonly vertical: string;
|
|
@@ -80,15 +80,20 @@ export declare function peerSeats(peers: PeerDeclarations, scopeId: string): Pee
|
|
|
80
80
|
export declare function admitPeer(db: SwitchSql, peers: PeerDeclarations, caller: VerticalCaller, operation: string | null): CheckSubject;
|
|
81
81
|
/**
|
|
82
82
|
* Move one peer's kill switch on one scope. Idempotent, and `held: false` with nothing written
|
|
83
|
-
* when the
|
|
84
|
-
*
|
|
85
|
-
*
|
|
83
|
+
* when the peer has no authority reaching the scope: no grant and no marker here, and no live
|
|
84
|
+
* TENANT-level `vertical:<slug>` grant (`tenantHeld`, which the caller reads from the directory).
|
|
85
|
+
* A typo'd slug, or a peer this scope was never provisioned with, must not write a marker that
|
|
86
|
+
* silently blocks it the day a version declaring it lands. A peer whose only authority here is a
|
|
87
|
+
* tenant-level grant IS held (#2030): OFF writes the marker, which the door and the evaluator
|
|
88
|
+
* both refuse it on, and tombstones nothing. Run inside one transaction.
|
|
86
89
|
*/
|
|
87
90
|
export declare function switchPeer(db: SwitchSql, input: {
|
|
88
91
|
vertical: string;
|
|
89
92
|
scopeId: string;
|
|
90
93
|
to: 'on' | 'off';
|
|
91
94
|
at: string;
|
|
95
|
+
tenantHeld?: boolean;
|
|
96
|
+
fence?: string;
|
|
92
97
|
}): SwitchOutcome;
|
|
93
98
|
/** Is this peer switched off on the scope `db` is? */
|
|
94
99
|
export declare function peerSwitchedOff(db: SwitchSql, vertical: string): boolean;
|
package/dist/peer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"peer.d.ts","sourceRoot":"","sources":["../src/peer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"peer.d.ts","sourceRoot":"","sources":["../src/peer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,KAAK,EACV,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,mBAAmB,EAEnB,cAAc,EAId,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,CAAC;AAG/C,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC3C;AACD,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAEpE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAA;CAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAe/G;AAED,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,CAQ9E;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,EAAE,EAAE,SAAS,EACb,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,YAAY,CAuBd;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,SAAS,EACb,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,GAAG,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/G,aAAa,CAGf;AAED,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,IAAI,GAAG,KAAK,GAAG,WAAW,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,EAAE,CAW5E;AAED,8DAA8D;AAC9D,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAC,CAAC;AAEvH;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,QAAQ,CAAC,yBAAyB,CAAC,EAC3C,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,GACf,kBAAkB,CAapB"}
|
package/dist/peer.js
CHANGED
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
* so a switched-off peer holds nothing to ANY check — `covers` included — without the checker
|
|
23
23
|
* knowing the marker exists; and `seatScopeTuple` seats nothing for a subject whose marker is
|
|
24
24
|
* live, so no re-provision can hand the grants back. `restoreToPeer` is the only way back.
|
|
25
|
+
* The switch is also recorded in the directory (#2029, `system-switch-record.ts`, kind
|
|
26
|
+
* `peer`), so a scope whose storage loses the marker — a wipe, a restore, a PITR rewind — gets
|
|
27
|
+
* it back from every carry's re-assert, exactly as a module's does.
|
|
25
28
|
* - **`peerGrantsStatus`** — the read half of that switch: every peer this scope holds or has
|
|
26
29
|
* held grants for, and where each stands. The SAME predicate `admitPeer` gates on, so the
|
|
27
30
|
* status a tenant reads and the answer a call gets cannot disagree.
|
|
@@ -35,11 +38,8 @@
|
|
|
35
38
|
*/
|
|
36
39
|
import { substratError, } from '@substrat-run/contracts';
|
|
37
40
|
import { isPrimaryScope } from './platform-sweep.js';
|
|
38
|
-
import { SYSTEM_SWITCH_OFF_RELATION, subjectGrantState, subjectSwitchedOff,
|
|
39
|
-
|
|
40
|
-
export const PEER_SUBJECT_PREFIX = 'vertical:';
|
|
41
|
-
/** The tuple subject a peer's grants are seated under. The slug only, never the instance. */
|
|
42
|
-
export const peerSubjectRef = (vertical) => `${PEER_SUBJECT_PREFIX}${vertical}`;
|
|
41
|
+
import { PEER_SUBJECT_PREFIX, SYSTEM_SWITCH_OFF_RELATION, peerSubjectRef, subjectGrantState, subjectSwitchedOff, moveSwitch, } from './system-switch.js';
|
|
42
|
+
export { PEER_SUBJECT_PREFIX, peerSubjectRef };
|
|
43
43
|
/**
|
|
44
44
|
* The union of every module's `peers` declaration, keyed by peer slug. Two modules naming one
|
|
45
45
|
* peer contribute both of their allowlists and both of their key sets: each module speaks for
|
|
@@ -103,17 +103,16 @@ export function admitPeer(db, peers, caller, operation) {
|
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* Move one peer's kill switch on one scope. Idempotent, and `held: false` with nothing written
|
|
106
|
-
* when the
|
|
107
|
-
*
|
|
108
|
-
*
|
|
106
|
+
* when the peer has no authority reaching the scope: no grant and no marker here, and no live
|
|
107
|
+
* TENANT-level `vertical:<slug>` grant (`tenantHeld`, which the caller reads from the directory).
|
|
108
|
+
* A typo'd slug, or a peer this scope was never provisioned with, must not write a marker that
|
|
109
|
+
* silently blocks it the day a version declaring it lands. A peer whose only authority here is a
|
|
110
|
+
* tenant-level grant IS held (#2030): OFF writes the marker, which the door and the evaluator
|
|
111
|
+
* both refuse it on, and tombstones nothing. Run inside one transaction.
|
|
109
112
|
*/
|
|
110
113
|
export function switchPeer(db, input) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
scopeId: input.scopeId,
|
|
114
|
-
to: input.to,
|
|
115
|
-
at: input.at,
|
|
116
|
-
});
|
|
114
|
+
const { vertical, ...rest } = input;
|
|
115
|
+
return moveSwitch(db, 'peer', { key: vertical, ...rest });
|
|
117
116
|
}
|
|
118
117
|
/** Is this peer switched off on the scope `db` is? */
|
|
119
118
|
export function peerSwitchedOff(db, vertical) {
|
package/dist/peer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"peer.js","sourceRoot":"","sources":["../src/peer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"peer.js","sourceRoot":"","sources":["../src/peer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EACL,aAAa,GAOd,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,GAGX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,CAAC;AAa/C;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,SAAoD;IAC/E,MAAM,GAAG,GAAG,IAAI,GAAG,EAAmF,CAAC;IACvG,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI;gBACtC,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,UAAU,EAAE,IAAI,GAAG,EAAU;gBAC7B,WAAW,EAAE,IAAI,GAAG,EAAU;aAC/B,CAAC;YACF,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,UAAU;gBAAE,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3D,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,WAAW;gBAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AASD;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,KAAuB,EAAE,OAAe;IAChE,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC5F,KAAK,MAAM,UAAU,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACtD,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,WAAW,UAAU,EAAE,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,EAAE,CAAC,CAAC;QACtH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CACvB,EAAa,EACb,KAAuB,EACvB,MAAsB,EACtB,SAAwB;IAExB,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,aAAa,CACjB,WAAW,EACX,aAAa,MAAM,CAAC,QAAQ,oEAAoE;YAC9F,8EAA8E,CACjF,CAAC;IACJ,CAAC;IACD,IAAI,kBAAkB,CAAC,EAAE,EAAE,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC5D,MAAM,aAAa,CACjB,WAAW,EACX,aAAa,MAAM,CAAC,QAAQ,kEAAkE,CAC/F,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9D,MAAM,aAAa,CACjB,WAAW,EACX,aAAa,MAAM,CAAC,QAAQ,qBAAqB,SAAS,qCAAqC;YAC7F,iDAAiD,CACpD,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;AACxE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CACxB,EAAa,EACb,KAAgH;IAEhH,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IACpC,OAAO,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,eAAe,CAAC,EAAa,EAAE,QAAgB;IAC7D,OAAO,kBAAkB,CAAC,EAAE,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC1D,CAAC;AAQD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAa,EAAE,GAAW;IACzD,MAAM,IAAI,GAAG,EAAE,CAAC,GAAG,CACjB;iCAC6B,mBAAmB,CAAC,MAAM,QAAQ,mBAAmB;kEACpB,0BAA0B;uBACrE,CACK,CAAC;IAC3B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACtB,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC/D,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,iBAAiB,CAAC,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC;AAKD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAA2C,EAC3C,QAAkB,EAClB,QAAgB;IAEhB,MAAM,OAAO,GAAgC,EAAE,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ;YAAE,SAAS;QACzE,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;YAAE,SAAS;QAClE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;IACvB,IAAI,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpG,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IAClF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;AAC7E,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Node, type RoleDefinition } from '@substrat-run/contracts';
|
|
1
|
+
import { type Coverage, type EntityRef, type Node, type PermissionKey, type RoleDefinition } from '@substrat-run/contracts';
|
|
2
2
|
import type { PermissionChecker } from './permission-checker.js';
|
|
3
3
|
import { type CapabilityRow } from './capability.js';
|
|
4
4
|
type MaybePromise<T> = T | Promise<T>;
|
|
@@ -37,6 +37,15 @@ export interface ScopeTupleReader {
|
|
|
37
37
|
* means every capability check DENIES, never that one is waved through.
|
|
38
38
|
*/
|
|
39
39
|
capability?(id: string): MaybePromise<CapabilityRow | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Is a kill switch holding `subject` (`system:<module>`, `vertical:<slug>`) off on this
|
|
42
|
+
* scope (#1666, #1706)? While it answers true the evaluator denies that subject here
|
|
43
|
+
* whatever grants it holds, scope- or tenant-level (#1823). Both adapters answer it today
|
|
44
|
+
* from the scope's own OFF marker, with `SYSTEM_SWITCH_OFF_QUERY` — the spelling the
|
|
45
|
+
* schedule gate and the grant refusal share. Required, so no reader can leave it out and
|
|
46
|
+
* fail open.
|
|
47
|
+
*/
|
|
48
|
+
switchedOff(subject: string): MaybePromise<boolean>;
|
|
40
49
|
}
|
|
41
50
|
/**
|
|
42
51
|
* Everything the evaluator needs to know, and nothing about where it comes from.
|
|
@@ -61,11 +70,49 @@ export interface PermissionTupleReader {
|
|
|
61
70
|
* exist (`ctx.relink`, #1864). Binds ONE parameter: the same `now`.
|
|
62
71
|
*/
|
|
63
72
|
export declare const liveTupleSql: (alias?: string) => string;
|
|
73
|
+
/**
|
|
74
|
+
* Is `entity` the `root`, or does it lie beneath it along live declared `parent` edges?
|
|
75
|
+
*
|
|
76
|
+
* The walk a capability check makes to its own root (`checkCapability` above), with no
|
|
77
|
+
* subject and no grant: a live read narrowed `within` an entity (#1853) asks exactly this
|
|
78
|
+
* of every frame, so it travels the same edges to the same depth, skipping the same
|
|
79
|
+
* revoked ones. A row linked to two parents (`ctx.link` twice) reaches either root; a row
|
|
80
|
+
* `ctx.relink`ed away from one reaches only the other (#1864).
|
|
81
|
+
*/
|
|
82
|
+
export declare function reachesWithin(scope: Pick<ScopeTupleReader, 'parents'>, entity: EntityRef, root: EntityRef, now: string): Promise<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* Every root `reachesWithin` would answer `true` for, as `entityType:entityId` refs — the
|
|
85
|
+
* entity itself included. The same walk, run to its full depth once, for a caller asking
|
|
86
|
+
* about many roots: a live fan-out with one socket per widget session (#1853).
|
|
87
|
+
*/
|
|
88
|
+
export declare function ancestorsWithin(scope: Pick<ScopeTupleReader, 'parents'>, entity: EntityRef, now: string): Promise<Set<string>>;
|
|
64
89
|
/**
|
|
65
90
|
* Build the evaluator over one adapter's reader. Stateless per call: everything it knows it
|
|
66
91
|
* reads at check time, which is what makes check-after-write consistent (the "no zookies"
|
|
67
92
|
* property) and what lets `reader.now()` decide expiry rather than the wall clock.
|
|
68
93
|
*/
|
|
69
94
|
export declare function createTupleEvaluator(reader: PermissionTupleReader): PermissionChecker;
|
|
95
|
+
/**
|
|
96
|
+
* A directory read that never yields (#1184): what a directory UNIT — one SQLite transaction,
|
|
97
|
+
* one synchronous ControlPlaneDO method — can consult without letting another write in. The
|
|
98
|
+
* tenant-level half of `PermissionTupleReader`, with every answer in hand.
|
|
99
|
+
*/
|
|
100
|
+
export interface TenantDirectoryReader {
|
|
101
|
+
now(): string;
|
|
102
|
+
tenantTuples(tenantId: string, subject: string, relationPrefix: string): PermissionTupleRow[];
|
|
103
|
+
getRole(tenantId: string, key: string): RoleDefinition | undefined;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* `covers` at the TENANT node, synchronously (#1184): the K-21 set comparison over the
|
|
107
|
+
* principal and every org it is a live member of, each one's live tenant-level `role:` and
|
|
108
|
+
* `granted:` tuples, roles expanded. The membership executor's bound, asked again inside the
|
|
109
|
+
* unit that writes the role, so a grant, role definition or demotion landing between its
|
|
110
|
+
* early check and its write cannot be written past.
|
|
111
|
+
*
|
|
112
|
+
* The same answer as `createTupleEvaluator(reader).covers(principal, required, tenantNode)` —
|
|
113
|
+
* a tenant node has no scope store, no entity walk and no switch — which
|
|
114
|
+
* `permission-eval.test.ts` pins over the cases where the two could part.
|
|
115
|
+
*/
|
|
116
|
+
export declare function tenantCoverage(reader: TenantDirectoryReader, tenantId: string, principal: string, required: readonly PermissionKey[]): Coverage;
|
|
70
117
|
export {};
|
|
71
118
|
//# sourceMappingURL=permission-eval.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-eval.d.ts","sourceRoot":"","sources":["../src/permission-eval.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"permission-eval.d.ts","sourceRoot":"","sources":["../src/permission-eval.ts"],"names":[],"mappings":"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"}
|