Skip to content
quarry Artifact [ef5767768d]
Login

Artifact [ef5767768d]

Artifact ef5767768d40f30378519a231ee6c50c87d8aa06102a3bb43783d1cbfa05f891:


// inbound.go holds what RFC 0014 Phase 1 needs the forge to remember
// about replies that arrive through notify: the secret notify signs
// its deliveries with, and which replies have already been applied.
//
// Both are forge-local state, not truth: a lost secret is re-minted
// and re-registered, a lost dedup row can at worst let one retried
// delivery post twice.
package oltp

import (
	"database/sql"
	"time"
)

const inboundSchema = `
CREATE TABLE IF NOT EXISTS notify_hook (
	id          INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),
	secret      TEXT NOT NULL,
	endpoint_id TEXT NOT NULL DEFAULT '',
	url         TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS inbound_reply (
	message_id TEXT NOT NULL PRIMARY KEY, -- the mail's Message-ID, notify's delivery key
	txn_id     TEXT NOT NULL,
	account    TEXT NOT NULL,
	outcome    TEXT NOT NULL,             -- what the reply became, or why it did not
	at         TEXT NOT NULL
);`

// NotifyHookSecret is the key notify signs reply deliveries with,
// minted on first use and stable after. quarry chooses it and hands it
// to notify when it registers the endpoint, so that a fresh forge is
// signed from its first delivery rather than after an operator pastes
// something.
func (d *DB) NotifyHookSecret() (string, error) {
	var secret string
	err := d.db.QueryRow(`SELECT secret FROM notify_hook WHERE id = 1`).Scan(&secret)
	if err == nil {
		return secret, nil
	}
	if err != sql.ErrNoRows {
		return "", err
	}
	secret, err = newSecret()
	if err != nil {
		return "", err
	}
	// Two nodes minting at once: the first insert wins and the second
	// reads it back, so a fleet still shares one secret.
	if _, err := d.db.Exec(`INSERT OR IGNORE INTO notify_hook (id, secret) VALUES (1, ?)`, secret); err != nil {
		return "", err
	}
	err = d.db.QueryRow(`SELECT secret FROM notify_hook WHERE id = 1`).Scan(&secret)
	return secret, err
}

// SetNotifyHookEndpoint records which notify endpoint carries the
// forge's replies, for the fleet page and for a later re-registration.
func (d *DB) SetNotifyHookEndpoint(id, url string) error {
	_, err := d.db.Exec(`UPDATE notify_hook SET endpoint_id = ?, url = ? WHERE id = 1`, id, url)
	return err
}

// InboundReplySeen answers whether a delivery was already applied and
// what it became. notify redelivers on any non-2xx and on transport
// error, so the same mail can arrive more than once; one mail is one
// post.
func (d *DB) InboundReplySeen(messageID string) (outcome string, seen bool, err error) {
	err = d.db.QueryRow(`SELECT outcome FROM inbound_reply WHERE message_id = ?`, messageID).Scan(&outcome)
	if err == sql.ErrNoRows {
		return "", false, nil
	}
	if err != nil {
		return "", false, err
	}
	return outcome, true, nil
}

// RecordInboundReply remembers what a delivery became.
func (d *DB) RecordInboundReply(messageID, txnID, account, outcome string) error {
	_, err := d.db.Exec(`INSERT OR REPLACE INTO inbound_reply (message_id, txn_id, account, outcome, at)
		VALUES (?, ?, ?, ?, ?)`, messageID, txnID, account, outcome, time.Now().UTC().Format(time.RFC3339))
	return err
}