From 12c8402644e0c9e0aabdc14886512f3110f3f42e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 26 Jun 2026 18:19:05 -0400 Subject: [PATCH] Extract organization seeding into its own module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OrganizationService was 552 lines, over the 500-line ceiling. The 80-line createOrganization routine — seed the org, the Board Chair, identity/governance/operations/SDLC/policy defaults, starter channels, the provider catalog — is a self-contained concern. Move it to organization-seed.ts; the service now delegates and drops the two service fields (operations, sdlc) only that routine used. Pure relocation, behaviour identical: 469 lines, full suite and e2e green. --- .../src/main/services/organization-seed.ts | 105 ++++++++++++++++++ .../src/main/services/organization-service.ts | 87 +-------------- 2 files changed, 107 insertions(+), 85 deletions(-) create mode 100644 AgentCompany-New/src/main/services/organization-seed.ts diff --git a/AgentCompany-New/src/main/services/organization-seed.ts b/AgentCompany-New/src/main/services/organization-seed.ts new file mode 100644 index 00000000..74be85a6 --- /dev/null +++ b/AgentCompany-New/src/main/services/organization-seed.ts @@ -0,0 +1,105 @@ +import type { DatabaseSync } from "node:sqlite"; +import type { CreateOrganizationInput } from "../../shared/commands"; +import { PROVIDER_CATALOG } from "../../shared/provider-catalog"; +import { createId } from "../../shared/ids"; +import { AuditService } from "./audit-service"; +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"; + +/** + * Seeds a brand-new organization in one transaction's worth of writes: the org + * row and its Board Chair employee, the identity / governance / operations / + * SDLC / policy defaults, the starter chat channels, the disabled provider + * catalog, and the active-org pointer. Returns the new organization id. + */ +export function seedOrganization(db: DatabaseSync, input: CreateOrganizationInput): string { + const audit = new AuditService(db); + const policy = new PolicyService(db); + const identityAccess = new IdentityAccessService(db); + const governance = new OrgGovernanceService(db); + const operations = new OperationsService(db); + const sdlc = new SdlcService(db); + + assertUsableMentionName(input.employeeName); + const orgId = createId("org"); + const employeeId = createId("emp"); + const now = new Date().toISOString(); + + db.prepare(` + insert into organizations( + id, name, mission, goal, description, execution_mode, auto_approve_hires, review_deliverables, created_at + ) + values (?, ?, ?, ?, ?, ?, ?, ?, ?) + `).run( + orgId, + input.organizationName.trim(), + input.mission.trim(), + input.goal.trim(), + input.description?.trim() ?? "", + input.executionMode ?? "ai_ceo", + input.autoApproveHires === false ? 0 : 1, + input.reviewDeliverables === true ? 1 : 0, + now, + ); + db.prepare("insert into employees(id, org_id, display_name, role, created_at) values (?, ?, ?, ?, ?)") + .run(employeeId, orgId, input.employeeName.trim(), "Board Chair", now); + identityAccess.seedEmployeeAccess(orgId, employeeId, now); + governance.seedOrganizationGovernance(orgId, employeeId, now); + operations.seedOrganizationOperations(orgId, now); + sdlc.seedOrganizationSdlc(orgId, now); + + for (const space of [ + ["company", "general", "Company coordination", "workspace"], + ["department", "product", "Product decisions", "workspace"], + ["department", "engineering", "Engineering delivery", "workspace"], + ["company", "board", "Board decisions", "restricted"], + ]) { + const spaceId = createId("spc"); + db.prepare(` + insert into chat_spaces(id, org_id, space_type, name, topic, visibility, created_at) + values (?, ?, ?, ?, ?, ?, ?) + `).run(spaceId, orgId, space[0], space[1], space[2], space[3], now); + db.prepare(` + insert into chat_memberships(space_id, principal_type, principal_id, role, mute_state) + values (?, ?, ?, ?, ?) + `).run(spaceId, "employee", employeeId, space[1] === "board" ? "approver" : "member", "unmuted"); + } + + for (const provider of PROVIDER_CATALOG) { + db.prepare(` + insert into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at) + values (?, ?, ?, ?, ?, ?, ?, ?, ?) + `).run( + createId("prv"), + orgId, + provider.key, + provider.label, + provider.authType, + provider.defaultModel, + null, + 0, + now, + ); + } + + policy.seedOrganizationPolicies(orgId, now); + db.prepare(` + insert into app_state(id, active_org_id, updated_at) + values (1, ?, ?) + on conflict(id) do update set active_org_id = excluded.active_org_id, updated_at = excluded.updated_at + `).run(orgId, now); + audit.record({ + orgId, + entityType: "organization", + entityId: orgId, + actorType: "employee", + actorId: employeeId, + action: "created", + payload: { name: input.organizationName.trim() }, + }); + return orgId; +} diff --git a/AgentCompany-New/src/main/services/organization-service.ts b/AgentCompany-New/src/main/services/organization-service.ts index c640f68f..bec22ed9 100644 --- a/AgentCompany-New/src/main/services/organization-service.ts +++ b/AgentCompany-New/src/main/services/organization-service.ts @@ -1,6 +1,5 @@ import type { DatabaseSync } from "node:sqlite"; import { APPROVAL_STATE, RISK_LEVEL, TASK_STATUS, type ApprovalState } from "../../shared/domain"; -import { PROVIDER_CATALOG } from "../../shared/provider-catalog"; import { createId } from "../../shared/ids"; import type { CreateAgentInput, CreateGoalInput, CreateOrganizationInput, CreateProjectInput, CreateTaskInput, CreateWorkspaceInput, DeleteTaskInput, UpdateAgentInput, UpdateTaskInput } from "../../shared/commands"; import { AuditService } from "./audit-service"; @@ -8,106 +7,24 @@ import { ensureAgentHeartbeat, resolveGoalKickoff, type GoalKickoffPlan } from " 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"; +import { seedOrganization } from "./organization-seed"; export class OrganizationService { private readonly audit: AuditService; private readonly policy: PolicyService; 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); this.policy = new PolicyService(db); this.identityAccess = new IdentityAccessService(db); this.governance = new OrgGovernanceService(db); - this.operations = new OperationsService(db); - this.sdlc = new SdlcService(db); } createOrganization(input: CreateOrganizationInput): string { - assertUsableMentionName(input.employeeName); - const orgId = createId("org"); - const employeeId = createId("emp"); - const now = new Date().toISOString(); - - this.db.prepare(` - insert into organizations( - id, name, mission, goal, description, execution_mode, auto_approve_hires, review_deliverables, created_at - ) - values (?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run( - orgId, - input.organizationName.trim(), - input.mission.trim(), - input.goal.trim(), - input.description?.trim() ?? "", - input.executionMode ?? "ai_ceo", - input.autoApproveHires === false ? 0 : 1, - input.reviewDeliverables === true ? 1 : 0, - now, - ); - this.db.prepare("insert into employees(id, org_id, display_name, role, created_at) values (?, ?, ?, ?, ?)") - .run(employeeId, orgId, input.employeeName.trim(), "Board Chair", now); - 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"], - ["department", "product", "Product decisions", "workspace"], - ["department", "engineering", "Engineering delivery", "workspace"], - ["company", "board", "Board decisions", "restricted"], - ]) { - const spaceId = createId("spc"); - this.db.prepare(` - insert into chat_spaces(id, org_id, space_type, name, topic, visibility, created_at) - values (?, ?, ?, ?, ?, ?, ?) - `).run(spaceId, orgId, space[0], space[1], space[2], space[3], now); - this.db.prepare(` - insert into chat_memberships(space_id, principal_type, principal_id, role, mute_state) - values (?, ?, ?, ?, ?) - `).run(spaceId, "employee", employeeId, space[1] === "board" ? "approver" : "member", "unmuted"); - } - - for (const provider of PROVIDER_CATALOG) { - this.db.prepare(` - insert into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at) - values (?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run( - createId("prv"), - orgId, - provider.key, - provider.label, - provider.authType, - provider.defaultModel, - null, - 0, - now, - ); - } - - this.policy.seedOrganizationPolicies(orgId, now); - this.db.prepare(` - insert into app_state(id, active_org_id, updated_at) - values (1, ?, ?) - on conflict(id) do update set active_org_id = excluded.active_org_id, updated_at = excluded.updated_at - `).run(orgId, now); - this.audit.record({ - orgId, - entityType: "organization", - entityId: orgId, - actorType: "employee", - actorId: employeeId, - action: "created", - payload: { name: input.organizationName.trim() }, - }); - return orgId; + return seedOrganization(this.db, input); } setActiveOrganization(orgId: string): void {