Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions __tests__/api/systems-per-primitive-provisioning-1044.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* GET /api/build/systems — the Live dashboard's per-primitive provisioning
* status must come from each primitive's OWN registry flag (#1044).
*
* THE BUG: the route read `entry.zerodbProjectId` into one `provisioned`
* boolean and handed it to buildSystems, which reused it for ZeroInvoice,
* ZeroCommerce AND OpenCapStack alike. Confirmed live 2026-10-09 against the
* real production company "silo": all three came back `provisioned: true`
* identically, purely because the company had a ZeroDB project — not because
* each primitive had actually been provisioned. Everything the route needed was
* already on the `entry` it fetched (`zeroinvoiceProvisioned`,
* `commerceProvisioned`, `capstackProvisioned`, … each written independently by
* setAppProvisioned at provision time) and simply discarded.
*
* These tests drive the REAL route handler with a realistic partially-
* provisioned registry entry and assert the response distinguishes primitives.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'

const h = vi.hoisted(() => ({
resolveApp: vi.fn<(...args: any[]) => Promise<any>>(),
getAinativeApiKey: vi.fn(() => ''),
}))

vi.mock('@/lib/build/app-registry', () => ({ resolveApp: h.resolveApp }))
vi.mock('@/lib/build/env-keys', () => ({ getAinativeApiKey: h.getAinativeApiKey }))

const IDEA =
'storefront selling specialty coffee beans with invoicing and billing, plus cap table and SAFE fundraising for investors'

async function callRoute(companyId: string, idea = IDEA) {
const { GET } = await import('@/app/api/build/systems/route')
const url = `https://builder.ainative.studio/api/build/systems?companyId=${encodeURIComponent(
companyId,
)}&idea=${encodeURIComponent(idea)}`
const res = await GET(new Request(url) as any)
return res.json() as Promise<any>
}

function provisionedOf(body: any, name: string): boolean {
const card = body.systems.find((s: any) => s.primitive === name)
expect(card, `expected a system card for ${name}`).toBeDefined()
return card.provisioned
}

beforeEach(() => {
vi.clearAllMocks()
// No service key → count reads short-circuit to {} without any network call.
h.getAinativeApiKey.mockReturnValue('')
})

describe('#1044 GET /api/build/systems per-primitive provisioning', () => {
it('distinguishes a real partial state: ZeroInvoice provisioned, ZeroCommerce + OpenCapStack not', async () => {
h.resolveApp.mockResolvedValue({
chatId: 'chat_silo',
slug: 'silo',
// The company DOES have a ZeroDB project — the old shared flag was true.
zerodbProjectId: 'proj_real_silo',
// ...but only ZeroInvoice actually got provisioned.
zeroinvoiceProvisioned: true,
commerceProvisioned: false,
capstackProvisioned: false,
})

const body = await callRoute('silo')

expect(provisionedOf(body, 'ZeroInvoice')).toBe(true)
expect(provisionedOf(body, 'ZeroCommerce')).toBe(false)
expect(provisionedOf(body, 'OpenCapStack')).toBe(false)
// And the response surfaces the real per-primitive truth explicitly.
expect(body.primitiveProvisioning).toMatchObject({
zerodb: true,
zeroinvoice: true,
commerce: false,
capstack: false,
})
})

it('no longer returns provisioned:true for all three off a bare ZeroDB project (the live "silo" bug)', async () => {
h.resolveApp.mockResolvedValue({
chatId: 'chat_silo',
slug: 'silo',
zerodbProjectId: 'proj_real_silo',
// No individual primitive flag was ever written for this company.
})

const body = await callRoute('silo')

expect(provisionedOf(body, 'ZeroInvoice')).toBe(false)
expect(provisionedOf(body, 'ZeroCommerce')).toBe(false)
expect(provisionedOf(body, 'OpenCapStack')).toBe(false)
})

it('shows all Live when every primitive is genuinely provisioned', async () => {
h.resolveApp.mockResolvedValue({
chatId: 'chat_full',
slug: 'fullyprovisioned',
zerodbProjectId: 'proj_full',
pipelineProvisioned: true,
zeroinvoiceProvisioned: true,
commerceProvisioned: true,
capstackProvisioned: true,
serviceosProvisioned: true,
})

const body = await callRoute('fullyprovisioned')

expect(provisionedOf(body, 'ZeroInvoice')).toBe(true)
expect(provisionedOf(body, 'ZeroCommerce')).toBe(true)
expect(provisionedOf(body, 'OpenCapStack')).toBe(true)
expect(body.primitiveProvisioning.pipeline).toBe(true)
expect(body.primitiveProvisioning.serviceos).toBe(true)
})

it('shows all Planned for a freshly-created, unprovisioned company', async () => {
h.resolveApp.mockResolvedValue({ chatId: 'chat_new', slug: 'brandnew' })

const body = await callRoute('brandnew')

expect(body.provisioned).toBe(false)
for (const s of body.systems) {
expect(s.provisioned, `${s.primitive} should be Planned`).toBe(false)
}
for (const [k, v] of Object.entries(body.primitiveProvisioning as Record<string, boolean>)) {
expect(v, `primitiveProvisioning.${k} should be false`).toBe(false)
}
})

it('keeps ZeroPipeline on its own flag (unregressed) and does not infer it from ZeroDB', async () => {
h.resolveApp.mockResolvedValue({
chatId: 'chat_crm',
slug: 'crmco',
zerodbProjectId: 'proj_crm',
pipelineProvisioned: false,
})

const body = await callRoute('crmco', 'B2B sales CRM pipeline deals')

expect(provisionedOf(body, 'ZeroPipeline')).toBe(false)
expect(body.pipelineProvisioned).toBe(false)
expect(body.provisioned).toBe(true)
})

it('still reports the shared `provisioned` field (ZeroDB project presence) for existing consumers', async () => {
h.resolveApp.mockResolvedValue({
chatId: 'chat_x',
slug: 'x',
zerodbProjectId: 'proj_x',
pipelineProvisioned: true,
})

const body = await callRoute('x')

// Live.tsx reads `provisioned` for the cloud-provisioning banner/button —
// it must keep meaning "this company has its own ZeroDB project".
expect(body.provisioned).toBe(true)
expect(body.pipelineProvisioned).toBe(true)
})
})
169 changes: 169 additions & 0 deletions __tests__/lib/business-systems-per-primitive-provisioning.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* #1044 — the Live dashboard's "Business systems" Live/Planned badge must reflect
* EACH primitive's own real provisioning state, not one shared
* "does this company have any ZeroDB project at all" boolean.
*
* Before this fix, buildSystems collapsed ZeroInvoice / ZeroCommerce / OpenCapStack
* onto `opts.provisioned` (derived from `entry.zerodbProjectId`), so all three showed
* an identical badge regardless of whether each one was actually provisioned. The
* per-primitive truth already exists on the registry entry (`zeroinvoiceProvisioned`,
* `commerceProvisioned`, `capstackProvisioned`, … written independently at provision
* time) — these tests pin that each primitive reads its OWN flag.
*/
import { describe, it, expect } from 'vitest'
import { buildSystems } from '@/lib/build/business-systems'

/**
* An idea that deterministically surfaces ZeroInvoice, ZeroCommerce and
* OpenCapStack together so one call can assert all three diverge.
*/
const IDEA_INVOICE_COMMERCE_CAPSTACK =
'storefront selling specialty coffee beans with invoicing and billing, plus cap table and SAFE fundraising for investors'

function byName(systems: ReturnType<typeof buildSystems>, name: string) {
const s = systems.find((x) => x.primitive === name)
expect(s, `expected system card for ${name}`).toBeDefined()
return s!
}

describe('#1044 per-primitive provisioning status (no shared collapse)', () => {
it('distinguishes a realistic PARTIAL provisioning state (ZeroInvoice live, ZeroCommerce + OpenCapStack not)', () => {
// Real shape: the company HAS a ZeroDB project (so the old shared `provisioned`
// boolean is true), but only ZeroInvoice actually got provisioned.
const systems = buildSystems(
IDEA_INVOICE_COMMERCE_CAPSTACK,
{},
{
provisioned: true,
zeroinvoiceProvisioned: true,
commerceProvisioned: false,
capstackProvisioned: false,
},
8,
)

expect(byName(systems, 'ZeroInvoice').provisioned).toBe(true)
expect(byName(systems, 'ZeroCommerce').provisioned).toBe(false)
expect(byName(systems, 'OpenCapStack').provisioned).toBe(false)
})

it('distinguishes the inverse partial state (ZeroCommerce live, ZeroInvoice + OpenCapStack not)', () => {
const systems = buildSystems(
IDEA_INVOICE_COMMERCE_CAPSTACK,
{},
{
provisioned: true,
zeroinvoiceProvisioned: false,
commerceProvisioned: true,
capstackProvisioned: false,
},
8,
)

expect(byName(systems, 'ZeroInvoice').provisioned).toBe(false)
expect(byName(systems, 'ZeroCommerce').provisioned).toBe(true)
expect(byName(systems, 'OpenCapStack').provisioned).toBe(false)
})

it('does not collapse three different primitives onto one identical value', () => {
const systems = buildSystems(
IDEA_INVOICE_COMMERCE_CAPSTACK,
{},
{ provisioned: true, capstackProvisioned: true },
8,
)
const trio = ['ZeroInvoice', 'ZeroCommerce', 'OpenCapStack'].map(
(n) => byName(systems, n).provisioned,
)
// Exactly one of the three is really provisioned — they must not all match.
expect(new Set(trio).size).toBeGreaterThan(1)
expect(trio).toEqual([false, false, true])
})

it('shows every relevant primitive Live when ALL are genuinely provisioned', () => {
const systems = buildSystems(
IDEA_INVOICE_COMMERCE_CAPSTACK,
{},
{
provisioned: true,
pipelineProvisioned: true,
zeroinvoiceProvisioned: true,
commerceProvisioned: true,
capstackProvisioned: true,
serviceosProvisioned: true,
socialgraphProvisioned: true,
livestreamingProvisioned: true,
zerovoiceProvisioned: true,
},
12,
)
const flagged = new Set([
'ZeroPipeline', 'ZeroInvoice', 'ZeroCommerce', 'OpenCapStack',
'ServiceOS', 'Social Graph', 'Live Streaming', 'ZeroVoice',
])
for (const s of systems) {
if (flagged.has(s.primitive)) {
expect(s.provisioned, `${s.primitive} should be Live`).toBe(true)
}
}
})

it('shows everything Planned for a freshly-created company with nothing provisioned', () => {
const systems = buildSystems(IDEA_INVOICE_COMMERCE_CAPSTACK, {}, {}, 12)
for (const s of systems) {
expect(s.provisioned, `${s.primitive} should be Planned`).toBe(false)
}
})

it('a ZeroDB project alone no longer implies ZeroInvoice/ZeroCommerce/OpenCapStack are Live', () => {
// The exact live-production state from #1044: entry.zerodbProjectId exists, so
// the shared flag is true, but no individual primitive flag was ever written.
const systems = buildSystems(
IDEA_INVOICE_COMMERCE_CAPSTACK,
{},
{ provisioned: true },
8,
)
expect(byName(systems, 'ZeroInvoice').provisioned).toBe(false)
expect(byName(systems, 'ZeroCommerce').provisioned).toBe(false)
expect(byName(systems, 'OpenCapStack').provisioned).toBe(false)
})

describe('primitives that were NOT part of the bug stay correct', () => {
it('ZeroPipeline still reads its own pipelineProvisioned flag', () => {
const live = buildSystems('B2B sales CRM pipeline deals', {}, { pipelineProvisioned: true })
expect(byName(live, 'ZeroPipeline').provisioned).toBe(true)

const planned = buildSystems('B2B sales CRM pipeline deals', {}, { pipelineProvisioned: false })
expect(byName(planned, 'ZeroPipeline').provisioned).toBe(false)
})

it('ZeroPipeline is NOT Live merely because a ZeroDB project exists', () => {
const s = buildSystems('B2B sales CRM pipeline deals', {}, { provisioned: true })
expect(byName(s, 'ZeroPipeline').provisioned).toBe(false)
})

it('primitives with no registry provisioning flag stay honestly Planned', () => {
// Community / Context Graph / Search & Discovery / Content Workflow have no
// per-company provisioning signal in the registry at all — never claim Live.
const systems = buildSystems(
'a social community app with groups, feeds, semantic search and a knowledge graph',
{},
{ provisioned: true, pipelineProvisioned: true },
12,
)
const noFlag = new Set(['Community', 'Context Graph', 'Search & Discovery', 'Content Workflow'])
for (const s of systems) {
if (noFlag.has(s.primitive)) {
expect(s.provisioned, `${s.primitive} has no real flag; must be Planned`).toBe(false)
}
}
})

it('ZeroDB reads the real zerodbProjectId-backed flag (it IS the ZeroDB project)', () => {
const s = buildSystems('a note-taking app that stores posts and records', {}, { provisioned: true }, 12)
const zdb = s.find((x) => x.primitive === 'ZeroDB')
if (zdb) expect(zdb.provisioned).toBe(true)
})
})
})
Loading
Loading