Skip to content

OES release build 12/05/2026

Latest

Choose a tag to compare

@nouverbe nouverbe released this 12 May 12:46
· 1 commit to master since this release

OES Enterprise v1.3.0 — alpha (2026-05-12)

239 commits since v1.2.6. Big-feature release across runtime, language, and platform. Three major arcs converged: a complete web-runtime path (wenterprise-server.exe + wfrontend.dll), a significant script-language extension (LINQ + first-class lambdas + closure capture + a C-style syntax), and accounting-domain metadata (Chart of Accounts / Chart of Characteristic Types / Accounting Register). Plus session-registry / connection-pool / worker-pool infrastructure, an AOT bytecode cache, macOS / Linux portability through CMake, and a PBKDF2 password migration.

This release is alpha — it ships the working prototype on which we have been iterating; some web-side controls and a few language polish items are still being filled in. See Known gaps at the bottom.


Script language

LINQ — language-integrated queries

Two equivalent surfaces compile to the same primitives.

Block syntax (eager, inline OPER_FOREACH):

var q = from o in Orders
        let doubled = o.Total * 2
        where doubled > Threshold
        orderby o.Date descending
        select { N = o.Number, V = doubled };

Clauses: from / where / let / skip / take / select / distinct / orderby [ascending|descending] / group X by K [into g] / join b in Y on a.K equals b.K. The into g continuation re-enters the block with g bound to Structure{Key, Values}.

Chain syntax (lazy, OPER_CALL_LINQ opcode → virtual ibValue::DispatchLinqMethod):

var sorted   = arr.OrderByDescending(Function(x) Return x.Total EndFunction);
var grouped  = orders.GroupBy(Function(o) Return o.Country EndFunction);
var joined   = customers.Join(sales,
                              Function(c) Return c.Id EndFunction,
                              Function(s) Return s.CustId EndFunction,
                              Function(c, s) Return c.Name + ": " + s.Amount EndFunction);
var total    = arr.Where(p).Select(s).Sum();

31 chain operations:

  • pipeline: Where / Select / Distinct / OrderBy / OrderByDescending / GroupBy / Join
  • limit / control: Skip(n) / Take(n) / SkipWhile(λ) / TakeWhile(λ) / Reverse()
  • set ops: Concat / Union / Intersect / Except
  • terminals: Count / ToArray / First / Last / Single / ElementAt(n) / Any / Contains(v) / SequenceEqual(other) / Aggregate(seed, λ) plus all *OrDefault variants
  • indexed: WhereIndexed / SelectIndexed — lambda gets (elem, index)
  • aggregations: Sum / Min / Max / Average no-arg and with a lambda selector

Implementation notes:

  • Hash-based Join and GroupBy via std::map<ibValue, …, KeyCmp> (O(log N + bucket) lookup) instead of an O(N×M) linear scan.
  • OPER_CALL_LINQ carries the ibLinqMethod enum id directly in m_param3.m_numIndex — no const-pool string lookup, no FindMethod walk at runtime.
  • ibValue::GetLinqMethodTable() is the single source of truth for { id, name, helper }; drives compile-side FindLinqMethodByName.
  • IntelliSense intentionally hides chain methods from the .-dropdown — they polluted suggestions on non-iterable receivers (scalars, forms, buttons, documents). Block-syntax keywords are the discoverable path. Chain still compiles fine when typed by hand.

First-class anonymous functions (lambdas)

Function(args) … EndFunction / Procedure(args) … EndProcedure (or CES Function(args) { … }) are now expressions, not just declarations:

var add = Function(x, y = 10) Return x + y EndFunction;
button.OnClick = Procedure(sender) LogClick(sender.Name) EndProcedure;
users.Where(Function(u) Return u.IsActive EndFunction);

add(5);       // 15  (y defaults to 10)
add(5, 20);   // 25
add(1, 2, 3); // throws "Too many arguments to function value"
  • Backed by ibByteCode::ibByteFunction with kind = Lambda in m_listFunc; runtime value is ibValueFunction(parentBc, funcIndex) (CLSID VL_FUNC).
  • Default parameter values: literal primitives only (number / string / date / bool / null) — keeps lambda values self-contained, no creation-time external object lifetime.
  • Runtime arg-count validation: too many → throw; too few with no default → throw with parameter name from m_listParamRealName.
  • Full debugger + eval support — call stack shows <lambda@N>(p0=…), m_listLocals carries parameter and local names so watch expressions resolve.
  • Host-side API: InvokeLambdaWithArg(callable, arg, retVal) (BACKEND_API) — used by aggregations (arr.Sum(λ)) to fire script lambdas from C++ without going through OPER_CALL_LAMBDA bytecode.

Closure capture

Outer-function locals are visible inside an anonymous function, with a live link (mutations propagate in both directions):

Function counter()
  var i = 0;
  var bump = Function() i = i + 1; Return i; EndFunction;
  Return bump;
EndFunction

var c = counter();
Message(c());   // 1
Message(c());   // 2  (i lives between calls)
Message(c());   // 3
var threshold = 5;
var big = arr.Where(Function(x) Return x > threshold EndFunction);

Mechanism — per-frame heap promotion (NOT per-slot boxing):

  • A function that contains an inner lambda has m_needsHeapFrame = true (lazy mark on ibCompileContext::ibFunction during the GetVariable parent walk).
  • Its run-time frame is allocated via std::make_shared<ibRunContext>(varCount); the lambda value holds vector<shared_ptr<ibRunContext>> m_capturedFrames.
  • OPER_CALL_CLOSURE (split from OPER_CALL) is emitted when the callee has m_needsHeapFrame — hot OPER_CALL stays probe-free.
  • OPER_CALL_LAMBDA per-invoke wires m_pppArrayList[1..N] from m_capturedFrames[0..N-1]->m_pRefLocVars; existing OPER_GET / OPER_SET with depth ≥ 1 work unchanged.
  • Lexical parent: m_parentRunContext on a lambda's frame points at its lexical (capturing) parent — first captured frame, not the dynamic caller — so triple-nested captures walk correctly.
  • Debugger renders captured-frame locals labelled <fn>.<var> in the Locals view, skipping non-heap-promoted ancestors.

Default-argument expressions and module-level-var capture remain out of scope (defaults stay literal primitives; module vars are passed via args / Context bindings).

CES — C-style syntax (default for new configurations)

if (…) { … }, for each (x in coll) { … }, while (…) { … }, Procedure F(args) { … }, ;-terminated statements, brace-delimited blocks.

  • ibCompileCode::SetCodeStyle(CODE_CES) is the new process-global default; existing configurations preserve their stored Syntax flag (the wire token still reads vbs for back-compat — the user-visible label is now ves).
  • VES (Visual-Basic-style ES, same family as VBScript / VB6 / 1C BSL — keyword-fenced If … Then … EndIf, For Each … Do … EndDo, End…) is kept for migration; it is the same parser, just a different lexer mode.
  • Block scope { … } in CES — variables declared inside a brace block are stamped with a compile-time m_scopeDepth; runtime tracks a single m_currentScopeDepth counter on ibRunContext; SendLocalVariables filters by entry.m_scopeDepth ≤ ctx.m_currentScopeDepth. No vector-of-bools, no per-IP scope-range tables.
  • IntelliSense + auto-indent + brace highlight + gutter gradient for CES; CompileBlock supports a single-statement form (allowSingleStmt) for module bodies and one-liner control structures.
  • New: 4 lambda opcodes (OPER_LFUNC, OPER_ENDLFUNC, OPER_CALL_LAMBDA, OPER_FUNC_PTR), plus the trio rename OPER_CALL_L → OPER_CALL_CLOSURE, OPER_CALL_M → OPER_CALL_METHOD, OPER_CALL_VAL → OPER_CALL_LAMBDA.

Typed-tag specialisation (ibValueTypes)

New type tags so hot dispatch paths skip the dynamic_cast walk through wxClassInfo:

  • TYPE_FUNCTION = 203 for ibValueFunction
  • TYPE_ITERATOR = 204 for ibValueIterator

Used at OPER_CALL_LAMBDA, OPER_FOREACH, OPER_NEXT_ITER, and the LINQ dispatcher; helpers AsFunction(ibValue&) / AsIterator(ibValue&) do a typed-tag check + static_cast plus one TYPE_REFFER hop. GetTypeIDByRef skip-list extended for the new tags (was causing infinite recursion through GetClassType ↔ GetTypeIDByRef).

ibValue layout tightening

ibNumber m_fData moved out of the ibValue::union. Non-trivial heap-owner types (with a conditional std::vector<uint32_t>* underneath the immediate-tagged uint64_t) cannot live in a union — switching from TYPE_REFFER to TYPE_NUMBER previously interpreted the lower 4 bytes of m_pRef as a BigImpl* and ran a vector destructor on vtable memory. ibValue grew by 8 bytes; the entire class of union-punning UB is gone.

AOT cache version v10

kAOTFormatVersion = 10 (was v9). Several incompatible changes accumulated:

  • m_needsHeapFrame per function.
  • m_scopeDepth per ibByteCodeVarInfo (for block-scope visibility filtering).
  • OPER_CALL_LINQ opcode insertion shifted every subsequent opcode value.

Old cache rows are rejected — first run after deploy recompiles + repopulates sys_bytecode_cache. Build pipeline:

  • byteCodeAOT.cpp::SerializeAOT / DeserializeAOT (linear binary, host-endian, magic 'PBC1').
  • sys_bytecode_cache row keyed by descriptor + source hash + metadata version, populated on ibRuntimeModuleDataObject::Compile, invalidated on module Save / Delete.
  • Cross-bc resolver is kind-driven (ibVarKind / ibFnKind enums on ibByteCodeVarInfo / ibByteFunction), no runtime dependency on ibCompileContext.

Web runtime (wenterprise-server.exe + wfrontend.dll)

A complete server-side rendering pipeline that opens existing OES configurations in a browser with no source changes.

Architecture

  • wenterprise-server.exe — standalone HTTP server hosting cpp-httplib, owns one OES process, serves one or more browser tabs.
  • wfrontend.dll — web variant of frontend.dll, ships a single-page HTML/CSS/JS client (webClient.cpp blob) plus the server-side render logic.
  • ibWebVisualHost — server-side mirror of ibVisualHost; renders form trees to JSON.
  • Per-tab ibWebSession — each browser tab is its own sys_session row with its own m_userInfo, m_workDate, debug listener.
  • Live updates via Server-Sent Events; polling fallback on /active (2 s interval) for the active tab's tree diff.

UI shell

  • Title bar / status bar / output panel (Message / Alert / BackendError) / modal dialogs (showDialog, alert, confirm, prompt).
  • Sidebar with Interfaces (subsystems) and All Functions dropdown — gated by activeMetaData->AccessRight_ModeAllFunction().
  • Tab strip with icons, modified * marker, scroll arrows on overflow.
  • Boot overlay until initial /session + /interfaces + /active promises resolve — eliminates FOUC.
  • Network-status indicator (loading spinner > 500 ms, sticky "no server" banner).

Controls ported

  • ibWebTextCtrl — full edit / focus / commit pipeline (POST /change/<id> with value=…).
  • ibWebButtonwxEVT_BUTTON via QueueEvent / ProcessPendingEvents, identical to desktop's OnButtonPressed.
  • ibWebToolbar + ibWebToolBarItem + ibWebToolBarSeparator — iteration 2: items + separators render, tool captions resolved via owner action collection.
  • ibWebBoxSizer + ibWebWrapSizer + ibWebStaticBoxSizer + ibWebGridSizer (in webSizer.{h,cpp}).
  • ibWebMDIChildFrame per tab; ~ibWebFrame deferred-close pipeline so script OnClose handlers run on the worker thread.

Still incremental: checkbox, combobox / choice, listbox, radiobutton, notebook, tablebox / gridbox — registered as ibWebStubControl(type) placeholders so metadata loads cleanly, JS renderers are filled in tier by tier.

Sessions, auth, debug

  • POST /loginwfrontendLoginibWebSession::Loginregistry.Connect + ticket.AttachappData->AuthenticateUser (PBKDF2, with MD5 fallback) → InstallUser.
  • /auth-info{ "hasUsers": bool } probe for the client to choose between open-access fast path and a credentials prompt.
  • Per-tab tabSid in sessionStorage, surfaced via X-OES-Session header or ?sid= query (for plain-GET endpoints like /tab/<i>/icon where browsers don't carry custom headers).
  • pagehide + sendBeacon/logout?sid=<tabSid> on tab close; 2-min idle sweep is the fallback for Safari / kill-9.
  • BroadcastChannel-based tab-duplicate detection (a cloned tab rotates tabSid + reload).
  • Per-session ibDebuggerServer — designer breakpoints / step land on the right tab; sessionGuid wire-tagged on a single TCP channel to designer.
  • /admin/diag JSON endpoint — pool / worker / session aggregate counters.
  • /admin/sessions/<guid>/kick and /admin/sessions/<guid>/reload HTTP endpoints + designer dialog right-click + C++ ibSessionRegistry::Instance().Kick(guid) / Reload(guid) — three entry points share WriteSessionSignal.

Launcher / Designer integration

  • Launcher's Web button spawns wenterprise-server.exe via cmd /C (tail-able temp log), then wxLaunchDefaultBrowser.
  • Designer Debug → Start debugging → Web client / Start without debugging → Web clientappData->RunWebServer(port, urlPrefix, withDebug).
  • Manifest handshake — launcher discovers running wenterprise-server.exe instances on the same machine (port auto-pick + manifest file in %TEMP%).

Workarounds (Windows-specific)

  • localhost → 127.0.0.1 302 redirect — Windows Happy-Eyeballs IPv6-first DNS adds ~200 ms per cold connect; the redirect lands subsequent requests on the IPv4 literal.
  • set_keep_alive_max_count(1) — cpp-httplib's keep-alive select() loop occasionally stalls for the full read_timeout on Windows. One TCP per request keeps stalls bounded (~200 ms cold connect, no multi-second pauses).

Accounting metadata

Three new metadata object types, all with full Designer support, icons, bindings, deletion protection, and appearance in All Functions:

  • ibValueMetaObjectChartOfCharacteristicTypes — user-defined dimensions (CLSID = MD_CHCT). Property selector ibPropertyChartOfCharacteristicTypes. Backing SubcontoKindsTable registered through ibValueMetaObjectSubcontoKindsTable.
  • ibValueMetaObjectChartOfAccountsCLSID = MD_CHA. Accounts with subconto kinds, currency / quantitative / off-balance flags, subaccount hierarchy. Property selector ibPropertyChartOfAccounts.
  • ibValueMetaObjectAccountingRegisterCLSID = MD_ACCR. Postings, Dr/Cr balances and turnovers (Balance / Turnovers / DrCrTurnovers), record-set movements (WriteRecordSet), full object lifecycle including XML / JSON round-trip.

ibPropertyInnerAttribute wraps SubcontoKindsTable for copy / compare correctness. Owner-dialog filters updated so Chart of Accounts and Chart of Characteristic Types are not offered as Owner of plain catalogs.

These types are now also present in:

  • ibDialogFunctionAll::BuildTree (runtime desktop).
  • wfrontendAllFunctionsJSON specs[] (web).
  • ibRoleEditor, ibInterfaceEditor, ibMetaTree (Designer).

Text-format configuration (XML / JSON)

Full configuration round-trip in two textual formats, useful for git storage, manual editing, and AI-assisted authoring.

  • OES-XML-2.0ibMetaDataConfigurationBase::SaveConfigToXML(fileName) / LoadConfigFromXML(fileName). Designer menu Configuration → Export/Import configuration to/from XML. File: src/engine/backend/metadataConfigurationXML.cpp.
  • OES-JSON-1.0 — same API surface (SaveConfigToJSON / LoadConfigFromJSON) via metadataConfigurationJSON.cpp. Library: nlohmann/json v3.11.3 (MIT, header-only, src/3rdparty/nlohmann/json.hpp).

Serialised content:

  • All 11 metadata object types (Catalog / Document / Enumeration / Constant / InformationRegister / AccumulationRegister / DataProcessor / Report / ChartOfCharacteristicTypes / ChartOfAccounts / AccountingRegister).
  • Attributes with full type qualifiers, tabular sections, forms (base64 binary + module text), object and manager modules, predefined values.
  • Default form assignments, MetaDescription bindings (Owner / Generation / RegisterRecord / ChartOfCharacteristicTypes / ChartOfAccounts), QuickChoice, WriteMode, Periodicity, RegisterType, dimensions, resources, enum values.
  • CLSID stored as u64 numeric values (not strings) — fix that landed during this arc.

ibValue primitive serialisation (valueSerialization.cpp::DoSerialize / DoDeserialize) covers Boolean / Number / String / Date for client-server data exchange. The metadata layer wraps values in the OES Serialize envelope (S: OES Serialize;;;C:<class>;;;L:<len>;;;D:<data>;;;E: OES Serialize;;;).


macOS / Linux portability

CMake-based cross-platform build (Windows continues to use MSBuild).

  • CMakeLists.txt at repo root; cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel 3 (parallelism capped on 16 GB RAM machines).
  • Targets: oes_backend, oes_frontend, oes_wfrontend, oes_enterprise, oes_designer, oes_launcher, oes_daemon, oes_codeRunner, oes_classChecker, oes_simplePlugin, oes_wenterprise-server.
  • Optional DB driver toggles: OES_USE_FIREBIRD, OES_USE_POSTGRES, OES_USE_SQLITE, OES_USE_MYSQL, OES_USE_ODBC, OES_USE_TBB.
  • POSIX socket / dlfcn headers wrapped for Clang on macOS arm64.
  • Aggregate-init fixes ({ … } initializers under -std=c++17), activation-recursion guards (Cocoa main-loop differences), Firebird engine13 plugin discovery on macOS.

Session registry, connection pool, worker pool

Replaces the legacy ibApplicationDataSessionUpdater (1 Hz heartbeat thread on the singleton) with a unified registry plus pools.

ibSessionRegistry

  • Single consumer thread, priority queue (Urgent / Normal / Low / Background, strict descending drain, FIFO within bin).
  • Owns three pool-checkout connections (write / probe; the old m_lockConn is retired together with the pessimistic-lock liveness model).
  • Tick schedule: 1 s JobHeartbeatOwn (UPDATE own lastActive) + JobRefreshSnapshot; 3 s JobSweepStale (zombie cleanup via TryProbeRowLock fast path with a 60 s lastActive cutoff fallback). Eager initial sweep + refresh so Active Users is populated immediately.
  • Fatal invariant: any exception out of ThreadBodyDie()std::terminate. IsThreadAlive() / IsFatal() accessors for producers.
  • ibSessionPolicy interface — first-veto-wins chain consulted in ProcessAdd. ibDesignerExclusivePolicy ports the old VerifySessionUpdater veto semantics.
  • ibSessionTicket — RAII, move-only owner handle, dtor submits Remove@Urgent. Attach() / Detach() / Release().
  • ibConnectRequest / ibConnectResult — unified Connect(req) entry for desktop / designer / web-server / web-cookie.
  • 3-phase NotifyAuthenticated:
    1. OnFirstConnect listeners (one-shot per process; populates activeMetaData).
    2. s->EnsureRoot() (idempotent CreateRoot(activeMetaData) — session's module manager allocated NOW).
    3. OnAuthenticated listeners (every authenticated session; RunDatabase / CompileRoot / AttachRuntime).
  • Auth split: pure appData->AuthenticateUser(user, pwd, &info) (PBKDF2 verify + lazy MD5 → PBKDF2 upgrade) + side-effect InstallUser(info, rawPassword).

sys_session schema

New columns: pid INTEGER, address VARCHAR(256), currentActivity VARCHAR(128), signal VARCHAR(32), kind INTEGER. MigrateTableSession issues per-column ALTER TABLE ADD COLUMN in try/catch so legacy databases work too. InsertSessionRow does a 6-column core INSERT followed by a tolerant UPDATE of the new columns — legacy schemas just keep them NULL.

ibSessionKind

Separate axis from ibRunMode (1:1 mirror plus a web split):

  • Launcher = eLAUNCHER_MODE, Designer = eDESIGNER_MODE, Enterprise = eENTERPRISE_MODE, Service = eSERVICE_MODE, WebServer = eWEB_ENTERPRISE_MODE (the wes process's technical row), WebClient = 100 (per-tab caller).
  • moduleManager::AttachRuntime filters by kind — runtime runs for Enterprise / WebClient / Service.

Per-driver NoWait transactions

ibTxOptions::noWait honoured in all 4 drivers:

  • Firebird: isc_tpb_nowait in TPB.
  • PostgreSQL: SET LOCAL lock_timeout = 0 after BEGIN.
  • MySQL / InnoDB: SET SESSION innodb_lock_wait_timeout = 1.
  • ODBC / MSSQL: SET LOCK_TIMEOUT 0.
  • SQLite: no-op.

Concrete HoldRowLocks / TryProbeRowLock virtuals — Firebird only so far; other drivers run on the heartbeat-cutoff scheme without the probe-lock fast path.

ibConnectionPool

Process-wide bounded pool, lazy clone allocation up to maxSize, idle-shrink at 60 s, never below minIdle (4 web, 2 daemon, 2 GUI). Master pinned against reaping. ibConnectionScope RAII guard.

ibSession switched from inheritance (: public ibDatabaseConnectionHolder) to composition — owns an m_dbHolder member. Façade: EnsureConnection, OpenConnectionScope, Holder(), static DatabaseLayer(). Pool is now holder-agnostic.

db_query / ses_query split

New ses_query macro routes through session's holder (auto-binds the checked-out connection as a scope for the holder's lifetime — multi-call functions share one connection). Migration done for 17 descriptor / runtime files. db_query stays for DDL, metadata load, bootstrap, and the service paths.

Worker pool

ibWorkerPool abstract + ibWorkerPoolHeadless concrete (in backend/session/). Lazy spawn up to maxWorkers, idle-shrink with 60 s timeout (kMinIdle = 1 survivor), per-session FIFO + lease, reentrant Submit-on-current-session inline.

  • ibSessionRegistry owns the pool; ctor picks maxWorkers per runMode.
  • ibSession::Submit(task) is the public dispatch API. ibWebApplication::PostWork / RunOnWorker forward to it.
  • Cancellation: RequestCancel / IsCancelRequested (atomic flag, interpreter checks per opcode); ibBackendInterruptException cooperative cancel from debugger Pause + admin Kick.
  • Force-exit: RequestForceExit (one-shot, fires OnForceExit() virtual once). ibSession::Close(true) folds in force-exit. ibSessionRegistry::CloseAll(force) for GUI app shutdown.
  • Per-session interpreter state — ibProcUnitState (procUnitState.h) holds m_currentRunModule, m_runContext stack, m_errorPlace, m_recCount, accessed via ibSession::GetPUState(). ibProcUnit::Execute caches the pointer once at function entry.

ibNumber — exact-decimal lazy-grow

Replacement of typedef ttmath::Big<128, 128> ibNumber with a self-contained 8-byte tagged-immediate / heap-vector class. Full rewrite plus follow-on cleanup of every call site (backend / frontend / designer / launcher / codeRunner / classChecker / wfrontend / wenterprise-server). ttmath removed from the tree entirely (13 files).

  • sizeof(ibNumber) == 8 always. Single uint64_t payload with bit-tag: bit 0 = tag, bits [16:1] = exp10, bits [63:17] = 47-bit signed mantissa. Covers ±70 трлн × 10^±32767 — most business numbers never allocate.
  • Heap tier: BigImpl { std::vector<uint32_t> limbs; bool negative; int32_t exp; } — exact decimal, magnitude grows on demand. Tested at 200-digit precision (100 fractional).
  • Self-contained: zero #include of ttmath; Add / Sub / Mul + base-2 long-division Div implemented as schoolbook routines in backend/number.cpp. MSVC x86/x64 use _addcarry_u32 / _subborrow_u32 intrinsics; portable 64-bit fallback for Clang / GCC / ARM.
  • Compact-zero encoding: GetBuffer() for zero produces a zero-byte buffer (no allocation, no header). Saves IO on millions-of-zero DB columns.
  • 128-bit raw: To128Bytes(uint8_t[16]) / From128Bytes(const uint8_t[16]) — for Firebird SQL_INT128.
  • Format struct with fracDigits, precision, decimalSep, groupSep, groupSize, minIntDigits (leading-zero pad for code generators).
  • GenerateNextIdentifier uses UPDATE … RETURNING for atomic increment + bootstrap INSERT for the first-ever call (Firebird + PostgreSQL); MySQL / ODBC throw with a clear message.
  • Tests: enterprise/tests/test_number.cpp — 111/111 pass.

Password hashing — PBKDF2

ibPasswordHash (src/engine/backend/utils/passwordHash.{hpp,cpp}) — PBKDF2-HMAC-SHA256 at 600 k iterations (OWASP 2023) with a 16-byte system-RNG salt, stored in PHC-style format $pbkdf2-sha256$<iter>$<saltB64>$<hashB64>.

Verify additionally accepts legacy 32-hex MD5 hashes from pre-migration databases; callers use NeedsRehash + Hash to upgrade silently on successful login (ibApplicationData::AuthenticationAndSetUser). MD5 stays in-tree for metadata integrity (ibMD5::ComputeMd5) — never reuse it for passwords.

Argon2id (OWASP #1, memory-hard) would be the stronger option but requires vendoring an external library; revisit when the threat model calls for it.


Firebird driver hardening (FB 5.0 embedded)

Audit and fix pass on ibDatabaseLayerFirebird for the embedded FB 5.0 deployment.

  • UB fixes: XSQLDA malloc / delete[] mismatches in result-set and parameter-collection cleanup; wxStrncpy over UTF-8 byte buffer in string-parameter binding.
  • Result-set integers: removed silent SQL_INT64 / INT128long truncation, fixed abs(scale) * 10 scale arithmetic (was wrong for |scale| ≥ 2), switched LP64-fragile *(long*) casts to int32_t memcpy.
  • DPB additions: isc_dpb_lc_ctype = UTF8 (charset on attach, since set_db_charset is only honoured at CREATE), isc_dpb_force_write = 0 (async writes for fresh CREATE — ~5–10× faster on Win NTFS), isc_dpb_session_time_zone = UTC.
  • TPB additions: isc_tpb_lock_timeout = 30 s on wait-mode (was blocking forever on contention), new read-only mode via ibTxOptions::readOnly (FB 4+ read_consistency for snapshot-style reads without write-intent locks).
  • Atomic seed phase in metadataConfigurationQuery::OnAfterSaveDatabase — wraps the post-DDL repair loop in its own TX so a partial seed failure rolls back instead of leaving half-filled enum tables.
  • Idempotent probe in ProcessPredefinedValue (SELECT 1 … WHERE uuid = ? before plain INSERT) so re-seeding existing tables does not PK-violate.
  • Page size widened to int32_t and bumped 8 192 → 16 384 (FB 5 OLTP recommendation; ~32 MB cache footprint per attach is fine).
  • fb_tr_list stack collapsed to single isc_tr_handle m_pTransaction (base-class nesting counter makes it always one node deep).
  • HoldRowLocks / TryProbeRowLock outer-TX guards (refuse if already in a TX — inner Begin would just bump the counter, silently breaking lock semantics).
  • Open() is now safe to re-call (detaches the old handle first); recursive Mkdir(…, wxPATH_MKDIR_FULL).

SetParamBlob handles SQL_BLOB / SQL_TEXT / SQL_VARYING (VARYING was a silent no-op previously). FB string-param binding no longer clamps by sqllen (which is mutated per row by the driver) — uses memcpy(length) + sqllen = length. Result-set integer extraction now wraps SQL_INT128 via ibNumber::From128Bytes.


Debugger

  • Stable wire under multi-tab F5 / step workloads: send-mutex serialises the WriteMsg(length) + WriteMsg(payload) pair (interleaved bytes from concurrent senders used to garble frames); IsConnected no longer filters on LastError() (per-socket state was leaking transient WOULDBLOCK / TIMEDOUT into the connection thread's next liveness check); read loop drops the inner WaitForRead (sockets are wxSOCKET_BLOCK | wxSOCKET_WAITALL, the timeout was skipping payload under jitter).
  • ibSession::WakeDebugLoop() cooperative cancel — sets m_forceExit on the session and notifies the per-session debug CV; bypasses process-level RequestForceExit.
  • CommandId_Pause and admin Kick both call ibWorkerPool::CancelSession; soft path (m_bDebugStopLine) still fires for scripts reaching line markers, hard path covers tight loops / native blocking calls.
  • Per-session ibDebuggerServer (one per web tab); WorkerLoop calls SetCurrent(m_debugServer) so ibProcUnit's breakpoint hook routes to the right listener.
  • ibBackendException::ibEvalModeScope RAII guard replaces three manual set / reset pairs in ibProcUnit::Evaluate.
  • Local window walks m_parentRunContext chain, renders captured-frame entries labelled <fn>.<var>, skips non-heap-promoted ancestors.
  • Designer Active Users dialog right-click: Kick session / Reload clients.

Designer UX

  • HiDPI / DPI-aware sizing — line-number / breakpoint / fold margins recompute on wxEVT_STC_ZOOM so the gutter scales with the font (SetEditorSettings uses max(FromDIP(16), TextHeight(0))).
  • Owner-drawn ib variants for text editor, checkbox, button, static text — honour BackgroundColour property even when wxWidgets defaults to system theme.
  • Autocomplete + calltip widgets follow editor zoom; print preview rendering fixed (sub-pixel gaps under GDI are mitigated; DirectWrite switch is tracked separately).
  • ibPrecompileCode / ibParserModule — Hungarian stripped (m_nCurrentPos → m_caretPos, m_pContext → m_activeContext, m_pCurrentContext → m_cursorContext, m_numCurrentCompile → m_cursor). Critical m_currentPos shadow bug fixed — derived ibPrecompileCode::m_currentPos was renamed during a Hungarian sweep and started shadowing the base lexer cursor, making IsEnd() return true on the first call.
  • Autocomplete: empty-blank-line dropdown fixed (stray \r/\n from PrepareExpression walking through whitespace lexems used to make the filter reject everything); CONSTANT-branch leak fixed (a string literal earlier in the source used to poison currentWord).
  • Predefined-value editor — create groups, render folder hierarchy.
  • Compact About dialog with auto-fit, hides empty plugins box.
  • ibFoldLevelParser — single std::array<short, FoldKindCount> m_counts replaces 5 separate counters; chained if/else trees collapsed to table lookups. FoldLinq kind added — KEY_FROM opens, KEY_SELECT closes, KEY_GROUP closes conditionally (peek-INTO).
  • IntelliSense ibPrecompileCode::CompileLinqExpression — parses block-syntax LINQ at IntelliSense time, registers from / let / join / group-into bindings in m_activeContext so o. inside from o in arr sees o (no element-type inference yet — future iteration).
  • IntelliSense — chain-method OPER_CALL_LINQ emit at chain-method site via ibValue::FindLinqMethodByName; runtime dispatch through OPER_CALL_LINQ (no const-pool string lookup).
  • CES fold-keyword fix — Allman-style Function\n{\n body\n} body now lands at correct indent (was doubled because keyword-fold opens stacked on top of brace folds without matching End* closers).

Build infrastructure

  • VS 2022 Professional (MSVC 14.42) on Windows; CMake (3.20+) on macOS / Linux.
  • enterprise.sln — 11-project solution: backend.dll, frontend.dll, wfrontend.dll, enterprise.exe, designer.exe, launcher.exe, daemon.exe, codeRunner.exe, classChecker.exe, simplePlugin.dll, wenterprise-server.exe.
  • Output: bin\Win64\Release\ / bin\Win32\Release\ for Release; Debug similarly.
  • wxWidgets updated to 3.3.2 (git submodule at src/3rdparty/wxWidgets). DLL Release Win32 and x64 wx libs required.
  • codeRunner + classChecker now explicitly depend on frontend in enterprise.sln — eliminates a parallel-build race where the linker started before frontend.lib was written.
  • Tests: Google Test 1.14 via FetchContent. Suites: test_number, test_clsid, test_value, test_connectionPool, test_session, test_byteCodeAOT, test_compiler, test_runtime, test_pagingRamTable. tests/scripts/ holds the LINQ / closure / lambda smoke fixtures.
  • Default backend locale set to en in backend.conf (was ru).

Documentation

  • docs/linq.md, docs/lambda.md, docs/closure-capture.md — comprehensive language docs covering grammar, semantics, mechanism, pivot risks, file table.
  • docs/paging-design.md, docs/session-registry.md, docs/runtime-facade.md, docs/connection-pool.md, docs/eval-scope-refactor.md, docs/firebird-driver-hardening.md, docs/const-meta-refactor.md, docs/metadata-mm-decoupling.md, docs/worker-pool-tls-audit.md, docs/register-totals-strategy.md, docs/compute-server-tiering.md, docs/backend-frontend-split.md — architecture references for each subsystem.
  • docs/ARCHITECTURE.md and docs/BUILD.md — top-level entry points.
  • docs/web/ — 10 docs for the web runtime (architecture.md, authentication.md, conventions.md, debugging.md, event-dispatcher.md, label-alignment.md, open-issues.md, session-lifecycle.md, ui-shell.md).
  • Pre-arc session handoffs (2026-04-30..05-09) collapsed into docs/archive-sessions.md with a date → topic-doc index.
  • Documentation pass: docs are English-only; references to peer enterprise platforms (1C:Enterprise, Mendix, OutSystems, ERPNext, Salesforce Lightning, etc.) are framed as comparable products, not parents.

Notable fixes

  • UAF in ibValueToolbar::OnTool after form closure — toolbar deferred-destroy pattern, fix in frontend/toolbar.
  • Rapid-F5 UAF on web — three coupled lifetimes: registry m_own overwrite, nested SessionScope restore via raw m_prev, concurrent Login / Destroy on the same ibWebSession. Closed architecturally via shared_ptr + weak_ptr + per-session lifecycle mutex.
  • backend_exception strip CRReplace('\r', '\0') was promoting the char overload to a 1-char NUL string, replacing every \r with NUL in user-visible error text. Now Replace(wxT("\r"), wxEmptyString).
  • Eval inside lambdam_pppArrayList[1..] of the shim runtime is spliced into eval's [2..] when host is a lambda. Catalogs.X / Documents.X / Manager.X resolve from watch expressions inside lambda bodies.
  • Compile / shutdown deadlock — registry Stop() is called unconditionally at the end of Disconnect (force-killed Designer used to leave a zombie thread holding backend.dll); listener socket destroyed before Wait().
  • DDL exclusive-mode gateibRestructureInfo::RequireExclusiveForDDL static helper, called at top of OnBeforeSaveDatabase. Auto-acquires + releases exclusive mode through the registry when no caller did. Skip when ibSession::Current() == nullptr (bootstrap path).
  • OnUnhandledException — writes a one-line diagnostic (timestamp + exception type + message) to enterprise_unhandled.log next to the .exe; persistent minidump on fatal SEH before wxDebugReport.
  • StrGetLine — process-wide static wxString _csStaticSource cache (race in worker pool) replaced with thread_local Cache; fixed if (nIndex < 0) unsigned bug; supports \r\n / \n / \r line endings.
  • OPER_CALL_M missing OPER_SETCONST arg handling — when a method receives a const-string arg (Structure.Insert("name", value)), the loop fell through to the OPER_SET path, treating the const-pool index as a frame slot. Garbage key reached Insert → "String expected!" runtime error.
  • Unary minus AddTypeSet — typed OPER_INVERT + TYPE_DELTA1 writes only m_fData; a fresh temp from CreateVariable has m_typeClass = TYPE_EMPTY, so an untyped downstream OPER_MULT read 0 instead of -3. Fix: emit OPER_SET_TYPE before the typed write (matches the existing NOT pattern).
  • Persistent slot trap (nested LINQ)m_needsReset compile-side scan of T's lex range detects JOIN with outer-ref keys; emits per-row OPER_LET hashSlot = empty so the IsEmpty guard rebuilds. GROUP uses lookup-or-create Container.Property to flatten semantically across outer iters.
  • group X by K into g — non-terminal group reuses ibValueStructure{Key, Values}; pre-bound CompileLinqBlock overload re-enters the leaf machinery after the post-block expansion.
  • OrDefault semantics — every LINQ terminal with an empty-result branch explicitly writes CopyValue(ret, ibValue()) to reset caller's slot from stale leftover.
  • CES block scope — runtime-depth-counter approach replaces three earlier dead ends (per-IP scope ranges, isInitialised filter, vector<bool> m_slotInScope + body scan).
  • CES Allman-style Function\n{\n body\n} indent — suppressed control-flow keyword fold opens in CES (only braces drive structure); LINQ keywords still fold.
  • IsAllowedKey — single keyword gate in IsKeyWord filters VES-only End* / Then / Do from CES through one path (lexer / highlighter / autocomplete / parser).
  • Connection-pool clone deadlock — fast pre-clone path during Init(primary, maxSize, minIdle), idle-shrink at 60 s, never below minIdle.
  • Sequence allocator driftGenerateNextIdentifier cross-machine safe via DB row lock; PG / FB.
  • Cookie / session id unification — single dashed-UUID format across sessionStorage.oes_tab_sid (browser), oes_session cookie, X-OES-Session header, SessionManager::m_sessions key, ibSession::m_id, sys_session.session PK.
  • <img src> per-tab routing?sid=<tabSid> fallback because browsers don't carry custom headers on plain GETs.

Breaking changes / migration notes

  • AOT cache invalidatedkAOTFormatVersion = 10. First run after deploy recompiles every module; sys_bytecode_cache rows from earlier versions are silently rejected.
  • Wire format break for TYPE_NUMBER — serialised ttmath::Big blobs from pre-2026-04-29 are not readable by the new ibNumber::SetBuffer. Production databases with pre-existing TYPE_NUMBER blobs need migration. Fresh DBs are unaffected.
  • sys_session schema — new columns added by MigrateTableSession (pid / address / currentActivity / signal / kind). Each ALTER TABLE ADD COLUMN is wrapped in try/catch so unsupported drivers degrade silently. Legacy schemas continue to work.
  • db_query semantics — runtime-path call sites moved to ses_query (session-bound). db_query is now the DDL / service / bootstrap channel; per-thread reservation via ThreadHolder.
  • GetModuleObject / GetManagerObject renamed to GetObjectModule / GetManagerModule (~440 references) to remove naming collision with the runtime ibValueModuleManager. Source-compat shims are not provided — external plugins compiled against earlier headers will need to rebuild.
  • ibSession — frame storage moved off the base class; GetFrame() is a virtual returning nullptr by default; ibGUISession and ibWebClientSession carry typed m_frame and override.
  • ibApplicationData — registry-only state machine (Transition / TransitionAuth / SetIdentity / Inserted / WaitForState / WaitForAuth) and auth-flow setters moved to private under friend ibSessionRegistry. Public surface is now ~15 methods.
  • ibPasswordHash::Verify — accepts both PBKDF2 and legacy 32-hex MD5; old hashes upgrade on first successful login. No script changes required.
  • ibProcUnit static-accessor removed — callers go through ibSession::GetPUState(). Hot loops cache the pointer at function entry.
  • CES is default for new configurations — existing configurations are unaffected (Syntax flag is stored per-configuration). New configurations open in CES mode.

Known gaps (alpha)

  • Web — incremental controls — checkbox / combobox / choice / listbox / radiobutton / notebook / tablebox-gridbox still render as ibWebStubControl placeholders. Forms with custom UI work to the extent of the ported controls; layout polish (label-column CSS grid) is in progress.
  • HoldRowLocks / TryProbeRowLock — concrete implementations only for Firebird. PG / MySQL / MSSQL drivers run the registry on heartbeat-cutoff without the probe-lock fast path; NoWait plumbing is in place for whoever fills these in.
  • AOT cacheSerialize / Deserialize and sys_bytecode_cache table land; Attach batch-load + Designer-save cascades remain TODO.
  • LINQ block-syntax is eager (materialises into __r) — chain syntax is the lazy alternative for RAM-pressured workloads. SQL pushdown is a separate (larger) arc.
  • IntelliSense — block-syntax bindings are registered without element-type inference; chain-method return-type propagation is not yet wired.
  • macOS — Firebird embedded works, builds compile, end-to-end smoke is incomplete on this release.
  • wfrontendInitServer path (TCP Firebird from web) — never exercised; only wfrontendInitFile has real coverage so far.

Assets

Pre-built Windows binaries: oes_12052026.zip (attached to the GitHub release) — contains enterprise.exe, designer.exe, launcher.exe, daemon.exe, codeRunner.exe, classChecker.exe, wenterprise-server.exe, plus backend.dll, frontend.dll, wfrontend.dll, simplePlugin.dll, the wxWidgets 3.3.2 runtime DLLs, the fbclient.dll / ib_util.dll / icudt63.dll Firebird runtime, OpenSSL (libcrypto-3-x64.dll / libssl-3-x64.dll), and PostgreSQL (libpq.dll) clients. Includes the backend.conf (locale en) and firebird.conf defaults.