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
209 lines
11 KiB
Markdown
209 lines
11 KiB
Markdown
# Foundation — SeedProject as a per-project Astro backend
|
|
|
|
**Status:** Design (approved, not yet implemented) · **Date:** 2026-07-04
|
|
**Related:** [documentation.md](documentation.md) (framework internals) · [llm.md](llm.md) (LLM layer) · [changelog.md](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). `/api` is served via an `Alias` → `.../api` with 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 dedicated `comiida` database + 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:
|
|
|
|
1. **Installer** — provisions DB + config (CLI + locked web wizard share one service).
|
|
2. **Migrations runner** — versioned schema evolution.
|
|
3. **Two-tier auth** — `PublicController` + `ApiController` base classes.
|
|
4. **Health round-trip** — a real end-to-end proof endpoint.
|
|
5. **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). Uses
|
|
`dirname(__DIR__, 2)` base paths, never `DOCUMENT_ROOT`. Responsibilities:
|
|
1. Validate + test DB connection (PDO).
|
|
2. Import `install/dump.sql` (the skeleton).
|
|
3. Generate keys with `bin2hex(random_bytes(32))` (crypto-safe; hex only — no quote bug).
|
|
4. Write `api/config.php` with the **correct `/api` constants** (`SITE_BASE=/api`,
|
|
`ASSETS=/api/public/assets/`, `URL`, `PROJECT_NAME` from args/env; DB creds).
|
|
5. Record the baseline as applied, then run pending migrations.
|
|
6. Write install lock `api/system/.installed`.
|
|
- **`commands/InstallCommand.php`** — `php console app:install` (Symfony Console,
|
|
registered in `console` next to `GreetCommand`). Reads creds from options
|
|
(`--db-host --db-name --db-user --db-pass --url --name`) or interactive prompt;
|
|
refuses to run if `.installed` exists unless `--force`.
|
|
- **Web wizard** (`install/`) — refactored to call `Installer` (fixes its path bugs);
|
|
refuses to run when `.installed` exists; 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.
|
|
- **`migrations`** tracking table (`id, filename, applied_at`) — created by the
|
|
installer; baseline recorded so it is never re-run.
|
|
- **`commands/MigrateCommand.php`** — `php console db:migrate` applies unapplied
|
|
files in filename order, each in a transaction where the DDL allows; records each.
|
|
`php console db:migrate --status` lists applied/pending.
|
|
|
|
## 3. Two-tier auth + response contract
|
|
|
|
Reuses the existing tables — no duplication.
|
|
|
|
- **`ApiController`** (base, privileged) — verifies a bearer token: either the
|
|
`ADMIN_TOKEN` (config constant) for first-party/admin calls, **or** an
|
|
`api_auth.apikey` for programmatic
|
|
consumers. On an API key: check `active`, enforce `api_plans.limit_minute` /
|
|
`limit_monthly` against `api_usage`, and log to `api_requests`. Failures →
|
|
`401` (missing/invalid) or `429` (over limit).
|
|
- **`PublicController`** (base, public) — for browser beacons. Verifies
|
|
`Origin`/`Referer` against a config **`ALLOWED_ORIGINS`** allowlist, applies
|
|
**IP-based rate limiting**, requires no user key. Failures → `403` (bad origin)
|
|
or `429` (flood). Emits same-origin CORS headers.
|
|
- **Response envelope** — a shared `json($data, $status)` helper on the base
|
|
`Controller` returns `{ "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 one
|
|
`config` row) → `{ 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 to `install/` (once `.installed` exists),
|
|
`db/`, and the `.installed` lock. Existing denies (`vendor|core|app|system|commands|
|
|
.memory|.reference_files`, dotfiles, `config.php`, `composer.*`) stay.
|
|
- `config.php` gitignored, 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
|
|
|
|
1. Browser `GET /api/health` (same-origin, no credentials).
|
|
2. Apache Alias → `api/index.php` → Bootstrap → Router → `Health` controller
|
|
(extends `PublicController`).
|
|
3. `PublicController` checks `Origin` ∈ `ALLOWED_ORIGINS`, checks IP rate limit.
|
|
4. Controller queries `Db` (lazy PDO connect to MariaDB), builds payload.
|
|
5. `json()` emits `{ ok, data }` + `200` (or `403`/`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 unless `DEBUG`). `system/ErrorHandler.php` remains the backstop.
|
|
- Auth failures return typed codes: `unauthorized` (401), `forbidden_origin` (403),
|
|
`rate_limited` (429).
|
|
|
|
## File inventory
|
|
|
|
**New**
|
|
- `app/Services/Installer.php`
|
|
- `commands/InstallCommand.php`, `commands/MigrateCommand.php`
|
|
- `app/Controllers/PublicController.php`, `app/Controllers/ApiController.php`
|
|
- `public/controllers/health.php` (public), `public/controllers/admin.php` (privileged)
|
|
- `db/migrations/` (+ a first example migration)
|
|
- `api/system/.installed` (generated at install)
|
|
|
|
**Modified**
|
|
- `console` — register `InstallCommand`, `MigrateCommand`
|
|
- `install/controllers/index.php` — delegate to `Installer` (path-bug fix, lock check)
|
|
- `core/Controller.php` (or a base) — add `json()` helper
|
|
- `config.php` — add `ADMIN_TOKEN`, `ALLOWED_ORIGINS` constants (generated by installer)
|
|
- Apache vhost (`/www/server/panel/vhost/apache/comiida.com.conf`) — extend denies
|
|
|
|
**Server (outside repo)**
|
|
- MariaDB: create `comiida` database + dedicated user.
|
|
|
|
## Verification
|
|
|
|
1. `php console app:install --db-… --url=https://www.comiida.com --name=Comiida`
|
|
→ tables created, `config.php` written with crypto keys, `.installed` present.
|
|
2. `php console db:migrate --status` → baseline + migrations applied.
|
|
3. `curl https://www.comiida.com/api/health` → `200`, `db:"connected"`.
|
|
4. From a disallowed `Origin` → `403`; flood → `429`.
|
|
5. `curl /api/admin/ping` no token → `401`; with `ADMIN_TOKEN` → `200`.
|
|
6. `curl /api/install/` after lock → denied.
|
|
7. Main Astro site (`/`) unaffected.
|
|
|
|
## Out of scope (future sub-projects, each its own spec)
|
|
|
|
- **Metrics** — event tables + `collect` beacon + admin dashboard.
|
|
- **Runtime agents/skills** — LLM agent endpoints (builds on [llm.md](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 `comiida` database (BT panel or root creds).
|
|
- Decision to rotate + delete `api/.env`.
|