Incidents
The rules stand in AGENTS.md. This is the record of what was measured to arrive at them, in date
order — each entry keeps its date, its measured output and, where the first diagnosis was wrong,
the retraction. Nothing here is an instruction: where an entry and AGENTS.md disagree,
AGENTS.md is the current position and the entry is history. A new trap adds a rule with its
one-line consequence there and an entry here, never a section per incident there (Crm#100 — the
split ReleasedPlatformCi already uses for the CI history).
Two write-path traps measured on the first deploy (2026-08-28)
- The hub serializer drops default-valued members (
WhenWritingDefault). Aboolwhose safe mode istruecan never be switched off from a client (dryRun:falsenever arrives — henceexecute:true), and an enum member numbered 0 is never stored (state: Openvanished, so astate:Openquery matched nothing). Every CRM enum starts at 1; every opt-in is atrue; never filter a declared query on a field whose value may be the type default. - An unscoped
nodeType:query sees only TOP-LEVEL nodes. Every mesh-wide query carriesscope:subtree; a client-anchorednamespace:{client}query does not need it. Without it the board counted 7 clients and 0 deals.
Three more traps measured on the live portal (2026-08-29)
- The board drew "0 clients" over seven of them — because the query never asked for
content. ⚠️ This entry first recorded the WRONG cause ("a page that materialises foreign nodes renders once, empty, and never again"). That was a guess from the symptom, and it was wrong: the reads were fine.BoardLogic.Rowsfiltered.Where(t => t.c is not null)over rows whosecontentthe projection never requested, so every row was dropped. Same for the grey journey chips. The rule is the content-projection one inAGENTS.md(A synced read MUST ask for content); the board's move to declared queries is good practice on its own merits, but it was NOT the fix for this. - An enum can never be matched by its member NAME. It indexes as a number, so
state:Openanswers42883: operator does not exist: smallint = textand the view comes back EMPTY rather than red. Filter on a stored boolean (isOpen:true) —CrmQueries.FiltersEnumByNamewalks every shipped query against this inBoard/Test. - An
@@embed resolves against the page's OWN node unless it starts with/.@@("Crm/Portfolio/area/Board")on/Portfolioasks for an area namedCrmon hubPortfolioand renders "Area not found". Absolute:@@("/Crm/Portfolio/area/Board").
The migration that ran the previous build (2026-08-30)
Crm/Migration was then a compiled control plane — a request node that fired at
requestedAction: Run — and it was removed the same day, once the work was done. Three rules came
out of this one day and stand in AGENTS.md: A dry run shows the PLAN, not the RESULT — verify
the WRITE, A NodeType's self-report is not proof — exercise the change, and Do one-off data
work with a script, not a NodeType. This is what was measured.
The dry run approved it; the write was empty
Planning before writing is still right, and the all-client dry run on 2026-08-30 caught a document reference about to become a contact, and the tool offering a client's own DEAL as a promotion candidate. But the first real run still destroyed data the dry run had approved:
- The plan was right, the write was empty.
PearlTechnology/Commercials— a 7,000-word commercial offer — was correctly planned for promotion toCrm/Offer, and the promotion dropped its entire text.ContentAs<T>returns null for every markdown node on the portal, because stored content carries a$typediscriminator that a polymorphic deserializer cannot map onto a DIFFERENT target type. The dry run prints the plan; it does not build the node, so it cannot show you an empty one.
The four documents were recovered from version history (get_versions → restore_version). The
rest of the day is recorded beside the rules it produced, in AGENTS.md: the migration ran the
PREVIOUS build while compilationStatus: Ok and compiledSources == currentSourceVersions both
read healthy (A NodeType's self-report is not proof); forcing a compile and a recycle to make the
fix live broke Crm/Migration itself, and requestedReleaseForce was silently discarded one layer
down (Never force a compile or recycle); the four offers were finally promoted by
rbuergi/Script/promote-offers, carrying every body intact and verifying each one (Do one-off
data work with a script).
The stronger rule that followed, and the deploy that falsified it
⚠️ AGENTS.md said something stronger and it was WRONG. For a few hours on 2026-08-30 it told
you to compare the NodeType against its Release and, on a mismatch, to stop — the portal is not
running your change. Then the very next deploy falsified it: Crm/Offer had all three fields
mismatched —
NodeType latestAssemblyPath: Crm_Offer/v63-s13fb7c2-8c434fd42aa0.dll
compiledFrameworkVersion: s13fb7c25… compiledSources[CrmQueries]: …098599428010
Release assemblyContentPath: Crm_Offer/v25-s770d75c-bcfce1529bfe.dll
artifacts[0].frameworkIdentity: s770d75c… sourceVersions[CrmQueries]: …424514972670
— and was serving perfectly: PG3/FundReportingPilotOffer/area/Offer rendered its title, its status
pill and all 7,111 characters of its body. latestReleasePath lags routinely. A rule that halts
a deploy on that would halt every deploy, which is worse than the gap it was meant to close.
What survives is what AGENTS.md states now: compilationStatus: Ok plus
compiledSources == currentSourceVersions does NOT mean your code is running, no field on the
NodeType settles it either way, and the only check that held is BEHAVIOURAL.
Two layout traps measured on the live portal (2026-09-05)
A declared grid's breakpoints are ignored — declare
MaxColumns.MeshSearchControlinFlatmode lays its hits out as a CSS grid built fromMaxColumns/MinItemWidthalone;GridConfig(WithGridBreakpoints) contributes nothing but its spacing, and with neither set the grid packs as many 220 px cells as fit.ClientBars.ListdeclaredWithGridBreakpoints(12,12,12,12)for "one column" and/PartnerRedrew the deal cards three abreast with a 2 px gap. Pinned inClient/Test: a list isMaxColumns(1), a gridMaxColumns(3), noGridConfig.A
Controls.Buttonis not a chip. It is afluent-button, and its 32 px control, padding and type ramp live in the component's shadow DOM where an inline style on the host never reaches — so a "chip" is a full-height button with the chip's padding added around it. A chip is an anchor throughControls.Html(ChipBar.Anchor), sized byChipStyle.Cssalone, its free text HTML-encoded. Two more sizes that live in the module, not the framework: anoterides on a chip only as a qualifier (≤ 24 characters), and a chip is capped at 280 px with the full label in its tooltip — four sentence-noted Documentation chips had made one bar wider than its card.The body's
@@("area/Search")is the graph navigator, not a documents list. A client page that deferred to it rendered an ancestor rail, a drill-down affordance per card and every record again, under the real sections. The page now always declares its own catalog (ClientBars.Documents— the framework's search control overDocumentsOf, search box and title inline, standard cards, no hub per hit) and takes the embed and its## Contentsheading out of the rendered body (ClientLayoutAreas.BodyWithoutCatalog). The stored body is untouched.A control flag that defaults to
truein C# cannot be switched OFF over the wire — the sameWhenWritingDefaulttrap as the enums, one layer up.SortConfig.Ascendingisbool = true, so an explicitfalseequals the CLR default, is dropped, and re-reads astrue: the activity feed asked for newest-first and rendered oldest-first, silently. Fixed in the framework (MeshWeaver#2687); when writing a control flag, check the rendered payload actually carries it.