Docs Decisions
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, where only management makes connections and a connection is only posted to. The sandbox (0007) 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_KEYand never shown again, not even to management. - Groups are management's (permissions.md), 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
adsand Levan'sadsare 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:
- their own connection of that name;
- else one shared with them under that name, if there is exactly one;
- 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 asAuthorization: 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:
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). 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_KEYmatter 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
httpandpostgres, such as MySQL or BigQuery, and OAuth sign-in.
Building it
- 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_connectionsand sharing over MCP, the switch, allowed hosts, management's view and audit, and what happens when someone leaves. - Sources: the
httpandpostgreskinds, theinputsexport, the reads and their limits, and the answers in the run record. - Docs in automations.md and the guide for agents that report back.