Every release, every fix. Nimbus is in active development — this page tracks every change since the first public release.
WHERE and grouped by a relationship-path field in GROUP BY — e.g. SELECT COUNT(Id), Answer__r.Question__c FROM Action__c WHERE isOpen__c = TRUE GROUP BY Answer__r.Question__c — failed at runtime with No such column 'isOpen__c' on entity 'Action__c', even though the field resolves fine in every other query shape. A field whose name starts with a lowercase letter (common for formula flags like isOpen__c) was resolved case-sensitively once the relationship GROUP BY introduced a join, so it missed the actual column; either ingredient alone worked. Such a field now resolves identically whether or not the query also groups by a relationship path, matching the platform. (#55)concurrent map writes error when two tests incremented a static field (a ++/--) on the same managed-package/stub class at once — because that class's static state wasn't isolated per test. Each test now gets its own copy of every class's statics, so parallel execution is stable regardless of how tests touch shared static fields. A timed-out test also now has its transaction cancelled and rolled back instead of being left running (which previously left a database connection idle in transaction and could deadlock the next run), and the timeout message no longer assumes an infinite loop — it points at parallel contention and suggests re-running with -p 1 to confirm. (#9)nimbus validate now catches unimplemented interfaces. A concrete class that declares implements but doesn't define every method of the interface is flagged as a deploy blocker (validate exits non-zero), matching the compile error Salesforce raises — so you catch it before the deploy does, consistent with the language server. Interfaces and base classes are resolved from the local project; the check skips conservatively when a type can't be resolved (a managed-package or cross-file interface) so it never blocks a valid deploy. (#54)nimbus validate now flags setting an AutoNumber field. Assigning an AutoNumber field in an SObject constructor (e.g. new Log__c(Name = 'x')) is reported as a deploy blocker — AutoNumber fields aren't writeable, and Salesforce rejects this at compile. This complements the runtime rejection added in 1.3.2, so the mistake surfaces at validate time too. (#52)Too many SOQL queries — while the same operation runs comfortably on Salesforce, which batches flow interviews. Get Records elements with an equality filter on a triggering-record field now run once for the whole batch (via an IN clause) and distribute the matching rows to each record, so query count scales with the number of elements, not the number of records — matching the platform. (#50)null after insert instead of receiving a generated sequence value. Inserts now assign the next value rendered against the field's display format — Log-{000000} yields Log-000001, and date tokens ({YYYY}/{YY}/{MM}/{DD}) are substituted anywhere in the pattern. Setting an AutoNumber field in Apex is now rejected with Field is not writeable, matching the platform (where it is a compile error). (#52)Type.forName() returns null for a type that doesn't exist, instead of a phantom Type. Type.forName('Nonexistent__c') returned a non-null Type whose newInstance() produced a phantom object, silently breaking the standard null-guard pattern for dynamic factories (if (t != null) took the wrong branch). Unknown types now return null as on the platform, while real standard and project custom objects still resolve. (#51)DescribeSObjectResult.childRelationships is now a non-null list. It returned null on every object, so any code iterating an object's child relationships threw a NullPointerException; on the platform it is always a non-null (possibly empty) list. Both access paths — getGlobalDescribe().get(name).getDescribe() and SObjectType.getDescribe() — now return the populated list, each entry exposing getChildSObject(), getField(), and getRelationshipName(). (#53)@testSetup shape — still failed with unable to encode 0 into text. The text coercion now covers every DML path (bulk insert, update, and non-transactional insert/update) and handles a whole affected column across all rows of a large batch in one pass. (#39)nimbus doctor no longer flags valid dynamic seed keys. Config keys like nimbus.seed.queue.<DeveloperName> and nimbus.seed.group.<Name> — documented and working at test time — were reported as unknown config keys by the doctor's syntax check. The doctor now recognises all documented dynamic seed-key prefixes. (#49)--local-shards auto now sizes itself by suite length, not just core count. Splitting a run into isolated local shards only pays off once a suite is long enough to amortize the extra process and database startup — on a short suite it can be slower than a single process. auto previously looked only at your CPU count and could pick multiple shards for a suite too short to benefit; it now also considers the wall-clock time of your last full run and stays single-process when sharding wouldn't help. The recorded duration only ever comes from a genuine full run, never a --shard slice or an --impacted subset.--local-shards runs now report where time went. The merged summary from a sharded run now includes the same SOQL / DML / code / setup time breakdown a single-process run shows — summed across shards, in both the human-readable summary and --json — instead of dropping it.Date__c == null or Field__c != null — was evaluated as a plain true/false, so a rule written that way fired on records the platform accepts. On Salesforce such a comparison is undefined, and a validation rule fires only on a strict true, which means these rules are effectively inert for the blank case. nimbus now matches: == null/!= null against a genuinely blank value no longer triggers the rule, while ISBLANK() remains the explicit way to test for blankness and still fires as before. (#44)Profile.Name relationship, each path could return a different profile for the same Id — so profile-gated logic silently evaluated false. A profile resolved on demand is now a real, consistent record: by-name, by-Id, relationship traversal, and an unfiltered SELECT ... FROM Profile all agree on its Id and name. (#46)equals() now works on Boolean values. Calling .equals(...) on a Boolean failed with "Unknown method equals on Boolean", even though every Apex type supports it and String and Integer already worked. This surfaced most often in generic code comparing Object-typed field values, such as dirty-checking a Checkbox field. (#45)WHERE Name IN :'a,b'.split(',') or = :'a,b'.substringBefore(',') — still leaked its raw text into the generated SQL and failed with a Postgres syntax error. Such binds are now evaluated and bound as values like any other. (#43)WHERE filter on a text or picklist field was case-insensitive in a flat query but case-sensitive inside a parent-to-child subquery — so (SELECT Id FROM Contacts WHERE LastName = 'SMITH') returned no rows when the stored value was Smith, while the same flat filter matched. Both positions are now case-insensitive, as on Salesforce. Relatedly, DescribeFieldResult.getType().name() now returns the uppercase enum name (STRING, PICKLIST) instead of mixed case, matching the platform and the bare Schema.DisplayType enum. (#48)fileName became filename), producing JSON with the wrong keys — a consumer reading result.fileName found nothing, and nothing threw. Member names now serialize with their declared casing verbatim, in the platform's member order. (#47)values()/keySet() could pass or fail from one run to the next. Those maps now keep a stable order, and a related dynamic WHERE Id = :x lookup on RecordType now filters correctly instead of matching everything.--local-shards with several shards is more reliable. Launching many shards at once made their embedded databases initialize simultaneously and contend for disk, which could push a shard past the startup deadline and fail the run. Shard startup is now staggered and the readiness timeout is more forgiving, so large shard counts start cleanly on a busy machine.nimbus test --shard i/N assigns test classes to balance each shard's wall-clock time, using per-test durations recorded from previous full runs, instead of dividing purely by class count. A few slow classes no longer pile onto one shard while the others finish early — on a large suite we track, the imbalance between two shards dropped from 2.2x to 1.26x. With no recorded timings yet (a new project, or a cleared cache) it falls back to the previous count-based split, and independent CI processes always compute the same assignment without coordinating.--local-shards N. nimbus test --local-shards N|auto spawns N shards as concurrent processes against the same project checkout, each with its own isolated local database, and merges their results into one report (human-readable, --json, or JUnit). Today this is about isolation, not speed — a single-process -p N run is still faster, and --coverage is not yet supported with --local-shards. The correctness and merge machinery is complete; a future release turns it into a speed win.WHERE Name IN :someString.split(',') or :MyClass.ids(x) left the argument parentheses stranded in the generated SQL, so the query failed with a Postgres syntax error near (; only no-argument calls such as :UserInfo.getUserId() happened to work. Bind expressions with arbitrary arguments now resolve properly. (#43)Name) could not satisfy a rule that required it — the record was rejected even though a real Salesforce save would have passed. Insert now runs before-triggers first, then validation rules, consistent with how update already behaved. (#42)--impacted. nimbus test --impacted builds a dependency graph from your local edits and runs just the test classes that exercise the changed code, skipping everything else. The class-dependency map is persisted between runs and wired into watch mode, so saving a file re-runs only its impacted tests automatically. Pass --no-impact to force a full run.execute_anonymous, query, and describe_schema tools let an AI agent or editor execute a snippet, run a SOQL query, and inspect an object's fields without an org connection. Test failures returned over MCP and JSON now carry structured diagnostics — the failing assertion, the exception, and the execution path that reached it — instead of a bare message.getInstance(), getOrgDefaults(), and getValues() lookups — which fire constantly in framework-heavy code — are now served from an in-memory snapshot taken once per run, including the very common case where the setting has no matching row for a user or profile. On settings-heavy suites this removes the bulk of query traffic. Tests that modify a setting still see their own writes.nimbus test/nimbus exec was using the database, stopping the daemon — by command or signal — used to clear the tables and shut down Postgres underneath it, failing every remaining test with "Failed to begin transaction". The daemon now detects an in-flight run and leaves the database up for it, completing its own shutdown without disturbing the run. (#10)SIGKILL, a crashed run, or a daemon stopped mid-run could leave a background Postgres process with no owner — invisible to nimbus daemon list and, if you never returned to that project, lingering until reboot. nimbus now reaps these on its next run in any project: an instance with no live daemon and no active connections is stopped and its data removed. Instances that are in use or daemon-managed are never touched. (#10)Map field whose name matches a system class now works. A field named CACHE collides with the Platform Cache class, and CACHE.put('k', 'v') failed with "Unknown method put on Type CACHE" — while .get() quietly returned null. The bare field name resolved to the system class instead of the field. A static field now shadows a same-named system class, just as instance fields and local variables already do, so the idiomatic static-Map cache populated via .put() (often in a static {} block) holds its entries. (#38)"0" and records whose @testSetup touched such a field save cleanly. (#39)nimbus sync --include-setup-data no longer aborts the whole pull when one setup object can't be queried. An org without Experience Cloud has no queryable Network, so SELECT Name, Status FROM Network errored and that single failure stopped the entire pull — no Group, queue, or UserRole entries were written. Each object type is now pulled independently: a type the org can't query is skipped with a warning and the rest still land. The pull only fails outright when no setup object can be queried at all (a bad org alias or no connection). (#33)nimbus config properties and nimbus config show. nimbus.seed.group.*, seed.queue.*, seed.role.*, seed.network.*, and seed.user.* were honored at runtime but absent from the config listings, so there was no way to confirm a hand-authored key was recognized. They are now documented alongside the other seed keys. (#33)nimbus.seed.network.<Name>=<Status> key. Code that gates on a community — [SELECT Id FROM Network WHERE Name = 'Member Hub'] — resolves the seeded row instead of failing on an empty single-row query. Status defaults to Live. Querying Network with no rows now returns 0 rather than erroring on a missing object. (#33)nimbus.seed.user.<Name>=<Username> key. Factories that look up integration or system users by name — [SELECT Id FROM User WHERE Name = 'System Salesforce'] — now resolve. The Name is split into FirstName/LastName so the generated Name field reconstructs exactly; the Username is optional. (#33)nimbus sync --include-setup-data now also pulls Networks. Alongside Groups, queue routing, and UserRoles, Network rows are read from the org and written as nimbus.seed.network.* entries. Named Users are left as an explicit seed list rather than bulk-synced, since a large org has thousands. (#33)$Record) silently nulled every temporal field on the saved record, on both insert and update — String and reference fields survived, which made it look field-type-specific. Those fields are now preserved when $Record is built for the flow. (#35, #36)SELECT COUNT() FROM Profile now counts the backing rows. The aggregate returned 0 while the same query with WHERE Name = '…' returned 1; the unfiltered count path didn't enumerate the Profile pseudo-table's rows. Filtered and unfiltered counts are consistent now. (#37)ProcessInstanceWorkitem, ProcessInstanceStep, UserLogin, RecentlyViewed, and NavigationLinkSet no longer fail with "relation does not exist", and ContentFolderItem.IsFolder resolves instead of "No such column". (#32)nimbus.seed.role.<DeveloperName>=<Name> and nimbus.seed.queue.<DeveloperName>=<SObject1>,<SObject2>. A queue seed inserts the queue plus its routing for each listed SObject, so case and lead assignment automation resolves owner candidates; role seeds make WHERE DeveloperName = … lookups against UserRole resolve. These are setup objects that test factories assume exist but repos rarely check into metadata. (#33)nimbus sync --include-setup-data pulls setup objects from the connected org. Groups, queue routing, and UserRoles are read from the org and written back as nimbus.seed.{group,role,queue}.* entries in nimbus.properties, under a delimited block. Hand-authored seed lines outside the block are preserved across re-runs, so you can reproduce org-resident setup data locally without authoring every entry by hand. (#33)Limits API and by nimbus exec enforcement. nimbus.governor.soql-queries and nimbus.governor.dml-statements were accepted and shown by nimbus config show, but Limits.getLimitQueries() / Limits.getLimitDmlStatements() always returned the standard 100 / 150 regardless of the override, and nimbus exec kept enforcing at the defaults. Both now reflect the configured values, so projects on orgs with Salesforce-raised limits can match their runtime ceiling locally. (Enforcement under nimbus test already honored the override.) (#34)@future, Batch) get the platform's 200-query ceiling instead of the synchronous 100, in both enforcement and the Limits class. Previously async code was capped at 100, so async tests that legitimately exceeded 100 queries failed locally despite passing in the org. An explicit nimbus.governor.soql-queries override stays authoritative across both contexts. (#34)DeveloperName, not just Name. Factories that looked up a seeded group by its DeveloperName previously came up empty because only Name was written. Both columns now carry the seed key.nimbus.properties silently had no effect until a cold start. Changes are now picked up on the next run.--fetch-missing is no longer ~20x slower. The retriever was issuing one sf sobject describe per missing object — each its own process spawn + org round-trip — serially. It now runs them across up to 8 concurrent goroutines. For N missing objects the wall time drops from N×t to roughly ceil(N/8)×t. (#8)workers + 5, cap 100) was too tight when per-worker-schema cloning and test execution overlapped — setup transactions competed with test transactions and the pool ran dry. Changed to workers × 2 + 10, cap 200, with a DropWorkerSchema retry loop for transient deadlocks matching the existing CreateWorkerSchema retry. (#9)ctx and metadataProvider propagation. executeSubflow created its sub-executor without forwarding the parent executor's context and metadata provider, so a subflow that queried custom metadata or used ctx-dependent features (timeouts, cancellation) could fail or behave differently from the calling flow.SELECT from them works instead of reporting a missing-relation error.Parent__r.ParentCode__c where ParentCode__c is itself a formula — returned null because the resolution logic stopped at the parent's stored columns and never resolved its computed fields. The cross-object resolver now detects when the requested field is absent from the parent's DB row and recursively resolves it via resolveFormulaFields on the parent SObject. The resolved value is cached so subsequent lookups avoid re-resolution.System.LimitException: Too many SOQL queries or a 25 s timeout. The trigger re-entry is now capped at one dispatch per flow per (object, trigger event) pair.Map<Id, SObject> from records without Ids throws, matching the platform. new Map<Id, SObject>(records) where a record has no Id — an unsaved list, or new Map<Id, X>(Trigger.new) in a before-insert — now throws System.ListException: Row with null Id at index: N (the first offending row) instead of silently producing an empty or partial map. Maps built from queried records, and the copy constructor, are unaffected.Schema.describeTabs() returns your project's tabs. The returned tab set is now populated from the *.tab-meta.xml (CustomTab) metadata in your project — source-driven, like the rest of the schema. An object tab resolves its getSobjectName() and label from the object; web, Visualforce, and Lightning tabs carry their own label. Code that resolves a tab or icon for an SObject now finds it instead of getting an empty list.message field is distinct from getMessage(). When a class that extends Exception declares its own public String message field, reading the field now returns the assigned value while getMessage() returns the exception's built-in message (defaulting to Script-thrown exception when none was set) — matching the platform, where the two are separate slots. Previously both resolved to the first constructor argument.Type.newInstance() on an inner class of an @IsTest class upcasts correctly. Instantiating a test-local subclass reflectively — the common pattern of registering SomeType.class in a Map<…, Type> and later doing (Base) registry.get(key).newInstance() with a mock subtype defined inside the test class — now resolves the inheritance instead of throwing Invalid conversion from runtime type.Schema.describeTabs() is implemented. It returns a non-empty List<Schema.DescribeTabSetResult> (matching the platform guarantee that the call is never empty) instead of aborting execution with a malformed error. Tab/app metadata isn't synced, so the set carries no per-object tabs.OpportunityStage reference data is seeded. The standard sales-process stages (Prospecting … Closed Won / Closed Lost, with IsClosed / IsWon / DefaultProbability / ForecastCategory) are now present, so common @testSetup idioms that pick a valid stage from org config no longer return zero rows. Orgs with a customised stage picklist can override via nimbus.seed.record.implements clause (including Queueable, Schedulable, Comparable, and Database.Batchable), and override a superclass's virtual/abstract methods. Served by the language server, so they appear in both the JetBrains plugin and the VS Code extension.Owner. or Account.Parent. resolves the chain and offers the related object's fields — and child-relationship subqueries ((SELECT … FROM Contacts)) complete the child object's columns. SOSL RETURNING Account(…) completes that object's fields too. Standard objects work with no sync. Relationship fields are also validated, so a typo like Owner.Namee is flagged once the chain resolves to a synced object.sysd → System.debug(), tm → an @IsTest method, soqlf → a bulk-safe SOQL for-loop, bulkt → a 200-record test-data block, and more — ship in both the JetBrains plugin (native live templates) and the VS Code extension (snippets).nimbus new <kind> <Name> scaffolds a class, test, trigger, trigger handler, batch, queueable, or schedulable (with its -meta.xml) into your default package. nimbus fixture <SObject>… generates a TestDataFactory with required fields filled in from the schema. nimbus coverage diff <base.json> <pr.json> reports the coverage delta — overall, per file, and the exact newly-uncovered lines — with --fail-on-drop for CI gates.nimbus mutate takes a -p/--parallel flag and runs mutants across multiple workers, the way nimbus test already parallelizes test runs (Pro).get_governor_usage (per-test SOQL/DML/CPU/heap from the last run) and run_mutation_tests (mutation score plus surviving mutants), so an agent can spot un-bulkified code and weak assertions without leaving its loop.with sharing instead of system mode, and a SOQL query using WITH SECURITY_ENFORCED throws a System.QueryException (the clause was removed). Classes on earlier API versions are unchanged.SELECT Id, (SELECT Id FROM Contacts) FROM Account — previously flagged the relationship name (Contacts) as an unknown SObject. It's now recognized as a child relationship.🪄 Coverage action now sits at the top of every test class (alongside each test method), so you can collect coverage for a whole class in one click. The class-level button is served by the language server, so it appears in both the JetBrains plugin and the VS Code extension.Organization.InstanceName. Querying InstanceName on Organization returns Nimbus by default, so code can branch on whether it's running locally ([SELECT InstanceName FROM Organization].InstanceName == 'Nimbus'). Real Salesforce instance names are always uppercase, so there's no collision; override it per project with nimbus.org.instance-name.null instanceof T follows the platform's API-version rule. It returns true for code on API 31.0 or earlier and false from 32.0 (Spring ’15), governed by the class that contains the expression — so frameworks that rely on the legacy behavior (for example apex-lambda’s comparers, where compare(null, null) must report equal) behave correctly.Decimal collections coerce integer literals. new Set<Decimal>{60, 150} now stores Decimal values rather than Integer, so membership checks against Decimal field values match instead of silently missing.instanceof and casts match the platform. A List<SObject> satisfies instanceof against any concrete SObject subtype, and an explicitly cast collection argument — Collection.of((List<Account>) Trigger.new) — resolves List/Set → Iterable<T> overloads instead of failing with “no matching overload.”WHERE Id IN :(Set<Id>) raw previously produced a database syntax error; the cast operand is now captured correctly.System.-qualified valueOf(…) works. System.Id.valueOf(…), System.String.valueOf(…), and the other qualified forms previously returned null; they now reach the same handlers as the unqualified forms.JSON.deserialize rebuilds compound address fields. Deserializing a record with BillingAddress/ShippingAddress now materializes a read-only System.Address, so record.BillingAddress.getCity() works instead of failing on a raw map.nimbus.stubs.namespaces setting from nimbus.properties, so references to auto-stubbed managed packages resolve the same way they do on the command line.nimbus CLI on your PATH.nimbus sync -s Account,Contact,My_Object__c. Re-sync just the objects you name instead of the whole org. When you're iterating on a handful of objects, there's no longer any reason to wait on a full-org sync — other tables are left untouched. (--sobjects is the long form.)nimbus sync when nothing has changed is now near-instant rather than a full rebuild. Pass --rebuild to force the old drop-and-recreate behavior — the only mode that applies column type changes (and it wipes table data).System.Address from the components of a directly queried record; this extends it to relationship-traversed compounds. SELECT Account.BillingAddress FROM Contact followed by record.Account.BillingAddress.getCity() — or the equivalent Parent__r.Address through a custom lookup — now hydrates the parent record with the address components and rebuilds the compound on access, matching the platform.String.fromCharArray(List<Integer>) is now supported. Building a string from a list of character codes — for example String.fromCharArray(new List<Integer>{160}) to produce a non-breaking space — previously failed with an "Unknown method" error. It now returns the expected string, so code that strips or inserts specific characters by code point runs as it does on the platform.Address on Lead/Contact, BillingAddress / ShippingAddress on Account, and the equivalents — and then reading it back (record.Address.getCity(), System.debug(record.Address)) returned null. Nimbus now reconstructs the System.Address from the underlying street/city/state/postal/country components for records returned by SOQL. Fresh in-memory records still return null for the compound field, matching the platform (the value only materializes on query).static final String x = baseMethod(); — failed with "Unknown function". Nimbus now walks the superclass chain when resolving an unqualified static call, so classes that extend an abstract base behave as they do on the platform.Pattern / Matcher and String regex methods.TIMEVALUE(...), a Date that defaults to TODAY() + 1 — now has that value computed and filled in when you insert a record without setting the field, matching the platform. Defaults are applied before before-insert triggers, so triggers and any value you set explicitly still take precedence. Literal defaults (text, number, checkbox) are unchanged.TIMEVALUE(...) or TODAY()), the formula text was written into the table schema as a literal default — which PostgreSQL rejected, and the failed table creation aborted the whole run, so no tests could execute. These defaults are now handled correctly. (v1.0.3 goes further and evaluates them on insert.)nimbus mutate now mutates the class under test when you point it at a test class. Running mutation testing against a test class (e.g. nimbus mutate FooServiceTest) previously found zero mutants — test code is never mutated — and dead-ended at a misleading 0.0% score. Nimbus now derives the class under test from the conventional Test prefix/suffix, mutates that, and scopes each mutant run to the named test suite. Behavior is unchanged when a production class is already in the selection.nimbus login to sign in your CLI.--coverage-report *.xml) and JUnit results (--results-xml) — previously a separate tier — are now available on any Pro license, including the CI license. CI already requires Pro, so your pipeline can produce SonarQube/Codecov/GitHub-quality-gate reports with no extra entitlement.nimbus test failed even with a correctly configured NIMBUS_LICENSE_KEY secret. License validation now falls back to a key-based check that works for every license type, and trusts the licensing server's current expiry rather than the date embedded in the key at issue time (which can lag a renewal).nimbus.governor.soql-queries and nimbus.governor.dml-statements in nimbus.properties are now applied at runtime (an unset value falls back to the platform default of 100 / 150). Previously only nimbus.governor.mode took effect and the numeric caps were ignored.<start> has only an AsyncAfterCommit scheduled path (no synchronous connector) used to fail to parse and fail every DML on the triggering object. It now degrades to a no-op for synchronous execution — the async path wouldn't run during a synchronous test anyway.emailSimple (Send Email) flow action is now a no-op instead of an error. It was being resolved as an Apex invocable and failing with no @InvocableMethod found in class: emailSimple, which failed every DML that triggered the flow. Standard side-effect actions (emailSimple, emailAlert, chatterPost) are now skipped — they have no record state a test asserts on, and the platform doesn't send email during tests.nimbus exec now evaluates validation rules, like nimbus test. An insert that violated an active validation rule used to succeed under exec but fail under test; both paths are now consistent.nimbus doctor detects dotted managed-package references. It now flags usages like dlrs.RollupService.triggerHandler() (previously only the ns__Object / field form was matched), while still ignoring ordinary SObject field access such as acc.Name — so it can nudge you to stub a managed package before tests fail at runtime.BillingCountryCode = 'NL' now populates BillingCountry = 'Netherlands' before triggers and validation run, using the field's own picklist mapping — so country validation rules stop misfiring on records that are valid in the org. (Country only for now; state codes aren't globally unique and need country-scoped resolution.)nimbus daemon list and nimbus daemon stop --all. Daemons are per-project, so stray daemons from a previous session (or an MCP client on a different org) used to linger invisibly and daemon stop only stopped the current project's. daemon list now shows every running daemon (pid, org, project) and daemon stop --all stops them all in one command.System.runAs now honor the System Administrator profile. When a runAs user had any permission set assigned, AccessLevel.USER_MODE access was derived from those permission sets alone — the user's profile contributed nothing. A System Administrator has implicit View All / Modify All plus full field-level security, and permission sets only add to that, so a query for a field the profile already grants (a standard field like Account.Industry, or a custom field not named in the perm set) wrongly threw System.QueryException: No such column. Sysadmin runAs users now get full access regardless of assignments; Standard User and restricted-profile semantics are unchanged.toLabel(My_Picklist__c), FORMAT(Amount), or convertCurrency(Amount) was checked for FLS as if the whole expression were a column name, so it failed with No such column 'Tolabel(my_picklist__c)' even when the underlying field was perfectly readable. The check now resolves the inner field before verifying access, matching the platform.git branch switch brought in, stayed invisible until the process was restarted. The runner now reconciles its in-memory view against the filesystem before each run, the file watcher waits for a freshly written class to finish flushing before parsing it, and a bulk change like a branch switch triggers a full re-scan — so the set of tests that runs is always the set that's on disk.region__c with relationship name Stores — had its parent's child relationship derived as region__Stores__r instead of Stores__r, because the leading region was misread as a managed-package namespace. Every SOQL subquery against the real relationship silently returned no rows, and getChildRelationships() reported the wrong name. A genuine namespace prefix carries two underscore pairs (ns__Field__c), so a single-underscore field name is now treated as un-namespaced, matching the platform.SObjectFieldDataType, SavedPaymentMethodStatus / SavedPaymentMethodType / SavedPaymentMethodUsageType, WorkflowProcessStatus, StreamingAppDataConnectorSubTypeEnum, DelimiterTypeEnum, SortOrder, and 30 more. Code that references these types at compile time — declarations, String.valueOf(MyEnum.MEMBER), equality checks, switch over enum values — now resolves locally instead of erroring on an unknown identifier.ConnectApi.MlRetrieverCitationConfigurationModeEnum. An earlier draft carried the canonicals from a sibling enum (DISABLED / ENABLED); the spec values are CUSTOM / DEFAULT. Customer code that switches on this enum will now see the right shape.Slack.PostMessageAction, handles slash commands and interactive components, or reads Slack.UserInfo / Slack.ConversationInfo describe shapes now runs end-to-end against Nimbus's local stubs. Slack integrations no longer need a connected app or a live workspace to test.System.* gaps. Type metadata, lightweight introspection, and runtime info round out the core platform surface so it's fully resolvable without leaning on the generic fallback path.acc.Name and acc.NAME are the same slot), but pre-seeded shells inside Nimbus had been storing some fields in their declared case — so a read after an assignment occasionally missed because the two paths normalized differently. All field storage now goes through a single lowercased key, matching what Apex does.null, Boolean to false, and Integer / Long to 0 — so customer code that guards on obj.SomeField != null, !obj.SomeBool, or obj.Count == 0 short-circuits the way it does in the platform instead of seeing a sentinel value from an uninitialized slot.nimbus app — local dev server for Salesforce Multi-Framework React+Vite bundles. Replaces sf ui-bundle dev for offline development. Mounts your ui-bundles/<name> source at http://localhost:<port> with Vite's HMR, and serves the Salesforce API surface your bundle calls into locally against Nimbus's embedded Postgres — no scratch org, no proxy, no internet round-trip. Sub-commands: list, schema, build, test, preview; --all serves every bundle in the project simultaneously./services/data/vXX.Y/ path works. Implemented: GraphQL UIAPI (queries, mutations, introspection, aggregates, cursors), UI API REST (object-info, records CRUD, picklist-values, list-ui, MRU, related-list, layout, record-defaults, record actions, duplicates), Composite API (composite / batch / tree / sobjects), Bulk API 2.0 (ingest + query lifecycles), Search / SOSL, Limits, OIDC userinfo, Connect / Chatter (users/me, feeds, communities, CMS channels), Apex Invocable Actions, and ContentVersion multipart upload. Whatever your bundle hits, it lands on the same local data your tests use.nimbus serve live dashboard at /__nimbus/dashboard is reused by nimbus app. Inspect the same database, replay the same requests — one mental model across both commands.nimbus serve sessions no longer surface driver: bad connection errors after idle periods. The server was opening a fresh per-request transaction against the embedded Postgres on every call, which the pool would eventually time out and close mid-request. The server now reuses the live interpreter's connection pool — the same path the test runner already used — so connections survive idle stretches and reconnect cleanly when the pool prunes a stale handle.nimbus test emitted compile-time-rule diagnostics for every class in the project at registry-load time — including classes the test never touched — and under the default nimbus.compat.mode=strict would fail the run because of violations in unrelated code. Diagnostics are now deferred and surface only when the interpreter actually invokes the offending class (static call, instance construction, or test-method entry). The strict-mode exit gate fires for the same scope. Project-wide compat audits stay in nimbus validate for explicit "scan the whole repo" workflows.nimbus test --coverage with console, JSON, and HTML output runs on every install — no license required. Governor limit enforcement (SOQL/DML/query-row counters, strict/warn/off modes) was already part of the core runtime and stays free. The Pro tier focuses on developer-productivity features (background daemon, parallel execution, watch mode, debugger, mutation testing, VSCode coverage gutters); JUnit XML and Cobertura XML output remain Team-tier for CI integration. The principle: Free lets you trust the result, paid tiers make you faster.manifest/nimbus-missing.xml whenever a test run surfaces missing metadata. The file is a standard SFDX manifest you can hand to sf project retrieve start --manifest manifest/nimbus-missing.xml -o <alias> to fetch every missing item in one command. The filename is distinct from package.xml so it never clobbers a user-managed deployment manifest. --fetch-missing now drives a single sf invocation internally — replacing up to four per-category calls with one — so retrievals are faster and immune to argv length limits on projects with many missing items.__mdt table that wasn't in the project source returned an empty list silently — the test would pass on data that didn't actually exist. The detector now surfaces the missing type in the run summary and (with --fetch-missing) retrieves both the source XML and the runtime JSON describe in one pass, so a re-run sees the type and its records.--fetch-missing no longer redundantly re-fetches items the org doesn't have. The previous retry loop attempted up to three retrieve passes for the same set of names; if the org didn't have them on the first attempt, the second and third were guaranteed to fail too — burning ~10–30s per pass on a real sf call. Two root causes: sf project retrieve can return success with empty result arrays (when none of the requested items exist) or with entries marked state: "Failed" in the response — both shapes were being miscounted as successful retrievals. The retriever now parses sf's response correctly, surfaces the per-file failure messages to the user, and the runner remembers which entries it has already attempted so the same name is never re-fetched within one run.<name>CustomMetadata</name> with bare type names — but that manifest type expects record-level members in Type.RecordName form. Type schemas live under <name>CustomObject</name> in package.xml, with the __mdt suffix on the member name as the signal to sf. The mapping is now correct, so the auto-generated manifest round-trips cleanly through sf project retrieve.X { get { … X … } } from looping was keyed on the field name only — so when class A's getter for FOO read class B's FOO, B's getter was short-circuited and returned its raw (uninitialized) backing storage instead of running. Nebula Logger v4.18's new declarative ignored-class config (LoggerStackTrace.IGNORED_APEX_ORIGINS chains into LoggerParameter.IGNORED_APEX_ORIGINS) was the visible breaking case — the ignored-class list came back empty regardless of the configured custom metadata. The marker is now scoped per (class, field), so cross-class reads land on the real getter.RETURNING blocks now honor their WHERE / ORDER BY / LIMIT filters when running against Test.setFixedSearchResults. The fixed-results stub previously returned the raw Id list verbatim, ignoring whatever filter the caller had appended inside the RETURNING parens. Strict-search patterns (FIND … RETURNING LogEntry__c (Id WHERE RecordId__c = :recordId)) couldn't exclude records that didn't match. The stub now translates the RETURNING spec into an equivalent SOQL query scoped to the fixed Ids and runs it through the normal SOQL evaluator, so bind variables and platform filter semantics apply.nimbus mcp exposes Nimbus to AI coding agents. A Model Context Protocol server that lets Cursor, Claude Code, and other MCP-aware agents run tests, list test classes, fetch coverage, and surface failures the same way you would from the CLI — so agents can iterate on Apex code without leaving the editor.nimbus skills manages installable agent skills. Skills are bundled task playbooks (list, add, remove, path) that an agent loads on demand. Ships with the apex-coverage-uplift skill, which reads Nimbus's per-line coverage and drives an agent to write targeted tests for the highest-leverage gaps rather than synthetic call-only tests.list_test_classes tool now returns file paths alongside class names. Saves a round-trip when an agent needs to open the source.nimbus upgrade no longer clobbers Homebrew and Scoop installs. An in-place binary swap was wrong for package-manager installs — brew and scoop track the installed version in their own metadata, and any subsequent brew or scoop command would revert the swap, so the user appeared to upgrade and then snapped back to the old version. Nimbus now detects the install source from the resolved binary path and routes you to brew upgrade nimbus or scoop update nimbus. The passive "new release available" notice and the VS Code extension's update prompt use the same detection.PATH. If a second nimbus binary lives elsewhere on PATH (e.g. a leftover ~/go/bin/nimbus or ~/.local/bin/nimbus from a prior install method), nimbus upgrade now lists those paths so a fresh shell doesn't silently fall back to an older copy.(Id) "15char" casts no longer expand to 18-char form. The expansion was added to support a single use case (case-only-different 15-char Ids producing distinct 18-char forms for unique-constraint purposes) but breaks every Map<Id, V> and Set<Id> keyed off raw 15-char record Ids — the cast site is too far upstream to keep collections in agreement, and a half-dozen frameworks (apex-test-kit's mock relationships, apex-fp's pluckIds/groupBy, forceea's data factory) regressed once a cast Id no longer matched its raw counterpart. The unique-constraint path needs to be solved at the SOQL/DML layer instead.SObjectType.getDescribe().getName() now returns the proper API casing regardless of how the type was reached. Previously, calling Account.SObjectType.getDescribe().getName() returned "Account" while account.getSObjectType().getDescribe().getName() on a queried record returned "account", breaking frameworks (fflib UoW) that key a Map<String,V> by the result and look it up the same way. The dispatcher now normalizes the lookup name through the schema provider so both paths agree.Map<Id, V> map literals lowercase their keys. Apex Id values are case-insensitive, but the literal initializer was using the raw key string. A subsequent .get(otherId) would compute the lowercased key and miss, returning null even when both Ids referred to the same record. Map literal evaluation now mirrors what put() does for Map<Id, V>.fields.getMap(), getGlobalDescribe(), RecordTypeInfo maps), OrgLimits.getMap(), HTTP headers, QueryException.getInaccessibleFields(), and SObject.getPopulatedFieldsAsMap() now carry an explicit case-insensitive flag. No behaviour change for users — regular Map<String, V> still defaults to case-insensitive lookup as before — but it sets up the correct foundation for the planned switch to spec-correct case-sensitive defaults.JSON.serialize(Map<String, V>) emits keys in reverse insertion order, matching Salesforce. Verified live across maps of size 1, 3, 5, 10, and 20: SF's JSON.serialize iterates string-keyed maps in the exact reverse of put()-order. Nimbus was emitting forward insertion order, which broke any test asserting on full-string JSON output (DLRS's RollupEditorController.testValidateRollupConfig). The fix also covers nested maps inside lists, SObjects, and custom objects — those previously fell through to Go's encoding/json map encoder, which alphabetizes keys, so they got the wrong order even when the top-level value was already correct.COUNT() / COUNT(field) aggregates regardless of how many underlying rows contributed; SUM / AVG / MIN / MAX still bill per underlying row. Nimbus was applying the underlying-row count uniformly, over-counting tests that asserted on the platform's post-Summer-'18 COUNT semantics. The dispatcher now detects COUNT-only aggregates and uses the result-row count.(SELECT Id, PricebookEntry.Product2.Name FROM OpportunityLineItems)) was projecting only the first-level FK column and dropping the grandparent reference, leaving the in-memory relationship null and causing NullPointerException on access. The dispatcher now recurses through dotted segments — fetches each level's parent rows in turn and chains them onto the SObject so oli.PricebookEntry.Product2.Name resolves correctly.ORDER BY drops cross-relationship terms instead of failing the query. A subquery sorted by a grandparent field (ORDER BY PricebookEntry.Product2.Name) couldn't resolve against the flat single-table SQL nimbus emits and failed with missing FROM-clause entry — the subquery returned zero rows even though the FK filter would have matched several. The dispatcher now drops dotted ORDER BY terms and falls back to the default Id ordering when none remain, so the rows still surface (in insert order rather than the requested cross-object order).logEntry.ErrorMessage__c += someMsg + '\n'; (DLRS's rollup-error logger) accumulate values terminated with newlines, and the matching equality assertions exclude the trailing newline. Nimbus was preserving the value verbatim through the in-memory obj.field = … path, so the assertion failed by exactly one character.Database.update with allOrNone=false now commits successful records when others in the batch fail. Salesforce processes per-record failures independently — a single validation-rule failure on row 2 doesn't block rows 0 and 1 from committing. Nimbus was running the batch as a single SQL update, so any failure rolled back every record. The partial-success path now retries record-by-record after a group throws so each row's outcome is recorded individually.Database.Error.getMessage() on a partial-success failure now returns just the validation-rule message. The DML exception nimbus throws on validation failure carries the platform-style framing (Update failed. First exception on row 0; first error: STATUS_CODE, message: [field]). Surfacing that whole string through Database.Error.getMessage() broke any framework that concatenates getMessage() + getStatusCode() + getFields() (DLRS's rollup-error logger does this). The dispatcher now parses the platform-format message and exposes the trio (statusCode, validationMessage, fields) on the underlying Database.Error object.<sharingModel>Private</sharingModel> in their .object-meta.xml are recorded on the schema, and SOQL run from a with sharing class under System.runAs(testUser) filters records by ownership: rows owned by the running user, plus rows where a matching row in the object's __Share table grants the user explicit access. Earlier nimbus passed every record through regardless of OWD; rollup tests asserting that User B sees only their own children failed because the engine summed everyone's children.GROUP BY) now have the OwnerId predicate injected before grouping. The previous WITH SHARING filter skipped queries with GROUP BY entirely on the assumption that the OwnerId column wasn't projected; in practice the predicate belongs in the WHERE so it filters rows before they reach the aggregate. The injection now finds the GROUP BY position and inserts the predicate ahead of it.StandardController.getId() now returns the 15-character form of the record Id. Visualforce reads the id from the URL id query parameter, which is the case-significant 15-char form. Code that builds a value from standardController.getId() and asserts equality against String.valueOf(record.Id).substring(0, 15) (DLRS's scheduled-rollup cron name pattern) was reading 18-char from nimbus and failing the comparison. The constructor now truncates to 15-char on retain.valuesEqual now falls back to a 15-char prefix check when both operands are Id-shaped strings of length 15 or 18.OwnerId filter on setup objects. Setup / metadata objects (ApexTrigger, ApexClass, Profile, PermissionSet, __mdt, etc.) have no record-level sharing — they're universally readable subject to object-level CRUD, which is checked separately. Nimbus was treating them like any other queried object, appending OwnerId = '' to the WHERE clause, which either filtered every row out (the records aren't owned by the running user) or failed against tables with no OwnerId column. A trigger-deployed validation rule that queries ApexTrigger from inside a non-admin System.runAs block (DLRS's rollup-summary insert validates that the configured rollup target trigger is deployed) silently came back with zero rows. The dispatcher now skips the OwnerId injection for setup/metadata objects.Limits.getQueryRows() reports the number of underlying rows that an aggregate (SELECT … GROUP BY or COUNT(field)) consumed; PostgreSQL returns one row per group, so nimbus was reporting the count of grouped result rows. Tests that asserted on the platform's underlying-row accounting (rollup-style verifiers that expect 9 child rows for 3 groups of 3) under-counted by the difference. The dispatcher now runs a SELECT count(*) sibling against the same FROM/WHERE and uses that for the SOQL row tracker.Database.update against soft-deleted records now raises ENTITY_IS_DELETED before validation rules fire. A trigger pattern that deletes a parent record then runs a rollup recalculation needs the update against the now-deleted parent to surface as a deleted-row error so the rollup framework can swallow it. Nimbus was running the update through, firing the validation rule on the recalculated value, and producing a FIELD_CUSTOM_VALIDATION_EXCEPTION instead. The dispatcher now pre-checks soft-delete state and throws System.DmlException with statuscode ENTITY_IS_DELETED before the update reaches the validation pipeline.System.schedule duplicate-name error now matches platform wording. Apex throws System.AsyncException with the message The Apex job named "X" is already scheduled for execution. when scheduling a job whose name already exists. Nimbus was throwing the wrong wording (Duplicate CronTrigger name: X), so any test asserting the exception message via String.equals failed.System.QueryException whose message read like SOQL execution error: query failed: ERROR: column foo.stage does not exist (SQLSTATE 42703) — fine for debugging, but anything that asserted on the message with String.contains('No such column') failed. The dispatcher now reformats column-not-exist errors to the platform string: No such column 'Stage' on entity 'Foo'. If you are attempting to use a custom field, be sure to append the '__c' …, with original casing recovered from the SOQL source.IsDeleted system field. Code that walked SObjectType.<X>.getDescribe().fields.getMap() looking for standard audit fields on a custom object came back empty for isdeleted — fflib's fflib_SObjectDescribe wrapper exposes the same map and DLRS's rollup-criteria validation rejected any criteria field that referenced IsDeleted with "Field isdeleted does not exist on the child object." The standard-fields injector now appends IsDeleted alongside CreatedById, OwnerId, SystemModstamp, etc.Database.undelete(opps[0].Id)) the trigger context was populated with a stub SObject whose Fields map only contained id. Triggers that read foreign keys or aggregated values (rollup recalculators are the obvious case) silently produced wrong results because the lookup field they needed was missing. The handler now re-queries each undeleted record after clearing isdeleted and merges the row into the trigger payload before firing.(Id) cast on a 15-character string now expands to the 18-character form. Real Apex normalises a 15-character Id to its 18-character canonical form when cast to Id, encoding the original case into the three-character checksum so two 15-char strings that differ only by case map to two distinct 18-char Ids. Nimbus left the value unchanged, so (String)((Id) "Aaa00000000") and (String)((Id) "aAA00000000") produced identical strings — patterns relying on case-insensitive unique constraints over a casted Id incorrectly tripped the constraint.Database.getQueryLocator now raises System.QueryException on parse errors. The locator was eagerly executing the query but silently dropping any error result, so callers using it as a "validate this string" probe (DLRS's RollupSummaries.onValidate, fflib's QueryFactory validation paths) couldn't catch malformed SOQL via try/catch. The dispatcher now propagates the underlying ThrowValue.String.format now follows Java MessageFormat quoting rules. A single quote starts/ends a quoted region (placeholders inside aren't expanded), and '' represents a literal single quote both inside and outside that region. Templates like 'aaa\'\'{0}\'\'bbb' now correctly produce aaa'x'bbb instead of aaa''x''bbb — matches platform behaviour and unblocks any test that does startsWith on a formatted error string.ORDER BY now defaults to NULLS FIRST for both ASC and DESC. Salesforce SOQL treats null as the smallest value regardless of direction; PostgreSQL's default is the opposite (ASC → NULLS LAST, DESC → NULLS FIRST). Tests that omit the explicit NULLS FIRST / NULLS LAST clause and depend on the platform's ordering — common in concatenation rollups — were getting differently-ordered result sets and asserting the wrong concatenated string. The translator now emits the SOQL-default NULLS FIRST when the caller didn't specify one.X__mdt objects are universally readable on the platform — every profile, including Read Only and Standard User, can read MDT records and fields. fflib's assertIsAccessible() wrapper around an MDT query (used in DLRS's LookupRollupSummary2__mdt selectors) was throwing under any System.runAs(readOnlyUser) context. CheckObjectPermission and CheckFieldPermission now short-circuit to true for read on __mdt suffixed types.Database.undelete now accepts Id and List<Id>. The handler only accepted SObject / List<SObject>; calls like Database.undelete(opps[0].Id) erroried with Cannot undelete *interpreter.StringValue. The dispatcher now resolves the SObject type from the Id's key prefix and synthesises a stub SObject so the rest of the path (savepoint, isdeleted clear, AFTER UNDELETE trigger) is unchanged.Schema.SObjectField now resolve through the field-describe lookup. The token returned by SObjectType.<Sobj>.fields.<Field> is a Schema.SObjectField per platform semantics; Apex routinely accepts the same value where a Schema.DescribeFieldResult is expected (Schema.DescribeFieldResult dfr = Account.fields.Description; followed by dfr.getType()). Nimbus emitted the SObjectField but only routed describe methods (getType, getName, getSObjectType, getLength, isAccessible, …) for the DescribeFieldResult class — calling them on an SObjectField returned null, breaking common rollup-validation patterns. The dispatcher now forwards both classes through the same describe-style lookup so both usage shapes resolve identically.{} blocks no longer eagerly execute during class-graph load. When loading the dependency graph for any entrypoint (anonymous Apex via nimbus exec or a focused nimbus test SomeTest), every transitively-pulled-in class's static initializer ran up front. For test classes that's wrong: their static {} blocks routinely insert seed records (Accounts, Contacts, custom-setting rows) that real Salesforce would isolate to that class's test transaction. Running them eagerly landed the inserts in the shared database with no rollback boundary, causing later [SELECT … LIMIT 1] queries to return rows from another test's fixture. The runner now skips static {} blocks for @isTest-annotated programs during eager init; field initializers still run so static fields hold their declared defaults, and the blocks fire lazily on first reference (inside the active test's transaction, where rollback applies).Calendar standard-object describe. SOQL referencing Calendar (commonly via WHERE OwnerId IN (SELECT Id FROM Calendar WHERE Type = 'Public') on Event-related controllers) was failing with "relation calendar does not exist" because the platform's standard Calendar object wasn't in nimbus's embedded describe set. Run nimbus sync to materialise the table in projects updating from earlier versions.field NOT IN (…) now matches Salesforce's null-tolerant semantics. The platform treats NULL NOT IN (…) as TRUE — so a row with a null lookup-traversal value (e.g. Lead.ConvertedContact.Status__c when the lead isn't converted) still satisfies the predicate. Standard SQL and PostgreSQL return NULL for the same comparison, which evaluates as false in WHERE, silently dropping the row. The translator now wraps every <lhs> NOT IN (literals) as (<lhs> NOT IN (literals) OR <lhs> IS NULL) so the LEFT-JOINed null falls back to TRUE. Subqueries on the right (NOT IN (SELECT …)) keep their existing semantics.format(field) [alias] projections now resolve to the underlying column. The translator was leaving the format(...) wrapper untouched, so the SELECT list quoted it as a literal column name ("task"."format(status) statusvalue") and Postgres rejected the query. format() on the platform is a locale-formatting projection for Date/Datetime/Number/Currency fields; nimbus emits the raw column under the requested alias so downstream .get('alias') reads return a sensible value.IS is no longer mis-quoted. The simple-identifier quoting pass treated any uppercase-starting token as a column reference; IS NULL emitted by the new NOT IN wrap (and any user-written same-case form) had its IS stamped as "<table>"."is", breaking the surrounding clause. IS is now in the keyword skip list alongside AND / OR / NOT / IN.db.schema.table.column. A WHERE clause like SELECT … FROM Event WHERE Event.StartDateTime != null sent the relationship resolver into the catch-all path because Event isn't in the join map (it IS the main table). The simple-identifier quoting pass then stamped both segments independently and produced "event"."event"."event"."startdatetime", which Postgres rejected as a cross-database reference. The relationship resolver now treats a parent prefix matching the main table as a self-reference and emits a single "event"."startdatetime".INCLUDES/EXCLUDES now translate to PostgreSQL = ANY(ARRAY[…]) / <> ALL(ARRAY[…]). The translation was wired into a legacy WHERE path that was never called from the active translator, so the operator passed through to PostgreSQL untouched. The downstream identifier-quoting pass then matched the bare keyword as a column name and stamped "table"."includes", surfacing as a syntax error pointing at the table identifier. The translation now runs in the live WHERE pipeline, and ARRAY/INCLUDES/EXCLUDES are added to the keyword skip list so subsequent passes don't mis-quote them.System.SelectOption.getLabel() now returns the stored label. The generic describe-class getLabel() dispatcher returned the receiver's ClassName as a fallback when none of its enumerated classes matched, which shadowed the dedicated SelectOption dispatcher further down — calls of opt.getLabel() on a Visualforce SelectOption were resolving to the literal string System.SelectOption. Visualforce extensions that match CSV header components against field labels via
opt.getLabel() failed to map any field; the import flow then quietly skipped its main insert. Fixed by handling SelectOption explicitly in the getLabel case.System.JSONException from the parser instead of looping forever. JSON.createParser(s) on a malformed string was returning a parser with zero tokens; consumer code shaped like while (parser.nextToken() != JSONToken.END_OBJECT) { … } would then loop because nextToken() kept returning null and null != END_OBJECT stays true. The parser now captures the underlying tokenize error at createParser time and replays it as a System.JSONException on the first token-reading call (nextToken, getCurrentToken, getText, get*Value, skipChildren) — matching real Salesforce, where createParser is lazy and the failure surfaces on consumption. After the throw the parser transitions to an exhausted state so a subsequent consumeObject call (often called outside the try/catch) doesn't immediately re-throw.while and do-while conditions now propagate ThrowValue. The condition expression was only checked for ErrorValue; a method call inside the condition (e.g. parser.nextToken() raising JSONException) had its ThrowValue fall through to IsTruthy, which returns true for unknown types — running the loop body and re-evaluating the condition forever. Both loops now treat the condition the same way the body already did: throws and errors return immediately so the surrounding try/catch can handle them.this.profile = [SELECT … FROM Foo__c WHERE Id = :id] assigns a single SObject (the first row) when the field is declared as a singleton, throwing if the result has > 1 row. The local-variable assignment path already coerced via coerceSoqlListToSObject; the instance-field path was storing the raw List<Foo__c> instead, so a downstream this.profile.Bar__c = X later in the same method failed with "UNIMPLEMENTED_ASSIGNMENT" because the receiver wasn't an SObject. The field-assignment path now applies the same coercion.public Foo__c profile { get; set; }) returns the inner value wrapped in a FieldReferenceValue; the profile.Bar__c = X assignment path was matching against the wrapper rather than the underlying SObject and falling through to the catch-all "UNIMPLEMENTED_ASSIGNMENT". The wrapper is now unwrapped before the type-switch.Datetime ± Integer arithmetic now returns a Datetime offset by N days. system.now() - 10 previously errored with "Unknown operator". Apex semantics: integer addition/subtraction on a Datetime advances/regresses the value by that number of days (matching Date). Both Datetime + Integer and Datetime - Integer are now handled, preserving the operand's IsGmt flag so subsequent comparisons stay on the correct reference frame.Limits.getFindSimilarCalls() / getLimitFindSimilarCalls() are now defined. The legacy duplicate-management counter was missing; codebases that defensively read every governor counter via reflection or that simply log the full set hit "Unknown method" when they touched it. Stubbed to 0 / 3 to match the platform shape.// … comments inside SOQL brackets no longer truncate the query. The translator was stripping Apex line comments AFTER collapsing whitespace; once newlines became single spaces, the regex //[^\n]* happily ate everything from the first // through to the closing bracket. Multi-line SOQL with field-trailing annotations dropped most of the SELECT list and arrived at the DB as a truncated query. Comment stripping now runs first.Database.executeBatch now writes the AsyncApexJob row shape Salesforce actually produces. Real SF emits one parent BatchApex row at executeBatch time plus one BatchApexWorker row per chunk processed; nimbus was emitting only a single row with one or the other JobType. The parent row is now inserted on enqueue and worker rows per scope iteration in executeBatchLifecycle, so empty-input batches produce one row (parent only) and chunked batches produce one + N.System.QueryException. The 0.1.36 chain-break that suppressed broken JOINs for the parent-to-child WHERE pattern (WHERE Contacts.Individual.Id = …) was too eager: it also swallowed genuinely invalid field paths like UserLicense.InvalidPath.Name, where the first segment resolves but a later one doesn't. The translator now silent-breaks only when the depth-0 segment matches a known child-relationship name (handled later as a semi-join); every other unresolved segment falls through to the broken-JOIN path, so the downstream SQL error is wrapped to System.QueryException at the SOQL boundary.Account/Contact/Lead.CleanStatus → Pending). The platform stamps these on insert even though the field describe lists no
defaultValue; nimbus stored them as null, which broke any rollup or evaluator that COUNTs over CleanStatus through a one-to-many path. Verified by running anonymous Apex against a real org: insert new Account(Name='X') followed by SELECT CleanStatus returns Pending. Structured as a per-SObject map so future implicit defaults drop in without touching call sites.
DAY_ONLY() now uses GMT semantics, matching the platform. Salesforce extracts the date from the stored UTC instant; nimbus was using the raw locally-stored components, so a CaptureDate at local 23:59:59 was incorrectly matched as todayby
DAY_ONLY = TODAY. The translator now wraps the column with AT TIME ZONE before CAST AS DATE, lifting the stored components into a real UTC instant first.IsGmt operands to a true UTC instant. Datetime.newInstanceGmt(d, t) and Datetime.newInstance(d, t) produce instants offset by the user's timezone; nimbus stored both as raw components, so a GMT-built reference compared against a local-flavoured stored value with raw component equality got the wrong answer for boundary records across midnight. When the operands disagree on IsGmt, each side now resolves to a UTC instant; same-IsGmt comparisons keep the historical component path, so codebases that store and compare exclusively non-GMT values are unaffected. addDays/addMonths/etc. now preserve the IsGmt flag, and Datetime.valueOf(stringWithTzMarker) returns an IsGmt=true result so downstream comparisons stay on the right reference frame.System.Assert.areEqual Datetime semantics now match Datetime.equals (second-precision). The Assert path fell through to a string-form fallback that included milliseconds, so a Datetime that round-tripped through SObject field assignment (which truncates millis to match Salesforce's stored precision) compared unequal to its pre-store twin under Assert.areEqual but equal under System.assertEquals. Apex's documented Datetime.equals() ignores millis; both paths now agree.SetNull child references only on hard delete; soft delete (default delete records;) keeps them so a subsequent undelete restores the chain end-to-end. Nimbus was nullifying immediately, so delete parentApps; undelete parentApps; left children with permanently null lookups. Cascade-delete (master-detail) is preserved unchanged — those still cascade into the recycle bin and restore on undelete.Set<T> instanceof Set<Object> now respects Apex's generic-parameter invariance. Set<String> is not a Set<Object> for instanceof purposes (it is for assignment, but those are different rules). The earlier permissive behaviour conflated the two and miscategorised typed-instanceof chains that branch on Set<Object> vs Set<SObject>. The legitimate covariance case (Set<Account> instanceof Set<SObject>) is unchanged.when literals now apply leading sign tokens. The Apex grammar permits when -7 as a single literal with a SUB child; the tree walker was reading only the integer part, so when 4, 5, 6, -6, -7, -8 silently collapsed to when 4, 5, 6, 6, 7, 8. Codebases that map negative offsets through Math.mod got off-by-one fiscal-month routing.Test.stopTest is now discarded between test methods. Real Salesforce tears down each test method's queued Queueables/Schedulables/Future jobs/Platform Events at method boundary; nimbus was carrying them on the shared interpreter, so a stale Schedulable from test N could fire during test N+1's Test.stopTest drain — running with whatever static state happened to be in scope. The runner now resets these queues alongside ClearAllStaticVars at the start of every test method.COUNT() with LIMIT N now caps the returned count at N. Salesforce semantics: SELECT COUNT() FROM X LIMIT 1 returns 0 or 1, never the full row count. PostgreSQL applies LIMIT to the result set (always one row for a count-only query), so the standalone clause was a no-op. The translator now folds the limit into the projection via LEAST(COUNT(*), N) when the SELECT is a single count and there's no GROUP BY.LEFT JOIN account AS what ON … WHERE what.type = 'Account' — the join alias is named after the relationship (what), not the table (account). The earlier polymorphic-Type-tautology pass only stripped the redundant filter when the alias name matched the literal directly. It now also accepts matches where the alias resolves through the joins map to a table matching the SObject literal.WHERE Contacts.Individual.Id = :recordIds — strict bare SOQL doesn't permit child-relationship traversal in WHERE, but the platform appears to silently rewrite to the equivalent EXISTS shape. The translator now does the same up front: id IN (SELECT AccountId FROM Contact WHERE Individual.Id = :recordIds). Operators supported: =, !=, <>, IN, NOT IN.toLabel(picklist) in SOQL now returns the picklist's display label. Salesforce's toLabel(Field__c) returns the human-readable label (e.g. Long Range) rather than the stored API name (
Long_Range). The translator was stripping the wrapper and projecting the raw column, so tests asserting against labels passed in the org but failed locally. Per-value labels are now captured in FieldSchema, propagated through the SFDX scanner, embedded JSON describe, and source cache; the translator emits a CASE expression mapping API names to labels, aliased to the underlying column. Falls back to the prior raw-value behavior when labels are unknown (global value sets, missing schema), so queries that already worked still do.__r placeholders now compare equal to null. The Apex pattern if (rec.Rel__r != null) { …rec.Rel__r.field… } was failing locally because the interpreter returned an unlinked SObject placeholder for __r reads on records with a null foreign key — so the guard fell through and the chained access blew up. Equality (== / !=) now treats these placeholders as null while preserving safe field reads on the same value, matching real Salesforce semantics.Test.loadData now coerces CSV cells to typed Apex values. Every cell was previously stored as a String regardless of the SObject's field schema — so test code that did acc.AnnualRevenue.intValue(), lead.IsConverted == true, or opp.CloseDate.year() on loaded records blew up because the value was a String, not a Decimal/Boolean/Date. The CSV reader now consults FieldSchema.Type per column: numeric fields parse to Integer/Decimal, checkboxes to Boolean, date/datetime via the same flexible parser the rest of the interpreter uses, and blank cells become null (matching Apex's blank-cell semantics). Reference fields and unknown columns fall back to String.nimbus stubs auto and nimbus test --write-stubs previously wrote new stubs/<Name>.cls files to disk but left them outside the runner's in-memory state, the consolidated AST cache, and any running daemon — so the next test run paid a re-parse cost, and a daemon serving LSP/IDE features stayed blind to the new classes until restart. The writer now hands new files back to the runner: programs map and class registry are mutated in place, .nimbus/cache is updated, and the daemon (if attached) is invalidated explicitly. The daemon also watches stubs/ on its filesystem watcher and pre-parses it at startup, so file events from any source — CLI, LSP, hand edits — flow through without a restart.nimbus mutate now loads nimbus.properties for the baseline pass. The mutate command built its test runner from CLI flags only, skipping the project-config block that nimbus test uses — so seeded labels, mock profiles, stub namespaces, org config, and a dozen other fields were silently zero-valued during the baseline. Projects that override custom labels via nimbus.seed.label.* (or rely on any other nimbus.properties seeding) saw baseline has 1 failing tests — fix them before mutation testingon tests that pass cleanly under
nimbus test. Mutate now mirrors the test runner's config loading so the baseline matches.nimbus test. A green test built on stale schema is the worst kind of false confidence — pass locally, fail on deploy when a custom field added in the org wasn't in the local DB. Free now pays the sync cost up front so the result is honest. Pro keeps the existing first-run-only behavior because the daemon owns sync lifecycle and keeps state warm; trigger an explicit refresh with nimbus sync when needed. The setup output prints a one-line hint so the speed cost lands paired with the upgrade path.nimbus upgrade works on Windows. The self-update path assumed every release artifact was a .tar.gz with a bare nimbus binary inside; on Windows the asset is a .zip containing nimbus.exe. Upgrade now extracts both archive types and matches the OS-correct binary name, so nimbus upgrade from a Windows shell completes instead of erroring with binary not found in archive.
shared_buffers=512MB, jit=off, fsync=off, autovacuum=off, etc.) from reaching pg_ctl on Windows — postgres booted with conservative defaults instead, leaving warm test runs 2-5× slower than the Unix path. We now apply the same parameters via ALTER SYSTEM + a quick restart, so Windows users get near-parity warm-run performance with Mac and Linux. CI on windows-latest verifies the tuning actually lands via SHOW assertions on every PR.initdb previously aborted at the post-bootstrap stage on Windows because of a path-layout mismatch between the bundled binary and the extension DLLs. The extractor now lifts dict_snowball.dll, plpgsql.dll, and friends to the directory the Windows binary expects, and we work around an upstream quoting bug that caused pg_ctl to reject our perf-tuning flags. No first-launch download required — the bundle ships in the binary and just works.windows-latest runner, so the Pro experience won't silently regress on Windows.nimbus-windows-amd64.exe binary alongside the macOS and Linux builds.@testSetup data across multiple methods. Sibling tests now see fresh flow state.EventBusSubscriber.LastProcessed / LastPublished, CronTrigger.JobType / CronJobDetailName). Resolves intermittent failures on parallel runs.nimbus upgrade — self-update from the CLI. Pulls the latest release tarball from GitHub and replaces the running binary in place. Mirrors the curl install script — same source, same naming. Aliased as nimbus update and nimbus self-update. Use --check to compare versions without installing. (This should have shipped in 0.1.20; better late than never.)nimbus login — browser-based machine activation. Sign in via your browser and the CLI registers itself; no key paste, no config files. Companion commands: nimbus logout frees a seat, nimbus whoami shows account + tier. Existing installs keep working untouched — same 7-day offline grace.stubs/Nebula/, stubs/fflib/, …) with optional objects/ subdir for namespaced custom objects. Walkthrough in berlinbrew-demo.nimbus license activate / deactivate / status are removed in favour of nimbus login / logout / whoami. NIMBUS_LICENSE_KEY still works in CI and AI-agent environments. Existing installs keep working until reinstall.machine already activated.
apiVersion=52.0 can't use a v66 feature like Database.getCursor or Blob.toPdf — Salesforce eventually rejects the deploy, but the discovery loop is slow. Nimbus now catches it before the deploy. The check reads each *.cls-meta.xml for the declared apiVersion, looks up every referenced symbol against a per-version catalog, and emits a warning when the symbol's introduction version is newer than the class's. Surfaces in three places:nimbus validate — inline alongside parser errors.apex-version-lint — standalone CLI for CI gates. Add -strict to exit non-zero on any finding.Database.query(String) (baseline) from Database.query(String, AccessLevel) (v57) so the validator doesn't false-positive on the older overload.// nimbus:ignore on the same line or the line directly above suppresses the apiVersion warning. Add a comma-separated rule list to suppress only specific rules. Bare // nimbus:ignore is a catch-all for everything Nimbus emits.System.Domain d as a parameter, local, field, cast target, or generic type argument now warns on the type itself — same diagnostic the call site would produce, attributed to the declaration line..cls without a sibling *-meta.xml silently disables apiVersion checks. VSCode now flags the file at the top so the user knows the gate isn't running. A meta.xml that exists but omits <apiVersion> gets a softer hint (the file is still deployable with Salesforce's default).Canvas.Test.mockRenderContext + Canvas.RenderContext / Canvas.ApplicationContext getters (v31).System.SandboxContext getters (v36), Metadata.DeployContainer.addMetadata + DeployCallbackContext.getCallbackJobId (v40).ConnectApi.NextBestAction.executeStrategy (v45), Formula.recalculateFormulas returning one FormulaRecalcResult per record (v47).Auth.VerificationPolicy.HIGH_ASSURANCE + UserManagement verification methods (v49–v50), Messaging.CustomNotification setters + send() (v50).Database.LeadConvert person-account fields + Setting__mdt.getInstance(QualifiedAPIName) (v51), B2B Commerce ConnectApi.* stubs + Messaging.SingleEmailMessage.setRelatedToId (v52).functions.FunctionInvocation getters (v53), System.Domain getters + DomainParser.parse overloads (v54).Invocable.Action.createCustomAction(type, ns, name) + getName / getNamespace / isStandard / setInvocationParameter (v56).System.Label.translationExists + ConnectApi.NamedCredentials.create / get / delete (v58), System.Collator + DataWeave.Script.createScript(ns, name) (v59), EventBus.TriggerContext.getRetries / lastError / setResumeCheckpoint (v62), FormulaEval.FormulaInstance.getReferencedFields (v63).Database.getCursor now returns a Database.Cursor (the v66 paginated cursor) instead of falling through to Database.QueryLocator. Apex code following the official docs (Database.Cursor c = Database.getCursor(query); c.fetch(0, 10);) was crashing with Unknown method fetch on object Database.QueryLocator.
FormulaRecalcResult.isSuccess() returns the stored success flag instead of always false. Same fix for getSObject() and getErrors() — all three now read from the result actually populated by Formula.recalculateFormulas.__mdt.getInstance(key) accepts the record Id and QualifiedAPIName forms added in Spring '21 (v51), not just the legacy DeveloperName. Setting__mdt.getInstance('MySetting.Alpha') now resolves; previously it returned null.Auth.VerificationPolicy.valueOf("HIGH_ASSURANCE") now returns the enum value instead of throwing NullPointerException.Metadata.DeployContainer.getMetadata() and similar season-specific accessors no longer get shadowed by a generic getter fallback that returned null. Affects every namespace stub class added in this release.nimbus serve — local Salesforce Pub/Sub API (Pro). gRPC endpoint with full Avro encoding, auth headers, replay-id support, and keepalive. Point CometD- or EMP-style consumers at localhost and develop platform-event flows with no org. Backed by 26 new exception types, 7 enums, and 15+ stdlib classes. See the Local Server page for setup.--assign-perms on nimbus test grants permission sets to the running user before execution.--quiet flag, transparent parallel gating, and honest defaults — nimbus test no longer over-promises about parallelism when the workload can't take advantage of it.stubs/: Site, SiteDetail, ContactPointEmail, ContactPointAddress, ContactPointPhone, ContactPointConsent, OperatingHours, DandBCompany, CallCenter, Individual, OpportunityContactRole, EntityDefinition, BusinessHours, TaskStatus, CaseStatus, Event. Embedded standard SObjects in .nimbus/schemas/ are now auto-recognized..object metadata loading: inline record types, picklist values, and child relationships from old-style metadata files. CumulusCI unpackaged/ directories are scanned for record types..labels and .translation are loaded. The labels parser tolerates bare <labels> fragments that ship without the <?xml?> header or <CustomLabels> wrapper.nimbus.seed.label.<FullName>=<value> overrides a Custom Label's resolved value at runtime. Use when a label's source-of-truth lives in the deployed org (e.g. an admin-set environment-specific profile Id that ships as PLACEHOLDER in the repo).nimbus.seed.group.<Name>=<Type> seeds standing Public Groups into the database. For regional or team groups that admins create once in the org and user-trigger automation queries by name — not usually checked into repo metadata. Deterministic 00G-prefix Id from the name.Test.loadData defaults OwnerId to the running user when the CSV omits it, mirroring what Apex DML does on insert.System.Version, Test.testInstall / InstallContext (managed-package upgrade simulation), Approval.lock / unlock with real LockResult / UnlockResult, System.SelectOption VF class, Schema.DescribeFieldResult.getController() for dependent picklists, Schema.SObjectField.getPicklistValues(), Network stubs (Communities/Experience Cloud), Database.Cursor.fetch(position, count), Metadata.* deploy-container ops (stubs), Messaging.SendEmailResult.isSuccess(), ApexPages.StandardController stub.IN, and ORDER BY are now case-insensitive for string-typed fields, matching Salesforce behavior. Same for __mdt.getInstance() DeveloperName lookups.Lead.Status back to Open before a flow filtered on Status = Unqualified). Flow $Record writes now also sync back onto the save unconditionally — nimbus.compat.flow-record-sync is removed and the Salesforce-truthful ordering (flow writes → before-triggers → validation rules) is the default. Previously gated flag-off suites may surface validation-rule failures that the old path was hiding; those are real bugs on the platform too.EncodingUtil.base64Decode now strips whitespace from the input, matching Java's MIME base64 decoder (what Salesforce uses). Fixes round-trips through urlDecode (which turns + into space under form-encoding) for ContentVersion upload flows and similar.Schema.getGlobalDescribe().get() is lenient about trailing whitespace in the lookup key, matching Salesforce. Fixes code that reads an SObject name from a Text custom field (not auto-trimmed) and passes it straight to getDescribe().Date and Datetime. Previously they fell through to the default branch and silently became null, which dropped $Record.SomeDate__c = Now writes from RecordBeforeSave flows.:sobj.Id falls back to the struct-level Id when the Fields map doesn't contain an "id" key. Some paths (notably Test.loadData) populate only the struct Id, so WHERE filters that bound :c.Id silently matched zero rows even when the record was present in the DB.__r returns null when the FK is null (instead of an empty SObject); EntityDefinition / FieldDefinition FKs resolve end-to-end; multi-line MDT SELECT parses cleanly; dynamic __mdt queries count toward Limits.getQueries(); SELECT COUNT() on __mdt returns Integer.AFTER-INSERT / AFTER-DELETE addError() now rolls the save back; partial-success Database.delete / update respects per-record addError; partial-success unique-constraint violations become failed SaveResults; heterogeneous List<SObject> updates dispatch per-type; Database.merge(master, Id) properly deletes the duplicate; required-field validation on custom __c insert; restricted picklist values enforced.DmlException preserves the original stack; trigger body pushes a stack frame so traces name the trigger; before-insert mutations to Name don't leak back to the caller's local; the Trigger.new copy barrier matches Salesforce.toString returning Apex null stringifies as "null"; exception toString matches the Type:[]: msg shape.Date / Datetime / Time casts throw TypeException for incompatible sources; Decimal preserves explicit scale from setScale; List and Set match instanceof Iterable<T>; super() chain terminates cleanly at built-in Exception; Type.forName for List / Set / Map and primitives; Set<T>.add / contains / remove honor user hashCode / equals; subclass static method doesn't shadow ancestor instance method; inner-class shadowing of standard SObjects is now scoped.deserialize handles Base64 fields, numeric-offset datetime formats, single-key SObject wrappers, and enum strings, and resolves user-class field types; Map.serialize preserves insertion order and original-case keys.System.schedule returns the CronTrigger Id (08e), not the AsyncApexJob Id, and rejects invalid day-of-month/week combos; SchedulableContext.getTriggerId wired up; EventBus.publish increments getDmlRows alongside getDmlStatements and stamps CreatedById / CreatedDate.LIMIT honored; subquery LIMIT :bindVar resolves at execute time; aggregate ORDER BY translates SOQL date functions (CALENDAR_YEAR, etc.); trailing AND / OR trimmed cleanly; LAST_N_* / NEXT_N_* / N_X_AGO use calendar boundaries instead of rolling windows; child subquery rows always include Id; parent-relationship SObjects carry QueriedFields from the SELECT, so untouched fields throw on access (matching real Salesforce); :true / :false / :null bind syntax resolves to literal constants.Decimal.valueOf(Integer) returns the value instead of 0.getPopulatedFieldsAsMap returns PascalCase keys.nimbus lsp starts a server every LSP-aware editor can launch — VSCode, JetBrains, Neovim, Zed, Helix, Emacs. 20 advertised capabilities: schema-backed completion (71+ real Account fields from .nimbus/schemas/, no org round-trip), live SOQL column validation, hover with mutation-score and flaky-rate overlays, signature help, go-to-definition / typeDefinition / implementation / declaration, find-all-references, rename, workspace symbol search, document symbols, document highlight, folding and selection ranges, semantic tokens, call hierarchy, code actions (SOQL typo auto-fix, generate test stub with schema-aware required-field constructors, extract-to-@TestSetup, add-@IsTest), inlay hints (coverage hit counts, surviving mutants, flaky warnings), code lenses (Run / Debug / Coverage / View Trace / Mutate), opinionated conservative formatter, 12 Apex snippets with Tab-through placeholders. See the Language Server page for per-editor setup.nimbus.lsp.enabled (default on). Disables the extension's legacy code-lens provider when LSP is active so Run Test / Debug / Coverage buttons don't double up.nimbus test: --dry-run / -n, --sandbox, --feature MultiCurrency,PersonAccounts, --faketime 2030-01-15T12:00:00Z, --timeout N, --readonly, --profile ci. All seven also available as nimbus.properties keys under nimbus.org.* and nimbus.test.* with CLI winning when both are set. --profile is a persistent root flag — activates Quarkus-style %ci.nimbus.foo=bar profile overrides for every subcommand in the same invocation.nimbus stub list / add / remove / path scaffolds plain-.cls stubs under stubs/ with --namespace and repeatable --method name:returnType options. Each generated stub exposes callCount and calls static counters so assertions are a one-liner.nimbus config show now loads on demand when invoked outside a test run instead of erroring configuration not loaded.
nimbus history / analytics / schema / trace) fall back to JSON / text when stdout is piped, so CI pipelines no longer crash on could not open a new TTY.
Decimal.valueOf(Integer) returns the value instead of 0. Three dispatch sites (type.go:118, methods.go:7110, methods.go:8287) now accept Integer, Decimal, String, Boolean, and Null operands. Integer.valueOf(Decimal) extended symmetrically.String x = ''; x == null now correctly returns false per the Apex spec — matched against real Salesforce behaviour. The DML round-trip behaviour (text fields stored as NULL) is preserved separately via dmlValueToInterface.:new Set<Id>{a.Id, b.Id} / :new List<Integer>{1, 2} bind variables now parse and evaluate inside SOQL literals. The previous SQLSTATE 42601 leak on WHERE Id IN :new Set<Id>{...} is gone; the parser's bind-variable scanner and a new resolveInlineNewBinds pre-pass rewrite inline constructors into literal IN lists before translation.<required>true</required> on custom __c fields is now enforced on DML insert. Skips fields with a DefaultValue, formula fields, and master-detail parents (handled elsewhere). Standard objects keep the existing business-field whitelist to avoid false positives on system fields like OwnerId.apexifySOQLError helper maps syntax error at or near …to
unexpected token: …, column does not existto
No such column on entity: …, and wraps everything in System.QueryException with a Salesforce-truthful message.GetTestMethods and the runner's cached registry now use methodDef.Name instead of the lowercased map key, so testReverse stops showing as testreverse.nimbus.test.parallel and profile overrides now actually work end-to-end. The --parallel flag defaulted to runtime.NumCPU(), which is non-zero, so every downstream caller thought it was explicitly set and the config value never got consulted. Fix: check cmd.Flags().Changed("parallel") before trusting the flag. Profile overrides (%ci.nimbus.test.parallel=1 + NIMBUS_PROFILE=ci) now visibly drop worker count.UserInfo.getLocale() / getLanguage() / getDefaultCurrency() and Organization.IsSandbox now read from the configured nimbus.org.* keys instead of being hardcoded to en_US / USD / false. UserInfo.isMultiCurrencyOrganization() observes the MultiCurrency feature toggle.Test.enableRecordTriggeredFlows path also works. (Historical note: 0.1.19 initially gated before-save $Record writes behind nimbus.compat.flow-record-sync; that flag was removed in 0.1.20 and the Salesforce-truthful ordering is now unconditional.)nimbus test and nimbus exec build every interpreter with the resolved OrgConfig installed, so currency / locale / sandbox / features / faketime / readonly propagate through triggers, async jobs, and @testSetup blocks uniformly.objects/Foo__c/fields/Bar__c.field-meta.xml (or a whole new Foo__c/ directory) while the daemon is running propagates to Postgres as CREATE TABLE / ALTER TABLE ADD COLUMN automatically — no nimbus sync required. Verified end-to-end with an editor-simulated live-add scenario.sfdx-project.json. Adding a new package directory (e.g. force-app-secondary/) is picked up on save: the new dir joins the watch set, its Apex files are pre-parsed, and the metadata provider is rebuilt..md-meta.xml changes and on new __mdt object definitions. New MDT types no longer require a daemon restart.nimbus test without the daemon now also reconciles the Postgres schema on every run via the same idempotent ApplySourceDelta. Users on the free tier get live field/object pickup too.force-app/main/default/objects/. Previously only terminal dirs with watched files were registered, so a newly-created child directory (objects/Foo__c/) fired no fsnotify event and the new object was invisible until daemon restart.mkdir -p are now watched unconditionally. Empty fields/ subdirs no longer slip through the selective startup scan and miss subsequent field-meta.xml creations.Text/Number fields flow into the interpreter's SchemaProvider without a re-sync, so insert new Foo__c(NewField__c = …) stops erroring with "Field not found in schema".parseCustomField with an empty file and dropping new fields from the scan.--seed-records, --seed-sites, --seed-list-views, --org-defaults, --default-record-types, --seed-record-excludes. Changing any of them correctly forces a re-seed instead of silently reusing stale rows from the previous run.EventBus.publish now routes against the correct governor counter based on the event's publishBehavior metadata. PublishImmediately events increment Limits.getPublishImmediateDml(); PublishAfterCommit events and generic List<SObject> payloads increment Limits.getDmlStatements(). Verified on a real org with a cross-matrix test.publishBehavior and eventType from platform-event object metadata are now parsed from object-meta.xml, round-tripped through .nimbus/schemas/*.json, and exposed via SchemaProvider.GetPublishBehavior. Requires a fresh nimbus sync.VariableTracker is now mutex-guarded. Timed-out test goroutines can no longer race the main goroutine reading GetExecutedLines, eliminating a class of "concurrent map iteration and map write" crashes near the end of long suites.Test.stopTest() now resets governor limits between queue-drain passes. A subscriber that re-publishes events when hitting the callout cap (the Onfido "retry-later" pattern) no longer loops forever — each pass models a separate async transaction with fresh limits, matching real Salesforce behaviour.nimbus sync now invalidates the daemon's "DB already seeded" flag so the next test run re-seeds standard profiles and the mock user. Previously the daemon kept the flag true, the post-sync test suite skipped seeding, and every @testSetup that touched Profile/User failed with Attempt to de-reference a null object: field Name.nimbus.seed.site.<Name>=<secureUrl> now land after ClearAllTables instead of before. The earlier race let TRUNCATE CASCADE wipe the sites a few ms after they were inserted.Name on the trigger copy no longer leak the mutation back to the caller's local variable. The copy barrier now matches Salesforce: auto-populated Names still propagate, but trigger-renamed Names do not.EventBus.publish. A List<SObject> generic list stays on the regular DML counter even when filled with PublishImmediately events.Identifier.Lower lazy cache is now stored via atomic.Pointer[string]. The previous plain-string assignment was not atomic (two-word header) and concurrent AST walks occasionally observed a torn value, crashing deep inside map access.Attachment.Name is no longer hidden by objectsWithoutNameField. Attachment has Name (the file name) in real Salesforce; hiding it broke every test that set Name on Attachment in @testSetup.stripInaccessible and SOQL user-mode access checks now treat Name as implicitly readable when the user has object-level read but no explicit FLS grant. The previous stricter rule over-stripped Name for standard users.lookupRecordById now quotes table and column identifiers. Tables whose API name is a PostgreSQL reserved word (e.g. User) no longer cause SELECT * FROM user WHERE id = $1 to resolve user as CURRENT_USER and abort the transaction with "column id does not exist".Insert and BatchInsert is now wrapped in savepoints. Under parallel workers, a lock-timed-out ALTER used to leave the transaction aborted and cascade failures deep in the trigger chain.externalStaticWrites) are now snapshotted to their initial values so cross-test reset restores the exact pre-test state instead of leaking mutations from previous tests.Dom.Document and Dom.XmlNode are now fully implemented with real XML parsing, serialization, attribute handling, and namespace support. Methods include load, getRootElement, createRootElement, toXmlString, and the full XmlNode API.Compression.compress and Compression.decompress for Zlib blob encoding.Flow.Interview.start() now actually executes the flow via the flow executor. Input variables (including Apex class instances) are preserved through the round-trip so getVariableValue() returns them correctly.transient keyword support: fields and properties marked transient are now excluded from JSON.serialize and JSON.serializePretty output, matching Salesforce behaviour.Assert.isInstanceOfType now checks implemented interfaces on ObjectValue, not just the inheritance chain. A class implementing System.Schedulable is now correctly recognised as an instance of it.autoproc@<OrgId>) is now seeded with no profile, matching Salesforce's system user.OrgLimits.getMap() registered as a builtin so it resolves via the standard static-method path.Database.query() now checks system object mocks (Organization, UserRole) before hitting the database.Name = 'X', bind variables (Name = :profileName), Name IN (...), and by-Id lookups. This eliminates stale isdeleted state from the daemon's cached database.Schema.SObjectType.isAccessible(), isCreateable(), isUpdateable(), and isDeletable() now delegate to CheckObjectPermission instead of always returning true.HttpResponse.getHeaderKeys() now returns keys in sorted order for deterministic output.isdeleted = FALSE on conflict, preventing stale soft-delete state.checkRestrictedProfile uses the seeded profile ID lookup table, avoiding raw SQL queries against a potentially stale profile table.List<SObject> instead of throwing "Insert requires SObject records".(?<!...), (?<=...)) and lookahead ((?!...), (?=...)) now compile and run. Go's stdlib regexp uses RE2 and rejects lookaround; nimbus now transparently falls back to a .NET-compatible engine when RE2 refuses the pattern. String.replaceAll, String.replaceFirst, and the full System.Pattern / System.Matcher API all route through the wrapper, so data-masking rules, SSN scrubbers, and anything else using advanced regex now behaves like real Apex.static { ... } block either directly writes another class's @TestVisible static field (MyService.client = new MockClient();) or triggers a side-effect via a helper method (MyService.useMocks();), nimbus records the resulting overrides and preserves them across the per-test-method reset. Previously the next access would lazily re-run the field initializer and silently swap the mock back to the production implementation, so any test that depended on the static-block mock would see real service behaviour and fail in surprising ways.TODAY, YESTERDAY, THIS_WEEK, THIS_MONTH, THIS_QUARTER, THIS_YEAR, and the LAST_/NEXT_ variants) are now translated as half-open ranges instead of point comparisons. A query like WHERE CreatedDate = THIS_WEEK used to generate createddate = date_trunc('week', NOW()), which only matched records created exactly at midnight Monday; it now generates a proper createddate >= start AND createddate < end range. Every comparison operator (=, !=, <, >, <=, >=) respects Salesforce's boundary semantics. Week ranges start on Sunday to match Date.toStartOfWeek() on US English locales.$1, $2, …) now work correctly when the capture group number is followed by literal letters or digits. A replacement like '$1XXX-XX-$4' used to expand to an empty string because Go's stdlib regexp greedily consumed 1XXX as a named group; nimbus now translates Java/Apex replacement syntax to Go's unambiguous ${N} form so SSN/credit-card formatters that embed literal text next to backref numbers produce the expected output.Matcher.replaceFirst() now expands group references in the replacement string. Previously it returned the replacement text verbatim without ever applying the captured groups, so a pattern (\d+) with replacement '[$1]' on the input 'abc 42 xyz' returned 'abc [$1] xyz' instead of 'abc [42] xyz'.SELECT or GROUP BY now translate to valid SQL. A query like SELECT LogEntry__r.Log__r.Status__c, COUNT(Id) FROM LogEntryTag__c GROUP BY LogEntry__r.Log__r.Status__c used to emit three separately-quoted identifiers ("logentry__r"."log__r"."status__c") which PostgreSQL rejected with "missing FROM-clause entry for table log__r". The aggregate translator now builds the chained JOIN alias the same way the regular translator does.ORDER BY LogEntry__r.Log__r.LogRetentionDate__c used to emit nonsense column references for anything past the first hop; the translator now walks the full dotted path exactly like the SELECT clause does.JSON.deserialize(..., Database.SaveResult.class) (and the UpsertResult, DeleteResult, UndeleteResult, Error variants) now reconstructs a typed object instead of a bare Map. Overload resolution against Database.Error parameters scored -1 when tests built mock results through serialize/deserialize round-trips; the typed path makes round-trip mocks behave the way integration code expects.Schema.LogEntryArchive__b-style Big Object custom objects are now loaded from project metadata. The schema scanner's allow-list filtered directories by suffix (__c, __mdt, __e) and silently dropped __b; Schema.MyBigObject__b.SObjectType.getDescribe().fields.getMap() used to return an empty map and cascade into null-deref chains. (Schema cache is keyed per project — a first run after upgrading will re-scan and pick them up.)ALL ROWS is now stripped during translation instead of forwarded to PostgreSQL as a literal identifier. Queries that wanted to include soft-deleted rows used to fail with syntax error near "ALL".SELECT and WHERE clauses that use the .Type discriminator on a polymorphic relationship (Owner.Type, What.Type, Who.Type, LinkedEntity.Type, RelatedTo.Type) now project NULL under an alias instead of trying to join against a non-existent type column on the target table. Full polymorphic dispatch still isn't implemented, but queries that reference .Type for logging or filtering now run instead of erroring.:getNamespacePrefix(), no receiver) now consume the trailing parentheses instead of leaving them dangling in the translated SQL. Previously the translator emitted nonsense like ... IS NULL() when the method returned null.Datetime.valueOf(Long) now constructs a datetime from a Unix-millisecond epoch, matching Salesforce's documented overload. Nimbus already handled the string overload; the Long overload silently returned null, so any code that round-tripped timestamps through Long.valueOf(epochString) ended up with null datetimes.SObject.getPopulatedFieldsAsMap() now excludes fields whose value is null. Apex's real behaviour returns only populated (explicitly-set) fields, so tests that iterate the map and skip based on containsKey now observe the same contract nimbus uses for serialization.Name field of type Text are now auto-populated with the record Id on insert when the caller leaves Name null or empty. Salesforce performs this auto-fill on text Name fields for Master-Detail/Lookup children so rec.Name is never bare null after insert; nimbus mirrors the behaviour.Formula slot as a summary:op:field string (historical hack), and the formula evaluator was parsing that as Apex, returning null, and silently clobbering the correct aggregate value that the rollup pipeline had just computed.Schema.DescribeFieldResult.getPrecision() and getScale() now read the field's declared metadata instead of returning hardcoded 18 / 2. Number fields with precision/scale populated via (3.14).setScale(fieldDescribe.getScale()) used to round to 2 digits regardless of the target column type.getLength() on any Id field now unconditionally returns 18 regardless of whether the containing SObject ships with a JSON describe. Mock-id generators that build ids as keyPrefix + zeros(length - prefix - suffix) + suffix used to produce 255-character strings for SObjects without a describe (CronTrigger, AsyncApexJob), overflowing downstream Text(18) fields.Schema.X prefixed references (e.g. Schema.AuthSession, Schema.LoginHistory) now resolve to the real standard SObject even when a project has an inner class with the same short name. The previous shadow-check short-circuit picked the inner class, so Schema.AuthSession.SObjectType.getDescribe() failed with "Method AuthSession.getDescribe not found" on any codebase that ships a fixture called AuthSession.Test.setMock(HttpCalloutMock.class, mock) and Test.setMock(System.HttpCalloutMock.class, mock) now land under the same registration key. Previously the qualified form was stored under system.httpcalloutmock while Http.send looked up httpcalloutmock, so fully-qualified registration silently did nothing.WHERE ... IN (SELECT ...) subqueries now keep only the first projected column. The translator's JOIN FK / parent-id auto-injection produced multi-column subqueries that PostgreSQL rejected; the new keepFirstSelectColumn rewrite fixes fflib selector patterns that embed subqueries.System.ParentJobResult.SUCCESS (and the other enum members) now resolve correctly when the project also defines a class named ParentJobResult. The enum path now accepts both the System.-qualified form and the bare form after a namespace strip.System.Flow is now recognised as a system class, so Flow.Interview and related APIs no longer fall through the auto-null stub path. Also added a generic TypeValue method dispatch so registered builtins under keyed names (Flow.Interview.createInterview, Cache.Org.getPartition, …) are found without an inline dispatcher stanza.ApexEmailNotification standard schema so SOQL queries that reference it no longer fail with "relation does not exist".System.LoggingLevel ordinals now match Salesforce's declaration order. The values are NONE, INTERNAL, FINEST, FINER, FINE, DEBUG, INFO, WARN, ERROR with ERROR as the highest (most severe, least verbose). Any logging framework that compares entries against the user's configured threshold via entry.ordinal() >= user.ordinal() now filters correctly instead of logging everything regardless of the threshold.switch on null now correctly matches only a literal when null clause. Previously nimbus tried to evaluate every when label as an identifier even when the switch value was null, so a statement like switch on partitionType { when ORGANIZATION ... when SESSION ... } would throw Unknown identifier: SESSION whenever partitionType was null (e.g. from a mock delegate calling super(null, null)).SELECT Id, (\n SELECT Id FROM Children__r\n ) FROM Parent__c used to silently drop the subquery, leaving the relationship field unpopulated and any parent.Children__r.size() call failing with Unknown method size on SObject Unknown.handler.execute(this.input.triggerNewMap) against overloads execute(List<SObject>) and execute(Map<Id, SObject>) now picks the map overload when triggerNewMap is null, instead of silently routing the call to the list overload and cascading into a null dereference.MyClass.MyEnum) now support valueOf(String) in addition to values(), including the standard NoSuchElementException on unknown names.OuterA.Mock that extends OuterB.Base is now recognised as a valid argument for a parameter typed Base, without the broader subclass-matching widening that caused regressions in earlier attempts.String conditionsLogic with the same name as a private String conditionsLogic field was silently writing to the field — the local was always returned empty. Apex scoping rules say the local wins, and nimbus now respects that.Schema.DescribeFieldResult.getSoapType() returns the actual SOAP type of the field instead of hardcoded STRING. Field-type-driven comparators (e.g. choosing string vs decimal vs datetime comparison) now work on non-string fields.Schema.DescribeFieldResult.getLength(), getPrecision(), and getScale() are now dispatched against the field's actual metadata instead of returning a single hardcoded default. Long text fields no longer get silently truncated because getLength() returned 255 for everything.Cache.Visibility.NAMESPACE, ALL, and SESSION are now proper enum values. Calls to partition-delegate put(...) methods that accept a Cache.Visibility parameter now match the intended overload instead of failing with "No matching overload".System.OrgLimits.getMap() now returns a map of stub System.OrgLimit objects so tests that iterate over governor limits run instead of throwing Method OrgLimits.getMap not found.Type.forName(String) and Type.newInstance() now prefer a real SObject type over any SFDX-generated stub class of the same name. Tests that did Type.forName('Account').newInstance() used to silently return an empty ObjectValue for the Account shadow class.Schema.X (and vice versa). Nimbus already treated Schema.Account and Account as equivalent for type references; overload resolution now also honours that equivalence, fixing dispatch for framework methods declared as foo(Schema.User u).bigint in the local schema) now map to the Double display type and the Double SOAP type. Tests that populate numeric fields via reflection no longer end up with the field name as a string in the column."0", so datetime rollups no longer throw "date/time field value out of range" when no child records exist.AuthSession, LoginHistory, and Network, so projects that query session/network metadata during test setup no longer hit "table does not exist".System.EventBus.publish() now dispatches correctly when called on a TypeValue reference. Publishing a platform event from a helper that resolves System.EventBus dynamically used to miss the method.this. The inside-getter raw-value short-circuit now distinguishes instance-field reads from StaticVars reads and correctly returns the freshly-assigned value.Limits.getDMLStatements() now reflects DML done through Database.insert, Database.update, Database.delete, and Database.upsert. The counter used to only track the bare-statement form (insert records;), so libraries that route every write through Database.* method calls — fflib unit-of-work patterns, builder libraries — saw getDMLStatements() return 0 after a commit and any assertion against the counter failed.00e000000000001AAA with the default name "Standard User" and seed the standard "Standard User" profile at 00e000000000003AAA, so queries like SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1 returned two rows and threw "List has more than 1 row for assignment to SObject". The mock user now points at whichever seeded standard profile matches its configured name; only custom names create a bespoke row. UserInfo.getProfileId() looks up the mock user's profile from the DB instead of returning a stale hard-coded id.-p worker counts no longer get slower than lower ones. Running with -p 50 on a large suite used to be slower than -p 4 because workers serialized on shared locks during result reporting and metadata lookups. The result-reporting path and the metadata provider are now structured so workers can actually run in parallel.UserInfo.getUserId() fallbacks — are now cached per test class and invalidated on DML against the same row. Wall-clock savings depend on your DB backend: on the embedded unix-socket postgres each round-trip is already cheap, but CI runners, Docker-on-Mac, and remote postgres setups should see measurable run-time drops.Owner:User.FirstName & " " & Owner:User.LastName (or anything richer than a single Rel:Type.Field reference) no longer fall into a fast-path that extracted nonsense table names and fired silently-failing SELECTs against imaginary tables. These formulas now route to the normal formula evaluator and return correct values.~/.nimbus/pg/: ~55 MB (down from 132 MB per-project).lib/postgresql/ during startup and catalog initialization — fewer plugins, less work.lib/pq to pgx/v5. Binary protocol, automatic prepared statement caching, and active maintenance.UNLOGGED tables, autovacuum=off, jit=off, larger shared_buffers / work_mem, and several activity-tracking knobs disabled. Cumulative speedup of 10–20% on a 2,300-test production suite.curl https://install.testnimbus.dev | sh installs the latest binary without Homebrew.ORDER BY clause now default to ORDER BY id, ensuring consistent, deterministic result ordering across all test runs.v prefix.nimbus daemon) is now available on the free tier. Previously gated behind Pro to enable VSCode extension for free users.nimbus/db/postgres.log instead of being written to stdout, eliminating log noise in terminal output.AccountShare, LeadShare, OpportunityShare) are now included in schema auto-sync.--workers 1 on every run.Trigger.new, Trigger.old, Trigger.newMap, Trigger.oldMap, and all boolean flags.nimbus daemon start) for warm-start performance — avoids full cold-start on every run.nimbus dev) — browser-based dashboard with live test results, schema explorer, and anonymous Apex execution.nimbus test:watch) — re-runs affected tests on file save.