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. تريدون تفاصيل أكثر؟ راجعوا كتالوج الوحدات أو حالة المنصة.