Add the conversation fingerprint and the v1 config keys

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-25 03:46:05 -07:00
parent 816614d6dd
commit 463cea18de
7 changed files with 317 additions and 5 deletions
+75 -3
View File
@@ -15,18 +15,25 @@ import (
"os"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/BurntSushi/toml"
)
// Duration is a time.Duration that TOML reads from a string such as "60s" or
// "30m".
// Duration is a time.Duration that TOML reads from a string such as "60s",
// "30m", or "7d" (an integer number of days).
type Duration struct{ time.Duration }
// UnmarshalText implements encoding.TextUnmarshaler via time.ParseDuration.
// UnmarshalText implements encoding.TextUnmarshaler. It accepts the "Nd" form
// — an integer number of days, so "7d" is 7 × 24h — in addition to
// time.ParseDuration syntax.
func (d *Duration) UnmarshalText(text []byte) error {
if days, ok := parseDays(text); ok {
d.Duration = days
return nil
}
dt, err := time.ParseDuration(string(text))
if err != nil {
return err
@@ -35,6 +42,22 @@ func (d *Duration) UnmarshalText(text []byte) error {
return nil
}
// dayPattern matches a run of digits followed by "d", e.g. "7d".
var dayPattern = regexp.MustCompile(`^[0-9]+d$`)
// parseDays reports whether text is the "Nd" day form and returns that many
// hours. The regex guarantees the prefix is a base-10 integer.
func parseDays(text []byte) (time.Duration, bool) {
if !dayPattern.MatchString(string(text)) {
return 0, false
}
n, err := strconv.Atoi(string(text[:len(text)-1]))
if err != nil {
return 0, false
}
return time.Duration(n) * 24 * time.Hour, true
}
// Model is the per-model tuning carried by a host entry.
type Model struct {
Parallel int `toml:"parallel"`
@@ -58,6 +81,9 @@ type Config struct {
Listen string `toml:"listen"`
PollInterval Duration `toml:"poll_interval"`
QueueMax int `toml:"queue_max"`
DB string `toml:"db"`
LeaseIdle Duration `toml:"lease_idle"`
Retention Duration `toml:"retention"`
Hosts map[string]Host `toml:"hosts"`
Routes map[string]Route `toml:"routes"`
}
@@ -76,6 +102,13 @@ const (
DefaultPollInterval = 60 * time.Second
DefaultQueueMax = 8
MinPollInterval = time.Second
DefaultDB = "crossbar.db"
DefaultLeaseIdle = 30 * time.Minute
DefaultRetention = 180 * 24 * time.Hour
MinLeaseIdle = time.Minute
MinRetention = 24 * time.Hour
)
var routeName = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
@@ -109,6 +142,15 @@ func Parse(r io.Reader) (*Config, error) {
return nil, &Error{Field: keys[0], Msg: "unknown key"}
}
if !md.IsDefined("db") {
c.DB = DefaultDB
}
if !md.IsDefined("lease_idle") {
c.LeaseIdle.Duration = DefaultLeaseIdle
}
if !md.IsDefined("retention") {
c.Retention.Duration = DefaultRetention
}
if c.PollInterval.Duration == 0 {
c.PollInterval.Duration = DefaultPollInterval
}
@@ -152,6 +194,15 @@ func (c *Config) validate() *Error {
if e := c.checkQueue(); e != nil {
return e
}
if e := c.checkDB(); e != nil {
return e
}
if e := c.checkLeaseIdle(); e != nil {
return e
}
if e := c.checkRetention(); e != nil {
return e
}
if e := c.checkHosts(); e != nil {
return e
}
@@ -193,6 +244,27 @@ func (c *Config) checkQueue() *Error {
return nil
}
func (c *Config) checkDB() *Error {
if c.DB == "" {
return &Error{Field: "db", Msg: "required"}
}
return nil
}
func (c *Config) checkLeaseIdle() *Error {
if c.LeaseIdle.Duration < MinLeaseIdle {
return &Error{Field: "lease_idle", Msg: "must be at least 1m"}
}
return nil
}
func (c *Config) checkRetention() *Error {
if c.Retention.Duration < MinRetention {
return &Error{Field: "retention", Msg: "must be at least 1d"}
}
return nil
}
func (c *Config) checkHosts() *Error {
if len(c.Hosts) == 0 {
return &Error{Field: "hosts", Msg: "at least one required"}