@team-semicolon/semicolony-cli 4.18.106 → 4.18.107

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,45 @@
1
+ -- 203_ops_meetings_board_grant.sql
2
+ --
3
+ -- Let the SEMO Ops control room read the team meeting record.
4
+ --
5
+ -- `/ops` reads `semo_ops` through the least-privilege `sc_fleet_board` role on a
6
+ -- separate pool, and that role holds SELECT on fourteen tables — `meetings` not
7
+ -- among them. Without this grant the meetings view fails with
8
+ -- `permission denied for table meetings`, which is how the action-item and
9
+ -- ontology cards failed in 196: a release gap, not a code bug.
10
+ --
11
+ -- SELECT only. The control room observes; every write goes through
12
+ -- `record_meeting`, and the writer roles must not overlap with the board.
13
+ --
14
+ -- Unlike `knowledge_base`, `semo_ops.meetings` has RLS disabled, so there is no
15
+ -- policy to extend and no silent-zero-rows failure mode here. The grant is the
16
+ -- whole permission story, which also means **restriction is not enforced by the
17
+ -- database for this table**: a `restricted` meeting is visible to any granted
18
+ -- role, and the reading query must filter `visibility` itself. That is stated in
19
+ -- semo-ops `docs/dashboard/code-and-authority-inventory-2026-08-13.md` and
20
+ -- KB `semo-ops/process/meetings-visibility-enforcement`.
21
+ --
22
+ -- `action_items` is already granted (196), so meeting → action-item lookups need
23
+ -- no further permission.
24
+ --
25
+ -- Safety: additive, SELECT only. The runner owns the transaction boundary, so no
26
+ -- BEGIN/COMMIT here, and it retargets the `semicolony.` qualifier to the active
27
+ -- schema.
28
+
29
+ GRANT SELECT ON TABLE semicolony.meetings TO sc_fleet_board;
30
+
31
+ DO $$
32
+ DECLARE v_cols int;
33
+ BEGIN
34
+ -- A table-level grant must cover every column; a partial grant would drop
35
+ -- fields from the view with no error at grant time.
36
+ SELECT count(*) INTO v_cols
37
+ FROM information_schema.role_column_grants
38
+ WHERE grantee = 'sc_fleet_board'
39
+ AND table_schema = current_schema()
40
+ AND table_name = 'meetings'
41
+ AND privilege_type = 'SELECT';
42
+ IF v_cols = 0 THEN
43
+ RAISE EXCEPTION 'sc_fleet_board did not receive SELECT on meetings';
44
+ END IF;
45
+ END $$;
@@ -0,0 +1,29 @@
1
+ -- The semo-ops-dashboard credential gained action:read and action:write from
2
+ -- 205's blanket scope amendment, but the dashboard never calls the Gateway's
3
+ -- action routes -- it reads and writes semo_ops.action_items through direct
4
+ -- queries in lib/core/action-items.ts. Measured 2026-08-14: zero occurrences
5
+ -- of /action/list|create|update|get anywhere in packages/semicolony-dashboard.
6
+ -- Take the unused scopes back.
7
+ UPDATE semicolony.gateway_credentials
8
+ SET scopes = array_remove(array_remove(scopes, 'action:read'), 'action:write')
9
+ WHERE status = 'active'
10
+ AND metadata->>'device_id' = 'semo-ops-dashboard';
11
+
12
+ DO $$
13
+ DECLARE
14
+ remaining text[];
15
+ BEGIN
16
+ SELECT scopes INTO remaining
17
+ FROM semicolony.gateway_credentials
18
+ WHERE status = 'active' AND metadata->>'device_id' = 'semo-ops-dashboard';
19
+
20
+ IF remaining IS NULL THEN
21
+ RAISE EXCEPTION 'semo-ops-dashboard credential not found or not active';
22
+ END IF;
23
+ IF 'workspace:assets-read' <> ALL(remaining) THEN
24
+ RAISE EXCEPTION 'workspace:assets-read was lost: %', remaining;
25
+ END IF;
26
+ IF 'action:read' = ANY(remaining) OR 'action:write' = ANY(remaining) THEN
27
+ RAISE EXCEPTION 'action scopes still present: %', remaining;
28
+ END IF;
29
+ END $$;
@@ -0,0 +1,48 @@
1
+ -- The dashboard's action-item surface moved off appdb.semicolony onto
2
+ -- semo_ops (see KB semicolon decision
3
+ -- semo-ops-dashboard-control-room-data-routing-2026-08-06, and the 42P01
4
+ -- outage measured 2026-08-16). Its pool connects as sc_fleet_board, which
5
+ -- held SELECT only. Writes still reach action_items exclusively through
6
+ -- SECURITY DEFINER functions -- this grants EXECUTE on the three the
7
+ -- dashboard calls (lib/core/action-items.ts: createActionItem,
8
+ -- assignActionItem, operatorUpdateActionItem -- cancelActionItem delegates
9
+ -- to operatorUpdateActionItem rather than calling its own function) and
10
+ -- adds no direct DML. The other five surfaces that moved to the fleet pool
11
+ -- in the same cutover (the /api/action-items roster query, /api/goals,
12
+ -- /api/person/[domain], lib/plugins/service/kpi.ts, lib/tasks-db.ts) are
13
+ -- read-only SELECTs against action_items/team_members/bot_commitments and
14
+ -- call no function, so they need no grant here. No read path selects from
15
+ -- action_item_events, so this migration does not grant SELECT on it either.
16
+ GRANT EXECUTE ON FUNCTION semicolony.create_action_item(
17
+ p_description text, p_project_domain text, p_deadline date,
18
+ p_assignee_kind text, p_assignee_member text, p_assignee_agent text,
19
+ p_assignee_device text, p_priority text, p_source_kind text,
20
+ p_source_ref text, p_related_url text, p_external_contact text,
21
+ p_metadata jsonb, p_origin_surface text, p_origin_actor_kind text,
22
+ p_origin_member text, p_origin_agent text, p_origin_device text,
23
+ p_origin_harness text, p_origin_session_key text
24
+ ) TO sc_fleet_board;
25
+
26
+ GRANT EXECUTE ON FUNCTION semicolony.assign_action_item(
27
+ p_action_item_id uuid, p_assignee_kind text, p_assignee_member text,
28
+ p_assignee_agent text, p_assignee_device text, p_actor_kind text,
29
+ p_actor_member text, p_actor_agent text
30
+ ) TO sc_fleet_board;
31
+
32
+ GRANT EXECUTE ON FUNCTION semicolony.operator_update_action_item(
33
+ p_action_item_id uuid, p_status text, p_deadline date, p_note text,
34
+ p_metadata jsonb, p_actor_kind text, p_actor_member text,
35
+ p_actor_agent text
36
+ ) TO sc_fleet_board;
37
+
38
+ DO $$
39
+ BEGIN
40
+ IF EXISTS (
41
+ SELECT 1 FROM information_schema.table_privileges
42
+ WHERE grantee = 'sc_fleet_board' AND table_schema = 'semicolony'
43
+ AND table_name = 'action_items'
44
+ AND privilege_type IN ('INSERT','UPDATE','DELETE')
45
+ ) THEN
46
+ RAISE EXCEPTION 'sc_fleet_board must never hold direct DML on action_items';
47
+ END IF;
48
+ END $$;
@@ -0,0 +1,45 @@
1
+ -- 207_action_grants_fleet_board.sql shipped a safety-net guard that can never
2
+ -- fire in the environment it targets:
3
+ --
4
+ -- WHERE grantee = 'sc_fleet_board' AND table_schema = 'semicolony' ...
5
+ --
6
+ -- `table_schema = 'semicolony'` is a string LITERAL compared against a column,
7
+ -- not a schema-qualified identifier. `retargetSchemaSql`
8
+ -- (packages/cli/src/commands/db.ts:35-44) rewrites `semicolony.` only when it
9
+ -- qualifies an identifier (`semicolony.<name>`, matched by
10
+ -- `(?<![\w./])semicolony\.`) -- a bare string literal with no trailing dot,
11
+ -- like `'semicolony'` in a WHERE clause, is left untouched by design (see
12
+ -- that function's own docstring, which names this exact pattern as excluded).
13
+ -- This migration always runs with SEMICOLONY_DB_SCHEMA=semo_ops against
14
+ -- team_knowledge_fabric, which has no `semicolony` schema at all -- so the
15
+ -- guard's WHERE clause matched zero rows regardless of grant state, and the
16
+ -- RAISE EXCEPTION was dead code. The invariant itself was never violated
17
+ -- (verified separately, see task-3-report.md); only the safety net was inert.
18
+ --
19
+ -- Fix: drop the schema filter rather than try to make it retarget-aware.
20
+ -- `current_schema()` was considered and rejected -- it happens to resolve to
21
+ -- `semo_ops` for the role this migration currently runs as (that role's
22
+ -- search_path defaults to `semo_ops, public, pg_catalog` in this database),
23
+ -- but that is a property of the connecting role's search_path, not of
24
+ -- SEMICOLONY_DB_SCHEMA, and would silently go back to matching nothing under
25
+ -- a role without that search_path -- the same failure mode this migration
26
+ -- exists to close. `action_items` is schema-unique in this database (checked
27
+ -- against information_schema.tables across all schemas: exactly one row,
28
+ -- semo_ops.action_items) so filtering on table_name alone is unambiguous
29
+ -- today, and if a second action_items ever appears anywhere and picks up
30
+ -- direct DML for sc_fleet_board, this guard should fire for that too -- a
31
+ -- schema-agnostic check is the more conservative invariant, not a weaker one.
32
+ --
33
+ -- Record the trap for future authors: a bare string literal inside a WHERE
34
+ -- clause is not an identifier and `retargetSchemaSql` will never rewrite it,
35
+ -- by design, no matter how much it looks like a schema reference.
36
+ DO $$
37
+ BEGIN
38
+ IF EXISTS (
39
+ SELECT 1 FROM information_schema.table_privileges
40
+ WHERE grantee = 'sc_fleet_board' AND table_name = 'action_items'
41
+ AND privilege_type IN ('INSERT','UPDATE','DELETE')
42
+ ) THEN
43
+ RAISE EXCEPTION 'sc_fleet_board must never hold direct DML on action_items';
44
+ END IF;
45
+ END $$;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semicolony-cli",
3
- "version": "4.18.106",
3
+ "version": "4.18.107",
4
4
  "description": "SemiColony CLI - AI operations and agent orchestration installer",
5
5
  "main": "dist/bundle.js",
6
6
  "bin": {