Ctrl K
ComunidadGuías…

docs/adr/0084-automation-milestones-tenant-opt-in-fail-closed-dns-carry-cost.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# ADR-0084: Automation milestones — tenant opt-in, fail-closed DNS writes, carry-cost verdicts

## Metadata

| Field          | Value                                                       |
| -------------- | ----------------------------------------------------------- |
| **Status**     | Accepted                                                    |
| **Date**       | 2026-10-15                                                  |
| **Authors**    | Alessio Brillo                                              |
| **Deciders**   | Alessio Brillo                                              |
| **Supersedes** | —                                                           |
| **Relates to** | ADR-0002, ADR-0004, ADR-0006, ADR-0023, ADR-0038, ADR-0082, ADR-0083 |
| **Project**    | DOMINUS                                                     |

## Context

ADR-0083 established the journaled post-acquisition pipeline
(`portfolio → site → indexing → dns → parking → listing`) behind the
`AUTOMATION_ENABLED` kill switch and the `AUTOMATION_DRY_RUN` default, and
its amendment fixed the shape of the fail-closed DNS write gate before any
provider existed. This ADR records the decisions taken while implementing
the remaining milestones (Phase 0, M3, the honest part of M4, M5) and the
boundaries that were deliberately not crossed.

The product goal is a fully automated lifecycle after a purchase — site,
indexing, DNS delegation, ads monetization, marketplace listing, exit — so
a SaaS tenant can buy a domain and let it become self-sustaining. Three
constructions in that path spend real money or mutate externally visible
state, and two external dependencies (parking and marketplace APIs) cannot
be verified without vendor access.

## Decision Drivers

1. A SaaS tenant must own its own switch, without weakening the operator's
   global brake (ADR-0002 conservatism, ADR-0038 tenant isolation).
2. The DNS step is the first step that mutates state a visitor's traffic
   depends on; the amendment's fail-closed model must survive contact
   with implementation.
3. The community edition must keep working with zero paid API
   dependencies; vendor adapters may only land verified (ADR-0082).
4. The carry-cost verdict must never drop a domain on its own: it feeds
   the drop engine as a Reprice signal, keeping the human gate.

## Decision

1. **Tenant opt-in (Phase 0).** A `tenant_automation_settings` table
   (one row per tenant, default: opted out, dry-run) holds the tenant's
   own switch. The effective gate is the conservative combination:
   `enabled = env AND tenant`, `dryRun = env OR tenant`. The operator
   brake can only ever tighten a tenant's wish, never loosen it. The
   Automation dashboard page exposes settings, step journal and audit
   trail; `dominus automate settings --enabled/--dry-run` does the same
   from the CLI.

2. **Fail-closed DNS writes (M3).** The `dns` step writes only after an
   independently corroborated conclusion about the current state: the
   write provider's view of the zone plus a public-DNS read of the
   delegation, and the write is confirmed publicly afterwards. Anything
   else is a named skip (with the manual instructions recorded in the
   journal) or a named, resumable failure (`write_unconfirmed`) — never a
   best-effort "probably applied". `ManualDnsWriteProvider` is the honest
   default; `CloudflareDnsWriteProvider` is the first automated adapter.

3. **Revenue ingestion is a read (M3).** `MonetizationProvider` exposes
   one method; the first implementation is a CSV reader over an operator
   export (zero paid API dependencies, same pattern as the comps/keyword
   providers). Writes are idempotent at (domain, date, source), so a
   repeated ingestion corrects figures instead of double-counting them.
   No provider → the step skips with `monetization_provider_unavailable`.

4. **Tenant listing policy (M4, partial).** What works today without
   verified vendor APIs: a fixed base price for automatic listings
   (`tenant_listing_settings.pricePolicy`) and offer rules (auto-decline
   below a floor, counter below a counter price; both off by default,
   the decline floor always checked first). The Afternic/Dan/Sedo
   ListingProvider adapters stay out until their APIs are verified
   (ADR-0082); the manual provider keeps tracking listings locally.

5. **Carry-cost verdicts (M5).** A scheduler job (`REVENUE_SYNC`,
   read-only) ingests revenue on a cron; `computeCarryCostVerdict`
   annualises revenue over the observed window (never extrapolating from
   less than 30 days) against the renewal cost and reports
   `sustainable | marginal | loss | insufficient_data`. A `loss` refines a
   `Keep` from the drop engine into `Reprice` — visible in the portfolio
   and at `GET /api/v1/sustainability` — and never drops a domain by
   itself.

## Considered Options

### Option A: Tenant opt-in as a single global switch (env only)

**Advantages:** nothing new to build.

**Disadvantages:** in a multi-tenant SaaS every tenant would inherit one
operator's choice, and the tenant could not turn automation off without
an operator. Rejected.

### Option B: Write-then-compensate DNS writes (retry/rollback on failure)

**Advantages:** familiar transactional shape.

**Disadvantages:** compensation assumes the compensating action succeeds —
precisely what is uncertain during the incident that produced the bad
read (ADR-0083 amendment). Rejected.

### Option C: Invent the Sedo/Afternic API contracts now

**Advantages:** the roadmap lines could be ticked.

**Disadvantages:** exactly the ADR-0082 failure mode — adapters that were
never exercised against the vendor, deleted one release later. Rejected;
they return with verified access.

## Consequences

**Positive:** the lifecycle is automated as far as honestly possible; the
dangerous steps (DNS, money) are gated twice (kill switch + dry-run) and
audited; the tenant sees and controls its own switch; every unverified
dependency degrades to a named skip instead of a silent failure.

**Negative:** new tables (0063–0065) and a new job type; operators must
understand that both switches must allow a run; the vendor adapters remain
a blocked line until API access exists.

**Compliance Check:**

- [x] ADR-0002 conservatism: no step acts on uncertain state; unset
      configuration always skips.
- [x] ADR-0004 provider abstraction: every external dependency is behind
      an interface (DnsWriteProvider, MonetizationProvider,
      PublicDelegationReader).
- [x] ADR-0006 trademark gate: untouched, still the publish gate for
      listings and offers.
- [x] ADR-0023 async execution: REVENUE_SYNC runs on the existing queue
      with handler registration asserted by the parity test.
- [x] ADR-0038 tenant isolation: every new table is repository-scoped.
- [x] ADR-0082 honesty: nothing is declared before it works; unverified
      adapters stay out.
- [x] English identifiers, comments and documentation throughout.