Every release, every fix. Nimbus is in active development — this page tracks every change since the first public release.
nimbus bisect — find the commit that broke a test. Give it a failing test and a known-good commit and it walks the history, running the test at each candidate, and names the culprit along with the command to reproduce it. Every candidate runs in a temporary worktree with its own database, so your checkout never moves and nothing you have open is disturbed; commits from before the test existed are skipped rather than counted as passes. --json keeps stdout clean for CI.nimbus fuzz — property-based testing for triggers. Generates records that are valid according to your real describes — required fields, restricted picklists, lengths, precision, one level of required parents — salted with the values that break things: nulls, boundaries, unicode. When something breaks it shrinks the input to the smallest case that still fails and prints it as paste-ready Apex. A validation rule rejecting bad data counts as the object working, not as a find. --seed replays a run byte-for-byte. Around 4 ms an iteration, which is why this can exist locally and cannot exist against an org.nimbus compare — run the same Apex or SOQL here and in an org, and see what differs. Results are compared structurally, and the comparison tells you what it excluded rather than pretending to compare it: record Ids are never comparable across sides, nor are audit stamps, nor row order when no ORDER BY makes it contractual. Differences it can explain it explains; differences it cannot are reported as unknown instead of guessed. Equal rows with differing cells are never called an engine bug on results alone.nimbus budget init and nimbus budget check — governor budgets that fail CI before they fail in production. init measures a suite's per-test governor usage and writes nimbus.budgets.json with headroom; check re-runs and fails with the test, counter, usage, budget and overshoot named — plus which budget entry governed, since resolution is per-counter with exact names beating wildcards beating defaults. CPU budgets are checkable in CI at all because nimbus counts operations rather than wall time. The gate is honest about the platform's blind spot: Test.startTest resets the counters, and both commands say so rather than quietly passing a test that measured nothing..flow-meta.xml and execution stops before that element runs. Paused, you see the flow frame, $Record and $Record__Prior, the flow's variables, and the element's type, label and next connector. Step lands on the branch a decision actually took, step-in enters an Apex action, and a test's own insert stops mid-pipeline inside the flow it fires. Flow node coverage is recorded per run.getMessage() is byte-identical to before. It appears in the runner's failure block, in nimbus explain, and as a trace event.nimbus dap is now a documented command, and it has grown up: watches and a debug console that run full Apex — method calls, SOQL, DML — against the hermetic local database, which no org-backed debugger can offer; variable editing that accepts whole expressions and writes into locals, list elements, map entries and SObject fields; conditional, hit-count, logpoint and exception breakpoints that behave as documented; a pause request that works; per-frame Locals, Arguments, this and Statics instead of one flattened bag; and Step Back over a recorded trace. Large collections cost nothing until you expand them.insert → before trigger → its query, with the after trigger and flows as siblings. Each span carries the governor resources it consumed as deltas — SOQL, query rows, DML statements, DML rows, CPU — so a trace answers which step spent the limit, not just the test total. Updates additionally carry a field-level before/after diff per record, with bookkeeping fields excluded so the fields your code changed are not buried.put(), metadata defaults applied at insert, flow write-backs, roll-up recomputes (carrying the definition that produced them), formula resolves inside the save pipeline, and platform stamps — each with the record's identity, the field, the old and new values, the kind of writer and its source location, in a per-test total order. A record keeps one identity from the caller's constructor through a before-trigger's overwrite to the save, so the whole story of a value reads in sequence.nimbus app is now daemon-managed: it boots a bundle's dev server as a supervised child, binds the port before configuring it so the address it reports is real, treats a clean exit before ready as the failure it is, and sweeps every server on shutdown. LWC bundles and Visualforce pages are both previewable as targets, and every API call the running app makes — method, path, status, duration — streams out live.GET /services/apexrest/Ping ran PingResource.doGet, which called PingHelper.compute rather than two unrelated views of the same second. The request feed carries trace and span ids so a client can follow one to the other. Off by default; untraced requests allocate nothing.OtherClass. or someVar. where the type lives in another file completed nothing — completion never consulted the workspace index that go-to-definition already used. It now offers statics on type receivers and instance members on typed variables, with same-file names shadowing workspace names, and respects Apex visibility including @TestVisible inside test classes.nimbus validate already runs now publishes through the language server as warnings. Rules whose verdict needs types from outside the file are excluded — they produce tens of thousands of false rejects without a full registry — while the file-local rules score zero false positives on every deployable project tested, which is what makes them safe on by default. Toggle with nimbus.diagnostics.sema.Network.getNetworkId() returns null outside a community context, which is what the platform returns. It was returning an empty string — harmless until 1.25.0 began validating Id assignments, at which point ordinary Id networkId = Network.getNetworkId(); failed and took down every test that touched the class holding it. #458Id.valueOf checks the 18-character checksum only from API 54.0 onward. The check is version-gated on the platform, and applying it to every class broke code compiled before the cutoff. Verified by deploying one probe class per API version: the behaviour flips at exactly 53 → 54. #429RecordTypeId describes as a reference, with the platform's label, and appears in the object's field list — on the objects that actually have record types, and absent on the ones that do not. It was describing as a string with a generated label. #432f(Map<Id,Object>) and f(Map<String,Object>) resolved every call to whichever was declared first; the platform resolves the pair by the key's declared type.stub pull records what the org actually holds. A relationship to another custom metadata record is recorded by the target's developer name rather than its Id, so traversals resolve (#437); recorded numbers are written as literals Apex can parse instead of scientific notation (#438); a dotted receiver in your own code is confirmed to be a namespace before it is queried (#439); and --namespaces "" no longer quietly becomes an org-wide pull (#441).Test.startTest. The platform resets the counters around that block, and nimbus reproduces that faithfully — which meant a reading taken at the end of a test reported zero for the many tests whose whole body sits inside it. Usage is now tracked as a high-water mark, so budgets and reports see what the test really spent.nimbus config properties and nimbus doctor agree with the code. Thirteen real configuration keys were missing from the registry, so they never appeared in the listing or the scaffolded example file; doctor warned about working per-profile release keys; and two documented defaults named settings that do not exist.@isTest — previously it saw none of the seeded data or stub namespaces a test sees.!= null before indexing depends on it. If you have code that iterates the result without a null check, it was relying on nimbus being more forgiving than the org and will now need the same guard it needs in production. Reading a relationship the query never selected raises SObjectException for the same reason — though only for relationships nimbus can describe, so an unrecognised name stays permissive rather than guessing.DmlException led with the wrong message. Assertions written against the first error will now see the error your code added first.Id variable now fails. Id x = 'foo'; was silently accepted, while the equivalent cast and Id.valueOf both rejected it. The declared-type assignment was doing half the work — expanding a 15-character Id to 18 — and skipping the validation.STRING_TOO_LONG with the field label and maximum length, matching the platform. Validation runs before your before-save logic, so a trigger cannot rescue an over-long value by trimming it — the save is refused first, exactly as it is on the org.System.runAs. Public Read/Write grants read and edit but not delete; delete needs ownership. This applies only in user mode — an explicit system-mode or without sharing operation is unaffected.Datetime.format() renders in the running user's timezone. It rendered in GMT, so it disagreed with hour() and date() on the same value.equals(), not just hashCode(). A class whose hashCode() keys on fewer fields than equals() compares — the correct way to write the pair — had distinct instances silently collapse into one entry.IsWon, IsClosed, Probability and ForecastCategory in step with a stage change, so a single-record update was right and a bulk update was stale — invisible unless you compared the two.Database.upsert(records, false) saves the rows that are valid. A partial-success upsert failed the whole batch and reported the first row's error against every record, so nothing was saved where the platform saves everything that passes.WHERE Name LIKE :prefix + '%' reached the database as unfinished SQL and failed outright.addError keeps your message. addError(message, false) read the escape flag as the message, so the error surfaced as the word "false" with your real text recorded as a field name.name() returned whatever casing the caller typed, so a value written into a record and compared against the declared set never matched — and a Map keyed by an enum disagreed with == about its own keys.expr0, so grouped results after the first came back under the wrong key.Task.IsClosed follows its status, and roll-ups grouped on it stop reporting zero.Double.valueOf returns a Double regardless of where the result is used.ApexPages messages behave. addMessages records at error severity, hasMessages(severity) filters by the severity you pass, errors added outside a trigger reach the page, and StandardController.view() returns a page reference instead of null.Schema.DescribeFieldResult.getLength() reports a text area's real length instead of a long text area's.COUNT() over profiles counts matching rows instead of returning zero.nimbus triage reproduces your test run. It built its own configuration, so it could report failures your actual run does not have.BR() renders per context — a line break inside a stored formula field, a newline at runtime.TypeException instead of failing later in a way no catch could reach.nimbus.mock.user-profile in nimbus.properties to pin the old behaviour. Permissions inside System.runAs are unaffected.getInlineHelpText() always returned null, even though the text ships in both field metadata and the bundled describes. Anything that maps or documents fields through help text — a pattern several managed packages rely on for their own configuration — read nothing and silently did nothing, with no error to show for it.__r inside such a subquery, and an unset checkbox on a child row, are fixed alongside it.Database.LeadConvert was missing most of its accessors, converting a lead did not copy the lead's address onto a contact that had none, and no primary OpportunityContactRole was created. The mapping fills blank contact fields only and leaves values that are already set alone, matching the platform.TODAY, LAST_N_DAYS and the rest were resolved against the database's clock rather than the one Date.today() reads. The two agree for most of the day and disagree either side of midnight in timezones ahead of UTC, so a suite that passed all afternoon could fail overnight for no reason anyone could reproduce the next morning. --faketime now reaches them too.HAVING comparisons against null match the right rows. HAVING SomeField != null matched nothing at all, so aggregate queries written that way came back empty and any roll-up built on one computed zero.Set.contains and Map.get missed it.getPopulatedFieldsAsMap() included a relationship the query never traversed, which broke code that inspects a record's populated fields and looks each one up.getFields() also returned the fields merely available to add to the set.ORDER BY on text failed outright instead of falling back to a simpler sort order.Organization and PermissionSetAssignment can be queried. Both were refused for users without administrative setup access, where the platform allows an ordinary read.nimbus record sets up managed-package record/replay in one command. Recording answers calls into stub classes, so a first recording took two commands in a particular order — pull the package's stubs from an org, then run the tests with recording on — and running the second one on its own recorded nothing. nimbus record -o <alias> [pattern] now does both against one org, after which plain nimbus test replays offline with no flag and no org connection. Both halves stay available separately: nimbus stub pull when you want the package's shape without running anything, nimbus test --record to re-record once stubs exist.--target-org is accepted on every command. It is the Salesforce CLI's spelling for what -o and --org already named, and all three are now one flag throughout, so a script written against either tool reads the same and runs unchanged.-o on a deploy or release command is no longer ignored. Those commands read the target org from --target-org only, while -o and --org were accepted on the command line without complaint and then had no effect — so nimbus deploy -o staging could deploy to whichever org a release profile or your Salesforce CLI default named, rather than the one you typed, with nothing in the output to say a different org had been chosen. All three spellings now resolve to the same org on every command. --target-org behaves exactly as it always did.nimbus test --record says when there is nothing for it to record. Recording answers calls into stub classes, so on a project that has none the run finished clean, reported nothing unusual, and left an empty recordings directory behind. It now says so before the tests start and names the command that pulls the stubs first.nimbus sync -o <alias> reads the org again on a current Salesforce CLI. Recent CLI versions redact the access token from sf org display, handing back a placeholder sentence instead. Nimbus sent that placeholder to the org, got a 401, and reported it as a rejected session — advice to log in again that could not possibly help, since the redaction has nothing to do with your session. The effect was that the org describe augmentation silently never ran: standard objects came from the bundle and every feature-gated field your org has stayed missing. Nimbus now asks the command the CLI names, and accepts both payload shapes that command has used across versions. An older CLI does not redact and is unaffected. #442WHERE Name = :(value) matched nothing, and IN :(new List<String>{ … }) failed with an internal name that should never have been visible. :(...) is ambiguous — :(Set<Id>) ids is a cast applied to an operand, :(ids) is the operand in parentheses — and nimbus read every one of them as a cast, so the parenthesised form bound nothing at all. They are now told apart by whether an operand follows the closing paren. Casts are unchanged. #444nimbus validate no longer reports warnings that depend on formatting. A comment inside a SOQL literal became part of the next column name, so the warning named a column that appears nowhere in the file — with the comment text and an escaped newline in it, and the line number pointing at the SELECT rather than the comment. Block comments did it too. Separately, a child subquery whose opening parenthesis sat on its own line had its relationship name reported as an unknown SObject, while the same query written on one line validated clean. Both queries ran correctly throughout, so this was the validator disagreeing with the runtime in the same project. #448 #449nimbus sync -o <alias> against an org with a managed package installed, insert new Account(Name = 'x') failed with REQUIRED_FIELD_MISSING naming every packaged Checkbox the org happens to have. A Checkbox is never nillable — a Boolean is always true or false — so the platform expresses "the caller need not supply this" through its defaulted on create answer instead, and nimbus was not carrying that answer: it was dropped when the schema was written to the project cache, and the runtime then fell back to the shipped describes, which cannot know about a field that exists only on your org. Affected every insert of a standard object the package extends, not only the fields it named. Re-syncing after upgrading is not required — the cache regenerates itself — but nimbus sync -o <alias> settles it if anything looks stale. #447nimbus test --record -o <alias> now forwards static calls on stub-loaded classes to an org that has the package installed, once, and writes what came back to .nimbus/recordings/ — one plain-JSON file per test method, meant to be committed so the rest of the team and CI run against the same captured answers with no org access at all. Later runs replay them with no flag and no org connection; --no-replay ignores them and runs the stub bodies. Anything that can't be answered falls back to the stub body rather than failing — a call that was never recorded, an argument with no JSON form, a package that has been upgraded since — so adding recordings to a suite cannot break it. Static methods only, since a local stub instance has no counterpart in the org to forward to; callouts stay out, because the platform refuses one in a test without Test.setMock and a deployable test therefore already ships its own mocks.nimbus stub pull generates package classes and records their constants. Previously it fetched fields, objects and labels; it now also writes stubs/<namespace>/<Class>.cls for the classes a package exposes to subscriber code — the real method signatures, constructors and enums — so builder chains construct and calls resolve instead of warning. Global constants are recorded with their actual values from the org. --data additionally captures custom-setting rows and custom-metadata records, which land as ordinary seed data your tests can query.System.Location with its latitude and longitude components, and DISTANCE() and GEOLOCATION() translate in both WHERE and ORDER BY — so proximity queries return the rows the platform returns, in the same order, rather than failing to translate. #161Map<Id, SObject> populated from a query and then read with an Id obtained some other way returned null, and the record looked absent rather than differently spelled. #427 #384String.valueOf renders from the argument's declared type, not the value it happens to hold. A Date held in a variable declared Object rendered with a time component, and the same mismatch ran through nested maps, sets and SObjects — so a serialized payload differed from the platform's in a way that only surfaced downstream, in a comparison or an assertion far from the call. #422 #426__r relationship was left stale after a bulk write, so the child carried a value computed from data that had since changed. #431Database.delete(Id), Database.delete(List<Id>) and Database.undelete(List<Id>) were unsupported for every object and failed outright where the platform accepts them; they now resolve the record and run the ordinary DML path, with the same triggers, limits and sharing treatment as any other caller. On top of that, a share record now describes with no key prefix and as not undeletable, its Id falls in the domain every share table shares, and a share inserted without one reads back RowCause = 'Manual' — all as the platform does. #193Schema.MyObject__Share.RowCause.MyReason__c evaluated to null, so any assertion comparing a queried RowCause against a sharing reason was comparing null to null and passing without testing anything. Both share forms now resolve, with or without the Schema. prefix; Schema.AccountShare.RowCause on its own remains the field token it has always been. #193nimbus stub pull fetches them from an org that has the package installed. System.Label.ns.LabelName previously warned that a label named after the namespace was missing and handed back nothing, so code guarded by a packaged label silently took the wrong branch — most visibly leaving a required field unset and failing rooms away from the cause. Labels now load from a stubs/labels/ directory alongside your project's own: CustomLabels.labels-meta.xml for ordinary labels, and <namespace>.labels-meta.xml where the file name declares the namespace. nimbus stub pull --org <alias> writes those files for you, along with the package's fields and any objects your project references but doesn't define — the org where you hit a managed-package gap is by definition an org that has the package. nimbus stub label scaffolds one by hand. Your own labels always win; a label that resolves nowhere still returns empty with a warning naming what to add, rather than failing the run. #406ApexPages.StandardSetController supports pagination. The controller constructed and then did almost nothing: setPageSize, setPageNumber, next, previous, first and last didn't exist and aborted the run, six getters silently returned null, and getRecords() handed back the QueryLocator it was built from. All of it now behaves as the platform does, verified against a live org: a fresh controller reports page 1 of 20 over the full result size, setPageSize returns to page one, next() past the last page is a no-op rather than an error. Note that getRecords() now returns the current page rather than the whole set, which is the platform's behaviour. #416WHERE Id = :fifteenCharId returned zero rows — bound, inlined as a literal, or inside an IN list — where the platform treats the two spellings as the same record. The common way to meet this is ApexPages.StandardController.getId(), which correctly returns 15 characters, so the ordinary Visualforce constructor idiom [SELECT … WHERE Id = :controller.getId()] loaded nothing and failed a frame or two later. Both spellings are now matched at the predicate itself, so nothing about how Ids are stored or compared elsewhere changes. Id-typed == between the two spellings is still being worked on separately. #417MALFORMED_ID, and a well-formed RecordTypeId naming a record type that doesn't exist fails with INVALID_CROSS_REFERENCE_KEY — both matching the platform's wording. Id.valueOf validates the checksum too; assignment and the (Id) cast deliberately do not, because the platform doesn't either. The record-type check only applies where nimbus actually holds record-type data, so a project that doesn't model them is unaffected. #429Map<Integer, V> and Map<Decimal, V> keys keep their type. Built as a literal, such a map handed back String keys. Indexing a list with one threw Index operator not supported; worse, three consequences were silent — list.get(key) returned null, sum += key + 1 concatenated to '041' instead of adding to 5, and sorting the keys ordered 10 before 2. Map<Id, V> is unaffected: its keys are strings on the platform too. #411FROM. Dynamic SOQL assembled by concatenation often produces …ORDER BY CreatedDate ASC)FROM Account…. SOQL is tokenised rather than whitespace-delimited, so the platform reads that fine; nimbus took the subquery's FROM for the query's own and then rejected every field the query selected, blaming a field on the wrong object. #430COUNT_DISTINCT, and toLabel() all translate. WHERE Account.Name = 'x' AND CALENDAR_YEAR(CloseDate) = 2019 emitted invalid SQL and surfaced a raw database error; COUNT_DISTINCT wasn't treated as an aggregate when it stood alone or appeared in ORDER BY; and toLabel() only worked in its barest form, failing with a mangled entity name when given a field alias, a parent field, or a place in WHERE. #412 #413 #414System.-qualified spelling. System.Crypto.getRandomInteger() reported an unknown method while the bare spelling worked — and several others failed silently, with System.Date.today(), System.Datetime.now(), System.String.join() and System.Schema.getGlobalDescribe() returning null or empty. Both spellings are legal Apex, and the qualified one is the habit in namespaced codebases. #418Schema. prefix. A List<Schema.FieldSetMember> argument didn't match a List<FieldSetMember> parameter, and rather than failing, resolution quietly fell through to a List<String> overload — so field set members were added to a set of strings and the query built from them came out empty. #428JSON.deserialize accepts ISO date/times without a zone. 2026-05-13T00:00:00.000, the same without milliseconds, and a bare date were all rejected where the platform reads a missing offset as GMT. This bites hardest on the common idiom of seeding a normally read-only audit field, where the throw lands on the deserialize rather than on the field access that motivated it. #415ALL ROWS rather than the text of the WHERE clause. A subquery whose filter merely mentioned IsDeleted lost the guard entirely and returned deleted rows. #409WHERE is parenthesised before nimbus's guards are added to it. An OR at the top level of a filter could return soft-deleted rows through its non-final arms. #403merge reparents related records through relationship fields only. Merging two records updated every column in the database that happened to hold the duplicate's Id — including plain text fields. A Description that contained the merged-out record's Id came back rewritten to the master's Id, where the platform leaves it untouched: reparenting is driven by field metadata, not by matching values. Org-verified on both directions — the child lookup moves to the master, the text field keeps the old Id. Lookups whose targets don't include the merged object's type, and external-Id lookups (which describe as references but hold external keys, never Ids), are left alone. #400DescribeFieldResult.isPermissionable() answers the platform's flag instead of a blanket true. Every field claimed to be permissionable, so code enumerating FLS-manageable fields saw system and audit fields it can't actually manage. The answer now comes from the describe's own flag, measured 99.9% accurate against a live org; fields the platform never permissions — OwnerId, RecordTypeId, LastActivityDate, and IsDeleted — answer false even on custom objects, where no bundled describe exists to say so. #377with sharing, a user's view of an Attachment or Note is decided by whether they can see the specific parent record — ownership and an explicit share are equally good, and object read on the parent is a separate, additional requirement. Nimbus either showed all of them or none. The rules were org-verified against a Private org-wide default, including the case the platform decides on record access a share row grants to a user who still lacks object read: hidden. Custom-object parents participate for the first time, whatever their sharing model. #393 #171NOT placement rules are enforced, and every negation tolerates nulls. The platform refuses WHERE A AND NOT (B) with a parse error but accepts a leading NOT (…) and NOT before a bare comparison; nimbus accepted all of them, so a query that throws QueryException in an org ran locally. Every condition site is now validated — WHERE, HAVING, relationship subqueries, semi-join bodies (where a leading NOT is legal and stays legal), dynamic queries, aggregates, and custom-metadata queries — and each negation matches rows whose field is null exactly as the platform does, including a leading EXCLUDES followed by further conditions. Apex line comments inside a query string no longer confuse the validator. #392merge issued one UPDATE per column per table in the schema. The largest suite in our open-source compatibility corpus (3,986 tests) went from 98s to 31s wall on 8 workers with a byte-identical failing set; its slowest test — a three-way contact merge — from 38.7s to 3.0s; a DML-library suite from 4.0s to 0.8s. The inference also now draws on the whole project's code deterministically rather than on whichever classes a test happened to load, which flips two relationship-package tests in that corpus suite from failing to passing.nimbus sync is 0.5–1s faster in projects with no configured org. Every sync spawned sf org display — a Node process — to ask about an org alias nimbus itself had already determined doesn't exist. The probe now only runs when an org is actually configured or requested; what sync writes is byte-identical. Test startup also drops ~20% on small projects: two passes over the embedded describe bundle were computing two views of the same data and now share one parallel pass.System.runAs enforces the running user's object permissions at API 67. Suites that write records as a restricted user will newly fail — that failure is the platform's answer. Summer '26 defaults database operations to user mode, with as system (or AccessLevel.SYSTEM_MODE) as the opt-out. Nimbus enforced user mode only when it was asked for explicitly, so every System.runAs block that wrote a record succeeded locally whatever the running user could actually do — the widest false pass in the tracker. All six statement verbs and the bare Database.* calls now raise System.SecurityException; the partial-success Database calls do not throw and instead fail every row. The API version that decides this is the one on the code unit containing the statement, so an API-66 class still writes when called from API-67 code. Explicit as user is unchanged at every API version, as system waives it, and outside System.runAs nothing changes. The mixed-DML idiom System.runAs(new User(Id = UserInfo.getUserId())) keeps full access. Field-level security under user mode and read CRUD on SOQL are deliberately still unenforced. #388 #168WHERE clause follows SOQL's null rules. (SELECT Id FROM Contacts WHERE Email != null) matched no child rows — an empty list rather than an error, so code skipped work and returned empty payloads while the parent query looked perfectly healthy. The gap was wider than the null comparison it was reported as: inside a subquery, != and NOT IN dropped rows whose field is null where the platform matches them, < and <= did the same because nulls sort before any value, and only TODAY, YESTERDAY and TOMORROW were understood — every other date literal matched nothing. Thirteen predicate forms were affected in all. A flat query, an unfiltered subquery, IN lists and case-insensitive text comparison were correct throughout and still are. #391System.SecurityException; the object visible but the verb not granted raises a per-verb shape that is never SecurityException — System.TypeException (DML operation INSERT not allowed on Account) for insert, update and delete, and distinct DmlException messages for upsert, undelete and each half of merge. A suite asserting on the refusal saw one shape where an org gives two. The shape also depends on the operand: an untyped List<SObject> defers the entity check for exactly undelete and upsert, so those two surface on record visibility and on the field pre-check instead. Two further divergences went with it — Database.<verb>(records, false, AccessLevel.USER_MODE) threw where the platform fails each row and returns results, and Database.undelete under user mode checked the wrong permission and invented its message. #390undelete under user mode is gated on the delete permission. It was checking a permission of its own that no user carries, so an undelete the platform allows was refused locally for any user carrying permission-set data — a false failure in the expensive direction. A user with full object CRUD undeletes under default mode and undelete as user alike; read, create and edit without delete is refused. #388SOQL time inflated far more than DML time under -p 8. Each statement opened its own PostgreSQL savepoint, and past 64 subtransactions a backend forces every other backend's visibility checks onto a shared cache: a read-path cost, which is why queries paid it and writes did not. Statements past a threshold now share a span-scoped checkpoint with a journal that restores the exact prior state when one fails, so a caught DmlException, a rolled-back savepoint and a partial-success call all behave exactly as before. On an 8-worker reproduction issuing 800 statements per test, parallel query time fell from 5.8–7.4s to 4.2–4.6s and serial write time by about 30%; suites below the threshold are untouched. #9allOrNone DML failure reports every failing row, not just the first. The platform validates all rows before throwing, and the exception carries each one: getNumDml() counts them and getDmlIndex(i), getDmlMessage(i), getDmlFieldNames(i) and getDmlType(i) answer per row. Nimbus stopped at the first failure, so a caller looping the accessors saw one problem, fixed it, and met the next on the following run. The per-row detail is genuinely per row — two rows missing different required fields report different field lists. The message itself is unchanged and still names only the first failing row, and a statement with a single failing row reports exactly what it did before. Failures raised during the write or trigger phase still surface the first row alone. #366nimbus sync -o <alias> reports what it read and added, or names the reason it fell back — so a sync that quietly used bundled describes is no longer indistinguishable from one that did not. #302String.valueOf(record) omits nested records, and record identity stops following the rendered text. A queried parent, an assigned parent and a child subquery were all printed; the platform prints none of them, while keeping them reachable and in getPopulatedFieldsAsMap(). This reaches past logging: library code builds cache and de-duplication keys from the rendered string, so an extra entry silently changed identity. Correcting it exposed a second problem underneath — a list took its identity from how its elements print, while a record's own hashCode() used its fields. The platform keeps the two apart: assigning a parent changes a list's hash even though the parent is never rendered. Both now use one field-based implementation. #379Type predicate filters whatever it is compared against. WHERE What.Type = :someType and WHERE What.Type IN :types matched nothing — a filter that silently returns zero rows, so a selector processed no records and looked like it simply had no work. Only string literals filtered. Negation was wrong in the other direction: != dropped rows whose lookup is null and NOT IN matched everything, where the platform includes null-lookup rows in both. #362ReplayId, EventUuid, CreatedById and CreatedDate — and nothing else. Nimbus injected its standard system-field set onto __e objects, so fields.getMap() answered for nine fields an event does not have, including Id and Name, while missing ReplayId and EventUuid entirely. Code that read them worked locally and failed in an org. Event publishing and delivery are unchanged. #364nimbus sync without an org no longer warns about one. A project with no default target-org resolved a placeholder alias, and the new sync diagnostics reported that its session could not be obtained — an org the user had never named. nimbus sync -o <alias> reports as before. #302Map<Id, V> built by JSON.deserialize can find its own keys again. This is a 1.19.1 regression — upgrade past 1.19.1. size() reported 1, keySet() handed you the key, and get() on that very key returned null; a map that cannot find a key it just gave you is self-inconsistent whatever the platform does. The cause was older than the release that exposed it. Id map and set keys were lowercased throughout, on the assumption that Ids are case-insensitive in Apex — which was never true, and stayed invisible only while the deserializer lowercased keys on the way in too. Once that stopped, one side kept the document's spelling and the other kept lowercasing. An Id key is now stored exactly as spelled and compared case-sensitively, the same rule String keys got in 1.19.1. Three false passes went with it: get() with a differently-cased Id, Set<Id>.contains(), and Set<Id> de-duplication all used to match where the platform does not. #380insert as user is refused on a field-history object at every API version. The 1.19.1 note said user-mode DML was deliberately unchanged because the describe still reports these objects as not createable. The describe does — but the user-mode permission check only runs inside System.runAs, so outside it the object-level gate was the only thing refusing the write, and moving that gate to API 67 took the refusal along with it. Default mode and as system below 67 are unaffected and still match the platform. #365System.SecurityException, not NoAccessException. These are distinct types — catch (NoAccessException) does not catch the platform's exception — so a test written that way passed locally and would not have caught the refusal in an org. as user on a DML statement also enforces the running user's object permissions now; the clause was previously parsed and discarded, making insert as user x identical to insert x. Outside System.runAs nothing changes, which is the same answer the platform gives for a full-access context. NoAccessException is unchanged everywhere it is genuinely raised, including Security.stripInaccessible. #383JSON.deserialize into a scalar type now matches the platform, including where it throws. An array or object handed to a scalar returned the whole rendered list. The platform returns the token's opening character for String — [ or {, leading whitespace skipped — and refuses every other scalar: JSONException for Integer, Long, Double, Decimal and Boolean, a format error for Date, Datetime and Time, and a bad-id error for Id. Four of those cells used to return a value where an org throws, so defensive try/catch (JSONException) never fired locally. #372Object is refused as a JSON.deserialize target type. Map<String, Object>.class, List<Object>.class, Set<Object>.class, a bare Object.class and nested forms such as Map<String, List<Object>>.class all raise JSONException on the platform, with a distinct message when Object is a map's key type. All were accepted locally, so the code compiled, ran, and threw in the org. JSON.deserializeUntyped is the supported way to get an untyped structure and is unchanged. A class whose own name merely ends in Object is unaffected. #382Map<Id, V> deserialize target validates its keys. A key that is not an Id was accepted and became an entry no correct code could look up. The platform refuses the document — a well-formed Id whose case-safe checksum does not verify is reported as malformed, a string that is not Id-shaped at all as bad. Key validation runs before value-type validation, as it does on the platform. #381Profile, RecordType, ApexClass, ApexTrigger, ApexPage, ApexComponent, StaticResource, Folder and ApexEmailNotification drop the verb — DML not allowed on Profile — while the field-history family, Report, Dashboard, Organization and the rest keep it. Both are System.TypeException, so only text that a suite asserts on changes. Thirty objects were compared to establish which is which: no describe property separates the two groups, so this is a verified list rather than a rule. #373nimbus validate no longer reports RecordTypeId as an unknown column on objects that have record types. It fired on queries the runtime executes successfully in the same project, on standard and custom objects alike, because RecordTypeId is synthesised rather than carried as a describe field. Record types are now read from both the bundled schema and your project source, so a custom object with recordTypes/ metadata is recognised. An object with no record types genuinely refuses the column, and that is still reported. #387nimbus validate reports a trigger declared on an SObject it does not recognise. Such a trigger was accepted silently and simply never fired — nothing mentioned it, and the cost lands somewhere else entirely: the trigger's effects are missing, so assertions about them fail pointing at the product code while the file that is actually wrong is never named. One report reached "roll-up recalculation does not fire parent triggers" from a mistyped object name, and only avoided filing it because a control case failed too. The new diagnostic is a warning rather than a deploy blocker, and says the trigger will not fire locally rather than claiming the file is invalid: a real platform object that Nimbus has no schema for looks identical to one that does not exist, and refusing valid code would be worse. #386Map<String, V> keys and List.contains/indexOf now compare case-sensitively, as Apex does. This can turn a passing test red, correctly. Apex is genuinely inconsistent here — == on String ignores case, but collection identity does not go through == — and Set<String> was already right, which is what made the disagreement visible. The worst of it was not the false pass: m.put('alpha', …) on a map already holding 'Alpha' overwrote that entry and rewrote its key, so a map accumulating records by name or external id silently dropped rows and any assertion on size() reported a number that looked like a product bug. A real casing bug — the kind that costs an afternoon in a sandbox — now fails locally instead of passing. Removing the old behaviour also surfaced seven places Nimbus itself was folding case where the platform does not: getPopulatedFieldsAsMap(), JSON.deserialize and deserializeUntyped, getAll() on custom metadata and custom settings, String.template, Test.createStubQueryRow, getRemovedFields() and getOutputParameters() all keep the real spelling now, each confirmed against an org rather than restored to lowercase. #370DeveloperName and label casing, and match keys exactly. getRecordTypeInfosByDeveloperName() and getRecordTypeInfosByName() returned lowercased keys, so anything iterating the key set rather than looking up a known name — building a picklist of record types, logging what is available, comparing a key against a constant — got a value the platform never emits, and a label like Affiliate Case Team was not recoverable by any transform. The lookup was wrong in the other direction too: these maps were case-insensitive, where the platform refuses a differently-cased name. Worth knowing, because it is the opposite of the two describe maps beside it: fields.getMap() and getGlobalDescribe() really do lowercase their keys and really do match case-insensitively. The pair is consistent rather than arbitrary — a map that has thrown away its key casing has to fold case to stay usable, and a record-type map has not thrown anything away. #371WITH USER_MODE and WITH SECURITY_ENFORCED, RecordTypeId was refused with the unknown-column message for any user who reached the field-permission check. Field-level security cannot be set on it, so its absence from a permission set says nothing about what was granted. The damage ran in the direction that costs the most trust: a user on a profile alone never reached that check, so assigning a permission set — which only ever widens access — was what took the field away, and a suite went red after a permissions change that had granted strictly more. Verified across seven standard objects, where OwnerId, RecordTypeId and LastActivityDate all report that no field-level security is settable on them, while ordinary optional fields report that it is — those are still denied when never granted. #374Map.putAll(List<SObject>) and new Map<K, SObject>(List<SObject>) both keyed by a lowercased record Id whatever the map's declared type, and putAll reported that lowercased spelling back through keySet() as well. The platform keys a Map<String, SObject> by the canonical 18-character Id and matches it case-sensitively. Invisible until the change above, then unmissable: a lookup by the Id you are holding found nothing, so code that carries accumulated per-record state in a Map<String, SObject> across passes picked up a fresh record each time instead of the accumulated one, and silently dropped whatever the previous pass had contributed — most visibly in rollups, which came out holding only the last contribution. Map<Id, V> is unchanged. #378JSON.serialize of a record carrying a queried parent emitted "account" where every real field on the same record came out properly cased and the platform emits "Account" — a relationship is not a field, so the name resolution had nothing to answer with and fell through. This surfaced through the case-sensitivity change above: the common idiom of round-tripping a record through JSON.serialize and deserializeUntyped, replacing the parent by name, and deserializing back began writing a second key beside the stale one, and the stale parent won — silently discarding the fields that had just been re-queried. Library code that re-queries parent fields on demand is the main thing affected. #378wEbSiTe2 comes back as 'wEbSiTe2' and RECORDTYPEID2 as 'RECORDTYPEID2'. A title-cased name reads like a typo in your own query rather than a permission decision. #376insert on AccountHistory, CaseHistory or a custom *__History was refused at every API version. The platform only refuses from 67: below it there is no object-level check at all, and the statement runs into ordinary row processing, where whether it succeeds is decided by the row. Confirmed with classes whose bodies are byte-identical and differ only in their declared API version — at 66 the insert reaches row-level validation, at 67 it never gets that far. A class that declares no API version keeps the 67 behaviour, as does anonymous Apex, which compiles at the org’s own version. The describe is unchanged and still reports these objects as not createable at both versions, so user-mode DML is refused below 67 exactly as before. #365nimbus sync --include-record-types now pulls your profile’s record-type defaults too. An insert that leaves RecordTypeId unset takes the default from the running user’s profile, and that default was readable only from profile metadata checked into project source — a project syncing permissions from the org had no route to it, and the resulting null is also the platform’s answer for a profile with no default, so the two were indistinguishable. Record-type defaults are a profile-only concept, which is why permission data cannot supply them even in principle. The finding that shaped this: a Profile retrieved on its own comes back with no record-type visibilities at all — the same profile requested alongside the record types returned seventeen. The Metadata API reports a profile’s permissions only for metadata named in the same request, so asking for a profile by itself yields one that appears to grant nothing. A profile you keep in source still wins. #334DmlException carried a compact CODE: message string instead of the shape Apex actually produces — no verb, no row index, no id — so anything parsing getMessage() read something the org never emits. getStatusCode() answered UNKNOWN_EXCEPTION on the partial-success path, getNumDml(), getDmlIndex() and getDmlId() answered constants, and the : [Field] suffix that belongs in getFields() was inside the message. Failures now read Insert failed. First exception on row 0 with id 001…; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, …: [Field__c], with the accessors populated to match. Verified against an org down to the details that are easy to get wrong: the row index is the first failing row’s position, not the first row; with id appears exactly when the failing row has one, so never on insert; and the empty field tail is : []. One project’s error-handling framework, which parses these messages, went from failing to passing on the strength of it. #293 #341@isTest class — verified by deploying both side by side, there is no field a test may write that ordinary code may not — but Nimbus skipped the check in test context, so a suite could set IsWon, IsEmailBounced or a compound Address and pass on code the org will not deploy. This can turn a passing test red, correctly. The subtlety that shaped the fix: the platform’s rule is a compile-time one, so only writes the compiler can see count — a value a record merely carries at run time is accepted. Re-inserting a clone of a queried record, describe defaults loaded by newSObject, a JSON-deserialized payload and compound parents are all exempt for that reason, each confirmed against the org rather than assumed. #276WITH USER_MODE, and Security.stripInaccessible(READABLE) handed back an emptied record where the platform refuses outright. Both are false passes — the platform enforces object read in every query shape. Verified across six probe rounds as freshly created Minimum Access users: every shape but a bare COUNT() throws QueryException with the full custom-object hint, a bare COUNT() throws SecurityException instead, and stripInaccessible with no object access throws NoAccessException — with the type name appended only when access is partial, and never for an empty list. AccessLevel.SYSTEM_MODE is exempt, and now on every entry point rather than just one. #350Organization, Profile, ApexClass, PermissionSet, PermissionSetGroup and PermissionSetAssignment answered rows where the org refuses. Each cell was probed directly rather than reasoned about, which was necessary because the two message forms are not derivable — the first three are refused with a short hint-less message and the last three with the full custom-object hint. The ViewSetup system permission restores every one of them; an ordinary permission set carrying only object and field grants restores none, which was probed with exactly that shape. ViewSetup declared in your permission-set metadata is now honoured. #360TopicAssignment.EntityId lists 35 targets, ContentDistribution.RelatedRecordId eight — was joined against the first declared target, so a query returned data for rows pointing at that one object and null for every other row. The platform resolves these per row: one query returns Entity.Type of Account for the account-pointing row and Contact for the contact-pointing row. Parents now resolve per row across the whole polymorphic family, driven by the describe data rather than a fixed list. #327Type predicates silently dropped every row. Those parents carried only a type and an id, so SELECT What.Name answered null — as it had since the beginning — and the fields a query explicitly named were not read at all. Worse, WHERE What.Type = 'Opportunity' did not filter permissively as intended: it matched nothing and removed every row from the result, which is the silent-wrong-answer direction. Named fields are now read from whichever object each row points at, and =, != and IN against a type filter per row. A related fix: ORDER BY on a polymorphic type sorts by type name, verified with a pair whose name order inverts their internal key order, so a prefix sort returns them backwards. #361 #362Case.EntitlementId and Case.ServiceContractId appeared to exist but carried no relationship information, so traversing them failed and their describe answered null. Two independent causes: nimbus sync was not asking the org for standard describes at all — only the automatic sync did — and even once merged the result did not survive, because the schema cache is rebuilt on every sync and the next run without an org alias quietly discarded it. Both are fixed, and what the org adds is now remembered so ordinary runs keep it. Re-run nimbus sync -o <alias> once after upgrading. #302isDefaultValue() was false for every entry, and an insert that omitted the field stored null. Where the field is also required — which is usually why a default is declared — that became a hard REQUIRED_FIELD_MISSING on a row the platform accepts, concentrated in @testSetup methods that take their whole class down with them. The same picklist written with an inline value list was always correct, which is what isolated the indirection. A default declared on the field itself still wins, and a default on an inactive value is ignored. #367String json = 'hello world'; json.contains('world'); failed with Unknown method contains on Type json, and the same held for system, database and every other system class name, for locals and method parameters alike. String json = JSON.serialize(x) is close to idiomatic, so this reached ordinary code. There had been a deliberate carve-out letting the type win, meant to keep a variable and the type usable in one scope — and the org settles that: a class declaring a local json and calling JSON.serialize in the same scope does not compile. The carve-out was protecting a shape that cannot be deployed, at the cost of breaking one that can. #368JSON.deserialize of a JSON object into a list type returned a corrupt value instead of throwing. The result was labelled as the list type but held the object’s entries, so it escaped the surrounding try — nothing was thrown inside it — and the caller then failed on the first list method, at a line with no visible connection to the JSON. The common defensive parse-and-fall-back-to-empty helper therefore never fell back. Any non-array handed to a List or Set, and any non-object handed to a Map, now throws the platform’s JSONException. Two carve-outs are load-bearing and were confirmed against an org: a literal null deserializes to null rather than throwing — code that round-trips JSON.serialize of an empty value depends on it — and a scalar target is left alone, because the platform does not throw there either. #369String.valueOf(record), and fields printed alphabetically. The platform prints an assigned null and omits a queried one — a distinction with a real basis, since a null read from disk never enters the record’s field map at all, which is also why getPopulatedFieldsAsMap() excludes it. It also prints fields in the order they entered the record, not sorted. Both now match, including the details that are only findable by probing: an Id assigned by an insert renders last, a clone keeps its source’s order, reassigning a field keeps its original position, and a queried null that is later assigned moves to the end rather than reclaiming its original slot. #279 #358newSObject(recordTypeId, true) left its loaded defaults unmaterialized. The defaults were applied but the record’s field map did not reflect them, so equality comparisons and anything walking the populated fields saw a different record than the platform does. Materializing them flushed out four separate places that had been reading “this field is present in the map” as “the caller wrote this field” — an auto-number writability check, insert validation, JSON.serialize, and the copies made by stripInaccessible, clone() and trigger context. Each was found by a corpus run rather than by inspection. JSON.serialize of a defaults-loaded record emits only the populated fields, verified against an org. #359ns__Member__c and Member__c as the same member for the project’s own code, and renders a type token as the namespace-qualified name. Nimbus did neither: String.valueOf(X__c.class) printed the local name, breaking registries keyed by qualified type names, and a qualified spelling in member access missed the stored field entirely. Both are fixed for dynamic access, typed reads and writes, and constructor arguments. Scoped deliberately to your own namespace: verified against an installed managed package, the local spelling of another package’s member throws on the platform, and that stays true. #165 #259getAll(), getInstance() returns null, SOQL returns no row — while getAll() keys stay the local developer name and the row carries the namespace separately. Custom metadata reaches Apex by two independent routes, and both enforce this now; fixing either alone looks correct and is not. Your own unpackaged and own-namespace protected records stay visible, as on the platform. #194isCustom is true, MRU is false, and the record-type list is empty. #184$Record got null. $Record.Account.Id on a record carrying only AccountId resolved nothing, because relationship resolution handled custom __r names only — and standard relationships cannot be guessed from the name, since Account resolves through AccountId only because the schema says so. Cross-object $Record references are a core flow feature, so this reached flows that work on the platform. Single-target relationships resolve; a polymorphic one keeps the previous behaviour. #151ConnectApi.ManagedContent methods accepted any community id. Every one of them validated nothing and returned an empty collection, so a test could pass a null, an empty string or an obviously wrong value and see success. Verified against an org, and the rule holds across the family: null or empty throws with one message, a malformed id another, and a well-formed id of the wrong type is refused on its key prefix. A well-formed id that simply does not exist is deliberately still answered with the empty collection — Nimbus has no content store, and a real community id must never become a false failure. #159nimbus sync --include-record-types pulls the org’s record types. Record types were read from your project source and nothing else, and source can only ever say what exists — so an object having none was indistinguishable from Nimbus never being told. For your own custom objects that was fine, since source is the whole definition; for a standard or packaged object the record types live in the org, and Contact has none on one org and several on the next. One unfiltered query returns the org’s entire set, which is what makes an object’s absence from it evidence rather than silence. That is the fact the RecordTypeId rule below needs to reach standard and packaged objects. Record types defined in your own metadata still win, per object; org record type Ids are discarded and the deterministic local ones kept, so Ids stay stable across machines; and a result the org paginated is marked partial and never read as proof that an object has none. #355@testSetup there was no watchdog at all, so the run stopped dead: no output, no result for any test, nothing to report. For an ungrouped test the timeout did fire, but the abandoned work could not be stopped and kept running at full speed for the rest of the process — the same leak fixed in the previous release, surviving on this one path. Class loading now runs under the same timeout, so a non-terminating initializer fails its class cleanly and reports every method in it, the way a failed @testSetup does. #349isCreateable(), isUpdateable() and isDeletable() answered true for objects the platform never lets you write. These are not permission questions a profile could answer differently — the platform describes Report and Profile as not createable for a system administrator, the most permissive user there is. So the common defensive shape if (Schema.SObjectType.Profile.isCreateable()) { insert p; } took the guarded branch locally and skips it in the org, meaning the suite exercised code the org never runs. All 163 bundled describes were re-checked against an org and now match it object for object; sixteen were wrong, in both directions — Campaign was marked unwritable, which would have refused every Campaign insert had the data been served unchecked. The answer is also per verb rather than per object: User can be created and updated but never deleted, GroupMember created and deleted but never updated, ContentDocument the reverse. #351insert new Report(), insert new Profile(), delete on a User, update on a GroupMember. Field-history objects were already covered; this is the rest of the family, and it is per verb for the same reason as above, so nothing is refused that the platform allows. undelete is deliberately not covered: the platform exposes no flag for it and gets past this check to fail on the record id instead. Note this can turn a locally-passing test red — correctly, since the operation cannot run in an org. With a statically-typed operand the platform refuses at compile time and Nimbus raises the same rule one phase later, at the DML. #318 #352RecordTypeId was queryable on every object. The platform exposes that column only on objects that actually have record types; on an object with none it does not exist, and naming it is an error rather than a null read — so code that cannot deploy was passing locally. It is now refused in the SELECT list, in WHERE, ORDER BY and GROUP BY, in aggregates, inline and dynamic alike, and through get() / put(). A child sub-query is checked against the child object it actually reads, and a reference qualified by a FROM alias or by the object’s own name counts as naming that object’s own field — while a genuine parent traversal such as Account.RecordTypeId FROM Contact stays legal, because it reads a different object’s field. Without an org record-type snapshot this applies only to objects your project defines, where source is the whole definition. #333 #353 #356Security.stripInaccessible(READABLE) removed ungranted optional fields from records read by a Read Only profile, where the org keeps them — a false denial, which is the damaging direction, because records come back thinner locally than in production. Measured across four users on an org: a profile that carries field-level security grants standard fields, so Standard User and Read Only reach every field in every shape and nothing is stripped. Only “Minimum Access - Salesforce”, which grants nothing without an explicit permission, refuses — and it refuses in every shape, including plain system-mode queries. Read-side answers now agree with the org and with each other; the write access types keep their stricter reading, since the platform’s rule for them differs. #344 #346Database.query(q, AccessLevel.USER_MODE) ignored its second argument entirely, so a query that explicitly asked for enforcement got none. #345isDefaultedOnCreate() answered false for every standard field. OwnerId is defaulted on create everywhere — on standard and custom objects alike, verified against an org — and the correct answer was in the bundled describe data all along, simply never read. It matters most to test-data frameworks, which build a parent record for every field that is createable, not nillable and not defaulted: on the platform that walk skips OwnerId and stops, while locally it descended through User and Profile to UserLicense and failed there. #354--dry-run listed the tests it would run, then reported that none existed. The summary said Tests: 0 total and No tests ran directly beneath a listing of three. Under --json there was no contradiction to notice — the document reported zero tests and an empty array, so the enumeration a dry run exists to produce was missing from the machine-readable output altogether, and a caller could neither distinguish “nothing matched” from “three matched” nor recover the names. The selection now reaches both surfaces, marked as skipped. A dry run also no longer claims tests passed: nothing was executed to pass. An empty selection still reports zero and warns. #348null rather than errors. -e/--exclude keeps paths out of compilation — that is what it is for — but nothing said so when it mattered: a test needing an excluded helper failed with a null dereference or a wrong value pointing at the code under test, never at the flag. At suite scale that reads as a scattering of genuine-looking product bugs; one report traced roughly 280 of them across four hand-split shards. The run already knew both halves — which files a pattern dropped, and which references went nowhere — so it now names the class and points at --shard, which splits a suite without removing code. #347--exclude did not, so selecting a pattern and excluding the same pattern overlapped instead of splitting the suite — quietly running some tests twice for anyone partitioning that way. Exclusion is now case-insensitive too, matching how Apex treats type names. Extra positional patterns are also rejected rather than ignored: nimbus test A B C ran only A’s tests and said nothing about the rest. #348try/catch swallowed it and kept executing at full speed for the remainder of the run, holding onto everything it touched. Every timed-out test of that shape added another: on a 2,469-test suite this reached 13–19 GB and a full swap file, with CPU well above one worker’s share, while the same tests split across separate processes stayed in the hundreds of megabytes — the observation that pinned the mechanism. A timed-out test now halts with an uncatchable fault the moment the runner abandons it, the way the platform’s own limit breaches halt code. Two fixes fell out: the configured per-test timeout (--timeout / nimbus.test.timeout) was silently ignored for classes grouped around @testSetup, which always waited 60 seconds; and when one test in such a class timed out, every remaining test in the class was reported as skipped — they now run. #298addError on one row of two keeps neither (the rollback unit is the statement, not the offending row), an update has every field it changed reverted, and a delete has its row restored. A test asserting a row count after a caught DmlException was reading a number the platform never produces, and a half-applied @testSetup left later methods running against state that cannot exist in production. Writes made by triggers and flows during the statement are not rolled back yet. #340Name arriving through JSON.deserialize failed the insert. Exporting records to JSON and re-importing them elsewhere is an ordinary configuration-migration shape, and Name rides along in the payload because it was in the export projection. Where the platform refuses depends on whether the compiler can see the write: a source-level assignment is a deploy error, while the same value arriving at run time is accepted and silently ignored. Verified against an org — a payload naming 000124 produces a stored row reading 000000. So the insert now succeeds and the supplied value is discarded, rather than being honoured, which would store something the org never stores. Two neighbouring divergences went with it: an upsert was writing the supplied Name over the stored sequence number, and put() threw at the DML instead of at the put() call. A source-level assignment is still rejected. #343nimbus test and nowhere else. nimbus.seed.row.PermissionSetGroup.X resolved to one row inside a test method and zero from nimbus exec — which is the first place anyone checks whether the config took. The same split affected named users, public groups, roles, queues, networks, list views, Sites, org defaults, and the project’s own permission sets and permission set groups. All of it now seeds for nimbus exec, nimbus app, the language server, the debugger and the MCP bridge as well. The divergence was invisible on any database that had run tests at least once, since those rows commit outside the per-test transaction and survive — so it appeared on a fresh clone, after a reset, or for anyone who had only ever run exec. #300 #336Contact.AccountId described as "Account Id" where the platform says "Account ID", and the fault ran far wider than casing: the derivation agreed with the platform for only about a third of standard fields. Account.NumberOfEmployees is "Employees", Account.BillingPostalCode is "Billing Zip/Postal Code", Account.Sic is "SIC Code" — none of which any derivation can recover, because a label is not a function of the field name. The correct labels were already bundled and were being discarded. A 19-field spread was verified against an org: all 19 now match exactly, where 10 did not before. #335Schema.RecordTypeInfo.isAvailable() reported whether the record type was active, not whether your profile grants it. Every active record type read available for every user, so code guarding on isAvailable() before selecting a record type took a branch the org would not. Verified against an org: two active custom record types report unavailable until the profile is given visibility of them, and eight active packaged Account record types assigned to no profile all report unavailable. The grant is now read from the same <recordTypeVisibilities> block the default record type comes from. It is consulted per object and only where your profile actually speaks — a profile retrieved with a partial manifest routinely omits objects the org does grant, and nothing distinguishes that from a real denial, so an object your profile does not mention keeps the permissive answer. #332nimbus reset left the database running, then failed its own re-initialisation. It removed the project’s .nimbus/ directory but never stopped the embedded PostgreSQL, so re-initialising hit "process already listening on port N" — and a second reset failed identically, meaning the command you reach for when something is already wrong could not fix the state it had just created. Two faults produced it, and each hid the other: the port sweep covered a fixed ten-port window while a project’s port is derived from its path across a thousand-port range, and the graceful stop looked for the database binary somewhere it does not live, so no clean shutdown was ever attempted. Reset now stops the server by its recorded process id, clears the project’s actual port, and waits for that port to be released before continuing. A current build also recovers a project an older one left stranded. #342--json output could be interrupted by progress and warning text. Anything written to standard output alongside the JSON document makes it unparseable, which breaks a CI step that pipes the result to a tool rather than a person — and it broke conditionally, so it survived testing. Warnings now go to standard error, leaving --json stdout a single parseable document. #337Class.Foo.bar: line 12, column 1; a caught or propagated one reported Class.Foo.bar: line 12 — and, in practice, no line at all, because nothing ever recorded where each frame was stopped. An exception raised and caught inside a single method came back with no stack whatsoever, which is the ordinary shape for a query exception from inline SOQL. The platform draws none of these distinctions. Verified by running an identical class against an org: every frame of a caught custom exception, a runtime null-pointer, a DML exception, a query exception, a rethrow, and an exception constructed but never thrown carries the column suffix and a real line — and Nimbus now reproduces the org’s line numbers exactly for the same source, including keeping the original throw site through a rethrow. This changes the failure output shown on every failing run, and anything asserting on getStackTraceString(). #296nimbus sync -s WorkOrder -o myorg — and remembers it, so later syncs keep it rather than regenerating without it. A release that adds the describe quietly takes over from the pulled copy. #326SELECT list. Under WITH USER_MODE, a user who cannot read a field could still filter on it, order by it or group by it — so a test written to prove a restricted user cannot reach a field passed locally whenever the field was filtered rather than projected. That is the direction that costs the most trust in a local runner, because the test looks green. Verified against an org: as a user granted object read and no field permissions, all four clauses are refused exactly as projecting the field is, while the same query without that field succeeds. The clause reader is deliberately cautious — a subquery’s fields belong to the other object, and bind variables, literals and date literals name nothing — since a misread name would refuse a query the org accepts. #328isAccessible() reported a field readable because the object was. A field with no grant at all answered true, and contradicted WITH USER_MODE on the same field, in the same transaction, for the same user — which is part of why field-access reports were hard to attribute. Verified against an org: Name rides on object read and answers true, while three ungranted optional fields on the same object all answer false. The two now agree. The denial is confined to objects Nimbus actually holds field grants for: a seeded standard profile such as “Read Only” contributes no field permissions locally and grants plenty in the org, so reading its silence as a refusal would invent a denial rather than model one. #329nimbus triage listed present, queryable objects as missing metadata. Platform-namespaced types were reported as absent — and written into the manifest it generates — while the same run warned about a lookup filter on one of those very objects. Retrieving them cannot fix anything, so the manifest sent you after metadata you already have. #304nimbus test without -o invoked the CLI as a subprocess on every single run — a Node process costing more than the rest of startup combined, and the largest single source of run-to-run variance. The answer is decided by files on disk and changes approximately never during a working session, so it is now cached against those files and the subprocess runs only when they change. On a small project a warm run went from 1.18s to 0.60s. The "no default org configured" answer is cached too, which was the worst case: a full subprocess on every run to be told nothing. #338X__History exists only when the parent’s metadata carries <enableHistory>true</enableHistory>, while a standard history object is present whether or not tracking is configured. Provisioning every history object unconditionally would accept queries the org rejects, and gating the standard ones on your metadata would reject queries it accepts. The tables are empty — writing history rows on a tracked-field update is a separate, larger piece of work. #303nimbus.seed.row seeds real database rows for objects that live in your org but not in your repo. nimbus.seed.record deliberately never inserts anything — it feeds getAll()/getInstance() for list custom settings and stops there — which left no route at all for an object that exists in the org, has no dedicated seeder, and is dereferenced straight through: orgWideAddress('x@y').Id, or a PermissionSetGroup looked up by DeveloperName on a User-trigger path, where the failure is uncatchable. One line per row: nimbus.seed.row.PermissionSetGroup.For_Everyone=MasterLabel=For Everyone. The name half of the key fills whichever identifying column the table actually has — DeveloperName where there is one, otherwise Name — so a lookup by that name resolves without repeating it in the value. Rows land once per environment setup rather than per test, since a test already runs inside a transaction that rolls back. #316Sent and Responded on every Campaign insert; Nimbus created none, so code querying or asserting on campaign member statuses saw an empty list. Both of the platform’s delete protections come with it, and they are two separate rules rather than one: deleting the default status fails, and deleting the last status marked Responded fails — a campaign with two responded statuses can still lose one. Setting a new default clears the previous one, on insert and update alike, which is what lets a package install its own statuses over the seeded ones. #175RecordTypeId stamped the Master placeholder instead of your profile’s default. Every such record got 012000000000000AAA, a describe-only value the platform never stores. This was harmless until lookup filters began being enforced, at which point a filter constrained on record type rejects the Master-typed row and takes the whole @testSetup with it — reporting the filter’s own message, with nothing in it pointing at record types. The running user’s profile default is now applied, read from <recordTypeVisibilities> in your profile metadata and matched by either the Setup label or the metadata API name ("System Administrator" and "Admin" both resolve), with System.runAs following the impersonated user. When the profile names no default the field is left null, which is what the org does. The value is stamped before before-insert automation runs and is never written back onto your in-memory record, both matching the platform. Permission sets are deliberately not consulted — they cannot carry a default. #331fields shortcut returned null instead of an answer. getLabel, getRelationshipName, getReferenceTo, getSoapType, getDefaultValue, getDefaultValueFormula and getLocalName fell through to a generic fallback that answers null for any qualified class name, so a field-metadata helper branching on getRelationshipName() != null to spot lookups saw every field as a non-lookup and nothing raised. isNillable and isMRUEnabled threw "Unknown method" — the mild half, since at least it surfaced. All 43 describe methods now answer on the shortcut, delegating to the same describe result getDescribe() builds so the two spellings agree by construction rather than by keeping two lists in step. #330WITH USER_MODE, and no metadata change could fix it. Any query naming a required custom field, a required lookup or a master-detail field failed with "You don’t have access to one or more fields" for a user whose grants arrive through permission sets. The check treated "no field permission grant" as a denial — correct for an optional field, wrong for a required one, because the platform stores no FieldPermissions row for a required field at all: its field-level security is forced on and an entry naming it is not accepted. So the absence of a grant means two opposite things, and only one of them is a denial. Verified against an org: required, master-detail and auto-number fields return zero permission rows org-wide while optional fields on the same objects return one per granting profile or permission set. An ungranted optional field is still denied, which is the half that keeps this from becoming "user mode stops checking". #82Id. delete [SELECT Id FROM Child__c WHERE ...] left every roll-up above the deleted rows at its pre-delete value, because the parent key was read off the in-memory record and a row queried Id-only carries none. Silent — no error, and a stale number is indistinguishable from a correct one. With a second roll-up level above the first, both stayed stale. That is the ordinary way to delete a working set in Apex: a selector returning Ids, a batch’s start() scope, a test cleanup step. The platform recalculates in every case because the server knows each row’s parent regardless of what the client selected, so the key is now recovered from the stored row. A key that is present and null is left alone — that is a record with genuinely no parent. #324INCLUDES and EXCLUDES are real multipicklist set operators. Both compared against the stored string as a whole rather than asking about its members, so INCLUDES ('A') missed every row with more than one value selected — a row holding A;B holds A, and the platform matches it. EXCLUDES was wrong at both ends: it kept rows that do hold the value and dropped rows where the field is null. INCLUDES ('') answered the exact complement of the platform, which reads it as "has nothing selected". Silent in every direction and shaped like a filter that works, so a selector over an optional multipicklist quietly processed the wrong rows. 25 query shapes were verified against an org and all now agree, including that a comma is OR, a semicolon inside one operand is AND, and EXCLUDES is the exact complement of INCLUDES on every row, null ones included. #323Restrict delete constraints were ignored whenever more than one record was deleted at a time. A list delete took a batch path that ran no checks, so every parent went and every child the constraint exists to protect was orphaned — succeeding locally and failing on the platform, which is the direction that defeats the point of running locally. The single-record case was fixed previously; its regression test passed only because its list happened to hold one element. The platform answers per row, so a partial save (allOrNone = false) now produces a result per record with the unblocked rows really deleted, while an all-or-none delete throws and deletes nothing. Both shapes match the org exactly, including which row survives. #319NOT LIKE dropped rows where the field is null. SOQL treats a negated predicate as true when the field is NULL. A pattern matching neither of two records returned only one of them, and NOT Type LIKE 'Exc%' returned nothing where the org returns the null-Type row. Compound forms — the predicate inside an AND or an OR — were wrong the same way and now agree with the org too. Plain LIKE is untouched: a null field doesn’t match it on either side. #322NOT IN dropped rows where the field is null when the operand was a bind variable. The literal form was already correct, so the two disagreed: Type NOT IN ('ExcludeMe') matched a null-Type row and Type NOT IN :typeSet did not. Any selector excluding values on an optional field — Type NOT IN :excluded, RecordTypeId NOT IN :ids — returned nothing where the platform returns everything, so a roll-up or batch processed no rows and reported success. != against a bind had the same gap. IN is deliberately unchanged: a null field doesn’t satisfy it on either side. #321IsClosed and IsWon read false on every opportunity ever written, including one staged "Closed Won", and Probability and ForecastCategory were never filled either. The stage data was already correct locally — only the join was missing. The rule, re-verified against an org: the stage wins unless the same DML statement also carries the field, in which case your value wins; an update touching neither leaves both alone. There is no stickiness across statements — a Probability of 77 set on insert becomes 100 after a later move to Closed Won, and an explicit ForecastCategory is overwritten the same way. Telling "the caller set this now" from "this was merged in from the stored row so triggers could read it" is what makes that precise, and it now uses the same comparison the update path already applies to decide which columns to write. #320ContentDistribution, Topic and TopicAssignment carried no relationship metadata at all, and Site shipped 13 of the org’s 51 fields, missing every reference field. The previous release’s stricter relationship checking turned that from a silent wrong answer into a hard error with no remedy available to you — a standard object can’t be authored in your project, and syncing from an org only augments objects Nimbus already bundles. A parent traversal now also requires the field’s own relationship name to agree with the name being traversed, so bundling these didn’t start accepting queries the platform rejects: ContentDistribution.ContentDocument has no relationship name on the org and is refused there and here alike. #325Limits.getCpuTime() read about ten times high. The reading is derived from work done rather than the wall clock, so identical code reports identical CPU on every machine — that part is deliberate and unchanged. The conversion factor was not: it divided a count of syntax-tree nodes by a figure meant for whole statements, and one statement is several nodes. The everyday casualty was the standard Apex trick of waiting out a wall-clock second to force an observable LastModifiedDate change, which reported the entire synchronous budget as consumed after one second of real time against the org’s 992ms; anything branching on getCpuTime() then took its limits-exceeded path locally and its normal path on the platform. The same loop now reports 998ms. Readings are roughly a tenth of what they were, so a test that deliberately exhausts CPU needs ten times the work to do it. #299isCreateable() and isUpdateable() answered false for every field on 39 standard objects. Those describes stated neither flag anywhere, and an omitted flag and an explicit false are indistinguishable once parsed, so nothing was enforced on them while every field reported uneditable. For 22 the org agrees there is nothing createable; the other 17 now match the org exactly. Truncated field lists were completed in the same pass — ListEmail shipped 4 of 28 fields, MobileApplicationDetail 12 of 30 — adding roughly 22,000 previously absent field details. The merge only adds what was missing and never replaces a bundled answer, so corrections accumulated against the platform over past releases are preserved. #277getRemovedFields() returned field names in lower case. Security.stripInaccessible reported {Obj__c={opttext__c}} where the platform answers {Obj__c={OptText__c}}. Set<String>.contains() is case-sensitive in Apex, so any caller checking the map for a specific field name never matched. The ordering of that set is now stable between runs as well. #285settings.isEnabled() && !System.isFuture() threw a null dereference inside a trigger. The metadata default is still applied server-side on insert only, and getPopulatedFieldsAsMap() stays empty, both matching the org. #315getInstance() returned null for a hierarchy custom setting that lives inside a managed package. The platform never returns null here: with no stored record it hands back an empty record whose Id is null. Nimbus only did that when it could confirm the object was a hierarchy setting, and that check reads local object metadata — which a packaged setting does not have, since your project carries only its own extension fields on that object. So Settings__c.getInstance().SomeField__c, an everyday shape in package-aware code, died on a null dereference. An object Nimbus knows about and knows is not a custom setting still returns null, so nothing it can classify changed. #314sync --include-permissions reported success while every custom PermissionSetGroup vanished, along with a large number of permission sets and grants. Replacing an existing row deleted it and re-inserted it — but that delete is a soft delete, so the row stayed, its primary key stayed taken, the re-insert failed, and the row was left flagged deleted and invisible to every query. A group correctly seeded from the org was therefore destroyed by the attempt to overwrite it with the identical definition from your repo. Rows are now updated in place, and any row an earlier version left in that state is revived rather than needing a fresh database. Two further causes fixed alongside: a permission row naming an object from a managed package overflowed a column sized from the field’s describe, and all three failures were written to a stream discarded at default verbosity — they now print unconditionally, because a missing grant surfaces much later as a confusing "List has no rows". #306AsyncApexJob shipped a field literally named ApexClass beside the real ApexClassId; no such field exists on the platform, where ApexClass is the relationship name. A query builder resolving ApexClass.Name matched the phantom, found no relationship name on it, and emitted SELECT .Name — so the column read null on every row. Two other bundled objects carried the same shape and were corrected against the org, where the right answer differed for each. A new check over every bundled describe keeps the class closed. #311GROUP BY ROLLUP(...) or a multi-term GROUP BY was written without its table name, so as soon as the query also traversed to a parent whose table has a column of the same name — OpportunityContactRole.ContactId with a hop through Opportunity is the everyday case — the database could not tell them apart. Both are valid SOQL and both now run. #312GROUP BY and HAVING over a relationship the local schema cannot resolve used to surface the database’s own complaint, naming an internal alias that appears nowhere in your query. They now throw the same catchable System.QueryException naming the relationship that the SELECT list, WHERE and ORDER BY have thrown since the previous release. #309ENTITY_IS_DELETED even when the parent is only in the recycle bin. Nimbus accepted it, so a try/catch that rolls back to a savepoint, an allOrNone = false partial save, or an error-logging framework asserting it wrote a record all took the success branch instead. The check is evidence-based: a row is rejected only when the referenced Id is found locally and marked deleted, so an Id with no local row — an org-real Id, a stub, a forward reference — is still accepted. Updates are not covered yet, because the platform only rejects a reference being assigned to a deleted record, not an unrelated edit to a row that already holds one. #313Recordtype, Servicecontract — while the column beside it kept the casing you typed. #310nimbus.seed.record says when it cannot do anything. It seeds list custom settings only — the rows are served to getAll()/getInstance() during tests and never inserted, so each test stays isolated. Naming any other object was accepted and silently did nothing, which is indistinguishable from a working line in a file that is committed and shared across a team. It now warns, and the header Nimbus generates documents the real contract instead of advertising a general record seeder. #307System.QueryException for this in every clause, and Nimbus now does the same, naming the first hop that couldn't be resolved. Ordering by such a path previously failed too, but blamed a column that does exist on an entity whose name was spelled wrong; it now reports the relationship. Dynamic queries throw a catchable exception, matching the platform, where a string built at run time has no compile step to reject it; queries written inline keep failing hard, because the platform rejects those at compile time and no catch block can observe that. Polymorphic lookups (What, Who, LinkedEntity, RelatedTo, Parent, and SetupOwner on a hierarchy custom setting) are unaffected — the platform resolves those per row. If a suite was silently relying on one of these paths reading null, it will now surface as a failure, which is the point. #305nimbus login now reconciles this machine with the license your account actually holds. Previously, a machine with stored credentials reported "already signed in" and stopped — so if your account's license had been re-issued or changed server-side, the machine stayed registered against the old one: the CLI looked signed in while the portal showed no machines at all. Login now always signs in through the browser; if nothing changed it says so, and if the license differs it deactivates the machine from the old license and registers it under the current one. Running nimbus login once fixes an out-of-sync machine end to end. #301Case.ServiceContractId simply did not exist, and a SELECT traversing the relationship returned nothing. A sync with an org connected now asks that org which standard fields it actually has and folds in what the bundle lacks, relationships included. The merge is additive — the curated bundled metadata stays authoritative for everything it already carries — and fully best-effort: no org, no network, or a partial response leaves the bundled schemas in place rather than failing the sync. #302nimbus login would do, without you having to notice first. The recovery re-uses an existing activation rather than adding one, so it can never consume a licence slot, and the machine cap is still enforced server-side: a licence at its limit stays at its limit. The run says so when it happens. #301nimbus login registered another activation until the licence policy evicted the oldest: your own machine, forcing another login. Identity is now keyed on a stable per-install id, logging in re-uses the machine already registered (including one registered under the old derivation), and a run that resolves to one worker because Pro stopped applying now says so and why, instead of silently running serial. #301JSON.serialize, String.valueOf on an SObject, and duplicate-field query errors rendered fields the bundled describes don't carry — custom fields, and the org-augmented standard fields above — as their lowercase storage keys: amount__c where the platform writes Amount__c. All three now use the field's declared casing, matching what the platform produces.__c suffix in the middle of a sentence — rstchild__cs where the platform says rstchilds. It now uses the configured plural label, so an object labelled Member Factory reads "member factories" rather than "member_factorys". Cosmetic unless a test asserts on the full message, in which case it diverged. #295SIGINT while a SIGUSR1 snapshot was still in flight exited the process under the writer, leaving an unreadable file — reported by someone profiling a large run, where the lost snapshot was the one nearest the moment worth capturing. Profile writes and exit are now serialized. #298booleanFilter expressions, $Source and $User criteria, field-to-field comparisons — and all of them were silent, so a save could succeed locally that the org would reject with nothing to indicate why. The run now names the field and the reason. A warning rather than an error: every skipped shape is one the platform accepts, so failing the run would reject projects that deploy cleanly. Inactive and optional filters report nothing, since Salesforce does not enforce those on DML either. #294equals filter with three permitted values accepted no record at all and rejected every insert that populated the lookup. Enforcement was new in 1.16.1 and is correct; it turned a latent parsing bug fatal. A reported suite lost 169 tests. Verified against the platform for all five operators: equals, contains and startsWith match any listed value, notEqual and notContain match none. A filter on RecordTypeId also works now — its values spell record-type names while the column holds an Id, and the two were never reconciled. #286Variable does not exist on the second, and a single bind failed the same way whenever an earlier literal appeared in the WHERE clause. Two binds differing only in their string argument also shared one entry, so one of them silently ran on the other's value — that one produced wrong rows rather than an error. #288OwnerId is populated during before-insert triggers. The platform applies the implicit owner default before before-save automation runs, and the field appears in getPopulatedFieldsAsMap() there. Locally it was applied at save time, so a trigger branching on OwnerId == null took the null-owner path on every local insert and never took it in the org. CreatedById, LastModifiedById and Id are genuinely null at that point and are unchanged. #289BEFORE_UPDATE context, with Trigger.oldMap populated, against a record that was being created. A before-save flow writing to $Record is an in-memory field assignment on the platform: it consumes no DML and fires no trigger. #290nimbus exec, nimbus app, the language server, the debugger and the MCP bridge all queried empty User and Profile tables. SELECT ... FROM User WHERE Id = :UserInfo.getUserId() returned no rows in anonymous Apex and one row in a test — and in its usual single-record form it threw rather than degrading, so any trigger containing that idiom broke every insert run through nimbus exec. #291nimbus exec -c runs a script that ends in a statement, instead of rewriting it. Inline code ending in insert a; was wrapped in System.debug() and failed to parse, reporting errors at column numbers past the end of what was typed, with nothing to indicate the input had been rewritten. Only a trailing bare expression is wrapped now, and semicolons inside string literals and for headers are no longer read as statement boundaries. #292Database result carries the platform status code. Database.delete(record, false) reported UNKNOWN_EXCEPTION where the platform reports DELETE_FAILED, while the list form of the same call was already correct — so the two disagreed with each other. Upsert and undelete had the same gap. #287CurrencyIsoCode exists only when MultiCurrency is enabled. The field was present on every object while UserInfo.isMultiCurrencyOrganization() reported false — an internally inconsistent org, and a false pass: code referencing it compiled and queried locally, then failed against any single-currency org. A single-currency org now answers No such column 'CurrencyIsoCode', character for character, and --feature MultiCurrency matches an enabled org. #270System.AsyncException with no location. The platform appends the whole stack, innermost frame first, on its own line. #266getOutputParameters() lowercased them, so iterating or comparing keySet() saw outvalue where the platform returns outValue. Lookups already worked, being case-insensitive. #284System.runAs for a minimum-access user, ContentVersion and Attachment described as unreadable at object and field level; on the platform both are readable to every profile. Record visibility is enforced separately and is unaffected. #144Id is settable in a constructor, and Contract and Profile no longer gain fields they do not have. Every describe reports createable=false for Id, but the compiler special-cases it — new Account(Id = '001…') is valid Apex and one of the most common test idioms there is. Separately, Contract was given a Name field (its name field is ContractNumber) and Profile an OwnerId; neither exists on the platform. #297NIMBUS_MEMPROFILE wrote only at clean exit and only the cumulative-allocation view, which is the wrong tool twice over for a run growing without bound: such a run gets killed, and the question is what is being retained. It now writes the heap profile, is flushed when the run is interrupted, and on macOS and Linux SIGUSR1 writes a snapshot mid-run without stopping it — so growth can be sampled at several points and diffed. #298INVALID_CROSS_REFERENCE_KEY while a non-Id-shaped one passed. That is not exotic data: an external system keying people by their Salesforce User Id is common, and one reported suite lost 512 tests to it. The same mapping also gave these fields an 18-character column, so an external key longer than an Id would have failed to store — fixed in the same change, before anyone hit it. #283isGroupable(), isSortable() and getByteLength() answered false/false/0 for every standard field, in every project — the shipped metadata carried the real values and three separate conversions dropped them on the way. Account.Name now reports groupable and sortable with a byte length of 765, and Account.Description neither with 96000, matching the platform exactly. #275put() on a change-event field now raises System.SObjectException: Field X is not editable, as the platform does. Constructing one with field values, and assigning to them, stay allowed — the platform permits both inside a class, which is where test code lives. #267FIELD_FILTER_VALIDATION_EXCEPTION and the filter's own error message. Locally such a row inserted cleanly, so this was a test passing on code the org refuses. Deliberately narrow: only filters expressed as plain criteria on the looked-up record are enforced, and filters using a boolean expression, $Source or $User are left alone rather than approximated — a wrong rejection breaks working code, which is worse than the missing one it replaces. #258Search.find and Apex invocable actions execute instead of returning stubs. Both previously handed back an empty shell, so a class that dispatches to an invocable action or reads SOSL results could be called but never actually exercised — the test passed without testing anything. They now run the real work: an invocable action resolves its target class and invokes it, and SOSL returns matching records. The error contract was verified against the platform in the same pass, including what an empty search snippet returns. #260 #261FormulaEval evaluates real expressions. Formula.builder() returned null for every expression given to it — including a bare string literal — so anything built on it silently produced nothing. It now evaluates, and $User and the other running-user globals resolve to the user actually executing rather than to blank. #241 #251SObjectException: SObject row was retrieved via SOQL without querying the requested field; locally the read returned null, so a missing field in a SELECT surfaced as a wrong value somewhere downstream rather than as the error that names it. This is the single most common way a test passes locally and fails in the org. #229new Account(Name='X') compared equal to new Account(Name='X', Industry=null), and a Set holding both kept one — the platform treats them as two different records. Sets, Maps and == now agree with each other and with the platform, including the subtler cases: a null a query produced is not part of identity, while assigning null afterwards makes it so. #274 #89Database.SaveResult.getErrors() carried placeholder data, so error-handling code — the branch a negative test exists to cover — was being exercised against something that did not resemble a real failure. Status code, message and field list now reflect what actually went wrong. #273isHtmlFormatted() answers. A rich text field reported STRING where the platform reports TEXTAREA, and the one attribute that distinguishes it from a long text area was hardcoded false — so code branching on either took the wrong path silently. #271Campaign field — Name and Status included — reported as non-createable to every project. Permission-dependent flags now model a fully-permissioned user, which is what the separate permission layer is for. #262nillable, restricted-picklist flags and their labels, groupable, sortable and byte length were present in the shipped metadata but lost before Apex could read them, so every field claimed the same defaults. A lookup to an external object now resolves as a reference rather than as text. #196 #199 #280scale() counts decimal places. A Number(18,2) field described as scale 0 once a project had synced its schema, and Decimal.scale() counted exponent digits on a value in scientific notation. #254 #228SUM and AVG accumulate in exact decimal and come back as Decimal. Aggregates were accumulating in binary floating point, so summing currency drifted by fractions of a cent over enough rows, and the result's runtime type did not match what the platform returns. #228String no longer casts to a number. (Integer) someString compiled and ran locally where the platform rejects it, so the rejection arrived on deploy. Inner-class references are resolved strictly for the same reason, and a malformed-JSON error now names the position the platform names. #233 #218Invocable.Action.Result.getOutputValues and the FeatureManagement String and Datetime parameter methods — are rejected rather than quietly answering. #169 #170 #264 #265RecordType table and silently match nothing on every RecordType.DeveloperName join; Limits.getCpuTime() read a wall clock, so real code calibrating batch sizes against it planned differently run to run; grouped query results and Trigger.old both came back in whatever order Postgres chose; datetime round-trips lost precision; and distinct child relationships sharing a name collapsed to one arbitrary survivor. All seven are fixed. A run that reports different results from the previous one is now telling you something real.Datetime carries milliseconds, so a value read out of a record was strictly smaller than the one stored: a record could not match its own timestamp. WHERE LastModifiedDate = :recordsOwnValue returned nothing, and <= excluded the record it came from, which is what made time-window queries look intermittent. Verified against the platform, which stores all datetimes to the second — including writable fields, where 05:06:07.123 reads back as 05:06:07.000. #247All tests passed! and exit code 0 — a green CI run in which nothing executed. An explicit pattern matching nothing is now an error with a non-zero exit; running with no pattern on a project that has no test classes still warns and exits 0. #243MyClass.total += 3 stored 3, whatever total held, so every cross-class accumulator on a static — counters, error strings, log buffers — kept only its last contribution, silently. Writing total += 1 inside the declaring class was always correct, and ++ was too, which is why this stayed hidden. #249Database.query but not on [SELECT ...], so a with sharing class read other users' private records through inline SOQL and a negative sharing test passed locally that fails in the org. Separately, the filter also fired for any restricted profile regardless of OWD, hiding rows the platform returns. Verified live: sharing follows OWD and nothing else, and the query form is irrelevant. #244 #245runAs and test-entry boundaries. FeatureManagement.checkPermission saw a custom permission the test itself had just granted; the platform keeps the outer context fixed for the whole transaction and only a runAs block sees the new grant — and only inside the block. An inherited sharing class called from a without sharing test class also inherited the bypass; a test class's declaration does not propagate, because the test method is the entry point. #174 #167:someProperty resolved to nothing where a plain field or local in the same query resolved fine, and the unresolved bind then degraded to an empty array — so the query returned no rows instead of erroring. An unresolved bind now raises Variable does not exist as the platform does. In one compatibility project this moved six tests from failing to passing, all of them queries that had been silently returning nothing. #246equals list is an any-of set rather than one literal string, notEqual and notContain count null rows, and contains/startsWith match the platform's casing rules. #237ISBLANK-style checks against a blank Text or an empty Lookup evaluate to true, while the same comparison on a Date or Number is undefined — so IF(Lookup__c == null, 'New', TEXT(Lookup__r.Field__c)) returned blank instead of taking the true branch. #239: belongs to the bind expression existed in three hand-written copies that drifted apart, and each reported failure taught only some of them — one fix turned a loud SQL error into a silent zero-row answer. There is now a single implementation, checked in CI against the Apex grammar itself, so a bind operand wrapped across lines by a formatter is read the same way everywhere. #242 #224where, select, limit, and, in or seven others compiled locally and failed on the first deploy. Each was verified against the platform in both directions — order, offset, with, count, first and last read like keywords but are legal, and are not rejected. #94Report, Dashboard, Schema.Location and ProcessDefinition are provisioned rather than resolving to null. #129 #162 #232 #236ORDER BY ties break on Id. Rows tied on the sort key came back in whatever order the query plan produced, so a suite could pass a query's ordering in isolation and fail it under load. The platform resolves those ties by creation order. #234checkPermission resolves a permission named with or without its namespace prefix. #145 #166List.contains/indexOf preserve the runtime numeric type. JSON.deserialize produces typed Time values, and newSObject(null, true) populates the platform's implicit defaults. #96 #101 #113 #98x.Parent__r) as its custom object rather than a generic row, so the intended overload is chosen. #70--exclude accepts glob patterns, and the config key works. --exclude '**/dist/**' now spans directories; a trailing slash means the directory and everything in it; plain substrings behave as before. nimbus.test.exclude existed in the schema but was never read — it now works and merges with the flag, and because nimbus.properties is also read from your home directory, it can be set globally. #216Test.testSandboxPostCopyScript is implemented. It was the only entry point for testing a SandboxPostCopy implementation, so those classes could not be covered locally at all. runApexClass is invoked synchronously with a SandboxContext carrying the supplied ids and name. #227nimbus validate rejects a nested subclass that hides a visible private method. The platform refuses to save it; locally it compiled, so the rejection arrived on deploy. #235Decimal.valueOf(1).divide(3, 33) returned 9E-15 and divide(3, 20) returned digits of the 64-bit integer limit rather than a quotient; 2.345.setScale(2, HALF_EVEN) rounded the wrong way because 2.345 has no float64 form; 1.0/3.0 stopped at 16 digits where the platform gives 33; 1E+3 rendered as 1000; and a trailing zero was lost through JSON.serialize. All eight rounding modes now compute exactly, division carries 33 significant digits, and scale survives rendering and serialization. Double keeps IEEE semantics — that is its platform behaviour, and the Decimal/Double distinction is preserved. #217TypeException. Casting an Object-held value to an incompatible type silently succeeded, so code that fails in production passed locally. (Integer) someString, (String) someInteger, (Contact) accountRecord, and the boxed narrowings (Integer) objectHoldingDecimal and (Integer) objectHoldingLong now raise the platform's Invalid conversion from runtime type X to Y. Legal shapes are untouched: direct narrowing, widening, Id/String interchange, upcasts, null casts and downcasting a generic row to its concrete type. #218USER_MODE under System.runAs. Access was derived from an allow-list, so any object absent from it read as denied rather than unknown — twenty objects a non-admin can read on the platform, including User, Profile, PermissionSet, RecordType, ContentDocument, Attachment, AsyncApexJob and ApexClass, failed with sObject type is not supported. Object-level read is granted for these; field-level security still applies, so negative FLS tests keep failing for the right reason. #78HAVING clause was never substituted and reached Postgres literally, while the same bind in WHERE worked. A bind operand combining an index with a method call — :entries[0].getValue() — was scanned only as far as the index, stranding the rest in the generated SQL. And relationship aliases in a FROM clause survived into SQL as schema qualifiers, so even SELECT Id, c.LastName FROM Contact c failed. #223 #224 #128Set<SObjectField>.addAll() deduplicates against add(). The two methods keyed elements differently, so the same field token added through each landed twice. A SELECT list built from such a set then carried a duplicate projection, which the duplicate-field check rejects — one reported suite saw 129 failures from this single cause. #222Id as a distinct static type. With both a String and an Id overload in scope the String candidate always won, because Ids are stored as strings and the static type was gone by the time resolution ran. Lookup fields, elements of an Id collection and call results now keep that type. In one compatibility project this alone moved 34 tests from failing to passing. #211JSON.deserializeStrict behaved exactly like the lenient variant for Apex class targets, accepting unknown fields — the one thing it exists to reject; it now throws, descending into nested members and collection elements. Deserializing a map preserves the document's key order and original casing rather than returning lexically sorted, lowercased keys. And malformed input inside an object reports the platform's wording. #210 #141 #221FORMATDURATION and TIMEVALUE match the platform. FORMATDURATION was missing entirely; its two forms also differ — a seconds count renders unbounded hours (86400 is 24:00:00) while the difference between two Datetimes grows a days component (1:00:00:00). TIMEVALUE required a fractional component the platform requires, kept the milliseconds it had been discarding, and reads those digits as a millisecond count — .5 is 5ms, not 500. A formula must also now be a single complete expression: This will not compile!!! was parsing as an identifier and being accepted. #127 #183isDefaultedOnCreate for both Text and AutoNumber, and an AutoNumber name is correctly non-writable. Custom objects expose their generated __Share child relationship, except under master-detail where the platform has none. Five standard objects that ship no bundled describe — BusinessProcess, CaseStatus, ContractStatus, PartnerRole, SolutionStatus — resolve instead of evaluating to null, and the type check and getGlobalDescribe now read one source so they cannot disagree again. #181 #197 #212String.valueOf of a Map or Set renders sorted — case-sensitive for text, numeric for numeric keys — while iteration stays in insertion order, which is what the platform does. Set<Integer> no longer leaks internal type tags into that output. Date.format() returns the locale form (3/5/2024) instead of ISO, and Date.parse round-trips it. A null element in String.join renders as empty rather than the text "null". #219 #220Database.QueryLocator compiles as an Iterable but the platform fails on for (SObject s : locator) with an internal fault no catch block stops. That is reproduced faithfully rather than as a catchable exception, because a catchable one would let the guard pass locally and die in production. Assigning the iterator directly, with or without a cast, works. #188Test.createStubQueryRow are read-only. Assignment now raises Record is read-only, and the marker survives clone() and deepClone(). #178 #187 in a custom label, or a bare ampersand in a picklist value, made the XML decoder reject the file — and because labels live in one aggregate file, a single entity silently resolved every unrelated Label.X to its own key. Both are now read the way the platform reads them, and a label that stays malformed costs only itself. #177 #200ORDER BY ties break on DeveloperName. Tied rows kept load order; the platform falls back to DeveloperName, which is also the order getAll().values() returns. #176Trigger.newMap is null before insert. The map is keyed by Id and the records have none yet, so the platform withholds it — nimbus built an empty map, turning a null-reference failure into a silent miss. Trigger.new is still populated. #190Test.createStub rejects inner Apex types as the platform does, and a rank-1 array of a generic keeps the generic as its element type so List<String>[] and List<List<String>> agree. #214 #213FeatureManagement.checkPermission with a literal name is a static edge and a computed one is recorded at runtime; only names matching a declared permission count, since an unknown name returning false is not a dependency. Static resources are reached through Test.loadData, PageReference.forResource and exact-name queries — enumerating queries deliberately record nothing, so the unread signal stays meaningful. Both kinds carry real file paths, so double-click opens the source.WITH HIGHLIGHT and WITH SPELL_CORRECTION. Both clauses previously failed to parse. #146put() on CreatedDate, IsDeleted, LastModifiedById and the like now throws Field X is not editable, and a read-only compound parent such as Contact.Name or MailingAddress reports the platform's distinct Invalid field X for Y instead. Reported against a Change Data Capture event, but nothing enforced this for any object. Id stays assignable — describe calls it non-createable, yet Apex permits it and mocking frameworks depend on that. #158getLineNumber() is -1 and getStackTraceString() reads External entry point. A custom exception keeps its type and message; only a failure no catch block could have handled arrives wrapped as System.AsyncException, carrying the original serialized into its message. #153ContentVersion keeps its platform-managed lifecycle fields. A new document's first version gets VersionNumber and IsLatest; adding a version to an existing document numbers it and demotes the previous latest, which previously never happened at all. #139instanceof follows interface inheritance transitively. A value typed as a child interface did not satisfy the parent it extends. Not stub-specific — the same check serves ordinary objects, and a plain class had the identical problem. #130addError carries field metadata through to Database.Error. The field overload's token survives, and a component field reports against its compound parent the way the platform does — an error on FirstName comes back as Name. #136 #148ProcessInstance and DuplicateRecordItem are described. Both objects were absent from the schema entirely, so approval child relationships reported none and field describes failed. A field can also be inherently non-updateable regardless of permissions: isUpdateable() consulted only field-level security, so a non-reparentable lookup reported true for anyone who could edit the object. #143 #147EmailMessage.Status defaults to Draft, and Entitlement.Status is derived from its active window — Expired once the window has ended, Active inside it, Inactive before it starts or with no dates at all. Both land before before-insert triggers run, so a trigger can still override them. Also adds EmailMessage.IsExternallyVisible, which was missing from the schema. #157 #163$Api server-URL globals resolve in Flow formulas. $Api.Enterprise_Server_URL_360 and its partner equivalent evaluated to nothing, so any condition referencing one silently compared against null. Host and org identity stay simulated, matching what the interpreter reports elsewhere. #150nimbus graph Label.Order_Error answers who reads a label: the classes that name it in source, everything reaching those classes, the tests among them — and, after a coverage run, the readers no source search can find. Nimbus executes your Apex, so when System.Label.get('', 'Order_Error_' + status) runs, the name is resolved at that moment and the read is recorded exactly. The overview counts labels nothing in Apex references; because Flows, LWC, Aura and formulas are not read, that means unreferenced from Apex — the output says so rather than letting it read as a delete list.__mdt type whose records name handler classes — a path that used to reach its handlers with no reference to follow, and the single case the graph's own limits called out as structurally invisible. nimbus graph Handler_Config__mdt reports who queries a type and which classes its records dispatch to (record values are matched against your classes before an edge is drawn), and Type.forName resolutions are recorded at runtime on coverage runs, so dispatch through a computed class name shows up too. Coverage edges the new chains explain stop being reported as unexplained: on NebulaLogger, unexplained runtime-only edges dropped from 110 to 78.label: / mdt: prefixes narrow the search to one kind. Tooltips carry a label's value and reader count, and a type's record and querier counts. Exports keep the class topology by default; --include-labels and --include-metadata opt metadata nodes into dot and mermaid output, and the JSON payload always carries everything.''' is mandatory — inline content is a compile error on the platform and is rejected here too — that leading newline is stripped while the trailing one before the closer is kept, escape sequences apply inside the body, and a lone quote is literal. #140LastModifiedDate — it was moving backwards. Delete stamped the transaction's start time, so inside a test transaction a deleted record carried a timestamp earlier than its own insert. Undelete and bulk delete had the same flaw. Code ordering by LastModifiedDate was getting a value that had moved the wrong way, not a stale one. #121ApexPages.Message keeps its severity. The documented new ApexPages.Message(ApexPages.Severity.X, ...) form silently produced an empty severity, and getSeverity() now returns the enum itself so it compares equal to the constant it came from. #134getLabelPlural() returns a value instead of null. The plural is derived from the API name with the platform's own defaulting rules — consonant-y becomes -ies, sibilants take -es, custom-object suffixes stay outside the plural. #97Assert.isNull() on them stops failing on a technicality. Named relationships keep their names. #122Unable to retrieve feature parameter X. No results found. #120Map deserialized from JSON keeps the document's key order and original Id casing. Both were lost structurally, so keySet() came back lexically ordered and lowercased. deserializeStrict into Map<Id, SObject> also materialises real SObjects — verified against an org rather than assumed. #141 #142JSON.serialize emits SObject fields in assignment order with attributes first, matching the platform instead of alphabetising. Completes the round-trip work that fixed field casing: queried columns and later assignments trail alphabetically, exactly as before. #138JSON.serialize of a bare Schema.SObjectField (or a list of them) now says Apex Type unsupported in JSON: ..., capital T included. #137System.Url is implemented. Constructing and inspecting URLs previously failed outright. Apex wraps Java's URL, so the conventions are Java's and are easy to get wrong from first principles: an absent port reports -1 rather than 0, an absent query or fragment is null rather than an empty string, and getFile() is the path plus query. Relative construction against a base URL, toExternalForm(), and the platform's rejection message for an unparseable spec all match. #109Double.toString thresholds — plain while the magnitude sits between 10-3 and 107, scientific outside it, always with a fractional digit — and Decimal deliberately does not share them. The same value prints as 1.0E7 from a Double and 10000000.0 from a Decimal; a Double zero is 0.0 where a Decimal zero is 0. Fixing that surfaced a deeper problem: comparisons such as Assert.areEqual(150, someDouble) were passing only because both sides happened to render identically, and fell back to comparing text. Integer against Decimal or Double now compares by value. #104System.NullPointerException: message, everything else as MyException:[]: message. Which form applies is not derivable from the name — IllegalArgumentException, UnsupportedOperationException and InvalidHeaderException are platform types that nonetheless use the second form, each confirmed against an org. #114new String[][]{ ... } failed to parse at all, while String[][] m; as a declaration and m[0][1] indexing already worked. Now N-dimensional, and String[][] interoperates with List<List<String>>. #116\p{Lower} and its twelve siblings made Pattern.compile throw outright rather than match differently — a total failure rather than a subtle one. Both the plain and negated \P{...} forms work; real Unicode categories such as \p{L} are unaffected. #110SELECT Id, Id FROM Account now raises QueryException naming the field in its canonical API casing. Aggregates, aliases, TYPEOF blocks, subqueries and FIELDS() are all excluded, and detection compares spellings exactly — matching the platform's case-insensitivity would have rejected queries that real applications run successfully. #125getRecordTypeInfos(). Standard value sets such as OpportunityStage, LeadStatus and TaskStatus reported a synthetic Master entry. Objects that can have record types, and every custom object, are unaffected. #133SobjectType shadows the universal property. On QueueSObject, ObjectPermissions, FieldPermissions, ListView and RecordType, the real field wins outright — assigning it to a Schema.SObjectType is a compile error on the platform, so the universal property is genuinely inaccessible there. #124SObjectField token from the wrong object reports which object it came from. new Contact().get(Account.NumberOfEmployees) now says Account.NumberOfEmployees does not belong to SObject type Contact instead of reporting the field as unknown — which sent you looking for a typo in a name that was spelled correctly. Applies to put as well. #132getPopulatedFieldsAsMap preserves constructor assignment order instead of alphabetising. #131Label.get and Label.translationExists previously returned the label name (or false) when a label could not be found, so a typo produced plausible-looking output rather than a failure. Both now raise InvalidParameterValueException naming the label alone; a null namespace means the default namespace and is accepted. #135 #112NoDataFoundException when never provisioned rather than returning the identity value for their type, which hid missing-parameter bugs entirely. Values written by a setter are still readable. #120JSONException. A declared Map<String, Date> also dropped its value type altogether, so entries came back as Strings with no error at all. #95System.Location returns a Location with null components rather than null itself. #123Decimal and half-up for Double — a v1.11.0 regression. The v1.11.0 rounding fix was right for Decimal but applied to the Double overloads too, and left Decimal.round(RoundingMode) ignoring the mode it was handed. Apex resolves these overloads from the static type: Math.round on a Double is half-up (Java's floor(x + 0.5), so ties break toward positive infinity and -1.5 gives -1), while on a Decimal it stays half-even. A numeric literal with a decimal point is a Decimal, which is why literal-based checks never exercised the Double path. Carrying the distinction means a declared type now survives assignment, casts and arithmetic — and, as on the platform, Double combined with Decimal widens back to Decimal. #201finally runs when the exception propagates to an outer catch. Cleanup that resets statics, releases flags or flushes logs was skipped entirely whenever the exception was handled further up the stack — a core control-flow guarantee. #88Map.put now returns the previous value instead of nothing, so Integer old = m.put(k, v); compiles and runs; Set.add returns whether the set changed, restoring the if (seen.add(id)) dedupe idiom that previously let every duplicate through; and List.remove with an out-of-range index throws ListException rather than silently doing nothing. #203 #204 #205FinalException. One of the most common Apex mistakes was invisible locally: the loop iterated a snapshot and completed. As on the platform the exception is uncatchable, so no defensive catch masks it. #202String.compareTo returns the character delta rather than a normalised -1/0/1 ('abc'.compareTo('ABC') is 32); substring throws StringException on an out-of-range index instead of clamping, so index bugs surface locally instead of only in production; and intValue() truncates to 32 bits. #83 #84 #91Decimal on the platform, so 250 / opp.Amount gives 0.25 where it used to truncate to 0. The same now holds for static and instance fields. An inexact quotient also renders its digits instead of collapsing to 0, and Decimal.scale() keeps trailing zeros. #92 #103String.format stops eating apostrophes. Apex implements only half of Java's MessageFormat quoting: '' collapses to one quote, but a lone quote is an ordinary character and placeholders keep substituting around it. String.format('It\'s {0}', ...) returns It's x, not Its {0}. #93JSON.serialize emits SObject fields in their canonical API casing ("Name", not "name"), and a number from JSON.deserializeUntyped now answers instanceof Integer, Long, Decimal and Double the way the platform's untyped value does. deserializeUntyped also parses the first complete value and ignores trailing text, and malformed-JSON errors name the offending token and its input location. #85 #86 #105 #118JSON.deserializeStrict is actually strict for SObjects. An unknown column now raises JSONException instead of being silently dropped. #119Pattern.matches is anchored to the whole input, so format checks no longer accept partial matches; Math.sqrt of a negative and asin/acos outside [-1, 1] throw MathException; NaN == NaN is true, as Apex has it rather than IEEE; a dangling $ in a replaceAll replacement throws; and String.hashCode is Java's, wrapping at 32 bits and case-sensitive. #207 #208 #209 #108Exception(message, cause) constructor records the cause so getCause() can walk the chain, and an unmatched enum name — including null — raises NoSuchElementException with the platform's wording instead of returning null or a type error. #87 #115String.join accepts any Iterable, and Type.newInstance reports a missing no-arg constructor. Joining a class that implements Iterable<T> returned an empty string rather than its elements; instantiating a class with no zero-argument constructor returned an object whose constructor never ran, and now throws TypeException. #102 #107getSObjects distinguishes an unknown relationship from an unqueried one. A name that is not a relationship raises SObjectException; a real relationship that simply was not queried still returns null. #111exec scripts work, and escaping covers backslashes. A local enum in an anonymous block no longer fails on first use, and String.escapeSingleQuotes escapes backslashes as well as apostrophes. DateTime.valueOf also accepts the platform's all-zero sentinel strings. #206 #100 #117nimbus validate catches two more save-time rejections. Reserved identifiers (inner, cast, group, join, outer, package and others) and classes extending Exception without an Exception suffix are both rejected by the platform at save time and were accepted locally — code that ran green here failed its first deploy. The reserved list is verified against a live org and deliberately excludes ordinary keywords, which the parser already rejects, and names such as offset, row, share and view that the platform genuinely permits. #94Math.round, Math.roundToLong, Decimal.round() and Decimal.setScale without an explicit RoundingMode all rounded .5 ties away from zero; Apex uses banker's rounding for every one of them. Verified live: Math.round(2.5) is 2, (3.5).setScale(0) is 4, (-2.5).round() is -2. Financial code that lands on exact .5 boundaries now computes the same numbers locally as in production. Explicit RoundingMode arguments keep their meanings. #71 #72Integer is a two's-complement 32-bit int: 2147483647 + 1 is -2147483648, silently. Nimbus kept growing past the boundary, so hash-mixing, checksums and overflow-sensitive comparisons diverged. Long arithmetic is unaffected, and Long-producing APIs such as Datetime.getTime() stay 64-bit. #73Date.addMonths and addYears clamp to the end of the target month. January 31 plus one month is February 28 (29 in leap years) — never March 3. The same rule now applies to negative offsets, to Datetime variants (time of day preserved), and the formula-engine ADDMONTHS follows its documented last-day rule. End-of-month billing and renewal logic no longer drifts into the following month. #74List.sort() places nulls first and orders numbers numerically. The default sort put nulls last and compared numeric elements as text, so {10, 9, 2} sorted to {10, 2, 9}. Both now match the platform, dates sort chronologically, and — verified live — a supplied Comparator receives the null elements and decides their position itself. #75Boolean in a condition throws NullPointerException, as on the platform. if (nullFlag) silently took the else-branch locally and crashed in production — the dangerous direction for a local runner. All condition positions now throw (if/while/for/ternary/!/&&/||, short-circuiting preserved), and null operands in arithmetic throw a catchable NPE. Fixing this exposed a second, mutually-masking divergence, fixed together: custom metadata checkboxes omitted from a record's XML now read back false — a deployed checkbox is never null. #76String.valueOf(Decimal) uses scientific notation where the platform does. 0.0000001 renders as 1E-7, matching the BigDecimal rule Apex inherits; toPlainString() remains the plain-format API, trailing zeros are preserved per scale, and toString() no longer prints large values like 1.234567e+06. #77private static Account held = [SELECT ... LIMIT 1]; assigned the raw list, so a later held.Name = ... failed with UNIMPLEMENTED_ASSIGNMENT — the last member of the family fixed in v1.10.1. #66record.Parent__r straight into a method reported No matching overload even with exactly one matching signature; binding it to a local first worked. The expression's type is now resolved from the relationship's schema target. #70System.runAs. Group and UserRole queries with WITH USER_MODE or WITH SECURITY_ENFORCED failed as a restricted user with sObject type not supported — on the platform these are readable for any user, so static caches built on such queries no longer explode order-dependently. #78SELECT (SELECT Id FROM Children__r) FROM Parent__c — the "grab the parent just for its children" fixture shape — failed in test context with a mangled query; the platform's implicit Id is now returned. #79WHERE Id IN :(Set<Id>) new Collector().of(rows).getSet() leaked the expression into the generated SQL; the whole constructor-plus-chain operand is now captured and evaluated. The simple-cast form fixed in #25 keeps working. #80Parent__c alongside Mid__r.Parent__c) failed with column reference is ambiguous; base-table references are now qualified while SOQL aliases stay untouched. #81COUNT() queries under WITH USER_MODE work for users on materialized profiles. The FLS check treated the aggregate itself as a field name, which only profiles with default CRUD forgave — so every filtered count failed for permission-set-driven users on profiles resolved on demand, while the same query passed on Standard User. Aggregates no longer masquerade as fields, and COUNT(Field) checks the field it aggregates. #82Get Records shapes. An element with no filters, and one whose filter compares a field against $Record with anything other than equality (not equal to, greater than, contains, …), each ran one query per record instead of one per batch — a 200-record insert through such a flow burned 200 SOQL queries and blew the limit on work the platform does in one. Both now run once for the batch. The non-equality form has no IN-clause equivalent, so the batched query fetches the superset its literal filters allow and each record narrows it to its own matches; results are identical to the per-record path.Limits.getQueries() untouched — in every form, static or dynamic, with ORDER BY, WHERE, COUNT() or getAll() — while an ordinary query beside it increments by one. Nimbus already exempted static SOQL, but Database.query on a custom metadata type and both flow Get Records paths each charged one, so the same query cost differently depending on how it was written. Configuration-driven code that reads custom metadata on every path no longer inflates a suite's apparent SOQL usage.nimbus exec now reports unresolved types. A type Nimbus cannot resolve becomes null, and the script usually dies later with Attempt to de-reference a null object naming nothing. nimbus test has always ended with a block listing those references; exec stayed silent. It now prints the same block — including when execution fails, which is exactly when it explains the failure.[SELECT …] to a member typed as an SObject left a List<SObject> in the member — silently, with no error at the assignment. The corruption only surfaced later, wherever something called an SObject method on that member, as Unknown List method pointing at code that was perfectly valid. Local variables already coerced correctly; fields, auto-properties and static fields now do too, for both static SOQL and Database.query. Collection-typed members — List<T>, Set<T>, Map<K,V>, T[], Iterable<T> — keep the whole result, as before.this.record = [SELECT …]; this.record.Name = 'x'; failed with an internal assignment error, while the same code against a constructed SObject succeeded. Re-querying a record into a member and then patching it before update is a common fixture-builder shape; it now behaves the same whichever way the member was populated.SELECT COUNT() returns the row count on seeded setup objects. On objects Nimbus serves from its built-in setup seeding — UserRole among them — a count query returned the records instead of the number, so Integer c = [SELECT COUNT() FROM UserRole] ended up holding an sObject. Counts now come back as an Integer on every object.FieldDescribeOptions is recognised by its bare name. field.getDescribe(FieldDescribeOptions.DEFAULT) — the describe-heavy idiom the platform recommends for skipping expensive picklist and reference materialisation — threw a null dereference, because the enum resolved only when spelled Schema.FieldDescribeOptions. Apex resolves it through the implicit Schema namespace, and so does Nimbus now. Both DEFAULT and FULL_DESCRIBE return the same describe result.decomposeCustomLabelsBeta2 gets one <Name>.label-meta.xml file per label and no aggregate file at all — and every Label.* reference in it silently fell back to the label’s own name, turning assertion failures into a hunt for a product bug. Both layouts now load, and a decomposed file is picked up on save like any other metadata change.objectSettings/ fragments. Under decomposePermissionSetBeta2 the parent file keeps only the header while the object and field grants live in per-object fragments. The permission set was seeded but its grants were not, so a user holding it failed WITH USER_MODE queries with sObject type … is not supported — reading like a schema gap rather than a permissions one. The fragments now merge into the parent, as the Metadata API does on deploy.ContentNote can be inserted. Creating a note threw Attempt to de-reference a null object, taking down every fixture that attaches notes to records. ContentNote is now a first-class sObject: the insert stores the note, derives TextPreview from the content along with the note file type, and returns an Id carrying the ContentDocument 069 prefix that works directly as ContentDocumentLink.ContentDocumentId — the standard attach-a-note-to-a-record pattern.nimbus graph no longer mistakes variables for class references. A class that shares its name with a common variable name — Account, User, Logger — collected a dependency edge from every method that merely declared such a variable, inflating its dependents count and distorting the graph around it. Apex resolves a bare identifier to the variable when one is in scope: the variable shadows the class, and the Schema. prefix exists precisely to reach the SObject past that shadow. The graph now applies the platform’s own resolution rule, so dependents counts reflect actual references. Surfaced by a public logging framework whose test suite deliberately ships empty SObject-named classes as a qualification tripwire — their correct dependent count is zero, and now it is.Integer a = Util.calc(), b = 2; the initializers were invisible to the dependency graph — a reference that only appeared in a combined declaration produced no edge at all.Run class, Bench class and Coverage on a test class — and Mutate on a production class — were anchored to the first line of the file instead of to the class. In the ordinary Apex layout, where a file opens with a doc block or a license header, that put the buttons inside the comment, and collapsing the comment took them with it. They now anchor on the class line, so they stay visible whatever precedes the declaration.Id.getSObjectType() now resolves OrderItem and OpportunityLineItem Ids. Both objects have shipped with full schema support for a long time, but their Id key prefixes (802 and 00k, verified live against the platform) were missing from the prefix table — so resolving an sObject type from one of their Ids came back empty while the same code worked for Order or Opportunity. Line-item Ids now resolve like every other standard object.nimbus explain. When a test fails, nimbus explain assembles the evidence into one versioned failure contract: the assertion, the code path that produced it, the data it saw, and — new in this release — a comparison against the last run where the same test passed, so "what changed" is an answer instead of an archaeology project. The same contract is served over MCP, so an AI agent debugging your suite reads exactly what you read.nimbus triage. A failing run with hundreds of red tests usually has a handful of causes. nimbus triage clusters failures by source-assigned diagnostic codes and ranks the clusters, so you fix the cause with the biggest blast radius first instead of scrolling the list top to bottom. Unresolved managed-package types are reported as run-level evidence — one line naming the missing stub, not five hundred identical stack traces. Available over MCP through the same construction path as the CLI.nimbus graph. Reachability queries over your Apex source: what depends on this class, what does it reach, where are the cycles. Scope by depth, export the result (--out writes Mermaid with a Markdown wrapper you can actually open), or explore it interactively in the Dev UI, VS Code, and IntelliJ — all three hosts render the same graph. Agents get the same answers through the query_graph MCP tool. The output is honest about its one structural blind spot: a static graph cannot see trigger dispatch, and says so rather than pretending completeness.nimbus doctor --suite. The doctor now examines your test suite itself: flake suspects backed by recorded run provenance, tests whose duration dominates the wall clock, and isolation hazards — the structural debt that makes a suite slow and untrustworthy, surfaced before it costs you an afternoon.nimbus fixture now generates required-lookup parent records automatically and fills fields with schema-valid values — a fixture for a child object arrives with the whole parent chain it needs to insert cleanly, matching how the platform enforces required relationships.nimbus history. Every run records the conditions it executed under — engine version, isolation mode, parallelism, schema fingerprint. A flake claim can now be checked against evidence: same test, same conditions, different outcome — or an environment difference that explains everything.NIMBUS_LICENSE_KEY register one stable machine per CI provider (named CI — GitHub Actions and the like) no matter how many ephemeral runners execute, and AI-agent sessions register the machine they run on — so the machines list in your portal finally reflects where your license is actually used. Registration is automatic, best-effort, and never delays or fails a run.Quote and QuoteLineItem standard objects. CPQ-adjacent test suites that touch the standard quoting objects now run without stubs.--impacted now forces a full run when a trigger changes. Impact selection maps changed classes to the tests that reach them — but trigger dispatch is invisible to a static graph, so a changed trigger selected zero tests and reported a green run against a change nothing had executed. A trigger change now runs the full suite, because a selection that cannot see the change must not claim to cover it.NullPointerException now carries a source location. An NPE thrown by the interpreter arrived with no file and line, leaving you to bisect by hand. It now points at the dereference that threw, like every other exception.nimbus login.nimbus login recognises the situation and signs you straight back in instead of insisting you are already signed in. nimbus login also finally exits non-zero when sign-in fails, so scripted setups can tell.nimbus release and nimbus deploy (Pro). Validate a change set locally, then deploy it to your org, with a signed receipt that records exactly what ran. nimbus release plan reconciles your source against the org read-only, so you can see what a deploy would change before it changes anything; changed-set releases enforce drift, refusing to deploy against an org that moved underneath you. nimbus release promote moves the same validated bundle through your environments unchanged, nimbus release rollback restores the org state a deploy overwrote, and nimbus release status / watch show the org's deploy queue and diagnose jobs stuck in Pending.nimbus release keygen. Production deploys can require a separate counter-signature, so the person who validates a release need not be the person who ships it. Release profiles fail closed: requireSigned refuses any unsigned receipt, and requireCodeAnalyzer refuses a deploy whose static-analysis gate never ran.sf code-analyzer (with a configurable rule policy), a real mutation-score gate, coverage, and a permission-model policy gate — each result is captured inside the signed receipt, so the evidence a release passed its checks travels with the release itself rather than living in a CI log that outlives nothing.nimbus assurance (Pro). A self-hosted, read-only web view of every release's signed evidence for the whole team, including the people who never open a terminal — release managers, auditors. Per release it shows who validated it, who deployed it, which checks passed, whether it drifted, and — verified live in the browser — whether the bundle and signatures are intact. Add --token to share it beyond localhost. Your code and receipts never leave your infrastructure; the console reads local receipts, never contacts a Salesforce org, and never deploys. For auditors without Nimbus, nimbus release export produces a portable evidence bundle that nimbus release verify-export re-verifies offline.deploy and release wrappers, and nimbus sf ... passes commands straight through to the Salesforce CLI.AsyncApexJob and CronTrigger Ids came from per-test counters that restarted at 1, so under parallel execution two concurrent tests that enqueued an async job inserted the same primary key into the shared job table. Postgres parks a duplicate-key insert on its unique index until the first inserter's transaction ends — and a test holds its transaction for its whole run — so a worker silently stalled for the remainder of an unrelated test and long suites blew the test timeout with spurious "possible infinite loop" failures. Ids are now process-global, parallel pass/fail matches serial exactly, and large suites run measurably faster. (#9)nimbus.governor.dml-statements override, so a lifted cap still threw Too many DML statements: 159 while Limits.getLimitDmlStatements() correctly reported the raised value. Both paths now go through the same effective limit. (#50)Number(18,0) field now accepts fractional values, matching the platform. Declared scale is display-only on Salesforce: verified live, both Test.loadData and plain DML store a value like 2995.2 in a scale-0 field untouched and read it back unrounded. Nimbus had typed the column as an integer, so a single fractional CSV value failed the entire load with a database type error. Scale-0 Number columns now keep 18-digit integers exact while accepting fractions. Existing project databases keep their old typing until a nimbus reset. (#57)Schema.ChildRelationship's isCascadeDelete / isRestrictedDelete / isDeprecatedAndHidden failed with Unknown method; they now dispatch and carry org-verified values. And the property forms of describe getters (fr.type, cr.childSObject, fr.referenceTo, …) returned null while the equivalent getter methods worked — a missing property now delegates to its getter, so the two forms can no longer diverge, matching the platform where every describe getter is also a property. (#60, #61)INVALID_CROSS_REFERENCE_KEY. Nimbus now validates non-namespaced custom-lookup values by Id key prefix and throws the platform's DmlException (surfaced as a row-level Database.Error under partial-success DML). An Id whose type Nimbus can't resolve is still accepted, so org-real Ids and stubs never produce a false rejection. (#62)nimbus validate now catches 233 of 235 org-rejected error classes in its 294-case conformance corpus, up from 178 of 182. The new checks cover inheritance and override contracts, declaration scopes, annotation placement and arguments, parameter modifiers and duplicates, interface implementation visibility and return compatibility, conflicting sharing modes, invalid generic array shapes, direct construction of the platform Exception type, and source-backed custom object and field resolution. All 59 legal controls remain accepted, and validation continues to report zero intrinsic false rejects across 3,350 deployable open-source programs. The two deferred cases depend on managed-package compilation context that is not present in ordinary project source.@nimbus-solution/nimbus-sf-plugin and run tests, validation, anonymous Apex, mutation testing, local servers, and account commands through sf nimbus .... The plugin reuses the existing Nimbus login, automatically downloads the correct native runtime when needed, verifies it against the release checksum, and preserves native output and exit codes. Salesforce-style aliases such as sf nimbus apex run test and sf nimbus apex validate are included.nimbus validate now catches 178 of 182 org-rejected error classes in its expanded conformance corpus. The new checks cover SOQL and SOSL compile rules, switch statements, constructor chaining, interface and inheritance contracts, illegal modifiers across classes and members, annotation placement, REST and Invocable signatures, generic type shapes, enum declarations, and property accessors. Every case is pinned to a real Salesforce check-only deploy, and the four deferred cases require complete synced-schema or managed-package context rather than another context-free language rule.nimbus validate no longer rejects legal generic forms such as List<void>. Salesforce accepts this unusual type even though raw collections, wrong generic arity, and generic scalar types remain compile errors. Nimbus now preserves that distinction instead of treating all unusual generic forms alike.nimbus sync --include-permissions pulls your org's permission model. Permission sets, their object and field grants, and permission set group composition are pulled from the connected org so WITH USER_MODE and System.runAs reflect how access is actually granted. Most orgs carry no object CRUD on profiles, so a test that builds a user, assigns a permission set or group, and then queries in user mode depends entirely on this. Permission sets defined in your own project metadata still take precedence.nimbus doctor reports permission sets your tests reference but nimbus can't resolve. A permission set group whose contents are unknown leaves access unrestricted rather than denied — safer than failing a test that passes in the org, but it means a user-mode assertion can pass for the wrong reason. That is now named explicitly, with the sync or retrieve command that closes it.CASESAFEID(Parent__r.GP__r.Id) came back blank even with the whole chain populated, while a single hop worked — so an (Id) cast on the result threw System.StringException: Invalid id:, and code that merely read such a field silently took the wrong branch. Relationship paths of any depth now resolve to the traversed value. (#56)"0". A formula assembled from concatenated IF(...) branches that all evaluated blank produced the string "0" — non-null, non-blank garbage that slipped past != null and String.isBlank() guards and then failed far from its source. The + operator is concatenation on Text and addition on Number, and with two blank operands there is no value to tell them apart; operand types are now resolved from the formula itself. Number formulas keep the platform's blank-as-zero behaviour. (#59)Test.loadData generates fresh record Ids instead of inserting the CSV's. On the platform the ID column is only a correlation key used to wire relationship columns across resources; every load receives new Ids. Nimbus inserted the literal value, so two resources for the same object sharing a row Id collided on the primary key — a pattern that runs daily in real orgs. Relationship columns still resolve across resources through the correlation. A deterministic conflict of this kind is also no longer retried as, or blamed on, a parallel-test conflict. (#57)WITH USER_MODE enforces object permissions. A System.runAs user on a Standard User profile could read any object in user mode, including custom objects the profile grants nothing on, so user-mode assertions passed regardless of access. The profile's real default footprint now applies: CRUD on core standard objects, and custom objects only where something explicitly grants them. In the other direction, access granted through a permission set group is honoured rather than denied. (#58)nimbus validate adds 15 more semantic checks — 57 of 57 error classes in its conformance corpus, up from 40, each verified against a real check-only deploy. New this release: interface conformance (a class must implement every method of an interface it declares, and implements must name an interface, not a class), impossible casts and always-false instanceof, incompatible comparisons, </> on Booleans, ! on non-Booleans and ++/-- on non-numerics, returning a value from a void method, @future methods that aren't static and void, more than one @InvocableMethod per class, inner classes that nest further or hold static members, and global members in non-global classes.List<Long> = new List<Integer>() deploys), numeric literals type contextually, narrowing casts and String/Id comparisons are allowed, and code after an infinite loop is reachable — validate accepts exactly what the platform accepts and rejects exactly what it rejects, holding a zero-false-reject record across thousands of deployable open-source classes.nimbus validate adds 19 semantic checks — every error class in its conformance corpus is now caught, 40 of 40, each verified against a real check-only deploy. New in this release: duplicate methods and variables, extending non-virtual classes, missing override keywords, unimplemented abstract methods, unreachable code, break outside a loop, ternary type mismatches, non-Boolean conditions, catching or throwing non-exceptions, DML on non-SObject types, iterating non-collections, @future parameter constraints, unknown enum values, unknown fields on your own classes, instance members referenced from static context, assigning void calls, and constructor arity. The corpus grows with every release and every rule keeps validate's record of zero false rejects across thousands of deployable open-source classes.nimbus validate now performs project-wide semantic analysis. Beyond the existing structural checks (unimplemented interfaces, non-writeable fields), validate resolves every type reference, variable, method call, and field access across the whole project and flags the same errors a Salesforce deploy would reject — undefined variables, unknown types, wrong argument counts, missing return statements, duplicate field assignments, type mismatches, and more. Findings are surfaced as deploy blockers by default (matching a check-only deploy), with --sema=warn to downgrade to warnings and --sema=off to disable. The semantic engine is validated against a corpus of 21 probe classes covering all known error classes from real org deploys, with zero false rejects.nimbus validate now detects unknown custom fields. A field reference like obj.Custom__c that doesn't match any field in the project's schema files or standard SObject definitions is flagged — so typos and stale schema references surface at validate time rather than at deploy. The check covers both explicit new SObject(Field__c = ...) constructors and dot-expression field access.node_modules directory — all required modules are bundled into the VSIX, so installation from the marketplace or a manual .vsix file works out of the box.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.