PhoenixKitAI.GuardBlocks (PhoenixKitAI v0.26.1)

Copy Markdown View Source

Blocks: a person may not call a group of AI tasks until the block is lifted (PhoenixKitAI.GuardBlock). A person sets them — there is no automatic block.

Groups

A block is on a task's block_group, or on the task's key when it names none, so tasks that take the same kind of free text (free_text: the Playground and an assistant's chat) share one block. A block of a group is private when any task of the group is (private: true): its reason is shown only to the holders of ai.manage.

The check

check/2 runs on every call of a declared task that has a user, before the model check (see PhoenixKitAI.Guard), and in PhoenixKitAI.Guard.admit/4: a blocked call is refused with {:error, {:task_blocked, block_group}}, which names the group and never the reason (the refusal is a journal row every holder of ai reads). A turn admitted before the block runs to its end. A dry run (process_image(dry_run: true)) is not checked.

Who may

set a blocklift a block
an Owneranybodyany block, their own too (kept as a record)
a holder of ai.manageanybody but an Owneranother person's, not an Owner's
a holder of agent.manage—another person's free_text block, not an Owner's
anybody else——

Nobody but an Owner lifts their own block. agent.manage is checked by its string; this module does not depend on the assistant's.

Each block and lift is a blocked / unblocked event, a notification to the watchers (PhoenixKitAI.GuardEvents), an in-app notice to the person, and an Activity entry with the event as its resource. None of them carries the reason or the note.

The cache

The active blocks of every group are kept on each node (the request cache's flags table), loaded with one query, so a call without a block costs none. A block or a lift drops this node's copy at once and broadcasts {:guard_blocks_changed, block_group} on PhoenixKitAI.TaskLimits.topic/0, which PhoenixKitAI.GuardWatch turns into the same drop on every node; a restarted watch drops it too. The copy is also read again after 60 seconds. The check fails open: a database error reads as no block.

Summary

Functions

The active block of user_uuid in task_key's group, or nil — without its reason and note, for a page or notice that shows the state. With the reason: active/3.

The active block as viewer (a scope) may see it: its reason only when PhoenixKitAI.Guard.reasons_visible?/2 allows it (any viewer of a block that is not private; ai.manage or an Owner for a private one).

Blocks user_uuid from the group of task_or_group (a task key, or a block group's name). reason is kept, cut to 500 characters; origin may name the flag it came from (%{event_uuid: uuid}, kept when the flag is that person's). Needs ai.manage, and an Owner to block an Owner (see the moduledoc).

Whether user_uuid is blocked from task_key now.

:ok, or {:error, {:task_blocked, block_group}} when user_uuid is blocked from the task's group. A call without a task or a user is :ok.

The block group of a task: its block_group, or its key.

The active blocks of a group, newest first (from the database).

Whether scope may block user_uuid (see the moduledoc).

Whether scope may lift block (see the moduledoc).

Whether a block of group is private: any of its tasks is.

The declared tasks of a block group.

Lifts block (a %GuardBlock{} or its uuid), keeping note (cut to 500 characters) and who lifted it. Who may: see the moduledoc.

Functions

active(task_key, user_uuid)

@spec active(String.t(), term()) :: PhoenixKitAI.GuardBlock.t() | nil

The active block of user_uuid in task_key's group, or nil — without its reason and note, for a page or notice that shows the state. With the reason: active/3.

active(task_key, user_uuid, viewer)

@spec active(String.t(), term(), term()) :: PhoenixKitAI.GuardBlock.t() | nil

The active block as viewer (a scope) may see it: its reason only when PhoenixKitAI.Guard.reasons_visible?/2 allows it (any viewer of a block that is not private; ai.manage or an Owner for a private one).

block(scope, task_or_group, user_uuid, reason, origin \\ nil)

@spec block(term(), String.t(), term(), term(), map() | keyword() | nil) ::
  {:ok, PhoenixKitAI.GuardBlock.t()}
  | {:error,
     :forbidden
     | :owner_only
     | :unknown_task
     | :invalid_user
     | :unknown_user
     | :already_blocked
     | Ecto.Changeset.t()}

Blocks user_uuid from the group of task_or_group (a task key, or a block group's name). reason is kept, cut to 500 characters; origin may name the flag it came from (%{event_uuid: uuid}, kept when the flag is that person's). Needs ai.manage, and an Owner to block an Owner (see the moduledoc).

blocked?(task_key, user_uuid)

@spec blocked?(String.t(), term()) :: boolean()

Whether user_uuid is blocked from task_key now.

check(spec, user_uuid)

@spec check(PhoenixKitAI.TaskSpec.t() | nil, term()) ::
  :ok | {:error, {:task_blocked, String.t()}}

:ok, or {:error, {:task_blocked, block_group}} when user_uuid is blocked from the task's group. A call without a task or a user is :ok.

group(task_spec)

@spec group(PhoenixKitAI.TaskSpec.t()) :: String.t()

The block group of a task: its block_group, or its key.

list_active(group)

@spec list_active(String.t()) :: [PhoenixKitAI.GuardBlock.t()]

The active blocks of a group, newest first (from the database).

may_block?(scope, user_uuid)

@spec may_block?(term(), String.t()) :: boolean()

Whether scope may block user_uuid (see the moduledoc).

may_lift?(scope, block)

@spec may_lift?(term(), PhoenixKitAI.GuardBlock.t()) :: boolean()

Whether scope may lift block (see the moduledoc).

private?(group)

@spec private?(String.t()) :: boolean()

Whether a block of group is private: any of its tasks is.

tasks_of(group)

@spec tasks_of(String.t()) :: [PhoenixKitAI.TaskSpec.t()]

The declared tasks of a block group.

unblock(scope, block_or_uuid, note)

@spec unblock(term(), PhoenixKitAI.GuardBlock.t() | String.t(), term()) ::
  {:ok, PhoenixKitAI.GuardBlock.t()}
  | {:error, :forbidden | :owner_only | :own_block | :not_blocked | :not_found}

Lifts block (a %GuardBlock{} or its uuid), keeping note (cut to 500 characters) and who lifted it. Who may: see the moduledoc.