You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 34286f6
Browse filesBrowse the repository at this point in the historyBrowse files
fix(spec): refuse sms.provider by name; sms carries retries alone
The stack cannot select the delivering SMS transport: the sms settings
namespace builds it from its own provider setting, so a stack-declared
provider is refused with the OS_SMS_PROVIDER / Settings prescription, as
providerOptions is. SmsProviderSchema leaves with its only consumer.
Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .changeset/22748-stack-email-sms-door.md
+11-12Lines changed: 11 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,19 +2,18 @@
2
2
"@objectstack/spec": minor
3
3
---
4
4
5
-
feat(spec): the stack definition admits `appName` and the non-secret members of `email` and `sms`; their credentials are refused by name
5
+
feat(spec): the stack definition admits `appName`, the non-secret members of `email`, and `sms.retries`; credentials and the SMS provider are refused by name
6
6
7
-
Clause-②: yes (widening: the stack definition admits `appName` and the non-secret members of `email` and `sms`, which it refused as unrecognized keys)
7
+
Clause-②: yes (widening: the stack definition admits `appName`, the non-secret members of `email`, and `sms` carrying `retries` alone, which it refused as unrecognized keys)
8
8
9
-
-**What you can write now.**`defineStack()` accepts three top-level keys it used to refuse as unrecognized. The mail and SMS readers already read all three, and `objectstack serve` and `bootStack` construct the email and SMS providers through those readers:
9
+
-**What you can write now.**`defineStack()` accepts three top-level keys it used to refuse as unrecognized. The mail and SMS readers already read them, and `objectstack serve` and `bootStack` construct the email and SMS providers through those readers:
10
10
-`appName`: the deployment's product name. It is the last configured rung of the name chain (`OS_APP_NAME` > `email.appName` > `email.defaultTemplateContext.appName` > `appName` > `'ObjectStack'`), and it reaches the email templates and AuthPlugin.
11
-
-`email` (`StackEmailConfigSchema`): every member of `EmailServiceConfigSchema` except `apiKey`. That is `provider`, `defaultFrom`, `retries`, `persist`, `queueDelivery`, `appName` and `defaultTemplateContext`, plus `options` narrowed to `host` / `port` / `secure` / `user` / `messageStream`.
-`OS_EMAIL_*` and `OS_SMS_PROVIDER` still override the file per key.
14
-
-**What stays in the environment, and is refused by name.** A stack definition is compiled into the published artifact, so it does not carry credentials. Each refusal names the key and the variable that replaces it:
15
-
-`email.apiKey` and `email.options.apiKey`: set `OS_EMAIL_API_KEY`.
16
-
-`email.options.password`: set `OS_EMAIL_SMTP_PASSWORD`. The reader layers it over the host / port / secure / user the file carries.
17
-
-`sms.providerOptions`: configure the provider in Setup → Settings → SMS Delivery, or through that namespace's overrides (`OS_SMS_PROVIDER`, `OS_SMS_TWILIO_*`, `OS_SMS_ALIYUN_*`). It is refused whole, because every non-`log` SMS transport needs a credential inside it and nothing merges an environment credential into it.
11
+
-`email` (`StackEmailConfigSchema`): every member of `EmailServiceConfigSchema` except `apiKey`. That is `provider`, `defaultFrom`, `retries`, `persist`, `queueDelivery`, `appName` and `defaultTemplateContext`, plus `options` narrowed to `host` / `port` / `secure` / `user` / `messageStream`. `OS_EMAIL_*` still overrides it per key.
12
+
-`sms` (`StackSmsConfigSchema`): `retries`, the SMS service's attempt budget. It applies to whichever transport the `sms` settings namespace selects.
13
+
-**What stays elsewhere, and is refused by name.** Each refusal names the key and the setting that replaces it:
14
+
-`email.apiKey` and `email.options.apiKey`: a credential, and a stack definition is compiled into the published artifact. Set `OS_EMAIL_API_KEY`.
15
+
-`email.options.password`: a credential. Set `OS_EMAIL_SMTP_PASSWORD`; the reader layers it over the host / port / secure / user the file carries.
16
+
-`sms.provider`, `sms.providerOptions` (and a flat `authToken` / `accessKeySecret`): the delivering SMS transport is built by the `sms` settings namespace from its own provider and credentials, never from a stack definition. Configure them in Setup → Settings → SMS Delivery, or through that namespace's overrides (`OS_SMS_PROVIDER`, `OS_SMS_TWILIO_*`, `OS_SMS_ALIYUN_*`).
18
17
-**Composition.** All three keys compose as single values. `composeStacks` passes an identical value through and refuses two different ones by key.
19
-
-**Nothing to migrate.** No stack that was accepted before is refused now. A configuration that set these keys only in an object `defineStack` did not build (the one `objectstack serve` boots) reads the same as before. To build it, move any credential it carries into the environment.
20
-
- New exports from `@objectstack/spec/system`: `StackEmailConfigSchema`, `StackEmailProviderOptionsSchema`, `StackSmsConfigSchema`, `SmsProviderSchema`, and their `z.input` / `z.infer` types.
18
+
-**Nothing to migrate.** No stack that was accepted before is refused now. A configuration that set these keys only in an object `defineStack` did not build (the one `objectstack serve` boots) reads the same as before. To build it, move any credential, and the SMS provider, into the environment or Settings.
19
+
- New exports from `@objectstack/spec/system`: `StackEmailConfigSchema`, `StackEmailProviderOptionsSchema`, `StackSmsConfigSchema`, and their `z.input` / `z.infer` types.
Copy file name to clipboardExpand all lines: content/docs/references/system/sms-config.mdx
+36-30Lines changed: 36 additions & 30 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,29 +12,47 @@ There was no SMS configuration schema in the spec before this file. The
12
12
shape below is typed from what its one reader reads:
13
13
`resolveSmsCapabilityArg` in `@objectstack/service-sms`, which
14
14
`objectstack serve` and `bootStack` both construct `SmsServicePlugin`
15
-
through. It reads three keys off `config.sms`:
16
-
17
-
| key | secret? | here |
18
-
|---|---|---|
19
-
|`provider`| no — a transport tag | admitted (`SmsProviderSchema`); `OS_SMS_PROVIDER` wins |
20
-
|`retries`| no | admitted |
21
-
|`providerOptions`| YES — every non-`log` transport requires a credential inside it (Twilio `authToken`, Aliyun `accessKeySecret`) | refused by name |
22
-
23
-
`providerOptions` is refused WHOLE rather than narrowed to its non-secret
15
+
through. It reads three keys off `config.sms`, and only one of them can do
16
+
from a stack definition what it says:
17
+
18
+
| key | secret? | delivered from a stack? | here |
19
+
|---|---|---|---|
20
+
|`retries`| no | yes | admitted |
21
+
|`provider`| no — a transport tag | NO, measured | refused by name |
22
+
|`providerOptions`| YES — every non-`log` transport requires a credential inside it (Twilio `authToken`, Aliyun `accessKeySecret`) | — | refused by name |
23
+
24
+
**`retries`** is the SMS service's own attempt budget. The service keeps it
25
+
when the `sms` settings namespace swaps its transport at `kernel:ready`
26
+
(`setTransport` replaces the transport and nothing else), so it applies to
27
+
whichever transport ends up delivering. It has no environment or settings
28
+
carrier, so this block is its only authoring door.
29
+
30
+
**`provider`** cannot select a delivering transport from here. A stack is
31
+
not allowed credentials (below), so the transport the plugin constructs from
32
+
this tag falls back to its log transport, and the transport that DOES
33
+
deliver is the one the settings namespace builds at `kernel:ready` from its
34
+
own `provider` setting — it never reads this one. Measured through
35
+
`bootStack`: `sms: { provider: 'twilio' }` with the Twilio credentials in the
36
+
environment boots with `isConfigured() === false`; adding `OS_SMS_PROVIDER`
37
+
makes it `true`. A declared key the runtime does not honour is refused rather
38
+
than admitted, with the setting that does work as its prescription. It comes
39
+
back, with a provider vocabulary, in the change that makes `SmsServicePlugin`
40
+
honour it.
41
+
42
+
**`providerOptions`** is refused WHOLE rather than narrowed to its non-secret
24
43
members, and the difference from mail's `options` is measured, not a
25
44
preference. The mail reader layers `OS_EMAIL_SMTP_*` over `config.email.options`
26
45
per key, so a file carrying host and user composes with an environment
27
46
carrying the password. The SMS reader hands `providerOptions` to the
28
47
transport as written, and nothing merges an environment credential into it:
29
48
a Twilio or Aliyun transport built from the non-secret members alone cannot
30
-
be constructed, so the plugin falls back to its log transport. A narrowed
31
-
`providerOptions` would therefore be a declared key no deployment could ever
32
-
deliver through. SMS provider settings — credentials included — live in the
@@ -69,8 +76,7 @@ const result = SmsProviderSchema.parse(data);
69
76
70
77
| Property | Type | Required | Description |
71
78
| :--- | :--- | :--- | :--- |
72
-
|**provider**|`Enum<'log' \| 'aliyun' \| 'twilio'>`| optional | SMS transport tag (OS_SMS_PROVIDER env wins). Default log. Credentials never come from the stack: the delivering aliyun / twilio transport is built at kernel:ready from the sms settings namespace (Setup → Settings → SMS Delivery, or its OS_SMS_* env overrides), whose own provider setting decides |
73
-
|**retries**|`integer`| optional | Retry attempts on transport throw (default 0) |
79
+
|**retries**|`integer`| optional | Retry attempts on transport throw (default 0); applies to whichever transport the sms settings namespace selects. The provider and its credentials are not authorable here: Setup → Settings → SMS Delivery, or OS_SMS_PROVIDER and the other OS_SMS_* overrides |
0 commit comments