@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.
@@ -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":"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"}
@@ -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';
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgBxB;;;;;;;;;;;;;;;;;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,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,CAqOzB;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,CAiCZ"}
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"}