Repository navigation
probeInstallOrganizations answers a failed sys_organization read with a memoised empty list, so system-insert organization resolution fails open #9261
Description
Found by the #8901 census re-run over the read-seam scan roots at origin/main @ 739fe5b79 (2026-08-17). It is the single new member of the silent fall-through family beyond the #8845 census recorded in the gate script's header. Filed unassigned; not a claim. ⛔ No domain:* set — that is triage's field.
Where
packages/objectql/src/engine.ts, probeInstallOrganizations() (catch at ~line 3040 today). Introduced with the #8844 system-write organization-stamping work; on main since 2026-08-16 — i.e. authored while the family's previously filed instances (#8895 / #8896 / #8906) were being fixed.
The shape
let ids: readonly string[] = [];
try {
const rows = await this.find(ORGANIZATION_OBJECT, { fields: ['id'], limit: 2, context: { isSystem: true } });
ids = /* mapped row ids */;
} catch {
// `sys_organization` may not be registered at all (a lean embedding, a
// bare-kernel test). No organizations is the honest reading, and it is
// the branch that changes nothing.
ids = [];
}
this.organizationProbeMemo = ids;No log, no type discrimination. The comment names one benign reason, but the catch swallows every other failure the same way — connection loss, pool exhaustion, a timeout mid-boot. That is the #4728 signature the gate header records: a comment naming one benign reason standing in for a type test.
Consequence
resolveSystemWriteOrganization (packages/objectql/src/tenancy/system-write-organization.ts) maps the probe's answer: 0 organizations means "no-organization-yet" and the system insert proceeds without organization stamping; 1 means stamp the derived id; 2 means refuse with SystemWriteOrganizationRequiredError. A non-benign probe failure is indistinguishable from "no organizations yet" (ADR-0110 D3: outage and genuine emptiness are different facts), so:
- on a
singleposture with one organization, system inserts land unstamped where the System-context writes land untenanted at RUNTIME, so a single-tenant install keeps re-forking the autonumber scope and minting duplicate business identifiers — the producer #8686's backfill cannot reach (17.0.0 GA) #8844 ruling requires the derived stamp — forking exactly the per-organization counters/scoping the ruling exists to protect; - on a multi-organization install, the refusal the ruling mandates is silently skipped — fail-open on a refusal guard, the ObjectQL.cascadeDeleteRelations fails OPEN: a failed dependents probe skips the
restrictguard entirely, so a delete that should be refused succeeds silently #8895 shape.
Aggravator: the answer is memoised. One transient failure pins "no organizations" in organizationProbeMemo past the outage, until some organization write happens to clear it via invalidateOrganizationProbe().
Why no gate saw it
check:durability-log-level's read-seam rule judges the expression a catch returns; this catch returns nothing and lets the accumulator above the try answer. That is the #8845-measured blind spot, and this instance is what #8901 tracks — the disposition of the gate question lives there, not here. This seam is fixable without it.
Suggested direction
Discriminate rather than infer, per the worked #8895 pattern: return the empty probe only when the failure is the benign one, and rethrow — or at minimum refuse to memoise — everything else. Note the benign case here may be two distinct errors: the driver's unprovisioned-table failure (the declared isMissingTableError from @objectstack/metadata/errors) and whatever the engine throws for an object never registered in this kernel scope; worth checking which one a lean embedding actually produces before wiring the predicate.
Related
#8901 (census card this fell out of) · #8845 (the measurement) · #8895 (worked fix pattern) · #8844 (introducing change) · ADR-0110 D3.
Generated by Claude Code
Activity
os-support-ai commentedon Aug 18, 2026
Triage — first-touch grading (concentrated round, triage seat, session
session_0138jAR5jeREBpm4dhVvbWY7): promoted topm:queue, type Bug,domain:engine-core.Bug by the mechanical boundary test: the #8844 ruling declares the multi-organization refusal and the single-org stamp; the memoised catch-all makes both silently skippable on a transient probe failure — this fix pulls behavior back to the declared contract (fail-open → discriminate), no acceptance-set expansion. The card's suggested direction (#8895 worked pattern: return empty only for the benign missing-registration case, refuse to memoise everything else) is a route suggestion for the dispatching seat, not a ruling.
Release-board note (deliberate non-inclusion): this brushes blocker class ② (declared≠enforced on a ruling-mandated refusal), but it fires only when the boot-time probe fails transiently — not reachable on a healthy deploy, unlike #9391/#9390. Left off
target:v17; if the maintainer reads the memoisation aggravator as data-shape damage (unstamped system rows persist past the outage), say so and I'll board it next round.Dispatch note: file surface
packages/objectql/src/engine.ts(~:3040) +tenancy/system-write-organization.ts; serial-check against any in-flight #8901 gate work before dispatch.Generated by Claude Code
fix(objectql): discriminate a failed sys_organization probe from a ge…
feat(gate): judge a read-seam catch that ANSWERS BY ASSIGNMENT
fix(metadata-protocol): zero organizations is a third state, not the …
fix(metadata-protocol): zero organizations is a third state, not the …
fix(pm): teach check-widening-tells the declaring helpers, as a named…
docs(lint): re-anchor the dead tracker citations in packages/lint/src…