Handoff
Where the product stands, what was decided and why, what is verified, and what is left. Read this before resuming work on Wateera.
Handoff — Wateera & Wajhati
Last updated: 2026-09-25 · State: product-complete, payments integrated (Moyasar + Tamara BNPL), Wajhati two-sided hiring platform established with cryptographic bundle verification and SEO public directory.
Resuming work? Read this file, then AGENTS.md.
This is a living document: update it at the end of any session that changes the state below.
1. Where everything is
| Product repository | agentjob.io monorepo |
| Working copy | /Users/fares-wdeftksa/development/agentjob.io |
| Core packages | @repo/payments (Moyasar + Tamara BNPL), @repo/core (Wajhati domain, hashing, matching), @repo/database (Prisma schema + queries), @repo/api (oRPC modules), @repo/i18n (bilingual ar/en) |
| SaaS application | apps/saas (Daily performance + Wajhati candidate, discovery, finite batches, employer portal) |
| Public marketing | apps/marketing (Landing pages + /jobs SEO directory targeting Arabic head term وظائف) |
| Local database | PostgreSQL on localhost:5432, database app; demo workspace demo-team |
Environment rule learned the hard way: node-linker layout, ports and URLs must agree.
SAAS_PORT/MARKETING_PORT/DOCS_PORT (3100/3101/3102) drive the dev scripts and must match the
NEXT_PUBLIC_* URLs in .env.local.
2. What the product is
- وتيرة (Wateera): Daily-performance loop (plan, execute, score, coach agents).
- وجهتي (Wajhati): An independent two-sided Saudi talent and hiring platform (
agentjob.io):- Candidate side: Default-private verifiable vaults, provenance-backed facts, finite approved application batches with cryptographic SHA-256 bundle hashes (
computeBundleHash,computeBatchManifestHash). - Employer side: Vacancy publication, consented candidate reviews, interview scheduling.
- Public SEO Directory:
/jobstargeting the Arabic search term "وظائف" with JSON-LDJobPostingschema. - Payments: Moyasar (Mada, Credit Card, Apple Pay) + Tamara BNPL (3/4/6 installments) via
@repo/payments.
- Candidate side: Default-private verifiable vaults, provenance-backed facts, finite approved application batches with cryptographic SHA-256 bundle hashes (
| Surface | Route |
|---|---|
| The day (plan, tasks, timer, close, plan agent) | /<workspace>/today |
| Wajhati Candidate Profile & Matching | /<workspace>/wajhati |
| Wajhati Company Discovery Agent | /<workspace>/wajhati/discovery |
| Wajhati Finite Application Batches | /<workspace>/wajhati/batches |
| Wajhati Employer Hiring Portal | /<workspace>/wajhati/employer |
| Public Jobs Directory (SEO وظائف) | /jobs (bilingual, city filters, JSON-LD) |
| Payments checkout & webhooks | Moyasar + Tamara BNPL via @repo/payments |
| Assistant access | MCP server, packages/mcp — see docs/mcp.md |
3. Decisions already made (do not re-litigate without new evidence)
| Decision | Why |
|---|---|
| Tamara BNPL & Moyasar integration | Market-standard Saudi payment rails: Mada, Credit Cards, Apple Pay via Moyasar, and Pay in installments (3/4/6) via Tamara. Fully covered by unit tests (13 tests each). |
| Zero-Haiku Strict Guardrail (D05) | Claude Haiku is strictly barred from all model execution, tool calls, and fallbacks. Enforced by assertNoHaiku and tested in CI. |
| Complete Jadarat Exclusion (D03) | Strictly 0 connectors, crawlers, placeholders, or dependencies on Jadarat. Enforced by assertNoJadarat and verified in unit tests. |
| Deterministic bundle & manifest hashing | Every application bundle is keyed by a canonical JSON SHA-256 hash (computeBundleHash). Batches cannot be altered after approval. |
| Truthful matching engine | Matches only confirmed candidate facts against vacancy requirements; gaps are computed deterministically without hallucinations. |
| Deterministic engine first, model optional | Works with no API key, testable in CI, no running cost; the model is a second implementation of the same schema, with same-request fallback. Every run stores which engine answered. |
| Plan gating in the API, not the UI | One resolver (resolveTenant / resolveWajhatiAccess), capability maps, refusals audited. |
| One output shape for every pattern | One panel renders all of them, and stored runs stay readable across releases. |
| Measurement is domain code with a published formula | 70% impact-weighted completion + 20% focus minutes + 10% closing the day; streaks count closed days only. A score nobody can derive is a score nobody trusts. |
| Plan gating in the API, not the UI | One resolver (resolveTenant), one capability map, refusals audited. |
| Display copy only in i18n; catalogs hold ids | Two locales stay in sync; generated content is authored in the request locale by the engine. |
| One product module, vertical removed | modules/registry loads performance only; the template's Saudi merchant surfaces were deleted (ADR-0010), and the module contract was trimmed to what a loaded module uses. |
| No containers | pnpm setup / db:setup / db:seed against a local Postgres; docker-compose.yml deleted. |
| Cloudflare via OpenNext, not vinext | npx vinext check scores this app 88% with two issues, one load-bearing: Vite ignores the webpack config that bundles Prisma and shims pg-native/cloudflare:sockets, and next/font/google stops being self-hosted. OpenNext consumes next build's output, so the verified bundle is the deployed bundle. Revisit once Prisma bundling has a Vite-native answer. |
pg-cloudflare patched | Its default export condition points at a stub while its workerd condition points at the real socket; the adapter's tracer and bundler disagreed. patches/[email protected] makes both resolve the real file. Remove when the adapter's tracer becomes condition-aware — the failure to watch for is the same Could not resolve "pg-cloudflare". |
| Workspace API keys act as their creator, inside one workspace | wk_<prefix>_<secret> in hex (base64url's _ makes the separator ambiguous), SHA-256 at rest, plaintext shown once. Authenticated in protectedProcedure, so every existing procedure is key-accessible with no rewrite, and a cross-workspace call is refused and audited. |
4. Verified state (as of the last session)
pnpm format:check && pnpm lint && pnpm type-check && pnpm test # gates
pnpm build # 4 apps
pnpm build:worker && pnpm --filter saas exec wrangler deploy --dry-run
cp .agents/skills/template-to-product/scripts/verify-product.mjs apps/saas/ && \
DEMO_EMAIL=... DEMO_PASSWORD=... node apps/saas/verify-product.mjs # browser pass- lint: 0 errors · type-check: 25 tasks passing · tests: 13 suites / 247 tests passing (including 26 payments tests, 60 core tests, 57 api tests) · build: 4/4 apps.
- Payments: Moyasar and Tamara BNPL providers fully tested with unit tests covering checkout links, webhooks, and idempotent records.
- Wajhati platform: candidate vaults, finite application batch cryptographic hashing, truthful matching, discovery agent, employer portal, and SEO public jobs directory verified.
- Browser pass on routes: no console errors.
- MCP: an end-to-end stdio run listed seven tools and called
get_today,add_tasks,get_usage,run_pattern; an invalid task returned readable text; a cross-workspace call returned 403 and an audit entry.
5. What is left, and who owns it
Blocking a paid launch
| Item | Owner | Notes |
|---|---|---|
| Deploy to a live Worker + production Postgres + domain | needs owner's Cloudflare account (wrangler login, account id, secrets) | code path is ready |
| Moyasar live keys + webhook registered + one real subscription | owner | docs/deploy.md §payments |
| Mail domain verified (SPF/DKIM/DMARC) | owner's DNS | otherwise magic links and invitations fail |
| SMS sender id registered | owner's Unifonic/Msegat account | phone OTP is the default login mode |
Legal review of privacy policy and terms, [ ] placeholders filled | a lawyer | drafts are written, not reviewed |
| Terms acceptance at signup | agent | missing today |
| Data retention/deletion matching the policy (30-day content, 12-month audit, 24-month usage) | agent | account deletion exists; the purge does not |
Monitoring on /api/health?deep=1, 5xx alerting, a restored backup, incident contact | agent + owner | nothing exists yet |
| MCP key management UI | agent | CLI only today (pnpm --filter @repo/scripts api-key) |
Not blocking, known and documented: no scheduled jobs (no reminders), no notification types beyond two, MCP server runs from the repo rather than a published package, no pricing validation.
6. Traps that already cost time (do not rediscover them)
- Two working copies of one repo drift. It happened (
development/tampvsGlm/tamp), and a pushed commit arrived in one while a local fix waited in the other. Keep one copy, or one branch per copy. - The dev server and
pnpm buildshareapps/*/.next. Building while dev runs wedges the dev server, and the next browser pass times out on login with no useful error. Stop dev first. - A reset database looks like a code bug.
401 User not foundmeans re-seed (pnpm db:seed); the seed prints a new demo password each run. - A running dev server holds a stale Prisma client and stale workspace packages. After a schema
change or a change in
packages/*, restart it (and clear.nextif it still misbehaves). MISSING_MESSAGEis fatal to a page. Rewriting an i18n namespace silently drops keys other components still use (app.menu.*is the easy one to break).- Plan copy lives in
shared.jsononly. Apricing.*block inmarketing.jsonshadows it and ships two prices for one plan. NFKDbreaks Arabic filenames (أ→ا+ a mark the "safe characters" filter then deletes). Use NFC and allow\p{M}.- A warm hue holds more chroma than a cool one. Copy the shape of the accent ramp, never the
numbers, when retinting the brand.
--accent-500is#E75724exactly;--accent-600is the solid-button step that keeps white labels ≥ 4.5:1. - History rewrites need
--force-with-lease. Done once to drop GitHub'sfirst commit; the remote was otherwise untouched. - GitHub reports a private repo you cannot see as "not found". The account switched
mid-session (
alswat1222↔diamondcloudai) and a push failed with the same message a deleted repo gives. Checkgh auth statusbefore concluding anything was deleted. - The disk was full (116 MB free) at one point, which silently blocks installs. Regenerable
artifacts under
~/developmentwere cleaned (38.3 GB) and 597 tracked files that were served build output (a Laravelpublic/admin/dist) were restored. Watchdf -h; anode_modulestree per project plus a.nextper app adds up.
7. Security notes
- No secrets are stored in this document or the repo.
.env.localis gitignored; only.env.local.examplewith empty values is tracked. - A demo API key for the
demo-teamworkspace exists in the local database (prefixbea4ccdc). It points at localhost and is worthless elsewhere, but revoke it if in doubt:pnpm --filter @repo/scripts api-key -- --revoke <keyId> --workspace demo-team. BETTER_AUTH_SECRETmust be unique per environment; rotating it signs everyone out.