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*OrDefaultvariants - indexed:
WhereIndexed / SelectIndexed— lambda gets(elem, index) - aggregations:
Sum / Min / Max / Averageno-arg and with a lambda selector
Implementation notes:
- Hash-based
JoinandGroupByviastd::map<ibValue, …, KeyCmp>(O(log N + bucket) lookup) instead of an O(N×M) linear scan. OPER_CALL_LINQcarries theibLinqMethodenum id directly inm_param3.m_numIndex— no const-pool string lookup, noFindMethodwalk at runtime.ibValue::GetLinqMethodTable()is the single source of truth for{ id, name, helper }; drives compile-sideFindLinqMethodByName.- 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::ibByteFunctionwithkind = Lambdainm_listFunc; runtime value isibValueFunction(parentBc, funcIndex)(CLSIDVL_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_listLocalscarries 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 throughOPER_CALL_LAMBDAbytecode.
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 onibCompileContext::ibFunctionduring theGetVariableparent walk). - Its run-time frame is allocated via
std::make_shared<ibRunContext>(varCount); the lambda value holdsvector<shared_ptr<ibRunContext>> m_capturedFrames. OPER_CALL_CLOSURE(split fromOPER_CALL) is emitted when the callee hasm_needsHeapFrame— hotOPER_CALLstays probe-free.OPER_CALL_LAMBDAper-invoke wiresm_pppArrayList[1..N]fromm_capturedFrames[0..N-1]->m_pRefLocVars; existingOPER_GET / OPER_SETwithdepth ≥ 1work unchanged.- Lexical parent:
m_parentRunContexton 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 storedSyntaxflag (the wire token still readsvbsfor back-compat — the user-visible label is nowves).- 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-timem_scopeDepth; runtime tracks a singlem_currentScopeDepthcounter onibRunContext;SendLocalVariablesfilters byentry.m_scopeDepth ≤ ctx.m_currentScopeDepth. No vector-of-bools, no per-IP scope-range tables. - IntelliSense + auto-indent + brace highlight + gutter gradient for CES;
CompileBlocksupports 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 renameOPER_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 = 203foribValueFunctionTYPE_ITERATOR = 204foribValueIterator
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_needsHeapFrameper function.m_scopeDepthperibByteCodeVarInfo(for block-scope visibility filtering).OPER_CALL_LINQopcode 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_cacherow keyed by descriptor + source hash + metadata version, populated onibRuntimeModuleDataObject::Compile, invalidated on module Save / Delete.- Cross-bc resolver is kind-driven (
ibVarKind/ibFnKindenums onibByteCodeVarInfo/ibByteFunction), no runtime dependency onibCompileContext.
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 offrontend.dll, ships a single-page HTML/CSS/JS client (webClient.cppblob) plus the server-side render logic.ibWebVisualHost— server-side mirror ofibVisualHost; renders form trees to JSON.- Per-tab
ibWebSession— each browser tab is its ownsys_sessionrow with its ownm_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+/activepromises 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>withvalue=…).ibWebButton—wxEVT_BUTTONviaQueueEvent/ProcessPendingEvents, identical to desktop'sOnButtonPressed.ibWebToolbar+ibWebToolBarItem+ibWebToolBarSeparator— iteration 2: items + separators render, tool captions resolved via owner action collection.ibWebBoxSizer+ibWebWrapSizer+ibWebStaticBoxSizer+ibWebGridSizer(inwebSizer.{h,cpp}).ibWebMDIChildFrameper tab;~ibWebFramedeferred-close pipeline so scriptOnClosehandlers 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 /login—wfrontendLogin→ibWebSession::Login→registry.Connect+ticket.Attach→appData->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
tabSidinsessionStorage, surfaced viaX-OES-Sessionheader or?sid=query (for plain-GET endpoints like/tab/<i>/iconwhere 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 rotatestabSid+ reload).- Per-session
ibDebuggerServer— designer breakpoints / step land on the right tab;sessionGuidwire-tagged on a single TCP channel to designer. /admin/diagJSON endpoint — pool / worker / session aggregate counters./admin/sessions/<guid>/kickand/admin/sessions/<guid>/reloadHTTP endpoints + designer dialog right-click + C++ibSessionRegistry::Instance().Kick(guid)/Reload(guid)— three entry points shareWriteSessionSignal.
Launcher / Designer integration
- Launcher's Web button spawns
wenterprise-server.exeviacmd /C(tail-able temp log), thenwxLaunchDefaultBrowser. - Designer Debug → Start debugging → Web client / Start without debugging → Web client —
appData->RunWebServer(port, urlPrefix, withDebug). - Manifest handshake — launcher discovers running
wenterprise-server.exeinstances on the same machine (port auto-pick + manifest file in%TEMP%).
Workarounds (Windows-specific)
localhost → 127.0.0.1302 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-aliveselect()loop occasionally stalls for the fullread_timeouton 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 selectoribPropertyChartOfCharacteristicTypes. BackingSubcontoKindsTableregistered throughibValueMetaObjectSubcontoKindsTable.ibValueMetaObjectChartOfAccounts—CLSID = MD_CHA. Accounts with subconto kinds, currency / quantitative / off-balance flags, subaccount hierarchy. Property selectoribPropertyChartOfAccounts.ibValueMetaObjectAccountingRegister—CLSID = 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).wfrontendAllFunctionsJSONspecs[](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.0 —
ibMetaDataConfigurationBase::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) viametadataConfigurationJSON.cpp. Library:nlohmann/jsonv3.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
u64numeric 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.txtat 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), Firebirdengine13plugin 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_lockConnis retired together with the pessimistic-lock liveness model). - Tick schedule: 1 s
JobHeartbeatOwn(UPDATE ownlastActive) +JobRefreshSnapshot; 3 sJobSweepStale(zombie cleanup viaTryProbeRowLockfast path with a 60 slastActivecutoff fallback). Eager initial sweep + refresh so Active Users is populated immediately. - Fatal invariant: any exception out of
ThreadBody→Die()→std::terminate.IsThreadAlive() / IsFatal()accessors for producers. ibSessionPolicyinterface — first-veto-wins chain consulted inProcessAdd.ibDesignerExclusivePolicyports the oldVerifySessionUpdaterveto semantics.ibSessionTicket— RAII, move-only owner handle, dtor submitsRemove@Urgent.Attach() / Detach() / Release().ibConnectRequest / ibConnectResult— unifiedConnect(req)entry for desktop / designer / web-server / web-cookie.- 3-phase
NotifyAuthenticated:OnFirstConnectlisteners (one-shot per process; populatesactiveMetaData).s->EnsureRoot()(idempotentCreateRoot(activeMetaData)— session's module manager allocated NOW).OnAuthenticatedlisteners (every authenticated session;RunDatabase/CompileRoot/AttachRuntime).
- Auth split: pure
appData->AuthenticateUser(user, pwd, &info)(PBKDF2 verify + lazy MD5 → PBKDF2 upgrade) + side-effectInstallUser(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::AttachRuntimefilters by kind — runtime runs forEnterprise / WebClient / Service.
Per-driver NoWait transactions
ibTxOptions::noWait honoured in all 4 drivers:
- Firebird:
isc_tpb_nowaitin TPB. - PostgreSQL:
SET LOCAL lock_timeout = 0after 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.
ibSessionRegistryowns the pool; ctor picksmaxWorkersperrunMode.ibSession::Submit(task)is the public dispatch API.ibWebApplication::PostWork / RunOnWorkerforward to it.- Cancellation:
RequestCancel/IsCancelRequested(atomic flag, interpreter checks per opcode);ibBackendInterruptExceptioncooperative cancel from debugger Pause + admin Kick. - Force-exit:
RequestForceExit(one-shot, firesOnForceExit()virtual once).ibSession::Close(true)folds in force-exit.ibSessionRegistry::CloseAll(force)for GUI app shutdown. - Per-session interpreter state —
ibProcUnitState(procUnitState.h) holdsm_currentRunModule,m_runContextstack,m_errorPlace,m_recCount, accessed viaibSession::GetPUState().ibProcUnit::Executecaches 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) == 8always. Singleuint64_tpayload 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
#includeof ttmath; Add / Sub / Mul + base-2 long-division Div implemented as schoolbook routines inbackend/number.cpp. MSVC x86/x64 use_addcarry_u32/_subborrow_u32intrinsics; 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 FirebirdSQL_INT128. Formatstruct withfracDigits,precision,decimalSep,groupSep,groupSize,minIntDigits(leading-zero pad for code generators).GenerateNextIdentifierusesUPDATE … RETURNINGfor 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:
XSQLDAmalloc/delete[]mismatches in result-set and parameter-collection cleanup;wxStrncpyover UTF-8 byte buffer in string-parameter binding. - Result-set integers: removed silent
SQL_INT64/INT128→longtruncation, fixedabs(scale) * 10scale arithmetic (was wrong for|scale| ≥ 2), switched LP64-fragile*(long*)casts toint32_tmemcpy. - DPB additions:
isc_dpb_lc_ctype = UTF8(charset on attach, sinceset_db_charsetis 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 son wait-mode (was blocking forever on contention), new read-only mode viaibTxOptions::readOnly(FB 4+read_consistencyfor 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_tand bumped 8 192 → 16 384 (FB 5 OLTP recommendation; ~32 MB cache footprint per attach is fine). fb_tr_liststack collapsed to singleisc_tr_handle m_pTransaction(base-class nesting counter makes it always one node deep).HoldRowLocks/TryProbeRowLockouter-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); recursiveMkdir(…, 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);IsConnectedno longer filters onLastError()(per-socket state was leaking transientWOULDBLOCK/TIMEDOUTinto the connection thread's next liveness check); read loop drops the innerWaitForRead(sockets arewxSOCKET_BLOCK | wxSOCKET_WAITALL, the timeout was skipping payload under jitter). ibSession::WakeDebugLoop()cooperative cancel — setsm_forceExiton the session and notifies the per-session debug CV; bypasses process-levelRequestForceExit.CommandId_Pauseand adminKickboth callibWorkerPool::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);WorkerLoopcallsSetCurrent(m_debugServer)soibProcUnit's breakpoint hook routes to the right listener. ibBackendException::ibEvalModeScopeRAII guard replaces three manual set / reset pairs inibProcUnit::Evaluate.Localwindow walksm_parentRunContextchain, 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_ZOOMso the gutter scales with the font (SetEditorSettingsusesmax(FromDIP(16), TextHeight(0))). - Owner-drawn
ibvariants for text editor, checkbox, button, static text — honourBackgroundColourproperty 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). Criticalm_currentPosshadow bug fixed — derivedibPrecompileCode::m_currentPoswas renamed during a Hungarian sweep and started shadowing the base lexer cursor, makingIsEnd()return true on the first call.- Autocomplete: empty-blank-line dropdown fixed (stray
\r/\nfromPrepareExpressionwalking through whitespace lexems used to make the filter reject everything); CONSTANT-branch leak fixed (a string literal earlier in the source used to poisoncurrentWord). - Predefined-value editor — create groups, render folder hierarchy.
- Compact About dialog with auto-fit, hides empty plugins box.
ibFoldLevelParser— singlestd::array<short, FoldKindCount> m_countsreplaces 5 separate counters; chainedif/elsetrees collapsed to table lookups.FoldLinqkind added —KEY_FROMopens,KEY_SELECTcloses,KEY_GROUPcloses conditionally (peek-INTO).- IntelliSense
ibPrecompileCode::CompileLinqExpression— parses block-syntax LINQ at IntelliSense time, registersfrom / let / join / group-intobindings inm_activeContextsoo.insidefrom o in arrseeso(no element-type inference yet — future iteration). - IntelliSense — chain-method
OPER_CALL_LINQemit at chain-method site viaibValue::FindLinqMethodByName; runtime dispatch throughOPER_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 matchingEnd*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;Debugsimilarly. wxWidgetsupdated to 3.3.2 (git submodule atsrc/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 beforefrontend.libwas 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
eninbackend.conf(wasru).
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.mdanddocs/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.mdwith 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::OnToolafter form closure — toolbar deferred-destroy pattern, fix infrontend/toolbar. - Rapid-F5 UAF on web — three coupled lifetimes: registry
m_ownoverwrite, nestedSessionScoperestore via rawm_prev, concurrentLogin / Destroyon the sameibWebSession. Closed architecturally viashared_ptr+weak_ptr+ per-session lifecycle mutex. backend_exceptionstrip CR —Replace('\r', '\0')was promoting the char overload to a 1-char NUL string, replacing every\rwith NUL in user-visible error text. NowReplace(wxT("\r"), wxEmptyString).- Eval inside lambda —
m_pppArrayList[1..]of the shim runtime is spliced into eval's[2..]when host is a lambda.Catalogs.X/Documents.X/Manager.Xresolve from watch expressions inside lambda bodies. - Compile / shutdown deadlock — registry
Stop()is called unconditionally at the end ofDisconnect(force-killed Designer used to leave a zombie thread holdingbackend.dll); listener socket destroyed beforeWait(). - DDL exclusive-mode gate —
ibRestructureInfo::RequireExclusiveForDDLstatic helper, called at top ofOnBeforeSaveDatabase. Auto-acquires + releases exclusive mode through the registry when no caller did. Skip whenibSession::Current() == nullptr(bootstrap path). OnUnhandledException— writes a one-line diagnostic (timestamp + exception type + message) toenterprise_unhandled.lognext to the .exe; persistent minidump on fatal SEH before wxDebugReport.StrGetLine— process-widestatic wxString _csStaticSourcecache (race in worker pool) replaced withthread_local Cache; fixedif (nIndex < 0)unsigned bug; supports\r\n/\n/\rline endings.- OPER_CALL_M missing
OPER_SETCONSTarg handling — when a method receives a const-string arg (Structure.Insert("name", value)), the loop fell through to theOPER_SETpath, treating the const-pool index as a frame slot. Garbage key reachedInsert→ "String expected!" runtime error. - Unary minus
AddTypeSet— typedOPER_INVERT + TYPE_DELTA1writes onlym_fData; a fresh temp fromCreateVariablehasm_typeClass = TYPE_EMPTY, so an untyped downstreamOPER_MULTread 0 instead of-3. Fix: emitOPER_SET_TYPEbefore the typed write (matches the existing NOT pattern). - Persistent slot trap (nested LINQ) —
m_needsResetcompile-side scan ofT's lex range detects JOIN with outer-ref keys; emits per-rowOPER_LET hashSlot = emptyso the IsEmpty guard rebuilds. GROUP uses lookup-or-createContainer.Propertyto flatten semantically across outer iters. group X by K into g— non-terminal group reusesibValueStructure{Key, Values}; pre-boundCompileLinqBlockoverload re-enters the leaf machinery after the post-block expansion.OrDefaultsemantics — every LINQ terminal with an empty-result branch explicitly writesCopyValue(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,
isInitialisedfilter,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 inIsKeyWordfilters VES-onlyEnd* / Then / Dofrom 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 belowminIdle. - Sequence allocator drift —
GenerateNextIdentifiercross-machine safe via DB row lock; PG / FB. - Cookie / session id unification — single dashed-UUID format across
sessionStorage.oes_tab_sid(browser),oes_sessioncookie,X-OES-Sessionheader,SessionManager::m_sessionskey,ibSession::m_id,sys_session.sessionPK. <img src>per-tab routing —?sid=<tabSid>fallback because browsers don't carry custom headers on plain GETs.
Breaking changes / migration notes
- AOT cache invalidated —
kAOTFormatVersion = 10. First run after deploy recompiles every module;sys_bytecode_cacherows from earlier versions are silently rejected. - Wire format break for
TYPE_NUMBER— serialisedttmath::Bigblobs from pre-2026-04-29 are not readable by the newibNumber::SetBuffer. Production databases with pre-existingTYPE_NUMBERblobs need migration. Fresh DBs are unaffected. sys_sessionschema — new columns added byMigrateTableSession(pid / address / currentActivity / signal / kind). EachALTER TABLE ADD COLUMNis wrapped in try/catch so unsupported drivers degrade silently. Legacy schemas continue to work.db_querysemantics — runtime-path call sites moved toses_query(session-bound).db_queryis now the DDL / service / bootstrap channel; per-thread reservation viaThreadHolder.GetModuleObject/GetManagerObjectrenamed toGetObjectModule/GetManagerModule(~440 references) to remove naming collision with the runtimeibValueModuleManager. 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 returningnullptrby default;ibGUISessionandibWebClientSessioncarry typedm_frameand override.ibApplicationData— registry-only state machine (Transition / TransitionAuth / SetIdentity / Inserted / WaitForState / WaitForAuth) and auth-flow setters moved toprivateunderfriend 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.ibProcUnitstatic-accessor removed — callers go throughibSession::GetPUState(). Hot loops cache the pointer at function entry.- CES is default for new configurations — existing configurations are unaffected (
Syntaxflag 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
ibWebStubControlplaceholders. 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;NoWaitplumbing is in place for whoever fills these in.- AOT cache —
Serialize / Deserializeandsys_bytecode_cachetable land;Attachbatch-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.
wfrontendInitServerpath (TCP Firebird from web) — never exercised; onlywfrontendInitFilehas 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.