Promote AgentCompany-New to the repository root and retire the old app

The legacy application - its src, tests, landing site, promo assets, docs,
release tooling, CI workflows, and root configs - leaves the tree; it stays
reachable in history. Everything under AgentCompany-New moves up unchanged:
same package name, so the production userData/database path is untouched,
and e2e/build scripts already resolved paths relative to the project. The
developer cred.txt stays gitignored at its new location. Full verify:ci
passes from the new root.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 00:04:13 -04:00
1 parent 2350f744e3
commit 07d0b4587d
1904 files changed
+1133 -282075

No files matched your search

+235
View File
@@ -0,0 +1,235 @@
export const initialEnterpriseSchema = `
create table if not exists organizations (
id text primary key,
name text not null,
mission text not null,
goal text not null,
created_at text not null
);
create table if not exists employees (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
display_name text not null,
role text not null,
created_at text not null
);
create table if not exists agents (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
name text not null,
role text not null,
department text not null,
model_provider_id text,
model_id text,
budget_cents integer not null,
manager_agent_id text references agents(id) on delete set null,
lifecycle text not null,
created_at text not null
);
create table if not exists tasks (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
title text not null,
description text not null,
status text not null,
assignee_agent_id text references agents(id) on delete set null,
risk text not null,
created_at text not null,
updated_at text not null
);
create table if not exists approvals (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
kind text not null,
title text not null,
details text not null,
risk text not null,
requested_by_type text not null,
requested_by_id text,
state text not null,
decided_by_employee_id text references employees(id) on delete set null,
created_at text not null,
decided_at text
);
create table if not exists chat_spaces (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
space_type text not null,
name text not null,
topic text not null,
visibility text not null,
created_at text not null
);
create table if not exists chat_memberships (
space_id text not null references chat_spaces(id) on delete cascade,
principal_type text not null,
principal_id text not null,
role text not null,
mute_state text not null,
primary key (space_id, principal_type, principal_id)
);
create table if not exists chat_messages (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
space_id text not null references chat_spaces(id) on delete cascade,
thread_id text,
sender_type text not null,
sender_id text,
body_md text not null,
created_at text not null,
edited_at text,
deleted_at text
);
create table if not exists file_objects (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
name text not null,
kind text not null,
size_bytes integer not null,
sha256 text not null,
storage_path text not null,
owner_type text not null,
owner_id text,
created_at text not null
);
create table if not exists providers (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
provider_key text not null,
label text not null,
auth_type text not null,
model text,
credential_id text,
enabled integer not null,
created_at text not null,
unique(org_id, provider_key)
);
create table if not exists credentials (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
provider_key text not null,
kind text not null,
ciphertext blob not null,
display_hint text not null,
created_at text not null
);
create table if not exists skill_definitions (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
slug text not null,
name text not null,
owner_type text not null,
status text not null,
created_at text not null,
unique(org_id, slug)
);
create table if not exists skill_revisions (
id text primary key,
skill_id text not null references skill_definitions(id) on delete cascade,
version_semver text not null,
manifest_json text not null,
source_type text not null,
source_ref text not null,
published_at text not null,
immutable_checksum text not null,
unique(skill_id, version_semver)
);
create table if not exists skill_artifacts (
id text primary key,
revision_id text not null references skill_revisions(id) on delete cascade,
artifact_type text not null,
object_key text not null,
sha256 text not null,
size_bytes integer not null
);
create table if not exists skill_tests (
id text primary key,
revision_id text not null references skill_revisions(id) on delete cascade,
test_type text not null,
expected_output_ref text not null,
last_result text,
last_run_at text
);
create table if not exists skill_bindings (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
agent_id text not null references agents(id) on delete cascade,
skill_id text not null references skill_definitions(id) on delete cascade,
pinned_revision_id text not null references skill_revisions(id) on delete cascade,
binding_mode text not null,
priority integer not null,
enabled integer not null,
unique(agent_id, skill_id)
);
create table if not exists social_accounts (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
platform text not null,
handle text not null,
display_name text not null,
authorized_employee_id text references employees(id) on delete set null,
created_at text not null
);
create table if not exists social_account_grants (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
social_account_id text not null references social_accounts(id) on delete cascade,
employee_id text not null references employees(id) on delete cascade,
role text not null,
scope_json text not null,
created_at text not null
);
create table if not exists browser_actions (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
social_account_id text not null references social_accounts(id) on delete cascade,
action_type text not null,
policy_decision text not null,
evidence_file_id text references file_objects(id) on delete set null,
created_at text not null
);
create table if not exists migration_reports (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
source_ref text not null,
report_json text not null,
created_at text not null
);
create table if not exists audit_events (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
entity_type text not null,
entity_id text not null,
actor_type text not null,
actor_id text,
action text not null,
payload_json text not null,
created_at text not null
);
create index if not exists idx_tasks_org_status on tasks(org_id, status);
create index if not exists idx_messages_space_created on chat_messages(space_id, created_at);
create index if not exists idx_audit_org_created on audit_events(org_id, created_at);
insert into schema_migrations(version, name, applied_at)
values (1, 'initial_enterprise_schema', datetime('now'));
`;
@@ -0,0 +1,117 @@
export const collaborationProviderPolicySchema = `
create table if not exists skill_dependencies (
revision_id text not null references skill_revisions(id) on delete cascade,
depends_on_revision_id text not null references skill_revisions(id) on delete cascade,
dependency_kind text not null,
optional integer not null,
primary key (revision_id, depends_on_revision_id)
);
create table if not exists skill_runtime_snapshots (
id text primary key,
agent_id text not null references agents(id) on delete cascade,
run_id text not null,
compiled_prompt text not null,
tools_manifest text not null,
source_revision_ids text not null,
created_at text not null
);
create table if not exists skill_catalog_tags (
skill_id text not null references skill_definitions(id) on delete cascade,
tag text not null,
tag_type text not null,
primary key (skill_id, tag, tag_type)
);
create table if not exists chat_threads (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
space_id text not null references chat_spaces(id) on delete cascade,
root_message_id text references chat_messages(id) on delete set null,
title text not null,
state text not null,
created_at text not null,
updated_at text not null
);
create table if not exists chat_mentions (
message_id text not null references chat_messages(id) on delete cascade,
target_type text not null,
target_id text not null,
offset_start integer not null,
offset_end integer not null
);
create table if not exists chat_attachments (
message_id text not null references chat_messages(id) on delete cascade,
file_id text not null references file_objects(id) on delete cascade,
transfer_type text not null,
checksum text not null,
preview_key text
);
create table if not exists chat_presence (
principal_type text not null,
principal_id text not null,
status text not null,
status_text text not null,
last_seen_at text not null,
primary key (principal_type, principal_id)
);
create table if not exists chat_slash_invocations (
id text primary key,
message_id text not null references chat_messages(id) on delete cascade,
command text not null,
args_json text not null,
result_ref text,
created_at text not null
);
create table if not exists provider_requests (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
provider_id text not null references providers(id) on delete cascade,
provider_key text not null,
model text not null,
prompt_sha256 text not null,
response_text text,
raw_response_json text,
state text not null,
error_message text,
created_at text not null,
completed_at text
);
create table if not exists policy_rules (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
subject_type text not null,
subject_id text,
action text not null,
effect text not null,
risk_ceiling text not null,
created_at text not null
);
create index if not exists idx_provider_requests_org_created on provider_requests(org_id, created_at);
create index if not exists idx_chat_threads_space_updated on chat_threads(space_id, updated_at);
insert into policy_rules(id, org_id, subject_type, subject_id, action, effect, risk_ceiling, created_at)
select
'pol_' || lower(hex(randomblob(16))),
id,
'organization',
id,
'task_create',
'require_approval',
'medium',
datetime('now')
from organizations
where not exists (
select 1 from policy_rules
where policy_rules.org_id = organizations.id and policy_rules.action = 'task_create'
);
insert into schema_migrations(version, name, applied_at)
values (2, 'collaboration_provider_policy_schema', datetime('now'));
`;
@@ -0,0 +1,76 @@
export const onboardingExecutionSchema = `
alter table organizations add column description text not null default '';
alter table organizations add column execution_mode text not null default 'ai_ceo';
alter table organizations add column auto_approve_hires integer not null default 1;
alter table organizations add column review_deliverables integer not null default 0;
alter table agents add column title text not null default '';
alter table agents add column role_level text not null default 'staff';
alter table agents add column model_strategy text not null default 'pooled';
alter table agents add column workspace_id text;
alter table agents add column heartbeat_enabled integer not null default 0;
alter table agents add column heartbeat_interval_sec integer not null default 0;
alter table tasks add column project_id text;
alter table tasks add column goal_id text;
alter table tasks add column workspace_id text;
alter table tasks add column priority text not null default 'normal';
alter table tasks add column requires_filesystem integer not null default 0;
create table if not exists workspaces (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
name text not null,
local_path text not null,
repo_url text not null,
repo_ref text not null,
project_id text,
is_primary integer not null,
created_at text not null,
updated_at text not null
);
create table if not exists goals (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
title text not null,
description text not null,
status text not null,
owner_agent_id text references agents(id) on delete set null,
created_at text not null,
updated_at text not null
);
create table if not exists projects (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
goal_id text references goals(id) on delete set null,
name text not null,
description text not null,
lead_agent_id text references agents(id) on delete set null,
status text not null,
created_at text not null,
updated_at text not null
);
create table if not exists task_runs (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
task_id text not null references tasks(id) on delete cascade,
agent_id text references agents(id) on delete set null,
provider_id text references providers(id) on delete set null,
trigger text not null,
state text not null,
created_at text not null,
started_at text,
completed_at text,
error_message text
);
create index if not exists idx_workspaces_org_primary on workspaces(org_id, is_primary);
create index if not exists idx_goals_org_status on goals(org_id, status);
create index if not exists idx_projects_org_status on projects(org_id, status);
create index if not exists idx_task_runs_task_created on task_runs(task_id, created_at);
insert into schema_migrations(version, name, applied_at)
values (3, 'onboarding_execution_schema', datetime('now'));
`;
@@ -0,0 +1,25 @@
export const filesPinnedAndScheduledPosts = `
alter table file_objects add column pinned integer not null default 0;
alter table file_objects add column space_id text;
alter table file_objects add column updated_at text;
create table if not exists scheduled_posts (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
social_account_id text not null references social_accounts(id) on delete cascade,
body_md text not null,
scheduled_for text not null,
state text not null,
approval_id text references approvals(id) on delete set null,
created_by_employee_id text references employees(id) on delete set null,
created_at text not null,
updated_at text not null
);
create index if not exists idx_files_org_pinned on file_objects(org_id, pinned);
create index if not exists idx_scheduled_posts_org_state on scheduled_posts(org_id, state);
create index if not exists idx_scheduled_posts_account_date on scheduled_posts(social_account_id, scheduled_for);
insert into schema_migrations(version, name, applied_at)
values (4, 'files_pinned_and_scheduled_posts', datetime('now'));
`;
@@ -0,0 +1,15 @@
export const chatReadState = `
create table if not exists chat_read_state (
org_id text not null references organizations(id) on delete cascade,
employee_id text not null references employees(id) on delete cascade,
space_id text not null references chat_spaces(id) on delete cascade,
last_read_message_id text references chat_messages(id) on delete set null,
last_read_at text not null,
primary key (employee_id, space_id)
);
create index if not exists idx_chat_read_state_org on chat_read_state(org_id, employee_id);
insert into schema_migrations(version, name, applied_at)
values (5, 'chat_read_state', datetime('now'));
`;
@@ -0,0 +1,14 @@
export const appStateSchema = `
create table if not exists app_state (
id integer primary key check (id = 1),
active_org_id text references organizations(id) on delete set null,
updated_at text not null
);
insert into app_state(id, active_org_id, updated_at)
select 1, (select id from organizations order by created_at asc limit 1), datetime('now')
where not exists (select 1 from app_state where id = 1);
insert into schema_migrations(version, name, applied_at)
values (6, 'app_state', datetime('now'));
`;
@@ -0,0 +1,4 @@
export const skillHardeningSchema = `
create unique index if not exists idx_skill_runtime_snapshots_agent_run
on skill_runtime_snapshots(agent_id, run_id);
`;
@@ -0,0 +1,6 @@
export const providerHealthSchema = `
alter table providers add column auth_state text not null default 'unconfigured';
alter table providers add column health_state text not null default 'unchecked';
alter table providers add column last_checked_at text;
alter table providers add column last_error text;
`;
@@ -0,0 +1,49 @@
export const socialGovernanceSchema = `
update social_account_grants
set scope_json = '{"actions":["login","draft","approve"],"riskCeiling":"medium"}'
where scope_json = '{"actions":["login","draft","approve"]}';
create temp table if not exists social_post_approval_backfill(
post_id text primary key,
approval_id text not null
);
delete from social_post_approval_backfill;
insert into social_post_approval_backfill(post_id, approval_id)
select id, 'app_' || lower(hex(randomblob(16)))
from scheduled_posts
where state = 'scheduled' and approval_id is null;
insert into approvals(
id, org_id, kind, title, details, risk, requested_by_type, requested_by_id,
state, decided_by_employee_id, created_at, decided_at
)
select
backfill.approval_id,
posts.org_id,
'social_publish',
'Approve social post',
posts.body_md,
'medium',
case when posts.created_by_employee_id is null then 'system' else 'employee' end,
posts.created_by_employee_id,
'pending',
null,
posts.created_at,
null
from scheduled_posts posts
join social_post_approval_backfill backfill on backfill.post_id = posts.id;
update scheduled_posts
set approval_id = (
select approval_id from social_post_approval_backfill
where social_post_approval_backfill.post_id = scheduled_posts.id
)
where id in (select post_id from social_post_approval_backfill);
drop table social_post_approval_backfill;
insert into schema_migrations(version, name, applied_at)
values (9, 'social_governance', datetime('now'));
`;
@@ -0,0 +1,52 @@
export const fileHandoffSecuritySchema = `
alter table file_objects add column scan_state text not null default 'approved';
alter table file_objects add column scan_labels_json text not null default '[]';
alter table file_objects add column scanned_at text;
create table if not exists file_access_grants (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
file_id text not null references file_objects(id) on delete cascade,
granted_to_type text not null,
granted_to_id text not null,
granted_by_type text not null,
granted_by_id text,
source_message_id text references chat_messages(id) on delete set null,
source_thread_id text references chat_threads(id) on delete set null,
access_level text not null,
created_at text not null,
unique(file_id, granted_to_type, granted_to_id, source_message_id, access_level)
);
insert into file_access_grants(
id, org_id, file_id, granted_to_type, granted_to_id, granted_by_type, granted_by_id,
source_message_id, source_thread_id, access_level, created_at
)
select
'fgr_' || lower(hex(randomblob(16))),
org_id,
id,
'org_group',
org_id || ':workspace',
'system',
null,
null,
null,
'manage',
coalesce(created_at, datetime('now'))
from file_objects
where owner_type = 'system'
and not exists (
select 1 from file_access_grants
where file_access_grants.file_id = file_objects.id
and file_access_grants.granted_to_type = 'org_group'
and file_access_grants.granted_to_id = file_objects.org_id || ':workspace'
);
create index if not exists idx_file_grants_org_file on file_access_grants(org_id, file_id);
create index if not exists idx_file_grants_principal on file_access_grants(org_id, granted_to_type, granted_to_id);
create index if not exists idx_files_org_scan on file_objects(org_id, scan_state);
insert into schema_migrations(version, name, applied_at)
values (10, 'file_handoff_security', datetime('now'));
`;
@@ -0,0 +1,29 @@
export const boardInterventionSchema = `
alter table chat_threads add column board_state text not null default 'open';
alter table chat_threads add column controlled_by_type text;
alter table chat_threads add column controlled_by_id text;
create table if not exists board_interventions (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
thread_id text not null references chat_threads(id) on delete cascade,
message_id text not null references chat_messages(id) on delete cascade,
mode text not null,
action text not null,
details text not null,
target_type text,
target_id text,
approval_id text references approvals(id) on delete set null,
created_by_type text not null,
created_by_id text,
created_at text not null,
unique(message_id)
);
create index if not exists idx_board_interventions_org_thread on board_interventions(org_id, thread_id, created_at);
create index if not exists idx_board_interventions_message on board_interventions(message_id);
create index if not exists idx_chat_threads_org_board_state on chat_threads(org_id, board_state);
insert into schema_migrations(version, name, applied_at)
values (11, 'board_interventions', datetime('now'));
`;
@@ -0,0 +1,47 @@
export const complianceControlsSchema = `
alter table chat_spaces add column retention_policy text not null default 'standard_365d';
create table if not exists compliance_exports (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
subject_type text not null,
subject_id text not null,
export_type text not null,
state text not null,
payload_json text not null,
requested_by_employee_id text references employees(id) on delete set null,
created_at text not null,
completed_at text
);
create table if not exists legal_holds (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
entity_type text not null,
entity_id text not null,
reason text not null,
created_by_employee_id text references employees(id) on delete set null,
active integer not null,
created_at text not null,
released_at text
);
create table if not exists retention_freezes (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
scope_type text not null,
scope_id text not null,
reason text not null,
created_by_employee_id text references employees(id) on delete set null,
active integer not null,
created_at text not null,
released_at text
);
create index if not exists idx_compliance_exports_org_created on compliance_exports(org_id, created_at);
create index if not exists idx_legal_holds_org_entity on legal_holds(org_id, entity_type, entity_id, active);
create index if not exists idx_retention_freezes_org_scope on retention_freezes(org_id, scope_type, scope_id, active);
insert into schema_migrations(version, name, applied_at)
values (12, 'compliance_controls', datetime('now'));
`;
@@ -0,0 +1,13 @@
export const searchDocumentsSchema = `
create virtual table if not exists search_documents using fts5(
org_id unindexed,
entity_type unindexed,
entity_id unindexed,
title,
body,
facet_json unindexed
);
insert into schema_migrations(version, name, applied_at)
values (13, 'search_documents', datetime('now'));
`;
@@ -0,0 +1,31 @@
export const providerPolicyUsageSchema = `
alter table providers add column data_policy text not null default 'standard';
alter table providers add column background_enabled integer not null default 0;
alter table providers add column monthly_request_limit integer not null default 1000;
alter table providers add column workspace_scope text not null default 'organization';
alter table provider_requests add column input_char_count integer not null default 0;
alter table provider_requests add column output_char_count integer not null default 0;
alter table provider_requests add column execution_mode text not null default 'foreground';
alter table provider_requests add column policy_decision text not null default 'allowed:standard';
create table if not exists provider_usage_windows (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
provider_id text not null references providers(id) on delete cascade,
window_start text not null,
window_end text not null,
request_count integer not null,
input_char_count integer not null,
output_char_count integer not null,
created_at text not null,
updated_at text not null,
unique(provider_id, window_start)
);
create index if not exists idx_provider_usage_windows_org_start
on provider_usage_windows(org_id, window_start);
insert into schema_migrations(version, name, applied_at)
values (14, 'provider_policy_usage', datetime('now'));
`;
@@ -0,0 +1,90 @@
export const identityAccessSchema = `
create table if not exists employee_identities (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
employee_id text not null references employees(id) on delete cascade,
source text not null,
external_subject text not null,
email text,
active integer not null,
created_at text not null,
last_seen_at text,
unique(org_id, source, external_subject)
);
create table if not exists service_identities (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
agent_id text not null references agents(id) on delete cascade,
name text not null,
scope_json text not null,
created_at text not null,
unique(org_id, agent_id)
);
create table if not exists role_grants (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
principal_type text not null,
principal_id text not null,
role_key text not null,
scope_type text not null,
scope_id text not null,
granted_by_employee_id text references employees(id) on delete set null,
created_at text not null,
unique(org_id, principal_type, principal_id, role_key, scope_type, scope_id)
);
create table if not exists access_decisions (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
principal_type text not null,
principal_id text not null,
action text not null,
resource_type text not null,
resource_id text not null,
decision text not null,
reason text not null,
created_at text not null
);
create index if not exists idx_role_grants_principal on role_grants(org_id, principal_type, principal_id);
create index if not exists idx_access_decisions_org_created on access_decisions(org_id, created_at);
insert into employee_identities(id, org_id, employee_id, source, external_subject, email, active, created_at, last_seen_at)
select 'eid_' || lower(hex(randomblob(16))), org_id, id, 'bootstrap', id, null, 1, datetime('now'), datetime('now')
from employees
where not exists (
select 1 from employee_identities
where employee_identities.org_id = employees.org_id and employee_identities.employee_id = employees.id
);
insert into role_grants(id, org_id, principal_type, principal_id, role_key, scope_type, scope_id, granted_by_employee_id, created_at)
select 'rgr_' || lower(hex(randomblob(16))), org_id, 'employee', id, seed_role_key, 'organization', org_id, null, datetime('now')
from employees
cross join (
select 'org_admin' as seed_role_key
union all select 'approval_decider'
union all select 'hiring_manager'
)
where not exists (
select 1 from role_grants
where role_grants.org_id = employees.org_id
and role_grants.principal_type = 'employee'
and role_grants.principal_id = employees.id
and role_grants.role_key = seed_role_key
and role_grants.scope_type = 'organization'
and role_grants.scope_id = employees.org_id
);
insert into service_identities(id, org_id, agent_id, name, scope_json, created_at)
select 'sid_' || lower(hex(randomblob(16))), org_id, id, name || ' service identity', json_object('agentId', id), datetime('now')
from agents
where not exists (
select 1 from service_identities
where service_identities.org_id = agents.org_id and service_identities.agent_id = agents.id
);
insert into schema_migrations(version, name, applied_at)
values (15, 'identity_access', datetime('now'));
`;
@@ -0,0 +1,89 @@
export const orgGovernanceSchema = `
create table if not exists org_departments (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
name text not null,
lead_agent_id text references agents(id) on delete set null,
headcount_limit integer not null,
monthly_budget_limit_cents integer not null,
created_at text not null,
updated_at text not null,
unique(org_id, name)
);
create table if not exists hiring_policies (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
department_name text not null,
max_headcount integer not null,
monthly_budget_limit_cents integer not null,
require_approval_above_cents integer not null,
created_at text not null,
updated_at text not null,
unique(org_id, department_name)
);
create table if not exists approval_routing_rules (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
kind text not null,
minimum_risk text not null,
route_to_type text not null,
route_to_id text not null,
created_at text not null,
unique(org_id, kind, minimum_risk)
);
create table if not exists approval_assignments (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
approval_id text not null references approvals(id) on delete cascade,
assignee_type text not null,
assignee_id text not null,
routing_rule_id text references approval_routing_rules(id) on delete set null,
created_at text not null,
unique(approval_id, assignee_type, assignee_id)
);
create index if not exists idx_org_departments_org_name on org_departments(org_id, name);
create index if not exists idx_hiring_policies_org_department on hiring_policies(org_id, department_name);
create index if not exists idx_approval_assignments_org_approval on approval_assignments(org_id, approval_id);
insert into org_departments(id, org_id, name, lead_agent_id, headcount_limit, monthly_budget_limit_cents, created_at, updated_at)
select 'dep_' || lower(hex(randomblob(16))), organizations.id, seed.name, null, 12, 2500000, datetime('now'), datetime('now')
from organizations
cross join (
select 'Executive' as name union all select 'Engineering' union all select 'Product' union all select 'Design'
union all select 'Marketing' union all select 'Operations' union all select 'Support'
) seed
where not exists (
select 1 from org_departments
where org_departments.org_id = organizations.id and org_departments.name = seed.name
);
insert into hiring_policies(id, org_id, department_name, max_headcount, monthly_budget_limit_cents, require_approval_above_cents, created_at, updated_at)
select 'hpl_' || lower(hex(randomblob(16))), org_id, name, headcount_limit, monthly_budget_limit_cents, 500000, datetime('now'), datetime('now')
from org_departments
where not exists (
select 1 from hiring_policies
where hiring_policies.org_id = org_departments.org_id and hiring_policies.department_name = org_departments.name
);
insert into approval_routing_rules(id, org_id, kind, minimum_risk, route_to_type, route_to_id, created_at)
select 'arr_' || lower(hex(randomblob(16))), organizations.id, '*', 'low', 'employee', employees.id, datetime('now')
from organizations
join employees on employees.org_id = organizations.id
where employees.id = (
select first_employee.id from employees first_employee
where first_employee.org_id = organizations.id
order by first_employee.created_at asc
limit 1
)
and not exists (
select 1 from approval_routing_rules
where approval_routing_rules.org_id = organizations.id and approval_routing_rules.kind = '*' and approval_routing_rules.minimum_risk = 'low'
);
insert into schema_migrations(version, name, applied_at)
values (16, 'org_governance', datetime('now'));
`;
@@ -0,0 +1,109 @@
export const operationsControlsSchema = `
create table if not exists feature_flags (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
key text not null,
label text not null,
enabled integer not null check(enabled in (0, 1)),
created_at text not null,
updated_at text not null,
unique(org_id, key)
);
create table if not exists slo_targets (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
signal_key text not null,
label text not null,
target_basis_points integer not null,
window_minutes integer not null,
created_at text not null,
updated_at text not null,
unique(org_id, signal_key)
);
create table if not exists operational_events (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
signal_key text not null,
source text not null,
success integer not null check(success in (0, 1)),
duration_ms integer,
detail_json text not null,
created_at text not null
);
create table if not exists chaos_drills (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
key text not null,
label text not null,
scenario_key text not null,
enabled integer not null check(enabled in (0, 1)),
created_at text not null,
updated_at text not null,
unique(org_id, key)
);
create table if not exists chaos_drill_runs (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
drill_id text not null references chaos_drills(id) on delete cascade,
state text not null,
result_json text not null,
started_at text not null,
completed_at text not null
);
create index if not exists idx_operational_events_org_signal_created
on operational_events(org_id, signal_key, created_at);
create index if not exists idx_chaos_drill_runs_org_drill_started
on chaos_drill_runs(org_id, drill_id, started_at);
insert into feature_flags(id, org_id, key, label, enabled, created_at, updated_at)
select 'ffg_' || lower(hex(randomblob(16))), organizations.id, seed.key, seed.label, seed.enabled, datetime('now'), datetime('now')
from organizations
cross join (
select 'provider_background_execution' as key, 'Provider background execution' as label, 1 as enabled
union all select 'social_publish_approval', 'Social publish approval', 1
union all select 'board_intervention', 'Board intervention', 1
union all select 'chaos_drills', 'Chaos drills', 1
) seed
where not exists (
select 1 from feature_flags
where feature_flags.org_id = organizations.id and feature_flags.key = seed.key
);
insert into slo_targets(id, org_id, signal_key, label, target_basis_points, window_minutes, created_at, updated_at)
select 'slo_' || lower(hex(randomblob(16))), organizations.id, seed.signal_key, seed.label, seed.target_basis_points, seed.window_minutes, datetime('now'), datetime('now')
from organizations
cross join (
select 'provider_request_success' as signal_key, 'Provider requests' as label, 9950 as target_basis_points, 43200 as window_minutes
union all select 'approval_route_coverage', 'Approval routing', 10000, 43200
union all select 'browser_session_boundary', 'Browser session boundary', 10000, 43200
union all select 'skill_resolve_success', 'Skill resolve', 9950, 43200
union all select 'migration_validation_success', 'Migration validation', 10000, 43200
) seed
where not exists (
select 1 from slo_targets
where slo_targets.org_id = organizations.id and slo_targets.signal_key = seed.signal_key
);
insert into chaos_drills(id, org_id, key, label, scenario_key, enabled, created_at, updated_at)
select 'cdr_' || lower(hex(randomblob(16))), organizations.id, seed.key, seed.label, seed.scenario_key, 1, datetime('now'), datetime('now')
from organizations
cross join (
select 'provider-outage-policy' as key, 'Provider outage policy' as label, 'provider_outage_policy_gate' as scenario_key
union all select 'approval-backlog-routing', 'Approval backlog routing', 'approval_backlog_gate'
union all select 'browser-session-boundary', 'Browser session boundary', 'browser_session_boundary'
union all select 'migration-rollback-pack', 'Migration rollback pack', 'migration_rollback_pack'
) seed
where not exists (
select 1 from chaos_drills
where chaos_drills.org_id = organizations.id and chaos_drills.key = seed.key
);
insert into schema_migrations(version, name, applied_at)
values (17, 'operations_controls', datetime('now'));
`;
@@ -0,0 +1,137 @@
export const sdlcControlsSchema = `
create table if not exists sdlc_review_agents (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
key text not null,
label text not null,
specialty text not null,
enabled integer not null check(enabled in (0, 1)),
created_at text not null,
updated_at text not null,
unique(org_id, key)
);
create table if not exists sdlc_review_runs (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
review_agent_id text not null references sdlc_review_agents(id) on delete cascade,
target_type text not null,
target_ref text not null,
state text not null,
result_json text not null,
created_at text not null,
completed_at text not null
);
create table if not exists eval_suites (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
key text not null,
label text not null,
scope text not null,
created_at text not null,
updated_at text not null,
unique(org_id, key)
);
create table if not exists eval_cases (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
suite_id text not null references eval_suites(id) on delete cascade,
title text not null,
source_type text not null,
source_ref text not null,
expected_signal text not null,
created_at text not null,
unique(suite_id, title)
);
create table if not exists eval_runs (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
suite_id text not null references eval_suites(id) on delete cascade,
state text not null,
passed_count integer not null,
failed_count integer not null,
result_json text not null,
created_at text not null,
completed_at text not null
);
create table if not exists pr_bot_gates (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
key text not null,
label text not null,
gate_type text not null,
enabled integer not null check(enabled in (0, 1)),
created_at text not null,
updated_at text not null,
unique(org_id, key)
);
create index if not exists idx_sdlc_review_runs_org_agent_created
on sdlc_review_runs(org_id, review_agent_id, created_at);
create index if not exists idx_eval_runs_org_suite_created
on eval_runs(org_id, suite_id, created_at);
insert into sdlc_review_agents(id, org_id, key, label, specialty, enabled, created_at, updated_at)
select 'sra_' || lower(hex(randomblob(16))), organizations.id, seed.key, seed.label, seed.specialty, 1, datetime('now'), datetime('now')
from organizations
cross join (
select 'code_reviewer' as key, 'Code Review Agent' as label, 'static quality and architecture' as specialty
union all select 'migration_reviewer', 'Migration Review Agent', 'schema diff and rollback evidence'
union all select 'policy_reviewer', 'Policy Review Agent', 'provider, approval, and access policy'
) seed
where not exists (
select 1 from sdlc_review_agents
where sdlc_review_agents.org_id = organizations.id and sdlc_review_agents.key = seed.key
);
insert into eval_suites(id, org_id, key, label, scope, created_at, updated_at)
select 'evs_' || lower(hex(randomblob(16))), organizations.id, seed.key, seed.label, seed.scope, datetime('now'), datetime('now')
from organizations
cross join (
select 'contract' as key, 'Contract checks' as label, 'provider adapters, event schemas, slash commands' as scope
union all select 'integration', 'Integration checks', 'chat, approval, file, skill resolution flows'
union all select 'replay', 'Replay checks', 'audit timeline and historical decision regression'
) seed
where not exists (
select 1 from eval_suites
where eval_suites.org_id = organizations.id and eval_suites.key = seed.key
);
insert into eval_cases(id, org_id, suite_id, title, source_type, source_ref, expected_signal, created_at)
select 'evc_' || lower(hex(randomblob(16))), eval_suites.org_id, eval_suites.id, seed.title, seed.source_type, seed.source_ref, seed.expected_signal, datetime('now')
from eval_suites
join (
select 'contract' as suite_key, 'Provider catalog has adapters' as title, 'provider_gateway' as source_type, 'providers' as source_ref, 'adapter_coverage' as expected_signal
union all select 'contract', 'Schema ledger is current', 'database', 'schema_migrations', 'migration_ledger'
union all select 'integration', 'Approval routes are assigned', 'org_governance', 'approval_assignments', 'route_coverage'
union all select 'integration', 'Skills can resolve runtime snapshots', 'skills_platform', 'skill_runtime_snapshots', 'skill_resolution'
union all select 'replay', 'Audit timeline has domain events', 'audit', 'audit_events', 'replayable_timeline'
union all select 'replay', 'Operational signals are captured', 'operations', 'operational_events', 'slo_signal'
) seed on seed.suite_key = eval_suites.key
where not exists (
select 1 from eval_cases
where eval_cases.suite_id = eval_suites.id and eval_cases.title = seed.title
);
insert into pr_bot_gates(id, org_id, key, label, gate_type, enabled, created_at, updated_at)
select 'pbg_' || lower(hex(randomblob(16))), organizations.id, seed.key, seed.label, seed.gate_type, 1, datetime('now'), datetime('now')
from organizations
cross join (
select 'static_checks' as key, 'Static checks' as label, 'typecheck_lint_test' as gate_type
union all select 'migration_diff', 'Migration diff' as label, 'schema_review' as gate_type
union all select 'policy_diff', 'Policy diff' as label, 'policy_review' as gate_type
union all select 'human_approval', 'Human approval' as label, 'approval_required' as gate_type
) seed
where not exists (
select 1 from pr_bot_gates
where pr_bot_gates.org_id = organizations.id and pr_bot_gates.key = seed.key
);
insert into schema_migrations(version, name, applied_at)
values (18, 'sdlc_controls', datetime('now'));
`;
@@ -0,0 +1,18 @@
export const auditReplaySchema = `
create table if not exists audit_replay_runs (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
thread_id text not null references chat_threads(id) on delete cascade,
event_count integer not null,
event_digest text not null,
replay_json text not null,
created_by_employee_id text references employees(id) on delete set null,
created_at text not null
);
create index if not exists idx_audit_replay_runs_org_created on audit_replay_runs(org_id, created_at);
create index if not exists idx_audit_replay_runs_thread on audit_replay_runs(thread_id, created_at);
insert into schema_migrations(version, name, applied_at)
values (19, 'audit_replay', datetime('now'));
`;
@@ -0,0 +1,25 @@
export const rollbackSnapshotsSchema = `
create table if not exists rollback_snapshots (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
migration_report_id text not null references migration_reports(id) on delete cascade,
source_database_path text not null,
source_fingerprint text not null,
source_database_sha256 text not null,
workspace_manifest_json text not null,
workspace_manifest_sha256 text not null,
delta_journal_json text not null,
restore_plan_json text not null,
active integer not null,
created_by_employee_id text references employees(id) on delete set null,
created_at text not null,
activated_at text
);
create index if not exists idx_rollback_snapshots_org_created on rollback_snapshots(org_id, created_at);
create index if not exists idx_rollback_snapshots_report on rollback_snapshots(migration_report_id);
create unique index if not exists idx_rollback_snapshots_one_active on rollback_snapshots(org_id) where active = 1;
insert into schema_migrations(version, name, applied_at)
values (20, 'rollback_snapshots', datetime('now'));
`;
@@ -0,0 +1,59 @@
export const providerAuthBrokerSchema = `
create table if not exists provider_auth_profiles (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
provider_id text not null references providers(id) on delete cascade,
credential_id text not null references credentials(id) on delete restrict,
provider_key text not null,
auth_type text not null,
profile_kind text not null,
display_hint text not null,
source_type text not null,
source_ref text,
capability_json text not null,
active integer not null check(active in (0, 1)),
created_at text not null
);
create index if not exists idx_provider_auth_profiles_org_provider
on provider_auth_profiles(org_id, provider_id, created_at);
create index if not exists idx_provider_auth_profiles_credential
on provider_auth_profiles(credential_id);
create unique index if not exists idx_provider_auth_profiles_active
on provider_auth_profiles(provider_id) where active = 1;
insert into provider_auth_profiles(
id, org_id, provider_id, credential_id, provider_key, auth_type,
profile_kind, display_hint, source_type, source_ref, capability_json,
active, created_at
)
select
'authp_' || lower(hex(randomblob(16))),
providers.org_id,
providers.id,
credentials.id,
providers.provider_key,
providers.auth_type,
providers.auth_type,
credentials.display_hint,
'migrated_credential',
null,
'{"providerKey":"' || providers.provider_key || '","authType":"' || providers.auth_type || '","capabilityTags":[]}',
1,
credentials.created_at
from providers
join credentials
on credentials.id = providers.credential_id
and credentials.org_id = providers.org_id
and credentials.provider_key = providers.provider_key
where providers.credential_id is not null
and not exists (
select 1
from provider_auth_profiles existing_profiles
where existing_profiles.provider_id = providers.id
and existing_profiles.active = 1
);
insert into schema_migrations(version, name, applied_at)
values (21, 'provider_auth_broker', datetime('now'));
`;
@@ -0,0 +1,21 @@
export const providerRateLimitObservationSchema = `
create table if not exists provider_rate_limit_observations (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
provider_id text not null references providers(id) on delete cascade,
provider_key text not null,
model text,
request_limit integer,
remaining_requests integer,
reset_at text,
retry_after_ms integer,
raw_state_json text not null,
observed_at text not null
);
create index if not exists idx_provider_rate_limit_observations_org_provider
on provider_rate_limit_observations(org_id, provider_id, observed_at desc);
insert into schema_migrations(version, name, applied_at)
values (22, 'provider_rate_limit_observations', datetime('now'));
`;
@@ -0,0 +1,12 @@
export const providerRequestRuntimeSchema = `
alter table provider_requests add column external_response_id text;
alter table provider_requests add column stream_event_count integer not null default 0;
alter table provider_requests add column cancelled_at text;
create index if not exists idx_provider_requests_external_response
on provider_requests(provider_id, external_response_id)
where external_response_id is not null;
insert into schema_migrations(version, name, applied_at)
values (23, 'provider_request_runtime', datetime('now'));
`;
@@ -0,0 +1,10 @@
export const taskRunProviderRequestSchema = `
alter table task_runs add column provider_request_id text references provider_requests(id) on delete set null;
create index if not exists idx_task_runs_provider_request
on task_runs(provider_request_id)
where provider_request_id is not null;
insert into schema_migrations(version, name, applied_at)
values (24, 'task_run_provider_request', datetime('now'));
`;
@@ -0,0 +1,10 @@
export const chatMessageDeliverySchema = `
alter table chat_messages add column body_structured_json text not null default '{}';
alter table chat_messages add column delivery_state text not null default 'sent';
create index if not exists idx_chat_messages_delivery_state
on chat_messages(org_id, delivery_state, created_at);
insert into schema_migrations(version, name, applied_at)
values (25, 'chat_message_delivery', datetime('now'));
`;
@@ -0,0 +1,127 @@
export const browserPlatformGovernanceSchema = `
create table if not exists browser_session_containers (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
social_account_id text not null references social_accounts(id) on delete cascade,
platform text not null,
partition_key text not null,
host_allowlist_json text not null,
permission_policy text not null,
sandbox_enabled integer not null,
created_at text not null,
last_used_at text,
unique(social_account_id)
);
create table if not exists browser_action_policies (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
social_account_id text not null references social_accounts(id) on delete cascade,
action_type text not null,
content_category text not null,
risk_level text not null,
approval_required integer not null,
approval_kind text,
created_at text not null,
unique(social_account_id, action_type, content_category)
);
create table if not exists social_approval_routes (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
social_account_id text not null references social_accounts(id) on delete cascade,
action_type text not null,
risk_level text not null,
approval_kind text not null,
route_to_type text not null,
route_to_id text,
created_at text not null,
unique(social_account_id, action_type, risk_level)
);
insert into browser_session_containers(
id, org_id, social_account_id, platform, partition_key, host_allowlist_json,
permission_policy, sandbox_enabled, created_at, last_used_at
)
select
'bsc_' || lower(hex(randomblob(16))),
org_id,
id,
platform,
'persist:agentcompany-social-' || id,
case platform
when 'x' then '["x.com","twitter.com"]'
when 'linkedin' then '["linkedin.com","www.linkedin.com"]'
when 'youtube' then '["accounts.google.com","youtube.com","www.youtube.com"]'
when 'instagram' then '["instagram.com","www.instagram.com"]'
else '[]'
end,
'deny_all',
1,
datetime('now'),
null
from social_accounts
where not exists (
select 1 from browser_session_containers
where browser_session_containers.social_account_id = social_accounts.id
);
insert into browser_action_policies(
id, org_id, social_account_id, action_type, content_category, risk_level,
approval_required, approval_kind, created_at
)
select
'bap_' || lower(hex(randomblob(16))),
social_accounts.org_id,
social_accounts.id,
policy.action_type,
policy.content_category,
policy.risk_level,
policy.approval_required,
policy.approval_kind,
datetime('now')
from social_accounts
join (
select 'login' action_type, 'auth' content_category, 'low' risk_level, 0 approval_required, null approval_kind
union all select 'draft', 'content', 'low', 0, null
union all select 'approve', 'content', 'medium', 1, 'social_publish'
) policy
where not exists (
select 1 from browser_action_policies
where browser_action_policies.social_account_id = social_accounts.id
and browser_action_policies.action_type = policy.action_type
and browser_action_policies.content_category = policy.content_category
);
insert into social_approval_routes(
id, org_id, social_account_id, action_type, risk_level, approval_kind,
route_to_type, route_to_id, created_at
)
select
'sar_' || lower(hex(randomblob(16))),
org_id,
id,
'approve',
'medium',
'social_publish',
'authorized_employee',
authorized_employee_id,
datetime('now')
from social_accounts
where not exists (
select 1 from social_approval_routes
where social_approval_routes.social_account_id = social_accounts.id
and social_approval_routes.action_type = 'approve'
and social_approval_routes.risk_level = 'medium'
);
create index if not exists idx_browser_session_containers_org
on browser_session_containers(org_id, social_account_id);
create index if not exists idx_browser_action_policies_org_account
on browser_action_policies(org_id, social_account_id, action_type);
create index if not exists idx_social_approval_routes_org_account
on social_approval_routes(org_id, social_account_id, action_type);
insert into schema_migrations(version, name, applied_at)
values (26, 'browser_platform_governance', datetime('now'));
`;
@@ -0,0 +1,62 @@
export const socialAuditRecordsSchema = `
create table if not exists social_audit_records (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
social_account_id text references social_accounts(id) on delete set null,
source_type text not null,
source_id text,
event_type text not null,
actor_type text not null,
actor_id text,
policy_decision text not null,
evidence_file_id text references file_objects(id) on delete set null,
metadata_json text not null,
created_at text not null,
unique(source_type, source_id, event_type)
);
insert into social_audit_records(
id, org_id, social_account_id, source_type, source_id, event_type,
actor_type, actor_id, policy_decision, evidence_file_id, metadata_json, created_at
)
select
'saud_' || lower(hex(randomblob(16))),
org_id,
social_account_id,
'browser_action',
id,
action_type,
'system',
null,
policy_decision,
evidence_file_id,
json_object('source', 'browser_actions'),
created_at
from browser_actions
where not exists (
select 1 from social_audit_records
where social_audit_records.source_type = 'browser_action'
and social_audit_records.source_id = browser_actions.id
and social_audit_records.event_type = browser_actions.action_type
);
create index if not exists idx_social_audit_records_org_created
on social_audit_records(org_id, created_at);
create index if not exists idx_social_audit_records_account_created
on social_audit_records(social_account_id, created_at);
create trigger if not exists trg_social_audit_records_no_update
before update on social_audit_records
begin
select raise(abort, 'Social audit records are immutable.');
end;
create trigger if not exists trg_social_audit_records_no_delete
before delete on social_audit_records
begin
select raise(abort, 'Social audit records are immutable.');
end;
insert into schema_migrations(version, name, applied_at)
values (27, 'social_audit_records', datetime('now'));
`;
@@ -0,0 +1,62 @@
export const migrationCutoverJournalSchema = `
create table if not exists migration_cutover_events (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
migration_report_id text not null references migration_reports(id) on delete cascade,
rollback_snapshot_id text references rollback_snapshots(id) on delete set null,
event_type text not null,
actor_type text not null,
actor_id text,
payload_json text not null,
created_at text not null
);
create table if not exists migration_reconciliation_items (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
migration_report_id text not null references migration_reports(id) on delete cascade,
rollback_snapshot_id text references rollback_snapshots(id) on delete set null,
source_event_id text not null,
target_type text not null,
target_id text not null,
reason text not null,
state text not null,
payload_json text not null,
created_at text not null,
resolved_at text,
resolved_by_employee_id text references employees(id) on delete set null,
unique(rollback_snapshot_id, source_event_id)
);
insert into migration_cutover_events(
id, org_id, migration_report_id, rollback_snapshot_id, event_type,
actor_type, actor_id, payload_json, created_at
)
select
'mce_' || lower(hex(randomblob(16))),
org_id,
migration_report_id,
id,
case active when 1 then 'rollback_snapshot_activated' else 'rollback_snapshot_created' end,
'system',
null,
json_object('sourceFingerprint', source_fingerprint, 'sourceDatabaseSha256', source_database_sha256),
created_at
from rollback_snapshots
where not exists (
select 1 from migration_cutover_events
where migration_cutover_events.rollback_snapshot_id = rollback_snapshots.id
);
create index if not exists idx_migration_cutover_events_org_created
on migration_cutover_events(org_id, created_at);
create index if not exists idx_migration_cutover_events_report
on migration_cutover_events(migration_report_id, created_at);
create index if not exists idx_migration_reconciliation_items_org_state
on migration_reconciliation_items(org_id, state, created_at);
create index if not exists idx_migration_reconciliation_items_report
on migration_reconciliation_items(migration_report_id, created_at);
insert into schema_migrations(version, name, applied_at)
values (28, 'migration_cutover_journal', datetime('now'));
`;
@@ -0,0 +1,65 @@
export const fileHandoffRecordsSchema = `
create table if not exists file_handoff_records (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
message_id text not null references chat_messages(id) on delete cascade,
file_id text not null references file_objects(id) on delete cascade,
source_principal_type text not null,
source_principal_id text,
target_principal_type text not null,
target_principal_id text not null,
transfer_type text not null,
checksum text not null,
scan_state text not null,
scan_labels_json text not null,
policy_decision text not null,
object_ref text not null,
created_at text not null,
unique(message_id, file_id, target_principal_type, target_principal_id)
);
insert into file_handoff_records(
id, org_id, message_id, file_id, source_principal_type, source_principal_id,
target_principal_type, target_principal_id, transfer_type, checksum, scan_state,
scan_labels_json, policy_decision, object_ref, created_at
)
select
'fhr_' || lower(hex(randomblob(16))),
messages.org_id,
attachments.message_id,
attachments.file_id,
messages.sender_type,
messages.sender_id,
coalesce(grants.granted_to_type, 'org_group'),
coalesce(grants.granted_to_id, messages.org_id || ':workspace'),
attachments.transfer_type,
attachments.checksum,
files.scan_state,
files.scan_labels_json,
case
when files.scan_state = 'approved' then 'approved_for_handoff'
else 'blocked_by_scan'
end,
files.storage_path,
messages.created_at
from chat_attachments attachments
join chat_messages messages on messages.id = attachments.message_id
join file_objects files on files.id = attachments.file_id
left join file_access_grants grants
on grants.file_id = attachments.file_id
and grants.source_message_id = attachments.message_id
where not exists (
select 1 from file_handoff_records records
where records.message_id = attachments.message_id
and records.file_id = attachments.file_id
and records.target_principal_type = coalesce(grants.granted_to_type, 'org_group')
and records.target_principal_id = coalesce(grants.granted_to_id, messages.org_id || ':workspace')
);
create index if not exists idx_file_handoff_org_created on file_handoff_records(org_id, created_at);
create index if not exists idx_file_handoff_file on file_handoff_records(org_id, file_id);
create index if not exists idx_file_handoff_target on file_handoff_records(org_id, target_principal_type, target_principal_id);
insert into schema_migrations(version, name, applied_at)
values (29, 'file_handoff_records', datetime('now'));
`;
@@ -0,0 +1,49 @@
export const approvalSourceRefsSchema = `
alter table approvals add column source_entity_type text;
alter table approvals add column source_entity_id text;
update approvals
set
source_entity_type = 'task',
source_entity_id = (
select tasks.id
from tasks
where tasks.org_id = approvals.org_id
and tasks.title = approvals.title
and tasks.description = approvals.details
order by tasks.created_at desc
limit 1
)
where kind = 'task_risk'
and source_entity_id is null
and exists (
select 1
from tasks
where tasks.org_id = approvals.org_id
and tasks.title = approvals.title
and tasks.description = approvals.details
);
update approvals
set
source_entity_type = 'scheduled_post',
source_entity_id = (
select scheduled_posts.id
from scheduled_posts
where scheduled_posts.approval_id = approvals.id
limit 1
)
where kind = 'social_publish'
and source_entity_id is null
and exists (
select 1
from scheduled_posts
where scheduled_posts.approval_id = approvals.id
);
create index if not exists idx_approvals_source
on approvals(org_id, source_entity_type, source_entity_id, kind, state);
insert into schema_migrations(version, name, applied_at)
values (30, 'approval_source_refs', datetime('now'));
`;
@@ -0,0 +1,10 @@
export const providerRequestWorkspaceSchema = `
alter table provider_requests add column workspace_id text references workspaces(id) on delete set null;
create index if not exists idx_provider_requests_workspace
on provider_requests(org_id, workspace_id, created_at)
where workspace_id is not null;
insert into schema_migrations(version, name, applied_at)
values (31, 'provider_request_workspace', datetime('now'));
`;
@@ -0,0 +1,6 @@
export const browserActionMetadataSchema = `
alter table browser_actions add column metadata_json text not null default '{}';
insert into schema_migrations(version, name, applied_at)
values (32, 'browser_action_metadata', datetime('now'));
`;
@@ -0,0 +1,16 @@
export const auditEventImmutabilitySchema = `
create trigger if not exists trg_audit_events_no_update
before update on audit_events
begin
select raise(abort, 'Audit events are immutable.');
end;
create trigger if not exists trg_audit_events_no_delete
before delete on audit_events
begin
select raise(abort, 'Audit events are immutable.');
end;
insert into schema_migrations(version, name, applied_at)
values (33, 'audit_event_immutability', datetime('now'));
`;
@@ -0,0 +1,27 @@
export const providerAuthSessionsSchema = `
create table if not exists provider_auth_sessions (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
provider_id text not null references providers(id) on delete cascade,
provider_key text not null,
employee_id text not null references employees(id) on delete cascade,
state text not null,
nonce text not null,
code_verifier_sha256 text not null,
redirect_uri text not null,
sign_in_url text not null,
partition text not null,
created_at text not null,
expires_at text not null,
completed_at text,
unique(provider_id, state)
);
create index if not exists idx_provider_auth_sessions_org_provider
on provider_auth_sessions(org_id, provider_id, created_at);
create index if not exists idx_provider_auth_sessions_employee
on provider_auth_sessions(org_id, employee_id, created_at);
insert into schema_migrations(version, name, applied_at)
values (34, 'provider_auth_sessions', datetime('now'));
`;
@@ -0,0 +1,16 @@
export const taskRunIsolationSchema = `
alter table task_runs add column execution_context_id text;
alter table task_runs add column service_identity_id text references service_identities(id) on delete set null;
alter table task_runs add column workspace_mount_json text not null default '[]';
alter table task_runs add column secret_scope_json text not null default '{}';
create unique index if not exists idx_task_runs_execution_context
on task_runs(execution_context_id)
where execution_context_id is not null;
create index if not exists idx_task_runs_service_identity
on task_runs(service_identity_id)
where service_identity_id is not null;
insert into schema_migrations(version, name, applied_at)
values (35, 'task_run_isolation', datetime('now'));
`;
@@ -0,0 +1,20 @@
export const additionalConnectorsSchema = `
insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at)
select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'claude-pro', 'Claude Pro/Max', 'oauth_pkce', 'claude-sonnet-4-5', null, 0, datetime('now')
from organizations;
insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at)
select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'mistral', 'Mistral', 'api_key', 'mistral-large-latest', null, 0, datetime('now')
from organizations;
insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at)
select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'cerebras', 'Cerebras', 'api_key', 'qwen-3-235b-a22b-instruct-2507', null, 0, datetime('now')
from organizations;
insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at)
select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'openrouter', 'OpenRouter', 'api_key', 'anthropic/claude-sonnet-4.5', null, 0, datetime('now')
from organizations;
insert into schema_migrations(version, name, applied_at)
values (36, 'additional_connectors', datetime('now'));
`;
@@ -0,0 +1,12 @@
export const customProvidersSchema = `
insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at)
select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'custom-openai', 'Custom OpenAI-compatible', 'cloud', null, null, 0, datetime('now')
from organizations;
insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at)
select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'custom-anthropic', 'Custom Anthropic-compatible', 'cloud', null, null, 0, datetime('now')
from organizations;
insert into schema_migrations(version, name, applied_at)
values (37, 'custom_providers', datetime('now'));
`;
@@ -0,0 +1,46 @@
// Deleting an organization cascades into its `credentials`, but
// `provider_auth_profiles.credential_id` referenced `credentials(id)` with
// `on delete restrict`, which blocked the cascade and made the whole DELETE
// throw — so deleting an organization silently failed. This rebuilds the
// (leaf) table with `on delete cascade`, preserving every existing row and
// index. Revocation only deactivates a profile (active = 0) and never deletes
// a credential row, so cascade has no effect on normal operation.
export const providerAuthProfileCascadeSchema = `
create table provider_auth_profiles_rebuild (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
provider_id text not null references providers(id) on delete cascade,
credential_id text not null references credentials(id) on delete cascade,
provider_key text not null,
auth_type text not null,
profile_kind text not null,
display_hint text not null,
source_type text not null,
source_ref text,
capability_json text not null,
active integer not null check(active in (0, 1)),
created_at text not null
);
insert into provider_auth_profiles_rebuild(
id, org_id, provider_id, credential_id, provider_key, auth_type,
profile_kind, display_hint, source_type, source_ref, capability_json, active, created_at
)
select
id, org_id, provider_id, credential_id, provider_key, auth_type,
profile_kind, display_hint, source_type, source_ref, capability_json, active, created_at
from provider_auth_profiles;
drop table provider_auth_profiles;
alter table provider_auth_profiles_rebuild rename to provider_auth_profiles;
create index if not exists idx_provider_auth_profiles_org_provider
on provider_auth_profiles(org_id, provider_id, created_at);
create index if not exists idx_provider_auth_profiles_credential
on provider_auth_profiles(credential_id);
create unique index if not exists idx_provider_auth_profiles_active
on provider_auth_profiles(provider_id) where active = 1;
insert into schema_migrations(version, name, applied_at)
values (38, 'provider_auth_profile_cascade', datetime('now'));
`;
@@ -0,0 +1,28 @@
// The audit_events and social_audit_records immutability triggers raised ABORT
// on EVERY delete, including the legitimate cascade when an organization is torn
// down — so deleting an organization always rolled back. These rebuilt triggers
// still block tampering deletes of individual records (the owning org exists),
// but allow the cascade once the organization row itself is gone. Direct
// deletes of audit rows whose org still exists remain forbidden.
export const auditImmutabilityOrgTeardownSchema = `
drop trigger if exists trg_audit_events_no_delete;
create trigger trg_audit_events_no_delete
before delete on audit_events
for each row
when exists (select 1 from organizations where id = old.org_id)
begin
select raise(abort, 'Audit events are immutable.');
end;
drop trigger if exists trg_social_audit_records_no_delete;
create trigger trg_social_audit_records_no_delete
before delete on social_audit_records
for each row
when exists (select 1 from organizations where id = old.org_id)
begin
select raise(abort, 'Social audit records are immutable.');
end;
insert into schema_migrations(version, name, applied_at)
values (39, 'audit_immutability_org_teardown', datetime('now'));
`;
@@ -0,0 +1,52 @@
export const documentsKnowledgeAutomationSchema = `
create table if not exists documents (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
title text not null,
doc_type text not null,
content text not null,
status text not null,
author_agent_id text references agents(id) on delete set null,
reviewer_agent_id text references agents(id) on delete set null,
project_id text references projects(id) on delete set null,
goal_id text references goals(id) on delete set null,
task_id text references tasks(id) on delete set null,
created_at text not null,
updated_at text not null
);
create index if not exists idx_documents_org on documents(org_id, doc_type);
create table if not exists knowledge_entries (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
topic text not null,
category text not null,
content text not null,
importance text not null,
source_type text not null,
source_id text,
created_at text not null,
updated_at text not null
);
create index if not exists idx_knowledge_org on knowledge_entries(org_id, category);
create table if not exists automation_rules (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
name text not null,
trigger_event text not null,
condition_json text not null,
action_type text not null,
action_config_json text not null,
priority integer not null,
enabled integer not null,
last_fired_at text,
fire_count integer not null default 0,
created_at text not null,
updated_at text not null
);
create index if not exists idx_automation_org on automation_rules(org_id, enabled);
`;
@@ -0,0 +1,3 @@
export const taskSubtasksDeleteSchema = `
alter table tasks add column parent_task_id text references tasks(id) on delete set null;
`;
@@ -0,0 +1,37 @@
export const approvalNotesMeetingsSprintsSchema = `
alter table approvals add column decision_notes text;
create table if not exists meetings (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
title text not null,
meeting_type text not null,
status text not null,
scheduled_at text,
duration_minutes integer not null,
agenda text not null,
notes text not null,
decisions text not null,
created_at text not null,
updated_at text not null
);
create index if not exists idx_meetings_org on meetings(org_id, scheduled_at);
create table if not exists sprints (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
name text not null,
goal text not null,
status text not null,
start_date text not null,
end_date text not null,
velocity_points integer not null default 0,
completed_points integer not null default 0,
retrospective text not null,
created_at text not null,
updated_at text not null
);
create index if not exists idx_sprints_org on sprints(org_id, status);
`;
@@ -0,0 +1,9 @@
export const composioIntegrationSchema = `
create table if not exists composio_settings (
org_id text not null references organizations(id) on delete cascade,
key text not null,
value text not null,
updated_at text not null,
primary key (org_id, key)
);
`;
@@ -0,0 +1,3 @@
export const mentionWakeStateSchema = `
alter table chat_mentions add column wake_state text not null default 'none';
`;
@@ -0,0 +1,26 @@
export const connectorGrantsSchema = `
create table if not exists connector_accounts (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
toolkit_slug text not null,
toolkit_name text not null,
auth_config_id text not null,
status text not null,
created_at text not null,
updated_at text not null,
unique (org_id, toolkit_slug)
);
create table if not exists agent_connector_grants (
id text primary key,
org_id text not null references organizations(id) on delete cascade,
agent_id text not null references agents(id) on delete cascade,
toolkit_slug text not null,
granted_by_type text not null,
granted_by_id text,
created_at text not null,
unique (org_id, agent_id, toolkit_slug)
);
create index if not exists idx_connector_grants_agent on agent_connector_grants(org_id, agent_id);
`;
@@ -1,263 +0,0 @@
import type { DatabaseSync } from "node:sqlite";
export const BASELINE_SCHEMA_VERSION = 56;
export const BASELINE_SCHEMA_NAME = "baseline_v56";
export const BASELINE_TABLE_SQL = [
"CREATE TABLE activity (id text primary key, company_id text not null, actor text not null, action text not null, entity_type text not null, entity_id text not null, detail text not null, created_at text not null)",
"CREATE TABLE agent_config_revisions (id TEXT PRIMARY KEY, company_id TEXT NOT NULL, agent_id TEXT NOT NULL, revision_number INTEGER NOT NULL, changed_keys TEXT NOT NULL DEFAULT '[]', config_before TEXT NOT NULL, config_after TEXT NOT NULL, source TEXT NOT NULL DEFAULT 'patch', rolled_back_from_revision_id TEXT, created_by_agent_id TEXT, created_by_user_id TEXT, created_at TEXT NOT NULL, FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE)",
"CREATE TABLE agent_message_mentions (\n message_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n company_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (message_id, agent_id)\n )",
"CREATE TABLE agent_message_projections (\n message_id TEXT NOT NULL,\n session_id TEXT NOT NULL,\n projected_at TEXT NOT NULL,\n PRIMARY KEY (message_id, session_id)\n )",
"CREATE TABLE agent_message_reads (message_id text not null, agent_id text not null, read_at text not null, primary key (message_id, agent_id))",
"CREATE TABLE agent_messages (id text primary key, company_id text not null, from_agent_id text not null, to_agent_id text, channel text not null default 'direct', channel_target_id text, subject text not null, body text not null, priority text not null default 'normal', parent_message_id text, attachments_json text not null default '[]', created_at text not null, sender_kind TEXT NOT NULL DEFAULT 'agent')",
"CREATE TABLE agent_skill_assignments (agent_id TEXT NOT NULL, skill_id TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (agent_id, skill_id), FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE, FOREIGN KEY (skill_id) REFERENCES company_skills(id) ON DELETE CASCADE)",
"CREATE TABLE agents (id text primary key, company_id text not null, name text not null, role text not null, title text not null, status text not null, reports_to text, connector_id text not null, workspace_id text, model text, capabilities text not null, budget_monthly_usd real not null default 0, spent_monthly_usd real not null default 0, metadata_json text not null default '{}', created_at text not null, updated_at text not null, department text, permissions_json TEXT NOT NULL DEFAULT '{}', heartbeat_policy_json TEXT, runtime_state_json TEXT, role_level TEXT NOT NULL DEFAULT 'ic', session_id TEXT, model_strategy TEXT NOT NULL DEFAULT 'explicit', heartbeat_enabled integer not null default 0, heartbeat_interval_sec integer not null default 120, last_heartbeat_at text)",
"CREATE TABLE approval_comments (id TEXT PRIMARY KEY, company_id TEXT NOT NULL, approval_id TEXT NOT NULL, author_agent_id TEXT, author_user_id TEXT, body TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (approval_id) REFERENCES approvals(id) ON DELETE CASCADE)",
"CREATE TABLE approvals (id text primary key, company_id text not null, related_task_id text, requested_by_agent_id text, type text not null, payload_summary text not null, impact_summary text not null, payload_json text, decision_note text not null default '', state text not null, created_at text not null, updated_at text not null, related_agent_id text)",
"CREATE TABLE automation_log (id text primary key, company_id text not null, rule_id text not null, rule_name text not null, trigger text not null, action text not null, detail text not null default '', executed_at text not null)",
"CREATE TABLE automation_rules (id text primary key, company_id text not null, name text not null, description text not null default '', trigger text not null, conditions_json text not null default '{}', action text not null, action_config_json text not null default '{}', source_department text, target_department text, priority integer not null default 50, status text not null default 'active', execution_count integer not null default 0, last_executed_at text, created_at text not null, updated_at text not null)",
"CREATE TABLE browser_actions (id text primary key, company_id text not null references companies(id) on delete cascade, social_account_id text not null references social_accounts(id) on delete cascade, agent_id text not null references agents(id) on delete cascade, task_id text, action_type text not null default 'custom_script', payload_json text not null default '{}', status text not null default 'queued', result_summary text not null default '', screenshot_path text, approval_id text, error_message text, started_at text, finished_at text, created_at text not null default (datetime('now')), updated_at text not null default (datetime('now')))",
"CREATE TABLE budget_incidents (id TEXT PRIMARY KEY, company_id TEXT NOT NULL, policy_id TEXT NOT NULL REFERENCES budget_policies(id), scope_type TEXT NOT NULL, scope_id TEXT NOT NULL, metric TEXT NOT NULL, window_kind TEXT NOT NULL, window_start TEXT NOT NULL, window_end TEXT NOT NULL, threshold_type TEXT NOT NULL, amount_limit INTEGER NOT NULL, amount_observed INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'open', resolved_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)",
"CREATE TABLE budget_policies (id TEXT PRIMARY KEY, company_id TEXT NOT NULL, scope_type TEXT NOT NULL, scope_id TEXT NOT NULL, metric TEXT NOT NULL DEFAULT 'billed_cents', window_kind TEXT NOT NULL DEFAULT 'calendar_month_utc', amount INTEGER NOT NULL DEFAULT 0, warn_percent INTEGER NOT NULL DEFAULT 80, hard_stop_enabled INTEGER NOT NULL DEFAULT 1, notify_enabled INTEGER NOT NULL DEFAULT 1, is_active INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)",
"CREATE TABLE companies (id text primary key, name text not null, description text not null, status text not null, created_at text not null, updated_at text not null, auto_approve_hires integer not null default 0, review_deliverables integer not null default 0, execution_mode text not null default 'ai_ceo')",
"CREATE TABLE \"company_skills\" (id TEXT PRIMARY KEY, company_id TEXT NOT NULL, key TEXT NOT NULL, slug TEXT NOT NULL, name TEXT NOT NULL, description TEXT, source_type TEXT NOT NULL DEFAULT 'local_path', source_locator TEXT, source_ref TEXT, trust_level TEXT NOT NULL DEFAULT 'markdown_only', compatibility TEXT NOT NULL DEFAULT 'compatible', file_inventory TEXT NOT NULL DEFAULT '[]', metadata TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)",
"CREATE TABLE connector_model_pool_settings (\n company_id TEXT NOT NULL,\n connector_id TEXT NOT NULL,\n disabled_models_json TEXT NOT NULL DEFAULT '[]',\n updated_at TEXT NOT NULL,\n PRIMARY KEY (company_id, connector_id),\n FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE\n )",
"CREATE TABLE connectors (id text primary key, label text not null, description text not null, status text not null, command text not null, version text, auth_state text not null, last_checked_at text, last_error text, model text, env_binding_text text not null, notes text not null, updated_at text, capability_json text)",
"CREATE TABLE conversation_events (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL REFERENCES conversation_threads(id) ON DELETE CASCADE,\n actor_id TEXT NOT NULL,\n actor_type TEXT NOT NULL,\n event_type TEXT NOT NULL,\n body TEXT NOT NULL,\n metadata_json TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL\n )",
"CREATE TABLE conversation_participants (\n thread_id TEXT NOT NULL REFERENCES conversation_threads(id) ON DELETE CASCADE,\n participant_id TEXT NOT NULL,\n participant_type TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'member',\n joined_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, participant_id)\n )",
"CREATE TABLE conversation_threads (\n id TEXT PRIMARY KEY,\n company_id TEXT NOT NULL,\n title TEXT NOT NULL,\n type TEXT NOT NULL DEFAULT 'task',\n related_entity_type TEXT,\n related_entity_id TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )",
"CREATE TABLE cost_entries (id text primary key, company_id text not null, run_id text not null, task_id text, agent_id text, connector_id text not null, amount_usd real, unattributed integer not null default 0, created_at text not null, project_id TEXT)",
"CREATE TABLE credentials (\n connector_id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n backend TEXT NOT NULL,\n ciphertext BLOB NOT NULL,\n display_hint TEXT NOT NULL DEFAULT '',\n status TEXT NOT NULL DEFAULT 'active',\n expires_at TEXT,\n last_refreshed_at TEXT,\n last_error TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )",
"CREATE TABLE document_lifecycle_events (\n id TEXT PRIMARY KEY,\n company_id TEXT NOT NULL,\n document_id TEXT NOT NULL,\n hook_type TEXT NOT NULL,\n status TEXT NOT NULL,\n attempts INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n payload_json TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n completed_at TEXT\n )",
"CREATE TABLE document_revisions (id TEXT PRIMARY KEY, company_id TEXT NOT NULL, document_id TEXT NOT NULL, revision_number INTEGER NOT NULL, body TEXT NOT NULL, change_summary TEXT, created_by_agent_id TEXT, created_by_user_id TEXT, created_at TEXT NOT NULL, FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE)",
"CREATE TABLE documents (id text primary key, company_id text not null, type text not null, title text not null, content text not null default '', author_agent_id text, reviewer_agent_id text, project_id text, goal_id text, parent_doc_id text, version integer not null default 1, status text not null default 'draft', tags_json text not null default '[]', created_at text not null, updated_at text not null, latest_revision_id TEXT, source_run_id TEXT)",
"CREATE TABLE goals (id text primary key, company_id text not null, title text not null, description text not null, parent_id text, owner_agent_id text, status text not null, created_at text not null, updated_at text not null)",
"CREATE TABLE knowledge_base (id text primary key, company_id text not null, category text not null, topic text not null, content text not null, author_agent_id text, importance text not null default 'medium', tags_json text not null default '[]', referenced_entity_type text, referenced_entity_id text, created_at text not null, updated_at text not null, source_run_id TEXT)",
"CREATE TABLE meetings (id text primary key, company_id text not null, type text not null, title text not null, organizer_agent_id text, participant_agent_ids text not null default '[]', scheduled_at text not null, duration_minutes integer not null default 30, agenda_json text not null default '[]', notes_json text not null default '[]', decisions_json text not null default '[]', action_items_json text not null default '[]', status text not null default 'scheduled', created_at text not null, updated_at text not null)",
"CREATE TABLE model_capabilities (\n provider TEXT NOT NULL,\n model_id TEXT NOT NULL,\n reasoning INTEGER NOT NULL,\n coding INTEGER NOT NULL,\n writing INTEGER NOT NULL,\n tier TEXT NOT NULL,\n cost_per_mtok_out REAL NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (provider, model_id)\n )",
"CREATE TABLE projects (id text primary key, company_id text not null, goal_id text, name text not null, description text not null, lead_agent_id text, status text not null, target_date text, created_at text not null, updated_at text not null)",
"CREATE TABLE run_events (id INTEGER PRIMARY KEY AUTOINCREMENT, company_id TEXT NOT NULL, run_id TEXT NOT NULL, agent_id TEXT NOT NULL, seq INTEGER NOT NULL, event_type TEXT NOT NULL, stream TEXT, level TEXT, message TEXT, payload TEXT, created_at TEXT NOT NULL, FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE CASCADE)",
"CREATE TABLE \"runs\" (\n id TEXT PRIMARY KEY,\n company_id TEXT NOT NULL,\n task_id TEXT,\n agent_id TEXT NOT NULL,\n workspace_id TEXT,\n connector_id TEXT NOT NULL,\n status TEXT NOT NULL,\n summary TEXT NOT NULL,\n error_message TEXT,\n exit_code INTEGER,\n signal TEXT,\n model TEXT,\n cost_usd REAL,\n unattributed_cost INTEGER NOT NULL DEFAULT 0,\n actor_type TEXT,\n actor_id TEXT,\n started_at TEXT,\n finished_at TEXT,\n log_path TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n , usage_status TEXT NOT NULL DEFAULT 'pre_first_message')",
"CREATE TABLE schema_migrations (version integer primary key, name text not null, applied_at text not null)",
"CREATE TABLE secrets (id text primary key, company_id text not null, name text not null, description text not null, backend text not null, ciphertext blob not null, updated_at text not null, created_at text not null, unique(company_id, name))",
"CREATE TABLE session_entries (\n id TEXT PRIMARY KEY,\n task_session_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n kind TEXT NOT NULL,\n role TEXT,\n payload_json TEXT NOT NULL,\n is_superseded INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL\n )",
"CREATE TABLE settings (key text primary key, value text not null)",
"CREATE TABLE social_accounts (id text primary key, company_id text not null references companies(id) on delete cascade, platform text not null default 'other', account_name text not null, display_name text not null default '', profile_url text not null default '', credential_secret_id text, status text not null default 'login_required', require_approval integer not null default 1, metadata_json text not null default '{}', last_used_at text, created_at text not null default (datetime('now')), updated_at text not null default (datetime('now')))",
"CREATE TABLE sprints (id text primary key, company_id text not null, name text not null, goal text not null default '', start_date text not null, end_date text not null, status text not null default 'planning', retrospective_notes text not null default '', velocity_points integer not null default 0, completed_points integer not null default 0, created_at text not null, updated_at text not null)",
"CREATE TABLE task_approval_links (task_id TEXT NOT NULL, approval_id TEXT NOT NULL, company_id TEXT NOT NULL, linked_by_agent_id TEXT, created_at TEXT NOT NULL, PRIMARY KEY (task_id, approval_id), FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE, FOREIGN KEY (approval_id) REFERENCES approvals(id) ON DELETE CASCADE)",
"CREATE TABLE task_comments (id text primary key, company_id text not null, task_id text not null, author_agent_id text, author_name text not null, body text not null, created_at text not null)",
"CREATE TABLE task_documents (id TEXT PRIMARY KEY, company_id TEXT NOT NULL, task_id TEXT NOT NULL, document_id TEXT NOT NULL, key TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE, FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE)",
"CREATE TABLE \"task_sessions\" (\n id TEXT PRIMARY KEY,\n company_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n adapter_type TEXT NOT NULL,\n last_run_id TEXT,\n last_error TEXT,\n run_count INTEGER NOT NULL DEFAULT 0,\n total_input_tokens INTEGER NOT NULL DEFAULT 0,\n total_output_tokens INTEGER NOT NULL DEFAULT 0,\n rotated_at TEXT,\n rotation_reason TEXT,\n handoff_markdown TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )",
"CREATE TABLE \"tasks\" (\n id TEXT PRIMARY KEY,\n company_id TEXT NOT NULL,\n project_id TEXT,\n goal_id TEXT,\n parent_id TEXT,\n title TEXT NOT NULL,\n description TEXT NOT NULL,\n assignee_agent_id TEXT,\n workspace_id TEXT,\n status TEXT NOT NULL,\n priority TEXT NOT NULL,\n active_run_id TEXT,\n cost_usd REAL NOT NULL DEFAULT 0,\n task_type TEXT NOT NULL DEFAULT 'general',\n requires_user_review INTEGER NOT NULL DEFAULT 0,\n retry_count INTEGER NOT NULL DEFAULT 0,\n last_failure_at TEXT,\n checkout_run_id TEXT,\n execution_locked_at TEXT,\n started_at TEXT,\n completed_at TEXT,\n cancelled_at TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n , requires_filesystem INTEGER NOT NULL DEFAULT 0, active_session_id text)",
"CREATE TABLE thread_subscriptions (\n thread_id TEXT NOT NULL REFERENCES conversation_threads(id) ON DELETE CASCADE,\n subscriber_id TEXT NOT NULL,\n subscriber_type TEXT NOT NULL,\n notify_on TEXT NOT NULL DEFAULT 'all',\n subscribed_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, subscriber_id)\n )",
"CREATE TABLE thread_wait_states (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL REFERENCES conversation_threads(id) ON DELETE CASCADE,\n company_id TEXT NOT NULL,\n waiting_for TEXT NOT NULL,\n related_approval_id TEXT,\n reason TEXT NOT NULL,\n created_at TEXT NOT NULL,\n resolved_at TEXT\n )",
"CREATE TABLE wakeup_requests (id TEXT PRIMARY KEY, company_id TEXT NOT NULL, agent_id TEXT NOT NULL, source TEXT NOT NULL, trigger_detail TEXT, reason TEXT, payload TEXT, status TEXT NOT NULL DEFAULT 'queued', coalesced_count INTEGER NOT NULL DEFAULT 0, requested_by_actor_type TEXT, requested_by_actor_id TEXT, idempotency_key TEXT, run_id TEXT, requested_at TEXT NOT NULL, claimed_at TEXT, finished_at TEXT, error TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, context_snapshot TEXT)",
"CREATE TABLE \"work_products\" (\n id TEXT PRIMARY KEY,\n company_id TEXT NOT NULL,\n project_id TEXT,\n task_id TEXT,\n type TEXT NOT NULL,\n provider TEXT NOT NULL,\n external_id TEXT,\n title TEXT NOT NULL,\n url TEXT,\n status TEXT NOT NULL DEFAULT 'active',\n review_state TEXT NOT NULL DEFAULT 'none',\n is_primary INTEGER NOT NULL DEFAULT 0,\n summary TEXT,\n metadata_json TEXT,\n created_by_run_id TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL, workspace_id TEXT, last_touched_by_run_id TEXT,\n FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE\n )",
"CREATE TABLE workflow_pipelines (id text primary key, company_id text not null, name text not null, description text not null default '', steps_json text not null default '[]', trigger_type text not null, trigger_config_json text not null default '{}', status text not null default 'draft', current_step_index integer not null default 0, run_count integer not null default 0, last_run_at text, created_at text not null, updated_at text not null)",
"CREATE TABLE workspaces (id text primary key, company_id text not null, name text not null, local_path text not null, repo_url text not null, repo_ref text not null, project_id text, state text not null, is_primary integer not null default 0, created_at text not null, updated_at text not null, lock_holder_run_id text, lock_acquired_at text)",
] as const;
export const BASELINE_INDEX_SQL = [
"CREATE INDEX idx_activity_company on activity(company_id)",
"CREATE INDEX idx_activity_company_actor_action ON activity(company_id, actor, action)",
"CREATE INDEX idx_activity_company_created_at on activity(company_id, created_at desc)",
"CREATE INDEX idx_agent_config_rev_agent ON agent_config_revisions(company_id, agent_id, created_at)",
"CREATE UNIQUE INDEX idx_agent_config_rev_num ON agent_config_revisions(agent_id, revision_number)",
"CREATE INDEX idx_agent_message_mentions_agent\n ON agent_message_mentions(company_id, agent_id, created_at)\n ",
"CREATE INDEX idx_agent_message_mentions_message\n ON agent_message_mentions(message_id)\n ",
"CREATE INDEX idx_agent_message_projections_session\n ON agent_message_projections(session_id, projected_at)\n ",
"CREATE INDEX idx_agent_message_reads_agent on agent_message_reads(agent_id)",
"CREATE INDEX idx_agent_messages_channel on agent_messages(channel)",
"CREATE INDEX idx_agent_messages_company on agent_messages(company_id)",
"CREATE INDEX idx_agent_messages_company_created_at on agent_messages(company_id, created_at desc)",
"CREATE INDEX idx_agent_messages_from on agent_messages(from_agent_id)",
"CREATE INDEX idx_agent_messages_to on agent_messages(to_agent_id)",
"CREATE INDEX idx_agent_skill_assignments_skill ON agent_skill_assignments(skill_id)",
"CREATE INDEX idx_agents_company on agents(company_id)",
"CREATE INDEX idx_agents_company_status on agents(company_id, status)",
"CREATE INDEX idx_agents_department on agents(company_id, department)",
"CREATE INDEX idx_agents_reports_to on agents(reports_to)",
"CREATE INDEX idx_agents_session_id ON agents(session_id)",
"CREATE INDEX idx_approval_comments_approval ON approval_comments(approval_id, created_at)",
"CREATE INDEX idx_approvals_company on approvals(company_id)",
"CREATE INDEX idx_approvals_company_created_at ON approvals(company_id, created_at DESC)",
"CREATE INDEX idx_approvals_company_state on approvals(company_id, state)",
"CREATE INDEX idx_approvals_company_state_type ON approvals(company_id, state, type)",
"CREATE INDEX idx_approvals_related_task ON approvals(related_task_id)",
"CREATE INDEX idx_automation_log_company on automation_log(company_id)",
"CREATE INDEX idx_automation_rules_company on automation_rules(company_id)",
"CREATE INDEX idx_automation_rules_company_status on automation_rules(company_id, status)",
"CREATE INDEX idx_automation_rules_trigger on automation_rules(trigger)",
"CREATE INDEX idx_browser_actions_account on browser_actions(social_account_id)",
"CREATE INDEX idx_browser_actions_company on browser_actions(company_id)",
"CREATE INDEX idx_budget_incidents_company_scope ON budget_incidents(company_id, scope_type, scope_id, status)",
"CREATE INDEX idx_budget_incidents_company_status ON budget_incidents(company_id, status)",
"CREATE INDEX idx_budget_incidents_policy ON budget_incidents(policy_id, window_start, threshold_type)",
"CREATE INDEX idx_budget_policies_company_scope ON budget_policies(company_id, scope_type, scope_id, is_active)",
"CREATE UNIQUE INDEX idx_budget_policies_unique ON budget_policies(company_id, scope_type, scope_id, metric, window_kind)",
"CREATE INDEX idx_comments_company on task_comments(company_id)",
"CREATE INDEX idx_comments_task on task_comments(task_id)",
"CREATE UNIQUE INDEX idx_company_skills_company_key ON company_skills(company_id, key)",
"CREATE INDEX idx_company_skills_company_name ON company_skills(company_id, name)",
"CREATE INDEX idx_connector_model_pool_company\n ON connector_model_pool_settings(company_id)",
"CREATE INDEX idx_conv_events_thread ON conversation_events(thread_id, created_at)",
"CREATE INDEX idx_conv_participants_participant ON conversation_participants(participant_id)",
"CREATE INDEX idx_conv_threads_company ON conversation_threads(company_id)",
"CREATE INDEX idx_conv_threads_entity ON conversation_threads(related_entity_type, related_entity_id)",
"CREATE INDEX idx_cost_entries_agent_created_at ON cost_entries(agent_id, created_at DESC)",
"CREATE INDEX idx_cost_entries_company on cost_entries(company_id)",
"CREATE INDEX idx_cost_entries_company_created_at on cost_entries(company_id, created_at desc)",
"CREATE INDEX idx_cost_entries_project ON cost_entries(project_id)",
"CREATE INDEX idx_cost_entries_run on cost_entries(run_id)",
"CREATE INDEX idx_credentials_status ON credentials(status)",
"CREATE INDEX idx_doc_rev_company ON document_revisions(company_id, document_id, created_at)",
"CREATE UNIQUE INDEX idx_doc_rev_doc_num ON document_revisions(document_id, revision_number)",
"CREATE INDEX idx_document_lifecycle_events_company\n ON document_lifecycle_events(company_id, status, updated_at)\n ",
"CREATE INDEX idx_document_lifecycle_events_document\n ON document_lifecycle_events(document_id, hook_type)\n ",
"CREATE INDEX idx_documents_author on documents(author_agent_id)",
"CREATE INDEX idx_documents_company on documents(company_id)",
"CREATE INDEX idx_documents_company_updated_at ON documents(company_id, updated_at DESC)",
"CREATE INDEX idx_documents_project on documents(project_id)",
"CREATE INDEX idx_goals_company on goals(company_id)",
"CREATE INDEX idx_knowledge_category on knowledge_base(category)",
"CREATE INDEX idx_knowledge_company on knowledge_base(company_id)",
"CREATE INDEX idx_meetings_company on meetings(company_id)",
"CREATE INDEX idx_meetings_company_scheduled_at ON meetings(company_id, scheduled_at DESC)",
"CREATE INDEX idx_meetings_organizer on meetings(organizer_agent_id)",
"CREATE INDEX idx_messages_channel on agent_messages(channel, channel_target_id)",
"CREATE INDEX idx_messages_company on agent_messages(company_id)",
"CREATE INDEX idx_model_capabilities_tier ON model_capabilities(tier)",
"CREATE INDEX idx_projects_company on projects(company_id)",
"CREATE INDEX idx_run_events_company ON run_events(company_id, run_id)",
"CREATE INDEX idx_run_events_run ON run_events(run_id, seq)",
"CREATE UNIQUE INDEX idx_run_events_run_seq_unique ON run_events(run_id, seq)",
"CREATE INDEX idx_runs_agent ON runs(agent_id)",
"CREATE INDEX idx_runs_agent_created_at ON runs(agent_id, created_at DESC)",
"CREATE INDEX idx_runs_company ON runs(company_id)",
"CREATE INDEX idx_runs_company_created_at ON runs(company_id, created_at DESC)",
"CREATE INDEX idx_runs_company_status ON runs(company_id, status)",
"CREATE INDEX idx_runs_task ON runs(task_id)",
"CREATE INDEX idx_secrets_company on secrets(company_id)",
"CREATE INDEX idx_session_entries_active ON session_entries(task_session_id, is_superseded, sequence)",
"CREATE INDEX idx_session_entries_session ON session_entries(task_session_id, sequence)",
"CREATE INDEX idx_social_accounts_company on social_accounts(company_id)",
"CREATE INDEX idx_sprints_company on sprints(company_id)",
"CREATE INDEX idx_sprints_company_start_date ON sprints(company_id, start_date DESC)",
"CREATE INDEX idx_task_approval_links_approval ON task_approval_links(approval_id)",
"CREATE INDEX idx_task_approval_links_task ON task_approval_links(task_id)",
"CREATE UNIQUE INDEX idx_task_documents_doc ON task_documents(document_id)",
"CREATE UNIQUE INDEX idx_task_documents_task_key ON task_documents(company_id, task_id, key)",
"CREATE INDEX idx_task_sessions_company_agent\n ON task_sessions(company_id, agent_id, updated_at)",
"CREATE INDEX idx_task_sessions_lookup\n ON task_sessions(company_id, agent_id, adapter_type)",
"CREATE INDEX idx_tasks_assignee ON tasks(assignee_agent_id)",
"CREATE INDEX idx_tasks_company ON tasks(company_id)",
"CREATE INDEX idx_tasks_company_status ON tasks(company_id, status)",
"CREATE INDEX idx_tasks_project ON tasks(project_id)",
"CREATE INDEX idx_thread_subs_subscriber ON thread_subscriptions(subscriber_id)",
"CREATE INDEX idx_thread_wait_company ON thread_wait_states(company_id, resolved_at)",
"CREATE INDEX idx_thread_wait_thread ON thread_wait_states(thread_id)",
"CREATE INDEX idx_wakeup_requests_agent ON wakeup_requests(agent_id, company_id, status)",
"CREATE INDEX idx_wakeup_requests_agent_requested ON wakeup_requests(agent_id, requested_at)",
"CREATE INDEX idx_wakeup_requests_company_agent_status ON wakeup_requests(company_id, agent_id, status)",
"CREATE INDEX idx_wakeup_requests_company_requested ON wakeup_requests(company_id, requested_at)",
"CREATE UNIQUE INDEX idx_work_products_identity\n ON work_products(company_id, workspace_id, provider, external_id)\n WHERE workspace_id IS NOT NULL AND external_id IS NOT NULL\n ",
"CREATE INDEX idx_work_products_last_touched\n ON work_products(company_id, last_touched_by_run_id)\n ",
"CREATE UNIQUE INDEX idx_work_products_primary_active\n ON work_products(company_id, task_id, type)\n WHERE task_id IS NOT NULL\n AND is_primary = 1\n AND status <> 'archived'",
"CREATE INDEX idx_work_products_task\n ON work_products(company_id, task_id, type)",
"CREATE INDEX idx_work_products_updated\n ON work_products(company_id, updated_at)",
"CREATE INDEX idx_workflows_company on workflow_pipelines(company_id)",
"CREATE INDEX idx_workspaces_company on workspaces(company_id)",
"CREATE UNIQUE INDEX secrets_company_name_idx on secrets(company_id, name)",
] as const;
function quoteIdentifier(name: string): string {
return `"${name.replaceAll("\"", "\"\"")}"`;
}
function parseSchemaName(sql: string, prefix: RegExp, kind: string): string {
const match = prefix.exec(sql);
if (!match) throw new Error(`Cannot parse baseline ${kind} name from SQL: ${sql}`);
return match[1] ?? match[2]!;
}
export function parseBaselineTableName(sql: string): string {
return parseSchemaName(
sql,
/^CREATE TABLE(?: IF NOT EXISTS)? (?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_]*))/,
"table",
);
}
export function parseBaselineIndexName(sql: string): string {
return parseSchemaName(
sql,
/^CREATE (?:UNIQUE )?INDEX(?: IF NOT EXISTS)? (?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_]*))/,
"index",
);
}
export function normalizeSchemaSql(sql: string): string {
return sql
.replace(/\s+/g, " ")
.replace(/\(\s+/g, "(")
.replace(/\s+\)/g, ")")
.replace(/^CREATE TABLE "([^"]+)"/, "CREATE TABLE $1")
.replace(/^CREATE INDEX "([^"]+)"/, "CREATE INDEX $1")
.replace(/^CREATE UNIQUE INDEX "([^"]+)"/, "CREATE UNIQUE INDEX $1")
.trim();
}
function ifNotExists(sql: string): string {
return sql
.replace(/^CREATE TABLE /, "CREATE TABLE IF NOT EXISTS ")
.replace(/^CREATE INDEX /, "CREATE INDEX IF NOT EXISTS ")
.replace(/^CREATE UNIQUE INDEX /, "CREATE UNIQUE INDEX IF NOT EXISTS ");
}
export function installBaselineTables(db: DatabaseSync): void {
db.exec(BASELINE_TABLE_SQL.map(ifNotExists).join(";\n"));
}
export function installBaselineIndexes(db: DatabaseSync): void {
db.exec(BASELINE_INDEX_SQL.map(ifNotExists).join(";\n"));
}
export function installCanonicalBaselineIndexes(db: DatabaseSync): void {
for (const sql of BASELINE_INDEX_SQL) {
db.exec(`DROP INDEX IF EXISTS ${quoteIdentifier(parseBaselineIndexName(sql))}`);
}
db.exec(BASELINE_INDEX_SQL.join(";\n"));
}
export function installBaselineSchema(db: DatabaseSync): void {
installBaselineTables(db);
installBaselineIndexes(db);
}
export function assertBaselineSchema(db: DatabaseSync): void {
assertSchemaEntries(
db,
"table",
BASELINE_TABLE_SQL.map((sql) => [parseBaselineTableName(sql), normalizeSchemaSql(sql)] as const),
);
assertSchemaEntries(
db,
"index",
BASELINE_INDEX_SQL.map((sql) => [parseBaselineIndexName(sql), normalizeSchemaSql(sql)] as const),
);
}
function assertSchemaEntries(
db: DatabaseSync,
type: "table" | "index",
expectedEntries: ReadonlyArray<readonly [string, string]>,
): void {
const selectSql = db.prepare("select sql from sqlite_master where type = ? and name = ?");
for (const [name, expectedSql] of expectedEntries) {
const row = selectSql.get(type, name) as { sql: string | null } | undefined;
const actualSql = row?.sql ? normalizeSchemaSql(row.sql) : null;
if (actualSql !== expectedSql) {
throw new Error(`Baseline v56 ${type} drift for ${name}: expected ${expectedSql}; actual ${actualSql ?? "missing"}`);
}
}
}
@@ -1,433 +0,0 @@
import { randomUUID } from "node:crypto";
import type { DatabaseSync } from "node:sqlite";
import {
BASELINE_SCHEMA_NAME,
BASELINE_SCHEMA_VERSION,
BASELINE_TABLE_SQL,
installCanonicalBaselineIndexes,
installBaselineTables,
normalizeSchemaSql,
parseBaselineTableName,
} from "./baseline-v56";
import {
columnExists,
nowIso,
tableExists,
type ReadAll,
} from "./types";
type ColumnInfo = {
name: string;
type: string;
notnull: number;
dflt_value: string | null;
pk: number;
};
type LegacyDeps = {
db: DatabaseSync;
readAll: ReadAll;
};
const BASELINE_TABLE_NAMES = BASELINE_TABLE_SQL.map((sql) => parseTableName(sql));
const EXPECTED_DROPPED_COLUMNS: Readonly<Record<string, ReadonlySet<string>>> = {
agent_messages: new Set(["read_at"]),
company_skills: new Set(["markdown"]),
runs: new Set(["context_snapshot_json", "session_display_id"]),
tasks: new Set(["is_heartbeat", "phase", "session_display_id", "session_json"]),
task_sessions: new Set(["session_display_id", "session_params_json", "task_id"]),
};
export function markBaselineSchema(db: DatabaseSync): void {
db.prepare("delete from schema_migrations where version <> ?").run(BASELINE_SCHEMA_VERSION);
db
.prepare("insert or replace into schema_migrations (version, name, applied_at) values (?, ?, ?)")
.run(BASELINE_SCHEMA_VERSION, BASELINE_SCHEMA_NAME, nowIso());
}
export function upgradeLegacyProfileToBaselineV56({ db, readAll }: LegacyDeps): void {
installBaselineTables(db);
migrateLegacySecrets(db);
migratePendingWakes(db, readAll);
migrateAgentBudgets(db, readAll);
for (const tableName of BASELINE_TABLE_NAMES) {
if (tableName === "schema_migrations") continue;
if (tableName === "runs") {
backfillRunsUsageStatus(db);
}
if (tableName === "tasks") {
migrateTaskSessions(db, readAll);
backfillRequiresFilesystem(db);
removeSyntheticHeartbeatTasks(db);
}
if (tableName === "work_products") {
detachDanglingWorkProducts(db);
backfillWorkProductProvenance(db);
normalizeWorkProductPrimaries(db);
}
rebuildTableToBaseline(db, tableName);
}
db.exec("drop table if exists pending_wakes");
db.exec("drop table if exists task_leases");
db.exec("drop trigger if exists trg_tasks_phase_from_status_insert");
db.exec("drop trigger if exists trg_tasks_phase_from_status_update");
installCanonicalBaselineIndexes(db);
assertNoForeignKeyViolations(db);
markBaselineSchema(db);
}
function parseTableName(sql: string): string {
return parseBaselineTableName(sql);
}
function quoteIdentifier(name: string): string {
return `"${name.replaceAll("\"", "\"\"")}"`;
}
function tableSql(tableName: string): string {
const sql = BASELINE_TABLE_SQL.find((entry) => parseTableName(entry) === tableName);
if (!sql) throw new Error(`Missing baseline SQL for table ${tableName}`);
return sql;
}
function tableColumns(db: DatabaseSync, tableName: string): ColumnInfo[] {
return db.prepare(`pragma table_info(${quoteIdentifier(tableName)})`).all() as ColumnInfo[];
}
function createSqlForTable(sql: string, tableName: string): string {
return sql.replace(/^CREATE TABLE (?:"[^"]+"|[A-Za-z_][A-Za-z0-9_]*)/, `CREATE TABLE ${quoteIdentifier(tableName)}`);
}
function rebuildTableToBaseline(db: DatabaseSync, tableName: string): void {
if (!tableExists(db, tableName)) return;
if (isBaselineTableDefinition(db, tableName)) return;
const tempTableName = `__baseline_${tableName}_${process.pid}`;
db.exec(`drop table if exists ${quoteIdentifier(tempTableName)}`);
db.exec(createSqlForTable(tableSql(tableName), tempTableName));
const beforeCount = (db.prepare(`select count(*) as count from ${quoteIdentifier(tableName)}`).get() as { count: number }).count;
const sourceColumns = new Set(tableColumns(db, tableName).map((column) => column.name));
const targetColumns = tableColumns(db, tempTableName);
assertDroppedColumnsAreExpected(tableName, sourceColumns, new Set(targetColumns.map((column) => column.name)));
const insertColumns = targetColumns.map((column) => quoteIdentifier(column.name));
const selectExpressions = targetColumns.map((column) => {
if (sourceColumns.has(column.name)) return quoteIdentifier(column.name);
if (column.dflt_value !== null) return column.dflt_value;
if (column.notnull === 0) return "NULL";
if (beforeCount === 0) return "NULL";
throw new Error(`Cannot rebuild ${tableName}: missing required column ${column.name}`);
});
db.exec(`insert into ${quoteIdentifier(tempTableName)} (${insertColumns.join(", ")}) select ${selectExpressions.join(", ")} from ${quoteIdentifier(tableName)}`);
const afterCount = (db.prepare(`select count(*) as count from ${quoteIdentifier(tempTableName)}`).get() as { count: number }).count;
if (afterCount !== beforeCount) {
throw new Error(`Cannot rebuild ${tableName}: copied ${afterCount} of ${beforeCount} rows`);
}
db.exec(`drop table ${quoteIdentifier(tableName)}`);
db.exec(`alter table ${quoteIdentifier(tempTableName)} rename to ${quoteIdentifier(tableName)}`);
}
function isBaselineTableDefinition(db: DatabaseSync, tableName: string): boolean {
const row = db
.prepare("select sql from sqlite_master where type = 'table' and name = ?")
.get(tableName) as { sql: string | null } | undefined;
if (!row?.sql) return false;
return normalizeSchemaSql(row.sql) === normalizeSchemaSql(tableSql(tableName));
}
function assertDroppedColumnsAreExpected(tableName: string, sourceColumns: Set<string>, targetColumns: Set<string>): void {
const allowed = EXPECTED_DROPPED_COLUMNS[tableName] ?? new Set<string>();
const unexpected = [...sourceColumns]
.filter((column) => !targetColumns.has(column))
.filter((column) => !allowed.has(column));
if (unexpected.length > 0) {
throw new Error(`Cannot rebuild ${tableName}: unexpected legacy columns would be dropped: ${unexpected.join(", ")}`);
}
}
function migrateLegacySecrets(db: DatabaseSync): void {
if (!tableExists(db, "secrets") || columnExists(db, "secrets", "company_id")) return;
const currentCompany = db.prepare("select value from settings where key = 'current_company_id'").get() as { value: string } | undefined;
const firstCompany = db.prepare("select id from companies order by created_at asc limit 1").get() as { id: string } | undefined;
const companyId = currentCompany?.value ?? firstCompany?.id;
if (!companyId) {
throw new Error("Cannot migrate legacy secrets table: no company row exists.");
}
db.exec(`
alter table secrets rename to secrets_legacy;
create table secrets (id text primary key, company_id text not null, name text not null, description text not null, backend text not null, ciphertext blob not null, updated_at text not null, created_at text not null, unique(company_id, name));
`);
db
.prepare("insert into secrets (id, company_id, name, description, backend, ciphertext, updated_at, created_at) select id, ?, name, description, backend, ciphertext, updated_at, created_at from secrets_legacy")
.run(companyId);
db.exec("drop table secrets_legacy");
}
function migratePendingWakes(db: DatabaseSync, readAll: ReadAll): void {
if (!tableExists(db, "pending_wakes")) return;
const rows = readAll<{ id: string; companyId: string; agentId: string; triggerReason: string; createdAt: string }>(
"select id, company_id as companyId, agent_id as agentId, trigger_reason as triggerReason, created_at as createdAt from pending_wakes",
);
const insert = db.prepare(`
insert or ignore into wakeup_requests (id, company_id, agent_id, source, trigger_detail, status, coalesced_count, requested_at, created_at, updated_at)
values (?, ?, ?, ?, ?, 'queued', 0, ?, ?, ?)
`);
const ts = nowIso();
for (const row of rows) {
const detail = JSON.stringify([{ trigger: row.triggerReason, at: row.createdAt, detail: row.triggerReason }]);
insert.run(row.id, row.companyId, row.agentId, row.triggerReason, detail, row.createdAt, ts, ts);
}
}
function migrateAgentBudgets(db: DatabaseSync, readAll: ReadAll): void {
if (!tableExists(db, "agents") || !tableExists(db, "budget_policies")) return;
if (!columnExists(db, "agents", "budget_monthly_usd")) return;
const agents = readAll<{ id: string; companyId: string; budgetMonthlyUsd: number }>(
"select id, company_id as companyId, budget_monthly_usd as budgetMonthlyUsd from agents where budget_monthly_usd > 0",
);
const existing = db.prepare("select id from budget_policies where company_id = ? and scope_type = 'agent' and scope_id = ? and metric = 'billed_cents' and window_kind = 'calendar_month_utc'");
const insert = db.prepare(`
insert into budget_policies (id, company_id, scope_type, scope_id, metric, window_kind, amount, warn_percent, hard_stop_enabled, notify_enabled, is_active, created_at, updated_at)
values (?, ?, 'agent', ?, 'billed_cents', 'calendar_month_utc', ?, 80, 1, 1, 1, ?, ?)
`);
const ts = nowIso();
for (const agent of agents) {
if (existing.get(agent.companyId, agent.id)) continue;
insert.run(randomUUID(), agent.companyId, agent.id, Math.round(agent.budgetMonthlyUsd * 100), ts, ts);
}
}
function migrateTaskSessions(db: DatabaseSync, readAll: ReadAll): void {
if (!tableExists(db, "tasks") || !columnExists(db, "tasks", "session_json")) return;
if (!columnExists(db, "tasks", "active_session_id")) {
db.exec("alter table tasks add column active_session_id text");
}
const rows = readAll<{
id: string;
companyId: string;
assigneeAgentId: string | null;
sessionJson: string;
sessionDisplayId: string | null;
updatedAt: string;
connectorId: string | null;
}>(`
select t.id,
t.company_id as companyId,
t.assignee_agent_id as assigneeAgentId,
t.session_json as sessionJson,
${columnExists(db, "tasks", "session_display_id") ? "t.session_display_id" : "NULL"} as sessionDisplayId,
t.updated_at as updatedAt,
a.connector_id as connectorId
from tasks t
left join agents a on a.id = t.assignee_agent_id
where t.session_json is not null
and (t.active_session_id is null or t.active_session_id = '')
`);
const sessionColumns = new Set(tableColumns(db, "task_sessions").map((column) => column.name));
const updateTask = db.prepare("update tasks set active_session_id = ? where id = ?");
const existingSessionByTask = sessionColumns.has("task_id")
? db.prepare("select id from task_sessions where task_id = ? order by updated_at desc limit 1")
: null;
const insertColumns = [
"id",
"company_id",
"agent_id",
"adapter_type",
"created_at",
"updated_at",
...(sessionColumns.has("task_id") ? ["task_id"] : []),
...(sessionColumns.has("session_params_json") ? ["session_params_json"] : []),
...(sessionColumns.has("session_display_id") ? ["session_display_id"] : []),
];
const insert = db.prepare(`
insert into task_sessions (${insertColumns.map(quoteIdentifier).join(", ")})
values (${insertColumns.map(() => "?").join(", ")})
`);
for (const row of rows) {
JSON.parse(row.sessionJson);
const existingSession = existingSessionByTask?.get(row.id) as { id: string } | undefined;
if (existingSession) {
updateTask.run(existingSession.id, row.id);
continue;
}
if (!row.assigneeAgentId) {
throw new Error(`Cannot migrate task session for task ${row.id}: assignee_agent_id is missing.`);
}
if (!row.connectorId) {
throw new Error(`Cannot migrate task session for task ${row.id}: assigned agent connector_id is missing.`);
}
const sessionId = randomUUID();
const values = [
sessionId,
row.companyId,
row.assigneeAgentId,
row.connectorId,
row.updatedAt,
row.updatedAt,
...(sessionColumns.has("task_id") ? [row.id] : []),
...(sessionColumns.has("session_params_json") ? [row.sessionJson] : []),
...(sessionColumns.has("session_display_id") ? [row.sessionDisplayId] : []),
];
insert.run(...values);
updateTask.run(sessionId, row.id);
}
}
function backfillRequiresFilesystem(db: DatabaseSync): void {
if (!tableExists(db, "tasks")) return;
if (!columnExists(db, "tasks", "requires_filesystem")) {
db.exec("alter table tasks add column requires_filesystem integer not null default 0");
}
if (columnExists(db, "tasks", "active_run_id")) {
db.exec(`
update tasks
set requires_filesystem = 1
where status = 'in_progress'
and active_run_id is not null
`);
}
if (columnExists(db, "tasks", "assignee_agent_id")) {
db.exec(`
update tasks
set requires_filesystem = 1
where status in ('todo', 'backlog')
and assignee_agent_id is not null
and exists (
select 1 from agents a
where a.id = tasks.assignee_agent_id
and (
a.role like '%cto%'
or a.role like '%engineer%'
or a.role like '%developer%'
or a.role like '%devops%'
or a.role like '%sre%'
or a.department = 'engineering'
)
)
`);
}
}
function removeSyntheticHeartbeatTasks(db: DatabaseSync): void {
if (!tableExists(db, "tasks") || !columnExists(db, "tasks", "is_heartbeat")) return;
if (tableExists(db, "runs")) {
db.exec("update runs set task_id = null where task_id in (select id from tasks where is_heartbeat = 1)");
}
db.exec("delete from tasks where is_heartbeat = 1");
}
function backfillRunsUsageStatus(db: DatabaseSync): void {
if (!tableExists(db, "runs")) return;
if (!columnExists(db, "runs", "usage_status")) {
db.exec("alter table runs add column usage_status text not null default 'pre_first_message'");
}
if (!columnExists(db, "runs", "unattributed_cost") || !columnExists(db, "runs", "status")) return;
db.exec(`
update runs
set usage_status = case
when unattributed_cost = 0 then 'ok'
when status in ('cancelled', 'interrupted') then 'aborted_before_usage'
else 'pre_first_message'
end
where usage_status = 'pre_first_message'
`);
}
function normalizeWorkProductPrimaries(db: DatabaseSync): void {
if (!tableExists(db, "work_products")) return;
db.exec(`
update work_products
set is_primary = 0,
updated_at = datetime('now')
where id in (
select id from (
select id,
row_number() over (
partition by company_id, task_id, type
order by updated_at desc, created_at desc, id desc
) as rank_in_group
from work_products
where task_id is not null
and is_primary = 1
and status <> 'archived'
)
where rank_in_group > 1
)
`);
}
function backfillWorkProductProvenance(db: DatabaseSync): void {
if (!tableExists(db, "work_products")) return;
if (!columnExists(db, "work_products", "workspace_id")) {
db.exec("alter table work_products add column workspace_id text");
}
if (!columnExists(db, "work_products", "last_touched_by_run_id")) {
db.exec("alter table work_products add column last_touched_by_run_id text");
}
if (tableExists(db, "tasks") && columnExists(db, "work_products", "task_id") && columnExists(db, "tasks", "workspace_id")) {
db.exec(`
update work_products
set workspace_id = (
select tasks.workspace_id from tasks
where tasks.id = work_products.task_id
)
where workspace_id is null
and task_id is not null
`);
}
if (tableExists(db, "runs") && columnExists(db, "work_products", "created_by_run_id") && columnExists(db, "runs", "workspace_id")) {
db.exec(`
update work_products
set workspace_id = (
select runs.workspace_id from runs
where runs.id = work_products.created_by_run_id
)
where workspace_id is null
and created_by_run_id is not null
`);
}
if (columnExists(db, "work_products", "created_by_run_id")) {
db.exec(`
update work_products
set last_touched_by_run_id = created_by_run_id
where last_touched_by_run_id is null
and created_by_run_id is not null
`);
}
}
function detachDanglingWorkProducts(db: DatabaseSync): void {
if (!tableExists(db, "work_products") || !tableExists(db, "tasks") || !columnExists(db, "work_products", "task_id")) return;
db.exec(`
update work_products
set task_id = null
where task_id is not null
and not exists (
select 1 from tasks
where tasks.id = work_products.task_id
)
`);
}
function assertNoForeignKeyViolations(db: DatabaseSync): void {
const violations = db.prepare("pragma foreign_key_check").all() as Array<{
table: string;
rowid: number | null;
parent: string;
fkid: number;
}>;
if (violations.length === 0) return;
const sample = violations
.slice(0, 5)
.map((row) => `${row.table}:${row.rowid ?? "unknown"}->${row.parent}#${row.fkid}`)
.join(", ");
throw new Error(`Cannot migrate legacy profile: ${violations.length} foreign key violations after v56 rebuild (${sample}).`);
}
-47
View File
@@ -1,47 +0,0 @@
import type { DatabaseSync, SQLInputValue } from "node:sqlite";
export type ReadAll = <T>(sql: string, ...params: SQLInputValue[]) => T[];
export type Transaction = <T>(operation: () => T) => T;
export type Migration = {
version: number;
name: string;
sql?: string;
fn?: () => void;
};
export type MigrationDeps = {
db: DatabaseSync;
readAll: ReadAll;
};
export function nowIso(): string {
return new Date().toISOString();
}
export function columnExists(
db: DatabaseSync,
table: string,
column: string,
): boolean {
const rows = db.prepare(`pragma table_info(${table})`).all() as Array<{
name: string;
}>;
return rows.some((row) => row.name === column);
}
export function tableExists(db: DatabaseSync, table: string): boolean {
const row = db
.prepare("select name from sqlite_master where type = 'table' and name = ?")
.get(table) as { name: string } | undefined;
return !!row;
}
export function addColumnIfMissing(
db: DatabaseSync,
table: string,
column: string,
sql: string,
): void {
if (!columnExists(db, table, column)) db.prepare(sql).run();
}
@@ -1,121 +0,0 @@
import type { DatabaseSync } from "node:sqlite";
import { STALE_LOCK_TIMEOUT_MS } from "../../constants";
import { columnExists, nowIso, tableExists } from "./types";
export const TASK_LEASE_SCHEMA_VERSION = 57;
export const TASK_LEASE_SCHEMA_NAME = "task_leases_runtime_ownership";
const LEASE_SECONDS = Math.floor(STALE_LOCK_TIMEOUT_MS / 1000);
export function migrateTaskLeasesV57(db: DatabaseSync): void {
installTaskLeasesTable(db);
backfillTaskLeases(db);
dropTaskOwnershipColumns(db);
db
.prepare("insert or replace into schema_migrations (version, name, applied_at) values (?, ?, ?)")
.run(TASK_LEASE_SCHEMA_VERSION, TASK_LEASE_SCHEMA_NAME, nowIso());
assertTaskLeasesV57(db);
}
export function assertTaskLeasesV57(db: DatabaseSync): void {
if (!tableExists(db, "task_leases")) {
throw new Error("Schema v57 drift: task_leases table is missing.");
}
if (!tableExists(db, "tasks")) {
throw new Error("Schema v57 drift: tasks table is missing.");
}
const leaseColumns = db
.prepare("pragma table_info(task_leases)")
.all()
.map((row) => row as { name: string; type: string; notnull: number; pk: number });
const expectedLeaseColumns = [
["task_id", "TEXT", 0, 1],
["company_id", "TEXT", 1, 0],
["run_id", "TEXT", 1, 0],
["agent_id", "TEXT", 1, 0],
["acquired_at", "TEXT", 1, 0],
["renewed_at", "TEXT", 1, 0],
["expires_at", "TEXT", 1, 0],
["created_at", "TEXT", 1, 0],
["updated_at", "TEXT", 1, 0],
];
const actualLeaseColumns = leaseColumns.map((column) => [
column.name,
column.type.toUpperCase(),
column.notnull,
column.pk,
]);
if (JSON.stringify(actualLeaseColumns) !== JSON.stringify(expectedLeaseColumns)) {
throw new Error("Schema v57 drift: task_leases columns do not match the canonical schema.");
}
const columns = new Set(
db.prepare("pragma table_info(tasks)").all().map((row) => (row as { name: string }).name),
);
for (const column of ["active_run_id", "checkout_run_id", "execution_locked_at", "active_session_id"]) {
if (columns.has(column)) {
throw new Error(`Schema v57 drift: tasks.${column} should be stored in task_leases.`);
}
}
}
function installTaskLeasesTable(db: DatabaseSync): void {
db.exec(`
create table if not exists task_leases (
task_id text primary key references tasks(id) on delete cascade,
company_id text not null,
run_id text not null unique references runs(id) on delete cascade,
agent_id text not null references agents(id) on delete cascade,
acquired_at text not null,
renewed_at text not null,
expires_at text not null,
created_at text not null,
updated_at text not null
);
create index if not exists idx_task_leases_company_agent on task_leases(company_id, agent_id);
create index if not exists idx_task_leases_expires_at on task_leases(expires_at);
`);
}
function backfillTaskLeases(db: DatabaseSync): void {
if (!tableExists(db, "tasks") || !tableExists(db, "runs")) return;
if (!columnExists(db, "tasks", "checkout_run_id") && !columnExists(db, "tasks", "active_run_id")) return;
const runExpr = [
columnExists(db, "tasks", "checkout_run_id") ? "t.checkout_run_id" : "NULL",
columnExists(db, "tasks", "active_run_id") ? "t.active_run_id" : "NULL",
].join(", ");
const lockExpr = columnExists(db, "tasks", "execution_locked_at")
? "coalesce(t.execution_locked_at, t.updated_at)"
: "t.updated_at";
const ts = nowIso();
db.exec(`
insert or ignore into task_leases (
task_id, company_id, run_id, agent_id, acquired_at, renewed_at, expires_at, created_at, updated_at
)
select t.id,
t.company_id,
coalesce(${runExpr}) as run_id,
r.agent_id,
${lockExpr} as acquired_at,
${lockExpr} as renewed_at,
strftime('%Y-%m-%dT%H:%M:%fZ', ${lockExpr}, '+${LEASE_SECONDS} seconds') as expires_at,
'${ts}' as created_at,
'${ts}' as updated_at
from tasks t
join runs r on r.id = coalesce(${runExpr})
where coalesce(${runExpr}) is not null
and r.status in ('queued', 'running')
`);
}
function dropTaskOwnershipColumns(db: DatabaseSync): void {
if (!tableExists(db, "tasks")) return;
for (const column of ["active_run_id", "checkout_run_id", "execution_locked_at", "active_session_id"]) {
if (columnExists(db, "tasks", column)) {
db.exec(`alter table tasks drop column ${column}`);
}
}
}
@@ -1,110 +0,0 @@
import type { DatabaseSync } from "node:sqlite";
import { APPROVAL_POLICY_SEED } from "../../approval-policy-definitions";
import { nowIso, tableExists } from "./types";
export const APPROVAL_POLICY_SCHEMA_VERSION = 58;
export const APPROVAL_POLICY_SCHEMA_NAME = "approval_policy_table";
export function migrateApprovalPoliciesV58(db: DatabaseSync): void {
installApprovalPoliciesTable(db);
seedApprovalPolicies(db);
db
.prepare("insert or replace into schema_migrations (version, name, applied_at) values (?, ?, ?)")
.run(APPROVAL_POLICY_SCHEMA_VERSION, APPROVAL_POLICY_SCHEMA_NAME, nowIso());
assertApprovalPoliciesV58(db);
}
export function assertApprovalPoliciesV58(db: DatabaseSync): void {
if (!tableExists(db, "approval_policies")) {
throw new Error("Schema v58 drift: approval_policies table is missing.");
}
const columns = db
.prepare("pragma table_info(approval_policies)")
.all()
.map((row) => row as { name: string; type: string; notnull: number; pk: number });
const actualColumns = columns.map((column) => [
column.name,
column.type.toUpperCase(),
column.notnull,
column.pk,
]);
const expectedColumns = [
["approval_type", "TEXT", 0, 1],
["scope_type", "TEXT", 1, 0],
["blocks_execution", "INTEGER", 1, 0],
["required_mode", "TEXT", 1, 0],
["auto_approvable", "INTEGER", 1, 0],
["created_at", "TEXT", 1, 0],
["updated_at", "TEXT", 1, 0],
];
if (JSON.stringify(actualColumns) !== JSON.stringify(expectedColumns)) {
throw new Error("Schema v58 drift: approval_policies columns do not match the canonical schema.");
}
const rows = db
.prepare(
"select approval_type as approvalType, scope_type as scopeType, blocks_execution as blocksExecution, required_mode as requiredMode, auto_approvable as autoApprovable from approval_policies order by approval_type",
)
.all() as Array<{
approvalType: string;
scopeType: string;
blocksExecution: number;
requiredMode: string;
autoApprovable: number;
}>;
const actual = rows.map((row) => ({
approvalType: row.approvalType,
scopeType: row.scopeType,
blocksExecution: row.blocksExecution,
requiredMode: row.requiredMode,
autoApprovable: row.autoApprovable,
}));
const expected = [...APPROVAL_POLICY_SEED]
.sort((a, b) => a.approvalType.localeCompare(b.approvalType))
.map((policy) => ({
approvalType: policy.approvalType,
scopeType: policy.scopeType,
blocksExecution: policy.blocksExecution ? 1 : 0,
requiredMode: policy.requiredMode,
autoApprovable: policy.autoApprovable ? 1 : 0,
}));
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error("Schema v58 drift: approval_policies seed rows do not match the canonical policy.");
}
}
function installApprovalPoliciesTable(db: DatabaseSync): void {
db.exec(`
create table if not exists approval_policies (
approval_type text primary key,
scope_type text not null check (scope_type in ('requester_or_task', 'related_agent')),
blocks_execution integer not null check (blocks_execution in (0, 1)),
required_mode text not null check (required_mode in ('any', 'ai_ceo', 'ai_cofounder')),
auto_approvable integer not null check (auto_approvable in (0, 1)),
created_at text not null,
updated_at text not null
);
`);
}
function seedApprovalPolicies(db: DatabaseSync): void {
const ts = nowIso();
const stmt = db.prepare(
`insert or replace into approval_policies (
approval_type, scope_type, blocks_execution, required_mode, auto_approvable, created_at, updated_at
) values (?, ?, ?, ?, ?, coalesce((select created_at from approval_policies where approval_type = ?), ?), ?)`,
);
for (const policy of APPROVAL_POLICY_SEED) {
stmt.run(
policy.approvalType,
policy.scopeType,
policy.blocksExecution ? 1 : 0,
policy.requiredMode,
policy.autoApprovable ? 1 : 0,
policy.approvalType,
ts,
ts,
);
}
}
@@ -1,163 +0,0 @@
import type { DatabaseSync } from "node:sqlite";
import { nowIso, tableExists } from "./types";
export const SOCIAL_COMPANY_SCOPE_SCHEMA_VERSION = 59;
export const SOCIAL_COMPANY_SCOPE_SCHEMA_NAME = "social_company_scope";
const TRIGGER_NAMES = [
"trg_social_accounts_credential_secret_scope_insert",
"trg_social_accounts_credential_secret_scope_update",
"trg_browser_actions_social_account_scope_insert",
"trg_browser_actions_social_account_scope_update",
] as const;
export function migrateSocialCompanyScopeV59(db: DatabaseSync): void {
cleanupInvalidSocialAccountSecretRefs(db);
cleanupInvalidBrowserActionRefs(db);
installSocialCompanyScopeTriggers(db);
db
.prepare("insert or replace into schema_migrations (version, name, applied_at) values (?, ?, ?)")
.run(SOCIAL_COMPANY_SCOPE_SCHEMA_VERSION, SOCIAL_COMPANY_SCOPE_SCHEMA_NAME, nowIso());
assertSocialCompanyScopeV59(db);
}
export function assertSocialCompanyScopeV59(db: DatabaseSync): void {
if (!tableExists(db, "social_accounts")) {
throw new Error("Schema v59 drift: social_accounts table is missing.");
}
if (!tableExists(db, "secrets")) {
throw new Error("Schema v59 drift: secrets table is missing.");
}
if (!tableExists(db, "browser_actions")) {
throw new Error("Schema v59 drift: browser_actions table is missing.");
}
const invalid = db
.prepare(
`select count(*) as count
from social_accounts sa
where sa.credential_secret_id is not null
and not exists (
select 1
from secrets s
where s.id = sa.credential_secret_id
and s.company_id = sa.company_id
)`,
)
.get() as { count: number } | undefined;
if ((invalid?.count ?? 0) > 0) {
throw new Error("Schema v59 drift: social account credential secrets must belong to the same company.");
}
const invalidActions = db
.prepare(
`select count(*) as count
from browser_actions ba
where not exists (
select 1
from social_accounts sa
where sa.id = ba.social_account_id
and sa.company_id = ba.company_id
)`,
)
.get() as { count: number } | undefined;
if ((invalidActions?.count ?? 0) > 0) {
throw new Error("Schema v59 drift: browser actions must reference social accounts in the same company.");
}
for (const triggerName of TRIGGER_NAMES) {
if (!triggerExists(db, triggerName)) {
throw new Error(`Schema v59 drift: ${triggerName} trigger is missing.`);
}
}
}
function cleanupInvalidSocialAccountSecretRefs(db: DatabaseSync): void {
if (!tableExists(db, "social_accounts") || !tableExists(db, "secrets")) return;
db
.prepare(
`update social_accounts
set credential_secret_id = null,
updated_at = ?
where credential_secret_id is not null
and not exists (
select 1
from secrets s
where s.id = social_accounts.credential_secret_id
and s.company_id = social_accounts.company_id
)`,
)
.run(nowIso());
}
function cleanupInvalidBrowserActionRefs(db: DatabaseSync): void {
if (!tableExists(db, "browser_actions") || !tableExists(db, "social_accounts")) return;
db
.prepare(
`delete from browser_actions
where not exists (
select 1
from social_accounts sa
where sa.id = browser_actions.social_account_id
and sa.company_id = browser_actions.company_id
)`,
)
.run();
}
function installSocialCompanyScopeTriggers(db: DatabaseSync): void {
for (const triggerName of TRIGGER_NAMES) {
db.exec(`drop trigger if exists ${triggerName}`);
}
db.exec(`
create trigger trg_social_accounts_credential_secret_scope_insert
before insert on social_accounts
when new.credential_secret_id is not null
and not exists (
select 1 from secrets
where id = new.credential_secret_id
and company_id = new.company_id
)
begin
select raise(abort, 'social account credential secret must belong to the same company');
end;
create trigger trg_social_accounts_credential_secret_scope_update
before update of credential_secret_id, company_id on social_accounts
when new.credential_secret_id is not null
and not exists (
select 1 from secrets
where id = new.credential_secret_id
and company_id = new.company_id
)
begin
select raise(abort, 'social account credential secret must belong to the same company');
end;
create trigger trg_browser_actions_social_account_scope_insert
before insert on browser_actions
when not exists (
select 1 from social_accounts
where id = new.social_account_id
and company_id = new.company_id
)
begin
select raise(abort, 'browser action social account must belong to the same company');
end;
create trigger trg_browser_actions_social_account_scope_update
before update of social_account_id, company_id on browser_actions
when not exists (
select 1 from social_accounts
where id = new.social_account_id
and company_id = new.company_id
)
begin
select raise(abort, 'browser action social account must belong to the same company');
end;
`);
}
function triggerExists(db: DatabaseSync, name: string): boolean {
const row = db
.prepare("select name from sqlite_master where type = 'trigger' and name = ? limit 1")
.get(name) as { name: string } | undefined;
return Boolean(row);
}
@@ -1,130 +0,0 @@
import type { DatabaseSync } from "node:sqlite";
import { nowIso, tableExists } from "./types";
export const FILESYSTEM_TASK_WORKSPACES_SCHEMA_VERSION = 60;
export const FILESYSTEM_TASK_WORKSPACES_SCHEMA_NAME = "filesystem_task_workspaces";
const ACTIVE_TASK_STATUSES = "'backlog', 'todo', 'in_progress', 'blocked'";
export function migrateFilesystemTaskWorkspacesV60(db: DatabaseSync): void {
backfillProjectWorkspaces(db);
backfillAssigneeWorkspaces(db);
backfillSingleCompanyWorkspaces(db);
db
.prepare("insert or replace into schema_migrations (version, name, applied_at) values (?, ?, ?)")
.run(FILESYSTEM_TASK_WORKSPACES_SCHEMA_VERSION, FILESYSTEM_TASK_WORKSPACES_SCHEMA_NAME, nowIso());
assertFilesystemTaskWorkspacesV60(db);
}
export function assertFilesystemTaskWorkspacesV60(db: DatabaseSync): void {
if (!tableExists(db, "tasks") || !tableExists(db, "workspaces")) {
throw new Error("Schema v60 drift: tasks/workspaces tables are missing.");
}
const unresolved = db
.prepare(
`select count(*) as count
from tasks t
where t.requires_filesystem = 1
and t.workspace_id is null
and t.status in (${ACTIVE_TASK_STATUSES})
and (
select count(*)
from workspaces w
where w.company_id = t.company_id
) = 1`,
)
.get() as { count: number } | undefined;
if ((unresolved?.count ?? 0) > 0) {
throw new Error("Schema v60 drift: single-workspace companies still have unresolved filesystem tasks.");
}
}
function backfillProjectWorkspaces(db: DatabaseSync): void {
if (!hasRequiredTables(db)) return;
db
.prepare(
`update tasks
set workspace_id = (
select w.id
from workspaces w
where w.company_id = tasks.company_id
and w.project_id = tasks.project_id
order by w.is_primary desc, w.created_at asc
limit 1
),
updated_at = ?
where requires_filesystem = 1
and workspace_id is null
and status in (${ACTIVE_TASK_STATUSES})
and project_id is not null
and exists (
select 1
from workspaces w
where w.company_id = tasks.company_id
and w.project_id = tasks.project_id
)`,
)
.run(nowIso());
}
function backfillAssigneeWorkspaces(db: DatabaseSync): void {
if (!hasRequiredTables(db) || !tableExists(db, "agents")) return;
db
.prepare(
`update tasks
set workspace_id = (
select a.workspace_id
from agents a
join workspaces w
on w.id = a.workspace_id
and w.company_id = a.company_id
where a.id = tasks.assignee_agent_id
and a.company_id = tasks.company_id
limit 1
),
updated_at = ?
where requires_filesystem = 1
and workspace_id is null
and status in (${ACTIVE_TASK_STATUSES})
and assignee_agent_id is not null
and exists (
select 1
from agents a
join workspaces w
on w.id = a.workspace_id
and w.company_id = a.company_id
where a.id = tasks.assignee_agent_id
and a.company_id = tasks.company_id
)`,
)
.run(nowIso());
}
function backfillSingleCompanyWorkspaces(db: DatabaseSync): void {
if (!hasRequiredTables(db)) return;
db
.prepare(
`update tasks
set workspace_id = (
select w.id
from workspaces w
where w.company_id = tasks.company_id
order by w.is_primary desc, w.created_at asc
limit 1
),
updated_at = ?
where requires_filesystem = 1
and workspace_id is null
and status in (${ACTIVE_TASK_STATUSES})
and (
select count(*)
from workspaces w
where w.company_id = tasks.company_id
) = 1`,
)
.run(nowIso());
}
function hasRequiredTables(db: DatabaseSync): boolean {
return tableExists(db, "tasks") && tableExists(db, "workspaces");
}