@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.
- package/dist/bundle.js +1230 -1224
- package/migrations/185_worker_agent_feedback_scope.sql +309 -0
- package/migrations/README.md +1 -0
- package/package.json +1 -1
|
@@ -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;
|
package/migrations/README.md
CHANGED
|
@@ -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
|
|