Files
FarmHouse-Holdings/db/schema.sql
T
fez5587andClaude Fable 5 ad4f603799 Step 5+6: event schema v1, core DDL, policy table, Slice 1 acceptance demo
- db/schema.sql: 9 core tables, append-only event log (trigger-enforced),
  deny-by-default policy_rule with seeded globals, runaway budgets on work items.
  Validated against Postgres 16 (DDL applies, trigger rejects UPDATE).
- engine/schemas/events.py: AgentMessage (flat, inference-enforced) +
  EventEnvelope pydantic models; models are the schema (ADR-003).
- adr/ADR-003: event schema and data model decisions.
- SLICE1_DEMO.md: verbatim acceptance script for Slice 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 23:26:49 -05:00

162 lines
8.5 KiB
PL/PgSQL

-- FarmHouse Holdings — core schema v1 (Slice 1 subset, PRD §14.3)
-- Postgres 15+. Deferred entities (Schedule, KnowledgeItem, Incident, ...) added when their slice needs them.
CREATE TABLE company (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
slug text NOT NULL UNIQUE,
name text NOT NULL,
description text NOT NULL DEFAULT '',
lifecycle_state text NOT NULL DEFAULT 'draft'
CHECK (lifecycle_state IN ('draft','active','paused','archived')),
-- repo, discord channel, office theme, autonomy policy, tz override
config jsonb NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE employee (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid NOT NULL REFERENCES company(id),
name text NOT NULL,
title text NOT NULL,
department text NOT NULL DEFAULT '',
manager_id uuid REFERENCES employee(id),
hermes_profile text, -- Hermes profile name (ADR-001)
model_alias text NOT NULL DEFAULT 'local-default',
authority_level int NOT NULL DEFAULT 2 CHECK (authority_level BETWEEN 0 AND 5),
soul_version int NOT NULL DEFAULT 1, -- versioned system prompt (PRD §6.6)
status text NOT NULL DEFAULT 'idle'
CHECK (status IN ('idle','working','blocked','offline')),
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (company_id, name)
);
CREATE TABLE work_item (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid NOT NULL REFERENCES company(id),
parent_id uuid REFERENCES work_item(id),
type text NOT NULL CHECK (type IN
('objective','milestone','epic','task','subtask','defect',
'clarification','approval','research','proposal','experiment',
'review','incident','postmortem')),
title text NOT NULL,
description text NOT NULL DEFAULT '',
status text NOT NULL DEFAULT 'backlog' CHECK (status IN
('backlog','ready','in_progress','blocked','review','done','cancelled')),
priority text NOT NULL DEFAULT 'medium' CHECK (priority IN
('critical','high','medium','low','debt','research','experiment','idea')),
owner_id uuid REFERENCES employee(id),
acceptance_criteria jsonb NOT NULL DEFAULT '[]',
-- runaway caps, engine-enforced (PRD_ADDENDUM §3): max_iterations, max_tokens, max_wall_seconds
budget jsonb NOT NULL DEFAULT '{"max_iterations":15,"max_tokens":100000,"max_wall_seconds":1800}',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX work_item_dispatch_idx ON work_item (company_id, status, priority);
-- Append-only event log: single source of truth for history (PRD §14.2).
CREATE TABLE event (
event_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid REFERENCES company(id), -- NULL = holdings-level
actor text NOT NULL, -- 'employee:<uuid>' | 'user:philip' | 'system'
event_type text NOT NULL, -- see engine/schemas/events.py
schema_version int NOT NULL DEFAULT 1,
work_item_id uuid REFERENCES work_item(id),
parent_event_id uuid REFERENCES event(event_id),
correlation_id uuid, -- groups one logical exchange
source text NOT NULL DEFAULT 'engine'
CHECK (source IN ('engine','web','api','discord','hermes','scheduler','cli')),
payload jsonb NOT NULL DEFAULT '{}',
policy_decision jsonb, -- {allowed, rule_id, level, reason}
provenance jsonb, -- {model, worker, tool, hermes_run_id}
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX event_company_time_idx ON event (company_id, created_at);
CREATE INDEX event_work_item_idx ON event (work_item_id) WHERE work_item_id IS NOT NULL;
CREATE FUNCTION event_immutable() RETURNS trigger AS $$
BEGIN RAISE EXCEPTION 'event log is append-only'; END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER event_no_update BEFORE UPDATE OR DELETE ON event
FOR EACH ROW EXECUTE FUNCTION event_immutable();
CREATE TABLE approval (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid NOT NULL REFERENCES company(id),
work_item_id uuid REFERENCES work_item(id),
requested_by uuid NOT NULL REFERENCES employee(id),
level int NOT NULL CHECK (level BETWEEN 3 AND 5),
summary text NOT NULL,
options jsonb NOT NULL DEFAULT '[]', -- clarification options (PRD §7.6)
status text NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','approved','rejected','expired')),
decided_by text, -- 'user:philip' | 'policy:<rule_id>'
rationale text,
created_at timestamptz NOT NULL DEFAULT now(),
decided_at timestamptz
);
CREATE TABLE artifact (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid NOT NULL REFERENCES company(id),
work_item_id uuid REFERENCES work_item(id),
kind text NOT NULL CHECK (kind IN
('evidence_bundle','screenshot','video','trace','test_report',
'build','preview','document','diff')),
uri text NOT NULL, -- file path or object-store URI
meta jsonb NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE tool_execution (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid NOT NULL REFERENCES company(id),
employee_id uuid NOT NULL REFERENCES employee(id),
work_item_id uuid REFERENCES work_item(id),
tool text NOT NULL,
args jsonb NOT NULL DEFAULT '{}',
authority_level int NOT NULL,
policy_decision jsonb NOT NULL, -- always recorded (PRD §16.1)
status text NOT NULL DEFAULT 'running'
CHECK (status IN ('running','ok','error','denied','timeout')),
result_summary text,
started_at timestamptz NOT NULL DEFAULT now(),
finished_at timestamptz
);
CREATE TABLE cost_entry (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid NOT NULL REFERENCES company(id),
employee_id uuid REFERENCES employee(id),
work_item_id uuid REFERENCES work_item(id),
kind text NOT NULL CHECK (kind IN ('local_inference','consultant','tool','storage')),
tokens_in bigint NOT NULL DEFAULT 0,
tokens_out bigint NOT NULL DEFAULT 0,
wall_seconds numeric NOT NULL DEFAULT 0,
usd numeric NOT NULL DEFAULT 0, -- 0 for local; real for consultants
meta jsonb NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX cost_company_time_idx ON cost_entry (company_id, created_at);
-- Deterministic policy: most specific enabled rule wins (company+employee > company > global).
-- Engine denies by default when no rule matches (PRD §8: enforced in code, not prompts).
CREATE TABLE policy_rule (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid REFERENCES company(id), -- NULL = global
employee_id uuid REFERENCES employee(id), -- NULL = any employee
tool_pattern text NOT NULL DEFAULT '*', -- glob: 'git.*', 'shell', '*'
environment text NOT NULL DEFAULT 'sandbox'
CHECK (environment IN ('sandbox','internal','external','production')),
max_level int NOT NULL CHECK (max_level BETWEEN 0 AND 5),
enabled boolean NOT NULL DEFAULT true,
note text NOT NULL DEFAULT '',
created_at timestamptz NOT NULL DEFAULT now()
);
-- Global defaults: read/propose/sandbox anywhere; nothing above level 2 without an explicit rule.
INSERT INTO policy_rule (tool_pattern, environment, max_level, note) VALUES
('*', 'sandbox', 2, 'global default: full sandbox freedom'),
('*', 'internal', 1, 'global default: propose-only against internal env'),
('*', 'external', 0, 'global default: observe-only external'),
('*', 'production', 0, 'global default: observe-only production');