Skip to content

9. Actors, Work, and Policy

Identity answers “who are you?” Access answers “what may you do here?” Work assignment answers “who should receive this task now?”

Blueprint keeps these questions separate.

Actor/task declarations come from vyuh_blueprint; the product action catalog comes from package:vyuh_iam/vyuh_iam.dart.

Actor sets are boolean algebra

dart
const eligibleReviewers = AllOf([
  Role(RoleRef('access.quality_reviewer')),
  Qualified(QualificationRef('training.gmp_current')),
  NoneOf([
    Initiator(),
  ]),
]);

The sealed actor-set vocabulary supports:

  • Anyone, NoOne, ActorUser, Initiator, ActorInField;
  • Role, Group, Grant, Qualified;
  • AllOf, AnyOf, NoneOf;
  • Locked, which permits scoped configuration to narrow but not broaden.

Runtimes can enumerate the eligible set or test one actor against it.

Action access is not task assignment

An action gate:

dart
Rule(
  id: 'ops.area.activate.actor',
  kind: RuleKind.access,
  condition: ActorInSetCondition(eligibleReviewers),
)

A task assignment:

dart
const reviewAssignment = AssignmentSpec(
  claimMode: ClaimMode.pool,
  selectors: [
    RoleSelector(roleRef: RoleRef('access.quality_reviewer')),
    GroupSelector(groupRef: GroupRef('access.hyderabad_quality')),
  ],
);

Role and group membership are expanded late at claim time, so staffing changes do not strand pooled tasks. UserSelector freezes a concrete user at spawn. DerivedSelector(fieldPath: ProjectedFieldRef('self.identity.owner_id')) resolves from context.

Declare a task template

dart
final qualityReviewTask = TaskTemplate(
  name: 'area_activation_review',
  title: 'Review area activation',
  kind: 'approval',
  completionActionRef: ActionRef(
    'ops.area.governance.activate',
  ),
  defaultAssignment: reviewAssignment,
  completion: const ClaimSingle(),
  expiry: const Duration(hours: 24),
  escalations: const [
    TaskEscalation(
      after: Duration(hours: 8),
      assignment: AssignmentSpec(
        selectors: [
          RoleSelector(roleRef: RoleRef('access.quality_manager')),
        ],
      ),
    ),
  ],
  separationOfDuties: const [
    SeparationOfDuties(
      subject: SodSubject.completer,
      notEqualTo: SodInitiator(),
      locked: true,
    ),
  ],
);

Register templates at module scope:

dart
Module(
  // ...
  taskTemplates: [qualityReviewTask],
)

Completion always names an action. There is no task-completion path that bypasses the action journal.

Spawn work through the effect channel

dart
Action(
  name: 'request_activation',
  effects: const [
    TaskEffect(
      templateRef: TaskTemplateRef('ops.area_activation_review'),
      consistencyOverride: ConsistencyMode.eventual,
    ),
  ],
)

TaskEffect targets workflow.task.lifecycle.create. The spawning module must depend on a workflow module that exports that trigger. Outbox delivery, causality, recursion limits, and retry behavior remain common runtime machinery.

Declaration, configuration, and resolution

PlaneExample
Declarationrole ref, actor set, task defaults, SoD lock
Configurationtenant/site assignment override, membership, grant, expiry
Resolutioneffective policies, concrete eligible users, disabled reason
Executionclaim, delegate, complete, expire action records

Do not put concrete tenant membership into the product Blueprint. Do not let a site override weaken a locked separation-of-duties constraint.

Effective actions

Bootstrap gathers facet actions into an EffectiveActionCatalog. At request time, EffectiveActionResolver evaluates placement, access, policies, and context into EffectiveActionSets:

text
catalog action
  + placement
  + actor/scope
  + lifecycle state
  + policy/configuration
  = hidden | disabled(reason) | enabled

The UI renders this result. It must not replicate authorization logic with ad-hoc button conditions.

Product action catalogs

IAM administration uses the same fully qualified action identities as the runtime:

dart
const opsPermissions = IamProductDescriptor(
  product: 'ops',
  title: 'Operations',
  actions: [
    IamActionDescriptor(
      action: 'ops.area.governance.activate',
      title: 'Activate Area',
      entityName: 'area',
      facetName: 'governance',
    ),
    IamActionDescriptor(
      action: 'ops.session.refresh',
      title: 'Refresh Session',
      resourceType: 'session',
      grantEditable: false,
    ),
  ],
);

editableActions includes only actions administrators may grant. Public, self-service, and system actions remain visible in the complete inventory with grantEditable: false. validate() catches duplicate or invalid entries, and validateEntityCoverage() reports entities missing from the product catalog.

Checkpoint

Model dual-control retirement:

  • the requester can submit retirement;
  • a qualified quality reviewer completes it;
  • the completer cannot be the initiator;
  • the task escalates to a quality manager;
  • the completion action still evaluates its own grant and lifecycle rules.

Next: Runtime execution.

References: Runtime operating model · Vocabulary.

Blue is the Vyuh Blueprint documentation surface.