@aotter/mantle 0.0.11-alpha.16 → 0.0.11-alpha.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/docs/adapter-guide.md +108 -0
- package/docs/adr/0001-four-atom-manifest-model.md +694 -0
- package/docs/adr/0002-closed-enums-for-bindings.md +226 -0
- package/docs/adr/0007-ai-as-primary-author.md +509 -0
- package/docs/adr/0008-structured-diagnostic-shape.md +353 -0
- package/docs/adr/0009-consumer-supplied-manifests.md +270 -0
- package/docs/adr/0010-locale-and-translates.md +450 -0
- package/docs/adr/0011-adapter-port-spec.md +272 -0
- package/docs/adr/0012-views-as-public-rest.md +137 -0
- package/docs/adr/0013-agent-provisioned-consumer-projects.md +147 -0
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +418 -0
- package/docs/adr/0016-site-semantic-layer.md +55 -0
- package/docs/adr/0017-media-multi-variant-agent-side-optimization.md +181 -0
- package/docs/adr/README.md +57 -0
- package/docs/design-atoms.md +962 -0
- package/docs/design-references/editorial-blog-2026-05-05.md +218 -0
- package/docs/labels.md +67 -0
- package/docs/prompts/README.md +55 -0
- package/docs/prompts/publication.en.md +1 -0
- package/docs/prompts/publication.zh-TW.md +1 -0
- package/docs/release-process.md +555 -0
- package/package.json +10 -8
- package/skills/.keep +0 -0
- package/skills/README.md +33 -0
- package/skills/customize-design/SKILL.md +214 -0
- package/skills/extend/SKILL.md +214 -0
- package/skills/install/SKILL.md +339 -0
- package/skills/provision/SKILL.md +238 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# ADR-0007: AI-as-primary-author — three feedback loops, two role surfaces
|
|
2
|
+
|
|
3
|
+
**Status:** Carried over from POC v0.0.x; refreshed and folded for v0.1.0 (incorporates POC ADR-0013).
|
|
4
|
+
|
|
5
|
+
**Date**: 2026-05-03
|
|
6
|
+
|
|
7
|
+
**Deciders**: phsu
|
|
8
|
+
|
|
9
|
+
**Related**: ADR-0001 (the manifest the contract operates on), ADR-0008 (the structured diagnostic shape this contract emits)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
This ADR is the local statement of an Aotter-wide thesis. mantle is
|
|
16
|
+
one half of **Mantle** (**C**onfig **L**anguage for **A**pps &
|
|
17
|
+
**M**odeling) — the other half is the OLAP / data-warehouse work in
|
|
18
|
+
[`aotter-mantle`](https://github.com/aotter/aotter-mantle). Both share a
|
|
19
|
+
single design grammar:
|
|
20
|
+
|
|
21
|
+
> **Agents write config; the runtime carries the complexity.**
|
|
22
|
+
|
|
23
|
+
Hard problems — schema validation, OAuth, locale canonicalization,
|
|
24
|
+
cache invalidation, transactional state — live in the runtime, where
|
|
25
|
+
they're written once by people who understand them. The authoring
|
|
26
|
+
surface is YAML the agent fills in, with structured diagnostics
|
|
27
|
+
catching mistakes before they become production failures. Non-coders
|
|
28
|
+
benefit from agent leverage *safely* because the load-bearing logic
|
|
29
|
+
isn't in their hands.
|
|
30
|
+
|
|
31
|
+
This ADR documents what that thesis means concretely for mantle's
|
|
32
|
+
authoring contract.
|
|
33
|
+
|
|
34
|
+
The primary author/integrator of consumer apps that depend on
|
|
35
|
+
`@aotter/mantle-*` is expected to be Claude Code (or a peer LLM
|
|
36
|
+
agent) running inside the consumer project. Human contributors review
|
|
37
|
+
and steer, but the moment-to-moment work — wiring a manifest, writing
|
|
38
|
+
a Procedure handler, registering it at boot, bisecting a failure — is
|
|
39
|
+
the AI's.
|
|
40
|
+
|
|
41
|
+
This is a different DX target from the typical SDK. Conventional
|
|
42
|
+
"developer experience" thinking optimizes for a human at a terminal:
|
|
43
|
+
prose error messages, IDE squiggles, stack traces inspected by eye,
|
|
44
|
+
"let me try one more thing and see what the server logs say." That
|
|
45
|
+
loop is fine for humans and bad for AI authors:
|
|
46
|
+
|
|
47
|
+
- An AI cannot cheaply form an intuition by "running the thing and
|
|
48
|
+
seeing what happens." It can run a CLI and read structured output;
|
|
49
|
+
it cannot easily watch a streaming server log over an SSH session
|
|
50
|
+
and infer state.
|
|
51
|
+
- Prose error messages force the AI to do natural-language parsing
|
|
52
|
+
before it can act. Structured diagnostics ({code, path, expected,
|
|
53
|
+
actual, candidates, suggestion}) are directly actionable.
|
|
54
|
+
- Errors that surface only at production runtime are invisible to an
|
|
55
|
+
AI working in a dev loop — by the time they appear, the AI has
|
|
56
|
+
finished its turn and walked away. Failures must show up in the
|
|
57
|
+
feedback channel the AI is currently watching: the CLI it just ran,
|
|
58
|
+
the test suite it just executed, the deploy it is currently
|
|
59
|
+
blocking on.
|
|
60
|
+
- "Try and see" debugging on an opaque deployed system has a long
|
|
61
|
+
feedback latency that compounds badly with AI cost-per-turn. Cheap,
|
|
62
|
+
deterministic, local feedback dominates.
|
|
63
|
+
|
|
64
|
+
The naive SDK posture is "it works at runtime, errors come back as
|
|
65
|
+
HTTP 500s." That is the correct target for the customer of the
|
|
66
|
+
consumer's app. It is the wrong target for the consumer-app author.
|
|
67
|
+
|
|
68
|
+
This ADR defines the contract that fixes that gap. It also folds in
|
|
69
|
+
the role-split decision (originally POC ADR-0013), because the
|
|
70
|
+
feedback loops below only make sense when paired with the question of
|
|
71
|
+
*which AI is reading them*: the **coder agent** (Claude Code in the
|
|
72
|
+
consumer's repo) versus the **operator agent** (a content-editing
|
|
73
|
+
client like Claude Desktop, talking to the deployed Worker over MCP).
|
|
74
|
+
|
|
75
|
+
## Decision
|
|
76
|
+
|
|
77
|
+
### Part A — Three feedback loops for the coder agent
|
|
78
|
+
|
|
79
|
+
The SDK provides **three explicit feedback loops**, ordered by
|
|
80
|
+
"leftward shift" — catch the failure as early in the author's
|
|
81
|
+
workflow as physically possible:
|
|
82
|
+
|
|
83
|
+
#### Loop 1 — Static validation (`mantle validate`)
|
|
84
|
+
|
|
85
|
+
Pure manifest + handler-source check. No D1, no runtime, no
|
|
86
|
+
network. Runs in the AI's terminal in milliseconds. Reads YAML files
|
|
87
|
+
and handler registration source files; emits structured diagnostics;
|
|
88
|
+
exits non-zero on any error.
|
|
89
|
+
|
|
90
|
+
The CLI ships in the `@aotter/mantle-spec` package — it's the
|
|
91
|
+
spec authority for what a v0.1 manifest must look like.
|
|
92
|
+
|
|
93
|
+
What it catches:
|
|
94
|
+
- Manifest envelope (`apiVersion`, `kind`, `metadata.name`)
|
|
95
|
+
- `Trigger.target.procedure` references a Procedure that exists
|
|
96
|
+
- `View.from` references a Schema that exists; `View.fields` and
|
|
97
|
+
`View.filter` refer only to declared properties of that Schema
|
|
98
|
+
- `Schema.uniqueIndexes` lists only properties declared in the
|
|
99
|
+
Schema's `spec.schema.properties`
|
|
100
|
+
- `x-mantle-bind: <value>` is in the closed enum
|
|
101
|
+
- `requires.auth.all` predicates are in the v0.1 vocabulary
|
|
102
|
+
- `Trigger.source.path` does not collide with another Trigger
|
|
103
|
+
- `metadata.name` is unique within `kind`
|
|
104
|
+
- Each `Procedure.handler.ref` has a corresponding
|
|
105
|
+
`sdk.registerHandler("<ref>", ...)` call somewhere in the
|
|
106
|
+
consumer's TS source (textual grep, not runtime — boot loop is
|
|
107
|
+
Loop 2's job)
|
|
108
|
+
|
|
109
|
+
This is the cheapest loop; it must be runnable without booting
|
|
110
|
+
anything. AI authors should run it after every manifest edit.
|
|
111
|
+
|
|
112
|
+
#### Test harness (planned public package surface)
|
|
113
|
+
|
|
114
|
+
Supporting tooling that consumers use inside their own test suites —
|
|
115
|
+
it is not itself a feedback loop, it's the harness those tests run
|
|
116
|
+
against. In-memory dispatcher invocation: the consumer's test suite
|
|
117
|
+
imports a public testing module that spins up a complete dispatcher
|
|
118
|
+
against an in-memory D1 substitute, seeds rows, and calls Procedures
|
|
119
|
+
or Views directly without going through HTTP.
|
|
120
|
+
|
|
121
|
+
Target API sketch:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { createTestDispatcher } from "@aotter/mantle-runtime/testing";
|
|
125
|
+
import { manifests } from "../manifests"; // loaded by build hook
|
|
126
|
+
import { handlers } from "../handlers"; // map of ref → fn
|
|
127
|
+
|
|
128
|
+
const dispatcher = await createTestDispatcher({ manifests, handlers });
|
|
129
|
+
await dispatcher.seed({ collection: "posts", rows: [...] });
|
|
130
|
+
|
|
131
|
+
const result = await dispatcher.invoke(
|
|
132
|
+
"send-contact-message",
|
|
133
|
+
{ name: "Alex", message: "hi" },
|
|
134
|
+
{ user: { id: "user-uuid" } },
|
|
135
|
+
);
|
|
136
|
+
expect(result).toEqual({ ok: true });
|
|
137
|
+
|
|
138
|
+
const view = await dispatcher.queryView("recent-published");
|
|
139
|
+
expect(view.items).toHaveLength(2);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Catches:
|
|
143
|
+
- Handler logic bugs (validation rules, branching, error returns)
|
|
144
|
+
- Auth predicate evaluation (handler called with wrong user/staff
|
|
145
|
+
context returns `AUTH_DENIED`)
|
|
146
|
+
- Input/output schema enforcement (caller-supplied `x-mantle-bind` field
|
|
147
|
+
is rejected; handler-returned shape mismatching `output` is
|
|
148
|
+
rejected)
|
|
149
|
+
- View result correctness against fixture data
|
|
150
|
+
- Cross-Trigger correctness when one Procedure has multiple bindings
|
|
151
|
+
|
|
152
|
+
The test harness is **not** mocked — it should share the real
|
|
153
|
+
dispatcher, validator, handler registry, and in-memory runtime ports.
|
|
154
|
+
The public `testing` export has not shipped yet; current v0.1.0 code
|
|
155
|
+
uses runtime package tests plus starter integration smokes to cover
|
|
156
|
+
the same contract until the consumer-facing harness is promoted.
|
|
157
|
+
|
|
158
|
+
#### Loop 2 — Boot-time fail-fast
|
|
159
|
+
|
|
160
|
+
Dispatcher build phase, after manifests are parsed and handlers are
|
|
161
|
+
registered, before the serve loop starts. Walks the entire manifest
|
|
162
|
+
graph and the in-memory handler registry; refuses to start serving if
|
|
163
|
+
anything is missing or inconsistent.
|
|
164
|
+
|
|
165
|
+
What it catches that Loop 1 cannot:
|
|
166
|
+
- `Procedure.handler.ref` that has a `sdk.registerHandler(...)` call
|
|
167
|
+
in source (Loop 1 saw it textually) but did not actually execute
|
|
168
|
+
(e.g. the registration file wasn't imported, the boot function was
|
|
169
|
+
skipped behind a feature flag)
|
|
170
|
+
- D1 schema drift — table state in the connected D1 does not match
|
|
171
|
+
the DDL derived from current manifests; SDK refuses to serve until
|
|
172
|
+
a migration is applied or the drift is acknowledged
|
|
173
|
+
- Cross-manifest references that a recent deploy introduced without
|
|
174
|
+
the corresponding atom (e.g. updated Trigger pointed at a Procedure
|
|
175
|
+
that wasn't included in the deploy bundle)
|
|
176
|
+
|
|
177
|
+
Failure mode is **process exit non-zero**, not "log a warning and
|
|
178
|
+
serve anyway." A missing handler must not surface as a runtime 500 to
|
|
179
|
+
a customer; it must surface as a boot failure to the deploy pipeline,
|
|
180
|
+
which is visible to the AI author who just shipped.
|
|
181
|
+
|
|
182
|
+
#### Loop 3 — Runtime errors
|
|
183
|
+
|
|
184
|
+
Runtime diagnostics (see ADR-0008 and the design-atoms reference)
|
|
185
|
+
cover the cases that reach this far. The contract's job is to make
|
|
186
|
+
Loop 3 the layer that handles **input it had no way to predict**
|
|
187
|
+
(request shape, auth state, transient infrastructure failures), not
|
|
188
|
+
the layer that handles **author-side mistakes** (those are Loops 1
|
|
189
|
+
and 2's job).
|
|
190
|
+
|
|
191
|
+
#### Phase ordering rationale
|
|
192
|
+
|
|
193
|
+
The author's lifecycle is:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
edit YAML → run validate (Loop 1) → run tests (test harness)
|
|
197
|
+
→ push → deploy boots (Loop 2) → serves (Loop 3)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The earlier a class of error is caught, the cheaper it is to fix and
|
|
201
|
+
the smaller the surface area for surprise. An author waits seconds for
|
|
202
|
+
`validate`, seconds-to-minutes for the test suite, minutes for boot,
|
|
203
|
+
hours-to-days for runtime errors to be reported. The discipline is:
|
|
204
|
+
**never let a class of error surface in a later loop than where it
|
|
205
|
+
could have been caught**. If the validator could find it, boot must
|
|
206
|
+
not be the first to find it.
|
|
207
|
+
|
|
208
|
+
An earlier draft of this contract ordered Boot before Test. That was
|
|
209
|
+
wrong: tests run during the AI's edit cycle, on the AI's machine,
|
|
210
|
+
inside the same turn. Boot runs only after a deploy is committed.
|
|
211
|
+
Tests must be left of Boot in the diagram and left of Boot in the
|
|
212
|
+
discipline.
|
|
213
|
+
|
|
214
|
+
### Part B — Role-split surfaces
|
|
215
|
+
|
|
216
|
+
(Folded in from POC ADR-0013, which was a separate ADR in the POC tree
|
|
217
|
+
and is consolidated here in v0.1.0.)
|
|
218
|
+
|
|
219
|
+
Two AI agents read and write to a `@aotter/mantle-*` deployment:
|
|
220
|
+
|
|
221
|
+
1. **The coder agent** — Claude Code (cc) running inside the
|
|
222
|
+
consumer's repo. Edits manifests, handlers, scaffold files,
|
|
223
|
+
`wrangler.toml`. Runs CLI commands (`mantle validate`,
|
|
224
|
+
`mantle emit-openapi`). Reads skill files. Has shell, filesystem, git,
|
|
225
|
+
and network access in the consumer's dev environment.
|
|
226
|
+
|
|
227
|
+
2. **The operator agent** — a content-editing client (Claude Desktop,
|
|
228
|
+
claude.ai, or any MCP-speaking host) connected to the deployed
|
|
229
|
+
Worker's `/mcp` endpoint over OAuth. Calls the Day-1 MCP tool
|
|
230
|
+
catalog: generic tools (`list_entries`, `get_entry`,
|
|
231
|
+
`request_publish`, `unpublish_entry`, `archive_entry`) plus
|
|
232
|
+
per-collection authoring tools (`create_draft_<collection>`,
|
|
233
|
+
`update_draft_<collection>`). Has *only* the MCP tools the Worker
|
|
234
|
+
exposes; no shell, no filesystem, no git.
|
|
235
|
+
|
|
236
|
+
These are different roles with different *capability surfaces*. The
|
|
237
|
+
coder agent has many more capabilities than the operator agent *by
|
|
238
|
+
virtue of where it runs*, not because anyone designed the asymmetry —
|
|
239
|
+
it's a property of the environments. cc can run an arbitrary command
|
|
240
|
+
in the consumer's repo; an MCP-only client cannot.
|
|
241
|
+
|
|
242
|
+
Without an explicit principle, every feature decision can be expressed
|
|
243
|
+
on either surface. "Should `mantle validate` also be an MCP tool?"
|
|
244
|
+
"Should `update_draft` also be a CLI command?" Answered ad-hoc, the
|
|
245
|
+
two surfaces drift toward feature parity, which destroys the role
|
|
246
|
+
boundary, which destroys the security and DX properties the boundary
|
|
247
|
+
protects.
|
|
248
|
+
|
|
249
|
+
The SDK has **two surfaces, mapped to two roles**:
|
|
250
|
+
|
|
251
|
+
| Surface | Audience | Trust | Default access |
|
|
252
|
+
|---|---|---|---|
|
|
253
|
+
| **Skills + CLI + SDK TS API** | the coder agent (cc) | unconstrained — runs in consumer's repo | filesystem, shell, git, full SDK |
|
|
254
|
+
| **MCP tools** | the operator agent (MCP client) | constrained — ops session over OAuth | only the verbs declared in the Day-1 catalog |
|
|
255
|
+
|
|
256
|
+
The two surfaces are **not symmetric** and should not become
|
|
257
|
+
symmetric. Each new feature picks **one** surface based on which role
|
|
258
|
+
needs it; if the answer is "both," the feature is fundamentally about
|
|
259
|
+
content state (in which case it belongs on MCP and the coder uses the
|
|
260
|
+
SDK TS API instead of needing a matching MCP tool).
|
|
261
|
+
|
|
262
|
+
#### What goes on Skills + CLI + SDK TS API (coder surface)
|
|
263
|
+
|
|
264
|
+
- Anything that touches the **manifest set** (Schema / View /
|
|
265
|
+
Procedure / Trigger declarations). The coder edits YAML; the
|
|
266
|
+
operator never sees the manifest layer.
|
|
267
|
+
- Anything that requires **shell or filesystem access** —
|
|
268
|
+
scaffolding, test running, deploy.
|
|
269
|
+
- Anything **dev-loop shaped** — the three feedback loops above
|
|
270
|
+
(validate / boot / runtime) plus the test harness are
|
|
271
|
+
coder-surface concerns, not operator-surface.
|
|
272
|
+
- Anything that **mutates code** in the consumer's repo — registering
|
|
273
|
+
handlers, wiring imports, updating `wrangler.toml`.
|
|
274
|
+
- **Skill files** carrying domain context. cc reads these; MCP clients
|
|
275
|
+
do not.
|
|
276
|
+
|
|
277
|
+
Concrete artifacts today:
|
|
278
|
+
- `skills/<name>/SKILL.md` files in the
|
|
279
|
+
[`aotter/mantle`](https://github.com/aotter/mantle)
|
|
280
|
+
repo, discoverable by URL. Distribution as a Claude plugin is
|
|
281
|
+
**optional** (a v0.1.x convenience), not required — the canonical
|
|
282
|
+
location is the in-repo path, and any agent that can fetch a URL
|
|
283
|
+
can consume them.
|
|
284
|
+
- `mantle validate` CLI (shipped in `@aotter/mantle-spec`)
|
|
285
|
+
- `mantle emit-openapi` CLI (likewise)
|
|
286
|
+
- Public testing harness (planned; current coverage is SDK tests +
|
|
287
|
+
starter integration smokes)
|
|
288
|
+
|
|
289
|
+
#### What goes on MCP (operator surface)
|
|
290
|
+
|
|
291
|
+
- **CRUD on content state** — the Day-1 generic tools plus the
|
|
292
|
+
per-collection create/update tools generated from Schemas.
|
|
293
|
+
- Anything an **operator user** would do in a hypothetical WordPress
|
|
294
|
+
admin: write a post, update a draft, request publish, list
|
|
295
|
+
submissions.
|
|
296
|
+
- Future ops-role verbs that don't exist yet but have an obvious
|
|
297
|
+
operator use case ("translate this draft", "schedule for Friday").
|
|
298
|
+
When added, they land as **new SDK builtin tools** — declared in
|
|
299
|
+
the MCP catalog source under the closed Day-1 set, not
|
|
300
|
+
auto-generated from consumer Procedures.
|
|
301
|
+
|
|
302
|
+
#### What goes on neither (the SDK TS API only)
|
|
303
|
+
|
|
304
|
+
A small third category exists: APIs that only the **runtime itself**
|
|
305
|
+
consumes (the dispatcher, the boot validator, the diagnostic types).
|
|
306
|
+
These are exported from `@aotter/mantle-spec` for the coder to
|
|
307
|
+
import inside their handler code, but are not CLI commands and not
|
|
308
|
+
MCP tools — they're library types.
|
|
309
|
+
|
|
310
|
+
#### What does NOT go on either surface
|
|
311
|
+
|
|
312
|
+
- **Consumer Procedures auto-emitted as MCP tools.** This was
|
|
313
|
+
considered as `Trigger.source.kind: mcp` and rejected. A
|
|
314
|
+
consumer-defined Procedure carries arbitrary code; auto-emitting it
|
|
315
|
+
to MCP would let the operator agent trigger arbitrary side effects
|
|
316
|
+
defined by the coder agent. The role boundary collapses. Ops-role
|
|
317
|
+
verbs land as SDK builtins, not as auto-bound consumer Procedures.
|
|
318
|
+
- **CLI commands that mutate runtime state.** The CLI is for the
|
|
319
|
+
pre-deploy authoring loop. Once deployed, runtime state changes via
|
|
320
|
+
MCP (operator) or via direct SDK calls inside handler code
|
|
321
|
+
(developer). No `mantle publish-entry <id>` is planned — that
|
|
322
|
+
would mix the two surfaces.
|
|
323
|
+
|
|
324
|
+
## Consequences
|
|
325
|
+
|
|
326
|
+
### Pros
|
|
327
|
+
|
|
328
|
+
- Errors land in the channel the coder agent is watching at the
|
|
329
|
+
moment they author. Validation errors → terminal output of the CLI
|
|
330
|
+
they just ran. Test errors → test runner output of the suite they
|
|
331
|
+
just executed. Boot errors → deploy pipeline log the deploy is
|
|
332
|
+
blocked on. Runtime errors → customer-visible failures (the only
|
|
333
|
+
place runtime errors should appear).
|
|
334
|
+
- One diagnostic format (ADR-0008) across all loops means the coder
|
|
335
|
+
agent writes one parser, not three.
|
|
336
|
+
- Test harness as a public SDK export means consumer apps can ship
|
|
337
|
+
handler tests as part of normal CI without wiring up their own
|
|
338
|
+
dispatcher harness.
|
|
339
|
+
- Boot fail-fast turns a class of "runtime 500 reaches customer"
|
|
340
|
+
failures into "deploy blocked, fix forward" failures.
|
|
341
|
+
- The framing — coder agent as primary author, operator agent as
|
|
342
|
+
bounded MCP consumer, with explicit phase ordering — gives reviewers
|
|
343
|
+
a yardstick for proposed SDK features: "which loop does this
|
|
344
|
+
affect, and which surface owns it?" New error types must declare a
|
|
345
|
+
code.
|
|
346
|
+
- Permission boundary maps cleanly to surface boundary. The operator
|
|
347
|
+
agent's permissions are bounded by the MCP tool catalog; the coder
|
|
348
|
+
agent's by what shell + git + the SDK can do. Neither agent can
|
|
349
|
+
escalate by switching tools.
|
|
350
|
+
- MCP catalog stays narrow as a **property**, not a discipline.
|
|
351
|
+
Adding things to MCP requires demonstrating an operator-role use
|
|
352
|
+
case.
|
|
353
|
+
|
|
354
|
+
### Costs
|
|
355
|
+
|
|
356
|
+
- v0.1 ships more than just a runtime: also the `mantle validate`
|
|
357
|
+
CLI, a public testing module, and a boot validator. Roughly +30%
|
|
358
|
+
of the v0.1 dispatcher work.
|
|
359
|
+
- Test harness needs an in-memory D1 substitute (most plausible:
|
|
360
|
+
`better-sqlite3` or an equivalent SQLite binding running the same
|
|
361
|
+
DDL the production D1 adapter applies). Adds a dev-dependency to
|
|
362
|
+
the SDK and a compatibility surface to keep honest against real D1
|
|
363
|
+
quirks.
|
|
364
|
+
- Diagnostic format is locked early (ADR-0008); any field-shape
|
|
365
|
+
change forces a doc revise and a code change across all loops.
|
|
366
|
+
- The error catalog (one named code per failure mode, per loop) must
|
|
367
|
+
stay synchronized with the validator/harness/runtime
|
|
368
|
+
implementations. A regression where the validator and the boot
|
|
369
|
+
loop emit different codes for the same underlying issue would
|
|
370
|
+
defeat the point.
|
|
371
|
+
- Two surfaces means two surfaces. Documentation, releases,
|
|
372
|
+
versioning, and contracts apply to both. Mitigated by the fact
|
|
373
|
+
that the two surfaces have little overlap by design — most features
|
|
374
|
+
touch only one.
|
|
375
|
+
- No cross-surface convenience. A hypothetical "consumer defines a
|
|
376
|
+
Procedure that's also an MCP tool with one YAML block" would be
|
|
377
|
+
ergonomically nice but is structurally rejected by Part B.
|
|
378
|
+
|
|
379
|
+
### Risks
|
|
380
|
+
|
|
381
|
+
- **Validator under-approximates** (rejects valid manifests): authors
|
|
382
|
+
will route around it (`--no-validate` etc.), defeating the purpose.
|
|
383
|
+
Mitigation: validator MUST err on the permissive side; ambiguous
|
|
384
|
+
cases emit `severity: warning` not `error`; exit code 0 unless an
|
|
385
|
+
error is found.
|
|
386
|
+
- **Test harness diverges from runtime**: in-memory SQLite has
|
|
387
|
+
slightly different SQL semantics (collation, type affinity, JSON1
|
|
388
|
+
edge cases) from production D1. Tests pass here, prod fails. Same
|
|
389
|
+
trap as mocking the database. Mitigation: SDK itself runs starter
|
|
390
|
+
integration tests against real D1 in CI; document the
|
|
391
|
+
known-divergence list in the testing module's README.
|
|
392
|
+
- **Boot fail-fast aggressiveness**: if boot refuses to start on
|
|
393
|
+
cosmetic issues (a Schema property has an unrecognized but harmless
|
|
394
|
+
`x-` extension), the deploy pipeline becomes hostile. Mitigation:
|
|
395
|
+
boot validates only **load-bearing** invariants (missing handler
|
|
396
|
+
refs, dangling Trigger targets, schema drift). Cosmetic / advisory
|
|
397
|
+
checks are warnings via `--strict` flag, not errors.
|
|
398
|
+
- **AI authors over-trust the contract**: "the SDK said it was fine
|
|
399
|
+
therefore it is fine." Loop 3 (runtime) still exists for reasons
|
|
400
|
+
the contract cannot prevent. Mitigation: docs make clear that
|
|
401
|
+
runtime errors are still possible and that the contract reduces —
|
|
402
|
+
not eliminates — surface area.
|
|
403
|
+
- **Drift via "MCP-ifying everything".** The MCP transport is trendy;
|
|
404
|
+
there's pressure (industry-wide) to expose every API as MCP.
|
|
405
|
+
Following that trend would erase the role boundary. Mitigation:
|
|
406
|
+
this ADR is the bar to clear. Any "expose X as MCP" PR has to
|
|
407
|
+
argue X is operator-role.
|
|
408
|
+
- **Confusion when cc *uses* MCP tools to inspect a deployed
|
|
409
|
+
worker.** cc CAN call MCP tools (it has MCP-tool capability). This
|
|
410
|
+
isn't a violation — when cc calls MCP tools, it's playing the
|
|
411
|
+
operator role to inspect runtime state, not extending the coder
|
|
412
|
+
surface. The principle is about which surface a feature is
|
|
413
|
+
*exposed on*, not who calls into it.
|
|
414
|
+
- **Future Anthropic SDK changes that blur the line.** If Claude
|
|
415
|
+
Code gains MCP-tool-only modes, or if MCP clients gain shell
|
|
416
|
+
access, the role/surface mapping might need revision. This ADR is
|
|
417
|
+
grounded in the current capabilities asymmetry; if that asymmetry
|
|
418
|
+
collapses, supersede with a new ADR.
|
|
419
|
+
|
|
420
|
+
## Alternatives considered
|
|
421
|
+
|
|
422
|
+
**(a) Runtime errors only.**
|
|
423
|
+
Rejected: primary author is AI; CF deploy logs are an inappropriate
|
|
424
|
+
UI for that author. Failures must land in the loop the author is
|
|
425
|
+
currently in.
|
|
426
|
+
|
|
427
|
+
**(b) Static validation only.**
|
|
428
|
+
Rejected as insufficient: cannot catch handler logic bugs, cannot
|
|
429
|
+
verify that a `registerHandler` call actually executes (only that it
|
|
430
|
+
appears textually), cannot test data-flow against fixtures.
|
|
431
|
+
|
|
432
|
+
**(c) Test harness only.**
|
|
433
|
+
Rejected: better than (a) but still lets misconfigurations land in
|
|
434
|
+
deploys (handler renamed but not re-registered, manifest references
|
|
435
|
+
stale schema name). Static is cheaper and finds these faster.
|
|
436
|
+
|
|
437
|
+
**(d) Skip the test harness; rely on consumer's own integration
|
|
438
|
+
tests.**
|
|
439
|
+
Rejected: it is the SDK's job to make handler-level testing trivial;
|
|
440
|
+
pushing the dispatcher-spin-up burden to every consumer is exactly
|
|
441
|
+
the kind of boilerplate that demotivates writing tests in the first
|
|
442
|
+
place.
|
|
443
|
+
|
|
444
|
+
**(e) Prose error messages.**
|
|
445
|
+
Rejected: see ADR-0008. AI parseability outweighs human readability
|
|
446
|
+
for the primary author; a `--format=text` mode preserves the human
|
|
447
|
+
option without locking the structure.
|
|
448
|
+
|
|
449
|
+
**(f) Feature parity across both surfaces.** Every CLI command also
|
|
450
|
+
an MCP tool; every MCP tool also a CLI command. Rejected: this
|
|
451
|
+
destroys the role boundary by giving each agent all capabilities.
|
|
452
|
+
The "operator agent has fewer capabilities than the coder agent"
|
|
453
|
+
property would no longer be a structural fact about the surfaces; it
|
|
454
|
+
would be a discipline imposed on individual operator sessions, which
|
|
455
|
+
we have no way to enforce.
|
|
456
|
+
|
|
457
|
+
**(g) MCP-only — drop the CLI.** All SDK operations as MCP tools.
|
|
458
|
+
Rejected: cc loses the sub-second feedback loops that depend on CLI
|
|
459
|
+
tools (validate, typecheck, build). MCP tool calls are slower (HTTP
|
|
460
|
+
round trip, JSON-RPC envelope), and harder to integrate into editor
|
|
461
|
+
/ filesystem contexts where cc lives.
|
|
462
|
+
|
|
463
|
+
**(h) CLI-only — drop MCP.** All operations as CLI; the operator
|
|
464
|
+
agent shells out. Rejected: the operator agent (an MCP-only client)
|
|
465
|
+
can't shell out. MCP exists precisely because the operator-role
|
|
466
|
+
environment doesn't have a shell.
|
|
467
|
+
|
|
468
|
+
## How to apply
|
|
469
|
+
|
|
470
|
+
When proposing a new SDK feature or behavior, declare:
|
|
471
|
+
|
|
472
|
+
1. **Which agent runs this?**
|
|
473
|
+
- If the coder agent → CLI subcommand and/or SDK TS API and/or
|
|
474
|
+
SKILL.md section.
|
|
475
|
+
- If the operator agent → MCP tool added to the Day-1 catalog.
|
|
476
|
+
- If "both" — recheck. The feature is probably fundamentally a
|
|
477
|
+
CRUD-on-content verb (operator → MCP, coder uses the SDK TS API
|
|
478
|
+
directly) or a manifest-side authoring concern (coder →
|
|
479
|
+
CLI/skills, operator never sees it). Real "both" cases should
|
|
480
|
+
be vanishingly rare.
|
|
481
|
+
|
|
482
|
+
2. **Which loop owns the failure mode?** (Loop 1 static, Loop 2
|
|
483
|
+
boot, Loop 3 runtime)
|
|
484
|
+
|
|
485
|
+
3. **Could it move left?** (e.g. a runtime check that could be a
|
|
486
|
+
boot check, a boot check that could be a static check)
|
|
487
|
+
|
|
488
|
+
4. **Does it emit the structured diagnostic shape?** (ADR-0008)
|
|
489
|
+
|
|
490
|
+
5. **Can a consumer reproduce the failure offline?** (i.e. is it
|
|
491
|
+
visible to Loop 1 or to the test harness, not only Loop 3)
|
|
492
|
+
|
|
493
|
+
6. **Does the feature mutate the consumer's repo?** → CLI (or SDK
|
|
494
|
+
API the CLI uses). Never MCP — the operator agent is not in the
|
|
495
|
+
consumer's repo.
|
|
496
|
+
|
|
497
|
+
7. **Does the feature observe / mutate runtime state in the deployed
|
|
498
|
+
Worker?** → MCP tool, with the ops-role description in the
|
|
499
|
+
tool's `description` field. Or HTTP Trigger + handler if the
|
|
500
|
+
feature is consumer-shaped (a Procedure the consumer wrote).
|
|
501
|
+
Don't auto-bridge consumer Procedures to MCP.
|
|
502
|
+
|
|
503
|
+
Reviewers should treat "this lands as a runtime 500 only" as a
|
|
504
|
+
yellow flag — sometimes correct (genuine runtime conditions), often
|
|
505
|
+
a sign that earlier loops were not considered. Likewise, when a PR
|
|
506
|
+
adds either an MCP tool or a CLI command, check whether the *other*
|
|
507
|
+
surface tries to mirror the change. If yes, that's a flag — push
|
|
508
|
+
the proposer to pick one surface or to articulate a real two-role
|
|
509
|
+
use case.
|