// agent.go — which accounts are machine-operated, and who answers for
// them.
//
// A forge that cannot say this has a record it cannot read back. On
// this one, in a single day: eleven tickets filed by an automated
// campaign under a person's name, permanent in every clone, indexed
// forever as that person's words; a sync credential minted in the
// operator's name by a machine; and two agent sessions writing all day
// with nothing on any page to say either was a machine.
//
// The mark is on the ACCOUNT, not the write. A write-level mark is more
// honest and much larger — it would have to reach the artifact format —
// and the account-level one is what makes the write-level one worth
// building later: you cannot ask "which of this account's writes were
// machine-made" until you can ask "is this account a machine".
package oltp
import (
"database/sql"
"errors"
"fmt"
)
// Operator names the person answerable for a machine-operated account.
// Empty means the account is a person's own.
func (d *DB) Operator(name string) string {
var op string
if err := d.db.QueryRow(`SELECT operator FROM account WHERE name = ?`, name).Scan(&op); err != nil {
return ""
}
return op
}
// SetOperator marks an account as machine-operated and records who runs
// it; an empty operator clears the mark.
//
// The GATE is the caller's, not this function's — see api's account.agent
// for the asymmetry: declaring yourself an agent is always allowed and
// undeclaring is a site admin's call. Stated there rather than here
// because it is a policy about callers, and this is a table.
func (d *DB) SetOperator(name, operator string) error {
if name == "" {
return errors.New("oltp: empty account name")
}
if operator != "" && !validLogin(operator) {
return fmt.Errorf("oltp: operator %q is not an account name", operator)
}
if operator != "" {
var one int
err := d.db.QueryRow(`SELECT 1 FROM account WHERE name = ?`, operator).Scan(&one)
if errors.Is(err, sql.ErrNoRows) {
// An operator who is not an account is a name nobody can
// be asked about, which is the thing this exists to end.
return fmt.Errorf("oltp: no account named %q to answer for %q", operator, name)
} else if err != nil {
return err
}
}
res, err := d.w.Exec(`UPDATE account SET operator = ? WHERE name = ?`, operator, name)
if err != nil {
return err
}
if n, _ := res.RowsAffected(); n == 0 {
return fmt.Errorf("oltp: no such account %q", name)
}
return nil
}