Cooperative email for PDS operators
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

README.md

Atmosphere Mail SML Rules#

SML rules consumed by the Osprey worker to evaluate relay events and apply labels to senders. Entry point: main.sml. Per-rule logic under rules/*.sml, keyed on EventType. The action-scoped files under actions/*.sml exist so the Osprey UI's validator discovers the action names — the real rule logic lives in rules/.

Label declarations live in ../config/labels.yaml.

Layout#

main.sml                  entry — Import models, Require every rule file
models/relay.sml          entity + field definitions the rules read
rules/<rule>.sml          one rule per file, keyed on EventType
actions/<action>.sml      one-liners per action so the UI enumerates them

Shadow mode (observe before enforce)#

High-impact rules (anything that auto-suspends, skips warming, or halves a member's rate) should land in shadow mode first. Run observation-only for a bake-in period; promote to enforce once the observed verdicts look right.

The convention is purely at the label level:

  1. A rule in shadow mode emits shadow:<label> instead of its real label. Example: a new suspension rule applies shadow:auto_suspended instead of auto_suspended.
  2. The relay's policyFromLabels (internal/relay/ospreyenforce.go) ignores anything with the shadow: prefix — see relay.IsShadowLabel. A shadow label therefore has no effect on sending policy.
  3. Shadow verdicts still land in relay_events.labels_applied and surface at /admin/shadow-verdicts so ops can audit what the rule would have done.
  4. When the observed behavior looks right, rename the label in the rule from shadow:<label> to <label> and redeploy. No engine changes, no config flags.

Authoring a shadow rule#

In the rule file, replace the enforcing label add with the shadow variant. For example, to shadow a new bounce-rate suspension rule:

# rules/bounce_aggressive.sml  (shadow)
when(EventType == 'bounce_received') {
    if (SenderDID.bounce_rate_24h > 0.10) {
        LabelAdd(SenderDID, 'shadow:auto_suspended')
    }
}

Declare the shadow label in ../config/labels.yaml alongside its real counterpart — label declarations are checked at validate time.

Promote by deleting the shadow: prefix in the LabelAdd call and in the labels.yaml declaration. A single-line diff keeps the before/after easy to eyeball.

Auditing shadow verdicts#

  • /admin/shadow-verdicts (admin UI, Tailscale-only) — filtered view of relay_events showing only events whose labels_applied contains a shadow: label.
  • SELECT * FROM relay_events WHERE labels_applied LIKE '%/shadow:%' in /var/lib/atmos-relay/relay.sqlite for deeper queries.
  • relay.IsShadowLabel(label) — Go-side helper used in tests and when building admin surfaces.

When not to shadow#

  • Label changes that are pure refactors (renaming, splitting existing rules) don't need shadow mode.
  • Rules that only add observational labels (no policy effect) don't need shadow mode — those are already harmless.
  • Anti-abuse rules where delaying enforcement costs more than a false positive — judgment call; flag in the PR description.