Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions API_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ The daemon exposes a **JSON-over-HTTP** interface on `localhost`.
* **Local-First**: Bound strictly to `127.0.0.1` (or a Unix Domain Socket in future strict modes).

### 1.2 Configuration
* **Default Port**: `8090` (configurable via env `RATELORD_PORT`)
* **Bind Address**: `127.0.0.1`
* **Default Address**: `127.0.0.1:8090` (configurable via env `RATELORD_ADDR` or CLI `--addr`)
* **Port Override**: `RATELORD_PORT` (legacy; used when `RATELORD_ADDR` is unset)
* **Content-Type**: `application/json` required for all POST bodies.

---
Expand Down
9 changes: 5 additions & 4 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ This guide covers deployment via Systemd, Docker, and Kubernetes.
- **Configuration**:
- `policy.json`: Defines pools, windows, and burst limits.
- Environment Variables: Store sensitive tokens (e.g., `GITHUB_TOKEN`, `OPENAI_API_KEY`).
- **Network**: Exposes an HTTP API on port `8090`.
- Daemon settings: `RATELORD_DB_PATH`, `RATELORD_POLICY_PATH` (or `RATELORD_CONFIG_PATH`), `RATELORD_ADDR`, `RATELORD_POLL_INTERVAL`, `RATELORD_WEB_ASSETS_MODE`, `RATELORD_WEB_DIR`.
- **Network**: Exposes an HTTP API on port `8090` (default bind `127.0.0.1:8090`).
- **Signals**:
- `SIGINT` / `SIGTERM`: Graceful shutdown (checkpoints state).
- `SIGHUP`: Hot-reload `policy.json`.
Expand Down Expand Up @@ -57,7 +58,7 @@ Type=simple
User=ratelord
Group=ratelord
# Adjust path to binary
ExecStart=/usr/local/bin/ratelord-d --config /etc/ratelord/policy.json --db /var/lib/ratelord/ratelord.db --addr 127.0.0.1:8090
ExecStart=/usr/local/bin/ratelord-d --policy /etc/ratelord/policy.json --db /var/lib/ratelord/ratelord.db --addr 127.0.0.1:8090
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
Expand Down Expand Up @@ -107,7 +108,7 @@ COPY policy.json /etc/ratelord/policy.json
# State volume
VOLUME /data
ENV RATELORD_DB_PATH=/data/ratelord.db
ENV RATELORD_CONFIG_PATH=/etc/ratelord/policy.json
ENV RATELORD_POLICY_PATH=/etc/ratelord/policy.json
ENV RATELORD_ADDR=0.0.0.0:8090

CMD ["ratelord-d"]
Expand Down Expand Up @@ -168,7 +169,7 @@ spec:
args:
- "--addr=127.0.0.1:8090"
- "--db=/data/ratelord.db"
- "--config=/etc/config/policy.json"
- "--policy=/etc/config/policy.json"
volumeMounts:
- name: ratelord-data
mountPath: /data
Expand Down
12 changes: 12 additions & 0 deletions LEARNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,15 @@ The "Required Document Set" is complete with 0 implementation code written, pres

### Challenges Encountered
1. **Context Continuity**: Deciding between `--continue` (stateful) and fresh runs (stateless). Fresh runs are safer for disk-sourced truth, but `--continue` can reduce repeated "orientation" overhead. We opted for NO `--continue` in the improved loop to allow the orchestrator to build on its internal reasoning.

## 2026-01-27: Configuration Implementation

### What Worked Well
1. **Spec-to-implementation alignment**: Updating deployment and API docs before code changes kept configuration expectations consistent with actual runtime behavior.
2. **Explicit defaults**: Centralizing defaults for DB path, policy path, and poll interval simplified daemon startup and made overrides easy to reason about.

### Challenges Encountered
1. **Legacy configuration naming**: Existing docs referenced `RATELORD_CONFIG_PATH`, requiring backward-compatible support while introducing a clearer `RATELORD_POLICY_PATH`.

### Improvements for Implementation Phase
1. **Config validation surface**: Add a dedicated validation pass (with better error messaging) as more config knobs accumulate.
14 changes: 4 additions & 10 deletions NEXT_STEPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@

## Current Context
- **v1.0.0 Released**: The system has been tagged and released.
- **Core Features**: Daemon authority, Event sourcing, Policy Engine, Forecasting, TUI, and initial Mock Provider are live.
- **Phase 7 Complete**: Real providers (GitHub, OpenAI) implemented. Dogfooding environment validated forecast accuracy.
- **Documentation**: Deployment guide (`DEPLOYMENT.md`) is now available. `CLIENT_SDK_SPEC.md` has been drafted.
- **Core Features**: Daemon authority, Event sourcing, Policy Engine, Forecasting, TUI, Web UI, and real providers are live.
- **Configuration Implemented**: Daemon config now supports env/flag overrides for DB path, policy path, poll interval, listen address, and web assets mode.
- **Documentation**: Deployment guide (`DEPLOYMENT.md`) and client SDK spec (`CLIENT_SDK_SPEC.md`) are available.

## Immediate Actions

1. **Phase 8: Operations & Expansion**:
- **Epic 18: Web UI**:
- [x] **M18.1-M18.4: Web UI V1**: Dashboard, Scaffolding, and Embedding are live.
- [x] **M18.5: History View**: Deep dive into event log history with filters.
- [x] **M18.6: Identity Explorer**: Detailed view of registered identities and their stats.

*Note: Epic 18 (Web UI) is now complete.*
1. **Define the next milestone**: All tasks in `TASKS.md` are complete; queue the next phase or open issues for post-1.0 improvements.

## Reference

Expand Down
3 changes: 3 additions & 0 deletions PHASE_LEDGER.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@
- **Action**: Implemented Web UI Dashboard View: Created API client (`web/src/lib/api.ts`), AppShell layout (`web/src/layouts/AppShell.tsx`), Dashboard page (`web/src/pages/Dashboard.tsx`), and updated App.tsx for routing. Built successfully with TypeScript and Vite.
- [x] **M18.4: Build Integration** (Epic 18) - Integrated Web UI into daemon using `//go:embed`. Updated Makefile to build web assets and embed them into `cmd/ratelord-d`. Added `--web-dir` flag to serve dev builds or embedded assets by default.
- [x] **Epic 18: Web UI Implementation** - Status: Completed. Implemented Dashboard, History, and Identity Explorer. React + Vite + Tailwind stack embedded in Go binary.

## 2026-01-27
- [x] **M1.4: Configuration** (Epic 1) - Added daemon config loader with env/flag overrides for DB path, policy path, poll interval, listen address, and web assets mode. Updated deployment and API docs plus tracking files.
4 changes: 3 additions & 1 deletion PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
| pkg/engine/poller.go | UPDATED | Agent | 2026-01-25 |
| pkg/engine/config.go | COMMITTED | Agent | 2026-01-25 |
| pkg/engine/loader.go | COMMITTED | Agent | 2026-01-25 |
| cmd/ratelord-d/main.go | UPDATED | Agent | 2026-01-25 |
| cmd/ratelord-d/main.go | UPDATED | Agent | 2026-01-27 |
| cmd/ratelord-d/config.go | CREATED | Agent | 2026-01-27 |
| cmd/ratelord-tui/main.go | CREATED | Agent | 2026-01-25 |
| cmd/ratelord-sim/main.go | CREATED | Agent | 2026-01-25 |
| Epic 7: Forecasting | COMPLETED | Agent | 2026-01-25 |
Expand All @@ -55,6 +56,7 @@
| M11.2: Implement client-side Wait/Modify logic | COMPLETED | Feature | 2026-01-25 |
| M12.1: Persist Provider State | COMPLETED | Fix | 2026-01-26 |
| M12.2: TUI Verification | COMPLETED | Terminal | 2026-01-26 |
| M1.4: Configuration | COMPLETED | Agent | 2026-01-27 |
| Epic 14: GitHub Provider | COMPLETED | Agent | 2026-01-26 |
| Epic 15: OpenAI Provider | COMPLETED | Agent | 2026-01-26 |
| pkg/client/types.go | CREATED | Implement | 2026-01-26 |
Expand Down
2 changes: 1 addition & 1 deletion TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Focus: Getting the process to run, manage its lifecycle, and handle signals corr
- [x] **M1.3: Logging & Observability**
- Setup structured logging (stdout/stderr).
- Emit `system_started` log on boot.
- [ ] **M1.4: Configuration**
- [x] **M1.4: Configuration**
- Implement configuration loader (env vars, defaults).
- *Note*: Split from M1.1 to ensure atomic commits.

Expand Down
154 changes: 154 additions & 0 deletions cmd/ratelord-d/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package main

import (
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
)

const (
defaultAddr = "127.0.0.1:8090"
defaultPollInterval = 10 * time.Second
defaultWebAssetsMode = "embedded"
)

type Config struct {
DBPath string
PolicyPath string
Addr string
PollInterval time.Duration
WebAssetsMode string
WebDir string
}

func LoadConfig(args []string) (Config, error) {
cwd, err := os.Getwd()
if err != nil {
return Config{}, fmt.Errorf("failed to get cwd: %w", err)
}

defaultDBPath := filepath.Join(cwd, "ratelord.db")
defaultPolicyPath := filepath.Join(cwd, "policy.json")

dbPath := envOrDefault("RATELORD_DB_PATH", defaultDBPath)
policyPath := envOrDefaultWithFallback([]string{"RATELORD_POLICY_PATH", "RATELORD_CONFIG_PATH"}, defaultPolicyPath)
addr := addrFromEnv(defaultAddr)
pollInterval := defaultPollInterval
if pollIntervalEnv := os.Getenv("RATELORD_POLL_INTERVAL"); pollIntervalEnv != "" {
parsed, err := time.ParseDuration(pollIntervalEnv)
if err != nil {
return Config{}, fmt.Errorf("invalid RATELORD_POLL_INTERVAL: %w", err)
}
pollInterval = parsed
}
webAssetsMode := envOrDefault("RATELORD_WEB_ASSETS_MODE", defaultWebAssetsMode)
webDir := os.Getenv("RATELORD_WEB_DIR")

flagSet := flag.NewFlagSet("ratelord-d", flag.ContinueOnError)
flagSet.SetOutput(io.Discard)
flagDB := flagSet.String("db", dbPath, "path to SQLite database")
flagPolicy := flagSet.String("policy", policyPath, "path to policy JSON")
flagAddr := flagSet.String("addr", addr, "HTTP listen address")
flagPollInterval := flagSet.String("poll-interval", pollInterval.String(), "provider poll interval")
flagWebAssets := flagSet.String("web-assets", webAssetsMode, "web assets mode: embedded|fs|off")
flagWebDir := flagSet.String("web-dir", webDir, "web assets directory when web-assets=fs")

if err := flagSet.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
flagSet.SetOutput(os.Stdout)
flagSet.PrintDefaults()
return Config{}, err
}
return Config{}, err
}

pollIntervalParsed, err := time.ParseDuration(*flagPollInterval)
if err != nil {
return Config{}, fmt.Errorf("invalid poll interval: %w", err)
}
Comment on lines +70 to +73

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The poll interval should be validated to ensure it's positive. A negative or zero poll interval would cause time.NewTicker to panic when the Poller starts. Add validation to check that pollIntervalParsed is greater than zero before returning the config.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback


resolvedDBPath := resolvePath(*flagDB, cwd)
resolvedPolicyPath := resolvePath(*flagPolicy, cwd)
mode := normalizeWebAssetsMode(*flagWebAssets)

config := Config{
DBPath: resolvedDBPath,
PolicyPath: resolvedPolicyPath,
Addr: strings.TrimSpace(*flagAddr),
PollInterval: pollIntervalParsed,
WebAssetsMode: mode,
WebDir: strings.TrimSpace(*flagWebDir),
}

if config.Addr == "" {
return Config{}, errors.New("addr cannot be empty")
}

if config.WebAssetsMode == "fs" {
if config.WebDir == "" {
return Config{}, errors.New("web-assets=fs requires web-dir")
}
config.WebDir = resolvePath(config.WebDir, cwd)
}

if config.WebAssetsMode != "embedded" && config.WebAssetsMode != "fs" && config.WebAssetsMode != "off" {
return Config{}, fmt.Errorf("unsupported web-assets mode: %s", config.WebAssetsMode)
}

return config, nil
}

func envOrDefault(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}

func envOrDefaultWithFallback(keys []string, fallback string) string {
for _, key := range keys {
if value := os.Getenv(key); value != "" {
return value
}
}
return fallback
}

func addrFromEnv(fallback string) string {
if value := os.Getenv("RATELORD_ADDR"); value != "" {
return value
}
if port := os.Getenv("RATELORD_PORT"); port != "" {
return fmt.Sprintf("127.0.0.1:%s", port)
}
return fallback
}

func resolvePath(path string, cwd string) string {
trimmed := strings.TrimSpace(path)
if trimmed == "" {
return trimmed
}
if filepath.IsAbs(trimmed) {
return trimmed
}
return filepath.Join(cwd, trimmed)
}

func normalizeWebAssetsMode(mode string) string {
switch strings.ToLower(strings.TrimSpace(mode)) {
case "", "embedded":
return "embedded"
case "fs", "dir", "directory":
return "fs"
case "off", "disabled", "none":
return "off"
default:
return strings.ToLower(strings.TrimSpace(mode))
}
}
Comment on lines +1 to +154

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new config.go file lacks test coverage. Given that other packages in the codebase (store, engine, provider, client) have comprehensive test coverage, this configuration loader should also have tests to verify env/flag precedence, validation logic, path resolution, and error cases. For example, tests should cover: invalid poll intervals, empty addresses, missing web-dir when mode is 'fs', invalid web-assets modes, and precedence of flags over environment variables.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Loading