@fabrika/control-contract 0.5.5 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabrika/control-contract",
3
- "version": "0.5.5",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,8 +25,8 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "zod": "^3.25.76",
28
- "@fabrika/auth-core": "0.5.5",
29
- "@fabrika/app": "0.5.5",
30
- "@fabrika/provider-contract": "0.5.5"
28
+ "@fabrika/auth-core": "0.7.0",
29
+ "@fabrika/app": "0.7.0",
30
+ "@fabrika/provider-contract": "0.7.0"
31
31
  }
32
32
  }
package/src/access.ts CHANGED
@@ -36,6 +36,15 @@ export const CONTROL_ACTIONS = Object.freeze({
36
36
  * the operator role does not hold it; only a human user principal in the console may use it. Scoped by app/environment.
37
37
  */
38
38
  ACCESS_APPROVE: 'access.approve',
39
+ /**
40
+ * List relayed mail and read its metadata. Scoped by app/environment for an application's messages; an installation
41
+ * sender's messages need a global grant.
42
+ */
43
+ MAIL_READ: 'mail.read',
44
+ /** Read a relayed message's recipient, subject and bodies. Global. */
45
+ MAIL_CONTENT_READ: 'mail.content.read',
46
+ /** Release or drop a held message, requeue a final one, and change the gate's rules. Global. */
47
+ MAIL_MANAGE: 'mail.manage',
39
48
  })
40
49
 
41
50
  /**
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from './access.js'
6
6
  export * from './builder-bootstrap.js'
7
7
  export * from './builder-connections.js'
8
8
  export * from './builder-pairing.js'
9
+ export * from './mail.js'
9
10
 
10
11
  export type { JsonValue }
11
12
 
@@ -566,6 +567,7 @@ export type {
566
567
  PutAppEnvironmentInput,
567
568
  PutAppSecretInput,
568
569
  PutAppVarInput,
570
+ RotateTokenKeyResponse,
569
571
  RunIdInput,
570
572
  RunListInput,
571
573
  SecretValueInput,
package/src/mail.ts ADDED
@@ -0,0 +1,96 @@
1
+ /** The mail relay as the console reads and steers it. */
2
+
3
+ /**
4
+ * `held` waits for a person, `queued` for the dispatcher, `sending` is one attempt a dispatcher claimed. `sent`,
5
+ * `failed` (not sent), `unknown` (may have left) and `dropped` are final.
6
+ */
7
+ export type MailMessageState = 'held' | 'queued' | 'sending' | 'sent' | 'failed' | 'unknown' | 'dropped'
8
+
9
+ export type MailRuleAction = 'pass' | 'hold' | 'drop'
10
+
11
+ /** A relayed message's metadata, behind `mail.read`. It carries no recipient, subject or body. */
12
+ export interface MailMessageDto {
13
+ readonly id: string
14
+ /** `<app>@<env>`, or `installation:<service>` for an installation sender. */
15
+ readonly sender: string
16
+ readonly recipientDomain: string
17
+ readonly state: MailMessageState
18
+ /** The rule that decided the message at acceptance; null when none did. */
19
+ readonly ruleId: string | null
20
+ /** Who released or dropped it, or requeued it once it was final. */
21
+ readonly decidedBy: string | null
22
+ readonly attempts: number
23
+ readonly nextAttemptAt: number | null
24
+ readonly providerMessageId: string | null
25
+ /**
26
+ * The `EmailDeliveryErrorCode` of the last failed attempt, or `dispatch_interrupted`: the dispatcher stopped after it
27
+ * claimed the attempt, before it recorded an outcome.
28
+ */
29
+ readonly errorCode: string | null
30
+ readonly createdAt: number
31
+ readonly updatedAt: number
32
+ }
33
+
34
+ /** One attachment as the console shows it: its name, type and size, never its bytes. */
35
+ export interface MailAttachmentDto {
36
+ readonly filename: string
37
+ readonly contentType: string
38
+ readonly bytes: number
39
+ }
40
+
41
+ /** What the recipient sees, behind `mail.content.read`. */
42
+ export interface MailMessageContentDto {
43
+ readonly to: string
44
+ readonly subject: string
45
+ readonly text: string
46
+ readonly html?: string
47
+ readonly replyTo?: string
48
+ readonly fromName?: string
49
+ readonly unsubscribeUrl?: string
50
+ readonly attachments: readonly MailAttachmentDto[]
51
+ }
52
+
53
+ /** `withheld`: the caller does not hold `mail.content.read`. `purged`: the relay no longer keeps it. */
54
+ export type MailContentDto =
55
+ | { readonly kind: 'withheld' }
56
+ | { readonly kind: 'purged' }
57
+ | { readonly kind: 'present'; readonly message: MailMessageContentDto }
58
+
59
+ export interface MailMessageDetailDto extends MailMessageDto {
60
+ readonly content: MailContentDto
61
+ /** Whether a requeue would send it again now: `failed`, or `unknown` over an idempotent transport, with its content kept. */
62
+ readonly requeueable: boolean
63
+ }
64
+
65
+ export interface MailMessageListInput {
66
+ readonly state?: MailMessageState
67
+ /** One sender: `<app>@<env>` or `installation:<service>`. */
68
+ readonly scope?: string
69
+ readonly cursor?: string
70
+ }
71
+
72
+ export interface MailMessageIdInput {
73
+ readonly id: string
74
+ }
75
+
76
+ export interface MailRuleDto {
77
+ readonly id: string
78
+ /** `*` or `<app>@<env>`. */
79
+ readonly scope: string
80
+ /** `*` or a lowercase recipient domain. */
81
+ readonly domain: string
82
+ readonly action: MailRuleAction
83
+ readonly createdAt: number
84
+ readonly updatedAt: number
85
+ }
86
+
87
+ /** Sets the action of the rule of `scope` and `domain`, creating it when there is none. */
88
+ export interface SetMailRuleInput {
89
+ readonly scope: string
90
+ readonly domain: string
91
+ readonly action: MailRuleAction
92
+ }
93
+
94
+ export interface MailRuleIdInput {
95
+ readonly id: string
96
+ }
package/src/rpc.ts CHANGED
@@ -47,6 +47,15 @@ import type {
47
47
  UpdateAppRequest,
48
48
  UpdateDeploymentNamespaceRequest,
49
49
  } from './index.js'
50
+ import type {
51
+ MailMessageDetailDto,
52
+ MailMessageDto,
53
+ MailMessageIdInput,
54
+ MailMessageListInput,
55
+ MailRuleDto,
56
+ MailRuleIdInput,
57
+ SetMailRuleInput,
58
+ } from './mail.js'
50
59
 
51
60
  type RpcProcedure<TInput, TOutput> = RpcProcedureContract<TInput, TOutput>
52
61
 
@@ -54,6 +63,11 @@ export interface AppIdInput {
54
63
  readonly appId: string
55
64
  }
56
65
 
66
+ /** Whether a rotation was recorded: `false` while the environment holds no token key. */
67
+ export interface RotateTokenKeyResponse {
68
+ readonly requested: boolean
69
+ }
70
+
57
71
  export interface AppEnvironmentInput extends AppIdInput {
58
72
  readonly env: string
59
73
  }
@@ -161,6 +175,8 @@ export interface ControlRpcContract {
161
175
  delete: RpcProcedure<AppEnvironmentInput, OkResponse>
162
176
  /** Human user principals with `access.approve` only, from the console (ADR-0050 §4). */
163
177
  protect: RpcProcedure<SetEnvironmentProtectionRequest, AppEnvDto>
178
+ /** The environment's next deploy retires the key its signed tokens are signed with and signs with a new one. */
179
+ rotateTokenKey: RpcProcedure<AppEnvironmentInput, RotateTokenKeyResponse>
164
180
  }
165
181
  secrets: {
166
182
  list: RpcProcedure<AppIdInput, ListResponse<AppSecretDto>>
@@ -214,6 +230,25 @@ export interface ControlRpcContract {
214
230
  connectors: {
215
231
  describe: RpcProcedure<DescribeConnectorRequest, ExportDescription>
216
232
  }
233
+ /** The mail relay: `mail.read`, `mail.content.read` and `mail.manage`. */
234
+ mail: {
235
+ messages: {
236
+ list: RpcProcedure<MailMessageListInput, CursorList<MailMessageDto>>
237
+ /** The content only with `mail.content.read`. */
238
+ get: RpcProcedure<MailMessageIdInput, MailMessageDetailDto>
239
+ /** A held message only. */
240
+ release: RpcProcedure<MailMessageIdInput, MailMessageDto>
241
+ /** A held message only. */
242
+ drop: RpcProcedure<MailMessageIdInput, MailMessageDto>
243
+ /** A `failed` message, or an `unknown` one over an idempotent transport, whose content is kept. */
244
+ requeue: RpcProcedure<MailMessageIdInput, MailMessageDto>
245
+ }
246
+ rules: {
247
+ list: RpcProcedure<void, ListResponse<MailRuleDto>>
248
+ set: RpcProcedure<SetMailRuleInput, MailRuleDto>
249
+ remove: RpcProcedure<MailRuleIdInput, OkResponse>
250
+ }
251
+ }
217
252
  }
218
253
 
219
254
  /**