@team-semicolon/semicolony-cli 4.18.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/LICENSE +200 -0
  2. package/NOTICE +8 -0
  3. package/README.md +152 -0
  4. package/dist/bundle.js +2887 -0
  5. package/dist/templates/launchd/ai.semicolony.host-signals-sidecar.plist +40 -0
  6. package/migrations/001_initial.sql +280 -0
  7. package/migrations/002_add_indexes.sql +11 -0
  8. package/migrations/003_add_fk.sql +32 -0
  9. package/migrations/004_session_count_trigger.sql +35 -0
  10. package/migrations/005_bot_workspace_audits.sql +16 -0
  11. package/migrations/006_bot_kb_subscriptions.sql +24 -0
  12. package/migrations/007_flatten_skill_names.sql +16 -0
  13. package/migrations/008_bot_workspace_files.sql +14 -0
  14. package/migrations/009_enforce_flat_skill_names.sql +53 -0
  15. package/migrations/010_bot_delegation.sql +27 -0
  16. package/migrations/011_drop_legacy_tables.sql +35 -0
  17. package/migrations/012_drop_bot_knowledge.sql +16 -0
  18. package/migrations/013_kb_workspace_domains.sql +88 -0
  19. package/migrations/014_timestamp_to_timestamptz.sql +33 -0
  20. package/migrations/015_kb_domain_enforcement.sql +83 -0
  21. package/migrations/016_ontology_redesign.sql +108 -0
  22. package/migrations/017_ontology_instance_model.sql +151 -0
  23. package/migrations/018_type_schema.sql +11 -0
  24. package/migrations/019_test_management.sql +69 -0
  25. package/migrations/020_bot_workspace_standard.sql +174 -0
  26. package/migrations/021_drop_dead_tables.sql +8 -0
  27. package/migrations/021_test_declarative.sql +19 -0
  28. package/migrations/022_bot_ontology_type.sql +38 -0
  29. package/migrations/022_kb_schema_enforcement.sql +77 -0
  30. package/migrations/023_decision_key_cleanup.ts +154 -0
  31. package/migrations/024_embedding_backfill.ts +137 -0
  32. package/migrations/025_kb_flat_keys.sql +33 -0
  33. package/migrations/026_drop_session_log.sql +19 -0
  34. package/migrations/027_kb_manager_skill.sql +22 -0
  35. package/migrations/028_skill_definitions_to_semicolony.sql +19 -0
  36. package/migrations/029_agents_citation_max_lines.sql +6 -0
  37. package/migrations/030_team_ontology.sql +54 -0
  38. package/migrations/031_gfp_tables.sql +101 -0
  39. package/migrations/031_kb_manager_domain_register.sql +12 -0
  40. package/migrations/032_gfp_skill_and_callback.sql +40 -0
  41. package/migrations/032_kebab_case_keys.sql +33 -0
  42. package/migrations/033_skill_workspace_rules.sql +10 -0
  43. package/migrations/034_current_to_date_keys.sql +13 -0
  44. package/migrations/034_current_to_date_keys.ts +104 -0
  45. package/migrations/035_bot_budgets.sql +51 -0
  46. package/migrations/036_gfp_stitch_design.sql +35 -0
  47. package/migrations/037_gfp_qa_sections.sql +13 -0
  48. package/migrations/038_gfp_phase_split.sql +26 -0
  49. package/migrations/039_meeting_audio_data.sql +2 -0
  50. package/migrations/040_bot_commitments.sql +68 -0
  51. package/migrations/041_palette_previews.sql +13 -0
  52. package/migrations/042_gfp_parallel_tracks.sql +37 -0
  53. package/migrations/043_blocker_key.sql +13 -0
  54. package/migrations/044_gfp_stitch_screenshots.sql +9 -0
  55. package/migrations/045_service_table_rename_and_projection.sql +150 -0
  56. package/migrations/046_service_ops_tables.sql +75 -0
  57. package/migrations/047_update_gfp_manager_skill.sql +16 -0
  58. package/migrations/048_service_migration_skill.sql +23 -0
  59. package/migrations/049_service_features.sql +39 -0
  60. package/migrations/050_service_kpi_action_items.sql +87 -0
  61. package/migrations/051_update_service_migration_skill_prompt.sql +9 -0
  62. package/migrations/052_feature_iteration_link.sql +11 -0
  63. package/migrations/052_services_table_rename.sql +149 -0
  64. package/migrations/053_feature_discovery.sql +47 -0
  65. package/migrations/054_deploy_verification.sql +17 -0
  66. package/migrations/055_feature_lifecycle.sql +37 -0
  67. package/migrations/056_service_incident_schema.sql +16 -0
  68. package/migrations/057_decision_date_to_metadata.ts +173 -0
  69. package/migrations/058_incubator_kb_schema.sql +8 -0
  70. package/migrations/059_incubator_sessions.sql +29 -0
  71. package/migrations/060_skill_reference_files.sql +8 -0
  72. package/migrations/061_cron_trigger_bridge.sql +7 -0
  73. package/migrations/062_bot_slack_profiles.sql +21 -0
  74. package/migrations/063_rename_gfp_skills.sql +7 -0
  75. package/migrations/064_bot_cost_log.sql +11 -0
  76. package/migrations/065_hub_spoke_orchestration.sql +35 -0
  77. package/migrations/066_cost_log_commitment.sql +9 -0
  78. package/migrations/067_module_ontology_type.sql +44 -0
  79. package/migrations/068_migrate_incubator_data.sql +32 -0
  80. package/migrations/069_action_items_sot.sql +156 -0
  81. package/migrations/069_cost_log_tokens.sql +45 -0
  82. package/migrations/070_services_sot_columns.sql +78 -0
  83. package/migrations/071_cleanup_deprecated_service_kb.sql +38 -0
  84. package/migrations/072_incubator_heartbeat.sql +12 -0
  85. package/migrations/072_service_role_schema.sql +57 -0
  86. package/migrations/073_service_type_and_platform.sql +8 -0
  87. package/migrations/074_schema_ref_type.sql +16 -0
  88. package/migrations/075_routing_delegation.sql +34 -0
  89. package/migrations/076_meeting_service_link.sql +5 -0
  90. package/migrations/077_agents_ontology_type.sql +93 -0
  91. package/migrations/078_incubator_agent.sql +45 -0
  92. package/migrations/079_drop_audio_blob_url.sql +4 -0
  93. package/migrations/080_kb_metadata_indexing.sql +9 -0
  94. package/migrations/081_person_unification.sql +41 -0
  95. package/migrations/082_discord_channel.sql +3 -0
  96. package/migrations/084_deprecate_kpi_metrics_table.sql +5 -0
  97. package/migrations/085_meeting_target_domain.sql +37 -0
  98. package/migrations/086_skill_routing_new_skills.sql +23 -0
  99. package/migrations/087_skill_routing_second_opinion.sql +8 -0
  100. package/migrations/088_meeting_notion_sync.sql +10 -0
  101. package/migrations/089_bot_commitments_dedup.sql +17 -0
  102. package/migrations/090_incubator_discord_guild.sql +8 -0
  103. package/migrations/091_drop_deprecated_gfp_kpi_views.sql +65 -0
  104. package/migrations/092_ontology_channel_mapping.sql +28 -0
  105. package/migrations/093_update_bot_workspace_paths.sql +15 -0
  106. package/migrations/094_cron_run_tracking.sql +28 -0
  107. package/migrations/095_drop_bot_cron_runs.sql +7 -0
  108. package/migrations/096_kb_pipeline_keys.sql +66 -0
  109. package/migrations/097_drop_empty_service_tables.sql +83 -0
  110. package/migrations/098_migrate_features_materials_drop.sql +119 -0
  111. package/migrations/099_migrate_sections_drop.sql +52 -0
  112. package/migrations/099_seed_cron_poller_tick.sql +14 -0
  113. package/migrations/100_migrate_services_drop.sql +57 -0
  114. package/migrations/101_drop_services.sql +5 -0
  115. package/migrations/102_cost_log_session_dedup.sql +13 -0
  116. package/migrations/103_board.sql +57 -0
  117. package/migrations/104_agent_factory.sql +284 -0
  118. package/migrations/105_kb_notify.sql +44 -0
  119. package/migrations/106_office_id_agents_commands.sql +73 -0
  120. package/migrations/107_agent_service_credentials.sql +79 -0
  121. package/migrations/108_drop_l2_ontology_seeds.sql +16 -0
  122. package/migrations/109_generic_ontology_descriptions.sql +21 -0
  123. package/migrations/110_kb_manager_skill_l2_cleanup.sql +34 -0
  124. package/migrations/111_drop_l2_team_ontology_seeds.sql +20 -0
  125. package/migrations/112_fix_pipeline_schema_and_role_trigger.sql +83 -0
  126. package/migrations/113_kb_history.sql +53 -0
  127. package/migrations/114_runtime_target_columns.sql +26 -0
  128. package/migrations/115_runtime_source_columns.sql +52 -0
  129. package/migrations/116_commitment_pattern_health.sql +81 -0
  130. package/migrations/117_commitment_pattern_alert_claims.sql +33 -0
  131. package/migrations/118_retire_semiclaw_semicolonybot_alias.sql +30 -0
  132. package/migrations/119_commitment_events.sql +51 -0
  133. package/migrations/120_host_signals.sql +51 -0
  134. package/migrations/121_agent_spec_derived_runtime_metadata.sql +68 -0
  135. package/migrations/122_agent_spec_derived_fk_policy.sql +18 -0
  136. package/migrations/123_agent_spec_projection_skip_targets.sql +24 -0
  137. package/migrations/124_bot_commitments_notify.sql +26 -0
  138. package/migrations/125_agent_personas.sql +36 -0
  139. package/migrations/128_entity_relations.sql +125 -0
  140. package/migrations/129_kb_multitenant_gateway.sql +86 -0
  141. package/migrations/130_jewelry_relation_types.sql +36 -0
  142. package/migrations/131_concierge_requests.sql +38 -0
  143. package/migrations/132_kb_supersession.sql +27 -0
  144. package/migrations/133_kb_claims.sql +52 -0
  145. package/migrations/134_migration_registry.sql +18 -0
  146. package/migrations/135_sc_roles.sql +102 -0
  147. package/migrations/136_runtime_tenant_columns.sql +71 -0
  148. package/migrations/137_runtime_backfill_team_semicolon.sql +131 -0
  149. package/migrations/138_runtime_constraints_not_valid.sql +56 -0
  150. package/migrations/139_tenant_vms.sql +31 -0
  151. package/migrations/140_fleet_slots.sql +23 -0
  152. package/migrations/141_provisioning_jobs.sql +29 -0
  153. package/migrations/142_rls_enable_semicolony.sql +129 -0
  154. package/migrations/143_rls_enable_public.sql +54 -0
  155. package/migrations/144_worker_device_registry.sql +211 -0
  156. package/migrations/145_workspace_profile_workstation_policy.sql +39 -0
  157. package/migrations/146_worker_profile_repo_scope.sql +24 -0
  158. package/migrations/147_worker_profile_semo_ops_repo_rename.sql +18 -0
  159. package/migrations/148_momo_runtime_convergence.sql +92 -0
  160. package/migrations/README.md +88 -0
  161. package/migrations-sqlite/kb/001_initial.sql +56 -0
  162. package/migrations-sqlite/ops/001_initial.sql +53 -0
  163. package/migrations-sqlite/ops/002_action_items.sql +48 -0
  164. package/package.json +78 -0
@@ -0,0 +1,57 @@
1
+ -- 100: Phase 4 — services 허브 테이블 (17건) KB 이식 + DROP
2
+ -- 코드는 이미 KB pipeline/config 기반으로 전환됨
3
+
4
+ -- 1. services → KB pipeline/config
5
+ -- drift 복구: 이미 DROP 된 소스 테이블은 to_regclass 가드로 skip (2026-04-23)
6
+ DO $$
7
+ DECLARE
8
+ rec RECORD;
9
+ BEGIN
10
+ IF to_regclass('semicolony.services') IS NULL THEN
11
+ RAISE NOTICE '100-1 skipped: services already dropped';
12
+ RETURN;
13
+ END IF;
14
+ FOR rec IN SELECT * FROM semicolony.services WHERE service_domain IS NOT NULL LOOP
15
+ INSERT INTO semicolony.knowledge_base (domain, key, sub_key, content, created_by, metadata)
16
+ VALUES (
17
+ rec.service_domain, 'pipeline', 'config',
18
+ COALESCE(rec.project_name, ''),
19
+ 'migration-100',
20
+ jsonb_build_object(
21
+ 'service_id', rec.service_id,
22
+ 'project_name', COALESCE(rec.project_name, ''),
23
+ 'owner_name', COALESCE(rec.owner_name, ''),
24
+ 'owner_contact', rec.owner_contact,
25
+ 'current_phase', COALESCE(rec.current_phase, 0),
26
+ 'infra_phase', rec.infra_phase,
27
+ 'status', COALESCE(rec.status, 'active'),
28
+ 'lifecycle', COALESCE(rec.lifecycle, 'build'),
29
+ 'service_type', COALESCE(rec.service_type, 'incubator'),
30
+ 'parent_service_id', rec.parent_service_id,
31
+ 'tech_stack', rec.tech_stack,
32
+ 'service_url', rec.service_url,
33
+ 'bm', rec.bm,
34
+ 'repo', rec.repo,
35
+ 'slack_channel', rec.slack_channel,
36
+ 'launched_at', rec.launched_at::text,
37
+ 'project_metadata', COALESCE(rec.metadata, '{}'::jsonb),
38
+ 'created_at', rec.created_at::text
39
+ )
40
+ )
41
+ ON CONFLICT (domain, key, sub_key) DO UPDATE SET
42
+ content = EXCLUDED.content,
43
+ metadata = EXCLUDED.metadata,
44
+ updated_at = NOW();
45
+ END LOOP;
46
+ END $$;
47
+
48
+ -- 2. 레거시 VIEW DROP
49
+ DROP VIEW IF EXISTS semicolony.service_projects CASCADE;
50
+ DROP VIEW IF EXISTS semicolony.gfp_projects CASCADE;
51
+ DROP VIEW IF EXISTS semicolony.gfp_phase_sections CASCADE;
52
+ DROP VIEW IF EXISTS semicolony.gfp_materials CASCADE;
53
+ DROP VIEW IF EXISTS semicolony.gfp_research_tasks CASCADE;
54
+ DROP VIEW IF EXISTS semicolony.gfp_infra_requests CASCADE;
55
+
56
+ -- 3. services 테이블 DROP → 101_drop_services.sql로 분리
57
+ -- sandbox.ts 에서 semicolony.services 참조(8곳)가 KB 전환 완료된 후 실행
@@ -0,0 +1,5 @@
1
+ -- 101: services 허브 테이블 최종 DROP
2
+ -- 전제 조건: sandbox.ts의 semicolony.services 참조(8곳)가 KB 기반으로 전환 완료되어야 함
3
+ -- migration 100에서 데이터 이식은 이미 완료됨
4
+
5
+ DROP TABLE IF EXISTS semicolony.services CASCADE;
@@ -0,0 +1,13 @@
1
+ -- 102: bot_cost_log에 세션/메시지 ID 추가 — Stop 훅 기반 턴별 토큰 추적 dedup용
2
+
3
+ BEGIN;
4
+
5
+ ALTER TABLE semicolony.bot_cost_log
6
+ ADD COLUMN IF NOT EXISTS session_id TEXT,
7
+ ADD COLUMN IF NOT EXISTS message_id TEXT;
8
+
9
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_cost_log_dedup
10
+ ON semicolony.bot_cost_log (session_id, message_id)
11
+ WHERE message_id IS NOT NULL;
12
+
13
+ COMMIT;
@@ -0,0 +1,57 @@
1
+ -- 103: 파일 게시판 (자료실) — Dashboard CRUD + Introduction 공개 조회
2
+ -- 권한 enforce는 API 레이어에서 수행 (pg 직접 접근, RLS 미사용)
3
+ -- 파일 바이너리는 bytea로 저장 (meetings.audio_data 패턴 재사용)
4
+
5
+ BEGIN;
6
+
7
+ CREATE TABLE IF NOT EXISTS semicolony.board_categories (
8
+ slug TEXT PRIMARY KEY,
9
+ label TEXT NOT NULL,
10
+ sort_order INT DEFAULT 0,
11
+ is_public_allowed BOOLEAN NOT NULL DEFAULT TRUE,
12
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
13
+ );
14
+
15
+ CREATE TABLE IF NOT EXISTS semicolony.board_posts (
16
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
17
+ title TEXT NOT NULL,
18
+ description TEXT,
19
+ category_slug TEXT NOT NULL REFERENCES semicolony.board_categories(slug),
20
+ uploader_id UUID,
21
+ uploader_email TEXT,
22
+ uploader_name_snapshot TEXT,
23
+ is_public BOOLEAN NOT NULL DEFAULT FALSE,
24
+ is_hidden BOOLEAN NOT NULL DEFAULT FALSE,
25
+ view_count INT NOT NULL DEFAULT 0,
26
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
27
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
28
+ );
29
+
30
+ CREATE TABLE IF NOT EXISTS semicolony.board_attachments (
31
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
32
+ post_id UUID NOT NULL REFERENCES semicolony.board_posts(id) ON DELETE CASCADE,
33
+ file_name TEXT NOT NULL,
34
+ mime_type TEXT NOT NULL,
35
+ size_bytes BIGINT NOT NULL,
36
+ file_data BYTEA NOT NULL,
37
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
38
+ );
39
+
40
+ CREATE INDEX IF NOT EXISTS idx_board_posts_public
41
+ ON semicolony.board_posts (is_public, created_at DESC)
42
+ WHERE is_hidden = FALSE;
43
+
44
+ CREATE INDEX IF NOT EXISTS idx_board_posts_category
45
+ ON semicolony.board_posts (category_slug, created_at DESC);
46
+
47
+ CREATE INDEX IF NOT EXISTS idx_board_attachments_post
48
+ ON semicolony.board_attachments (post_id);
49
+
50
+ -- 시드 카테고리
51
+ INSERT INTO semicolony.board_categories (slug, label, sort_order, is_public_allowed) VALUES
52
+ ('materials', '자료/문서', 10, TRUE),
53
+ ('press', '보도자료/외부 공개', 20, TRUE),
54
+ ('recruiting', '채용/회사 소개', 30, TRUE)
55
+ ON CONFLICT (slug) DO NOTHING;
56
+
57
+ COMMIT;
@@ -0,0 +1,284 @@
1
+ -- 104: Agent Factory 기반 마련
2
+ -- 1) bot_status에 Factory 메타 컬럼 추가 (slack/kb/budget/template 하드코딩 제거 준비)
3
+ -- 2) bot_id_aliases: 불변 bot_id + 표시명 변경을 위한 별칭 테이블
4
+ -- 3) bot_seats: Claude Max seat 물리/논리 분리 (런타임 동적 할당)
5
+ -- 4) semiclaw → semicolonybot 리브랜드 1단계 (새 row + alias 활성화)
6
+ -- 5) NOTIFY 트리거: bot_status / bot_delegation / bot_id_aliases 변경 시 핫 리로드 시그널
7
+
8
+ BEGIN;
9
+
10
+ -- ============================================================
11
+ -- 1. bot_status 컬럼 확장
12
+ -- ============================================================
13
+
14
+ ALTER TABLE semicolony.bot_status
15
+ ADD COLUMN IF NOT EXISTS kb_domains TEXT[],
16
+ ADD COLUMN IF NOT EXISTS budget_per_message NUMERIC(6,3),
17
+ ADD COLUMN IF NOT EXISTS slack_username TEXT,
18
+ ADD COLUMN IF NOT EXISTS slack_icon_emoji TEXT,
19
+ ADD COLUMN IF NOT EXISTS created_by_bot_id TEXT,
20
+ ADD COLUMN IF NOT EXISTS template_bot_id TEXT;
21
+
22
+ -- 백필 — 기존 하드코딩 맵(packages/common/src/slack/bot-config.ts)을 DB로 이식
23
+ -- kb_domains: orchestrator는 빈 배열(전체 허용), 나머지는 명시적 리스트
24
+ -- budget_per_message: 봇별 기본 예산
25
+ -- slack_username/icon_emoji: FALLBACK_SLACK_PROFILES
26
+ UPDATE semicolony.bot_status SET
27
+ kb_domains = '{}',
28
+ budget_per_message = 1.5,
29
+ slack_username = 'SemiClaw',
30
+ slack_icon_emoji = ':clipboard:',
31
+ created_by_bot_id = '__genesis__'
32
+ WHERE bot_id = 'semiclaw';
33
+
34
+ UPDATE semicolony.bot_status SET
35
+ kb_domains = ARRAY['semicolon','semicolony'],
36
+ budget_per_message = 1.0,
37
+ slack_username = 'PlanClaw',
38
+ slack_icon_emoji = ':bar_chart:',
39
+ created_by_bot_id = '__genesis__'
40
+ WHERE bot_id = 'planclaw';
41
+
42
+ UPDATE semicolony.bot_status SET
43
+ kb_domains = ARRAY['semicolon'],
44
+ budget_per_message = 1.0,
45
+ slack_username = 'DesignClaw',
46
+ slack_icon_emoji = ':art:',
47
+ created_by_bot_id = '__genesis__'
48
+ WHERE bot_id = 'designclaw';
49
+
50
+ UPDATE semicolony.bot_status SET
51
+ kb_domains = ARRAY['semicolon'],
52
+ budget_per_message = 1.5,
53
+ slack_username = 'WorkClaw',
54
+ slack_icon_emoji = ':hammer_and_wrench:',
55
+ created_by_bot_id = '__genesis__'
56
+ WHERE bot_id = 'workclaw';
57
+
58
+ UPDATE semicolony.bot_status SET
59
+ kb_domains = ARRAY['semicolon'],
60
+ budget_per_message = 0.8,
61
+ slack_username = 'ReviewClaw',
62
+ slack_icon_emoji = ':mag:',
63
+ created_by_bot_id = '__genesis__'
64
+ WHERE bot_id = 'reviewclaw';
65
+
66
+ UPDATE semicolony.bot_status SET
67
+ kb_domains = ARRAY['semicolon','semicolony'],
68
+ budget_per_message = 1.0,
69
+ slack_username = 'InfraClaw',
70
+ slack_icon_emoji = ':gear:',
71
+ created_by_bot_id = '__genesis__'
72
+ WHERE bot_id = 'infraclaw';
73
+
74
+ UPDATE semicolony.bot_status SET
75
+ kb_domains = ARRAY['semicolon'],
76
+ budget_per_message = 0.5,
77
+ slack_username = 'GrowthClaw',
78
+ slack_icon_emoji = ':chart_with_upwards_trend:',
79
+ created_by_bot_id = '__genesis__'
80
+ WHERE bot_id = 'growthclaw';
81
+
82
+ UPDATE semicolony.bot_status SET
83
+ kb_domains = ARRAY['semicolon'],
84
+ budget_per_message = 1.0,
85
+ slack_username = 'Incubator',
86
+ slack_icon_emoji = ':hatching_chick:',
87
+ created_by_bot_id = '__genesis__'
88
+ WHERE bot_id = 'incubator';
89
+
90
+ -- ============================================================
91
+ -- 2. bot_id_aliases — 표시명 변경 시 기존 bot_id 참조 보존
92
+ -- ============================================================
93
+
94
+ CREATE TABLE IF NOT EXISTS semicolony.bot_id_aliases (
95
+ alias TEXT PRIMARY KEY,
96
+ canonical_bot_id TEXT NOT NULL REFERENCES semicolony.bot_status(bot_id),
97
+ retired_at TIMESTAMPTZ,
98
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
99
+ );
100
+
101
+ CREATE INDEX IF NOT EXISTS idx_bot_aliases_canonical
102
+ ON semicolony.bot_id_aliases (canonical_bot_id);
103
+
104
+ -- ============================================================
105
+ -- 3. bot_seats — Claude Max seat 할당 (물리 seat ↔ 논리 bot)
106
+ -- ============================================================
107
+
108
+ CREATE TABLE IF NOT EXISTS semicolony.bot_seats (
109
+ seat_id TEXT PRIMARY KEY,
110
+ claude_config_dir TEXT NOT NULL,
111
+ max_concurrent INT NOT NULL DEFAULT 1,
112
+ current_bot_id TEXT REFERENCES semicolony.bot_status(bot_id),
113
+ allocated_at TIMESTAMPTZ,
114
+ status TEXT NOT NULL DEFAULT 'available'
115
+ CHECK (status IN ('available','allocated','quarantined')),
116
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
117
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
118
+ );
119
+
120
+ CREATE INDEX IF NOT EXISTS idx_bot_seats_status
121
+ ON semicolony.bot_seats (status);
122
+
123
+ CREATE INDEX IF NOT EXISTS idx_bot_seats_current_bot
124
+ ON semicolony.bot_seats (current_bot_id) WHERE current_bot_id IS NOT NULL;
125
+
126
+ -- ============================================================
127
+ -- 4. semiclaw → semicolonybot 리브랜드 1단계
128
+ -- ============================================================
129
+
130
+ -- 4.1 semicolonybot row 생성 (semiclaw를 템플릿으로 복사)
131
+ INSERT INTO semicolony.bot_status (
132
+ bot_id, name, emoji, role, workspace_path, status,
133
+ kb_domains, budget_per_message, slack_username, slack_icon_emoji,
134
+ created_by_bot_id, template_bot_id
135
+ )
136
+ SELECT
137
+ 'semicolonybot', 'SemiColonyBot', ':robot_face:', 'orchestrator',
138
+ REPLACE(workspace_path, 'semiclaw', 'semicolonybot'),
139
+ status,
140
+ kb_domains, budget_per_message, 'SemiColonyBot', ':robot_face:',
141
+ '__genesis__', 'semiclaw'
142
+ FROM semicolony.bot_status WHERE bot_id = 'semiclaw'
143
+ ON CONFLICT (bot_id) DO NOTHING;
144
+
145
+ -- 4.2 alias 활성화 (semiclaw → semicolonybot)
146
+ INSERT INTO semicolony.bot_id_aliases (alias, canonical_bot_id)
147
+ VALUES ('semiclaw', 'semicolonybot')
148
+ ON CONFLICT (alias) DO NOTHING;
149
+
150
+ -- 4.3 semicolonybot agents 온톨로지 row (entity_type='agents')
151
+ INSERT INTO semicolony.ontology (domain, entity_type, schema, description)
152
+ VALUES ('semicolonybot', 'agents', '{}'::jsonb, 'SEMICOLONY 오케스트레이터 (semiclaw 후계)')
153
+ ON CONFLICT (domain) DO UPDATE SET
154
+ entity_type = EXCLUDED.entity_type,
155
+ description = EXCLUDED.description;
156
+
157
+ -- 4.4 semicolonybot KB 키 복사 (identity, delegation, model-config, status, slack-config 등)
158
+ INSERT INTO semicolony.knowledge_base (domain, key, sub_key, content, metadata, created_by)
159
+ SELECT
160
+ 'semicolonybot', key, sub_key, content, metadata, 'migration-104'
161
+ FROM semicolony.knowledge_base
162
+ WHERE domain = 'semiclaw'
163
+ AND key IN ('identity','delegation','model-config','status','slack-config','slack-profile','cron-schedule','tools','skills','kb-access')
164
+ ON CONFLICT (domain, key, sub_key) DO NOTHING;
165
+
166
+ -- 4.5 identity 콘텐츠의 이름은 SemiColonyBot으로 덮어쓰기
167
+ UPDATE semicolony.knowledge_base
168
+ SET content = format(
169
+ E'name: %s\nemoji: %s\ntagline: %s\nrole: %s\nagent_type: %s',
170
+ 'SemiColonyBot', ':robot_face:', 'SEMICOLONY 오케스트레이터', 'orchestrator', 'orchestrator'
171
+ ), updated_at = NOW()
172
+ WHERE domain = 'semicolonybot' AND key = 'identity' AND sub_key = '';
173
+
174
+ -- 4.6 slack-profile 덮어쓰기 (SemiColonyBot 표시명)
175
+ UPDATE semicolony.knowledge_base
176
+ SET content = E'username: SemiColonyBot\nicon_emoji: ":robot_face:"', updated_at = NOW()
177
+ WHERE domain = 'semicolonybot' AND key IN ('slack-profile','slack-config') AND sub_key = '';
178
+
179
+ -- 4.7 bot_delegation 복제 — 기존 semiclaw → X 위임을 semicolonybot → X 로 복제
180
+ -- (semiclaw 행은 alias 호환성 유지 차원에서 삭제하지 않음)
181
+ -- 075에서 unique 제약이 (from,to,type,metadata->>'label')의 composite 으로 바뀌어
182
+ -- ON CONFLICT 타겟을 명시하면 매치 실패. DO NOTHING 타겟 생략으로 변경.
183
+ INSERT INTO semicolony.bot_delegation (
184
+ from_bot_id, to_bot_id, delegation_type, domains, method, channel,
185
+ max_roundtrips, priority, is_active, metadata
186
+ )
187
+ SELECT
188
+ 'semicolonybot', to_bot_id, delegation_type, domains, method, channel,
189
+ max_roundtrips, priority, is_active, COALESCE(metadata, '{}'::jsonb)
190
+ FROM semicolony.bot_delegation
191
+ WHERE from_bot_id = 'semiclaw'
192
+ ON CONFLICT DO NOTHING;
193
+
194
+ -- ============================================================
195
+ -- 5. bot_seats 초기 행 — snamanager0 (현재 유일한 Claude Max seat)
196
+ -- ============================================================
197
+
198
+ INSERT INTO semicolony.bot_seats (seat_id, claude_config_dir, current_bot_id, allocated_at, status)
199
+ VALUES ('snamanager0', '$HOME/.claude/snamanager0', 'semicolonybot', NOW(), 'allocated')
200
+ ON CONFLICT (seat_id) DO UPDATE SET
201
+ current_bot_id = EXCLUDED.current_bot_id,
202
+ allocated_at = EXCLUDED.allocated_at,
203
+ status = EXCLUDED.status;
204
+
205
+ -- ============================================================
206
+ -- 6. NOTIFY 트리거 — 라우팅 설정 변경 시 핫 리로드 시그널
207
+ -- ============================================================
208
+
209
+ CREATE OR REPLACE FUNCTION semicolony.notify_routing_reload()
210
+ RETURNS TRIGGER AS $$
211
+ DECLARE
212
+ payload JSONB;
213
+ op TEXT := TG_OP;
214
+ tbl TEXT := TG_TABLE_NAME;
215
+ bot_id TEXT;
216
+ BEGIN
217
+ IF (TG_OP = 'DELETE') THEN
218
+ IF (tbl = 'bot_status') THEN
219
+ bot_id := OLD.bot_id;
220
+ ELSIF (tbl = 'bot_delegation') THEN
221
+ bot_id := OLD.to_bot_id;
222
+ ELSIF (tbl = 'bot_id_aliases') THEN
223
+ bot_id := OLD.canonical_bot_id;
224
+ END IF;
225
+ ELSE
226
+ IF (tbl = 'bot_status') THEN
227
+ bot_id := NEW.bot_id;
228
+ ELSIF (tbl = 'bot_delegation') THEN
229
+ bot_id := NEW.to_bot_id;
230
+ ELSIF (tbl = 'bot_id_aliases') THEN
231
+ bot_id := NEW.canonical_bot_id;
232
+ END IF;
233
+ END IF;
234
+
235
+ payload := jsonb_build_object('op', op, 'table', tbl, 'bot_id', bot_id, 'at', NOW());
236
+ PERFORM pg_notify('semicolony_routing_reload', payload::text);
237
+ RETURN COALESCE(NEW, OLD);
238
+ END;
239
+ $$ LANGUAGE plpgsql;
240
+
241
+ DO $$
242
+ BEGIN
243
+ IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trg_notify_bot_status') THEN
244
+ CREATE TRIGGER trg_notify_bot_status
245
+ AFTER INSERT OR UPDATE OR DELETE ON semicolony.bot_status
246
+ FOR EACH ROW EXECUTE FUNCTION semicolony.notify_routing_reload();
247
+ END IF;
248
+ IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trg_notify_bot_delegation') THEN
249
+ CREATE TRIGGER trg_notify_bot_delegation
250
+ AFTER INSERT OR UPDATE OR DELETE ON semicolony.bot_delegation
251
+ FOR EACH ROW EXECUTE FUNCTION semicolony.notify_routing_reload();
252
+ END IF;
253
+ IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trg_notify_bot_aliases') THEN
254
+ CREATE TRIGGER trg_notify_bot_aliases
255
+ AFTER INSERT OR UPDATE OR DELETE ON semicolony.bot_id_aliases
256
+ FOR EACH ROW EXECUTE FUNCTION semicolony.notify_routing_reload();
257
+ END IF;
258
+ END $$;
259
+
260
+ -- ============================================================
261
+ -- 7. 검증용 헬퍼 뷰
262
+ -- ============================================================
263
+
264
+ CREATE OR REPLACE VIEW semicolony.v_bot_factory_status AS
265
+ SELECT
266
+ bs.bot_id,
267
+ bs.name,
268
+ bs.status,
269
+ bs.role,
270
+ bs.created_by_bot_id,
271
+ bs.template_bot_id,
272
+ bs.slack_username,
273
+ bs.budget_per_message,
274
+ bs.kb_domains,
275
+ COALESCE(
276
+ (SELECT array_agg(alias ORDER BY alias) FROM semicolony.bot_id_aliases WHERE canonical_bot_id = bs.bot_id),
277
+ '{}'
278
+ ) AS aliases,
279
+ seat.seat_id AS seat_id,
280
+ seat.claude_config_dir AS claude_config_dir
281
+ FROM semicolony.bot_status bs
282
+ LEFT JOIN semicolony.bot_seats seat ON seat.current_bot_id = bs.bot_id;
283
+
284
+ COMMIT;
@@ -0,0 +1,44 @@
1
+ -- Migration 105: semicolony.knowledge_base 변경 이벤트 NOTIFY 트리거
2
+ --
3
+ -- Phase 1c KbStore.watch() 구현을 지원한다. 클라이언트는
4
+ -- `LISTEN semicolony_kb_change` 로 구독하고 JSON payload 를 파싱한다.
5
+ --
6
+ -- payload 스키마: { "type": "upsert" | "delete", "domain": "...", "key": "...",
7
+ -- "subKey": "...", "kbId": 123 }
8
+
9
+ CREATE OR REPLACE FUNCTION semicolony.notify_kb_change()
10
+ RETURNS TRIGGER AS $$
11
+ DECLARE
12
+ payload JSONB;
13
+ row_data RECORD;
14
+ change_type TEXT;
15
+ BEGIN
16
+ IF TG_OP = 'DELETE' THEN
17
+ row_data := OLD;
18
+ change_type := 'delete';
19
+ ELSE
20
+ row_data := NEW;
21
+ change_type := 'upsert';
22
+ END IF;
23
+
24
+ payload := jsonb_build_object(
25
+ 'type', change_type,
26
+ 'domain', row_data.domain,
27
+ 'key', row_data.key,
28
+ 'subKey', COALESCE(row_data.sub_key, ''),
29
+ 'kbId', row_data.kb_id
30
+ );
31
+
32
+ PERFORM pg_notify('semicolony_kb_change', payload::text);
33
+ RETURN COALESCE(NEW, OLD);
34
+ END;
35
+ $$ LANGUAGE plpgsql;
36
+
37
+ DO $$
38
+ BEGIN
39
+ IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trg_notify_kb_change') THEN
40
+ CREATE TRIGGER trg_notify_kb_change
41
+ AFTER INSERT OR UPDATE OR DELETE ON semicolony.knowledge_base
42
+ FOR EACH ROW EXECUTE FUNCTION semicolony.notify_kb_change();
43
+ END IF;
44
+ END $$;
@@ -0,0 +1,73 @@
1
+ -- 106: office_id 컬럼을 agent_definitions / command_definitions 에도 확장
2
+ -- L2 tenant 격리의 기본 메커니즘 — skill_definitions 에 이미 존재, 동일 패턴으로 확장.
3
+ -- 모든 기존 레코드는 office_id=NULL (공개 L0 카탈로그) 로 유지.
4
+ -- drift 복구: 소스 테이블이 없으면 전체 섹션 skip (2026-04-23)
5
+ BEGIN;
6
+
7
+ -- 1+3. agent_definitions 확장
8
+ DO $$
9
+ BEGIN
10
+ IF to_regclass('semicolony.agent_definitions') IS NULL THEN
11
+ RAISE NOTICE '106 section agent_definitions skipped: table missing';
12
+ RETURN;
13
+ END IF;
14
+
15
+ ALTER TABLE semicolony.agent_definitions
16
+ ADD COLUMN IF NOT EXISTS office_id UUID DEFAULT NULL;
17
+
18
+ CREATE INDEX IF NOT EXISTS idx_agent_definitions_office
19
+ ON semicolony.agent_definitions(office_id)
20
+ WHERE office_id IS NOT NULL;
21
+
22
+ IF EXISTS (
23
+ SELECT 1 FROM pg_constraint
24
+ WHERE conname = 'agent_definitions_name_key'
25
+ AND conrelid = 'semicolony.agent_definitions'::regclass
26
+ ) THEN
27
+ ALTER TABLE semicolony.agent_definitions DROP CONSTRAINT agent_definitions_name_key;
28
+ END IF;
29
+
30
+ IF NOT EXISTS (
31
+ SELECT 1 FROM pg_indexes
32
+ WHERE schemaname = 'semicolony' AND tablename = 'agent_definitions'
33
+ AND indexname = 'agent_definitions_name_office_uniq'
34
+ ) THEN
35
+ CREATE UNIQUE INDEX agent_definitions_name_office_uniq
36
+ ON semicolony.agent_definitions(name, COALESCE(office_id, '00000000-0000-0000-0000-000000000000'::uuid));
37
+ END IF;
38
+ END $$;
39
+
40
+ -- 2+4. command_definitions 확장
41
+ DO $$
42
+ BEGIN
43
+ IF to_regclass('semicolony.command_definitions') IS NULL THEN
44
+ RAISE NOTICE '106 section command_definitions skipped: table missing';
45
+ RETURN;
46
+ END IF;
47
+
48
+ ALTER TABLE semicolony.command_definitions
49
+ ADD COLUMN IF NOT EXISTS office_id UUID DEFAULT NULL;
50
+
51
+ CREATE INDEX IF NOT EXISTS idx_command_definitions_office
52
+ ON semicolony.command_definitions(office_id)
53
+ WHERE office_id IS NOT NULL;
54
+
55
+ IF EXISTS (
56
+ SELECT 1 FROM pg_constraint
57
+ WHERE conname = 'command_definitions_folder_name_key'
58
+ AND conrelid = 'semicolony.command_definitions'::regclass
59
+ ) THEN
60
+ ALTER TABLE semicolony.command_definitions DROP CONSTRAINT command_definitions_folder_name_key;
61
+ END IF;
62
+
63
+ IF NOT EXISTS (
64
+ SELECT 1 FROM pg_indexes
65
+ WHERE schemaname = 'semicolony' AND tablename = 'command_definitions'
66
+ AND indexname = 'command_definitions_folder_name_office_uniq'
67
+ ) THEN
68
+ CREATE UNIQUE INDEX command_definitions_folder_name_office_uniq
69
+ ON semicolony.command_definitions(folder, name, COALESCE(office_id, '00000000-0000-0000-0000-000000000000'::uuid));
70
+ END IF;
71
+ END $$;
72
+
73
+ COMMIT;
@@ -0,0 +1,79 @@
1
+ -- 107: agent service credentials (PAT vault)
2
+ -- SEMICOLONY를 중앙 발급/검증자로 하는 Personal Access Token 저장소.
3
+ -- 관련 결정: `semicolon decision agent-authenticated-action-standard`
4
+
5
+ BEGIN;
6
+
7
+ CREATE TABLE IF NOT EXISTS semicolony.agent_service_credentials (
8
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
9
+ bot_id TEXT NOT NULL,
10
+ service_domain TEXT NOT NULL,
11
+ token_hash TEXT NOT NULL UNIQUE,
12
+ token_prefix TEXT NOT NULL,
13
+ scopes TEXT[] NOT NULL DEFAULT '{}',
14
+ issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
15
+ expires_at TIMESTAMPTZ,
16
+ last_used_at TIMESTAMPTZ,
17
+ revoked_at TIMESTAMPTZ,
18
+ revoked_reason TEXT,
19
+ issued_by TEXT NOT NULL,
20
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
21
+
22
+ CONSTRAINT chk_token_hash_format
23
+ CHECK (token_hash ~ '^[0-9a-f]{64}$'),
24
+ CONSTRAINT chk_token_prefix_format
25
+ CHECK (token_prefix ~ '^semicolony_pat_(live|test)_[a-z0-9_-]+_$'),
26
+ CONSTRAINT chk_revoke_consistency
27
+ CHECK (revoked_reason IS NULL OR revoked_at IS NOT NULL),
28
+ CONSTRAINT chk_expires_after_issued
29
+ CHECK (expires_at IS NULL OR expires_at > issued_at)
30
+ );
31
+
32
+ CREATE INDEX IF NOT EXISTS idx_agent_creds_bot_id
33
+ ON semicolony.agent_service_credentials (bot_id);
34
+
35
+ CREATE INDEX IF NOT EXISTS idx_agent_creds_service
36
+ ON semicolony.agent_service_credentials (service_domain);
37
+
38
+ CREATE INDEX IF NOT EXISTS idx_agent_creds_active
39
+ ON semicolony.agent_service_credentials (bot_id, service_domain)
40
+ WHERE revoked_at IS NULL;
41
+
42
+ CREATE INDEX IF NOT EXISTS idx_agent_creds_expires
43
+ ON semicolony.agent_service_credentials (expires_at)
44
+ WHERE revoked_at IS NULL AND expires_at IS NOT NULL;
45
+
46
+ CREATE OR REPLACE VIEW semicolony.v_agent_credentials_audit AS
47
+ SELECT
48
+ id,
49
+ bot_id,
50
+ service_domain,
51
+ token_prefix,
52
+ scopes,
53
+ issued_at,
54
+ expires_at,
55
+ last_used_at,
56
+ revoked_at,
57
+ revoked_reason,
58
+ issued_by,
59
+ CASE
60
+ WHEN revoked_at IS NOT NULL THEN 'revoked'
61
+ WHEN expires_at IS NOT NULL AND expires_at < NOW() THEN 'expired'
62
+ ELSE 'active'
63
+ END AS status,
64
+ metadata
65
+ FROM semicolony.agent_service_credentials;
66
+
67
+ COMMENT ON TABLE semicolony.agent_service_credentials IS
68
+ 'PAT vault for agent→service authenticated writes. Plaintext never stored — only HMAC-SHA256(pepper, plaintext) hex. See decision: agent-authenticated-action-standard.';
69
+
70
+ COMMENT ON COLUMN semicolony.agent_service_credentials.token_hash IS
71
+ '64-char lowercase hex of HMAC-SHA256(SEMICOLONY_CREDENTIAL_PEPPER, plaintext). Unique globally.';
72
+
73
+ COMMENT ON COLUMN semicolony.agent_service_credentials.token_prefix IS
74
+ 'Deterministic prefix ending in underscore (e.g. "semicolony_pat_live_workclaw_"). Safe to expose to services for audit logging.';
75
+
76
+ COMMENT ON COLUMN semicolony.agent_service_credentials.scopes IS
77
+ 'Format "{resource}:{action}" (e.g. ["board:write","kpi:read"]). Wildcard "resource:*" supported in verify.';
78
+
79
+ COMMIT;
@@ -0,0 +1,16 @@
1
+ -- 108: L2 ontology seed cleanup
2
+ -- migration 017 시드된 세미콜론 L2 ontology 행(jungchipan, playland)을 제거.
3
+ -- 외부 OSS 신규 인스턴스에서는 자동 삭제되어 깨끗한 상태로 시작.
4
+ -- 기존 인스턴스에 해당 도메인 KB 데이터가 살아있으면 ontology 행은 보존 (안전).
5
+ --
6
+ -- 관련 인벤토리: docs/L2-INVENTORY.md (HIGH #1)
7
+
8
+ BEGIN;
9
+
10
+ DELETE FROM semicolony.ontology o
11
+ WHERE o.domain IN ('jungchipan', 'playland')
12
+ AND NOT EXISTS (
13
+ SELECT 1 FROM semicolony.knowledge_base kb WHERE kb.domain = o.domain
14
+ );
15
+
16
+ COMMIT;
@@ -0,0 +1,21 @@
1
+ -- 109: ontology JSON Schema description 일반화
2
+ -- migration 015 에서 KPI ontology 의 properties.project.description 에 세미콜론 L2 도메인
3
+ -- 예시("gameland, jungchipan")가 박혀 있었음. OSS 배포 시 외부 사용자에게 노출되므로 일반화.
4
+ --
5
+ -- 관련 인벤토리: docs/L2-INVENTORY.md (MEDIUM #2)
6
+
7
+ BEGIN;
8
+
9
+ UPDATE semicolony.ontology
10
+ SET
11
+ schema = jsonb_set(
12
+ schema,
13
+ '{properties,metadata,properties,project,description}',
14
+ '"프로젝트 ID (예: my-service)"'::jsonb
15
+ ),
16
+ version = version + 1
17
+ WHERE domain = 'kpi'
18
+ AND schema #> '{properties,metadata,properties,project,description}'
19
+ = '"프로젝트 ID (예: gameland, jungchipan)"'::jsonb;
20
+
21
+ COMMIT;