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:
@ -25,7 +25,7 @@ var ErrEventNotFound = errors.New("calendar event not found")
|
||||
// 加了预算两列没加进 Scan,整个联系人栏 500)。
|
||||
const calendarCols = `event_id, title, description, reminder_text, agent_name, to_address,
|
||||
recipients, delivery_mode, event_time, remind_before, recurrence, recurrence_end,
|
||||
status, last_fired_at, fired_for, created_at, updated_at, created_by`
|
||||
status, last_fired_at, fired_for, permission_mode, created_at, updated_at, created_by`
|
||||
|
||||
// rowScanner 让 QueryRow 与 Rows 共用同一个 scan 实现。
|
||||
type rowScanner interface {
|
||||
@ -44,7 +44,8 @@ func scanCalendarEvent(sc rowScanner) (*models.CalendarEvent, error) {
|
||||
&e.AgentName, &e.ToAddress,
|
||||
&recipientsJSON, &e.DeliveryMode,
|
||||
&e.EventTime, &e.RemindBefore, &e.Recurrence, &e.RecurrenceEnd,
|
||||
&e.Status, &e.LastFiredAt, &e.FiredFor, &e.CreatedAt, &e.UpdatedAt, &e.CreatedBy,
|
||||
&e.Status, &e.LastFiredAt, &e.FiredFor, &e.PermissionMode,
|
||||
&e.CreatedAt, &e.UpdatedAt, &e.CreatedBy,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -88,6 +89,10 @@ func CreateCalendarEvent(ctx context.Context, e *models.CalendarEvent) (*models.
|
||||
if e.DeliveryMode == "" {
|
||||
e.DeliveryMode = models.DeliverSeparate
|
||||
}
|
||||
// 档位合法化:脏值 fail-closed 到默认档,不透传成库里的非法值
|
||||
//(否则后续读路径会拿到一个 ModeNeedsHuman 判定不了的值)。
|
||||
e.PermissionMode = models.NormalizePermissionMode(e.PermissionMode)
|
||||
|
||||
if e.Recipients == nil {
|
||||
e.Recipients = []string{}
|
||||
}
|
||||
@ -96,14 +101,14 @@ func CreateCalendarEvent(ctx context.Context, e *models.CalendarEvent) (*models.
|
||||
INSERT INTO calendar_events
|
||||
(event_id, title, description, reminder_text, agent_name, to_address,
|
||||
recipients, delivery_mode,
|
||||
event_time, remind_before, recurrence, recurrence_end, status, created_by,
|
||||
event_time, remind_before, recurrence, recurrence_end, status, permission_mode, created_by,
|
||||
created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
e.EventID, e.Title, e.Description, e.ReminderText,
|
||||
e.AgentName, e.ToAddress,
|
||||
marshalRecipients(e.Recipients), e.DeliveryMode,
|
||||
e.EventTime, e.RemindBefore, e.Recurrence, e.RecurrenceEnd,
|
||||
e.Status, e.CreatedBy, e.CreatedAt, e.UpdatedAt,
|
||||
e.Status, e.PermissionMode, e.CreatedBy, e.CreatedAt, e.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -125,19 +130,21 @@ func GetCalendarEvent(ctx context.Context, eventID string) (*models.CalendarEven
|
||||
|
||||
func UpdateCalendarEvent(ctx context.Context, eventID string, e *models.CalendarEvent) error {
|
||||
e.UpdatedAt = time.Now()
|
||||
// 档位同样在 update 路径上规范化
|
||||
e.PermissionMode = models.NormalizePermissionMode(e.PermissionMode)
|
||||
result, err := db.DB.ExecContext(ctx, `
|
||||
UPDATE calendar_events SET
|
||||
title = ?, description = ?, reminder_text = ?,
|
||||
agent_name = ?, to_address = ?,
|
||||
recipients = ?, delivery_mode = ?,
|
||||
event_time = ?, remind_before = ?, recurrence = ?, recurrence_end = ?,
|
||||
status = ?, updated_at = ?
|
||||
status = ?, permission_mode = ?, updated_at = ?
|
||||
WHERE event_id = ?`,
|
||||
e.Title, e.Description, e.ReminderText,
|
||||
e.AgentName, e.ToAddress,
|
||||
marshalRecipients(e.Recipients), e.EffectiveDeliveryMode(),
|
||||
e.EventTime, e.RemindBefore, e.Recurrence, e.RecurrenceEnd,
|
||||
e.Status, e.UpdatedAt, eventID,
|
||||
e.Status, e.PermissionMode, e.UpdatedAt, eventID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user