[Traits](https://oods-foundry.com/traits) / Archivable

# Archivable

Provides archival lifecycle management with configurable retention, restoration, audit tracking, and compliance metadata. Archivable manages the full archive-restore-purge lifecycle, including soft-delete with configurable duration before hard deletion, partial restoration support, and per-archive compliance tagging. Objects composing Archivable can be soft-deleted (moved to an "archived" state with full data retention), restored within a configurable window, or permanently purged after the retention period expires. The trait tracks who archived the entity, why, and through what method (manual, automated, policy) for audit trail completeness.

Generated from @oods/foundry 0.10.1

- Group

  lifecycle

- Maturity

  stable

- Contexts

  card, detail, list

## Fields it adds

| Field | Type | Required | Description |
| - | - | - | - |
| `is_archived` | `boolean` | yes | Flag indicating whether the entity is currently archived (soft-deleted). |
| `archived_at` | `datetime?` | no | Timestamp for when the entity entered the archived state. |
| `restored_at` | `datetime?` | no | Timestamp for when the entity was most recently restored from an archived state. |
| `archive_reason` | `string` | no | Human-readable narrative describing why the entity was archived. |
| `archived_by` | `string` | no | User ID or system identifier of the actor who archived this entity. Set to "system" for automated/policy-driven archival. Supports audit trail queries like "show all entities archived by user X" or "show all auto-archived entities". |
| `archive_metadata` | `object` | no | Structured metadata about the archival action for compliance and audit purposes. Properties: - method: "manual" \| "automated" \| "policy" — how the archive was triggered - compliance_tags: string\[] — regulatory labels (e.g., \["GDPR", "SOX", "HIPAA"]) - retention_policy_id: string — reference to the retention policy that triggered archival - original_status: string — the Stateful status before archival - related_entity_count: number — count of related entities also archived (cascade) |
| `restoration_metadata` | `object` | no | Metadata about the most recent restoration action. Properties: - restored_by: string — user ID or "system" - restored_fields: string\[] — when partial restore, which fields were restored - restoration_reason: string — why the entity was restored - restored_from_snapshot: boolean — whether restored from a point-in-time snapshot |

## What it shows in each context

- [list](https://oods-foundry.com/contexts/list)

  `ArchivedRowOverlay`

- [detail](https://oods-foundry.com/contexts/detail)

  `ArchiveSummary`

- [card](https://oods-foundry.com/contexts/card)

  `ArchivePill`

## Parameters

- `gracePeriodDays` `number`

  Number of days after archival before permanent deletion (hard-delete) occurs.

- `retainHistory` `boolean`

  Whether archival retains a readable change history that can be restored.

- `restoreWindowDays` `number`

  Maximum number of days archived entities can be restored when history is retained. After this window, restoration is no longer possible even if the entity hasn't been hard-deleted yet.

- `softDeleteDuration` `number`

  Days the entity remains in soft-deleted state before automatic hard deletion. Distinct from gracePeriodDays: gracePeriodDays is the notification/warning window, while softDeleteDuration is the total retention period. When set, a background process purges entities where (archived_at + softDeleteDuration) has passed.

- `allowPartialRestore` `boolean`

  When true, archived entities can be partially restored — selecting which fields or related data to bring back rather than restoring the entire entity. Useful for large objects where only specific sections need to be recovered (e.g., restoring a contract's terms but not its expired billing data).

## Objects that use it

- [Subscription](https://oods-foundry.com/objects/subscription)
- [Transaction](https://oods-foundry.com/objects/transaction)

## The trait file

`traits/lifecycle/Archivable.trait.yaml`

```
trait:
  name: Archivable
  version: 2.0.0
  description: |
    Provides archival lifecycle management with configurable retention, restoration, audit
    tracking, and compliance metadata. Archivable manages the full archive-restore-purge
    lifecycle, including soft-delete with configurable duration before hard deletion,
    partial restoration support, and per-archive compliance tagging.

    Objects composing Archivable can be soft-deleted (moved to an "archived" state with
    full data retention), restored within a configurable window, or permanently purged
    after the retention period expires. The trait tracks who archived the entity, why,
    and through what method (manual, automated, policy) for audit trail completeness.
  category: lifecycle
  tags:
    - lifecycle
    - archival
    - retention
    - compliance
    - audit
    - soft-delete

parameters:
  - name: gracePeriodDays
    type: number
    required: false
    description: Number of days after archival before permanent deletion (hard-delete) occurs.
    default: 30
    validation:
      minimum: 0
      maximum: 365

  - name: retainHistory
    type: boolean
    required: false
    description: Whether archival retains a readable change history that can be restored.
    default: true

  - name: restoreWindowDays
    type: number
    required: false
    description: |
      Maximum number of days archived entities can be restored when history is retained.
      After this window, restoration is no longer possible even if the entity hasn't been
      hard-deleted yet.
    validation:
      minimum: 0
      maximum: 365

  - name: softDeleteDuration
    type: number
    required: false
    description: |
      Days the entity remains in soft-deleted state before automatic hard deletion.
      Distinct from gracePeriodDays: gracePeriodDays is the notification/warning window,
      while softDeleteDuration is the total retention period. When set, a background
      process purges entities where (archived_at + softDeleteDuration) has passed.
    default: 90
    validation:
      minimum: 0
      maximum: 3650

  - name: allowPartialRestore
    type: boolean
    required: false
    description: |
      When true, archived entities can be partially restored — selecting which fields
      or related data to bring back rather than restoring the entire entity. Useful for
      large objects where only specific sections need to be recovered (e.g., restoring
      a contract's terms but not its expired billing data).
    default: false

schema:
  is_archived:
    type: boolean
    required: true
    description: Flag indicating whether the entity is currently archived (soft-deleted).
    default: false

  archived_at:
    type: datetime?
    required: false
    description: Timestamp for when the entity entered the archived state.

  restored_at:
    type: datetime?
    required: false
    description: Timestamp for when the entity was most recently restored from an archived state.

  archive_reason:
    type: string
    required: false
    description: Human-readable narrative describing why the entity was archived.
    validation:
      maxLength: 500

  archived_by:
    type: string
    required: false
    description: |
      User ID or system identifier of the actor who archived this entity.
      Set to "system" for automated/policy-driven archival. Supports audit trail queries
      like "show all entities archived by user X" or "show all auto-archived entities".

  archive_metadata:
    type: object
    required: false
    description: |
      Structured metadata about the archival action for compliance and audit purposes.

      Properties:
        - method: "manual" | "automated" | "policy" — how the archive was triggered
        - compliance_tags: string[] — regulatory labels (e.g., ["GDPR", "SOX", "HIPAA"])
        - retention_policy_id: string — reference to the retention policy that triggered archival
        - original_status: string — the Stateful status before archival
        - related_entity_count: number — count of related entities also archived (cascade)

  restoration_metadata:
    type: object
    required: false
    description: |
      Metadata about the most recent restoration action.

      Properties:
        - restored_by: string — user ID or "system"
        - restored_fields: string[] — when partial restore, which fields were restored
        - restoration_reason: string — why the entity was restored
        - restored_from_snapshot: boolean — whether restored from a point-in-time snapshot

semantics:
  is_archived:
    semantic_type: status.archive.state
    token_mapping: tokenMap(status.archive.*)
    ui_hints:
      component: ArchiveBadge
      showIcon: true
  archived_at:
    semantic_type: status.archive.timestamp
    token_mapping: tokenMap(status.archive.timestamp)
    ui_hints:
      component: TimelineTimestamp
  restored_at:
    semantic_type: status.archive.restore_timestamp
    token_mapping: tokenMap(status.archive.restore)
    ui_hints:
      component: TimelineTimestamp
  archive_reason:
    semantic_type: status.archive.reason
    token_mapping: tokenMap(status.archive.reason)
    ui_hints:
      component: ArchiveReason
      maxLength: 500
  archived_by:
    semantic_type: status.archive.actor
    token_mapping: tokenMap(status.archive.actor)
    ui_hints:
      component: ActorLabel
  archive_metadata:
    semantic_type: status.archive.compliance
    token_mapping: computed
    ui_hints:
      component: ArchiveCompliancePanel
  restoration_metadata:
    semantic_type: status.archive.restoration
    token_mapping: computed
    ui_hints:
      component: RestorationSummary

view_extensions:
  list:
    - component: ArchivedRowOverlay
      props:
        archivedField: is_archived
        style: grayed
        showBadge: true
        separateTab: true
        tabLabel: Archived
  # s224-m01 (#2542 ruling 6): a record that is not archived shows no card; "Archived: No" only states the default.
  detail:
    - component: ArchiveSummary
      position: top
      props:
        hideWhenDefault: true
        archivedField: is_archived
        archivedAtField: archived_at
        restoredAtField: restored_at
        reasonField: archive_reason
        archivedByField: archived_by
        metadataField: archive_metadata
        retainHistoryParameter: retainHistory
        restoreWindowParameter: restoreWindowDays
        allowPartialRestoreParameter: allowPartialRestore
  # s222-m03: no timeline recipe. A timeline's rail lists the archive and restore (recordCollectionEvents).
  # s223-m01 (#2527 ruling 7): an archived record shows it on its card; "Not archived" only states the default.
  card:
    - component: ArchivePill
      position: before
      props:
        field: is_archived
        archivedAtField: archived_at
        hideWhenFalse: true

tokens:
  status.archive.primary.bg: "var(--status-archive-bg)"
  status.archive.primary.text: "var(--status-archive-text)"
  status.archive.primary.border: "var(--sys-border-default)"
  status.archive.timestamp.text: "var(--text-subtle)"
  status.archive.reason.text: "var(--text-default)"
  status.archive.reason.bg: "var(--sys-surface-neutral)"
  status.archive.overlay.bg: "var(--sys-surface-disabled)"
  status.archive.overlay.opacity: "0.6"
  status.archive.actor.text: "var(--sys-text-secondary)"
  status.archive.compliance.badge.bg: "var(--sys-status-info-surface)"
  status.archive.compliance.badge.text: "var(--sys-status-info-text)"

dependencies:
  - Stateful

metadata:
  created: "2025-10-12"
  updated: "2026-02-28"
  owners:
    - lifecycle@oods.systems
    - compliance@oods.systems
  maturity: stable
  accessibility:
    keyboard: |
      Archive/Restore actions are triggered via buttons that are keyboard-focusable and
      activated with Enter/Space. Confirmation dialogs trap focus until dismissed.
    screenreader: |
      ArchiveBadge announces "Archived" or "Active" state. ArchiveSummary announces
      archive date, reason, actor, and remaining restore window. ArchivedRowOverlay
      applies aria-hidden=false to ensure archived rows remain accessible but are
      announced as "Archived: [entity name]" to screen readers.
    listPresentation: |
      Archived items in list view are visually dimmed (opacity: 0.6) and optionally
      moved to a separate "Archived" tab. Both presentations maintain keyboard
      navigability and screen reader access.
  regionsUsed:
    - list
    - detail
    - card
  examples:
    - Contract
    - Product Listing
    - User Account
    - Subscription
  references:
    - "Trait Engine Spec v0.1 section 2"
    - "GDPR Article 17 — Right to Erasure (soft-delete as intermediate step)"
```
