Declarative CRM
Pharos supplies database, query, action, policy and UI capabilities for managing contacts, organizations, leads, records, notes and follow-ups.
Declare entities, relationships, permissions, routes and presentation in the application contracts. The runtime executes validated queries, transactional writes, access checks and rendering through the configured database adapter.
Available capabilities
| Capability | App declaration | Shared framework behavior | | --- | --- | --- | | Contacts, organizations and leads | Logical entities, typed fields, identities, indexes and migrations | Durable reads and writes through the configured SQLite, PostgreSQL, MySQL or MongoDB adapter | | Create, read and update | Feature queries, validated action inputs and transaction steps | Request dispatch, input validation, parameterized database operations and transactional execution | | Search, filters and ordering | Declared query filters, field ordering and bounded result limits | Database-backed selection and rendering of the requested records | | Status and follow-up tracking | Enumerated input values, status fields and validated date fields | Rejection of invalid values before mutation; persistence of accepted changes | | Notes and activity history | Note/activity entities, validated parent relationships, actor and timestamp mappings | Atomic record/history writes through declared transactions | | Linked records | References, relationship entities and scoped queries | Generic relationship operations and database constraint enforcement | | Archive, restore and removal | Status actions or generated soft-delete surfaces; explicit relationship guards | Declared lifecycle changes and rejection of protected deletions | | Concurrent editing | Record revision field, submitted previous revision and matching read/update filters | Execution of guarded transactions without silently accepting an unguarded overwrite | | Administration UI and APIs | App-owned pages, forms, templates and fragments, or generated_admin/generated_api surfaces | Shared routing, rendering, generated CRUD execution and fragment responses | | Access control | Session requirements, policies, capabilities and owner/tenant filters | Authentication, CSRF checks and enforcement of declared access boundaries |
Tags, consent flags, customer/account links and pipeline stages use declared fields and relationships. Configure their validation and UI controls explicitly. Follow-up reminders use separately configured jobs and notification actions.
Contract layout
Use the same source layout as other dynamic Pharos apps:
pharos.app.yml
data/contracts/database.yml
data/contracts/policies.yml
data/contracts/routes.yml
data/contracts/actions.yml
data/features/contacts.yml
templates/contacts.html
templates/fragments/contact_row.html
templates/fragments/contact_form.html
The manifest selects the logical database contract and, when feature bundles are used, their source pattern:
contracts:
feature_sources: data/features/*.yml
route_contract: data/contracts/routes.yml
action_contract: data/contracts/actions.yml
policy_contract: data/contracts/policies.yml
database:
migration_contract: data/contracts/database.yml
Feature bundles compile into the ordinary route and action contracts. YAML database authoring compiles into the JSON contract consumed by the database runtime. Keep one manifest-selected logical contract and preserve existing migration IDs and applied checksums when adding CRM entities.
Declare records and relationships
Define each entity under schema.entities in database.yml, with its identity, typed fields and bounds. A contact commonly needs an integer identity, name, email, optional phone/company, status, follow-up date and record revision. Declare owner or tenant identity fields when records are scoped to an owner or organization.
Use separate entities for notes, activity records and many-to-many relationships. Give notes a parent relationship, body, actor identity and creation/update timestamps. Give activity rows a parent relationship, summary, actor and timestamp. Where a note can refer to multiple entity types, validate the declared parent kind and the actual parent record before insertion; a free-form parent ID alone does not establish a valid relationship.
Add logical migration changes for the declared schema, seeds and relationships. Preserve existing migration IDs and applied checksums.
Declare validated actions
This feature-source excerpt illustrates input validation and insertion for an app that has already declared a contact entity, an owner policy and the referenced page/fragment queries. It is not a complete app contract:
actions:
contact_create:
method: POST
policy: owner
path: /admin/contacts/create
input:
name: {type: string, required: true, max_length: 300}
email: {type: string, required: false, format: email, max_length: 254}
status: {type: enum, required: true, values: [prospect, active, inactive], max_length: 32}
follow_up: {type: string, required: false, format: date, max_length: 100}
transaction:
steps:
- insert:
entity: contact
values:
name: input.name
email: input.email
status: input.status
follow_up: input.follow_up
owner_user_id: subject.user_id
record_updated_at: generated.record_revision
response:
render: contacts_list
target: app-main
queries: [contacts_records]
fallback: {redirect: /admin/contacts}
Map owner identity from trusted session context, not a submitted owner ID. Apply the same owner/tenant scope to list/detail queries, updates, note creation and deletion. An owner role grants a capability; it does not by itself constrain every query to that owner's records.
For activity history, add an insert step to the same transaction and map actor_user_id to subject.user_id and created_at to server.now. When inserting a parent and its history together, an insert can declare bind: created_record; later steps can read query.created_record.id. App declarations determine which operations create history; the framework does not automatically audit every CRM change.
For edits, return the current revision in the form as previous_updated_at. Match both the record ID and revision in the selected-record query and update filters, require the selected record before mutation, and write a new generated.record_revision on success. Refresh the authoritative record and report a conflict when the submitted revision is stale. Do not remove revision guards to make a stale save succeed.
Archive linked records when deleting them would discard required history. Declare permanent deletion only with the necessary relationship checks; generated soft-delete surfaces also support restore. See Feature Transaction Primitives, Generated Surfaces and Feature Input Formats.
Compose the UI
Declare list/detail pages and queries, record forms, named fragments and their response targets. Search, status/type filters and ordering controls supply validated query inputs. Refresh the list or selected-record fragment after a successful action rather than maintaining a second browser-side record store.
Use app-owned HTML and shared Pharos controls for compact rows, expandable details, editable fields, icon buttons and notes. Preserve editable row/container/control grouping so Architect can select and move them. Generate admin/API surfaces when their declared CRUD model fits; feature-source pages and generated surfaces are distinct authoring paths that share framework services.
Keep contact details, consent, notes and activity behind their declared policies. Public record cards should use explicit public projections, not the complete private CRM query. Never send private notes or credentials to social collection or model context merely because an app also enables those services.
Integrations and operation
Social connections are a separate opt-in framework capability. Apps choose how retained observations relate to their records. A CRM contact does not automatically become a collection target, and a connected social account does not authorize publication of private content. Likewise, outbound email, background work and external integrations require their own declarations and configuration; adding a CRM note sends no message.
Build the app and apply its logical migrations using the normal Pharos CLI. Keep databases, uploads, state and credentials outside installed app bundles. Test authorized CRUD, denied access, malformed inputs, stale saves, archive/delete relationships, history retention and fragment refreshes against the configured backend before accepting an app's CRM workflow.
References
- Contract, UI, and Surface Reference: entity, query, policy, form, action and UI declaration shapes.
- Identity and tenancy: trusted identities and access boundaries.
- Generated Surfaces: generated admin/API CRUD and soft-delete/restore.
- Feature Transaction Primitives: scoped updates, deletes, relationship changes and guarded transactions.
- Feature Input Formats: email, date, timestamp and URL validation.
- UI Surface Library: shared controls and app-owned composition.