convex/ (schema, functions, deploy role for :3210) moved to
joelclaw-api; apps/web consumes generated api/types via link:.
sessions schema is a deliberate temporary v.union (auth + call
telemetry shapes) — separation chartered. All four readback checks
pass: deploy clean, tsc clean, no old-path refs, live read works.
Worker-authored, steering-verified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the dev-bearer table with PDS-backed App Password authentication
per ADR-0243 Rule 20, scoped to single-user (Joel only) for now.
packages/system-bus/src/lib/pds.ts
+ pdsCreateAppPassword — calls com.atproto.server.createAppPassword on
the caller's session; returns { name, password, did, handle }
+ pdsRevokeAppPassword — revokes by name
+ pdsValidateAppPassword — one-shot validation via createSession; used
at register time only, NOT per-request (hash lookup is cheaper +
safer, and doesn't create server-side session state at PDS)
packages/memory/src/schemas/machines.ts — NEW Typesense schema for
machines_dev collection: id, user_id, did, handle, machine_name,
app_password_name, app_password_sha256, created_at, last_seen_at,
revoked_at
apps/web/lib/memory-auth.ts — NEW auth middleware
authenticateMemoryRequest(req) → MemoryIdentity | null
Hot path: sha256(bearer) → Typesense machines_dev lookup →
{ user_id, machine_id, did, source: "app-password" }
No PDS roundtrip per request. App Password validity is established
at register time; the hash is the identity key thereafter.
Dev-bearer fallback retained during transition (MEMORY_DEV_BEARER_TOKENS
env var); effectively disappears when that var is empty in prod.
Phase 3.5 TODO: users_dev collection (DID→user_id map is hardcoded for
now).
apps/web/app/api/runs/*/route.ts — all 6 handlers now call
authenticateMemoryRequest instead of the inline DEV_BEARER_TOKENS
table + local authenticate() helper. Rule 4 privacy filtering is
unchanged (still keyed on auth.user_id from the middleware).
scripts/joelclaw-machine-register.ts — NEW provisioning CLI
Flow: load cached PDS session from ~/.joelclaw/pds-session.json →
call com.atproto.server.createAppPassword → sha256(plaintext) →
upsert machines_dev row → back up old auth.json → write new
auth.json (0600) with the plaintext bearer. Ready to register Panda
once the infra is back.
Verification status (pending Typesense reachability):
[x] pds.ts compiles; curl-tested createAppPassword directly against
PDS (returned valid password)
[x] Bun fetch works against PDS directly (ruled out fetch-lib bugs)
[x] All route handlers typecheck + biome clean
[x] Auth middleware compiles
[ ] End-to-end: register panda → POST /api/runs with new bearer →
Run indexes → search returns it — BLOCKED on Colima SSH tunnel
which is flapping (ssh 192.168.64.2:22 timing out; matches the
2026-04-17 healer-suicide-loop memory note)
When infra comes back:
TYPESENSE_API_KEY=<...> joelclaw-machine-register --name panda --user joel
then POST /api/runs with the new bearer to confirm.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
notFound() is a runtime API that requires workUnitAsyncStorage context.
Calling it inside 'use cache' causes InvariantError during prerendering.
CachedArticleContent now returns null when post not found, and the
caller (StaticArticleShell) handles notFound() in request context.
Also uses cacheLife('minutes') so new articles appear without deploy.
getPost() now caches for minutes instead of max, so new articles
appear within minutes of Convex upsert. Explicit tag revalidation
makes them appear instantly. 404 check moved outside cache boundary
in [slug]/page.tsx so stale null results don't block new content.
getPost() no longer uses 'use cache' — null results were cached
indefinitely, blocking new articles until a redeploy.
Now: getPost() fetches from Convex on every request (uncached).
CachedArticleContent receives post as prop, caches only MDX rendering.
New articles appear immediately after Convex upsert + tag revalidation.
Soft-deleted records (deletedAt set) were matching the hash guard
and returning 'skipped', preventing re-sync after removal.
Now skips hash guard for soft-deleted records so they get restored.
- posts.ts: strip leading frontmatter/metadata before rendering
- content-review.ts: normalize content on fetch/persist to prevent
future writes from reintroducing raw frontmatter
- seed-articles.ts: defensive strip on seed path
Fixes frontmatter metadata rendering as visible article body text.
parseAdrFields returned null when date was falsy, causing 500.
Now extracts date from markdown body (**Date**: YYYY-MM-DD) and
falls back to 'Unknown'. Date is no longer a hard requirement.
Adds escapeMdxAngleBrackets() utility that preserves code fences,
inline code, and valid HTML tags while escaping comparison operators
like < 0.5, >50%, <5MB that break MDX compilation.
Fixes 500 errors on ADR-0163, 0164, 0165, 0169 and any future ADRs
with raw angle brackets in Convex-sourced content.
CFP-2 made getPost/getAllPosts async for Convex reads, but Next.js 16
with PPR rejects uncached async data access outside <Suspense>.
ALL article pages failed prerender with 'Uncached data was accessed
outside of <Suspense>'.
CFP-2 and CFP-3 need to land together — Convex reads require
'use cache' boundaries first.
retries: 0 made worker restarts fatal — Inngest couldn't retry
the step on the new worker. With retries: 2, the 1s restart window
is covered by Inngest's built-in retry backoff.
ADR-0156 rewritten: blue/green port swap > drain-then-restart.
Inngest is step-level stateless — we don't need to wait for runs,
just for the current in-flight step.
VALID_STATUSES only had 6 entries — shipped, deferred, in_progress,
researching, withdrawn were all normalizing to 'proposed'. Now matches
status-config.ts. Also synced 0142→deferred, 0145→deferred,
0146→shipped, 0147→shipped from Vault.
- Titles wrap fully instead of truncating
- Relevance text shows complete (no line-clamp)
- Date shown as relative time (2d ago) above title, not stealing width
- Single type badge (repo/article/video) replaces full tag list
- Source URL removed from index (shown on detail page)
- Tighter card spacing for better scan density
- Re-removed duplicate self-hosting discovery (also from Vault source)