@substrat-run/contracts 0.88.0 → 0.90.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/control-plane.d.ts +6 -0
- package/dist/control-plane.d.ts.map +1 -1
- package/dist/control-plane.js +7 -0
- package/dist/control-plane.js.map +1 -1
- package/dist/denial.d.ts +4 -0
- package/dist/denial.d.ts.map +1 -1
- package/dist/denial.js +8 -0
- package/dist/denial.js.map +1 -1
- package/dist/errors.d.ts +34 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +81 -1
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +86 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +72 -0
- package/dist/events.js.map +1 -1
- package/dist/idempotency.d.ts +139 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +161 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/impersonation.d.ts +134 -0
- package/dist/impersonation.d.ts.map +1 -0
- package/dist/impersonation.js +125 -0
- package/dist/impersonation.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/openapi.d.ts +10 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +20 -0
- package/dist/openapi.js.map +1 -1
- package/dist/operations.d.ts +58 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +15 -0
- package/dist/operations.js.map +1 -1
- package/dist/platform-request.d.ts +8 -0
- package/dist/platform-request.d.ts.map +1 -1
- package/dist/platform-request.js +8 -0
- package/dist/platform-request.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request idempotency on the operation surface (#116).
|
|
3
|
+
*
|
|
4
|
+
* A client whose request times out does not know whether the work happened. It
|
|
5
|
+
* retries, and a second work order exists. This is the vocabulary that lets the
|
|
6
|
+
* retry return the FIRST response instead of doing the work twice.
|
|
7
|
+
*
|
|
8
|
+
* ## This is not the event spine's idempotency
|
|
9
|
+
*
|
|
10
|
+
* Consumers have been required-idempotent since the beginning and the contract
|
|
11
|
+
* suite checks it: a consumer may see an event more than once and must settle to
|
|
12
|
+
* the same state. That is about the spine re-delivering. This is about a CLIENT
|
|
13
|
+
* re-sending — a different boundary, a different actor, and no relationship
|
|
14
|
+
* between the two beyond the word.
|
|
15
|
+
*
|
|
16
|
+
* ## Why the wire half lives here and the rest does not
|
|
17
|
+
*
|
|
18
|
+
* The same split `concurrency.ts` makes one file over: contracts sits below the
|
|
19
|
+
* spine, so it can say what the header is called, what makes a key well-formed
|
|
20
|
+
* and what makes two requests the same request. It must not know which table
|
|
21
|
+
* remembers the answer. `@substrat-run/kernel`'s `idempotency.ts` owns that, and
|
|
22
|
+
* the adapters own where it runs.
|
|
23
|
+
*
|
|
24
|
+
* ## The property that makes this cheap
|
|
25
|
+
*
|
|
26
|
+
* Invokes are serialised per scope in both adapters (`rt.actor.enqueue`, the
|
|
27
|
+
* ScopeDO's queue), so a duplicate cannot overlap the original — by the time the
|
|
28
|
+
* retry takes its turn, the first request has committed or rolled back. Every
|
|
29
|
+
* other implementation of this feature needs an in-flight state and a 409 for
|
|
30
|
+
* "still running"; this one does not, and the reason is a property of the host
|
|
31
|
+
* rather than an accident worth relying on quietly.
|
|
32
|
+
*/
|
|
33
|
+
/** The request header carrying the client's retry token. */
|
|
34
|
+
export declare const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
|
|
35
|
+
/**
|
|
36
|
+
* Set on a response the server did not compute — it replayed a recorded one.
|
|
37
|
+
*
|
|
38
|
+
* Advisory, and worth having anyway: without it a retry is indistinguishable
|
|
39
|
+
* from a first request that happened to succeed, which makes "did my key work?"
|
|
40
|
+
* unanswerable from the client side and turns every integration test of a retry
|
|
41
|
+
* path into a database query.
|
|
42
|
+
*/
|
|
43
|
+
export declare const IDEMPOTENCY_REPLAYED_HEADER = "Idempotency-Replayed";
|
|
44
|
+
/**
|
|
45
|
+
* The header a cross-origin browser client cannot read unless the server says it
|
|
46
|
+
* may — the same trap `PAGE_EXPOSED_HEADERS` and `CONCURRENCY_EXPOSED_HEADERS`
|
|
47
|
+
* document, and the mildest of the three: an unexposed `Idempotency-Replayed`
|
|
48
|
+
* costs a client an observation, never a guarantee. The dedupe still happened.
|
|
49
|
+
*/
|
|
50
|
+
export declare const IDEMPOTENCY_EXPOSED_HEADERS: readonly ["Idempotency-Replayed"];
|
|
51
|
+
/**
|
|
52
|
+
* How long a key is remembered: 24 hours.
|
|
53
|
+
*
|
|
54
|
+
* Long enough to cover the retries anything sane performs — an agent's backoff,
|
|
55
|
+
* a queue's redelivery, a person reloading a page that failed — and short enough
|
|
56
|
+
* that the recorded responses are a cache rather than an archive.
|
|
57
|
+
*
|
|
58
|
+
* The window is not only a storage bound, and the other reason is the one worth
|
|
59
|
+
* writing down: a recorded response is a SECOND COPY of whatever the operation
|
|
60
|
+
* returned, sitting in the scope database outside the erasure path that reaches
|
|
61
|
+
* the outbox (a shred nulls `payload` and keeps the row; it does not know about
|
|
62
|
+
* this table). A copy that expires in a day is defensible. One that expires in a
|
|
63
|
+
* quarter is a disclosure nobody declared, and one that never expires is a second
|
|
64
|
+
* database of personal data with no owner.
|
|
65
|
+
*/
|
|
66
|
+
export declare const IDEMPOTENCY_RETENTION_MS: number;
|
|
67
|
+
/**
|
|
68
|
+
* The largest result that is recorded for replay: 128 KiB of JSON.
|
|
69
|
+
*
|
|
70
|
+
* Above it the key is still recorded — with no body — and a replay is REFUSED
|
|
71
|
+
* rather than re-executed. That is the fail-closed direction: refusing a retry
|
|
72
|
+
* costs a caller an error it can act on, while re-running the operation is the
|
|
73
|
+
* duplicate this feature exists to prevent, arrived at through the feature
|
|
74
|
+
* itself. Writes return entity-shaped results and do not approach this; a list
|
|
75
|
+
* read might, and a list read never carries a key (the mount forwards the header
|
|
76
|
+
* on unsafe methods only).
|
|
77
|
+
*/
|
|
78
|
+
export declare const IDEMPOTENCY_RESULT_LIMIT: number;
|
|
79
|
+
/** Longest key accepted, matching the IETF draft's guidance for the header. */
|
|
80
|
+
export declare const IDEMPOTENCY_KEY_MAX_LENGTH = 255;
|
|
81
|
+
/**
|
|
82
|
+
* Is this a key we will store and compare?
|
|
83
|
+
*
|
|
84
|
+
* Visible ASCII, bounded, non-empty. Deliberately permissive about STRUCTURE — a
|
|
85
|
+
* UUID is the convention and this refuses to require one, because a client whose
|
|
86
|
+
* natural key is an order number should not have to hash it into a shape we
|
|
87
|
+
* prefer. What it refuses is a key that would make the table a place to put
|
|
88
|
+
* things: control characters, whitespace, and anything unbounded.
|
|
89
|
+
*
|
|
90
|
+
* The key is never interpreted. It is compared, and it is scoped to the subject
|
|
91
|
+
* that sent it, so one client's choice of key cannot collide with another's.
|
|
92
|
+
*/
|
|
93
|
+
export declare function isValidIdempotencyKey(key: string): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Deterministic JSON — the same value serialises to the same string, whatever
|
|
96
|
+
* order its keys arrived in.
|
|
97
|
+
*
|
|
98
|
+
* `JSON.stringify` preserves insertion order, so `{a:1,b:2}` and `{b:2,a:1}`
|
|
99
|
+
* produce different text for the same request. A fingerprint built on that would
|
|
100
|
+
* call a retry a different request roughly whenever a client rebuilt its body
|
|
101
|
+
* from a map — which is to say, unpredictably, and in production rather than in
|
|
102
|
+
* a test.
|
|
103
|
+
*
|
|
104
|
+
* Arrays keep their order, because in a request body order IS meaning (the
|
|
105
|
+
* second line item is not the first). Only object keys are sorted.
|
|
106
|
+
*/
|
|
107
|
+
export declare function canonicalJson(value: unknown): string;
|
|
108
|
+
/**
|
|
109
|
+
* What makes two requests the same request: the operation and its PARSED input.
|
|
110
|
+
*
|
|
111
|
+
* **Parsed, not raw**, and that is load-bearing. The host parses every invocation
|
|
112
|
+
* against the operation's declared schema before anything else runs, which
|
|
113
|
+
* applies defaults — so a retry that omits an optional field the original sent
|
|
114
|
+
* explicitly at its default value is the same request, and fingerprinting the raw
|
|
115
|
+
* body would call it a different one and refuse the retry with a 409.
|
|
116
|
+
*
|
|
117
|
+
* The operation name is inside the hash rather than beside it in the key, so a
|
|
118
|
+
* client reusing one key for two different operations is a MISMATCH (409) rather
|
|
119
|
+
* than two independent records. A key is a client's assertion that "this is the
|
|
120
|
+
* same request I sent before"; two operations is the clearest possible case of
|
|
121
|
+
* that assertion being false, and silently honouring it would replay one
|
|
122
|
+
* operation's response for another one's call.
|
|
123
|
+
*
|
|
124
|
+
* SHA-256 via Web Crypto — the same API in Node, Workers and browsers, per the
|
|
125
|
+
* repo's standing rule against node-only imports and against hand-rolled hashes.
|
|
126
|
+
*/
|
|
127
|
+
export declare function requestFingerprint(operation: string, input: unknown): Promise<string>;
|
|
128
|
+
/**
|
|
129
|
+
* The `conflict` reason slugs this feature owns.
|
|
130
|
+
*
|
|
131
|
+
* Slugs rather than codes, per the closed taxonomy: a module never invents a
|
|
132
|
+
* code, it narrows an existing one with a reason it owns. Both are `conflict`
|
|
133
|
+
* (409) because both mean the same thing to a client — *the key you sent is not
|
|
134
|
+
* available for this request* — and differ only in why, which is what the slug
|
|
135
|
+
* carries.
|
|
136
|
+
*/
|
|
137
|
+
export declare const IDEMPOTENCY_REUSED = "idempotency_key_reused";
|
|
138
|
+
export declare const IDEMPOTENCY_REPLAY_UNAVAILABLE = "idempotency_replay_unavailable";
|
|
139
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAExD;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,yBAAyB,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,mCAAyC,CAAC;AAElF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,QAAsB,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,QAAa,CAAC;AAEnD,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAWpD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAI3F;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D,eAAO,MAAM,8BAA8B,mCAAmC,CAAC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request idempotency on the operation surface (#116).
|
|
3
|
+
*
|
|
4
|
+
* A client whose request times out does not know whether the work happened. It
|
|
5
|
+
* retries, and a second work order exists. This is the vocabulary that lets the
|
|
6
|
+
* retry return the FIRST response instead of doing the work twice.
|
|
7
|
+
*
|
|
8
|
+
* ## This is not the event spine's idempotency
|
|
9
|
+
*
|
|
10
|
+
* Consumers have been required-idempotent since the beginning and the contract
|
|
11
|
+
* suite checks it: a consumer may see an event more than once and must settle to
|
|
12
|
+
* the same state. That is about the spine re-delivering. This is about a CLIENT
|
|
13
|
+
* re-sending — a different boundary, a different actor, and no relationship
|
|
14
|
+
* between the two beyond the word.
|
|
15
|
+
*
|
|
16
|
+
* ## Why the wire half lives here and the rest does not
|
|
17
|
+
*
|
|
18
|
+
* The same split `concurrency.ts` makes one file over: contracts sits below the
|
|
19
|
+
* spine, so it can say what the header is called, what makes a key well-formed
|
|
20
|
+
* and what makes two requests the same request. It must not know which table
|
|
21
|
+
* remembers the answer. `@substrat-run/kernel`'s `idempotency.ts` owns that, and
|
|
22
|
+
* the adapters own where it runs.
|
|
23
|
+
*
|
|
24
|
+
* ## The property that makes this cheap
|
|
25
|
+
*
|
|
26
|
+
* Invokes are serialised per scope in both adapters (`rt.actor.enqueue`, the
|
|
27
|
+
* ScopeDO's queue), so a duplicate cannot overlap the original — by the time the
|
|
28
|
+
* retry takes its turn, the first request has committed or rolled back. Every
|
|
29
|
+
* other implementation of this feature needs an in-flight state and a 409 for
|
|
30
|
+
* "still running"; this one does not, and the reason is a property of the host
|
|
31
|
+
* rather than an accident worth relying on quietly.
|
|
32
|
+
*/
|
|
33
|
+
/** The request header carrying the client's retry token. */
|
|
34
|
+
export const IDEMPOTENCY_KEY_HEADER = 'Idempotency-Key';
|
|
35
|
+
/**
|
|
36
|
+
* Set on a response the server did not compute — it replayed a recorded one.
|
|
37
|
+
*
|
|
38
|
+
* Advisory, and worth having anyway: without it a retry is indistinguishable
|
|
39
|
+
* from a first request that happened to succeed, which makes "did my key work?"
|
|
40
|
+
* unanswerable from the client side and turns every integration test of a retry
|
|
41
|
+
* path into a database query.
|
|
42
|
+
*/
|
|
43
|
+
export const IDEMPOTENCY_REPLAYED_HEADER = 'Idempotency-Replayed';
|
|
44
|
+
/**
|
|
45
|
+
* The header a cross-origin browser client cannot read unless the server says it
|
|
46
|
+
* may — the same trap `PAGE_EXPOSED_HEADERS` and `CONCURRENCY_EXPOSED_HEADERS`
|
|
47
|
+
* document, and the mildest of the three: an unexposed `Idempotency-Replayed`
|
|
48
|
+
* costs a client an observation, never a guarantee. The dedupe still happened.
|
|
49
|
+
*/
|
|
50
|
+
export const IDEMPOTENCY_EXPOSED_HEADERS = [IDEMPOTENCY_REPLAYED_HEADER];
|
|
51
|
+
/**
|
|
52
|
+
* How long a key is remembered: 24 hours.
|
|
53
|
+
*
|
|
54
|
+
* Long enough to cover the retries anything sane performs — an agent's backoff,
|
|
55
|
+
* a queue's redelivery, a person reloading a page that failed — and short enough
|
|
56
|
+
* that the recorded responses are a cache rather than an archive.
|
|
57
|
+
*
|
|
58
|
+
* The window is not only a storage bound, and the other reason is the one worth
|
|
59
|
+
* writing down: a recorded response is a SECOND COPY of whatever the operation
|
|
60
|
+
* returned, sitting in the scope database outside the erasure path that reaches
|
|
61
|
+
* the outbox (a shred nulls `payload` and keeps the row; it does not know about
|
|
62
|
+
* this table). A copy that expires in a day is defensible. One that expires in a
|
|
63
|
+
* quarter is a disclosure nobody declared, and one that never expires is a second
|
|
64
|
+
* database of personal data with no owner.
|
|
65
|
+
*/
|
|
66
|
+
export const IDEMPOTENCY_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
67
|
+
/**
|
|
68
|
+
* The largest result that is recorded for replay: 128 KiB of JSON.
|
|
69
|
+
*
|
|
70
|
+
* Above it the key is still recorded — with no body — and a replay is REFUSED
|
|
71
|
+
* rather than re-executed. That is the fail-closed direction: refusing a retry
|
|
72
|
+
* costs a caller an error it can act on, while re-running the operation is the
|
|
73
|
+
* duplicate this feature exists to prevent, arrived at through the feature
|
|
74
|
+
* itself. Writes return entity-shaped results and do not approach this; a list
|
|
75
|
+
* read might, and a list read never carries a key (the mount forwards the header
|
|
76
|
+
* on unsafe methods only).
|
|
77
|
+
*/
|
|
78
|
+
export const IDEMPOTENCY_RESULT_LIMIT = 128 * 1024;
|
|
79
|
+
/** Longest key accepted, matching the IETF draft's guidance for the header. */
|
|
80
|
+
export const IDEMPOTENCY_KEY_MAX_LENGTH = 255;
|
|
81
|
+
/**
|
|
82
|
+
* Is this a key we will store and compare?
|
|
83
|
+
*
|
|
84
|
+
* Visible ASCII, bounded, non-empty. Deliberately permissive about STRUCTURE — a
|
|
85
|
+
* UUID is the convention and this refuses to require one, because a client whose
|
|
86
|
+
* natural key is an order number should not have to hash it into a shape we
|
|
87
|
+
* prefer. What it refuses is a key that would make the table a place to put
|
|
88
|
+
* things: control characters, whitespace, and anything unbounded.
|
|
89
|
+
*
|
|
90
|
+
* The key is never interpreted. It is compared, and it is scoped to the subject
|
|
91
|
+
* that sent it, so one client's choice of key cannot collide with another's.
|
|
92
|
+
*/
|
|
93
|
+
export function isValidIdempotencyKey(key) {
|
|
94
|
+
if (key.length === 0 || key.length > IDEMPOTENCY_KEY_MAX_LENGTH)
|
|
95
|
+
return false;
|
|
96
|
+
return /^[\x21-\x7e]+$/.test(key);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Deterministic JSON — the same value serialises to the same string, whatever
|
|
100
|
+
* order its keys arrived in.
|
|
101
|
+
*
|
|
102
|
+
* `JSON.stringify` preserves insertion order, so `{a:1,b:2}` and `{b:2,a:1}`
|
|
103
|
+
* produce different text for the same request. A fingerprint built on that would
|
|
104
|
+
* call a retry a different request roughly whenever a client rebuilt its body
|
|
105
|
+
* from a map — which is to say, unpredictably, and in production rather than in
|
|
106
|
+
* a test.
|
|
107
|
+
*
|
|
108
|
+
* Arrays keep their order, because in a request body order IS meaning (the
|
|
109
|
+
* second line item is not the first). Only object keys are sorted.
|
|
110
|
+
*/
|
|
111
|
+
export function canonicalJson(value) {
|
|
112
|
+
if (value === undefined)
|
|
113
|
+
return 'null';
|
|
114
|
+
if (value === null || typeof value !== 'object')
|
|
115
|
+
return JSON.stringify(value) ?? 'null';
|
|
116
|
+
if (Array.isArray(value))
|
|
117
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`;
|
|
118
|
+
const entries = Object.entries(value)
|
|
119
|
+
// `undefined` is absent, not a value — `JSON.stringify` drops such a property
|
|
120
|
+
// and so must this, or an optional field explicitly passed as `undefined`
|
|
121
|
+
// would fingerprint differently from the same field simply omitted.
|
|
122
|
+
.filter(([, v]) => v !== undefined)
|
|
123
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
124
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* What makes two requests the same request: the operation and its PARSED input.
|
|
128
|
+
*
|
|
129
|
+
* **Parsed, not raw**, and that is load-bearing. The host parses every invocation
|
|
130
|
+
* against the operation's declared schema before anything else runs, which
|
|
131
|
+
* applies defaults — so a retry that omits an optional field the original sent
|
|
132
|
+
* explicitly at its default value is the same request, and fingerprinting the raw
|
|
133
|
+
* body would call it a different one and refuse the retry with a 409.
|
|
134
|
+
*
|
|
135
|
+
* The operation name is inside the hash rather than beside it in the key, so a
|
|
136
|
+
* client reusing one key for two different operations is a MISMATCH (409) rather
|
|
137
|
+
* than two independent records. A key is a client's assertion that "this is the
|
|
138
|
+
* same request I sent before"; two operations is the clearest possible case of
|
|
139
|
+
* that assertion being false, and silently honouring it would replay one
|
|
140
|
+
* operation's response for another one's call.
|
|
141
|
+
*
|
|
142
|
+
* SHA-256 via Web Crypto — the same API in Node, Workers and browsers, per the
|
|
143
|
+
* repo's standing rule against node-only imports and against hand-rolled hashes.
|
|
144
|
+
*/
|
|
145
|
+
export async function requestFingerprint(operation, input) {
|
|
146
|
+
const bytes = new TextEncoder().encode(canonicalJson([operation, input ?? null]));
|
|
147
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes);
|
|
148
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The `conflict` reason slugs this feature owns.
|
|
152
|
+
*
|
|
153
|
+
* Slugs rather than codes, per the closed taxonomy: a module never invents a
|
|
154
|
+
* code, it narrows an existing one with a reason it owns. Both are `conflict`
|
|
155
|
+
* (409) because both mean the same thing to a client — *the key you sent is not
|
|
156
|
+
* available for this request* — and differ only in why, which is what the slug
|
|
157
|
+
* carries.
|
|
158
|
+
*/
|
|
159
|
+
export const IDEMPOTENCY_REUSED = 'idempotency_key_reused';
|
|
160
|
+
export const IDEMPOTENCY_REPLAY_UNAVAILABLE = 'idempotency_replay_unavailable';
|
|
161
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,4DAA4D;AAC5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,iBAAiB,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,sBAAsB,CAAC;AAElE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,2BAA2B,CAAU,CAAC;AAElF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,GAAG,IAAI,CAAC;AAEnD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,0BAA0B;QAAE,OAAO,KAAK,CAAC;IAC9E,OAAO,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACvC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC;IACxF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7F,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;QAC9D,8EAA8E;QAC9E,0EAA0E;QAC1E,oEAAoE;SACnE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;SAClC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAC9F,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,SAAiB,EAAE,KAAc;IACxE,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,SAAS,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IAClF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAC3D,MAAM,CAAC,MAAM,8BAA8B,GAAG,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Acting as a principal with the REAL actor preserved (K-42, #868).
|
|
4
|
+
*
|
|
5
|
+
* Supporting a customer's live vertical means seeing what a named person sees.
|
|
6
|
+
* Every platform grows that surface eventually, and the version that grows by
|
|
7
|
+
* itself is a session swap: the staff member becomes the user, and the trail
|
|
8
|
+
* says the user did it. That is the version an audit fails.
|
|
9
|
+
*
|
|
10
|
+
* So an impersonated operation carries **two** actors. The permission model
|
|
11
|
+
* answers as the impersonated principal — that is the whole point, and an
|
|
12
|
+
* intersection with the staff actor's own authority would be empty, because a
|
|
13
|
+
* platform actor is not a principal in any tenant and holds no scope permissions
|
|
14
|
+
* at all (`PlatformActorId` is branded apart from `PrincipalId` for exactly that
|
|
15
|
+
* reason). What bounds the session instead is its MODE, its clock and its
|
|
16
|
+
* reason: `read-only` unless someone wrote down why not, expiring on its own,
|
|
17
|
+
* and admin-logged before it can be used.
|
|
18
|
+
*
|
|
19
|
+
* Every record the scope writes about who did what keeps both: the outbox
|
|
20
|
+
* envelope, the denial log, the platform-intent journal. Stamped kernel-side on
|
|
21
|
+
* K-34's pattern — `impersonation` is absent from `DomainEventInput`, so module
|
|
22
|
+
* code can neither claim a session it is not in nor drop the one it is.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The hard ceiling on a session's life, in minutes.
|
|
26
|
+
*
|
|
27
|
+
* A support session is bounded because the alternative is a credential: a
|
|
28
|
+
* session with no end is a second way to be that person, held by whoever last
|
|
29
|
+
* opened one. K-33's rewind is time-boxed and audited on the same argument, and
|
|
30
|
+
* the number is deliberately short enough that renewing is the normal case —
|
|
31
|
+
* each renewal being a fresh admin-log row is the feature, not the friction.
|
|
32
|
+
*/
|
|
33
|
+
export declare const IMPERSONATION_MAX_MINUTES = 60;
|
|
34
|
+
/** What a caller gets by not saying — a quarter hour, well inside the ceiling. */
|
|
35
|
+
export declare const IMPERSONATION_DEFAULT_MINUTES = 15;
|
|
36
|
+
/**
|
|
37
|
+
* The floor on a reason, in characters.
|
|
38
|
+
*
|
|
39
|
+
* Not a validation nicety: the reason is the only field of this record a human
|
|
40
|
+
* writes, and 'x' passing means the field is decoration. Short enough that a
|
|
41
|
+
* ticket reference ('#4182 — invoice missing') clears it.
|
|
42
|
+
*/
|
|
43
|
+
export declare const IMPERSONATION_MIN_REASON = 8;
|
|
44
|
+
/** ULID, minted platform-side. Brands apart from every other id in the tree. */
|
|
45
|
+
export declare const impersonationSessionId: z.core.$ZodBranded<z.ZodString, "ImpersonationSessionId", "out">;
|
|
46
|
+
export type ImpersonationSessionId = z.infer<typeof impersonationSessionId>;
|
|
47
|
+
/**
|
|
48
|
+
* What the session may do, and the answer to #868's last open question.
|
|
49
|
+
*
|
|
50
|
+
* `read-only` is most of the debugging value at a fraction of the argument, so
|
|
51
|
+
* it is the default and it is MECHANICAL: a read-only invocation's transaction
|
|
52
|
+
* is rolled back rather than committed, and the effecting verbs (`emit`,
|
|
53
|
+
* `requestPlatform`, `grant`, `revoke`, `link`) refuse outright, so a support
|
|
54
|
+
* engineer cannot approve an invoice by accident and a vertical cannot arrange
|
|
55
|
+
* for them to. `write` exists because "reproduce the failing save" is a real
|
|
56
|
+
* support task — it just has to be asked for, in a session that says so.
|
|
57
|
+
*/
|
|
58
|
+
export declare const impersonationMode: z.ZodEnum<{
|
|
59
|
+
"read-only": "read-only";
|
|
60
|
+
write: "write";
|
|
61
|
+
}>;
|
|
62
|
+
export type ImpersonationMode = z.infer<typeof impersonationMode>;
|
|
63
|
+
/**
|
|
64
|
+
* What staff supply to open a session. The acting actor is NOT here: it is the
|
|
65
|
+
* `PlatformActorId` every `HostAdmin` verb already takes, so it can no more be
|
|
66
|
+
* chosen by the caller than the actor on an admin-log row can.
|
|
67
|
+
*/
|
|
68
|
+
export declare const beginImpersonationInput: z.ZodObject<{
|
|
69
|
+
tenantId: z.core.$ZodBranded<z.ZodString, "TenantId", "out">;
|
|
70
|
+
scopeId: z.core.$ZodBranded<z.ZodString, "ScopeId", "out">;
|
|
71
|
+
principal: z.core.$ZodBranded<z.ZodString, "PrincipalId", "out">;
|
|
72
|
+
reason: z.ZodString;
|
|
73
|
+
minutes: z.ZodOptional<z.ZodNumber>;
|
|
74
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
75
|
+
"read-only": "read-only";
|
|
76
|
+
write: "write";
|
|
77
|
+
}>>;
|
|
78
|
+
}, z.core.$strip>;
|
|
79
|
+
export type BeginImpersonationInput = z.infer<typeof beginImpersonationInput>;
|
|
80
|
+
/**
|
|
81
|
+
* A session as the directory holds it, and as `listImpersonations` reads it back.
|
|
82
|
+
*
|
|
83
|
+
* `endedAt` is the explicit close. It is distinct from expiry: a session that
|
|
84
|
+
* ran out is over because time passed, one that was ended is over because
|
|
85
|
+
* somebody stopped it, and an incident review wants to be able to tell those
|
|
86
|
+
* apart. Neither is a delete — this record is evidence (K-21's tombstone rule).
|
|
87
|
+
*/
|
|
88
|
+
export declare const impersonationSession: z.ZodObject<{
|
|
89
|
+
id: z.core.$ZodBranded<z.ZodString, "ImpersonationSessionId", "out">;
|
|
90
|
+
actor: z.core.$ZodBranded<z.ZodString, "PlatformActorId", "out">;
|
|
91
|
+
principal: z.core.$ZodBranded<z.ZodString, "PrincipalId", "out">;
|
|
92
|
+
tenantId: z.core.$ZodBranded<z.ZodString, "TenantId", "out">;
|
|
93
|
+
scopeId: z.core.$ZodBranded<z.ZodString, "ScopeId", "out">;
|
|
94
|
+
reason: z.ZodString;
|
|
95
|
+
mode: z.ZodEnum<{
|
|
96
|
+
"read-only": "read-only";
|
|
97
|
+
write: "write";
|
|
98
|
+
}>;
|
|
99
|
+
startedAt: z.core.$ZodBranded<z.ZodString, "Instant", "out">;
|
|
100
|
+
expiresAt: z.core.$ZodBranded<z.ZodString, "Instant", "out">;
|
|
101
|
+
endedAt: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "Instant", "out">>;
|
|
102
|
+
}, z.core.$strip>;
|
|
103
|
+
export type ImpersonationSession = z.infer<typeof impersonationSession>;
|
|
104
|
+
/**
|
|
105
|
+
* The two actors a record keeps — the stamp the kernel puts on an event
|
|
106
|
+
* envelope, a denial row and a platform intent raised under a session.
|
|
107
|
+
*
|
|
108
|
+
* `by` rather than `actor`, because the envelope's `actor` field is already
|
|
109
|
+
* taken and already correct: the impersonated principal is who the permission
|
|
110
|
+
* model answered about and who the domain fact is about. This says who was
|
|
111
|
+
* holding the keyboard, which is a different question with a different answer.
|
|
112
|
+
*/
|
|
113
|
+
export declare const impersonationStamp: z.ZodObject<{
|
|
114
|
+
session: z.core.$ZodBranded<z.ZodString, "ImpersonationSessionId", "out">;
|
|
115
|
+
by: z.core.$ZodBranded<z.ZodString, "PlatformActorId", "out">;
|
|
116
|
+
}, z.core.$strip>;
|
|
117
|
+
export type ImpersonationStamp = z.infer<typeof impersonationStamp>;
|
|
118
|
+
/**
|
|
119
|
+
* How a caller narrows a read of the session log. `active` is evaluated against
|
|
120
|
+
* the reader's clock — a session neither ended nor expired — because "who is in
|
|
121
|
+
* a customer's data right now" is the question an incident opens with.
|
|
122
|
+
*/
|
|
123
|
+
export declare const impersonationFilter: z.ZodObject<{
|
|
124
|
+
tenantId: z.ZodOptional<z.ZodString>;
|
|
125
|
+
scopeId: z.ZodOptional<z.ZodString>;
|
|
126
|
+
actor: z.ZodOptional<z.ZodString>;
|
|
127
|
+
principal: z.ZodOptional<z.ZodString>;
|
|
128
|
+
active: z.ZodOptional<z.ZodBoolean>;
|
|
129
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
130
|
+
}, z.core.$strip>;
|
|
131
|
+
export type ImpersonationFilter = z.infer<typeof impersonationFilter>;
|
|
132
|
+
/** How many sessions an unbounded read returns — a screenful, newest first. */
|
|
133
|
+
export declare const DEFAULT_IMPERSONATION_LIMIT = 50;
|
|
134
|
+
//# sourceMappingURL=impersonation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impersonation.d.ts","sourceRoot":"","sources":["../src/impersonation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,kFAAkF;AAClF,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,kEAAsD,CAAC;AAC1F,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB;;;EAAiC,CAAC;AAChE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;iBAUlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;iBAa/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB;;;iBAG7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;iBAO9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,KAAK,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { instant, platformActorId, principalId, scopeId, tenantId } from './ids.js';
|
|
3
|
+
/**
|
|
4
|
+
* Acting as a principal with the REAL actor preserved (K-42, #868).
|
|
5
|
+
*
|
|
6
|
+
* Supporting a customer's live vertical means seeing what a named person sees.
|
|
7
|
+
* Every platform grows that surface eventually, and the version that grows by
|
|
8
|
+
* itself is a session swap: the staff member becomes the user, and the trail
|
|
9
|
+
* says the user did it. That is the version an audit fails.
|
|
10
|
+
*
|
|
11
|
+
* So an impersonated operation carries **two** actors. The permission model
|
|
12
|
+
* answers as the impersonated principal — that is the whole point, and an
|
|
13
|
+
* intersection with the staff actor's own authority would be empty, because a
|
|
14
|
+
* platform actor is not a principal in any tenant and holds no scope permissions
|
|
15
|
+
* at all (`PlatformActorId` is branded apart from `PrincipalId` for exactly that
|
|
16
|
+
* reason). What bounds the session instead is its MODE, its clock and its
|
|
17
|
+
* reason: `read-only` unless someone wrote down why not, expiring on its own,
|
|
18
|
+
* and admin-logged before it can be used.
|
|
19
|
+
*
|
|
20
|
+
* Every record the scope writes about who did what keeps both: the outbox
|
|
21
|
+
* envelope, the denial log, the platform-intent journal. Stamped kernel-side on
|
|
22
|
+
* K-34's pattern — `impersonation` is absent from `DomainEventInput`, so module
|
|
23
|
+
* code can neither claim a session it is not in nor drop the one it is.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* The hard ceiling on a session's life, in minutes.
|
|
27
|
+
*
|
|
28
|
+
* A support session is bounded because the alternative is a credential: a
|
|
29
|
+
* session with no end is a second way to be that person, held by whoever last
|
|
30
|
+
* opened one. K-33's rewind is time-boxed and audited on the same argument, and
|
|
31
|
+
* the number is deliberately short enough that renewing is the normal case —
|
|
32
|
+
* each renewal being a fresh admin-log row is the feature, not the friction.
|
|
33
|
+
*/
|
|
34
|
+
export const IMPERSONATION_MAX_MINUTES = 60;
|
|
35
|
+
/** What a caller gets by not saying — a quarter hour, well inside the ceiling. */
|
|
36
|
+
export const IMPERSONATION_DEFAULT_MINUTES = 15;
|
|
37
|
+
/**
|
|
38
|
+
* The floor on a reason, in characters.
|
|
39
|
+
*
|
|
40
|
+
* Not a validation nicety: the reason is the only field of this record a human
|
|
41
|
+
* writes, and 'x' passing means the field is decoration. Short enough that a
|
|
42
|
+
* ticket reference ('#4182 — invoice missing') clears it.
|
|
43
|
+
*/
|
|
44
|
+
export const IMPERSONATION_MIN_REASON = 8;
|
|
45
|
+
/** ULID, minted platform-side. Brands apart from every other id in the tree. */
|
|
46
|
+
export const impersonationSessionId = z.string().min(1).brand();
|
|
47
|
+
/**
|
|
48
|
+
* What the session may do, and the answer to #868's last open question.
|
|
49
|
+
*
|
|
50
|
+
* `read-only` is most of the debugging value at a fraction of the argument, so
|
|
51
|
+
* it is the default and it is MECHANICAL: a read-only invocation's transaction
|
|
52
|
+
* is rolled back rather than committed, and the effecting verbs (`emit`,
|
|
53
|
+
* `requestPlatform`, `grant`, `revoke`, `link`) refuse outright, so a support
|
|
54
|
+
* engineer cannot approve an invoice by accident and a vertical cannot arrange
|
|
55
|
+
* for them to. `write` exists because "reproduce the failing save" is a real
|
|
56
|
+
* support task — it just has to be asked for, in a session that says so.
|
|
57
|
+
*/
|
|
58
|
+
export const impersonationMode = z.enum(['read-only', 'write']);
|
|
59
|
+
/**
|
|
60
|
+
* What staff supply to open a session. The acting actor is NOT here: it is the
|
|
61
|
+
* `PlatformActorId` every `HostAdmin` verb already takes, so it can no more be
|
|
62
|
+
* chosen by the caller than the actor on an admin-log row can.
|
|
63
|
+
*/
|
|
64
|
+
export const beginImpersonationInput = z.object({
|
|
65
|
+
tenantId,
|
|
66
|
+
scopeId,
|
|
67
|
+
/** WHO to act as. A principal of this tenant; nothing here mints one. */
|
|
68
|
+
principal: principalId,
|
|
69
|
+
/** Why. Recorded on the session and in the admin log, and never optional. */
|
|
70
|
+
reason: z.string().min(IMPERSONATION_MIN_REASON),
|
|
71
|
+
/** Capped at `IMPERSONATION_MAX_MINUTES`; a longer ask is refused, not clamped. */
|
|
72
|
+
minutes: z.number().int().positive().max(IMPERSONATION_MAX_MINUTES).optional(),
|
|
73
|
+
mode: impersonationMode.optional(),
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* A session as the directory holds it, and as `listImpersonations` reads it back.
|
|
77
|
+
*
|
|
78
|
+
* `endedAt` is the explicit close. It is distinct from expiry: a session that
|
|
79
|
+
* ran out is over because time passed, one that was ended is over because
|
|
80
|
+
* somebody stopped it, and an incident review wants to be able to tell those
|
|
81
|
+
* apart. Neither is a delete — this record is evidence (K-21's tombstone rule).
|
|
82
|
+
*/
|
|
83
|
+
export const impersonationSession = z.object({
|
|
84
|
+
id: impersonationSessionId,
|
|
85
|
+
/** The REAL actor: the staff member who opened the session. */
|
|
86
|
+
actor: platformActorId,
|
|
87
|
+
/** The principal being acted as — who the permission model answers about. */
|
|
88
|
+
principal: principalId,
|
|
89
|
+
tenantId,
|
|
90
|
+
scopeId,
|
|
91
|
+
reason: z.string().min(IMPERSONATION_MIN_REASON),
|
|
92
|
+
mode: impersonationMode,
|
|
93
|
+
startedAt: instant,
|
|
94
|
+
expiresAt: instant,
|
|
95
|
+
endedAt: instant.nullable(),
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* The two actors a record keeps — the stamp the kernel puts on an event
|
|
99
|
+
* envelope, a denial row and a platform intent raised under a session.
|
|
100
|
+
*
|
|
101
|
+
* `by` rather than `actor`, because the envelope's `actor` field is already
|
|
102
|
+
* taken and already correct: the impersonated principal is who the permission
|
|
103
|
+
* model answered about and who the domain fact is about. This says who was
|
|
104
|
+
* holding the keyboard, which is a different question with a different answer.
|
|
105
|
+
*/
|
|
106
|
+
export const impersonationStamp = z.object({
|
|
107
|
+
session: impersonationSessionId,
|
|
108
|
+
by: platformActorId,
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* How a caller narrows a read of the session log. `active` is evaluated against
|
|
112
|
+
* the reader's clock — a session neither ended nor expired — because "who is in
|
|
113
|
+
* a customer's data right now" is the question an incident opens with.
|
|
114
|
+
*/
|
|
115
|
+
export const impersonationFilter = z.object({
|
|
116
|
+
tenantId: z.string().min(1).optional(),
|
|
117
|
+
scopeId: z.string().min(1).optional(),
|
|
118
|
+
actor: z.string().min(1).optional(),
|
|
119
|
+
principal: z.string().min(1).optional(),
|
|
120
|
+
active: z.boolean().optional(),
|
|
121
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
122
|
+
});
|
|
123
|
+
/** How many sessions an unbounded read returns — a screenful, newest first. */
|
|
124
|
+
export const DEFAULT_IMPERSONATION_LIMIT = 50;
|
|
125
|
+
//# sourceMappingURL=impersonation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impersonation.js","sourceRoot":"","sources":["../src/impersonation.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,9 +24,11 @@ export * from './tenancy.js';
|
|
|
24
24
|
export * from './introspection.js';
|
|
25
25
|
export * from './pagination.js';
|
|
26
26
|
export * from './concurrency.js';
|
|
27
|
+
export * from './idempotency.js';
|
|
27
28
|
export * from './connections.js';
|
|
28
29
|
export * from './control-plane.js';
|
|
29
30
|
export * from './permission.js';
|
|
31
|
+
export * from './impersonation.js';
|
|
30
32
|
export * from './events.js';
|
|
31
33
|
export * from './errors.js';
|
|
32
34
|
export * from './platform-request.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -24,9 +24,11 @@ export * from './tenancy.js';
|
|
|
24
24
|
export * from './introspection.js';
|
|
25
25
|
export * from './pagination.js';
|
|
26
26
|
export * from './concurrency.js';
|
|
27
|
+
export * from './idempotency.js';
|
|
27
28
|
export * from './connections.js';
|
|
28
29
|
export * from './control-plane.js';
|
|
29
30
|
export * from './permission.js';
|
|
31
|
+
export * from './impersonation.js';
|
|
30
32
|
export * from './events.js';
|
|
31
33
|
export * from './errors.js';
|
|
32
34
|
export * from './platform-request.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC"}
|
package/dist/openapi.d.ts
CHANGED
|
@@ -62,6 +62,16 @@ export interface ApiOperationDoc {
|
|
|
62
62
|
over: string;
|
|
63
63
|
idFrom: string;
|
|
64
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* `false` when the operation declared out of request idempotency (#116).
|
|
67
|
+
*
|
|
68
|
+
* Present only as a refusal, matching the declaration: every other unsafe
|
|
69
|
+
* operation honours `Idempotency-Key`, so the header is documented on all of
|
|
70
|
+
* them and this is what removes it from the one that would refuse it. A header
|
|
71
|
+
* documented where it is refused is worse than one documented nowhere — a
|
|
72
|
+
* client reads it and builds a retry it does not have.
|
|
73
|
+
*/
|
|
74
|
+
idempotency?: false;
|
|
65
75
|
paged?: {
|
|
66
76
|
/** Present on a handler-composed read: the ENTRY field the cursor walks. */
|
|
67
77
|
sortKey?: string;
|
package/dist/openapi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mFAAmF;IACnF,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IACnB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE;QAAE,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7E;;;;OAIG;IACH;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,KAAK,CAAC,EAAE;QACN,4EAA4E;QAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;QACvB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB;;;;WAIG;QACH,IAAI,CAAC,EAAE;YACL,MAAM,EAAE,MAAM,CAAC;YACf,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;YAC5B,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;SAChC,CAAC;KACH,CAAC;CACH;AAgBD,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAEzD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,UAAU,GAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsPzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC5C,KAAK,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAM,GAC3E,UAAU,CAoCZ"}
|