import { type Declarable } from "./declarable.js"; import type { ResourceMetadata } from "./lexicon.js"; import type { UnobservedEntity } from "./observation.js"; /** * Graph IR — the engine-neutral, lint-gated representation of a project's * resolved infrastructure graph. Painters (mermaid, graphviz, custom SVG) and * the agentic diagrammer consume this; it is a pure function of lint-clean * source. Every node traces to the file that declared it; every edge is a real * cross-resource reference (AttrRef). * * Emitted by `chant graph --format ir`. See issue #493 / epic #492. */ /** Where in the source a node came from. Entity-level (file), not a line map. */ export interface SourceLoc { /** Path of the declaring file, relative to the project root when possible. */ file: string; /** * Line number, when available. Provenance is entity-level today, so this is * usually absent — reserved for a future source map. */ line?: number; } /** A reference in an attribute projection: `.`. */ export interface AttrRefEnvelope { $ref: string; } /** One resource in the graph. */ export interface IRNode { /** Logical name (the export name, or composite-expanded name). */ id: string; /** Resource type, e.g. "GkeCluster". */ kind: string; /** Lexicon the resource belongs to, e.g. "gcp". */ lexicon: string; /** * Name of the composite *type* that expanded this node, when it came from one * (e.g. "CockroachDbCluster"). */ compositeParent?: string; /** * The composite *instance* (export name) this node belongs to — shared by every * node from the same composite call. Detail tiers collapse on this (#494). */ compositeInstance?: string; /** Literal/const/ref-resolved props. References appear as `{ $ref }`. */ attrs: Record; /** Where the node was declared. */ sourceLoc?: SourceLoc; /** * Live-only (`chant graph --live`): the observed physical identifier * (id / ARN). Absent for source-derived IR. The reference resolver (#778) * indexes on this to reconstruct edges from live resource references. */ physicalId?: string; /** * Live-only: ownership verdict read from the resource's marker (#119/#120). * `owned` = chant-managed. Absent for source-derived IR. */ ownership?: "owned" | "foreign"; /** * Live-only, undeclared nodes: the declared entity this node's * owner-reference chain resolves to (#1077) — a Pod a declared Deployment's * controller created, for instance. Presence of this field is what an * overlay reads to paint the node `runtime` instead of `warn`/foreign; a * node without it (including every declared, source-derived node) is * unaffected. */ runtimeOwner?: string; } /** A directed dependency: `from` references an attribute of `to`. */ export interface IREdge { from: string; to: string; /** "ref" for an AttrRef-derived edge. */ kind: "ref"; /** The consumer-side property the reference flows through, when derivable. */ viaAttr?: string; /** The producer-side attribute referenced (e.g. "id"). Added at detail T3. */ toAttr?: string; } /** Grouping metadata for cluster/subgraph rendering. Maps group name -> node ids. */ export interface IRGroups { byLexicon?: Record; byComposite?: Record; /** * Deployable-stack grouping (`stackName → nodeIds`). Consumers (e.g. pinhole's * boundary boxes) read this rather than inferring stacks — which is the point, * so it should never require one. * * Two sources, depending on how the project is shaped: * * - **side-by-side stacks**, declared in config and composed by * `buildDeclaredPerStack` — keys are the declared stack names. Nothing is * inferred; the project stated both the names and the membership. * - **one source tree** — keys are lexicon partitions, since each lexicon * serialises to one deployable stack. * * Formerly documented as awaiting "#513 phase 2" to regroup by nested * child-project. #513 is closed and that phase was never filed; directory * partitioning is the exception rather than the rule (#1433). */ byStack?: Record; /** Live containment (#779): a container node id → the node ids directly inside * it (VPC → subnets/SGs, subnet → instances/service). Nested *flatly* — a * subnet is both a member of its VPC's entry and a key with its own members — * so a boundary-box renderer recurses it. Populated by `chant graph --live` * from the reference resolver's containment output; absent for source IR. */ byContainer?: Record; /** Component-graph deploy wave (`chant graph --components --format ir`): * `wave-N → component node ids` that deploy in parallel in that wave. A * wave-laned renderer reads this the way `byStack` drives boundary boxes. * Present only for the component-DAG projection; absent for entity IR. */ byWave?: Record; } /** A cross-stack export this stack publishes (a `stackOutput`/`output`): its * name (the matching key another stack imports by) and the node that produces it. */ export interface IRExport { /** Output name — the cross-stack handle (e.g. "ClusterArn"). */ name: string; /** Logical name of the producing node, when resolvable. */ node?: string; /** Producer-side attribute the output reads (e.g. "Arn"). */ attr?: string; /** The published value, when the graph was observed rather than declared (#1279). */ value?: unknown; /** The deployed stack that publishes it, on an observed graph (#1279). */ stack?: string; } /** A cross-stack import this stack consumes — a `Parameter` fed from another * stack's output at deploy time. A viewer matches an import `name` to another * stack's export `name` to draw the cross-stack edge; the parameter's in-stack * consumers are ordinary `$ref` edges to `node` (#513). */ export interface IRImport { /** Parameter name — the cross-stack handle (e.g. "clusterArn"). */ name: string; /** Logical name of the parameter node. */ node: string; } /** * One generated CI job in the pipeline projection (`chant graph --components * --format ir --projection `, #989) — the same job a CI-provider * lexicon's `generateComponentPipeline` synthesizes for `chant build * --components --generate ` (see `ComponentPipelineJob`, * ./lexicon.ts), reshaped into the IR's node vocabulary. */ export interface IRPipelineNode { /** CI job name (the generator's job id — a safe YAML/workflow key). */ id: string; kind: "CIJob"; /** The component this job triggers. */ component: string; /** The stage/wave this job runs in — the same wave index as the component * graph's `groups.byWave` (one CI stage/`needs:`-level per wave). */ stage: string; } /** A `needs:` dependency between two generated CI jobs — mirrors the * `dependsOn` edge it derives from, consumer job → producer job. */ export interface IRPipelineEdge { from: string; to: string; kind: "needs"; } /** * The CI/pipeline projection of a component graph (#989): the stages/jobs/ * `needs` a CI-provider lexicon (gitlab, github, forgejo, or any lexicon * implementing `generateComponentPipeline`) would synthesize for `chant build * --components --generate `, reused here — never re-derived — as * first-class IR nodes/edges. A consumer (e.g. behold) reads this alongside * the component graph's `nodes`/`edges`/`groups.byWave` to render the CI * shape without parsing generated YAML. Present only when `chant graph * --components --format ir` is invoked with `--projection `. */ export interface IRPipeline { /** The CI-provider lexicon that produced this projection (e.g. "gitlab"). */ provider: string; /** Wave-ordered stage names — 1:1 with the component graph's `groups.byWave` keys. */ stages: string[]; nodes: IRPipelineNode[]; edges: IRPipelineEdge[]; } /** The full graph IR for a project at the default (declarable) detail level. */ export interface GraphIR { nodes: IRNode[]; edges: IREdge[]; groups: IRGroups; /** Outputs this stack publishes for other stacks to import (#513). */ exports?: IRExport[]; /** Parameters this stack imports from other stacks. A viewer matches an import's * `name` to another stack's export `name` to draw the cross-stack edge; the * parameter's in-stack consumers are ordinary `$ref` edges to it (#513). */ imports?: IRImport[]; /** The CI/pipeline projection alongside the component graph (#989) — see {@link IRPipeline}. */ pipeline?: IRPipeline; /** * Attributes chant computed rather than read back from the provider, keyed by the kind * they were folded onto. An enrichment pass records what it derived here so callers can * report the graph's own surface without knowing any attribute name — the set is whatever * the passes produced, not a list maintained by hand. */ derivedAttrs?: Record; } /** * Build the graph IR from resolved entities (the output of `discover`). Pure and * deterministic: nodes and edges are sorted, so the same source yields identical * IR. `projectPath` relativizes source-file paths for portable output. */ export declare function buildGraphIr(entities: Map, projectPath?: string): GraphIR; /** One lexicon's observed resources, keyed by logical name — the output of a * plugin's `describeResources()`. The input to {@link buildLiveGraphIr}. */ export interface LiveObservation { lexicon: string; resources: Record; /** * Declared entities the lexicon could not observe (#1089), keyed by name. * They are not live nodes — but they are not confirmed-absent either, so the * overlay must not paint them "pending". See {@link sourceOverlayGraphs}. */ unobserved?: Record; /** * Relationships the lexicon observed between the resources it read (#1271). * * Without these an observation can only say what exists, never how it * connects — so a fold over topology has nothing to traverse on the live side * and a lexicon has to compute derived answers itself and inject them as * attributes. Reporting the edges instead lets the one graph fold in * {@link import("./graph-effective.js").enrichEffectiveTopology} do the work, * for every lexicon, over live and recorded observations alike. * * `from`/`to` are node ids in the same space as {@link LiveObservation.resources} * keys, so an edge can only reference something the observation also reported. */ edges?: IREdge[]; /** * What each deployed stack publishes (#1279), keyed by stack name. Stack-level * by nature, so it is carried here once and projected onto the IR's `exports` * rather than copied onto every node's `attrs` — which is what the AWS * observation used to do, leaving every VPC with the stack's `expWebIp` and * no `CidrBlock` of its own. */ stackExports?: Record>; } /** * Build the graph IR from **live-observed** resources (`chant graph --live`) — * the provisioned graph, not the declared one. This is the C1 half of epic #776: * nodes only. Edges are reconstructed separately by the per-lexicon reference * resolver (#778); containment grouping by #779. Pure and deterministic given a * fixed observation set (nodes and group ids sorted), so the same snapshot yields * identical IR. * * Each node carries the observed `physicalId` (the reference resolver's index * key) and `ownership` marker. `attrs` is the resource's observed attributes. */ export declare function buildLiveGraphIr(observations: LiveObservation[]): GraphIR; /** How an overlay learns which declared nodes were never looked at (#1089). */ export interface OverlayOptions { /** * Declared entities the observation could not read, keyed by name (union of * every {@link LiveObservation}'s `unobserved`). They are tagged `neutral` * instead of `accent`: "not yet provisioned" is a claim the read never * supported. */ unobserved?: Record; } /** The union of every observation's unobserved entities — the input to the overlays. */ export declare function collectUnobserved(observations: LiveObservation[]): Record; /** * Overlay the declared graph on the provisioned one (#780, `chant graph --live * --overlay`) and classify each resource, tagging a `_status` a renderer colours: * - **managed** (declared + provisioned) → `good` * - **runtime** (provisioned, not declared, owner chain reaches a declared * entity — #1077) → `runtime`, e.g. a Pod a declared Deployment's * controller created — expected, not a foreign resource needing attention * - **foreign** (provisioned, not declared, no declared owner) → `warn` * - **pending** (declared, provider confirmed absent) → `accent` * - **unobserved** (declared, chant could not look — #1089) → `neutral`, * plus an `_unobserved` attr carrying the reason * Live nodes keep their edges/containment; pending nodes are appended (they have * no live edges). Sorted; the live groups pass through unchanged. */ export declare function overlayGraphs(live: GraphIR, declared: GraphIR, opts?: OverlayOptions): GraphIR; /** * Source-anchored overlay (#821) — the inverse of {@link overlayGraphs}. The * **declared** graph is the canvas, so its edges are kept: cross-substrate edges * (an ECS service wired to a k8s workload) are a source-graph property, since live * reconstruction is per-substrate (identifier value-match) and never crosses * providers. Use this when the overlay must keep the mixed topology; use * `overlayGraphs` when the provisioned graph itself is the subject. * * Each declared node is classified against live observation and tagged `_status`: * - **managed** (declared + provisioned) → `good`, carrying the observed * `physicalId` / `ownership` onto the declared node * - **pending** (declared, provider confirmed absent) → `accent` * - **unobserved** (declared, chant could not look — #1089) → `neutral`, with * the reason on `_unobserved`. A wrong-cluster or unsupported-kind read used * to paint the whole estate "pending", which is the diagram equivalent of * planning a create for something that already exists. * **Foreign** resources (provisioned, not declared, no declared owner) are * appended and tagged `warn`; a provisioned-but-undeclared resource whose * owner chain reaches a declared entity (#1077) is tagged `runtime` instead — * both carry any live-reconstructed edges that touch them, since a declared * edge cannot describe an undeclared resource. Declared groups/exports pass * through unchanged; nodes and edges are sorted for deterministic output. */ export declare function sourceOverlayGraphs(declared: GraphIR, live: GraphIR, opts?: OverlayOptions): GraphIR; //# sourceMappingURL=graph-ir.d.ts.map