# Foundry Data Product bindings

`NDC Foundry Binding` is a deploy/control-plane operation. Realtime facts do
not pass through the node: Foundry persists an approved page-slot binding and
its server-owned consumer reads snapshot and durable patch from External Data
Plane. The same-origin BFF serves the persisted projection and bounded history
without exposing the reader grant.

## Private workload API

```text
GET  /internal/foundry/v1/data-products
POST /internal/foundry/v1/data-product-bindings
Authorization: Bearer ndc_fndbg_<opaque capability>
```

The POST body is `nodedc.foundry.binding-upsert/v1`. It contains only
application, page, binding, Data Product projection and a stable idempotency
key. Provider, tenant, connection, endpoint, URL, raw payload and credentials
are rejected.

This credential is not a Foundry MCP `fnd1.*` capability. MCP capabilities are
short-lived interactive grants for AI Workspace. It is also not
`NODEDC_INTERNAL_ACCESS_TOKEN`, a browser session, or an EDP writer/reader
grant.

## Workload grant

The opaque value never enters a Foundry env file or application manifest.
Foundry hashes it with SHA-256 and resolves a root-owned, non-writable record
from `NODEDC_FOUNDRY_BINDING_GRANTS_DIR` on every request. Removing or replacing
the record therefore revokes or rotates access immediately.

A `nodedc.module-foundry.binding-grant.v1` record fixes:

- expiry and actions (`catalog.read`, `map-data-product.upsert`);
- actor and owner used by the existing replay-safe Foundry write path;
- exact allowlisted tuples of application, page, binding, slot, Data Product,
  semantic types and field projection.

No authorization claim is accepted from request headers or workflow data.

Before persisting a binding, Foundry verifies that the target-specific EDP
reader grant exists, is active, permits the selected product and exposes a
`snapshot+patch` product. The workload API remains fail-closed: if the grant is
not ready, POST fails with `409 data_product_reader_grant_not_ready`; an
unusable visual binding is never saved.

The Foundry MCP consumer lifecycle owns managed grant creation. Its safe
`plan` asks EDP to resolve unique active writer coverage by Data Product id.
The response contains no provider, tenant or connection. Exact `apply`
generates an opaque target token inside the persistent Foundry runtime and
sends only its SHA-256 digest in a request signed by a dedicated Foundry
Ed25519 service identity. EDP persists a durable, explicitly revocable reader
binding. The signing private key is runner-managed and distinct from Engine;
EDP mounts only its public trust copy. Legacy root-owned deployment grants are
read-only compatibility fallback, not the normal lifecycle.

The canonical Map entity-stream slot is `points`. Other templates may define
their own typed slots, but the workload grant must name them explicitly.

## Server-owned consumer lifecycle

An approved binding is the only addressable consumer target. Foundry persists
consumer state under the runtime volume, keyed by application/page/binding; it
stores the binding identity, Data Product version, policy version, cursor,
snapshot generation, safe subjects, presentation status and reconnect
metadata. It never stores the reader capability value or EDP endpoint.

Foundry MCP exposes exact `plan`/`apply`, safe `status`, bounded `accept` and
non-destructive `rollback` for this target. A missing legacy consumer record is
bootstrapped lazily from the already approved binding; a missing target grant
appears as the explicit `ensure-target-scoped-reader-grant` plan effect. An
existing Map Page therefore does not require a new provider workflow, native
n8n credential or manual pin migration.

Snapshot replacement and patch application are atomic per target. Cursor is
committed before browser fan-out. Patch replay is idempotent; a gap causes
snapshot rebase. One active upstream stream is shared by every viewer of the
same target and is closed after the final viewer releases it. Temporary source
or network failure preserves subjects. Stale and removal use the versioned
provider-neutral policy registry: removal is allowed only by authoritative
snapshot absence or canonical tombstone/revocation.
