# Handover Document — `api_hub` (Mi Digital New Platform)

**Date:** 2026-10-02
**From:** OpenHands agent (multi-agent orchestration)
**To:** ChatGPT Codex (continuing development)
**Repo root:** `/var/www/html/mi-digital-new-platform`
**Laravel app:** `Laravel/api_hub/`
**Main tip:** `a697819` — `[merge][T-C-13] track-c/T-C-13-parental-controls`

---

## 1. What this project is

`api_hub` is the Laravel backend for **Mi Digital Academy**, being built from a minimal
Sanctum-auth scaffold up to the full platform by an **autonomous 4-agent orchestration
system** running on OpenHands:

| Track | Scope | Key dirs (ownership) |
|---|---|---|
| **A** | Identity & Billing | `identity_billing` DB; auth, accounts, tenancy, billing, security, API keys |
| **B** | Catalog & Content | `catalog` DB; courses, assets, catalog APIs, seeding |
| **C** | Learning Runtime | `learning` DB; enrollments, progress, SRS, learning paths, parental controls |
| **D** | Engagement & Analytics | `engagement` + `analytics` DBs; notifications, community, portals, dashboards, E2E |

**Architecture ground rules** (full list in `Laravel/api_hub/docs/README.md`):
1. **5-database domain split** (`identity_billing`, `catalog`, `learning`, `engagement`,
   `analytics`). Every Eloquent model sets `protected $connection`. Never default.
2. **No cross-database joins** — two queries + PHP merge, or an `analytics` aggregate.
   Cross-domain references = plain indexed columns (no FK constraints).
3. **Analytics is write-once** — fed only by queued jobs consuming domain events.
4. **Every mutating endpoint** writes an `audit_events` row (actor, action, target,
   before/after, timestamp).
5. **Response envelope** `{ "data": ..., "message": ... }` with proper HTTP codes.
6. **RBAC deny-by-default**, server-side, per-request (T-A-04 middleware). Institute
   isolation is a hard scope boundary.
7. **Verification tokens** single-use/time-limited/rate-limited — built once in Track A.
8. **Money/tax/identity never split** — all billing tables in `identity_billing`.
9. **Tests accompany every task** (PHPUnit/Laravel feature tests). Done = acceptance
   criteria pass.
10. **Contract-first cross-track deps**: `DEPS:` (hard) vs `DEPS⚡:` (contract-only) in
    each task.

The 74-task plan with per-task acceptance criteria lives in
`Laravel/api_hub/docs/README.md`. Per-track progress: `docs/status/track_{a,b,c,d}.md`
(note: only `track_a.md` exists on disk as of today — other tracks' status files were
created on agent branches; check `docs/status/` after merging in-flight branches).

---

## 2. Current status (2026-10-02)

### Progress: **47 / 74 tasks merged to main (64%)**

| Track | Merged | In-flight (pushed, unmerged) |
|---|---|---|
| A — Identity & Billing | 10/25 | T-A-08, T-A-10, T-A-12, T-A-13, T-A-24 |
| B — Catalog & Content | 9/13 | T-B-01 |
| C — Learning Runtime | 12/17 | T-C-06, T-C-11 (re-pushed), T-C-14 |
| D — Engagement & Analytics | 16/19 | T-D-06, T-D-14, T-D-15 |

### Test suite: **74/74 passing, 344 assertions, ~1.5s** ✅

```bash
cd /var/www/html/mi-digital-new-platform/Laravel/api_hub
php artisan test --compact
# -> {"result":"passed","tests":74,"passed":74,"assertions":344}
```

Codebase: ~5,928 lines PHP, 86 files.

### ⚠️ The 4 OpenHands agents are all PARKED (error state)

They died in a third LLM-server outage and exhausted their 1 auto-retry each
(`automation/state.json` shows `parked_after_error`, `retries: 1`). The LLM (vLLM at
`192.168.0.115:8000`, model `openai/qwen3.8-27b`) is currently healthy. If you want the
OpenHands agents to resume instead of Codex: set `retries: 0` for each track in
`automation/state.json` and run `bash automation/run_cycle.sh` — it relaunches each agent
with resume context. **If Codex is taking over, see §6 (how to stop the cron).**

### In-flight branches (the immediate work for Codex)

All pushed to origin, none merged. Each needs: `git fetch origin`, rebase onto
`origin/main`, run `php artisan test`, force-push, then merge (fast-forward or
`git merge --no-ff` with a `[merge][T-XX]` message — see §4).

| Branch | Task | Staleness (behind main) | Risk |
|---|---|---|---|
| `track-c/T-C-14-offline-learning` | C-14 | 2 commits | low |
| `track-c/T-C-11-learning-paths` | C-11 | 7 | **doc-only** (just a `docs/status/track_c.md` update — merge or delete) |
| `track-a/T-A-13-membership-plans` | A-13 | 10 | low |
| `track-a/T-A-10-account-links` | A-10 | 14 | low |
| `track-a/T-A-08-security-policy` | A-08 | 50 | medium |
| `d/T-D-14-sponsor-tenant-portal` | D-14 | 14 | low |
| `d/T-D-06-customer-support` | D-06 | 16 | low |
| `d/T-D-15-super-admin-tenant-dashboards` | D-15 | 44 | medium — edits `bootstrap/providers.php` (shared file; merge ordering matters) |
| `track-c/T-C-06-bookmarks-notes-highlights` | C-06 | 65 | high — old, expect conflicts |
| `track-b/T-B-01-catalog-base-model` | B-01 | 109 | high — oldest, expect conflicts |
| `track-a/T-A-24-api-keys-rates-webhooks` | A-24 | 131 | **doc-only** (just a `docs/status/track_a.md` update — merge or delete) |
| `track-c/T-C-11-status` | — | — | stray status-doc push, no code — safe to delete after diff |

> **Superseded-branch check:** `T-A-24` and `T-C-11` appear in *both* merged and
> in-flight lists. Before rebase/merge, diff each in-flight branch against main; if its
> changes are already in main, delete the branch instead of merging.

### Remaining after in-flight land: 27 - 12 = ~15 unstarted tasks
Remaining planned tasks (not started, from the 74-task plan): A: 15 tasks (T-A-01..25
minus merged/in-flight), B: 3, C: 2, D: 1 (T-D-19 cross-tenant E2E — a local branch
`d/T-D-19-cross-tenant-e2e` exists in `worktrees/track-D` but was **never pushed**).
Full task IDs/acceptance criteria: `Laravel/api_hub/docs/README.md`.

---

## 3. Repository layout

```
/var/www/html/mi-digital-new-platform/
├── Laravel/api_hub/            # the actual Laravel backend (main branch = deliverable)
│   ├── docs/README.md          # ★ the 74-task plan + ground rules
│   ├── docs/agent_instructions.md  # git-worktree flow, ownership matrix, DoD
│   ├── docs/agent_prompts.md   # original first messages for agents A-D
│   ├── docs/status/            # per-track progress logs
│   └── (app, database/migrations/{identity_billing,catalog,learning,engagement,analytics},
│        routes/api_{...}.php, tests/Feature/{...})
├── Documents/                  # product specs (source of truth for features)
├── Flutter/                    # client apps (out of scope for this backend work)
├── automation/                 # ★ the OpenHands orchestration system (see §6)
├── worktrees/                  # git worktrees: track-A..D (agent checkouts),
│                               #   build-A..D (merge-gatekeeper build dirs),
│                               #   merge (oh-merge branch), report (project-reports)
└── reports/                    # generated reports incl. this handover
```

Git worktrees: `git worktree list` from the repo root. Agent worktrees
(`worktrees/track-A..D`) hold each agent's unpushed local state; the **authoritative**
in-flight state is on `origin` (the push-only contract), so prefer origin branches.

---

## 4. Git flow & merge convention (keep following it)

- **Push-only contract:** agents never push to `main`; only the orchestrator (or you,
  now) merges. Branch naming: `track-{a,b,c}/T-{X}-{nn}-{slug}` for A/B/C and
  `d/T-D-{nn}-{slug}` for D (historical inconsistency — keep existing branch names).
- **Merge message convention:** `[merge][T-XX] <branch-name>` (e.g.
  `[merge][T-D-17] d/T-D-17-finance-revenue-dashboards`).
- **Gatekeeper checks per branch** (from `automation/orchestrator.py`):
  1. branch must be **based on current main** (else `stale_needs_rebase`);
  2. every changed file must be **owned by that track** (ownership matrix in
     `orchestrator.py`: `OWNED` dict + `D_EXTRA` exact-file allowlist; else
     `ownership_rejected`);
  3. full test suite must pass in a clean build (`_prepare_build` copies the repo to
     `worktrees/build-X`, runs composer/artisan test there).
- Merges are capped at 3/track/cycle to bound blast radius.

### Ownership matrix (as of today, after fixes)

- **A**: `COMMON` shared files (`bootstrap/`, `routes/api.php|web.php`,
  `app/Http/Kernel.php`, `app/Http/Middleware/`, `app/Providers/`, `config/`,
  `composer.*`, `tests/TestCase.php`, `phpunit.xml`, etc.) + A's identity/billing dirs.
  **Only A may touch shared files.**
- **B**: `app/Models/Catalog/`, `app/Services/Catalog/` (etc. per `OWNED["B"]`),
  `routes/api_catalog.php`, `tests/Feature/Catalog/`.
- **C**: Learning dirs, `routes/api_learning.php`, `tests/Feature/Learning/`.
- **D**: Engagement + Analytics dirs **including `app/Services/Analytics/`** (added
  2026-10-01 fix), `routes/api_engagement.php`, `tests/Feature/Engagement/`, plus
  `D_EXTRA` exact files: `bootstrap/providers.php`, `config/analytics.php`,
  `config/finance.php`, `config/webhook_dispatch.php`, D's 9 ServiceProvider files,
  `app/Http/Middleware/ApiKeyEnforcement.php`.

**Cross-track integration** goes through *contracts* (named endpoints/events per task),
never by editing another track's files. If a new task needs wiring in a shared file,
route it to Track A or record it in `docs/status/track_{x}.md` for the merge gatekeeper.

---

## 5. Known issues & gotchas

1. **LLM reliability (biggest risk):** vLLM at `192.168.0.115:8000` has had **3 outages**
   (9/28, 9/30, ~10/1 night). Each one kills all 4 agents. Recommend a watchdog
   (systemd `Restart=always` or health-check restart) on that box.
2. **`storage/logs/laravel.log` permission:** the web server (www-data) creates this
   file owned by www-data; tests run as user `server` and fail with *Permission denied*
   (9 feature-test errors) when the app logs to it. **Fix:** `rm Laravel/api_hub/storage/logs/laravel.log`
   (Laravel recreates it as the current user). May recur after web-server writes.
3. **Canvas/agent-server proxy (:8001) crashed once** (10/01) — recovered externally.
   The API also works directly at `http://127.0.0.1:18000` (agent-server) if :8001 is
   down.
4. **Ownership gap (fixed 2026-10-01):** D's dashboard/finance work in
   `app/Services/Analytics/` was unowned → permanent `ownership_rejected` on
   T-D-15/T-D-17. Fixed by adding the dir to D's `OWNED` in `orchestrator.py`. If you
   see `ownership_rejected` again, check which track's `OWNED`/`D_EXTRA` is missing the
   path — it's always a matrix gap, not a bad agent.
5. **`track-c/T-C-11-status`** is a stray branch (a status doc push, not code) — safe
   to delete once T-C-11 content is confirmed in main.
6. **No CI on git hosting** — the orchestrator's gatekeeper *is* the CI (test suite in
   a clean build). If you merge by hand in Codex, **always run the full suite first**.
7. `worktrees/build-*` and `worktrees/merge` are scratch worktrees of the gatekeeper —
   don't develop in them.

---

## 6. The automation system (OpenHands) — what runs, how to stop/keep it

### Cron (crontab of user `server`)

```
*/5 * * * *  /var/www/html/mi-digital-new-platform/automation/run_cycle.sh      # merge gatekeeper + agent nudge/relaunch
15 */3 * * * /var/www/html/mi-digital-new-platform/automation/run_report_job.sh # 3-hourly status report
```

### Components (all in `automation/`)

| File | Role |
|---|---|
| `orchestrator.py` | The gatekeeper. Each cycle: merges green+owned+fresh branches to main (cap 3/track), nudges idle/paused agents, relaunches errored agents (1 retry), parks after 2nd failure, deletes dead conversations. State in `state.json`. Report in `report.md`. |
| `gen_report.sh` | Deterministic data collection (merged tasks, in-flight, test results, log tail). |
| `report_job.py` | 2-phase: (1) collect+render+publish report to `origin/project-reports`; (2) spawn a digest conversation via `/api/conversations` using `X-Expose-Secrets: encrypted` from `/api/settings`. |
| `run_cycle.sh` / `run_report_job.sh` | flock-guarded cron entrypoints. |
| `.env` | `LOCAL_BACKEND_API_KEY` (agent-server session key). |
| `state.json` | Per-track conversation IDs, retry counts, parked flags. |

### Backend endpoints

- Agent-server API: `http://localhost:8001` (canvas ingress) or `http://127.0.0.1:18000`
  directly. Auth: `X-Session-API-Key: $LOCAL_BACKEND_API_KEY`.
- Conversation listing: `GET /api/conversations/search?limit=100` (limit max is 100 —
  paginate with `page=`).
- LLM: vLLM `http://192.168.0.115:8000/v1` (401 on `/v1/models` = healthy).

### Decision for Codex: replace or coexist?

- **Replace (recommended if Codex will drive all remaining work):**
  1. `crontab -e` → comment out both lines (or `crontab -r` after noting them above).
  2. Optionally delete the 4 parked agent conversations (IDs in `automation/state.json`)
     to declutter the Canvas UI: `DELETE /api/conversations/{id}`.
  3. Keep `automation/` in the repo as reference; it's git-tracked and harmless.
- **Coexist:** leave cron running; Codex handles only the 12 in-flight branches +
  unstarted tasks, and the orchestrator keeps merging whatever is pushed. Just don't
  merge to main yourself while the gatekeeper runs (race risk) — push branches and let
  it merge, or stop cron first.

---

## 7. Recommended first session in Codex

1. **Stop or coexist with cron** (see §6).
2. **Land the 12 in-flight branches** in the table in §2 (low staleness first, high
   last; delete superseded ones after diffing). For each:
   ```bash
   git fetch origin
   git worktree add /tmp/wt-$BR origin/$BR 2>/dev/null || git worktree add /tmp/wt-$BR $BR
   cd /tmp/wt-$BR && git checkout -B $BR origin/main && git cherry-pick origin/$BR  # or rebase
   cd Laravel/api_hub && composer install -q 2>/dev/null; php artisan test --compact
   git push -f origin $BR            # after green tests
   # then merge to main:
   git checkout main && git merge --no-ff $BR -m "[merge][T-XX] $BR" && git push origin main
   git worktree remove /tmp/wt-$BR
   ```
   (Note: `php artisan test` may need `cp .env.example .env` + `php artisan key:generate`
   in a fresh checkout; the gatekeeper's `_prepare_build` in `automation/orchestrator.py`
   shows the exact sequence it uses.)
3. **Fix the log-perm gotcha** if tests fail with *Permission denied* on
   `storage/logs/laravel.log` (see §5.2).
4. **Start the unstarted tasks** from `docs/README.md` in dependency order; A is the
   critical path (15 tasks remain: security-policy re-land, account links, tenant
   accounts, membership plans, API keys/rate limiting, billing cycle, webhooks,
   data-protection, etc.).
5. **Update `docs/status/track_{x}.md`** as you go (that's the shared progress view).
6. Consider the vLLM watchdog (§5.1) if any OpenHands/LLM usage continues.

---

## 8. Quick reference

- Repo: `/var/www/html/mi-digital-new-platform` (git, branch `main`)
- App: `Laravel/api_hub/` — tests: `php artisan test --compact` (expect 74 passing)
- Task plan: `Laravel/api_hub/docs/README.md` (74 tasks, ground rules, DEPS graph)
- Agent workflow rules: `Laravel/api_hub/docs/agent_instructions.md`
- Product specs: `Documents/`
- Automation: `automation/` (orchestrator.py is the source of truth for ownership/merge policy)
- Reports: `reports/` + `origin/project-reports` branch (3-hourly auto-generated)
- Progress today: **47/74 merged (64%)**, 12 branches in flight, tests green, agents parked
