Commit Graph
59 Commits
Author SHA1 Message Date
jcoffey-dev dac1808bbd Tests: retire pending-rebuild, which nothing has been gated behind for a day
The feature held the shared tests of features the fork hadn't rebuilt yet.
All nine are built and the last gate came off with per-domain directories,
so the feature was defined and documented but gated nothing.

The crate builds and holds the same tests without it: 113 by default, 118
with postgres, mysql and redis. SPEC §2.2b keeps its account of the first
import and now says when the gates came off, so a spec that still describes
a suite as gated reads as the record of its own date, which is what
monitoring.md's gate table already calls itself.
2026-09-19 17:28:14 -07:00
jcoffey-dev 08b3210cff Specs: the regression after the ACME fix, 87 passed and 3 failed
Ran it single-threaded on RocksDb: 87 passed, 3 failed, 23 ignored, all 113
tests a default build holds. lmtp_delivery passed this time, which is what
the note about queue timing under a sequential run predicted, so the four
documented failures are three. The ACME suite logged no 400 at all, so the
renewal fix holds; it still ends on the certificate that never came,
because pebble can't validate across the bridge with ufw up.

The line it replaces claimed 86 passed, 4 failed, 27 ignored from the same
command. That totals 117, and no feature set of this tree produces 117 —
113 by default, 118 with all three backends — with no test added or removed
since. Said so rather than presenting the two as a trend.
2026-09-19 17:21:58 -07:00
jcoffey-dev 9f444d2458 Fork tooling: the third-party code upstream carries, listed and checked
A few of upstream's dual-licensed files carry code from other projects
under MIT or BSD terms. The fork redistributes it, so their licenses
require the notices to travel with it. THIRD-PARTY.md reproduces them.

strip.py now reads the stripped tree's comments for another copyright
holder, another license, or a note that code came from somewhere else, and
names any file THIRD-PARTY.md doesn't cover. It reports, never fails: the
notice goes in with the merge that brings the release in.

On v0.16.22 it finds 14 files, all of them covered. The rest of the report
is byte-for-byte what the committed one says, so the scan disturbs nothing
it already did.
2026-09-19 16:59:36 -07:00
jcoffey-dev adfa22c817 Specs: the nine ignored suites re-run, and the STORE each one needs
Re-ran all nine on containers removed beforehand, one suite at a time.
All nine pass. mysql_replica_position_tests failed the first time on test
18's lag assertion, with the pair half a minute old and still catching up,
and passed on a second run against the same containers; it fails before
the point where it changes the replica's settings, so it leaves nothing to
restore. Noted both, with each suite's time.

The table's STORE column said "default" for five suites, which reads as
"leave it unset". The harness has no default: it panics with "Missing or
invalid store type" before the suite starts. They run on RocksDb, as the
regression does, so the column now names it.
2026-09-19 16:54:27 -07:00
jcoffey-dev 309835be1d ACME: post a challenge once, then poll the authorization
The renewal loop re-posted the challenge every time it polled and found the
authorization still pending. RFC 8555 section 7.5.1 has the client post a
challenge once to say it is ready and then poll; a server that has already
moved the challenge to "processing" refuses a second post, and pebble
answers 400 malformed, which failed the whole renewal.

Verified against pebble: the 400s are gone and the client polls. The suite
still can't finish on this machine, because pebble never reaches the test
server to validate the challenge; that path is the environment, and the
runbook now says so.
2026-09-19 16:28:15 -07:00
jcoffey-dev ce12659138 Specs: what a plain regression leaves failing, and why none of it is ours 2026-09-19 16:15:09 -07:00
jcoffey-dev e492b7875a Specs: how to run the suites a plain regression doesn't reach
`cargo test -p tests` runs none of the fork's own feature suites except
what's inside `system_tests`: SCIM, per-domain directories, the sharded
stores and the four replica suites are all ignored, each needing a
container, a STORE the harness only builds on request, or both. The
runbook lists what each one needs, and the container-reuse trap.
2026-09-19 16:14:44 -07:00
jcoffey-dev 5cc28784df SCIM: turning a domain's SCIM authority off takes effect at once (SCIM-60)
`allowScimProvisioning` is cached with the domain as DOMAIN_FLAG_SCIM, but
it wasn't among the fields whose change drops the cached entry, so flipping
the flag changed nothing until something else evicted the domain. SCIM-60
says the change takes effect without a restart.

Found by the two acceptance checks that were written but never called from
the driver, so neither had ever run: `authority` (SCIM-58 to SCIM-60,
through `synchronize_account` itself) and `rate_limits` (SCIM-14). Both are
wired in now, and `scim_tests` passes with them.
2026-09-19 16:14:38 -07:00
jcoffey-dev cee4fd6bc6 Specs: how to run the eight compat tests on a copy of INBUXA's data, and the statuses of tests 10, 11, 17 to 19
docs/spec/compat-tests.md lists each compat test, what it needs, what it
checks and what a failure means, and says plainly that they run against a
copy only, since the monitoring one purges the history it reads. The
scale-out and per-domain statuses record the acceptance tests that now
run.
2026-09-19 15:11:04 -07:00
jcoffey-dev 216bb5b711 Scale-out storage: implementation status of read replicas (ST-5 to ST-15)
Where the routing lives and what a read scope covers, which acceptance
tests the container suite runs, what isn't exercised (two nodes, the
primary stopped, and the MySQL paths, which are built but unrun), what
was settled from the code, and the known limits.
2026-09-19 14:21:54 -07:00
jcoffey-dev a635b490ec Per-domain directories: implementation status and the compat check (DIR-1 to DIR-32, test 20)
per_domain_directory_compat, ignored, checks a copy of INBUXA's data has
no directory, no server default and no domain with its own directory, as
observed. The status names where the rules live, which suite covers each
acceptance test and how far, what was settled from the code, and the
known limits. SCIM's status notes its test 5 now passes.
2026-09-19 10:51:41 -07:00
jcoffey-dev b080fa0736 Scale-out storage: implementation status of the sharded stores (ST-1 to ST-4, ST-16 to ST-30)
Where the code lives, which suite covers each acceptance test, what isn't
exercised, what was settled from the code, and that read-replica routing
(ST-5 to ST-15) waits for per-domain directories.
2026-09-19 10:21:30 -07:00
jcoffey-dev e7efa91cbc Scale-out storage decision: sharded stores first, read-replica routing after per-domain directories, with configured replicas reported at startup meanwhile (ST-2) 2026-09-19 10:01:34 -07:00
jcoffey-dev 00b2eb6f4b SCIM: implementation status (SCIM-1 to SCIM-61)
Where each part lives, which suite covers each acceptance test, test 5
deferred to per-domain directories and test 31 unrun until a copy of
INBUXA's data, what was settled from the code, and the known limits.
2026-09-19 09:59:53 -07:00
jcoffey-dev 2d3f8251c5 SCIM decision: a groups value equal to the current membership in a POST or PUT body is accepted and ignored, since Okta sends groups: [] on create (SCIM-28) 2026-09-19 09:04:25 -07:00
jcoffey-dev 4b389b6b4e SCIM decisions: build before per-domain directories, with the OIDC authority test deferred; build to the spec without the profile drafts or vendor docs (SCIM-61, open questions 12 and 13) 2026-09-19 08:56:53 -07:00
jcoffey-dev f107443b29 Monitoring: implementation status, and the compat test for INBUXA's data (test 26)
monitoring_compat, ignored, checks the observed settings against a copy
of INBUXA's data, reads the old history without an error, and purges it.
The status names where each part lives, which suite covers each
acceptance test, the tests not exercised, and the known limits.
2026-09-19 08:54:21 -07:00
jcoffey-dev 143a10fcf0 Monitoring decision: a live token may be reused within its 60 seconds, since INBUXA Admin reconnects with the same token (MON-23) 2026-09-19 08:34:03 -07:00
jcoffey-dev 715f219528 Monitoring: correct the storage decision; history uses the AGPL telemetry key classes with the registry's encoding, and INBUXA's old history can't be read 2026-09-19 08:12:11 -07:00
jcoffey-dev 3ea242f7fb Monitoring decisions before implementation: live streams and stored histograms match INBUXA Admin, and history is stored as x:Trace and x:Metric registry items (MON-4, MON-20, MON-22, MON-23) 2026-09-19 08:10:27 -07:00
jcoffey-dev 88090b3ea6 AI spam classification: calibration results for commercially usable models; Qwen3 4B Instruct 2507 recommended, 4 vCPU minimum 2026-09-19 07:42:12 -07:00
jcoffey-dev 554cc4fcd2 AI spam classification decisions from calibration: maxContentBytes 2048 and spamMaxAdded 2.0 by default, for low-end CPU-only instances 2026-09-19 06:19:38 -07:00
jcoffey-dev 9490fc4677 AI spam classification: the model's opinion as one bounded spam signal, and the llm_prompt Sieve function (AI-1 to AI-28)
The classifier sends only the subject and text, between unforgeable markers
after the operator's prompt, to an OpenAI-compatible endpoint the operator
configured; nothing is preset. Its answer maps to an LLM_ tag whose score is
clamped (+5.0, -1.0 by default) and can never discard or reject on its own;
X-Spam-LLM is sanitized, encoded and folded, and a planted one is removed.
Failures, timeouts past the ceiling, a full slot or a paused model leave
mail flowing untagged. llm_prompt answers trusted scripts, and accounts
holding interactAi within an hourly limit. Redirects aren't followed and no
content or secret is logged. The limits live in inbuxa:AiLimits.
Acceptance tests 1 and 3 to 21; test 2 as the re-enabled shared llm case,
whose setup no longer waits on a rules file from a developer's own path;
test 22 written as the ignored ai_compat.
2026-09-19 00:41:00 -07:00
jcoffey-dev cba48cf03b AI spam classification decision before implementation: the limits singleton is inbuxa:AiLimits under urn:inbuxa:jmap (open question 10) 2026-09-18 23:50:33 -07:00
jcoffey-dev 0bc6b03dcd Branding and templates: per-domain, tenant and server logos, /logo, operator calendar email templates and RSVP page (BT-1 to BT-26)
Logos resolve domain, then tenant, then server-wide, then the built-in, with
subdomains finding their domain. GET /logo serves a data-URL image, redirects
to a URL logo without fetching it, sandboxes SVG, and answers 404 when no
custom logo applies. Emails embed the first PNG, JPEG or GIF logo. Logo and
template writes are checked; stored templates are read at send time, always
escaped, and fall back to the built-in with a build warning when they don't
parse. The RSVP page is served byte for byte with a CSP and no-referrer. The
sign-in and RSVP pages load the logo through an image element. MT-22's
session logo follows the chain to the server-wide logo.
Acceptance tests 1 to 17; test 18 written as the ignored branding_compat.
2026-09-18 22:27:19 -07:00
jcoffey-dev ecbdfd533b Undelete: deleted accounts are kept for their period, hold their addresses, and are restored or destroyed through inbuxa:DeletedAccount (UD-15 to UD-17a)
With archiveDeletedAccountsFor set, a destroyed account's record is kept in
the fork subspace with its id, its DestroyAccount task is due at the end of
the period, and its shares are suspended both ways. Its addresses can't be
taken by new accounts, aliases, lists or masks. inbuxa:DeletedAccount/get
lists kept accounts to server and tenant administrators; /set restores one
with a new password (same id, task cancelled, shares reinstated) or destroys
it now. The destroy task also clears undelete's own records.
Acceptance test 14; test 16 written as the ignored undelete_compat.
2026-09-18 21:35:48 -07:00
jcoffey-dev 0a2c29e8fd Undelete decision before implementation: a kept account's shares are suspended both ways and reinstated on restore (UD-17a) 2026-09-18 21:19:09 -07:00
jcoffey-dev a1ce14b76b Undelete decisions before implementation: restore data, kept accounts, the restore API (UD-4, UD-15a, UD-17) 2026-09-18 19:49:34 -07:00
jcoffey-dev ac2232c98d Multi-tenancy decision before implementation: impersonate has no effect inside a tenant (MT-1, MT-15) 2026-09-18 18:49:10 -07:00
jcoffey-dev 4a9aa9c548 Masked email: rewrite to the owner at RCPT TO, create responses carry the address, admins query all masks (ME-4, ME-9, ME-13, ME-19)
Found by running system_tests, which masked email no longer stops:
- rcpt_resolve rewrites a live mask to its owner's address, so
  Delivered-To names the account; delivery recognizes the mask from the
  original recipient when it belongs to that account.
- x:MaskedEmail/set create responses carry the server-set email.
- x:MaskedEmail/query returns every mask to a server-level impersonate
  holder, and filters on accountId.
- The refusal for an unlinked emailDomain uses upstream's wording.
- The shared delivery test checks the fork's address format (ME-13).
- The masked email test's tenant domain uses manual DKIM, so its cleanup
  leaves nothing behind.
2026-09-18 18:29:19 -07:00
jcoffey-dev 31b8ca8ed5 Masked email built: implementation status, deferral and known limits (ME-1 to ME-19) 2026-09-18 16:40:14 -07:00
jcoffey-dev 7ef2cdc273 Masked email: the fork's subspace is _, not X, which SQL backends fold into upstream's x 2026-09-18 16:10:50 -07:00
jcoffey-dev edfb357cb1 Masked email decisions before implementation: storage, start state, rate config, who administers (ME-7a, ME-15, ME-19) 2026-09-18 16:09:57 -07:00
jcoffey-dev 2e43f5fc0d Multi-tenancy built: implementation status, deferrals and known limits (MT-1 to MT-23) 2026-09-18 15:38:29 -07:00
jcoffey-dev 2cbecac415 Multi-tenancy decision before implementation: generated DKIM keys count against maxDkimKeys (MT-17) 2026-09-18 15:28:24 -07:00
jcoffey-dev b6b345a129 Multi-tenancy decision before implementation: sharing grants across tenants (MT-3) 2026-09-18 15:17:03 -07:00
jcoffey-dev d0d4ac4317 Open decisions: the Enterprise License text stays until the headers are cleaned up 2026-09-18 14:52:45 -07:00
jcoffey-dev a1254cd3c4 Multi-tenancy decision before implementation: what moves with a domain (MT-8, MT-17) 2026-09-18 14:50:57 -07:00
jcoffey-dev edc6a8ccbd Multi-tenancy decisions before implementation: MT-3, MT-7, MT-12, MT-19a, MT-22
Test 3 expects invalidForeignKey, as MT-3 says. A tenant admin reads its own
tenant through sysTenantGet and sysTenantQuery, added to the default Tenant
Administrator role for new installs only. The server adds nothing for MT-7's
dashboard list. The MT-19a submission warning is deferred. MT-22's logo is
the urn:inbuxa:jmap account capability's logo field (contract C-1).
2026-09-18 14:48:22 -07:00
jcoffey-dev cf59d5183a Branding: INBUXA's last two logo values removed 2026-09-18 14:45:38 -07:00
jcoffey-dev 8c1879e853 Cross-origin requests only from the front ends' origins (contract C-14) 2026-09-18 13:44:55 -07:00
jcoffey-dev a45e0ef8b1 Feature specs 4-9: observations from INBUXA's live server 2026-09-18 13:42:00 -07:00
jcoffey-dev cd7c1a6d4c Feature specs 4-9: branding and templates, AI spam classification, monitoring, SCIM, scale-out storage, per-domain directories 2026-09-18 13:18:39 -07:00
jcoffey-dev 05d220ae4f No web interface on the mail host: first boot installs and downloads none (SPEC §5.3) 2026-09-18 13:10:32 -07:00
jcoffey-dev c2be7e2956 Multi-tenancy implementation hand-off brief 2026-09-18 13:06:47 -07:00
jcoffey-dev d6fc4600cd OAuth: registration required by default; first-party clients registered on every start (contract C-5, C-6) 2026-09-18 13:04:20 -07:00
jcoffey-dev f82f15d863 Contract: note the browser sign-in check after the OAuth fix 2026-09-18 12:53:59 -07:00
jcoffey-dev ad3322183a Contract: record the OAuth registration fix applied on production 2026-09-18 12:40:33 -07:00
jcoffey-dev 2e2eb76301 Contract: C-7 matches C-5 (third-party OAuth apps are admin-registered unless open registration is chosen) 2026-09-18 12:31:18 -07:00
jcoffey-dev 559bb3d1a6 Contract: INBUXA production OAuth findings; C-5 also turns anonymous client registration off 2026-09-18 12:31:07 -07:00