Add SDLC review and eval controls

This commit is contained in:
ZacharyZhang-NY committed 2026-05-23 18:39:05 -04:00
1 parent bc14a2fbcb
commit bbf90a1a62
19 files changed
+927 -2

No files matched your search

@@ -17,6 +17,7 @@ import { ComplianceService } from "../services/compliance-service";
import { SearchService } from "../services/search-service";
import { IdentityAccessService } from "../services/identity-access-service";
import { OperationsService } from "../services/operations-service";
import { SdlcService } from "../services/sdlc-service";
export interface IpcDeps {
connection: DatabaseConnection;
@@ -38,6 +39,7 @@ export function registerIpcHandlers(deps: IpcDeps): void {
const search = new SearchService(deps.connection.db);
const identityAccess = new IdentityAccessService(deps.connection.db);
const operations = new OperationsService(deps.connection.db);
const sdlc = new SdlcService(deps.connection.db);
const onboarding = new OnboardingService({
db: deps.connection.db,
saveProviderCredential: (input) => providers.saveCredential(input),
@@ -157,6 +159,9 @@ export function registerIpcHandlers(deps: IpcDeps): void {
ipcMain.handle(IPC_CHANNELS.createMigrationReport, (_event, input) => mutate(() => migrations.createReport(input)));
ipcMain.handle(IPC_CHANNELS.setFeatureFlag, (_event, input) => mutate(() => operations.setFeatureFlag(input)));
ipcMain.handle(IPC_CHANNELS.runChaosDrill, (_event, input) => mutate(() => operations.runChaosDrill(input)));
ipcMain.handle(IPC_CHANNELS.runSdlcReview, (_event, input) => mutate(() => sdlc.runReview(input)));
ipcMain.handle(IPC_CHANNELS.runEvalSuite, (_event, input) => mutate(() => sdlc.runEvalSuite(input)));
ipcMain.handle(IPC_CHANNELS.setPrBotGate, (_event, input) => mutate(() => sdlc.setPrBotGate(input)));
ipcMain.handle(IPC_CHANNELS.createComplianceExport, (_event, input) => mutate(() => compliance.createExport(input)));
ipcMain.handle(IPC_CHANNELS.createLegalHold, (_event, input) => mutate(() => compliance.createLegalHold(input)));
ipcMain.handle(IPC_CHANNELS.setLegalHoldState, (_event, input) => mutate(() => compliance.setLegalHoldState(input)));
@@ -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'));
`;
@@ -16,6 +16,7 @@ import { providerPolicyUsageSchema } from "./migrations/014-provider-policy-usag
import { identityAccessSchema } from "./migrations/015-identity-access";
import { orgGovernanceSchema } from "./migrations/016-org-governance";
import { operationsControlsSchema } from "./migrations/017-operations-controls";
import { sdlcControlsSchema } from "./migrations/018-sdlc-controls";
interface Migration {
version: number;
@@ -41,6 +42,7 @@ const MIGRATIONS: Migration[] = [
{ version: 15, name: "identity_access", sql: identityAccessSchema },
{ version: 16, name: "org_governance", sql: orgGovernanceSchema },
{ version: 17, name: "operations_controls", sql: operationsControlsSchema },
{ version: 18, name: "sdlc_controls", sql: sdlcControlsSchema },
];
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
@@ -31,6 +31,12 @@ const LEGACY_TABLES = [
"operational_events",
"chaos_drills",
"chaos_drill_runs",
"sdlc_review_agents",
"sdlc_review_runs",
"eval_suites",
"eval_cases",
"eval_runs",
"pr_bot_gates",
"chat_spaces",
"chat_messages",
"chat_threads",
@@ -142,6 +148,15 @@ const RELATION_CHECKS: RelationCheck[] = [
{ table: "chaos_drills", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "chaos_drill_runs", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "chaos_drill_runs", column: "drill_id", referencedTable: "chaos_drills", referencedColumn: "id", reason: "dangling_drill_id" },
{ table: "sdlc_review_agents", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "sdlc_review_runs", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "sdlc_review_runs", column: "review_agent_id", referencedTable: "sdlc_review_agents", referencedColumn: "id", reason: "dangling_review_agent_id" },
{ table: "eval_suites", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "eval_cases", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "eval_cases", column: "suite_id", referencedTable: "eval_suites", referencedColumn: "id", reason: "dangling_suite_id" },
{ table: "eval_runs", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "eval_runs", column: "suite_id", referencedTable: "eval_suites", referencedColumn: "id", reason: "dangling_suite_id" },
{ table: "pr_bot_gates", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "chat_spaces", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "chat_messages", column: "org_id", referencedTable: "organizations", referencedColumn: "id", reason: "dangling_org_id" },
{ table: "chat_messages", column: "space_id", referencedTable: "chat_spaces", referencedColumn: "id", reason: "dangling_space_id" },
@@ -8,6 +8,7 @@ import { PolicyService } from "./policy-service";
import { IdentityAccessService } from "./identity-access-service";
import { OrgGovernanceService } from "./org-governance-service";
import { OperationsService } from "./operations-service";
import { SdlcService } from "./sdlc-service";
import { assertUsableMentionName } from "./mention-handles";
export class OrganizationService {
@@ -16,6 +17,7 @@ export class OrganizationService {
private readonly identityAccess: IdentityAccessService;
private readonly governance: OrgGovernanceService;
private readonly operations: OperationsService;
private readonly sdlc: SdlcService;
constructor(private readonly db: DatabaseSync) {
this.audit = new AuditService(db);
@@ -23,6 +25,7 @@ export class OrganizationService {
this.identityAccess = new IdentityAccessService(db);
this.governance = new OrgGovernanceService(db);
this.operations = new OperationsService(db);
this.sdlc = new SdlcService(db);
}
createOrganization(input: CreateOrganizationInput): string {
@@ -52,6 +55,7 @@ export class OrganizationService {
this.identityAccess.seedEmployeeAccess(orgId, employeeId, now);
this.governance.seedOrganizationGovernance(orgId, employeeId, now);
this.operations.seedOrganizationOperations(orgId, now);
this.sdlc.seedOrganizationSdlc(orgId, now);
for (const space of [
["company", "general", "Company coordination", "workspace"],
@@ -0,0 +1,292 @@
import type { DatabaseSync } from "node:sqlite";
import type { RunEvalSuiteInput, RunSdlcReviewInput, SetPrBotGateInput } from "../../shared/commands";
import {
EVAL_SUITE_KEY,
PR_BOT_GATE_KEY,
SDLC_REVIEW_AGENT_KEY,
SDLC_REVIEW_TARGET_TYPE,
SDLC_RUN_STATE,
type EvalSuiteKey,
type SdlcReviewAgentKey,
type SdlcReviewTargetType,
} from "../../shared/domain";
import { PROVIDER_CATALOG } from "../../shared/provider-catalog";
import { createId } from "../../shared/ids";
import { AuditService } from "./audit-service";
import { providerAdapters } from "./provider-adapters";
const REVIEW_AGENTS = [
{ key: SDLC_REVIEW_AGENT_KEY.CODE_REVIEWER, label: "Code Review Agent", specialty: "static quality and architecture" },
{ key: SDLC_REVIEW_AGENT_KEY.MIGRATION_REVIEWER, label: "Migration Review Agent", specialty: "schema diff and rollback evidence" },
{ key: SDLC_REVIEW_AGENT_KEY.POLICY_REVIEWER, label: "Policy Review Agent", specialty: "provider, approval, and access policy" },
] as const;
const EVAL_SUITES = [
{ key: EVAL_SUITE_KEY.CONTRACT, label: "Contract checks", scope: "provider adapters, event schemas, slash commands" },
{ key: EVAL_SUITE_KEY.INTEGRATION, label: "Integration checks", scope: "chat, approval, file, skill resolution flows" },
{ key: EVAL_SUITE_KEY.REPLAY, label: "Replay checks", scope: "audit timeline and historical decision regression" },
] as const;
const EVAL_CASES = [
{ suiteKey: EVAL_SUITE_KEY.CONTRACT, title: "Provider catalog has adapters", sourceType: "provider_gateway", sourceRef: "providers", expectedSignal: "adapter_coverage" },
{ suiteKey: EVAL_SUITE_KEY.CONTRACT, title: "Schema ledger is current", sourceType: "database", sourceRef: "schema_migrations", expectedSignal: "migration_ledger" },
{ suiteKey: EVAL_SUITE_KEY.INTEGRATION, title: "Approval routes are assigned", sourceType: "org_governance", sourceRef: "approval_assignments", expectedSignal: "route_coverage" },
{ suiteKey: EVAL_SUITE_KEY.INTEGRATION, title: "Skills can resolve runtime snapshots", sourceType: "skills_platform", sourceRef: "skill_runtime_snapshots", expectedSignal: "skill_resolution" },
{ suiteKey: EVAL_SUITE_KEY.REPLAY, title: "Audit timeline has domain events", sourceType: "audit", sourceRef: "audit_events", expectedSignal: "replayable_timeline" },
{ suiteKey: EVAL_SUITE_KEY.REPLAY, title: "Operational signals are captured", sourceType: "operations", sourceRef: "operational_events", expectedSignal: "slo_signal" },
] as const;
const PR_BOT_GATES = [
{ key: PR_BOT_GATE_KEY.STATIC_CHECKS, label: "Static checks", gateType: "typecheck_lint_test" },
{ key: PR_BOT_GATE_KEY.MIGRATION_DIFF, label: "Migration diff", gateType: "schema_review" },
{ key: PR_BOT_GATE_KEY.POLICY_DIFF, label: "Policy diff", gateType: "policy_review" },
{ key: PR_BOT_GATE_KEY.HUMAN_APPROVAL, label: "Human approval", gateType: "approval_required" },
] as const;
interface ReviewAgentRow {
id: string;
key: SdlcReviewAgentKey;
enabled: number;
}
interface EvalSuiteRow {
id: string;
key: EvalSuiteKey;
}
interface GateCheck {
key: string;
passed: boolean;
value: number | string | boolean;
}
export class SdlcService {
private readonly audit: AuditService;
constructor(private readonly db: DatabaseSync) {
this.audit = new AuditService(db);
}
seedOrganizationSdlc(orgId: string, now: string): void {
for (const agent of REVIEW_AGENTS) {
this.db.prepare(`
insert into sdlc_review_agents(id, org_id, key, label, specialty, enabled, created_at, updated_at)
values (?, ?, ?, ?, ?, 1, ?, ?)
on conflict(org_id, key) do nothing
`).run(createId("sra"), orgId, agent.key, agent.label, agent.specialty, now, now);
}
for (const suite of EVAL_SUITES) {
this.db.prepare(`
insert into eval_suites(id, org_id, key, label, scope, created_at, updated_at)
values (?, ?, ?, ?, ?, ?, ?)
on conflict(org_id, key) do nothing
`).run(createId("evs"), orgId, suite.key, suite.label, suite.scope, now, now);
}
for (const item of EVAL_CASES) {
const suite = this.requireSuiteByKey(orgId, item.suiteKey);
this.db.prepare(`
insert into eval_cases(id, org_id, suite_id, title, source_type, source_ref, expected_signal, created_at)
values (?, ?, ?, ?, ?, ?, ?, ?)
on conflict(suite_id, title) do nothing
`).run(createId("evc"), orgId, suite.id, item.title, item.sourceType, item.sourceRef, item.expectedSignal, now);
}
for (const gate of PR_BOT_GATES) {
this.db.prepare(`
insert into pr_bot_gates(id, org_id, key, label, gate_type, enabled, created_at, updated_at)
values (?, ?, ?, ?, ?, 1, ?, ?)
on conflict(org_id, key) do nothing
`).run(createId("pbg"), orgId, gate.key, gate.label, gate.gateType, now, now);
}
}
runReview(input: RunSdlcReviewInput): string {
const agent = this.db.prepare("select id, key, enabled from sdlc_review_agents where id = ? and org_id = ?")
.get(input.reviewAgentId, input.orgId) as ReviewAgentRow | undefined;
if (!agent) throw new Error("SDLC review agent was not found.");
if (agent.enabled !== 1) throw new Error("SDLC review agent is disabled.");
this.assertTarget(input.orgId, input.targetType, input.targetRef);
const checks = this.reviewChecks(input.orgId, agent.key, input.targetType);
const state = checks.every((check) => check.passed) ? SDLC_RUN_STATE.PASSED : SDLC_RUN_STATE.FAILED;
const id = createId("srr");
const now = new Date().toISOString();
this.db.prepare(`
insert into sdlc_review_runs(id, org_id, review_agent_id, target_type, target_ref, state, result_json, created_at, completed_at)
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(id, input.orgId, input.reviewAgentId, input.targetType, input.targetRef, state, JSON.stringify({ checks }), now, now);
this.audit.record({
orgId: input.orgId,
entityType: "sdlc_review_run",
entityId: id,
actorType: "system",
actorId: null,
action: "created",
payload: { reviewAgentKey: agent.key, state, targetType: input.targetType },
});
return id;
}
runEvalSuite(input: RunEvalSuiteInput): string {
const suite = this.db.prepare("select id, key from eval_suites where id = ? and org_id = ?")
.get(input.suiteId, input.orgId) as EvalSuiteRow | undefined;
if (!suite) throw new Error("Eval suite was not found.");
const checks = this.evalChecks(input.orgId, suite.key);
const passedCount = checks.filter((check) => check.passed).length;
const failedCount = checks.length - passedCount;
const state = failedCount === 0 ? SDLC_RUN_STATE.PASSED : SDLC_RUN_STATE.FAILED;
const id = createId("evr");
const now = new Date().toISOString();
this.db.prepare(`
insert into eval_runs(id, org_id, suite_id, state, passed_count, failed_count, result_json, created_at, completed_at)
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(id, input.orgId, input.suiteId, state, passedCount, failedCount, JSON.stringify({ checks }), now, now);
this.audit.record({
orgId: input.orgId,
entityType: "eval_run",
entityId: id,
actorType: "system",
actorId: null,
action: "created",
payload: { suiteKey: suite.key, state, passedCount, failedCount },
});
return id;
}
setPrBotGate(input: SetPrBotGateInput): void {
const gate = this.db.prepare("select key from pr_bot_gates where id = ? and org_id = ?")
.get(input.gateId, input.orgId) as { key: string } | undefined;
if (!gate) throw new Error("PR bot gate was not found.");
const now = new Date().toISOString();
const result = this.db.prepare("update pr_bot_gates set enabled = ?, updated_at = ? where id = ? and org_id = ?")
.run(input.enabled ? 1 : 0, now, input.gateId, input.orgId);
if (result.changes === 0) throw new Error("PR bot gate was not found.");
this.audit.record({
orgId: input.orgId,
entityType: "pr_bot_gate",
entityId: input.gateId,
actorType: "system",
actorId: null,
action: "updated",
payload: { key: gate.key, enabled: input.enabled },
});
}
private requireSuiteByKey(orgId: string, key: EvalSuiteKey): { id: string } {
const suite = this.db.prepare("select id from eval_suites where org_id = ? and key = ?")
.get(orgId, key) as { id: string } | undefined;
if (!suite) throw new Error("Eval suite is required.");
return suite;
}
private assertTarget(orgId: string, type: SdlcReviewTargetType, ref: string): void {
if (!Object.values(SDLC_REVIEW_TARGET_TYPE).includes(type)) throw new Error("SDLC review target type is invalid.");
const targetRef = ref.trim();
if (!targetRef) throw new Error("SDLC review target is required.");
if (type === SDLC_REVIEW_TARGET_TYPE.WORKSPACE) {
const org = this.db.prepare("select id from organizations where id = ?").get(targetRef);
if (!org || targetRef !== orgId) throw new Error("SDLC workspace target is invalid.");
return;
}
if (type === SDLC_REVIEW_TARGET_TYPE.MIGRATION) {
const report = this.db.prepare("select id from migration_reports where id = ? and org_id = ?").get(targetRef, orgId);
if (!report) throw new Error("SDLC migration target was not found.");
return;
}
const policy = this.db.prepare("select id from policy_rules where id = ? and org_id = ?").get(targetRef, orgId);
if (!policy) throw new Error("SDLC policy target was not found.");
}
private reviewChecks(orgId: string, agentKey: SdlcReviewAgentKey, targetType: SdlcReviewTargetType): GateCheck[] {
if (agentKey === SDLC_REVIEW_AGENT_KEY.MIGRATION_REVIEWER) return this.migrationReviewChecks(orgId);
if (agentKey === SDLC_REVIEW_AGENT_KEY.POLICY_REVIEWER) return this.policyReviewChecks(orgId);
return this.codeReviewChecks(orgId, targetType);
}
private codeReviewChecks(orgId: string, targetType: SdlcReviewTargetType): GateCheck[] {
const enabledGates = count(this.db, "select count(*) as count from pr_bot_gates where org_id = ? and enabled = 1", orgId);
const auditEvents = count(this.db, "select count(*) as count from audit_events where org_id = ?", orgId);
return [
{ key: "target_type", passed: targetType === SDLC_REVIEW_TARGET_TYPE.WORKSPACE, value: targetType },
{ key: "enabled_pr_gates", passed: enabledGates >= 3, value: enabledGates },
{ key: "recent_audit_events", passed: auditEvents > 0, value: auditEvents },
];
}
private migrationReviewChecks(orgId: string): GateCheck[] {
const reports = this.db.prepare("select report_json from migration_reports where org_id = ?").all(orgId) as Array<{ report_json: string }>;
const rollbackPacks = reports.filter((report) => hasObjectField(report.report_json, "rollbackPack")).length;
const migrationGateEnabled = this.isGateEnabled(orgId, PR_BOT_GATE_KEY.MIGRATION_DIFF);
return [
{ key: "migration_gate_enabled", passed: migrationGateEnabled, value: migrationGateEnabled },
{ key: "rollback_pack_coverage", passed: reports.length === rollbackPacks, value: `${rollbackPacks}/${reports.length}` },
];
}
private policyReviewChecks(orgId: string): GateCheck[] {
const providerConflicts = count(this.db, "select count(*) as count from providers where org_id = ? and data_policy = 'zero_data_retention' and background_enabled = 1", orgId);
const routeRules = count(this.db, "select count(*) as count from approval_routing_rules where org_id = ?", orgId);
const policyGateEnabled = this.isGateEnabled(orgId, PR_BOT_GATE_KEY.POLICY_DIFF);
return [
{ key: "policy_gate_enabled", passed: policyGateEnabled, value: policyGateEnabled },
{ key: "provider_policy_conflicts", passed: providerConflicts === 0, value: providerConflicts },
{ key: "approval_routes", passed: routeRules > 0, value: routeRules },
];
}
private evalChecks(orgId: string, suiteKey: EvalSuiteKey): GateCheck[] {
if (suiteKey === EVAL_SUITE_KEY.CONTRACT) return this.contractEvalChecks();
if (suiteKey === EVAL_SUITE_KEY.INTEGRATION) return this.integrationEvalChecks(orgId);
return this.replayEvalChecks(orgId);
}
private contractEvalChecks(): GateCheck[] {
const catalogKeys = PROVIDER_CATALOG.map((provider) => provider.key);
const adapterKeys = Object.keys(providerAdapters);
const missing = catalogKeys.filter((key) => !adapterKeys.includes(key));
const schemaVersion = count(this.db, "select coalesce(max(version), 0) as count from schema_migrations");
return [
{ key: "provider_adapter_coverage", passed: missing.length === 0, value: `${adapterKeys.length}/${catalogKeys.length}` },
{ key: "schema_version", passed: schemaVersion >= 18, value: schemaVersion },
];
}
private integrationEvalChecks(orgId: string): GateCheck[] {
const chatSpaces = count(this.db, "select count(*) as count from chat_spaces where org_id = ?", orgId);
const approvalRoutes = count(this.db, "select count(*) as count from approval_routing_rules where org_id = ?", orgId);
const skillSnapshots = count(this.db, `
select count(*) as count
from skill_runtime_snapshots
join agents on agents.id = skill_runtime_snapshots.agent_id
where agents.org_id = ?
`, orgId);
return [
{ key: "chat_spaces", passed: chatSpaces > 0, value: chatSpaces },
{ key: "approval_routes", passed: approvalRoutes > 0, value: approvalRoutes },
{ key: "skill_runtime_snapshots", passed: skillSnapshots > 0, value: skillSnapshots },
];
}
private replayEvalChecks(orgId: string): GateCheck[] {
const auditEvents = count(this.db, "select count(*) as count from audit_events where org_id = ?", orgId);
const operationalEvents = count(this.db, "select count(*) as count from operational_events where org_id = ?", orgId);
return [
{ key: "audit_events", passed: auditEvents > 0, value: auditEvents },
{ key: "operational_events", passed: operationalEvents > 0, value: operationalEvents },
];
}
private isGateEnabled(orgId: string, key: string): boolean {
const row = this.db.prepare("select enabled from pr_bot_gates where org_id = ? and key = ?")
.get(orgId, key) as { enabled: number } | undefined;
if (!row) throw new Error("PR bot gate is required.");
return row.enabled === 1;
}
}
function count(db: DatabaseSync, sql: string, ...params: string[]): number {
return (db.prepare(sql).get(...params) as { count: number }).count;
}
function hasObjectField(json: string, field: string): boolean {
const parsed = JSON.parse(json) as Record<string, unknown>;
return typeof parsed[field] === "object" && parsed[field] !== null;
}
@@ -59,6 +59,10 @@ export class SnapshotService {
.map((event) => ({ ...event, success: toBool(event.success) }));
const chaosDrills = list<AppSnapshot["chaosDrills"][number]>(this.db, "chaos_drills", "key asc")
.map((drill) => ({ ...drill, enabled: toBool(drill.enabled) }));
const sdlcReviewAgents = list<AppSnapshot["sdlcReviewAgents"][number]>(this.db, "sdlc_review_agents", "key asc")
.map((agent) => ({ ...agent, enabled: toBool(agent.enabled) }));
const prBotGates = list<AppSnapshot["prBotGates"][number]>(this.db, "pr_bot_gates", "key asc")
.map((gate) => ({ ...gate, enabled: toBool(gate.enabled) }));
return {
activeOrgId,
@@ -111,6 +115,12 @@ export class SnapshotService {
operationalEvents,
chaosDrills,
chaosDrillRuns: list(this.db, "chaos_drill_runs", "started_at desc"),
sdlcReviewAgents,
sdlcReviewRuns: list(this.db, "sdlc_review_runs", "created_at desc"),
evalSuites: list(this.db, "eval_suites", "key asc"),
evalCases: list(this.db, "eval_cases", "title asc"),
evalRuns: list(this.db, "eval_runs", "created_at desc"),
prBotGates,
auditEvents: list<AppSnapshot["auditEvents"][number]>(this.db, "audit_events", "created_at desc").slice(0, 200),
};
}
+3
View File
@@ -40,6 +40,9 @@ const api: DesktopApi = {
createMigrationReport: (input) => invoke(IPC_CHANNELS.createMigrationReport, input),
setFeatureFlag: (input) => invoke(IPC_CHANNELS.setFeatureFlag, input),
runChaosDrill: (input) => invoke(IPC_CHANNELS.runChaosDrill, input),
runSdlcReview: (input) => invoke(IPC_CHANNELS.runSdlcReview, input),
runEvalSuite: (input) => invoke(IPC_CHANNELS.runEvalSuite, input),
setPrBotGate: (input) => invoke(IPC_CHANNELS.setPrBotGate, input),
createComplianceExport: (input) => invoke(IPC_CHANNELS.createComplianceExport, input),
createLegalHold: (input) => invoke(IPC_CHANNELS.createLegalHold, input),
setLegalHoldState: (input) => invoke(IPC_CHANNELS.setLegalHoldState, input),
@@ -7,6 +7,7 @@ import { BillingPanel } from "./settings/BillingPanel";
import { ModelsPanel } from "./settings/ModelsPanel";
import { PoliciesPanel } from "./settings/PoliciesPanel";
import { OperationsPanel } from "./settings/OperationsPanel";
import { SdlcPanel } from "./settings/SdlcPanel";
import { MigrationPanel } from "./settings/MigrationPanel";
import { CompliancePanel } from "./settings/CompliancePanel";
import { NotificationsPanel, ProfilePanel, SecurityPanel } from "./settings/AccountPanels";
@@ -22,6 +23,7 @@ type SettingsTab =
| "skills"
| "policies"
| "operations"
| "sdlc"
| "compliance"
| "migration"
| "profile"
@@ -44,6 +46,7 @@ const AGENTS_GROUP: NavSpec[] = [
{ id: "skills", label: "Skills" },
{ id: "policies", label: "Policies" },
{ id: "operations", label: "Operations" },
{ id: "sdlc", label: "SDLC" },
{ id: "compliance", label: "Compliance" },
{ id: "migration", label: "Migration" },
];
@@ -91,6 +94,12 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
const operationalEvents = useMemo(() => snapshot.operationalEvents.filter((item) => item.orgId === orgId), [snapshot.operationalEvents, orgId]);
const chaosDrills = useMemo(() => snapshot.chaosDrills.filter((item) => item.orgId === orgId), [snapshot.chaosDrills, orgId]);
const chaosDrillRuns = useMemo(() => snapshot.chaosDrillRuns.filter((item) => item.orgId === orgId), [snapshot.chaosDrillRuns, orgId]);
const sdlcReviewAgents = useMemo(() => snapshot.sdlcReviewAgents.filter((item) => item.orgId === orgId), [snapshot.sdlcReviewAgents, orgId]);
const sdlcReviewRuns = useMemo(() => snapshot.sdlcReviewRuns.filter((item) => item.orgId === orgId), [snapshot.sdlcReviewRuns, orgId]);
const evalSuites = useMemo(() => snapshot.evalSuites.filter((item) => item.orgId === orgId), [snapshot.evalSuites, orgId]);
const evalCases = useMemo(() => snapshot.evalCases.filter((item) => item.orgId === orgId), [snapshot.evalCases, orgId]);
const evalRuns = useMemo(() => snapshot.evalRuns.filter((item) => item.orgId === orgId), [snapshot.evalRuns, orgId]);
const prBotGates = useMemo(() => snapshot.prBotGates.filter((item) => item.orgId === orgId), [snapshot.prBotGates, orgId]);
return (
<div className="split-main" style={{ height: "calc(100vh - 44px)", margin: "-28px -32px -40px" }}>
@@ -158,6 +167,20 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
actions={actions}
/>
)}
{tab === "sdlc" && (
<SdlcPanel
orgId={orgId}
reviewAgents={sdlcReviewAgents}
reviewRuns={sdlcReviewRuns}
evalSuites={evalSuites}
evalCases={evalCases}
evalRuns={evalRuns}
prBotGates={prBotGates}
migrationReports={migrationReports}
policies={policies}
actions={actions}
/>
)}
{tab === "compliance" && (
<CompliancePanel
orgId={orgId}
@@ -0,0 +1,240 @@
import { Button, Pill } from "../../shared/ui";
import type {
EvalCaseRecord,
EvalRunRecord,
EvalSuiteRecord,
MigrationReportRecord,
PolicyRuleRecord,
PrBotGateRecord,
SdlcReviewAgentRecord,
SdlcReviewRunRecord,
SdlcReviewTargetType,
} from "../../../../shared/domain";
import { SDLC_REVIEW_AGENT_KEY, SDLC_REVIEW_TARGET_TYPE } from "../../../../shared/domain";
import type { SnapshotActions } from "../../app/useSnapshot";
import { formatRelative } from "../types";
export function SdlcPanel({
orgId,
reviewAgents,
reviewRuns,
evalSuites,
evalCases,
evalRuns,
prBotGates,
migrationReports,
policies,
actions,
}: {
orgId: string;
reviewAgents: SdlcReviewAgentRecord[];
reviewRuns: SdlcReviewRunRecord[];
evalSuites: EvalSuiteRecord[];
evalCases: EvalCaseRecord[];
evalRuns: EvalRunRecord[];
prBotGates: PrBotGateRecord[];
migrationReports: MigrationReportRecord[];
policies: PolicyRuleRecord[];
actions: SnapshotActions;
}) {
return (
<div className="page">
<h1 className="page-h1">SDLC</h1>
<p className="page-sub">{reviewAgents.length} agents / {evalSuites.length} suites / {prBotGates.length} gates.</p>
<ReviewAgentsTable
orgId={orgId}
agents={reviewAgents}
runs={reviewRuns}
migrationReports={migrationReports}
policies={policies}
actions={actions}
/>
<EvalSuitesTable orgId={orgId} suites={evalSuites} cases={evalCases} runs={evalRuns} actions={actions} />
<PrBotGatesTable orgId={orgId} gates={prBotGates} actions={actions} />
</div>
);
}
function ReviewAgentsTable({
orgId,
agents,
runs,
migrationReports,
policies,
actions,
}: {
orgId: string;
agents: SdlcReviewAgentRecord[];
runs: SdlcReviewRunRecord[];
migrationReports: MigrationReportRecord[];
policies: PolicyRuleRecord[];
actions: SnapshotActions;
}) {
return (
<>
<div className="section-label" style={{ marginBottom: 10 }}>Review agents</div>
<div className="card" style={{ padding: 0, overflow: "hidden", marginBottom: 24 }}>
<div className="table-row head" style={{ gridTemplateColumns: "minmax(0, 1.4fr) minmax(0, 1fr) 96px 82px" }}>
<div>Agent</div><div>Target</div><div>Last run</div><div>Action</div>
</div>
{agents.map((agent, index) => {
const target = resolveReviewTarget(agent, orgId, migrationReports, policies);
const run = runs.find((item) => item.reviewAgentId === agent.id) ?? null;
return (
<div
key={agent.id}
className="table-row"
style={{
gridTemplateColumns: "minmax(0, 1.4fr) minmax(0, 1fr) 96px 82px",
borderBottom: index === agents.length - 1 ? "none" : "1px solid var(--border-soft)",
}}
>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{agent.label}</div>
<div className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{agent.specialty}</div>
</div>
<div><Pill>{target.label}</Pill></div>
<div className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>{run ? `${run.state} · ${formatRelative(run.createdAt)}` : "0 runs"}</div>
<Button
size="small"
disabled={!agent.enabled || !target.ready}
onClick={() => void actions.run(() => window.agentCompany.runSdlcReview({
orgId,
reviewAgentId: agent.id,
targetType: target.type,
targetRef: target.ref,
}))}
>
Run
</Button>
</div>
);
})}
</div>
</>
);
}
function EvalSuitesTable({
orgId,
suites,
cases,
runs,
actions,
}: {
orgId: string;
suites: EvalSuiteRecord[];
cases: EvalCaseRecord[];
runs: EvalRunRecord[];
actions: SnapshotActions;
}) {
return (
<>
<div className="section-label" style={{ marginBottom: 10 }}>Eval suites</div>
<div className="card" style={{ padding: 0, overflow: "hidden", marginBottom: 24 }}>
<div className="table-row head" style={{ gridTemplateColumns: "minmax(0, 1.4fr) 90px 96px 82px" }}>
<div>Suite</div><div>Cases</div><div>Last run</div><div>Action</div>
</div>
{suites.map((suite, index) => {
const run = runs.find((item) => item.suiteId === suite.id) ?? null;
const caseCount = cases.filter((item) => item.suiteId === suite.id).length;
return (
<div
key={suite.id}
className="table-row"
style={{
gridTemplateColumns: "minmax(0, 1.4fr) 90px 96px 82px",
borderBottom: index === suites.length - 1 ? "none" : "1px solid var(--border-soft)",
}}
>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{suite.label}</div>
<div className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{suite.scope}</div>
</div>
<div className="num mono">{caseCount}</div>
<div className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>{run ? `${run.state} · ${formatRelative(run.createdAt)}` : "0 runs"}</div>
<Button size="small" onClick={() => void actions.run(() => window.agentCompany.runEvalSuite({ orgId, suiteId: suite.id }))}>
Run
</Button>
</div>
);
})}
</div>
</>
);
}
function PrBotGatesTable({
orgId,
gates,
actions,
}: {
orgId: string;
gates: PrBotGateRecord[];
actions: SnapshotActions;
}) {
return (
<>
<div className="section-label" style={{ marginBottom: 10 }}>PR bot gates</div>
<div className="card" style={{ padding: 0, overflow: "hidden" }}>
<div className="table-row head" style={{ gridTemplateColumns: "minmax(0, 1.4fr) 112px minmax(0, 1fr)" }}>
<div>Gate</div><div>State</div><div>Updated</div>
</div>
{gates.map((gate, index) => (
<div
key={gate.id}
className="table-row"
style={{
gridTemplateColumns: "minmax(0, 1.4fr) 112px minmax(0, 1fr)",
borderBottom: index === gates.length - 1 ? "none" : "1px solid var(--border-soft)",
}}
>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{gate.label}</div>
<div className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{gate.gateType}</div>
</div>
<label className="row" style={{ gap: 8, fontSize: 12, color: "var(--text-2)" }}>
<input
aria-label={`${gate.label} enabled`}
type="checkbox"
checked={gate.enabled}
onChange={(event) => {
void actions.run(() => window.agentCompany.setPrBotGate({ orgId, gateId: gate.id, enabled: event.target.checked }));
}}
/>
{gate.enabled ? "On" : "Off"}
</label>
<div className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>{formatRelative(gate.updatedAt)}</div>
</div>
))}
</div>
</>
);
}
function resolveReviewTarget(
agent: SdlcReviewAgentRecord,
orgId: string,
migrationReports: MigrationReportRecord[],
policies: PolicyRuleRecord[],
): { type: SdlcReviewTargetType; ref: string; label: string; ready: boolean } {
if (agent.key === SDLC_REVIEW_AGENT_KEY.MIGRATION_REVIEWER) {
const report = migrationReports[0] ?? null;
return {
type: SDLC_REVIEW_TARGET_TYPE.MIGRATION,
ref: report?.id ?? orgId,
label: report ? "migration" : "migration report",
ready: Boolean(report),
};
}
if (agent.key === SDLC_REVIEW_AGENT_KEY.POLICY_REVIEWER) {
const policy = policies[0] ?? null;
return {
type: SDLC_REVIEW_TARGET_TYPE.POLICY,
ref: policy?.id ?? orgId,
label: policy ? "policy" : "policy rule",
ready: Boolean(policy),
};
}
return { type: SDLC_REVIEW_TARGET_TYPE.WORKSPACE, ref: orgId, label: "workspace", ready: true };
}
+19
View File
@@ -11,6 +11,7 @@ import type {
RiskLevel,
RoleKey,
SearchEntityType,
SdlcReviewTargetType,
SocialPlatform,
TaskPriority,
TaskStatus,
@@ -288,6 +289,24 @@ export interface RunChaosDrillInput {
drillId: string;
}
export interface RunSdlcReviewInput {
orgId: string;
reviewAgentId: string;
targetType: SdlcReviewTargetType;
targetRef: string;
}
export interface RunEvalSuiteInput {
orgId: string;
suiteId: string;
}
export interface SetPrBotGateInput {
orgId: string;
gateId: string;
enabled: boolean;
}
export interface BootstrapOnboardingInput {
companyName: string;
companyDescription: string;
@@ -27,6 +27,8 @@ import type {
LaunchSocialLoginInput,
ResolveAgentSkillsInput,
RunChaosDrillInput,
RunEvalSuiteInput,
RunSdlcReviewInput,
SaveProviderCredentialInput,
SearchInput,
SendMessageInput,
@@ -34,6 +36,7 @@ import type {
SetFilePinnedInput,
SetFeatureFlagInput,
SetLegalHoldStateInput,
SetPrBotGateInput,
SetRetentionFreezeStateInput,
SetScheduledPostStateInput,
SyncEmployeeIdentityInput,
@@ -79,6 +82,9 @@ export interface DesktopApi {
createMigrationReport(input: CreateMigrationReportInput): Promise<DesktopResult<AppSnapshot>>;
setFeatureFlag(input: SetFeatureFlagInput): Promise<DesktopResult<AppSnapshot>>;
runChaosDrill(input: RunChaosDrillInput): Promise<DesktopResult<AppSnapshot>>;
runSdlcReview(input: RunSdlcReviewInput): Promise<DesktopResult<AppSnapshot>>;
runEvalSuite(input: RunEvalSuiteInput): Promise<DesktopResult<AppSnapshot>>;
setPrBotGate(input: SetPrBotGateInput): Promise<DesktopResult<AppSnapshot>>;
createComplianceExport(input: CreateComplianceExportInput): Promise<DesktopResult<AppSnapshot>>;
createLegalHold(input: CreateLegalHoldInput): Promise<DesktopResult<AppSnapshot>>;
setLegalHoldState(input: SetLegalHoldStateInput): Promise<DesktopResult<AppSnapshot>>;
+3
View File
@@ -4,12 +4,14 @@ import type { AccessDecisionRecord, EmployeeIdentityRecord, RoleGrantRecord, Ser
import type { ApprovalAssignmentRecord, ApprovalRoutingRuleRecord, HiringPolicyRecord, OrgDepartmentRecord } from "./org-governance-domain";
import type { ChaosDrillRecord, ChaosDrillRunRecord, FeatureFlagRecord, OperationalEventRecord, SloTargetRecord } from "./operations-domain";
import type { ProviderDataPolicy, ProviderExecutionMode } from "./provider-policy-domain";
import type { EvalCaseRecord, EvalRunRecord, EvalSuiteRecord, PrBotGateRecord, SdlcReviewAgentRecord, SdlcReviewRunRecord } from "./sdlc-domain";
export { BOARD_INTERVENTION_ACTION, BOARD_INTERVENTION_MODE, CHAT_THREAD_BOARD_STATE, type BoardInterventionAction, type BoardInterventionMode, type BoardInterventionRecord, type ChatThreadBoardState } from "./board-domain";
export { COMPLIANCE_EXPORT_STATE, COMPLIANCE_EXPORT_TYPE, type ComplianceExportRecord, type ComplianceExportState, type ComplianceExportType, type ComplianceSubjectType, type LegalHoldEntityType, type LegalHoldRecord, type RetentionFreezeRecord, type RetentionFreezeScopeType } from "./compliance-domain";
export { ACCESS_DECISION, IDENTITY_SOURCE, ROLE_KEY, type AccessDecision, type AccessDecisionRecord, type EmployeeIdentityRecord, type IdentitySource, type PrincipalType, type RoleGrantRecord, type RoleKey, type ServiceIdentityRecord } from "./identity-domain";
export { ROUTE_TO_TYPE, type ApprovalAssignmentRecord, type ApprovalRoutingRuleRecord, type HiringPolicyRecord, type OrgDepartmentRecord, type RouteToType } from "./org-governance-domain";
export { CHAOS_DRILL_SCENARIO, CHAOS_DRILL_STATE, FEATURE_FLAG_KEY, SLO_SIGNAL_KEY, type ChaosDrillRecord, type ChaosDrillRunRecord, type ChaosDrillScenario, type ChaosDrillState, type FeatureFlagKey, type FeatureFlagRecord, type OperationalEventRecord, type SloSignalKey, type SloTargetRecord } from "./operations-domain";
export { PROVIDER_DATA_POLICY, PROVIDER_EXECUTION_MODE, type ProviderDataPolicy, type ProviderExecutionMode } from "./provider-policy-domain";
export { EVAL_SUITE_KEY, PR_BOT_GATE_KEY, SDLC_REVIEW_AGENT_KEY, SDLC_REVIEW_TARGET_TYPE, SDLC_RUN_STATE, type EvalCaseRecord, type EvalRunRecord, type EvalSuiteKey, type EvalSuiteRecord, type PrBotGateKey, type PrBotGateRecord, type SdlcReviewAgentKey, type SdlcReviewAgentRecord, type SdlcReviewRunRecord, type SdlcReviewTargetType, type SdlcRunState } from "./sdlc-domain";
export { SEARCH_ENTITY_TYPES, type SearchEntityType, type SearchResultRecord } from "./search-domain";
export const TASK_STATUS = { BACKLOG: "backlog", IN_PROGRESS: "in_progress", REVIEW: "review", DONE: "done" } as const;
export const APPROVAL_STATE = { PENDING: "pending", APPROVED: "approved", DECLINED: "declined" } as const;
@@ -474,5 +476,6 @@ export interface AppSnapshot {
employeeIdentities: EmployeeIdentityRecord[]; serviceIdentities: ServiceIdentityRecord[]; roleGrants: RoleGrantRecord[]; accessDecisions: AccessDecisionRecord[];
orgDepartments: OrgDepartmentRecord[]; hiringPolicies: HiringPolicyRecord[]; approvalRoutingRules: ApprovalRoutingRuleRecord[]; approvalAssignments: ApprovalAssignmentRecord[];
featureFlags: FeatureFlagRecord[]; sloTargets: SloTargetRecord[]; operationalEvents: OperationalEventRecord[]; chaosDrills: ChaosDrillRecord[]; chaosDrillRuns: ChaosDrillRunRecord[];
sdlcReviewAgents: SdlcReviewAgentRecord[]; sdlcReviewRuns: SdlcReviewRunRecord[]; evalSuites: EvalSuiteRecord[]; evalCases: EvalCaseRecord[]; evalRuns: EvalRunRecord[]; prBotGates: PrBotGateRecord[];
auditEvents: AuditEventRecord[];
}
+3
View File
@@ -32,6 +32,9 @@ export const IPC_CHANNELS = {
createMigrationReport: "agentcompany:create-migration-report",
setFeatureFlag: "agentcompany:set-feature-flag",
runChaosDrill: "agentcompany:run-chaos-drill",
runSdlcReview: "agentcompany:run-sdlc-review",
runEvalSuite: "agentcompany:run-eval-suite",
setPrBotGate: "agentcompany:set-pr-bot-gate",
createComplianceExport: "agentcompany:create-compliance-export",
createLegalHold: "agentcompany:create-legal-hold",
setLegalHoldState: "agentcompany:set-legal-hold-state",
@@ -0,0 +1,48 @@
type ValueOf<T> = T[keyof T];
export const SDLC_REVIEW_AGENT_KEY = {
CODE_REVIEWER: "code_reviewer",
MIGRATION_REVIEWER: "migration_reviewer",
POLICY_REVIEWER: "policy_reviewer",
} as const;
export const SDLC_REVIEW_TARGET_TYPE = { WORKSPACE: "workspace", MIGRATION: "migration", POLICY: "policy" } as const;
export const EVAL_SUITE_KEY = { CONTRACT: "contract", INTEGRATION: "integration", REPLAY: "replay" } as const;
export const SDLC_RUN_STATE = { PASSED: "passed", FAILED: "failed" } as const;
export const PR_BOT_GATE_KEY = {
STATIC_CHECKS: "static_checks",
MIGRATION_DIFF: "migration_diff",
POLICY_DIFF: "policy_diff",
HUMAN_APPROVAL: "human_approval",
} as const;
export type SdlcReviewAgentKey = ValueOf<typeof SDLC_REVIEW_AGENT_KEY>;
export type SdlcReviewTargetType = ValueOf<typeof SDLC_REVIEW_TARGET_TYPE>;
export type EvalSuiteKey = ValueOf<typeof EVAL_SUITE_KEY>;
export type SdlcRunState = ValueOf<typeof SDLC_RUN_STATE>;
export type PrBotGateKey = ValueOf<typeof PR_BOT_GATE_KEY>;
export interface SdlcReviewAgentRecord {
id: string; orgId: string; key: SdlcReviewAgentKey; label: string; specialty: string; enabled: boolean; createdAt: string; updatedAt: string;
}
export interface SdlcReviewRunRecord {
id: string; orgId: string; reviewAgentId: string; targetType: SdlcReviewTargetType; targetRef: string;
state: SdlcRunState; resultJson: string; createdAt: string; completedAt: string;
}
export interface EvalSuiteRecord {
id: string; orgId: string; key: EvalSuiteKey; label: string; scope: string; createdAt: string; updatedAt: string;
}
export interface EvalCaseRecord {
id: string; orgId: string; suiteId: string; title: string; sourceType: string; sourceRef: string; expectedSignal: string; createdAt: string;
}
export interface EvalRunRecord {
id: string; orgId: string; suiteId: string; state: SdlcRunState; passedCount: number; failedCount: number; resultJson: string; createdAt: string; completedAt: string;
}
export interface PrBotGateRecord {
id: string; orgId: string; key: PrBotGateKey; label: string; gateType: string; enabled: boolean; createdAt: string; updatedAt: string;
}
+9 -1
View File
@@ -53,6 +53,14 @@ test("onboards, hires, and creates work in the Electron shell", async () => {
await expect(page.getByText("Chaos drills").first()).toBeVisible();
await page.getByRole("button", { name: "Run" }).first().click();
await expect(page.getByText("passed").first()).toBeVisible();
await page.getByRole("button", { name: "SDLC", exact: true }).click();
await expect(page.getByText("Review agents", { exact: true })).toBeVisible();
await expect(page.getByText("Eval suites", { exact: true })).toBeVisible();
await expect(page.getByText("PR bot gates", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "Run" }).first().click();
await expect(page.getByText("passed").first()).toBeVisible();
await page.getByRole("button", { name: "Run" }).nth(3).click();
await expect(page.getByText(/passed ·/).first()).toBeVisible();
await page.getByRole("button", { name: "Work", exact: true }).click();
await page.getByRole("button", { name: "New task" }).click();
@@ -134,7 +142,7 @@ async function verifyPagesDoNotOverflow(page: Page): Promise<void> {
await page.getByRole("button", { name: nav, exact: true }).click();
await expectPageToFit(page);
}
for (const tab of ["Workspace", "Members", "Billing", "Models", "Providers", "Skills", "Policies", "Operations", "Compliance", "Migration", "Profile", "Notifications", "Security"]) {
for (const tab of ["Workspace", "Members", "Billing", "Models", "Providers", "Skills", "Policies", "Operations", "SDLC", "Compliance", "Migration", "Profile", "Notifications", "Security"]) {
await page.getByRole("button", { name: "Settings" }).click();
await page.getByRole("button", { name: tab, exact: true }).click();
await expectPageToFit(page);
@@ -23,7 +23,7 @@ describe("domain services", () => {
const connection = createConnection();
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]);
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]);
connection.close();
});
@@ -51,6 +51,9 @@ describe("domain services", () => {
expect(state.featureFlags.map((flag) => flag.key)).toContain("chaos_drills");
expect(state.sloTargets.map((target) => target.signalKey)).toContain("provider_request_success");
expect(state.chaosDrills.map((drill) => drill.scenarioKey)).toContain("approval_backlog_gate");
expect(state.sdlcReviewAgents.map((agent) => agent.key)).toContain("code_reviewer");
expect(state.evalSuites.map((suite) => suite.key)).toContain("contract");
expect(state.prBotGates.map((gate) => gate.key)).toContain("static_checks");
expect(state.chatSpaces.map((space) => space.name)).toContain("board");
expect(state.policyRules[0]?.action).toBe("task_create");
expect(state.auditEvents[0]?.action).toBe("created");
@@ -113,6 +113,12 @@ describe("migration service", () => {
create table operational_events(id text primary key, org_id text);
create table chaos_drills(id text primary key, org_id text);
create table chaos_drill_runs(id text primary key, org_id text, drill_id text);
create table sdlc_review_agents(id text primary key, org_id text);
create table sdlc_review_runs(id text primary key, org_id text, review_agent_id text);
create table eval_suites(id text primary key, org_id text);
create table eval_cases(id text primary key, org_id text, suite_id text);
create table eval_runs(id text primary key, org_id text, suite_id text);
create table pr_bot_gates(id text primary key, org_id text);
create table chat_spaces(id text primary key, org_id text);
create table chat_messages(id text primary key, org_id text, space_id text, thread_id text);
create table chat_threads(id text primary key, org_id text, space_id text, root_message_id text);
@@ -173,6 +179,12 @@ describe("migration service", () => {
insert into operational_events(id, org_id) values ('op-1', 'org-1');
insert into chaos_drills(id, org_id) values ('drill-1', 'org-1');
insert into chaos_drill_runs(id, org_id, drill_id) values ('drill-run-1', 'org-1', 'drill-1');
insert into sdlc_review_agents(id, org_id) values ('reviewer-1', 'org-1');
insert into sdlc_review_runs(id, org_id, review_agent_id) values ('review-run-1', 'org-1', 'reviewer-1');
insert into eval_suites(id, org_id) values ('suite-1', 'org-1');
insert into eval_cases(id, org_id, suite_id) values ('case-1', 'org-1', 'suite-1');
insert into eval_runs(id, org_id, suite_id) values ('eval-run-1', 'org-1', 'suite-1');
insert into pr_bot_gates(id, org_id) values ('gate-1', 'org-1');
insert into chat_threads(id, org_id, space_id, root_message_id) values ('thread-1', 'org-1', 'space-1', null);
insert into chat_messages(id, org_id, space_id, thread_id) values ('msg-1', 'org-1', 'space-1', 'thread-1');
insert into chat_memberships(space_id, principal_type, principal_id) values ('space-1', 'employee', 'emp-1');
@@ -219,6 +231,8 @@ describe("migration service", () => {
expect(body.tableCounts.hiring_policies).toBe(1);
expect(body.tableCounts.feature_flags).toBe(1);
expect(body.tableCounts.chaos_drill_runs).toBe(1);
expect(body.tableCounts.sdlc_review_runs).toBe(1);
expect(body.tableCounts.eval_cases).toBe(1);
expect(body.tableCounts.role_grants).toBe(1);
expect(body.tableCounts.skill_artifacts).toBe(1);
expect(body.tableCounts.chat_read_state).toBe(1);
@@ -235,6 +249,8 @@ describe("migration service", () => {
expect(body.idMaps.approval_assignments).toEqual(["assign-1"]);
expect(body.idMaps.operational_events).toEqual(["op-1"]);
expect(body.idMaps.chaos_drill_runs).toEqual(["drill-run-1"]);
expect(body.idMaps.sdlc_review_runs).toEqual(["review-run-1"]);
expect(body.idMaps.eval_runs).toEqual(["eval-run-1"]);
expect(body.idMaps.board_interventions).toEqual(["bdi-1"]);
expect(body.idMaps.retention_freezes).toEqual(["freeze-1"]);
expect(body.idMaps.provider_usage_windows).toEqual(["usage-1"]);
@@ -0,0 +1,88 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { SdlcService } from "../../src/main/services/sdlc-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { SDLC_REVIEW_TARGET_TYPE } from "../../src/shared/domain";
function createConnection(): DatabaseConnection {
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-sdlc-${randomUUID()}.db`));
}
function createOrganization(organization: OrganizationService): string {
return organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
}
describe("SDLC service", () => {
test("seeds review agents, eval suites, eval cases, and PR bot gates", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
connection.transaction(() => {
createOrganization(organization);
});
const state = snapshot.load();
expect(state.sdlcReviewAgents.map((agent) => agent.key).sort()).toEqual(["code_reviewer", "migration_reviewer", "policy_reviewer"]);
expect(state.evalSuites.map((suite) => suite.key).sort()).toEqual(["contract", "integration", "replay"]);
expect(state.evalCases).toHaveLength(6);
expect(state.prBotGates.map((gate) => gate.key).sort()).toEqual(["human_approval", "migration_diff", "policy_diff", "static_checks"]);
connection.close();
});
test("runs review agents against validated workspace targets", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const sdlc = new SdlcService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let reviewAgentId = "";
connection.transaction(() => {
orgId = createOrganization(organization);
reviewAgentId = (connection.db.prepare("select id from sdlc_review_agents where org_id = ? and key = ?")
.get(orgId, "code_reviewer") as { id: string }).id;
sdlc.runReview({ orgId, reviewAgentId, targetType: SDLC_REVIEW_TARGET_TYPE.WORKSPACE, targetRef: orgId });
});
const state = snapshot.load();
expect(state.sdlcReviewRuns[0]).toMatchObject({ orgId, reviewAgentId, targetType: SDLC_REVIEW_TARGET_TYPE.WORKSPACE, state: "passed" });
expect(state.auditEvents.some((event) => event.entityType === "sdlc_review_run" && event.action === "created")).toBe(true);
connection.close();
});
test("runs contract eval suites and toggles PR bot gates", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const sdlc = new SdlcService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let suiteId = "";
let gateId = "";
connection.transaction(() => {
orgId = createOrganization(organization);
suiteId = (connection.db.prepare("select id from eval_suites where org_id = ? and key = ?")
.get(orgId, "contract") as { id: string }).id;
gateId = (connection.db.prepare("select id from pr_bot_gates where org_id = ? and key = ?")
.get(orgId, "static_checks") as { id: string }).id;
sdlc.runEvalSuite({ orgId, suiteId });
sdlc.setPrBotGate({ orgId, gateId, enabled: false });
});
const state = snapshot.load();
expect(state.evalRuns[0]).toMatchObject({ orgId, suiteId, state: "passed", failedCount: 0 });
expect(state.prBotGates.find((gate) => gate.id === gateId)?.enabled).toBe(false);
expect(state.auditEvents.some((event) => event.entityType === "pr_bot_gate" && event.action === "updated")).toBe(true);
connection.close();
});
});