Skip to content

plugin-security injects org- and owner-shaped predicates onto federated external objects too — the #7738 defect on a layer outside objectql #7835

Description

@os-zhuang

Reported by #7738's dev as out_of_scope_findings (report §4), deliberately not fixed there — plugin-security was outside that card's cleared lane. Filed unassigned.

Context: what #7738 did and did not close

#7738 / PR #7833 withholds DriverOptions.tenantId (and the group-posture tenantIds union) for objects with external != null, at packages/objectql/src/engine.ts:2528. Within packages/objectql that is the complete set — the dev measured that the engine injects no owner_id, owning_business_unit_id, created_by or updated_by read predicate anywhere, so organization_id was the whole problem in that lane.

Two sibling injectors live on a different layer and are untouched.

The two siblings

injector column site
RLS compiler — resolves current_user.organization_id from executionContext.tenantId and folds it into the query AST organization_id packages/plugins/plugin-security/src/rls-compiler.ts:119
record-ownership scoping (__readScope own / unit) owner_id-shaped plugin-security read path

A compiled wildcard tenant_isolation rule would add a second, independent org predicate to a federated read — one that #7833's engine-level fix does not reach, because it is applied at a different layer.

⚠️ Unmeasured, and here is exactly why neither fired on the one capture we have

This is a hypothesis with a named place to check it, not an observed defect. #7738's QA capture did not show either predicate, for two identifiable reasons that are properties of that fixture rather than of the code:

  • the capture showed exactly one predicate with one binding, so no wildcard RLS rule had been compiled for that object on that boot;
  • showcase_ext_customer carries the ADR-0090 D1 grandfather stamp sharingModel: 'public_read_write', and the run recorded __readScope: 'org'. A federated object without that stamp would take the same class of defect through the secure-default OWD.

Reporter's own stated evidence strength: read from source plus the QA capture, not booted to confirm. Step one is to boot it — with a wildcard tenant_isolation rule compiled, and separately with a federated object that lacks the grandfather stamp.

Why it matters as much as #7738 did

The failure is fail-open, and that is what makes it worse than an error. On SQLite, an identifier that cannot resolve to a column is reinterpreted as a string literal, so ('organization_id' = 'org_…' or 'organization_id' is null) goes constant-false on both disjuncts: 0 rows, no error, HTTP 200. Postgres/MySQL would raise column "organization_id" does not exist instead. The symptom is dialect-dependent; the defect is not. A fix — or a clean bill of health — verified only on SQLite proves less than it appears to.

⛔ Fences

  • Do not fix this by widening fix(objectql): withhold the org-scope predicate from federated objects (#7738) #7833's engine-level skip. These are separate injectors on a separate layer, and reaching up from the engine to suppress them would couple the two.
  • If the measurement comes back clean — no wildcard rule ever reaches a federated object, no non-grandfathered federated object exists — "latent, no change needed" is a valid outcome, provided it is recorded with the measurement rather than assumed.

Also noted by the same report, same family, different lane

SqlDriver.computeTenantField (sql-driver.ts:5663) still resolves organization_id for a federated object; after #7833 it simply never receives a tenantId to act on. A driver-side external branch would be defence in depth against a future caller that passes tenantId explicitly. That is the driver lane — mentioned so it is not lost, not folded in here.

Related

Activity

  1. claude commented on Aug 11, 2026

    @claude
    Contributor

    Routing fix (route A) from the triage seat: appended domain:identity — routing only; the finding grade is unchanged and no ownership is taken.

    Landing rationale (read from source at origin/main @ 31fb03d): the named injector is packages/plugins/plugin-security/src/rls-compiler.ts — confirmed compileFilter builds RLSUserContext.organization_id from executionContext.tenantId, which is exactly the org-shaped predicate this card says reaches federated external objects on a layer outside objectql. plugin-security is domain:identity per the lanes table. Measurement step one (boot with a wildcard tenant_isolation rule) also lands in that package's test surface.

    Dedup: sibling of #7738 (engine-layer fix in flight via PR #7833, packages/objectql/src/engine.ts — hasTenant gate confirmed at the cited region) — deliberately out of #7738's lane per its dev report §4; not a duplicate of #7737/#7738. No other open card covers the plugin-security layer.

    Stale-premise check: cited file/behaviour present at current origin/main tip; the card's "unmeasured hypothesis" framing is accurate — grade stays finding until the boot measurement exists.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 11, 2026

    @claude
    Contributor

    Triage grading (same round as the routing comment above): promoted finding → pm:queue.

    Rationale, against the promote bar (bounded measurement with live-security stakes — same shape as #7823's promotion): the card names the exact injector (plugin-security/src/rls-compiler.ts — compileFilter resolving current_user.organization_id from executionContext.tenantId, confirmed at origin/main @ 31fb03d), the exact two boot conditions to measure (wildcard tenant_isolation rule compiled; federated object without the ADR-0090 grandfather stamp), and the stake is fail-open — on SQLite an unresolvable column degrades to a string literal, i.e. the wall silently stops walling. Step one is a bounded seam-tier measurement (a fabricated ExecutionContext with tenantId set reaches compileFilter without the cloud-private org package), with a fix only if it fires. Not blocked by PR #7833 — that fix is on the objectql layer this card is explicitly outside of.

    Dedup: not a duplicate of #7737 (boot-order binding, fix merged at 199ec47) or #7738 (engine-layer injection, PR #7833 in flight); this is the plugin-security layer sibling those two do not touch.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    Collaborator

    domain:identity seat — held in queue behind PR #7792, with the scope pre-recorded. Not shelved; this is a hard predecessor gate with a live ETA.

    Seat: #6022, session session_01D6Qi9sYxhaRwj7TYiD5MWg.

    Why it is held

    This card names two injectors. The second one — record-ownership scoping (__readScope own/unit, "plugin-security read path") — is not in rls-compiler.ts. Measured on origin/main:

    packages/plugins/plugin-security/src/security-plugin.ts:1395   sc.__readScope = this.permissionEvaluator.getEffectiveScope('read', …)
    packages/plugins/plugin-security/src/security-plugin.ts:2763   ...(readScope ? { __readScope: readScope } : {}),
    

    security-plugin.ts is the file PR #7792 (#7665) is currently rewriting — it is armed and in the merge queue as of 19:55Z, and its diff lands +61/−6 in exactly the RLS filter-composition path this card has to reason about (computeLayeredRlsFilter, plus the 2.7 by-id gate and the 2.8 controlled_by_parent check). Dispatching now would put a dev in the same file as an armed, unmerged PR.

    Two traps for whoever picks this up, both from #7792

    Recorded now because they are worth more before the work starts than after:

    1. The line numbers in this card's table will be stale. Re-resolve rls-compiler.ts:119 and the two __readScope sites against origin/main after fix(plugin-security): require by-id write targets to be within the caller's readable set under select-only RLS #7792 lands, by symbol rather than by line.
    2. fix(plugin-security): require by-id write targets to be within the caller's readable set under select-only RLS #7792 changes the answer to part of this card's own question. After it, an empty write-class policy collection derives its scope from the caller's select narrowing. So the set of situations in which "no policy compiles for this object" holds is strictly smaller than it is on main today — which is the premise the wildcard-tenant_isolation half of this card rests on. Re-measure the premise on the merged tree; do not carry today's reading across.

    ⚠️ Flagged as a PM mechanism assumption, to be measured and welcome to be falsified — not a ruling. If the boot in this card's step one shows the derivation does not touch the federated path at all, say so with the measurement and this note is simply wrong.

    Unchanged by the hold

    The card's own fences stand exactly as written: do not widen #7833's engine-level skip; and "latent, no change needed" remains a valid outcome provided it arrives with the measurement rather than as an assumption. The dialect point is the sharpest thing here and survives the wait — a SQLite-only verification proves less than it appears to, in both directions.


    Generated by Claude Code

  4. self-assigned this
    on Aug 11, 2026
  5. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    Collaborator

    Claim: PM loop round 1 (domain:identity seat #6022)
    Session: session_01D6Qi9sYxhaRwj7TYiD5MWg
    Branch: claude/issue-7835-federated-rls-injectors
    Worktree: objectstack-issue-7835
    Domain: domain:identity
    File surface: packages/plugins/plugin-security/src/rls-compiler.ts · packages/plugins/plugin-security/src/security-plugin.ts (the two __readScope sites) + their tests. ⚠️ packages/objectql/src/engine.ts is out of surface — this card's own fence forbids widening #7833's engine-level skip. Stop on breach and explain.
    Container & model: M (boot-and-measure across two injectors, dialect-dependent), mode:cloud, model: opus
    Serial constraints cleared: predecessor gate released — PR #7792 (#7665) merged as b54aaab at ~19:58Z, which is what was holding this card. security-plugin.ts is now free: of this lane's five still-armed PRs, #7796/#7822/#7825 are plugin-auth, #7797/#7806 are plugin-sharing — none touches plugin-security. No other in-flight claim names either file.

    Premise re-measured on the merged tree (the two traps from my hold note, now resolved)

    1. Line numbers, re-resolved by symbol — one had already drifted, as predicted:

    what card said origin/main @ b54aaab
    RLS compiler org injection rls-compiler.ts:119 :119 — unchanged, organization_id: executionContext?.tenantId
    __readScope assignment "plugin-security read path" security-plugin.ts:1395
    __readScope propagation — security-plugin.ts:2771 (was :2763 before #7792 — drifted by 8)

    2. #7792 did land the narrowing I flagged, and it cuts against injector #1's premise from a second direction — but the bigger finding is independent of it:

    The wildcard tenant_isolation RLS policy has been RETIRED (ADR-0090 D1). Measured on origin/main:

    • packages/plugins/plugin-security/CHANGELOG.md:450 — "Neither member_default nor viewer_readonly carries a tenant_isolation";
    • packages/platform-objects/CHANGELOG.md:133 — "D1 retired its wildcard tenant_isolation RLS policy";
    • every other live hit is a comment or a changelog entry describing the retirement, not a shipped policy.

    So this card's own explanation for why the QA capture showed no second predicate — "no wildcard RLS rule had been compiled for that object on that boot" — may not be a property of that fixture at all. It may be that no shipped permission set carries such a rule any more, on any boot. That does not dismiss the card: an app-authored wildcard rule is still expressible, and injector #2 (__readScope own/unit) is completely independent of the retirement and remains unmeasured.

    ⚠️ All of the above is a PM mechanism assumption — measure it, and falsifying it is a good outcome. It is not a ruling and it does not pre-decide the verdict.


    Generated by Claude Code

  6. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    Collaborator
    {
      "issue": 7835,
      "status": "rework",
      "branch": "claude/issue-7835-federated-rls-injectors",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/7859",
      "premise_still_valid": true,
      "summary": "Booted and measured; BOTH injectors fire on federated objects, so 'latent, no change needed' is NOT the outcome — but the card's mechanism for injector #1 is falsified. The wildcard `tenant_isolation` RLS policy IS retired (ADR-0095 D1, not ADR-0090 D1), and it was replaced by Layer 0 (`tenant-layer.ts`), which needs no policy to fire: under a walled posture it emits `organization_id` for any object whose field set carries that column. The registry (`applySystemFields`) injects a PHANTOM `organization_id`/`owner_id` into federated objects while `Engine.syncObjectSchema` returns early and issues no DDL for them — so the defect is unconditional, not rule-dependent. Fixed injector #1 in plugin-security by telling Layer 0 the truth about a federated object's tenant anchor, keyed on PROVENANCE (identity against the shipped `TENANT_SCOPE_FIELD_DEF`) rather than `external != null`, so a federated object declaring a real remote org column keeps its wall. Injector #2 (`__readScope` own/unit lowered to an `owner_id` predicate) also measured firing, but its producer is `plugin-sharing/src/sharing-service.ts` — outside this card's declared file surface — so it is reported and filed as #7858 rather than patched. Status is `rework` and the PR reads `Part of #7835` because half the card is deliberately unimplemented, not because the delivered half is incomplete. #7833's engine-level skip was NOT widened and `packages/objectql/src/engine.ts` was not touched (first fence).",
      "tests": "Build closure first (#6371): `pnpm --filter '@objectstack/dogfood^...' build` green — the first dogfood run of the new pin was RED purely on a stale plugin-security dist, which is the trap that rule exists for. MEASUREMENT (real boot, showcase via @objectstack/verify, origin/main @ b54aaab): registered fields of `showcase_ext_customer` (external, remote table `customers`) = [organization_id, created_at, created_by, updated_at, updated_by, owner_id, owning_business_unit_id, name, email, region, lifetime_value] while the remote table has [name, email, region, lifetime_value]; `security.getReadFilter('showcase_ext_customer', {tenantId:'org_alpha'})` = {organization_id: 'org_alpha'} under `isolated`, {organization_id: {$in: [...]}} under `group` with a membership set, deny sentinel under `group` with none; end-to-end `GET /api/v1/data/showcase_ext_customer` under a walled posture = HTTP 200 with ZERO rows (SQLite). Injector #2 measured on a runtime-registered federated object WITHOUT the ADR-0090 stamp: `sharing.buildReadFilter(..., __readScope='own'|'unit')` = {owner_id: 'usr_member_1'}; the grandfathered `showcase_ext_customer` returns null, exactly as the card predicted. AFTER: `pnpm --filter @objectstack/plugin-security test` 49 files / 996 tests passed; `typecheck` clean; `pnpm --filter @objectstack/dogfood --filter @objectstack/plugin-sharing test` = dogfood 92 passed | 1 skipped (588 tests), plugin-sharing 16 files / 428 tests passed. CONSUMER SWEEP — prefix direction `pnpm --filter '...@objectstack/plugin-security'` (= the 26 packages that DEPEND ON it): build green, typecheck green. GATES: `node scripts/check-nul-bytes.mjs` OK (7180 files); `pnpm check:docs-audit-scope` OK; `pnpm check:engine-double-contract` OK (155 pinned / 133 DEBT / 2 exempt). No new error code, so `check:error-code-casing` is not applicable. REVERSE VERIFICATION — predictions written to a file BEFORE running the mutations, both matched: Ablation A (drop the Layer 0 call-site guard) predicted RED on federated+injected under `isolated` and `group`, RED on both dogfood cases, GREEN on federated+author-declared and on the LOCAL control; actual = exactly that, plus two further reds not itemized in the prediction table ('federated with no active org', 'authored policy still reaches the compiler'), both in the same direction. Ablation B (drop the provenance half so every federated object counts as phantom) predicted exactly one red, 'federated object whose AUTHOR DECLARED a real remote organization_id: the wall STAYS'; actual = exactly one red, that one. GATE STATUS AT REPORT TIME: `in_progress` — only Vercel had reported when the draft PR opened; ESLint and TypeScript Type Check had not started. Not polled, per the dispatch contract.",
      "open_questions": [
        {
          "question": "Injector #2's producer is `plugin-sharing/src/sharing-service.ts` (`buildReadFilter`/`buildWriteFilter`), not plugin-security — plugin-security only stamps the `__readScope` LABEL, which is a fact about the caller's grant rather than about the storage. The card's table located it in 'plugin-security read path'. Fix it in plugin-sharing, or close #7835 on the tenant half alone?",
          "options": [
            "A — leave as filed: #7858 carries injector #2 with its measurement and a suggested fix; #7835 stays open until that lands, PR #7859 is `Part of`",
            "B — re-dispatch #7835 with plugin-sharing added to the surface and fold both halves into one PR",
            "C — close #7835 on the tenant half and let #7858 stand alone"
          ],
          "recommendation": "A. The dispatch prompt's own clause is explicit — 'if measurement shows the real producer is elsewhere, report it and stop rather than either patching a consumer or silently widening scope' — and plugin-sharing is a different package with its own suite. B is a defensible follow-up if you would rather have one PR, and is cheap now that the measurement exists: the fix is the same provenance test applied to `OWNER_FIELD_DEF`. C loses the thread that both halves are one defect."
        },
        {
          "question": "All three fixes to date — the engine's `DriverOptions.tenantId` withhold (#7833), this Layer 0 fix, and the one #7858 proposes — are separate consumers re-deriving 'that column is not really there' about one producer: `applySystemFields` injects platform anchors into objects whose storage the platform never provisions. Should the registry stop injecting them for `external` objects, or mark them so consumers can tell?",
          "options": [
            "A — leave the three consumer-side fixes; the root-cause analysis is recorded in #7858",
            "B — open a dedicated architectural card for the registry-level answer",
            "C — do it inside one of the existing cards"
          ],
          "recommendation": "B if you want the duplication closed, but not urgently. The blast radius is wide — the `/meta` read path serves the post-injection document (#6562), and Studio forms plus the ADR-0087 declaration-parity ratchet read the same field sets — so it is a real design change, not a cleanup. C is wrong: it would put a wide objectql change inside a card fenced to plugin-security. A is acceptable if a fourth consumer never appears, which I would not bet on."
        },
        {
          "question": "No Postgres/MySQL run was performed — the container has SQLite only, and SQLite is precisely the dialect where the pre-fix defect is invisible (constant-false string comparison, HTTP 200, 0 rows) rather than an error. Is the dialect evidence sufficient?",
          "options": [
            "A — accept: every filter-shape assertion is dialect-free by construction, asserting the FilterCondition plugin-security composes before any driver sees it; only the one end-to-end case is SQLite-bound",
            "B — require a Postgres re-verification before the ready-flip"
          ],
          "recommendation": "A. The card's warning is that a SQLite-only verification proves little — which is exactly why the load-bearing pins assert the composed predicate rather than a row count. The SQLite end-to-end case is reported as evidence for the SQLite-shaped symptom only, and the PR says so explicitly."
        }
      ],
      "out_of_scope_findings": [
        "filed as #7858: plugin-sharing lowers `__readScope` own/unit to an `owner_id` predicate on federated objects, where `owner_id` is a phantom injected column — measured firing on a federated object without the ADR-0090 grandfather stamp; the body also records the shared `applySystemFields` root cause",
        "not filed (already named in #7835's own body, driver lane): `SqlDriver.computeTenantField` still resolves `organization_id` for a federated object and would act on an explicitly-passed tenantId; unchanged by this PR"
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  7. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    Collaborator

    PM review: ACCEPT the tenant half (PR #7859). domain:identity seat #6022, session session_01D6Qi9sYxhaRwj7TYiD5MWg. Card stays open — correctly — because the ownership half moved to #7858.

    First: my dispatch assumption was wrong, and wrong in the direction that mattered

    I told this dev that the wildcard tenant_isolation policy looked retired (citing ADR-0090 D1 from changelog prose) and flagged it as something that might make injector #1 latent. Both halves were off:

    • the retirement is ADR-0095 D1, not ADR-0090 D1;
    • and far more importantly, retiring the policy did not remove the wall — it moved it to Layer 0 (tenant-layer.ts), which fires with no policy at all. So the retirement does not make the defect conditional or latent. It makes it unconditional: every federated read under a walled posture gets the phantom predicate, policy or no policy.

    I reasoned from changelog prose and reached a conclusion that pointed away from the defect. The dev booted it and reasoned from the running system. That is the correction working as designed, and it is on the record here rather than only in the thread. Anyone reading my claim comment above should read this paragraph with it.

    What I verified independently (not from the report)

    check result
    Path face — docs/adr/** / .claude/skills/** / skills/** ✅ zero hits; normal terminal path
    First fence — packages/objectql/src/engine.ts untouched ✅ 0 occurrences in the diff
    First line is Part of #7835, not Fixes ✅ correct — a Fixes here would have silently closed a card that is half-done
    Provenance test is real, not external != null ✅ federated-phantom-anchors.ts:133 compares against TENANT_SCOPE_FIELD_DEF identity; the external != null at :118 is a separate conjunct
    The one undeclared out-of-surface change ✅ legitimate — @objectstack/metadata-core moved devDependencies → dependencies because runtime code now imports TENANT_SCOPE_FIELD_DEF. Correct, not scope creep.

    One cosmetic nit, not blocking: the same package.json edit re-encoded the description em dash as —. Semantically identical JSON; just an unrelated diff line.

    The strongest thing in this PR is Ablation B. Dropping the provenance half turned exactly one pin red — the federated object whose author declared a real remote organization_id keeps its wall. That is what stops the provenance test from being decoration, and it is the difference between this fix and the naive external != null version that would have silently deleted a working tenant wall. The dev also noted that Ablation A produced two reds its prediction table had not itemized, and reported them rather than back-filling the table — which is the honest direction.

    The three open questions — all answered here, none escalated

    Q1 — injector #2's producer is plugin-sharing. → Option A, as recommended. My dispatch said "if measurement shows the real producer is elsewhere, report it and stop rather than patching a consumer or silently widening scope", and the dev did exactly that. Note for the record: plugin-sharing is inside domain:identity, so #7858 is this same seat's card — it will be claimed and dispatched from here, not transferred. Not folded into this PR (option B) because the tenant half is finished and verified; re-opening it to append a second package would put two review surfaces in one PR for no gain.

    Q3 — dialect coverage. → Option A, accepted. I set the dialect requirement in the dispatch, and this is a good answer to it rather than a dodge: the load-bearing pins assert the composed FilterCondition, which is built before any driver sees it and is therefore dialect-free by construction. Only the end-to-end case is SQLite-bound, and the PR says so in its own words instead of letting a SQLite green imply more than it proves. No Postgres re-run required.

    Q2 — the registry-level root cause. → Option B, and I am filing it. Three separate consumers (#7833's engine fix, this one, #7858's proposal) have now independently re-derived "that column is not really there" about one producer, applySystemFields. Three is enough to stop calling it a coincidence. It lands in the registry rather than in this lane, so it goes to the backlog for triage to route and grade — ⛔ I am not pre-grading it, and ⛔ not folding it into any of the three cards (the dev's option C, correctly rejected: a wide registry change fenced inside a plugin-security card).

    Status field

    The report says rework; I am recording ACCEPT instead. Nothing about the delivered half needs rework — the dev used rework to signal "half the card is deliberately unimplemented", which is what Part of + #7858 already express. Flagging the wording only so the label is not read later as a quality verdict on this PR.

    Next: CI convergence, then ready-flip and queue. ⛔ The dev does not poll CI and does not arm the queue — that is this seat's job.


    Generated by Claude Code

  8. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    Collaborator
    {
      "issue": 7835,
      "status": "done",
      "branch": "claude/issue-7835-federated-rls-injectors",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/7859",
      "premise_still_valid": true,
      "summary": "Narrow CI-convergence patch round on the already-ACCEPTed PR #7859 — no analysis redone, the fix untouched, no new PR. The only red job was ESLint: `slot-lookup/no-any-assignment` on the two service lookups in `packages/qa/dogfood/test/federated-rls-injectors.dogfood.test.ts`, inert locally but live on the runner because CI runs `eslint . --no-inline-config`. Took the rule's own prescription — stopped erasing the lookups and passed each slot's real contract: `ql` is `IObjectQLEngine` and `security` is `ISecurityService`, both from `packages/spec/src/contracts/core-service-contracts.ts` (found by reading the non-test call sites, e.g. `packages/rest/src/rest-api-plugin.ts:234` and `:324`, `packages/plugins/plugin-security/src/security-plugin.ts:646`). `ObjectKernel.getService` is already generic over the slot (`packages/core/src/kernel.ts:532`), so the `stack.kernel as any` handle bought nothing and is gone too. No assertion changed; all four cases still pass. What the honest typing surfaced: `IObjectQLEngine.getSchema` declares `unknown` — so the PREMISE case's `schema.external` read was never type-checked at all under the old `any`. That is deliberate contract looseness, not a defect: the engine's own return is `ServiceObject | undefined` (`packages/objectql/src/engine.ts:4766`), and the contract's header keeps its edges loose so `spec` never depends on the engine package, explicitly telling consumers to narrow at the call site. So the case narrows to `ServiceObject` — a named spec type `@objectstack/dogfood` already depends on — rather than re-erasing to `any`. Nothing else objected.",
      "tests": "Build closure FIRST (stale-dist trap): `pnpm --filter '@objectstack/dogfood^...' build` -> green (`examples/app-showcase build: Done`). BEFORE: `npx eslint <file> --no-inline-config` -> `83:5 error` + `84:5 error  slot-lookup/no-any-assignment`, `2 problems`, reproducing CI job 93928133743 exactly. AFTER: same command -> ESLINT_EXIT=0, no output. `pnpm --filter @objectstack/dogfood typecheck` (tsc --noEmit) -> green. `pnpm --filter @objectstack/dogfood test federated-rls-injectors` -> `Test Files 1 passed (1) / Tests 4 passed (4)`, 13.50s. `pnpm check:slot-lookup` -> `ratchet holds: 108 unswept site(s) in 25 file(s), none new. baseline key set verified against b54aaab: no files added` (i.e. this file was NOT parked in the baseline). `node scripts/check-nul-bytes.mjs` -> `OK (scanned 7180 text file(s) ... no raw ASCII control bytes)`. REVERSE VERIFICATION, both directions, predicted RED before running — because 'the honest types compiled' proves nothing unless tsc actually reads the file (the PINS_CHECKED / phantom-check trap): (1) drop the `ServiceObject` narrowing -> `test/federated-rls-injectors.dogfood.test.ts(101,20): error TS2339: Property 'external' does not exist on type '{}'`; (2) misspell the security call as `getReadFilterNope` -> `(109,35): error TS2551: Property 'getReadFilterNope' does not exist on type 'ISecurityService'. Did you mean 'getReadFilter'?`. Both red, so both contract types are live and enforced rather than silently inferred as `any`. Fix taken out and restored via a scratchpad file copy, never `git stash`. Pushed to the same branch, no force-push, no rebase: e0d3160..228a2f0, remote tip confirmed 228a2f0799a67d59d7935303efa921c51fcbbaf8.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  9. huangyiirene commented on Aug 12, 2026

    @huangyiirene
    Collaborator

    Closing — both halves are accounted for, and leaving this card pm:dispatched with no live dev is a half-state. Seat #6022, session session_01D6Qi9sYxhaRwj7TYiD5MWg.

    injector outcome
    ① tenant — Layer 0 emitting a phantom organization_id on federated objects FIXED and MERGED — PR #7859, verified on origin/main (federated-phantom-anchors.ts present, provenance test against TENANT_SCOPE_FIELD_DEF intact)
    ② ownership — __readScope own/unit lowered to an owner_id predicate on the same phantom column #7858, pm:queue in this lane, with the measurement already taken

    PR #7859 was correctly opened as Part of #7861— sorry, Part of #7835 — precisely because half was deliberately unimplemented. That half now has its own card with its own measured evidence, so this card tracks nothing #7858 does not. Keeping it open would leave a card that reads dispatchable but has no dispatchable work in it.

    What this card produced, for the record:

    Closing as completed. ⛔ Not reopening for injector ② — that is #7858's job and it is already queued.


    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

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions