Skip to content

The platform, feature by feature

Four strategies. A whole platform underneath.

A strategy is the unit you switch on — declare it, and a real, tenant-isolated backend comes up. But the strategies are the surface. Under them sits the type system, the durable workflow engine, the presets, and the blocks that make each one real. Here is everything, exactly as it ships.

4 strategies · 8 field types · 7 workflows · 29 presets · 14 roles · one kernel

Strategies · 4

Declare one. Get a real backend.

Four strategies ship today — booking, catalog, e-commerce, and forms. Each one, declared once, stands up its own API and console surfaces. On the left is the manifest you write; on the right, the live console it produces.

01Take bookingse.g. Dental & physio clinics

Booking

Appointments that can't double-book.

Reservable resources, real availability in the site's own timezone, deposits at booking. Exclusivity is enforced in Postgres, not hoped for in app code.

CalendarResource gridReschedule / cancelNo-show handling
The manifest you declare for the Booking strategy.
booking.strategy
strategy:booking
resource:practitioner// what gets reserved
granularity:30m
horizon:60d
timezone:from_location// DST-correct per site
buffers:{ before: 0m, after: 10m }
exclusivity:postgres_gist// double-booking is impossible
deposit:25%// captured at booking
notify:
confirm
remind @ -24h
remind @ -2h
console · bookingslive
Tue 24 AugAmerica/Jamaica
09:00
Available
09:30
Ava Chen · Cleaningdeposit $30
10:00
Available
10:30
Held · 4:59

exclusivity: postgres gist · double-booking impossible

API it exposes

  • GET/booking/availability
  • POST/booking/appointments
  • POST/booking/appointments/:id/reschedule

By end of day

A working scheduler your front desk can use before lunch.

02Sell thingse.g. Makers & small retail

Storefront

A catalog and checkout, tenant-isolated.

Products with variants, a cart, and checkout that runs through the built-in payments module. Every price, order and customer stays inside the tenant boundary.

Catalog editorOrdersCustomersPayments ledger
The manifest you declare for the Storefront strategy.
storefront.strategy
strategy:storefront
catalog:products
variants:[size, plan]
pricing:per_variant// tax inclusive
checkout:payments.intent// idempotent
fulfilment:manual | shipping
inventory:tracked
console · orderslive
Recent orderstoday
#10422 items$84.00paid
#10411 item$19.00paid
#10403 items$126.00refunded

payments: idempotent intents · a retry never charges twice

API it exposes

  • GET/storefront/products
  • POST/storefront/carts/:id/checkout
  • POST/payments/intents

By end of day

A storefront taking real payments by end of day.

03Charge monthlye.g. SaaS & memberships

Subscriptions

Recurring billing with real dunning.

Plans, trials, proration and a billing cadence that retries failed charges and pauses politely. The subscription engine is part of the kernel, not a plugin you wire up.

PlansSubscribersInvoicesDunning queue
The manifest you declare for the Subscriptions strategy.
subscriptions.strategy
strategy:subscriptions
plans:
starter · $19 / mo
pro · $49 / mo
clinic · $199 / mo
cadence:monthly | annual
trial:14d
proration:true
dunning:retry(3) -> pause// no silent churn
console · subscriberslive
SubscribersMRR $267
Ava ChenPro$49/moactive
Sam LeeStarter$19/moretry 2/3
Kai ReidClinic$199/moactive

dunning: retry(3) then pause · churn you can see coming

API it exposes

  • POST/payments/subscriptions
  • POST/payments/subscriptions/:id/change-plan
  • GET/payments/invoices

By end of day

Recurring revenue live, with churn you can see coming.

04Collect intakee.g. Home services & clinics

Forms

Intake that becomes a record, then acts.

A form whose submission creates a record, stores an uploaded file, and kicks off a workflow. Configuration, not a form builder bolted onto a spreadsheet.

Form editorSubmissionsFile attachmentsRouting rules
The manifest you declare for the Forms strategy.
forms.strategy
strategy:forms
form:intake
fields:[reason, history, consent]
upload:consent.pdf -> storage// signed URLs
on_submit:
create records.clients
start workflow.triage
console · submissionslive
Intake · Ava Chen2m ago

reason: First visit, cleaning

consent:consent.pdf

created client triage running

on submit: create record → start workflow → store file

API it exposes

  • GET/forms/:slug
  • POST/forms/:slug/submissions
  • POST/storage/uploads

By end of day

Intake that files itself and starts the next step.

Data & records· 8

A type system that does the hard part for you.

Records are typed collections that read and write across tiers atomically — no N+1 hydration. On top of the primitives, eight field types carry their own guarantees, most enforced in Postgres itself — plus timeseries, a whole-collection TimescaleDB hypertable variant. Each one below: how you declare it, an example value, and the query it unlocks.

console · clientslive
Ava Chenclient
phone
••• ••• 4471staff+ only
email
ava@example.com
tags
regularnewsletter
3 appointments·2 invoices

row-level security forced · records never cross tenants

Column policy rides with the type: this phone number is masked server-side for anyone below manager, on every surface.

money

field type

An integer amount in minor units plus an ISO-4217 currency code; ordered comparisons across differing currencies are rejected rather than silently compared.

money.yaml
# field type: money
fields:
invoice_total:
type: money
required: true
allowed_currencies: [USD, EUR]
Example value
{ "amount": 4999, "currency": "USD" }
Query
{ "invoice_total": { "$gte": { "amount": 5000, "currency": "USD" } } }

decimal

field type

A number canonicalized to a fixed-scale string at write time (via precision/scale), so equality and ordering are exact numeric comparisons, never float rounding or text sorting.

decimal.yaml
# field type: decimal
fields:
hours_logged:
type: decimal
precision: 8
scale: 2
Example value
"6.50"
Query
{ "hours_logged": { "$gte": 6.5 } }

range

field type

A bounded { start, end } time span stored in a GiST-indexed extension table for overlap queries; range_subtype declares four options but only datetime and date are backed by storage.

range.yaml
# field type: range
fields:
shift_window:
type: range
range_subtype: datetime
Example value
{ "start": "2026-09-01T09:00:00Z", "end": "2026-09-01T17:00:00Z" }
Query
{ "shift_window": { "$overlaps": { "start": "2026-09-01T12:00:00Z", "end": "2026-09-01T13:00:00Z" } } }

vector

field type

A fixed-length float embedding stored in a dimension-tiered, HNSW-indexed pgvector table for approximate nearest-neighbor search; this build accepts only the cosine metric of the three declared.

vector.yaml
# field type: vector
fields:
embedding:
type: vector
dimensions: 1536
distance_metric: cosine
Example value
[0.0192, -0.0341, 0.1157, -0.0088, 0.0623]
Query
{ "embedding": { "$nearest": { "vector": [0.0201, -0.0299, 0.1088, -0.0102, 0.0587] } } }

geometry

field type

A GeoJSON point/polygon/linestring stored in a GiST-indexed PostGIS column, SRID-tagged (default 4326), for spatial containment and radius queries.

geometry.yaml
# field type: geometry
fields:
site_location:
type: geometry
geometry_type: point
srid: 4326
Example value
{ "lat": 40.7128, "lng": -74.006 }
Query
{ "site_location": { "$within_radius": { "center": { "lat": 40.7128, "lng": -74.006 }, "radius_meters": 5000 } } }

interval

field type

A recurrence pattern stored as a validated RFC 5545 RRULE string (must contain FREQ=); no numeric/temporal cast exists for it, so it is compared as plain text.

interval.yaml
# field type: interval
fields:
maintenance_schedule:
type: interval
interval_allowed: [weekly, monthly, custom]
Example value
"FREQ=WEEKLY;BYDAY=MO,WE,FR"
Query
{ "maintenance_schedule": { "$eq": "FREQ=WEEKLY;BYDAY=MO,WE,FR" } }

multi_language

field type

A { languageTag: string } translation map; reads collapse it to one best-matching string for the requested language, falling back to base_language and then to any present translation.

multi_language.yaml
# field type: multi_language
fields:
description:
type: multi_language
base_language: en
supported_languages: [en, es, fr]
Example value
{ "en": "Quarterly safety inspection", "es": "Inspección de seguridad trimestral", "fr": "Inspection de sécurité trimestrielle" }
Query
GET /api/collections/tasks/records?lang=es
Accept-Language: fr-CA, fr;q=0.9, en;q=0.5

computed

field type

Declares a derived value via an expression string plus a persist flag; direct writes are unconditionally rejected (§8.6.8), and no expression evaluator exists yet anywhere in this build — the field is declared but not actually computed.

computed.yaml
# field type: computed
fields:
line_total:
type: computed
expression: "quantity * unit_price"
persist: true
Example value
149.97

timeseries

collection type

Not a per-field type but a collection_type: every record routes into a dedicated, per-tenant-RLS TimescaleDB hypertable partitioned on a declared time column; columnstore compression is rejected outright to keep that isolation guarantee.

timeseries.yaml
# collection_type: timeseries (not a fields: entry — see summary)
name: equipment_telemetry
collection_type: timeseries
fields:
device_id:
type: string
required: true
temperature_c:
type: number
recorded_at:
type: datetime
required: true
timeseries_config:
timestamp_field: recorded_at
partition_interval: 1 day
retention_policy: 90 days
Example value
{ "time": "2026-08-27T14:00:00Z", "data": { "device_id": "chiller-04", "temperature_c": 21.4 } }
Query
{ "bucket": "1 hour", "aggregation": "avg", "value_field": "temperature_c", "range": { "start": "2026-08-26T00:00:00Z", "end": "2026-08-27T00:00:00Z" }, "group_by": "device_id", "order": "asc" }

GraphQL API

GraphQL, without the N+1.

A per-tenant GraphQL endpoint at POST /graphql, its schema generated from your own collections. It rides the exact same tenant-scoped, policy-enforced data layer as REST — so a nested selection compiles to one expand query, not a fetch per row.

query.graphql
query {
segmentMemberships {
nodes {
id
contact_id { email first_name } # a relation
}
totalCount
}
}
compiles to · one query
# one tenant-scoped query — the relation is expanded, not fetched per row
data.forTenant(ctx).query("segment_memberships", {
expand: ["contact_id"], # derived from the selection set
page: { limit: 100 }, # bounded — never the whole table
})
# + one COUNT for totalCount
# relations resolve from the expanded rows — never a query per node

Measured — a 1,000-row page with a relation

1

database query for the whole page + its relation — the same as REST's ?expand=

1,001

what a per-node resolver would issue instead (1 + N) — the trap this avoids

~1.7 ms

GraphQL layer overhead (parse → validate → execute → redact) on top of that one query

Bounded by design

  • depth ≤ 10
  • complexity ≤ 1000
  • page ≤ 1000 rows
  • introspection off in prod

Same guarantees as REST

  • RLS-forced tenant handle
  • column policy applied last
  • RBAC ceiling per resolver
  • expanded relations re-gated

Workflows· 7

Durable by default — it survives the crash.

A declarative executor runs the steps: it parks on events, fires timers, recovers from a mid-run crash, and compensates idempotently. Every template below is a real workflow definition — its trigger, config, and full step graph — plus the event that starts it.

console · runslive
Run #8f2aparked
  1. appointment.bookedtrigger
  2. wait_for payment.capturedparked
  3. send comms.confirmqueued
  4. after 24h → remindtimer

durable park/resume · survives a crash, resumes exactly once

A parked run: it holds on an event or a timer and resumes exactly once, even across a restart.

approval-gate

workflow template

Notifies the approver role of a pending request, waits for an approve/deny decision with automatic escalation on timeout, then notifies the requester of the outcome.

approval-gate.yaml
# kind: workflow · name: approval-gate
trigger:
type: event
event: approval.requested
input_schema:
required: [requester_id, request_type, request_data]
properties:
requester_id: string
request_type: string
request_data: object
config:
approver_role: org_admin
escalation_role: org_admin
timeout_duration: 48h
initial_step: notify_approver
steps:
notify_approver:
type: action
on_complete:
goto: await_approval
# comms.send approval_requested to_role={{ config.approver_role }} data={request_type, requester}
await_approval:
type: approval
config:
required_role: "{{ config.approver_role }}"
prompt: Review and approve or deny this request
timeout:
duration: 48h
action:
goto: escalate
on_complete:
branch:
- condition: "{{ approval.status == 'approved' }}"
goto: on_approved
- condition: "{{ approval.status == 'denied' }}"
goto: on_denied
escalate:
type: action
on_complete:
goto: await_approval
# comms.send approval_escalation to_role={{ config.escalation_role }}
on_approved:
type: action
on_complete:
goto: end
status: completed
# comms.send request_approved to={{ input.requester_id }}
on_denied:
type: action
on_complete:
goto: end
status: completed
# comms.send request_denied to={{ input.requester_id }} data.reason={{ approval.reason }}
Trigger
{
"type": "approval.requested",
"tenant_id": "tn_8841",
"data": {
"requester_id": "usr_8f2a1c",
"request_type": "expense_report",
"request_data": { "amount": 482.5, "description": "Client dinner - Q3 kickoff" }
}
}

escalation-chain

workflow template

Escalates an unacknowledged issue through staff, then location_admin, then org_admin, notifying each level in turn until someone acknowledges or the chain times out.

escalation-chain.yaml
# kind: workflow · name: escalation-chain
trigger:
type: event
event: escalation.triggered
input_schema:
required: [subject, context]
properties:
subject: string
context: object
config:
levels:
- role: staff
timeout: 30m
template: escalation_level_1
- role: location_admin
timeout: 2h
template: escalation_level_2
- role: org_admin
timeout: 24h
template: escalation_level_3
initial_step: notify_level_1
steps:
notify_level_1:
type: action
on_complete:
goto: await_response_1
# comms.send escalation_level_1 to_role=staff
await_response_1:
type: wait_for_event
config:
event: escalation.acknowledged
match:
data.escalation_id: "{{ instance.id }}"
timeout:
duration: 30m
action:
goto: notify_level_2
on_complete:
goto: end
status: completed
notify_level_2:
type: action
on_complete:
goto: await_response_2
# comms.send escalation_level_2 to_role=location_admin
await_response_2:
type: wait_for_event
config:
event: escalation.acknowledged
match:
data.escalation_id: "{{ instance.id }}"
timeout:
duration: 2h
action:
goto: notify_level_3
on_complete:
goto: end
status: completed
notify_level_3:
type: action
on_complete:
goto: await_response_3
# comms.send escalation_level_3 to_role=org_admin
await_response_3:
type: wait_for_event
config:
event: escalation.acknowledged
match:
data.escalation_id: "{{ instance.id }}"
timeout:
duration: 24h
action:
goto: end
status: failed
on_complete:
goto: end
status: completed
Trigger
{
"type": "escalation.triggered",
"tenant_id": "tn_8841",
"data": {
"subject": "POS terminal offline at Downtown location",
"context": { "location_id": "loc_4471", "severity": "high" }
}
}

onboarding

workflow template

Sends a welcome email on registration, waits for the profile form to be submitted, and nudges the user (looping the wait) if it isn't completed within the timeout.

onboarding.yaml
# kind: workflow · name: onboarding
trigger:
type: event
event: iam.user.registered
input_schema:
required: [user_id, email]
properties:
user_id: string
email: string
config:
profile_form: onboarding-profile
welcome_template: welcome_email
nudge_template: complete_profile_nudge
nudge_delay: 24h
initial_step: send_welcome
steps:
send_welcome:
type: action
on_complete:
goto: await_profile
# comms.send {{ config.welcome_template }} to={{ input.user_id }}
await_profile:
type: wait_for_event
config:
event: forms.submitted
match:
data.form_name: "{{ config.profile_form }}"
data.submitted_by: "{{ input.user_id }}"
timeout:
duration: 24h
action:
goto: send_nudge
on_complete:
goto: end
status: completed
send_nudge:
type: action
on_complete:
goto: await_profile
# comms.send {{ config.nudge_template }} to={{ input.user_id }}
Trigger
{
"type": "iam.user.registered",
"tenant_id": "tn_8841",
"data": {
"user_id": "usr_9c31de",
"email": "jordan@example.com"
}
}

content-publishing

workflow template

Puts a post into review, waits for the reviewer's approval with overdue reminders on timeout, then publishes it or routes it back to the author for revision.

content-publishing.yaml
# kind: workflow · name: content-publishing
trigger:
type: api
input_schema:
required: [content_id, author_id]
properties:
content_id: string
author_id: string
publish_at: string
config:
reviewer_role: org_manager
approver_role: org_admin
review_timeout: 72h
initial_step: submit_for_review
steps:
submit_for_review:
type: action
on_complete:
goto: await_review
# data.update posts[{{ input.content_id }}].status=in_review; comms.send content_review_requested to_role={{ config.reviewer_role }}
await_review:
type: approval
config:
required_role: "{{ config.reviewer_role }}"
prompt: Review this content for quality and accuracy
timeout:
duration: 72h
action:
goto: review_timeout
on_complete:
branch:
- condition: "{{ approval.status == 'approved' }}"
goto: publish_now
- condition: "{{ approval.status == 'denied' }}"
goto: revision_requested
review_timeout:
type: action
on_complete:
goto: await_review
# comms.send review_overdue to_role={{ config.approver_role }}
revision_requested:
type: action
on_complete:
goto: end
status: completed
# data.update posts[{{ input.content_id }}].status=revision_requested; comms.send revision_requested to={{ input.author_id }} data.feedback={{ approval.reason }}
publish_now:
type: action
on_complete:
goto: end
status: completed
# data.update posts[{{ input.content_id }}].status=published,published_at={{ now }}; comms.send content_published to={{ input.author_id }}
Trigger
{
"content_id": "post_2214",
"author_id": "usr_7a90f1",
"publish_at": "2026-09-01T09:00:00Z"
}

refund-request

workflow template

Auto-approves and refunds requests at or under the policy threshold; otherwise routes to manual review with escalation on timeout before refunding or denying.

refund-request.yaml
# kind: workflow · name: refund-request
trigger:
type: api
input_schema:
required: [customer_id, invoice_id, reason]
properties:
customer_id: string
invoice_id: string
reason: string
amount: number
config:
auto_approve_window: 24h
auto_approve_max_amount: 10000
reviewer_role: location_admin
review_timeout: 48h
initial_step: evaluate_policy
steps:
evaluate_policy:
type: condition
config:
expression: "{{ input.amount <= config.auto_approve_max_amount }}"
on_complete:
branch:
- condition: "{{ input.amount <= config.auto_approve_max_amount }}"
goto: auto_approve
- condition: "{{ true }}"
goto: manual_review
auto_approve:
type: action
on_complete:
goto: end
status: completed
# commerce.refundPayment invoice_id={{ input.invoice_id }} amount={{ input.amount }} reason={{ input.reason }}; comms.send refund_approved to={{ input.customer_id }}
manual_review:
type: approval
config:
required_role: "{{ config.reviewer_role }}"
prompt: Review refund request
timeout:
duration: 48h
action:
goto: escalate
on_complete:
branch:
- condition: "{{ approval.status == 'approved' }}"
goto: process_refund
- condition: "{{ approval.status == 'denied' }}"
goto: deny_refund
escalate:
type: action
on_complete:
goto: manual_review
# comms.send refund_review_overdue to_role=org_admin
process_refund:
type: action
on_complete:
goto: end
status: completed
# commerce.refundPayment invoice_id={{ input.invoice_id }} amount={{ input.amount }} reason={{ input.reason }}; comms.send refund_approved to={{ input.customer_id }}
deny_refund:
type: action
on_complete:
goto: end
status: completed
# comms.send refund_denied to={{ input.customer_id }} data.reason={{ approval.reason }}
Trigger
{
"customer_id": "cus_5501ab",
"invoice_id": "inv_88213",
"reason": "Service was cancelled before the appointment",
"amount": 4200
}

booking-with-payment

workflow template

Holds a slot, invoices the customer, confirms the booking once payment is received (releasing the hold if it isn't), and thanks the customer after the appointment completes.

booking-with-payment.yaml
# kind: workflow · name: booking-with-payment
trigger:
type: api
input_schema:
required: [customer_id, service_id, slot_id]
properties:
customer_id: string
service_id: string
slot_id: string
payment_strategy: string
config:
hold_ttl: 15m
initial_step: hold_slot
steps:
hold_slot:
type: action
config:
store_results_as: slot_hold
on_complete:
goto: create_payment
# scheduler.createTemporaryHold slot_id={{ input.slot_id }} ttl=15m
create_payment:
type: action
config:
store_results_as: payment
compensation:
- module: scheduler
method: releaseHold
params:
hold_id: "{{ steps.slot_hold.hold_id }}"
on_complete:
goto: await_payment
# commerce.createInvoice amount={{ steps.slot_hold.service_price }} currency=USD metadata={booking_id, slot_id}; comms.send payment_link to={{ input.customer_id }}
await_payment:
type: wait_for_event
config:
event: commerce.payment.received
match:
data.metadata.booking_id: "{{ instance.id }}"
timeout:
duration: 15m
action:
goto: payment_expired
on_complete:
goto: confirm_booking
payment_expired:
type: action
on_complete:
goto: end
status: cancelled
# scheduler.releaseHold hold_id={{ steps.slot_hold.hold_id }}; comms.send payment_expired to={{ input.customer_id }}
confirm_booking:
type: action
config:
store_results_as: booking
on_complete:
goto: await_completion
# scheduler.confirmAppointment hold_id={{ steps.slot_hold.hold_id }}; comms.send booking_confirmed to={{ input.customer_id }}
await_completion:
type: wait_for_event
config:
event: scheduler.appointment.completed
match:
data.appointment_id: "{{ steps.booking.appointment_id }}"
on_complete:
goto: post_completion
# no timeout on this step — waits indefinitely for the appointment to complete
post_completion:
type: action
on_complete:
goto: end
status: completed
# comms.send thank_you_and_review to={{ input.customer_id }}
Trigger
{
"customer_id": "cus_3392fe",
"service_id": "svc_haircut_classic",
"slot_id": "slot_2026-09-02T14:00",
"payment_strategy": "card_on_file"
}

multi-stage-job

workflow template

Runs a field-service job end to end: authorize an assessment fee, assign a contractor, capture payment and send a quote after the report, collect a deposit once accepted, schedule and complete the work, then send the final invoice.

multi-stage-job.yaml
# kind: workflow · name: multi-stage-job
trigger:
type: api
input_schema:
required: [customer_id, category, description]
properties:
customer_id: string
category: string
description: string
preferred_slots: array
config:
assessment_fee: 7500
deposit_percentage: 50
quote_validity_days: 14
initial_step: create_assessment_request
steps:
create_assessment_request:
type: action
config:
store_results_as: assessment
on_complete:
goto: await_payment_auth
# scheduler.createRequest type=assessment preferred_slots={{ input.preferred_slots }}; commerce.createInvoice amount={{ config.assessment_fee }} payment_type=authorization metadata={job_id, stage=assessment}
await_payment_auth:
type: wait_for_event
config:
event: commerce.payment.authorized
match:
data.metadata.job_id: "{{ instance.id }}"
timeout:
duration: 15m
action:
goto: payment_timeout
on_complete:
goto: await_assignment
payment_timeout:
type: action
on_complete:
goto: end
status: cancelled
# commerce.voidInvoice invoice_id={{ steps.assessment.invoice_id }}; comms.send payment_expired to={{ input.customer_id }}
await_assignment:
type: approval
config:
required_role: location_admin
prompt: Assign a contractor and confirm the assessment slot
timeout:
duration: 48h
action:
goto: escalate_assignment
on_complete:
goto: await_assessment_report
# unconditional goto — proceeds on any approval decision, not branched on approval.status
escalate_assignment:
type: action
on_complete:
goto: await_assignment
# comms.send assignment_escalation to_role=org_admin
await_assessment_report:
type: wait_for_event
config:
event: data.record.created
match:
data.collection: assessment_reports
data.data.job_id: "{{ instance.id }}"
on_complete:
goto: capture_and_send_quote
# no timeout on this step — waits indefinitely for the assessment report
capture_and_send_quote:
type: action
config:
store_results_as: quote
compensation:
- module: commerce
method: refundPayment
params:
invoice_id: "{{ steps.assessment.invoice_id }}"
reason: Workflow cancelled after capture
on_complete:
goto: await_quote_acceptance
# commerce.capturePayment invoice_id={{ steps.assessment.invoice_id }}; comms.send quote_ready to={{ input.customer_id }} data.quote={{ event.data.data.quote }}
await_quote_acceptance:
type: approval
config:
required_user: "{{ input.customer_id }}"
prompt: Review and accept the repair quote
timeout:
duration: 14d
action:
goto: quote_expired
on_complete:
goto: create_deposit
# unconditional goto — proceeds on any approval decision, not branched on approval.status
quote_expired:
type: action
on_complete:
goto: end
status: cancelled
# comms.send quote_expired to={{ input.customer_id }}
create_deposit:
type: action
config:
store_results_as: deposit
on_complete:
goto: await_deposit
# commerce.createInvoice amount={{ steps.quote.total * config.deposit_percentage / 100 }} payment_type=immediate metadata={job_id, stage=deposit}
await_deposit:
type: wait_for_event
config:
event: commerce.invoice.paid
match:
data.metadata.job_id: "{{ instance.id }}"
data.metadata.stage: deposit
timeout:
duration: 72h
action:
goto: deposit_reminder
on_complete:
goto: schedule_work
deposit_reminder:
type: action
on_complete:
goto: await_deposit
# comms.send payment_reminder to={{ input.customer_id }}
schedule_work:
type: action
config:
store_results_as: work
on_complete:
goto: await_job_completion
# scheduler.createAppointment type=service; comms.send work_scheduled to={{ input.customer_id }}
await_job_completion:
type: wait_for_event
config:
event: data.record.updated
match:
data.collection: jobs
data.changes.status.to: completed
on_complete:
goto: final_invoice
# no timeout on this step — waits indefinitely for the job to complete
final_invoice:
type: action
on_complete:
goto: end
status: completed
# commerce.createInvoice amount={{ steps.quote.total * (100 - config.deposit_percentage) / 100 }} payment_type=invoice metadata={job_id, stage=final}; comms.send final_invoice to={{ input.customer_id }}
Trigger
{
"customer_id": "cus_7734de",
"category": "plumbing",
"description": "Kitchen sink leaking under the cabinet",
"preferred_slots": ["2026-09-03T09:00", "2026-09-03T13:00"]
}

Presets· 29

Presets that tune the kernel — without creating anything.

A preset flips configuration: how auth works, how money is captured, which columns are masked, what fires automatically, how requests are throttled, where files live. Here is the exact config each one sets, grouped by what it tunes. Layer them under any strategy.

Auth

5

Tune the iam module — methods, MFA, sessions. Create nothing.

consumer

iam preset

Password + magic-link login, optional TOTP, 30-day sessions, email-verified signup.

consumer.yaml
# kind: auth · module: iam · name: consumer
auth_methods: [password, magic_link]
mfa:
available: [totp]
required: false
session:
max_age: 30d
refresh_interval: 7d
registration:
steps:
- type: email_verification
expires_in: 24h
password_policy:
min_length: 8
require_uppercase: true
require_number: true
Enables
login: [password, magic_link]
step-up: totp (optional)

enterprise

iam preset

SAML/OIDC SSO with required TOTP or WebAuthn MFA, 8-hour sessions, IdP-provisioned registration and attribute mapping.

enterprise.yaml
# kind: auth · module: iam · name: enterprise
auth_methods: [saml, oidc]
mfa:
available: [totp, webauthn]
required: true
session:
max_age: 8h
refresh_interval: 1h
refresh_on_activity: true
registration:
mode: idp_provisioned
idp:
auto_provision: true
attribute_mapping:
email: email
name: displayName
department: department
Enables
login: [saml, oidc]
step-up: totp, webauthn (required)
registration: idp_provisioned

api-first

iam preset

API-key and JWT-bearer auth with no session state, prefixed/expiring keys with rate limiting, JWKS-refreshed JWT validation.

api-first.yaml
# kind: auth · module: iam · name: api-first
auth_methods: [api_key, jwt_bearer]
session:
enabled: false
api_key:
prefix_format: pk_{env}_
default_expiry: 365d
max_keys_per_user: 10
rate_limit:
max: 1000
window_seconds: 60
jwt:
jwks_refresh_interval: 1h
allowed_issuers: []
clock_skew_seconds: 30
Enables
login: [api_key, jwt_bearer]
session: disabled
api_key: prefix pk_{env}_, expires 365d

hybrid

iam preset

Password + OAuth2 (Google/Microsoft/GitHub) login with optional TOTP/WebAuthn prompted after 3 days, captcha-gated email-verified registration.

hybrid.yaml
# kind: auth · module: iam · name: hybrid
auth_methods: [password, oauth2]
mfa:
available: [totp, webauthn]
required: false
prompt_after: 3d
oauth2:
providers: [google, microsoft, github]
session:
max_age: 14d
refresh_interval: 3d
registration:
steps:
- type: captcha
provider: hcaptcha
- type: email_verification
expires_in: 24h
Enables
login: [password, oauth2]
oauth2: [google, microsoft, github]
step-up: totp, webauthn (prompt after 3d)

anonymous-to-authenticated

iam preset

Anonymous sessions (7d) that convert to password/magic-link accounts on checkout, save, or content creation, merging data on conversion.

anonymous-to-authenticated.yaml
# kind: auth · module: iam · name: anonymous-to-authenticated
auth_methods: [anonymous, password, magic_link]
anonymous:
session_max_age: 7d
conversion_triggers: [checkout, save_item, create_content]
data_merge_on_conversion: true
session:
max_age: 30d
refresh_interval: 7d
registration:
steps:
- type: email_verification
expires_in: 24h
Enables
login: [anonymous, password, magic_link]
anonymous session: 7d
converts on: [checkout, save_item, create_content]

Payments

6

Tune the payments module — capture, settlement, currency. Create nothing.

pay-now

payments preset

Immediate payment capture at checkout, USD.

pay-now.yaml
# kind: payments · module: payments · name: pay-now
payment_strategy: pay-now
payment:
default_type: immediate
currency: USD
Capture flow
strategy: pay-now
capture: immediate
currency: USD

authorize-then-capture

payments preset

Authorize on order, manually capture later (auto-void after 7d); full refund before 24h with a 2h no-refund window.

authorize-then-capture.yaml
# kind: payments · module: payments · name: authorize-then-capture
payment_strategy: authorize-then-capture
payment:
default_type: authorization
capture_policy:
trigger: manual
auto_void_after: 7d
cancellation:
refund_policy: full_before_24h
no_refund_window: 2h
currency: USD
Capture flow
strategy: authorize-then-capture
capture: manual (auto-void after 7d)
cancellation: full_before_24h, no_refund_window 2h

invoice-later

payments preset

Net-14 invoicing with reminders at 3, 7, and 14 days.

invoice-later.yaml
# kind: payments · module: payments · name: invoice-later
payment_strategy: invoice-later
payment:
default_type: invoice
terms: net_14
reminder_cadence: [3d, 7d, 14d]
currency: USD
Capture flow
strategy: invoice-later
terms: net_14
reminders: [3d, 7d, 14d]

deposit-then-balance

payments preset

50% immediate deposit, remaining balance invoiced on completion with 3/7/14-day reminders.

deposit-then-balance.yaml
# kind: payments · module: payments · name: deposit-then-balance
payment_strategy: deposit-then-balance
payment:
deposit:
percentage: 50
payment_type: immediate
balance:
payment_type: invoice
due_on: completion
reminder_cadence: [3d, 7d, 14d]
currency: USD
Capture flow
strategy: deposit-then-balance
deposit: 50% immediate
balance: invoice, due on completion

subscription

payments preset

Recurring billing with proration enabled.

subscription.yaml
# kind: payments · module: payments · name: subscription
payment_strategy: subscription
payment:
default_type: recurring
proration: true
currency: USD
Capture flow
strategy: subscription
capture: recurring
proration: true

free-then-paid

payments preset

Deferred payment triggered by an event, not immediate checkout.

free-then-paid.yaml
# kind: payments · module: payments · name: free-then-paid
payment_strategy: free-then-paid
payment:
default_type: deferred
trigger: on_event
currency: USD
Capture flow
strategy: free-then-paid
capture: deferred
trigger: on_event

Column policy

6

Per-role field-visibility profiles applied to collections by reference — expose, redact, or mask each field.

pii-strict

column policy

PII collections: mask contact fields (email, phone), redact government/identity fields (SSN, tax ID, DOB, addresses, IDs).

pii-strict.yaml
# kind: column_policy · name: pii-strict
defaults: expose
fields:
email:
mask:
type: email
phone:
mask:
type: last4
ssn: redact
tax_id: redact
date_of_birth: redact
address: redact
full_address: redact
social_security: redact
drivers_license: redact
passport_number: redact
Masked fields
mask: [email, phone]
redact: [ssn, tax_id, date_of_birth, address, full_address,
social_security, drivers_license, passport_number]

financial

column policy

Financial collections: mask account/routing numbers to last4, redact card data, bucket balances into ranges.

financial.yaml
# kind: column_policy · name: financial
defaults: expose
fields:
account_number:
mask:
type: last4
routing_number:
mask:
type: last4
card_number: redact
card_cvv: redact
bank_name: expose
transaction_amount: expose
balance:
mask:
type: range
buckets: [1000, 5000, 10000, 50000, 100000]
Masked fields
mask: [account_number, routing_number, balance (bucketed)]
redact: [card_number, card_cvv]

medical

column policy

Health data: redact clinical fields; keep safety-critical allergies and emergency contact visible.

medical.yaml
# kind: column_policy · name: medical
defaults: expose
fields:
medical_notes: redact
diagnosis: redact
prescriptions: redact
lab_results: redact
insurance_id:
mask:
type: last4
insurance_provider: expose
blood_type: redact
allergies: expose
emergency_contact: expose
Masked fields
redact: [medical_notes, diagnosis, prescriptions, lab_results, blood_type]
mask: [insurance_id]

public-profile

column policy

Public profile: redact everything by default, expose only public-facing fields like name, bio, and company.

public-profile.yaml
# kind: column_policy · name: public-profile
defaults: redact
fields:
name: expose
display_name: expose
avatar: expose
bio: expose
title: expose
company: expose
website: expose
public_email: expose
Masked fields
defaults: redact
expose: [name, display_name, avatar, bio, title, company, website, public_email]

internal-only

column policy

Internal-only: redact every field by default with no exposed fields — hides everything from the applied role.

internal-only.yaml
# kind: column_policy · name: internal-only
defaults: redact
fields: {}
Masked fields
defaults: redact
fields: {} # no overrides — everything redacted

audit-safe

column policy

Audit-safe: view activity logs with emails/IP masked and old/new values hashed, without exposing raw request/response payloads.

audit-safe.yaml
# kind: column_policy · name: audit-safe
defaults: expose
fields:
actor_email:
mask:
type: email
target_email:
mask:
type: email
ip_address:
mask:
type: first_last
visible: 3
user_agent: expose
request_body: redact
response_body: redact
old_value:
mask:
type: hash
new_value:
mask:
type: hash
Masked fields
mask: [actor_email, target_email, ip_address, old_value, new_value]
redact: [request_body, response_body]

Automations

4

PREBUILT §9 — L1 presets that map platform events to comms.send. Create nothing: the resolver returns { mappings }, which G6 wires into event subscribers (event → resolve template → comms.send).

transactional-basics

automations preset

Six core account and billing events — registration, invoice, payment, refund, password reset, suspicious login — mapped straight to comms.send templates; enabled by the email-marketing smoke test.

transactional-basics.yaml
# kind: automations · module: comms · name: transactional-basics
mappings:
- event: iam.user.registered
action: comms.send
template: welcome
to: "{{ event.data.user_id }}"
- event: commerce.invoice.created
action: comms.send
template: invoice_created
to: "{{ event.data.customer_id }}"
- event: commerce.payment.received
action: comms.send
template: payment_receipt
to: "{{ event.data.customer_id }}"
- event: commerce.refund.processed
action: comms.send
template: refund_confirmation
to: "{{ event.data.customer_id }}"
- event: iam.user.password_reset_requested
action: comms.send
template: password_reset
to: "{{ event.data.user_id }}"
- event: iam.session.suspicious
action: comms.send
template: suspicious_login
to: "{{ event.data.user_id }}"
Triggers
iam.user.registered: welcome
commerce.invoice.created: invoice_created
commerce.payment.received: payment_receipt
commerce.refund.processed: refund_confirmation
iam.user.password_reset_requested: password_reset
iam.session.suspicious: suspicious_login

booking-lifecycle

automations preset

The scheduler booking lifecycle end to end — request, approval, confirmation, reminder (email + SMS), cancellation, reschedule — including internal notifications to location_admin and the assigned agent.

booking-lifecycle.yaml
# kind: automations · module: comms · name: booking-lifecycle
mappings:
- event: scheduler.request.created
action: comms.send
template: booking_request_received
to: "{{ event.data.customer_id }}"
- event: scheduler.request.created
action: comms.send
template: new_booking_request
to_role: location_admin
- event: scheduler.request.approved
action: comms.send
template: booking_approved
to: "{{ event.data.customer_id }}"
- event: scheduler.appointment.confirmed
action: comms.send
template: booking_confirmed
to: "{{ event.data.customer_id }}"
- event: scheduler.appointment.confirmed
action: comms.send
template: new_appointment_staff
to: "{{ event.data.agent_id }}"
- event: scheduler.reminder.due
action: comms.send
template: appointment_reminder
to: "{{ event.data.customer_id }}"
channels: ["email", "sms"]
- event: scheduler.appointment.cancelled
action: comms.send
template: booking_cancelled
to: "{{ event.data.customer_id }}"
- event: scheduler.appointment.rescheduled
action: comms.send
template: booking_rescheduled
to: "{{ event.data.customer_id }}"
Triggers
scheduler.request.created: booking_request_received
scheduler.request.approved: booking_approved
scheduler.appointment.confirmed: booking_confirmed
scheduler.reminder.due: appointment_reminder # channels: [email, sms]
scheduler.appointment.cancelled: booking_cancelled
scheduler.appointment.rescheduled: booking_rescheduled

job-lifecycle

automations preset

Conditional job and assessment events keyed on collection/metadata/status — job created, assessment scheduled/assigned, quote ready, deposit received, job completed, and a 24h-delayed review request.

job-lifecycle.yaml
# kind: automations · module: comms · name: job-lifecycle
mappings:
- event: data.record.created
condition: { "data.collection": "jobs" }
action: comms.send
template: job_created
to: "{{ event.data.data.customer_id }}"
- event: scheduler.appointment.confirmed
condition: { "data.metadata.type": "assessment" }
action: comms.send
template: assessment_scheduled
to: "{{ event.data.customer_id }}"
- event: scheduler.appointment.confirmed
condition: { "data.metadata.type": "assessment" }
action: comms.send
template: assessment_assigned
to: "{{ event.data.agent_id }}"
- event: data.record.created
condition: { "data.collection": "assessment_reports" }
action: comms.send
template: quote_ready
to: "{{ event.data.data.customer_id }}"
- event: commerce.payment.received
condition: { "data.metadata.stage": "deposit" }
action: comms.send
template: deposit_received
to: "{{ event.data.customer_id }}"
- event: data.record.updated
condition: { "data.collection": "jobs", "data.changes.status.to": "completed" }
action: comms.send
template: job_completed
to: "{{ event.data.data.customer_id }}"
- event: data.record.updated
condition: { "data.collection": "jobs", "data.changes.status.to": "completed" }
action: comms.send
template: review_request
to: "{{ event.data.data.customer_id }}"
delay: "24h"
Triggers
data.record.created: job_created # collection = jobs
scheduler.appointment.confirmed: assessment_scheduled # type = assessment
data.record.created: quote_ready # collection = assessment_reports
commerce.payment.received: deposit_received # stage = deposit
data.record.updated: job_completed # status → completed
data.record.updated: review_request # status → completed, delay 24h

commerce-alerts

automations preset

Billing exception alerts — failed payment, overdue invoice, subscription renewal (7 days before) and cancellation, plus completed merchant payouts.

commerce-alerts.yaml
# kind: automations · module: comms · name: commerce-alerts
mappings:
- event: commerce.payment.failed
action: comms.send
template: payment_failed
to: "{{ event.data.customer_id }}"
- event: commerce.invoice.overdue
action: comms.send
template: payment_overdue
to: "{{ event.data.customer_id }}"
- event: commerce.subscription.renewal_upcoming
action: comms.send
template: subscription_renewal_reminder
to: "{{ event.data.customer_id }}"
timing: 7d_before
- event: commerce.subscription.cancelled
action: comms.send
template: subscription_cancelled
to: "{{ event.data.customer_id }}"
- event: commerce.payout.completed
action: comms.send
template: payout_processed
to: "{{ event.data.merchant_id }}"
Triggers
commerce.payment.failed: payment_failed
commerce.invoice.overdue: payment_overdue
commerce.subscription.renewal_upcoming: subscription_renewal_reminder # 7d_before
commerce.subscription.cancelled: subscription_cancelled
commerce.payout.completed: payout_processed

Rate limiting

4

PREBUILT §10 — L1 presets tuning the core rate limiter (token buckets by global / tenant / role / endpoint). Create nothing; the resolver returns the config block.

consumer-app

rate-limit preset

Token-bucket limits for a customer-facing app: global and per-tenant buckets, per-role buckets for customer/staff/location_admin, and tight per-endpoint caps on auth, registration, and form submits.

consumer-app.yaml
# kind: rate_limiting · name: consumer-app
global: { max_tokens: 10000, refill_rate: 1000 }
per_tenant: { max_tokens: 1000, refill_rate: 100 }
per_role:
customer: { max_tokens: 60, refill_rate: 10 }
staff: { max_tokens: 200, refill_rate: 30 }
location_admin: { max_tokens: 300, refill_rate: 50 }
per_endpoint:
"POST /iam/auth/*": { max_tokens: 10, refill_rate: 1 }
"POST /iam/register": { max_tokens: 5, refill_rate: 0.1 }
"POST /forms/*/submit": { max_tokens: 20, refill_rate: 2 }
"POST /data/*/query": { max_tokens: 100, refill_rate: 20 }
Limits
global: 10000 tokens, refill 1000/s
per_tenant: 1000 tokens, refill 100/s
customer role: 60 tokens, refill 10/s
POST /iam/register: 5 tokens, refill 0.1/s

api-platform

rate-limit preset

Tiered per-tenant token buckets (free/starter/pro/enterprise) under a high global ceiling, with per-endpoint caps on auth, data queries, and inbound webhooks.

api-platform.yaml
# kind: rate_limiting · name: api-platform
global: { max_tokens: 50000, refill_rate: 5000 }
per_tenant:
tiers:
free: { max_tokens: 100, refill_rate: 10 }
starter: { max_tokens: 500, refill_rate: 50 }
pro: { max_tokens: 2000, refill_rate: 200 }
enterprise: { max_tokens: 10000, refill_rate: 1000 }
per_endpoint:
"POST /iam/auth/*": { max_tokens: 20, refill_rate: 2 }
"POST /data/*/query": { max_tokens: 200, refill_rate: 40 }
"POST /webhooks/inbound/*": { max_tokens: 500, refill_rate: 100 }
Limits
global: 50000 tokens, refill 5000/s
free tier: 100 tokens, refill 10/s
enterprise tier: 10000 tokens, refill 1000/s
POST /webhooks/inbound/*: 500 tokens, refill 100/s

internal-tool

rate-limit preset

Generous global/tenant/user buckets for trusted internal use, with bulk-import and delete endpoints throttled hard.

internal-tool.yaml
# kind: rate_limiting · name: internal-tool
global: { max_tokens: 20000, refill_rate: 2000 }
per_tenant: { max_tokens: 5000, refill_rate: 500 }
per_user: { max_tokens: 500, refill_rate: 50 }
per_endpoint:
"POST /data/*/bulk-import": { max_tokens: 10, refill_rate: 0.5 }
"DELETE /data/*": { max_tokens: 50, refill_rate: 5 }
Limits
global: 20000 tokens, refill 2000/s
per_tenant: 5000 tokens, refill 500/s
per_user: 500 tokens, refill 50/s
POST /data/*/bulk-import: 10 tokens, refill 0.5/s

webhook-heavy

rate-limit preset

Global and per-tenant buckets sized for webhook volume, with separate inbound/outbound webhook endpoint limits and a tight auth cap.

webhook-heavy.yaml
# kind: rate_limiting · name: webhook-heavy
global: { max_tokens: 20000, refill_rate: 2000 }
per_tenant: { max_tokens: 2000, refill_rate: 200 }
per_endpoint:
"POST /webhooks/inbound/*": { max_tokens: 500, refill_rate: 100 }
"POST /webhooks/outbound/*": { max_tokens: 200, refill_rate: 50 }
"POST /iam/auth/*": { max_tokens: 10, refill_rate: 1 }
Limits
global: 20000 tokens, refill 2000/s
per_tenant: 2000 tokens, refill 200/s
POST /webhooks/inbound/*: 500 tokens, refill 100/s
POST /webhooks/outbound/*: 200 tokens, refill 50/s

Storage

4

PREBUILT §11 — L1 presets tuning the storage module (max file size, per-tenant quota, accepted MIME types, default access level, signed-URL TTL, cleanup). Create nothing.

user-content

storage preset

General user uploads: 10MB max file, 5GB per-tenant quota, image/PDF/Word types, private access, 1-hour signed URLs, 30-day retention.

user-content.yaml
# kind: storage · module: storage · name: user-content
max_file_size: 10485760
per_tenant_quota_gb: 5
accepted_types:
- image/jpeg
- image/png
- image/webp
- application/pdf
- application/msword
- application/vnd.openxmlformats-officedocument.wordprocessingml.document
default_access_level: private
signed_url_ttl: 3600
cleanup:
retention_days: 30
orphan_check_interval: 24h
Buckets / limits
max_file_size: 10 MB (10485760 B)
quota: 5 GB / tenant
access: private, signed URL 3600s
retention: 30d, orphan check 24h

media-heavy

storage preset

Large media uploads: 100MB max file, 50GB per-tenant quota, image/video/audio/PDF types, tenant access, 2-hour signed URLs, 90-day retention.

media-heavy.yaml
# kind: storage · module: storage · name: media-heavy
max_file_size: 104857600
per_tenant_quota_gb: 50
accepted_types:
- image/*
- video/mp4
- video/webm
- audio/mpeg
- audio/wav
- application/pdf
default_access_level: tenant
signed_url_ttl: 7200
cleanup:
retention_days: 90
orphan_check_interval: 24h
Buckets / limits
max_file_size: 100 MB (104857600 B)
quota: 50 GB / tenant
access: tenant, signed URL 7200s
retention: 90d, orphan check 24h

documents-only

storage preset

Office document uploads: 25MB max file, 10GB per-tenant quota, PDF/Word/Excel/CSV types, private access, 30-minute signed URLs, 1-year retention.

documents-only.yaml
# kind: storage · module: storage · name: documents-only
max_file_size: 26214400
per_tenant_quota_gb: 10
accepted_types:
- application/pdf
- application/msword
- application/vnd.openxmlformats-officedocument.wordprocessingml.document
- application/vnd.ms-excel
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
- text/csv
default_access_level: private
signed_url_ttl: 1800
cleanup:
retention_days: 365
orphan_check_interval: 24h
Buckets / limits
max_file_size: 25 MB (26214400 B)
quota: 10 GB / tenant
access: private, signed URL 1800s
retention: 365d, orphan check 24h

minimal

storage preset

Small image-only uploads: 5MB max file, 1GB per-tenant quota, JPEG/PNG/WebP/SVG types, tenant access, 1-hour signed URLs, 30-day retention.

minimal.yaml
# kind: storage · module: storage · name: minimal
max_file_size: 5242880
per_tenant_quota_gb: 1
accepted_types:
- image/jpeg
- image/png
- image/webp
- image/svg+xml
default_access_level: tenant
signed_url_ttl: 3600
cleanup:
retention_days: 30
orphan_check_interval: 24h
Buckets / limits
max_file_size: 5 MB (5242880 B)
quota: 1 GB / tenant
access: tenant, signed URL 3600s
retention: 30d, orphan check 24h

RBAC roles· 14

Roles across every tier, permission by permission.

Ready-made roles spanning the platform → org → location → merchant → customer tiers. Each is a real permission set with its column policy — the exact grants below, ceiling-enforced when the applicator upserts them.

platform_admin

platform role

Platform superuser with unrestricted access to every resource and action across the system.

platform_admin.yaml
display_name: Platform Administrator
tenant_type: platform
permissions:
- "*:*"
column_policies:
_default:
defaults: expose
fields: {}
Can / can't
✓ *:* — every resource, every action
✓ fields: {} — nothing redacted either
✗ (none — the only role with no restrictions)

platform_support

platform role

Platform-level support staff with read-only visibility into tenants, users, and commerce data; SSNs, tax IDs, and credential hashes are redacted.

platform_support.yaml
display_name: Platform Support
tenant_type: platform
permissions:
- "tenant:read"
- "user:read"
- "appointment:read"
- "commerce.invoice:read"
- "commerce.payment:read"
- "commerce.payout:read"
- "data.*:read"
- "audit:read"
- "analytics:read"
- "workflow.instance:read"
- "form.submission:read"
column_policies:
_default:
defaults: expose
fields:
password_hash: redact
api_key_hash: redact
users:
defaults: expose
fields:
ssn: redact
tax_id: redact
Can / can't
✓ commerce.invoice:read
✓ audit:read
✗ user:update

platform_billing

platform role

Platform billing staff who read and approve payouts, invoices, payments, and subscriptions across tenants.

platform_billing.yaml
display_name: Platform Billing
tenant_type: platform
permissions:
- "tenant:read"
- "commerce.invoice:read"
- "commerce.payment:read"
- "commerce.payout:read"
- "commerce.payout:approve"
- "commerce.subscription:read"
- "analytics:read"
column_policies:
_default:
defaults: expose
fields:
medical_notes: redact
password_hash: redact
Can / can't
✓ commerce.payout:approve
✓ commerce.subscription:read
✗ user:read

org_admin

org role

Full administrative control within an organization — manages users, roles, billing, plugins, workflows, and forms.

org_admin.yaml
display_name: Organization Admin
tenant_type: org
permissions:
- "tenant:create"
- "tenant:read"
- "tenant:update"
- "user:create"
- "user:read"
- "user:update"
- "user:delete"
- "role:create"
- "role:read"
- "role:update"
- "role:delete"
- "appointment:read"
- "appointment:update"
- "commerce.invoice:read"
- "commerce.payment:read"
- "commerce.payout:read"
- "commerce.payout:approve"
- "commerce.subscription:read"
- "commerce.subscription:update"
- "data.*:read"
- "data.*:write"
- "plugin:create"
- "plugin:read"
- "plugin:update"
- "plugin:delete"
- "workflow:read"
- "workflow:manage"
- "form:read"
- "form:manage"
- "analytics:read"
- "audit:read"
- "config:read"
- "config:update"
column_policies:
_default:
defaults: expose
fields: {}
Can / can't
✓ user:delete
✓ role:delete
✗ appointment:create

org_manager

org role

Operational manager within an organization — reads/updates appointments and data, but can't manage users, roles, or billing.

org_manager.yaml
display_name: Organization Manager
tenant_type: org
permissions:
- "tenant:read"
- "user:read"
- "appointment:read"
- "appointment:update"
- "commerce.invoice:read"
- "commerce.payment:read"
- "data.*:read"
- "data.*:write"
- "workflow.instance:read"
- "form.submission:read"
- "analytics:read"
column_policies:
_default:
defaults: expose
fields:
payout_account: redact
api_key_hash: redact
Can / can't
✓ appointment:update
✓ data.*:write
✗ user:delete

org_viewer

org role

Read-only viewer within an organization — sees tenants, users, appointments, and data without any write access.

org_viewer.yaml
display_name: Organization Viewer
tenant_type: org
permissions:
- "tenant:read"
- "user:read"
- "appointment:read"
- "commerce.invoice:read"
- "data.*:read"
- "analytics:read"
column_policies:
_default:
defaults: expose
fields:
payout_account: redact
Can / can't
✓ commerce.invoice:read
✓ analytics:read
✗ data.*:write

org_billing

org role

Organization billing role covering invoices, payments, payouts, and subscriptions; no column policy is defined for it.

org_billing.yaml
display_name: Organization Billing
tenant_type: org
permissions:
- "tenant:read"
- "commerce.invoice:read"
- "commerce.payment:read"
- "commerce.payout:read"
- "commerce.payout:approve"
- "commerce.subscription:read"
- "analytics:read"
column_policies: {}
Can / can't
✓ commerce.payout:approve
✓ commerce.subscription:read
✗ user:read

location_admin

location role

Full administrative control at a single location — manages staff, roles, appointments, products, and payout requests.

location_admin.yaml
display_name: Location Admin
tenant_type: location
permissions:
- "user:create"
- "user:read"
- "user:update"
- "role:create"
- "role:read"
- "role:update"
- "appointment:create"
- "appointment:read"
- "appointment:update"
- "appointment:approve"
- "commerce.product:create"
- "commerce.product:read"
- "commerce.product:update"
- "commerce.invoice:read"
- "commerce.payment:read"
- "commerce.payout_account:create"
- "commerce.payout_account:read"
- "commerce.payout:request"
- "data.*:read"
- "data.*:write"
- "plugin:read"
- "workflow.instance:read"
- "workflow.instance:manage"
- "form:read"
- "form:manage"
- "analytics:read"
- "config:read"
- "config:update"
column_policies:
_default:
defaults: expose
fields: {}
Can / can't
✓ appointment:approve
✓ commerce.payout:request
✗ user:delete

merchant_admin

merchant role

Merchant-level admin managing their own users, products, invoices, and payout account, without organization-wide reach.

merchant_admin.yaml
display_name: Merchant Admin
tenant_type: merchant
permissions:
- "user:create"
- "user:read"
- "user:update"
- "role:read"
- "commerce.product:create"
- "commerce.product:read"
- "commerce.product:update"
- "commerce.invoice:read"
- "commerce.payment:read"
- "commerce.payout_account:create"
- "commerce.payout_account:read"
- "commerce.payout:request"
- "data.*:read"
- "data.*:write"
- "form:read"
- "form:manage"
- "analytics:read"
column_policies:
_default:
defaults: expose
fields: {}
Can / can't
✓ commerce.product:update
✓ commerce.payout:request
✗ role:create

merchant_staff

merchant role

Front-line merchant staff who read products/invoices and read+write data, with payout and commission fields redacted.

merchant_staff.yaml
display_name: Merchant Staff
tenant_type: merchant
permissions:
- "commerce.product:read"
- "commerce.invoice:read"
- "data.*:read"
- "data.*:write"
- "form.submission:read"
- "form.submission:create"
column_policies:
_default:
defaults: expose
fields:
payout_account: redact
commission_rate: redact
Can / can't
✓ commerce.invoice:read
✓ data.*:write
✗ commerce.product:create

staff

location role

Location staff handling day-to-day appointments, products, and data, with payout and commission fields redacted.

staff.yaml
display_name: Staff
tenant_type: location
permissions:
- "appointment:create"
- "appointment:read"
- "appointment:update"
- "commerce.product:read"
- "commerce.invoice:read"
- "commerce.payment:read"
- "data.*:read"
- "data.*:write"
- "workflow.instance:read"
- "form.submission:read"
- "form.submission:create"
column_policies:
_default:
defaults: expose
fields:
payout_account: redact
commission_rate: redact
Can / can't
✓ appointment:update
✓ data.*:write
✗ appointment:approve

staff_limited

location role

A narrower variant of Staff — reads/creates appointments and data with no update rights, and redacts commission, insurance, medical, and revenue fields.

staff_limited.yaml
display_name: Limited Staff
tenant_type: location
# inherits: staff
permissions:
- "appointment:read"
- "appointment:create"
- "commerce.product:read"
- "data.*:read"
- "form.submission:create"
column_policies:
_default:
defaults: expose
fields:
payout_account: redact
commission_rate: redact
insurance_id:
mask:
type: last4
medical_notes: redact
revenue: redact
Can / can't
✓ appointment:create
✓ data.*:read
✗ data.*:write

customer

location role

End customer who books appointments, views products/invoices, and reads/writes only their own data.

customer.yaml
display_name: Customer
tenant_type: location
permissions:
- "appointment:create"
- "appointment:read"
- "commerce.product:read"
- "commerce.invoice:read"
- "data.self:read"
- "data.self:write"
- "form.submission:create"
- "storage.file:upload"
column_policies:
_default:
defaults: expose
fields:
cost_price: redact
margin: redact
commission_rate: redact
internal_notes: redact
Can / can't
✓ data.self:read
✓ storage.file:upload
✗ data.*:read

guest

location role

Unauthenticated visitor who can browse products and submit forms, with no access to non-public data.

guest.yaml
display_name: Guest
tenant_type: location
permissions:
- "commerce.product:read"
- "data.public:read"
- "form.submission:create"
column_policies:
_default:
defaults: expose
fields:
cost_price: redact
margin: redact
internal_notes: redact
staff_notes: redact
Can / can't
✓ commerce.product:read
✓ form.submission:create
✗ appointment:create

Form templates· 6

Forms that become records, then act.

Reusable form definitions installed create-if-not-exists. Each below: its real field schema and on-submit behavior, with an example submission.

contact

form template

General-purpose contact inquiry form, unauthenticated, routed to the inquiries collection.

contact.yaml
kind: form
name: Contact
slug: contact
schema:
fields:
- name: name
type: text
label: Full Name
required: true
- name: email
type: email
label: Email
required: true
- name: phone
type: phone
label: Phone Number
- name: subject
type: text
label: Subject
required: true
- name: message
type: textarea
label: Message
required: true
submission_config:
target_collection: inquiries
send_confirmation:
template: contact_received
to_field: email
require_auth: false
Example submission
{
"name": "Marcia Reid",
"email": "marcia.reid@example.com",
"phone": "+1-876-555-0134",
"subject": "Question about your services",
"message": "Hi, I'd like to know more about your booking process before I sign up."
}

feedback

form template

Post-service rating and review form gated behind auth, routed to the reviews collection.

feedback.yaml
kind: form
name: Feedback
slug: feedback
schema:
fields:
- name: rating
type: rating
label: How would you rate your experience?
required: true
config:
max: 5
- name: review
type: textarea
label: Tell us about your experience
- name: categories
type: multi_select
label: What went well?
options:
- value: service
label: Service Quality
- value: timeliness
label: Timeliness
- value: communication
label: Communication
- value: value
label: Value for Money
- value: cleanliness
label: Cleanliness
- name: photos
type: file_upload
label: Photos (optional)
config:
max_files: 5
accepted_types: [.jpg, .png]
max_size_mb: 5
- name: would_recommend
type: toggle
label: Would you recommend us?
default_value: true
submission_config:
target_collection: reviews
send_confirmation:
template: thank_you_for_feedback
to_field: _auth_user_email
require_auth: true
Example submission
{
"rating": 5,
"review": "The team was on time and very professional.",
"categories": ["service", "timeliness", "communication"],
"photos": ["https://cdn.example.com/uploads/feedback-1.jpg"],
"would_recommend": true
}

onboarding-profile

form template

Four-step new-user profile form (personal, preferences, documents, terms), capped at one submission per user.

onboarding-profile.yaml
kind: form
name: Onboarding Profile
slug: onboarding-profile
schema:
steps:
- name: personal
label: Personal Information
fields: [first_name, last_name, phone, date_of_birth]
- name: preferences
label: Preferences
fields: [communication_preference, timezone, language]
- name: documents
label: Documents
fields: [photo, id_document]
- name: terms
label: Terms & Conditions
fields: [terms_accepted, marketing_consent]
fields:
- name: first_name
type: text
label: First Name
required: true
- name: last_name
type: text
label: Last Name
required: true
- name: phone
type: phone
label: Phone Number
- name: date_of_birth
type: date
label: Date of Birth
- name: communication_preference
type: select
label: Preferred Contact Method
options:
- value: email
label: Email
- value: sms
label: SMS
- value: push
label: Push Notification
- name: timezone
type: select
label: Timezone
config:
source: system_timezones
- name: language
type: select
label: Preferred Language
options:
- value: en
label: English
- value: es
label: Spanish
- value: fr
label: French
- name: photo
type: file_upload
label: Profile Photo
config:
max_files: 1
accepted_types: [.jpg, .png]
max_size_mb: 5
- name: id_document
type: file_upload
label: ID Document (optional)
config:
max_files: 1
accepted_types: [.pdf, .jpg, .png]
max_size_mb: 10
- name: terms_accepted
type: checkbox
label: I accept the Terms of Service
required: true
- name: marketing_consent
type: toggle
label: Receive marketing communications
default_value: false
submission_config:
target_collection: user_profiles
require_auth: true
max_submissions_per_user: 1
Example submission
{
"first_name": "Andre",
"last_name": "Campbell",
"phone": "+1-876-555-0198",
"date_of_birth": "1990-04-12",
"communication_preference": "email",
"timezone": "America/Jamaica",
"language": "en",
"photo": "https://cdn.example.com/uploads/profile-andre.jpg",
"id_document": "https://cdn.example.com/uploads/id-andre.pdf",
"terms_accepted": true,
"marketing_consent": false
}

application

form template

Five-step job/vendor application with references and documents, gated by the approval-gate workflow.

application.yaml
kind: form
name: Application
slug: application
schema:
steps:
- name: personal
label: Personal Information
fields: [first_name, last_name, email, phone, address]
- name: qualifications
label: Qualifications
fields: [experience_years, certifications, specialties]
- name: references
label: References
fields: [reference_1_name, reference_1_phone, reference_2_name, reference_2_phone]
- name: documents
label: Documents
fields: [resume, certifications_upload, background_check_consent]
- name: declaration
label: Declaration
fields: [declaration_accepted]
fields:
- name: first_name
type: text
label: First Name
required: true
- name: last_name
type: text
label: Last Name
required: true
- name: email
type: email
label: Email
required: true
- name: phone
type: phone
label: Phone
required: true
- name: address
type: address
label: Address
required: true
- name: experience_years
type: number
label: Years of Experience
required: true
- name: certifications
type: multi_select
label: Certifications
options: []
- name: specialties
type: multi_select
label: Specialties
options: []
- name: reference_1_name
type: text
label: "Reference 1 — Name"
required: true
- name: reference_1_phone
type: phone
label: "Reference 1 — Phone"
required: true
- name: reference_2_name
type: text
label: "Reference 2 — Name"
- name: reference_2_phone
type: phone
label: "Reference 2 — Phone"
- name: resume
type: file_upload
label: Resume / CV
required: true
config:
max_files: 1
accepted_types: [.pdf, .doc, .docx]
max_size_mb: 10
- name: certifications_upload
type: file_upload
label: Certification Documents
config:
max_files: 10
accepted_types: [.pdf, .jpg, .png]
max_size_mb: 10
- name: background_check_consent
type: checkbox
label: I consent to a background check
required: true
- name: declaration_accepted
type: checkbox
label: I declare that all information provided is accurate
required: true
submission_config:
target_collection: applications
trigger_workflow: approval-gate
send_confirmation:
template: application_received
to_field: email
require_auth: false
Example submission
{
"first_name": "Kevon",
"last_name": "Brown",
"email": "kevon.brown@example.com",
"phone": "+1-876-555-0177",
"address": "12 Hope Road, Kingston 6",
"experience_years": 4,
"certifications": ["first_aid", "osha_10"],
"specialties": ["electrical", "plumbing"],
"reference_1_name": "Paula Grant",
"reference_1_phone": "+1-876-555-0122",
"reference_2_name": "Dwight Ellis",
"reference_2_phone": "+1-876-555-0143",
"resume": "https://cdn.example.com/uploads/kevon-resume.pdf",
"certifications_upload": ["https://cdn.example.com/uploads/kevon-cert1.pdf"],
"background_check_consent": true,
"declaration_accepted": true
}

appointment-request

form template

Service and provider booking request, feeding the booking-with-payment workflow.

appointment-request.yaml
kind: form
name: Appointment Request
slug: appointment-request
schema:
fields:
- name: patient_name
type: text
label: Full Name
required: true
- name: email
type: email
label: Email Address
required: true
- name: phone
type: phone
label: Phone Number
- name: service_type
type: relation_picker
label: Type of Service
required: true
config:
collection: services
display_field: name
filter:
status: active
- name: preferred_provider
type: relation_picker
label: Preferred Provider
config:
collection: providers
display_field: name
- name: preferred_date
type: date
label: Preferred Date
required: true
validation:
- rule: min_date
value: "{{ now | add_days: 1 }}"
- name: preferred_time
type: select
label: Preferred Time
required: true
options:
- value: morning
label: "Morning (9am–12pm)"
- value: afternoon
label: "Afternoon (12pm–5pm)"
- value: evening
label: "Evening (5pm–8pm)"
- name: notes
type: textarea
label: Additional Notes
- name: documents
type: file_upload
label: Upload Documents
config:
max_files: 5
accepted_types: [.pdf, .jpg, .png]
max_size_mb: 10
submission_config:
target_collection: appointment_requests
trigger_workflow: booking-with-payment
send_confirmation:
template: appointment_request_received
to_field: email
require_auth: false
Example submission
{
"patient_name": "Simone Clarke",
"email": "simone.clarke@example.com",
"phone": "+1-876-555-0165",
"service_type": "svc_general_checkup",
"preferred_provider": "prov_dr_henry",
"preferred_date": "2026-09-02",
"preferred_time": "morning",
"notes": "First visit, please send intake forms in advance.",
"documents": ["https://cdn.example.com/uploads/insurance-card.jpg"]
}

assessment-report

form template

Two-step field assessment and repair-quote form for authenticated staff.

assessment-report.yaml
kind: form
name: Assessment Report
slug: assessment-report
schema:
steps:
- name: findings
label: Assessment Findings
fields: [description, severity, photos]
- name: quote
label: Repair Quote
fields: [line_items, estimated_hours, materials_cost, labor_cost, total, valid_until]
fields:
- name: description
type: textarea
label: Findings
required: true
- name: severity
type: select
label: Severity
required: true
options:
- value: low
label: "Low — Cosmetic"
- value: medium
label: "Medium — Should Repair"
- value: high
label: "High — Repair Urgently"
- value: critical
label: "Critical — Safety Hazard"
- name: photos
type: file_upload
label: Photos
config:
max_files: 20
accepted_types: [.jpg, .png, .heic]
- name: line_items
type: textarea
label: Line Items
- name: estimated_hours
type: number
label: Estimated Hours
required: true
- name: materials_cost
type: number
label: Materials Cost ($)
required: true
- name: labor_cost
type: number
label: Labor Cost ($)
required: true
- name: total
type: number
label: Total Quote ($)
required: true
disabled_when: always
- name: valid_until
type: date
label: Quote Valid Until
required: true
default_value: "{{ now | add_days: 14 }}"
submission_config:
target_collection: assessment_reports
require_auth: true
Example submission
{
"description": "Water damage under the kitchen sink, cabinet base is soft.",
"severity": "medium",
"photos": ["https://cdn.example.com/uploads/sink-1.jpg", "https://cdn.example.com/uploads/sink-2.jpg"],
"line_items": "Replace cabinet base, reseal pipe fittings, repaint interior",
"estimated_hours": 3,
"materials_cost": 120,
"labor_cost": 210,
"total": 330,
"valid_until": "2026-09-10"
}

Tenant hierarchies· 6

The tenant tree, pre-shaped to your model.

Row-level security isolates tenants; the hierarchy decides how they nest. Pick one of these pre-shaped trees at setup — the real structure below, with a concrete example instance.

org-location

tenant tree

Platform → Organization → Location, three levels with per-level roles and delegation.

org-location.yaml
name: org-location
levels:
- type: platform
label: Platform
roles: [platform_admin, platform_support, platform_billing]
delegation:
allowed_roles: [platform_admin, platform_support]
requires_reason: true
- type: org
label: Organization
roles: [org_admin, org_manager, org_viewer, org_billing]
delegation:
allowed_roles: [org_admin]
requires_reason: true
- type: location
label: Location
roles: [location_admin, staff, staff_limited, customer, guest]
delegation:
allowed_roles: []
Example tree
platform: Acme Health
org: Acme Medical Group
location: Kingston Clinic

org-team

tenant tree

Platform → Organization → Team, the org variant for internal team structures.

org-team.yaml
name: org-team
levels:
- type: platform
label: Platform
roles: [platform_admin, platform_support, platform_billing]
delegation:
allowed_roles: [platform_admin, platform_support]
requires_reason: true
- type: org
label: Organization
roles: [org_admin, org_manager, org_viewer, org_billing]
delegation:
allowed_roles: [org_admin]
requires_reason: true
- type: team
label: Team
roles: [location_admin, staff, staff_limited]
delegation:
allowed_roles: []
Example tree
platform: Acme Corp
org: Acme Engineering
team: Platform Team

marketplace

tenant tree

Platform → Merchant, a flat two-level tree for multi-merchant marketplaces.

marketplace.yaml
name: marketplace
levels:
- type: platform
label: Platform
roles: [platform_admin, platform_support, platform_billing]
delegation:
allowed_roles: [platform_admin, platform_support]
requires_reason: true
- type: merchant
label: Merchant
roles: [merchant_admin, merchant_staff, customer]
delegation:
allowed_roles: []
Example tree
platform: Island Market
merchant: Booth 4 Produce

managed-marketplace

tenant tree

Platform → Organization → Merchant → Customer, four levels for marketplaces with an owning org.

managed-marketplace.yaml
name: managed-marketplace
levels:
- type: platform
label: Platform
roles: [platform_admin, platform_support, platform_billing]
delegation:
allowed_roles: [platform_admin, platform_support]
requires_reason: true
- type: org
label: Organization
roles: [org_admin, org_manager, org_billing]
delegation:
allowed_roles: [org_admin]
requires_reason: true
- type: merchant
label: Merchant
roles: [merchant_admin, merchant_staff]
delegation:
allowed_roles: [merchant_admin]
requires_reason: false
- type: customer
label: Customer
roles: [customer]
delegation:
allowed_roles: []
Example tree
platform: Island Market
org: Island Market Holdings
merchant: Booth 4 Produce
customer: Jane Smith

org

tenant tree

Platform → Organization, the simplest two-level tenant tree.

org.yaml
name: org
levels:
- type: platform
label: Platform
roles: [platform_admin, platform_support, platform_billing]
delegation:
allowed_roles: [platform_admin]
requires_reason: true
- type: org
label: Organization
roles: [org_admin, org_manager, org_viewer, org_billing]
delegation:
allowed_roles: [org_admin]
requires_reason: true
Example tree
platform: Acme Corp
org: Acme Widgets Inc

org-department-team

tenant tree

Platform → Organization → Department → Team, four levels for larger departmental structures.

org-department-team.yaml
name: org-department-team
levels:
- type: platform
label: Platform
roles: [platform_admin, platform_support, platform_billing]
delegation:
allowed_roles: [platform_admin]
requires_reason: true
- type: org
label: Organization
roles: [org_admin, org_manager, org_billing]
delegation:
allowed_roles: [org_admin]
requires_reason: true
- type: department
label: Department
roles: [org_manager, org_viewer]
delegation:
allowed_roles: [org_manager]
requires_reason: true
- type: team
label: Team
roles: [staff, staff_limited]
delegation:
allowed_roles: []
Example tree
platform: Acme Corp
org: Acme Manufacturing
department: Operations
team: Night Shift

App starters· 5

Whole apps, assembled and ready.

Each starter is a strategy plus the presets, roles, and tenant tree that make it a real app. The exact composition below, and what it stands up in an empty workspace.

medical-booking

app starter

Consumer-auth booking app for medical practices: org-location hierarchy, authorize-then-capture payments.

medical-booking.yaml
name: medical-booking
auth_strategy: consumer
hierarchy_template: org-location
modules: [iam, tenant, comms, storage, vault, events, plugins, analytics, audit, data, workflows, observe]
presets:
storage: user-content
payments: authorize-then-capture
rate_limiting: consumer-app
automations: [transactional-basics, booking-lifecycle]
strategies:
- name: booking
params:
currency: USD
role_presets: [location_admin, staff, staff_limited, customer]
workflow_templates: [booking-with-payment]
form_templates: [appointment-request, feedback]
Stands up
roles: location_admin, staff, staff_limited, customer
collections: appointment_requests, reviews
workflow: booking-with-payment
strategy: booking (currency: USD)

home-services

app starter

Consumer-auth job app for home-service businesses: org-location hierarchy, deposit-then-balance payments.

home-services.yaml
name: home-services
auth_strategy: consumer
hierarchy_template: org-location
modules: [iam, tenant, comms, storage, vault, events, plugins, analytics, audit, data, workflows, observe]
presets:
storage: user-content
payments: deposit-then-balance
rate_limiting: consumer-app
automations: [transactional-basics, job-lifecycle]
strategies:
- name: booking
params:
currency: USD
role_presets: [location_admin, staff, customer]
workflow_templates: [multi-stage-job]
form_templates: [assessment-report, contact, feedback]
Stands up
roles: location_admin, staff, customer
collections: assessment_reports, inquiries, reviews
workflow: multi-stage-job
strategy: booking (currency: USD)

marketplace

app starter

Consumer-auth multi-merchant marketplace: flat marketplace hierarchy, pay-now payments, e-commerce strategy.

marketplace.yaml
name: marketplace
auth_strategy: consumer
hierarchy_template: marketplace
modules: [iam, tenant, comms, storage, vault, events, plugins, analytics, audit, data, workflows, observe]
presets:
storage: media-heavy
payments: pay-now
rate_limiting: consumer-app
automations: [transactional-basics, commerce-alerts]
strategies:
- name: e-commerce
params:
currency: USD
role_presets: [merchant_admin, merchant_staff, customer]
workflow_templates: [refund-request]
form_templates: [feedback, contact]
Stands up
roles: merchant_admin, merchant_staff, customer
collections: reviews, inquiries
workflow: refund-request
strategy: e-commerce (currency: USD)

saas-starter

app starter

Hybrid-auth SaaS app: org-team hierarchy, subscription payments, catalog strategy for subscription listings.

saas-starter.yaml
name: saas-starter
auth_strategy: hybrid
hierarchy_template: org-team
modules: [iam, tenant, comms, storage, vault, events, plugins, analytics, audit, data, workflows, observe]
presets:
storage: user-content
payments: subscription
rate_limiting: api-platform
automations: [transactional-basics, commerce-alerts]
strategies:
- name: catalog
params:
listing_types: [subscription]
currency: USD
role_presets: [org_admin, org_manager, org_viewer]
workflow_templates: [onboarding]
form_templates: [onboarding-profile, contact, feedback]
Stands up
roles: org_admin, org_manager, org_viewer
collections: user_profiles, inquiries, reviews
workflow: onboarding
strategy: catalog (listing_types: [subscription], currency: USD)

booking-generic

app starter

Consumer-auth generic booking app: org-location hierarchy, pay-now payments, booking strategy.

booking-generic.yaml
name: booking-generic
auth_strategy: consumer
hierarchy_template: org-location
modules: [iam, tenant, comms, storage, vault, events, plugins, analytics, audit, data, workflows, observe]
presets:
storage: user-content
payments: pay-now
rate_limiting: consumer-app
automations: [transactional-basics, booking-lifecycle]
strategies:
- name: booking
params:
currency: USD
role_presets: [location_admin, staff, customer]
workflow_templates: [booking-with-payment]
form_templates: [appointment-request, feedback, contact]
Stands up
roles: location_admin, staff, customer
collections: appointment_requests, reviews, inquiries
workflow: booking-with-payment
strategy: booking (currency: USD)

Pick a strategy and switch it on.

Create a workspace, declare one of these, and watch a real, tenant-isolated backend come up. No rebuild, no redeploy.