Adds the SQL substrate (6 tables + memory_status view) and the helpers every user story depends on: - migration 028_memory_consolidation.sql + smoke test - internal/messaging/memory_config.go (env-flag plumbing) - internal/messaging/dispatch_tokens.go (32-byte rand, 15m TTL, single-job-bound) - internal/messaging/memory_channels.go (open-brain / reflections-* / is_memory flag) - internal/agents/owner.go (OwnerFor with sentinel errors) Deviations from spec, all documented in code: - owner_id is stored as INTEGER FK to users; OwnerFor converts to the string scope-key the new tables use. - MemoryChannel is a local struct to avoid an import cycle between internal/channels and internal/messaging. - channels.metadata column does not exist yet; IsMemoryChannel honors it conditionally so MemoryChannelIDs can extend trivially when added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
54 lines
1.7 KiB
Go
54 lines
1.7 KiB
Go
package agents
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
// Sentinel errors returned by OwnerFor.
|
|
//
|
|
// Callers in the proactive-memory / dream-worker path use these to
|
|
// distinguish "no such agent" from "agent exists but has no owner"
|
|
// without parsing error strings.
|
|
var (
|
|
// ErrAgentNotFound is returned when no row exists in `agents` for
|
|
// the requested name.
|
|
ErrAgentNotFound = errors.New("agent not found")
|
|
|
|
// ErrAgentUnowned is returned when an agent row exists but its
|
|
// owner_id is zero / empty. In the current schema owner_id is
|
|
// declared NOT NULL, so this is effectively an integrity guard for
|
|
// rows backfilled with 0.
|
|
ErrAgentUnowned = errors.New("agent has no owner")
|
|
)
|
|
|
|
// OwnerFor returns the string-encoded owner_id of the named agent.
|
|
//
|
|
// The schema stores `agents.owner_id` as INTEGER (FK to `users.id`), but
|
|
// the proactive-memory tables and the request-context `owner_id`
|
|
// (populated by auth middleware via `trace.ContextWithOwnerID`) carry it
|
|
// as a string. OwnerFor canonicalizes to that string form so call sites
|
|
// can compare without re-converting.
|
|
//
|
|
// Returns ("", ErrAgentNotFound) when no row matches; ("",
|
|
// ErrAgentUnowned) when a row exists but owner_id is 0.
|
|
func OwnerFor(ctx context.Context, db *sql.DB, agentName string) (string, error) {
|
|
var ownerID int64
|
|
err := db.QueryRowContext(ctx,
|
|
`SELECT owner_id FROM agents WHERE name = ?`, agentName,
|
|
).Scan(&ownerID)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return "", ErrAgentNotFound
|
|
}
|
|
return "", fmt.Errorf("query owner for agent %q: %w", agentName, err)
|
|
}
|
|
if ownerID == 0 {
|
|
return "", ErrAgentUnowned
|
|
}
|
|
return strconv.FormatInt(ownerID, 10), nil
|
|
}
|