@team-semicolon/semicolony-cli 4.18.78 → 4.18.80

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.
@@ -0,0 +1,309 @@
1
+ -- 185_worker_agent_feedback_scope.sql
2
+ --
3
+ -- Promote the two fixed Hermes actor credentials from Phase 3 KB read to the
4
+ -- exact Phase 4 capability pair. Existing active credentials are updated only
5
+ -- through their normalized actor binding; the release-only host credential is
6
+ -- not selected. The existing record_feedback capability remains the sole DB
7
+ -- write primitive and now preserves validated runtime-native attribution.
8
+
9
+ CREATE OR REPLACE FUNCTION semicolony.issue_worker_agent_actor_credential(
10
+ p_team_tenant_id uuid,
11
+ p_actor_id text,
12
+ p_token_hash text,
13
+ p_token_prefix text,
14
+ p_issued_by text,
15
+ p_expires_days integer
16
+ )
17
+ RETURNS uuid
18
+ LANGUAGE plpgsql
19
+ SECURITY DEFINER
20
+ SET search_path = pg_catalog
21
+ AS $function$
22
+ DECLARE
23
+ v_credential_id uuid;
24
+ v_previous_credential_id uuid;
25
+ v_runtime_profile text;
26
+ BEGIN
27
+ IF p_actor_id NOT IN ('semi', 'colony') THEN
28
+ RAISE EXCEPTION 'invalid Worker-agent actor' USING ERRCODE = '22023';
29
+ END IF;
30
+ v_runtime_profile := CASE p_actor_id WHEN 'semi' THEN 'semo-semi' ELSE 'semo-colony' END;
31
+ IF p_token_hash IS NULL OR p_token_hash !~ '^[0-9a-f]{64}$' THEN
32
+ RAISE EXCEPTION 'invalid worker-agent actor credential token hash' USING ERRCODE = '22023';
33
+ END IF;
34
+ IF p_token_prefix IS NULL
35
+ OR p_token_prefix !~ ('^wck_' || p_actor_id || '_[A-Za-z0-9_-]{6}$') THEN
36
+ RAISE EXCEPTION 'invalid worker-agent actor credential token prefix' USING ERRCODE = '22023';
37
+ END IF;
38
+ IF p_issued_by IS NULL OR p_issued_by <> btrim(p_issued_by)
39
+ OR btrim(p_issued_by) = '' OR char_length(p_issued_by) > 128
40
+ OR p_issued_by ~ '[[:cntrl:]]' THEN
41
+ RAISE EXCEPTION 'invalid credential issuer' USING ERRCODE = '22023';
42
+ END IF;
43
+ IF p_expires_days IS NULL OR p_expires_days < 1 OR p_expires_days > 90 THEN
44
+ RAISE EXCEPTION 'worker-agent credential expiry must be between 1 and 90 days'
45
+ USING ERRCODE = '22023';
46
+ END IF;
47
+
48
+ PERFORM t.id
49
+ FROM public.tenants AS t
50
+ WHERE t.id = p_team_tenant_id
51
+ AND t.slug = 'team-semicolon'
52
+ AND t.tenant_type = 'team'
53
+ AND coalesce(t.metadata ->> 'status', 'active') = 'active';
54
+ IF NOT FOUND THEN
55
+ RAISE EXCEPTION 'active team-semicolon tenant not found' USING ERRCODE = 'P0002';
56
+ END IF;
57
+
58
+ PERFORM wd.device_id
59
+ FROM semicolony.worker_devices AS wd
60
+ WHERE wd.device_id = 'hermes-principals@hermes'
61
+ AND wd.installation_kind = 'agent-runtime'
62
+ AND wd.installed_profile_id = 'semo-ops-worker-agent'
63
+ AND wd.kb_access_mode = 'worker-gateway'
64
+ AND wd.status = 'active'
65
+ FOR UPDATE;
66
+ IF NOT FOUND THEN
67
+ RAISE EXCEPTION 'active Worker-agent installation not found' USING ERRCODE = 'P0002';
68
+ END IF;
69
+
70
+ SELECT binding.gateway_credential_id
71
+ INTO v_previous_credential_id
72
+ FROM semicolony.worker_agent_actor_bindings AS binding
73
+ WHERE binding.installation_id = 'hermes-principals@hermes'
74
+ AND binding.actor_id = p_actor_id
75
+ FOR UPDATE;
76
+
77
+ INSERT INTO semicolony.gateway_credentials (
78
+ tenant_id, tenant_slug, token_hash, token_prefix, scopes, issued_by, expires_at, metadata
79
+ ) VALUES (
80
+ p_team_tenant_id,
81
+ 'team-semicolon',
82
+ p_token_hash,
83
+ p_token_prefix,
84
+ ARRAY['kb:read', 'feedback:write']::text[],
85
+ p_issued_by,
86
+ clock_timestamp() + make_interval(days => p_expires_days),
87
+ jsonb_build_object(
88
+ 'credential_kind', 'worker-agent-actor',
89
+ 'actor_id', p_actor_id,
90
+ 'installation_id', 'hermes-principals@hermes',
91
+ 'profile_id', 'semo-ops-worker-agent',
92
+ 'runtime_kind', 'hermes-cli',
93
+ 'runtime_profile', v_runtime_profile,
94
+ 'tenant_anchor', 'team-semicolon',
95
+ 'lineage_status', 'active'
96
+ )
97
+ )
98
+ RETURNING id INTO v_credential_id;
99
+
100
+ INSERT INTO semicolony.worker_agent_actor_bindings (
101
+ installation_id, actor_id, runtime_profile, profile_id, runtime_kind,
102
+ gateway_credential_id, status, issued_at, rotated_at, updated_at
103
+ ) VALUES (
104
+ 'hermes-principals@hermes', p_actor_id, v_runtime_profile,
105
+ 'semo-ops-worker-agent', 'hermes-cli', v_credential_id, 'active',
106
+ clock_timestamp(), CASE WHEN v_previous_credential_id IS NULL THEN NULL ELSE clock_timestamp() END,
107
+ clock_timestamp()
108
+ )
109
+ ON CONFLICT (installation_id, actor_id) DO UPDATE
110
+ SET runtime_profile = EXCLUDED.runtime_profile,
111
+ profile_id = EXCLUDED.profile_id,
112
+ runtime_kind = EXCLUDED.runtime_kind,
113
+ gateway_credential_id = EXCLUDED.gateway_credential_id,
114
+ status = 'active',
115
+ rotated_at = clock_timestamp(),
116
+ updated_at = clock_timestamp();
117
+
118
+ IF v_previous_credential_id IS NOT NULL AND v_previous_credential_id <> v_credential_id THEN
119
+ UPDATE semicolony.gateway_credentials
120
+ SET status = 'revoked',
121
+ revoked_at = clock_timestamp(),
122
+ revoked_reason = 'worker-agent actor credential rotated'
123
+ WHERE id = v_previous_credential_id
124
+ AND status = 'active';
125
+ END IF;
126
+
127
+ RETURN v_credential_id;
128
+ END;
129
+ $function$;
130
+
131
+ REVOKE ALL ON FUNCTION semicolony.issue_worker_agent_actor_credential(
132
+ uuid, text, text, text, text, integer
133
+ ) FROM PUBLIC, sc_app, sc_provider;
134
+ GRANT EXECUTE ON FUNCTION semicolony.issue_worker_agent_actor_credential(
135
+ uuid, text, text, text, text, integer
136
+ ) TO sc_app;
137
+
138
+ UPDATE semicolony.gateway_credentials AS gc
139
+ SET scopes = ARRAY['kb:read', 'feedback:write']::text[]
140
+ FROM semicolony.worker_agent_actor_bindings AS binding
141
+ WHERE binding.gateway_credential_id = gc.id
142
+ AND binding.installation_id = 'hermes-principals@hermes'
143
+ AND binding.profile_id = 'semo-ops-worker-agent'
144
+ AND binding.runtime_kind = 'hermes-cli'
145
+ AND binding.actor_id IN ('semi', 'colony')
146
+ AND binding.status = 'active'
147
+ AND gc.status = 'active'
148
+ AND coalesce(gc.expires_at, 'infinity'::timestamptz) > clock_timestamp()
149
+ AND gc.metadata ->> 'credential_kind' = 'worker-agent-actor'
150
+ AND gc.metadata ->> 'actor_id' = binding.actor_id
151
+ AND gc.metadata ->> 'installation_id' = binding.installation_id
152
+ AND gc.metadata ->> 'profile_id' = binding.profile_id
153
+ AND gc.metadata ->> 'runtime_kind' = binding.runtime_kind
154
+ AND gc.scopes IS DISTINCT FROM ARRAY['kb:read', 'feedback:write']::text[];
155
+
156
+ CREATE OR REPLACE FUNCTION semicolony.record_feedback(
157
+ p_slug text,
158
+ p_summary text,
159
+ p_content text,
160
+ p_metadata jsonb,
161
+ p_embedding text,
162
+ p_worker_id text,
163
+ p_device_id text
164
+ )
165
+ RETURNS TABLE(kb_id bigint, action_item_id uuid, duplicate boolean)
166
+ LANGUAGE plpgsql
167
+ SECURITY DEFINER
168
+ SET search_path = pg_catalog
169
+ AS $function$
170
+ DECLARE
171
+ v_kb_id bigint;
172
+ v_action_item_id uuid;
173
+ v_duplicate boolean := false;
174
+ v_metadata jsonb;
175
+ v_priority text;
176
+ v_source text;
177
+ BEGIN
178
+ IF p_slug IS NULL
179
+ OR char_length(p_slug) < 3
180
+ OR char_length(p_slug) > 60
181
+ OR p_slug !~ '^[[:alnum:]가-힣][[:alnum:]가-힣-]*$' THEN
182
+ RAISE EXCEPTION 'invalid feedback slug' USING ERRCODE = '22023';
183
+ END IF;
184
+ IF p_summary IS NULL OR char_length(btrim(p_summary)) < 8 OR char_length(p_summary) > 500 THEN
185
+ RAISE EXCEPTION 'invalid feedback summary' USING ERRCODE = '22023';
186
+ END IF;
187
+ IF p_content IS NULL OR char_length(p_content) < 30 OR char_length(p_content) > 30000 THEN
188
+ RAISE EXCEPTION 'invalid feedback content' USING ERRCODE = '22023';
189
+ END IF;
190
+ IF p_metadata IS NULL OR jsonb_typeof(p_metadata) <> 'object' THEN
191
+ RAISE EXCEPTION 'invalid feedback metadata' USING ERRCODE = '22023';
192
+ END IF;
193
+ IF p_metadata ->> 'category' NOT IN ('context-failure', 'env-drift', 'policy-gap', 'tooling') THEN
194
+ RAISE EXCEPTION 'invalid feedback category' USING ERRCODE = '22023';
195
+ END IF;
196
+ v_priority := coalesce(p_metadata ->> 'severity', 'normal');
197
+ IF v_priority NOT IN ('low', 'normal', 'high', 'urgent') THEN
198
+ RAISE EXCEPTION 'invalid feedback severity' USING ERRCODE = '22023';
199
+ END IF;
200
+ IF p_worker_id IS NULL OR p_worker_id !~ '^[a-z][a-z0-9-]{0,63}$'
201
+ OR p_device_id IS NULL
202
+ OR (p_device_id !~ '^[a-z][a-z0-9-]{0,99}$'
203
+ AND p_device_id <> 'hermes-principals@hermes') THEN
204
+ RAISE EXCEPTION 'invalid worker/device binding' USING ERRCODE = '22023';
205
+ END IF;
206
+
207
+ v_source := coalesce(p_metadata ->> 'source', 'session-hook');
208
+ IF v_source NOT IN ('session-hook', 'runtime-native-tool') THEN
209
+ RAISE EXCEPTION 'invalid feedback source' USING ERRCODE = '22023';
210
+ END IF;
211
+ IF v_source = 'runtime-native-tool' AND NOT (
212
+ p_worker_id IN ('semi', 'colony')
213
+ AND p_device_id = 'hermes-principals@hermes'
214
+ AND p_metadata ->> 'reported_by' = p_worker_id
215
+ AND p_metadata ->> 'installation_id' = 'hermes-principals@hermes'
216
+ AND p_metadata ->> 'runtime_kind' = 'hermes-cli'
217
+ AND p_metadata ->> 'profile_id' = 'semo-ops-worker-agent'
218
+ AND (
219
+ (p_worker_id = 'semi' AND p_metadata ->> 'runtime_profile' = 'semo-semi')
220
+ OR (p_worker_id = 'colony' AND p_metadata ->> 'runtime_profile' = 'semo-colony')
221
+ )
222
+ ) THEN
223
+ RAISE EXCEPTION 'invalid runtime-native feedback attribution' USING ERRCODE = '22023';
224
+ END IF;
225
+
226
+ PERFORM p_embedding::public.vector;
227
+ PERFORM pg_advisory_xact_lock(hashtextextended('feedback:' || p_slug, 0));
228
+
229
+ v_metadata := p_metadata || jsonb_build_object(
230
+ 'status', 'open',
231
+ 'source', v_source,
232
+ 'worker_id', p_worker_id,
233
+ 'device_id', p_device_id
234
+ );
235
+
236
+ INSERT INTO semicolony.knowledge_base AS kb (
237
+ domain, key, sub_key, content, created_by, embedding, metadata
238
+ ) VALUES (
239
+ 'semicolon',
240
+ 'feedback',
241
+ p_slug,
242
+ p_content,
243
+ 'feedback-pipeline:' || p_worker_id,
244
+ p_embedding::public.vector,
245
+ v_metadata
246
+ )
247
+ ON CONFLICT (domain, key, sub_key) DO UPDATE SET
248
+ content = EXCLUDED.content,
249
+ embedding = EXCLUDED.embedding,
250
+ metadata = coalesce(kb.metadata, '{}'::jsonb) || EXCLUDED.metadata,
251
+ archived = false,
252
+ updated_at = clock_timestamp()
253
+ RETURNING kb.kb_id INTO v_kb_id;
254
+
255
+ SELECT ai.action_item_id
256
+ INTO v_action_item_id
257
+ FROM semicolony.action_items AS ai
258
+ WHERE ai.owner_domain = 'semicolon'
259
+ AND ai.source = 'feedback'
260
+ AND ai.status = 'open'
261
+ AND ai.metadata ->> 'feedback_slug' = p_slug
262
+ ORDER BY ai.created_at DESC
263
+ LIMIT 1
264
+ FOR UPDATE;
265
+
266
+ IF FOUND THEN
267
+ v_duplicate := true;
268
+ ELSE
269
+ INSERT INTO semicolony.action_items (
270
+ owner_domain,
271
+ target_domain,
272
+ description,
273
+ priority,
274
+ category,
275
+ source,
276
+ related_url,
277
+ metadata
278
+ ) VALUES (
279
+ 'semicolon',
280
+ 'semicolon',
281
+ '[SEMO Ops feedback] ' || btrim(p_summary),
282
+ v_priority,
283
+ 'feedback',
284
+ 'feedback',
285
+ 'semo kb get semicolon feedback ' || p_slug,
286
+ jsonb_build_object(
287
+ 'feedback_slug', p_slug,
288
+ 'failed_rule', coalesce(p_metadata ->> 'failed_rule', 'general'),
289
+ 'worker_id', p_worker_id,
290
+ 'device_id', p_device_id,
291
+ 'reported_by', p_metadata ->> 'reported_by',
292
+ 'installation_id', p_metadata ->> 'installation_id',
293
+ 'runtime_kind', p_metadata ->> 'runtime_kind',
294
+ 'profile_id', p_metadata ->> 'profile_id'
295
+ )
296
+ )
297
+ RETURNING action_items.action_item_id INTO v_action_item_id;
298
+ END IF;
299
+
300
+ RETURN QUERY SELECT v_kb_id, v_action_item_id, v_duplicate;
301
+ END;
302
+ $function$;
303
+
304
+ REVOKE ALL ON FUNCTION semicolony.record_feedback(
305
+ text, text, text, jsonb, text, text, text
306
+ ) FROM PUBLIC, sc_app, sc_provider;
307
+ GRANT EXECUTE ON FUNCTION semicolony.record_feedback(
308
+ text, text, text, jsonb, text, text, text
309
+ ) TO sc_app;
@@ -23,6 +23,7 @@ SEMICOLONY CLI의 PostgreSQL 마이그레이션 파일들.
23
23
  | `179_worker_feedback_credential_scope.sql` | 워커 자격증명 `feedback:write` 허용 및 활성 디바이스 바인딩에 범위 백필 |
24
24
  | `183_worker_agent_gateway_credentials.sql` | Hermes Agent host release credential과 Semi/Colony actor KB-read credential의 서버 바인딩 및 회전 |
25
25
  | `184_worker_agent_workspace_profile.sql` | Hermes Agent immutable release가 참조하는 `semo-ops-worker-agent` DB 정본 프로필 시드 |
26
+ | `185_worker_agent_feedback_scope.sql` | Hermes Semi/Colony actor에 정확한 feedback 범위·귀속을 추가하고 기존 바인딩만 백필 |
26
27
 
27
28
  ## 007: Flat Skill Naming
28
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semicolony-cli",
3
- "version": "4.18.78",
3
+ "version": "4.18.80",
4
4
  "description": "SemiColony CLI - AI operations and agent orchestration installer",
5
5
  "main": "dist/bundle.js",
6
6
  "bin": {