# 0025 · Connections belong to whoever makes them, are shared like pages, and can be read before a run

**Status:** Accepted · 2026-10-28. It amends [0023](https://codna.me/docs/adr/0023-automations-reach-outside.md), where only
management makes connections and a connection is only posted to. The sandbox
([0007](https://codna.me/docs/adr/0007-quickjs-sandbox.md)) does not change.

## Context

A connection today is made by an instance's management, has one name across the instance, and is
something a script posts to. 0023 rejected connections that each person makes: "where an instance's
data may go is decided once, by management". It left data coming in open.

The work people now ask for needs both. A marketing specialist wants a daily report on the team's
campaigns. The numbers are in the ad platform and in the campaign database, and the team reaches
both with credentials of its own. The product team has other credentials for other systems. Neither
team should hold, or even see, the other's. With management making every connection:

- management holds every team's secrets, and has to be asked for each new one;
- every connection is usable by every script on the instance, whoever's team it belongs to;
- the names are one list, so two teams cannot both call theirs `ads`.

And a script can read only CoDNA. A report built from outside data has no way to get the data in.

What the rest of CoDNA already does fits these needs:

- **Pages are private to whoever made them until they share them,** with a person or a group, and
  an automation acts as its owner, seeing and doing only what they may.
- **Secrets are sealed** with `CODNA_SECRETS_KEY` and never shown again, not even to management.
- **Groups are management's** ([permissions.md](https://codna.me/docs/permissions.md#groups-and-everyone)), so a group
  is a team people can share with but cannot make up.
- **A departed person's automations stop,** and arrive switched off with whoever takes them over.

## Decision

A connection works the way a page does. Whoever makes it owns it, it is private to them, and they
share it with the people and groups who should use it. Some connections are read before a run, so
data comes in without the sandbox changing.

### A connection has an owner

- **Any person may make one**, when management lets them (below). Management still makes
  connections for the whole instance, as today. An agent makes none.
- **Its owner names it,** and names are unique per owner: Dana's `ads` and Levan's `ads` are two
  connections. CoDNA keeps them apart by owner.
- **It is private to its owner,** who alone sees it, changes its address or secret, shares it and
  removes it.

### Shared for use

- **The owner shares it with people or groups,** for example `group:marketing`, so teammates use the
  same connection and do not make their own.
- **Sharing grants use, nothing more.** Those it is shared with see its name, kind, host,
  description, example and owner, and their scripts may name it. Nobody but the owner changes it,
  and nobody, the owner included, reads its address's secret parts or its secret back.
- **Being given one, or losing one, shows in the inbox,** as a shared page does.
- **Use is checked on every run, as the automation's owner.** When the owner unshares it, removes
  it, or the person leaves the group, the next run is refused that action, and its record says why.

### How a script names one

A script names a connection the way it does today, `actions.post('ads', …)`, and CoDNA looks for it
in the automation owner's view at that moment:

1. their own connection of that name;
2. else one shared with them under that name, if there is exactly one;
3. else the instance's connection of that name, so every existing script keeps working.

When two people shared an `ads` with the same person, the bare name is refused, and the refusal
gives the full forms: `dana@acme.ge/ads`, or `instance/ads` for the instance's. The run record
names the connection used, by owner and name.

### Reading before a run

Two new kinds are **sources**, which CoDNA reads rather than posts to:

- **`http`**: a JSON API. A GET, or a POST with a body, to a path under the connection's address,
  with its one secret header, such as `Authorization: Bearer …`. The answer must be JSON.
- **`postgres`**: a database, given as a connection URL with its password. One SQL query with
  parameters, run in a read-only transaction.

The secrets are pasted when the connection is made. Signing in with OAuth, as Google's APIs want,
comes later.

A script that reads exports a second function, `inputs`, which CoDNA calls first. It is as short and
as synchronous as the main one, and returns what to read:

```js
export function inputs({ event }) {
  const day = event.at.slice(0, 10)
  return {
    ads: source('ads', { path: '/v1/campaigns/stats', query: { from: day, days: 2 } }),
    spend: source('campaign-db', {
      sql: 'select campaign, day, spend, clicks from daily_stats where day >= $1::date - 1',
      params: [day],
    }),
  }
}

export default function ({ inputs, actions }) {
  // inputs.ads.data and inputs.spend.rows, or .error when that read failed
}
```

CoDNA carries the reads out between the two calls, at most ten, in parallel, with 30 seconds and
5 MB each, and at most 10,000 rows from a query. Reporting APIs are often slow, and the reads happen
before the script runs. The script is then given time to parse what they answered, 400 ms for each
MB on top of its 100 ms, up to 30 seconds: a megabyte takes the sandbox about a third of a second. Each answer, or the error in its place, goes to the
main function. A source's address follows the rules posts follow: https for `http`, and only public
addresses, checked after the name resolves. A self-hosted instance may allow private addresses with
a setting, since its databases are usually on its own network.

The read-only transaction is a second guard. The owner should still give a connection a database
user that can only read.

A dry run says what it would read and reads nothing. Trying it with real data reads for real and
applies nothing ([0026](https://codna.me/docs/adr/0026-scripts-ask-a-model.md)). A run's record keeps what each read
answered, cut at 64 KB, for 30 days, so a wrong report can be traced to the data it was given.

### Management keeps control

- **Who may make connections** is a switch: management only, as today, or every member. Existing
  instances stay on management only until management changes it. A new instance on codna.me starts
  with members allowed: the console switches it on when it makes one. A self-hosted core starts with
  management only, for its owner to decide.
- **An optional list of allowed hosts:** when it is set, a person's connections may point only at
  those hosts.
- **Management sees every connection,** with its owner, kind, host, who it is shared with and how
  its posts and reads went. It can switch any one off or remove it. It never sees a secret.
- **Every change is in the audit**: made, changed, shared, unshared, switched off, removed, and by
  whom.
- **When someone leaves,** their connections stop with their automations. Handed over to the person
  management names, they arrive switched off, as automations do, and that person is told. Switching
  one on again restores it for everyone it was shared with.

### Where they are made

A secret is pasted only into CoDNA's own pages: **Connections** in the web UI, for anyone the switch
allows, and the console or the `codna` command for management. No MCP tool makes a connection or
changes its secret, so a secret never passes through an AI conversation. An agent can list the
connections its person may use, with how to name each in a script (`list_connections`), and share
one its person owns. When one is missing, it tells its person what to make and links the page.

## Consequences

- **Anyone allowed can send what they can read to an address of their choosing.** An automation
  reads only as its owner, so that is what they could already copy or export. The switch and the
  allowed hosts are for companies that want it decided once.
- **CoDNA holds many people's secrets,** which makes `CODNA_SECRETS_KEY` matter more. Backups hold
  them only encrypted, and losing the key means everyone pastes theirs again.
- **Data comes in from outside,** read as the owner's connection and handed to their script. It
  stays out of pages unless the script writes it into one.
- **A hosted instance reaches only public databases.** A team's database behind a firewall needs a
  read replica it exposes, or a self-hosted instance.
- **Nothing existing changes:** management's connections keep their names, and scripts that name
  them keep working.

## Alternatives rejected

- **Management makes every connection** (0023): every team's secrets with management, and every
  team's connections usable by everyone.
- **Connections owned by a group:** a group cannot paste a secret or answer for one, and groups are
  management's. A person owns it and shares it with the group.
- **One list of names across the instance:** teams would compete for `ads`, and a name would say
  nothing about whose it is.
- **`fetch()` inside the script:** what 0023 rejected it for still holds: a run's 100 ms, safe dry
  runs, and secrets kept out of the sandbox.
- **A read as an action after the run, its answer written to a page that starts a second
  automation:** two automations and a page of raw data for every report.
- **An MCP tool that makes connections:** the secret would pass through the conversation.

## Settled when it was accepted

Tornike accepted it as proposed on 2026-10-28, having settled:

- Each person names their own connections, and the same name may be used by several people; CoDNA
  keeps them apart by owner.
- The owner lists who else may use a connection, such as their team, so nobody has to make it twice.
- Pasted tokens first.

And after it was accepted, the same day:

- A new hosted instance starts with members allowed to make connections; a self-hosted core and
  every instance that already exists stay as they are.
- The reads are larger than first proposed: ten a run, 30 seconds, 5 MB and 10,000 rows each.

## Still open

- Kinds after `http` and `postgres`, such as MySQL or BigQuery, and OAuth sign-in.

## Building it

1. **Owners and sharing** for the kinds that post: an owner on each connection, names unique per
   owner, use grants, the lookup, the Connections page in the web UI, `list_connections` and sharing
   over MCP, the switch, allowed hosts, management's view and audit, and what happens when someone
   leaves.
2. **Sources:** the `http` and `postgres` kinds, the `inputs` export, the reads and their limits,
   and the answers in the run record.
3. **Docs** in [automations.md](https://codna.me/docs/automations.md) and the guide for agents that report back.
