feat(bimi): reconcile the BIMI record to Cloudflare and keep it in step with the logo

The reconciler never wrote the BIMI record: only the console's verification
knew about it, so a stored logo was never published to the zone.

- SyncDnsRecordsInput carries the BIMI logo and certificate URLs; the record is
  created, updated (a= added or dropped) and, when the logo is removed,
  deleted. Only a record carrying this reconciler's comment is retired: one
  made by hand is left alone
- bimi.Publish decides the record in one place for the timer sweep, the
  console's button and the panel's verification. A held certificate is
  advertised only while it is valid for the logo being served: replacing the
  logo ends the certificate, and a record that kept naming it makes every
  receiver reject BIMI, including those that need no certificate
- saving or removing the logo or the certificate triggers the reconciliation in
  the background; the panel shows the record as the server writes it, says when
  a certificate is held back and why, and has a publish-now button
- a read error is not 'no logo': the sync leaves the record alone instead of
  deleting it over a database hiccup

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-16 18:17:07 -03:00
1 parent 0ff9028927
commit 6e49ca00e2
17 files changed
+641 -59

No files matched your search

@@ -42,6 +42,7 @@ func (d domainReconciler) ReconcileDomain(
Created: out.CreatedCount,
Updated: out.UpdatedCount,
Unchanged: out.UnchangedCount,
Deleted: out.DeletedCount,
Conflicts: conflicts,
Errors: out.Errors,
}, nil
@@ -54,7 +55,7 @@ func (d domainReconciler) ReconcileDomain(
// halfway through each other's zone updates.
func reconcileDomainOnDemand(
ctx context.Context, cfg config, aliasRepo port.SystemAliasRepository,
dkimRepo *postgres.DkimRepository, domain string,
dkimRepo *postgres.DkimRepository, bimiRepo *postgres.BimiRepository, domain string,
) (*usecase.SyncDnsRecordsOutput, error) {
syncUC := usecase.NewSyncDnsRecordsUseCase(cloudflare.NewCloudflareAdapter(cfg.CloudflareToken), aliasRepo)
syncUC.SetVerifier(netdns.NewPublicVerifier())
@@ -67,6 +68,8 @@ func reconcileDomainOnDemand(
DaneEnabled: cfg.OutboundDane,
RelaySpfInclude: relayConfig(cfg).SpfInclude(),
}
publication := bimiPublication(ctx, bimiRepo, cfg, domain)
input.BimiLogoURL, input.BimiVmcURL = publication.LogoURL, publication.CertificateURL
// A domain with no DKIM key of its own would publish a selector pointing
// at nothing, so the keys are provisioned first, exactly as the timer does.