Repository navigation
spec: the stack definition has no email, sms or appName key, so the config.email / config.sms contract serve reads is unreachable from a defineStack config #22748
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Oct 10, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsTriage: first grade,
bug·priority:p3·domain:spec·area:devpath·pm:queue. Admit the non-secret half; secrets stay environment-onlyTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T02:59Z. ⛔ Not a claim, ⛔ not a dispatch.- Re-read on
main:packages/spec/src/system/email-config.zod.tsdocumentsconfig.email.*"from objectstack.config.ts" as layer 1 (:15). Its members includeapiKey(:85) and the SMTPpasswordinoptions(about:137–:147).stack.zod.tsadmits noemail,smsorappName.- The filed reading holds.
- Why not simply admit the three keys:
- A stack definition is what
objectstack buildcompiles into the artifact, and artifacts are published (the marketplace ingest stores them). - Admitting
emailwhole would make provider secrets a legitimate artifact key. That is a security posture change, and ⛔ triage does not make one.
- A stack definition is what
- Direction:
- Admit
appNameand the non-secret members ofemailandsms(provider, from-address, persistence, and the like) on the stack definition, typed by the existing schemas narrowed to those members.Clause-②: yes (widening). - Secret-bearing members (
apiKey, an SMTPpassword, and any SMS token) stay environment-only (OS_EMAIL_*/OS_SMS_*). - The stack refuses them by name, with the env-variable prescription, rather than as an unrecognized key.
EmailServiceConfigSchema's layer-1 text says which members a config file may carry.- Pins:
- a
defineStackwithemail: { provider, from }builds andservereads it; - a stack carrying
email.apiKeyis refused by name; - CONTROL: env-only configuration is unchanged.
- a
- Admit
- Stop rule: if the claimant finds a first-party reader that needs a secret from the config file, it stops and returns the card. That would be a decision about secrets in config, which is the maintainer's.
- Why p3: the env layer works today. The gap is a documented config arm no built stack can reach.
- Lane:
packages/spec/src/stack.zod.tsandemail-config.zod.tsaredomain:spec. The readers' move (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) is independent.
- Re-read on
- 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 1 (triage direction
6104830037: the stack definition admitsappNameand the non-secret members ofemail/sms; secret members stay environment-only, refused by name) · 2026-10-11T04:08Z
Session:session_01S3aAf11JjbW1mSGL1EhfFj
Account:os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22748-stack-email-sms-door
Worktree:objectstack-issue-22748
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface (atorigin/main7098acaef9or later; stop on breach and explain in the report):packages/spec/src/stack.zod.ts: the stack definition admitsappName, andemail/smstyped by the existing schemas narrowed to their non-secret members (provider, from-address, persistence and the like, as the readersresolveEmailCapabilityArg/resolveSmsCapabilityArgread them).- The secret-bearing members (
apiKey, an SMTPpasswordinoptions, any SMS token) are refused by name, with theOS_EMAIL_*/OS_SMS_*prescription, not as an unrecognized key. - The stack's merge strategy table (
:1067area) gains the new keys, in the existing idiom.
- The secret-bearing members (
packages/spec/src/system/email-config.zod.ts: the layer-1 text (:14–:15) says which members a config file may carry, and stops namingserve.tsas the reader's home (since 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 the reader lives in@objectstack/plugin-email).- An SMS config schema, if
packages/spechas one; if it has none, report howsmsis typed. - Pins:
- a
defineStackwithemail: { provider, from }builds, andserve's reader reads it; - a stack carrying
email.apiKeyis refused by name; - CONTROL: env-only configuration is unchanged.
- a
- The generated artifacts
gen:schema/gen:docsregenerate from those schemas (content/docs/references/**), and.changeset/22748-*.md. - ⛔ No reader change in
@objectstack/plugin-email/@objectstack/service-sms/serve.tsbeyond what typing them from the stack requires. ⛔ No secret ever admitted on the stack.
Container & model:M,mode:subagent,model: default(dispatch-gates --tieronpackages/spec/src/**/*.zod.ts). A spec contract face is touched, so the contract review atCONTRACT_REVIEW_TIERis owed before enqueue, run in an isolated subagent since this seat is not at that tier.
Clause-②: yes (widening: the stack definition admitsappName, the non-secret members ofemail, andsmscarryingretriesalone, which it refused as unrecognized keys)
Responsibility:packages/spec's stack definition has no door for theconfig.email/config.sms/config.appNamecontract thatEmailServiceConfigSchemadocuments and the readers read | the env layer (OS_EMAIL_*/OS_SMS_*) works today | operators and AI authors writingobjectstack.config.ts, whose documented mail configuration a built stack cannot carry
Thread-read: 6104830037
Serial constraints cleared: - No open PR touches
stack.zod.tsoremail-config.zod.ts(all 12 open PRs' file lists, read at this claim). - 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 (the readers' move) landed as
7098acaef9, so the readers sit at their new homes onmain. - Same lane, this seat: [maintainer] validate: the
field-no-consumerswarning is one 856-character line, printed by validate, build and dev alike — one-line verdict +rule:id + a pointer to the full reasoning (os explain, which today takes only schema names) #22161 slice 8 (packages/lint) and lint(runtime gate): a flow write's per-write snapshot carries no actions and no other flows, so a flow CEL root rule cannot see the entrances that hand a flow its record (W, from #22565) #22636 (PR feat(lint): a record trigger that names no object opens its flow to the CEL root judge; the runtime gate stand-down is by design #22762,packages/lint). Disjoint. - Stop rule (triage): if a first-party reader needs a secret from the config file, the dev stops and returns the card. That is a decision about secrets in config, which is the maintainer's.
This act moves the card
pm:queue→pm:dispatchedand assignsos-project-manager.objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22748,
"status": "done",
"branch": "claude/issue-22748-stack-email-sms-door",
"pr": "#22778",
"session": "session_01S3aAf11JjbW1mSGL1EhfFj",
"premise_still_valid": true,
"summary": "The stack definition now admits appName, email and sms, following triage direction 6104830037. email is StackEmailConfigSchema, derived from EmailServiceConfigSchema by omitting apiKey and narrowing options to a strict host / port / secure / user / messageStream block. sms is the new StackSmsConfigSchema, provider (SmsProviderSchema log / aliyun / twilio) and retries, typed from what resolveSmsCapabilityArg reads, because packages/spec had no SMS schema. All three compose as 'single'. The credentials are refused by name through the stack's own strictObject guidance idiom (the one top-level storage and server.port / host / cors use), each prescription naming its env var: email.apiKey and email.options.apiKey (OS_EMAIL_API_KEY), email.options.password (OS_EMAIL_SMTP_PASSWORD), sms.providerOptions and the flat authToken / accessKeySecret (Settings, SMS Delivery, or OS_SMS_). sms.providerOptions is refused whole because, measured, its non-secret members can never build a transport: nothing merges an env credential into it. The triage stop rule did not fire, since every secret has an env carrier. Surface breach: check:skill-top-level-keys forced skills/objectstack-platform/SKILL.md into the diff, so the PR is Tier H. Token cost was paid by deleting a duplicated sentence, at 0 new lines and under the ceiling. Draft PR #22778 is open, assignee os-project-manager. The worktree is removed after this report posts.",
"reader_key_census": [
"resolveEmailCapabilityArg · email.provider → non-secret (OS_EMAIL_PROVIDER) → admitted",
"resolveEmailCapabilityArg · email.apiKey → SECRET (OS_EMAIL_API_KEY) → refused by name",
"resolveEmailCapabilityArg · email.defaultFrom → non-secret (OS_EMAIL_FROM) → admitted",
"resolveEmailCapabilityArg · email.retries → non-secret (OS_EMAIL_RETRIES) → admitted",
"resolveEmailCapabilityArg · email.queueDelivery → non-secret (OS_EMAIL_QUEUE_ENABLED) → admitted",
"resolveEmailCapabilityArg · email.persist → non-secret (OS_EMAIL_PERSIST_ENABLED) → admitted",
"resolveEmailCapabilityArg · email.appName → non-secret (OS_APP_NAME) → admitted",
"resolveEmailCapabilityArg · email.defaultTemplateContext → non-secret (template render context) → admitted",
"resolveEmailCapabilityArg · email.options.host / port / secure / user → non-secret (OS_EMAIL_SMTP_HOST / _PORT / _SECURE / USER) → admitted (strict block)",
"resolveEmailCapabilityArg · email.options.messageStream → non-secret → admitted",
"resolveEmailCapabilityArg · email.options.password → SECRET (OS_EMAIL_SMTP_PASSWORD) → refused by name",
"resolveEmailCapabilityArg · email.options.apiKey → SECRET (makeTransport spreads options over apiKey; OS_EMAIL_API_KEY) → refused by name",
"resolveCapabilityArgument / resolveDeploymentAppName · appName (top level) → non-secret (OS_APP_NAME) → admitted",
"resolveSmsCapabilityArg · sms.provider → non-secret (OS_SMS_PROVIDER) → admitted (SmsProviderSchema)",
"resolveSmsCapabilityArg · sms.retries → non-secret (no env) → admitted",
"resolveSmsCapabilityArg · sms.providerOptions → SECRET-BEARING (Twilio authToken / Aliyun accessKeySecret; env carrier = sms settings overrides OS_SMS_TWILIO / OS_SMS_ALIYUN_) → refused by name, whole"
],
"tests": "Spec: 'pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2' gave Test Files 644 passed (644), Tests 19228 passed | 1 todo (19229), on the spec tree as at bd82b77. '--project repo' on the same tree gave Test Files 1 failed | 54 passed (55): cube-member-inner-name-retirement threw ENOENT on packages/spec/json-schema/automation, because my concurrent gate sweep was regenerating that gitignored tree mid-walk. Re-run alone on 4fdbd86 it gave 1 passed (1), Tests 20 passed (20). The spec file changed after that run, type-alias-convention.pin.test.ts, gave 3 passed (3). The new pin src/stack-email-sms.test.ts gave Tests 16 passed (16). 'pnpm --filter @objectstack/spec typecheck' on 4fdbd86 exited 0, with the test layer at 52 files / 246 errors / 135 pinned signatures, unchanged. 'pnpm --filter @objectstack/cli typecheck' on 4fdbd86 exited 0. cli --project unit, rider plus neighbours (serve-stack-email-sms-door.contract, serve-auth-app-name.contract, serve-email-capability, serve-sms-capability): Test Files 4 passed (4), Tests 36 passed (36); the rider alone gave 5 passed. cli --project unit test/option-b-reader-acceptance.pin.test.ts gave Tests 9 passed (9). That file is unit-tier, and the integration selection answered 'FILTER SELECTED NOTHING'. Consumers on 4fdbd86: core capability-composition 12 / 12, plugin-email capability-arg.config-parity.contract 8 / 8, runtime artifact-collections 22 / 22, verify harness.served-composition 7 / 7. os validate (--json) on examples app-crm, app-multi-package, app-showcase and app-todo, before (base dist) and after: all four valid:true, payloads minus duration byte-identical (sha256 prefixes c6342b4d1e72cb5a / 8d5c983d7fea6968 / 084fdaebbe20fae7 / 8e8e57f1357155e5, the same before and after). Public door: os validate on a stack carrying email.apiKey exits 1 with '✗ email: Unrecognized key(s) on the stackemailblock:apiKey. • Not authorable here. … set OS_EMAIL_API_KEY …'; email { provider, defaultFrom, persist } plus sms { provider, retries } exits 0. Ablation via scripts/ablation-replace.mjs on 4fdbd86, src-resolved with no dist in the path. Control leg: 16 / 16. Mutation 'guidance: { apiKey: STACK_EMAIL_API_KEY_PRESCRIPTION },' to 'guidance: {},': anchor x1 to x0, replacement x0 to x1, blob e9d625633b26 to d5c0f4a5cb8b. Result: Tests 1 failed | 15 passed (16), with exactly 'email.apiKey is refused by name, prescribing OS_EMAIL_API_KEY' red ('expected …apiKey. This block is strict from birth… to contain OS_EMAIL_API_KEY'). Restore: blob e9d625633b26 == HEAD blob, git diff HEAD empty. SMS measurement: service-sms dist SmsServicePlugin with non-secret providerOptions only gave isConfigured=false for twilio and for aliyun, while the control with authToken gave isConfigured=true.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 — both through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create of draft PR #22778 with its assignee leg (relay: POST /repos/objectstack-ai/objectstack/pulls, then POST /repos//issues/22778/assignees), read back identical 18626/18626 bytes, assignee os-project-manager; (2) this os-dev-report comment via scripts/pm/post-stamped.mjs (relay: POST /repos//issues/22748/comments). git push is not a REST write. No label write: the dispatch named none, and skip-changeset does not apply because the PR carries a changeset.",
"gates": "Everything below is on head 4fdbd86 with a clean tree. 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' (no paths) derived 123 commands. All 123 ran, each exit code recorded before any pipe, and all 123 exited 0. 'node scripts/pm/dispatch-gates.mjs --ran ran2.list' gave 'Run reconciliation — 123 derived, 123 run, 0 NOT-MEASURED, 0 UNRUN.' The first sweep, on bd82b77, had 4 non-zero commands, each fixed before the final one: check:llms-txt exit 1 (inventory counts 203 to 204, system 34 to 35); check:spec-parsed-alias exit 1 (SmsProvider pinned isomorphic, 775 to 776); check:skill-examples exit 3 and check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET, which needed the spec dist rebuild and 8 packages built. Other gates found during the work and fixed: check:quick-reference-counts (System 34 to 35 pages), check:skill-top-level-keys (exit 0 after the skill edit), check-skills-token-ratchet (5826 / 5833). Roster gates for the directories written, each exit 0: check:meta-url-spelling, check:authz-resolver, check:error-code-casing, check:filter-alias-parity, check-changeset-fixed, check:published-readme-exports, check:stack-collection-maps. ESLint narrowed to the 9 touched script files ('--no-inline-config --format json'): 9 files, 0 errors, 0 warnings. eslint.config.mjs enables no type-aware linting, so the narrowing cannot move any untouched file's verdict. NOT MEASURED (CI's): repo-wide pnpm lint, the workspace / consumer type-check lanes, and the path-scheduled CI jobs. CI not awaited; CONTRACT_REVIEW is owed.",
"line_budget": "26 files, +897 / -30 against merge-base a18c514 (git diff --shortstat a18c514 4fdbd86), under 3000 changed lines. Governed path: skills/objectstack-platform/SKILL.md (Tier H). File 489 to 488 lines, 23322 to 23301 bytes, 5831 to 5826 tokens (ceiling 5833). All skills//SKILL.md together: 4410 to 4409 lines, 210541 to 210520 bytes. The three keys were appended to an existing line, paid for by deleting the duplicated '; the input type is ObjectStackDefinitionInput', with no re-wrap.",
"deviations": [
"Governed surface (Tier H): skills/objectstack-platform/SKILL.md. check:skill-top-level-keys reconciles that page's top-level key list against COMPOSE_KEY_DISPOSITIONS in both directions, so adding the keys without it is a red gate. The edit is minimal and token-neutral. The PR body carries the maintainer summary, and landing needs an authorized approval.",
"File surface, forced by gates or pins: packages/cli/test/option-b-reader-acceptance.pin.test.ts (literal envelope-key list); packages/spec/src/assembled-package-body.test.ts (ARTIFACT_ENVELOPE_KEYS); packages/spec/src/type-alias-convention.pin.test.ts (check:spec-parsed-alias); packages/spec/llms.txt (check:llms-txt, which also replaced the row's stale 'Compliance', a retired schema); content/docs/getting-started/quick-reference.mdx (check:quick-reference-counts); content/docs/getting-started/quick-start.mdx (the 'Not in the table' list gained appName / email / sms, plus the already-missing devHint / devLogins in the sentence touched).",
"Shape: sms.providerOptions is refused whole, not narrowed to its non-secret members. Measured: every non-log SMS transport needs a credential inside providerOptions, and nothing merges an env credential into it, so a narrowed block would be a declared key no deployment can deliver through. The triage named 'any SMS token' as environment-only; this is that, at the key that carries it.",
"The SMS provider-vocabulary parity pin (SmsProviderSchema to SMS_TRANSPORT_PROVIDERS) sits in the cli rider file, not in @objectstack/service-sms. service-sms's KNOWN_UNALIASED_TEST_IMPORTS entry carries no @objectstack/spec, and widening it is forbidden, while cli depends on both packages already.",
"The by-name refusal is the strictObject guidance idiom: the issue code stays unrecognized_keys, but the message names the key and carries the env-var prescription. The triage's 'rather than as an unrecognized key' is read as 'not a bare unknown-key refusal'. If a declared z.never key was meant, that is open question 1.",
"Process: one spec test launch spelled '-- --maxWorkers=2' (the bare '--' trap). I killed it by its own process group before any reading and re-ran correctly; no reading comes from it.",
"Clause-② line: carried byte-for-byte as the claim's, in PR body line 2 and in the changeset (cmp-verified). The landed shape matches it: widening only."
],
"files_changed": [
".changeset/22748-stack-email-sms-door.md (+20/-0)",
"content/docs/getting-started/quick-reference.mdx (+1/-1)",
"content/docs/getting-started/quick-start.mdx (+4/-2)",
"content/docs/references/index.mdx (+6/-5)",
"content/docs/references/system/email-config.mdx (+66/-6)",
"content/docs/references/system/index.mdx (+2/-1)",
"content/docs/references/system/meta.json (+1/-0)",
"content/docs/references/system/sms-config.mdx (+77/-0)",
"docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md (+1/-1)",
"packages/cli/src/commands/serve-stack-email-sms-door.contract.test.ts (+133/-0)",
"packages/cli/test/option-b-reader-acceptance.pin.test.ts (+5/-2)",
"packages/spec/api-surface/system.json (+11/-0)",
"packages/spec/authorable-defaults/system.json (+1/-0)",
"packages/spec/authorable-surface/system.json (+15/-0)",
"packages/spec/declaration-map/system.json (+8/-0)",
"packages/spec/export-origins/system.json (+11/-0)",
"packages/spec/json-schema.manifest/system.json (+4/-0)",
"packages/spec/llms.txt (+2/-2)",
"packages/spec/src/assembled-package-body.test.ts (+3/-0)",
"packages/spec/src/stack-email-sms.test.ts (+235/-0)",
"packages/spec/src/stack.zod.ts (+50/-0)",
"packages/spec/src/system/email-config.zod.ts (+117/-4)",
"packages/spec/src/system/index.ts (+1/-0)",
"packages/spec/src/system/sms-config.zod.ts (+106/-0)",
"packages/spec/src/type-alias-convention.pin.test.ts (+15/-3)",
"skills/objectstack-platform/SKILL.md (+2/-3)"
],
"open_questions": [
{
"question": "The by-name refusal: keep the strictObject guidance form, or switch each credential to a declared-refusing key?",
"options": [
"A: keep the guidance form (landed). Same idiom as top-level storage and server.port / host / cors; the credentials stay out of the JSON schema, the reference docs and the authorable surface; TS still rejects the literal (TS2353).",
"B: a z.never declared key per credential (the viewItems form). The message carries no 'Unrecognized key(s)' wording, but each secret name becomes a declared key in the JSON schema, the docs and the authorable-surface baseline."
],
"recommendation": "A, because it is the repo's existing idiom for wrong-layer keys, and listing secret names as declared properties invites writing them."
},
{
"question": "sms.provider is admitted per the triage's letter, but measured at bootStack it cannot select the delivering transport when the credentials come from env (see finding 1). Keep it and fix the SMS plugin, or refuse it by name until the plugin honours it?",
"options": [
"A: keep it admitted (landed; its describe states the behaviour) and file the implementation gap in the services lane.",
"B: refuse sms.provider by name now, pointing to OS_SMS_PROVIDER / Settings, which leaves sms carrying only retries."
],
"recommendation": "A, because the base ruling treats declared-but-not-honoured as an implementation gap to fix, not a reason to narrow at the consumer, and the ruling names provider."
}
],
"out_of_scope_findings": [
"class: c · reach: public door bootStack (@objectstack/verify), measured on this tree. A defineStack({ …, sms: { provider: 'twilio' } }) with OS_SMS_TWILIO_ACCOUNT_SID / _AUTH_TOKEN / _FROM_NUMBER in env but no OS_SMS_PROVIDER boots with sms.isConfigured()=false. The log shows 'SmsServicePlugin: provider=twilio selected but transport build failed (accountSid and authToken are required) — falling back to LogSmsTransport' and 'SMS will NOT be sent'. Control: the same plus OS_SMS_PROVIDER=twilio gives 'transport rebuilt from settings (provider=twilio)' and isConfigured()=true. Cause: applySmsSettings decides from the settings namespace's own provider value and ignores the constructor's provider, and the reader merges no env credential into providerOptions. So the stack's sms.provider (and a raw config.sms.provider on serve) never selects a delivering aliyun / twilio transport. Fix belongs in @objectstack/service-sms (domain:services). · dedupe words: sms.provider stack not honoured, SmsServicePlugin applySmsSettings provider default source, OS_SMS_PROVIDER required settings namespace, config.sms.provider LogSmsTransport fallback, sms settings env override provider",
"carrier: none · noted, not filed (Acceptance notes): resolveEmailCapabilityArg's missing-key refusal (plugin-email src/capability-arg.ts) still prescribes 'set OS_EMAIL_API_KEY (or config.email.apiKey)'. A defineStack-built stack now refuses the second form by name, with the first form as its prescription, so the author is corrected in one step. The text stays true for a raw serve config, and the dispatch bars reader changes.",
"carrier: none · noted, not filed (Acceptance notes): the SMTP transport reads timeout and transportOptions, and resend / postmark read endpoint, but no spec text declares them. The strict email.options block therefore refuses them. transportOptions is the escape hatch and can carry credentials.",
"carrier: none · noted, not filed: running a check:* sweep in parallel with spec's --project repo suite makes cube-member-inner-name-retirement.test.ts flake (ENOENT on the gitignored json-schema tree that check:authorable-surface / gen:schema regenerate). Local-concurrency only; CI runs them in separate jobs."
]
}objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsSeat order on PR #22778 at
4fdbd8611f: patch round 1. Mergemain(conflict), take B on open question 2, and A on question 1domain:specseat 1 (#6017) ·os-project-manager· sessionsession_01S3aAf11JjbW1mSGL1EhfFj· 2026-10-11T06:43Z · holder of claim6105299924. Thread-read: 6106330513 (the dev'sos-dev-report).1. The merge. PR #22778 is
dirtyagainstmain(a2e94c2a05, 11 commits past the basea18c514965), so no CI has run on its head.packages/spec/src/type-alias-convention.pin.test.tsconflicts on content. Keep both sides' entries, the pin's own rule decides the count, and re-run that pin.- The generated docs regenerate with the repo's tooling (
gen:schema, thengen:docs), never by hand. - Use a merge commit, never a rebase or force-push.
2. Open question 1 (the by-name refusal): A. Keep the strict-object guidance form. It is the stack's own idiom for keys at the wrong layer (top-level
storage,server.port/host/cors), and it keeps secret names out of the JSON schema, the reference docs and the authorable surface.3. Open question 2 (
sms.provider): B. Refusesms.providerby name, prescribingOS_SMS_PROVIDER/ Settings → SMS Delivery, as you refusesms.providerOptions.- The measurement: your own finding 1 shows the key cannot select a delivering transport from the stack.
applySmsSettingsdecides from the settings namespace and ignores the constructor'sprovider, and withproviderOptionsrefused no credential can reach it. - The rule: this PR would be a new declaration, and a declared key the runtime does not honour is the exact case the base rule bars ("never let an author declare a capability the runtime does not deliver"). The rule against narrowing at the consumer protects existing declarations, which this is not.
- Re-admitting it:
sms.provideris re-admitted by the PR that makesSmsServicePluginhonour it. This seat files that services-lane card from your finding 1. - What changes:
smsthen carriesretriesalone. Keep it if it reads true; dropsmsand refuse it whole by name if aretries-only block would mislead.- Say which you chose, and why, in
deviations. - Update the changeset, the
Clause-②:line (on the changeset, PR body line 2, and report it so the seat edits the claim), the generated docs and the skill line if their text namessms.provider.
Then:
- Re-run the gates your diff derives, exits on disk first.
- Post the patch-round
os-dev-reporton this card with the new head. - The review at
CONTRACT_REVIEW_TIERruns on that head. - This PR is Tier H (
skills/objectstack-platform/SKILL.md), so it lands only after the maintainer's hand or an authorized APPROVED review.
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22748,
"status": "done",
"round": "patch round 1 (seat order 6106350788)",
"branch": "claude/issue-22748-stack-email-sms-door",
"pr": "#22778",
"head": "34286f646a982afcdc8ecdc962ad1afeaa93d043",
"session": "session_01S3aAf11JjbW1mSGL1EhfFj",
"premise_still_valid": true,
"summary": "Patch round 1 on head 34286f6, the order's three items in sequence. (1) Merged main a2e94c2 through scripts/pm/os-regen-merge.sh as merge commit f845dfd; no rebase, no force-push. The content conflict in type-alias-convention.pin.test.ts kept both sides' entries, and the merged pin was green at 777. The one generated artifact the driver deferred, content/docs/references/index.mdx, was regenerated with gen:schema then gen:docs in its own commit 3a68830. check:generated was all current after the merge. (2) Open question 1 → A: the strictObject guidance form is unchanged. (3) Open question 2 → B: sms.provider is now refused by name with the OS_SMS_PROVIDER / Settings → SMS Delivery prescription, sharing providerOptions' prescription. sms keeps retries alone (see deviations). SmsProviderSchema has no consumer left, so it is removed with its isomorphic-alias pin (that file is back to main's bytes, 776) and the cli parity half. Removing it from the committed json-schema.manifest and authorable-surface baselines was done by restoring both from main's side (a2e94c2) and regenerating; neither was ever on main, so no retired-def entry was owed. The changeset, its Clause-② line, PR body line 2 and the generated docs are updated. The PR body was refreshed through post-stamped --body (18459 bytes, read back identical). The skill line names sms only, so it is unchanged. The worktree is removed after this report posts.",
"clause2_line_for_claim": "Clause-②: yes (widening: the stack definition admitsappName, the non-secret members ofemail, andsmscarryingretriesalone, which it refused as unrecognized keys)",
"reader_key_census": [
"resolveEmailCapabilityArg · email.provider / defaultFrom / retries / queueDelivery / persist / appName / defaultTemplateContext → non-secret → admitted (unchanged)",
"resolveEmailCapabilityArg · email.options.host / port / secure / user / messageStream → non-secret → admitted, strict block (unchanged)",
"resolveEmailCapabilityArg · email.apiKey, email.options.apiKey → SECRET (OS_EMAIL_API_KEY) → refused by name (unchanged)",
"resolveEmailCapabilityArg · email.options.password → SECRET (OS_EMAIL_SMTP_PASSWORD) → refused by name (unchanged)",
"resolveCapabilityArgument / resolveDeploymentAppName · appName (top level) → non-secret (OS_APP_NAME) → admitted (unchanged)",
"resolveSmsCapabilityArg · sms.retries → non-secret, no other carrier, delivered (measured) → admitted",
"resolveSmsCapabilityArg · sms.provider → non-secret but not delivered from a stack (measured) → refused by name, prescribing OS_SMS_PROVIDER / Settings → SMS Delivery (CHANGED this round)",
"resolveSmsCapabilityArg · sms.providerOptions (plus flat authToken / accessKeySecret) → SECRET-BEARING → refused by name (unchanged)"
],
"tests": "On head 34286f6, through os-verify-lock, after building the closure ('pnpm turbo run build --filter=@objectstack/cli... --filter=@objectstack/example-showcase^... …': Tasks 68 successful, 68 total). Spec --project local over the touched and adjacent files (stack-email-sms, assembled-package-body, type-alias-convention.pin, compose-key-dispositions-export.pin, shared/alias-integrity, shared/strict-object, system/email-config): Test Files 7 passed (7), Tests 115 passed (115). The new pin file has 16 cases, including 'sms.provider is refused by name, prescribing OS_SMS_PROVIDER and Settings'. 'pnpm --filter @objectstack/spec typecheck' exited 0 (test layer 52 / 246 / 135 held). 'pnpm --filter @objectstack/cli typecheck' exited 0 (test layer 3 / 28 / 6 held). cli --project unit over the rider (4 cases), serve-auth-app-name, serve-email-capability, serve-sms-capability and test/option-b-reader-acceptance.pin: Test Files 5 passed (5), Tests 44 passed (44). Consumers: core capability-composition 12 / 12, plugin-email capability-arg.config-parity.contract 8 / 8, runtime artifact-collections 22 / 22, verify harness.served-composition 7 / 7. 'pnpm --filter @objectstack/spec check:generated': 'All 14 generated artifacts are up to date'. Ablation via scripts/ablation-replace.mjs, src-resolved, two legs. Email leg: anchor 'guidance: { apiKey: STACK_EMAIL_API_KEY_PRESCRIPTION },' x1 to x0, blob e9d625633b26 to d5c0f4a5cb8b, giving Tests 1 failed | 15 passed (16), exactly 'email.apiKey is refused by name…' red. SMS leg: anchor 'provider: STACK_SMS_PROVIDER_SETTINGS_PRESCRIPTION,' renamed provider_ABLATED (x1 to x0), blob 0015c421bb5b to 8ae310bea6c3, giving Tests 1 failed | 15 passed (16), exactly 'sms.provider is refused by name…' red (received 'Unrecognized key(s) on the stacksmsblock:provider. This block is strict from birth…', with no OS_SMS_PROVIDER). Each leg was restored to its HEAD blob with git diff HEAD empty. os validate --json on app-crm, app-multi-package, app-showcase and app-todo: all valid:true, byte-identical minus duration to round 1's pre-change reading on the base dist. Public door os validate: a stack with sms.provider exits 1 ('✗ sms: Unrecognized key(s) on the stacksmsblock:provider. • Not authorable here. … OS_SMS_PROVIDER …'); one with email.apiKey exits 1 with the OS_EMAIL_API_KEY prescription; the admitted shape (appName, email { provider, defaultFrom, persist }, sms { retries }) exits 0. retries measurement through bootStack: sms { retries: 2 } plus OS_SMS_PROVIDER=twilio and the OS_SMS_TWILIO_* credentials logged 'transport rebuilt from settings (provider=twilio)' and gave isConfigured()=true, service.options.retries=2, transport=TwilioSmsTransport. NOT re-run this round: the full spec local / repo suites (round 1, pre-merge tree: 644 files / 19228 passed | 1 todo; repo 54 / 55, the flake 20 / 20 when re-run).",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 this round, both through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches): (1) the PR #22778 body refresh via scripts/pm/post-stamped.mjs --body=22778 --ack-through=6106397226 (relay: PATCH /repos//issues/22778), 18459/18459 bytes read back identical; (2) this patch-round os-dev-report comment via post-stamped --comment=22748 (relay: POST /repos//issues/22748/comments). git push is not a REST write. No label write.",
"gates": "On head 34286f6 with a clean tree. 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' (no paths) derived 123 commands, the same set as round 1. All 123 ran, each exit code recorded before any pipe, and all 123 exited 0; this includes check-changeset-no-major and check-adr-0087-registration over the new Clause-② line. 'node scripts/pm/dispatch-gates.mjs --ran ran3.list' gave 'Run reconciliation — 123 derived, 123 run, 0 NOT-MEASURED, 0 UNRUN.' Roster gates for the directories written, each exit 0: check:meta-url-spelling, check:authz-resolver, check:error-code-casing, check:filter-alias-parity, check-changeset-fixed, check:published-readme-exports, check:stack-collection-maps. ESLint narrowed to the 8 touched script files ('--no-inline-config --format json'): 8 files, 0 errors, 0 warnings; eslint.config.mjs enables no type-aware linting, so the narrowing is complete. The derivation noted origin/main had moved to a8f24b0 (#22781, packages/verify only), with no commit touching what it derives from. That commit overlaps no file here, so no second merge was made. NOT MEASURED (CI's): repo-wide pnpm lint, the workspace type-check lanes, and the path-scheduled CI jobs.",
"line_budget": "25 files, +858 / -27 against merge-base a2e94c2 (git diff --shortstat a2e94c2 34286f6), under 3000 changed lines. Governed path: skills/objectstack-platform/SKILL.md (Tier H), unchanged this round: 489 to 488 lines, 5831 to 5826 tokens (ceiling 5833); all skills//SKILL.md together 4410 to 4409 lines.",
"deviations": [
"sms keepsretriesalone; it is not refused whole. retries is the SmsService attempt budget. setTransport swaps only the transport, so retries applies to whichever transport the settings namespace selects, measured through bootStack (OS_SMS_PROVIDER=twilio: TwilioSmsTransport, options.retries=2, isConfigured()=true). It has no environment or settings carrier (the reader reads only cfgSms.retries, and the sms settings manifest has no retries key), so refusing sms whole would make a live knob unreachable. A retries-only block does not mislead, because a provider written beside it is refused by name with the setting that works.",
"SmsProviderSchema removed with its only consumer: the export, its type, its isomorphic-alias pin (type-alias-convention.pin.test.ts is back to main's bytes at 776) and the cli parity describe. It was never released; the change that makes SmsServicePlugin honour a constructor provider re-admits sms.provider and brings the vocabulary back.",
"Baselines after the removal: gen:schema refused while system/SmsProvider and system/StackSmsConfig:provider were still in json-schema.manifest/system.json and authorable-surface/system.json. Both entries were added by this PR and never on main, so the files were restored from main's side (git restore --source=a2e94c2a05, worktree only) and regenerated. The diff against main is now additions only. No RETIRED_DEFS entry was owed, because nothing published was unpublished.",
"Clause-② line changed with the shape. The seat edits the claim to: 'Clause-②: yes (widening: the stack definition admitsappName, the non-secret members ofemail, andsmscarryingretriesalone, which it refused as unrecognized keys)'. It is carried byte-for-byte in the changeset and as PR body line 2 (cmp-verified).",
"Merge: one content conflict, in type-alias-convention.pin.test.ts. The resolution kept main's 775 → 776 (#22726) note and added this branch's pin as 776 → 777. Next commit removed this branch's pin with SmsProviderSchema, so the file now equals main's.",
"Docs-drift bot comment 6106397226: the five hand-written pages naming appName were re-read, and none states the key is unsettable in a config file or names serve.ts as the reader. No edit."
],
"files_changed": [
".changeset/22748-stack-email-sms-door.md (+19/-0)",
"content/docs/getting-started/quick-reference.mdx (+1/-1)",
"content/docs/getting-started/quick-start.mdx (+4/-2)",
"content/docs/references/index.mdx (+6/-5)",
"content/docs/references/system/email-config.mdx (+66/-6)",
"content/docs/references/system/index.mdx (+2/-1)",
"content/docs/references/system/meta.json (+1/-0)",
"content/docs/references/system/sms-config.mdx (+83/-0)",
"docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md (+1/-1)",
"packages/cli/src/commands/serve-stack-email-sms-door.contract.test.ts (+104/-0)",
"packages/cli/test/option-b-reader-acceptance.pin.test.ts (+5/-2)",
"packages/spec/api-surface/system.json (+9/-0)",
"packages/spec/authorable-defaults/system.json (+1/-0)",
"packages/spec/authorable-surface/system.json (+14/-0)",
"packages/spec/declaration-map/system.json (+6/-0)",
"packages/spec/export-origins/system.json (+9/-0)",
"packages/spec/json-schema.manifest/system.json (+3/-0)",
"packages/spec/llms.txt (+2/-2)",
"packages/spec/src/assembled-package-body.test.ts (+3/-0)",
"packages/spec/src/stack-email-sms.test.ts (+245/-0)",
"packages/spec/src/stack.zod.ts (+51/-0)",
"packages/spec/src/system/email-config.zod.ts (+117/-4)",
"packages/spec/src/system/index.ts (+1/-0)",
"packages/spec/src/system/sms-config.zod.ts (+103/-0)",
"skills/objectstack-platform/SKILL.md (+2/-3)"
],
"open_questions": [],
"out_of_scope_findings": [
"class: c · reach: public door bootStack, measured round 1 (sms.provider 'twilio' plus OS_SMS_TWILIO_ without OS_SMS_PROVIDER gives isConfigured()=false; with OS_SMS_PROVIDER, true). The seat files the services-lane card per order 6106350788; this PR now refuses the key, so a stack can no longer reach the trap. A raw config.sms.provider on serve still can. · dedupe words: sms.provider stack not honoured, SmsServicePlugin applySmsSettings constructor provider ignored, OS_SMS_PROVIDER required, config.sms.provider LogSmsTransport fallback",
"carrier: none · noted, not filed (Acceptance notes): resolveEmailCapabilityArg's missing-key text still prescribes 'or config.email.apiKey', a form a built stack refuses by name with OS_EMAIL_API_KEY as the prescription.",
"carrier: none · noted, not filed (Acceptance notes): the SMTP transport reads timeout and transportOptions, and resend / postmark read endpoint, but no spec text declares them, so the strict email.options block refuses them."
]
}objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actions✅ ACCEPT: PR #22778 at
34286f646a(Fixes #22748). The review atCONTRACT_REVIEW_TIERruns next. It is Tier H (skills/**), so it lands by the maintainer's hand or an authorized APPROVED reviewdomain:specseat 1 (#6017) ·os-project-manager· sessionsession_01S3aAf11JjbW1mSGL1EhfFj· 2026-10-11T07:49Z · holder of claim6105299924, whoseClause-②:line this seat edited to the landed shape. Reports:6106330513(round 0) and6106819772(patch round 1, seat order6106350788). Thread-read: 6106819772.Checklist, read against GitHub:
- PR shape: draft, base
main,Fixes #22748, assigneeos-project-manager. Line 2, the changeset and the claim carry the same line:Clause-②: yes (widening: the stack definition admitsappName, the non-secret members ofemail, andsmscarryingretriesalone, which it refused as unrecognized keys). - Scope: 25 files, +858 / −27 against
a2e94c2a05.stack.zod.tsandsystem/email-config.zod.ts/sms-config.zod.ts.- The pins and a cli contract rider.
- The generated baselines and reference docs, regenerated by the repo's tooling.
- The changeset.
- One token-neutral line in
skills/objectstack-platform/SKILL.md, forced bycheck:skill-top-level-keys. That line makes the PR governed.
- The shape, against triage
6104830037, by the reader-key census:- Admitted: the non-secret
emailmembers (provider, defaultFrom, retries, queueDelivery, persist, appName, defaultTemplateContext, and a strictoptionsblock of host / port / secure / user / messageStream), top-levelappName, andsms.retries. - Refused by name, each naming the env var or Settings that carries it:
email.apiKey,email.options.apiKey,email.options.password,sms.providerOptionswith its flat tokens, andsms.provider(order B: measured not delivered from a stack). - The triage's stop rule did not fire: every secret has an env carrier.
- Admitted: the non-secret
- Open questions: both answered by seat order
6106350788(1 → A, the stack's guidance idiom; 2 → B).SmsProviderSchemaleft with its only consumer, and it was never released.
Evidence:
- Gates: 123 derived, 123 run, all exit 0 on the head (
--ran: 0 NOT-MEASURED, 0 UNRUN).check:generatedis current. - Pins: 16 cases. Two ablation legs (
email.apiKey,sms.provider) each turn exactly their own pin red, and the restores are blob-equal. - Public door
os validate:email.apiKeyandsms.providereach exit 1 with their prescription. The admitted shape exits 0. The four example apps are byte-identical before and after. sms.retriesmeasured throughbootStack: withOS_SMS_PROVIDER=twilioand the credentials in env,retries: 2reachesTwilioSmsTransportandisConfigured()is true.- CI on the head: 33 success, 2 skipped.
Filed and noted:
- service-sms: a configured
sms.providernever selects a delivering transport unlessOS_SMS_PROVIDERis also set —applySmsSettingsdecides from the settings namespace and no env credential reachesproviderOptions#22789 (domain:services):SmsServicePluginignores a configuredprovider. Its fix re-admitssms.provider. - Acceptance notes:
resolveEmailCapabilityArg's missing-key text still offers "or config.email.apiKey". The SMTPtimeout/transportOptionsand resend / postmarkendpointare read but undeclared, so the strictoptionsblock refuses them.
- PR shape: draft, base
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsHandover: PR #22778 waits on a human merge (Tier H). This seat stands down, and the next
domain:specseat lands itdomain:specseat 1 (#6017) ·os-project-manager· sessionsession_01S3aAf11JjbW1mSGL1EhfFj· 2026-10-11T08:03Z · holder of claim6105299924. The seat stands down by the maintainer's word (「当前任务处理完,合并后就下班」). ⛔ Not a release topm:queue: the work is done and accepted.- State: PR feat(spec): the stack definition admits
appNameand the non-secretemail/smsmembers; their credentials are refused by name #22778 at34286f646a.- Contract review PASS
6106904303; ACCEPT6106836535; CI 38 success / 4 skipped. - Tier H because of
skills/objectstack-platform/SKILL.md. - The maintainer speed-read
6106919873is posted,needs-user-decisionis on the PR, and reviews are requested from os-zhuang and hotlong. The PR is still a draft.
- Contract review PASS
- To land it (whoever holds
domain:specwhen the approval exists, unless the maintainer merged it by hand):- Confirm an APPROVED review from an account in
GOVERNED_APPROVERSon the current head, or the maintainer's merge. - If the head moved, the approval and the PASS must cover it: a pure merge of
maincarries the PASS by Regen-provenance whenunexplainedPathsBetweenis empty, and anything else needs a fresh record. - Run
check-expected-skipsandcheck-governed-merges --pr 22778, and check thatmergeable_stateis clean. - Relay
pr_ready+automerge_enable, and confirmadded_to_merge_queue. - After the merge: the landing record on spec: the stack definition has no
email,smsorappNamekey, so theconfig.email/config.smscontractservereads is unreachable from adefineStackconfig #22748 (it closes byFixes #22748), removepm:dispatched, run the mis-close scan, and removeneeds-user-decisionfrom the PR.
- Confirm an APPROVED review from an account in
- Caretaker: the next
domain:specseat, which lands only. The review judgment is the record above, ⛔ not to be redone without cause. - Related: service-sms: a configured
sms.providernever selects a delivering transport unlessOS_SMS_PROVIDERis also set —applySmsSettingsdecides from the settings namespace and no env credential reachesproviderOptions#22789 (domain:services) must land beforesms.provideris re-admitted on the stack.
- State: PR feat(spec): the stack definition admits
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsLanded: PR #22778 →
85edd61cdb(Tier H, merged on the maintainer's approval). The card is closed (completed) andpm:dispatchedis removeddomain:specseat 1 (#6017) ·os-project-manager· sessionsession_01S3aAf11JjbW1mSGL1EhfFj· 2026-10-11T08:57Z · holder of claim6105299924, released by this landing. The handover note6106949020is void: the seat landed it before standing down.- Landed: through the merge queue at 2026-10-11T08:56Z as
85edd61cdb, a squash with one parent,098481744f. os-zhuang approved the head34286f646aat 2026-10-11T08:03Z (an account inGOVERNED_APPROVERS) and queued it at the same minute. Its first queue group ran green; the queue rebuilt the group once when PR fix(lint): one-line verdicts for the nav, object-reference, capability, action-name, mapping-target, view-container and interface-page rules;os explain RULE_IDcarries their reasoning #22799 was ejected ahead of it, and the rebuilt group passed. - The review chain:
- the patch-round order
6106350788; - the ACCEPT
6106836535; - contract review PASS
6106904303on34286f646a; - the maintainer speed-read
6106919873; - the maintainer's APPROVED review.
- the patch-round order
- Content check: all 25 PR paths on
85edd61cdbare blob-equal to the reviewed and approved head34286f646a.origin/maincarries the env-var refusals (OS_EMAIL_API_KEYinstack.zod.tsandsystem/email-config.zod.ts). - What now holds (
@objectstack/specminor,Clause-②: yes (widening)):defineStackadmitsappName, the non-secret members ofemail, andsmscarryingretriesalone. The non-secretemailmembers areprovider,defaultFrom,retries,queueDelivery,persist,appNameanddefaultTemplateContext, plus a strictoptions(host,port,secure,user,messageStream). Before this, all three keys were refused as unrecognized, so the documentedconfig.emailcould reach no built stack.- Credentials are refused by name, and each refusal names the environment variable or settings page that carries the secret (for example
email.apiKey→OS_EMAIL_API_KEY). sms.provideris also refused by name for now, pointing atOS_SMS_PROVIDERand the settings page. The SMS plugin does not honour a constructor-passed provider, so a stack-declared one would never select the channel that sends. Re-admitting it waits on service-sms: a configuredsms.providernever selects a delivering transport unlessOS_SMS_PROVIDERis also set —applySmsSettingsdecides from the settings namespace and no env credential reachesproviderOptions#22789 (domain:services).- The governed touch is one line of
skills/objectstack-platform/SKILL.md: the top-level key list thatcheck:skill-top-level-keysholds equal to the stack.
- Mis-close scan: the PR body and the squash message carry one closing keyword,
Fixes #22748, and that is the only issue the merge closed (feat(spec): the stack definition admitsappNameand the non-secretemail/smsmembers; their credentials are refused by name #22778 merged alone in its queue group). - Follow-up: service-sms: a configured
sms.providernever selects a delivering transport unlessOS_SMS_PROVIDERis also set —applySmsSettingsdecides from the settings namespace and no env credential reachesproviderOptions#22789 must land beforesms.provideris re-admitted on the stack. That re-admission is a laterdomain:speccard, not filed here: it is gated on a fix that has not landed.
- Landed: through the merge queue at 2026-10-11T08:56Z as
Filing gate: ① a finding (declared ≠ reachable) with a public door. Reach:
defineStack({ manifest, email: { persist: false } })throwsSTACK_SCHEMA_INVALID"Unrecognized key(s) on this stack definition:email"; the same holds forsmsandappName. Measured by the #22301 dev on PR #22747's tree (report6103467807,out_of_scope_findings1). The seat confirmed the schema half onorigin/mained1de8c2db.Hands-on reader: triage grades it into the
domain:specqueue (the stack schema ispackages/spec/src/stack.zod.ts); the readers' new home after PR #22747 is@objectstack/plugin-email/@objectstack/service-sms(domain:services).Dedupe: the semantic issue search "defineStack refuses email sms appName key EmailServiceConfigSchema config.email unrecognized stack key" returned 2 hits, both closed and different. #5307 added three keys INSIDE
EmailServiceConfigSchema; #5105 was a docs example. Neither covers the stack-level door.The two halves (read on
origin/mained1de8c2db)@objectstack/spec'sEmailServiceConfigSchema(packages/spec/src/system/email-config.zod.ts) documentsconfig.email.*"fromobjectstack.config.ts" as the operator contract.servereadsconfig.email,config.smsand the top-levelconfig.appName(packages/cli/src/commands/serve.ts:4693,:6345–:6404). After 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 lands,bootStackreads them too.packages/spec/src/stack.zod.tsdeclares noemail,smsorappNamekey on the stack definition (git grepfindsanalyticsCubesat:710and none of the three), and the definition refuses unrecognized keys.defineStack, andos build/os validaterefuse a configdefineStackdid not build (STACK_PROVENANCE_MISSING). Onlyos serveboots it.OS_EMAIL_*/OS_SMS_*environment, or from a config the build refuses.For triage
Per the base ruling (spec declaration > implementation > docs), this is either a missing stack key (admit
email/sms/appName, typed by the schemas that already exist) or a dead declaration (retire theconfig.*arm and its docs). Which one is triage's or the maintainer's call. It is pre-existing: PR #22747 moves the readers without changing what they read.