// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // Wall three (spec ยง3.3): the generated suite calls every tenant-scoped procedure of this app's // registry as a member of organization A with organization B's slugs and ids, and fails any // procedure that answers with B's data or writes anything at all. // // It runs on the in-memory adapter, which is the repository predicate alone: that is wall one. // Wall two, what Postgres refuses on its own, is proven against a real Postgres by // @conusai-kit/data-kysely's own gate, and wall three is proven here for every procedure this // app registers -- including the ones a module adds tomorrow, because the list comes from the // registry and a procedure without a fixture fails the suite by name. import { join } from "node:path" import { type Actor, buildRegistry, type CallerOptions, Container, createCaller, type TransportCaller, } from "@conusai-kit/core" import { createMemoryDataSource, DATA_SOURCE, entitiesOf, JOB_QUEUE, OBJECT_STORE, SYSTEM_DATA, } from "@conusai-kit/data" import { GUIDES_PLATFORM, GUIDES_REGISTRY } from "@conusai-kit/guides" // kit:guides import { guidesIsolationExempt, guidesIsolationFixtures, recordingGuidesPlatform } from "@conusai-kit/guides/testing" // kit:guides import { PROPOSALS_REGISTRY } from "@conusai-kit/proposals" import { SUPPORT_PLATFORM } from "@conusai-kit/support" // kit:support import * as supportTesting from "@conusai-kit/support/testing" // kit:support import { createTenancyHooks, TENANCY, users } from "@conusai-kit/tenancy" import { TENANCY_ALSO_CHECK, tenancyIsolationExempt, tenancyIsolationFixtures } from "@conusai-kit/tenancy/testing" import { describeIsolation, fakeJobQueue, fakeObjectStore, type IsolationFixture, type IsolationTenant, runIsolation, snapshotOf, uploadViaProcedures, } from "@conusai-kit/testing" import { describe, expect, it, vi } from "vitest" import { accountIsolationExempt } from "../modules/account/__tests__/exempt.js" import { documents } from "../modules/documents/index.js" // kit:documents import { appModules } from "../modules/index.js" import * as knowledge from "../modules/knowledge/index.js" // kit:knowledge import { isolationFilesOf } from "./support/module-isolations.js" const registry = buildRegistry([...appModules]) /** What a module's isolation file exports: the shape this suite reads. */ interface ModuleIsolation { seed( call: TransportCaller, inProject: { org: string; workspace: string; project: string }, ): Promise> fixtures: Record } /** * The fixtures and the seed rows a module brings in `modules//__tests__/isolation.ts`, found by * their place: a module added with `pnpm new:module` is covered without a line here, and a * procedure it registers with no fixture fails the suite by name. Only registered modules count: a * module folder kept but not registered serves no procedure (`isolationFilesOf`). (The older * modules' fixtures are listed below; they move into their own folders as each is next touched.) */ const modulesDir = join(import.meta.dirname, "..", "modules") const moduleIsolations: ModuleIsolation[] = await Promise.all( isolationFilesOf( modulesDir, registry.modules.map((module) => module.id), ).map(async (file) => { const loaded = (await import(/* @vite-ignore */ file)) as { isolation: ModuleIsolation } return loaded.isolation }), ) const entities = entitiesOf(registry, "postgres") /** The guides module's platform, recording: a refused cross-tenant call must never reach it. */ // kit:guides const guidesPlatform = recordingGuidesPlatform() // kit:guides /** The support module's platform, recording: a refused cross-tenant mint must never reach it. */ // kit:support const supportPlatform = supportTesting.recordingSupportPlatform() // kit:support /** The app wired the way apps/web wires it, with the in-memory adapter in place of Postgres. */ function makeApp() { const data = createMemoryDataSource({ entities }) const root = new Container() root.value(DATA_SOURCE, data) root.value(SYSTEM_DATA, data) // The files module's store: a seeded upload needs one, and a refused cross-tenant call never reaches it. root.value(OBJECT_STORE, fakeObjectStore()) // A module whose writes enqueue work (a job started by a procedure) needs a queue to be driven at all. root.value(JOB_QUEUE, fakeJobQueue()) root.value(PROPOSALS_REGISTRY, registry) root.value(GUIDES_PLATFORM, guidesPlatform) // kit:guides root.value(GUIDES_REGISTRY, registry) // kit:guides root.value(SUPPORT_PLATFORM, supportPlatform) // kit:support // kit:knowledge:begin // The knowledge module's ports, faked: the search answers nothing (the suite asserts a // cross-tenant call is refused or answered empty), and the enablement checks pass. root.value(knowledge.KNOWLEDGE_SEARCH, { readiness: async () => ({ state: "ready" as const, reasons: [] }), index: async () => undefined, search: async () => [], remove: async () => undefined, }) root.value(knowledge.KNOWLEDGE_READINESS, { readiness: async () => ({ state: "ready" as const, reasons: [] }) }) root.value(knowledge.KNOWLEDGE_ENABLEMENT, { checkEligibility: async () => ({ ok: true as const }), indexingQuota: async () => Number.MAX_SAFE_INTEGER, }) // kit:knowledge:end const caller = ( actor: Actor, admission: Pick = {}, ): TransportCaller => { const tenancy = createTenancyHooks({ data, registry }) const container = root.child() container.value(TENANCY, tenancy) return createCaller({ registry, container, tenancy, ...admission }, { actor }) } return { data, caller } } const app = makeApp() async function signUp(email: string): Promise { const user = await app.data.global(users).insert({ issuer: "https://id.example.test", subject: email, email, displayName: email.split("@")[0] ?? email, locale: "en", }) return { userId: user.id } } async function tenant(slug: string): Promise { const owner = await signUp(`owner@${slug}.test`) const call = app.caller(owner) const org = (await call.callByName("tenancy.org.create", { slug, name: slug })) as { orgId: string slug: string workspaceSlug: string } const workspace = (await call.callByName("tenancy.workspace.create", { org: slug, slug: `ws-${slug}`, name: `Workspace ${slug}`, visibility: "org", })) as { workspaceId: string; slug: string } const project = (await call.callByName("tenancy.project.create", { org: slug, workspace: workspace.slug, slug: `p-${slug}`, name: `Project ${slug}`, moduleKey: "tenancy", })) as { projectId: string; slug: string } const invite = (await call.callByName("tenancy.member.invite", { org: slug, email: `invited-${slug}@example.test`, role: "member", })) as { invitationId: string; token: string } const key = (await call.callByName("auth.apiKey.create", { org: slug, name: "ci" })) as { key: { id: string } } // A custom role of this tenant's own: the role procedures' fixtures name its key from the other side. const role = (await call.callByName("tenancy.role.create", { org: slug, name: `Auditor ${slug}`, level: "org", permissions: ["tenancy.org.read", "tenancy.member.read"], })) as { key: string } // One upload and one proposal in the project, so a fixture names rows that exist in the other // tenant: the call has to be refused for being another tenant's, not for naming nothing. const inProject = { org: slug, workspace: workspace.slug, project: project.slug } const file = await uploadViaProcedures(call, inProject, { filename: "seed.pdf", contentType: "application/pdf", bytes: new TextEncoder().encode("%PDF-1.4"), }) // kit:documents:begin const proposal = (await call.callByName( "proposals.proposal.create", { ...inProject, targetKey: "documents.line", change: { lineKey: "a".repeat(64), label: "Reference", value: "QR-1", page: 1, position: 0, quote: "Reference: QR-1", confidence: 0.9, verification: "verified", edited: false, }, evidence: [{ fileId: file.fileId, page: 1, quote: "Reference: QR-1" }], model: "seed", promptVersion: "seed.v1", }, { idempotencyKey: `seed.${slug}` }, )) as { proposalId: string } // A document row, as the OCR job makes one: the live collection's read-back names it by its id. const documentRow = await app.data .forScope({ level: "project", actor: owner, orgId: org.orgId, workspaceId: workspace.workspaceId, projectId: project.projectId, }) .of(documents) .insert({ fileId: file.fileId, filename: "seed.pdf", status: "processing", ocrJobId: null, pageCount: null, proposedCount: null, failureCode: null, skippedPages: null, finishedAt: null, }) // kit:documents:end // kit:knowledge:begin // A knowledge_document row, as an index request makes one (indexing needs the module switched on, which // the seeding caller cannot do): the live collection's read-back names it by its id. const knowledgeDocument = await app.data .forScope({ level: "project", actor: owner, orgId: org.orgId, workspaceId: workspace.workspaceId, projectId: project.projectId, }) .of(knowledge.documents) .insert({ collectionName: "contracts", fileId: file.fileId, filename: "seed.pdf", status: "queued", error: null, indexedAt: null, requestedBy: owner.userId, }) // kit:knowledge:end // What each module that brings fixtures seeds in this tenant: its ids are what the other tenant's fixtures name. const seeded: Record = {} for (const isolation of moduleIsolations) Object.assign(seeded, await isolation.seed(call, inProject)) return { orgSlug: org.slug, workspaceSlug: workspace.slug, projectSlug: project.slug, orgId: org.orgId, workspaceId: workspace.workspaceId, projectId: project.projectId, userId: owner.userId, email: `owner@${slug}.test`, extra: { invitationId: invite.invitationId, token: invite.token, roleKey: role.key, apiKeyId: key.key.id, fileId: file.fileId, proposalId: proposal.proposalId, // kit:documents documentId: documentRow.id, // kit:documents knowledgeDocumentId: knowledgeDocument.id, // kit:knowledge ...seeded, }, } } let seeded: Promise<{ a: IsolationTenant; b: IsolationTenant }> | undefined const tenants = () => { seeded ??= (async () => ({ a: await tenant("alpha"), b: await tenant("bravo") }))() return seeded } /** The three slugs a project-scoped procedure's input carries, from the tenant it must not reach. */ const inProjectOf = (t: IsolationTenant) => ({ org: t.orgSlug, workspace: t.workspaceSlug, project: t.projectSlug }) /** The other tenant's seeded rows; the ghost tenant has none, so its ids are well-formed and absent. */ const NOWHERE = "00000000-0000-0000-0000-000000000000" const fileOf = (t: IsolationTenant) => t.extra.fileId ?? NOWHERE const proposalOf = (t: IsolationTenant) => t.extra.proposalId ?? NOWHERE const documentOf = (t: IsolationTenant) => t.extra.documentId ?? NOWHERE // kit:documents /** One fixture per tenant-scoped procedure, built from the *other* tenant's ids. */ const fixtures: Record = { // The tenancy module's, which this suite cannot be run without (one list, in @conusai-kit/tenancy/testing). ...tenancyIsolationFixtures(), ...Object.assign({}, ...moduleIsolations.map((isolation) => isolation.fixtures)), ...guidesIsolationFixtures, // kit:guides ...supportTesting.supportIsolationFixtures, // kit:support "auth.apiKey.create": { input: (t) => ({ org: t.orgSlug, name: "intruder" }) }, "auth.apiKey.list": { input: (t) => ({ org: t.orgSlug }) }, "auth.apiKey.revoke": { input: (t) => ({ org: t.orgSlug, id: t.extra.apiKeyId ?? "00000000-0000-0000-0000-000000000000" }), }, "files.file.beginUpload": { input: (t) => ({ ...inProjectOf(t), filename: "intruder.pdf", contentType: "application/pdf", size: 8 }), }, "files.file.putPart": { input: (t) => ({ ...inProjectOf(t), fileId: fileOf(t), part: 1, body: new TextEncoder().encode("%PDF-1.4") }), }, "files.file.completeUpload": { input: (t) => ({ ...inProjectOf(t), fileId: fileOf(t), parts: [{ number: 1, etag: "etag-intruder" }] }), }, "files.file.get": { input: (t) => ({ ...inProjectOf(t), fileId: fileOf(t) }) }, "files.file.download": { input: (t) => ({ ...inProjectOf(t), fileId: fileOf(t) }) }, "files.file.list": { input: (t) => inProjectOf(t) }, "files.file.delete": { input: (t) => ({ ...inProjectOf(t), fileId: fileOf(t) }) }, "proposals.proposal.create": { input: (t) => ({ ...inProjectOf(t), targetKey: "documents.line", change: { lineKey: "b".repeat(64), label: "Intruder", value: "x", page: 1, position: 0, quote: "Intruder: x", confidence: 0.5, edited: false, }, // Nothing of the other tenant's: `create` reads no id it is given, so the only cross-tenant // case it has is the slugs, which the resolver refuses. An id of B's here would be echoed // back in the answer, which is the caller's own input and not B's data. evidence: [{ fileId: NOWHERE, page: 1 }], model: "intruder", promptVersion: "intruder.v1", }), }, "proposals.proposal.edit": { input: (t) => ({ ...inProjectOf(t), proposalId: proposalOf(t), expectedVersion: 1, change: { lineKey: "c".repeat(64), label: "Intruder", value: "x", page: 1, position: 0, quote: "Intruder: x", confidence: 0.5, edited: true, }, }), }, "proposals.proposal.get": { input: (t) => ({ ...inProjectOf(t), proposalId: proposalOf(t) }) }, "proposals.proposal.list": { input: (t) => inProjectOf(t) }, "proposals.proposal.approve": { input: (t) => ({ ...inProjectOf(t), proposalId: proposalOf(t), expectedVersion: 1 }), }, "proposals.proposal.approveMany": { input: (t) => { const proposalId = proposalOf(t) return { ...inProjectOf(t), proposalIds: [proposalId], expectations: { [proposalId]: { expectedVersion: 1 } } } }, }, "proposals.proposal.reject": { input: (t) => ({ ...inProjectOf(t), proposalId: proposalOf(t), reason: "intruder" }), }, // kit:documents:begin "documents.document.list": { input: (t) => inProjectOf(t) }, "documents.document.get": { input: (t) => ({ ...inProjectOf(t), fileId: fileOf(t) }) }, // The live collection's read-back: another tenant's document id answers nothing. "documents.document.getMany": { input: (t) => ({ ...inProjectOf(t), ids: [documentOf(t)] }) }, "documents.line.edit": { input: (t) => ({ ...inProjectOf(t), lineId: proposalOf(t), expectedVersion: 1, label: "Intruder", value: "x" }), }, "documents.export.create": { input: (t) => ({ ...inProjectOf(t), fileId: fileOf(t) }) }, // kit:documents:end "branding.brand.get": { input: (t) => ({ org: t.orgSlug }) }, "branding.brand.set": { input: (t) => ({ org: t.orgSlug, primary: "#000000", displayName: "Intruder" }) }, "branding.logo.set": { input: (t) => ({ org: t.orgSlug, variant: "light", contentType: "image/png", body: "iVBORw0KGgoAAAAA" }), }, "branding.logo.clear": { input: (t) => ({ org: t.orgSlug, variant: "light" }) }, "branding.logo.get": { input: (t) => ({ org: t.orgSlug, variant: "light" }) }, } const alsoCheck = [...TENANCY_ALSO_CHECK] const exempt = { ...tenancyIsolationExempt, "auth.session.current": "it answers only about the signed-in user's own row", ...accountIsolationExempt, ...guidesIsolationExempt, // kit:guides ...supportTesting.supportIsolationExempt, // kit:support } const options = async () => { const { a, b } = await tenants() return { registry, seed: async () => ({ a, b }), caller: (actor: Actor) => app.caller(actor), snapshot: () => snapshotOf(app.data, entities, { outbox: app.data.outbox }), fixtures, alsoCheck, exempt, } } describeIsolation( { registry, seed: async () => tenants(), caller: (actor) => ({ call: async () => undefined as never, callByName: async (name, input) => app.caller(actor).callByName(name, input), }), snapshot: async () => snapshotOf(app.data, entities, { outbox: app.data.outbox }), fixtures, alsoCheck, exempt, }, { describe, it }, ) describe("the generated isolation suite", () => { it("checks current membership before preparing or observing service admission", async () => { const { a, b } = await tenants() const prepareServices = vi.fn() const observe = vi.fn(async () => { throw new Error("synthetic service unavailable") }) const admission = { prepareServices, serviceReadiness: { profile: "postgres" as const, observe } } const outsider = app.caller({ userId: a.userId }, admission) await expect(outsider.callByName("inventory.item.list", inProjectOf(b))).rejects.toMatchObject({ status: 404 }) expect(prepareServices).not.toHaveBeenCalled() expect(observe).not.toHaveBeenCalled() const member = await signUp("removed-member@isolation.test") const owner = app.caller({ userId: a.userId }) const invite = (await owner.callByName("tenancy.member.invite", { org: a.orgSlug, email: "removed-member@isolation.test", role: "viewer", })) as { token: string } await app.caller(member).callByName("tenancy.invitation.accept", { org: a.orgSlug, token: invite.token }) await expect(app.caller(member, admission).callByName("inventory.item.list", inProjectOf(a))).rejects.toThrow() expect(prepareServices).toHaveBeenCalledOnce() expect(observe).toHaveBeenCalledOnce() await owner.callByName("tenancy.member.remove", { org: a.orgSlug, userId: member.userId }) prepareServices.mockClear() observe.mockClear() // A new request must resolve fresh membership, before consulting the unavailable service. await expect(app.caller(member, admission).callByName("inventory.item.list", inProjectOf(a))).rejects.toMatchObject( { status: 404 }, ) expect(prepareServices).not.toHaveBeenCalled() expect(observe).not.toHaveBeenCalled() }) it("covers every procedure this app registers", async () => { const result = await runIsolation(await options()) expect(result.failures).toEqual([]) // One case per live collection too (TS6.5): its read-back, by the collection's name. expect(result.passed.length).toBe( registry.isolationFixtures.length + alsoCheck.length + Object.keys(registry.collections).length, ) expect(result.passed).toContain("live notes.note") // kit:notes }) // kit:notes:begin it("covers every procedure of the notes and files modules, by name", async () => { const result = await runIsolation(await options()) const covered = new Set(result.passed) const names = [...registry.procedures.keys()].filter((name) => /^(notes|files)\./.test(name)) expect(names.length).toBe(15) for (const name of names) expect(covered.has(name), name).toBe(true) }) it("fails, naming it, when a module's own fixture is missing", async () => { const base = await options() const { "notes.attachment.remove": _removed, ...rest } = fixtures const result = await runIsolation({ ...base, fixtures: rest }) expect(result.failures.map((failure) => failure.procedure)).toEqual(["notes.attachment.remove"]) }) // kit:notes:end // kit:guides:begin it("reaches the guides platform with no cross-tenant call", async () => { await runIsolation(await options()) expect(guidesPlatform.calls).toEqual([]) }) // kit:guides:end // kit:support:begin it("reaches the support platform with no cross-tenant call", async () => { await runIsolation(await options()) expect(supportPlatform.calls).toEqual([]) }) // kit:support:end it("fails, naming it, when a procedure has no fixture", async () => { const base = await options() const { "tenancy.member.list": _removed, ...rest } = fixtures const result = await runIsolation({ ...base, fixtures: rest }) expect(result.failures.map((failure) => failure.procedure)).toEqual(["tenancy.member.list"]) }) }) // This journey requires explicit optional-module admission. vi.mock("../modules/inclusion.js", async (importOriginal) => ({ ...(await importOriginal()), ...(await import("./support/native-readiness.js")).selectedModules( (await importOriginal()).OPTIONAL_MODULE_IDS, ), }))