Assignment

Table of contents
  1. Assignment
    1. Intent
    2. Summary
    3. Structure
      1. Identity model
      2. Inputs
      3. Outputs
      4. State
      5. Flow
      6. Decision points
      7. Behavior
      8. Feedback
      9. 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. Edge cases and explicit non-goals
    6. Composition notes
    7. Standards references
    8. Status
    9. Lineage notes

A productivity primitive: a binding of a unit of work to the actor responsible for completing it. Each assignment has an opaque immutable id; the task reference and assignee reference are immutable properties set at assignment time. An assignment is Active until recalled or transferred. At most one assignment is Active per task at any time.


Intent

Every system that distributes work across multiple actors must answer two questions: who is responsible for this unit of work right now, and what was the history of responsibility. The first question determines accountability; the second determines auditability. Both require a record, and the record must survive reassignment, withdrawal, and transfer cleanly.

The pattern addresses a class of needs that recurs across virtually every domain where work is delegated: task assignment in project management, ticket routing in support queues, patient-to-nurse assignment in clinical workflows, job allocation in manufacturing, case assignment in legal and government systems. The shape is constant — a unit of work is bound to an actor, the binding may be transferred or recalled, and the full history of who held responsibility and when is a recoverable record.

This is a freestanding (can be specified without naming any other pattern) atom in the EOS (Essence of Software — Daniel Jackson’s framework for specifying software concepts as freestanding, composable units) sense. It has its own state (the assignment record), its own actions (assign, recall, reassign), and its own operational principles (at most one Active assignment per task; both recall and transfer are terminal; the audit history is complete). It does not implement accept/decline workflows, expiry-based auto-recall, assigner authorization rules, or work capacity constraints. Each is a separate composable atom; see Composition notes.


Summary

Assignment is a single-responsibility-binding atom (a freestanding pattern spec that does not name any other pattern) that answers two enduring questions about any system distributing work across people: who is responsible for a given unit of work right now, and what is the complete history of everyone who held that responsibility. It models a binding — a named record that ties a task reference to an assignee reference — and tracks that binding through its full lifecycle: Active (in force), Recalled (withdrawn without a successor), or Transferred (superseded by a new Active binding). Every assignment is given an opaque identifier (a system-generated ID with no meaningful content) at creation; that identifier, the task reference, the assignee reference, and the creation timestamp are all immutable once set, which means no assignment record is ever silently overwritten or lost.

The atom enforces exactly one invariant (a condition that must always hold) about simultaneous coverage: at most one assignment may be Active for any given task at any moment. The reassign action satisfies this invariant atomically — it transitions the old assignment to Transferred and creates the new Active assignment in a single committed operation, so there is never a window during which the task is unassigned. By contrast, a caller-driven recall-then-assign sequence would produce such a gap; reassign exists precisely to eliminate it. The atom does not model whether the assignee accepts the binding, who is authorized to issue assignments, how many assignments a single actor may hold, or what happens when the task itself is completed — all of these are composing-pattern concerns that attach to the atom’s boundaries without changing its core mechanics. The result is a pattern that applies without modification to project-management boards, support-ticket queues, healthcare shift assignments, legal case routing, and any other domain where accountability and auditability of work distribution matter. Grounded (passed all required review passes and is stable enough to generate from) after one full three-pass review and one refinement round.


Structure

Identity model

Every assignment known to the system has an assignment_id — an opaque, immutable, system-generated identifier produced by assign. The id is the assignment’s identity; the task reference and assignee reference are immutable properties of the assignment, not its identity.

Two assignments for the same task have different ids — a reassignment creates a new record with its own id, its own assigned_at, and its own lifecycle. Ids are not reused after an assignment reaches a terminal state.

The opaque-id model is load-bearing. Identifying an assignment by task_ref would make reassignments overwrite the previous record, destroying the history of who held responsibility and for how long. Identifying by (task_ref, assignee_ref) would collapse re-assignments of the same actor after an intervening recall. Opaque ids preserve the one-assignment-one-id discipline that makes per-task responsibility history recoverable from the records alone.

Inputs

  • A task reference identifying the unit of work being assigned. Opaque — the atom does not know what a task is or how its lifecycle is managed.
  • An assignee reference identifying the actor being assigned responsibility. Opaque — the actor registry is a separate concern.
  • Actions:
    • assign(task_ref, assignee_ref) → assignment_id | rejected(invalid-request | already-assigned | storage-failure)
    • recall(assignment_id) → ok | rejected(not-known | not-active | storage-failure)
    • reassign(assignment_id, new_assignee_ref) → new_assignment_id | rejected(not-known | not-active | invalid-request | storage-failure)
  • An implicit clock providing wall-time timestamps.

Outputs

  • The current set of Active assignments.
  • The full set of Recalled and Transferred assignments.
  • For each assignment: assignment_id, task_ref, assignee_ref, assigned_at, status, and — where applicable — recalled_at or transferred_at.
  • assign returns the new assignment_id on success, or a rejection naming the failed precondition.
  • recall returns ok on success, or a rejection.
  • reassign returns the new assignment_id on success, or a rejection.

State

An assignment occupies exactly one of three states:

  • Active — the assignment is in force; the assignee is the current responsible actor for the task.
  • Recalled — the assignment was withdrawn by the assigner without creating a successor. The task is now unassigned. Terminal.
  • Transferred — the assignment was superseded by a reassign that created a new Active assignment. The task has a new responsible actor. Terminal.

Recalled and Transferred are distinct terminal states because the organizational events they represent are distinct: Recalled means the task is no longer assigned to anyone; Transferred means it has been handed off. Both are terminal, but they answer different audit questions.

Each assignment carries:

  • assignment_id — opaque, immutable, system-generated. Set on assign or the assign inside reassign. Never changes.
  • task_ref — opaque reference to the unit of work. Set on creation. Never changes.
  • assignee_ref — opaque reference to the responsible actor. Set on creation. Never changes.
  • assigned_at — wall-time (clock time as a human would read it, not an internal counter) when the assignment was created. Set on creation. Never changes.
  • statusactive, recalled, or transferred. Set to active on creation.
  • recalled_at — wall-time of recall. Present only in Recalled. Set on recall. Never changes after set.
  • transferred_at — wall-time of transfer. Present only in Transferred. Set on reassign. Never changes after set.

Transitions:

  • assign(task_ref, assignee_ref) → a new assignment is created in Active with a fresh assignment_id, the supplied task_ref and assignee_ref, and assigned_at = now. Returns assignment_id.
  • recall(assignment_id) → the assignment at assignment_id moves Active → Recalled; recalled_at = now. Returns ok. The task is now unassigned.
  • reassign(assignment_id, new_assignee_ref) → atomically: the assignment at assignment_id moves Active → Transferred (transferred_at = now); a new assignment is created in Active for the same task_ref with new_assignee_ref and assigned_at = now. Returns the new assignment_id.

Flow

  1. Assign. A work distributor calls assign(task_ref, assignee_ref). The atom confirms no Active assignment already exists for task_ref, creates the assignment in Active, and returns the id. (Start.)
  2. Active. The assignee holds responsibility. The task proceeds under their ownership.
  3. Resolve — three branches:
    • Recall. The assigner withdraws the assignment: recall(assignment_id). Active → Recalled. The task is unassigned; the assigner may re-assign separately.
    • Reassign. The assigner transfers responsibility: reassign(assignment_id, new_assignee_ref). Active → Transferred; a new Active assignment is created. The task has a new responsible actor without a gap in coverage.
    • External completion. The composing system (Personal Todo, or the host task tracker) records the task as complete. The assignment record is not modified by the atom — completion is the task system’s event, not the assignment’s. The assignment remains Active in the atom’s records; the composing system decides whether to trigger a recall on completion or let the Active assignment stand as a historical record.
  4. Terminal. The assignment is in Recalled or Transferred. (End.)

Decision points

  • At assign(task_ref, assignee_ref)task_ref and assignee_ref must be well-formed and non-empty; otherwise invalid-request. There must be no Active assignment for task_ref in the system; otherwise already-assigned. The atom enforces the at-most-one invariant at this boundary. If the store write fails, the atom returns rejected(storage-failure); no assignment is created.
  • At recall(assignment_id)assignment_id must reference a known assignment; otherwise not-known. The referenced assignment must be in Active; otherwise not-active. If the store write fails, the atom returns rejected(storage-failure); the assignment remains Active.
  • At reassign(assignment_id, new_assignee_ref)assignment_id must reference a known assignment; otherwise not-known. The referenced assignment must be in Active; otherwise not-active. new_assignee_ref must be well-formed and non-empty; otherwise invalid-request. The transition is atomic: both the Transferred state of the old assignment and the Active state of the new assignment are committed together. If the transactional write fails at any point, the atom returns rejected(storage-failure) and both writes must be rolled back — the old assignment remains Active and no new assignment is created. A partial state where the old assignment is Transferred but no new Active assignment exists violates Invariant 7 and must not be observable.

Behavior

Observed behavior, derived from how work-distribution systems are actually used:

  • A task is either unassigned (no Active assignment) or assigned (exactly one Active assignment). There is no in-between, and there is no second assignment that could produce ambiguity about who is responsible.
  • Reassign is the preferred mechanism for handing off responsibility. It is atomic — there is no window between the old assignment’s Transferred state and the new assignment’s Active state where the task is unassigned. Composing systems that implement recall-then-assign introduce a gap during which the task has no responsible actor; reassign eliminates the gap.
  • The atom does not model whether the assignee has accepted responsibility. assign creates the binding immediately; whether the assignee is notified, whether they must acknowledge, whether they can decline — all are composing concerns. The base atom models the binding as unilateral: the assigner assigns, and the assignment is Active.
  • The atom does not model completion. When the task completes (in Personal Todo or the host task system), the assignment record is not automatically resolved. The composing system decides: leave the Active assignment as a record of who completed the task, or call recall to close the assignment record. Both are valid operational patterns; the atom supports either.
  • An assignee may be assigned multiple tasks simultaneously. The at-most-one invariant is per task, not per assignee. A single actor holding Active assignments on ten tasks is unremarkable; each task’s assignment is independent.
  • The full assignment history for any task is recoverable from the assignment store: all assignments (Active, Recalled, Transferred) where assignment.task_ref = task_ref, ordered by assigned_at. The chain of responsibility is complete.

Feedback

Each successful action produces an observable, measurable change:

  • After assign — a new assignment appears in Active with a fresh assignment_id, the supplied task_ref and assignee_ref, and assigned_at. Active count and total count each increase by one. The id is returned.
  • After recall — the assignment moves Active → Recalled with recalled_at. Active count decreases by one; Recalled count increases by one; total count unchanged.
  • After reassign — the old assignment moves Active → Transferred with transferred_at; a new assignment appears in Active with a fresh assignment_id and the new assignee_ref. Active count unchanged (one removed, one added); Transferred count increases by one; total count increases by one.

Each rejected action produces an observable refusal naming the failed precondition: invalid-request, already-assigned, not-known, not-active, or storage-failure.

The Active assignment set is queryable. The full assignment store (Active, Recalled, Transferred) is queryable for audit. Per-assignment fields are observable to operators and — where appropriate — to the assignee and assigner.

Invariants

The following hold across all valid sequences of actions and constitute the verification surface of the atom:

  • Invariant 1 — At most one Active assignment per task. At any time, no task_ref has more than one assignment in Active state.
  • Invariant 2 — Assignment immutability. Once recorded, an assignment’s assignment_id, task_ref, assignee_ref, and assigned_at never change.
  • Invariant 3 — Status monotonicity. An assignment’s status transitions only in one direction: Active → Recalled or Active → Transferred. No assignment returns from a terminal state to Active.
  • Invariant 4 — Terminal states are absorbing. Once an assignment is in Recalled or Transferred, no further transitions occur for that assignment_id.
  • Invariant 5 — Id stability. An assignment’s assignment_id is set on creation and never changes.
  • Invariant 6 — No id reuse. No two assignments share an assignment_id across the lifetime of the system.
  • Invariant 7 — Reassign atomicity. After a successful reassign, exactly one assignment is Active for the affected task_ref — the new one. The old assignment is in Transferred. There is no observable state in which both are Active, or in which neither is Active.
  • Invariant 8 — Timestamp ordering. For any assignment in Recalled state, assigned_at ≤ recalled_at. For any assignment in Transferred state, assigned_at ≤ transferred_at. Both are best-effort under non-monotonic clocks.
  • Invariant 9 — Complete responsibility history. For any task_ref, the set of all assignments where assignment.task_ref = task_ref records the complete chain of responsibility: every actor who held the assignment, when they received it, and when and how it ended.
  • Invariant 10 — Assignment store durability. Once recorded, an assignment is never deleted from the store. recall transitions an assignment from Active to Recalled; reassign transitions an assignment from Active to Transferred and creates a new Active assignment. Neither operation removes any record. The total assignment count is monotonically non-decreasing.

At-most-one-Active-per-task and reassign atomicity together give the unambiguous accountability property — at any moment, the question “who is responsible for this task?” has exactly one answer or no answer, never two answers. Assignment immutability, complete responsibility history, and assignment store durability together give the auditability property — the full chain of responsibility for any task is recoverable from the assignment store alone, and no record is ever silently removed.


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 calculations use assigned_at and transferred_at per 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 assignment 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).

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


Edge cases and explicit non-goals

What this atom does not cover:

  • Accept/decline workflow. The atom creates the binding immediately; whether the assignee must acknowledge or can decline belongs to a Workflow / Acceptance composing pattern. The base atom models assignment as unilateral.
  • Expiry-based auto-recall. The atom does not model time-bounded assignments that expire automatically. A deadline-based assignment composes with a Temporal Grant pattern that triggers recall at expiry.
  • Assigner authorization. The atom does not check whether the actor issuing assign or reassign is permitted to do so. That check belongs to Permissions composing with the assign action before it is called.
  • Assigner attribution. The atom does not record who issued the assignment. Assigner identity — which actor created this assignment — belongs to Actor Identity composing with assign (recording an attestation alongside the assignment record).
  • Capacity constraints. The atom does not limit how many Active assignments an assignee may hold simultaneously. A workload cap (e.g., no agent holds more than five tickets) belongs to a Capacity Constraint composing pattern that checks the assignee’s Active count before allowing assign.
  • Group or team assignment. The atom binds exactly one assignee_ref per Active assignment per task. Assigning to a team (where any team member may act) belongs to a Team Assignment composing pattern.
  • Task lifecycle. The atom does not know whether a task is open, closed, or deleted. assign accepts any well-formed task_ref; validating that the task exists and is in an assignable state is the composing system’s responsibility.
  • Completion handling. When a task is completed (in Personal Todo or the host system), the assignment is not automatically recalled. The composing system decides whether to recall the assignment on completion. Both patterns — leaving it Active as a completion-attribution record, or recalling it to close the assignment lifecycle — are valid.
  • Concurrent assign races. Two simultaneous assign calls for the same task_ref resolve serially under the host environment’s serialization guarantees. The first wins; the second receives already-assigned.
  • Reassign atomicity and crash semantics. reassign is specified as atomic. A crash between marking the old assignment Transferred and creating the new Active one would leave the task unassigned and Invariant 1 vacuously satisfied but Invariant 7 violated. The implementor is responsible for the transactional boundary that makes atomicity hold.
  • Reassign storage failure. A store-write failure during reassign is a two-write scenario: the old assignment must be marked Transferred and a new Active assignment must be created. If either write fails, the atom returns rejected(storage-failure) and both writes must be rolled back — the old assignment remains Active and no new assignment is created. A partial state where the old assignment is Transferred but no new Active assignment exists violates Invariant 7 (the task is unassigned after a reassign call that the caller may believe succeeded). Implementations that cannot provide full transactional rollback must detect and repair this partial state on recovery before accepting new requests.
  • Clock semantics. assigned_at, recalled_at, and transferred_at are wall-time from the implicit clock. Skew, monotonicity, and timezone handling are deployment concerns. Invariant 8 is best-effort under non-monotonic clocks.

Where the atom breaks down: when responsibility is genuinely shared simultaneously (requiring group assignment); when assignment must be time-bounded without external revocation (requiring temporal grant); when the assigner must be authorized before assigning (requiring permissions composition); when the assignee must consent (requiring workflow composition).


Composition notes

Assignment is freestanding and is designed as a direct prerequisite for the Shared Todo composition:

  • Personal Todo — the composing system wires Personal Todo’s task lifecycle with Assignment’s responsibility binding. When a task is added (add in Personal Todo), the composing system may immediately assign it. When a task is completed or deleted, the composing system decides whether to recall the assignment or leave it as a completion-attribution record.
  • Permissions — controls which actors may call assign, recall, and reassign. Without Permissions composition, the atom accepts any caller with a well-formed request. In a regulated context (e.g., only a supervisor may assign; only the current assignee may recall their own assignment), Permissions supplies the authorization check.
  • Actor Identity — records who issued the assignment. attest(assign_action_ref, assigner_ref, credential) alongside assign produces a non-repudiation record for each assignment creation and transfer.
  • Event Log — records the assignment lifecycle as a stream of events. Each assign, recall, and reassign appends an event; the event log is the time-ordered record of work-distribution decisions.
  • Shared TodoPersonal Todo + Permissions + Assignment. The composition that makes a single-user task list multi-actor: Permissions controls who can see and modify which tasks; Assignment controls who is responsible for which tasks.
  • Multi-Party Approval — uses Assignment to create an in-tray binding for each pending approval step, surfacing the approver’s obligation as an active assignment they must act on. The assignment is the work-queue mechanism; the approval decision is the terminal event that resolves it.
  • Workflow / Acceptance (forthcoming) — adds accept/decline to the assignment lifecycle. The assignment moves through Pending → Accepted Declined rather than becoming Active immediately on assign.
  • Capacity Constraint (forthcoming) — limits how many Active assignments an assignee may hold simultaneously. Checks active_assignments_for(assignee_ref).count < cap before allowing assign or reassign.
  • Temporal Grant (forthcoming) — wraps assignment with an expiry deadline, triggering recall at the deadline if the assignment has not already resolved.
  • Team Assignment (forthcoming) — assigns a task to a team rather than an individual; any team member may claim responsibility; the first to claim converts the team assignment to an individual Assignment.

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 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 concerns rather than absorbing them.
  • Eiffel’s design-by-contract — preconditions on assign, recall, reassign; named rejection reasons.

Status

grounded — 2026-05-20 — all required structural elements resolved; identity model explicit; assign, recall, and reassign preconditions explicit; rejection paths exercised in examples across four domains; deferred concerns (accept/decline, expiry, assigner authorization, assigner attribution, capacity constraints, group assignment, task lifecycle, completion handling, concurrent races, reassign atomicity, clock semantics) named as out-of-scope with composing patterns where applicable. Second entry in atoms/productivity/. Direct prerequisite for the Shared Todo composition.


Lineage notes

Assignment is drafted as the second prerequisite for the Shared Todo composition, following Permissions. Unlike the compliance atoms, Assignment carries no external regulatory acceptance bar, so the regulated-pattern conventions (Regulated adversarial scenarios, Generation acceptance) are not required.

Pass 1 — Structural completeness (GRID). Clean. All nine MUSE nodes populated. The reassign action is the structural novelty: it is simultaneously a state transition on an existing assignment (Active → Transferred) and the creation of a new assignment (Active). Both halves are captured in State (the Transferred terminal state and the new Active creation), Decision points (the atomicity requirement and the transactional boundary responsibility), and Invariants (Invariant 7 — reassign atomicity). The three-branch Flow (recall, reassign, external-completion-no-action) covers the full lifecycle including the deliberate design decision to leave completion handling to the composing system.

Pass 2 — Conceptual independence (EOS). Clean. Eight concerns were candidates for absorption and are all correctly named as composing patterns:

  • Accept/decline — whether the assignee must consent recurs across approval workflows in many domains. Belongs to a Workflow / Acceptance composing pattern.
  • Expiry-based auto-recall — time-bounded responsibility appears in on-call rotations, shift assignments, and SLA-governed queues. Belongs to Temporal Grant.
  • Assigner authorization — who may assign whom recurs across every delegation system. Belongs to Permissions.
  • Assigner attribution — who created this assignment recurs across every audited system. Belongs to Actor Identity.
  • Capacity constraints — workload caps recur across scheduling and queue-management systems. Belongs to Capacity Constraint.
  • Group/team assignment — assigning to a collective recurs across team-based work systems. Belongs to Team Assignment.
  • Task lifecycle validation — whether a task is assignable (open, not deleted) is the task system’s concern.
  • Completion handling — what happens to the assignment when the task completes is a composing-system policy, not the atom’s.

The strongest temptation was absorbing accept/decline — many real systems treat assignment as pending until the assignee acknowledges. Resisted: the base atom models unilateral assignment (assigning creates Active immediately); accept/decline is a workflow layer that some deployments need and others don’t. Keeping it out preserves the atom’s use in simple systems (project boards, sprint trackers) where acknowledgement is implicit.

Pass 3 — Adversarial scrutiny (Linus mode). Four findings, all closed in-pattern:

  • Reassign atomicity not specified. Early draft described reassign as two sequential operations (recall old, assign new) without naming the atomicity requirement or the crash-semantics responsibility. Resolved: Decision points explicitly requires atomic commit of both halves; Edge cases names the crash scenario (task unassigned, Invariant 7 violated) and places the transactional boundary responsibility on the implementor.
  • Completion handling left implicit. The draft was silent on what happens to an Active assignment when the task is completed in Personal Todo. This is a load-bearing operational decision. Resolved: Flow, Behavior, and Edge cases all name it explicitly — the atom makes no automatic transition on completion; the composing system decides whether to recall or leave the assignment Active as a completion-attribution record. Both are valid.
  • Invariant 8 clock-safety. Timestamp ordering (assigned_at ≤ recalled_at, assigned_at ≤ transferred_at) assumed a non-decreasing clock. Resolved: qualified as best-effort under non-monotonic clocks; clock semantics added to Edge cases.
  • Rejection-path examples absent. Initial examples covered only happy-path flows (assign, reassign, recall in smooth sequences). Resolved: fourth example added walking all four rejection reasons (invalid-request, already-assigned, not-known, not-active) in a single thread.

Refinement round 1. Five findings, all closed in-pattern. Conventions inherited from the methodology directly, not re-derived from predecessor atoms.

  • Action signatures used rejected(reason) placeholders. All three signatures named rejected(reason) with the actual reason taxonomy living only in Feedback prose. Resolved: all three signatures expanded — assign returns rejected(invalid-request | already-assigned | storage-failure), recall returns rejected(not-known | not-active | storage-failure), reassign returns rejected(not-known | not-active | invalid-request | storage-failure). Feedback updated to include storage-failure in the enumeration.
  • storage-failure missing from all three actions. All three actions write to the store; none named a store-write failure as a rejection reason. Resolved: storage-failure added to each signature and to Decision points, with the behavior specified for each: assign — no assignment created; recall — assignment remains Active; reassign — both writes rolled back.
  • reassign Decision point ambiguous on not-active vs. not-known. The phrasing “otherwise not-active or not-known” did not specify which condition produces which reason. Resolved: Decision point restructured as two sequential checks — not-known if the id is not in the store, not-active if it exists but is in a terminal state.
  • No durability invariant. Nine invariants; none stated that assignments are never deleted. Resolved: Invariant 10 — Assignment store durability — added: assignments are never deleted; recall transitions Active → Recalled; reassign transitions Active → Transferred and creates a new record; neither removes any record; total count monotonically non-decreasing. The auditability summary paragraph updated to name durability alongside immutability and complete history.
  • Reassign partial-write scenario not framed under storage-failure in Edge cases. The crash-semantics edge case named the transactional boundary but did not address the storage-failure path (where the atom returns a rejection rather than crashing). The two-write nature of reassign makes this the same structural situation as revoke in Permissions: a partial write leaves a Transferred assignment with no Active successor, violating Invariant 7. Resolved: new edge case — Reassign storage failure — added, requiring rollback of both writes on failure, with recovery guidance for implementations without full transactional support.

Scheduled rescan: 2026-05-20 — clean.


Grace Commons — open foundation for business logic patterns.

This site uses Just the Docs, a documentation theme for Jekyll.