Pharos Framework

← Back

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

Today the runtime supports these executable generated surface kinds:

  • generated_api
  • generated_admin

Other generated surface kinds may still appear in app graph fixtures as forward-looking contract nodes, but they are not yet executed by the shared runtime path.

Fixture envelope

A generated surface fixture is a JSON 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

The current runtime expects these core fields for executable generated surfaces:

{
  "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 id
  • surface_kind: generated_api or generated_admin
  • entity_id: logical entity name
  • entity_collection: state collection mutated by the runtime
  • form_id: shared form contract used for validation/input extraction
  • identity_field: record key field, usually id
  • identity_param: route parameter for item-level operations
  • scope_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 record
  • param: route parameter name
  • match_field: record field matched against the route parameter
  • context_id_field: field copied into the generated record scope
  • context_label_field: label returned in API responses
  • tenant_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.

Generated API

The current runtime supports these API operations:

  • list
  • schema
  • create
  • update
  • delete

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"
    }
  }
}

Generated admin

The current runtime supports these admin operations:

  • manage
  • create
  • edit
  • update
  • delete

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.

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 today:

  • exact
  • contains
  • boolean
  • array_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. This is the proving path used for UCAL weekday availability filtering.

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 today:

  • string
  • number
  • weekday

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:

  • items
  • pagination.page
  • pagination.page_size
  • pagination.total_items
  • pagination.total_pages
  • sort

Write behavior

Generated writes compose shared Pharos validation, relation checks, uniqueness checks, audit logging, and state mutation.

Defaults and coercion

  • defaults supplies framework-owned default values
  • field_types coerces integers, booleans, and integer arrays
  • auto_sequence_field assigns the next in-scope order value on create
  • soft_delete_field marks records inactive instead of removing them

Validation

Generated surfaces reuse the shared form contract and validation runtime, then layer in surface-specific rules:

  • field_constraints
  • relation_inputs
  • uniqueness_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 today:

  • positive_integer
  • decimal
  • email
  • time
  • date
  • enum

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, and presentation copy:

{
  "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_title
  • records_title
  • create_title
  • create_button_label
  • save_button_label
  • empty_state_text
  • delete_button_label

Admin surfaces may also declare delete_strategy:

  • soft or omitted: mark the configured soft_delete_field inactive
  • hard: remove the matching record from the collection entirely

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:

  • label
  • help_text
  • placeholder

Relation-input groups may override shared presentation with:

  • label
  • help_text
  • empty_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:

sh pharos/scripts/pharos_runtime.sh api generate app ucal --build-dir pharos/dist --text
sh pharos/scripts/pharos_runtime.sh api schema app ucal --build-dir pharos/dist --text
sh pharos/scripts/pharos_runtime.sh admin generate app ucal --build-dir pharos/dist --text

Use these commands to inspect which generated surfaces the framework sees for an app and which schema contract a generated API will publish.

Current proof coverage

UCAL currently proves 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 proving app only wires fixtures and host dispatch into Pharos. The generated CRUD behavior itself lives in shared framework code under pharos/runtime/ and pharos/scripts/.