Context
AEGIS daemon's aegis#564 Phase 2 shipped a compat helper (`getProcedureWithDerivedStats` / `getAllProceduresWithDerivedStats`) that logs cached-vs-derived drift into `shadow_read_drift` so the Phase 3 cached-column drop on `procedural_memory` can be data-driven.
The gap
The public routes that actually exercise procedural reads — `GET /procedures` and `GET /dashboard` — are owned by `@stackbilt/aegis-core`:
- `src/routes/observability.ts:58` — `/procedures` calls bare `getAllProcedures`.
- `src/dashboard.ts:4,118` (via `src/routes/pages.ts:36`) — `/dashboard` calls bare `getAllProcedures`.
First-match-wins in Hono mounting (core mounts before extensions in `src/core.ts:210-211`), so the daemon's instrumented duplicates are dead code. Net effect: the Phase 2 drift gauge stayed empty even under normal daemon traffic, because the router hot path (sample 0.1) is the only non-shadowed reader and episodic volume is low.
Daemon shipped a synthetic hourly `drift-probe` cron in v2.1.6 to unblock Phase 3 readiness, but that's a tactical patch — the correct long-term fix lives here.
Proposal
Move the compat helper down to aegis-core and swap both readers:
- Add `getProcedureWithDerivedStats` / `getAllProceduresWithDerivedStats` + `DriftLogOpts` to core's `kernel/memory/procedural.ts` (structure-identical port from daemon's `web/src/kernel/memory/procedural.ts`).
- Swap `src/dashboard.ts:getDashboardData` → `getAllProceduresWithDerivedStats(db, { reader: 'dashboard' })`.
- Swap `src/routes/observability.ts:/procedures` → `getAllProceduresWithDerivedStats(c.env.DB, { reader: 'observability' })`.
- Keep the compat layer gated on the `shadow_read_drift` table's existence (core shouldn't require a daemon-owned migration). Either check via `sqlite_master` on first call or no-op cleanly if the table isn't present.
Once published and pulled, daemon removes the `drift-probe` cron and the local `dashboard.ts` shadow.
Why now
Part of the `project_daemon_kernel_shadow.md` epic (daemon-side memory in `memory/project_daemon_kernel_shadow.md`). This is exactly the class of drift the collapse is meant to close: daemon ships instrumentation for a shared primitive, core doesn't know about it, the instrumentation silently no-ops.
References
cc: @stackbilt-admin
Context
AEGIS daemon's aegis#564 Phase 2 shipped a compat helper (`getProcedureWithDerivedStats` / `getAllProceduresWithDerivedStats`) that logs cached-vs-derived drift into `shadow_read_drift` so the Phase 3 cached-column drop on `procedural_memory` can be data-driven.
The gap
The public routes that actually exercise procedural reads — `GET /procedures` and `GET /dashboard` — are owned by `@stackbilt/aegis-core`:
First-match-wins in Hono mounting (core mounts before extensions in `src/core.ts:210-211`), so the daemon's instrumented duplicates are dead code. Net effect: the Phase 2 drift gauge stayed empty even under normal daemon traffic, because the router hot path (sample 0.1) is the only non-shadowed reader and episodic volume is low.
Daemon shipped a synthetic hourly `drift-probe` cron in v2.1.6 to unblock Phase 3 readiness, but that's a tactical patch — the correct long-term fix lives here.
Proposal
Move the compat helper down to aegis-core and swap both readers:
Once published and pulled, daemon removes the `drift-probe` cron and the local `dashboard.ts` shadow.
Why now
Part of the `project_daemon_kernel_shadow.md` epic (daemon-side memory in `memory/project_daemon_kernel_shadow.md`). This is exactly the class of drift the collapse is meant to close: daemon ships instrumentation for a shared primitive, core doesn't know about it, the instrumentation silently no-ops.
References
cc: @stackbilt-admin