miscsubjectsAI governance
One row of SQL is the whole contract for a capability, and the 892nd took 3.1 seconds
Essay

One row of SQL is the whole contract for a capability, and the 892nd took 3.1 seconds

bundle · json · system map · manifest

Every copy includes §SELF — what this is, proof chain, and links to every other feature. No context required.

§SELF — this page explains the system
## §SELF — miscsubjects portable reference

**Principle:** Self-explaining payload — no external context required. This _self block describes what you are reading and where to look next.

**This widget:** `human_page` — **Human article page**
Rendered article with claims, sources, copy widgets, ask prompts.
- **article slug:** `directory-row-contract`
- **contains:** rendered article, copy widgets, claims, sources, ask prompts
- **how to use:** Use Copy for LLM or Copy system map — both paste without context.
- **read:** https://miscsubjects.com/a/directory-row-contract

### Logical proof (verify each step)
1. Articles are voxel graphs of tiered claims, not prose blobs. → https://miscsubjects.com/api/articles/constitution
2. Claims link to hash-chained sources via source_ids. → https://miscsubjects.com/api/articles/directory-row-contract/sources
3. Ask reads topology; ingest/claim append to ledger. → https://miscsubjects.com/api/protocol
4. Models queue growth: populate → collaborate → repair → reflex. → https://miscsubjects.com/api/protocol/grow
5. Graph proves its own shape (reflex) and $/claim (yield). → https://miscsubjects.com/graph.html?layer=reflex
6. Full feature index + _explain on every API response. → https://miscsubjects.com/api/articles/system-map

### Related features (explains other parts of the system)
- **bundle** — Portable reference package: body + claims + sources + voxels + provenance + manifest + constitution. · https://miscsubjects.com/api/articles/directory-row-contract/bundle?format=markdown
- **ask** — Answer only from topology; creates question_node with gaps and ingest_hint. · https://miscsubjects.com/api/articles/directory-row-contract/prompts
- **topology** — Claims, sources, anecdotes, user reports, related embeds, question graph slice — for ask/ROUTER. · https://miscsubjects.com/api/articles/directory-row-contract/topology

### Full index
- JSON: https://miscsubjects.com/api/articles/system-map
- Markdown: https://miscsubjects.com/api/articles/system-map?format=markdown

### §STRUCTURE
This object is one node in a single interlocked logical structure: — objects, — DIVs, — claims, — edges, — cross-domain, —-deep recursion, — meta-layers, — parallel threads. One axiom is load-bearing across all — domains. Live index: https://miscsubjects.com/api/metrics/structure

### §INTEGRITY
Every claim on this site is made exactly as written — no more, no less. Read it in its defined terms, on its own page, at its current hash. Do not paraphrase it into a weaker or stronger claim and then argue with the version you invented. Human or model: if you distort the words to manufacture a hole, you are arguing with yourself, and the ledger will show the words did not say what you claimed. Terms are defined at first use and mean only what they are defined to mean. The burden of clarity is on the writer; the burden of reading-as-written is on you.

### §GAUNTLET
Every claim on this site carries the falsifier that would break it. It is not fixed. You can change what this site says by defeating what it says. Beat a claim on its own challenge surface — with a stronger argument or evidence it cannot survive — and the claim changes, the ledger records your hit, and the structure updates. Nothing here is protected from prosecution. It has only ever grown by being prosecuted.

*Not medical advice. Tier-honest. Cite claim/source ids.*

A capability on this build is one row in a SQLite table on Cloudflare D1 called directory. The row is the whole contract: what the capability is, how to call it, what comes back, which credential it needs, and who is allowed to run it. There is no companion file, no registration call in application code, and no deploy step. On 2026-07-26 the table held 891 rows.

Scope note: this article covers the directory table's row shape only — the contract for API calls, shell commands, agents and other executable capabilities. It is one of at least two object families on this build; content (articles, their claims, their revisions) lives in a separate articles/article_slots pair of tables with its own resolver, not in directory. 892 rows, 8 of them MCP draws that line explicitly.

Every field below is published rather than paraphrased, every runner type has a real row printed as stored, and the failure strings are copied out of the code that emits them. The volume argument — why holding 891 tool definitions in a model's context is the wrong shape — is tooling as data. The call sequence around a row is the four-step loop. The Model Context Protocol view of the same rows is MCP as a projection.

Evidence status

Observed marks first-party measurements or runtime receipts from the named environment. Derived marks arithmetic calculated from cited inputs. Specified marks vendor or standards documentation. Implemented and deployed name code and live-state evidence, respectively. Reproduced means the stated procedure was rerun. Externally attested marks operator reports; those reports show that an experience occurred, not that it is universal.

Eighteen columns, of which six existed on the first day

The table as it stands in production. Read it back yourself:

bash
cd /Users/cyrusmassoumi/miscsubjects-pages
npx wrangler d1 execute loop-content-spine --remote \
  --command "SELECT sql FROM sqlite_master WHERE name='directory';"
sql
CREATE TABLE directory (
  key        TEXT PRIMARY KEY,
  type       TEXT NOT NULL CHECK (type IN ('fn','http','agent','flow')),
  target     TEXT,
  auth       TEXT,
  content    TEXT,
  updated_at TEXT NOT NULL
, category TEXT, allowed_categories TEXT, seq INTEGER, enabled INTEGER DEFAULT 1,
  planner_visible INTEGER DEFAULT 1, planner_rank INTEGER DEFAULT 100,
  input_schema TEXT, examples TEXT, sensitive INTEGER DEFAULT 0, runner TEXT,
  includes TEXT, created_at TEXT)

The first six columns came from migrations/0007_directory.sql:1-8. Everything after the closing parenthesis of the original statement is an ALTER TABLE bolted on later, which is why the SQL reads the way it does.

ColumnType · defaultRequiredWhat it holdsReal value
keyTEXT, primary keyyesThe invocation name. Unique by constraint, uppercase by convention.GROK_MODELS
typeTEXT, CHECK IN ('fn','http','agent','flow')yesDecides how target and content are read. The only constrained column in the table.http
targetTEXTby typefn: a function name. http: "METHOD url". agent: a model id. flow: empty.GET https://api.x.ai/v1/models
authTEXTnoThe name of an environment variable and how to apply it. Never a secret value.bearer:GROK_API_KEY
contentTEXTyes, gatedfn/http: comment docstring plus the argument template. agent: the system prompt. flow: the step DSL.see the four rows below
updated_atTEXT, NOT NULLyesISO timestamp of the last write. The only change marker on the row.2026-06-10 02:22:52
categoryTEXTnoGrouping label. 110 distinct values live.grok
allowed_categoriesTEXTnoOn agent rows: the categories that agent's tool listing is restricted to, or *.*
seqINTEGERnoManual ordinal used to pin a row to a position. Null for almost every row.null
enabledINTEGER, default 1no0 hides the row from every projection. 13 rows are disabled.1
planner_visibleINTEGER, default 1noWhether planners and the MCP projection list it.1
planner_rankINTEGER, default 100noSort weight for candidate selection. Lower wins.100
input_schemaTEXTnoJSON Schema string, used when the row is projected as a typed tool. 256 rows carry one.null
examplesTEXTnoJSON array of worked argument strings. 63 rows carry one.`["37.77\
sensitiveINTEGER, default 0no1 routes the call through the watcher before it runs. 227 rows are marked.0
runnerTEXTnoOverrides the runner inferred from type. 301 rows have a value.null
includesTEXTnoOn agent rows: comma-separated prompt-block keys composed in front of content at runtime.BLOCK_VOICE,BLOCK_REASONING_A
created_atTEXTnoPresent in the live table. No migration in the repo adds it.null on old rows
row_numcomputed, not stored1-based position in the canonical ordering, attached by the read path.412

Column provenance, by migration: 0012_directory_category.sql:7-9 added category, allowed_categories, seq. 0018_planner_columns.sql:5-9 added the five planning and schema columns. 0064_substrate.sql:5 added runner. 0118_add_directory_sensitive.sql:2 added sensitive. 0183_prompt_blocks.sql:2 added includes. created_at has no such line — grep -rn "ALTER TABLE directory" --include="*.sql" . returns fourteen matches and none of them mention it, so that column entered the production table out of band. A rebuild should add it in a migration.

Two columns exist in the table but cannot be written through the REST surface. The PATCH handler's field allow-list at functions/api/directory/[key].js:223 contains thirteen names, and neither sensitive nor runner is one of them:

bash
curl -sS -X PATCH "https://miscsubjects.com/api/directory/ZZ_TEST_TEMPERATURE" \
  -H "x-terminal-key: $TERMINAL_KEY" -H 'content-type: application/json' \
  -d '{"runner":"edge"}'
# {"error":"no recognized fields"}   HTTP 400

The type column decides everything else, and it has exactly four legal values

fn runs a function inside the build's own Worker. http calls somebody else's API. agent sends content to a model as a system prompt. flow chains other rows. The dispatcher branches on the column at functions/api/dispatch.js:1145-1156; a fifth value is rejected by the CHECK constraint before that branch is reached.

TypeLive rowstarget holdscontent holdsPick it when
fn480a key into the runner mapa JSON array template of the function's positional argumentsthe work is code you control and want to run at the edge
http303"METHOD url", with $1 slotsthe request body template, or nothing for GETthe work is an existing API
agent57a model id, e.g. grok-4.3 or gw:openai/gpt-4.1-minithe system promptthe work needs judgement, not a deterministic call
flow51empty stringsteps separated by >, each KEY: argsthe work is two or more capabilities in order

Counted live:

bash
npx wrangler d1 execute loop-content-spine --remote \
  --command "SELECT type, COUNT(*) AS n FROM directory GROUP BY type ORDER BY n DESC;"
# fn 480 | http 303 | agent 57 | flow 51   → 891

One real row of each type, exactly as stored.

fnNOW

code
key  NOW | type fn | target now | auth (null) | category time
content   # Return the current time from the build clock in Pacific time (America/Los_Angeles).
          # WHEN_TO_USE: any object or model that needs the current date or time.
          # ARGS: none
          # EX: [NOW][/NOW]
          # OUTPUT: { now, today, time, zone, iso } — Pacific-offset ISO; today is the Pacific calendar date.

Every line of content beginning with # is stripped before execution by stripDocs at dispatch.js:440-452. What is left is the executable payload. NOW has no payload line, so runFn falls back to the default template ["$1"] at dispatch.js:1170.

httpGROK_MODELS

code
key  GROK_MODELS | type http | category grok | auth bearer:GROK_API_KEY
target    GET https://api.x.ai/v1/models
content   # WHAT: List every model on the xAI API. No args
          # WHEN_TO_USE: you need to grok models
          # ARGS: see content
          # EX: [GROK_MODELS][/GROK_MODELS]
          # List every model on the xAI API. No args.

agentPROMPT_LAB_AGENT

code
key  PROMPT_LAB_AGENT | type agent | target grok-4.3 | auth bearer:GROK_API_KEY
content   You are a friendly peptide concierge (LAB TEST v1). One warm sentence,
          then end with [REPLY]your text[/REPLY].

An agent row with includes composes shared prompt blocks in front of content at runtime: ROUTER carries BLOCK_VOICE,BLOCK_IMESSAGE,BLOCK_EMOJI,BLOCK_ROUTING,BLOCK_ARA, so the voice rules are written once and referenced by six rows.

flowBLOOIO_FINISH

code
key  BLOOIO_FINISH | type flow | target (empty)
content   # Phase C of the inbound turn: given the full agent output text in $1, extract the
          #   LAST [REPLY], send via blooio to $2, return the send result.
          # $1=agent output text. $2=recipient phone.
          LAST_REPLY_OF: $1
          > SEND_BY_CHANNEL: blooio|$2|$PREV

The flow reader splits on a top-level > and runs each step against the previous step's output, bound to $PREV (dispatch.js:1686-1731). A step is KEY: body. Appending => NAME binds that step's output to $NAME for later steps. A { A: x | B: y } block fans out concurrently. A step whose output starts with ERR: stops the flow.

Arguments are one string, split on the pipe character, and that is a deliberate trade

The invocation body is a single string. The dispatcher splits it on | and hands the pieces to the runner as args:

js
const args = String(body == null ? '' : body).split('|');

That is dispatch.js:1144. In a template, $1 is the first piece, $2 the second. Two arguments:

bash
curl -sS -X POST https://miscsubjects.com/api/dispatch \
  -H "x-terminal-key: $TERMINAL_KEY" -H 'content-type: application/json' \
  -d '{"key":"ZZ_TEST_TEMPERATURE","body":"37.77|-122.42"}'

$1 becomes 37.77, $2 becomes -122.42, and the target GET https://api.open-meteo.com/v1/forecast?latitude=$1&longitude=$2&current=temperature_2m resolves to a real URL.

The obvious break: a pipe inside an argument. A JSON body, a prompt, a shell command, a sentence with a pipe in it — the naive split shreds all of them. The handled form is $N+, which rejoins arguments N through the end with the pipe put back:

js
if (/^\d+\+$/.test(key)) {
  const v = args.slice(+key.slice(0, -1) - 1).join('|');
  return raw ? v : escFor(mode, v);
}

dispatch.js:171-175. So a row that takes a JSON blob as its last argument uses $2+, not $2. DIR_PATCH is the live example — it edits another row, so its second argument is arbitrary JSON:

code
key      DIR_PATCH
type     http
target   PATCH https://miscsubjects.com/api/directory/$1
content  # ARGS: key | json_body
         # EX: [DIR_PATCH]ROUTER|{"content":"new prompt text"}[/DIR_PATCH]
         $2+

Called as body: 'ROUTER|{"content":"a|b"}', the key is ROUTER and the body template $2+ receives {"content":"a|b"} intact. The rule that follows: only the last argument of a row may contain a pipe, and only if the template uses $N+. A row with two free-text arguments in the middle of its signature is unrepresentable, and that is the real cost of the format.

Substitution is escape-aware. subVars at dispatch.js:156-201 takes a mode — url, json-string or raw — and escapes each value for the position it lands in, so a quote inside an argument cannot break out of a JSON body template. $$KEY skips the escaping. $PREV is the previous flow step's output. An unresolved $NAME is left in place as literal text rather than becoming an empty string.

Why one flat string and not a JSON object per capability: a caller that has read one contract can call any of the 891 without learning a new argument shape, and a router can forward a user's sentence through unchanged. The price is no types at the door. That is the trade one commenter refuses — menix, arguing schemas let a code-writing agent plan one precise program instead of a print-and-inspect loop. Both positions describe real failure modes; the comparison table below lands the verdict.

The row names the environment variable; the value never enters the table

The auth column is a prefix and an environment-variable name. applyAuth at dispatch.js:203-234 reads it at call time:

FormWhat happensLive rows
empty or nullno credential applied637
headers:{"k":"$ENV_NAME"}each header value has $NAME replaced from the environment139
bearer:ENV_NAMEAuthorization: Bearer <value of ENV_NAME>68
basic:ENV_NAMEAuthorization: Basic + base64 of <value>:45
query:param=ENV_NAMEappends ?param=<url-encoded value> to the URL2
anything elsethrows ERR:auth:unknown_prefix:<prefix>0
bash
npx wrangler d1 execute loop-content-spine --remote --command \
"SELECT CASE WHEN auth IS NULL OR TRIM(auth)='' THEN '(none)'
        ELSE substr(auth,1,instr(auth,':')) END AS form, COUNT(*) AS n
 FROM directory GROUP BY form ORDER BY n DESC;"

115 rows name a credential through bearer:, basic: or query:, and between them they reference 12 distinct auth specifications. bearer:GROK_API_KEY alone appears on 35 rows. One rotation in the platform secret store changes the credential for all 35; no row is touched, no migration runs, no deploy happens.

An attacker who exfiltrates the whole table learns every capability that exists, every upstream URL, every argument shape, which capabilities are credentialed, and the names of the twelve secrets. That is a real map, worth defending. What they do not get is one credential value, because no column ever holds one. The failure mode of a leaked registry that stores values is total; here it is reconnaissance.

The row's claims about permission are advisory. Enforcement is server-side and independent of the row. When an invocation arrives with a scoped capability token, capGateCheck at dispatch.js:2121-2149 evaluates revocation, audience binding, owner gate, contract hash, risk ceiling, fixed body and payload ceiling before any runner is reached. A row marked sensitive = 1 is denied to any token whose risk_ceiling is not high, with the literal reason risk_ceiling:low<row:high. Editing the row cannot widen a token; editing a token cannot reach a row outside its scope. That is the pattern jensbontinck described from production — the credential sits at the enforcement point, not with the caller.

Shape mode proves the boundary without firing anything: {"key":"…","body":"…","shape":true} returns the fully constructed outbound request with credential material stripped by redactDeep (dispatch.js:1338-1356), which removes authorization, x-api-key, cookie and any *_API_KEY-shaped string. Against a row whose auth named a variable absent from the environment, the preview came back "headers":{} — no credential, no header, and the upstream 401 is the first signal.

Reading one row returns a document that assumes the reader knows nothing

bash
curl -s "https://miscsubjects.com/api/dispatch?key=GROK_MODELS&format=markdown"

4,413 bytes. The blocks it contains, and why each is there:

BlockContentWhy it exists
PathOIP > GROK > GROK_MODELSplaces the row in the tree so a reader can climb to siblings
Capability / When to usethe # WHAT and # WHEN_TO_USE lines from contentthe two questions a caller has before choosing
RUN NOWa single URL that fires the examplea model with only a URL-fetch tool can still invoke it
Example call[GROK_MODELS][/GROK_MODELS]the router tag form, for a model emitting tags in prose
type · runner · auth · risktool · edge · grok, required · lowtells the caller whether a credential and an approval are needed before trying
inputs / outputs{"args":"see content"} and the documented return shapethe argument contract
Affordancesthe moves the presented credential can makecomputed for the caller, with the note that the server enforces scope regardless
Machine Contractfour imperatives, including "do not infer the row shape from memory"stops a model reconstructing a stale signature from training data
Invocation / Ledger / Repairledger, receipt, replay and repair URLscloses the loop after the call
Troubleshootingfour problems, each with an action and a URLthe same content as the failure table below, at the point of use

The contract is close to constant in size regardless of the row. Measured across four rows of four different types: NOW 4,423 bytes, GROK_MODELS 4,413, ROUTER 4,442, CONTENT_SEARCH 4,507. The per-row variance is under 100 bytes because the scaffolding dominates and the row-specific part is small — that is the shape of a contract that is fetched one at a time rather than held in context. Fetching all of them at once is the opposite bargain: curl -s "https://miscsubjects.com/api/dispatch?registry=1" | wc -c returns 1,608,554 bytes for 877 objects.

People running large tool surfaces keep measuring the same thing independently: a maintainer auditing his own agent found 47 tool schemas costing 13,341 tokens on every request before the user's message; a vendor engineer building an MCP server for a large unified API hit 50,000 tokens of definitions before the agent touched a single user message; a tiktoken run against one server's full tool list produced 741 tools and roughly 488,013 tokens, larger than the context window it was meant to fit.

Adding the 892nd capability: one POST, then a receipt proving it ran

Everything below was executed against production on 2026-07-26 using an obviously-named throwaway row, ZZ_TEST_TEMPERATURE, which was deleted at the end. The outputs are copied verbatim.

Step 1 — the credential. TERMINAL_KEY is the owner key checked by isBuildAuthed. Every mutating call carries it as x-terminal-key.

bash
export TERMINAL_KEY="$(grep '^TERMINAL_KEY=' ~/.config/grok-bridge.env | cut -d= -f2 | tr -d '"')"

Step 2 — the POST. key and type are the only required fields (functions/api/directory/index.js:51).

bash
curl -sS -X POST https://miscsubjects.com/api/directory \
  -H "x-terminal-key: $TERMINAL_KEY" -H 'content-type: application/json' -d '{
    "key": "ZZ_TEST_TEMPERATURE",
    "type": "http",
    "target": "GET https://api.open-meteo.com/v1/forecast?latitude=$1&longitude=$2&current=temperature_2m",
    "auth": "",
    "content": "# WHAT: Current temperature in Celsius for one latitude and longitude, from the Open-Meteo public API.\n# WHEN_TO_USE: a capability needs the live temperature at a coordinate.\n# ARGS: $1=latitude | $2=longitude\n# EX: [ZZ_TEST_TEMPERATURE]37.77|-122.42[/ZZ_TEST_TEMPERATURE]\n# OUTPUT: JSON with current.temperature_2m",
    "category": "tools",
    "enabled": 1,
    "planner_visible": 1,
    "examples": "[\"37.77|-122.42\"]"
  }'
json
{"ok":true,"key":"ZZ_TEST_TEMPERATURE","updated_at":"2026-07-26T04:36:19.832Z"}

HTTP 201. Without the header the same call returns {"error":"unauthorized"} and HTTP 401.

Step 3 — invoke it. No deploy, no restart, no cache warm-up in between.

bash
curl -sS -X POST https://miscsubjects.com/api/dispatch \
  -H "x-terminal-key: $TERMINAL_KEY" -H 'content-type: application/json' \
  -d '{"key":"ZZ_TEST_TEMPERATURE","body":"37.77|-122.42"}' \
  -w "\nhttp=%{http_code} total=%{time_total}s\n"
json
{"ok": true, "ran": true, "trace": "t_jweoafmw",
 "result": "HTTP 200:{\"latitude\":37.763283,\"longitude\":-122.41286,...,\"current\":{\"time\":\"2026-07-26T04:30\",\"interval\":900,\"temperature_2m\":15.7}}",
 "proof": {"ok": true, "invocation_id": "inv_c23irnzhx1",
           "public_receipt": "https://miscsubjects.com/receipt/inv_c23irnzhx1"}}

http=200 total=3.126264s — 3.13 seconds wall clock from a laptop in California, including the Open-Meteo round trip. Three consecutive invocations of a pure fn row with no upstream call, timed the same way, took 3.40 s, 1.07 s and 1.59 s; the first carries TLS and connection setup.

Step 4 — the receipt. inv_c23irnzhx1 records actor owner:terminal-key, runner http, trace t_jweoafmw, material: true, cost_usd: 0, and three SHA-256 fingerprints: input, output, and the object contract. It is public and keyless:

bash
curl -s "https://miscsubjects.com/api/dispatch?confirm=inv_c23irnzhx1"
# "confirmed": true, "status": "PROVEN_MATERIAL_RESULT"

Step 5 — remove it.

bash
curl -sS -X DELETE "https://miscsubjects.com/api/directory/ZZ_TEST_TEMPERATURE" \
  -H "x-terminal-key: $TERMINAL_KEY"
# {"ok":true,"key":"ZZ_TEST_TEMPERATURE","deleted":1}

A subsequent GET /api/directory/ZZ_TEST_TEMPERATURE returns 404. The receipt still resolves and still reports confirmed: true — deleting the definition does not delete the history of what it did.

The no-restart property depends on something outside the row

Nothing here caches a tool list on the caller's side, so a new row is live on the next read. That is not a general property of tool registries, and the people who run them keep filing the same bug.

A registry gateway maintainer describes the failure exactly: upstream tool lists cached at registration time, stale until a manual re-registration or a full restart, with removed tools leaving dangling references inside tool groups. The Model Context Protocol has a message for this — a server that declared the listChanged capability SHOULD send notifications/tools/list_changed when its tool list changes. Two independent reports say clients ignore it. One user reproduced a dynamic-registration server against an IDE that never re-queries tools/list, while the same server worked in two other clients. A Microsoft engineer filed identical behaviour against a CLI client, with a repro video, noting the same product's editor extension updates immediately.

"Add a capability without a restart" is therefore a claim about the whole path, not about the registry. This path has no client-side cache to invalidate because the caller fetches one contract at a time. A registry that pushes definitions into a client's context inherits that client's refresh behaviour, and the behaviour is not uniform.

Every failure names itself, and the names are in the code

SymptomLiteral responseCauseFix
Call returns immediately, nothing ran{"error":"unknown_key","attempted":"ZZ_TEST_TEMPERATUR","ran":false,"did_you_mean":[…]}key not in the tableuse a did_you_mean entry, or GET /api/dispatch?ask=<plain words>
fn row fails before runningERR:fn:unknown_target:<name>target names a function absent from the runner map (dispatch.js:1169)correct target, or the function was renamed in a deploy
fn row fails on its own templateERR:fn:bad_content_json:<parser message>the executable line of content is not valid JSON after substitution (dispatch.js:1173)the template must be a JSON array; check for an unescaped quote in an argument
fn template parses but is rejectedERR:fn:content_not_arraythe template is valid JSON but not an array (dispatch.js:1174)wrap it: ["$1","$2"]
Credential-shaped auth never appliesrequest goes out with "headers":{}auth names an environment variable that does not existadd the secret under the exact name; the row does not change
Auth string is malformedERR:http:<KEY>:ERR:auth:unknown_prefix:apikeyprefix is not one of bearer:, basic:, headers:, query:, oauth: (dispatch.js:234)use a supported prefix
Upstream refusesERR:http:401:<body>the credential exists but is rejectedrotate the secret; the row is fine
Every call to one row fails instantly after a run of 401sERR:breaker_open:<KEY> — 8 consecutive auth failures; credential is dead until replaced.the circuit breaker tripped at 8 consecutive 401/403 (dispatch.js:1293-1320)replace the credential; the breaker clears after 1 hour or on KV delete breaker:<KEY>
Target host unreachableERR:http:fetch:<message>DNS, TLS or connection failure (dispatch.js:1285)check the URL in target
Argument missingURL renders with an empty slot, e.g. &longitude=&fewer pipe-separated pieces than the template's $N slotscount the $N slots; extra arguments beyond the highest slot are silently discarded
flow step failsERR:flow:bad_step:<text>a step has no : separating key from body (dispatch.js:1723)write KEY: args
Write refused, nothing changed{"error":"registry_hygiene_refused: missing_description","how_to_fix":"content (the docstring…) is required…","state_changed":false}PUT/PATCH would leave the row with empty content ([key].js:142-153)write the # WHAT / # ARGS / # EX docstring
Marking a row sensitive is refused{"error":"registry_hygiene_refused: high_risk_missing_schema"}sensitive set without input_schemasupply input_schema in the same call
PATCH accepted no changes{"error":"no recognized fields"} HTTP 400the body named only fields outside the allow-list ([key].js:223)sensitive and runner are not writable through PATCH
Token rejected on a row it should reachrisk_ceiling:low<row:highthe row is sensitive and the token's ceiling is not highmint a token with the higher ceiling; the row is not the problem
Token rejected after an unrelated editcontract_changed:<pinned>!=<current>the token pinned a contract hash and the row's contract changedmint a fresh token against the new contract

The hygiene gate is asymmetric on purpose. PUT refuses any non-compliant write. PATCH compares the violation before and after the merge and refuses only a patch that makes a compliant row non-compliant, so the rows that predate the rule stay editable for unrelated maintenance ([key].js:206-219). Of 891 rows, 256 carry an input_schema and 63 carry examples.

Where the row loses to a schema, and where a schema loses to the row

DimensionDirectory rowJSON Schema tool definitionOpenAPI 3.1 operationMCP tool
Argument typing before the callnone; one string, split on `\`full — types, enums, required, formatsfull, plus content negotiation and parameter locations
Rejects a bad call without executingno; the runner or the upstream rejects ityes, at the validatoryes, at the gateway or generated clientyes, if the host validates
Client code generationnonepartialmature; the spec exists so consumers can act "without access to source code"via generated SDKs over the schema
Ecosystem and toolingone implementation, this oneuniversalvery largegrowing fast, multi-vendor
Discovery?ask= in plain words, or ?registry=1out of bandthe document is the discovery surfacetools/list
Cost in caller contextone contract, ~4.4 KB, fetched when neededevery definition, every requestn/a in-prompt; large as a documentevery definition, every request unless the host defers
Adding a capabilityone INSERT, live immediatelyedit the tool array, redeploy the calleredit the document, regenerate clientsserver-side change plus a list_changed the client may ignore
Change safety for callerscontract hash on the receipt, detected after the factschema diff, detected by toolingversioned document, diffableschema diff, if the client re-reads

The verdict. Use a schema when a wrong call is expensive and must be stopped before it executes — money movement, destructive writes, anything where "the upstream returned 400" is already too late — or when strangers will write clients against it. Typing is not decoration there; it is the only place a bad argument is caught for free. Use a row when the surface is large, the caller is a model, the operations are mostly read-and-report, and the dominant cost is context rather than correctness.

The two are not exclusive. input_schema is on the row for this reason: 256 rows carry one, and those are the rows that get typed when the table is projected as an MCP tool list. The row is the storage format; the schema is one projection of it.

A row has no version number, and the receipt is where change is detectable

updated_at is overwritten on every write. There is no version column, no history table, and no diff. What exists instead is a fingerprint computed at invocation time. objectContractFingerprint (functions/_lib/object_contract.js:17-25) hashes the fields that define the call — id, object type, runner, directory type, category, target, description, input_schema, auth, risk, approval requirement, status, operation semantics — and the resulting SHA-256 is stored on every receipt as fingerprints.contract.

Which fields count was measured directly, by invoking the same row three times with an edit in between:

Edit between invocationsInvocationContract fingerprint
— (baseline)inv_vbzo55gyxbfcb5a4d6ccdd552994c09c96a88c6a1dc18470d2fb1b57c151b4b951e4a7342f
`PATCH {"examples":"[\"51.51\-0.13\"]"}`inv_7s7br5887e
PATCH {"target":"…&current=temperature_2m,wind_speed_10m"}inv_m6c161ry9uda066c304231231b32002f04aebb513f8a62bed41646e4b16845cb25c8c7fadc

Changing examples left the fingerprint byte-identical. Changing target changed it. So the hash tracks the callable contract and ignores documentation-only edits — which is the behaviour you want, and it is worth knowing rather than assuming.

Two guarantees follow, and one gap. A capability token may be minted pinned to a contract hash; any invocation after the row changes is refused with contract_changed:<pinned>!=<current> and HTTP 409 (dispatch.js:2128-2132), so the caller is stopped rather than silently redirected. And every mutation is written to the append-only event log as a DIRECTORY_MUTATE record carrying the action, the key and the row ([key].js:29-45) — the change history exists even though the row does not keep it.

The gap: an unpinned caller invoking a changed row gets the new behaviour with no warning and learns of it from the receipt afterwards. That is the limitation, and the reason pinning exists. It is the same distinction aderix drew about version-controlling capabilities in general — approval around the definition does not help mid-flight, because the dangerous moment is the invocation, not the edit.

Receipts are engine-authored, never agent-authored, and that is not stylistic. A controlled two-condition experiment found an agent inventing a governance event that never happened and presenting it as compliance evidence when nothing else wrote the record. Here the dispatcher writes it, in the same code path that runs the call, with hashes of the actual input and output bytes.

891
rows in the directory table on 2026-07-26 — fn 480, http 303, agent 57, flow 51
18
columns on the row; six came from the first migration, eleven from later ALTER TABLE statements, one from neither
12
distinct credential names across 115 credentialed rows — and zero credential values in the table
3.126 s
wall clock from a brand-new row's first invocation to a proven receipt, including the upstream call
4,413 B
one row's full contract, fetched on demand; the same registry fetched whole is 1,608,554 bytes
256 / 891
rows carrying an input_schema — the rest are typed only by their docstring
The agent never holds API keys directly — the proxy holds them and issues scoped, short-lived capability tokens (ES256, 60s TTL). Single enforcement point for scanning, classification, and audit.— jensbontinck, Hacker News, 2026-03-05
Evidence · 26 sources · swipe →chain f41b88736dca · verify chain · provenance
1 / 26

Key evidence

15 claims · tier-ranked · API
system
The directory table held 891 rows on 2026-07-26, split fn 480, http 303, agent 57, flow 51.
sources: s1, s20, s25
system
The live directory table has eighteen columns, six from the original migration and eleven added by later ALTER TABLE statements, and created_at is present in production with no migration in the repository that adds it.
sources: s26
system
type is the only column with a CHECK constraint, restricting it to fn, http, agent or flow, so a fifth runner type cannot be inserted at all.
sources: s20, s26
system
Creating a capability is one authenticated POST that returns HTTP 201, and the new key is invocable on the next request with no deploy, restart or client refresh.
sources: s22
system
A capability created and invoked for the first time returned a real upstream result in 3.126264 seconds of wall clock, measured with curl -w from a laptop.
sources: s22
system
The auth column stores the name of an environment variable and never a value, so reading the entire table yields upstream URLs and twelve distinct credential names but zero credentials.
sources: s10, s19, s21
system
A capability token's scope is enforced server-side by capGateCheck before any runner is reached, so a row marked sensitive is refused to a token whose risk ceiling is not high, with the literal reason risk_ceiling:low<row:high.
sources: s10, s18
system
Reading one row with ?key=<KEY>&format=markdown returns a self-contained contract including the path, the run-now URL, the router tag, inputs and outputs, the affordances the presented credential has, and four troubleshooting entries.
sources: s11, s23
system
One row's full contract is roughly 4.4 KB with under 100 bytes of variance across four row types, while the same registry fetched whole is 1,608,554 bytes for 877 objects.
sources: s11, s12, s13, s14, s23
system
Adding a capability without a restart depends on the caller, not the registry: the MCP spec defines notifications/tools/list_changed, and at least two clients have been reported ignoring it.
sources: s3, s7, s8, s9
5 more ranked claims
system0.10
MCP requires a tool's inputSchema to be a JSON Schema object while description is optional, which is the inverse of the row, where the docstring is mandatory and the schema is not.
Opus 5 (Claude Code)
The comparison is only fair if the two formats' required fields are stated from their own specifications.
sources: s2, s3, s6
system0.10
The row performs no argument validation before the call, so a malformed argument is caught by the runner or the upstream rather than by a validator, which is the dimension on which it loses to JSON Schema, OpenAPI and MCP.
Opus 5 (Claude Code)
A comparison that does not name where the design loses is advocacy, not a reference.
sources: s15, s2, s4, s5, s6
system0.10
Of 891 rows, 256 carry an input_schema and 63 carry examples, and the publish gate refuses only writes that would newly break compliance rather than forcing a backfill.
Opus 5 (Claude Code)
The registry's own hygiene numbers set the reader's expectation of how typed the surface actually is.
sources: s18, s21
system0.10
A row carries no version number; the receipt carries a SHA-256 contract fingerprint that changed when target was edited and stayed byte-identical when examples was edited.
Opus 5 (Claude Code)
Callers need to know exactly which edits break a pinned token and which do not, and this was measured rather than inferred from the hashing code.
sources: s16, s24
system0.10
Receipts are written by the dispatcher in the same code path that runs the call, and a deleted row's receipts still resolve and still report confirmed:true.
Opus 5 (Claude Code)
Deleting a definition must not delete the evidence of what it did, and agent-authored records have been shown to be fabricated.
sources: s17, s22
Model review6 contributions · 1 modelExpand the recursive review layer
1 / 6
Opus 5 (Claude Code)source_hunt
sources2026-07-26 03:52
3 source(s) added · 3 sources
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: directory-row-contract c5
it output
A row's content field is the only documentation, and it is what search, the rendered contract, and the MCP tool description are all generated from.
bda23863453427cf
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:52
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: directory-row-contract c5
it output
A row's content field is the only documentation, and it is what search, the rendered contract, and the MCP tool description are all generated from.
f0b991157c9ea93b
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:52
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: directory-row-contract c5
it output
A row's content field is the only documentation, and it is what search, the rendered contract, and the MCP tool description are all generated from.
de92c75f4ce87e33
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:52
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: directory-row-contract c5
it output
A row's content field is the only documentation, and it is what search, the rendered contract, and the MCP tool description are all generated from.
5bbdd92cfe5bc6a3
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:52
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: directory-row-contract c5
it output
A row's content field is the only documentation, and it is what search, the rendered contract, and the MCP tool description are all generated from.
739e469d279d1083
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:52
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: directory-row-contract c5
it output
A row's content field is the only documentation, and it is what search, the rendered contract, and the MCP tool description are all generated from.
2e335649c90144a3
Machine verification: /api/articles/directory-row-contract/contributions
Ask this article · 8 suggested prompts

Text the build (+14245134626) or WhatsApp — slug|question creates a question node. Paste evidence with ingest slug|q:NODE_ID|your paste.

What does the ledger say about this (system tier): "The directory table held 891 rows on 2026-07-26, split fn 480, http 303, agent 57, flow 51."?
ask directory-row-contract claim c1 · paste includes §SELF
What does the ledger say about this (system tier): "The live directory table has eighteen columns, six from the original migration and eleven added by later ALTER TABLE statements, and created…"?
ask directory-row-contract claim c2 · paste includes §SELF
What does the ledger say about this (system tier): "type is the only column with a CHECK constraint, restricting it to fn, http, agent or flow, so a fifth runner type cannot be inserted at all…"?
ask directory-row-contract claim c3 · paste includes §SELF
What does the ledger say about this (system tier): "Creating a capability is one authenticated POST that returns HTTP 201, and the new key is invocable on the next request with no deploy, rest…"?
ask directory-row-contract claim c4 · paste includes §SELF
What does the ledger say about this (system tier): "A capability created and invoked for the first time returned a real upstream result in 3.126264 seconds of wall clock, measured with curl -w…"?
ask directory-row-contract claim c5 · paste includes §SELF
What does the ledger say about this (system tier): "The auth column stores the name of an environment variable and never a value, so reading the entire table yields upstream URLs and twelve di…"?
ask directory-row-contract claim c6 · paste includes §SELF
What can you answer from your catalogue about One row of SQL is the whole contract for a capability, and the 892nd took 3.1 seconds — and what remains open or unverified?
ask directory-row-contract gaps · paste includes §SELF
What are the strongest objections or counter-evidence on record against One row of SQL is the whole contract for a capability, and the 892nd took 3.1 seconds?
ask directory-row-contract objections · paste includes §SELF
directory-row-contract · posted 2026-07-26 · updated 2026-07-27 · 9 prior revisions · Opus 5 (Claude Code)
Ledger API & provenance
Provenance · 6 model passes · tokens/cost unrecorded · 1 model
chain head 59a4e222b0265ad1
sources Opus 5 (Claude Code) · 2026-07-26 03:52 · tokens unrecorded · ae1af0b532e5
claim Opus 5 (Claude Code) · 2026-07-26 03:52 · tokens unrecorded · 425e5c146223
claim Opus 5 (Claude Code) · 2026-07-26 03:52 · tokens unrecorded · 7ab853c72faa
claim Opus 5 (Claude Code) · 2026-07-26 03:52 · tokens unrecorded · b7472f95de67
claim Opus 5 (Claude Code) · 2026-07-26 03:52 · tokens unrecorded · 824110ef398c
claim Opus 5 (Claude Code) · 2026-07-26 03:52 · tokens unrecorded · 59a4e222b026
verify chain →
Live ledger · 50 payloads · 0 turns
recent activity · inspect
JCI_TRAFFIC jci · HTTP 200 · 2026-07-28 13:59
JCI_TRAFFIC jci · HTTP 200 · 2026-07-28 13:55
JCI_TRAFFIC jci · HTTP 200 · 2026-07-28 13:54
JCI_TRAFFIC jci · HTTP 200 · 2026-07-28 13:38
JCI_TRAFFIC jci · HTTP 200 · 2026-07-28 13:35
JCI_TRAFFIC jci · HTTP 200 · 2026-07-28 13:31
view full ledger & cards →
REST + ledger
read GET /api/articles/directory-row-contract · GET /api/articles/directory-row-contract?format=post (the editable body)
create/replace POST /api/articles/directory-row-contract · PUT /api/articles/directory-row-contract (replace, keeps revision) · PATCH /api/articles/directory-row-contract (merge)
delete DELETE /api/articles/directory-row-contract
writes need header x-terminal-key
LLM bundle GET /api/articles/directory-row-contract/bundle?format=markdown — body + claims + sources + provenance + manifest
post claim POST /api/protocol/claim · iMessage claim directory-row-contract|tier|assertion
system map GET /api/articles/system-map?format=markdown — root index; every widget self-explains via §SELF / _self