Architecture
The Soccorso framework, its entry points and routing, and where each kind of thing lives.
CCC runs on Soccorso, a deliberately minimal in-house plain-PHP framework. No Composer, no autoloader, no namespaces in application code, and no dependency beyond a hand-vendored template engine. That is a decision, not an accident: the constitution treats simplicity as a feature and overengineering as a defect.
AGENTS.md is the authority on all of this. This page is the orientation; it does not restate the rules.
Where things live
| Path | Holds |
|---|---|
public/ | Entry points and static assets. The docroot |
app/models/ | Every class. One lowercase class per file, filename matching the class |
app/http/ | Page controllers, one directory per module, one file per page |
app/data/ | JSON endpoints feeding the tables on those pages |
app/api/ | Token-guarded external endpoints |
app/public/ | Token-guarded embeddable dashboards |
app/jobs/ | Batch jobs, grouped per source or per step |
config/ | Reference data, seeds, credentials and the cron inventory |
templates/ | Templates, mirroring app/http/ |
Entry points
Each is a separate front door with its own guard. There is no shared bootstrap; each defines the constants it needs.
The main application is session, database, templating, authorisation, router. The JSON endpoint applies the same per-page permission. The API and the public dashboards use tokens instead. The job runner and the CSV exporter each have their own guard.
A route is always resolved against a set derived from disk, then required. Building a path from the request and testing whether it exists is not a guard, and this rule applies to any new entry point.
Routing
The first URI segment matching a directory is the module; the next segment is the page. Remaining segments stay available to the page for sub-views.
The consequence that catches people: a folder inside a module is not a URL segment. /dictionary/classifications/transactions works because classifications is a page file and transactions is a sub-view, not because classifications is a folder.
Page files run in the router's scope, so the database, templating, user and router variables are ambient. That is the pattern; keep it.
Models
Most data models take the database in their constructor, expose a public property per result set, and fill it through getters named for the thing they fetch. Filters accumulate into a list and are joined; paging is a cast limit. Disk-only docs, Markdown and settings models need no database; upstream clients have their own construction patterns. Copy the relevant model rather than imposing a database constructor on every class.
SQL is written inline as strings. There are no prepared statements and no transactions. Two rules make that safe and both are in AGENTS.md: escaping protects a value only inside quotes, and an identifier is checked against the real set rather than escaped.
Jobs and queues
Most jobs consume the batches queue. See the pipeline for what that means operationally.
A recurring job exists in four places and all four are easy to forget: the file, its schedule line, its entry in the cron inventory, and a healthcheck where its output matters. Nothing detects a missing one at runtime. AGENTS.md flow step 7 is the checklist.
Deployment
Merging to the acceptance branch deploys acceptance; merging to the main branch deploys production. A merge is a release. The deploy reinstalls the schedule and regenerates the permission matrix, so neither is a manual step on those environments.
Related
Evidence
Framework constraints from .specify/memory/constitution.md, principles II and III. Layout, entry points, routing, model shape and the four-places job rule from AGENTS.md. Basis: code-checked at 30f5313. AGENTS.md remains the authority; where this page and it disagree, it wins.
- Type
development - Status
active - Updated
2026-09-14 - Created
2026-09-13