Generated Surfaces
Pharos provides a framework-owned generated surface runtime for tenant-scoped CRUD entities.
The implementation is contract-driven:
- app fixtures declare the surface contract
- shared Pharos runtime code matches routes, enforces policy, validates input, and performs state mutation
- apps only supply fixtures, forms, policies, and host wiring
The executable generated surface kinds are:
generated_apigenerated_admin
Related framework-owned execution families such as declarative actions, workflows, bounded side effects, webhook delivery, and jobs operator reporting are documented through their own contracts rather than this generated-surface fixture envelope.
Only the surface kinds listed above are currently executed by the shared runtime path.
Do not confuse generated surfaces with feature-source bundles. Feature bundles reduce repeated route, page, and fragment-response wiring inside data/features/*.yml, then compile down to ordinary route and action contracts. Generated surfaces remain the framework-owned CRUD execution family described in this document.
When to use generated surfaces
Use generated surfaces when the app needs framework-owned CRUD behavior derived from declared entities, forms, routes, and scoped context rather than hand-authoring every management or API endpoint shape separately.
Use the shipped examples in ucal when you need a larger proving app, and use the broader fixture reference in the App surface reference when you need parameter-level detail.
Validation path
Use this path to validate generated surfaces:
- Open
apps/ucal/data/contracts/routes.yml. - Open
apps/ucal/data/contracts/actions.yml. - Build the app.
- Run the tenant API proof.
Commands:
bin/pharos build app ucal --build-dir dist
sh scripts/check.sh ucal tenant-api
Expected result:
- generated CRUD routes execute through the shared runtime
- default generated mutation routes do not need to be repeated manually in app
- soft-delete entities gain framework-owned restore behavior
- wrong-content-type generated mutation requests are rejected through the
source when the generated surface already defines them
request-hook contract
Fixture envelope
A generated surface fixture is a YAML document with a surfaces array:
fixture_id: generated_surface_fixture_v1
surfaces:
- surface_id: service_generated_api
surface_kind: generated_api
entity_id: service
Each executable surface declares the entity it manages, the scoped context it lives under, the operations it exposes, and the validation and projection rules that the framework must honor.
Required runtime contract shape
Executable generated surfaces use these core fields:
surface_id: service_generated_api
surface_kind: generated_api
entity_id: service
entity_collection: services
form_id: service_form
identity_field: id
identity_param: service_id
scope_field: business_id
context_binding:
state_collection: businesses
param: business_slug
match_field: slug
context_id_field: id
context_label_field: name
tenant_collection: tenants
tenant_match_field: source_business_id
tenant_match_from_context_field: id
operations:
list:
method: GET
path: /api/businesses/{business_slug}/services/
capability: manage_services
Core fields
surface_id: stable generated surface idsurface_kind:generated_apiorgenerated_adminentity_id: logical entity nameentity_collection: state collection mutated by the runtimeform_id: shared form contract used for validation/input extractionidentity_field: record key field, usuallyididentity_param: route parameter for item-level operationsscope_field: tenant or parent-context foreign key written into records
Context binding
context_binding resolves the route context and tenant scope from state:
state_collection: collection to search for the context recordparam: route parameter namematch_field: record field matched against the route parametercontext_id_field: field copied into the generated record scopecontext_label_field: label returned in API responsestenant_collection,tenant_match_field,tenant_match_from_context_field: optional tenant lookup fields for capability enforcement
If the bound context is missing, the runtime returns a clear not-found error instead of mutating global state.
Operations
Operation entries define route matching and policy binding.
When an app ships the default generated-admin or generated-API shape, Pharos can derive the matching build-artifact routes instead of requiring the source route fixture to repeat every create, update, and delete mapping manually. The lower-level route artifact remains inspectable in built data/contracts/routes.json.
Generated API
Supported API operations:
listschemacreateupdatedeleterestorefor soft-delete entities
When the app-derived generated API surface is present, the default mutation routes are now build-time derivable:
<entity_id>_api_create<entity_id>_api_update<entity_id>_api_delete<entity_id>_api_restorewhen the entity usessoft_delete_field
Those default routes target the ordinary action ids:
create_<entity_id>update_<entity_id>delete_<entity_id>restore_<entity_id>when the entity usessoft_delete_field
Derived generated API mutation routes also carry the narrow request-admission hook json_body_required, so the shared runtime rejects non-JSON mutation requests before the generated API handler runs.
Example:
operations:
list:
method: GET
path: /api/businesses/{business_slug}/services/
capability: manage_services
schema:
method: GET
path: /api/businesses/{business_slug}/services/schema/
capability: manage_services
create:
method: POST
path: /api/businesses/{business_slug}/services/
capability: manage_services
update:
method: POST
path: /api/businesses/{business_slug}/services/{service_id}/
capability: manage_services
delete:
method: POST
path: /api/businesses/{business_slug}/services/{service_id}/delete/
capability: manage_services
restore:
method: POST
path: /api/businesses/{business_slug}/services/{service_id}/restore/
capability: manage_services
Generated admin
Supported admin operations:
managecreateeditupdatedeleterestorefor soft-delete entities
When the app-derived generated admin surface is present, the default child routes are now build-time derivable from the manage route:
<prefix>_create<prefix>_edit<prefix>_update<prefix>_delete<prefix>_restorewhen the entity usessoft_delete_field
where <prefix> is the manage route id without the trailing _manage.
Example:
operations:
manage:
method: GET
path: /businesses/{business_slug}/services/
capability: manage_services
create:
method: POST
path: /businesses/{business_slug}/services/new/
capability: manage_services
edit:
method: GET
path: /businesses/{business_slug}/services/{service_id}/edit/
capability: manage_services
update:
method: POST
path: /businesses/{business_slug}/services/{service_id}/edit/
capability: manage_services
delete:
method: POST
path: /businesses/{business_slug}/services/{service_id}/delete/
capability: manage_services
Every operation may declare a capability. When present, Pharos enforces authentication plus tenant-aware policy evaluation before serving the generated surface.
Authoring checklist
For one generated surface family, make sure the app declares all of these together:
- the entity or collection being managed
- the form contract used for input validation
- the generated surface declaration itself
- the route declarations binding request paths to that surface
- any scoped context-binding values needed for parent or tenant resolution
- any app-owned templates or schema surfaces referenced by the generated path
If one of these pieces is missing, the app may still build partway through authoring, but the generated surface contract will not be complete.
Collection behavior
Generated list surfaces support framework-owned filtering, sorting, projection, and pagination.
Response projection
response_fields controls the JSON fields returned by generated API list responses:
- id
- business_id
- name
- cost
- duration_minutes
- is_active
Filters
filters configures query-string filters:
- query: name
field: name
match: contains
label: Service name
- query: is_active
field: is_active
match: boolean
default: 'true'
label: Status
Supported filter match kinds:
exactcontainsbooleanarray_contains_integer
A filter entry may also declare an options array. When present on generated admin surfaces, Pharos renders that filter as a shared framework-owned <select> instead of a free-text input.
Working examples
Use these shipped surfaces when authoring or reviewing generated behavior:
apps/ucal/data/contracts/routes.ymlapps/ucal/data/contracts/forms.ymlapps/ucal/data/contracts/form-fields.ymlapps/ucal/data/contracts/entity-fields.ymlapps/ucal/templates/api/*.schema.json
Use the fixture and surface list when you want the broader contract inventory. Use the UCAL build and validation commands on this page when you want the exact local proof path.
Sorts
sorts plus default_sort define stable collection ordering:
- key: sort_order_asc
field: sort_order
direction: asc
type: number
label: Display order
- key: name_asc
field: name
direction: asc
type: string
label: Name
Supported sort types:
stringnumberweekday
A sort entry may also declare secondary_field plus optional secondary_type to keep schedule-like collections stable within the primary ordering.
Pagination
page_size_default and page_size_max control generated collection pagination.
The generated API list response includes:
itemspagination.pagepagination.page_sizepagination.total_itemspagination.total_pagessort
Write behavior
Generated writes compose shared Pharos validation, relation checks, uniqueness checks, audit logging, and state mutation.
Defaults and coercion
defaultssupplies framework-owned default valuesfield_typescoerces integers, booleans, and integer arraysauto_sequence_fieldassigns the next in-scope order value on createsoft_delete_fieldmarks records inactive instead of removing themrestoresets the configuredsoft_delete_fieldback to its active value
Validation
Generated surfaces reuse the shared form contract and validation runtime, then layer in surface-specific rules:
field_constraintsrelation_inputsuniqueness_rules
Example:
field_constraints:
- field: cost
kind: decimal
rule_id: service_cost_decimal
relation_inputs:
- field: technician_ids
related_collection: technicians
related_scope_field: business_id
label_field: name
form_checkbox_prefix: technician_ids__
invalid_rule_id: service_technician_ids_invalid
scope_rule_id: service_technician_scope_invalid
uniqueness_rules:
- fields:
- business_id
- name
casefold_fields:
- name
rule_id: service_name_unique
message: A service with that name already exists for this business.
Supported field constraint kinds:
positive_integerdecimalemailtimedateenum
Audit events
Generated writes append audit entries to audit_log. Surface fixtures can override the emitted event names:
audit_events:
create: generated_service_created
update: generated_service_updated
delete: generated_service_deactivated
Generated admin presentation
generated_admin surfaces may define an admin block for titles, navigation, list columns, deletion behavior, presentation copy, and generated-surface composition hints:
admin:
list_title: Generated Service Admin
list_intro: Framework-owned generated admin surface for business services.
back_path_template: /businesses/{business_slug}/
create_title: Create service
item_title_field: name
list_columns:
- field: sort_order
label: Display order
- field: name
label: Name
- field: cost
label: Cost
- field: duration_minutes
label: Duration
When list_columns is omitted, Pharos derives a short default list view from response_fields.
The admin block may also override presentation text such as:
filter_titlerecords_titlecreate_titlecreate_button_labelsave_button_labelempty_state_textdelete_button_label
Admin surfaces may also point at an app-owned composition source under the app root:
admin:
composition_source: generated_admin/service.yaml
The native loader consumes the JSON-compatible YAML subset for these files so apps can keep composition hints in generated_admin/ without adding app-local runtime code.
Composition sources can override generated-admin section order and records presentation, including:
page_wrapper_classfilter_form_classcreate_form_classsection_order- per-section
class - per-section
title - per-section
intro - per-section
form_class - records-section
records_view - records-section
records_wrapper_class - records-section
record_card_class
Current records views are:
tablecard_griddetail_list
Admin surfaces may also declare delete_strategy:
softor omitted: mark the configuredsoft_delete_fieldinactivehard: remove the matching record from the collection entirely
Restore routes are emitted only for the soft lifecycle path. Entities that declare delete_strategy: hard do not receive restore operations.
Relation inputs automatically render as scoped checkbox groups in generated admin forms.
Form fields with widget: "select" and an options array render as framework-owned <select> inputs in generated admin forms. This is the shared path used by UCAL weekday availability management.
Form-field presentation may also be customized in the shared path with:
labelhelp_textplaceholder
Relation-input groups may override shared presentation with:
labelhelp_textempty_options_text
Response contracts
Generated API responses use framework-owned envelopes:
- list:
pharos.generated.api.list.v1 - item:
pharos.generated.api.item.v1 - schema:
pharos.generated.api.schema.v1
Validation failures return the shared pharos.validation.report.v1 envelope. Authorization and authentication failures return the shared pharos.error.v1 envelope.
CLI surfaces
Pharos exposes reporting commands for generated surfaces:
pharos/bin/pharos api generate app ucal --build-dir pharos/dist
pharos/bin/pharos api schema app ucal --build-dir pharos/dist
pharos/bin/pharos admin generate app ucal --build-dir pharos/dist
Use these commands to inspect which generated surfaces the framework sees for an app and which schema contract a generated API will publish.
Job queue admin surfaces
Queue management is a framework-owned runtime surface, not an entity-generated pipeline, because job_execution is a runtime JSON state array rather than an app-owned relational entity. UCAL ships the reference wiring:
- Routes in
apps/ucal/data/contracts/routes.yml: - Capabilities:
view_job_queue(staff_admin, business_owner),manage_job_queue(staff_admin) inpolicy-fixture-v1.yml, withguarded_actionscancel_queue_job/retry_queue_job - Page specs and body templates under
apps/ucal/templates/queue/ - Page params builders
job_queue_list_page_params_json_dup_runtime/job_queue_detail_page_params_json_dup_runtimeinjson_runtime_ops.c, wired intoresolve_declared_page_params_json_runtime - Action handlers
cancel_queue_job/retry_queue_jobinhandle_action_routereusenative_dynamic_job_cancel_report_json/native_dynamic_job_retry_report_jsonand redirect back to the detail page
- GET /admin/jobs/ — queue list filtered by execution_status, paginated, searchable by job_id/handler_id - GET /admin/jobs/:job_id/ — job detail with job_execution_attempt history, payload, and error messages - POST /admin/jobs/:job_id/cancel/ — cancel a queued job (CSRF-protected) - POST /admin/jobs/:job_id/retry/ — re-enqueue a failed job (CSRF-protected)
The cancel and retry actions record job_cancelled / job_retried workflow execution events, matching the observability contract of the job queue.
Current app coverage
UCAL currently exercises the shared framework path on three tenant-scoped entities:
- services
- technicians
- business availability
All three run through the same framework-owned runtime for:
- generated admin
- generated API
- auth-aware policy checks
- HTML and JSON validation parity
- filtering, sorting, and pagination
- soft delete behavior
- hard delete behavior where explicitly configured
- schema export
- enum-backed validation rules
- weekday plus secondary-field sorting for schedule-like collections
The reference app supplies declarative fixtures and templates. Generated CRUD behavior lives in shared Rust framework code under src/runtime/ with the compiled pharos CLI as the command surface.