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:
| Format | Content |
|---|---|
'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: stringThe 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:
| Route | Method | Function |
|---|---|---|
/api/arch/[projectId]/view | GET | getArchView |
/api/arch/[projectId]/synthesize | GET | synthesizeArch |
/api/arch/[projectId]/drift | GET | getArchDrift |
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 row | Platform type | Key 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' | DriftEntry | severity 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_id → observation, source_session_id → session) 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.
Related pages
- Arch Review Dashboard - operator surface backed by the separate
createArchReviewSdk()read service - Knowledge Graph Store - the
graph_nodesandobservationstables this layer reads - Tech-Stack Fingerprinting - stack similarity used to gate cross-project knowledge transfer