@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,36 @@
1
+ -- 125: agent_personas — 고객용 base 에이전트 행동 SoT (Semi/Colony 및 향후 고객 봇)
2
+ --
3
+ -- 설계: docs/superpowers/specs/2026-06-02-agent-behavior-sot-and-propagation-design.md
4
+ -- 배경: Semi/Colony 행동(SOUL.md)의 단일 SoT 를 DB 로 승격. 프로토타입 hermes SOUL.md 와
5
+ -- 미래 고객 install 이 모두 여기서 resolve. operator(슬랙)가 컨펌 기반으로 편집.
6
+ -- 주의: semicolony.agent_definitions(내부 7봇 → ~/.claude/agents 동기화)와는 별개 테이블이다.
7
+
8
+ BEGIN;
9
+
10
+ CREATE TABLE IF NOT EXISTS semicolony.agent_personas (
11
+ slug TEXT PRIMARY KEY, -- 'semi','colony','jumuni' ...
12
+ display_name TEXT,
13
+ soul_md TEXT NOT NULL, -- 행동 정의 본문 (= 현 SOUL.md)
14
+ version INTEGER NOT NULL DEFAULT 1,
15
+ status TEXT NOT NULL DEFAULT 'active'
16
+ CHECK (status IN ('active', 'deprecated')),
17
+ updated_by TEXT, -- 'mark' | 'reus' | ... (감사)
18
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
19
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
20
+ );
21
+
22
+ -- 편집 이력 (롤백·감사·diff 근거). 매 편집마다 append.
23
+ CREATE TABLE IF NOT EXISTS semicolony.agent_persona_revisions (
24
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
25
+ slug TEXT NOT NULL REFERENCES semicolony.agent_personas(slug) ON DELETE CASCADE,
26
+ version INTEGER NOT NULL,
27
+ soul_md TEXT NOT NULL,
28
+ updated_by TEXT,
29
+ note TEXT,
30
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
31
+ );
32
+
33
+ CREATE INDEX IF NOT EXISTS idx_agent_persona_revisions_slug
34
+ ON semicolony.agent_persona_revisions(slug, version);
35
+
36
+ COMMIT;
@@ -0,0 +1,125 @@
1
+ -- 128_entity_relations.sql
2
+ -- semicolony 제품 facade 기반: 고객/플랫폼 엔티티 관계 그래프.
3
+ --
4
+ -- 원칙:
5
+ -- - additive only: 기존 KB/ontology/action_items/bot_commitments write path 변경 없음.
6
+ -- - relation_type은 DB 등록 테이블로 통제한다. free-string relation을 금지한다.
7
+ -- - 추출 relation은 proposed가 기본이며 승인 후 approved로 live projection에 노출한다.
8
+ -- - tenant_id와 scope를 분리해 tenant-local 관계와 platform-global 관계를 구분한다.
9
+ -- runner(db.ts)가 파일 전체를 BEGIN/COMMIT 으로 감싸므로 여기엔 트랜잭션 구문을 넣지 않는다.
10
+
11
+ CREATE TABLE IF NOT EXISTS semicolony.relation_types (
12
+ relation_type TEXT PRIMARY KEY,
13
+ description TEXT NOT NULL,
14
+ inverse_type TEXT,
15
+ status TEXT NOT NULL DEFAULT 'active'
16
+ CHECK (status IN ('active', 'retired')),
17
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
18
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
19
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
20
+ );
21
+
22
+ COMMENT ON TABLE semicolony.relation_types IS
23
+ '등록형 relation enum. 코드 배포 없이 relation_type을 통제 추가하되 free-string 관계 생성을 막는다.';
24
+
25
+ INSERT INTO semicolony.relation_types (relation_type, description, inverse_type)
26
+ VALUES
27
+ ('owned_by', '엔티티의 소유/책임 주체', 'owns'),
28
+ ('owns', '소유/책임지는 대상', 'owned_by'),
29
+ ('depends_on', '완료/운영에 필요한 선행 의존성', 'required_by'),
30
+ ('required_by', '다른 엔티티가 의존하는 선행 항목', 'depends_on'),
31
+ ('decided_by', '결정한 사람/팀/에이전트', 'decided'),
32
+ ('decided', '결정 주체가 내린 결정', 'decided_by'),
33
+ ('blocks', '다른 업무/엔티티를 막는 원인', 'blocked_by'),
34
+ ('blocked_by', '다른 업무/엔티티에 의해 막힘', 'blocks'),
35
+ ('uses_tool', '업무 수행에 사용하는 도구/시스템', 'used_by'),
36
+ ('used_by', '도구/시스템을 사용하는 주체', 'uses_tool'),
37
+ ('requires_approval', '실행 전 승인이 필요한 대상', 'approves'),
38
+ ('approves', '승인 권한/승인 주체', 'requires_approval'),
39
+ ('reported_to', '보고 대상 사람/팀/에이전트', 'receives_report_from'),
40
+ ('receives_report_from', '보고를 받는 관계의 역방향', 'reported_to')
41
+ ON CONFLICT (relation_type) DO NOTHING;
42
+
43
+ CREATE TABLE IF NOT EXISTS semicolony.entity_relations (
44
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
45
+ tenant_id UUID,
46
+ scope TEXT NOT NULL DEFAULT 'tenant-local'
47
+ CHECK (scope IN ('tenant-local', 'platform-global')),
48
+ source_ref JSONB NOT NULL
49
+ CHECK (jsonb_typeof(source_ref) = 'object'),
50
+ relation_type TEXT NOT NULL
51
+ REFERENCES semicolony.relation_types(relation_type),
52
+ target_ref JSONB NOT NULL
53
+ CHECK (jsonb_typeof(target_ref) = 'object'),
54
+ status TEXT NOT NULL DEFAULT 'proposed'
55
+ CHECK (status IN ('proposed', 'approved', 'retired', 'rejected')),
56
+ confidence NUMERIC(5,4)
57
+ CHECK (confidence IS NULL OR (confidence >= 0 AND confidence <= 1)),
58
+ provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
59
+ acl JSONB NOT NULL DEFAULT '{}'::jsonb,
60
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
61
+ created_by TEXT,
62
+ approved_by TEXT,
63
+ approved_at TIMESTAMPTZ,
64
+ retired_at TIMESTAMPTZ,
65
+ valid_from TIMESTAMPTZ,
66
+ valid_to TIMESTAMPTZ,
67
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
68
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
69
+ CHECK (scope = 'platform-global' OR tenant_id IS NOT NULL),
70
+ CHECK (valid_to IS NULL OR valid_from IS NULL OR valid_to >= valid_from)
71
+ );
72
+
73
+ COMMENT ON TABLE semicolony.entity_relations IS
74
+ '고객 업무 facade용 관계 그래프. proposed→approved 승인 게이트를 통과한 관계만 live projection에서 사용한다.';
75
+ COMMENT ON COLUMN semicolony.entity_relations.source_ref IS
76
+ '관계 출발점. 예: {"kind":"agent","id":"ag-team-jumuni"} 또는 {"kind":"kb","domain":"axoracle","key":"decision","sub_key":"..."}';
77
+ COMMENT ON COLUMN semicolony.entity_relations.target_ref IS
78
+ '관계 도착점. source_ref와 같은 JSON object reference 규약을 쓴다.';
79
+ COMMENT ON COLUMN semicolony.entity_relations.provenance IS
80
+ '추출/승인 출처. 예: {"source":"onboarding","message_ts":"...","extractor":"colony"}';
81
+ COMMENT ON COLUMN semicolony.entity_relations.acl IS
82
+ 'tenant/user/agent별 읽기·실행 제한 메타데이터. enforcement는 후속 phase에서 적용한다.';
83
+
84
+ CREATE INDEX IF NOT EXISTS idx_relation_types_status
85
+ ON semicolony.relation_types (status);
86
+
87
+ CREATE INDEX IF NOT EXISTS idx_entity_relations_tenant_status
88
+ ON semicolony.entity_relations (tenant_id, status);
89
+
90
+ CREATE INDEX IF NOT EXISTS idx_entity_relations_scope_status
91
+ ON semicolony.entity_relations (scope, status);
92
+
93
+ CREATE INDEX IF NOT EXISTS idx_entity_relations_type_status
94
+ ON semicolony.entity_relations (relation_type, status);
95
+
96
+ CREATE INDEX IF NOT EXISTS idx_entity_relations_source_ref
97
+ ON semicolony.entity_relations USING GIN (source_ref jsonb_path_ops);
98
+
99
+ CREATE INDEX IF NOT EXISTS idx_entity_relations_target_ref
100
+ ON semicolony.entity_relations USING GIN (target_ref jsonb_path_ops);
101
+
102
+ CREATE OR REPLACE FUNCTION semicolony.trg_relations_updated()
103
+ RETURNS TRIGGER AS $$
104
+ BEGIN
105
+ NEW.updated_at = NOW();
106
+ RETURN NEW;
107
+ END;
108
+ $$ LANGUAGE plpgsql;
109
+
110
+ DROP TRIGGER IF EXISTS trg_relation_types_updated ON semicolony.relation_types;
111
+ CREATE TRIGGER trg_relation_types_updated
112
+ BEFORE UPDATE ON semicolony.relation_types
113
+ FOR EACH ROW EXECUTE FUNCTION semicolony.trg_relations_updated();
114
+
115
+ DROP TRIGGER IF EXISTS trg_entity_relations_updated ON semicolony.entity_relations;
116
+ CREATE TRIGGER trg_entity_relations_updated
117
+ BEFORE UPDATE ON semicolony.entity_relations
118
+ FOR EACH ROW EXECUTE FUNCTION semicolony.trg_relations_updated();
119
+
120
+ CREATE OR REPLACE VIEW semicolony.v_entity_relations_live AS
121
+ SELECT *
122
+ FROM semicolony.entity_relations
123
+ WHERE status = 'approved'
124
+ AND (valid_from IS NULL OR valid_from <= NOW())
125
+ AND (valid_to IS NULL OR valid_to > NOW());
@@ -0,0 +1,86 @@
1
+ -- 129_kb_multitenant_gateway.sql
2
+ -- SemiColony 멀티테넌트 KB 게이트웨이 기반 스키마.
3
+ -- 결정: semicolony/decision/semicolony-gateway-tenancy-auth-persona-resolved
4
+ -- (+ 원 결정 semicolony-kb-access-via-multitenant-api-gateway)
5
+ -- 선례: 128_entity_relations.sql (tenant_id + scope CHECK), 107_agent_service_credentials.sql (token_hash vault).
6
+ --
7
+ -- 핵심 설계(zero blast radius):
8
+ -- - knowledge_base 에 tenant_id + scope 를 **가산적**으로 추가(기존 행은 전부 platform-global/NULL → 무변경).
9
+ -- - 물리적 키 격리는 per-tenant 도메인 `t-{tenantSlug}` 로 수행 → 기존 UNIQUE(domain,key,sub_key) 와
10
+ -- ON CONFLICT(domain,key,sub_key) 를 쓰는 10개 writer 를 **건드리지 않는다**(도메인이 테넌트를 내포).
11
+ -- - tenant_id 컬럼은 RLS(Phase 2)·cascade·분석·게이트웨이 이중검증용 비정규화.
12
+ --
13
+ -- 러너(db.ts)가 파일 전체를 BEGIN/COMMIT 으로 감싸고 `semicolony.` 한정자를 활성 스키마로 retarget 한다.
14
+ -- → 여기엔 트랜잭션 구문을 넣지 않는다. `public.` 은 retarget 대상이 아니므로 appdb 공용 스키마 그대로 유지.
15
+
16
+ -- ============================================================
17
+ -- 0) ontology_types: 'tenant-kb' 타입 등록
18
+ -- ontology.entity_type → ontology_types.type_key FK 가 있으므로, 게이트웨이가 런타임에
19
+ -- per-tenant 도메인(t-{slug})을 ontology 에 등록하려면 이 타입이 먼저 존재해야 한다.
20
+ -- 팀 서비스 카탈로그 타입(service/module/agents…)과 분리된 고객 KB 전용 타입.
21
+ -- ============================================================
22
+ INSERT INTO semicolony.ontology_types (type_key, schema, description)
23
+ VALUES ('tenant-kb', '{}'::jsonb, '고객(Colony) 테넌트 KB 네임스페이스 — per-tenant 도메인 t-{slug}')
24
+ ON CONFLICT (type_key) DO NOTHING;
25
+
26
+ -- ============================================================
27
+ -- 1) knowledge_base: 테넌트 스코프 (가산적·비파괴, 128 패턴 동형)
28
+ -- ============================================================
29
+ ALTER TABLE semicolony.knowledge_base
30
+ ADD COLUMN IF NOT EXISTS tenant_id UUID,
31
+ ADD COLUMN IF NOT EXISTS scope TEXT NOT NULL DEFAULT 'platform-global';
32
+
33
+ DO $$
34
+ BEGIN
35
+ IF NOT EXISTS (
36
+ SELECT 1 FROM pg_constraint
37
+ WHERE conname = 'kb_scope_enum_chk'
38
+ AND conrelid = 'semicolony.knowledge_base'::regclass
39
+ ) THEN
40
+ ALTER TABLE semicolony.knowledge_base
41
+ ADD CONSTRAINT kb_scope_enum_chk CHECK (scope IN ('tenant-local', 'platform-global'));
42
+ END IF;
43
+ -- 128 과 동일한 무결성: tenant-local 행은 반드시 tenant_id 를 가진다.
44
+ IF NOT EXISTS (
45
+ SELECT 1 FROM pg_constraint
46
+ WHERE conname = 'kb_tenant_scope_chk'
47
+ AND conrelid = 'semicolony.knowledge_base'::regclass
48
+ ) THEN
49
+ ALTER TABLE semicolony.knowledge_base
50
+ ADD CONSTRAINT kb_tenant_scope_chk CHECK (scope = 'platform-global' OR tenant_id IS NOT NULL);
51
+ END IF;
52
+ END $$;
53
+
54
+ -- tenant-local 조회/cascade 용 인덱스 (platform-global=NULL 행은 partial 로 제외 → 작고 빠름).
55
+ CREATE INDEX IF NOT EXISTS idx_kb_tenant
56
+ ON semicolony.knowledge_base (tenant_id)
57
+ WHERE tenant_id IS NOT NULL;
58
+
59
+ -- ============================================================
60
+ -- 2) gateway_credentials: per-tenant bearer 자격증명 (107_agent_service_credentials 미러)
61
+ -- 게이트웨이가 DB 를 보는 유일한 주체이므로, 외부 Colony 인증의 SoT.
62
+ -- ============================================================
63
+ CREATE TABLE IF NOT EXISTS semicolony.gateway_credentials (
64
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
65
+ tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
66
+ tenant_slug TEXT NOT NULL, -- 도메인 t-{slug} 파생용 비정규화
67
+ token_hash TEXT NOT NULL UNIQUE, -- sha256(token) — 평문은 저장 안 함
68
+ token_prefix TEXT NOT NULL, -- 식별용 접두(sck_{slug}_xxxxx)
69
+ scopes TEXT[] NOT NULL DEFAULT ARRAY['kb:read','kb:write','persona:read'],
70
+ status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','revoked')),
71
+ issued_at TIMESTAMPTZ NOT NULL DEFAULT now(),
72
+ expires_at TIMESTAMPTZ,
73
+ last_used_at TIMESTAMPTZ,
74
+ revoked_at TIMESTAMPTZ,
75
+ revoked_reason TEXT,
76
+ issued_by TEXT,
77
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb
78
+ );
79
+
80
+ CREATE INDEX IF NOT EXISTS idx_gateway_credentials_tenant
81
+ ON semicolony.gateway_credentials (tenant_id, status);
82
+
83
+ -- 회전(rotate) 시 활성 자격증명을 빠르게 찾기 위한 부분 인덱스.
84
+ CREATE INDEX IF NOT EXISTS idx_gateway_credentials_active
85
+ ON semicolony.gateway_credentials (tenant_slug)
86
+ WHERE status = 'active';
@@ -0,0 +1,36 @@
1
+ -- 130_jewelry_relation_types.sql
2
+ -- 소상공인(주얼리) 1차 온톨로지 컨벤션 — relation_types 데이터 시드.
3
+ -- 원칙(128 계승): 업종별 DDL 금지. generic 테이블에 ROW 만 추가(ON CONFLICT DO NOTHING).
4
+ -- 엔티티 kind 는 source_ref/target_ref JSON 의 "kind" 컨벤션:
5
+ -- product·material·category·customer·segment·order·supplier·channel·promo·season·review·care_guide·occasion·brand
6
+ -- 속성(가격·소재명·사이즈)은 관계가 아니라 KB(knowledge_base)/ref 에 저장. 여기는 엔티티 간 "관계"만.
7
+
8
+ INSERT INTO semicolony.relation_types (relation_type, description, inverse_type) VALUES
9
+ ('made_of', '상품이 사용하는 소재(product→material)', 'material_of'),
10
+ ('material_of', '소재가 쓰인 상품(역방향)', 'made_of'),
11
+ ('variant_of', '색상/사이즈 변형이 본품의(variant→base)', 'has_variant'),
12
+ ('has_variant', '본품이 가지는 변형(역방향)', 'variant_of'),
13
+ ('in_category', '상품이 속한 카테고리(product→category)', 'category_has'),
14
+ ('category_has', '카테고리가 포함하는 상품(역방향)', 'in_category'),
15
+ ('listed_on', '상품이 등록된 판매채널(product→channel)', 'lists_product'),
16
+ ('lists_product', '채널이 노출하는 상품(역방향)', 'listed_on'),
17
+ ('supplied_by', '상품/소재의 공급처(→supplier)', 'supplies'),
18
+ ('supplies', '공급처가 대는 상품/소재(역방향)', 'supplied_by'),
19
+ ('targets_segment', '상품이 겨냥하는 고객군(product→segment)', 'segment_targeted_by'),
20
+ ('segment_targeted_by', '고객군을 겨냥하는 상품(역방향)', 'targets_segment'),
21
+ ('promoted_by', '상품을 홍보하는 이벤트/프로모(product→promo)', 'promotes'),
22
+ ('promotes', '프로모가 홍보하는 상품(역방향)', 'promoted_by'),
23
+ ('bundled_with', '함께 묶이는 상품(선물세트, product-product)', 'bundled_with'),
24
+ ('season_for', '상품이 적합한 시즌/기념일(product→season)', 'season_includes'),
25
+ ('season_includes', '시즌이 미는 상품(역방향)', 'season_for'),
26
+ ('gift_suitable_for', '상품이 어울리는 선물 상황(product→occasion)', 'occasion_fits'),
27
+ ('occasion_fits', '선물 상황에 맞는 상품(역방향)', 'gift_suitable_for'),
28
+ ('cared_by', '상품의 관리법(product→care_guide)', 'care_for'),
29
+ ('care_for', '관리법이 적용되는 상품(역방향)', 'cared_by'),
30
+ ('ordered_by', '주문을 넣은 고객(order→customer)', 'placed_order'),
31
+ ('placed_order', '고객이 넣은 주문(역방향)', 'ordered_by'),
32
+ ('contains_item', '주문이 포함하는 상품(order→product)', 'item_in_order'),
33
+ ('item_in_order', '상품이 담긴 주문(역방향)', 'contains_item'),
34
+ ('reviewed_in', '상품에 달린 리뷰(product→review)', 'review_of'),
35
+ ('review_of', '리뷰가 가리키는 상품(역방향)', 'reviewed_in')
36
+ ON CONFLICT (relation_type) DO NOTHING;
@@ -0,0 +1,38 @@
1
+ -- 131_concierge_requests.sql
2
+ -- E4 컨시어지 요청 큐 — 고객이 요청(예: 이미지 생성)하면 적재 → 우리 팀이 처리 → 산출물 전달.
3
+ -- additive only. tenant_id 격리(129 패턴). kind 통제(현재 image; 확장 가능).
4
+ CREATE TABLE IF NOT EXISTS semicolony.concierge_requests (
5
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
6
+ tenant_id UUID NOT NULL,
7
+ kind TEXT NOT NULL DEFAULT 'image'
8
+ CHECK (kind IN ('image', 'other')),
9
+ payload JSONB NOT NULL DEFAULT '{}'::jsonb, -- 요청 상세(상품·스타일·문구 등)
10
+ status TEXT NOT NULL DEFAULT 'pending'
11
+ CHECK (status IN ('pending', 'in_progress', 'delivered', 'rejected', 'canceled')),
12
+ requested_by TEXT, -- 사장님/에이전트
13
+ assignee TEXT, -- 처리 담당(우리 팀)
14
+ result_ref JSONB, -- 전달 산출물 참조(중앙 저장 경로 등)
15
+ notified_at TIMESTAMPTZ, -- 우리 슬랙 알림 시각
16
+ delivered_at TIMESTAMPTZ,
17
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
18
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
19
+ );
20
+ COMMENT ON TABLE semicolony.concierge_requests IS
21
+ 'E4 컨시어지 요청 큐(이미지 등). pending→in_progress→delivered. tenant 격리.';
22
+
23
+ CREATE INDEX IF NOT EXISTS idx_concierge_tenant_status
24
+ ON semicolony.concierge_requests (tenant_id, status);
25
+ CREATE INDEX IF NOT EXISTS idx_concierge_status_created
26
+ ON semicolony.concierge_requests (status, created_at);
27
+
28
+ DROP TRIGGER IF EXISTS trg_concierge_updated ON semicolony.concierge_requests;
29
+ CREATE TRIGGER trg_concierge_updated
30
+ BEFORE UPDATE ON semicolony.concierge_requests
31
+ FOR EACH ROW EXECUTE FUNCTION semicolony.trg_relations_updated(); -- 128에서 정의한 updated_at 트리거 재사용
32
+
33
+ -- 우리 팀 처리 대기열 뷰(오래된 순)
34
+ CREATE OR REPLACE VIEW semicolony.v_concierge_queue AS
35
+ SELECT id, tenant_id, kind, payload, requested_by, created_at
36
+ FROM semicolony.concierge_requests
37
+ WHERE status = 'pending'
38
+ ORDER BY created_at;
@@ -0,0 +1,27 @@
1
+ -- 132_kb_supersession.sql
2
+ -- 지식 도서관 "폐가/최신판 관리"(G2) — 본문 supersession 컬럼.
3
+ -- 설계: docs/superpowers/specs/2026-06-12-knowledge-library-design.md (M1).
4
+ -- additive only / 멱등. ⚠️ kb_history(113) 트리거는 OLD 스냅샷(감사)만 보관 → 검색가능한 superseded 행이 아님.
5
+ -- 따라서 "현행 유효 vs 폐기" 구분은 아래 컬럼으로 표현한다. 기존 행은 전부 is_latest=true(default)로 무해.
6
+ -- ⚠️ 검색 기본필터(is_latest) 적용은 코드 측 "검색 계약"(D4) 변경 + 회귀테스트 통과 후 별도 적용.
7
+
8
+ ALTER TABLE semicolony.knowledge_base
9
+ ADD COLUMN IF NOT EXISTS is_latest BOOLEAN NOT NULL DEFAULT TRUE,
10
+ ADD COLUMN IF NOT EXISTS superseded_by BIGINT NULL REFERENCES semicolony.knowledge_base(kb_id),
11
+ ADD COLUMN IF NOT EXISTS supersedes BIGINT NULL REFERENCES semicolony.knowledge_base(kb_id),
12
+ ADD COLUMN IF NOT EXISTS valid_from TIMESTAMPTZ NULL, -- 현실시간 유효 시작(미상=NULL 허용)
13
+ ADD COLUMN IF NOT EXISTS valid_to TIMESTAMPTZ NULL; -- 폐기/만료(미상=NULL)
14
+
15
+ COMMENT ON COLUMN semicolony.knowledge_base.is_latest IS
16
+ '현행 유효 장서 여부. 모순 대체 시 옛 행 false. 검색 기본은 is_latest=true (검색계약 D4).';
17
+ COMMENT ON COLUMN semicolony.knowledge_base.superseded_by IS '이 장서를 대체한 새 장서 kb_id.';
18
+
19
+ -- 현행 장서 빠른 조회용 partial index
20
+ CREATE INDEX IF NOT EXISTS idx_kb_is_latest
21
+ ON semicolony.knowledge_base (domain, is_latest) WHERE is_latest = TRUE;
22
+ CREATE INDEX IF NOT EXISTS idx_kb_superseded_by
23
+ ON semicolony.knowledge_base (superseded_by) WHERE superseded_by IS NOT NULL;
24
+
25
+ -- 현행 장서 뷰(검색계약 적용 전, 코드가 점진 채택)
26
+ CREATE OR REPLACE VIEW semicolony.v_kb_current AS
27
+ SELECT * FROM semicolony.knowledge_base WHERE is_latest = TRUE;
@@ -0,0 +1,52 @@
1
+ -- 133_kb_claims.sql
2
+ -- 지식 도서관 "원자 사실 카드"(G1) sidecar — 문서 행을 쪼개지 않고 옆에 붙인다.
3
+ -- 설계: docs/superpowers/specs/2026-06-12-knowledge-library-design.md (M2).
4
+ -- additive only / 멱등. 가격·담당자·상태·정책·영업시간·고객속성 등 바뀌고 검증가능한 최소 단위.
5
+ -- 권위 모델: Colony 추출 등은 status='proposed'로만 적재 → 승인(D3 선택 게이트) 후 approved 만 응대에 사용.
6
+
7
+ CREATE TABLE IF NOT EXISTS semicolony.kb_claims (
8
+ kb_claim_id BIGSERIAL PRIMARY KEY,
9
+ tenant_id UUID NOT NULL,
10
+ scope TEXT NOT NULL DEFAULT 'tenant-local'
11
+ CHECK (scope IN ('tenant-local', 'platform-global')),
12
+ source_kb_id BIGINT NULL REFERENCES semicolony.knowledge_base(kb_id), -- 출처 문서(있으면)
13
+ subject_ref JSONB NOT NULL, -- 주어(엔티티) 참조 {kind,id,...}
14
+ predicate TEXT NOT NULL, -- 술어(통제 어휘 권장)
15
+ object_ref JSONB NULL, -- 목적어가 엔티티면
16
+ object_value TEXT NULL, -- 목적어가 리터럴(가격·상태 등)이면
17
+ status TEXT NOT NULL DEFAULT 'proposed'
18
+ CHECK (status IN ('proposed', 'approved', 'retired', 'rejected')),
19
+ confidence NUMERIC NULL CHECK (confidence IS NULL OR (confidence >= 0 AND confidence <= 1)),
20
+ evidence JSONB NULL, -- 근거(메시지 ts·span·출처)
21
+ extractor_version TEXT NULL, -- 어떤 추출기/버전이 제안했나
22
+ valid_from TIMESTAMPTZ NULL, -- 현실시간 유효(미상=NULL 허용)
23
+ valid_to TIMESTAMPTZ NULL,
24
+ tx_from TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- DB 시간(bitemporal)
25
+ tx_to TIMESTAMPTZ NULL,
26
+ created_by TEXT NULL,
27
+ approved_by TEXT NULL,
28
+ approved_at TIMESTAMPTZ NULL,
29
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
30
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
31
+ CHECK (object_ref IS NOT NULL OR object_value IS NOT NULL)
32
+ );
33
+ COMMENT ON TABLE semicolony.kb_claims IS
34
+ '원자 사실 카드(sidecar). proposed→approved 게이트. bitemporal(valid_*/tx_*). 응대는 approved+current만.';
35
+
36
+ CREATE INDEX IF NOT EXISTS idx_kb_claims_tenant_status ON semicolony.kb_claims (tenant_id, status);
37
+ CREATE INDEX IF NOT EXISTS idx_kb_claims_subject ON semicolony.kb_claims USING gin (subject_ref);
38
+ CREATE INDEX IF NOT EXISTS idx_kb_claims_source ON semicolony.kb_claims (source_kb_id) WHERE source_kb_id IS NOT NULL;
39
+ CREATE INDEX IF NOT EXISTS idx_kb_claims_current ON semicolony.kb_claims (tenant_id, predicate)
40
+ WHERE status = 'approved' AND tx_to IS NULL;
41
+
42
+ DROP TRIGGER IF EXISTS trg_kb_claims_updated ON semicolony.kb_claims;
43
+ CREATE TRIGGER trg_kb_claims_updated
44
+ BEFORE UPDATE ON semicolony.kb_claims
45
+ FOR EACH ROW EXECUTE FUNCTION semicolony.trg_relations_updated(); -- 128 정의 updated_at 트리거 재사용
46
+
47
+ -- 현행·승인된 사실만(응대 그라운딩용)
48
+ CREATE OR REPLACE VIEW semicolony.v_kb_claims_current AS
49
+ SELECT * FROM semicolony.kb_claims
50
+ WHERE status = 'approved'
51
+ AND tx_to IS NULL
52
+ AND (valid_to IS NULL OR valid_to > NOW());
@@ -0,0 +1,18 @@
1
+ -- 134: unified migration registry (additive only, no behavior change)
2
+ --
3
+ -- `sc db migrate` 가 두 마이그레이션 트리(cli/migrations=semicolony, semicolony-dashboard/migrations=public)를
4
+ -- 하나의 순서화된 이력으로 적용하기 위한 레지스트리. namespace+version 으로 멱등 재적용을 막고,
5
+ -- checksum 으로 적용 후 파일 변조를 감지하며, transaction_mode 로 비-트랜잭션 마이그(@transaction: off)를
6
+ -- 구분한다. 기존 semicolony.schema_migrations 와 공존(점진 이행)하며 RLS/동작 변경은 없다.
7
+ CREATE TABLE IF NOT EXISTS semicolony.migration_files (
8
+ namespace TEXT NOT NULL,
9
+ version TEXT NOT NULL,
10
+ name TEXT NOT NULL,
11
+ path TEXT NOT NULL,
12
+ checksum TEXT NOT NULL,
13
+ transaction_mode TEXT NOT NULL DEFAULT 'transaction'
14
+ CHECK (transaction_mode IN ('transaction', 'none')),
15
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT now(),
16
+ PRIMARY KEY (namespace, version),
17
+ UNIQUE (path)
18
+ );
@@ -0,0 +1,102 @@
1
+ -- @schema-retarget: off
2
+ -- 135_sc_roles.sql
3
+ -- SemiColony RLS 기반: 비-소유자 앱 역할 + RLS 헬퍼 함수.
4
+ --
5
+ -- 결정: semicolony/decision/rls-trusted-service-guc-boundary-2026-06-19.
6
+ -- 선례: 129_kb_multitenant_gateway.sql (tenant scope), 128_entity_relations.sql.
7
+ --
8
+ -- 핵심 불변식(NON-NEGOTIABLE):
9
+ -- - sc_app / sc_provider 는 SUPERUSER / BYPASSRLS 가 아니다 → RLS 가 항상 적용된다.
10
+ -- - 두 역할은 DDL/소유권이 없다(SELECT/INSERT/UPDATE 만). 마이그레이션/스키마 변경은
11
+ -- 소유자(중앙) 역할이 수행한다.
12
+ -- - 테넌트 VM 은 이 자격증명을 절대 받지 않는다 → 중앙 trusted 서비스만 사용.
13
+ --
14
+ -- @schema-retarget: off 이유:
15
+ -- - 이 파일은 `public.` 헬퍼 함수와 `pg_roles` 를 참조한다(테넌트 스키마와 무관).
16
+ -- - GRANT 대상 테이블의 스키마는 아래에서 `current_setting('search_path')` 가 아니라
17
+ -- 활성 스키마를 동적으로 결정해야 하므로, retarget 을 끄고 `DO` 블록에서 직접 처리한다.
18
+ -- - public.current_tenant_id()/is_provider() 는 절대 retarget 되면 안 된다(GUC 계약).
19
+
20
+ -- ============================================================
21
+ -- 1) 역할 생성 (멱등) — LOGIN, 비-소유자, NOBYPASSRLS
22
+ -- ============================================================
23
+ DO $$
24
+ BEGIN
25
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'sc_app') THEN
26
+ CREATE ROLE sc_app LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS;
27
+ END IF;
28
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'sc_provider') THEN
29
+ CREATE ROLE sc_provider LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS;
30
+ END IF;
31
+ END $$;
32
+
33
+ -- ============================================================
34
+ -- 2) RLS 헬퍼 함수 (public 고정 — GUC 계약의 단일 진실)
35
+ -- STABLE: 한 쿼리/트랜잭션 내에서 GUC 값이 고정.
36
+ -- current_setting(..., true): GUC 미설정 시 NULL(은 RLS 에서 fail-closed).
37
+ -- ============================================================
38
+ CREATE OR REPLACE FUNCTION public.current_tenant_id() RETURNS uuid
39
+ LANGUAGE sql STABLE
40
+ AS $$ SELECT NULLIF(current_setting('app.current_tenant_id', true), '')::uuid $$;
41
+
42
+ CREATE OR REPLACE FUNCTION public.is_provider() RETURNS boolean
43
+ LANGUAGE sql STABLE
44
+ AS $$ SELECT coalesce(current_setting('app.is_provider', true), 'off') = 'on' $$;
45
+
46
+ COMMENT ON FUNCTION public.current_tenant_id() IS
47
+ 'RLS 헬퍼: SET LOCAL app.current_tenant_id 로 주입된 현재 테넌트 UUID. 미설정 시 NULL(fail-closed).';
48
+ COMMENT ON FUNCTION public.is_provider() IS
49
+ 'RLS 헬퍼: SET LOCAL app.is_provider=on 일 때만 true. 테넌트 인증 경로에서 설정 금지.';
50
+
51
+ -- 헬퍼 함수는 두 앱 역할이 RLS 정책 평가 시 실행할 수 있어야 한다.
52
+ GRANT EXECUTE ON FUNCTION public.current_tenant_id() TO sc_app, sc_provider;
53
+ GRANT EXECUTE ON FUNCTION public.is_provider() TO sc_app, sc_provider;
54
+
55
+ -- ============================================================
56
+ -- 3) GRANT — sc_app 에 서비스 테이블 SELECT/INSERT/UPDATE 만 부여.
57
+ -- DDL/DELETE/소유권 없음. 활성 스키마(semicolony 또는 flip 후 semicolony)와 public 양쪽.
58
+ -- 스키마명을 retarget 없이 동적으로 결정하기 위해 GUC(app.runtime_schema, 미설정 시 'semicolony')
59
+ -- 를 사용한다(러너가 flip 시 SET app.runtime_schema 가능). EXECUTE 로 명시 GRANT.
60
+ -- ============================================================
61
+ DO $$
62
+ DECLARE
63
+ active_schema text := coalesce(nullif(current_setting('app.runtime_schema', true), ''), 'semicolony');
64
+ svc_table text;
65
+ svc_tables text[] := ARRAY[
66
+ 'bot_status', 'bot_sessions', 'bot_commitments', 'action_items',
67
+ 'agent_personas', 'agent_persona_revisions',
68
+ 'knowledge_base', 'entity_relations', 'kb_claims', 'concierge_requests'
69
+ ];
70
+ BEGIN
71
+ -- 스키마 USAGE (DDL 아님 — 객체 참조 권한만)
72
+ EXECUTE format('GRANT USAGE ON SCHEMA %I TO sc_app, sc_provider', active_schema);
73
+ EXECUTE 'GRANT USAGE ON SCHEMA public TO sc_app, sc_provider';
74
+
75
+ -- 활성 스키마의 서비스 테이블: SELECT/INSERT/UPDATE 만 (존재하는 것만)
76
+ FOREACH svc_table IN ARRAY svc_tables LOOP
77
+ IF to_regclass(format('%I.%I', active_schema, svc_table)) IS NOT NULL THEN
78
+ EXECUTE format(
79
+ 'GRANT SELECT, INSERT, UPDATE ON %I.%I TO sc_app',
80
+ active_schema, svc_table
81
+ );
82
+ END IF;
83
+ END LOOP;
84
+
85
+ -- public 멀티테넌트 테이블: 게이트웨이/런타임-컨트롤이 trusted 로 읽고 쓴다.
86
+ IF to_regclass('public.tenants') IS NOT NULL THEN
87
+ EXECUTE 'GRANT SELECT ON public.tenants TO sc_app';
88
+ END IF;
89
+ IF to_regclass('public.agent_installs') IS NOT NULL THEN
90
+ EXECUTE 'GRANT SELECT, INSERT, UPDATE ON public.agent_installs TO sc_app';
91
+ END IF;
92
+ IF to_regclass('public.agent_activity') IS NOT NULL THEN
93
+ EXECUTE 'GRANT SELECT, INSERT, UPDATE ON public.agent_activity TO sc_app';
94
+ END IF;
95
+ IF to_regclass('public.agent_listings') IS NOT NULL THEN
96
+ -- 라이브러리(플랫폼 카탈로그)는 RLS 미적용 — 읽기만 부여.
97
+ EXECUTE 'GRANT SELECT ON public.agent_listings TO sc_app';
98
+ END IF;
99
+
100
+ -- 시퀀스 USAGE (BIGSERIAL/uuid default 가 아닌 serial 컬럼 INSERT 대비)
101
+ EXECUTE format('GRANT USAGE ON ALL SEQUENCES IN SCHEMA %I TO sc_app', active_schema);
102
+ END $$;
@@ -0,0 +1,71 @@
1
+ -- 136_runtime_tenant_columns.sql
2
+ -- 런타임 테이블에 tenant_id / scope / install_id 를 **가산적**으로 추가(RLS Phase 2 준비).
3
+ --
4
+ -- 결정: semicolony/decision/rls-trusted-service-guc-boundary-2026-06-19.
5
+ -- 선례: 129_kb_multitenant_gateway.sql (knowledge_base tenant_id+scope 동형 패턴).
6
+ --
7
+ -- 원칙(zero blast radius):
8
+ -- - 모든 컬럼 ADD COLUMN IF NOT EXISTS → 재실행 멱등, 기존 행 무변경.
9
+ -- - scope DEFAULT 'platform-global' → 기존 모든 행은 platform-global 로 분류(RLS read 허용),
10
+ -- 백필(137) 후 tenant-local 로 재분류한다.
11
+ -- - scope/tenant_id 연결 CHECK 는 여기서 추가하지 않는다 → 백필(137) 완료 후 138 에서
12
+ -- NOT VALID + VALIDATE 로 안전하게 건다(긴 락 회피).
13
+ -- - 러너가 `semicolony.` 를 활성 스키마로 retarget 하고 파일 전체를 BEGIN/COMMIT 으로 감싼다.
14
+ -- → 여기엔 트랜잭션 구문/`@schema-retarget: off` 를 넣지 않는다.
15
+
16
+ -- ── bot_status ──────────────────────────────────────────────
17
+ ALTER TABLE semicolony.bot_status
18
+ ADD COLUMN IF NOT EXISTS tenant_id UUID,
19
+ ADD COLUMN IF NOT EXISTS scope TEXT NOT NULL DEFAULT 'platform-global'
20
+ CHECK (scope IN ('tenant-local', 'platform-global')),
21
+ ADD COLUMN IF NOT EXISTS install_id UUID;
22
+ CREATE INDEX IF NOT EXISTS idx_bot_status_tenant
23
+ ON semicolony.bot_status (tenant_id) WHERE tenant_id IS NOT NULL;
24
+
25
+ -- ── bot_sessions ────────────────────────────────────────────
26
+ ALTER TABLE semicolony.bot_sessions
27
+ ADD COLUMN IF NOT EXISTS tenant_id UUID,
28
+ ADD COLUMN IF NOT EXISTS scope TEXT NOT NULL DEFAULT 'platform-global'
29
+ CHECK (scope IN ('tenant-local', 'platform-global')),
30
+ ADD COLUMN IF NOT EXISTS install_id UUID;
31
+ CREATE INDEX IF NOT EXISTS idx_bot_sessions_tenant
32
+ ON semicolony.bot_sessions (tenant_id) WHERE tenant_id IS NOT NULL;
33
+
34
+ -- ── bot_commitments ─────────────────────────────────────────
35
+ ALTER TABLE semicolony.bot_commitments
36
+ ADD COLUMN IF NOT EXISTS tenant_id UUID,
37
+ ADD COLUMN IF NOT EXISTS scope TEXT NOT NULL DEFAULT 'platform-global'
38
+ CHECK (scope IN ('tenant-local', 'platform-global')),
39
+ ADD COLUMN IF NOT EXISTS install_id UUID;
40
+ CREATE INDEX IF NOT EXISTS idx_bot_commitments_tenant
41
+ ON semicolony.bot_commitments (tenant_id) WHERE tenant_id IS NOT NULL;
42
+
43
+ -- ── action_items ────────────────────────────────────────────
44
+ -- (semicolony.service_action_items 는 이 베이스 테이블에 대한 VIEW — 자동 전파)
45
+ ALTER TABLE semicolony.action_items
46
+ ADD COLUMN IF NOT EXISTS tenant_id UUID,
47
+ ADD COLUMN IF NOT EXISTS scope TEXT NOT NULL DEFAULT 'platform-global'
48
+ CHECK (scope IN ('tenant-local', 'platform-global')),
49
+ ADD COLUMN IF NOT EXISTS install_id UUID;
50
+ CREATE INDEX IF NOT EXISTS idx_action_items_tenant
51
+ ON semicolony.action_items (tenant_id) WHERE tenant_id IS NOT NULL;
52
+
53
+ -- ── agent_personas ──────────────────────────────────────────
54
+ ALTER TABLE semicolony.agent_personas
55
+ ADD COLUMN IF NOT EXISTS tenant_id UUID,
56
+ ADD COLUMN IF NOT EXISTS scope TEXT NOT NULL DEFAULT 'platform-global'
57
+ CHECK (scope IN ('tenant-local', 'platform-global')),
58
+ ADD COLUMN IF NOT EXISTS install_id UUID;
59
+ CREATE INDEX IF NOT EXISTS idx_agent_personas_tenant
60
+ ON semicolony.agent_personas (tenant_id) WHERE tenant_id IS NOT NULL;
61
+
62
+ -- ── agent_persona_revisions ─────────────────────────────────
63
+ -- install_id 는 슬러그 단위로 의미가 옅으므로 tenant_id + scope 만 추가(revision 은
64
+ -- 부모 persona 의 tenant 를 상속). install_id 도 가산해 분석 일관성을 유지한다.
65
+ ALTER TABLE semicolony.agent_persona_revisions
66
+ ADD COLUMN IF NOT EXISTS tenant_id UUID,
67
+ ADD COLUMN IF NOT EXISTS scope TEXT NOT NULL DEFAULT 'platform-global'
68
+ CHECK (scope IN ('tenant-local', 'platform-global')),
69
+ ADD COLUMN IF NOT EXISTS install_id UUID;
70
+ CREATE INDEX IF NOT EXISTS idx_agent_persona_revisions_tenant
71
+ ON semicolony.agent_persona_revisions (tenant_id) WHERE tenant_id IS NOT NULL;