L2: permission mode propagation across forward/calendar/adopt + PUT endpoint + tests
P1 - forward inherits mode:
- doForward: when creating a new target session, use InheritedMode from
the source session (plan parent → plan child, cannot escalate)
- enforcement snapshot set from receiving agent
P1 - calendar_events gets permission_mode column:
- Added to 3 migration sites (sqlite init, pg init, incremental alter)
- CalendarEvent model gains PermissionMode field
- CreateCalendarEvent / UpdateCalendarEvent normalize + persist the field
- resolveCalendarSession: on new session → write event's mode;
on reuse → ModeAtMost(cur, eventMode), prevents escalation
(plan Agent's reminder fires into a workspace session = bypass)
- SendCalendarMail now takes permMode and threads it through
- fireEvent passes event.PermissionMode to all delivery paths
P1 - AdoptPlatformSession explicitly writes default mode:
- Writes DefaultPermissionMode + enforcement on adopt, instead of
relying on DB column default (avoids silent drift on schema changes)
P2 - PUT /sessions/{id}/permission endpoint:
- New handler UpdateSessionPermission (auth required, access check)
- Registers PUT route alongside existing budget/alias endpoints
- Broadcasts session_update on change
- Does NOT refresh enforcement (design: snapshot at creation)
P3 - Tests (24 new cases):
- permission_mode_test.go: InheritedMode (6 cases), SetSessionPermissionMode
roundtrip, dirty value fail-closed, NormalizePermissionMode, calendar event
roundtrip/dirty/update, adopt writes default mode, plan escalation guard,
3-level inheritance chain
- defaultsession_test.go: budget regression, permission mode regression
(pins created=false → no reset on reuse)
Deploys with: bash deploy/redeploy-gateway.sh --skip-tests
Schema migration: auto via addMissingColumns (new column default 'workspace')
This commit is contained in:
@ -84,10 +84,17 @@ var sqliteAddColumns = []struct{ table, column, ddl string }{
|
||||
// to_address / agent_name,历史事件因此继续工作,不需要数据迁移。
|
||||
{"calendar_events", "recipients", "ALTER TABLE calendar_events ADD COLUMN recipients TEXT NOT NULL DEFAULT '[]'"},
|
||||
{"calendar_events", "delivery_mode", "ALTER TABLE calendar_events ADD COLUMN delivery_mode TEXT NOT NULL DEFAULT 'separate'"},
|
||||
// 日历事件已触发的 occurrence。旧库为 NULL:等价于「从未触发」,
|
||||
// 日历事件已触发的 occurrence。旧库为 NULL:等价于「从未触发」,
|
||||
// 于是已过期的一次性事件会补发一次提醒 —— 这是可接受的,
|
||||
// 而反过来(默认成 event_time)会让正在等的提醒永远发不出去。
|
||||
{"calendar_events", "fired_for", "ALTER TABLE calendar_events ADD COLUMN fired_for DATETIME"},
|
||||
// 日历事件的权限档位(plan / workspace / full)。事件触发时若新建会话,
|
||||
// 用这一列定死档位;复用已有会话则取「会话现档 与 事件档」中更严那个。
|
||||
//
|
||||
// 旧库默认 'workspace':历史事件补发提醒不该静默升到 full(提权路径
|
||||
// 会被 P1 calendar 投递接线堵住,但这里默认值也得守住)。
|
||||
// 与 sessions.permission_mode 的默认取向一致。
|
||||
{"calendar_events", "permission_mode", "ALTER TABLE calendar_events ADD COLUMN permission_mode TEXT NOT NULL DEFAULT 'workspace'"},
|
||||
// 本侧会话接管的平台会话 id。旧库默认空串 = 「不是接管来的」,
|
||||
// 与新建会话的语义一致,不需要数据迁移。
|
||||
{"sessions", "platform_id", "ALTER TABLE sessions ADD COLUMN platform_id TEXT NOT NULL DEFAULT ''"},
|
||||
|
||||
@ -395,6 +395,8 @@ CREATE TABLE IF NOT EXISTS calendar_events (
|
||||
last_fired_at TIMESTAMPTZ,
|
||||
-- 已触发的 occurrence(= 当时的 event_time)。见 init_sqlite.sql 的说明。
|
||||
fired_for TIMESTAMPTZ,
|
||||
-- 权限档位(plan / workspace / full)。见 init_sqlite.sql 的说明。
|
||||
permission_mode TEXT NOT NULL DEFAULT 'workspace',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
created_by VARCHAR(128) NOT NULL DEFAULT ''
|
||||
|
||||
@ -433,6 +433,10 @@ CREATE TABLE IF NOT EXISTS calendar_events (
|
||||
-- 按 occurrence 比相等则精确:AdvanceRecurrence 改了 event_time 就再触发,
|
||||
-- 没改就永不重发。
|
||||
fired_for DATETIME,
|
||||
-- 权限档位(plan / workspace / full)。事件触发时新建会话 → 用此档位定死;
|
||||
-- 复用已有会话 → 取「会话现档 与 事件档」中更严那个(ModeAtMost),
|
||||
-- 不允许通过重复事件提权(plan 档 Agent 建的日程触发时拿 workspace 就绕开了 plan)。
|
||||
permission_mode TEXT NOT NULL DEFAULT 'workspace',
|
||||
created_at DATETIME DEFAULT (strftime('%Y-%m-%d %H:%M:%f','now')),
|
||||
updated_at DATETIME DEFAULT (strftime('%Y-%m-%d %H:%M:%f','now')),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user