@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.
Files changed (88) hide show
  1. package/dist/capability.d.ts +2 -0
  2. package/dist/capability.d.ts.map +1 -1
  3. package/dist/capability.js +2 -0
  4. package/dist/capability.js.map +1 -1
  5. package/dist/delivery-refusal.d.ts +21 -0
  6. package/dist/delivery-refusal.d.ts.map +1 -0
  7. package/dist/delivery-refusal.js +21 -0
  8. package/dist/delivery-refusal.js.map +1 -0
  9. package/dist/index.d.ts +23 -17
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +18 -12
  12. package/dist/index.js.map +1 -1
  13. package/dist/invocation-log.d.ts +25 -1
  14. package/dist/invocation-log.d.ts.map +1 -1
  15. package/dist/invocation-log.js +45 -6
  16. package/dist/invocation-log.js.map +1 -1
  17. package/dist/job-run.d.ts +370 -47
  18. package/dist/job-run.d.ts.map +1 -1
  19. package/dist/job-run.js +472 -81
  20. package/dist/job-run.js.map +1 -1
  21. package/dist/lifecycle-flow.d.ts.map +1 -1
  22. package/dist/lifecycle-flow.js +2 -1
  23. package/dist/lifecycle-flow.js.map +1 -1
  24. package/dist/membership-executor.d.ts +97 -0
  25. package/dist/membership-executor.d.ts.map +1 -0
  26. package/dist/membership-executor.js +246 -0
  27. package/dist/membership-executor.js.map +1 -0
  28. package/dist/membership-fence.d.ts +57 -0
  29. package/dist/membership-fence.d.ts.map +1 -0
  30. package/dist/membership-fence.js +90 -0
  31. package/dist/membership-fence.js.map +1 -0
  32. package/dist/operation-series.d.ts +58 -0
  33. package/dist/operation-series.d.ts.map +1 -0
  34. package/dist/operation-series.js +63 -0
  35. package/dist/operation-series.js.map +1 -0
  36. package/dist/peer.d.ts +13 -8
  37. package/dist/peer.d.ts.map +1 -1
  38. package/dist/peer.js +13 -14
  39. package/dist/peer.js.map +1 -1
  40. package/dist/permission-eval.d.ts +48 -1
  41. package/dist/permission-eval.d.ts.map +1 -1
  42. package/dist/permission-eval.js +137 -18
  43. package/dist/permission-eval.js.map +1 -1
  44. package/dist/read-only-sql.d.ts +2 -26
  45. package/dist/read-only-sql.d.ts.map +1 -1
  46. package/dist/read-only-sql.js +13 -9
  47. package/dist/read-only-sql.js.map +1 -1
  48. package/dist/refusal-query.d.ts +4 -1
  49. package/dist/refusal-query.d.ts.map +1 -1
  50. package/dist/refusal-query.js +6 -4
  51. package/dist/refusal-query.js.map +1 -1
  52. package/dist/refusals.d.ts +80 -8
  53. package/dist/refusals.d.ts.map +1 -1
  54. package/dist/refusals.js +141 -22
  55. package/dist/refusals.js.map +1 -1
  56. package/dist/scope-copy.d.ts +18 -0
  57. package/dist/scope-copy.d.ts.map +1 -1
  58. package/dist/scope-copy.js +18 -0
  59. package/dist/scope-copy.js.map +1 -1
  60. package/dist/scope-host.d.ts +266 -15
  61. package/dist/scope-host.d.ts.map +1 -1
  62. package/dist/scope-host.js +41 -0
  63. package/dist/scope-host.js.map +1 -1
  64. package/dist/spine-restore.d.ts +7 -2
  65. package/dist/spine-restore.d.ts.map +1 -1
  66. package/dist/spine-restore.js +13 -5
  67. package/dist/spine-restore.js.map +1 -1
  68. package/dist/system-switch-record.d.ts +290 -74
  69. package/dist/system-switch-record.d.ts.map +1 -1
  70. package/dist/system-switch-record.js +353 -101
  71. package/dist/system-switch-record.js.map +1 -1
  72. package/dist/system-switch.d.ts +146 -20
  73. package/dist/system-switch.d.ts.map +1 -1
  74. package/dist/system-switch.js +135 -27
  75. package/dist/system-switch.js.map +1 -1
  76. package/dist/timeline.d.ts +23 -0
  77. package/dist/timeline.d.ts.map +1 -1
  78. package/dist/timeline.js +30 -0
  79. package/dist/timeline.js.map +1 -1
  80. package/dist/ulid.d.ts +5 -0
  81. package/dist/ulid.d.ts.map +1 -1
  82. package/dist/ulid.js +9 -0
  83. package/dist/ulid.js.map +1 -1
  84. package/dist/vertical-events.d.ts +7 -1
  85. package/dist/vertical-events.d.ts.map +1 -1
  86. package/dist/vertical-events.js +14 -0
  87. package/dist/vertical-events.js.map +1 -1
  88. 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
- /** The tuple-subject prefix of a peer vertical — `vertical:acme/board-room`. */
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 scope holds neither a grant nor a marker for that peer — a typo'd slug, or a peer this
84
- * scope was never provisioned with, must not write a marker that silently blocks it the day a
85
- * version declaring it lands. Run inside one transaction.
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;
@@ -1 +1 @@
1
- {"version":3,"file":"peer.d.ts","sourceRoot":"","sources":["../src/peer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;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,EAKL,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAE5B,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAE/C,6FAA6F;AAC7F,eAAO,MAAM,cAAc,aAAc,MAAM,KAAG,MAA6C,CAAC;AAEhG,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;;;;;GAKG;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,CAAA;CAAE,GACzE,aAAa,CAOf;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"}
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, switchSubjectGrants, } from './system-switch.js';
39
- /** The tuple-subject prefix of a peer vertical — `vertical:acme/board-room`. */
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 scope holds neither a grant nor a marker for that peer — a typo'd slug, or a peer this
107
- * scope was never provisioned with, must not write a marker that silently blocks it the day a
108
- * version declaring it lands. Run inside one transaction.
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
- return switchSubjectGrants(db, {
112
- subject: peerSubjectRef(input.vertical),
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":"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"}
1
+ {"version":3,"file":"peer.js","sourceRoot":"","sources":["../src/peer.ts"],"names":[],"mappings":"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"}
@@ -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,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;;;;OAIG;IACH,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;AAeD;;;GAGG;AACH,eAAO,MAAM,YAAY,WAAY,MAAM,KAAG,MAG7C,CAAC;AAyDF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,qBAAqB,GAAG,iBAAiB,CAsQrF"}
1
+ {"version":3,"file":"permission-eval.d.ts","sourceRoot":"","sources":["../src/permission-eval.ts"],"names":[],"mappings":"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"}