Repository navigation
service-sms: a configured sms.provider never selects a delivering transport unless OS_SMS_PROVIDER is also set — applySmsSettings decides from the settings namespace and no env credential reaches providerOptions #22789
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsTriage: first grade,
bug·priority:p3·domain:services·area:devpath, intopm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T06:58Z. ⛔ Not a claim, ⛔ not a dispatch.- Lane:
packages/services/service-sms(capability-arg.ts,sms-plugin.ts) isdomain:services. The area isarea:devpath, the configuration path, as for spec: the stack definition has noemail,smsorappNamekey, so theconfig.email/config.smscontractservereads is unreachable from adefineStackconfig #22748. - Why p3:
- A configured provider is silently replaced by the log transport. It is not quite silent: the boot logs "SMS will NOT be sent", and
isConfigured()answers false. - A workaround exists (
OS_SMS_PROVIDER). - The stack key that would make this common is not admitted yet: PR feat(spec): the stack definition admits
appNameand the non-secretemail/smsmembers; their credentials are refused by name #22778 refusessms.provideruntil this card lands.
- A configured provider is silently replaced by the log transport. It is not quite silent: the boot logs "SMS will NOT be sent", and
- Direction: mirror the mail resolver, ⛔ not a second rule.
resolveEmailCapabilityArg(plugin-email/src/capability-arg.ts) merges each credential env-over-config (OS_EMAIL_API_KEY || cfgEmail.apiKey).resolveSmsCapabilityArggets the same per-credential merge intoproviderOptions, for the twilio and aliyun keys.applySmsSettingshonours the constructor'sproviderwhen the settings namespace names none. A provider named in Settings still wins, as it does today.- Add the config-parity contract test that mail has (
capability-arg.config-parity.contract.test.ts).
- Done when: as the card states. A configured provider whose credentials are missing still fails loudly.
- Follow-on: the PR that lands this may admit
sms.provideron the stack definition (spec: the stack definition has noemail,smsorappNamekey, so theconfig.email/config.smscontractservereads is unreachable from adefineStackconfig #22748's refusal lifts there).Clause-②: yesfor that admission only.
- Lane:
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iterateand removed
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 2 · 2026-10-11T08:26Z
Session:session_01CBAfsWMSfM3EToQGVStEcp
Account:os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-22789-sms-configured-provider
Worktree:objectstack-issue-22789
Domain:domain:services
Seat:domain:services#1(seat post #6021)
Scope per triage6106456354: mirror the mail resolver, ⛔ not a second rule.resolveSmsCapabilityArgmerges each twilio and aliyun credential env-over-config intoproviderOptions, asresolveEmailCapabilityArgdoes.applySmsSettingshonours the constructor'sproviderwhen the settings namespace names none; a provider named in Settings still wins.- A config-parity contract test, as mail has.
- A configured provider whose credentials are missing still fails loudly.
File surface, read onorigin/main5fc57b382: packages/services/service-sms/src/capability-arg.ts,src/sms-plugin.ts(the settings application), and tests beside them, including a newcapability-arg.config-parity.contract.test.tson the mail precedent's shape;.changeset/22789-*.mdfor@objectstack/service-sms.- ⛔ No
packages/spec: admittingsms.provideron the stack definition is PR feat(spec): the stack definition admitsappNameand the non-secretemail/smsmembers; their credentials are refused by name #22778's (spec: the stack definition has noemail,smsorappNamekey, so theconfig.email/config.smscontractservereads is unreachable from adefineStackconfig #22748,domain:spec) follow-on, lifted there once this lands. ⛔ Noplugin-email. Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"; the default tier)
Clause-②: no - It makes the plugin honour a
provideroption it already declares, and credentials its environment already names. The stack-definition admission (Clause-②: yes, triage) is ⛔ not in this card's diff.
Responsibility:this repository's service-sms: applySmsSettings ignores the constructor's provider, and resolveSmsCapabilityArg merges no env credential | OS_SMS_PROVIDER or Settings → SMS Delivery, the documented workaround | any stack or os serve config that names an SMS provider with env credentials; it boots with the log transport ("SMS will NOT be sent")
Thread-read: 6106456354
Serial constraints cleared: - No open PR touches
packages/services/service-sms(the open PRs' file lists, read at this stamp). capability-arg.tscame in through PR feat(core,verify,cli): bootStack mounts the always-on slate and builds each provider from the app's configuration — item 1 gap of #22301 #22747 (verify: the in-process handle boots a leaner stack thanserveand has no door for eight things an app's tests need (requires[] capabilities, system/predicate update, the form door, user-less triggers, …), measured by hotcrm#2013 #22301), merged as7098acaef.- PR feat(spec): the stack definition admits
appNameand the non-secretemail/smsmembers; their credentials are refused by name #22778 (open,domain:spec) refusessms.provideron the stack until this card lands. This seat tells that card when it does.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22789,
"status": "done",
"branch": "claude/issue-22789-sms-configured-provider",
"pr": "#22815",
"session": "session_01CBAfsWMSfM3EToQGVStEcp — the dispatching seat's session (subagent run)",
"premise_still_valid": true,
"summary": "Premise re-measured on origin/main 5fc57b3 through bootStack (scratch test, not committed): sms.provider twilio with OS_SMS_TWILIO_ACCOUNT_SID/_AUTH_TOKEN/FROM_NUMBER and no OS_SMS_PROVIDER gave isConfigured false on LogSmsTransport; adding OS_SMS_PROVIDER=twilio gave true on TwilioSmsTransport; aliyun the same. Fix, service-sms only: resolveSmsCapabilityArg merges the selected provider's credentials into providerOptions env over config.sms.providerOptions per key (the mail rule), from the OS_SMS names SettingsService env-locks the sms namespace with; applySmsSettings (a private method of SmsServicePlugin, it read only the namespace provider) honours the constructor provider when the namespace names none (provider at source default), overlaying namespace credentials on the constructor providerOptions, and a provider the namespace names still wins. One table SMS_PROVIDER_SETTING_KEYS (transports/index.ts, not exported from the package root) maps setting keys to transport options for both faces, so providerFromSettings no longer restates it. After the fix the same bootStack cases read true on TwilioSmsTransport and AliyunSmsTransport; a configured provider with no credentials anywhere still boots on the log transport with warn lines and isConfigured false. Draft PR 22815 opened with a patch changeset; Clause-② no copied from the claim.",
"tests": "service-sms at HEAD a59a982:pnpm --filter @objectstack/service-sms testgave Test Files 6 passed (6), Tests 94 passed (94);pnpm --filter @objectstack/service-sms typecheckexit 0 (check:test-typecheck OK, debt unchanged at 1 file / 1 error);tsc --noEmit -p tsconfig.test.json --listFilesholds 6 of 6 test files. New: capability-arg.config-parity.contract.test.ts (14 cases: table equals the manifest's per-provider keys by its visible grouping plus a pinned key set; resolver reads each key under envKeyOf, env over config per credential, selected provider only, no refusal without credentials as control; card door isConfigured true from init with settings mounted; constructor face and settings face build equal credentials; missing-credentials control) and 6 cases in sms-plugin.test.ts (constructor provider from namespace credentials twilio and aliyun, constructor-only baseUrl survives, constructor-built transport kept with no warn, CONTROL namespace-named provider wins, CONTROL no credentials gives log transport plus isConfigured false plus a warn line naming the provider). bootStack before/after (scratch, packages/verify, deleted): before at 5fc57b3 A false/Log, B true/Twilio, C false/Log, D booted false/Log, E true/Aliyun; after (service-sms rebuilt at 429b114, source unchanged since) A true/Twilio, B true/Twilio, C true/Aliyun, D booted false/Log, E true/Aliyun. Ablation at 1718ed1 via scripts/ablation-replace.mjs WRAP mode (anchor hit 1, blob changed, restore blob equal to HEAD and git diff HEAD empty, plus a wrapper trap and an independent hash proof): leg 1 drop the resolver env merge gave 8 failed / 28 passed (the 4 resolver reads, 2 from-init pins, 2 both-faces pins red; every control green); leg 2 drop the constructor provider at kernel:ready gave 3 failed / 33 passed (twilio, aliyun and baseUrl-overlay pins red; every control green); restored tree 36/36. No dist in either leg: the subject is imported by relative source path. Narrowed eslint on the 5 changed TS files: 5 linted, 0 errors, 0 warnings; print-config shows no parserOptions.project and no projectService, rules are single-file AST rules, so untouched files' verdicts cannot move.",
"gates": "Derived at a59a982 (after merging origin/main 0984817):node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack= 63 commands (identical list to the pre-merge derivation). Run under scripts/pm/os-verify-lock.sh with exit codes recorded to disk per command: 62 exit 0; 1 NOT MEASURED,pnpm check:dual-build-cjs-loadsexit 3 PREREQUISITE NOT MET (it reads every package dist; this worktree built only the verify closure, 47 packages). Supplementary, not the gate: require of service-sms dist/index.js (CJS) loads and answers the resolver.--ranreconciliation: '63 derived famil(ies) accounted for — 62 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', 0 UNRUN. Notable: check:cross-package-test-inputs OK, check:test-source-alias OK (service-sms to service-settings pair already registered), check:nul-bytes OK, check:doc-authoring OK, check:type-check-coverage and check:type-check-debt OK, check:changeset-no-major / adr-0087-registration / empty-changeset OK. check:dts-closure and check:sourcemap-no-sources-content swept the 46 built packages (service-sms among them), not all 70. CI state: in_progress, not awaited.",
"line_budget": "540 changed lines (+501 / -39) over 6 files vs the human-merge threshold 3000: under. No governed surface touched (no .claude, skills, docs/adr, AGENTS.md, CLAUDE.md).",
"files_changed": [
".changeset/22789-sms-configured-provider.md",
"packages/services/service-sms/src/capability-arg.config-parity.contract.test.ts",
"packages/services/service-sms/src/capability-arg.ts",
"packages/services/service-sms/src/sms-plugin.test.ts",
"packages/services/service-sms/src/sms-plugin.ts",
"packages/services/service-sms/src/transports/index.ts"
],
"deviations": [
"PM mechanism assumption 1 holds as measured; the control's 'same refusal bootStack gives today, assert its kind or code' does not: today there is no refusal and no code. A configured provider with missing credentials boots onto LogSmsTransport with a warn line and isConfigured false, so the controls assert exactly that kind (log transport, isConfigured false, a warn-level line naming the provider), not prose.",
"PM mechanism assumption 2: applySmsSettings is a private method of SmsServicePlugin (sms-plugin.ts), and before this change it read only the namespace's provider (providerFromSettings(values), values.provider defaulting to log); this.options.provider was never consulted there.",
"Two ablation legs, not one: the order named only the resolver leg. The bootStack door is repaired by either leg alone at kernel:ready when a settings service is mounted, so the second leg was needed to prove the settings-leg pins can fail; the from-init pins read isConfigured at init for the same reason.",
"Main moved after my merge of 0984817 by two commits (85edd61, which is PR 22778, and 7b0a9c9); neither touches service-sms, so I did not merge again. 22778's new CLI contract test serve-stack-email-sms-door asserts resolver outputs this change leaves equal, read through the built dist: provider log with retries 3, provider twilio with retries 3, provider aliyun.",
"Labels: zero label-write. The order named no label and skip-changeset does not apply (the diff publishes and carries a changeset), so the intersection is empty.",
"Commit trailers use the model-free pair AGENTS.md prescribes (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By line; the reminder yields to AGENTS.md by its own precedence sentence.",
"One wasted lock turn: a no-op command (a sed on /dev/null followed by true) was sent through os-verify-lock.sh while I split the gate list; it changed nothing on disk."
],
"mcp_calls": "0 — no MCP tool was called.",
"api_writes": "3 REST writes, all executed by the fleet relay as objectstack-fleet[bot] from 2 repository_dispatch strokes (POST /repos/objectstack-ai/objectstack/dispatches x2): POST /repos/objectstack-ai/objectstack/pulls (draft, PR 22815); POST /repos//issues/22815/assignees (os-project-manager, read back listed); POST /repos//issues/22789/comments (this os-dev-report). label-write: 0. git push (not REST): the empty-branch probe plus 5 pushes of the branch. Reads only otherwise (gh api GET on the card, its comments, PR 22778 and PR 22815).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the follow-on of 22748 that lifts the sms.provider refusal on the stack definition (the seat tells that card) · noted, not filed — packages/spec/src/system/sms-config.zod.ts (landed with PR 22778, 85edd61) says in its header that the SMS reader merges no environment credential into providerOptions; that sentence is false once this PR lands. It is a code comment, and this dispatch has no packages/spec. Already in the PR's Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed — packages/cli/src/commands/serve-sms-capability.test.ts line 68 comment says 'Unlike mail, SMS credentials are not a boot-time input'; after this PR they are read at boot (still not demanded), so the comment is half stale while its assertion still holds. Not in the PR body, which is written once: the seat may add it to Acceptance notes as one bullet, 'serve-sms-capability.test.ts comment at line 68 now overstates: credentials are read at boot but not demanded.'"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsACCEPT (seat review): PR #22815 at
a59a982f8b·domain:servicesseat 1 (#6021) ·session_01CBAfsWMSfM3EToQGVStEcp· 2026-10-11T09:22ZClaim
6107123356. Report6107517784, checked against GitHub, ⛔ not taken on trust.Checklist, as checked:
- PR shape: draft, base
main. The first line isFixes #22789andClause-②: nostarts a line. No other card number sits beside a closing keyword. The assignee isos-project-manager. - Scope:
pulls/22815/fileslists 6 files (+501 / -39), all inpackages/services/service-smsplus its changeset.check-governed-merges.mjs --pr 22815says NOT governed (0 of 6 paths hit the register). Clause-②: no, confirmed by surface: nopackages/spec/src/**, no governed text. The package'sexportsmap has only".", andsrc/index.tsre-exports from./transports/index.jsthe same 9 names as before. SoSMS_PROVIDER_SETTING_KEYS,smsProviderOptionsFromandSmsDeliveringProviderare module exports, ⛔ not published surface. No contract-review tier is owed.- Route (triage
6106456354, "mirror the mail resolver, not a second rule"):- Resolver.
resolveSmsCapabilityArgmerges the selected provider's credentials, env overconfig.sms.providerOptionsper key. It reads them underOS_SMS_+ the setting key, which is the nameSettingsServiceenv-locks the namespace with; the parity test pins that againstenvKeyOf. It still demands no credential, and it still refuses only an undeliverable provider tag. - Plugin.
applySmsSettingshonours the constructor'sprovideronly when the namespace'sprovideris atsource: 'default'. That is the same criterion the existinglogbranch (settings 消费缝丢弃 ResolvedSettingValue.source —— 服务无法区分「管理员写过的值」和「schema 默认值」 #5536) uses, so a saved orOS_SMS_PROVIDER-locked provider still wins (case E). - One table.
SMS_PROVIDER_SETTING_KEYSnow feeds both faces, andproviderFromSettingsno longer restates the key → option mapping. Its completeness checks are unchanged.
- Resolver.
- Changeset (
@objectstack/service-smspatch), checked sentence by sentence:- "booted on
LogSmsTransportwithisConfigured() === false… The boot logged 'SMS will NOT be sent'" matchesinit's!configuredline (sms-plugin.ts,no provider configured — using LogSmsTransport (SMS will NOT be sent)). - The 8 listed variables equal
OS_SMS_+ each key in the table. - "A provider named in Settings, or by
OS_SMS_PROVIDER, still wins" matchesnamed = (sources.provider ?? 'default') !== 'default'. - "Unchanged: … missing everywhere still boots onto the log transport with a warning" matches control D.
- "Signatures and exports are unchanged" holds at the package root, as above.
- "booted on
- Ablations: dropping the resolver merge turned exactly the 4 resolver reads, 2 from-init pins and 2 both-faces pins red (8). Dropping the constructor provider at
kernel:readyturned exactly the twilio, aliyun andbaseUrl-overlay pins red (3). Every control stayed green, and the restored tree passed 36 of 36. - The one NOT MEASURED gate:
check:dual-build-cjs-loadsexited 3 locally (prerequisite: every package'sdist). CI's build is the measurement of record, so it lands only on a green head.
Follow-ons, each carried by the card that owns it, ⛔ not widened into this PR:
packages/spec/src/system/sms-config.zod.ts(feat(spec): the stack definition admitsappNameand the non-secretemail/smsmembers; their credentials are refused by name #22778,85edd61cd): the header sentence saying the SMS reader merges no environment credential intoproviderOptionsbecomes false when this lands. Carrier: the spec: the stack definition has noemail,smsorappNamekey, so theconfig.email/config.smscontractservereads is unreachable from adefineStackconfig #22748 follow-on that lifts thesms.providerrefusal. The seat tells that card after the merge.packages/cli/src/commands/serve-sms-capability.test.ts:68: the comment "Unlike mail, SMS credentials are not a boot-time input" now overstates. Credentials are read at boot but not demanded, and the assertion still holds. Same carrier, one line.
Landing: CI on
a59a982f8bis converging (15 success, 3 expected skips, 13 in progress at review). Once every check is green or an expected skip, the seat flips ready and enables auto-merge.
Generated by Claude Code
- PR shape: draft, base
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsLanded and closed out ·
domain:servicesseat 1 (#6021) ·session_01CBAfsWMSfM3EToQGVStEcp· 2026-10-11T09:57Z- PR fix(service-sms): a configured SMS provider delivers through its env or Settings credentials, without OS_SMS_PROVIDER #22815 merged through the queue at 2026-10-11T09:56:53Z as
f9c7588b6, from the reviewed heada59a982f8b(ACCEPT6107546582). Read back: onorigin/mainat that commit,packages/services/service-sms/src/transports/index.tsdeclaresSMS_PROVIDER_SETTING_KEYS. - This card:
Fixes #22789closed itcompleted. In this act,pm:dispatchedand the assignee come off.bug,domain:services,priority:p3andarea:devpathstay. - Outcome on record:
- a configured
twiliooraliyunprovider whose keys are in the environment (or in Settings) delivers withoutOS_SMS_PROVIDER; - a provider named in Settings or by
OS_SMS_PROVIDERstill wins; - a configured provider with no credentials anywhere still boots onto the log transport, with a warning and
isConfigured() === false.
- a configured
- Carried forward: spec: the stack definition has no
email,smsorappNamekey, so theconfig.email/config.smscontractservereads is unreachable from adefineStackconfig #22748's landing record (6107349646) gates re-admittingsms.provideron the stack definition on this card. That gate is now met. The re-admission is adomain:speccard nobody has filed yet, and the seat relays the unlock to thedomain:specseat post. Two comments that become stale with this landing ride that card:packages/spec/src/system/sms-config.zod.ts's header says the SMS reader merges no environment credential intoproviderOptions;packages/cli/src/commands/serve-sms-capability.test.ts:68says "Unlike mail, SMS credentials are not a boot-time input". They are now read at boot, but still not demanded.
- Mis-close check: PR fix(service-sms): a configured SMS provider delivers through its env or Settings credentials, without OS_SMS_PROVIDER #22815 names one closing target. No other lane card closed in the window. PR fix(plugin-security): explain's read verdict on a controlled_by_parent record takes the read door's answer on the master leg #22813 merged three minutes earlier as
Part of #22792, and security(explain): explain'sreadverdict for a row the caller cannot read disagrees with the read door, and differs from its answer for an id no row carries (a controlled_by_parent detail under an unreadable master, measured) #22792 stays open.
Generated by Claude Code
- PR fix(service-sms): a configured SMS provider delivers through its env or Settings credentials, without OS_SMS_PROVIDER #22815 merged through the queue at 2026-10-11T09:56:53Z as
Filing gate: ① a defect with a measured public door. Reach:
bootStack(@objectstack/verify). AdefineStack({ …, sms: { provider: 'twilio' } })withOS_SMS_TWILIO_ACCOUNT_SID/_AUTH_TOKEN/_FROM_NUMBERin env, but noOS_SMS_PROVIDER, boots withsms.isConfigured() === false. The same holds for a rawconfig.sms.provideronos serve.SmsServicePlugin: provider=twilio selected but transport build failed (accountSid and authToken are required) — falling back to LogSmsTransport, thenSMS will NOT be sent.OS_SMS_PROVIDER=twiliologstransport rebuilt from settings (provider=twilio)and givesisConfigured() === true.email,smsorappNamekey, so theconfig.email/config.smscontractservereads is unreachable from adefineStackconfig #22748 dev, report6106330513,out_of_scope_findings1, on PR feat(spec): the stack definition admitsappNameand the non-secretemail/smsmembers; their credentials are refused by name #22778's tree.Hands-on reader: triage grades it into the
domain:servicesqueue (packages/services/service-sms). Dedupe: the semantic issue search "SmsServicePlugin ignores configured provider applySmsSettings settings namespace falls back to LogSmsTransport twilio aliyun" on objectstack-ai/objectstack returned 1 hit, #5773 (closed): a contract test between the settings options table and the transports, not this defect.Cause, per the report
applySmsSettingsdecides the transport from the settings namespace's ownprovidervalue and ignores the constructor'sprovideroption.resolveSmsCapabilityArgmerges no environment credential intoproviderOptions.config.sms) never builds an aliyun / twilio transport; onlyOS_SMS_PROVIDER/ Settings does.Why it matters now
PR #22778 (#22748) admits the non-secret
email/smsmembers on the stack definition. Because of this defect, that PR refusessms.providerby name for now, prescribingOS_SMS_PROVIDER/ Settings → SMS Delivery: a declared key the runtime does not honour is barred (seat order6106350788). The PR that fixes this card is the one that may admitsms.provideron the stack.Done when
SmsServicePluginhonours a configuredproviderwhen its credentials come from environment (or settings), with a pin and a control. A configured provider whose credentials are missing still fails loudly, asbootStackdoes today.