@substrat-run/kernel 0.87.0 → 0.89.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/denial-query.d.ts +84 -0
- package/dist/denial-query.d.ts.map +1 -0
- package/dist/denial-query.js +135 -0
- package/dist/denial-query.js.map +1 -0
- package/dist/entity-version.d.ts +115 -0
- package/dist/entity-version.d.ts.map +1 -0
- package/dist/entity-version.js +70 -0
- package/dist/entity-version.js.map +1 -0
- package/dist/idempotency.d.ts +143 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +197 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/scope-host.d.ts +203 -2
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js +7 -0
- package/dist/scope-host.js.map +1 -1
- package/dist/timeline.d.ts +97 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +108 -0
- package/dist/timeline.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { IDEMPOTENCY_REPLAY_UNAVAILABLE, IDEMPOTENCY_RESULT_LIMIT, IDEMPOTENCY_RETENTION_MS, IDEMPOTENCY_REUSED, isValidIdempotencyKey, subjectRef, substratError, } from '@substrat-run/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* The spine half of request idempotency (#116) — what remembers a key, and what
|
|
4
|
+
* a second request carrying it is answered with.
|
|
5
|
+
*
|
|
6
|
+
* `@substrat-run/contracts` owns the wire (the header names, what makes a key
|
|
7
|
+
* well-formed, what makes two requests the same request); this owns the table
|
|
8
|
+
* and the decisions read off it. Both adapters call these rather than writing
|
|
9
|
+
* the SQL twice, for the reason `entityVersionQuery` gives one file over: two
|
|
10
|
+
* surfaces answering one question must not drift on what the answer means. Here
|
|
11
|
+
* the answer decides whether work happens at all.
|
|
12
|
+
*
|
|
13
|
+
* ## Recorded INSIDE the operation's transaction, which is the whole design
|
|
14
|
+
*
|
|
15
|
+
* The row is written in the same transaction as the work it describes, after the
|
|
16
|
+
* handler and before `COMMIT`. Three properties fall out of that one placement,
|
|
17
|
+
* and none of them needed a mechanism of its own:
|
|
18
|
+
*
|
|
19
|
+
* - **A failed request is retried, not replayed.** The operation threw, the
|
|
20
|
+
* transaction rolled back, and the row went with it. There is nothing to find,
|
|
21
|
+
* so the retry executes — which is correct, because nothing happened the first
|
|
22
|
+
* time. Recording failures would have meant deciding which of them are
|
|
23
|
+
* permanent, and that is a judgement no generic layer can make.
|
|
24
|
+
* - **A replayed response describes work that actually committed.** The row and
|
|
25
|
+
* the rows it is about are the same transaction; there is no window in which
|
|
26
|
+
* one exists without the other.
|
|
27
|
+
* - **The dedupe cannot be defeated by a concurrent retry.** Invokes serialise
|
|
28
|
+
* per scope in both adapters, so the second request takes its turn after the
|
|
29
|
+
* first has committed — no in-flight state, no "still processing" 409.
|
|
30
|
+
*
|
|
31
|
+
* ## What a replay is NOT
|
|
32
|
+
*
|
|
33
|
+
* It is not a fresh authorization. The recorded response is returned without
|
|
34
|
+
* running the handler, and the permission check lives inside the handler — so a
|
|
35
|
+
* caller whose access was revoked in the last 24 hours can still replay their own
|
|
36
|
+
* response. This is bounded by the two things that make it defensible: the row is
|
|
37
|
+
* keyed by the SUBJECT, so a caller can only ever reach responses they themselves
|
|
38
|
+
* received, and the window is a day. It is stated here rather than discovered,
|
|
39
|
+
* because the alternative — re-running the operation to re-check the permission —
|
|
40
|
+
* is the duplicate execution this feature exists to prevent.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* The dedupe table, kernel-owned so that no vertical carries a migration for it.
|
|
44
|
+
*
|
|
45
|
+
* Keyed `(subject, key)` rather than `(key)`, and that is a safety property, not
|
|
46
|
+
* a namespacing convenience: a key is a string a client chose, two clients will
|
|
47
|
+
* choose `1`, and a lookup that found the other one's row would replay a response
|
|
48
|
+
* across a principal boundary. With the subject in the primary key that is not a
|
|
49
|
+
* check that could be forgotten — it is a row that cannot be reached.
|
|
50
|
+
*
|
|
51
|
+
* `operation` is stored as well as hashed into `fingerprint`. The hash is what
|
|
52
|
+
* decides a mismatch; the column is what makes the table readable when someone is
|
|
53
|
+
* working out why a client is getting 409s.
|
|
54
|
+
*/
|
|
55
|
+
export const IDEMPOTENCY_DDL = `
|
|
56
|
+
CREATE TABLE IF NOT EXISTS _substrat_idempotency (
|
|
57
|
+
subject TEXT NOT NULL,
|
|
58
|
+
key TEXT NOT NULL,
|
|
59
|
+
operation TEXT NOT NULL,
|
|
60
|
+
-- SHA-256 over (operation, parsed input). A second request under this key
|
|
61
|
+
-- whose fingerprint differs is a REUSE, refused with 409 — never served the
|
|
62
|
+
-- first request's response, which is a different request's answer.
|
|
63
|
+
fingerprint TEXT NOT NULL,
|
|
64
|
+
-- The operation's return value as JSON. NULL means one of two things, which
|
|
65
|
+
-- \`oversized\` separates: the operation returned nothing, or the result was
|
|
66
|
+
-- too large to record and a replay must be refused rather than re-executed.
|
|
67
|
+
result TEXT,
|
|
68
|
+
oversized INTEGER NOT NULL DEFAULT 0,
|
|
69
|
+
-- #129's tag, replayed with the body so a retry hands the client the same
|
|
70
|
+
-- ETag the original did. Without it a replayed response has no validator and
|
|
71
|
+
-- the client's next conditional write has nothing to send.
|
|
72
|
+
entity_version TEXT,
|
|
73
|
+
recorded_at TEXT NOT NULL,
|
|
74
|
+
PRIMARY KEY (subject, key)
|
|
75
|
+
);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS _substrat_idempotency_recorded
|
|
77
|
+
ON _substrat_idempotency (recorded_at);
|
|
78
|
+
`;
|
|
79
|
+
/** The subject a key is scoped to, in the form the column stores. */
|
|
80
|
+
export function idempotencySubject(subject) {
|
|
81
|
+
return subjectRef(subject);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Refuse a malformed key at the door.
|
|
85
|
+
*
|
|
86
|
+
* `validation_failed` rather than `conflict`: nothing is in conflict, the caller
|
|
87
|
+
* sent a header we cannot store. Refused rather than ignored, for the reason the
|
|
88
|
+
* `If-Match` path gives — a caller who believes their retry is safe and whose
|
|
89
|
+
* key was silently dropped is in exactly the position this feature exists to
|
|
90
|
+
* prevent, arrived at through the feature itself.
|
|
91
|
+
*/
|
|
92
|
+
export function assertIdempotencyKey(key) {
|
|
93
|
+
if (isValidIdempotencyKey(key))
|
|
94
|
+
return;
|
|
95
|
+
throw substratError('validation_failed', 'Idempotency-Key must be 1-255 visible ASCII characters with no spaces');
|
|
96
|
+
}
|
|
97
|
+
/** The lookup a retry is answered from. */
|
|
98
|
+
export function idempotencyLookupQuery(subject, key) {
|
|
99
|
+
return {
|
|
100
|
+
sql: 'SELECT operation, fingerprint, result, oversized, entity_version ' +
|
|
101
|
+
'FROM _substrat_idempotency WHERE subject = ? AND key = ?',
|
|
102
|
+
params: [idempotencySubject(subject), key],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Decide what a second request under this key gets.
|
|
107
|
+
*
|
|
108
|
+
* Two refusals and one replay, both refusals `conflict` (409) with a reason slug
|
|
109
|
+
* this feature owns:
|
|
110
|
+
*
|
|
111
|
+
* - **Reuse.** Same key, different request. The client's assertion that this is
|
|
112
|
+
* the request it sent before is false, and the one thing that must not happen
|
|
113
|
+
* is serving the earlier request's response to it.
|
|
114
|
+
* - **Unavailable.** The original response was too large to record. Refused
|
|
115
|
+
* rather than re-executed, which is the fail-closed direction: an error the
|
|
116
|
+
* caller can act on, instead of the duplicate work the key was sent to avoid.
|
|
117
|
+
*/
|
|
118
|
+
export function replayFor(key, fingerprint, row) {
|
|
119
|
+
if (row.fingerprint !== fingerprint) {
|
|
120
|
+
throw substratError('conflict', `Idempotency-Key '${key}' was already used for a different request ` +
|
|
121
|
+
`(${row.operation}). A key identifies one request; use a fresh one`, { reason: IDEMPOTENCY_REUSED });
|
|
122
|
+
}
|
|
123
|
+
if (row.oversized !== 0) {
|
|
124
|
+
throw substratError('conflict', `the original response for Idempotency-Key '${key}' was too large to record, ` +
|
|
125
|
+
'so this retry cannot be answered from it — the original request did complete, ' +
|
|
126
|
+
'and re-running it would duplicate the work the key exists to prevent', { reason: IDEMPOTENCY_REPLAY_UNAVAILABLE });
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
result: row.result === null ? undefined : JSON.parse(row.result),
|
|
130
|
+
entityVersion: row.entity_version,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The row a completed operation leaves behind.
|
|
135
|
+
*
|
|
136
|
+
* Serialisation happens here rather than at each call site so the size decision
|
|
137
|
+
* has one home: over `IDEMPOTENCY_RESULT_LIMIT` the body is dropped and the key
|
|
138
|
+
* is recorded as oversized, which is what makes a later replay a refusal instead
|
|
139
|
+
* of a silent re-execution.
|
|
140
|
+
*
|
|
141
|
+
* `undefined` and `null` results are both stored as a NULL body with
|
|
142
|
+
* `oversized = 0`; a replay returns `undefined` for either. An operation whose
|
|
143
|
+
* return value a caller distinguishes on that difference has a bigger problem
|
|
144
|
+
* than this table.
|
|
145
|
+
*/
|
|
146
|
+
export function idempotencyRecordStatement(subject, key, operation, fingerprint, result, entityVersion, at) {
|
|
147
|
+
const serialised = result === undefined ? null : JSON.stringify(result) ?? null;
|
|
148
|
+
const oversized = serialised !== null && serialised.length > IDEMPOTENCY_RESULT_LIMIT;
|
|
149
|
+
return {
|
|
150
|
+
sql: 'INSERT INTO _substrat_idempotency ' +
|
|
151
|
+
'(subject, key, operation, fingerprint, result, oversized, entity_version, recorded_at) ' +
|
|
152
|
+
'VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
|
153
|
+
params: [
|
|
154
|
+
idempotencySubject(subject),
|
|
155
|
+
key,
|
|
156
|
+
operation,
|
|
157
|
+
fingerprint,
|
|
158
|
+
oversized ? null : serialised,
|
|
159
|
+
oversized ? 1 : 0,
|
|
160
|
+
entityVersion,
|
|
161
|
+
at,
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Age rows out, in the same transaction as the write that added one.
|
|
167
|
+
*
|
|
168
|
+
* Opportunistic rather than swept, deliberately. A sweeper would be a second
|
|
169
|
+
* schedule, a second failure mode and a second thing to deploy, for a table whose
|
|
170
|
+
* only writer is already here holding a transaction open. Bounded work: the
|
|
171
|
+
* `recorded_at` index makes it a range delete, and it runs only on an invocation
|
|
172
|
+
* that carried a key — so a fleet that never uses the feature never pays for it.
|
|
173
|
+
*
|
|
174
|
+
* The consequence worth stating: a scope that stops receiving keyed requests
|
|
175
|
+
* keeps its last rows past the window. They are inert (nothing reads a row
|
|
176
|
+
* without a key that matches it) and the next keyed request clears them.
|
|
177
|
+
*/
|
|
178
|
+
export function idempotencyPruneStatement(now) {
|
|
179
|
+
const cutoff = new Date(Date.parse(now) - IDEMPOTENCY_RETENTION_MS).toISOString();
|
|
180
|
+
return {
|
|
181
|
+
sql: 'DELETE FROM _substrat_idempotency WHERE recorded_at < ?',
|
|
182
|
+
params: [cutoff],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The refusal an operation that declared `idempotency: false` answers a key with.
|
|
187
|
+
*
|
|
188
|
+
* Not a `conflict` — nothing conflicts — and not silence, which is the failure
|
|
189
|
+
* mode every branch of this feature is written to avoid. The operation opted out
|
|
190
|
+
* because its response must not be recorded; a caller who sent a key and got a
|
|
191
|
+
* 200 would believe a retry is safe when the second one will execute again.
|
|
192
|
+
*/
|
|
193
|
+
export function idempotencyOptedOutMessage(operation) {
|
|
194
|
+
return (`${operation} declares \`idempotency: false\` and cannot honour an Idempotency-Key — ` +
|
|
195
|
+
'its response is not recorded, so a retry would execute it a second time');
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AccessLogFilter, AttachmentUploadInput, AuditLogFilter, BlobStoreProvisionInput, BlobStoreRecord, ConsumerHandler, ExecutorDeadLetter, ExecutorDrainReport, ExecutorHandler, ExecutorRetryPolicy, ConnectorConnection, ScopedConnectorConnection, ConnectorContext, ConnectorHandler, ConnectorOptions, ConnectorRequestInit, ConnectorResponse, Clock, FetchLike, GuardPredicate, HostAdmin, MigrateScopeOutcome, MigrationFrontier, ModuleRegistration, ConsumersOf, EventContract, EventPayloadOf, EventTypeOf, TypedConsumerHandler, TypedConsumers, OpenedAttachment, OperationContext, PageParams, OperationHandler, OpsFailureFilter, OpsFailureInput, ProvisionScopeInput, RoleFilter, ScheduleRegistration, ScheduleRunReport, ScopeAttachments, ScopedSql, ScopeFilter, ScopeHost, ScopeStub, ScopeStubOptions, SqlMigration, SqlValue, TenantBlobStore, TenantRelationalStore, TenantStoreProvisionInput, TenantStoreRecord, } from './scope-host.js';
|
|
1
|
+
export type { AccessLogFilter, AttachmentUploadInput, AuditLogFilter, BlobStoreProvisionInput, BlobStoreRecord, ConsumerHandler, ExecutorDeadLetter, ExecutorDrainReport, ExecutorHandler, ExecutorRetryPolicy, ConnectorConnection, ScopedConnectorConnection, ConnectorContext, ConnectorHandler, ConnectorOptions, ConnectorRequestInit, ConnectorResponse, Clock, FetchLike, GuardPredicate, HostAdmin, MigrateScopeOutcome, MigrationFrontier, ModuleRegistration, ConsumersOf, EventContract, EventPayloadOf, EventTypeOf, TypedConsumerHandler, TypedConsumers, OpenedAttachment, OperationContext, PageParams, OperationHandler, OpsFailureFilter, OpsFailureInput, ProvisionScopeInput, RoleFilter, ScheduleRegistration, ScheduleRunReport, ScopeAttachments, ScopedSql, ScopeFilter, ScopeHost, ScopeStub, ScopeStubOptions, InvokeOptions, SqlMigration, SqlValue, TenantBlobStore, TenantRelationalStore, TenantStoreProvisionInput, TenantStoreRecord, } from './scope-host.js';
|
|
2
2
|
export { attachmentBlobKey, consumersFor, entitlementDenial, backoffAt, parseValidationRecords, resolveRetryPolicy, OPS_FAILURE_RETENTION_DAYS, } from './scope-host.js';
|
|
3
3
|
export { isSecretBoxConfigured, SecretBoxUnconfiguredError, unconfiguredSecretBox, webCryptoSecretBox, } from './secret-box.js';
|
|
4
4
|
export type { SealedSecret, SecretBox } from './secret-box.js';
|
|
@@ -25,6 +25,11 @@ export { readRoutedNode, RouterAssertionError } from './routed-node.js';
|
|
|
25
25
|
export type { RoutedNode, HeaderReader } from './routed-node.js';
|
|
26
26
|
export { assertPlatformCall, PlatformCallError, PLATFORM_SECRET_HEADER, PLATFORM_REQUEST_HEADER, CONNECTOR_ATTACHMENT_RECORD_HEADER, } from './platform-call.js';
|
|
27
27
|
export { PLATFORM_REQUEST_COLUMNS, platformRequestHistoryQuery, } from './platform-request-query.js';
|
|
28
|
+
export { DENIAL_COLUMNS, DENIAL_WINDOW_QUERY, denialListQuery, denialSummaryQuery, denialTotalsQuery, mapDenialRow, mapDenialBucketRow, storedActor, type DenialRow, type DenialBucketRow, type DenialWindowRow, } from './denial-query.js';
|
|
29
|
+
export { entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, type EntityVersion, type EntityVersionRow, } from './entity-version.js';
|
|
30
|
+
export { readTimeline, readHistory } from './timeline.js';
|
|
31
|
+
export type { TimelineReader } from './timeline.js';
|
|
32
|
+
export { IDEMPOTENCY_DDL, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencySubject, idempotencyOptedOutMessage, replayFor, type IdempotencyRow, type IdempotentReplay, } from './idempotency.js';
|
|
28
33
|
export { isTerminalDispatchFailure, isTerminalProviderError, providerErrorStatus, RETRYABLE_CLIENT_STATUSES, } from './provider-error.js';
|
|
29
34
|
export { runPlatformSweep, startPlatformSweeper } from './platform-sweep.js';
|
|
30
35
|
export type { AccessLogSink, AccessLogSweepReport, ConnectorSweeper, MigrationSweepReport, PlatformSweepOptions, PlatformSweepReport, PlatformSweeperHandle, ScheduleSweepReport, StartPlatformSweeperOptions, } from './platform-sweep.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,10 @@ export { assertReadOnlyQuery } from './read-only-sql.js';
|
|
|
14
14
|
export { readRoutedNode, RouterAssertionError } from './routed-node.js';
|
|
15
15
|
export { assertPlatformCall, PlatformCallError, PLATFORM_SECRET_HEADER, PLATFORM_REQUEST_HEADER, CONNECTOR_ATTACHMENT_RECORD_HEADER, } from './platform-call.js';
|
|
16
16
|
export { PLATFORM_REQUEST_COLUMNS, platformRequestHistoryQuery, } from './platform-request-query.js';
|
|
17
|
+
export { DENIAL_COLUMNS, DENIAL_WINDOW_QUERY, denialListQuery, denialSummaryQuery, denialTotalsQuery, mapDenialRow, mapDenialBucketRow, storedActor, } from './denial-query.js';
|
|
18
|
+
export { entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, } from './entity-version.js';
|
|
19
|
+
export { readTimeline, readHistory } from './timeline.js';
|
|
20
|
+
export { IDEMPOTENCY_DDL, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencySubject, idempotencyOptedOutMessage, replayFor, } from './idempotency.js';
|
|
17
21
|
export { isTerminalDispatchFailure, isTerminalProviderError, providerErrorStatus, RETRYABLE_CLIENT_STATUSES, } from './provider-error.js';
|
|
18
22
|
export { runPlatformSweep, startPlatformSweeper } from './platform-sweep.js';
|
|
19
23
|
export { MIGRATION_FLAG_THRESHOLD, migrationFleet, migrationProgress, migrationSummary, scopeMigrationState, } from './migration-progress.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuDA,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,sBAAsB,EACtB,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,oCAAoC,EACpC,sBAAsB,EACtB,mBAAmB,EACnB,UAAU,EACV,MAAM,EACN,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EACL,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,uBAAuB,EACvB,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAQ3B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,QAAQ,EACR,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,SAAS,EACT,WAAW,GACZ,MAAM,iBAAiB,CAAC;AAOzB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExE,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,kCAAkC,GACnC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,WAAW,GAIZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,mBAAmB,GAGpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE1D,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,EAClB,0BAA0B,EAC1B,SAAS,GAGV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAY7E,OAAO,EACL,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/scope-host.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { AdminAction, ListPage, Connection, ConnectionFilter, ConnectionId, ConnectionGrant, ConnectionGrantRecord, ConnectionSecret, CreateConnectionInput, OpenConnection, ProjectedConnectionGrant, ProjectedConnectionKey, AccessLogEntry, BindHostnameInput, AdminLogEntry, OpsFailureEntry, CapabilityGrant, CreateTenantInput, Decision, Instant, DomainEvent, DomainEventInput, PlatformRequestInput, PlatformRequestId, PlatformRequest, PlatformRequestFilter, PlatformRequestStatus, PlatformRequestFailure, EntitlementGrant, EntitlementGrantInput, EntitlementView, MeterReading, EntityRef, IdentityLink, IdentityPool, Jurisdiction, ModuleId, ModuleManifest, ScheduleSpec, SystemGrant, CreateOrgInput, Node, Org, OrgId, OrgMembership, PermissionKey, PlatformActorId, ChannelName, ChannelHistoryEntry, DnsRecord, HostnameBinding, HostnameStatus, PromotionAcknowledgement, PublishVersionInput, RegisterVerticalInput, VerticalServingState, RouteTarget, DirectoryDump, PrincipalId, ResolvedIdentity, RoleAssignment, RoleDefinition, QueryScopeInput, ReadScopeTableInput, Scope, ScopeDump, SubjectShredReceipt, ScopeQueryResult, ScopeId, ScopeStatus, ScopeTable, ScopeTablePage, StorageShape, Tenant, TenantId, TenantRole, TenantStoreHandle, AttachmentRecord, BlobStoreHandle, Visibility, Vertical, VerticalChannel, VerticalVersion, TenantStatus, Page, CountedPage } from '@substrat-run/contracts';
|
|
1
|
+
import type { AdminAction, ListPage, Connection, ConnectionFilter, ConnectionId, ConnectionGrant, ConnectionGrantRecord, ConnectionSecret, CreateConnectionInput, OpenConnection, ProjectedConnectionGrant, ProjectedConnectionKey, AccessLogEntry, BindHostnameInput, AdminLogEntry, OpsFailureEntry, CapabilityGrant, CreateTenantInput, Decision, Instant, DomainEvent, DomainEventInput, PlatformRequestInput, PlatformRequestId, PlatformRequest, PlatformRequestFilter, PlatformRequestStatus, PlatformRequestFailure, EntitlementGrant, EntitlementGrantInput, EntitlementView, MeterReading, EntityRef, IdentityLink, IdentityPool, Jurisdiction, ModuleId, ModuleManifest, ScheduleSpec, SystemGrant, CreateOrgInput, Node, Org, OrgId, OrgMembership, PermissionKey, PlatformActorId, ChannelName, ChannelHistoryEntry, DnsRecord, HostnameBinding, HostnameStatus, PromotionAcknowledgement, PublishVersionInput, RegisterVerticalInput, VerticalServingState, RouteTarget, DirectoryDump, PrincipalId, ResolvedIdentity, RoleAssignment, RoleDefinition, QueryScopeInput, ReadScopeTableInput, Scope, ScopeDump, SubjectShredReceipt, ScopeQueryResult, ScopeId, ScopeStatus, ScopeTable, DenialFilter, DenialSummary, PermissionDenial, ScopeTablePage, StorageShape, Tenant, TenantId, TenantRole, TenantStoreHandle, AttachmentRecord, BlobStoreHandle, Visibility, Vertical, VerticalChannel, VerticalVersion, TenantStatus, Page, CountedPage } from '@substrat-run/contracts';
|
|
2
2
|
import type { SealedSecret } from './secret-box.js';
|
|
3
3
|
import type { SearchHit, SearchOptions } from './search-index.js';
|
|
4
|
+
import type { EntityVersion } from './entity-version.js';
|
|
4
5
|
/**
|
|
5
6
|
* What a caller asks a paged read for (#811).
|
|
6
7
|
*
|
|
@@ -107,6 +108,39 @@ export interface OperationContext {
|
|
|
107
108
|
* code writing `_substrat_*`), so an intent's status is only ever the platform's answer.
|
|
108
109
|
*/
|
|
109
110
|
platformRequests(filter?: PlatformRequestFilter): PlatformRequest[];
|
|
111
|
+
/**
|
|
112
|
+
* This entity's version (#901) — the ULID of the last event about it, or
|
|
113
|
+
* `null` if nothing has ever been emitted about it.
|
|
114
|
+
*
|
|
115
|
+
* There is no version column anywhere, and there is deliberately not going to
|
|
116
|
+
* be one: `_substrat_outbox` has recorded `entity_type` and `entity_id`
|
|
117
|
+
* against a monotonic ULID since it was written, so every mutation that
|
|
118
|
+
* followed the fat-event rule already versioned the thing it touched. See
|
|
119
|
+
* `entity-version.ts` for why the alternative — a `_version` column bumped by
|
|
120
|
+
* an emitted trigger — was rejected despite working.
|
|
121
|
+
*
|
|
122
|
+
* ```ts
|
|
123
|
+
* const before = ctx.versionOf({ entityType: 'customer', entityId: id });
|
|
124
|
+
* // …mutate, emit…
|
|
125
|
+
* ctx.versionOf({ entityType: 'customer', entityId: id }) !== before // true
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* **Conservative, by construction.** ANY event about the entity moves this,
|
|
129
|
+
* including one that changed nothing the caller read. A precondition built on
|
|
130
|
+
* it can refuse a write that would have been safe; it cannot admit one that
|
|
131
|
+
* would not. That is the correct direction to fail, and it is a real
|
|
132
|
+
* difference from a per-row counter.
|
|
133
|
+
*
|
|
134
|
+
* Rule 3 permits a projection read of `_substrat_*`, so a vertical *could*
|
|
135
|
+
* hand-roll this `SELECT`. It should not: the spine's schema is private and a
|
|
136
|
+
* vertical pinned to it is pinned to a table the kernel may re-shape. Same
|
|
137
|
+
* reasoning as `platformRequests`.
|
|
138
|
+
*
|
|
139
|
+
* **Checks no permission** — nothing on `ctx` does. A version is not a read of
|
|
140
|
+
* the entity, but it is evidence the entity exists, so an operation that hands
|
|
141
|
+
* one to an untrusted caller does its own `assertAllowed` first.
|
|
142
|
+
*/
|
|
143
|
+
versionOf(entity: EntityRef): EntityVersion | null;
|
|
110
144
|
/** Node-level check; pass `entity` for per-entity checks (portal access, §4.2 rule 3). */
|
|
111
145
|
check(permission: PermissionKey, entity?: EntityRef): Promise<Decision>;
|
|
112
146
|
/**
|
|
@@ -284,11 +318,85 @@ export interface OperationContext {
|
|
|
284
318
|
atomic<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
285
319
|
}
|
|
286
320
|
export type OperationHandler<I = unknown, O = unknown> = (ctx: OperationContext, input: I) => O | Promise<O>;
|
|
321
|
+
/**
|
|
322
|
+
* The per-invocation transport channel: what the caller requires to be true
|
|
323
|
+
* before the operation runs, and the transport facts it needs back (#129, #116).
|
|
324
|
+
*
|
|
325
|
+
* A separate parameter rather than fields on the input, because these are facts
|
|
326
|
+
* about the REQUEST and not about the domain. A handler's declared input is what
|
|
327
|
+
* the operation MEANS, and threading a retry token or an entity tag through it
|
|
328
|
+
* would make every in-process caller state something it does not have.
|
|
329
|
+
* `mountOperations` reads them off headers; a test, a seed or a schedule omits
|
|
330
|
+
* them entirely.
|
|
331
|
+
*
|
|
332
|
+
* Per INVOCATION rather than on `ScopeStubOptions`, where `onPlatformRequests`
|
|
333
|
+
* lives, and the difference is not stylistic. A stub is minted by the vertical's
|
|
334
|
+
* own `resolveStub`, so anything hung off it requires that vertical to cooperate;
|
|
335
|
+
* `If-Match` and the `ETag` are wholly the mount's business and must work with no
|
|
336
|
+
* change to a vertical at all. They are also genuinely per-call — one stub serves
|
|
337
|
+
* one request, but nothing in the contract says so.
|
|
338
|
+
*
|
|
339
|
+
* Deliberately one bag rather than a parameter per concern. `If-Match` and
|
|
340
|
+
* `Idempotency-Key` are ONE precondition pass at one point in the invoke — before
|
|
341
|
+
* the guards, inside the transaction — which is what #116's note asked of
|
|
342
|
+
* whichever landed first. #129 built the bag; #116 declared into it and added no
|
|
343
|
+
* second interception point.
|
|
344
|
+
*/
|
|
345
|
+
export interface InvokeOptions {
|
|
346
|
+
/**
|
|
347
|
+
* The version the caller believes it is writing over, verbatim from `If-Match`
|
|
348
|
+
* (quoted, and possibly a list — `ifMatchAdmits` owns the parsing).
|
|
349
|
+
*
|
|
350
|
+
* Honoured only by an operation that DECLARES `concurrency`. Sending it to one
|
|
351
|
+
* that does not is an error rather than a no-op: a caller who believes it is
|
|
352
|
+
* protected and is not is the failure this whole mechanism exists to prevent,
|
|
353
|
+
* and silence is exactly how that belief survives.
|
|
354
|
+
*/
|
|
355
|
+
readonly ifMatch?: string;
|
|
356
|
+
/**
|
|
357
|
+
* Called after a guarded operation COMMITS, with the entity's version as it
|
|
358
|
+
* stands at commit — the `ETag` the transport hands back.
|
|
359
|
+
*
|
|
360
|
+
* Read after the handler and inside the same transaction, so the tag describes
|
|
361
|
+
* the row as the caller's own write left it rather than as the caller found it.
|
|
362
|
+
* A client that echoed back what it sent would loop on its own stale value.
|
|
363
|
+
*
|
|
364
|
+
* Never called for a rolled-back operation, and never for an operation that
|
|
365
|
+
* declares no `concurrency`: a version that did not survive its transaction is
|
|
366
|
+
* not a tag anyone may hold, and an operation that opted out must not pay for a
|
|
367
|
+
* spine read on every invocation.
|
|
368
|
+
*/
|
|
369
|
+
readonly onEntityVersion?: (version: string | null) => void;
|
|
370
|
+
/**
|
|
371
|
+
* The client's retry token, verbatim from `Idempotency-Key` (#116).
|
|
372
|
+
*
|
|
373
|
+
* Honoured by every operation on an unsafe method — there is no declaration to
|
|
374
|
+
* make, because a retried write creating a second entity is a hazard on all of
|
|
375
|
+
* them. The exception is an operation that declared `idempotency: false`, whose
|
|
376
|
+
* response must not be recorded; sending a key to one is an error rather than a
|
|
377
|
+
* no-op, for the same reason an unhonoured `If-Match` is.
|
|
378
|
+
*
|
|
379
|
+
* A first request under a key runs, and its return value is recorded inside the
|
|
380
|
+
* operation's own transaction. A second request under the same key returns that
|
|
381
|
+
* recording without running the handler. A second request under the same key
|
|
382
|
+
* with a DIFFERENT input is refused — a key names one request, and serving the
|
|
383
|
+
* first one's response to a second one would be a lie a client acts on.
|
|
384
|
+
*/
|
|
385
|
+
readonly idempotencyKey?: string;
|
|
386
|
+
/**
|
|
387
|
+
* Called when this invocation was answered from a recording rather than run.
|
|
388
|
+
*
|
|
389
|
+
* The transport sets `Idempotency-Replayed` from it. Advisory: a caller that
|
|
390
|
+
* ignores this is not wrong about anything, it simply cannot tell a retry from
|
|
391
|
+
* a first request — which is enough of a debugging cost to be worth a callback.
|
|
392
|
+
*/
|
|
393
|
+
readonly onIdempotentReplay?: () => void;
|
|
394
|
+
}
|
|
287
395
|
/** The capability stub — the ONLY way code outside the scope reaches it. */
|
|
288
396
|
export interface ScopeStub {
|
|
289
397
|
readonly tenantId: TenantId;
|
|
290
398
|
readonly scopeId: ScopeId;
|
|
291
|
-
invoke<O = unknown, I = unknown>(operation: string, input?: I): Promise<O>;
|
|
399
|
+
invoke<O = unknown, I = unknown>(operation: string, input?: I, options?: InvokeOptions): Promise<O>;
|
|
292
400
|
}
|
|
293
401
|
/**
|
|
294
402
|
* Observers a caller may attach when minting a stub (#458). Harness-level, not
|
|
@@ -801,6 +909,80 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
|
|
|
801
909
|
manifest: ModuleManifest;
|
|
802
910
|
migrations?: SqlMigration[];
|
|
803
911
|
operations?: Record<string, OperationHandler<never, unknown>>;
|
|
912
|
+
/**
|
|
913
|
+
* name → the schema the host parses an invocation's input against, BEFORE the
|
|
914
|
+
* guards and the handler see it (#893).
|
|
915
|
+
*
|
|
916
|
+
* Derived from the declared operation surface — `operationInputsOf(ops)` — and
|
|
917
|
+
* never written a second time. A module that declares its operations gets the
|
|
918
|
+
* parse by handing the same object over:
|
|
919
|
+
*
|
|
920
|
+
* ```ts
|
|
921
|
+
* operations: { 'rally/book': bookOp, … },
|
|
922
|
+
* operationInputs: operationInputsOf(rallyOperations),
|
|
923
|
+
* ```
|
|
924
|
+
*
|
|
925
|
+
* **This is where "parse, don't trust" is kept, rather than in 85 handlers.**
|
|
926
|
+
* `OperationShape.input` calls itself *"the SAME Zod object the handler
|
|
927
|
+
* parses"* and across the fleet it mostly was not — rally declared 32 inputs
|
|
928
|
+
* and parsed 2. One place that cannot be forgotten beats a rule every new
|
|
929
|
+
* operation has to remember, which is the same argument `mountOperations`
|
|
930
|
+
* already makes for the page trio.
|
|
931
|
+
*
|
|
932
|
+
* A name here that no operation binds is an error: it is a schema enforcing
|
|
933
|
+
* nothing, and it reads as coverage. A bound operation with no entry is
|
|
934
|
+
* allowed and means what it always meant — nothing was declared to parse.
|
|
935
|
+
*
|
|
936
|
+
* Typed structurally rather than as `z.ZodType` so the kernel keeps its single
|
|
937
|
+
* dependency and no zod version is pinned by the scope-host contract. The
|
|
938
|
+
* shape is the whole surface the host uses: throw to refuse, return the value
|
|
939
|
+
* to accept.
|
|
940
|
+
*/
|
|
941
|
+
operationInputs?: Record<string, {
|
|
942
|
+
parse(value: unknown): unknown;
|
|
943
|
+
}>;
|
|
944
|
+
/**
|
|
945
|
+
* name → the entity whose version this operation's `If-Match` is compared
|
|
946
|
+
* against, and the input field carrying its id (#129).
|
|
947
|
+
*
|
|
948
|
+
* Derived from the declared operation surface — `operationConcurrencyOf(ops)` —
|
|
949
|
+
* and never written a second time, exactly as `operationInputs` is:
|
|
950
|
+
*
|
|
951
|
+
* ```ts
|
|
952
|
+
* operations: { 'callout/update-customer': updateCustomerOp, … },
|
|
953
|
+
* operationInputs: operationInputsOf(calloutOperations),
|
|
954
|
+
* operationConcurrency: operationConcurrencyOf(calloutOperations),
|
|
955
|
+
* ```
|
|
956
|
+
*
|
|
957
|
+
* **The host compares, not the handler.** A precondition a handler evaluates is
|
|
958
|
+
* a precondition a handler can forget, and the one that is forgotten is
|
|
959
|
+
* indistinguishable from one that passed. Here the comparison happens between
|
|
960
|
+
* `BEGIN` and the guards for every caller and every transport, or the operation
|
|
961
|
+
* does not claim to have it.
|
|
962
|
+
*
|
|
963
|
+
* A name here that no operation binds is an error, for the same reason it is on
|
|
964
|
+
* `operationInputs`: it reads as coverage while enforcing nothing.
|
|
965
|
+
*/
|
|
966
|
+
operationConcurrency?: Record<string, {
|
|
967
|
+
entity: string;
|
|
968
|
+
idFrom: string;
|
|
969
|
+
}>;
|
|
970
|
+
/**
|
|
971
|
+
* The operations that declared `idempotency: false` (#116) — the ones whose
|
|
972
|
+
* response must not be recorded, and which therefore refuse an
|
|
973
|
+
* `Idempotency-Key` instead of honouring it.
|
|
974
|
+
*
|
|
975
|
+
* Derived like the two above, and never written a second time:
|
|
976
|
+
*
|
|
977
|
+
* ```ts
|
|
978
|
+
* operationIdempotencyOptOuts: operationIdempotencyOptOutsOf(calloutOperations),
|
|
979
|
+
* ```
|
|
980
|
+
*
|
|
981
|
+
* A list of refusals rather than a list of participants, because that is what
|
|
982
|
+
* the declaration is. Absent means every operation honours a key, which is the
|
|
983
|
+
* default and the reason there is nothing to remember.
|
|
984
|
+
*/
|
|
985
|
+
operationIdempotencyOptOuts?: readonly string[];
|
|
804
986
|
/**
|
|
805
987
|
* eventType → handler; the types must appear in manifest.events.consumes.
|
|
806
988
|
*
|
|
@@ -1322,6 +1504,18 @@ export interface HostAdmin {
|
|
|
1322
1504
|
* the spine is kept because a fork must carry the event/migration state to be faithful.
|
|
1323
1505
|
*/
|
|
1324
1506
|
exportScope(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId): Promise<ScopeDump>;
|
|
1507
|
+
/**
|
|
1508
|
+
* A bounded page of raw denial rows, newest first. Narrow with `actor` (who was
|
|
1509
|
+
* refused), `permission` (which key), `operation`, and a `since`/`until` window.
|
|
1510
|
+
*/
|
|
1511
|
+
listDenials(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, filter?: DenialFilter): Promise<PermissionDenial[]>;
|
|
1512
|
+
/**
|
|
1513
|
+
* The same log bucketed per (actor, permission) — K-35's "first occurrence + count
|
|
1514
|
+
* per actor/key/window" — busiest first, with the filtered totals and the unfiltered
|
|
1515
|
+
* window facts beside them. This is the view an operator opens first: "who has been
|
|
1516
|
+
* probing for access they don't hold" is a question about counts, not about rows.
|
|
1517
|
+
*/
|
|
1518
|
+
summarizeDenials(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, filter?: DenialFilter): Promise<DenialSummary>;
|
|
1325
1519
|
/**
|
|
1326
1520
|
* A COMPLETE dump of the directory itself: tenants, scopes, hostnames, verticals,
|
|
1327
1521
|
* entitlements, identities, and the audit spine. The platform-level analogue of
|
|
@@ -1909,6 +2103,13 @@ export declare function attachmentBlobKey(scopeId: string, attachmentId: string)
|
|
|
1909
2103
|
* `held` is every key projected for the tenant, expired ones included and marked: a lapsed
|
|
1910
2104
|
* grant denies exactly like an absent one, and "you have it, it ran out" is a different fix
|
|
1911
2105
|
* from "you never had it".
|
|
2106
|
+
*
|
|
2107
|
+
* **Its code is `not_found`** at all three throw sites (#113). Not an oversight and not
|
|
2108
|
+
* `forbidden`: the taxonomy's `not_found` row already covers "exists, and must read as
|
|
2109
|
+
* absent" for K-3's cross-tenant case, and every vertical had independently arrived at a
|
|
2110
|
+
* 404 here for the same reason — *"a 403 would confirm the feature exists"*
|
|
2111
|
+
* ([`todo/routes.ts`](../../../demos/todo/src/routes.ts)). Naming the code once is what
|
|
2112
|
+
* retires those hand-written patterns.
|
|
1912
2113
|
*/
|
|
1913
2114
|
export declare function entitlementDenial(operation: string, requiredKey: string, held: readonly {
|
|
1914
2115
|
key: string;
|