A platform engineer asks two questions on the same morning. First: “Who owns the payments-api service, what’s its on-call rotation, and is it production-ready?” Second: “I’m bumping the shared terraform-modules/networking module to v3.3.0 — which application repos consume it, at what version pin?” The first is a modeled-catalog question and Port answers it at the top of the category. The second looks similar from a distance, has the same surface frame — “what’s the cross-repo state of this thing” — and is, mechanically, a completely different graph. This post is about why.


A note on what this post is, and isn’t

This is not a competitive teardown. Port raised $100M in December 2025 at an $800M valuation, led by General Atlantic with participation from Accel, Bessemer, and Team8. Their customer list runs through GitHub, British Telecom, Visa, Sonar, StubHub, and Nando’s, and they reported 300% revenue growth in the prior year. Their repositioning as the Agentic Engineering Platform is one of the cleaner category pivots in platform-engineering tooling, and Zohar Einy’s May 2026 post The hidden technical debt of agentic engineering is the most useful taxonomy of agent infrastructure debt anyone has written this year.

The argument here is narrower: modeled catalogs and source-derived dependency graphs are orthogonal categories. This isn’t a flaw in Port. It follows from the design choices that make Blueprints work in the first place. The same way Sourcegraph’s symbol graph is structurally outside what an IaC dependency graph needs — which I covered in the companion post on symbol graphs and artifact graphs — Port’s modeled catalog is structurally outside what a source-derived dependency graph produces.

If you’re shopping for an IDP and want a feature comparison, this isn’t that — Port’s own comparison pages cover the modeled-catalog category they compete in well, and this post is about an adjacent category that doesn’t appear there. The practical takeaway for platform teams evaluating “context infrastructure for the agentic era” is that the modeling layer and the parsed-dependency layer are peers, not alternatives, and the tools should look as different as the questions do.

Two questions that look similar from a distance

Sit down with a platform team in 2026 and they keep coming back to two shapes of question. They sound similar enough that vendor categories blur them.

Question one: Who owns the payments-api service? What’s the team’s on-call rotation? Is the service tagged as production-ready by our scorecards? What’s its tier-of-criticality? Which Jira project tracks its work?

This is a modeled-catalog question. The answers don’t live in source files. They live in policies, processes, and human-declared ownership boundaries. The right shape of tool is a catalog: someone declares that payments-api is owned by team-checkout, on-call goes through PagerDuty rotation checkout-primary, tier is T1, runbook lives at this Confluence URL. The catalog aggregates that information across hundreds of services and exposes a unified surface. Port is the standard-bearer for this category in 2026 and has earned that position.

Question two: I’m bumping terraform-modules/networking from v3.2.0 to v3.3.0. Which application repos consume it? Of those, which are pinned to exact v3.2.0, which to ~> 3.2, which to >= 3.0? Which umbrella modules re-export networking and pull it in transitively? What about the consumers that reference it as git::https://gitlab.company.com/infra/terraform-modules.git//networking?ref=v3.2.0 with a git ref pin?

This is a parsed-dependency question. The answers already exist, written down in machine-readable form, in the customer’s source files: source = "..." blocks in HCL, dependencies: lists in Chart.yaml, FROM lines in Dockerfiles, uses: strings in GitHub Actions workflows, include: directives in GitLab CI YAML, require statements in go.mod. Nobody declares those relationships into a catalog. The engineer declares them once, in source, when they write the IaC that consumes the artifact. To answer the question, something has to parse that source.

Both questions are reasonable. The first has a clean tool category — Port and the IDP space have made this their territory. The second has a different shape and needs different machinery.

Port’s Context Lake, read carefully

Port’s clearest architectural statement in 2026 lives on their Context Lake product page. The four feature claims, verbatim:

Feed agents with complete SDLC data — Create a unified knowledge graph. Bring your own data model so agents understand your world.

Build a library of actions for agents — Give agents what they need to operate autonomously.

Set access rules for agents — Protect your organization from destructive AI actions by enforcing guardrails.

Exposed via MCP — The context lake is accessible to every agent in your ecosystem.

The phrase to sit with is the one I emphasized: bring your own data model so agents understand your world. That sentence is the architecture compressed into ten words. The Context Lake holds exactly what the customer’s data model models. The customer’s data model is defined in Blueprints. Blueprints are JSON Schemas the platform team writes.

Team8, one of Port’s lead investors, summarized the same architecture in their December 2025 investment post: “Blueprints power a living graph of context, while agentic workflows run on top.” The word “living” matters: continuously ingested, evolving, kept current by integrations. It does not mean source-derived. The Blueprints are alive in the sense that the data flowing into them is fresh. The Blueprints themselves remain the contract the platform team defined.

Stefan Daugaard Poulsen at Eficode — now an official Port partner and one of the most experienced IDP consultants in the Nordic market — describes the same architecture from the inside: “When you start out with Port you put your attention into defining blueprints, which are the basic building blocks of your portal. Each blueprint consists of properties which you can populate from your different integrations.” That’s not criticism — that’s how Port is designed to work. The platform team designs the schema; the integrations populate it. Stefan’s broader post is unambiguously positive — titled “Choosing an internal developer portal is hard, but Port made it easy” — and the quoted line is describing the intended workflow approvingly, not flagging modeling overhead as a problem.

What a Blueprint looks like

Here’s Port’s own canonical example of how a Blueprint declares its relations to other Blueprints:

{
  "identifier": "microservice",
  "title": "Microservice",
  "schema": {
    "properties": {
      "language": { "type": "string" },
      "on-call": { "type": "string", "format": "email" },
      "slack-notifications": { "type": "string", "format": "url" }
    }
  },
  "relations": {
    "owningTeam": {
      "title": "Owning Team",
      "target": "team",
      "required": true,
      "many": false
    },
    "deployedTo": {
      "title": "Deployed To",
      "target": "environment",
      "required": false,
      "many": true
    }
  }
}

Every property the catalog will track is declared in the schema. Every relation the graph will represent is also declared in the schema: the target Blueprint, whether the relation is required, whether it’s many. The graph topology is whatever the schema says.

For modeled-catalog questions — ownership, scorecards, on-call, business criticality, deployment tier, compliance class — this is exactly the right architecture. Those properties are not declared in main.tf. They are declared by humans on purpose because humans are the source of truth for them.

What Riftmap returns for the parallel question

Now consider the parallel artifact for the parsed-dependency question. You’re the owner of a shared Helm chart at v1.2.0 and you want the list of consumer repos, their version constraints, and which of them will float to the next release.

GET /api/v1/artifacts/{helm_chart_id}/consumers
{
  "artifact": {
    "id": "b3790d64-c693-47d5-83b0-3a2c3872faf9",
    "artifact_type": "helm_chart",
    "name": "helm-library-chart",
    "source_repository": {
      "full_path": "polaris-works/platform/helm-library-chart"
    },
    "version": "1.2.0",
    "consumer_count": 5,
    "is_orphan": false
  },
  "consumers": [
    {
      "repository": { "full_path": "polaris-works/data/data-helm" },
      "version_constraint": "1.1.0",
      "source_file": "Chart.yaml", "source_line": 8,
      "is_latest": false
    },
    {
      "repository": { "full_path": "polaris-works/portal/portal-helm" },
      "version_constraint": ">=1.0.0 <2.0.0",
      "source_file": "Chart.yaml", "source_line": 8,
      "is_latest": true
    },
    {
      "repository": { "full_path": "polaris-works/payments/payments-helm" },
      "version_constraint": "~1.2.0",
      "source_file": "Chart.yaml", "source_line": 8,
      "is_latest": true
    }
  ],
  "consumers_on_latest": 4,
  "consumers_lagging": 1,
  "latest_version": "1.2.0"
}

Every relationship in this response was derived by reading the consumer repos’ Chart.yaml files — no Blueprint, no Relation declared in advance, no integration custom-built for helm_chart semantics. The parser estate already knows what a Helm chart is and what its consumer relationships look like, because the IaC tools have their own grammars and the resolver implements those grammars. The constraint shapes the parser normalises (exact-current 1.2.0, exact-behind 1.1.0, tilde range ~1.2.0, explicit range >=1.0.0 <2.0.0) are evaluated against the chart’s published version, server-side, before the agent or engineer sees the response.

The contrast is the structural argument. Port’s API requires a data model because you are the source of truth for which Blueprints matter at your organization, and that’s correct for the questions Port is built to answer. Riftmap’s API does not require a data model because the source code is the source of truth for the dependency graph, and that’s correct for the questions Riftmap is built to answer.

What Port’s “Terraform integration” actually does

It’s worth being precise here, because Port and Terraform appear in the same sentence a lot. Port has two distinct things they call “Terraform”:

The first is the port_blueprint Terraform provider. This lets you define Port’s Blueprints in HCL instead of clicking through the Builder UI. It’s catalog-as-code: your catalog schema is versioned in a Terraform state file. The provider manages Port’s catalog. It does not parse your .tf source files for module dependencies.

The second is the Terraform Cloud integration. This connects Port to HCP Terraform’s REST API and ingests Organizations, Projects, Workspaces, Runs, State Versions, State Files, and Health Assessments into Port Entities. It’s an excellent integration for what it does — you can see which workspaces ran which Terraform plans and surface failed runs in a dashboard. It is entirely API-shaped: the data comes from app.terraform.io’s REST endpoints, not from parsing customer source files. If your Terraform doesn’t run through Terraform Cloud (a great many organizations run it through Atlantis, Spacelift, env0, Scalr, custom GitHub Actions, or terraform apply from a bastion), the integration has nothing to ingest.

There’s a sharper distinction worth naming, because it shapes the rest of the argument. Port’s integrations parse the outputs of upstream systems — Terraform Cloud’s REST API, Kubernetes’ CRD schemas, Datadog’s runbook YAML, GitHub’s CODEOWNERS, PagerDuty’s escalation policies. That is a kind of parsing, and Port does it well. What Port’s integration estate doesn’t do is parse customer source files for the IaC tool’s own dependency-evaluation semantics — terraform init’s module installer, Helm’s chart dependency resolver, docker build’s ARG substitution, GitHub Actions’ reusable workflow resolution. Those evaluators are part of the IaC tools themselves, not API surfaces of upstream vendors. The dependency relationship between the networking module’s source repo and the consumer repos lives in HCL source = "..." declarations across the consumer repos, evaluated by Terraform itself at init time, and Port’s integration estate doesn’t reach that layer.

Where the architecture pushes back

Concretely, here are the shapes of dependency that show up in every infrastructure-heavy organization and that the Blueprint-plus-integration model doesn’t naturally absorb. I covered the parsing details in the Find Every Consumer series, so I’ll keep these short.

1. Terraform module sources with git URLs and ref pins.

module "vpc" {
  source = "git::https://gitlab.company.com/infra/terraform-modules.git//networking?ref=v3.2.0"
}

To model this in Port you’d need a terraform_module Blueprint, an application_repo Blueprint, and a consumes_module Relation between them — plus an integration that walks every consumer repo, parses HCL, normalizes git URLs across https://, git@, and stripped-.git forms, parses the ?ref= query parameter, and evaluates whether the ref is a tag, a branch, or a commit SHA. Port doesn’t ship that integration. (How to find every consumer of your Terraform module.)

2. Docker FROM with build-arg substitution.

ARG BASE_TAG=latest
FROM company/base-runtime:${BASE_TAG}

The actual base image consumed in production depends on --build-arg values that live in a separate file: .github/workflows/build.yml, docker-bake.hcl, docker-compose.yml, a Makefile target. Resolving the real consumer relationship requires reading the Dockerfile, finding the default, then reading the build invocation across YAML and shell. (How to find every consumer of your Docker base image.)

3. Helm chart consumers across three reference formats.

# Chart.yaml dependency
dependencies:
  - name: platform-services
    version: "~3.2.0"
    repository: "oci://registry.company.com/charts"
    alias: monitoring
# ArgoCD Application
spec:
  source:
    chart: platform-services
    repoURL: https://charts.company.com
    targetRevision: 3.2.1
# Flux HelmRelease
spec:
  chart:
    spec:
      chart: platform-services
      version: ">=3.0.0 <4.0.0"
      sourceRef:
        kind: HelmRepository
        name: company-charts

All three reference the same chart. The first uses OCI, the second HTTPS, the third a Flux source pointer that needs separate resolution. A complete answer to “who consumes platform-services at v3.2.0” needs to normalize all three forms to a canonical chart identity, evaluate constraints against published versions, follow Flux source pointers, and follow umbrella charts that re-export platform-services as a subchart. (How to find every consumer of your Helm chart.)

4. Reusable GitHub Actions workflows.

jobs:
  deploy:
    uses: company/actions/.github/workflows/deploy.yml@v2

The uses: value is a single string encoding <owner>/<repo>/<path>@<ref>. Resolving “who uses my deploy workflow” means parsing this format, normalizing the repo identifier, evaluating the ref pin, and following transitive includes when a reusable workflow itself calls other reusable workflows. None of that is exposed in the GitHub REST API in structured form. (How to find every consumer of your reusable GitHub Actions workflow.)

Port’s Blueprint system can absolutely represent the resulting graph once it’s been computed. What it can’t naturally do is compute it — because the integration estate is shaped to consume upstream system APIs, not to evaluate IaC tools’ dependency semantics against customer source files.

The maintenance gradient, scoped properly

I covered the broader version of this in the catalog maintenance trap post, and the unqualified version of that critique applies more to first-generation IDPs (Backstage in particular) than to Port’s current architecture. For the data Port’s integrations cover — services discovered via Git, deployments via Kubernetes, on-call via PagerDuty — Port’s integration-fed catalog materially reduces the drift problem.

The drift problem applies precisely where it can’t apply differently: data that lives in customer source files for which no upstream API exposes structured semantics. The relationship between payments-helm’s Chart.yaml and helm-library-chart at v1.2.0 is not in GitHub’s REST API as structured dependency data. It’s in YAML that the customer wrote, that Helm evaluates at install time, against a registry that the customer also wrote. To get it into a Blueprint, somebody — Port, a platform team, an Ocean partner — has to evaluate that grammar against those sources. The catalog-maintenance argument is unchanged for this specific layer: ask a human to write down what’s already declared in source and the declaration goes stale. Build a parser that reads the source on every scan and it doesn’t.

This is the gap. Not modeled catalogs in general — modeled catalogs for IaC-source-derived data specifically.

Steelmanned objections

A few objections, taken at their strongest.

”Couldn’t Port add IaC parsers via the Ocean framework?”

In principle, yes. Port’s Ocean Extensibility Framework lets anyone build a custom integration that ingests data into Port’s Blueprints. Nothing prevents a third party — or Port themselves — from shipping an Ocean integration that parses customer Terraform, Helm, and Docker sources, and populates Port Entities with the resulting graph.

The natural place for that work to live is not inside Port. It’s at the layer below Port — a parser estate that does the IaC-specific resolution once and exposes structured output that any downstream consumer can ingest. Port via Ocean. Backstage via a plugin. Cortex via their integration framework. An MCP server for any agent. A REST API for custom dashboards. The parser estate is the same; the consumers differ. The composition story isn’t competitive — it’s compositional: a source-derived dependency graph is most useful when it’s exposed through every catalog and every agent tool list, not when it’s locked inside any single IDP.

What Port couldn’t do without changing the architecture is make source-derived parsing a native primitive — the way SCIP is native to Sourcegraph. The Blueprint-and-integration model is committed to “data model is platform-team-defined, populated by integrations from external systems.” Native parsing of customer source for IaC dependency semantics would mean a separate ingestion pipeline that derives Blueprints rather than being populated into them. That’s a sister architecture, not an extension.

”Isn’t Port’s AEP pitch the same context-for-agents pitch as Riftmap’s?”

The pitches converge at the surface and diverge at the layer. Both products are betting that “context for agents” is the next major budget category in platform engineering. Both expose MCP. Both make a credible case that agents without structured context are dangerous in production.

The layer is where they diverge. Port’s context layer is the modeled SDLC context: services, owners, scorecards, incident history, deployment history, on-call rotations, ticketing relationships. The agent picking up a Jira ticket needs to know which service is affected, who owns it, what the production tier is, what changed recently. Riftmap’s context layer is the source-derived IaC dependency graph: which Terraform modules are consumed by which repos at which versions, which Helm charts are deployed against which Kubernetes manifests, which GHA workflows are reused across the org. The agent doing a cross-repo refactor needs to know which downstream consumers will break.

An agent doing serious work in 2026–2027 needs both inputs. The dual-audience framing here is identical to the one in the symbol-graphs-and-artifact-graphs post: specialized context layers, composed by the agent’s tool list, exposed through their respective MCP servers.

Zohar Einy’s runtime-context example in the hidden-tech-debt post is actually consistent with this. His example splits cleanly across the two architectures: “what language and framework the service uses” and “who owns the downstream service it calls” are modeled-catalog answers. “How retries are handled in other services in this org” and “whether there’s been a recent config change” sit closer to source-derived. The taxonomy his post lays out is right; the inference that one platform layer should provide all of it doesn’t follow.

”Doesn’t Port’s roadmap inevitably head this direction?”

Maybe, eventually. The strategic question isn’t whether Port could ship a feature labeled “Terraform module dependency graph.” It’s whether the architecture that ships it is Blueprint-shaped or parser-shaped. A Blueprint-shaped version — where Port asks the platform team to declare terraform_module and application_repo Blueprints and populate them via a custom Ocean integration — is mostly already possible today and is the recommended path for custom data models. A parser-shaped version — where Port natively ingests customer Terraform source and derives the graph — would require a different architectural commitment.

I expect the Blueprint-shaped path to ship first, because it fits Port’s existing architecture. I expect it to underperform what dedicated parsers produce, because the resolver complexity is real and you can’t compete with multi-quarter parser investments by writing a custom integration on top of a generic ingestion framework.

Composition, not competition

Every vendor in adjacent categories has repositioned around “context for agents” in 2026. Sourcegraph 7.0 in February called itself “the intelligence layer for AI coding agents and developers”. Port relaunched at the December 2025 Series C as the Agentic Engineering Platform with the explicit framing that “humans and agents will run the SDLC together.” Cursor, Claude Code, and Amp all ship MCP integrations and rules files. The category language is converging from three different starting points.

The architecture taking shape across the agent ecosystem is multi-context composition. An agent working on a non-trivial cross-cutting change pulls from several specialized context layers:

  • Symbol context. The code-graph layer Sourcegraph owns: cross-repo function calls, type usages, API references.
  • Modeled catalog context. The IDP layer Port, Cortex, OpsLevel, Compass, and Backstage compete for: ownership, scorecards, incident history, deployment history, on-call rotations, business criticality.
  • Source-derived artifact context. The IaC dependency layer Riftmap is built for: Terraform module consumers, Helm chart consumers, Docker base image consumers, GHA workflow consumers, transitive umbrella resolution, version constraint evaluation.
  • Runtime telemetry context. Datadog, Honeycomb, Sentry, log aggregators.
  • Project and decision context. Linear, Jira, Notion, Confluence, ADR repos.

Each layer is its own product category, with its own architecture and its own MCP server. The agent’s tool list composes them. Cross-layer orchestration — composing the modeled catalog, the parsed dependency graph, the symbol graph, and the telemetry feeds into a single agent context — isn’t owned by any one platform. It’s configured by the platform team and executed by the agent. Within-layer orchestration is a different and legitimate question — Port’s Workflow Orchestrator is exactly that pitch for the modeled-catalog layer, and it’s a coherent product inside that scope. Neither it nor any tool from an adjacent layer competes to be the cross-layer orchestrator, because the cross-layer orchestrator is the agent.

This is also the structural answer to the LLM-loop objection (couldn’t Cursor or Claude Code just grep through this?) — the artifact-graph case breaks the glob-and-grep loop because the truth requires resolver semantics, not just text matching. The symbol-graphs-and-artifact-graphs post covers the architectural reason in detail.

The test for whether this category has matured is whether nobody is surprised that the IaC dependency layer lives behind its own MCP server, just as nobody is surprised that the code-symbol layer lives behind its own.

The short version

Modeled catalogs hold what humans declare about their systems. The schema is whatever the platform team designed; the contents are whatever the integrations ingested. The source of truth is human judgment. For “who owns this service, what tier is it, is it production-ready, what’s the on-call rotation” — modeled catalog, Port, end of discussion.

Parsed dependency graphs hold what infrastructure-as-code already declares about itself. The schema follows the ecosystems’ own grammars; the contents are derived by parsing source files; the source of truth is the customer’s repos. For “who consumes my Terraform module at v3.2.0, which Helm releases pin which chart version, which reusable GHA workflow is called across the org” — parsed dependency graph, Riftmap, built for exactly this.

The questions sound similar. The graphs are different shapes. Most platform teams have both questions, and the right answer is to use both kinds of tool — the same way a serious agent setup composes multiple specialized context layers instead of asking any one platform to serve every question.


This is the kind of question Riftmap is built to answer. It scans your GitHub or GitLab organisation with a read-only token, parses Terraform, Docker, Helm, Kustomize, Kubernetes, GitHub Actions, GitLab CI, Ansible, Go modules, and npm, and builds the cross-repo artifact graph as a queryable surface — for engineers in the UI, for agents over MCP. Five minutes to first graph. If you’ve read this far, the free tier is here.

If you’re interested in the architectural distinction from a different angle, the symbol-graphs-and-artifact-graphs post covers the same shape of argument for Sourcegraph and code-symbol graphs, and the catalog maintenance trap is the shorter companion piece on why modeled catalogs go stale for source-derivable data.

For the underlying parsing work for any single ecosystem, the Find Every Consumer series goes one ecosystem at a time: Docker base images, Terraform modules, GitHub Actions workflows, Helm charts, Go modules.