Traits / Cancellable
Cancellable
Adds lifecycle-aware cancellation workflows with policy guardrails and semantic token mappings.
Generated from @oods/foundry 0.10.1
- Group
- lifecycle
- Maturity
- stable
- Contexts
- card, detail, form
Fields it adds
| Field | Type | Required | Description |
|---|---|---|---|
cancel_at_period_end | boolean | yes | Whether cancellation occurs at the natural period end instead of immediately. When true the entity is in a REVERSIBLE pending-cancellation state: the schedule can be undone (set back to false) any time before period end, returning the entity to active. This is distinct from a terminal cancellation (a `terminated`/canceled subscription), which is irreversible and non-reactivatable. Mirrors Stripe's cancel_at_period_end flag (docs.stripe.com/billing/subscriptions/cancel). |
cancellation_reason | string | no | Free-form detail describing why cancellation occurred. |
cancellation_reason_code | string | no | Structured reason code chosen from the allowedReasons parameter. |
cancellation_requested_at | datetime | no | Timestamp capturing when the cancellation workflow was initiated. |
What it shows in each context
Parameters
allowCancellationAfterStartboolean- Whether cancellation may occur after the entity transitions to an active state.
cancellationWindowHoursnumber- Number of hours after activation where cancellation is permitted.
requireReasonboolean- Whether a structured cancellation reason must be supplied by the actor.
allowedReasonsstring[]- Allow list of valid cancellation reason codes when requireReason is true.
Objects that use it
The trait file
traits/lifecycle/Cancellable.trait.yaml
trait:
name: Cancellable
version: 1.0.0
description: |
Adds lifecycle-aware cancellation workflows with policy guardrails and semantic token mappings.
category: lifecycle
tags:
- lifecycle
- cancellation
- retention
- workflow
parameters:
- name: allowCancellationAfterStart
type: boolean
required: false
description: Whether cancellation may occur after the entity transitions to an active state.
default: true
- name: cancellationWindowHours
type: number
required: false
description: Number of hours after activation where cancellation is permitted.
validation:
minimum: 0
maximum: 720
- name: requireReason
type: boolean
required: false
description: Whether a structured cancellation reason must be supplied by the actor.
default: false
- name: allowedReasons
type: string[]
required: false
description: Allow list of valid cancellation reason codes when requireReason is true.
validation:
minItems: 1
uniqueItems: true
items:
pattern: "^[a-z0-9_\\-]+$"
minLength: 1
maxLength: 64
schema:
cancel_at_period_end:
type: boolean
required: true
description: >-
Whether cancellation occurs at the natural period end instead of immediately.
When true the entity is in a REVERSIBLE pending-cancellation state: the schedule
can be undone (set back to false) any time before period end, returning the entity
to active. This is distinct from a terminal cancellation (a `terminated`/canceled
subscription), which is irreversible and non-reactivatable. Mirrors Stripe's
cancel_at_period_end flag (docs.stripe.com/billing/subscriptions/cancel).
default: false
cancellation_reason:
type: string
required: false
description: Free-form detail describing why cancellation occurred.
validation:
maxLength: 160
cancellation_reason_code:
type: string
required: false
description: Structured reason code chosen from the allowedReasons parameter.
validation:
enumFromParameter: allowedReasons
cancellation_requested_at:
type: datetime
required: false
description: Timestamp capturing when the cancellation workflow was initiated.
semantics:
cancel_at_period_end:
semantic_type: status.cancel.deferred
token_mapping: tokenMap(status.cancel.*)
ui_hints:
component: CancellationToggle
emphasizeWhenTrue: true
cancellation_reason:
semantic_type: status.cancel.reason
token_mapping: tokenMap(status.cancel.reason)
ui_hints:
component: CancellationReason
multiline: true
maxLength: 160
cancellation_reason_code:
semantic_type: status.cancel.reason_code
token_mapping: tokenMap(status.cancel.code.*)
ui_hints:
component: ReasonPicker
parameterSource: allowedReasons
cancellation_requested_at:
semantic_type: status.cancel.timestamp
token_mapping: tokenMap(status.cancel.timestamp)
ui_hints:
component: TimelineTimestamp
view_extensions:
# s224-m01 (#2542 ruling 6): a record with no cancellation scheduled or recorded shows no card; "Cancel at period end:
# No" only states the default.
detail:
- component: CancellationSummary
position: top
props:
hideWhenDefault: true
cancelAtPeriodEndField: cancel_at_period_end
reasonField: cancellation_reason
codeField: cancellation_reason_code
requestedAtField: cancellation_requested_at
# s222-m03: no timeline recipe. A timeline's rail lists the cancellation request (recordCollectionEvents).
form:
- component: CancellationForm
position: top
props:
reasonField: cancellation_reason
codeField: cancellation_reason_code
requireReasonParameter: requireReason
allowedReasonsParameter: allowedReasons
windowParameter: cancellationWindowHours
# s223-m01 (#2527 ruling 7): a scheduled cancellation shows on the card; "No cancellation scheduled" only states the default.
card:
- component: CancellationBadge
position: before
props:
field: cancel_at_period_end
hideWhenFalse: true
tokens:
status.cancel.primary.bg: "var(--status-cancel-bg)"
status.cancel.primary.text: "var(--status-cancel-text)"
status.cancel.reason.text: "var(--text-default)"
status.cancel.timestamp.text: "var(--text-subtle)"
dependencies:
- Stateful
metadata:
created: "2025-10-12"
owners:
- lifecycle@oods.systems
- support@oods.systems
maturity: stable
accessibility:
keyboard: "Supports toggling of cancellation switches."
screenreader: "Announces cancellation state and reason."
regionsUsed:
- detail
- form
- card
examples:
- Subscription
- Reservation
references:
- "Trait Engine Spec v0.1 §2"