@team-semicolon/semicolony-cli 4.18.80 → 4.18.82

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,468 @@
1
+ -- 186_worker_agent_decision_capture.sql
2
+ --
3
+ -- Add one constrained decision capability for the two fixed Hermes actors.
4
+ -- An operator-owned authorization fixes actor, target, content digest, and
5
+ -- authority. The Gateway role can execute the recorder but cannot inspect or
6
+ -- mutate the authorization ledger or either target table directly.
7
+
8
+ CREATE TABLE semicolony.worker_agent_decision_authorizations (
9
+ decision_ref uuid PRIMARY KEY DEFAULT gen_random_uuid(),
10
+ actor_id text NOT NULL CHECK (actor_id IN ('semi', 'colony')),
11
+ target_domain text NOT NULL,
12
+ target_slug text NOT NULL,
13
+ content_sha256 text NOT NULL CHECK (content_sha256 ~ '^[0-9a-f]{64}$'),
14
+ decided_by text NOT NULL,
15
+ authority_kind text NOT NULL DEFAULT 'explicit-human-decision'
16
+ CHECK (authority_kind = 'explicit-human-decision'),
17
+ authority_reference text NOT NULL,
18
+ issued_by text NOT NULL,
19
+ issued_at timestamptz NOT NULL DEFAULT clock_timestamp(),
20
+ expires_at timestamptz NOT NULL,
21
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
22
+ revoked_at timestamptz,
23
+ revoked_by text,
24
+ consumed_at timestamptz,
25
+ consumed_kb_id bigint,
26
+ CONSTRAINT worker_agent_decision_target_check CHECK (
27
+ target_domain ~ '^[a-z][a-z0-9-]{1,62}$'
28
+ AND target_domain <> 'semo'
29
+ AND target_domain !~ '^t-'
30
+ AND target_slug ~ '^[a-z0-9][a-z0-9-]{2,99}$'
31
+ ),
32
+ CONSTRAINT worker_agent_decision_authority_check CHECK (
33
+ decided_by = btrim(decided_by)
34
+ AND char_length(decided_by) BETWEEN 1 AND 128
35
+ AND decided_by !~ '[[:cntrl:]]'
36
+ AND authority_reference = btrim(authority_reference)
37
+ AND char_length(authority_reference) BETWEEN 8 AND 500
38
+ AND authority_reference !~ '[[:cntrl:]]'
39
+ AND issued_by = btrim(issued_by)
40
+ AND char_length(issued_by) BETWEEN 1 AND 128
41
+ AND issued_by !~ '[[:cntrl:]]'
42
+ ),
43
+ CONSTRAINT worker_agent_decision_revocation_check CHECK (
44
+ (status = 'active' AND revoked_at IS NULL AND revoked_by IS NULL)
45
+ OR (status = 'revoked' AND revoked_at IS NOT NULL AND revoked_by IS NOT NULL)
46
+ )
47
+ );
48
+
49
+ CREATE UNIQUE INDEX worker_agent_decision_authorizations_active_target
50
+ ON semicolony.worker_agent_decision_authorizations (actor_id, target_domain, target_slug)
51
+ WHERE status = 'active';
52
+
53
+ COMMENT ON TABLE semicolony.worker_agent_decision_authorizations IS
54
+ 'Operator-issued exact authorization for actor-bound SEMO Ops decision recording; contains no bearer material.';
55
+
56
+ CREATE OR REPLACE FUNCTION semicolony.issue_worker_agent_decision_authorization(
57
+ p_actor_id text,
58
+ p_target_domain text,
59
+ p_target_slug text,
60
+ p_content_sha256 text,
61
+ p_decided_by text,
62
+ p_authority_kind text,
63
+ p_authority_reference text,
64
+ p_issued_by text,
65
+ p_expires_minutes integer
66
+ )
67
+ RETURNS uuid
68
+ LANGUAGE plpgsql
69
+ SECURITY DEFINER
70
+ SET search_path = pg_catalog
71
+ AS $function$
72
+ DECLARE
73
+ v_decision_ref uuid;
74
+ BEGIN
75
+ IF p_actor_id NOT IN ('semi', 'colony') THEN
76
+ RAISE EXCEPTION 'invalid Worker-agent actor' USING ERRCODE = '22023';
77
+ END IF;
78
+ IF p_target_domain IS NULL OR p_target_domain !~ '^[a-z][a-z0-9-]{1,62}$'
79
+ OR p_target_domain = 'semo' OR p_target_domain ~ '^t-' THEN
80
+ RAISE EXCEPTION 'invalid decision domain' USING ERRCODE = '22023';
81
+ END IF;
82
+ IF p_target_slug IS NULL OR p_target_slug !~ '^[a-z0-9][a-z0-9-]{2,99}$' THEN
83
+ RAISE EXCEPTION 'invalid decision slug' USING ERRCODE = '22023';
84
+ END IF;
85
+ IF p_content_sha256 IS NULL OR p_content_sha256 !~ '^[0-9a-f]{64}$' THEN
86
+ RAISE EXCEPTION 'invalid decision content digest' USING ERRCODE = '22023';
87
+ END IF;
88
+ IF p_authority_kind IS DISTINCT FROM 'explicit-human-decision' THEN
89
+ RAISE EXCEPTION 'decision authority must be explicit' USING ERRCODE = '22023';
90
+ END IF;
91
+ IF p_decided_by IS NULL OR p_decided_by <> btrim(p_decided_by)
92
+ OR char_length(p_decided_by) < 1 OR char_length(p_decided_by) > 128
93
+ OR p_decided_by ~ '[[:cntrl:]]' THEN
94
+ RAISE EXCEPTION 'invalid decision authority' USING ERRCODE = '22023';
95
+ END IF;
96
+ IF p_authority_reference IS NULL OR p_authority_reference <> btrim(p_authority_reference)
97
+ OR char_length(p_authority_reference) < 8 OR char_length(p_authority_reference) > 500
98
+ OR p_authority_reference ~ '[[:cntrl:]]' THEN
99
+ RAISE EXCEPTION 'invalid authority reference' USING ERRCODE = '22023';
100
+ END IF;
101
+ IF p_issued_by IS NULL OR p_issued_by <> btrim(p_issued_by)
102
+ OR char_length(p_issued_by) < 1 OR char_length(p_issued_by) > 128
103
+ OR p_issued_by ~ '[[:cntrl:]]' THEN
104
+ RAISE EXCEPTION 'invalid authorization issuer' USING ERRCODE = '22023';
105
+ END IF;
106
+ IF p_expires_minutes IS NULL OR p_expires_minutes < 1 OR p_expires_minutes > 1440 THEN
107
+ RAISE EXCEPTION 'decision authorization expiry must be between 1 and 1440 minutes'
108
+ USING ERRCODE = '22023';
109
+ END IF;
110
+
111
+ PERFORM wd.device_id
112
+ FROM semicolony.worker_devices AS wd
113
+ WHERE wd.device_id = 'hermes-principals@hermes'
114
+ AND wd.installation_kind = 'agent-runtime'
115
+ AND wd.installed_profile_id = 'semo-ops-worker-agent'
116
+ AND wd.kb_access_mode = 'worker-gateway'
117
+ AND wd.status = 'active';
118
+ IF NOT FOUND THEN
119
+ RAISE EXCEPTION 'active Worker-agent installation not found' USING ERRCODE = 'P0002';
120
+ END IF;
121
+
122
+ INSERT INTO semicolony.worker_agent_decision_authorizations (
123
+ actor_id, target_domain, target_slug, content_sha256, decided_by,
124
+ authority_kind, authority_reference, issued_by, expires_at
125
+ ) VALUES (
126
+ p_actor_id, p_target_domain, p_target_slug, p_content_sha256, p_decided_by,
127
+ p_authority_kind, p_authority_reference, p_issued_by,
128
+ clock_timestamp() + make_interval(mins => p_expires_minutes)
129
+ )
130
+ RETURNING decision_ref INTO v_decision_ref;
131
+
132
+ RETURN v_decision_ref;
133
+ END;
134
+ $function$;
135
+
136
+ CREATE OR REPLACE FUNCTION semicolony.revoke_worker_agent_decision_authorization(
137
+ p_decision_ref uuid,
138
+ p_revoked_by text
139
+ )
140
+ RETURNS boolean
141
+ LANGUAGE plpgsql
142
+ SECURITY DEFINER
143
+ SET search_path = pg_catalog
144
+ AS $function$
145
+ BEGIN
146
+ IF p_revoked_by IS NULL OR p_revoked_by <> btrim(p_revoked_by)
147
+ OR char_length(p_revoked_by) < 1 OR char_length(p_revoked_by) > 128
148
+ OR p_revoked_by ~ '[[:cntrl:]]' THEN
149
+ RAISE EXCEPTION 'invalid revocation authority' USING ERRCODE = '22023';
150
+ END IF;
151
+ UPDATE semicolony.worker_agent_decision_authorizations
152
+ SET status = 'revoked', revoked_at = clock_timestamp(), revoked_by = p_revoked_by
153
+ WHERE decision_ref = p_decision_ref AND status = 'active';
154
+ RETURN FOUND;
155
+ END;
156
+ $function$;
157
+
158
+ CREATE OR REPLACE FUNCTION semicolony.record_worker_agent_decision(
159
+ p_credential_id uuid,
160
+ p_decision_ref uuid,
161
+ p_domain text,
162
+ p_slug text,
163
+ p_content text,
164
+ p_embedding text
165
+ )
166
+ RETURNS TABLE(
167
+ kb_id bigint,
168
+ duplicate boolean,
169
+ actor_id text,
170
+ decided_by text,
171
+ decision_ref uuid
172
+ )
173
+ LANGUAGE plpgsql
174
+ SECURITY DEFINER
175
+ SET search_path = pg_catalog
176
+ AS $function$
177
+ DECLARE
178
+ v_actor_id text;
179
+ v_runtime_profile text;
180
+ v_authorization semicolony.worker_agent_decision_authorizations%ROWTYPE;
181
+ v_content_sha256 text;
182
+ v_kb_id bigint;
183
+ v_existing_ref text;
184
+ v_existing_content text;
185
+ v_duplicate boolean := false;
186
+ BEGIN
187
+ IF p_credential_id IS NULL OR p_decision_ref IS NULL THEN
188
+ RAISE EXCEPTION 'trusted credential and decision reference are required'
189
+ USING ERRCODE = '22023';
190
+ END IF;
191
+ IF p_domain IS NULL OR p_domain !~ '^[a-z][a-z0-9-]{1,62}$'
192
+ OR p_domain = 'semo' OR p_domain ~ '^t-' THEN
193
+ RAISE EXCEPTION 'invalid decision domain' USING ERRCODE = '22023';
194
+ END IF;
195
+ IF p_slug IS NULL OR p_slug !~ '^[a-z0-9][a-z0-9-]{2,99}$' THEN
196
+ RAISE EXCEPTION 'invalid decision slug' USING ERRCODE = '22023';
197
+ END IF;
198
+ IF p_content IS NULL OR p_content <> btrim(p_content)
199
+ OR char_length(p_content) < 30 OR char_length(p_content) > 10000
200
+ OR translate(p_content, E'\n\t', '') ~ '[[:cntrl:]]'
201
+ OR p_content ~* '(DATABASE_URL|postgres://|SEMO_OPS_WORKER_KB_TOKEN|Authorization:[[:space:]]*Bearer|wck_[A-Za-z0-9_-]+)' THEN
202
+ RAISE EXCEPTION 'invalid decision content' USING ERRCODE = '22023';
203
+ END IF;
204
+ PERFORM p_embedding::public.vector;
205
+ v_content_sha256 := encode(public.digest(convert_to(p_content, 'UTF8'), 'sha256'), 'hex');
206
+
207
+ SELECT binding.actor_id, binding.runtime_profile
208
+ INTO v_actor_id, v_runtime_profile
209
+ FROM semicolony.worker_agent_actor_bindings AS binding
210
+ JOIN semicolony.gateway_credentials AS gc
211
+ ON gc.id = binding.gateway_credential_id
212
+ WHERE binding.gateway_credential_id = p_credential_id
213
+ AND binding.installation_id = 'hermes-principals@hermes'
214
+ AND binding.profile_id = 'semo-ops-worker-agent'
215
+ AND binding.runtime_kind = 'hermes-cli'
216
+ AND binding.actor_id IN ('semi', 'colony')
217
+ AND binding.status = 'active'
218
+ AND gc.status = 'active'
219
+ AND coalesce(gc.expires_at, 'infinity'::timestamptz) > clock_timestamp()
220
+ AND gc.metadata ->> 'credential_kind' = 'worker-agent-actor'
221
+ AND gc.metadata ->> 'actor_id' = binding.actor_id
222
+ AND gc.metadata ->> 'installation_id' = binding.installation_id
223
+ AND gc.metadata ->> 'profile_id' = binding.profile_id
224
+ AND gc.metadata ->> 'runtime_kind' = binding.runtime_kind
225
+ AND gc.metadata ->> 'runtime_profile' = binding.runtime_profile
226
+ AND gc.scopes = ARRAY['kb:read', 'feedback:write', 'decision:write']::text[]
227
+ FOR UPDATE OF binding, gc;
228
+ IF NOT FOUND THEN
229
+ RAISE EXCEPTION 'active decision credential binding not found' USING ERRCODE = '42501';
230
+ END IF;
231
+
232
+ PERFORM pg_advisory_xact_lock(hashtextextended('worker-agent-decision:' || p_decision_ref::text, 0));
233
+
234
+ SELECT auth.*
235
+ INTO v_authorization
236
+ FROM semicolony.worker_agent_decision_authorizations AS auth
237
+ WHERE auth.decision_ref = p_decision_ref
238
+ FOR UPDATE;
239
+ IF NOT FOUND THEN
240
+ RAISE EXCEPTION 'decision authorization not found' USING ERRCODE = 'P0002';
241
+ END IF;
242
+ IF v_authorization.status <> 'active'
243
+ OR v_authorization.expires_at <= clock_timestamp()
244
+ OR v_authorization.authority_kind <> 'explicit-human-decision'
245
+ OR v_authorization.actor_id <> v_actor_id
246
+ OR v_authorization.target_domain <> p_domain
247
+ OR v_authorization.target_slug <> p_slug
248
+ OR v_authorization.content_sha256 <> v_content_sha256 THEN
249
+ RAISE EXCEPTION 'decision authorization mismatch' USING ERRCODE = '42501';
250
+ END IF;
251
+
252
+ SELECT kb.kb_id, kb.metadata ->> 'decision_ref', kb.content
253
+ INTO v_kb_id, v_existing_ref, v_existing_content
254
+ FROM semicolony.knowledge_base AS kb
255
+ WHERE kb.domain = p_domain AND kb.key = 'decision' AND kb.sub_key = p_slug
256
+ FOR UPDATE;
257
+
258
+ IF FOUND THEN
259
+ IF v_existing_ref <> p_decision_ref::text OR v_existing_content <> p_content THEN
260
+ RAISE EXCEPTION 'decision target already exists with different authority'
261
+ USING ERRCODE = '23505';
262
+ END IF;
263
+ v_duplicate := true;
264
+ ELSE
265
+ INSERT INTO semicolony.knowledge_base (
266
+ domain, key, sub_key, content, created_by, embedding, metadata
267
+ ) VALUES (
268
+ p_domain,
269
+ 'decision',
270
+ p_slug,
271
+ p_content,
272
+ 'decision-pipeline:' || v_actor_id,
273
+ p_embedding::public.vector,
274
+ jsonb_build_object(
275
+ 'source', 'runtime-native-tool',
276
+ 'decision_ref', p_decision_ref::text,
277
+ 'decided_by', v_authorization.decided_by,
278
+ 'authority_kind', v_authorization.authority_kind,
279
+ 'authority_reference', v_authorization.authority_reference,
280
+ 'reported_by', v_actor_id,
281
+ 'installation_id', 'hermes-principals@hermes',
282
+ 'runtime_kind', 'hermes-cli',
283
+ 'profile_id', 'semo-ops-worker-agent',
284
+ 'runtime_profile', v_runtime_profile
285
+ )
286
+ )
287
+ RETURNING knowledge_base.kb_id INTO v_kb_id;
288
+ END IF;
289
+
290
+ UPDATE semicolony.worker_agent_decision_authorizations
291
+ SET consumed_at = coalesce(consumed_at, clock_timestamp()),
292
+ consumed_kb_id = v_kb_id
293
+ WHERE worker_agent_decision_authorizations.decision_ref = p_decision_ref;
294
+
295
+ RETURN QUERY SELECT v_kb_id, v_duplicate, v_actor_id,
296
+ v_authorization.decided_by, p_decision_ref;
297
+ END;
298
+ $function$;
299
+
300
+ REVOKE ALL ON TABLE semicolony.worker_agent_decision_authorizations
301
+ FROM PUBLIC, sc_app, sc_provider;
302
+
303
+ REVOKE ALL ON FUNCTION semicolony.issue_worker_agent_decision_authorization(
304
+ text, text, text, text, text, text, text, text, integer
305
+ ) FROM PUBLIC, sc_app, sc_provider;
306
+ GRANT EXECUTE ON FUNCTION semicolony.issue_worker_agent_decision_authorization(
307
+ text, text, text, text, text, text, text, text, integer
308
+ ) TO app;
309
+
310
+ REVOKE ALL ON FUNCTION semicolony.revoke_worker_agent_decision_authorization(uuid, text)
311
+ FROM PUBLIC, sc_app, sc_provider;
312
+ GRANT EXECUTE ON FUNCTION semicolony.revoke_worker_agent_decision_authorization(uuid, text)
313
+ TO app;
314
+
315
+ REVOKE ALL ON FUNCTION semicolony.record_worker_agent_decision(
316
+ uuid, uuid, text, text, text, text
317
+ ) FROM PUBLIC, app, sc_provider;
318
+ GRANT EXECUTE ON FUNCTION semicolony.record_worker_agent_decision(
319
+ uuid, uuid, text, text, text, text
320
+ ) TO sc_app;
321
+
322
+ CREATE OR REPLACE FUNCTION semicolony.issue_worker_agent_actor_credential(
323
+ p_team_tenant_id uuid,
324
+ p_actor_id text,
325
+ p_token_hash text,
326
+ p_token_prefix text,
327
+ p_issued_by text,
328
+ p_expires_days integer
329
+ )
330
+ RETURNS uuid
331
+ LANGUAGE plpgsql
332
+ SECURITY DEFINER
333
+ SET search_path = pg_catalog
334
+ AS $function$
335
+ DECLARE
336
+ v_credential_id uuid;
337
+ v_previous_credential_id uuid;
338
+ v_runtime_profile text;
339
+ BEGIN
340
+ IF p_actor_id NOT IN ('semi', 'colony') THEN
341
+ RAISE EXCEPTION 'invalid Worker-agent actor' USING ERRCODE = '22023';
342
+ END IF;
343
+ v_runtime_profile := CASE p_actor_id WHEN 'semi' THEN 'semo-semi' ELSE 'semo-colony' END;
344
+ IF p_token_hash IS NULL OR p_token_hash !~ '^[0-9a-f]{64}$' THEN
345
+ RAISE EXCEPTION 'invalid worker-agent actor credential token hash' USING ERRCODE = '22023';
346
+ END IF;
347
+ IF p_token_prefix IS NULL
348
+ OR p_token_prefix !~ ('^wck_' || p_actor_id || '_[A-Za-z0-9_-]{6}$') THEN
349
+ RAISE EXCEPTION 'invalid worker-agent actor credential token prefix' USING ERRCODE = '22023';
350
+ END IF;
351
+ IF p_issued_by IS NULL OR p_issued_by <> btrim(p_issued_by)
352
+ OR btrim(p_issued_by) = '' OR char_length(p_issued_by) > 128
353
+ OR p_issued_by ~ '[[:cntrl:]]' THEN
354
+ RAISE EXCEPTION 'invalid credential issuer' USING ERRCODE = '22023';
355
+ END IF;
356
+ IF p_expires_days IS NULL OR p_expires_days < 1 OR p_expires_days > 90 THEN
357
+ RAISE EXCEPTION 'worker-agent credential expiry must be between 1 and 90 days'
358
+ USING ERRCODE = '22023';
359
+ END IF;
360
+
361
+ PERFORM t.id
362
+ FROM public.tenants AS t
363
+ WHERE t.id = p_team_tenant_id
364
+ AND t.slug = 'team-semicolon'
365
+ AND t.tenant_type = 'team'
366
+ AND coalesce(t.metadata ->> 'status', 'active') = 'active';
367
+ IF NOT FOUND THEN
368
+ RAISE EXCEPTION 'active team-semicolon tenant not found' USING ERRCODE = 'P0002';
369
+ END IF;
370
+
371
+ PERFORM wd.device_id
372
+ FROM semicolony.worker_devices AS wd
373
+ WHERE wd.device_id = 'hermes-principals@hermes'
374
+ AND wd.installation_kind = 'agent-runtime'
375
+ AND wd.installed_profile_id = 'semo-ops-worker-agent'
376
+ AND wd.kb_access_mode = 'worker-gateway'
377
+ AND wd.status = 'active'
378
+ FOR UPDATE;
379
+ IF NOT FOUND THEN
380
+ RAISE EXCEPTION 'active Worker-agent installation not found' USING ERRCODE = 'P0002';
381
+ END IF;
382
+
383
+ SELECT binding.gateway_credential_id
384
+ INTO v_previous_credential_id
385
+ FROM semicolony.worker_agent_actor_bindings AS binding
386
+ WHERE binding.installation_id = 'hermes-principals@hermes'
387
+ AND binding.actor_id = p_actor_id
388
+ FOR UPDATE;
389
+
390
+ INSERT INTO semicolony.gateway_credentials (
391
+ tenant_id, tenant_slug, token_hash, token_prefix, scopes, issued_by, expires_at, metadata
392
+ ) VALUES (
393
+ p_team_tenant_id,
394
+ 'team-semicolon',
395
+ p_token_hash,
396
+ p_token_prefix,
397
+ ARRAY['kb:read', 'feedback:write', 'decision:write']::text[],
398
+ p_issued_by,
399
+ clock_timestamp() + make_interval(days => p_expires_days),
400
+ jsonb_build_object(
401
+ 'credential_kind', 'worker-agent-actor',
402
+ 'actor_id', p_actor_id,
403
+ 'installation_id', 'hermes-principals@hermes',
404
+ 'profile_id', 'semo-ops-worker-agent',
405
+ 'runtime_kind', 'hermes-cli',
406
+ 'runtime_profile', v_runtime_profile,
407
+ 'tenant_anchor', 'team-semicolon',
408
+ 'lineage_status', 'active'
409
+ )
410
+ )
411
+ RETURNING id INTO v_credential_id;
412
+
413
+ INSERT INTO semicolony.worker_agent_actor_bindings (
414
+ installation_id, actor_id, runtime_profile, profile_id, runtime_kind,
415
+ gateway_credential_id, status, issued_at, rotated_at, updated_at
416
+ ) VALUES (
417
+ 'hermes-principals@hermes', p_actor_id, v_runtime_profile,
418
+ 'semo-ops-worker-agent', 'hermes-cli', v_credential_id, 'active',
419
+ clock_timestamp(), CASE WHEN v_previous_credential_id IS NULL THEN NULL ELSE clock_timestamp() END,
420
+ clock_timestamp()
421
+ )
422
+ ON CONFLICT (installation_id, actor_id) DO UPDATE
423
+ SET runtime_profile = EXCLUDED.runtime_profile,
424
+ profile_id = EXCLUDED.profile_id,
425
+ runtime_kind = EXCLUDED.runtime_kind,
426
+ gateway_credential_id = EXCLUDED.gateway_credential_id,
427
+ status = 'active',
428
+ rotated_at = clock_timestamp(),
429
+ updated_at = clock_timestamp();
430
+
431
+ IF v_previous_credential_id IS NOT NULL AND v_previous_credential_id <> v_credential_id THEN
432
+ UPDATE semicolony.gateway_credentials
433
+ SET status = 'revoked',
434
+ revoked_at = clock_timestamp(),
435
+ revoked_reason = 'worker-agent actor credential rotated'
436
+ WHERE id = v_previous_credential_id
437
+ AND status = 'active';
438
+ END IF;
439
+
440
+ RETURN v_credential_id;
441
+ END;
442
+ $function$;
443
+
444
+ REVOKE ALL ON FUNCTION semicolony.issue_worker_agent_actor_credential(
445
+ uuid, text, text, text, text, integer
446
+ ) FROM PUBLIC, sc_provider;
447
+ GRANT EXECUTE ON FUNCTION semicolony.issue_worker_agent_actor_credential(
448
+ uuid, text, text, text, text, integer
449
+ ) TO sc_app;
450
+
451
+ UPDATE semicolony.gateway_credentials AS gc
452
+ SET scopes = ARRAY['kb:read', 'feedback:write', 'decision:write']::text[]
453
+ FROM semicolony.worker_agent_actor_bindings AS binding
454
+ WHERE binding.gateway_credential_id = gc.id
455
+ AND binding.installation_id = 'hermes-principals@hermes'
456
+ AND binding.profile_id = 'semo-ops-worker-agent'
457
+ AND binding.runtime_kind = 'hermes-cli'
458
+ AND binding.actor_id IN ('semi', 'colony')
459
+ AND binding.status = 'active'
460
+ AND gc.status = 'active'
461
+ AND coalesce(gc.expires_at, 'infinity'::timestamptz) > clock_timestamp()
462
+ AND gc.metadata ->> 'credential_kind' = 'worker-agent-actor'
463
+ AND gc.metadata ->> 'actor_id' = binding.actor_id
464
+ AND gc.metadata ->> 'installation_id' = binding.installation_id
465
+ AND gc.metadata ->> 'profile_id' = binding.profile_id
466
+ AND gc.metadata ->> 'runtime_kind' = binding.runtime_kind
467
+ AND gc.metadata ->> 'runtime_profile' = binding.runtime_profile
468
+ AND gc.scopes IS DISTINCT FROM ARRAY['kb:read', 'feedback:write', 'decision:write']::text[];
@@ -0,0 +1,42 @@
1
+ -- 187_worker_agent_decision_digest_repair.sql
2
+ --
3
+ -- Migration 186 qualified pgcrypto.digest through public, but production keeps
4
+ -- pgcrypto in the isolated knowledge_fabric_crypto schema. Repair the already
5
+ -- installed recorder without depending on any extension schema. Fresh installs
6
+ -- already receive the pg_catalog SHA-256 primitive from the corrected 186 file.
7
+
8
+ DO $repair$
9
+ DECLARE
10
+ v_function regprocedure :=
11
+ 'semicolony.record_worker_agent_decision(uuid,uuid,text,text,text,text)'::regprocedure;
12
+ v_definition text;
13
+ v_repaired text;
14
+ v_old constant text :=
15
+ 'public.digest(convert_to(p_content, ''UTF8''), ''sha256'')';
16
+ v_new constant text :=
17
+ 'pg_catalog.sha256(convert_to(p_content, ''UTF8''))';
18
+ v_old_occurrences integer;
19
+ BEGIN
20
+ SELECT pg_get_functiondef(v_function::oid) INTO STRICT v_definition;
21
+ v_old_occurrences :=
22
+ (char_length(v_definition) - char_length(replace(v_definition, v_old, '')))
23
+ / char_length(v_old);
24
+
25
+ IF v_old_occurrences = 0 AND position(v_new IN v_definition) > 0 THEN
26
+ RETURN;
27
+ END IF;
28
+ IF v_old_occurrences <> 1 THEN
29
+ RAISE EXCEPTION 'unexpected Worker-agent decision recorder digest definition';
30
+ END IF;
31
+
32
+ v_repaired := replace(v_definition, v_old, v_new);
33
+ EXECUTE v_repaired;
34
+ END;
35
+ $repair$;
36
+
37
+ REVOKE ALL ON FUNCTION semicolony.record_worker_agent_decision(
38
+ uuid, uuid, text, text, text, text
39
+ ) FROM PUBLIC, app, sc_provider;
40
+ GRANT EXECUTE ON FUNCTION semicolony.record_worker_agent_decision(
41
+ uuid, uuid, text, text, text, text
42
+ ) TO sc_app;
@@ -24,6 +24,8 @@ SEMICOLONY CLI의 PostgreSQL 마이그레이션 파일들.
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
26
  | `185_worker_agent_feedback_scope.sql` | Hermes Semi/Colony actor에 정확한 feedback 범위·귀속을 추가하고 기존 바인딩만 백필 |
27
+ | `186_worker_agent_decision_capture.sql` | operator 승인 reference와 actor-bound decision recorder를 추가하고 정확한 decision scope만 백필 |
28
+ | `187_worker_agent_decision_digest_repair.sql` | Phase 6 decision recorder를 extension schema와 무관한 PostgreSQL SHA-256 primitive로 교정 |
27
29
 
28
30
  ## 007: Flat Skill Naming
29
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semicolony-cli",
3
- "version": "4.18.80",
3
+ "version": "4.18.82",
4
4
  "description": "SemiColony CLI - AI operations and agent orchestration installer",
5
5
  "main": "dist/bundle.js",
6
6
  "bin": {