Assignment

Atom grounded on Final Critique 4 — 2026-06-18 10 invariants TLA+ model buggy twin ×1 regulated · via 3 composers composed by 3
Derived card — generated from the corpus (status line, invariant headers, sibling formal-model files, Composes edges). The specification below is canonical. composition graph →
Table of contents
  1. Assignment
    1. Summary
    2. Intent
    3. Structure
      1. Identity model
      2. State
      3. Capability requirement
      4. Operations
      5. Invariants
    4. Examples
      1. Project management — task handoff mid-sprint
      2. Customer support — ticket escalation
      3. Healthcare — patient-to-nurse assignment on a ward
      4. Rejection paths
    5. Generation acceptance
      1. Conformance checks
      2. External checks
    6. Non-goals
    7. Edge cases
      1. Reassign atomicity
      2. The assign race
    8. Composition notes
    9. Terms
      1. Vocabulary
        1. Assignment
        2. Status
        3. Assign
        4. Recall
        5. Reassign
        6. Active For
        7. History For
        8. Assignment Id
        9. Task Ref
        10. Assignee Ref
        11. Assigned At
        12. Recalled At
        13. Transferred At
        14. New Assignee Ref
        15. Now
        16. Active
        17. Recalled
        18. Transferred
        19. Invalid Request
        20. Already Assigned
        21. Not Known
        22. Not Active
        23. Storage Failure
    10. Standards references
    11. Status
    12. Ledger
    13. Decisions

Summary

Assignment records who is responsible for a piece of work and the full history of everyone who has held that responsibility. Each assignment is a record that links a task to the person responsible for it. It moves through three states: Active (in force), Recalled (withdrawn with no replacement), or Transferred (handed off to a new person). The pattern guarantees that a task can have at most one responsible person at a time. The handoff action (reassign) does the swap in a single step, so the task is never left with no one responsible. And because records are never overwritten or deleted, you can always reconstruct who held a task and when. Each assignment carries an opaque, immutable identifier the host supplies at the atom’s one boundary with the outside world — the same place the clock is read, so the pattern’s own logic stays free of both; the task it is for and the actor it binds are fixed when the assignment is created and never change. It deliberately leaves out related questions — whether the person accepts the work, who is allowed to assign it, how much work one person may hold — because those are handled by separate patterns that attach to it. This makes it usable as-is for project boards, support-ticket queues, healthcare shift handoffs, legal case routing, and any other place where accountability for work needs to be tracked.

Also known as: a work assignment, a responsibility binding, a task owner, an owner of record.


Intent

WHY: Work gets handed to people, and the handing has to be answerable: who holds this now, who held it before, and when did it change hands. Most systems carry an assignee column and lose the second two questions the moment somebody edits it. This atom makes each binding a record with its own life — created, held, ended one of two ways — so the column becomes a history. Two decisions do the work. A task has at most one live assignment, so who is responsible has exactly one answer or none, never two. And a handoff is one step, not a recall followed by an assign, so the task is never momentarily nobody’s. Everything else an assignment regime wants — acceptance, deadlines, workload caps, who was allowed to assign — is a composing pattern, named and declined here.

Structure

Identity model

Identity 1: The atom MUST identify an assignment by the assignment id.
Identity 2: The host MUST allocate an assignment id at the atom's seam.
Identity 3: The transition MUST NOT allocate an assignment id.
Identity 4: The business caller MUST NOT supply an assignment id.
Identity 5: The atom MUST NOT reuse an assignment id.
Identity 6: The atom MUST NOT identify an assignment by the task ref.
Identity 7: The atom MUST NOT identify an assignment by the task ref with the assignee ref.
Identity 8: Two assignments over one task MUST carry two assignment ids.

Term assignment: one binding of a unit of work to a responsible actor — an Assignment.

Term assignment id: the opaque value naming one assignment — an Assignment Id.

Term task ref: the opaque reference naming the unit of work — a Task Ref; the host owns what a task is.

Term assignee ref: the opaque reference naming the responsible actor — an Assignee Ref; the actor registry is a separate concept.

Term seam: the atom’s I/O boundary as the section titled Logic Confinement Principle in execution-contract.md declares it; the host injects the clock reading and the assignment id here.

Term transition: the atom’s evaluation of one call against the assignment store, as the section titled Logic Confinement Principle in execution-contract.md declares it.

Term business caller: the party whose action the call carries, as the section titled Logic Confinement Principle in execution-contract.md declares it; never the source of an injected value.

Term now: the wall-time reading the host takes at the seam and hands to the transition, as the section titled Logic Confinement Principle in execution-contract.md declares it; never read inside the transition, never supplied by the business caller.

WHY: Identity by task would make a reassignment overwrite its predecessor, which destroys the answer to who held this before; identity by the task-and-assignee pair would collapse a re-assignment of the same actor after an intervening recall, which destroys how many times (Identity 6, Identity 7). One binding, one id, is what makes Invariant 9.1’s chain recoverable.

State

State 1: EVERY assignment MUST stand in EXACTLY ONE OF active, recalled, transferred.
State 2: EVERY assignment MUST carry assignment id, task ref, assignee ref, assigned at and status.
State 3: A recalled assignment MUST carry recalled at.
State 4: A transferred assignment MUST carry transferred at.
State 5: [Assign] MUST stamp assigned at from the injected now.
State 6: [Recall] MUST stamp recalled at from the injected now.
State 7: [Reassign] MUST stamp transferred at from the injected now.
State 8: The atom MUST NOT offer a transition out of recalled.
State 9: The atom MUST NOT offer a transition out of transferred.
State 10: The atom MUST NOT delete an assignment.
State 11: The atom MUST NOT hold a task's lifecycle.
State 12: The atom MUST NOT hold an assignee's workload.
State 13: The atom MUST NOT re-derive a stamp from a later reading.
Term status: active recalled transferred — in force, withdrawn with nobody after, or handed on to a successor.

Term assigned at: the instant the assignment was created — an Assigned At.

Term recalled at: the instant the assignment was withdrawn — a Recalled At.

Term transferred at: the instant the assignment was handed on — a Transferred At.

WHY: Recalled and transferred are two terminal values of the Status rather than one because they answer different audit questions: recalled means the task is nobody’s, transferred means it is somebody else’s, and a single closed state would make an auditor infer the difference from the presence of a successor (State 8, State 9). Nothing is deleted, so the chain of responsibility is the store rather than a reconstruction (State 10).

Capability requirement

Capability requirement 1: The deployment MUST supply now at the seam.
Deleted: Capability requirement 2. Execution Contract Logic confinement 7 owns it.
Deleted: Capability requirement 3. Execution Contract Logic confinement 7 owns it.
Deleted: Clock semantics 1. Execution Contract Logic confinement 7 owns it.
Deleted: Clock semantics 2. Execution Contract Logic confinement 7 owns it.
Deleted: Clock semantics 3. State 13 owns it.

WHY: What the deployment supplies, which is what the family means. The rule stood under Operation — one action’s rules — while naming no action, because this spec was migrated before the standard family had a home in an atom; the five atoms migrated a day later put the same obligation here. The words are the words the rule carried (council read 76).

Operations

assign(task_ref, assignee_ref)
  answers assignment_id
  refuses invalid-request | already-assigned | storage-failure

recall(assignment_id)
  answers ok
  refuses not-known | not-active | storage-failure

reassign(assignment_id, new_assignee_ref)
  answers new_assignment_id
  refuses not-known | not-active | invalid-request | storage-failure

active_for(task_ref)
  answers assignment | none

history_for(task_ref)
  answers assignments
Operation 1: [Assign] MUST record EXACTLY ONE assignment per successful call.
Operation 2: [Assign] MUST stand the assignment in active.
Operation 3: [Assign] MUST answer assignment id.
Operation 4: IF task ref EQUALS blank THEN [Assign] MUST answer invalid-request.
Operation 5: IF assignee ref EQUALS blank THEN [Assign] MUST answer invalid-request.
Operation 6: IF an active assignment EXISTS for the task ref THEN [Assign] MUST answer already-assigned.
Operation 7: IF the store refuses the write THEN [Assign] MUST answer storage-failure.
Operation 8: IF no assignment EXISTS for the assignment id THEN [Recall] MUST answer not-known.
Operation 9: IF the assignment's status EQUALS recalled THEN [Recall] MUST answer not-active.
Operation 10: IF the assignment's status EQUALS transferred THEN [Recall] MUST answer not-active.
Operation 11: [Recall] MUST stand the assignment in recalled.
Operation 12: [Recall] MUST leave the task ref with no active assignment.
Operation 13: IF the store refuses the write THEN [Recall] MUST answer storage-failure.
Operation 14: IF no assignment EXISTS for the assignment id THEN [Reassign] MUST answer not-known.
Operation 15: IF the assignment's status EQUALS recalled THEN [Reassign] MUST answer not-active.
Operation 16: IF the assignment's status EQUALS transferred THEN [Reassign] MUST answer not-active.
Operation 17: IF new assignee ref EQUALS blank THEN [Reassign] MUST answer invalid-request.
Operation 18: [Reassign] MUST stand the old assignment in transferred.
Operation 19: [Reassign] MUST record EXACTLY ONE active assignment for the task ref.
Operation 20: [Reassign] MUST commit the two writes together.
Operation 21: [Reassign] MUST answer the new assignment id.
Operation 22: IF the store refuses either write THEN [Reassign] MUST answer storage-failure.
Operation 23: [Reassign] MUST withdraw both writes on storage-failure.
Operation 24: A refused call MUST leave the store as the call found the store.
Operation 25: [Active For] MUST answer the active assignment for the task ref.
Operation 26: [Active For] MUST answer none for a task ref with no active assignment.
Operation 27: [History For] MUST answer EVERY assignment carrying the task ref.
Operation 28: [History For] MUST order the answer by assigned at.
Operation 29: [Active For] MUST NOT write.
Operation 30: [History For] MUST NOT write.
Operation 30a: [Reassign] MUST read one now per call.
Operation 30b: [Reassign] MUST stamp transferred at and assigned at against that one now.
Deleted: Operation 31. Capability requirement 1 owns it.
Deleted: Operation 32. Execution Contract Logic confinement 3 owns it.
Deleted: Operation 33. Execution Contract Logic confinement 3 owns it.

Term new assignee ref: the opaque reference naming the successor a reassignment hands the task to — a New Assignee Ref.

The case space, and the rule that owns each case:

Call Case Answer Effect on the assignment store
Assign refs present, task unassigned, store accepts assignment id one assignment lands in Active (Operation 1, Operation 2)
Assign blank task ref or assignee ref Invalid Request none (Operation 4, Operation 5)
Assign task already has a live assignment Already Assigned none (Operation 6)
Recall assignment is live ok ActiveRecalled; the task is nobody’s (Operation 11, Operation 12)
Recall assignment is recalled or transferred Not Active none (Operation 9, Operation 10)
Reassign assignment live, successor present, store accepts the new assignment id old → Transferred and a new Active one, in one commit (Operation 18 through 21)
Reassign blank new assignee ref Invalid Request none (Operation 17)
Reassign assignment is recalled or transferred Not Active none (Operation 15, Operation 16)
Reassign either write refused Storage Failure none — both withdrawn, the old stays Active (Operation 22, Operation 23)
any id names nothing Not Known none (Operation 8, Operation 14)
Active For task has a live assignment that assignment none — the call reads (Operation 25, Operation 29)
Active For task is unassigned none none (Operation 26)
History For any task every assignment for it, by assigned at none (Operation 27, Operation 28, Operation 30)

WHY: Reassign is one commit and not a recall followed by an assign, which is the whole reason the operation exists: the two-call version leaves a window where the task is nobody’s, and a system that reports coverage during that window reports a gap that never should have opened (Operation 20, Invariant 7.1). Its failure arm is the expensive one — two writes, and a partial landing leaves the task unassigned after a call the caller believes succeeded — so the withdrawal is stated rather than assumed (Operation 23, Reassign atomicity 1 through 4). Both stamps come from one clock reading, the discipline Retention Window states for its purge: two readings would drift the successor’s assigned at from the predecessor’s transferred at, and Check 2.3 reads a handoff by that equality (Operation 30a, Operation 30b).

Invariants

  • Invariant 1 — At most one Active assignment per task.
    Invariant 1.1: Two active assignments MUST NOT share a task ref.
    
  • Invariant 2 — Assignment immutability.
    Invariant 2.1: A recorded assignment's assignment id, task ref, assignee ref and assigned at MUST NOT change.
    
  • Invariant 3 — Status monotonicity.
    Invariant 3.1: A status MUST move from active to EXACTLY ONE OF recalled, transferred.
    Invariant 3.2: A status MUST NOT move to active from a terminal status.
    
  • Invariant 4 — Terminal states are absorbing.
    Invariant 4.1: A recalled assignment MUST NOT take a further transition.
    Invariant 4.2: A transferred assignment MUST NOT take a further transition.
    
  • Invariant 5 — Id stability.
    Invariant 5.1: [Assign] MUST set the assignment id.
    Invariant 5.2: An assignment id MUST NOT change.
    
  • Invariant 6 — No id reuse.
    Invariant 6.1: Two assignments MUST NOT share an assignment id.
    
  • Invariant 7 — Reassign atomicity.
    Invariant 7.1: A task ref MUST carry EXACTLY ONE active assignment once [Reassign] lands.
    Invariant 7.2: The reassigned assignment MUST stand in transferred once [Reassign] lands.
    Invariant 7.3: A reader MUST NOT observe two active assignments for one task ref.
    Invariant 7.4: A reader MUST NOT observe the task ref unassigned once the first write lands AND no second write EXISTS.
    
  • Invariant 8 — Timestamp ordering.
    Invariant 8.1: IF recalled at DOES NOT EQUAL blank THEN assigned at MUST NOT EXCEED recalled at.
    Invariant 8.2: IF transferred at DOES NOT EQUAL blank THEN assigned at MUST NOT EXCEED transferred at.
    Invariant 8.3: The atom MUST stamp EVERY timestamp once.
    

    WHY: best-effort under a clock that moves backward; a stamp is never re-derived from a later reading (Execution Contract Logic confinement 7).

  • Invariant 9 — Complete responsibility history.
    Invariant 9.1: The assignments carrying one task ref MUST record EVERY actor who held the task.
    Invariant 9.2: The assignments carrying one task ref MUST record when each holding began.
    Invariant 9.3: The assignments carrying one task ref MUST record how each holding ended.
    
  • Invariant 10 — Assignment store durability.
    Invariant 10.1: The atom MUST NOT delete an assignment record.
    Invariant 10.2: The assignment set MUST NOT shrink.
    

At-most-one and reassign atomicity give the unambiguous accountability property — who is responsible for this task? has one answer or none, never two. Immutability, the complete history and durability give auditability — the chain is the store, and nothing leaves it quietly.

Examples

The same atom, four domains, identical mechanic.

Project management — task handoff mid-sprint

A sprint board has a task “implement login flow” (task ref: task_t44). The engineering manager assigns it to a developer: assign(task_t44, dev_alice) → assignment_id a1. Alice picks it up. Mid-sprint, Alice is pulled onto a production incident; the manager reassigns: reassign(a1, dev_bob) → a2. Alice’s assignment (a1) moves to Transferred; Bob’s (a2) is now Active. The sprint retrospective can reconstruct: Alice held the task from day 1 to day 4; Bob held it from day 4 to completion. At no point was the task unassigned.

Customer support — ticket escalation

A support ticket is auto-assigned to a tier-1 agent: assign(ticket_t99, agent_tier1_j) → a5. The agent cannot resolve the issue; they escalate. The supervisor calls reassign(a5, agent_tier2_k) → a6. Tier-2 resolves it. The audit log shows: tier-1 held the ticket for 2 hours, tier-2 for 45 minutes. If the customer complains about resolution time, both ownership windows are on record. SLA (Service-Level Agreement — a commitment to a measurable level of service, such as a maximum resolution time) calculations use the Assigned At and Transferred At of each assignment record.

Healthcare — patient-to-nurse assignment on a ward

A patient is admitted and assigned to the on-call nurse: assign(patient_p31, nurse_n7) → a12. At shift change, the charge nurse reassigns: reassign(a12, nurse_n14) → a13. If a clinical incident occurs overnight, the investigation can determine which nurse held the assignment at what time. The assignment store is the accountability record; the clinical event log (Event Log atom) is the action record; both compose to answer the investigation’s questions.

Rejection paths

A single sequence exercising all rejection reasons:

  • assign(task_t1, dev_a) → a1 — accepted.
  • assign(task_t1, dev_b) → rejected already-assigned (Invariant 1; task_t1 already has an Active in a1).
  • recall(unknown_id) → rejected not-known.
  • recall(a1) → oka1 moves to Recalled; task_t1 is now unassigned.
  • recall(a1) → rejected not-active (a1 is already Recalled; terminal).
  • reassign(a1, dev_c) → rejected not-active (a1 is terminal).
  • assign(task_t1, dev_b) → a2 — accepted; task_t1 is now unassigned so a fresh Assignment is allowed.
  • reassign(a2, "") → rejected invalid-request (empty assignee).
  • assign(task_t2, dev_c) → rejected storage-failure (store write fails; no Assignment created; task_t2 remains unassigned).

All five rejection reasons (invalid-request, already-assigned, not-known, not-active, storage-failure) exercised in one thread.


Generation acceptance

This atom’s acceptance is what an external auditor can clear from the assignment store’s stored fields, with no recourse to source code, runbooks or developer narration.

Conformance checks

Check 1.1: An auditor MUST find no task ref carrying two active assignments (Invariant 1.1).
Check 2.1: An auditor MUST reconstruct a task's chain of responsibility from the assignments carrying the task ref (Invariant 9.1, Invariant 9.2, Invariant 9.3).
Check 2.2: An auditor MUST read a recalled assignment as the task standing unassigned at recalled at (Operation 12).
Check 2.3: An auditor MUST read a transferred assignment as a successor whose status EQUALS active at transferred at (Invariant 7.1, Invariant 7.2).
Check 3.1: An auditor MUST find no assignment whose status moved out of a terminal status (Invariant 3.2, Invariant 4.1, Invariant 4.2).
Check 4.1: An auditor MUST find no assignment whose terminal stamp PRECEDES the assignment's assigned at (Invariant 8.1, Invariant 8.2).
Check 5.1: An auditor MUST identify which composing patterns a deployment wired in (Composition note 1).

External checks

External check 1: An auditor MUST read the assigner's authority from the composing Permissions records (Non-goal 5).
External check 2: An auditor MUST read who issued an assignment from the composing Actor Identity attestations (Non-goal 7).
External check 3: An auditor MUST read a task's completion from the host's task system (Non-goal 11).
External check 4: An auditor MUST read the serialization evidence from the deployment's own concurrency probe (Invariant 7.3, Invariant 7.4, Assign race 1).

NOTE: Invariant 7.3 and Invariant 7.4 forbid a reader observing a state. Records written after the fact cannot show what was observable between two writes, so no conformance check clears them and External check 4 names the probe that can — the records-alone gap the section titled Generation trust in open-questions.md docks, with its first load-bearing resident (council read 14).

NOTE: EVERY check names the rule the check tests. The assignment store answers who holds this and who held it; who was allowed to hand it over, and whether the work is done, are the composing patterns’ records.

Non-goals

Non-goal 1: The atom MUST NOT require an assignee's acceptance.
Non-goal 2: A deployment needing acceptance MUST compose an acceptance pattern.
Non-goal 3: The atom MUST NOT expire an assignment.
Non-goal 4: A deployment needing a time-bounded assignment MUST compose a temporal-grant pattern.
Non-goal 5: The atom MUST NOT check an assigner's authority.
Non-goal 6: A deployment needing an authorized assigner MUST compose Permissions.
Non-goal 7: The atom MUST NOT record who issued an assignment.
Non-goal 8: A deployment needing assigner attribution MUST compose Actor Identity.
Non-goal 9: The atom MUST NOT cap an assignee's active assignments.
Non-goal 10: The atom MUST NOT bind two assignees to one active assignment.
Non-goal 11: The atom MUST NOT read a task's state.
Non-goal 12: The atom MUST NOT recall an assignment on the task's completion.

WHY: The atom binds and records; every judgment around the binding is somebody else’s. Acceptance, deadlines, authority and attribution each compose (Non-goal 1 through 8). A workload cap is a Capacity Constraint pattern reading the assignee’s live count before the assign, which this atom deliberately does not count (Non-goal 9). Team assignment — where any member may act — is a different concept and not a wider assignee ref (Non-goal 10). Completion is the task system’s event: the composing pattern decides whether a finished task leaves its assignment standing as an attribution record or is recalled to close the lifecycle, and both are ordinary (Non-goal 12, Composition note 4).

Where the atom breaks down: when responsibility is genuinely shared at the same time; when an assignment must end on its own without anyone withdrawing it; when the assigner must be authorized before assigning; when the assignee must consent before holding.

Edge cases

Reassign atomicity

Reassign atomicity 1: The implementation MUST commit the transferred write and the active write together.
Reassign atomicity 2: A crash inside [Reassign] MUST NOT leave the task ref unassigned.
Reassign atomicity 3: A crash inside [Reassign] MUST NOT leave two active assignments for one task ref.
Reassign atomicity 4: An implementation that cannot withdraw a landed write MUST NOT accept a further call BEFORE the implementation repairs the partial state.

WHY: The dangerous half is the quiet one: old marked transferred, successor never written, task unassigned, and the caller told the handoff succeeded. Invariant 1.1 is satisfied vacuously by that state; Reassign atomicity 2 forbids the crash residue and Invariant 7.4 forbids any reader seeing the gap, whether transient or stable — and an implementation without rollback owes a repair pass rather than a note in a runbook (the section titled Multi-write atomicity in execution-contract.md).

The assign race

Assign race 1: The implementation MUST make the active-assignment check and the write one transition.
Assign race 2: The implementation MUST NOT record two active assignments for one task ref under concurrent calls.
Assign race 3: The second concurrent [Assign] for one task ref MUST answer already-assigned.

Composition notes

Composition note 1: A deployment MUST declare which composing patterns the deployment wired in.
Composition note 2: A composing pattern MUST own what a task is.
Composition note 3: A composing pattern MUST own whether an assignee may hold the task.
Composition note 4: A composing pattern MUST own whether a completed task's assignment is recalled.
Composition note 5: A composing pattern needing assigner attribution MUST attest [Assign] under the assigner's credential.

WHY: Shared Todo is the landed wiring: Personal Todo supplies the task, Permissions gates who may act, and this atom binds responsibility — three atoms, one multi-actor list, none of them knowing the others’ rules. Attribution composes the same way Attributed Permissions Admin does it for grants: an Actor Identity attestation beside the record, never a field added here (Composition note 5). Forthcoming: Acceptance, Temporal Grant, Capacity Constraint, Team Assignment.

Terms

Each [Term] marker above links to its term entry here; a term entry states what the concept is and its Kind.

Vocabulary

Term actors: the atom; the host; the transition; the implementation; the deployment; a composing pattern (also: a pattern); a business caller; an assigner; an assignee; an auditor; a reader; the store; an assignment; a task; a call; a crash.

Term records: assignment — one binding, carrying assignment id, task ref, assignee ref, assigned at, status and, once it ends, recalled at or transferred at.

Term record verbs: identify, allocate, supply, reuse, carry, stand, stamp, offer, delete, hold, record, answer, leave, write, read, commit, withdraw, order, set, change, move, take, share, observe, shrink, make, repair, accept, require, expire, check, cap, bind, recall, compose, own, attest, declare, find, reconstruct, re-derive, exceed.

Term value sets: status = active recalled transferred.

Term bounds: empty.

Term cadences: empty.

Term qualifiers: migrated — rewritten in GRACE lang v0.35 (2026-09-12).

Term terms: assignment, assignment id, task ref, assignee ref, seam, transition, business caller, now, status, assigned at, recalled at, transferred at, new assignee ref.

Assignment

The record this atom defines: a binding of a unit of work to the actor responsible for completing it. It carries its Assignment Id, Task Ref, Assignee Ref, Assigned At, the status field below, and — where applicable — Recalled At or Transferred At. The Assignment Id, Task Ref, Assignee Ref, and Assigned At are immutable from creation. Its status field holds one of Active, Recalled, or Transferred; at most one Assignment per task is Active at any time.

Kind: Type

Status

The Assignment’s lifecycle state — Active, Recalled or Transferred. Set to Active on creation; moves once, to one terminal state, and never back (Invariant 3.1, Invariant 3.2).

Kind: Field Field of: the assignment Projection: status

Assign

The behavior that binds a unit of work to a responsible actor, recording a new Assignment. It assigns a fresh Assignment Id from injected id material at the seam, sets Task Ref, Assignee Ref, and Assigned At = Now, enters the Assignment in Active, and returns the Assignment Id (or a rejection naming the failed precondition). It refuses Already Assigned if an Active already exists for the Task Ref.

Kind: Operation

Recall

The behavior that withdraws an Active without a successor, leaving the task unassigned. Permitted only on an Active; it moves the AssignmentRecalled and stamps Recalled At. On an unknown id it is rejected Not Known; on an already-terminal Assignment it is rejected Not Active.

Kind: Operation

Reassign

The behavior that hands an Active off to a new actor atomically. In one committed step it moves the old AssignmentTransferred (stamping Transferred At) and creates a new Active for the same Task Ref with the New Assignee Ref; it returns the new Assignment Id. There is no observable state in which both are Active or neither is (Invariant 7).

Kind: Operation

Active For

The read query that returns the at-most-one Active for a given Task Ref, or none if the task is currently unassigned. Read-only; consistent with Invariant 1.

Kind: Operation

History For

The read query that returns all assignments (Active, Recalled, Transferred) for a given Task Ref, ordered by Assigned At — the complete responsibility chain required by Invariant 9. Read-only.

Kind: Operation

Assignment Id

The opaque, immutable identity of an Assignment, host-allocated from injected id material at the I/O seam and never reused after a terminal state. The Task Ref and Assignee Ref are properties of the Assignment, not its identity.

Kind: Field Field of: Assignment Projection: assignment_id

Task Ref

The opaque reference identifying the unit of work an Assignment is for. The atom does not know what a task is or how its lifecycle is managed. Set on creation, immutable thereafter.

Kind: Field Field of: Assignment Projection: task_ref

Assignee Ref

The opaque reference identifying the actor an Assignment binds responsibility to. The actor registry is a separate concept. Set on creation, immutable thereafter.

Kind: Field Field of: Assignment Projection: assignee_ref

Assigned At

The wall-time the Assignment was created, stamped from the injected Now on Assign (and on the Assign inside Reassign). Immutable thereafter. Assigned AtRecalled At and Assigned AtTransferred At always hold.

Kind: Field Field of: Assignment Projection: assigned_at

Recalled At

The wall-time the Assignment was recalled, stamped from the injected Now on Recall. Present only in Recalled; immutable once set.

Kind: Field Field of: Assignment Projection: recalled_at

Transferred At

The wall-time the Assignment was transferred, stamped from the injected Now on Reassign. Present only in Transferred; immutable once set.

Kind: Field Field of: Assignment Projection: transferred_at

New Assignee Ref

The reference to the new responsible actor Reassign consumes and writes into the new Active’s Assignee Ref. Required well-formed and non-empty. It is not stored under its own name — only the new Assignment’s Assignee Ref is stored.

Kind: Parameter Parameter of: Reassign Projection: new_assignee_ref

Now

The current clock reading every writing action consumes — the pipeline’s clock_t, supplied at the atom’s seam, never read inside the transition and never a signature parameter. Its only use is the immutable timestamp stamps inside committed transitions (Assigned At, Recalled At, Transferred At).

Kind: Parameter Parameter of: Assign, Recall and Reassign Projection: now

Active

The single non-terminal state: the Assignment is in force and the assignee is the current responsible actor for the task. At most one Assignment per Task Ref is Active. The lifecycle proceeds Active → one of {Recalled, Transferred}.

Kind: Member Member of: the assignment status Role: Outcome

Recalled

The terminal state an Assignment reaches when the assigner withdrew it without a successor — the task is now unassigned. Absorbing: no action transitions it elsewhere.

Kind: Member Member of: the assignment status Role: Outcome

Transferred

The terminal state an Assignment reaches when a Reassign superseded it with a new Active — the task has a new responsible actor. Absorbing: no action transitions it elsewhere.

Kind: Member Member of: the assignment status Role: Outcome

Invalid Request

The refusal Assign returns when Task Ref or Assignee Ref is not well-formed or is empty, and Reassign returns when New Assignee Ref is not well-formed or is empty. A guard rejection that fails before any store write; no Assignment is created or changed.

Kind: Member Member of: the action rejection Role: Outcome Projection: invalid-request

Already Assigned

The refusal Assign returns when an Active already exists for the Task Ref. The loser of a concurrent assign race for the same Task Ref also receives this. No Assignment is created. This is the at-most-one-Active guard (Invariant 1) enforced at the Assign boundary.

Kind: Member Member of: the Assign rejection Role: Outcome Projection: already-assigned

Not Known

The refusal Recall or Reassign returns when the supplied Assignment Id references no known Assignment. A lookup miss, distinct from a state rejection.

Kind: Member Member of: the resolving-action rejection Role: Outcome Projection: not-known

Not Active

The refusal Recall or Reassign returns when the referenced Assignment is already terminal — Recalled or Transferred. This is the terminal-absorption guard: a resolving action on an already-resolved Assignment is refused without modifying any record.

Kind: Member Member of: the resolving-action rejection Role: Outcome Projection: not-active

Storage Failure

The refusal any action returns when the store write fails after the preconditions pass. No Assignment is created (for Assign), the Assignment remains Active (for Recall), or both Reassign writes are rolled back. The caller must treat it as definitive.

Kind: Member Member of: the action rejection Role: Outcome Projection: storage-failure


Standards references

Assignment is a productivity primitive with broad operational anchoring and lighter regulatory footprint than the compliance atoms:

  • ITIL (IT Infrastructure Library) — incident and request management define assignment as the binding of a work item to a responsible individual or group. ITIL’s assignment and escalation mechanics are the operational reference for the support-queue examples.
  • ISO/IEC 20000 (IT Service Management) — formalizes incident assignment and reassignment as required process steps with audit-trail obligations. The assignment store satisfies the audit requirement.
  • HL7 FHIR (Health Level Seven Fast Healthcare Interoperability Resources — the standard for exchanging healthcare data electronically) Task resource — healthcare task management defines assignment as a Task.owner binding, with history of ownership tracked per task. The atom’s responsibility-history invariant (Invariant 9) is the FHIR-compatible form.
  • PMI PMBOK (Project Management Body of Knowledge) — responsibility assignment matrices (RAM / RACI) are the structured form of the same mechanic: binding work packages to responsible individuals. The atom is the dynamic runtime form of a RACI row.
  • GDPR (EU General Data Protection Regulation) Article 5(1)(f) and Article 32 — in systems processing personal data, assignment records establish who had access and responsibility for personal data at what time. The assignment store is part of the accountability trail.

It inherits from:

  • Daniel Jackson, The Essence of Software — freestanding-atom posture; the discipline of keeping accept/decline, authorization, capacity, and expiry as composing concepts rather than absorbing them.
  • Eiffel’s design-by-contract — preconditions on Assign, Recall, Reassign; named rejection reasons.

Status

grounded on Final Critique 4 — 2026-06-18 — see the Ledger.

Ledger

status: grounded on Final Critique 4 — 2026-06-18
formal: verified — assignment.tla + 1 twin, 2026-06-03
last gate: 2026-06-18 — Final Critique 4, fresh reader — clean

open: none

Decisions

Directional changes only — the turns a future reader must know the pattern took, and why. Everything smaller lives in the commit that made it: git log -- atoms/assignment.md.

  • 2026-09-12 — Rewritten in GRACE lang v0.35; nothing but language changed. Chose: labelled rules in fenced blocks, the five actions as a signature block, the refusal order carried by each rule’s own condition, the ten invariant numbers unchanged, Generation acceptance as conformance checks plus external checks ahead of Non-goals, Non-goals and Edge cases as two sections, the transition table kept beside the rules as the case space. Over: the prose spec. Because: the migration plan; cites.py --into assignment prints nothing, so no number is frozen from outside.

NOTE: End of Assignment.