Product architecture map + gap ledger
Mainark observes a product’s architecture; it does not own it.
| Concern | Source of truth |
|---|---|
| Code dependencies | Nx project graph (posted in the snapshot) |
| Architectural intent | Product repo .mainark/architecture.yaml (parsed into manifest) |
| Engineering standards | Product docs (e.g. Fulreach .ai/fulreach-standards.md) — rules in Mainark mirror them |
| Detected gaps | Mainark, derived on ingest |
| Engineering work | Mainark issues (via idempotent open-task) |
Ingest ordering
Snapshots carry ingestSequence (monotonic integer from CI or the exporter). Git SHAs are not used for ordering.
On POST /api/architecture/snapshot:
- Validate the full payload (reject with
400— no partial write). - If
ingestSequenceis less than stored →409 ignored_stale, DB unchanged. - If equal sequence and same
gitSha→200 unchanged(idempotent re-POST). - If equal sequence and different
gitSha→409 sequence_conflict, DB unchanged (CI must bump the sequence). - Otherwise, in one transaction: upsert the snapshot, upsert gaps by stable id, mark missing gaps
open = 0, preservelinkedIssueIdon gaps that remain.
Stable gap id: full hex digest of sha256(product + ":" + ruleId + ":" + subject). Dependency-rule subjects use the full Nx project ids (@fulreach/web->@fulreach/api), not bare basenames.
Gap granularity
Standards rules that inspect dependencies use Nx project → project edges only. Intra-project module violations are out of scope for phase 1–2.
Open Task
Orchestrator: POST /api/architecture/gaps/:id/open-task (bearer MAINARK_API_TOKEN) creates or returns the linked issue in one shot.
Web UI: call same-origin POST /api/architecture/gaps/:id/open-task (Next.js route). That route checks the session cookie and proxies with the server-only token — never put MAINARK_API_TOKEN in next.config env (it would ship to the browser). The Next server must be able to reach MAINARK_API_URL (local dev, or a reachable API URL — not Tailscale-only from Vercel).
UI
Authenticated /architecture is a single grouped explorer + inspector over the same snapshot.
Architecture is the map. Issues are the work. Agents execute the work using context from the map.
| Pane | Role |
|---|---|
| Left explorer | Always visible (desktop). Three-level tree: PRODUCT / IMPLEMENTATION (section headers, no counts) → collapsible categories (Product Areas, Workflows, Shared Capabilities, Services, Nx Projects) → selectable entities. Default: Product Areas open; other categories collapsed. Empty Shared Capabilities hidden. Selecting an entity (including via relationships / deep links) auto-expands its category and scrolls the row into view when needed. Client-side search filters entities and opens matching categories. |
| Right workspace | Architecture overview (no selection), entity inspector, findings panel, or Build graph (Nx substrate) with a shared selection side panel |
| Header | Snapshot status · open-gaps badge (opens findings) · List | Build graph view control |
Entities
| Entity | Manifest field | Role |
|---|---|---|
| Domain | domains | What the software implements |
| Component | services | Where it runs / deploys |
| Workflow | workflows | Cross-cutting flows (authored links) |
| Build graph | nx | Project→project substrate only — not deployable services |
Findings (gaps) are not explorer entities. They open from the open-gaps badge into a findings panel (filters for open / resolved / unassigned).
Selection and URL
Every architecture entity is independently selectable. Relationships navigate by selecting the destination — no Product/Components page toggle.
URL: ?entity=<type>:<id> (e.g. domain:campaigns, project:@fulreach/api). Optional view=graph, panel=findings. Browser Back/Forward restores selection. Unknown or removed ids show an unavailable state with Back to Architecture (do not silently clear). Search and group expansion do not write the URL. Relationship clicks preserve explorer scroll position.
Default (no entity): lightweight Architecture overview (counts, open findings, last synced, short needs-attention list).
domain.kind (optional)
product(default when omitted) — Product Areas (e.g. Campaigns, Inbox, Billing)capability— Shared Capabilities (e.g. Infrastructure, Observability); first-class, not second-class
Product repos should mark shared platforms (e.g. Fulreach infra) as kind: capability so they leave Product Areas. That YAML change is separate from this UI.
Domain title convention (UI)
Prefer a short navigation label. If title contains " / " (space-slash-space), the Architecture UI uses the part before as the explorer label and the part after as Scope in the inspector — e.g. Sequences / funnel sends → nav Sequences, inspector Funnel sends · Scope. Scope is a short focus phrase for the area (not a workflow name and not a technical id). Avoid " / " inside a title unless you intend that split. Longer prose belongs in a future dedicated description field, not the title.
Domain ↔ service associations
UI cross-links use manifest nxProjects overlap only (empty ingested Nx graph does not clear associations).
Each link is:
relation: "associated_with"— inferred; not proof of implementationevidence: "nx_project_overlap"matchedProjects: string[]
Future explicit ownership can use relation: "implemented_by" without replacing this model. Inspector rows show provenance as via <matchedProjects> (full explanation in the tooltip). Gap badges / open counts on explorer rows appear only when open count > 0.
Gap open-counts still use direct evidence only (domain= / workflow=), not these associations.
Inspector (context-first)
- Identity — type, title, owner/description
- Relationships — typed, clickable, with provenance
- Implementation — code paths (full paths)
- Engineering health — findings for this entity
- Actions — Open task beside a specific finding only (no generic “Dispatch AI” on entities)
Future agent context (not this surface)
When assembling agent context from the map, distinguish:
| Tier | Meaning |
|---|---|
| Explicit scope | Entities directly attached to the issue |
| Related context | Authored relationships (e.g. workflow domainIds / serviceIds) |
| Inferred context | Candidates from weak evidence (associated_with / nx overlap) |
Agents may inspect beyond suggested paths. Inferred edges never imply implementation ownership. Architecture-aware issues and agent dispatch are later stages; issues remain the unit of work.
CI reachability
GitHub-hosted runners cannot reach a Tailscale-only Mainark API. Upload from a self-hosted runner on the tailnet (or Tailscale’s GitHub Action), after Mainark’s API is live.
Related HTTP routes
See HTTP API — Architecture section.