⚠ Reconciled at canonical schema v1.0.0. This narrative was authored against a bespoke minimal schema (Agent D, PR #1) that is now superseded. Any claim below that a construct is “UNREPRESENTABLE in schema v1.0.0” or “pending-v1.1” refers to that bespoke schema, not the canonical one — the canonical schema expresses these constructs. The executable truth for this domain is the canonical scene fixture
conformance/case-studies/services/services.json, which validates + audits clean vianode conformance/runner/run.mjs. See case-studies/README.md for the reconciled status table and schema/BACKLOG-v1.1.md for the real findings.
Case Study: Services Commerce
Adversarial test corpus — executable. This is one of the domains the Commerce Model Formal Sufficiency Test claims to have passed. The JSON fixtures under
conformance/case-studies/services/validate against schema v1.0.0 and passauditCommerce. Run them:node conformance/audit.mjs conformance/case-studies/services.
Reference platforms: Calendly, Mindbody, Stripe Billing, FitStream, local salon booking apps.
Fixtures:
The domain and the hard cases it stresses
Services commerce differs from physical goods in three structural ways:
-
The value is consumed at the moment of production. A haircut cannot be warehoused. A fitness session cannot be returned. The service Value exists as a time-slot reservation before it is delivered, and transitions immediately to evidence of completion — there is no in-transit phase.
-
Non-attendance is a commerce event. When a client books a slot and does not arrive, the service Value is consumed (the slot is blocked, the performer is present) but the Commitment is not fulfilled. The provider’s contractual right to a no-show fee is a new forward Commitment — not a mutation of the original booking.
-
Subscriptions never reach
fulfilled. An ongoing subscription Commitment transitionsaccepted → activeand staysactiveindefinitely. Each billing period is a separateFulfillmentwith aperiod[start, end] pair. The Commitment closing is always an explicit cancellation or a dispute, never a natural fulfillment.
The hard cases this domain exercises:
- No-show fee path: The no-show Commitment is a child of the cancelled
booking Commitment. This verifies Invariant 6 (parent-child tree consistency)
and Invariant 2 (the booking reaches
cancelledcleanly while the fee Commitment runs its own forward lifecycle tofulfilled). - Recoverable payment failure and retry: A billing Fulfillment transitions
planned → in_progress → failed(recoverable:true) → planned → in_progress → completed. Thefailed → plannedarc is the only conditional transition in the model: it is only valid when the failure’srecoverableflag istrue. This is the executable expression of a grace period. ReservationBasis.time_slotandrecurring_time_slot: Service capacity is not physical stock. The model usestime_slot(a single appointment window) andrecurring_time_slot(multiple future billing windows for a subscription) to show that the capacity being reserved is the performer’s time, not a unit of inventory.
Fixture 1 — Appointment with no-show policy
File: appointment-no-show.json
Scenario
Two clients book hair appointments at Salon Zaha in Casablanca on the same day:
- Salma attends her 10:00–11:00 appointment. Service is performed, payment
is collected at the chair. Commitment reaches
fulfilled. - Tariq books a 14:00–15:00 slot but does not arrive. After the 15-minute
grace period (a
NoShowPolicyterm in the booking conditions), the salon cancels the appointment Commitment and immediately raises a no-show fee Commitment for MAD 50.
NoShowPolicy — prose, not a schema field
NoShowPolicy is a prose-level term from the Commerce Model spec (v0.3). It
describes terms attached to a Commitment: a grace window, a fee amount, and
what happens if the client does not appear within the window. In the model it
manifests as:
- The booking Commitment’s
reasonfield atacceptedrecords the policy terms (grace_minutes=15, fee=MAD 50). - When the no-show occurs, the booking Commitment transitions
accepted → cancelled(by the salon, with reason). - A new child Commitment (
CMT-NSFEE-1) is created with the salon asinitiatorand the client ascounterparty. It runs its own lifecycle tofulfilledvia amoney_transferFulfillment.
There is no NoShowPolicy field in the schema. The policy’s mechanical
consequence — the fee Commitment — is fully representable in the base
primitives. This is recorded as extensions_exercised: ["NoShowPolicy"]
to flag that the prose term is exercised here even though no new schema
keyword is required.
Value state: reserved with time_slot
{
"id": "val_haircut_session_salma",
"form": { "type": "service", "service_type": "hair_styling", "duration_minutes": 60 },
"state": {
"reserved": {
"commitment_id": "CMT-APPT-1",
"basis": {
"time_slot": {
"slot_start": "2026-06-12T10:00:00+00:00",
"slot_end": "2026-06-12T11:00:00+00:00",
"capacity_unit": "stylist_chair"
}
}
}
}
}
capacity_unit: "stylist_chair" distinguishes service capacity from physical
stock (Invariant 3: capacity reservation is explicit and verifiable).
Lifecycle
Intent INT-BOOK-1: active → converted(CMT-APPT-1)
Intent INT-BOOK-2: active → converted(CMT-APPT-2)
Commitment CMT-APPT-1 (Salma — attended):
draft → proposed → accepted
→ partially_fulfilled(service done, payment pending)
→ fulfilled
Fulfillment F-SERVICE-1 (service_performance): planned → in_progress → completed
Fulfillment F-PAY-APPT-1 (money_transfer): planned → in_progress → completed
Commitment CMT-APPT-2 (Tariq — no-show):
draft → proposed → accepted → cancelled(by salon, NoShowPolicy)
[children: CMT-NSFEE-1]
Commitment CMT-NSFEE-1 (no-show fee — child of CMT-APPT-2):
draft → proposed → accepted
→ partially_fulfilled(fee pending)
→ fulfilled
Fulfillment F-NSFEE-1 (money_transfer): planned → in_progress → completed
Why the appointment Commitment goes through partially_fulfilled
A service appointment delivers two values: the service itself and the payment.
These are Fulfillments in sequence. The model requires accepted → partially_fulfilled (service performed, payment outstanding) before
partially_fulfilled → fulfilled (payment received). There is no
accepted → fulfilled shortcut — that transition is not in the valid table.
Evidence
The attended appointment uses service_completion evidence (Invariant 2:
completion evidence is part of the Fulfillment record):
{
"service_completion": {
"confirmed_by": "party_salon_zaha",
"timestamp": "2026-06-12T11:05:00+00:00",
"duration_minutes": 63,
"notes": "cut and blow-dry; client satisfied"
}
}
Fixture 2 — Subscription with failed payment grace period
File: subscription-failed-payment-retry.json
Scenario
Nadia subscribes to FitStream Premium (MAD 99/month). Month 1 and Month 3 bill
cleanly. Month 2 (due 2026-05-01) fails with card_declined: insufficient_funds. The platform honours a 7-day grace period and retries on
2026-05-08; the retry succeeds. The subscription Commitment remains active
throughout — it never pauses or cancels due to the transient failure.
GracePeriod — prose, not a schema field
GracePeriod is a prose-level term: a window after a billing failure during
which the subscription remains active and the payment is retried. Its
mechanical expression in the model is the recoverable:true flag on the
failed Fulfillment state, which unlocks the otherwise-forbidden
failed → planned retry transition. The 7-day window itself is business
logic in the platform, not a schema constraint.
Subscription stays active — it never reaches fulfilled
Commitment CMT-SUB-1:
draft → proposed → accepted → active
(stays active; each month is a Fulfillment)
The accepted → active transition requires the Commitment to be a subscription
or ongoing service type (per the model’s transition table). active is the
correct terminal state for a live subscription. The Commitment only leaves
active via cancelled (cancellation), modified (plan change), or
disputed.
RecurringTimeSlot — capacity reservation across billing windows
{
"id": "val_sub_access",
"state": {
"reserved": {
"commitment_id": "CMT-SUB-1",
"basis": {
"recurring_time_slot": {
"slots": [
["2026-04-01T00:00:00+00:00", "2026-05-01T00:00:00+00:00"],
["2026-05-01T00:00:00+00:00", "2026-06-01T00:00:00+00:00"],
["2026-06-01T00:00:00+00:00", "2026-07-01T00:00:00+00:00"]
]
}
}
}
}
}
recurring_time_slot signals that the reserved capacity spans multiple
future periods. Each slot corresponds to one billing Fulfillment. This makes
the capacity reservation explicit and machine-verifiable (Invariant 3).
The failed-payment retry — the conditional transition
The Month 2 billing Fulfillment (F-BILL-M2) is the key state machine:
planned → in_progress → failed(reason:"card_declined", recoverable:true)
→ planned [RETRY — only valid because recoverable:true]
→ in_progress → completed
The failed → planned arc is the only transition in the entire model that
is conditionally valid. The auditor enforces: if a Fulfillment history shows
failed → planned, the failed state’s recoverable flag MUST be true.
A terminal failure (recoverable:false) cannot retry — it can only be
superseded by the platform creating a new Fulfillment.
{
"from": "in_progress",
"to": { "failed": { "reason": "card_declined: insufficient_funds", "recoverable": true } },
"at": "2026-05-01T08:02:00+00:00",
"actor": "party_fitstream"
},
{
"from": { "failed": { "reason": "card_declined: insufficient_funds", "recoverable": true } },
"to": "planned",
"at": "2026-05-01T08:03:00+00:00",
"actor": "party_fitstream"
}
Period field on billing Fulfillments
Each billing Fulfillment carries a period array to identify which calendar
month it covers:
"period": ["2026-05-01T00:00:00+00:00", "2026-06-01T00:00:00+00:00"]
This is the model’s Option<DateRange> field — available on any Fulfillment
but mandatory for subscription billing periods where you need to know which
month a payment covers.
Invariants exercised
| Invariant | Fixture | How exercised |
|---|---|---|
| I-2 State Monotonicity | Both | Appointment: partially_fulfilled path; no-show booking reaches cancelled (forward, not backward). Subscription: active Commitment stays active; retry arc validated as conditional. |
| I-3 Capacity Verification | Both | time_slot and recurring_time_slot on Value make capacity reservation explicit. All parties reaching Accepted carry verified_at. |
| I-4 Temporal Integrity | Both | All history timestamps non-decreasing; retry Fulfillment timestamps across the 7-day gap are monotonic. |
| I-5 Identity Permanence | Both | All ids across parties, values, intents, commitments, fulfillments are unique within each fixture. |
| I-6 Commitment Tree Consistency | Fixture 1 | CMT-NSFEE-1.parent = CMT-APPT-2; CMT-APPT-2.children = ["CMT-NSFEE-1"]. Auditor verifies bidirectional linkage. |
| I-1 Value Conservation | Both | Every Value’s commitment_id resolves; every Fulfillment’s commitment field resolves. |
Extensions exercised
| Extension | Fixture | Representation |
|---|---|---|
| NoShowPolicy | Fixture 1 | Prose term in booking conditions. Mechanically expressed as: CMT-APPT-2 cancelled (accepted→cancelled), then child CMT-NSFEE-1 Commitment for the fee. No schema field required. |
| GracePeriod | Fixture 2 | Prose term. Mechanically expressed as recoverable:true on the failed billing Fulfillment, enabling the failed→planned retry transition. |
| ServicePerformance | Both | FulfillmentMethod.service_performance with performer, location, and scheduled_at fields. Evidence.service_completion with confirmed_by, timestamp, duration_minutes, notes. |
| RecurringTimeSlot | Fixture 2 | ReservationBasis.recurring_time_slot with three [start, end] slot pairs. |
FINDINGS
FINDING-SVC-1 (gap, minor): The service_completion Evidence type has no
no_show flag or equivalent. When a slot is blocked and the client does not
appear, the Fulfillment for the booking simply does not exist — the
appointment Commitment is cancelled rather than fulfilled, so there is no
Fulfillment to attach evidence to. This is correct model behaviour (a
cancelled Commitment has no Fulfillment records), but it means the no-show
event is only recorded in the Commitment history’s reason string, not as
structured evidence. A future extension could add NoShowRecord to the
Evidence set for audit purposes. Not a schema change needed for correct
modelling today.
FINDING-SVC-2 (observation): Subscription access delivery (the token grant
that enables platform access) is a distinct Fulfillment (F-ACCESS-GRANT-1)
with digital_delivery method and access_grant evidence. This is separate
from the recurring billing Fulfillments. The separation is correct — access
provisioning happens once at subscription start, billing happens monthly — but
producers should be aware that a subscription Commitment may have N+1
Fulfillments: one access grant and N billing periods.
Run it
node conformance/audit.mjs conformance/case-studies/services
# ✓ conformance/case-studies/services/appointment-no-show.json
# ✓ conformance/case-studies/services/subscription-failed-payment-retry.json
# auditCommerce: 2 passed, 0 failed, 0 warnings, 2 fixtures