@team-semicolon/semicolony-cli 4.18.127 → 4.18.128

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,5 @@
1
+ -- /ops/actions now reads per-item history as sc_fleet_board. Migration 203
2
+ -- deliberately omitted events before that reader existed; 226 retained it.
3
+ -- Add only the missing read privilege. Mutations still use guarded functions.
4
+ -- Qualified schema identifiers are retargeted by the production migration runner.
5
+ GRANT SELECT ON TABLE semicolony.action_item_events TO sc_fleet_board;
@@ -0,0 +1,279 @@
1
+ -- 229_worker_asset_default_policy.sql
2
+ --
3
+ -- Materialize the approved person-bound asset policy as the exact project
4
+ -- grants already consumed by the Worker Gateway. This deliberately adds no
5
+ -- wildcard authorization path and does not alter Worker-agent actors.
6
+ --
7
+ -- Decision:
8
+ -- semicolon/decision/reus-worker-default-all-project-asset-read-write-2026-09-14
9
+ --
10
+ -- The migration runner owns the transaction boundary.
11
+
12
+ CREATE TABLE IF NOT EXISTS semicolony.worker_asset_access_policies (
13
+ policy_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
14
+ member_id uuid NOT NULL
15
+ REFERENCES semicolony.team_members(member_id) ON DELETE RESTRICT,
16
+ selector text NOT NULL CHECK (selector = 'all-team-projects'),
17
+ access_mode text NOT NULL CHECK (access_mode = 'read-write'),
18
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
19
+ granted_by text NOT NULL CHECK (NULLIF(btrim(granted_by), '') IS NOT NULL),
20
+ reason text NOT NULL CHECK (NULLIF(btrim(reason), '') IS NOT NULL),
21
+ decision_ref text NOT NULL CHECK (NULLIF(btrim(decision_ref), '') IS NOT NULL),
22
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
23
+ updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
24
+ last_reconciled_at timestamptz,
25
+ UNIQUE (member_id, selector)
26
+ );
27
+
28
+ COMMENT ON TABLE semicolony.worker_asset_access_policies IS
29
+ 'Audited person-bound policies materialized as exact Worker workspace asset grants.';
30
+
31
+ CREATE OR REPLACE FUNCTION semicolony.reconcile_worker_asset_access_policy(uuid)
32
+ RETURNS integer
33
+ LANGUAGE plpgsql
34
+ SECURITY DEFINER
35
+ SET search_path = pg_catalog
36
+ AS $function$
37
+ DECLARE
38
+ p_policy_id alias for $1;
39
+ v_policy record;
40
+ v_credential record;
41
+ v_previous_scopes text[];
42
+ v_next_scopes text[];
43
+ v_changed integer;
44
+ v_total integer := 0;
45
+ BEGIN
46
+ SELECT p.policy_id, p.granted_by, p.reason, p.decision_ref, tm.domain AS member_domain
47
+ INTO v_policy
48
+ FROM semicolony.worker_asset_access_policies AS p
49
+ JOIN semicolony.team_members AS tm ON tm.member_id = p.member_id
50
+ WHERE p.policy_id = p_policy_id
51
+ AND p.status = 'active'
52
+ AND p.selector = 'all-team-projects'
53
+ AND p.access_mode = 'read-write'
54
+ AND tm.status = 'active';
55
+ IF NOT FOUND THEN
56
+ RETURN 0;
57
+ END IF;
58
+
59
+ FOR v_credential IN
60
+ SELECT gc.id, gc.scopes
61
+ FROM semicolony.gateway_credentials AS gc
62
+ WHERE gc.status = 'active'
63
+ AND gc.revoked_at IS NULL
64
+ AND (gc.expires_at IS NULL OR gc.expires_at > clock_timestamp())
65
+ AND gc.metadata ->> 'credential_kind' = 'worker'
66
+ AND gc.metadata ->> 'worker_id' = v_policy.member_domain
67
+ FOR UPDATE
68
+ LOOP
69
+ INSERT INTO semicolony.workspace_projection_grants AS existing (
70
+ gateway_credential_id,
71
+ project_id,
72
+ capability,
73
+ environments,
74
+ status,
75
+ expires_at,
76
+ metadata
77
+ )
78
+ SELECT
79
+ v_credential.id,
80
+ project.domain,
81
+ capability.name,
82
+ ARRAY[]::text[],
83
+ 'active',
84
+ NULL,
85
+ jsonb_build_object(
86
+ 'source', 'worker-asset-access-policy',
87
+ 'policy_id', v_policy.policy_id,
88
+ 'decision_ref', v_policy.decision_ref,
89
+ 'granted_by', v_policy.granted_by,
90
+ 'reason', v_policy.reason
91
+ )
92
+ FROM semicolony.ontology AS project
93
+ CROSS JOIN (VALUES ('assets-read'), ('assets-write')) AS capability(name)
94
+ WHERE project.entity_type IN ('service', 'project')
95
+ OR project.domain = 'semicolon'
96
+ ON CONFLICT (gateway_credential_id, project_id, capability) DO UPDATE
97
+ SET status = 'active',
98
+ expires_at = NULL,
99
+ metadata = CASE
100
+ WHEN existing.metadata ->> 'source' = 'worker-asset-access-policy'
101
+ THEN EXCLUDED.metadata
102
+ ELSE existing.metadata
103
+ END
104
+ WHERE existing.status IS DISTINCT FROM 'active'
105
+ OR existing.expires_at IS NOT NULL
106
+ OR (
107
+ existing.metadata ->> 'source' = 'worker-asset-access-policy'
108
+ AND existing.metadata IS DISTINCT FROM EXCLUDED.metadata
109
+ );
110
+ GET DIAGNOSTICS v_changed = ROW_COUNT;
111
+ v_total := v_total + v_changed;
112
+
113
+ v_previous_scopes := v_credential.scopes;
114
+ v_next_scopes := v_previous_scopes;
115
+ IF NOT ('workspace:assets-read' = ANY(v_next_scopes)) THEN
116
+ v_next_scopes := array_append(v_next_scopes, 'workspace:assets-read');
117
+ END IF;
118
+ IF NOT ('workspace:assets-write' = ANY(v_next_scopes)) THEN
119
+ v_next_scopes := array_append(v_next_scopes, 'workspace:assets-write');
120
+ END IF;
121
+
122
+ IF v_next_scopes IS DISTINCT FROM v_previous_scopes THEN
123
+ INSERT INTO semicolony.gateway_credential_scope_amendments (
124
+ credential_id,
125
+ previous_scopes,
126
+ next_scopes,
127
+ reason,
128
+ amended_by
129
+ ) VALUES (
130
+ v_credential.id,
131
+ v_previous_scopes,
132
+ v_next_scopes,
133
+ v_policy.reason,
134
+ v_policy.granted_by
135
+ );
136
+
137
+ UPDATE semicolony.gateway_credentials
138
+ SET scopes = v_next_scopes
139
+ WHERE id = v_credential.id;
140
+ END IF;
141
+ END LOOP;
142
+
143
+ UPDATE semicolony.worker_asset_access_policies
144
+ SET last_reconciled_at = clock_timestamp(),
145
+ updated_at = clock_timestamp()
146
+ WHERE policy_id = p_policy_id;
147
+
148
+ RETURN v_total;
149
+ END;
150
+ $function$;
151
+
152
+ CREATE OR REPLACE FUNCTION semicolony.reconcile_worker_asset_policy_for_ontology()
153
+ RETURNS trigger
154
+ LANGUAGE plpgsql
155
+ SECURITY DEFINER
156
+ SET search_path = pg_catalog
157
+ AS $function$
158
+ DECLARE
159
+ v_policy_id uuid;
160
+ BEGIN
161
+ IF NEW.entity_type IN ('service', 'project') OR NEW.domain = 'semicolon' THEN
162
+ FOR v_policy_id IN
163
+ SELECT policy_id
164
+ FROM semicolony.worker_asset_access_policies
165
+ WHERE status = 'active'
166
+ LOOP
167
+ PERFORM semicolony.reconcile_worker_asset_access_policy(v_policy_id);
168
+ END LOOP;
169
+ END IF;
170
+ RETURN NEW;
171
+ END;
172
+ $function$;
173
+
174
+ CREATE OR REPLACE FUNCTION semicolony.reconcile_worker_asset_policy_for_credential()
175
+ RETURNS trigger
176
+ LANGUAGE plpgsql
177
+ SECURITY DEFINER
178
+ SET search_path = pg_catalog
179
+ AS $function$
180
+ DECLARE
181
+ v_policy_id uuid;
182
+ BEGIN
183
+ IF NEW.status = 'active'
184
+ AND NEW.revoked_at IS NULL
185
+ AND (NEW.expires_at IS NULL OR NEW.expires_at > clock_timestamp())
186
+ AND NEW.metadata ->> 'credential_kind' = 'worker' THEN
187
+ FOR v_policy_id IN
188
+ SELECT p.policy_id
189
+ FROM semicolony.worker_asset_access_policies AS p
190
+ JOIN semicolony.team_members AS tm ON tm.member_id = p.member_id
191
+ WHERE p.status = 'active'
192
+ AND tm.status = 'active'
193
+ AND tm.domain = NEW.metadata ->> 'worker_id'
194
+ LOOP
195
+ PERFORM semicolony.reconcile_worker_asset_access_policy(v_policy_id);
196
+ END LOOP;
197
+ END IF;
198
+ RETURN NEW;
199
+ END;
200
+ $function$;
201
+
202
+ DROP TRIGGER IF EXISTS trg_worker_asset_policy_ontology ON semicolony.ontology;
203
+ CREATE TRIGGER trg_worker_asset_policy_ontology
204
+ AFTER INSERT OR UPDATE OF domain, entity_type ON semicolony.ontology
205
+ FOR EACH ROW
206
+ EXECUTE FUNCTION semicolony.reconcile_worker_asset_policy_for_ontology();
207
+
208
+ DROP TRIGGER IF EXISTS trg_worker_asset_policy_credential ON semicolony.gateway_credentials;
209
+ CREATE CONSTRAINT TRIGGER trg_worker_asset_policy_credential
210
+ AFTER INSERT ON semicolony.gateway_credentials
211
+ DEFERRABLE INITIALLY DEFERRED
212
+ FOR EACH ROW
213
+ EXECUTE FUNCTION semicolony.reconcile_worker_asset_policy_for_credential();
214
+
215
+ DO $reus_member_guard$
216
+ BEGIN
217
+ IF NOT EXISTS (
218
+ SELECT 1
219
+ FROM semicolony.team_members AS tm
220
+ WHERE tm.domain = 'reus'
221
+ AND tm.status = 'active'
222
+ ) THEN
223
+ RAISE EXCEPTION 'active reus team member is required for worker asset policy'
224
+ USING ERRCODE = '22023';
225
+ END IF;
226
+ END;
227
+ $reus_member_guard$;
228
+
229
+ INSERT INTO semicolony.worker_asset_access_policies (
230
+ member_id,
231
+ selector,
232
+ access_mode,
233
+ status,
234
+ granted_by,
235
+ reason,
236
+ decision_ref
237
+ )
238
+ SELECT
239
+ tm.member_id,
240
+ 'all-team-projects',
241
+ 'read-write',
242
+ 'active',
243
+ 'reus',
244
+ 'Approved default asset read/write access for all current and future team projects',
245
+ 'semicolon/decision/reus-worker-default-all-project-asset-read-write-2026-09-14'
246
+ FROM semicolony.team_members AS tm
247
+ WHERE tm.domain = 'reus'
248
+ AND tm.status = 'active'
249
+ ON CONFLICT (member_id, selector) DO UPDATE
250
+ SET access_mode = EXCLUDED.access_mode,
251
+ status = EXCLUDED.status,
252
+ granted_by = EXCLUDED.granted_by,
253
+ reason = EXCLUDED.reason,
254
+ decision_ref = EXCLUDED.decision_ref,
255
+ updated_at = clock_timestamp();
256
+
257
+ DO $initial_reconciliation$
258
+ DECLARE
259
+ v_policy_id uuid;
260
+ BEGIN
261
+ SELECT p.policy_id
262
+ INTO STRICT v_policy_id
263
+ FROM semicolony.worker_asset_access_policies AS p
264
+ JOIN semicolony.team_members AS tm ON tm.member_id = p.member_id
265
+ WHERE tm.domain = 'reus'
266
+ AND p.selector = 'all-team-projects'
267
+ AND p.status = 'active';
268
+ PERFORM semicolony.reconcile_worker_asset_access_policy(v_policy_id);
269
+ END;
270
+ $initial_reconciliation$;
271
+
272
+ REVOKE ALL ON TABLE semicolony.worker_asset_access_policies
273
+ FROM PUBLIC, sc_app, sc_provider;
274
+ REVOKE ALL ON FUNCTION semicolony.reconcile_worker_asset_access_policy(uuid)
275
+ FROM PUBLIC, sc_app, sc_provider;
276
+ REVOKE ALL ON FUNCTION semicolony.reconcile_worker_asset_policy_for_ontology()
277
+ FROM PUBLIC, sc_app, sc_provider;
278
+ REVOKE ALL ON FUNCTION semicolony.reconcile_worker_asset_policy_for_credential()
279
+ FROM PUBLIC, sc_app, sc_provider;
@@ -30,6 +30,7 @@ SEMICOLONY CLI의 PostgreSQL 마이그레이션 파일들.
30
30
  | `196_ops_control_room_board_grants.sql` | `sc_fleet_board` 에 `action_items`·`ontology`·`knowledge_base` SELECT 부여 + knowledge_base SELECT 정책 롤 확장 — /ops 홈의 `permission denied` 카드 수정 |
31
31
  | `218_feedback_loop_state.sql` | 피드백 해소 루프 상태를 KB slug 키로 DB 에 둔다 (ledger.json 대체) |
32
32
  | `219_feedback_action_item_on_demand.sql` | `record_feedback` 이 액션아이템을 만들지 않고, `duplicate` 를 재발 카운터로 판정한다 |
33
+ | `229_worker_asset_default_policy.sql` | `reus` 팀원의 현재·미래 Human Worker 자격증명에 모든 팀 프로젝트 자산 read/write 권한을 exact grant로 감사 가능하게 자동 반영 |
33
34
 
34
35
  ## 007: Flat Skill Naming
35
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semicolony-cli",
3
- "version": "4.18.127",
3
+ "version": "4.18.128",
4
4
  "description": "SemiColony CLI - AI operations and agent orchestration installer",
5
5
  "main": "dist/bundle.js",
6
6
  "bin": {