@substrat-run/contracts 0.138.0 → 0.140.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/connections.d.ts +131 -0
  2. package/dist/connections.d.ts.map +1 -1
  3. package/dist/connections.js +90 -1
  4. package/dist/connections.js.map +1 -1
  5. package/dist/control-plane.d.ts +366 -10
  6. package/dist/control-plane.d.ts.map +1 -1
  7. package/dist/control-plane.js +254 -11
  8. package/dist/control-plane.js.map +1 -1
  9. package/dist/deploy.d.ts +83 -3
  10. package/dist/deploy.d.ts.map +1 -1
  11. package/dist/deploy.js +109 -3
  12. package/dist/deploy.js.map +1 -1
  13. package/dist/email-intent.d.ts +106 -0
  14. package/dist/email-intent.d.ts.map +1 -0
  15. package/dist/email-intent.js +111 -0
  16. package/dist/email-intent.js.map +1 -0
  17. package/dist/entity-state.d.ts +96 -0
  18. package/dist/entity-state.d.ts.map +1 -0
  19. package/dist/entity-state.js +85 -0
  20. package/dist/entity-state.js.map +1 -0
  21. package/dist/findings.d.ts +208 -0
  22. package/dist/findings.d.ts.map +1 -0
  23. package/dist/findings.js +116 -0
  24. package/dist/findings.js.map +1 -0
  25. package/dist/ids.d.ts +13 -0
  26. package/dist/ids.d.ts.map +1 -1
  27. package/dist/ids.js +30 -0
  28. package/dist/ids.js.map +1 -1
  29. package/dist/index.d.ts +5 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +5 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/introspection.d.ts +22 -0
  34. package/dist/introspection.d.ts.map +1 -1
  35. package/dist/introspection.js +1 -1
  36. package/dist/introspection.js.map +1 -1
  37. package/dist/kernel-events.d.ts.map +1 -1
  38. package/dist/kernel-events.js +12 -1
  39. package/dist/kernel-events.js.map +1 -1
  40. package/dist/manifest.d.ts +53 -0
  41. package/dist/manifest.d.ts.map +1 -1
  42. package/dist/manifest.js +40 -2
  43. package/dist/manifest.js.map +1 -1
  44. package/dist/model.d.ts +119 -0
  45. package/dist/model.d.ts.map +1 -1
  46. package/dist/model.js +147 -0
  47. package/dist/model.js.map +1 -1
  48. package/dist/operations.d.ts +117 -1
  49. package/dist/operations.d.ts.map +1 -1
  50. package/dist/operations.js +159 -1
  51. package/dist/operations.js.map +1 -1
  52. package/dist/pagination.d.ts +0 -0
  53. package/dist/pagination.d.ts.map +1 -1
  54. package/dist/pagination.js +0 -0
  55. package/dist/pagination.js.map +1 -1
  56. package/dist/peer.d.ts +34 -0
  57. package/dist/peer.d.ts.map +1 -1
  58. package/dist/peer.js +17 -2
  59. package/dist/peer.js.map +1 -1
  60. package/dist/permission.d.ts +56 -0
  61. package/dist/permission.d.ts.map +1 -1
  62. package/dist/permission.js +58 -3
  63. package/dist/permission.js.map +1 -1
  64. package/dist/registry-diff.d.ts +18 -1
  65. package/dist/registry-diff.d.ts.map +1 -1
  66. package/dist/registry-diff.js +24 -7
  67. package/dist/registry-diff.js.map +1 -1
  68. package/dist/registry.d.ts +6 -0
  69. package/dist/registry.d.ts.map +1 -1
  70. package/dist/registry.js +8 -2
  71. package/dist/registry.js.map +1 -1
  72. package/dist/relay-caller.d.ts +28 -0
  73. package/dist/relay-caller.d.ts.map +1 -0
  74. package/dist/relay-caller.js +28 -0
  75. package/dist/relay-caller.js.map +1 -0
  76. package/dist/sql-tokens.d.ts +9 -1
  77. package/dist/sql-tokens.d.ts.map +1 -1
  78. package/dist/sql-tokens.js +4 -1
  79. package/dist/sql-tokens.js.map +1 -1
  80. package/dist/subject-erasure.d.ts +109 -0
  81. package/dist/subject-erasure.d.ts.map +1 -0
  82. package/dist/subject-erasure.js +93 -0
  83. package/dist/subject-erasure.js.map +1 -0
  84. package/dist/vertical-events.d.ts +1 -0
  85. package/dist/vertical-events.d.ts.map +1 -1
  86. package/dist/vertical-events.js +6 -0
  87. package/dist/vertical-events.js.map +1 -1
  88. package/dist/wire-headers.d.ts +6 -1
  89. package/dist/wire-headers.d.ts.map +1 -1
  90. package/dist/wire-headers.js.map +1 -1
  91. package/package.json +1 -1
@@ -0,0 +1,111 @@
1
+ import { z } from 'zod';
2
+ import { EMAIL_RELAY_MAX_ATTACHMENTS } from './control-plane.js';
3
+ import { entityRef } from './events.js';
4
+ import { dataSubjectId, platformRequestId } from './ids.js';
5
+ /**
6
+ * Email as a transactional platform intent (#2102).
7
+ *
8
+ * A vertical's mail used to be a synchronous POST to the control plane's email relay, made
9
+ * outside the operation's transaction: an operation that threw after the call had still sent
10
+ * the mail, and a send that failed was simply lost. Requested as an intent instead, the send
11
+ * is a row in the scope's own spine written by the operation — so it commits or rolls back
12
+ * with everything else the operation did — and the platform's drain delivers it afterwards,
13
+ * retrying what a provider deferred and saying what became of it as an event.
14
+ *
15
+ * The payload is the relay's body without the caller's identity: the drain reads the row out
16
+ * of the scope that wrote it, so who is asking is never something the payload says.
17
+ */
18
+ export const SEND_EMAIL_KIND = 'send-email';
19
+ export const sendEmailRequest = z
20
+ .object({
21
+ to: z.string().email(),
22
+ subject: z.string().min(1),
23
+ html: z.string().min(1),
24
+ text: z.string().min(1),
25
+ /** The display name for the FROM. With no `from`, the address stays the platform's. */
26
+ fromName: z.string().min(1).optional(),
27
+ /** Send as this tenant address, through the tenant's mail connection that covers it (#2098). */
28
+ from: z.string().email().optional(),
29
+ /** Files from the sending scope, by attachment id. Only with `from`. */
30
+ attachments: z
31
+ .array(z.object({ attachmentId: z.string().min(1) }).strict())
32
+ .max(EMAIL_RELAY_MAX_ATTACHMENTS)
33
+ .optional(),
34
+ /**
35
+ * The entity this mail is about — an invoice, a ticket. The outcome event is written on
36
+ * that entity, so its timeline says the mail went (or did not). Absent, the event is
37
+ * written on the intent itself (`{ entityType: 'platform-request', entityId: <id> }`).
38
+ */
39
+ about: entityRef.optional(),
40
+ /**
41
+ * The person this mail is to or about, when it is one. The row holds their address and the
42
+ * message, so it is personal data in the spine, and this id is what a subject erasure finds
43
+ * it by: a held send is cancelled and the row becomes a tombstone, as an event copy does.
44
+ * Without it, nothing links the row to anybody (kernel-design.md §13.1, limit 7).
45
+ */
46
+ subjectId: dataSubjectId.optional(),
47
+ /**
48
+ * Stamped by `requestEmail` beside `subjectId` — never needed from module code. With
49
+ * `subjectId` it is the outbox's own erasure predicate (`subjectId` + a `piiClass` other
50
+ * than `none`), which the intent redaction already applies to every payload it walks.
51
+ */
52
+ piiClass: z.literal('direct').optional(),
53
+ })
54
+ .strict()
55
+ .refine((m) => m.piiClass === undefined || m.subjectId !== undefined, {
56
+ message: '`piiClass` classifies a `subjectId`; there is none',
57
+ path: ['piiClass'],
58
+ })
59
+ .refine((m) => !m.attachments?.length || m.from !== undefined, {
60
+ message: "attachments ride only with `from` — the platform's own sender carries none",
61
+ path: ['attachments'],
62
+ });
63
+ /** The entity type an outcome is written on when the request named none. */
64
+ export const PLATFORM_REQUEST_ENTITY_TYPE = 'platform-request';
65
+ /** The provider accepted the message. Not delivery to an inbox: "accepted" is all most say. */
66
+ export const EMAIL_SENT = 'email.sent';
67
+ /** The provider, or the platform on its behalf, refused the message for good. Not retried. */
68
+ export const EMAIL_REFUSED = 'email.refused';
69
+ /** Every attempt the platform allows met a transient failure. The send is over. */
70
+ export const EMAIL_DEAD_LETTERED = 'email.dead-lettered';
71
+ export const emailOutcomeType = z.enum([EMAIL_SENT, EMAIL_REFUSED, EMAIL_DEAD_LETTERED]);
72
+ /**
73
+ * The outcome event's payload. Deliberately carries no address and no provider prose: an
74
+ * event is retained beyond the person it would name, and a provider's refusal can quote
75
+ * them. The full refusal stays on the intent row (`ctx.platformRequests`), which erasure
76
+ * reaches; the event says which request, what happened, and the codes a screen branches on.
77
+ */
78
+ export const emailOutcomePayload = z
79
+ .object({
80
+ /** The id `requestEmail` returned — how a vertical joins the event to what it asked. */
81
+ request: platformRequestId,
82
+ about: entityRef.nullable(),
83
+ /** Whose sender carried it: the platform's own address, or a tenant connection's. */
84
+ sender: z.enum(['platform', 'tenant']),
85
+ /** The provider's id for the message, when the provider gives one. */
86
+ messageId: z.string().min(1).nullable(),
87
+ /** Attempts made, this one included. */
88
+ attempts: z.number().int().positive(),
89
+ /** The taxonomy code of a refusal or of the last transient failure; `null` on `email.sent`. */
90
+ code: z.string().min(1).nullable(),
91
+ /** The provider's HTTP status, when the failure carried one. */
92
+ status: z.number().int().nullable(),
93
+ })
94
+ .strict();
95
+ /**
96
+ * An event a platform-request SETTLE writes into the scope (#2102) — the drain's way of saying
97
+ * what became of an intent as a fact the vertical can consume, rather than a row it must poll.
98
+ *
99
+ * The drain names the type, the entity and the payload; the scope stamps the rest (id, time,
100
+ * tenant, scope, the kernel as actor) and writes it in the same transaction as the settle, and
101
+ * only when that settle moved the row out of `pending`. A type outside this enum is refused,
102
+ * so the settle route cannot be used to write an arbitrary event into a scope.
103
+ */
104
+ export const platformOutcomeEvent = z
105
+ .object({
106
+ type: emailOutcomeType,
107
+ entity: entityRef,
108
+ payload: emailOutcomePayload,
109
+ })
110
+ .strict();
111
+ //# sourceMappingURL=email-intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-intent.js","sourceRoot":"","sources":["../src/email-intent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Entity archive and trash (#119) — the vocabulary the kernel and a module share.
3
+ *
4
+ * Three different acts get habitually conflated, and only one of them lives here:
5
+ *
6
+ * - **Subject erasure** (#37, K-37) removes a person's data from everywhere the platform can
7
+ * reach, history included. Not reversible, and not about an entity.
8
+ * - **Tenant delete and retention** (#36) removes a whole tenant after a grace window. Not
9
+ * about an entity either.
10
+ * - **Archive and trash** — this — hide ONE entity from active views and keep it. Both are
11
+ * reversible, both keep the row, and neither touches history: the events about the entity
12
+ * stay exactly as they were.
13
+ *
14
+ * Archive and trash are deliberately two acts with two permission keys. Archive says "done
15
+ * with this, keep it": the row stays readable through the entity's ordinary read authority.
16
+ * Trash says "I meant to delete this": the row stays recoverable, but only someone holding
17
+ * the declared trash key can see it or bring it back.
18
+ *
19
+ * ## The state is two columns, and the visible state is derived from them
20
+ *
21
+ * An entity that declares either one gets kernel-owned columns on its own table,
22
+ * `_substrat_archived_at` and `_substrat_trashed_at`. Stored apart rather than as one enum,
23
+ * so trash never forgets an archive: trashing an archived row and restoring it brings it back
24
+ * archived, never silently into the active view. What a reader sees is one state, derived:
25
+ *
26
+ * trashed when `_substrat_trashed_at` is set
27
+ * archived else when `_substrat_archived_at` is set
28
+ * active otherwise
29
+ */
30
+ import { z } from 'zod';
31
+ /** The state an archivable or trashable entity is in, as a reader sees it. */
32
+ export declare const entityStateName: z.ZodEnum<{
33
+ active: "active";
34
+ archived: "archived";
35
+ trashed: "trashed";
36
+ }>;
37
+ export type EntityStateName = z.infer<typeof entityStateName>;
38
+ /** The kernel-owned column that records an archive. */
39
+ export declare const ARCHIVED_AT_COLUMN = "_substrat_archived_at";
40
+ /** The kernel-owned column that records a trash. */
41
+ export declare const TRASHED_AT_COLUMN = "_substrat_trashed_at";
42
+ /**
43
+ * One entity's declaration, as the manifest carries it.
44
+ *
45
+ * Written into a model as `archive: { permission }` / `trash: { permission }` on the entity
46
+ * (`defineEntities`); `manifestEntities()` turns that into this and fills in `table` and
47
+ * `idColumn` from the registry, as it does for `searchables`. A hand-written manifest (an
48
+ * engine's) may declare it directly.
49
+ *
50
+ * `archivePermission` gates `ctx.archive` and `ctx.unarchive`; `trashPermission` gates
51
+ * `ctx.trash`, `ctx.restore` and the trashed readers. At least one is required. A key here
52
+ * must be one the module declares.
53
+ */
54
+ export declare const entityStateDeclaration: z.ZodObject<{
55
+ entityType: z.ZodString;
56
+ archivePermission: z.ZodOptional<z.core.$ZodBranded<z.ZodString, "PermissionKey", "out">>;
57
+ trashPermission: z.ZodOptional<z.core.$ZodBranded<z.ZodString, "PermissionKey", "out">>;
58
+ purgeAfterDays: z.ZodOptional<z.ZodNumber>;
59
+ table: z.ZodOptional<z.ZodString>;
60
+ idColumn: z.ZodOptional<z.ZodString>;
61
+ }, z.core.$strip>;
62
+ export type EntityStateDeclaration = z.infer<typeof entityStateDeclaration>;
63
+ /** `ctx.archive` moved an entity out of the active view. Entity: the entity. */
64
+ export declare const ENTITY_ARCHIVED = "entity.archived";
65
+ /** `ctx.unarchive` brought an archived entity back. Entity: the entity. */
66
+ export declare const ENTITY_UNARCHIVED = "entity.unarchived";
67
+ /** `ctx.trash` moved an entity into the trash. Entity: the entity. */
68
+ export declare const ENTITY_TRASHED = "entity.trashed";
69
+ /** `ctx.restore` took an entity out of the trash, back to the state it was trashed from. */
70
+ export declare const ENTITY_RESTORED = "entity.restored";
71
+ /**
72
+ * The one payload all four carry: the entity, and the visible state before and after.
73
+ *
74
+ * Who did it and when are not here — they are the envelope's actor and `occurredAt`, stamped
75
+ * by the kernel, with the K-34 authorization chain beside them. `from` and `to` are the
76
+ * derived states, so `entity.restored` says where the entity went back to (`active` or
77
+ * `archived`) without the reader having to replay anything.
78
+ */
79
+ export declare const entityStateChangedPayload: z.ZodObject<{
80
+ entity: z.ZodObject<{
81
+ entityType: z.ZodString;
82
+ entityId: z.ZodString;
83
+ }, z.core.$strip>;
84
+ from: z.ZodEnum<{
85
+ active: "active";
86
+ archived: "archived";
87
+ trashed: "trashed";
88
+ }>;
89
+ to: z.ZodEnum<{
90
+ active: "active";
91
+ archived: "archived";
92
+ trashed: "trashed";
93
+ }>;
94
+ }, z.core.$strip>;
95
+ export type EntityStateChangedPayload = z.infer<typeof entityStateChangedPayload>;
96
+ //# sourceMappingURL=entity-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-state.d.ts","sourceRoot":"","sources":["../src/entity-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,8EAA8E;AAC9E,eAAO,MAAM,eAAe;;;;EAA4C,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,uDAAuD;AACvD,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAC1D,oDAAoD;AACpD,eAAO,MAAM,iBAAiB,yBAAyB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB;;;;;;;iBAYjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,gFAAgF;AAChF,eAAO,MAAM,eAAe,oBAAoB,CAAC;AACjD,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AACrD,sEAAsE;AACtE,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAC/C,4FAA4F;AAC5F,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;iBAIpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Entity archive and trash (#119) — the vocabulary the kernel and a module share.
3
+ *
4
+ * Three different acts get habitually conflated, and only one of them lives here:
5
+ *
6
+ * - **Subject erasure** (#37, K-37) removes a person's data from everywhere the platform can
7
+ * reach, history included. Not reversible, and not about an entity.
8
+ * - **Tenant delete and retention** (#36) removes a whole tenant after a grace window. Not
9
+ * about an entity either.
10
+ * - **Archive and trash** — this — hide ONE entity from active views and keep it. Both are
11
+ * reversible, both keep the row, and neither touches history: the events about the entity
12
+ * stay exactly as they were.
13
+ *
14
+ * Archive and trash are deliberately two acts with two permission keys. Archive says "done
15
+ * with this, keep it": the row stays readable through the entity's ordinary read authority.
16
+ * Trash says "I meant to delete this": the row stays recoverable, but only someone holding
17
+ * the declared trash key can see it or bring it back.
18
+ *
19
+ * ## The state is two columns, and the visible state is derived from them
20
+ *
21
+ * An entity that declares either one gets kernel-owned columns on its own table,
22
+ * `_substrat_archived_at` and `_substrat_trashed_at`. Stored apart rather than as one enum,
23
+ * so trash never forgets an archive: trashing an archived row and restoring it brings it back
24
+ * archived, never silently into the active view. What a reader sees is one state, derived:
25
+ *
26
+ * trashed when `_substrat_trashed_at` is set
27
+ * archived else when `_substrat_archived_at` is set
28
+ * active otherwise
29
+ */
30
+ import { z } from 'zod';
31
+ import { entityRef } from './events.js';
32
+ import { permissionKey } from './ids.js';
33
+ /** The state an archivable or trashable entity is in, as a reader sees it. */
34
+ export const entityStateName = z.enum(['active', 'archived', 'trashed']);
35
+ /** The kernel-owned column that records an archive. */
36
+ export const ARCHIVED_AT_COLUMN = '_substrat_archived_at';
37
+ /** The kernel-owned column that records a trash. */
38
+ export const TRASHED_AT_COLUMN = '_substrat_trashed_at';
39
+ /**
40
+ * One entity's declaration, as the manifest carries it.
41
+ *
42
+ * Written into a model as `archive: { permission }` / `trash: { permission }` on the entity
43
+ * (`defineEntities`); `manifestEntities()` turns that into this and fills in `table` and
44
+ * `idColumn` from the registry, as it does for `searchables`. A hand-written manifest (an
45
+ * engine's) may declare it directly.
46
+ *
47
+ * `archivePermission` gates `ctx.archive` and `ctx.unarchive`; `trashPermission` gates
48
+ * `ctx.trash`, `ctx.restore` and the trashed readers. At least one is required. A key here
49
+ * must be one the module declares.
50
+ */
51
+ export const entityStateDeclaration = z.object({
52
+ entityType: z.string().min(1),
53
+ archivePermission: permissionKey.optional(),
54
+ trashPermission: permissionKey.optional(),
55
+ /**
56
+ * The purge horizon (#119): an entity trashed longer than this many days is permanently
57
+ * deleted by the platform's sweep, through the module's own `trashed: 'purges'` operation
58
+ * (run as a derived schedule — `purgeSchedulesOf`). Requires `trashPermission`.
59
+ */
60
+ purgeAfterDays: z.number().int().positive().optional(),
61
+ table: z.string().min(1).optional(),
62
+ idColumn: z.string().min(1).optional(),
63
+ });
64
+ /** `ctx.archive` moved an entity out of the active view. Entity: the entity. */
65
+ export const ENTITY_ARCHIVED = 'entity.archived';
66
+ /** `ctx.unarchive` brought an archived entity back. Entity: the entity. */
67
+ export const ENTITY_UNARCHIVED = 'entity.unarchived';
68
+ /** `ctx.trash` moved an entity into the trash. Entity: the entity. */
69
+ export const ENTITY_TRASHED = 'entity.trashed';
70
+ /** `ctx.restore` took an entity out of the trash, back to the state it was trashed from. */
71
+ export const ENTITY_RESTORED = 'entity.restored';
72
+ /**
73
+ * The one payload all four carry: the entity, and the visible state before and after.
74
+ *
75
+ * Who did it and when are not here — they are the envelope's actor and `occurredAt`, stamped
76
+ * by the kernel, with the K-34 authorization chain beside them. `from` and `to` are the
77
+ * derived states, so `entity.restored` says where the entity went back to (`active` or
78
+ * `archived`) without the reader having to replay anything.
79
+ */
80
+ export const entityStateChangedPayload = z.object({
81
+ entity: entityRef,
82
+ from: entityStateName,
83
+ to: entityStateName,
84
+ });
85
+ //# sourceMappingURL=entity-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-state.js","sourceRoot":"","sources":["../src/entity-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;AAGzE,uDAAuD;AACvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAC1D,oDAAoD;AACpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7B,iBAAiB,EAAE,aAAa,CAAC,QAAQ,EAAE;IAC3C,eAAe,EAAE,aAAa,CAAC,QAAQ,EAAE;IACzC;;;;OAIG;IACH,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACtD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC;AAGH,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AACjD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AACrD,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAC/C,4FAA4F;AAC5F,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,eAAe;IACrB,EAAE,EAAE,eAAe;CACpB,CAAC,CAAC"}
@@ -0,0 +1,208 @@
1
+ import { z } from 'zod';
2
+ import { tenantId } from './ids.js';
3
+ /**
4
+ * Findings (#1748): anomalies a tenant triages like email, in one inbox with one lifecycle.
5
+ *
6
+ * - `invariant`: a rule was broken — expected 0, observed N (a scheduled run that failed).
7
+ * - `drift`: unusual against an expectation — a freshness expectation gone stale.
8
+ * - `recurring`: one failure shape, seen again and again — the tenant's own projection of an
9
+ * `_substrat_issues` fingerprint (#1233), counted over that tenant's occurrences only.
10
+ *
11
+ * A finding is TENANT-scoped by construction: its counters, versions and evidence come from
12
+ * that tenant's own rows, so the fleet aggregate it shares a fingerprint with is never visible
13
+ * through it. It carries no free text from the evidence (no message, no upstream reference):
14
+ * the evidence ref names where the rows are, and the reader reads them through its own
15
+ * tenant-forced view.
16
+ */
17
+ export declare const findingKind: z.ZodEnum<{
18
+ drift: "drift";
19
+ invariant: "invariant";
20
+ recurring: "recurring";
21
+ }>;
22
+ export type FindingKind = z.infer<typeof findingKind>;
23
+ /**
24
+ * The lifecycle. `_substrat_issues` maps onto it 1:1 — new → `open`, regressed → `open` with
25
+ * `regressed`, resolved → `resolved`, ignored → `suppressed` — and `acked` is the one state an
26
+ * issue has no word for: "somebody is on it".
27
+ */
28
+ export declare const findingStatus: z.ZodEnum<{
29
+ acked: "acked";
30
+ open: "open";
31
+ resolved: "resolved";
32
+ suppressed: "suppressed";
33
+ }>;
34
+ export type FindingStatus = z.infer<typeof findingStatus>;
35
+ /**
36
+ * What a verdict may SET. `suppressed` is never set directly: it is what a suppress RULE does,
37
+ * because a suppression without a scope and an expiry is a finding nobody will ever see again.
38
+ */
39
+ export declare const findingStatusInput: z.ZodEnum<{
40
+ acked: "acked";
41
+ open: "open";
42
+ resolved: "resolved";
43
+ }>;
44
+ export type FindingStatusInput = z.infer<typeof findingStatusInput>;
45
+ export declare const findingSeverity: z.ZodEnum<{
46
+ critical: "critical";
47
+ info: "info";
48
+ warning: "warning";
49
+ }>;
50
+ export type FindingSeverity = z.infer<typeof findingSeverity>;
51
+ /** Where the occurrences behind a finding are read: the reader's own tenant-forced view of them. */
52
+ export declare const findingEvidence: z.ZodDiscriminatedUnion<[z.ZodObject<{
53
+ source: z.ZodLiteral<"ops-failures">;
54
+ fingerprint: z.ZodString;
55
+ }, z.core.$strip>, z.ZodObject<{
56
+ source: z.ZodLiteral<"sweep-runs">;
57
+ kind: z.ZodEnum<{
58
+ freshness: "freshness";
59
+ schedule: "schedule";
60
+ }>;
61
+ unit: z.ZodString;
62
+ }, z.core.$strip>], "source">;
63
+ export type FindingEvidence = z.infer<typeof findingEvidence>;
64
+ /**
65
+ * Why the finding is believed to have started, with the reason stated. Only `deploy` today: a
66
+ * finding that comes back under a version other than the one it was resolved under names the
67
+ * new version. Nothing is claimed when nothing changed — no cause is better than an invented one.
68
+ */
69
+ export declare const findingLikelyCause: z.ZodObject<{
70
+ kind: z.ZodLiteral<"deploy">;
71
+ version: z.ZodString;
72
+ reason: z.ZodString;
73
+ }, z.core.$strip>;
74
+ export type FindingLikelyCause = z.infer<typeof findingLikelyCause>;
75
+ export declare const findingEntry: z.ZodObject<{
76
+ id: z.ZodString;
77
+ tenantId: z.core.$ZodBranded<z.ZodString, "TenantId", "out">;
78
+ kind: z.ZodEnum<{
79
+ drift: "drift";
80
+ invariant: "invariant";
81
+ recurring: "recurring";
82
+ }>;
83
+ subject: z.ZodString;
84
+ status: z.ZodEnum<{
85
+ acked: "acked";
86
+ open: "open";
87
+ resolved: "resolved";
88
+ suppressed: "suppressed";
89
+ }>;
90
+ regressed: z.ZodBoolean;
91
+ severity: z.ZodEnum<{
92
+ critical: "critical";
93
+ info: "info";
94
+ warning: "warning";
95
+ }>;
96
+ title: z.ZodString;
97
+ operation: z.ZodNullable<z.ZodString>;
98
+ codes: z.ZodArray<z.ZodEnum<{
99
+ conflict: "conflict";
100
+ forbidden: "forbidden";
101
+ internal: "internal";
102
+ not_found: "not_found";
103
+ permission_denied: "permission_denied";
104
+ precondition_failed: "precondition_failed";
105
+ rate_limited: "rate_limited";
106
+ unauthenticated: "unauthenticated";
107
+ unavailable: "unavailable";
108
+ validation_failed: "validation_failed";
109
+ }>>;
110
+ vertical: z.ZodNullable<z.ZodString>;
111
+ scopeId: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ScopeId", "out">>;
112
+ count: z.ZodNumber;
113
+ firstSeen: z.core.$ZodBranded<z.ZodString, "Instant", "out">;
114
+ lastSeen: z.core.$ZodBranded<z.ZodString, "Instant", "out">;
115
+ lastVersion: z.ZodNullable<z.ZodString>;
116
+ resolvedVersion: z.ZodNullable<z.ZodString>;
117
+ resolvedAt: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "Instant", "out">>;
118
+ resolution: z.ZodNullable<z.ZodEnum<{
119
+ stale: "stale";
120
+ verdict: "verdict";
121
+ }>>;
122
+ acknowledgedAt: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "Instant", "out">>;
123
+ ruleId: z.ZodNullable<z.ZodString>;
124
+ evidence: z.ZodDiscriminatedUnion<[z.ZodObject<{
125
+ source: z.ZodLiteral<"ops-failures">;
126
+ fingerprint: z.ZodString;
127
+ }, z.core.$strip>, z.ZodObject<{
128
+ source: z.ZodLiteral<"sweep-runs">;
129
+ kind: z.ZodEnum<{
130
+ freshness: "freshness";
131
+ schedule: "schedule";
132
+ }>;
133
+ unit: z.ZodString;
134
+ }, z.core.$strip>], "source">;
135
+ likelyCause: z.ZodNullable<z.ZodObject<{
136
+ kind: z.ZodLiteral<"deploy">;
137
+ version: z.ZodString;
138
+ reason: z.ZodString;
139
+ }, z.core.$strip>>;
140
+ }, z.core.$strip>;
141
+ export type FindingEntry = z.infer<typeof findingEntry>;
142
+ /** The longest a suppression may last. A permanent "never show me this" is not a rule; it is a blind spot. */
143
+ export declare const FINDING_RULE_MAX_DAYS = 90;
144
+ /**
145
+ * A suppress rule (#1748): a scope plus an expiry. A matching occurrence is still COUNTED on its
146
+ * finding, but the finding stays `suppressed` instead of opening. Each set field narrows; at
147
+ * least one must be set, so a rule never silences a whole tenant.
148
+ */
149
+ export declare const findingRuleInput: z.ZodObject<{
150
+ kind: z.ZodOptional<z.ZodEnum<{
151
+ drift: "drift";
152
+ invariant: "invariant";
153
+ recurring: "recurring";
154
+ }>>;
155
+ operation: z.ZodOptional<z.ZodString>;
156
+ code: z.ZodOptional<z.ZodEnum<{
157
+ conflict: "conflict";
158
+ forbidden: "forbidden";
159
+ internal: "internal";
160
+ not_found: "not_found";
161
+ permission_denied: "permission_denied";
162
+ precondition_failed: "precondition_failed";
163
+ rate_limited: "rate_limited";
164
+ unauthenticated: "unauthenticated";
165
+ unavailable: "unavailable";
166
+ validation_failed: "validation_failed";
167
+ }>>;
168
+ subject: z.ZodOptional<z.ZodString>;
169
+ expiresAt: z.core.$ZodBranded<z.ZodString, "Instant", "out">;
170
+ reason: z.ZodString;
171
+ }, z.core.$strip>;
172
+ export type FindingRuleInput = z.infer<typeof findingRuleInput>;
173
+ export declare const findingRuleEntry: z.ZodObject<{
174
+ id: z.ZodString;
175
+ tenantId: z.core.$ZodBranded<z.ZodString, "TenantId", "out">;
176
+ kind: z.ZodNullable<z.ZodEnum<{
177
+ drift: "drift";
178
+ invariant: "invariant";
179
+ recurring: "recurring";
180
+ }>>;
181
+ operation: z.ZodNullable<z.ZodString>;
182
+ code: z.ZodNullable<z.ZodEnum<{
183
+ conflict: "conflict";
184
+ forbidden: "forbidden";
185
+ internal: "internal";
186
+ not_found: "not_found";
187
+ permission_denied: "permission_denied";
188
+ precondition_failed: "precondition_failed";
189
+ rate_limited: "rate_limited";
190
+ unauthenticated: "unauthenticated";
191
+ unavailable: "unavailable";
192
+ validation_failed: "validation_failed";
193
+ }>>;
194
+ subject: z.ZodNullable<z.ZodString>;
195
+ expiresAt: z.core.$ZodBranded<z.ZodString, "Instant", "out">;
196
+ reason: z.ZodString;
197
+ createdBy: z.ZodString;
198
+ createdAt: z.core.$ZodBranded<z.ZodString, "Instant", "out">;
199
+ }, z.core.$strip>;
200
+ export type FindingRuleEntry = z.infer<typeof findingRuleEntry>;
201
+ /** Filter for `listFindings`. `tenantId` absent is the staff fleet read; a tenant's read is forced to it. */
202
+ export interface FindingFilter {
203
+ tenantId?: z.infer<typeof tenantId>;
204
+ status?: FindingStatus;
205
+ kind?: FindingKind;
206
+ limit?: number;
207
+ }
208
+ //# sourceMappingURL=findings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../src/findings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAoB,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGtD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW;;;;EAA8C,CAAC;AACvE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;EAAsD,CAAC;AACjF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;EAAwC,CAAC;AACxE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,eAAe;;;;EAA0C,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,oGAAoG;AACpG,eAAO,MAAM,eAAe;;;;;;;;;;6BAK1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;iBAK7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmCvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,8GAA8G;AAC9G,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;iBAWzB,CAAC;AACL,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;iBAW3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,6GAA6G;AAC7G,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1,116 @@
1
+ import { z } from 'zod';
2
+ import { instant, scopeId, tenantId } from './ids.js';
3
+ import { errorCode } from './errors.js';
4
+ /**
5
+ * Findings (#1748): anomalies a tenant triages like email, in one inbox with one lifecycle.
6
+ *
7
+ * - `invariant`: a rule was broken — expected 0, observed N (a scheduled run that failed).
8
+ * - `drift`: unusual against an expectation — a freshness expectation gone stale.
9
+ * - `recurring`: one failure shape, seen again and again — the tenant's own projection of an
10
+ * `_substrat_issues` fingerprint (#1233), counted over that tenant's occurrences only.
11
+ *
12
+ * A finding is TENANT-scoped by construction: its counters, versions and evidence come from
13
+ * that tenant's own rows, so the fleet aggregate it shares a fingerprint with is never visible
14
+ * through it. It carries no free text from the evidence (no message, no upstream reference):
15
+ * the evidence ref names where the rows are, and the reader reads them through its own
16
+ * tenant-forced view.
17
+ */
18
+ export const findingKind = z.enum(['invariant', 'drift', 'recurring']);
19
+ /**
20
+ * The lifecycle. `_substrat_issues` maps onto it 1:1 — new → `open`, regressed → `open` with
21
+ * `regressed`, resolved → `resolved`, ignored → `suppressed` — and `acked` is the one state an
22
+ * issue has no word for: "somebody is on it".
23
+ */
24
+ export const findingStatus = z.enum(['open', 'acked', 'resolved', 'suppressed']);
25
+ /**
26
+ * What a verdict may SET. `suppressed` is never set directly: it is what a suppress RULE does,
27
+ * because a suppression without a scope and an expiry is a finding nobody will ever see again.
28
+ */
29
+ export const findingStatusInput = z.enum(['open', 'acked', 'resolved']);
30
+ export const findingSeverity = z.enum(['info', 'warning', 'critical']);
31
+ /** Where the occurrences behind a finding are read: the reader's own tenant-forced view of them. */
32
+ export const findingEvidence = z.discriminatedUnion('source', [
33
+ /** `GET /ops-failures`, grouped by this fingerprint. */
34
+ z.object({ source: z.literal('ops-failures'), fingerprint: z.string().min(1) }),
35
+ /** `GET /sweep-runs?kind=…&unit=…`. */
36
+ z.object({ source: z.literal('sweep-runs'), kind: z.enum(['schedule', 'freshness']), unit: z.string().min(1) }),
37
+ ]);
38
+ /**
39
+ * Why the finding is believed to have started, with the reason stated. Only `deploy` today: a
40
+ * finding that comes back under a version other than the one it was resolved under names the
41
+ * new version. Nothing is claimed when nothing changed — no cause is better than an invented one.
42
+ */
43
+ export const findingLikelyCause = z.object({
44
+ kind: z.literal('deploy'),
45
+ /** The version-registry id the finding was seen under. */
46
+ version: z.string().min(1),
47
+ reason: z.string().min(1),
48
+ });
49
+ export const findingEntry = z.object({
50
+ id: z.string().min(1),
51
+ tenantId,
52
+ kind: findingKind,
53
+ /** The dedupe key within (tenant, kind): a fingerprint, or a sweep unit. */
54
+ subject: z.string().min(1),
55
+ status: findingStatus,
56
+ /** A fresh occurrence reopened a resolved finding. Cleared by the next `resolved` verdict. */
57
+ regressed: z.boolean(),
58
+ severity: findingSeverity,
59
+ title: z.string().min(1),
60
+ operation: z.string().nullable(),
61
+ /** The taxonomy codes seen on this finding, newest first, at most five. */
62
+ codes: z.array(errorCode),
63
+ vertical: z.string().nullable(),
64
+ scopeId: scopeId.nullable(),
65
+ /** This tenant's occurrences since the finding was first seen — never a fleet count. */
66
+ count: z.number().int().positive(),
67
+ firstSeen: instant,
68
+ lastSeen: instant,
69
+ lastVersion: z.string().nullable(),
70
+ /** `lastVersion` when the last `resolved` landed — the X of "resolved under X, seen again under Y". */
71
+ resolvedVersion: z.string().nullable(),
72
+ resolvedAt: instant.nullable(),
73
+ /**
74
+ * How the last resolve happened: a person's `verdict`, or `stale` — an open finding that went
75
+ * quiet for the whole retention window, resolved (and audited) by the retention pass rather
76
+ * than deleted out from under whoever was watching it.
77
+ */
78
+ resolution: z.enum(['verdict', 'stale']).nullable(),
79
+ acknowledgedAt: instant.nullable(),
80
+ /** The suppress rule holding it, while `suppressed`. */
81
+ ruleId: z.string().nullable(),
82
+ evidence: findingEvidence,
83
+ likelyCause: findingLikelyCause.nullable(),
84
+ });
85
+ /** The longest a suppression may last. A permanent "never show me this" is not a rule; it is a blind spot. */
86
+ export const FINDING_RULE_MAX_DAYS = 90;
87
+ /**
88
+ * A suppress rule (#1748): a scope plus an expiry. A matching occurrence is still COUNTED on its
89
+ * finding, but the finding stays `suppressed` instead of opening. Each set field narrows; at
90
+ * least one must be set, so a rule never silences a whole tenant.
91
+ */
92
+ export const findingRuleInput = z
93
+ .object({
94
+ kind: findingKind.optional(),
95
+ operation: z.string().min(1).optional(),
96
+ code: errorCode.optional(),
97
+ subject: z.string().min(1).optional(),
98
+ expiresAt: instant,
99
+ reason: z.string().min(1).max(500),
100
+ })
101
+ .refine((r) => r.kind !== undefined || r.operation !== undefined || r.code !== undefined || r.subject !== undefined, {
102
+ message: 'a suppress rule names at least one of kind, operation, code or subject',
103
+ });
104
+ export const findingRuleEntry = z.object({
105
+ id: z.string().min(1),
106
+ tenantId,
107
+ kind: findingKind.nullable(),
108
+ operation: z.string().nullable(),
109
+ code: errorCode.nullable(),
110
+ subject: z.string().nullable(),
111
+ expiresAt: instant,
112
+ reason: z.string(),
113
+ createdBy: z.string(),
114
+ createdAt: instant,
115
+ });
116
+ //# sourceMappingURL=findings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings.js","sourceRoot":"","sources":["../src/findings.ts"],"names":[],"mappings":"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"}