Built in public بناء أمام الجميع
Changelog سجل التغييرات
80 releases shipped, latest is iter 440. Every commit on the Mithaq platform is in this feed. 80 إصداراً مُنزّلاً، آخرها التكرار 440. كل التزام على منصة ميثاق يظهر هنا.
Apr 2026
- third brand pivot — Diwan → Mithaq (ميثاق, 'Charter / Covenant')
Diwan cancelled by TRA Article 6 (sovereign-title rule) on 28 Apr 2026. Mithaq registered with GulfCyberTech 29 Apr 2026 — action-noun (not a title), premium semantic anchored on Mithaq al-Madinah (the first written Islamic constitution, 622 CE). Code rename via the brand-config-pattern safety net (iter 347): - src/lib/brand.ts: nameEn=Mithaq, nameAr=ميثاق, domain=mithaq.om, rootUrl=https://mithaq.om. One-file flip touches every JSON-LD URL,
- fix ai.configured=false in production + brand health.service almajlis→diwan
(1) Health endpoint service field: was 'almajlis', now 'diwan'. The internal tenant slug stays 'almajlis' for backwards compat (license envelopes, sessions, webhooks) but every public-facing string must read Diwan. (2) MOONSHOT_API_KEY/AI_CHAT_API_KEY were in /opt/bhd-erp/.env but missing from running PM2 process env (verified via pm2 env 0). They got dropped during iter 357's chunk-emit-drop recovery (pm2 delete + start with 'set -a; source .env' didn't actually re-export, possibly because some
- real codebase audit caught 2 bugs
After surface walk reported 74/74 'clean', a proper source-level audit found: 1) THREE registered modules without tab branches in board.astro ai-minutes, ai-agenda, ai-summary appear in MODULE_REGISTRY (and therefore in /modules catalog + sitemap + /modules/<slug> pages) but /board?t=ai-{minutes,agenda,summary} routed to NO branch — authenticated users hit a blank content area. The features themselves exist as - deep functional check of all 74 modules + AR-mode polish
Audit pass: visited every /modules/<slug> page (74 total), verified each renders cleanly with title, h1 (EN+AR spans), full description (both languages), sector/sub-sector chips with correct hrefs, dependsOn deep links, plan badge for pro/enterprise, no undefined/[object Object]/NaN leaks in DOM, 'aj-marketing' class present (not OFC layout), no OFC text leaks on core modules. Result: 74/74 pages pass content checks. Two false-alarm 'issues' on brackets resolved (HTML entity-encoding of '&' in 'Draws & brackets' is correct).
- tenant-aware /changelog with auto Diwan release feed
Pre-fix: /changelog rendered the OFC fencing-specific changelog on Diwan tenant (wrong layout, wrong content, just the title brand-renamed). Post-fix: tenant-conditional. Diwan tenant gets a new MajlisMarketing-wrapped DiwanChangelog component that auto-renders from git log. Every commit matching '^loop iter <N>:' becomes a release entry with iter number, date, short sha, subject and (truncated) body. Grouped by month for scannability. No content/loop-log.txt to maintain — adding a commit ships a new changelog
- 5 more sub-sector modules
Fills 5 strategically valuable empty sub-sectors (3 sovereign-aligned): - government/regulator: enforcement-actions (fines, cease-and-desist orders, consent decrees, suspension/revocation hearings, public-notice schedule, appeal status) [pro] - finance/insurance-board: claims-reserves (case reserves, IBNR, ULAE by line of business, quarterly actuarial review, board attestations) [pro] - corporate/public-company: disclosures-calendar (quarterly earnings, AGM resolutions, related-party transactions, insider-dealing windows,
- on-prem backup automation, daily rolling cron snapshots
NEW: scripts/backup-rotate.sh — unattended companion to the on-demand /api/admin/backup.tar.gz endpoint shipped iter 353. Tar+gzip every active tenant's data dir to $BACKUP_DIR (default /var/backups/diwan), prune archives older than $RETAIN_DAYS (default 7), compute sha256 manifest per archive, write atomic state file at data/backup-state.json. Cron suggestion: '15 3 * * * /www/wwwroot/fencing.om/scripts/backup-rotate.sh >> /var/log/diwan-backup.log 2>&1' (03:15 UTC daily).
- per-module detail pages at /modules/<slug>
Adds 69 SEO-indexable per-module URLs (one per registry slug). Diwan tenant only (404 on OFC). Auto-generated from MODULE_REGISTRY: name (EN+AR), description, layer + group + sector + sub-sector breadcrumb chips, visibility/default-state/plan/slug meta cells, dependsOn list (with deep links to dependency module pages), 6 related-module cards (siblings by group for core, siblings by sector for sector/sub-sector layers). JSON-LD WebPage + BreadcrumbList per page. Adding a module to
- public /modules catalog page
Flat indexable catalog of every module in the registry. Diwan tenant only (404 on OFC). Groups core modules by group (governance/operations/content/ system/finance), then sector + sub-sector modules grouped by parent sector. Each card: EN+AR name, description, plan tier badge (free/pro/enterprise), sub-sector tag where applicable. Top pillrow shows total/free/pro/ent counts. JSON-LD ItemList enumerates all 69 modules for indexing. Footer link to /wizard for evaluators who prefer the 3-click sector picker.
- 5 more sub-sector modules
Fills strategic gaps in 5 sub-sectors that previously had zero tooling: - government/ministry: ministry-circulars (circulars + bulletins to subsidiary directorates with version history, addressee groups, acknowledgement tracking) - healthcare/medical-council: licensure-renewals (practitioner licence renewal queue with CME balance, exam schedule, suspension/revocation hearings, public-register sync) [pro] - education/university: senate-resolutions (academic policy, curriculum approvals, faculty appointments, honorary-degree decisions with
- scripts/audit-platform.mjs, end-to-end platform integrity check
Combines schema validation + reference checking + page probing into one runnable script. Pulls modules + sectors from the live API (catches drift between source and deployed dist), validates every module's slug, en/ar labels, icon, group, layer, sector/sub-sector references, dependsOn closure, and requiresPlan enum. Then probes: - 14 Diwan marketing pages (expect 200) - 15 sector pages /sectors/<id> (expect 200) - 48 sub-sector pages /sectors/<sector>/<sub> (expect 200)
- 5 new sub-sector modules filling empty parents
Adds 5 modules in 5 sub-sectors that previously had zero sector-aware tooling: - sport/football: match-officials (referees, assistants, VAR rosters + per-fixture appointments with cert/distance/same-day conflict detection) - corporate/holding-board: subsidiary-register (subsidiaries, ownership %, board seats, intercompany ledger, dividend up-stream history) [pro] - media/tv-board: licence-renewals (broadcasting-licence schedule, regulator filings, local-content quota tracking, renewal reminders) - energy/oil-gas-board: concession-blocks (block status, JV partner shares,
- on-prem polish, admin tenant backup endpoint + UI
GET /api/admin/backup.tar.gz, admin/superadmin gated, streams a gzipped tar of the current tenant's data dir (data/tenants/<slug>/). Includes meetings, action items, minutes, decisions, motions, attachments, signatures, audit log, license envelope, settings, branding, AI quota state. Spawns 'tar -czf -' and pipes to ReadableStream so multi-GB archives never load in memory. Audit-logs every download (actor, slug, IP). /admin/backup page: one-click download button with tenant name, estimated
- extract literal Diwan/diwan.om strings to BRAND.* in remaining 5 marketing pages (MajlisAbout, MajlisOnPrem, MajlisSecurity, MajlisCustomers, MajlisIntegrations). All JSON-LD blocks now use ${BRAND.rootUrl}/<path> + ${BRAND.nameEn} template literals: AboutPage, BreadcrumbList × 5, Product (on-prem with Brand name), WebPage × 4, FAQPage. Combined with iter 350, all marketing JSON-LD across the Diwan tenant is now variable-driven, future brand pivot = edit src/lib/brand.ts only.
- fix Diwan AR mode invisible content. OFC Base.astro stylesheet bleeds into shared CSS bundle (Astro pulls both branches of conditional in src/pages/index.astro), and OFC's '.ar-only{display:none!important}' rule wins because MajlisNav only had HIDE rules, no explicit SHOW. Added 'html.aj-ar .ar-only{display:inline!important}' + 'html:not(.aj-ar) .en-only{display:inline!important}' SHOW rules. Comment in style block documents the OFC bleed for future maintainers.
- extract literal Diwan strings to BRAND.* in 4 high-traffic marketing files (MajlisNav, MajlisFooter, MajlisMarketing layout, MajlisHome). Nav + footer + layout now read brand text + logo paths from src/lib/brand.ts. MajlisHome JSON-LD block fully variable-driven (SoftwareApplication, Organization, FAQPage, BreadcrumbList, WebSite all use BRAND.rootUrl/nameEn/nameAr/parentGroupEn/logoStacked). Hero-card slug template + tenant card host now read BRAND.domain. Future brand pivot from Diwan = edit src/lib/brand.ts only, deploy. Body-copy literals in FAQ + tagline text remain (low-risk, clean up next iter).
- new Diwan OG share card (1200x630, 187KB JPEG, deep emerald + gold + cream, Fraunces wordmark + IBM Plex Sans Arabic, 8-pt star ornament). Adds public/diwan-og.jpg + scripts/diwan-og-card.html + scripts/render-diwan-og.py. brand.ts ogImage and MajlisMarketing default flipped from /almajlis-og.jpg to /diwan-og.jpg. Mailbox: [email protected] alias to [email protected] added on VPS so /api/almajlis/contact form receiver works.
- full AlMajlis→Diwan brand rename + src/lib/brand.ts source-of-truth
Bulk substitution across 83 files: AlMajlis→Diwan, المجلس→الديوان, almajlis.om→diwan.om. Tenant slug 'almajlis' (lowercase, internal identifier) preserved everywhere it appears as a code constant; only the public brand text and domain were renamed. New file src/lib/brand.ts establishes a single source of truth for future pivots. Per skill brand-config-pattern + memory feedback_brand_name_must_be_single_variable.md: the rename trigger was AlMajlis hitting a WIPO trademark conflict at the OM registrar 27 Apr 2026, refund pending. diwan.om bought same day.
- fix /login SEO leak, AlMajlis tenant was inheriting OFC description
- add /almajlis-status to AlMajlis sitemap (was crawl-orphan)
- fix 4 dead anchor links from iter 341 trim (#deploy/#trust/#modules → /onprem,/security,/wizard)
- og:locale en_OM/ar_OM -> en_US/ar_AR (OG spec compliance)
- trim AlMajlis homepage from 14 sections to 8 (delete deploy/layers/trust/modules/integrations/bhd-group, push to dedicated pages)
- fix /board?t=athletes ReferenceError canSeeNotes (declare props)
- on-prem ed25519 license envelope (lib + issue/verify CLIs + health.json surface)
- link /almajlis-status from AlMajlis footer with green status dot
- preventive prerender=false on 13 tenant-gated SSR pages
- public /almajlis-status page (live module count + last deploy + uptime)
- enrich /api/almajlis/health.json with git deploy info + process uptime
- AlMajlis 1200x630 OG share image (fixes WhatsApp/Twitter previews)
- AlMajlis /press marketing page (tenant-branched)
- AI translate buttons in chair-notes editor
- AI translate buttons on new-thread + new-meeting drawers
- AI translate buttons on action sub-task add form
- sector cards show module count (visibility for iter 323 sub-sector additions)
- +4 sub-sector modules → count 11→15 (school-library, duty-roster, parish-services, hotel-occupancy)
- draws CSV export endpoint + download link on bracket cards
- bracket seeding helper, pick from athletes.json
- AI summary button on meeting page
- agenda template list + delete on Meetings tab
- per-agenda-item translate buttons (EN<->AR)
- print stylesheet for public draws page (A4 landscape)
- public bracket page renders results + Champion call-out
- bracket result entry, winners auto-advance
- public draws page /events/[slug]/draws + CTA on event detail
- draws + bracket generator (single-elim + pool-play)
- AI summary on action threads (entity/entityId shape)
- AI agenda template-export (save+delete)
- referee assignments tab + conflict detection
- AI summary endpoint + Summarize button on discussions
- AI agenda inline panel on meeting page
- AI agenda suggester (Kimi K2 / Moonshot)
Mirrors the iter 285 AI Minutes pattern. Same provider, same plan quota gating via aiQuota. src/lib/aiAgenda.ts: generateAgenda({context, meetingTitle?, meetingDate?, durationMinutes?, carriedActions?, recentTopics?, lang?}) calls Kimi K2 with strict-JSON system prompt. Returns 6-10 agenda items with titleEn/titleAr/notes/minutesAllocated/type/references. Rules: lead with admin/standing items, then decisions, then updates; - webhook retry queue + delivery log
src/lib/webhookDeliveries.ts (new): - Append-only JSONL at data/tenants/<slug>/webhook-deliveries.jsonl, trailing 1000 entries cap. - DeliveryStatus state machine: pending → success (2xx terminal) | failed (4xx terminal) | retry (5xx/timeout) | exhausted (after 5 attempts). - Backoff: 60s, 5m, 30m, 2h, 12h (5 attempts, ~14h total window). - trackAndFire(): writes the row, fires the first attempt async.
- sport rankings — points-decay engine + public API
src/lib/rankings.ts (new): - computeCurrentRankings({weapon?, gender?, windowMonths, limit, asOf?}) - Points formula: medal × levelMultiplier × recencyFactor + placingBonus · medal: gold=10, silver=6, bronze=3 · level: olympic=8, world=6, asian=4, regional=3, national=2, local=1 · recency: linear-decay over windowMonths (today=1.0, oldest=0.0, beyond-window=excluded) · placing: top4=+2, top8=+1 (no medal for 4th-8th but still ranked) - /about page (team + mission + BHD Group context)
AlMajlis-tenant only. The page a procurement officer reads after pricing — they want to know who they're buying from. Hero, mission, Ali team card with full identity, why-a-print-shop narrative, 11-brand BHD grid (incl. Hosn, Tech Foundry, BHD-ERP, Cardify, Dardasha, CupsByAA, ReachScreens, Rabab, arabian.ceo), how-we-work 2x2 cards, procurement CTA. JSON-LD AboutPage + BreadcrumbList. Sitemap +1 (priority 0.7 monthly).
- /customers marketing page with live OFC case study
AlMajlis-tenant only (verified OFC returns 404 via the tenant gate). src/components/majlis/MajlisCustomers.astro: brand-green case-study card with REAL numbers SSR-pulled from the OFC tenant via tenantStore.run('ofc', ...) — board members, athletes, events, meetings, action items, completion %. No fake testimonials. Body covers: what they replaced (WhatsApp groups + Drive + paper), - webhook 'Send test event' button (Phase 8 follow-up polish)
Stripe-style verification flow so an admin can confirm their receiver works before going live. src/pages/api/board/webhooks/[id]/test.ts (new): admin-only POST with optional {event} in body (defaults to first subscribed event). Builds a real HMAC-signed sample payload with body.test=true + header X-AlMajlis-Test: 1, fires a 15s POST to the webhook's URL, returns {ok, status, ms, error?, response, sentEvent, signaturePrefix}. Same - outbound webhooks per tenant — Phase 8 final
Closes Phase 8 (Billing 298 + API keys 299 + bearer-auth 300 + webhooks 301). src/lib/webhooks.ts (new): - Per-tenant store at data/tenants/<slug>/webhooks.json. 25 active cap. - 6 supported events: meeting.created, meeting.signed-off, decision.passed, member.joined, payment.received, esignature.completed. - createWebhook generates a 'whsec_<24-base64url>' secret on creation. - dispatchEvent(event, data) fires HMAC-SHA256-signed POST to every
- bearer-auth middleware (Phase 8 — API keys live)
Wires the iter 299 ApiKey store into the middleware chain so an HTTP client that sends 'Authorization: Bearer ak_*' is recognised by every downstream endpoint. src/middleware.ts: - New apiKeyAuth step (after tenantResolver, before securityHeaders). - Parses the bearer token, looks it up in the CURRENT tenant's api-keys.json via findKeyByPlaintext (sha256 hash compare).
- API keys management (Phase 8 — admin tab + endpoints)
src/lib/apiKeys.ts: per-tenant store, SHA-256 hashed at rest, plaintext shown ONCE, ak_<24-char base64url> format, 25-key cap. createApiKey/revokeApiKey/recordUsage. POST /api/board/api-keys/create, GET .../list, POST .../[id]/revoke — all admin-gated, audit-logged. src/components/admin/tabs/ApiKeysTab.astro: name+scope form, plaintext reveal with 60s auto-reload + copy button, table with scope badge + lastUsed + revoke button.
- Billing tab — current plan + AI quota + self-serve Paymob upgrade
Wires the iter 295 Paymob backend into a real /board?t=billing surface so a tenant admin can self-serve an upgrade without curl. Direct revenue path (was missing despite Paymob being live for 3 iters). src/components/admin/tabs/BillingTab.astro (new): - SSR header: tenant name + current plan (free/pro/enterprise/onprem). - AI usage card: progress bar with usedThisMonth/monthlyLimit + remaining, red bar when <10 left, friendly upgrade CTA when monthlyLimit=0,
- Folders tree in Documents tab (final sprint UI gap)
Wires the iter 291 folder backend into the existing Documents tab so the chair gets folder organization without curl. src/components/admin/tabs/DocumentsTab.astro: - New folders panel above search: folder-tree depth-flattened to chips, All / Unfiled / per-folder with file count badges. Click filters the doc-card grid client-side, no reload. - New-folder button (admin-only) — prompt-based for v1; full dialog UI
- Scheduling polls admin tab — slot picker + live tally + close
Wires the iter 288 backend into a real /board?t=scheduling-polls surface so the chair doesn't curl POST. src/components/admin/tabs/SchedulingPollsTab.astro: - New-poll form (admin-only): title, optional description, dynamic slot rows (datetime-local + duration + label, add/remove with 2-min and 10-max guards), one-per-line 'Name <email>' voter parser. POSTs JSON to /api/board/scheduling-polls/create with
- Paymob billing + tenant-aware email + eSig invitation emails
THREE infra additions wiring AlMajlis to the BHD shared services (same Paymob/SMTP credentials BHD-ERP already uses, so on-prem customers still manage one set of keys). src/lib/email.ts (rewritten): - Generic sendEmail({to, subject, html, text, attachments, replyTo, bcc}). - Tenant-aware From + branded HTML/text footer (name + domain + secondary color border, derived from readBranding()). - eSignatures admin tab — envelope creator + list (sprint UI follow-up)
Wires the iter 287 eSignatures backend into a real admin surface. A chair can now create an envelope from /board?t=esignatures without touching curl. src/components/admin/tabs/EsignaturesTab.astro: - New-envelope form (admin-only): title + optional description + one-per-line 'Name <email>' signers parser. Posts to /api/board/esignatures/create, then renders the per-signer copy-
- Engagement tile on /board overview (sprint UI follow-up)
Wires the engagement-reporting backend (iter 289) into the most-visible surface — the /board home/overview tab that every staff user lands on. OverviewTab.astro: imports engagementForTenant, computes 4-bucket counters (engaged/partial/at-risk/inactive), top-3 contributors, and top-5 at-risk for nudging. Rendered as a single Card right after the existing 'Members at risk' card. CSV export link on the header (/api/board/engagement.csv?months=12). Score formula footnote and
- wire UIs for AI Minutes + personal Calendar URL (sprint follow-up)
The Boardable parity sprint (iters 285-291) shipped 6 backend-only features. This iter wires the two highest-leverage UIs so admins + board members can actually use them without curl. Meeting page (board/meeting/[id].astro): - New 'AI assist' button next to the Edit-minutes button. - Hidden inline panel with transcript textarea, Generate button, live quota badge (used/limit from /api/board/ai/usage),
- Document Center polish — folders + per-file audit (Boardable parity gap #7)
Final gap of the Boardable parity sprint. Drive/Dropbox/OneDrive sync explicitly deferred to Phase 9 (each needs an OAuth dance — too big for one iter, and only ~3 of our top-10 buyer asks). src/lib/folders.ts: per-tenant folder tree at folders.json, max depth 6, max name 80 chars, sibling-name uniqueness (case-insensitive). createFolder / renameFolder / deleteFolder all atomic via withFileLock. deleteFolder rejects if subfolders exist (caller checks attachments
- per-user signed ICS feed (Boardable parity gap #6 v1)
Closes the Calendar Sync gap at v1 lean: every board member gets a personal subscribable ICS URL they paste into Outlook / Google Calendar / Apple Calendar. Two-way OAuth sync is the v2 follow-up (much bigger). src/lib/calendarToken.ts: per-user 32-char base64url token stored on the User record (lazy-generated via ensureCalendarToken, rotatable via rotateCalendarToken). Atomic via withFileLock on users.json. findUserByCalendarToken does the reverse lookup.
- Engagement-reporting dashboard (Boardable parity gap #5)
src/lib/engagement.ts: - engagementForTenant({months, includeInactive}) returns per-member trailing-N-month scorecard. No new write paths — pure read aggregation across meetings.json, action_items.json, polls.json. - Per-member metrics: attendance % (attended÷invited), minutes-read % (read÷attended-completed-meetings), action-completion % (done÷assigned), poll-participation % (voted÷eligible). - Composite score (0-100) = attendance×0.4 + actions×0.3 + - Scheduling polls module (Boardable parity gap #4)
src/lib/schedulingPolls.ts: - Poll + PollSlot + PollVoter types with 32-char base64url tokens. - createPoll: 2-10 slots cap, max 50 voters per poll, ISO datetime validation, atomic via withFileLock. - submitVote: votes[slotId] = yes|no|maybe; burns the token after vote. - tallyPoll: scores each slot (yes=2, maybe=1, no=0), ranks by score with yes-count tiebreaker, includes per-slot responseRate. - closePoll: picks winner (top-ranked with yes+maybe > 0), burns all
- eSignatures module (Boardable parity gap #3)
v1 scope: typed-name signature with full audit trail (IP, UA, ts). Visual signature drawing + PDF embedding ship in follow-up iters; the signed envelope JSON is the legal record of intent under Oman Royal Decree 69/2008 (Electronic Transactions). src/lib/esignatures.ts: - Envelope + Signer + AuditEvent types with sequential signing order. - createEnvelope: generates 32-char base64url tokens (cryptographically
- Board Packet PDF (Boardable parity gap #2)
New page: src/pages/board/meeting/[id]/packet.astro Single printable HTML route that combines, in this order: 1. Cover sheet (title, number, date, location, status, totals) 2. Attendees + apologies (2-column list) 3. Agenda (numbered table with time-box + AR title + notes) 4. Attached documents (numbered, with size) 5. Open action items (carried forward, with owner + due)
- Boardable feature-parity audit + ship gap #1 (AI Minutes)
AUDIT: docs/competitive/boardable-vs-almajlis.md (full scored matrix across 18 Boardable features: 2 Live, 11 Partial, 5 Missing). Top 7 gaps prioritised: AI Minutes, Board Packet PDF, eSignatures, Scheduling polls, Engagement reporting, M365/Google Cal sync, Doc Center polish (Drive sync). Marketing wins documented: bilingual, multi-tenant, on-prem, sector specialisation, OMR pricing, free tier. GAP #1 SHIPPED: AI Minutes endpoint
- 6 new sub-sector modules across 6 sectors
Adds (all defaultEnabled:false, full descriptionEn+descriptionAr): - timetable (education/k12-school) — class grid + conflict detection + parent PDF - donor-pledges (nonprofit/charity, requiresPlan:pro) — recurring gifts + segmentation - regulatory-filings (finance/bank-board, requiresPlan:pro) — CMA/CBO/FSA tracker - lease-roll (real-estate/owners-association) — per-unit lease ledger - catch-quota (agriculture/fisheries-board) — vessel quota + MAF returns - cme-credits (professional-association/engineering-council) — CPD log
- harden Majlis on-prem packaging (Dockerfile + compose + .env + .dockerignore)
Replaces the basic Dockerfile.onprem with a 3-stage build that ships prod-only deps, runs as non-root (uid 1001), uses tini for graceful SIGTERM, and exposes a healthcheck against /api/almajlis/health.json. Pinned Node 22.12.0. docker-compose.onprem.yml gains: image tag, healthcheck mirror, log rotation (10m x 5), no-new-privileges, /tmp tmpfs, explicit HOST=0.0.0.0.
- /api/public/referees.json public referee directory
Returns active roster with public-safe fields only (id, names, level, weapons, certExpires, active). PII like phone/email and the per-tournament availability rota stays internal. Filters: ?weapon=foil|epee|sabre, ?level=club|national|continental|fie, ?active=false (default true), ?limit=N (default 200, max 500). Aggregate counts (byLevel, byWeapon) bundled so a federation or
- /integrations honest live-vs-planned partner page
AlMajlis tenant only (verified OFC returns 404 via tenant gate). 7 groups, 28 items: Identity (5), Communication (4), Payments (3), Calendar (4), API (6), Storage (4), Documents (3). Each item has a Live/Beta/Planned status pill, body copy, and an honest detail line (target phase or constraint). 4-tile total/live/beta/planned counter at the top.
- /onprem dedicated AlMajlis on-prem deep marketing page
AlMajlis tenant only (verified OFC returns 404 via tenant gate). Sections: hero + 7 highlight pills, what we ship (5 cards), hardware sizing (4-tier table), 5-step install, security posture (5 cards: keys/no-phone-home/network/audit/escrow), FAQ (8 Qs), CTA card linking to security/pricing/dpa. JSON-LD: Product+Offer (OMR 12000), BreadcrumbList, FAQPage.
- tenant-aware /security — AlMajlis platform security page
OFC's existing vulnerability-disclosure policy at /security stays unchanged on fencing.om. AlMajlis tenant now renders a dedicated platform security page built for procurement + IT teams who ask for a security questionnaire response before signing. Renders 8 sections (tenant isolation, authentication, authorisation, audit trail, transport+storage, data residency, operations, incident response) with 4-bullet detail per section, 10 compliance pill badges
Want a deeper look? See the module catalog or platform status. تريدون تفاصيل أكثر؟ راجعوا كتالوج الوحدات أو حالة المنصة.