The Node content pipeline (agents/) and the astroagent in-site authoring console are a proprietary, CLI-strategy agent system that shouldn't ship in the cloneable SeedProject base. Remove both and all their wiring: - Delete agents/ (pipeline scripts, prompts, admin server, libs) + runtime - Delete astroagent config, app/.astroagent/ skill, DevConsole component; unhook it from BaseLayout.astro and astro.config.mjs preview env logic - Delete api/cli/resources.php (DB->pipeline resource bridge) - Drop agent blocks from configure.mjs, agent runtime rules from .gitignore, topic/audience from site.config.json, agent prompts from new-site.sh - Strip agent sections from AGENTS.md / README.md / api/.memory/foundation.md Kept: api/app/LLM/* (API-key/REST multi-provider layer — distinct from the CLI agents), api/cli/rebuild.php, and the mde_resources schema. Frontend build verified (npm run build → 14 pages). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfzaSFv5okUxCCdvq5RXu1
11 KiB
Foundation — SeedProject as a per-project Astro backend
Status: Design (approved, not yet implemented) · Date: 2026-07-04 Related: documentation.md (framework internals) · llm.md (LLM layer) · changelog.md
Context & goal
SeedProject (this api/ framework) is mounted at comiida.com/api/ and boots
(see the 2026-07-04 changelog). The goal now: make it a solid, reusable backend
platform that a static Astro frontend calls to reach a database, run agents,
trigger CLI processes, and record metrics.
That vision is four subsystems on a shared base. This doc specifies only the Foundation (sub-project #1) — the database + a secure, reproducible, reusable request round-trip. Metrics, agents, and CLI business commands are separate specs built on top of it.
Locked decisions
| Question | Decision |
|---|---|
| Reuse model | Per-project copy — each Astro site gets its own api/ install + own DB. A drop-in starter, fully isolated. |
| Auth | Two-tier — public (origin + rate-limit) for browser beacons; privileged (bearer token / API key) for reads/admin. |
| Schema mgmt | Versioned SQL migrations via CLI — install/dump.sql baseline + db/migrations/NNN_*.sql applied by php console db:migrate. |
| Install | CLI-first (php console app:install); the existing web wizard is fixed but hard-locked. |
Deployment facts (this project)
- Web server: Apache (
/www/server/apache), DocumentRoot =.../public(static Astro build)./apiis served via anAlias→.../apiwith a php-fpm 8.3 handler and front-controller rewrite. (nginx conf in the BT panel is inert.) - DB engine: MariaDB 10.11 on
/tmp/mysql.sock(BT-managed). Foundation provisions a dedicatedcomiidadatabase + user. - Frontend calls are browser-side, same-origin
fetch("/api/…")(comiida is static; there is no Astro SSR). This is why the public tier is origin/rate-limited rather than server-secret-authenticated.
Architecture
Browser (static Astro page)
│ fetch("/api/health") same-origin
▼
Apache :443 ──Alias /api──▶ api/index.php ──▶ core/Bootstrap ──▶ Router
│ │
│ (php-fpm 8.3 via /tmp/php-cgi-83.sock) ┌────────────────┴───────────────┐
│ ▼ ▼
│ PublicController ApiController
│ (origin + rate limit) (bearer / api_auth
│ │ + plan limits + logging)
│ ▼ ▼
│ App logic ───────▶ Db helper ───▶ MariaDB (comiida)
▼
JSON envelope { ok, data, error }
The Foundation adds five units, each independently understandable/testable:
- Installer — provisions DB + config (CLI + locked web wizard share one service).
- Migrations runner — versioned schema evolution.
- Two-tier auth —
PublicController+ApiControllerbase classes. - Health round-trip — a real end-to-end proof endpoint.
- Security hardening — Apache denies, install lock, crypto keys.
1. Install (CLI-first)
Problem with the shipped installer (install/controllers/index.php): it reads
dump.sql/writes config.php via $_SERVER['DOCUMENT_ROOT'] (wrong under the
/api Alias — that's comiida's public/); it generates a config with SITE_BASE '/',
ASSETS '/public/assets/', seedproject.com (would clobber the correct /api
config); it is web-reachable and re-runnable; and getName() uses non-crypto
rand() over an alphabet containing ', \, / (a quote breaks the single-quoted
config.php → PHP syntax error).
Design:
app/Services/Installer.php— one service both entrypoints call (DRY). Usesdirname(__DIR__, 2)base paths, neverDOCUMENT_ROOT. Responsibilities:- Validate + test DB connection (PDO).
- Import
install/dump.sql(the skeleton). - Generate keys with
bin2hex(random_bytes(32))(crypto-safe; hex only — no quote bug). - Write
api/config.phpwith the correct/apiconstants (SITE_BASE=/api,ASSETS=/api/public/assets/,URL,PROJECT_NAMEfrom args/env; DB creds). - Record the baseline as applied, then run pending migrations.
- Write install lock
api/system/.installed.
commands/InstallCommand.php—php console app:install(Symfony Console, registered inconsolenext toGreetCommand). Reads creds from options (--db-host --db-name --db-user --db-pass --url --name) or interactive prompt; refuses to run if.installedexists unless--force.- Web wizard (
install/) — refactored to callInstaller(fixes its path bugs); refuses to run when.installedexists; denied at the Apache layer by default.
Config stays as config.php (framework-native, already gitignored) — no .env
loader is introduced (YAGNI). The stray CreditPullEngine .env remains vestigial and
should be deleted/rotated by the operator.
2. Schema & migrations
install/dump.sql= baseline (imported once). Tables already provided:users*,roles/permissions/role_perm(RBAC),org*(orgs/profiles),api_auth/users_api(API keys),api_plans(rate limits),api_requests(request log),api_usage(daily/monthly/yearly counters),config(key/value).db/migrations/NNN_description.sql— ordered, forward-only SQL files.migrationstracking table (id, filename, applied_at) — created by the installer; baseline recorded so it is never re-run.commands/MigrateCommand.php—php console db:migrateapplies unapplied files in filename order, each in a transaction where the DDL allows; records each.php console db:migrate --statuslists applied/pending.
3. Two-tier auth + response contract
Reuses the existing tables — no duplication.
ApiController(base, privileged) — verifies a bearer token: either theADMIN_TOKEN(config constant) for first-party/admin calls, or anapi_auth.apikeyfor programmatic consumers. On an API key: checkactive, enforceapi_plans.limit_minute/limit_monthlyagainstapi_usage, and log toapi_requests. Failures →401(missing/invalid) or429(over limit).PublicController(base, public) — for browser beacons. VerifiesOrigin/Refereragainst a configALLOWED_ORIGINSallowlist, applies IP-based rate limiting, requires no user key. Failures →403(bad origin) or429(flood). Emits same-origin CORS headers.- Response envelope — a shared
json($data, $status)helper on the baseControllerreturns{ "ok": bool, "data": …, "error": { "code", "message" } }with the matching HTTP status.
4. Health round-trip (the demonstrable slice)
GET /api/health(public) → runs a real DB query (SELECT 1+ read oneconfigrow) →{ ok:true, data:{ db:"connected", app, version, time } }.GET /api/admin/ping(privileged) → echoes authenticated context.- A small client-side
fetch("/api/health")on an Astro test page renders the result — proving browser → Apache → php-fpm → MariaDB → JSON end to end.
5. Security hardening
- Apache
<DirectoryMatch>denies extended toinstall/(once.installedexists),db/, and the.installedlock. Existing denies (vendor|core|app|system|commands| .memory|.reference_files, dotfiles,config.php,composer.*) stay. config.phpgitignored, written with crypto-safe keys.- Public endpoints origin-gated + rate-limited.
- Operator TODO: rotate/delete the vestigial
api/.env(live CreditPullEngine secrets + a GitHub PAT).
Data flow — a public request
- Browser
GET /api/health(same-origin, no credentials). - Apache Alias →
api/index.php→ Bootstrap → Router →Healthcontroller (extendsPublicController). PublicControllerchecksOrigin∈ALLOWED_ORIGINS, checks IP rate limit.- Controller queries
Db(lazy PDO connect to MariaDB), builds payload. json()emits{ ok, data }+200(or403/429/500).
Error handling
- All controller output goes through
json(); no raw echo. HTTP status always set. - DB/PDO exceptions caught →
{ ok:false, error:{ code:"db_error", … } }+500(detail hidden unlessDEBUG).system/ErrorHandler.phpremains the backstop. - Auth failures return typed codes:
unauthorized(401),forbidden_origin(403),rate_limited(429).
File inventory
New
app/Services/Installer.phpcommands/InstallCommand.php,commands/MigrateCommand.phpapp/Controllers/PublicController.php,app/Controllers/ApiController.phppublic/controllers/health.php(public),public/controllers/admin.php(privileged)db/migrations/(+ a first example migration)api/system/.installed(generated at install)
Modified
console— registerInstallCommand,MigrateCommandinstall/controllers/index.php— delegate toInstaller(path-bug fix, lock check)core/Controller.php(or a base) — addjson()helperconfig.php— addADMIN_TOKEN,ALLOWED_ORIGINSconstants (generated by installer)- Apache vhost (
/www/server/panel/vhost/apache/comiida.com.conf) — extend denies
Server (outside repo)
- MariaDB: create
comiidadatabase + dedicated user.
Verification
php console app:install --db-… --url=https://www.comiida.com --name=Comiida→ tables created,config.phpwritten with crypto keys,.installedpresent.php console db:migrate --status→ baseline + migrations applied.curl https://www.comiida.com/api/health→200,db:"connected".- From a disallowed
Origin→403; flood →429. curl /api/admin/pingno token →401; withADMIN_TOKEN→200.curl /api/install/after lock → denied.- Main Astro site (
/) unaffected.
Out of scope (future sub-projects, each its own spec)
- Metrics — event tables +
collectbeacon + admin dashboard. - Runtime agents/skills — LLM agent endpoints (builds on llm.md).
- Business CLI commands — cron/process runners.
The Foundation delivers the CLI infrastructure (app:install, db:migrate) and the
secure round-trip those three build on.
Open items for the operator
- DB name/user/password for the
comiidadatabase (BT panel or root creds). - Decision to rotate + delete
api/.env.