Skip to content

probeInstallOrganizations answers a failed sys_organization read with a memoised empty list, so system-insert organization resolution fails open #9261

Closed

Description

@os-steve

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:

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

  1. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Triage — first-touch grading (concentrated round, triage seat, session session_0138jAR5jeREBpm4dhVvbWY7): promoted to pm: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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions