Skip to content
Draft
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
21 changes: 21 additions & 0 deletions .cursor/rules/graphify.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
description: graphify knowledge graph context
alwaysApply: true
---

This project has a graphify knowledge graph at graphify-out/.

**MANDATORY: Before using Read, Grep, Glob, or Bash to explore the codebase, you MUST run graphify first:**
- `graphify query "<question>"` — scoped subgraph for any codebase or architecture question
- `graphify path "<A>" "<B>"` — dependency path between two symbols
- `graphify explain "<concept>"` — all nodes related to a concept

This applies to YOU and to every subagent you spawn. Include this rule explicitly in every subagent prompt that involves code exploration. Do not skip graphify because files are "already known" or because you are executing a plan — the graph surfaces cross-file dependencies and INFERRED edges that grep and Read cannot find.

Only use Read/Grep/Glob directly when:
1. graphify has already oriented you and you need to modify or debug specific lines
2. `graphify-out/graph.json` does not exist yet

- If `graphify-out/wiki/index.md` exists, navigate it instead of reading raw files
- Read `graphify-out/GRAPH_REPORT.md` only for broad architecture review when query/path/explain do not surface enough context
- After modifying code files, run `graphify update .` to keep the graph current (AST-only, no API cost)
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ code/packages/ingestion-core/src/*.js

# Chromium extension Vite outDir (versioned per release, e.g. build55647)
code/apps/extension-chromium/build*/

# graphify local/runtime sidecars (graph.json + report are committed)
graphify-out/cost.json
graphify-out/.graphify_python
graphify-out/.graphify_root
graphify-out/.needs_update
# AST cache lands under the scan root (code/); keep local only
code/graphify-out/
34 changes: 34 additions & 0 deletions .graphifyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Initial graphify pass: codebase AST only.
# PDFs and documentation are excluded; add selectively later with /graphify --update.
*.pdf
**/*.pdf
*.md
**/*.md
*.mdx
**/*.mdx
*.txt
**/*.txt
*.html
**/*.html
*.rst
**/*.rst
*.qmd
**/*.qmd
*.yaml
**/*.yaml
*.yml
**/*.yml
*.png
**/*.png
*.jpg
**/*.jpg
*.jpeg
**/*.jpeg
*.webp
**/*.webp
*.gif
**/*.gif
*.svg
**/*.svg
**/THIRD_PARTY_LICENSES/**
**/docs/**
34 changes: 34 additions & 0 deletions code/.graphifyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Initial graphify pass: codebase AST only.
# PDFs and documentation are excluded; add selectively later with /graphify --update.
*.pdf
**/*.pdf
*.md
**/*.md
*.mdx
**/*.mdx
*.txt
**/*.txt
*.html
**/*.html
*.rst
**/*.rst
*.qmd
**/*.qmd
*.yaml
**/*.yaml
*.yml
**/*.yml
*.png
**/*.png
*.jpg
**/*.jpg
*.jpeg
**/*.jpeg
*.webp
**/*.webp
*.gif
**/*.gif
*.svg
**/*.svg
**/THIRD_PARTY_LICENSES/**
**/docs/**
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Regression: internalSandboxesApi Host/Sandbox decisions must use
* deriveInternalHostAiPeerRoles + assertRecordForServiceRpc, not local_role.
*/

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { HandshakeState, type HandshakeRecord, type SSOSession } from '../types'
import {
computeAuthoritativeDeviceInternalRole,
isEligibleActiveInternalHostSandboxRecord,
} from '../internalSandboxesApi'
import { localDeviceRole } from '../../internalInference/policy'
import { createHandshakeTestDb } from './handshakeTestDb'
import { insertHandshakeRecord } from '../db'
import { mockKeypairFields } from './mockKeypair'

const getInstanceIdMock = vi.hoisted(() => vi.fn(() => 'dev-host-1'))
vi.mock('../../orchestrator/orchestratorModeStore', async (importOriginal) => {
const a = await importOriginal<typeof import('../../orchestrator/orchestratorModeStore')>()
return { ...a, getInstanceId: () => getInstanceIdMock() }
})

const session: SSOSession = {
wrdesk_user_id: 'user-a',
email: 'a@example.com',
iss: 'https://id.example',
sub: 'sub-a',
email_verified: true,
plan: 'free',
currentHardwareAttestation: null,
currentDnsVerification: null,
currentWrStampStatus: null,
session_expires_at: new Date(Date.now() + 3600_000).toISOString(),
}

function hostSandboxRow(overrides: Partial<HandshakeRecord> = {}): HandshakeRecord {
return {
handshake_id: 'hs-canon-1',
relationship_id: 'rel-1',
state: HandshakeState.ACTIVE,
handshake_type: 'internal',
// Misleading local_role view: claims initiator/host while this machine may be sandbox.
local_role: 'initiator',
initiator_device_role: 'host',
acceptor_device_role: 'sandbox',
initiator_coordination_device_id: 'dev-host-1',
acceptor_coordination_device_id: 'dev-sand-1',
internal_coordination_identity_complete: true,
p2p_endpoint: 'https://coord.example/beap',
local_x25519_public_key_b64: 'dGVzdC1sb2NhbC14MjU1MTktcHViLWtleQ==',
peer_x25519_public_key_b64: 'cGVlci14MjU1MTk=',
peer_mlkem768_public_key_b64: 'cGVlci1tbGtlbQ==',
initiator: {
email: 'a@example.com',
wrdesk_user_id: 'user-a',
iss: 'https://id.example',
sub: 'sub-a',
},
acceptor: {
email: 'a@example.com',
wrdesk_user_id: 'user-a',
iss: 'https://id.example',
sub: 'sub-a',
},
created_at: new Date().toISOString(),
activated_at: new Date().toISOString(),
sharing_mode: null,
reciprocal_allowed: true,
tier_snapshot: {} as any,
current_tier_signals: {} as any,
last_seq_sent: 0,
last_seq_received: 0,
last_capsule_hash_sent: '',
last_capsule_hash_received: '',
effective_policy: {} as any,
external_processing: 'none',
expires_at: null,
revoked_at: null,
revocation_source: null,
initiator_wrdesk_policy_hash: '',
initiator_wrdesk_policy_version: '1',
acceptor_wrdesk_policy_hash: null,
acceptor_wrdesk_policy_version: null,
initiator_context_commitment: null,
acceptor_context_commitment: null,
...mockKeypairFields(),
...overrides,
} as HandshakeRecord
}

describe('internalSandboxesApi canonical Host-AI roles', () => {
let db: ReturnType<typeof createHandshakeTestDb>

beforeEach(() => {
db = createHandshakeTestDb()
getInstanceIdMock.mockReturnValue('dev-host-1')
})

afterEach(() => {
getInstanceIdMock.mockReturnValue('dev-host-1')
})

it('local_role host view must not make sandbox instance clone-eligible', () => {
getInstanceIdMock.mockReturnValue('dev-sand-1')
const rec = hostSandboxRow()
// Precondition: weaker local_role helper would still say "host".
expect(localDeviceRole(rec)).toBe('host')
expect(isEligibleActiveInternalHostSandboxRecord(rec, session)).toBe(false)
})

it('authoritative role uses coordination ids — sandbox instance is sandbox despite local_role', () => {
getInstanceIdMock.mockReturnValue('dev-sand-1')
insertHandshakeRecord(db, hostSandboxRow({ local_role: 'initiator' }))
expect(computeAuthoritativeDeviceInternalRole(db, session)).toBe('sandbox')
})

it('isEligible true for host instance even if local_role claims acceptor', () => {
getInstanceIdMock.mockReturnValue('dev-host-1')
const rec = hostSandboxRow({
// Wrong per-device view: acceptor would map local_role helper to sandbox.
local_role: 'acceptor',
})
expect(localDeviceRole(rec)).toBe('sandbox')
expect(isEligibleActiveInternalHostSandboxRecord(rec, session)).toBe(true)
})

it('authoritative role is host when coordination id matches host device', () => {
getInstanceIdMock.mockReturnValue('dev-host-1')
insertHandshakeRecord(db, hostSandboxRow({ local_role: 'acceptor' }))
expect(computeAuthoritativeDeviceInternalRole(db, session)).toBe('host')
})

it('service-RPC ineligible (identity incomplete) fails even when roles derive as host', () => {
getInstanceIdMock.mockReturnValue('dev-host-1')
const rec = hostSandboxRow({ internal_coordination_identity_complete: false })
expect(isEligibleActiveInternalHostSandboxRecord(rec, session)).toBe(false)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Regression: handshake.get / queryStatus / delete must apply row-level
* session visibility (same rules as handshake.list). Unauthorized or
* unauthenticated callers fail closed as HANDSHAKE_NOT_FOUND.
*/

import { describe, test, expect, beforeEach } from 'vitest'
import {
handleHandshakeRPC,
setSSOSessionProvider,
_resetSSOSessionProvider,
} from '../ipc'
import { buildTestSession } from '../sessionFactory'
import { createHandshakeTestDb } from './handshakeTestDb'
import { migrateIngestionTables } from '../../ingestion/persistenceDb'
import { insertHandshakeRecord, getHandshakeRecord } from '../db'
import { mockKeypairFields } from './mockKeypair'
import { ReasonCode, type HandshakeRecord, type SSOSession } from '../types'

const ISS = 'https://auth.optimando.ai'

function partySession(user: 'a' | 'b' | 'c'): SSOSession {
return buildTestSession({
wrdesk_user_id: `user-${user}`,
email: `${user}@test.com`,
sub: `sub-${user}`,
iss: ISS,
})
}

function standardHandshake(handshakeId: string): HandshakeRecord {
return {
handshake_id: handshakeId,
relationship_id: 'rel-row-auth',
state: 'ACTIVE',
handshake_type: 'standard',
initiator: {
wrdesk_user_id: 'user-a',
email: 'a@test.com',
iss: ISS,
sub: 'sub-a',
email_verified: true,
},
acceptor: {
wrdesk_user_id: 'user-b',
email: 'b@test.com',
iss: ISS,
sub: 'sub-b',
email_verified: true,
},
local_role: 'initiator',
sharing_mode: 'reciprocal',
reciprocal_allowed: true,
tier_snapshot: { plan: 'free' },
current_tier_signals: {},
last_seq_sent: 0,
last_seq_received: 0,
last_capsule_hash_sent: '',
last_capsule_hash_received: '',
effective_policy: {},
external_processing: 'none',
created_at: new Date().toISOString(),
initiator_wrdesk_policy_hash: '',
initiator_wrdesk_policy_version: '1.0',
...mockKeypairFields(),
} as HandshakeRecord
}

describe('handshake IPC row-level session authorization', () => {
let db: ReturnType<typeof createHandshakeTestDb>

beforeEach(() => {
db = createHandshakeTestDb()
migrateIngestionTables(db)
_resetSSOSessionProvider()
insertHandshakeRecord(db, standardHandshake('hs-row-1'))
})

test('party session can get / queryStatus the row', async () => {
setSSOSessionProvider(() => partySession('a'))
const get = await handleHandshakeRPC('handshake.get', { handshake_id: 'hs-row-1' }, db)
expect(get.error).toBeUndefined()
expect(get.record?.handshake_id).toBe('hs-row-1')

const status = await handleHandshakeRPC('handshake.queryStatus', { handshakeId: 'hs-row-1' }, db)
expect(status.reason).toBe(ReasonCode.OK)
expect(status.record?.handshake_id).toBe('hs-row-1')
})

test('unrelated session cannot get / queryStatus / delete (fail-closed NOT_FOUND)', async () => {
setSSOSessionProvider(() => partySession('c'))

const get = await handleHandshakeRPC('handshake.get', { handshake_id: 'hs-row-1' }, db)
expect(get.error).toBe('Handshake not found')
expect(get.reason).toBe(ReasonCode.HANDSHAKE_NOT_FOUND)
expect(get.record).toBeUndefined()

const status = await handleHandshakeRPC('handshake.queryStatus', { handshakeId: 'hs-row-1' }, db)
expect(status.reason).toBe(ReasonCode.HANDSHAKE_NOT_FOUND)
expect(status.record).toBeNull()

const del = await handleHandshakeRPC('handshake.delete', { handshakeId: 'hs-row-1' }, db)
expect(del.success).toBe(false)
expect(del.reason).toBe(ReasonCode.HANDSHAKE_NOT_FOUND)
expect(getHandshakeRecord(db, 'hs-row-1')).not.toBeNull()
})

test('no SSO session cannot get / queryStatus / delete (fail-closed NOT_FOUND)', async () => {
_resetSSOSessionProvider()

const get = await handleHandshakeRPC('handshake.get', { handshake_id: 'hs-row-1' }, db)
expect(get.reason).toBe(ReasonCode.HANDSHAKE_NOT_FOUND)

const status = await handleHandshakeRPC('handshake.queryStatus', { handshakeId: 'hs-row-1' }, db)
expect(status.reason).toBe(ReasonCode.HANDSHAKE_NOT_FOUND)
expect(status.record).toBeNull()

const del = await handleHandshakeRPC('handshake.delete', { handshakeId: 'hs-row-1' }, db)
expect(del.success).toBe(false)
expect(del.reason).toBe(ReasonCode.HANDSHAKE_NOT_FOUND)
expect(getHandshakeRecord(db, 'hs-row-1')).not.toBeNull()
})

test('party session can delete a revoked row after visibility check', async () => {
// deleteHandshakeRecord only allows REVOKED / EXPIRED / own PENDING_ACCEPT.
insertHandshakeRecord(db, { ...standardHandshake('hs-row-revoked'), state: 'REVOKED' })
setSSOSessionProvider(() => partySession('b'))
const del = await handleHandshakeRPC('handshake.delete', { handshakeId: 'hs-row-revoked' }, db)
expect(del.success).toBe(true)
expect(del.reason).not.toBe(ReasonCode.HANDSHAKE_NOT_FOUND)
})

test('authorized party on ACTIVE gets delete policy error, not NOT_FOUND leak', async () => {
setSSOSessionProvider(() => partySession('a'))
const del = await handleHandshakeRPC('handshake.delete', { handshakeId: 'hs-row-1' }, db)
expect(del.success).toBe(false)
expect(del.reason).not.toBe(ReasonCode.HANDSHAKE_NOT_FOUND)
expect(String(del.error || '')).toMatch(/revoked|expired|pending/i)
expect(getHandshakeRecord(db, 'hs-row-1')).not.toBeNull()
})
})
Loading
Loading