@substrat-run/contracts 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/concurrency.d.ts +82 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +94 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/denial.d.ts +121 -0
- package/dist/denial.d.ts.map +1 -0
- package/dist/denial.js +117 -0
- package/dist/denial.js.map +1 -0
- package/dist/errors.d.ts +40 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +163 -14
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +78 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +57 -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/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/openapi.d.ts +24 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +81 -1
- package/dist/openapi.js.map +1 -1
- package/dist/operations.d.ts +283 -11
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +318 -1
- package/dist/operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimistic concurrency on the operation surface (#129).
|
|
3
|
+
*
|
|
4
|
+
* Two people open the same record, both save, and the second write silently
|
|
5
|
+
* destroys the first. Nobody notices until the data is gone — there is no error,
|
|
6
|
+
* no log line, and no way to reconstruct what was lost. This is the vocabulary
|
|
7
|
+
* that lets an operation refuse the second write instead.
|
|
8
|
+
*
|
|
9
|
+
* ## What a version is, and why it is not in here
|
|
10
|
+
*
|
|
11
|
+
* The version is the ULID of the last event about the entity (`entityVersionOf`
|
|
12
|
+
* in the kernel, #901). This module holds only the WIRE half — the header names
|
|
13
|
+
* and the comparison — because contracts sits below the spine: it can say what an
|
|
14
|
+
* `ETag` is called and what makes one match, and must not know which table
|
|
15
|
+
* answers the question.
|
|
16
|
+
*
|
|
17
|
+
* ## Why an opaque token rather than a number
|
|
18
|
+
*
|
|
19
|
+
* A caller compares; it never reads. That is what lets the version be a ULID
|
|
20
|
+
* today and something else later without a client noticing, and it is also what
|
|
21
|
+
* stops a client doing arithmetic on it — `If-Match: <version + 1>` is not a
|
|
22
|
+
* thing anyone can write.
|
|
23
|
+
*
|
|
24
|
+
* ## The direction this fails
|
|
25
|
+
*
|
|
26
|
+
* ANY event about the entity moves the version, including one that changed
|
|
27
|
+
* nothing the caller read. So a precondition built on it is CONSERVATIVE: it can
|
|
28
|
+
* refuse a write that would in fact have been safe, and it cannot admit one that
|
|
29
|
+
* would not. Documented here as well as in the kernel because this is the half a
|
|
30
|
+
* client author reads when they are debugging a 412 they believe is spurious.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The response header carrying an entity's current version.
|
|
34
|
+
*
|
|
35
|
+
* Strong, never weak (`W/"…"`): the comparison is exact, and a weak validator
|
|
36
|
+
* means "semantically equivalent", which is precisely the judgement no generic
|
|
37
|
+
* layer is entitled to make about a domain entity.
|
|
38
|
+
*/
|
|
39
|
+
export declare const ETAG_HEADER = "ETag";
|
|
40
|
+
/** The request header carrying the version the caller believes it is writing over. */
|
|
41
|
+
export declare const IF_MATCH_HEADER = "If-Match";
|
|
42
|
+
/**
|
|
43
|
+
* The headers a cross-origin browser client cannot read unless the server says it
|
|
44
|
+
* may — the same trap already documented for `Link` and `X-Total-Count` in
|
|
45
|
+
* `PAGE_EXPOSED_HEADERS`, and worse here.
|
|
46
|
+
*
|
|
47
|
+
* A `Link` a browser cannot read looks like "there is only one page". An `ETag` a
|
|
48
|
+
* browser cannot read looks like nothing at all: `response.headers.get('ETag')` is
|
|
49
|
+
* `null`, the client sends no `If-Match`, the server has nothing to compare, and
|
|
50
|
+
* every write succeeds. The protection silently switches itself off, in the one
|
|
51
|
+
* deployment shape (an SPA on another origin) where two editors are most likely.
|
|
52
|
+
*
|
|
53
|
+
* So a vertical exposing its API cross-origin must expose these, and this constant
|
|
54
|
+
* is what it lists rather than a string it retypes.
|
|
55
|
+
*/
|
|
56
|
+
export declare const CONCURRENCY_EXPOSED_HEADERS: readonly ["ETag"];
|
|
57
|
+
/**
|
|
58
|
+
* Format a version as an `ETag` value.
|
|
59
|
+
*
|
|
60
|
+
* Quoted, per RFC 9110 §8.8.3 — an entity-tag is a quoted string and a bare token
|
|
61
|
+
* is malformed. Intermediaries do parse this: an unquoted value is the kind of
|
|
62
|
+
* thing that works against a dev server and is stripped by a proxy in production.
|
|
63
|
+
*/
|
|
64
|
+
export declare function etagOf(version: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Does the caller's `If-Match` admit this version?
|
|
67
|
+
*
|
|
68
|
+
* Handles the three spellings RFC 9110 §13.1.1 allows a client to send: the quoted
|
|
69
|
+
* tag, a comma-separated list of them, and `*` (meaning "any current version" —
|
|
70
|
+
* i.e. the entity must merely EXIST, which for us means it has a version at all).
|
|
71
|
+
*
|
|
72
|
+
* A weak tag (`W/"…"`) never matches. §13.1.1 requires the strong comparison
|
|
73
|
+
* function for `If-Match`, and honouring a weak one here would admit exactly the
|
|
74
|
+
* write this exists to refuse.
|
|
75
|
+
*
|
|
76
|
+
* `version` is null when the entity has no events yet. Nothing matches that except
|
|
77
|
+
* an absent header, which is not this function's call to make — a caller that sent
|
|
78
|
+
* `If-Match` against an entity with no version is asking to write over something
|
|
79
|
+
* that was never written, and gets a refusal.
|
|
80
|
+
*/
|
|
81
|
+
export declare function ifMatchAdmits(ifMatch: string, version: string | null): boolean;
|
|
82
|
+
//# sourceMappingURL=concurrency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,SAAS,CAAC;AAElC,sFAAsF;AACtF,eAAO,MAAM,eAAe,aAAa,CAAC;AAE1C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,2BAA2B,mBAAyB,CAAC;AAElE;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAQ9E"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimistic concurrency on the operation surface (#129).
|
|
3
|
+
*
|
|
4
|
+
* Two people open the same record, both save, and the second write silently
|
|
5
|
+
* destroys the first. Nobody notices until the data is gone — there is no error,
|
|
6
|
+
* no log line, and no way to reconstruct what was lost. This is the vocabulary
|
|
7
|
+
* that lets an operation refuse the second write instead.
|
|
8
|
+
*
|
|
9
|
+
* ## What a version is, and why it is not in here
|
|
10
|
+
*
|
|
11
|
+
* The version is the ULID of the last event about the entity (`entityVersionOf`
|
|
12
|
+
* in the kernel, #901). This module holds only the WIRE half — the header names
|
|
13
|
+
* and the comparison — because contracts sits below the spine: it can say what an
|
|
14
|
+
* `ETag` is called and what makes one match, and must not know which table
|
|
15
|
+
* answers the question.
|
|
16
|
+
*
|
|
17
|
+
* ## Why an opaque token rather than a number
|
|
18
|
+
*
|
|
19
|
+
* A caller compares; it never reads. That is what lets the version be a ULID
|
|
20
|
+
* today and something else later without a client noticing, and it is also what
|
|
21
|
+
* stops a client doing arithmetic on it — `If-Match: <version + 1>` is not a
|
|
22
|
+
* thing anyone can write.
|
|
23
|
+
*
|
|
24
|
+
* ## The direction this fails
|
|
25
|
+
*
|
|
26
|
+
* ANY event about the entity moves the version, including one that changed
|
|
27
|
+
* nothing the caller read. So a precondition built on it is CONSERVATIVE: it can
|
|
28
|
+
* refuse a write that would in fact have been safe, and it cannot admit one that
|
|
29
|
+
* would not. Documented here as well as in the kernel because this is the half a
|
|
30
|
+
* client author reads when they are debugging a 412 they believe is spurious.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The response header carrying an entity's current version.
|
|
34
|
+
*
|
|
35
|
+
* Strong, never weak (`W/"…"`): the comparison is exact, and a weak validator
|
|
36
|
+
* means "semantically equivalent", which is precisely the judgement no generic
|
|
37
|
+
* layer is entitled to make about a domain entity.
|
|
38
|
+
*/
|
|
39
|
+
export const ETAG_HEADER = 'ETag';
|
|
40
|
+
/** The request header carrying the version the caller believes it is writing over. */
|
|
41
|
+
export const IF_MATCH_HEADER = 'If-Match';
|
|
42
|
+
/**
|
|
43
|
+
* The headers a cross-origin browser client cannot read unless the server says it
|
|
44
|
+
* may — the same trap already documented for `Link` and `X-Total-Count` in
|
|
45
|
+
* `PAGE_EXPOSED_HEADERS`, and worse here.
|
|
46
|
+
*
|
|
47
|
+
* A `Link` a browser cannot read looks like "there is only one page". An `ETag` a
|
|
48
|
+
* browser cannot read looks like nothing at all: `response.headers.get('ETag')` is
|
|
49
|
+
* `null`, the client sends no `If-Match`, the server has nothing to compare, and
|
|
50
|
+
* every write succeeds. The protection silently switches itself off, in the one
|
|
51
|
+
* deployment shape (an SPA on another origin) where two editors are most likely.
|
|
52
|
+
*
|
|
53
|
+
* So a vertical exposing its API cross-origin must expose these, and this constant
|
|
54
|
+
* is what it lists rather than a string it retypes.
|
|
55
|
+
*/
|
|
56
|
+
export const CONCURRENCY_EXPOSED_HEADERS = [ETAG_HEADER];
|
|
57
|
+
/**
|
|
58
|
+
* Format a version as an `ETag` value.
|
|
59
|
+
*
|
|
60
|
+
* Quoted, per RFC 9110 §8.8.3 — an entity-tag is a quoted string and a bare token
|
|
61
|
+
* is malformed. Intermediaries do parse this: an unquoted value is the kind of
|
|
62
|
+
* thing that works against a dev server and is stripped by a proxy in production.
|
|
63
|
+
*/
|
|
64
|
+
export function etagOf(version) {
|
|
65
|
+
return `"${version}"`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Does the caller's `If-Match` admit this version?
|
|
69
|
+
*
|
|
70
|
+
* Handles the three spellings RFC 9110 §13.1.1 allows a client to send: the quoted
|
|
71
|
+
* tag, a comma-separated list of them, and `*` (meaning "any current version" —
|
|
72
|
+
* i.e. the entity must merely EXIST, which for us means it has a version at all).
|
|
73
|
+
*
|
|
74
|
+
* A weak tag (`W/"…"`) never matches. §13.1.1 requires the strong comparison
|
|
75
|
+
* function for `If-Match`, and honouring a weak one here would admit exactly the
|
|
76
|
+
* write this exists to refuse.
|
|
77
|
+
*
|
|
78
|
+
* `version` is null when the entity has no events yet. Nothing matches that except
|
|
79
|
+
* an absent header, which is not this function's call to make — a caller that sent
|
|
80
|
+
* `If-Match` against an entity with no version is asking to write over something
|
|
81
|
+
* that was never written, and gets a refusal.
|
|
82
|
+
*/
|
|
83
|
+
export function ifMatchAdmits(ifMatch, version) {
|
|
84
|
+
const candidates = ifMatch
|
|
85
|
+
.split(',')
|
|
86
|
+
.map((raw) => raw.trim())
|
|
87
|
+
.filter((raw) => raw.length > 0);
|
|
88
|
+
if (candidates.includes('*'))
|
|
89
|
+
return version !== null;
|
|
90
|
+
if (version === null)
|
|
91
|
+
return false;
|
|
92
|
+
return candidates.includes(etagOf(version));
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=concurrency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC;AAElC,sFAAsF;AACtF,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;AAE1C;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,WAAW,CAAU,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,UAAU,MAAM,CAAC,OAAe;IACpC,OAAO,IAAI,OAAO,GAAG,CAAC;AACxB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,OAAsB;IACnE,MAAM,UAAU,GAAG,OAAO;SACvB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;SACxB,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnC,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,KAAK,IAAI,CAAC;IACtD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AAC9C,CAAC"}
|
package/dist/denial.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* The read side of the K-35 denial log — a scope-local record of every ENFORCED
|
|
4
|
+
* permission refusal (`assertAllowed`), written on the deny path as a fresh
|
|
5
|
+
* autocommit AFTER the rollback it is evidence of.
|
|
6
|
+
*
|
|
7
|
+
* This is the *other* kind of evidence from a conformance receipt. A receipt says
|
|
8
|
+
* "we attempted the attack in CI at commit X"; these rows say "on your data, in
|
|
9
|
+
* production, here is every refusal, by whom, against which key". K-35 made the
|
|
10
|
+
* case for the row itself: a denial is the one event where an actor's intent and
|
|
11
|
+
* the permission model visibly disagree.
|
|
12
|
+
*
|
|
13
|
+
* Two properties of the log shape everything below, and both come straight from
|
|
14
|
+
* K-35's own reasoning about why denials are NOT admin-log entries:
|
|
15
|
+
*
|
|
16
|
+
* 1. **The volume is attacker-influenceable.** A probing client mints unlimited
|
|
17
|
+
* rows, so a newest-first page of raw rows is the wrong default view — 200 rows
|
|
18
|
+
* from one prober hide everyone else. That is why `denialSummary` exists beside
|
|
19
|
+
* the row list, and why K-35 called rate-bucketing sanctionable up front.
|
|
20
|
+
* 2. **The window is a storage bound, not a retention policy.** Rows `drain` rather
|
|
21
|
+
* than expire (K-24's split). Until a Tier-2 sink exists, what is here is simply
|
|
22
|
+
* what has not been pruned — so the summary reports the window's own floor
|
|
23
|
+
* (`windowOldestAt`) rather than letting a caller read absence as "never happened".
|
|
24
|
+
*/
|
|
25
|
+
/** How many denial rows an unbounded read returns — a screenful, newest-first. */
|
|
26
|
+
export declare const DEFAULT_DENIAL_LIMIT = 50;
|
|
27
|
+
/** The hard ceiling on one page of denial rows, and on one page of buckets. */
|
|
28
|
+
export declare const DENIAL_LIMIT_MAX = 200;
|
|
29
|
+
/**
|
|
30
|
+
* One recorded refusal. `scopeId` is null for a tenant-node check (one that named no
|
|
31
|
+
* scope); `operation` is null when the denial unwound something that was not an
|
|
32
|
+
* operation invocation. `drainedAt` marks a row already shipped to a Tier-2 sink and
|
|
33
|
+
* therefore eligible to be pruned — bookkeeping, not a judgement about the denial.
|
|
34
|
+
*/
|
|
35
|
+
export declare const permissionDenial: z.ZodObject<{
|
|
36
|
+
id: z.ZodString;
|
|
37
|
+
actor: z.ZodUnion<readonly [z.core.$ZodBranded<z.ZodString, "PrincipalId", "out">, z.ZodObject<{
|
|
38
|
+
system: z.core.$ZodBranded<z.ZodString, "ModuleId", "out">;
|
|
39
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
40
|
+
connection: z.ZodString;
|
|
41
|
+
}, z.core.$strip>]>;
|
|
42
|
+
permission: z.core.$ZodBranded<z.ZodString, "PermissionKey", "out">;
|
|
43
|
+
tenantId: z.core.$ZodBranded<z.ZodString, "TenantId", "out">;
|
|
44
|
+
scopeId: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ScopeId", "out">>;
|
|
45
|
+
operation: z.ZodNullable<z.ZodString>;
|
|
46
|
+
at: z.ZodString;
|
|
47
|
+
drainedAt: z.ZodNullable<z.ZodString>;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export type PermissionDenial = z.infer<typeof permissionDenial>;
|
|
50
|
+
/**
|
|
51
|
+
* What narrows a denial read. Every field is an exact match except the `since`/`until`
|
|
52
|
+
* bounds on `at` (inclusive lower, exclusive upper) — enough to answer "who probed
|
|
53
|
+
* this key", "what did this actor try", and "what happened during the incident window"
|
|
54
|
+
* without a SQL console.
|
|
55
|
+
*
|
|
56
|
+
* `actor` takes the LOGICAL actor — a bare principal ULID, or the object form for a
|
|
57
|
+
* system/connection actor (`{"system":"invoicing"}`). The writer persists
|
|
58
|
+
* `JSON.stringify(actor)`, so a principal is stored with its quotes; normalizing to that
|
|
59
|
+
* encoding is the reader's job (`storedActor` in the kernel's query builder), not every
|
|
60
|
+
* caller's.
|
|
61
|
+
*/
|
|
62
|
+
export declare const denialFilter: z.ZodObject<{
|
|
63
|
+
actor: z.ZodOptional<z.ZodString>;
|
|
64
|
+
permission: z.ZodOptional<z.ZodString>;
|
|
65
|
+
operation: z.ZodOptional<z.ZodString>;
|
|
66
|
+
since: z.ZodOptional<z.ZodString>;
|
|
67
|
+
until: z.ZodOptional<z.ZodString>;
|
|
68
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
export type DenialFilter = z.infer<typeof denialFilter>;
|
|
71
|
+
/**
|
|
72
|
+
* One (actor, permission) bucket — K-35's "first occurrence + count per actor/key/
|
|
73
|
+
* window", which is the shape that survives a flood. `operations` is the number of
|
|
74
|
+
* DISTINCT operations the actor was refused this key on, and it is the discriminator
|
|
75
|
+
* worth the extra aggregate: one operation refused four hundred times is a broken
|
|
76
|
+
* screen or a misconfigured role, while the same count spread across a dozen
|
|
77
|
+
* operations is someone walking the surface.
|
|
78
|
+
*/
|
|
79
|
+
export declare const denialBucket: z.ZodObject<{
|
|
80
|
+
actor: z.ZodUnion<readonly [z.core.$ZodBranded<z.ZodString, "PrincipalId", "out">, z.ZodObject<{
|
|
81
|
+
system: z.core.$ZodBranded<z.ZodString, "ModuleId", "out">;
|
|
82
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
83
|
+
connection: z.ZodString;
|
|
84
|
+
}, z.core.$strip>]>;
|
|
85
|
+
permission: z.core.$ZodBranded<z.ZodString, "PermissionKey", "out">;
|
|
86
|
+
count: z.ZodNumber;
|
|
87
|
+
operations: z.ZodNumber;
|
|
88
|
+
firstAt: z.ZodString;
|
|
89
|
+
lastAt: z.ZodString;
|
|
90
|
+
}, z.core.$strip>;
|
|
91
|
+
export type DenialBucket = z.infer<typeof denialBucket>;
|
|
92
|
+
/**
|
|
93
|
+
* The bucketed view of a scope's denial log, plus the facts that keep it honest.
|
|
94
|
+
*
|
|
95
|
+
* `total` counts every row matching the filter, so a caller can tell a capped bucket
|
|
96
|
+
* list from a complete one. `windowOldestAt` is deliberately computed WITHOUT the
|
|
97
|
+
* filter: it is a statement about the log, not about the query — the earliest moment
|
|
98
|
+
* this scope can still speak to. An empty result older than it means "no denials";
|
|
99
|
+
* an empty result at it means "we no longer hold that far back".
|
|
100
|
+
*/
|
|
101
|
+
export declare const denialSummary: z.ZodObject<{
|
|
102
|
+
buckets: z.ZodArray<z.ZodObject<{
|
|
103
|
+
actor: z.ZodUnion<readonly [z.core.$ZodBranded<z.ZodString, "PrincipalId", "out">, z.ZodObject<{
|
|
104
|
+
system: z.core.$ZodBranded<z.ZodString, "ModuleId", "out">;
|
|
105
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
106
|
+
connection: z.ZodString;
|
|
107
|
+
}, z.core.$strip>]>;
|
|
108
|
+
permission: z.core.$ZodBranded<z.ZodString, "PermissionKey", "out">;
|
|
109
|
+
count: z.ZodNumber;
|
|
110
|
+
operations: z.ZodNumber;
|
|
111
|
+
firstAt: z.ZodString;
|
|
112
|
+
lastAt: z.ZodString;
|
|
113
|
+
}, z.core.$strip>>;
|
|
114
|
+
total: z.ZodNumber;
|
|
115
|
+
actors: z.ZodNumber;
|
|
116
|
+
windowOldestAt: z.ZodNullable<z.ZodString>;
|
|
117
|
+
windowNewestAt: z.ZodNullable<z.ZodString>;
|
|
118
|
+
drained: z.ZodNumber;
|
|
119
|
+
}, z.core.$strip>;
|
|
120
|
+
export type DenialSummary = z.infer<typeof denialSummary>;
|
|
121
|
+
//# sourceMappingURL=denial.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"denial.d.ts","sourceRoot":"","sources":["../src/denial.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;iBAc3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY;;;;;;;iBASvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;iBAQvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;iBAgBxB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
|
package/dist/denial.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { actor } from './events.js';
|
|
3
|
+
import { permissionKey, scopeId, tenantId } from './ids.js';
|
|
4
|
+
/**
|
|
5
|
+
* The read side of the K-35 denial log — a scope-local record of every ENFORCED
|
|
6
|
+
* permission refusal (`assertAllowed`), written on the deny path as a fresh
|
|
7
|
+
* autocommit AFTER the rollback it is evidence of.
|
|
8
|
+
*
|
|
9
|
+
* This is the *other* kind of evidence from a conformance receipt. A receipt says
|
|
10
|
+
* "we attempted the attack in CI at commit X"; these rows say "on your data, in
|
|
11
|
+
* production, here is every refusal, by whom, against which key". K-35 made the
|
|
12
|
+
* case for the row itself: a denial is the one event where an actor's intent and
|
|
13
|
+
* the permission model visibly disagree.
|
|
14
|
+
*
|
|
15
|
+
* Two properties of the log shape everything below, and both come straight from
|
|
16
|
+
* K-35's own reasoning about why denials are NOT admin-log entries:
|
|
17
|
+
*
|
|
18
|
+
* 1. **The volume is attacker-influenceable.** A probing client mints unlimited
|
|
19
|
+
* rows, so a newest-first page of raw rows is the wrong default view — 200 rows
|
|
20
|
+
* from one prober hide everyone else. That is why `denialSummary` exists beside
|
|
21
|
+
* the row list, and why K-35 called rate-bucketing sanctionable up front.
|
|
22
|
+
* 2. **The window is a storage bound, not a retention policy.** Rows `drain` rather
|
|
23
|
+
* than expire (K-24's split). Until a Tier-2 sink exists, what is here is simply
|
|
24
|
+
* what has not been pruned — so the summary reports the window's own floor
|
|
25
|
+
* (`windowOldestAt`) rather than letting a caller read absence as "never happened".
|
|
26
|
+
*/
|
|
27
|
+
/** How many denial rows an unbounded read returns — a screenful, newest-first. */
|
|
28
|
+
export const DEFAULT_DENIAL_LIMIT = 50;
|
|
29
|
+
/** The hard ceiling on one page of denial rows, and on one page of buckets. */
|
|
30
|
+
export const DENIAL_LIMIT_MAX = 200;
|
|
31
|
+
/**
|
|
32
|
+
* One recorded refusal. `scopeId` is null for a tenant-node check (one that named no
|
|
33
|
+
* scope); `operation` is null when the denial unwound something that was not an
|
|
34
|
+
* operation invocation. `drainedAt` marks a row already shipped to a Tier-2 sink and
|
|
35
|
+
* therefore eligible to be pruned — bookkeeping, not a judgement about the denial.
|
|
36
|
+
*/
|
|
37
|
+
export const permissionDenial = z.object({
|
|
38
|
+
/** ULID — chronological, so it is also the sort key. */
|
|
39
|
+
id: z.string().min(1),
|
|
40
|
+
/** WHO was refused: a principal, a `{ system }` module, or a `{ connection }`. */
|
|
41
|
+
actor,
|
|
42
|
+
/** The key `assertAllowed` checked and refused. */
|
|
43
|
+
permission: permissionKey,
|
|
44
|
+
tenantId,
|
|
45
|
+
scopeId: scopeId.nullable(),
|
|
46
|
+
/** The operation the denial rolled back, e.g. `workorder/complete`. */
|
|
47
|
+
operation: z.string().nullable(),
|
|
48
|
+
/** ISO 8601. */
|
|
49
|
+
at: z.string().min(1),
|
|
50
|
+
drainedAt: z.string().nullable(),
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* What narrows a denial read. Every field is an exact match except the `since`/`until`
|
|
54
|
+
* bounds on `at` (inclusive lower, exclusive upper) — enough to answer "who probed
|
|
55
|
+
* this key", "what did this actor try", and "what happened during the incident window"
|
|
56
|
+
* without a SQL console.
|
|
57
|
+
*
|
|
58
|
+
* `actor` takes the LOGICAL actor — a bare principal ULID, or the object form for a
|
|
59
|
+
* system/connection actor (`{"system":"invoicing"}`). The writer persists
|
|
60
|
+
* `JSON.stringify(actor)`, so a principal is stored with its quotes; normalizing to that
|
|
61
|
+
* encoding is the reader's job (`storedActor` in the kernel's query builder), not every
|
|
62
|
+
* caller's.
|
|
63
|
+
*/
|
|
64
|
+
export const denialFilter = z.object({
|
|
65
|
+
actor: z.string().min(1).optional(),
|
|
66
|
+
permission: z.string().min(1).optional(),
|
|
67
|
+
operation: z.string().min(1).optional(),
|
|
68
|
+
/** ISO 8601, inclusive. */
|
|
69
|
+
since: z.string().min(1).optional(),
|
|
70
|
+
/** ISO 8601, exclusive. */
|
|
71
|
+
until: z.string().min(1).optional(),
|
|
72
|
+
limit: z.number().int().min(1).max(DENIAL_LIMIT_MAX).optional(),
|
|
73
|
+
});
|
|
74
|
+
/**
|
|
75
|
+
* One (actor, permission) bucket — K-35's "first occurrence + count per actor/key/
|
|
76
|
+
* window", which is the shape that survives a flood. `operations` is the number of
|
|
77
|
+
* DISTINCT operations the actor was refused this key on, and it is the discriminator
|
|
78
|
+
* worth the extra aggregate: one operation refused four hundred times is a broken
|
|
79
|
+
* screen or a misconfigured role, while the same count spread across a dozen
|
|
80
|
+
* operations is someone walking the surface.
|
|
81
|
+
*/
|
|
82
|
+
export const denialBucket = z.object({
|
|
83
|
+
actor,
|
|
84
|
+
permission: permissionKey,
|
|
85
|
+
count: z.number().int().positive(),
|
|
86
|
+
operations: z.number().int().nonnegative(),
|
|
87
|
+
/** ISO 8601 — the first occurrence still in the window (see `windowOldestAt`). */
|
|
88
|
+
firstAt: z.string().min(1),
|
|
89
|
+
lastAt: z.string().min(1),
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* The bucketed view of a scope's denial log, plus the facts that keep it honest.
|
|
93
|
+
*
|
|
94
|
+
* `total` counts every row matching the filter, so a caller can tell a capped bucket
|
|
95
|
+
* list from a complete one. `windowOldestAt` is deliberately computed WITHOUT the
|
|
96
|
+
* filter: it is a statement about the log, not about the query — the earliest moment
|
|
97
|
+
* this scope can still speak to. An empty result older than it means "no denials";
|
|
98
|
+
* an empty result at it means "we no longer hold that far back".
|
|
99
|
+
*/
|
|
100
|
+
export const denialSummary = z.object({
|
|
101
|
+
buckets: z.array(denialBucket),
|
|
102
|
+
/** Rows matching the filter. Bucket counts sum to this when `buckets` is uncapped. */
|
|
103
|
+
total: z.number().int().nonnegative(),
|
|
104
|
+
/** Distinct actors among the matching rows. */
|
|
105
|
+
actors: z.number().int().nonnegative(),
|
|
106
|
+
/**
|
|
107
|
+
* The oldest row STILL HELD, filter ignored — the window's floor, null when the log
|
|
108
|
+
* is empty. The window is a storage bound and not a retention policy (K-35): what is
|
|
109
|
+
* absent before this instant was not necessarily never recorded.
|
|
110
|
+
*/
|
|
111
|
+
windowOldestAt: z.string().nullable(),
|
|
112
|
+
/** The newest row held, filter ignored. */
|
|
113
|
+
windowNewestAt: z.string().nullable(),
|
|
114
|
+
/** Rows already shipped to a Tier-2 sink, filter ignored — prunable, not pruned. */
|
|
115
|
+
drained: z.number().int().nonnegative(),
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=denial.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"denial.js","sourceRoot":"","sources":["../src/denial.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -33,6 +33,18 @@ import { z } from 'zod';
|
|
|
33
33
|
* decision stays a one-line change here for exactly as long as that holds.
|
|
34
34
|
*/
|
|
35
35
|
export declare const PROBLEM_TYPE_BASE = "https://substrat.net/errors";
|
|
36
|
+
/** What a problem body is served as. Never `application/json` — RFC 9457 §3. */
|
|
37
|
+
export declare const PROBLEM_CONTENT_TYPE = "application/problem+json";
|
|
38
|
+
/**
|
|
39
|
+
* The `type` of a failure that has a status and nothing else.
|
|
40
|
+
*
|
|
41
|
+
* RFC 9457 §4.2.1: `about:blank` means "no semantics beyond the status code", and the
|
|
42
|
+
* title is then the status phrase. That is the honest shape for the two cases a
|
|
43
|
+
* transport cannot type — an untyped throw it refuses to call the platform's fault,
|
|
44
|
+
* and a downstream status it is relaying — and it is what keeps the closed taxonomy
|
|
45
|
+
* closed while every body still parses as a problem.
|
|
46
|
+
*/
|
|
47
|
+
export declare const PROBLEM_TYPE_BLANK = "about:blank";
|
|
36
48
|
/**
|
|
37
49
|
* The taxonomy. CLOSED — an open one is a suggestion.
|
|
38
50
|
*
|
|
@@ -140,7 +152,12 @@ export declare const PROBLEM_EXTENSIONS: {
|
|
|
140
152
|
message: z.ZodString;
|
|
141
153
|
}, z.core.$strip>>>;
|
|
142
154
|
}, z.core.$strip>;
|
|
143
|
-
readonly precondition_failed: z.ZodObject<{
|
|
155
|
+
readonly precondition_failed: z.ZodObject<{
|
|
156
|
+
entity: z.ZodOptional<z.ZodObject<{
|
|
157
|
+
entityType: z.ZodString;
|
|
158
|
+
entityId: z.ZodString;
|
|
159
|
+
}, z.core.$strip>>;
|
|
160
|
+
}, z.core.$strip>;
|
|
144
161
|
readonly rate_limited: z.ZodObject<{
|
|
145
162
|
retryAfter: z.ZodOptional<z.ZodNumber>;
|
|
146
163
|
}, z.core.$strip>;
|
|
@@ -163,7 +180,7 @@ export declare const problem: z.ZodObject<{
|
|
|
163
180
|
detail: z.ZodOptional<z.ZodString>;
|
|
164
181
|
instance: z.ZodOptional<z.ZodString>;
|
|
165
182
|
error: z.ZodOptional<z.ZodString>;
|
|
166
|
-
code: z.ZodEnum<{
|
|
183
|
+
code: z.ZodOptional<z.ZodEnum<{
|
|
167
184
|
conflict: "conflict";
|
|
168
185
|
forbidden: "forbidden";
|
|
169
186
|
internal: "internal";
|
|
@@ -174,7 +191,7 @@ export declare const problem: z.ZodObject<{
|
|
|
174
191
|
unauthenticated: "unauthenticated";
|
|
175
192
|
unavailable: "unavailable";
|
|
176
193
|
validation_failed: "validation_failed";
|
|
177
|
-
}
|
|
194
|
+
}>>;
|
|
178
195
|
permission: z.ZodOptional<z.ZodString>;
|
|
179
196
|
entity: z.ZodOptional<z.ZodObject<{
|
|
180
197
|
entityType: z.ZodString;
|
|
@@ -248,6 +265,26 @@ export declare function validationIssuesFrom(error: z.ZodError): ValidationIssue
|
|
|
248
265
|
* quietly widening it in the name of better errors.
|
|
249
266
|
*/
|
|
250
267
|
export declare function toProblem(err: unknown, instance?: string): Problem;
|
|
268
|
+
/**
|
|
269
|
+
* A problem body for a status and nothing else — the `about:blank` form.
|
|
270
|
+
*
|
|
271
|
+
* Two callers, both transports, both relaying rather than raising:
|
|
272
|
+
*
|
|
273
|
+
* - **A throw the taxonomy does not recognise.** Every vertical answers one with the
|
|
274
|
+
* caller's 400 and relays the message, deliberately (#559: an unrecognised throw must
|
|
275
|
+
* not claim to be the platform's fault, because the control plane retries 5xx). That
|
|
276
|
+
* status is a decision about blame, not a claim about what went wrong, and this is the
|
|
277
|
+
* body that says so.
|
|
278
|
+
* - **A status raised somewhere else.** A downstream vertical's own refusal, a Durable
|
|
279
|
+
* Object fault the runtime named (502). Inventing a code for those would put our
|
|
280
|
+
* vocabulary on someone else's failure.
|
|
281
|
+
*
|
|
282
|
+
* `detail` is carried as the caller passes it. That is safe here and not in `toProblem`
|
|
283
|
+
* because a caller of THIS function has a status it chose or received, which means it
|
|
284
|
+
* has already looked at what it is relaying; `toProblem`'s `internal` branch is the one
|
|
285
|
+
* holding an unreviewed message, and it still refuses to disclose it.
|
|
286
|
+
*/
|
|
287
|
+
export declare function problemForStatus(status: number, detail?: string, instance?: string): Problem;
|
|
251
288
|
/**
|
|
252
289
|
* The statuses an operation can actually answer with today, for the emitted document.
|
|
253
290
|
*
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,gCAAgC,CAAC;AAE/D;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;EAWpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAElD,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAEtD;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe;;iBACP,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,iBAAiB;;;iBACnC,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,mBAAmB;;;iBAC/C,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,WAAW;;;iBAC/B,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,WAAW;;;iBAChC,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,UAAU;;;iBACrB,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,mBAAmB;;;iBACrC,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,qBAAqB;;;iBAChD,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,cAAc;;;iBACnC,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,qBAAqB;;;iBAC5C,MAAM,EAAE,GAAG;iBAAE,KAAK,EAAE,gBAAgB;;CACuB,CAAC;AAE1E,0DAA0D;AAC1D,eAAO,MAAM,eAAe;;;iBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|