Rensei docs
Arch

Arch Query Layer

Platform-native architectural-intelligence read path over graph_nodes (no OSS SDK).

Platform-native (no OSS SDK). As of the 2026-06-07 boundary migration (platform#273), the platform's architectural-intelligence read path is implemented directly in platform code - it no longer depends on the OSS @donmai/architectural-intelligence package. Per ADR-2026-06-07-intelligence-implementation-is-platform, intelligence is platform-implemented; OSS ships execution + contracts only. The read path (getArchView, synthesizeArch, getArchDrift) is shipped and in production.

Per-repo corpus scoping (repos[]) requires a nullable repo column on graph_nodes - currently a runtime no-op.

Why no SDK? The earlier design wrapped the OSS @donmai/architectural-intelligence SDK's PostgresArchitecturalIntelligence. That dependency was removed: intelligence is owned and implemented by the closed platform, and the OSS Postgres adapter never functioned against real tenants (it assumed uuid org/project ids and a non-existent repo column). The OSS package's domain model and contracts remain documented at donmai.dev/docs/architectural-intelligence; this page documents the platform's own read layer.

The platform's architectural-intelligence read path lives in src/lib/arch/query.ts. It reads the graph_nodes table directly through the platform's postgres-js client (getRawClient().unsafe(...)) and projects rows into the platform's HTTP-stable shapes (ArchEntry / DriftEntry / ArchView). There is no intermediate SDK - the module owns both the SQL and the row→domain mapping.

The HTTP boundary at /api/arch/[projectId]/{view,synthesize,drift} is unchanged; the response shapes are stable.

Architecture

How rows are read

Reads go through a single scoped helper that binds the kind set as a text[] param and org/project as positional params:

const sql = getRawClient()
const text = `
  SELECT ${SELECT_GRAPH_NODE_COLUMNS}
  FROM graph_nodes
  WHERE type = ANY ($1::text[]) AND org_id = $2 AND project_id = $3
  ORDER BY importance_weight DESC, updated_at DESC
`
const rows = await sql.unsafe(text, [Array.from(kinds), orgId, projectId])

Every read filters org_id = $orgId AND project_id = $projectId at the application layer. The production Neon app role is BYPASSRLS, so this WHERE clause is the authoritative tenant boundary (the same contract as PgGraphStore - see Knowledge Graph Store).

Public functions

Three functions are exported from query.ts. All require an active DATABASE_URL.

getArchView(orgId, projectId, paths?, repos?)

Returns the full architectural view for a project: patterns, conventions, decisions, and drift entries, sorted by confidence descending.

const view = await getArchView('org_abc', 'proj_xyz')
// view: ArchView
// {
//   projectId: 'proj_xyz',
//   patterns: ArchEntry[],    // kind='pattern', sorted by confidence desc
//   conventions: ArchEntry[], // kind='convention'
//   decisions: ArchEntry[],   // kind='decision' (active only)
//   drift: DriftEntry[],      // deviations, sorted by detectedAt desc
//   totalNodes: number,
//   asOf: string,             // ISO-8601
// }

Patterns, conventions, and decisions are read in one query (type = ANY('{pattern,convention,decision}')). Deviations are fetched via a separate query (type = 'deviation') and surfaced as the drift array. Decision rows are surfaced only when properties.status === 'active' (an unset status defaults to active).

synthesizeArch(orgId, projectId, format, paths?, repos?)

Calls getArchView and renders the result into a human-readable format. Format options:

FormatContent
'markdown'Sectioned Markdown with confidence percentages
'mermaid'graph TD Mermaid diagram (patterns/conventions/decisions/deviations as styled nodes)
'json'Full ArchView JSON
const result = await synthesizeArch('org_abc', 'proj_xyz', 'markdown')
// result.content: string (rendered markdown)
// result.nodeCount: number
// result.format: 'markdown'
// result.generatedAt: string

The platform's markdown / mermaid renderers live in query.ts (renderMarkdown / renderMermaid) - consumers (the operator dashboard, agent prompts, and the nightly synthesis cron arch-nightly-synthesis) depend on the layout, so it is held stable.

getArchDrift(orgId, projectId, since)

Fetches deviation nodes updated after since, sorted by detectedAt ascending.

const { alerts, total } = await getArchDrift(
  'org_abc',
  'proj_xyz',
  new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // last 7 days
)

HTTP routes

The three functions back project-scoped HTTP routes:

RouteMethodFunction
/api/arch/[projectId]/viewGETgetArchView
/api/arch/[projectId]/synthesizeGETsynthesizeArch
/api/arch/[projectId]/driftGETgetArchDrift

Each route resolves the project, asserts project.orgId === auth.orgId, and then calls the query function scoped to the authenticated org - tenants cannot read each other's graphs.

Row → platform shape mapping

query.ts maps graph_nodes rows directly to the platform's HTTP wire shapes (ArchEntry, DriftEntry). The mapping mirrors the former SDK's row→domain projection so the response shape is unchanged:

graph_nodes rowPlatform typeKey mapping
type='pattern'ArchEntry (kind='pattern')paths from properties.locations[].path
type='convention'ArchEntry (kind='convention')paths from properties.examples[].path
type='decision'ArchEntry (kind='decision')description from properties.rationale; surfaced only when properties.status === 'active'
type='deviation'DriftEntryseverity from properties.severity (low/medium/high, default medium)

confidence on ArchEntry is the row's importance_weight scalar, clamped to 0..1. Citations are built from the provenance back-links (source_observation_idobservation, source_session_idsession) followed by any well-formed entries in properties.citations (kinds observation, session, change), deduped by kind:id.

Per-repo corpus scoping (no-op)

getArchView and synthesizeArch accept a repos[] parameter reserved for true per-repo corpus scoping:

await getArchView('org_abc', 'proj_xyz', undefined, ['rensei-platform', 'donmai'])

graph_nodes has no repo column today, so this parameter is a runtime no-op (kept for API stability) - the corpus is the whole project. The within-project paths[] filter (pattern locations only) works today.

On this page