Move Axpert material to reference/ and docs to docs/
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
219
docs/AI_LAYER_DESIGN_NOTES.md
Normal file
219
docs/AI_LAYER_DESIGN_NOTES.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# AI Layer & Event/Audit Design Notes
|
||||
|
||||
These notes keep the useful ideas from an earlier ChatGPT brainstorm. They have been corrected against the real Axpert 11.2 code and the krishtech database. The rest of that conversation is no longer needed.
|
||||
|
||||
- **Scope:** the AI agent, the event/action engine, the SQL layer and the audit engine.
|
||||
- **Out of scope:** the platform spec itself (forms, grids, reports, security, workflow). That comes from the code analysis.
|
||||
|
||||
---
|
||||
|
||||
## 1. Guiding principles
|
||||
|
||||
1. **Don't clone the software.** Work out the behaviour, the metadata and the database, then build a clean web engine. Write all code from scratch; never copy Axpert code, SQL or assets.
|
||||
2. **Replace the runtime first, then grow into a low-code platform.** First run existing Axpert apps (forms, reports, workflow) against the existing database. Then add the designer and the AI-driven app building.
|
||||
3. **The agent defines, the runtime enforces, the database stores.** The AI never runs anything directly. It produces application definitions, which are validated, previewed and then run by the runtime.
|
||||
|
||||
## 2. Event → Action model
|
||||
|
||||
Events are stored as **structured actions (JSON)**, not raw SQL or script text.
|
||||
|
||||
```json
|
||||
{
|
||||
"trigger": "FIELD_CHANGE",
|
||||
"field": "customer_code",
|
||||
"actions": [
|
||||
{
|
||||
"type": "LOOKUP",
|
||||
"source": "customer",
|
||||
"where": { "customer_code": "$field.customer_code" },
|
||||
"select": ["customer_name", "credit_limit"],
|
||||
"map": { "customer_name": "$field.customer_name",
|
||||
"credit_limit": "$field.credit_limit" }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Triggers** map to Axpert's `apply` values:
|
||||
|
||||
| Trigger | Axpert `apply` value |
|
||||
|---|---|
|
||||
| `FORM_LOAD` | On Form Load |
|
||||
| `DATA_LOAD` | On Data Load |
|
||||
| `FIELD_ENTER` | On Field Enter |
|
||||
| `FIELD_CHANGE` | On Field Exit |
|
||||
| `BEFORE_SAVE` | (new) |
|
||||
| `AFTER_SAVE` | After save transaction |
|
||||
| `BEFORE_DELETE` | (new) |
|
||||
| `BUTTON_CLICK` | (new) |
|
||||
|
||||
**Action types:**
|
||||
|
||||
| Group | Actions |
|
||||
|---|---|
|
||||
| UI | `SHOW`, `HIDE`, `ENABLE`, `DISABLE`, `REQUIRE`, `SET_VALUE`, `MESSAGE`, `ADD_ROW` |
|
||||
| Data | `LOOKUP` (preferred), `SQL_QUERY`, `SQL_INSERT`, `SQL_UPDATE`, `SQL_DELETE`, `PROCEDURE_CALL`, `FUNCTION_CALL` |
|
||||
| Flow | `IF` / `ELSE_IF` / `ELSE` (condition = expression in the shared expression language) |
|
||||
| Navigation | `OPEN_FORM`, `OPEN_FORM_WITH_DATA`, `OPEN_REPORT`, `OPEN_PAGE` (Axpert: `LoadForm`, `LoadFormAndData`, `LoadIView`, `LoadPage`, `OpenPage`) |
|
||||
| Other | `FILL_GRID`, `SAVE`, `PRINT`, `CALL_API` |
|
||||
|
||||
**Rule example**, written the way a user would say it:
|
||||
|
||||
```
|
||||
WHEN customer_type changes
|
||||
IF customer_type = "CREDIT"
|
||||
THEN show credit_limit, enable credit_days, require credit_limit
|
||||
```
|
||||
|
||||
### Converting old Axpert events
|
||||
|
||||
Old events are **parsed and converted, never executed** as raw code:
|
||||
|
||||
```
|
||||
Axpert <actions> XML / formcontrol tokens / expressions
|
||||
↓ parser
|
||||
syntax tree
|
||||
↓ converter
|
||||
structured actions (above)
|
||||
```
|
||||
|
||||
The Axpert format is already known from the code analysis:
|
||||
|
||||
- `<actions>` holds named actions with `apply="On Field Exit|…"`. Each action contains steps:
|
||||
- `op=0` if (with an `iif` expression in `exprset`)
|
||||
- `op=1` else
|
||||
- `op=5` task (Field Access, Save, Open TStruct/Iview)
|
||||
- `op=8` end
|
||||
- `<formcontrol>` holds token lists:
|
||||
- Triggers: `f<field>` / `f_On Form Load`.
|
||||
- Control words: `cif`, `celseif`, `celse`, `cend`, `cand`, with `et`/`net` for equals / not equals.
|
||||
- Target prefixes: `8` hide, `9` show, `1`/`2` enable/disable, `3` set value.
|
||||
- Advanced users can still hand-write events in an **Advanced Event Editor** (Monaco).
|
||||
|
||||
## 3. SQL layer
|
||||
|
||||
```
|
||||
Event → Action Engine → Query Compiler → SQL Executor → DB adapter → Postgres
|
||||
↓
|
||||
result mapping → form fields
|
||||
```
|
||||
|
||||
1. **The agent prefers `LOOKUP`**, a high-level description of the query. The **Query Compiler** turns it into SQL for the target database, so the agent doesn't have to think about database-specific syntax.
|
||||
2. **Raw SQL is allowed for complex cases**: aggregates, joins, updates and stored procedures.
|
||||
3. **Every query is parameterized.** Queries use `:customer_code` placeholders that are bound at run time, and are never built by pasting strings together. Axpert does exactly that, and it is a weakness you can sell against.
|
||||
4. **Axpert's SQL placeholders must be supported:**
|
||||
- `:fieldname` binds to the current field's value.
|
||||
- `{field*}` expands to an IN-list over all grid rows.
|
||||
- Parent values come from the current row or DC context.
|
||||
5. **No endpoint ever runs SQL sent by the client.** Axpert's `callExecuteSQL` does, and it must not be copied. Named SQL is kept server-side and allow-listed, the equivalent of Axpert's `axdirectsql`.
|
||||
6. **Database adapter:** support Postgres first, because the krishtech customer runs PostgreSQL 16. Add MSSQL or Oracle only if a paying customer needs one.
|
||||
|
||||
## 4. AI agent: writing SQL and definitions automatically
|
||||
|
||||
The user describes what they want in business terms and never has to write SQL.
|
||||
|
||||
```
|
||||
User: "When I select a customer, show their name and credit limit."
|
||||
↓ intent FIELD_CHANGE on customer_code
|
||||
↓ schema agent read metadata: Customer form → customer_code, customer_name, credit_limit
|
||||
↓ query planner LOOKUP customer WHERE customer_code = current RETURN name, credit_limit
|
||||
↓ SQL generator parameterized SQL (only for complex cases)
|
||||
↓ validation schema check + test run (EXPLAIN)
|
||||
↓ event definition attached to the field
|
||||
↓ preview the user sees it working before publishing
|
||||
```
|
||||
|
||||
Complex example: "Average monthly sales for the last 6 months, excluding cancelled orders." The agent reads the schema and writes the aggregate SQL itself. The SQL is hidden unless the user opens the advanced details.
|
||||
|
||||
**Safety limits:**
|
||||
|
||||
- The agent works only from **your metadata and schema** (the metadata tables, `information_schema`). It never guesses table names.
|
||||
- Every definition the agent produces is **validated against the metadata schema** (Zod or JSON Schema) before it is saved.
|
||||
- AI-generated **reads** run under a **read-only database role** with row-level permissions applied.
|
||||
- AI-generated **writes** (`SQL_UPDATE`/`INSERT`/`DELETE`, procedures) **always need a human to approve** them before publishing.
|
||||
- Every AI change is versioned and shows a diff, so it can be rolled back.
|
||||
|
||||
**Other agent features to build on the same pipeline:**
|
||||
|
||||
- Build an app from a description, e.g. "Create a GRN form with a PO lookup and item grid."
|
||||
- Ask a question in plain language and get a report.
|
||||
- Explain and write expressions.
|
||||
- Fill a form from a document, e.g. an invoice PDF becomes a purchase bill with its item grid filled.
|
||||
- Workflow insights and anomaly flags.
|
||||
|
||||
## 5. Audit engine
|
||||
|
||||
**Axpert today:** one `<transid>history` table per form, with 81 of them in krishtech. Columns: `recordid, fieldname, oldvalue, newvalue, rowno, modno, frameno, …`. Each form can track all fields or selected fields, and optionally only some users.
|
||||
|
||||
**Design:** a separate **Audit Engine module**, stored **in the same database** for V1.
|
||||
|
||||
```
|
||||
Save / Delete Engine
|
||||
→ capture OLD values → write data → capture NEW values
|
||||
→ Audit Engine (policy + change capture)
|
||||
→ audit tables ← all in ONE database transaction (consistent)
|
||||
```
|
||||
|
||||
**Tables:**
|
||||
|
||||
```
|
||||
audit_transaction -- one row per save or delete
|
||||
audit_id, app, form (transid), record_id, action (INSERT/UPDATE/DELETE/CANCEL),
|
||||
user_id, at (timestamptz), ip, device, source (form/api/agent/import),
|
||||
definition_version
|
||||
|
||||
audit_change -- one row per field changed
|
||||
audit_id, dc, row_no, row_key, field, old_value, new_value
|
||||
```
|
||||
|
||||
`dc`, `row_no` and `row_key` are needed so that changes to **grid rows** can be traced. ChatGPT's version left these out.
|
||||
|
||||
**Audit policy lives in the form definition**, and the agent sets it:
|
||||
|
||||
```json
|
||||
"audit": { "enabled": true, "mode": "selected_fields",
|
||||
"fields": ["salary", "designation"], "users": "all" }
|
||||
```
|
||||
|
||||
"Track changes to salary and designation" → the agent writes this policy, and the runtime does the rest.
|
||||
|
||||
**Compatibility:** per-form views (`<transid>history`) built on the new tables, so Axpert-style history screens keep working.
|
||||
|
||||
**Scaling path** (not needed for the first version). The rough load: 5,000 stores × 100 transactions × 20 fields is about 10M change rows a day.
|
||||
|
||||
```
|
||||
V1: same Postgres, audit written in the same transaction (partition audit_change by month)
|
||||
V2: outbox table → NATS JetStream → audit store (Postgres, or ClickHouse for analytics)
|
||||
```
|
||||
|
||||
The application model does not change between V1 and V2; only the storage behind the Audit Engine does.
|
||||
|
||||
## 6. Overall runtime shape
|
||||
|
||||
```
|
||||
User ──► Conversational Agent ──► Application Definition (forms, events, rules, audit policy)
|
||||
│ validate + preview + publish
|
||||
▼
|
||||
RUNTIME
|
||||
┌──────────────┬──────────────┼──────────────┬──────────────┐
|
||||
Form Engine Event/Action Rule/Expr Security Workflow
|
||||
Engine (shared) Engine Engine
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
UI actions DB actions Mapping
|
||||
│
|
||||
Query Compiler → SQL Executor → DB adapter
|
||||
│
|
||||
Save Engine ──► Audit Engine
|
||||
│
|
||||
Postgres (business data + new metadata schema + audit)
|
||||
```
|
||||
|
||||
## 7. Open decision
|
||||
|
||||
**Go vs TypeScript for the backend.** The expression engine must run in both the browser and the server.
|
||||
|
||||
- **TypeScript everywhere:** one codebase, the simplest option.
|
||||
- **Go backend:** the expression engine is written in Go and compiled to WASM for the browser.
|
||||
|
||||
Decide this before starting the metadata schema and the expression engine.
|
||||
349
docs/TECH_ARCHITECTURE.md
Normal file
349
docs/TECH_ARCHITECTURE.md
Normal file
@@ -0,0 +1,349 @@
|
||||
# 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`)
|
||||
|
||||
```sql
|
||||
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.
|
||||
Reference in New Issue
Block a user