email_template.owner_qbit: written by EmailApi.ensureTemplate but never read, and the built-in did not set it. Fixed: built-in names qbit-email; messages from a template record its owner as sender_qbit unless the request names one (EmailSetupTest.theTemplatesOwnerQbitIsTheDefaultSender); field is read-only with help.
email_provider.default_slot: hidden, non-editable column that enforces one default provider through a unique key; no reader needed. Left as is.
email_message.bodyEncrypted and retentionDays do not exist in this repo (no such columns, DDL, or metadata). Body encryption lives in EmailBodyEncryption and retention uses the config's bodyRetentionDays in email_retention. Nothing to fix.
Added
email-defaults.changelog.sql (sql-check preconditions only, -- rollback empty, tested through real Liquibase: second update runs nothing, edits survive, host brand untouched, missing table MARK_RAN, rollback keeps host rows, seeded brand passes contrast checks and changes no rendering); help/qbit-email.yaml + EmailHelp (STAND-IN core#1198); Setup card using EmailApi.readiness and the sender's provider/brand resolution; EmailTablesGuardrailTest; README, UPGRADE.md, CHANGELOG.
Tests
./mvnw verify green (314 tests, checkstyle, JaCoCo). EmailHomePolishTest expectations updated for the new first section.
Not done
No seeded provider: it is the host's own connection.
No "Create defaults" action: the one seedable row ships in the changelog.
Existing built-in templates created earlier keep an empty Owner Qbit.
## Why
Owner requirement: every qbit table is populated, used, and explains itself when empty.
## Audit per table
| table | used | seeded | empty-state help |
|---|---|---|---|
| email_brand | renderer (EmailBrands), API readiness, unsubscribe | `neutral`, non-default, only when no brand exists | yes |
| email_template | outbox, renderer, EmailApi.ensureTemplate, EmailBuiltIns | no: built-in secure-message created on first use and when the home opens; others registered by qbits | yes |
| email_template_version | renderer, versions, actions | no: created with templates | yes |
| email_provider | dispatcher, service, readiness, home | no: host connection (server + secret variable NAMES) | yes |
| email_provider_sender_address | service, API | no: optional | yes |
| email_message, message_attempt, webhook_inbox, event, suppression | dispatcher, inbox processor, tracking | no: runtime data | yes |
## Half-wired
- `email_template.owner_qbit`: written by `EmailApi.ensureTemplate` but never read, and the built-in did not set it. Fixed: built-in names `qbit-email`; messages from a template record its owner as `sender_qbit` unless the request names one (`EmailSetupTest.theTemplatesOwnerQbitIsTheDefaultSender`); field is read-only with help.
- `email_provider.default_slot`: hidden, non-editable column that enforces one default provider through a unique key; no reader needed. Left as is.
- `email_message.bodyEncrypted` and `retentionDays` do not exist in this repo (no such columns, DDL, or metadata). Body encryption lives in `EmailBodyEncryption` and retention uses the config's `bodyRetentionDays` in `email_retention`. Nothing to fix.
## Added
`email-defaults.changelog.sql` (sql-check preconditions only, `-- rollback empty`, tested through real Liquibase: second update runs nothing, edits survive, host brand untouched, missing table MARK_RAN, rollback keeps host rows, seeded brand passes contrast checks and changes no rendering); `help/qbit-email.yaml` + `EmailHelp` (STAND-IN core#1198); Setup card using `EmailApi.readiness` and the sender's provider/brand resolution; `EmailTablesGuardrailTest`; README, UPGRADE.md, CHANGELOG.
## Tests
`./mvnw verify` green (314 tests, checkstyle, JaCoCo). `EmailHomePolishTest` expectations updated for the new first section.
## Not done
- No seeded provider: it is the host's own connection.
- No "Create defaults" action: the one seedable row ships in the changelog.
- Existing built-in templates created earlier keep an empty Owner Qbit.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Why
Owner requirement: every qbit table is populated, used, and explains itself when empty.
Audit per table
neutral, non-default, only when no brand existsHalf-wired
email_template.owner_qbit: written byEmailApi.ensureTemplatebut never read, and the built-in did not set it. Fixed: built-in namesqbit-email; messages from a template record its owner assender_qbitunless the request names one (EmailSetupTest.theTemplatesOwnerQbitIsTheDefaultSender); field is read-only with help.email_provider.default_slot: hidden, non-editable column that enforces one default provider through a unique key; no reader needed. Left as is.email_message.bodyEncryptedandretentionDaysdo not exist in this repo (no such columns, DDL, or metadata). Body encryption lives inEmailBodyEncryptionand retention uses the config'sbodyRetentionDaysinemail_retention. Nothing to fix.Added
email-defaults.changelog.sql(sql-check preconditions only,-- rollback empty, tested through real Liquibase: second update runs nothing, edits survive, host brand untouched, missing table MARK_RAN, rollback keeps host rows, seeded brand passes contrast checks and changes no rendering);help/qbit-email.yaml+EmailHelp(STAND-IN core#1198); Setup card usingEmailApi.readinessand the sender's provider/brand resolution;EmailTablesGuardrailTest; README, UPGRADE.md, CHANGELOG.Tests
./mvnw verifygreen (314 tests, checkstyle, JaCoCo).EmailHomePolishTestexpectations updated for the new first section.Not done
🤖 Generated with Claude Code