Skip to content

Witness-Alert Primitive β€” every domain inherits Apple-alert + RSS for free

GFTCL-WITNESS-ALERT-001 Β· v117 substrate Β· finance is the first declared domain Β· ships in this commit.

The pattern, named once

Every domain in the cell already emits witnessed events on NATS. What's been missing everywhere is the uniform path from witnessed-event to operator-notification-plus-durable-subscribable-record. This primitive ships that path as a shared substrate layer that any domain plugs into by writing one Swift declaration β€” not a thousand lines of finance-specific or health-specific plumbing.

The shape, in plain terms:

  1. A domain witnesses something on NATS β€” gaiaftcl.finance.capture.sealed, gaiaftcl.health.protocol.cure_proxy_transition, gaiaftcl.mesh.cell.unreachable, etc.
  2. An alert rule binds (subject, field, comparison, threshold, signal) to that event class. "When Nakamoto < 5, fire a BAD alert." Operator-editable.
  3. When a rule fires, the alert lands in alert_queue β€” a durable, append-only, content-addressed witness log in the cell's SQLite substrate.
  4. Two adapters read the queue independently and deliver to the operator:
  5. Apple-alert adapter β†’ UNUserNotificationCenter local push (signal β†’ interruption level: bad=time-sensitive, good=passive, neutral=active)
  6. RSS adapter β†’ /feed/alerts.xml (every domain) + /feed/alerts/{domain}.xml (per-domain), served on the existing local HTTP server
  7. Every delivery attempt is logged in alert_delivery_log β€” append-only audit of what reached the operator and when.

The queue's witness columns are immutable by constitutional trigger (trig_alert_queue_witness_immutable); the delivery-log is append-only (trig_alert_delivery_log_append_only). The same discipline the Lion Math sweep receipts run under, now for every alert.

Why two surfaces, and the division of labor

  • Apple alert = push. Interrupts the operator now; ephemeral by nature; urgent; single-operator; on-device.
  • RSS feed = durable witness. Every alert that ever fired, timestamped, immutable, subscribable, re-readable. Each entry carries the receipt reference (rule, comparison, observed value, snapshot hash) so a feed item is not a notification β€” it's a re-runnable witnessed event.
  • Queue between them. Decouples "the cell witnessed something" from "the operator was told." Nothing is lost when the operator is away, the device is asleep, or push delivery fails.

RSS is the sovereign choice: open standard, pull-based, no third-party push vendor, inherently a feed of timestamped immutable entries (which is an append-only witness log in another costume). Anything can subscribe β€” a reader app, a script, another cell, a regulator. v1 is local-private (127.0.0.1:8423 only); authorized-public via the one-membrane-token architecture is a named next summit.

Architecture

                                NATS witnessed events
                                         β”‚
                                         β–Ό
                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                              β”‚   AlertGovernor    β”‚  ← domain-agnostic actor
                              β”‚  (GaiaFTCLCore)    β”‚     in GaiaFTCLCore
                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β”‚ writes
                                        β–Ό
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚     alert_queue (SQLite)     β”‚  ← APPEND-ONLY
                         β”‚  witness cols IMMUTABLE      β”‚     witness log
                         β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                              β”‚                  β”‚
                       reads  β”‚                  β”‚ reads
                              β–Ό                  β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚  AppleAlertAdapter   β”‚   β”‚  AlertFeedRenderer   β”‚
              β”‚ UNUserNotification   β”‚   β”‚ /feed/alerts*.xml    β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each domain plugs in via the AlertableDomain protocol with five things:

public protocol AlertableDomain: Sendable {
    var domainID: String { get }                            // "finance"
    var watchedSubjects: [String] { get }                   // ["gaiaftcl.finance.capture.sealed", ...]
    var seedRules: [AlertRule] { get }                      // seeded once; operator-editable
    func projectFields(payload: [String: Any]) -> [String: AlertableValue]
    func renderSnapshot(rule: AlertRule, observedValue: AlertableValue,
                        payload: [String: Any]) -> AlertSnapshot
}

That's it. No domain ever writes queue rows, calls UNUserNotificationCenter, or renders RSS β€” those are the shared primitive's job.

The first domain β€” finance (reference impl)

Sources/FinanceAlertableEvents/FinanceAlertableEvents.swift declares:

Watched subjects: - gaiaftcl.finance.capture.sealed β€” capture-concentration sealed - gaiaftcl.finance.alert.tripped β€” direct trip event - gaiaftcl.finance.resolution.concentrated β€” poly-market resolution concentration

Watchable fields (projected from raw payload to AlertableValue β€” exact Rat where finance currency lives): - nakamoto β€” Int Nakamoto coefficient - top1_share_pct, top5_share_pct, resolution_share β€” exact rationals - market_id, signal_hint β€” text

Five seed rules: | ruleID | comparison | signal | meaning | | --- | --- | --- | --- | | rule.finance.capture.nakamoto_lt_5 | nakamoto < 5 | bad | < 5 holders control > 50% β€” capture risk | | rule.finance.capture.top1_share_gt_50pct | top1_share_pct > 1/2 | bad | single holder > half the market | | rule.finance.capture.deconcentration_ok | nakamoto β‰₯ 10 | good | healthy de-concentration | | rule.finance.resolution.concentrated_gt_2_3 | resolution_share > 2/3 | bad | single resolver > 2/3 of outcomes | | rule.finance.alert.tripped_named_bad | signal_hint == "bad" | bad | direct finance trip with named bad signal |

Operator edits to these rules persist across launches (seeds use INSERT OR IGNORE).

Survey β€” where this primitive lands across the cell

The whole point of generalizing: one primitive, every domain inherits. Open frontier of declared-domain summits:

  • Finance / poly markets β€” primary, shipped. Capture-positive, de-concentration, resolution-concentration.
  • Health (OWL protocols) β€” alertable transitions: a protocol moving from CURE-PROXY toward CURE-CLOSED (good), a seal's falsifier theorem breaking under new input (bad), a GAMP-5 receipt expiring (bad). The RSS feed of health-seal state changes IS a regulatory artifact.
  • Engineering β€” composite-seal-invalidated when a constituent primitive changes (bad), summit-closed (good). Design-review backstop subscribes; engineer is alerted before shipping against a broken seal.
  • Substrate / mesh health β€” the cell watching itself. Cell unreachable, NATS subject backing up, dead-letter, witness-store write latency climbing. Sovereign ops monitoring with no third-party observability vendor β€” possibly the highest-daily-value use for a sole operator.
  • Governance (Franklin guardian) β€” REFUSED terminal states, constitutional rule trips, entropy spent without value. The RSS feed becomes the constitutional audit log.
  • Ingestion (VIE / Earth Substrate) β€” feed-source stale, schema-validation failures, witnessed-data freshness lapsing.

Each is one AlertableDomain declaration away β€” same shape as FinanceAlertableEvents.swift. No more plumbing.

What ships in this commit

File Purpose
cells/xcode/Sources/GaiaFTCLCore/AlertableEvent.swift Protocol + types (AlertableDomain, AlertableValue, AlertRule, AlertSnapshot, AlertSignal, AlertComparison, WitnessHasher)
cells/xcode/Sources/GaiaFTCLCore/NarratorSchemaV117.swift Migration: alert_rules, alert_queue, alert_delivery_log, alert_domain_registry + immutability triggers + PQ CALORIE receipt
cells/xcode/Sources/GaiaFTCLCore/AlertGovernor.swift Domain-agnostic actor: register, ingest, evaluate, queue-write. Idempotent (alert IDs content-addressed on rule_id + payload_sha256)
cells/xcode/Sources/GaiaFTCLCore/AlertQueueReader.swift GRDB-backed reader over the shared SubstrateDatabase pool
cells/xcode/Sources/GaiaFTCLCore/AppleAlertAdapter.swift UNUserNotificationCenter push adapter with signal→interruption-level mapping. Suppresses gracefully when no app bundle.
cells/xcode/Sources/GaiaNodeServerCore/AlertLedgerReader.swift Thin shim delegating to AlertQueueReader (kept here so existing RSS code path stays clean)
cells/xcode/Sources/GaiaNodeServerCore/AlertFeedRenderer.swift RSS 2.0 renderer with signal-prefixed titles ([BAD] / [GOOD] / [INFO]) + audit body
cells/xcode/Sources/GaiaNodeServerCore/HTTPListener.swift Routes /feed/alerts.xml + /feed/alerts/{domain}.xml (slug-validated, no path traversal)
cells/xcode/Sources/FinanceAlertableEvents/FinanceAlertableEvents.swift First declared domain β€” finance
cells/xcode/Sources/M8WitnessAlertSmokeTest/main.swift End-to-end verification

Verification

swift run M8WitnessAlertSmokeTest runs end-to-end against the real substrate.sqlite and asserts:

  1. βœ… All four V117 tables present
  2. βœ… AlertGovernor.register(FinanceAlertableEvents()) writes domain row + 5 seed rules
  3. βœ… BAD payload (Nakamoto=3) fires 2 expected rules
  4. βœ… GOOD payload (Nakamoto=12) fires the de-concentration rule
  5. βœ… Replaying the same payload is idempotent (0 new queue rows β€” content-addressed alert IDs)
  6. βœ… RSS all-feed contains [BAD] entries; finance feed carries witness markers
  7. βœ… Content-Type: application/rss+xml; charset=utf-8
  8. βœ… Trigger ABORTS any UPDATE to a witness column with witness columns are immutable
  9. βœ… Apple-alert drain runs without crash (suppressed when no app bundle / no auth)

swift build β€” full workspace builds clean. swift run M8InventoryAuditor β€” OVERALL TERMINAL: COMPLETE.

v1 scope vs named-next-summit

Shipped in v1: - Schema + governor + queue - Apple-alert adapter (graceful no-op when not in an app bundle) - RSS adapter on existing local HTTP server (local-private only, 127.0.0.1) - Finance reference declaration with 5 seed rules - End-to-end smoke test

Named next, deferred: - Authorized-public RSS via membrane-token gate (so a regulator / consortium / authorized public can subscribe to a safety feed without sovereignty leak) - Franklin chat rule-authoring ("alert me when any finance capture goes Nakamoto < 5") - HOME alert-queue surface in the workbench (co-resident, signal-colored, expandable to receipt) - Additional domain declarations (health, engineering, mesh, governance, ingestion) β€” each is one Swift file like FinanceAlertableEvents.swift

Constitutional posture

  • No new external dependency. Native Apple UNUserNotificationCenter + Network.framework HTTP + GRDB (already in use).
  • Substrate never crosses the wire β€” only the RSS rendering of verdicts + receipt references.
  • Witness columns of alert_queue are immutable by SQL trigger; tampering with sealed alerts is REFUSED by the database itself.
  • Alert IDs are content-addressed: same rule against same payload digest β†’ same alert ID β†’ INSERT OR IGNORE β†’ no double-fire. Replays are safe and provably so.
  • No NATS traffic leaves the cell. No telemetry. No analytics service.

Patent / license notice

Β© 2026 Richard Gillespie. All rights reserved. USPTO patent applications 19/460,960 and 19/096,071.


Federation-cosigned

This page's source is sealed in the GaiaFTCL federation manifest β€” page SHA-256 307370b8fed881b5…, manifest witness a090592e0609adc8…, signed 2026-06-02T18:58:22Z by cell gaiaftcl-mac-cell. Verify with gaiaftcl wiki sign --all and compare wiki-all-signatures.json.