Files
kwenter/TECH_ARCHITECTURE.md
Suriyakumarvijayanayagam 949f78f6d0 Initial import: Axpert 11.2 reference material and platform blueprint
- TECH_ARCHITECTURE.md: technical blueprint for the new platform
- AI_LAYER_DESIGN_NOTES.md: AI agent, event/action, SQL and audit design
- Axpert 11.2 web runtime source, structure exports and release notes (reference)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:22:58 +05:30

20 KiB
Raw Permalink Blame History

Technical Architecture Blueprint

A modern, AI-native low-code platform that can take over from Axpert. It can adopt or migrate existing Axpert customers.

  • Status: Draft v1 (2026-09-25)
  • Related: AI_LAYER_DESIGN_NOTES.md covers the event/action model, the AI agent and the audit engine.
  • Test customer: krishtech (PostgreSQL 16, about 1,194 tables, 358 forms, 378 reports).

0. Decisions made

# Decision Choice
D1 Main language TypeScript for the platform, UI, API, runtime and AI agent
D2 Second language Go for high-volume data work: migration data mover, attachment extraction, test-data anonymizer, large exports
D3 Database PostgreSQL 16+, one schema per tenant
D4 Business data model Real relational tables, never JSON blobs. Existing Axpert tables are adopted in place first.
D5 Axpert compatibility A core feature: importer, expression parity and migration verification are built from phase 1
D6 Hosting VPS running Docker Compose. Split onto more servers as load grows.
D7 Architecture style Modular monolith (one TS API + one TS worker + Go services). No microservices yet.
D8 Clean room We rebuild the concepts only. No Axpert code, SQL, CSS or assets are copied.

1. System overview

                         ┌────────────── Browser ───────────────┐
                         │ React app: shell · form runtime ·    │
                         │ reports · designer · AI chat · admin │
                         └───────────────┬──────────────────────┘
                                         │ HTTPS
                                   ┌─────▼─────┐
                                   │   Caddy   │  TLS, reverse proxy
                                   └─────┬─────┘
             ┌───────────────┬───────────┼────────────────┬──────────────┐
             ▼               ▼           ▼                ▼              ▼
        ┌─────────┐    ┌──────────┐  ┌────────┐     ┌──────────┐   ┌──────────┐
        │ web     │    │ api (TS) │  │Keycloak│     │ worker   │   │ migrator │
        │ static  │    │ Fastify  │  │  SSO   │     │ (TS)     │   │ (Go)     │
        └─────────┘    └────┬─────┘  └────────┘     │ pg-boss  │   │ internal │
                            │                       └────┬─────┘   └────┬─────┘
             ┌──────────────┼──────────────────────────┬─┴──────────────┘
             ▼              ▼                          ▼
      ┌────────────┐  ┌───────────┐             ┌────────────┐
      │ PostgreSQL │  │  Valkey   │             │   MinIO    │
      │ (all data) │  │ (cache)   │             │ (files/S3) │
      └────────────┘  └───────────┘             └────────────┘
                   ▲
       (read-only) │  source Axpert DB during migration (Postgres / Oracle / MSSQL)

2. Tech stack

Area Choice Notes
Monorepo pnpm workspaces + Turborepo; Go modules under /go One repo, shared CI
Frontend React 19 + Vite, TanStack Router and Query, Tailwind + shadcn/ui Single-page app; no iframes
Form state Zustand store per open form, keyed by {dc, row, field} Replaces Axpert's DOM-ID data model
Grids AG Grid Community (inline editing), TanStack Table (read-only lists)
Designer dnd-kit / gridstack (layout), Monaco (expressions/SQL), React Flow (workflow)
Charts ECharts
i18n i18next, including RTL Axpert supports RTL and multiple languages
API Fastify + Zod + zod-to-openapi REST and OpenAPI, which also serve as the public integration API
DB access (TS) Kysely + pg Typed, dynamic SQL for tables created at run time
DB access (Go) pgx (COPY protocol for bulk)
Validation Zod, exported to JSON Schema The same schema validates AI agent output
Expression engine Hand-written Pratt parser → syntax tree → evaluator (TS) Runs in both browser and server
Jobs pg-boss (inside Postgres) Email, print, import/export, scheduled jobs, workflow deadlines
Auth Keycloak (OIDC and SAML, LDAP/AD, Google, O365, Okta) Custom user-storage plugin for Axpert password hashes
Files MinIO (S3-compatible) Swappable for any S3 provider
PDF Playwright (HTML → PDF) Print formats are HTML templates
AI Claude API through the Anthropic TS SDK, with tool use Tools: read_metadata, propose_definition, dry_run_query, …
SQL translation sqlglot (Python helper, migration tool only) Converts Oracle/MSSQL SQL to Postgres
Observability OpenTelemetry → Grafana stack (Loki, Tempo, Prometheus), Sentry
Testing Vitest, Playwright end-to-end, Go test, Axpert parity suite (§8)
CI/CD GitHub Actions → build images → push to registry → deploy over SSH

3. Repository layout

/apps
  web/                 React app (runtime + designer + admin + AI chat)
  api/                 Fastify API (modular monolith)
  worker/              pg-boss job runner (same modules as api, different entrypoint)
  cli/                 `plat` CLI: migrate, tenant, publish, parity
/packages
  schema/              Zod metadata schema (forms, reports, pages, workflow, events)
  expr/                expression engine: lexer, parser, evaluator, function library
  actions/             event/action model + action runner interface
  depgraph/            field dependency graph builder and resolver
  query/               query compiler (LOOKUP → SQL), SQL safety checker
  axpert-compat/       Axpert XML/JSON decoders, converters, compatibility report
  ui/                  shared React components (field widgets, grid, layout)
  sdk/                 typed API client (used by web + external integrations)
/go
  migrator/            bulk data mover, attachment extractor, row verifier
  anonymizer/          produce safe dev copies of customer databases
/infra
  compose/             docker-compose.yml (+ overrides per environment)
  caddy/  keycloak/  grafana/  backups/
/docs

4. Runtime modules (inside apps/api)

Module Responsibility
metadata Store definitions (draft/published), validate, compile (resolve dependencies, pre-parse expressions), cache in Valkey
forms Load a new or existing record, handle dependency requests, save/delete/cancel, record locking (optimistic, via a version column)
query LOOKUP compiler, allow-listed named SQL, parameter binding (:field, {field*}), dialect adapter
reports Report execution (parameters, paging, totals, grouping, pivot), export (large exports go to the Go migrator/exporter)
workflow State machine per form: approve/reject/return/review/forward, delegation, deadlines
security Access per role, responsibility, form, field and button; sets the context for row-level security (RLS)
audit Captures changes inside the same transaction; policy per form
files Upload/download via presigned URLs, attachment metadata
ddl Definition → table/column changes (preview + apply) for native forms
agent AI conversation, tools, proposal → validate → preview → publish
migration Discover/convert/report/verify/cutover; drives the Go migrator
admin Tenants, users, settings, licences

How modules talk: they call each other in-process through typed interfaces. Side effects are sent as pg-boss jobs using the outbox pattern: the job is written in the same transaction as the business data, so nothing is lost.

5. Database design, layer by layer

PostgreSQL cluster
├── platform        tenants, apps, plans/licences, global settings
├── identity        users, roles, responsibilities, permissions, row rules, api keys
└── <tenant>  (e.g. krishtech — maps to Axpert "project")
    ├── meta        form_def, report_def, page_def, workflow_def, publish_log, customtypes
    ├── data        business tables (adopted Axpert tables OR generated native tables)
    ├── wf          wf_instance, wf_task, wf_comment, wf_delegation
    ├── audit       audit_transaction, audit_change (monthly partitions)
    ├── files       file (id, bucket, key, size, mime, owner form/record/field, …)
    ├── jobs        pg-boss tables
    └── mig         migration runs, mapping tables, verification results

Adopted mode: the customer's existing Axpert tables stay in their current schema, for example krishtech, and meta.form_def points at them. We add our schemas next to them and never change their tables without a migration step that has been previewed first.

5.1 Metadata (meta)

form_def (
  id uuid pk, key text, version int, status text check (status in ('draft','published','archived')),
  definition jsonb not null,            -- validated by @pkg/schema
  compiled jsonb,                       -- dependency graph, parsed expressions (cache source)
  source text,                          -- 'native' | 'axpert-import' | 'agent'
  source_ref text,                      -- e.g. Axpert transid
  checksum text, created_by, created_at, published_by, published_at,
  unique (key, version)
)
-- report_def, page_def, workflow_def follow the same pattern

5.2 Business data (data or the adopted schema)

  • Native tables get these system columns: id uuid, created_at/by, updated_at/by, version int (optimistic lock), status, wf_state, tenant_id (only if tables are shared across tenants).
  • Grid DC = a child table with parent_id, row_no and row_key uuid.
  • Adopted Axpert tables keep Axpert's standard columns, which are mapped in the definition's columnMap:
    • <table>id numeric(16)
    • cancel, sourceid, mapname, username, modifiedon, createdby, createdon
    • wkid, app_level, app_desc, app_slevel, cancelremarks, wfroles
    • Grid tables also have <parent>id and <table>row.

5.3 Security

  • Keycloak authenticates the user. The API issues a short-lived token.
  • Every database transaction starts with SET LOCAL app.user_id = …, app.roles = ….
  • Row-level rules (Axpert axpermissions view/edit conditions) become Postgres RLS policies, generated from the definition.
  • Field-, button- and DC-level access is enforced in the forms module and hidden in the UI.
  • Separate database roles:
Role Used for
app_rw The runtime
app_ro_agent AI read queries
migrator Migration, with source access read-only
owner Running DDL; used only by the ddl module

5.4 Audit, workflow, files

See AI_LAYER_DESIGN_NOTES.md §5 for audit. Workflow deadlines are scheduled as pg-boss jobs. Files are stored in MinIO; files.file holds the metadata.

5.5 Cache (Valkey)

Compiled definitions (key includes the version), sessions, rate limits and lookup caches. Never rendered HTML and never open-form state, so any API instance can serve any request.

6. Key runtime flows

Open form → GET the compiled definition (from cache) → the client builds the Zustand store → form-load events run on the client, with server calls only for actions that need data.

Field change → @pkg/expr recalculates dependents instantly on the client. Server-backed dependents (LOOKUP/SQL/fill grid) go to POST /forms/:key/resolve with a delta, and the server returns the field values.

Save

auth → permission check → load compiled def → re-run expressions + validations (same engine)
BEGIN
  SET LOCAL app.* (RLS context)
  BEFORE_SAVE actions
  write header + grid rows (parameterized; optimistic lock on version)
  audit rows
  workflow transition
  outbox jobs (email, notifications, post-to-other-form)
  AFTER_SAVE actions
COMMIT → invalidate caches → return record + new version

7. Axpert migration and compatibility

7.1 Object mapping

Axpert object Target Automatic?
tstructs.props (zlib XML) meta.form_def Yes
ax_layoutdesign JSON Layout section of the form definition Yes
iviews / lviews XML meta.report_def Yes; SQL dialect checked
axpages meta.page_def (menu) Yes
Expressions and validations @pkg/expr (same function names) Yes; checked by the parity suite
<actions>, <formcontrol> Event/action JSON Mostly; server-only functions are re-implemented
Genmap / mdmap / fill grid POST_TO_FORM / UPDATE_MASTER / FILL_GRID actions Yes
Business tables Adopted in place, or copied Yes
Users, roles, permissions identity + RLS Yes; passwords via the legacy-hash plugin
Workflow definitions meta.workflow_def Classic yes; PEG needs review
Pending approvals (axactivetasks, *workflow) wf.wf_task Yes, at cutover
*history tables audit.audit_change Yes
*attach (bytea) + disk files MinIO + files.file Yes (Go migrator)
Stored functions (212 plpgsql in krishtech) Kept unchanged Yes
Drafts (Redis) — No; users finish drafts before cutover
Custom DLLs, JS hooks, custom HTML — No; flagged for manual rework

7.2 Modes

  • A. Adopt in place: a Postgres source; no data copy. This is the first mode we build, with krishtech.
  • B. Copy (ETL): Oracle/MSSQL sources, cleanup, or moving the customer onto our hosting. The Go migrator streams the data with COPY.

7.3 Pipeline

  1. Discover: a read-only scan that produces the inventory.
  2. Convert: definitions are created as drafts.
  3. Compatibility report: converted/partial/manual per object, unsupported functions, SQL issues.
  4. Dry run: load into a staging tenant, render every form, run every report.
  5. Verify:
    • row counts and checksums
    • expression parity against real records
    • report output compared between Axpert and us
  6. Cutover: freeze Axpert, final sync of changes, move pending tasks, switch users.
  7. Rollback: take a snapshot before the cutover. In mode B the source is never modified.

It runs as a CLI (plat migrate discover|convert|report|verify|cutover) and as an admin wizard.

7.4 Passwords

Axpert appears to store MD5-based password hashes; this must be confirmed against axusers. Import the hash, check it on the first login through the Keycloak user-storage plugin, then re-hash with Argon2. No forced reset.

8. Testing strategy

  • Axpert parity suite:
    • Extract every expression, validation and form-control rule from krishtech's 358 forms.
    • Evaluate them with @pkg/expr against real records.
    • Compare the results with Axpert's stored or computed values.
    • Runs in CI on every change to expr.
  • Report parity: run each of the 378 reports with fixed parameters on both systems and compare the rows.
  • Unit and property tests for the parser/evaluator.
  • Playwright end-to-end: open, edit, save and approve on key krishtech forms.
  • Migration verification (§7.3 step 5) also runs as a test in staging.

9. Customer data rules (krishtech)

krishtech is real customer data.

  • Confirm in writing that the customer allows it to be used for development and testing.
  • Developer machines get only anonymized copies, made by go/anonymizer. It masks names, phone numbers, emails, GSTIN/PAN, addresses and bank details, and keeps the data realistic.
  • The real copy lives only on the staging VPS:
    • encrypted disk and backups
    • access over VPN/SSH only
    • no public demo URLs
  • Host in India for Indian customers, to meet data-residency expectations under the DPDP Act.

10. VPS deployment

10.1 Environments

Env Where Data
dev Laptop, docker compose Anonymized krishtech
staging VPS #1 Real krishtech copy (restricted access)
prod VPS #2 (later a separate DB VPS) Customers

10.2 Sizing

Stage Setup
Staging / first customer 1 VPS: 8 vCPU, 32 GB RAM, 300+ GB NVMe
Growth App VPS (API/worker/Keycloak/MinIO) + dedicated DB VPS (16–32 GB RAM, NVMe) + a read replica for reports
Scale Several app VPSs behind a load balancer (the design allows it), managed or clustered Postgres, external S3

Providers with Indian regions: DigitalOcean (Bangalore), AWS Lightsail (Mumbai), E2E Networks, Hetzner (outside India, cheaper).

10.3 Services (docker compose)

caddy        TLS (auto Let's Encrypt), reverse proxy, static web files
api          Node 22, Fastify           (2 replicas possible)
worker       Node 22, pg-boss jobs
migrator     Go (internal only)
keycloak     + its own DB schema
postgres     16/17, tuned; pgBackRest for backups
valkey       cache
minio        files
grafana-stack  (optional on staging; Grafana Cloud free tier is an alternative)

10.4 Operations

  • Backups:
    • pgBackRest keeps full and incremental backups plus the transaction log (WAL), so any point in time can be restored.
    • Copies go to off-server S3 storage (Backblaze B2 / Wasabi / another region).
    • MinIO is mirrored off-server.
    • Restore drills every month.
  • Security:
    • SSH keys only, firewall (only 80/443 public), fail2ban.
    • Postgres not exposed publicly.
    • Admin tools only through WireGuard VPN.
    • Automatic security updates.
    • Secrets kept in .env files encrypted with SOPS.
  • Deploy: GitHub Actions builds the images and pushes them to GHCR. It then deploys over SSH (docker compose pull && up -d) and runs DB migrations as a separate step.
  • Monitoring: uptime check, disk/CPU/memory alerts, Postgres metrics, error alerts (Sentry).

11. Delivery phases

Phase Deliverable Done when
0. Foundations Monorepo, CI, docker compose, VPS staging, anonymizer, krishtech restored on staging Team can run everything locally with anonymized data
1. Core engine schema, expr (Axpert function set), depgraph, axpert-compat decoders + form converter, parity suite About 90% of krishtech forms convert, and the parity suite passes on their expressions
2. Form runtime Read-only render → edit → save on adopted tables, grids, lookups, fill grid, locking, audit Key krishtech transactions (e.g. PO, GRN, invoice) can be created and edited end-to-end
3. Reports + menu Report engine, report converter, menu/pages, exports Report parity passes for most krishtech reports
4. Security + workflow Keycloak, legacy passwords, roles/permissions/RLS, workflow + migration of pending tasks krishtech users log in with their existing passwords and approvals work
5. Migration toolkit Full pipeline + wizard, Go migrator (copy mode, attachments), cutover runbook A dry-run migration of krishtech with a clean verification report
6. Designer + AI Form/report/workflow designer, AI agent (build app, lookups, reports, document-to-form) A new form built from a sentence is published and works
7. Hardening Performance, mobile/PWA, printing, jobs, i18n, docs First production cutover

12. Open items

  • Confirm the Axpert password hash format in axusers.
  • List the Axpert server-only functions krishtech actually uses (from the parity extraction). That list sets the phase-2 scope.
  • Decide the licensing and pricing model (it affects multi-tenant features in platform).
  • Get written permission from krishtech to use its data for testing.