wowbagger 0.1.0-alpha.2 → 0.1.0-alpha.4
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/CHANGELOG.md +29 -0
- package/README.md +74 -35
- package/adapters/claude-code/wowbagger-adapter.json +1 -1
- package/docs/adapter-contract.md +1280 -0
- package/docs/work-claim-contract.md +21 -4
- package/package.json +3 -1
- package/skills/wowbagger/SKILL.md +37 -5
- package/src/adapter/entrypoint-main.js +19 -5
- package/src/claim-coordinator.js +113 -7
- package/src/claim-journal.js +45 -2
- package/src/claim-publication.js +121 -59
- package/src/claim-store.js +20 -8
- package/src/cli.js +118 -11
- package/src/mutation.js +151 -32
- package/src/ready.js +64 -38
- package/src/report-html.js +198 -0
- package/src/report-markdown.js +171 -0
- package/src/report.js +396 -0
|
@@ -0,0 +1,1280 @@
|
|
|
1
|
+
# Harness-neutral adapter contract
|
|
2
|
+
|
|
3
|
+
Status: version 1 remains accepted and frozen; version 2 is implemented by the
|
|
4
|
+
shipped adapters and independent reference oracle.
|
|
5
|
+
|
|
6
|
+
This is the public contract for a thin adapter between a coding-agent harness
|
|
7
|
+
and the existing Wowbagger core CLI. It supplements [SPEC.md](../SPEC.md),
|
|
8
|
+
[the mutation contract](mutation-contract.md), and
|
|
9
|
+
[ADR 0005](adr/0005-harness-neutral-adapter-contract.md). The core contracts
|
|
10
|
+
remain authoritative for ledger meaning, command JSON, exit codes, and mutation
|
|
11
|
+
limits.
|
|
12
|
+
|
|
13
|
+
The words MUST, MUST NOT, SHOULD, and MAY are normative.
|
|
14
|
+
|
|
15
|
+
## 1. Scope and terms
|
|
16
|
+
|
|
17
|
+
An **adapter** is a small integration that translates a configured harness
|
|
18
|
+
request into a documented Wowbagger core command. It is not a second core.
|
|
19
|
+
|
|
20
|
+
| Term | Meaning |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Core | The standalone `wowbagger` CLI and its published JSON contracts. |
|
|
23
|
+
| Harness | A host that may provide a workspace, filesystem, command runner, instruction inputs, and model interaction. |
|
|
24
|
+
| Model transport | A protocol that sends model requests and responses. It can be OpenAI-compatible without being a usable coding harness. |
|
|
25
|
+
| Consumer | The repository owner or authorized operator who configures the adapter and grants authority. |
|
|
26
|
+
| Workspace | A consumer-approved repository root known to a harness under an opaque identifier. |
|
|
27
|
+
| Instruction input | Bounded text that the harness explicitly supplies for a session. It is not discovered by guessing a filename. |
|
|
28
|
+
|
|
29
|
+
Version 1 covers only these core commands: `validate`, `ready`, `capabilities`,
|
|
30
|
+
`inspect`, `create`, and `transition`. An adapter MUST use their documented
|
|
31
|
+
`--json` forms. It MUST NOT create an alternate interpretation of lifecycle,
|
|
32
|
+
readiness, revisions, locks, error codes, or mutation state.
|
|
33
|
+
|
|
34
|
+
Sections 1 through 11 define version 1 unchanged. Section 12 defines version 2
|
|
35
|
+
only as explicit deltas from that base; an unmodified rule applies to both
|
|
36
|
+
versions.
|
|
37
|
+
|
|
38
|
+
The core contract version and this adapter-contract version are different
|
|
39
|
+
numbers. `contract_version` in core JSON is defined by the core. This document
|
|
40
|
+
uses `adapter_contract_version`.
|
|
41
|
+
|
|
42
|
+
Every adapter manifest, describe result, invocation, result, instruction input,
|
|
43
|
+
and handoff is one UTF-8 JSON object with no duplicate member names at any
|
|
44
|
+
depth. A receiver rejects an invalid or duplicate-member object rather than
|
|
45
|
+
choosing a last member.
|
|
46
|
+
|
|
47
|
+
## 2. Boundary
|
|
48
|
+
|
|
49
|
+
The adapter boundary is deliberately narrow.
|
|
50
|
+
|
|
51
|
+
| Concern | Core | Adapter | Harness or consumer |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| Ledger validation and readiness | Owns | Forwards | May request |
|
|
54
|
+
| Local mutation scope and revisions | Owns | Probes and forwards | May approve a request |
|
|
55
|
+
| Command process and byte streams | Produces | Launches and preserves | Supplies a safe runner |
|
|
56
|
+
| Workspace selection and path guard | Rejects unsafe ledger entries | Verifies its own input boundary | Configures approved roots |
|
|
57
|
+
| Instruction discovery | Does not do it | Carries declared inputs | Supplies or configures inputs |
|
|
58
|
+
| Model API | Does not do it | May describe it | Provides it when applicable |
|
|
59
|
+
| Session state | Does not retain it | Carries an explicit handoff only | Stores or delivers it deliberately |
|
|
60
|
+
| Commit, push, install, or setup | Does not do it | Disabled by default | Requires separate approval |
|
|
61
|
+
|
|
62
|
+
An OpenAI-compatible API is model transport only. It MUST NOT cause an adapter
|
|
63
|
+
to report filesystem access, no-follow path handling, command execution,
|
|
64
|
+
standard-stream forwarding, instruction discovery, or mutation authority. A
|
|
65
|
+
host that has transport but lacks the required tools is API-only and cannot
|
|
66
|
+
invoke the core.
|
|
67
|
+
|
|
68
|
+
## 3. Version and capability negotiation
|
|
69
|
+
|
|
70
|
+
An adapter package has a static manifest and a dynamic `describe` operation.
|
|
71
|
+
Discovery is passive: parsing a manifest MUST NOT install software, run a
|
|
72
|
+
command, start a daemon, or contact a network service.
|
|
73
|
+
|
|
74
|
+
The consumer selects an installed package or host registration. No adapter may
|
|
75
|
+
scan a repository for a vendor file, infer a host from a model name, or select
|
|
76
|
+
a package merely because an API endpoint is reachable.
|
|
77
|
+
|
|
78
|
+
Before an invocation, a client MUST:
|
|
79
|
+
|
|
80
|
+
1. use fixed `bootstrap_wire_version` 1 to call `describe`;
|
|
81
|
+
2. select adapter contract version 1 when both peers include it, or refuse;
|
|
82
|
+
3. obtain `describe` and reject a required capability that is missing or
|
|
83
|
+
`false`;
|
|
84
|
+
4. invoke core `capabilities --json` before assuming a core mutation,
|
|
85
|
+
coordination, or durability capability; and
|
|
86
|
+
5. treat an unknown field, missing field, unsupported version, or failed probe
|
|
87
|
+
as unavailable.
|
|
88
|
+
|
|
89
|
+
The describe request is the exact object `bootstrap_wire_version`,
|
|
90
|
+
`supported_adapter_contract_versions`, and `request_id`, with no other members.
|
|
91
|
+
The request ID uses the safe opaque-ID syntax. Request version arrays are
|
|
92
|
+
nonempty, unique, sorted-ascending arrays of positive safe integers. The
|
|
93
|
+
version 1 manifest array is exactly `[1]`, and the successful dynamic result
|
|
94
|
+
selects exactly `1`; advertising a shared future version does not activate an
|
|
95
|
+
undocumented schema or handler. A future version requires a separately
|
|
96
|
+
registered manifest, request, result, and invoke-schema handler.
|
|
97
|
+
Malformed request, manifest, and dynamic objects are refused as
|
|
98
|
+
`invalid-describe-request`, `invalid-adapter-manifest`, and
|
|
99
|
+
`invalid-describe-result`, respectively; validation never depends on host
|
|
100
|
+
array methods or property access before the containing schema has passed.
|
|
101
|
+
|
|
102
|
+
The bootstrap wire version 1 refusal for a malformed `describe` request is the
|
|
103
|
+
exact envelope below. `message` is stable. For a parsed request that fails the
|
|
104
|
+
describe schema, `details.member` names the first failing member check. For an
|
|
105
|
+
input read or parse failure, `details` preserves the reader's complete
|
|
106
|
+
diagnostic object.
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"ok": false,
|
|
111
|
+
"bootstrap_wire_version": 1,
|
|
112
|
+
"error": {
|
|
113
|
+
"code": "invalid-describe-request",
|
|
114
|
+
"message": "The adapter describe request is invalid.",
|
|
115
|
+
"details": {
|
|
116
|
+
"member": "request_id"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Bootstrap is deliberately non-circular. Wire version 1 is fixed independently
|
|
123
|
+
of the adapter versions being negotiated. The client sends its supported
|
|
124
|
+
adapter versions; `describe` selects one or refuses. An implementation MUST
|
|
125
|
+
compare the static ID, adapter version, platform declaration, and required core
|
|
126
|
+
contract version with the dynamic result, then compare the required core
|
|
127
|
+
contract version with the actual `capabilities --json` probe. Any mismatch or
|
|
128
|
+
unsupported platform is a refusal before the requested core command launches.
|
|
129
|
+
For each invocation, it MUST determine the active runtime platform and launch
|
|
130
|
+
only when the matching static and dynamic platform value is exactly
|
|
131
|
+
`supported`. `unsupported`, `unverified`, and an unlisted active platform are
|
|
132
|
+
`adapter-platform-mismatch` refusals with `platform`, `status`, and
|
|
133
|
+
`required: "supported"` details before core launch.
|
|
134
|
+
|
|
135
|
+
The adapter MUST preserve the core `capabilities` result as core output. The
|
|
136
|
+
probe is the exact successful version 1 core `capabilities --json` envelope:
|
|
137
|
+
its root, backend, operations (`inspect`, `create`, `transition`, and
|
|
138
|
+
`work_claim`), durability, and limits objects accept no missing or additional
|
|
139
|
+
members and every fixed value must match the published core contract. A
|
|
140
|
+
missing, extra, malformed, wrong-command, or wrong-contract probe is refused.
|
|
141
|
+
The adapter's complete ordered version 1 command list must match the probed
|
|
142
|
+
core contract. `optional_features.claims` is derived only from
|
|
143
|
+
`operations.work_claim.supported`; `optional_features.policy` remains false
|
|
144
|
+
because core version 1 advertises no policy feature. Static or dynamic claims
|
|
145
|
+
cannot elevate either value. The adapter MUST NOT turn a local mutation
|
|
146
|
+
capability into cross-worktree, cross-clone, or
|
|
147
|
+
work-claim support.
|
|
148
|
+
|
|
149
|
+
### 3.1 Static package manifest
|
|
150
|
+
|
|
151
|
+
An installed package MAY expose a UTF-8 JSON file named
|
|
152
|
+
`wowbagger-adapter.json`. A host registration may carry the same object without
|
|
153
|
+
a file. This packaging name is not an instruction-discovery convention.
|
|
154
|
+
|
|
155
|
+
The manifest has these required members:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"adapter_manifest_version": 1,
|
|
160
|
+
"adapter_id": "org.example.wowbagger.adapter",
|
|
161
|
+
"adapter_version": "1.0.0",
|
|
162
|
+
"adapter_contract_versions": [1],
|
|
163
|
+
"bootstrap_wire_version": 1,
|
|
164
|
+
"required_core_contract_version": 1,
|
|
165
|
+
"entrypoints": {
|
|
166
|
+
"describe": {
|
|
167
|
+
"kind": "command",
|
|
168
|
+
"executable": "bin/wowbagger-adapter",
|
|
169
|
+
"fixed_args": ["describe"]
|
|
170
|
+
},
|
|
171
|
+
"invoke": {
|
|
172
|
+
"kind": "command",
|
|
173
|
+
"executable": "bin/wowbagger-adapter",
|
|
174
|
+
"fixed_args": ["invoke"]
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"platforms": {
|
|
178
|
+
"darwin": "unverified",
|
|
179
|
+
"linux": "unverified",
|
|
180
|
+
"win32": "unverified"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`adapter_id` is a stable reverse-domain-style identifier. `adapter_version` is
|
|
186
|
+
the package version. Version arrays are unique and sorted ascending. An
|
|
187
|
+
entrypoint is either a consumer-registered `host-tool` or the exact command
|
|
188
|
+
schema shown above. `executable` is a nonempty forward-slash relative path
|
|
189
|
+
anchored at the installed package root. Absolute, drive, UNC, device, volume,
|
|
190
|
+
backslash, control-character, empty-segment, `.`, and `..` forms are invalid on
|
|
191
|
+
every platform. The package root, every parent component, and the final regular
|
|
192
|
+
file are resolved no-follow and their stable identities are rechecked
|
|
193
|
+
immediately before launch. `fixed_args` is an array of UTF-8 strings without
|
|
194
|
+
NUL or control characters; arguments are passed directly and never through a
|
|
195
|
+
shell. Consumer
|
|
196
|
+
registration may replace the entire entrypoint, but a request may replace
|
|
197
|
+
neither field nor append arguments. The runner launches it directly without a
|
|
198
|
+
shell. Host-tool registrations have equivalent consumer-granted authority and
|
|
199
|
+
MUST identify the registered tool by a fixed name.
|
|
200
|
+
|
|
201
|
+
The manifest root and each entrypoint object are exact. Every displayed root
|
|
202
|
+
member is required, unknown members are refused, and a `host-tool` entrypoint
|
|
203
|
+
contains exactly `kind: "host-tool"` and a nonempty string `name`.
|
|
204
|
+
|
|
205
|
+
After manifest validation, a command entrypoint is joined to the approved
|
|
206
|
+
package root only after its relative syntax passes. A missing, link, junction,
|
|
207
|
+
reparse point, special file, escaping component, or identity replacement is a
|
|
208
|
+
refusal before the adapter process launches.
|
|
209
|
+
|
|
210
|
+
Platform values are `supported`, `unsupported`, or `unverified`. `supported`
|
|
211
|
+
requires native evidence from the common adapter vectors. A manifest MUST use
|
|
212
|
+
`unverified` until it has that evidence.
|
|
213
|
+
|
|
214
|
+
### 3.2 Dynamic describe result
|
|
215
|
+
|
|
216
|
+
`describe` returns exactly one UTF-8 JSON object. Its successful envelope has
|
|
217
|
+
these members and no undocumented root members:
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"ok": true,
|
|
222
|
+
"bootstrap_wire_version": 1,
|
|
223
|
+
"selected_adapter_contract_version": 1,
|
|
224
|
+
"adapter_id": "org.example.wowbagger.adapter",
|
|
225
|
+
"adapter_version": "1.0.0",
|
|
226
|
+
"core": {
|
|
227
|
+
"required_core_contract_version": 1,
|
|
228
|
+
"commands": ["capabilities", "create", "inspect", "ready", "transition", "validate"]
|
|
229
|
+
},
|
|
230
|
+
"host": {
|
|
231
|
+
"command_execution": {
|
|
232
|
+
"supported": true,
|
|
233
|
+
"arguments_array": true,
|
|
234
|
+
"shell": false,
|
|
235
|
+
"stdio": true,
|
|
236
|
+
"process_tree_containment": true,
|
|
237
|
+
"orphan_detection": true,
|
|
238
|
+
"timeout_enforcement": true,
|
|
239
|
+
"stdout_limit": true,
|
|
240
|
+
"stderr_limit": true
|
|
241
|
+
},
|
|
242
|
+
"filesystem": {
|
|
243
|
+
"workspace_selection": "guarded-relative",
|
|
244
|
+
"no_follow_resolution": true,
|
|
245
|
+
"stable_identity": true,
|
|
246
|
+
"component_walk": true
|
|
247
|
+
},
|
|
248
|
+
"model_transport": {
|
|
249
|
+
"available": true,
|
|
250
|
+
"protocol": "openai-compatible"
|
|
251
|
+
},
|
|
252
|
+
"instruction_input": {
|
|
253
|
+
"mode": "host-provided",
|
|
254
|
+
"max_sources": 8,
|
|
255
|
+
"max_bytes": 65536
|
|
256
|
+
},
|
|
257
|
+
"handoff": {
|
|
258
|
+
"supported": true,
|
|
259
|
+
"persistence": "explicit-only"
|
|
260
|
+
},
|
|
261
|
+
"trusted_approval": {
|
|
262
|
+
"supported": true,
|
|
263
|
+
"sources": ["consumer"]
|
|
264
|
+
},
|
|
265
|
+
"integration_mechanisms": {
|
|
266
|
+
"hooks": false,
|
|
267
|
+
"slash_commands": false,
|
|
268
|
+
"mcp": false,
|
|
269
|
+
"daemon": false
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
"optional_features": {
|
|
273
|
+
"claims": false,
|
|
274
|
+
"policy": false
|
|
275
|
+
},
|
|
276
|
+
"limits": {
|
|
277
|
+
"max_request_bytes": 65536,
|
|
278
|
+
"max_context_bytes": 65536,
|
|
279
|
+
"max_stdout_bytes": 1048576,
|
|
280
|
+
"max_stderr_bytes": 65536,
|
|
281
|
+
"max_timeout_ms": 30000
|
|
282
|
+
},
|
|
283
|
+
"platforms": {
|
|
284
|
+
"darwin": "unverified",
|
|
285
|
+
"linux": "unverified",
|
|
286
|
+
"win32": "unverified"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The example does not assert that such a host exists. It shows the required
|
|
292
|
+
separation of capabilities. `model_transport` is descriptive. It does not
|
|
293
|
+
change `command_execution` or `filesystem`.
|
|
294
|
+
|
|
295
|
+
The root and every nested object shown above are exact, except that
|
|
296
|
+
`host.trusted_approval` MAY be absent to declare mutations unavailable. Every
|
|
297
|
+
other displayed member is required and no additional member is accepted.
|
|
298
|
+
Command arrays are unique,
|
|
299
|
+
ordered subsets of the version 1 core command list; capability flags are
|
|
300
|
+
booleans; enumerated strings and bounded integers use the domains described
|
|
301
|
+
below. A missing member, extra member, wrong type, unknown enumeration, or
|
|
302
|
+
invalid bound is `invalid-describe-result`.
|
|
303
|
+
|
|
304
|
+
When present, version 1 trusted approval has exactly one authority label:
|
|
305
|
+
`trusted_approval.sources` MUST equal `["consumer"]`. Model, agent, system,
|
|
306
|
+
tool, harness, and additional source labels are invalid describe results; they
|
|
307
|
+
cannot become trusted through configuration. `trusted_approval.supported` MUST
|
|
308
|
+
be `true` for `create` or `transition`. A false or absent member makes those
|
|
309
|
+
commands unavailable before an approval is validated or redeemed. Read-only
|
|
310
|
+
commands remain available.
|
|
311
|
+
|
|
312
|
+
A non-null `handoff_carrier` requires `host.handoff.supported: true` before
|
|
313
|
+
the carrier is parsed. A supported value of `false` is a
|
|
314
|
+
`capability-unavailable` refusal with `missing: ["handoff"]`; an absent or
|
|
315
|
+
malformed handoff capability is `invalid-describe-result`. Neither case can
|
|
316
|
+
launch the core.
|
|
317
|
+
|
|
318
|
+
`command_execution.supported` is true only when the adapter can launch the
|
|
319
|
+
configured core executable with an argument array, without a shell, and capture
|
|
320
|
+
both byte streams. `filesystem.workspace_selection` is `guarded-relative` only
|
|
321
|
+
when the adapter can apply section 4. `instruction_input.mode` is `none`,
|
|
322
|
+
`host-provided`, or `configured-relative-paths`. `handoff.persistence` is
|
|
323
|
+
always `explicit-only` in version 1.
|
|
324
|
+
|
|
325
|
+
Capability fields obey these cross-field invariants; a contradictory describe
|
|
326
|
+
result is `invalid-describe-result`.
|
|
327
|
+
|
|
328
|
+
| Mode | Required dependent values |
|
|
329
|
+
| --- | --- |
|
|
330
|
+
| `command_execution.supported: true` | `arguments_array`, `stdio`, `process_tree_containment`, `orphan_detection`, `timeout_enforcement`, `stdout_limit`, and `stderr_limit` are `true`; `shell` is `false`; every advertised byte/time limit is a positive safe integer. |
|
|
331
|
+
| `command_execution.supported: false` | Every dependent execution Boolean is `false`, including `shell`; `core.commands` is empty, so the adapter does not advertise invoke capability. |
|
|
332
|
+
| `filesystem.workspace_selection: "guarded-relative"` | `no_follow_resolution`, `stable_identity`, and `component_walk` are all `true`. |
|
|
333
|
+
| `filesystem.workspace_selection: "none"` | `no_follow_resolution`, `stable_identity`, and `component_walk` are all `false`. |
|
|
334
|
+
| `instruction_input.mode: "none"` | `max_sources` and `max_bytes` are zero. |
|
|
335
|
+
| Other instruction-input modes | `max_sources` and `max_bytes` are positive safe integers. |
|
|
336
|
+
|
|
337
|
+
Byte limits are finite nonnegative safe integers; zero permits no content.
|
|
338
|
+
`max_timeout_ms` is a finite positive safe integer. Byte limits apply to raw
|
|
339
|
+
bytes before base64 expansion. An adapter MAY advertise smaller limits than
|
|
340
|
+
the example. It MUST NOT imply an unbounded context, output, or execution time.
|
|
341
|
+
|
|
342
|
+
`optional_features.claims` and `optional_features.policy` default to `false`.
|
|
343
|
+
Claims become true only when the independently probed version 1 core reports
|
|
344
|
+
`work_claim.supported: true`; policy remains false because version 1 advertises
|
|
345
|
+
no policy feature. The mutation capability probe is advisory and cannot imply
|
|
346
|
+
publication fencing or safe exclusive dispatch. Ledger-specific callers must
|
|
347
|
+
use `claim capabilities` to distinguish an unprovisioned advisory ledger from
|
|
348
|
+
a provisioned merge-coordinated ledger. A mutation lock remains a short local
|
|
349
|
+
mutation lock, not a claim.
|
|
350
|
+
|
|
351
|
+
### 3.3 Bootstrap command wire
|
|
352
|
+
|
|
353
|
+
Command entrypoints use the same bootstrap transport for `describe` and
|
|
354
|
+
`invoke`:
|
|
355
|
+
|
|
356
|
+
- The runner writes exactly one strict UTF-8 JSON object to stdin and closes
|
|
357
|
+
stdin. Duplicate members, trailing bytes, and invalid UTF-8 are refused.
|
|
358
|
+
- The entrypoint writes exactly one strict JSON object followed by one LF to
|
|
359
|
+
stdout. No prefix, progress record, or second object is allowed. Diagnostic
|
|
360
|
+
text is bounded and may use stderr; it is never parsed as a result.
|
|
361
|
+
- Exit 0 means a complete bootstrap response was written, including an
|
|
362
|
+
`ok:false` response. A nonzero exit, signal, timeout, malformed response, or
|
|
363
|
+
incomplete bounded stream is transport failure.
|
|
364
|
+
- The installed package root is the entrypoint working directory. The
|
|
365
|
+
invocation's separately guarded `cwd` applies only to the core child. The
|
|
366
|
+
adapter environment is a consumer-configured allowlist; inherited secrets,
|
|
367
|
+
Git variables, shell startup, and request-supplied environment entries are
|
|
368
|
+
excluded.
|
|
369
|
+
- The runner applies finite stdin, stdout, stderr, and wall-clock limits. It
|
|
370
|
+
starts the adapter and core in a containable process-tree unit and, on limit,
|
|
371
|
+
timeout, or cancellation, terminates that whole unit and verifies that no
|
|
372
|
+
descendant remains. A runner unable to provide or verify containment MUST
|
|
373
|
+
advertise command execution unavailable.
|
|
374
|
+
|
|
375
|
+
The describe request contains only `bootstrap_wire_version`,
|
|
376
|
+
`supported_adapter_contract_versions`, and an opaque `request_id`. The invoke
|
|
377
|
+
request uses the selected version. A describe result advertises
|
|
378
|
+
`trusted_approval`; absence means mutations are unavailable. Static and dynamic
|
|
379
|
+
adapter ID, adapter version, selected contract, complete three-platform map,
|
|
380
|
+
and `core.required_core_contract_version` MUST match exactly. The independently
|
|
381
|
+
launched core probe must then match that required core version. Error
|
|
382
|
+
precedence is describe-request schema, static-manifest schema, bootstrap wire
|
|
383
|
+
compatibility, common-version selection, dynamic-result schema, static/dynamic
|
|
384
|
+
identity and version, selected contract, required core, platform map, core
|
|
385
|
+
probe, capabilities, path/input validation, approval, then launch.
|
|
386
|
+
|
|
387
|
+
## 4. Workspace and path selection
|
|
388
|
+
|
|
389
|
+
The model never supplies an absolute workspace root. A consumer preconfigures
|
|
390
|
+
an opaque `workspace_id` that maps to one approved root. The adapter accepts
|
|
391
|
+
only logical paths relative to that root.
|
|
392
|
+
|
|
393
|
+
A logical path is `.` or one or more forward-slash-separated segments. It MUST
|
|
394
|
+
NOT be absolute, empty, contain a backslash, NUL, drive prefix, volume prefix,
|
|
395
|
+
`.` segment, or `..` segment. Windows drive-relative forms such as `C:repo`,
|
|
396
|
+
drive-rooted forms using either separator, UNC forms using either separator,
|
|
397
|
+
device namespaces such as `\\?\C:` and `\\.\COM1`, and `Volume{...}` roots are all
|
|
398
|
+
invalid on every host platform. The same logical syntax is used on macOS,
|
|
399
|
+
Linux, and Windows; the adapter rejects platform prefixes before converting a
|
|
400
|
+
logical path to a host path.
|
|
401
|
+
|
|
402
|
+
For a core request with a workspace, the adapter MUST:
|
|
403
|
+
|
|
404
|
+
1. resolve `workspace_id` to a consumer-approved real directory without
|
|
405
|
+
following a link or reparse point at the root;
|
|
406
|
+
2. resolve `cwd` and `ledger` from that root with a no-follow check on every
|
|
407
|
+
existing path component, including Windows reparse points;
|
|
408
|
+
3. reject a missing, symbolic-link, junction, reparse-point, special, or
|
|
409
|
+
escaping component before launching the core; and
|
|
410
|
+
4. re-check stable platform file identities immediately before launch, refuse
|
|
411
|
+
a replacement, and launch the core with the resolved `cwd` plus an absolute
|
|
412
|
+
ledger argument anchored at the workspace root.
|
|
413
|
+
|
|
414
|
+
`cwd` never changes the base for `ledger`. For example, `cwd: "nested"` and
|
|
415
|
+
`ledger: "ledger"` select `<root>/nested` as the child working directory and
|
|
416
|
+
`<root>/ledger` as the ledger; `<root>/nested/ledger` is a decoy and MUST NOT
|
|
417
|
+
be selected. Root, cwd, ledger, and every component are directories with
|
|
418
|
+
no-follow `lstat` identity snapshots. The adapter checks the root and each
|
|
419
|
+
cumulative component (for example `nested`, then `nested/deep`) before and
|
|
420
|
+
immediately before launch. A component replacement between validation and
|
|
421
|
+
launch is `path-replaced`, not permission to retry through the new component.
|
|
422
|
+
|
|
423
|
+
Every before/after snapshot is the exact object `{ "kind": ..., "identity":
|
|
424
|
+
... }`. `kind` is the required portable kind for that position (`directory`
|
|
425
|
+
for package/workspace roots and parents, `regular-file` for a command
|
|
426
|
+
executable). `identity` is either a nonempty control-free opaque stable token,
|
|
427
|
+
an exact POSIX `{ "dev": ..., "ino": ... }` object, or an exact Windows
|
|
428
|
+
`{ "volume_id": ..., "file_id": ... }` object. Explicit identity members are
|
|
429
|
+
nonempty control-free strings or nonnegative safe integers. Missing, malformed,
|
|
430
|
+
or extra snapshot members on either side are `path-rejected`; two valid
|
|
431
|
+
same-kind snapshots with unequal identities are `path-replaced`.
|
|
432
|
+
|
|
433
|
+
The core separately rejects a symbolic-link ledger root and symbolic-link
|
|
434
|
+
entries below it. The adapter boundary does not claim to eliminate privileged
|
|
435
|
+
filesystem races. It prevents a caller from selecting an arbitrary path or
|
|
436
|
+
silently traversing a link before the core gets its own fail-closed check.
|
|
437
|
+
|
|
438
|
+
An adapter that cannot make this no-follow determination MUST report
|
|
439
|
+
`no_follow_resolution: false` and MUST NOT accept local workspace or ledger
|
|
440
|
+
selection. An API-only host therefore cannot claim a local core invocation.
|
|
441
|
+
|
|
442
|
+
## 5. Invocation
|
|
443
|
+
|
|
444
|
+
An adapter accepts a structured request. It MUST NOT accept raw shell source,
|
|
445
|
+
an arbitrary executable, an arbitrary CLI argument list, or an arbitrary input
|
|
446
|
+
file path. It constructs the documented core argument vector itself.
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"adapter_contract_version": 1,
|
|
451
|
+
"request_id": "ready-forwarding-0001",
|
|
452
|
+
"workspace": {
|
|
453
|
+
"workspace_id": "example-workspace",
|
|
454
|
+
"cwd": "."
|
|
455
|
+
},
|
|
456
|
+
"core_request": {
|
|
457
|
+
"command": "ready",
|
|
458
|
+
"ledger": "ledger",
|
|
459
|
+
"as_of": "2030-01-15"
|
|
460
|
+
},
|
|
461
|
+
"instruction_input": {
|
|
462
|
+
"required": false,
|
|
463
|
+
"instruction_input_version": 1,
|
|
464
|
+
"sources": []
|
|
465
|
+
},
|
|
466
|
+
"handoff_carrier": null,
|
|
467
|
+
"limits": {
|
|
468
|
+
"context_bytes": 4096,
|
|
469
|
+
"stdout_bytes": 65536,
|
|
470
|
+
"stderr_bytes": 4096,
|
|
471
|
+
"timeout_ms": 30000
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
`request_id` is an opaque ASCII identifier of 1 through 128 characters using
|
|
477
|
+
letters, digits, period, underscore, and hyphen. The adapter returns it
|
|
478
|
+
unchanged. Requested byte limits MUST be finite nonnegative safe integers no
|
|
479
|
+
greater than the advertised values. `timeout_ms` is required, positive, and no
|
|
480
|
+
greater than `max_timeout_ms`; it bounds the complete contained adapter/core
|
|
481
|
+
process observation.
|
|
482
|
+
|
|
483
|
+
The runner applies its finite local `max_request_bytes` ceiling before parsing
|
|
484
|
+
the raw invocation. After describe succeeds, the same raw bytes MUST also be
|
|
485
|
+
at most `described.limits.max_request_bytes`; that advertised cap is
|
|
486
|
+
authoritative for the invocation. A describe result that advertises a request
|
|
487
|
+
cap above the local ceiling is inconsistent and is refused as
|
|
488
|
+
`invalid-describe-result`. The adapter never enlarges either cap.
|
|
489
|
+
|
|
490
|
+
`workspace` is required for every command except `capabilities`. `cwd` defaults
|
|
491
|
+
to `.` when omitted. `ledger` is required for every workspace command and uses
|
|
492
|
+
the logical path rules in section 4.
|
|
493
|
+
|
|
494
|
+
The tagged `core_request` members are exact:
|
|
495
|
+
|
|
496
|
+
| Command | Required members | Core argument vector |
|
|
497
|
+
|---|---|---|
|
|
498
|
+
| `capabilities` | `command` | `capabilities --json` |
|
|
499
|
+
| `validate` | `command`, `ledger` | `validate --ledger <ledger> --json` |
|
|
500
|
+
| `ready` | `command`, `ledger`, `as_of` | `ready --ledger <ledger> --as-of <date> --json` |
|
|
501
|
+
| `inspect` | `command`, `ledger`, `id` | `inspect --ledger <ledger> --id <id> --json` |
|
|
502
|
+
| `create` | `command`, `ledger`, `input_base64` | `create --ledger <ledger> --input - --json` |
|
|
503
|
+
| `transition` | `command`, `ledger`, `input_base64` | `transition --ledger <ledger> --input - --json` |
|
|
504
|
+
|
|
505
|
+
`input_base64` is RFC 4648 base64 without line breaks. Its decoded bytes are
|
|
506
|
+
the exact UTF-8 JSON request sent to standard input. The adapter MUST check the
|
|
507
|
+
decoded byte limit before launching the core. It MUST use `--input -`; it MUST
|
|
508
|
+
NOT write a caller-selected request file.
|
|
509
|
+
|
|
510
|
+
`as_of`, `id`, and decoded mutation requests retain their core validation
|
|
511
|
+
rules. The adapter does not normalize dates, IDs, JSON whitespace, YAML, or
|
|
512
|
+
request bytes.
|
|
513
|
+
|
|
514
|
+
The version 1 request root always carries `instruction_input` and
|
|
515
|
+
`handoff_carrier`: an optional instruction set is represented by the exact
|
|
516
|
+
empty carrier `{ "instruction_input_version": 1, "required": false,
|
|
517
|
+
"sources": [] }`, and no handoff is represented by `null`. This keeps the root
|
|
518
|
+
schema exact instead of making validation depend on omitted fields. The
|
|
519
|
+
carriers are validated as sections 7 and 8 specify before core launch. A
|
|
520
|
+
required instruction set that is
|
|
521
|
+
missing or invalid produces a named diagnostic refusal; it cannot be silently
|
|
522
|
+
replaced by guessed files or prior-session memory.
|
|
523
|
+
|
|
524
|
+
### 5.1 Consumer authority
|
|
525
|
+
|
|
526
|
+
`create` and `transition` require an explicit consumer approval event for that
|
|
527
|
+
invocation and `trusted_approval.supported: true` in describe. The event may be
|
|
528
|
+
represented to the adapter by a trusted host mechanism, but a model-supplied
|
|
529
|
+
Boolean is not approval. If approval is absent, the adapter returns
|
|
530
|
+
`consumer-approval-required` without launching the core. If trusted approval
|
|
531
|
+
is false or absent, it returns `capability-unavailable` with
|
|
532
|
+
`missing: ["trusted-approval"]` before validating or redeeming any approval.
|
|
533
|
+
|
|
534
|
+
Read operations do not grant mutation authority. No version 1 request grants
|
|
535
|
+
authority to commit, push, modify remote configuration, install dependencies,
|
|
536
|
+
start a setup script, edit a harness configuration, or bypass repository
|
|
537
|
+
instructions or safety gates. These actions are outside this interface.
|
|
538
|
+
|
|
539
|
+
Trusted command-entrypoint approval uses this exact object:
|
|
540
|
+
|
|
541
|
+
```json
|
|
542
|
+
{
|
|
543
|
+
"approval_version": 1,
|
|
544
|
+
"source": "consumer",
|
|
545
|
+
"nonce": "single-use-opaque-value",
|
|
546
|
+
"issued_at": "2030-01-15T12:00:00Z",
|
|
547
|
+
"expires_at": "2030-01-15T12:05:00Z",
|
|
548
|
+
"invocation_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
The digest covers this exact binding object; no member is optional and no
|
|
553
|
+
extra member is accepted:
|
|
554
|
+
|
|
555
|
+
```json
|
|
556
|
+
{
|
|
557
|
+
"request_id": "mutation-approval-0002",
|
|
558
|
+
"adapter": {
|
|
559
|
+
"id": "org.example.wowbagger.adapter",
|
|
560
|
+
"version": "1.0.0",
|
|
561
|
+
"contract_version": 1
|
|
562
|
+
},
|
|
563
|
+
"core": {
|
|
564
|
+
"executable_identity": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
565
|
+
"contract_version": 1,
|
|
566
|
+
"argv": ["transition", "--ledger", "/approved/workspace/ledger", "--input", "-", "--json"],
|
|
567
|
+
"input_base64": "e30K"
|
|
568
|
+
},
|
|
569
|
+
"workspace": {
|
|
570
|
+
"id": "example-workspace",
|
|
571
|
+
"root": "/approved/workspace",
|
|
572
|
+
"cwd": "/approved/workspace",
|
|
573
|
+
"ledger": "/approved/workspace/ledger"
|
|
574
|
+
},
|
|
575
|
+
"limits": {
|
|
576
|
+
"context_bytes": 4096,
|
|
577
|
+
"stdout_bytes": 65536,
|
|
578
|
+
"stderr_bytes": 4096,
|
|
579
|
+
"timeout_ms": 30000
|
|
580
|
+
},
|
|
581
|
+
"instruction_set_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
582
|
+
"handoff_digest": null
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
The consumer authority signs or delivers this record through the configured
|
|
587
|
+
trusted channel; the model and instruction inputs are never trusted sources.
|
|
588
|
+
The configured trusted-source set MUST be exactly `{consumer}`. A missing
|
|
589
|
+
consumer or any additional model, agent, system, tool, harness, or other label
|
|
590
|
+
is `approval-source-untrusted`, even when the approval object itself says
|
|
591
|
+
`source: "consumer"`.
|
|
592
|
+
The adapter canonicalizes a binding as UTF-8 JSON with object keys sorted
|
|
593
|
+
lexicographically, no insignificant whitespace, arrays retained in order, and
|
|
594
|
+
ordinary JSON scalar encoding. The SHA-256 binding includes request ID;
|
|
595
|
+
workspace ID, root, cwd, and absolute ledger; exact core executable identity,
|
|
596
|
+
contract version, argv, and stdin bytes; adapter ID/version/selected contract;
|
|
597
|
+
all byte/time limits; instruction-set digest; and handoff digest. Any change
|
|
598
|
+
requires new approval. Approval and binding objects use exact-member schemas.
|
|
599
|
+
Version must be 1; nonce is 16–128 ASCII letters, digits, periods, underscores,
|
|
600
|
+
or hyphens; digests use lowercase `sha256:` plus 64 hex characters. Issued,
|
|
601
|
+
expiry, and current time use canonical whole-second RFC 3339 UTC; invalid time,
|
|
602
|
+
`issued_at >= expires_at`, or current time before issue fails closed. The nonce
|
|
603
|
+
is redeemed atomically once, expires at `now >= expires_at`, and is scoped to
|
|
604
|
+
this consumer/adapter instance. Replay, expiry, unknown source, and binding
|
|
605
|
+
mismatch refuse before launch. More
|
|
606
|
+
restrictive repository, consumer, or harness policy wins; approval never
|
|
607
|
+
overrides an instruction or safety refusal.
|
|
608
|
+
|
|
609
|
+
## 6. Result, streams, and exit status
|
|
610
|
+
|
|
611
|
+
When the core process starts and both streams fit the requested bounds, the
|
|
612
|
+
adapter returns exactly one JSON result envelope:
|
|
613
|
+
|
|
614
|
+
```json
|
|
615
|
+
{
|
|
616
|
+
"ok": true,
|
|
617
|
+
"adapter_contract_version": 1,
|
|
618
|
+
"request_id": "ready-forwarding-0001",
|
|
619
|
+
"result": {
|
|
620
|
+
"core_command": "ready",
|
|
621
|
+
"core_exit_code": 0,
|
|
622
|
+
"stdout": {
|
|
623
|
+
"encoding": "base64",
|
|
624
|
+
"data": "eyJhc19vZiI6IjIwMzAtMDEtMTUiLCJ2YWxpZCI6dHJ1ZSwicmVhZHkiOltdfQo=",
|
|
625
|
+
"sha256": "sha256:73b7879d83e6047beee2720b38f4e882b98dc351452112d5a9e5f85cac7e8eda",
|
|
626
|
+
"byte_length": 47
|
|
627
|
+
},
|
|
628
|
+
"stderr": {
|
|
629
|
+
"encoding": "base64",
|
|
630
|
+
"data": "",
|
|
631
|
+
"sha256": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
632
|
+
"byte_length": 0
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
The result envelope's `ok` means that the adapter completed transport. It does
|
|
639
|
+
not mean that the core command succeeded. `core_exit_code` MUST equal the
|
|
640
|
+
actual child-process exit code, including a nonzero validation, request,
|
|
641
|
+
conflict, capability, or operation result. The base64 values decode to the
|
|
642
|
+
exact child stream bytes. Their SHA-256 values cover those decoded bytes.
|
|
643
|
+
|
|
644
|
+
An adapter MUST NOT trim, reformat, parse-and-reserialize, prefix, suffix, or
|
|
645
|
+
replace a core stream. It MAY provide a separately named parsed JSON view only
|
|
646
|
+
when it proves that it is derived from the complete decoded standard output;
|
|
647
|
+
that view is never the authoritative core result.
|
|
648
|
+
|
|
649
|
+
For every `--json` core command, decoded standard output is complete only when
|
|
650
|
+
it is one compact JSON object followed by exactly one LF. Whitespace outside
|
|
651
|
+
JSON strings, a missing final LF, or a CR or second LF before that final LF is
|
|
652
|
+
a core protocol failure. The adapter still preserves those exact captured bytes
|
|
653
|
+
in its process observation; it does not normalize them into a valid result.
|
|
654
|
+
|
|
655
|
+
For `inspect`, the decoded core standard output is complete only when its
|
|
656
|
+
exact command and contract version match, exactly one of `result` or `error`
|
|
657
|
+
is present, and its exit is consistent with that success or documented error.
|
|
658
|
+
On success, `result` contains exactly `item`. The item has the exact documented
|
|
659
|
+
lossless core shape: canonical ID, safe ledger-relative Markdown path, source
|
|
660
|
+
encoding and media type, canonical source bytes, matching source digest, and a
|
|
661
|
+
normalized core/body view that agrees with those decoded UTF-8 source bytes.
|
|
662
|
+
The adapter validates core version 1 field domains and rejects an extra,
|
|
663
|
+
missing, source-inconsistent, or semantically invalid item as a protocol
|
|
664
|
+
failure.
|
|
665
|
+
For `create` and `transition`, the same checks also require the exact mutation
|
|
666
|
+
state: success is `state: "committed"` with `result` and exit 0; an error has
|
|
667
|
+
`error` and its documented `unchanged`, `committed`, or `unknown` state and
|
|
668
|
+
exit. A declared `unknown` state is an adapter
|
|
669
|
+
`mutation-outcome-unknown`, not a completed mutation result. Any malformed,
|
|
670
|
+
incomplete, mismatched-command, mismatched-version, mismatched-state,
|
|
671
|
+
result/error, or exit-inconsistent mutation envelope is likewise
|
|
672
|
+
`mutation-outcome-unknown` with section 6 recovery.
|
|
673
|
+
|
|
674
|
+
Before forwarding a complete core envelope, the adapter also binds every
|
|
675
|
+
request-derived response member to the exact canonical request it launched.
|
|
676
|
+
`ready` success repeats the requested `as_of`; an `inspect` item or
|
|
677
|
+
`item-not-found` detail repeats the requested ID. Every mutation response with
|
|
678
|
+
a target ID repeats the caller's ID. A transition `revision-conflict` repeats
|
|
679
|
+
the requested expected revision and has an actual revision that differs from
|
|
680
|
+
it. A committed-recovery transition reports a new revision rather than the
|
|
681
|
+
expected pre-transition revision. Create success uses the requested default
|
|
682
|
+
path and the exact canonical candidate source bytes (including permitted
|
|
683
|
+
extension data), whose digest and normalized view already have to match the
|
|
684
|
+
returned item. A committed create recovery reports the SHA-256 digest of those
|
|
685
|
+
same exact candidate bytes for that caller ID and default path; a merely
|
|
686
|
+
well-formed digest is not enough. Transition success repeats the target status,
|
|
687
|
+
date, and required decision evidence. Error operation and lock-owner
|
|
688
|
+
identifiers retain their documented meanings and are validated against their
|
|
689
|
+
target item rather than being treated as opaque strings. A response that is
|
|
690
|
+
valid in isolation but cannot be bound to this request is a protocol failure
|
|
691
|
+
(and therefore an unknown mutation outcome for a mutation). In particular, a
|
|
692
|
+
core `invalid-request` mutation response is acceptable only when the supplied
|
|
693
|
+
mutation bytes did not form a canonical valid request. The adapter retains the
|
|
694
|
+
exact decoded request bytes for this response check, including malformed JSON
|
|
695
|
+
and duplicate-key observations; it does not promote an invalid request into a
|
|
696
|
+
valid mutation merely to correlate its error.
|
|
697
|
+
|
|
698
|
+
The adapter also rejects self-contradictory or nondeterministic mutation
|
|
699
|
+
details. An `atomic-scope-required` envelope has a nonempty, unique blocker
|
|
700
|
+
array sorted by code, item ID, then field, and it retains its possibly empty
|
|
701
|
+
but ordered precondition issue array. Invalid-request issues sort by path,
|
|
702
|
+
code, then message. Transition issues sort by code, field, then related-ID
|
|
703
|
+
sequence, and each related-ID sequence is unique and ascending. Recovery
|
|
704
|
+
artifacts are unique by path, sorted by path then kind, contain at most 16 entries,
|
|
705
|
+
and use the documented truncation rule. These checks preserve the core's
|
|
706
|
+
deterministic response semantics instead of accepting an equivalent-looking
|
|
707
|
+
but impossible envelope. Blocker and precondition-issue fields also retain the
|
|
708
|
+
fixed meanings of their codes: dependency codes use `depends_on`, child codes
|
|
709
|
+
use `parent`, date checks use `date`, and an invalid edge uses `to_status`.
|
|
710
|
+
|
|
711
|
+
The adapter parses every returned item source, checks its source digest,
|
|
712
|
+
lossless body, and normalized view, then applies the authoritative ledger
|
|
713
|
+
validator to the returned item. It supplies only valid synthetic relation
|
|
714
|
+
targets needed to evaluate invariants that a one-item response can establish;
|
|
715
|
+
any validator error attributed to the returned item is a protocol failure.
|
|
716
|
+
This covers the version 1 kind and status domains, epic `in-progress`
|
|
717
|
+
prohibition, ID-created-date agreement, timestamp ordering, terminal dates and
|
|
718
|
+
decisions, dependency and relation rules, and terminal epic rollup evidence.
|
|
719
|
+
The core remains responsible for complete-ledger facts that cannot be observed
|
|
720
|
+
from one returned item. A valid validation result has an empty error array.
|
|
721
|
+
Every invalid-ledger, candidate-invalid, or ledger-invalid response has a
|
|
722
|
+
nonempty validation-error array in the deterministic `path`, `field`, `code`,
|
|
723
|
+
then `message` order.
|
|
724
|
+
|
|
725
|
+
`ready` has two exact complete core forms. A valid ready result has exactly
|
|
726
|
+
`as_of`, `valid: true`, and `ready`, with exit 0. A documented invalid-ledger
|
|
727
|
+
result has exactly `valid: false` and a nonempty deterministically ordered
|
|
728
|
+
`errors` array, with exit 1. The adapter forwards the latter's nonzero exit and
|
|
729
|
+
exact bytes. Other hybrid, extra, empty-error, or inconsistent ready shapes
|
|
730
|
+
are `core-protocol-error`.
|
|
731
|
+
|
|
732
|
+
If a wrapper also offers a stream passthrough mode, it MUST write the exact core
|
|
733
|
+
standard output and standard error and use the same exit code. It MUST NOT add
|
|
734
|
+
the outer envelope to either core stream.
|
|
735
|
+
|
|
736
|
+
When the adapter cannot provide a complete core observation, it returns this
|
|
737
|
+
error envelope:
|
|
738
|
+
|
|
739
|
+
```json
|
|
740
|
+
{
|
|
741
|
+
"ok": false,
|
|
742
|
+
"adapter_contract_version": 1,
|
|
743
|
+
"request_id": "ready-forwarding-0001",
|
|
744
|
+
"error": {
|
|
745
|
+
"code": "capability-unavailable",
|
|
746
|
+
"message": "The configured host cannot invoke the Wowbagger core.",
|
|
747
|
+
"details": {
|
|
748
|
+
"missing": ["command-execution"]
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
The following operation classes are exhaustive and disjoint. Their sorted
|
|
755
|
+
union is the complete public version 1 registry. Every code is emitted by the
|
|
756
|
+
reference model and exercised as an expected vector result; adding or removing
|
|
757
|
+
a code requires changing the classes, registry, executable evidence, and drift
|
|
758
|
+
test together.
|
|
759
|
+
|
|
760
|
+
<!-- adapter-error-code-classes:start -->
|
|
761
|
+
```json
|
|
762
|
+
{
|
|
763
|
+
"approval": [
|
|
764
|
+
"approval-binding-mismatch",
|
|
765
|
+
"approval-expired",
|
|
766
|
+
"approval-not-yet-valid",
|
|
767
|
+
"approval-replayed",
|
|
768
|
+
"approval-source-untrusted",
|
|
769
|
+
"consumer-approval-required",
|
|
770
|
+
"invalid-approval",
|
|
771
|
+
"invalid-approval-binding",
|
|
772
|
+
"invalid-approval-time",
|
|
773
|
+
"invalid-approval-time-order"
|
|
774
|
+
],
|
|
775
|
+
"handoff": [
|
|
776
|
+
"handoff-digest-mismatch",
|
|
777
|
+
"handoff-instruction-set-mismatch",
|
|
778
|
+
"handoff-item-mismatch",
|
|
779
|
+
"handoff-limit-exceeded",
|
|
780
|
+
"handoff-resume-binding-mismatch",
|
|
781
|
+
"handoff-stale-item-revision",
|
|
782
|
+
"handoff-workspace-mismatch",
|
|
783
|
+
"invalid-handoff-bytes",
|
|
784
|
+
"invalid-handoff-carrier",
|
|
785
|
+
"invalid-handoff-json",
|
|
786
|
+
"invalid-handoff-object",
|
|
787
|
+
"invalid-handoff-resume-request"
|
|
788
|
+
],
|
|
789
|
+
"instruction": [
|
|
790
|
+
"duplicate-instruction-source-id",
|
|
791
|
+
"instruction-byte-limit-exceeded",
|
|
792
|
+
"instruction-source-limit-exceeded",
|
|
793
|
+
"invalid-instruction-input",
|
|
794
|
+
"invalid-instruction-source",
|
|
795
|
+
"required-instruction-input-missing"
|
|
796
|
+
],
|
|
797
|
+
"invocation": [
|
|
798
|
+
"capability-unavailable",
|
|
799
|
+
"context-limit-exceeded",
|
|
800
|
+
"core-launch-failed",
|
|
801
|
+
"core-observation-incomplete",
|
|
802
|
+
"core-protocol-error",
|
|
803
|
+
"core-signaled",
|
|
804
|
+
"core-timeout",
|
|
805
|
+
"invalid-invocation",
|
|
806
|
+
"mutation-outcome-unknown",
|
|
807
|
+
"output-limit-exceeded",
|
|
808
|
+
"path-rejected",
|
|
809
|
+
"path-replaced",
|
|
810
|
+
"timeout-limit-exceeded"
|
|
811
|
+
],
|
|
812
|
+
"negotiation": [
|
|
813
|
+
"adapter-contract-selection-mismatch",
|
|
814
|
+
"adapter-identity-mismatch",
|
|
815
|
+
"adapter-platform-mismatch",
|
|
816
|
+
"adapter-version-mismatch",
|
|
817
|
+
"core-contract-version-mismatch",
|
|
818
|
+
"invalid-adapter-manifest",
|
|
819
|
+
"invalid-describe-request",
|
|
820
|
+
"invalid-describe-result",
|
|
821
|
+
"required-core-contract-version-mismatch",
|
|
822
|
+
"unsupported-adapter-contract-version",
|
|
823
|
+
"unsupported-bootstrap-wire-version"
|
|
824
|
+
]
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
<!-- adapter-error-code-classes:end -->
|
|
828
|
+
|
|
829
|
+
The flattened registry is:
|
|
830
|
+
|
|
831
|
+
<!-- adapter-error-codes:start -->
|
|
832
|
+
```text
|
|
833
|
+
adapter-contract-selection-mismatch
|
|
834
|
+
adapter-identity-mismatch
|
|
835
|
+
adapter-platform-mismatch
|
|
836
|
+
adapter-version-mismatch
|
|
837
|
+
approval-binding-mismatch
|
|
838
|
+
approval-expired
|
|
839
|
+
approval-not-yet-valid
|
|
840
|
+
approval-replayed
|
|
841
|
+
approval-source-untrusted
|
|
842
|
+
capability-unavailable
|
|
843
|
+
consumer-approval-required
|
|
844
|
+
context-limit-exceeded
|
|
845
|
+
core-contract-version-mismatch
|
|
846
|
+
core-launch-failed
|
|
847
|
+
core-observation-incomplete
|
|
848
|
+
core-protocol-error
|
|
849
|
+
core-signaled
|
|
850
|
+
core-timeout
|
|
851
|
+
duplicate-instruction-source-id
|
|
852
|
+
handoff-digest-mismatch
|
|
853
|
+
handoff-instruction-set-mismatch
|
|
854
|
+
handoff-item-mismatch
|
|
855
|
+
handoff-limit-exceeded
|
|
856
|
+
handoff-resume-binding-mismatch
|
|
857
|
+
handoff-stale-item-revision
|
|
858
|
+
handoff-workspace-mismatch
|
|
859
|
+
instruction-byte-limit-exceeded
|
|
860
|
+
instruction-source-limit-exceeded
|
|
861
|
+
invalid-adapter-manifest
|
|
862
|
+
invalid-approval
|
|
863
|
+
invalid-approval-binding
|
|
864
|
+
invalid-approval-time
|
|
865
|
+
invalid-approval-time-order
|
|
866
|
+
invalid-describe-request
|
|
867
|
+
invalid-describe-result
|
|
868
|
+
invalid-handoff-bytes
|
|
869
|
+
invalid-handoff-carrier
|
|
870
|
+
invalid-handoff-json
|
|
871
|
+
invalid-handoff-object
|
|
872
|
+
invalid-handoff-resume-request
|
|
873
|
+
invalid-instruction-input
|
|
874
|
+
invalid-instruction-source
|
|
875
|
+
invalid-invocation
|
|
876
|
+
mutation-outcome-unknown
|
|
877
|
+
output-limit-exceeded
|
|
878
|
+
path-rejected
|
|
879
|
+
path-replaced
|
|
880
|
+
required-core-contract-version-mismatch
|
|
881
|
+
required-instruction-input-missing
|
|
882
|
+
timeout-limit-exceeded
|
|
883
|
+
unsupported-adapter-contract-version
|
|
884
|
+
unsupported-bootstrap-wire-version
|
|
885
|
+
```
|
|
886
|
+
<!-- adapter-error-codes:end -->
|
|
887
|
+
|
|
888
|
+
Error details are bounded JSON. They MUST NOT expose
|
|
889
|
+
credentials, environment values, raw instruction contents, arbitrary absolute
|
|
890
|
+
paths, or platform exception text.
|
|
891
|
+
|
|
892
|
+
If a stream exceeds its requested bound, the adapter MUST stop or contain the
|
|
893
|
+
core process according to the host's safe runner rule. It returns
|
|
894
|
+
`output-limit-exceeded`; it MUST NOT present a partial core JSON result as
|
|
895
|
+
valid. It may report only bounded byte counts and stream names needed for
|
|
896
|
+
recovery. It must not silently enlarge a limit.
|
|
897
|
+
|
|
898
|
+
The runner supplies the exact process observation object `started`,
|
|
899
|
+
`process_tree_contained`, `orphaned`, `exit_code`, `signal` (a portable runner
|
|
900
|
+
label, not necessarily a POSIX signal), `timed_out`, `stdout_complete`,
|
|
901
|
+
`stderr_complete`, `stdout_base64`, and `stderr_base64`. Booleans, canonical
|
|
902
|
+
base64 streams, a null or nonnegative integer exit code, and a null or
|
|
903
|
+
nonempty control-free signal label are type-checked before classification; no
|
|
904
|
+
unknown members are accepted. A normally terminated child tree has an integer
|
|
905
|
+
exit code and no timeout or signal. A null exit without another incomplete
|
|
906
|
+
observation condition, a missing or malformed exit, or an integer exit paired
|
|
907
|
+
with timeout/signal is an incomplete observation. The outer process summary
|
|
908
|
+
derives, rather than accepts, `core_envelope_present` and
|
|
909
|
+
`core_envelope_valid` from the captured stdout bytes. A result is complete only
|
|
910
|
+
after the child tree has ended, both streams ended within bounds, no descendant
|
|
911
|
+
remains, and stdout contains the strict complete core envelope expected for
|
|
912
|
+
that command.
|
|
913
|
+
|
|
914
|
+
Launch classification is tri-state for mutations. A normal
|
|
915
|
+
`core-launch-failed` is allowed only for a complete, internally consistent
|
|
916
|
+
observation that proves `started: false`: no exit, signal, timeout, output,
|
|
917
|
+
orphan, or missing stream/containment evidence. A missing, malformed, or
|
|
918
|
+
contradicted `started` member leaves launch unknown, as does every other
|
|
919
|
+
ambiguous observation, and therefore produces `mutation-outcome-unknown`.
|
|
920
|
+
Read-only commands retain their ordinary transport errors and never acquire a
|
|
921
|
+
mutation outcome.
|
|
922
|
+
|
|
923
|
+
The adapter independently decodes each captured base64 stream and compares its
|
|
924
|
+
decoded byte length with the invocation's requested stdout or stderr limit.
|
|
925
|
+
An over-limit capture is `output-limit-exceeded` for a read and
|
|
926
|
+
`mutation-outcome-unknown` for a mutation, even when the runner reports the
|
|
927
|
+
stream complete. Runner completion flags cannot enlarge a byte bound.
|
|
928
|
+
|
|
929
|
+
For read-only commands precedence is: a proven not-started observation →
|
|
930
|
+
`core-launch-failed`; timeout → `core-timeout`; signal → `core-signaled`;
|
|
931
|
+
containment/orphan doubt or malformed observation →
|
|
932
|
+
`core-observation-incomplete`; stream truncation → `output-limit-exceeded`;
|
|
933
|
+
then missing or invalid complete envelope → `core-protocol-error`. Exit code is
|
|
934
|
+
recorded but never overrides an earlier transport failure. No partial core JSON
|
|
935
|
+
is valid.
|
|
936
|
+
|
|
937
|
+
Mutation commands are stricter: unless the observation proves that launch did
|
|
938
|
+
not occur, any timeout or signal produces `mutation-outcome-unknown` even when
|
|
939
|
+
both buffered streams and a nominal success envelope appear complete.
|
|
940
|
+
Containment uncertainty, incomplete stdout, incomplete stderr, a missing or
|
|
941
|
+
invalid `started` observation, or a missing/invalid complete core envelope also
|
|
942
|
+
produces `mutation-outcome-unknown`—even if exit was zero or captured bytes
|
|
943
|
+
look like a success. The adapter MUST NOT label it failed, roll it back, or
|
|
944
|
+
retry it.
|
|
945
|
+
|
|
946
|
+
Every create request therefore carries a caller-generated item ID. Recovery is
|
|
947
|
+
bounded and explicit: validate the ledger, inspect that known ID, and retry
|
|
948
|
+
only after `item-not-found` plus review/recovery of any audited publication
|
|
949
|
+
artifact. For transition, validate, inspect the known ID, and compare the
|
|
950
|
+
caller-known expected revision with the current revision and state; never
|
|
951
|
+
retry until that observation is reviewed. This distinction prevents both a
|
|
952
|
+
hidden committed mutation and an orphaned duplicate mutation.
|
|
953
|
+
|
|
954
|
+
## 7. Instruction inputs
|
|
955
|
+
|
|
956
|
+
The contract does not assume `CLAUDE.md`, `AGENTS.md`, any other filename,
|
|
957
|
+
slash command, hook, MCP server, daemon, or model vendor. A harness may supply
|
|
958
|
+
instruction inputs through this bounded envelope:
|
|
959
|
+
|
|
960
|
+
```json
|
|
961
|
+
{
|
|
962
|
+
"instruction_input_version": 1,
|
|
963
|
+
"required": true,
|
|
964
|
+
"sources": [
|
|
965
|
+
{
|
|
966
|
+
"source_id": "repository-rules",
|
|
967
|
+
"origin": "repository",
|
|
968
|
+
"content_encoding": "base64",
|
|
969
|
+
"content_base64": "VXNlIHRoZSBhcHByb3ZlZCBsZWRnZXIgb25seS4K",
|
|
970
|
+
"sha256": "sha256:f42a9b7fa06a5825cc6c5faf5a5ed2b217ecf65672aee9e40572feddb36e2578",
|
|
971
|
+
"byte_length": 30,
|
|
972
|
+
"logical_path": "config/repository-rules.txt"
|
|
973
|
+
}
|
|
974
|
+
]
|
|
975
|
+
}
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
Sources are ordered by the harness. Each array member must first be an exact
|
|
979
|
+
JSON object; null, primitive, array, missing-member, and extra-member values
|
|
980
|
+
are deterministic `invalid-instruction-source` refusals and never cause
|
|
981
|
+
property-access exceptions. `origin` is `repository`, `consumer`,
|
|
982
|
+
`harness`, `user`, or `adapter`. A source MAY have an optional logical relative
|
|
983
|
+
path for diagnostics, but the path has no built-in semantic meaning. A host
|
|
984
|
+
with `configured-relative-paths` can read only consumer-configured paths using
|
|
985
|
+
section 4. It MUST NOT fall back to a guessed name.
|
|
986
|
+
|
|
987
|
+
The carrier has exactly `instruction_input_version`, `required`, and `sources`.
|
|
988
|
+
Version is 1 and `required` is Boolean. Each source has exactly `source_id`,
|
|
989
|
+
`origin`, `content_encoding`, `content_base64`, `sha256`, and `byte_length`,
|
|
990
|
+
plus optional `logical_path`. Source IDs are unique safe ASCII identifiers;
|
|
991
|
+
origin uses the stated enum; encoding is `base64`; base64 is canonical; and
|
|
992
|
+
logical paths use section 4 syntax. Unknown members or versions are refused.
|
|
993
|
+
|
|
994
|
+
The adapter verifies base64, byte length, SHA-256, source count, and total byte
|
|
995
|
+
limit before presenting input to a model. Instruction contents are data, not
|
|
996
|
+
executable configuration. Missing inputs do not waive repository instructions,
|
|
997
|
+
consumer policy, safety gates, or approval requirements.
|
|
998
|
+
|
|
999
|
+
The adapter preserves supplied order and exposes a bounded diagnostic record
|
|
1000
|
+
for each source: zero-based ordinal, `source_id`, origin, numeric precedence,
|
|
1001
|
+
optional logical source path, byte length, and digest. Content is never echoed
|
|
1002
|
+
in diagnostics. The
|
|
1003
|
+
`instruction_set_digest` is SHA-256 over canonical ordered records containing
|
|
1004
|
+
source ID, origin, byte length, and content digest; changing order changes the
|
|
1005
|
+
set digest. Source precedence is consumer, repository, harness, user, then
|
|
1006
|
+
adapter defaults, but a lower-precedence source cannot weaken a higher one and
|
|
1007
|
+
the stricter safety rule wins. A configured required source that is absent,
|
|
1008
|
+
invalid, over limit, or unreadable produces
|
|
1009
|
+
`required-instruction-input-missing` or `invalid-instruction-source` before
|
|
1010
|
+
model or core invocation.
|
|
1011
|
+
|
|
1012
|
+
Instruction refusals are `invalid-instruction-input`,
|
|
1013
|
+
`required-instruction-input-missing`, `invalid-instruction-source`,
|
|
1014
|
+
`duplicate-instruction-source-id`, `instruction-source-limit-exceeded`, and
|
|
1015
|
+
`instruction-byte-limit-exceeded`.
|
|
1016
|
+
|
|
1017
|
+
## 8. Explicit handoff and resume
|
|
1018
|
+
|
|
1019
|
+
An adapter MAY carry an explicit handoff carrier. It has no hidden retrieval
|
|
1020
|
+
mechanism and no authority by itself. The decoded handoff bytes contain this
|
|
1021
|
+
exact strict JSON object:
|
|
1022
|
+
|
|
1023
|
+
```json
|
|
1024
|
+
{
|
|
1025
|
+
"handoff_version": 1,
|
|
1026
|
+
"workspace_id": "example-workspace",
|
|
1027
|
+
"instruction_set_digest": "sha256:f42a9b7fa06a5825cc6c5faf5a5ed2b217ecf65672aee9e40572feddb36e2578",
|
|
1028
|
+
"item": {
|
|
1029
|
+
"id": "wb_01KDWPVNG00000000000000000",
|
|
1030
|
+
"revision": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
The invoke envelope carries this exact carrier schema:
|
|
1036
|
+
|
|
1037
|
+
```json
|
|
1038
|
+
{
|
|
1039
|
+
"handoff_carrier_version": 1,
|
|
1040
|
+
"workspace_id": "example-workspace",
|
|
1041
|
+
"content_encoding": "base64",
|
|
1042
|
+
"content_base64": "eyJoYW5kb2ZmX3ZlcnNpb24iOjEsIndvcmtzcGFjZV9pZCI6ImV4YW1wbGUtd29ya3NwYWNlIiwiaW5zdHJ1Y3Rpb25fc2V0X2RpZ2VzdCI6InNoYTI1NjpmNDJhOWI3ZmEwNmE1ODI1Y2M2YzVmYWY1YTVlZDJiMjE3ZWNmNjU2NzJhZWU5ZTQwNTcyZmVkZGIzNmUyNTc4IiwiaXRlbSI6eyJpZCI6IndiXzAxS0RXUFZORzAwMDAwMDAwMDAwMDAwMDAwIiwicmV2aXNpb24iOiJzaGEyNTY6MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJ9fQo=",
|
|
1043
|
+
"byte_length": 287,
|
|
1044
|
+
"sha256": "sha256:31a1d0490913a7117eb4ff50937ff265af41d385fc80e17593ba0a75550b260b",
|
|
1045
|
+
"resume_request": {
|
|
1046
|
+
"item_id": "wb_01KDWPVNG00000000000000000",
|
|
1047
|
+
"expected_revision": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
1048
|
+
"instruction_set_digest": "sha256:f42a9b7fa06a5825cc6c5faf5a5ed2b217ecf65672aee9e40572feddb36e2578"
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
No carrier, resume-request, handoff-object, or item member may be missing or
|
|
1054
|
+
extra. The adapter validates version 1, `base64`, canonical base64 bytes,
|
|
1055
|
+
length, digest, strict UTF-8 JSON including duplicate-member rejection,
|
|
1056
|
+
workspace binding, item ID/revision, and instruction-set digest. Item IDs in
|
|
1057
|
+
both `resume_request.item_id` and decoded `item.id` MUST match the canonical
|
|
1058
|
+
Wowbagger grammar `^wb_[0-7][0-9A-HJKMNP-TV-Z]{25}$`; a merely generic safe
|
|
1059
|
+
identifier is invalid. Carrier,
|
|
1060
|
+
decoded handoff, configured workspace, resume request, current inspected item,
|
|
1061
|
+
and current instruction set must agree. Handoff text cannot grant approval or
|
|
1062
|
+
enlarge limits.
|
|
1063
|
+
|
|
1064
|
+
Instruction decoded bytes plus decoded handoff bytes share the invocation's
|
|
1065
|
+
single `context_bytes` bound; their individual bounds do not create extra
|
|
1066
|
+
capacity. The handoff MUST be explicitly stored or
|
|
1067
|
+
delivered by the consumer or harness. The adapter MUST NOT create a hidden
|
|
1068
|
+
database, infer a prior session, or silently reload memory.
|
|
1069
|
+
|
|
1070
|
+
Handoff refusals are `handoff-digest-mismatch`, `invalid-handoff-carrier`,
|
|
1071
|
+
`invalid-handoff-bytes`, `handoff-limit-exceeded`, `invalid-handoff-json`, `invalid-handoff-object`,
|
|
1072
|
+
`invalid-handoff-resume-request`, `handoff-workspace-mismatch`,
|
|
1073
|
+
`handoff-resume-binding-mismatch`, `handoff-instruction-set-mismatch`,
|
|
1074
|
+
`handoff-item-mismatch`, and `handoff-stale-item-revision`.
|
|
1075
|
+
|
|
1076
|
+
On resume, an adapter re-negotiates capabilities and validates the ledger. If a
|
|
1077
|
+
handoff names an item or revision, it re-inspects and compares current bytes
|
|
1078
|
+
before a mutation. It MUST NOT auto-transition an item, renew a claim, commit,
|
|
1079
|
+
push, or treat a stale revision as permission to overwrite.
|
|
1080
|
+
|
|
1081
|
+
The current instruction-set digest and item revision MUST exactly match the
|
|
1082
|
+
resume request. A mismatch is a bounded refusal naming only the mismatched
|
|
1083
|
+
member and digests or revisions. The adapter retains no unlisted memory
|
|
1084
|
+
between invocations and does not search for handoff records.
|
|
1085
|
+
|
|
1086
|
+
## 9. Installation and portability
|
|
1087
|
+
|
|
1088
|
+
The manifest lets a consumer register a package in a native harness, a local
|
|
1089
|
+
tool registry, or a generic command runner. The packaging contract does not
|
|
1090
|
+
require a network service, MCP, a daemon, Gastown, Beads, a particular package
|
|
1091
|
+
manager, a shell, or a model API.
|
|
1092
|
+
|
|
1093
|
+
Portable adapters use UTF-8, JSON, argument arrays, bounded standard streams,
|
|
1094
|
+
and logical forward-slash paths. They do not assume POSIX shell syntax,
|
|
1095
|
+
Unix-only signals, hard links, a global install location, or a particular
|
|
1096
|
+
working-directory convention. The core itself reports filesystem-dependent
|
|
1097
|
+
mutation capabilities per operation.
|
|
1098
|
+
|
|
1099
|
+
macOS, Linux, and Windows claims are separate. An adapter marks a platform
|
|
1100
|
+
`supported` only after the conformance suite runs successfully on that native
|
|
1101
|
+
platform with its real path and command runner. A current absence of a platform
|
|
1102
|
+
implementation is `unverified`, not `unsupported` and not a compatibility
|
|
1103
|
+
promise.
|
|
1104
|
+
|
|
1105
|
+
## 10. Conformance
|
|
1106
|
+
|
|
1107
|
+
The synthetic vectors in [spec/fixtures/adapters](../spec/fixtures/adapters/)
|
|
1108
|
+
are normative. They contain no consumer source, policy, people, or ledger data.
|
|
1109
|
+
Each manifest is strict JSON and hashes every test artifact. The fixture test
|
|
1110
|
+
checks JSON duplicate-member rejection, hashes, safe relative paths, and the
|
|
1111
|
+
applicable target list. `direct-core` appears only on equivalence cases; path,
|
|
1112
|
+
authority, instructions, handoff, process containment, and adapter negotiation
|
|
1113
|
+
are adapter concerns and do not pretend that the core implements them.
|
|
1114
|
+
|
|
1115
|
+
The direct core is the baseline. A future adapter does not pass by emitting an
|
|
1116
|
+
equivalent-looking object; it passes by preserving the baseline core exit code
|
|
1117
|
+
and exact standard-output and standard-error bytes for each compatible case.
|
|
1118
|
+
|
|
1119
|
+
The implementation runner selects the runtime scenario only from each
|
|
1120
|
+
manifest's declared `mode`:
|
|
1121
|
+
|
|
1122
|
+
- `equivalence` launches a real core child for both the direct baseline and the
|
|
1123
|
+
shipped adapter transaction. It compares the exit code and exact stdout and
|
|
1124
|
+
stderr bytes; reconstructed output is not equivalent.
|
|
1125
|
+
- `negative-capability` supplies the declared capability profile, process
|
|
1126
|
+
observation, or refusal input. A process-level audit loads before the shipped
|
|
1127
|
+
entrypoint's modules and records asynchronous child resources plus successful
|
|
1128
|
+
calls to Node's public synchronous child-process APIs. The runner fails the
|
|
1129
|
+
case if that audit records a child, including a launch that bypasses the
|
|
1130
|
+
injected launcher or whose result is discarded.
|
|
1131
|
+
- `protocol` supplies the declared contract input, observation, or handoff to
|
|
1132
|
+
the applicable shipped contract implementation. Its bootstrap transaction
|
|
1133
|
+
uses a supplied core capability snapshot and does not launch a core child.
|
|
1134
|
+
|
|
1135
|
+
Every mode still starts the shipped adapter entrypoint as a real process and
|
|
1136
|
+
exercises the strict bootstrap wire for every case. Only `equivalence` uses a
|
|
1137
|
+
live core child because only that mode has a direct-core baseline to preserve.
|
|
1138
|
+
The `06-bounded-output` classification still uses its declared process
|
|
1139
|
+
observation. Its `output-bound` assertion also starts a runner-owned writer
|
|
1140
|
+
child through the production core launcher, writes one byte past the request's
|
|
1141
|
+
stdout limit, and requires prompt termination, an incomplete stdout marker,
|
|
1142
|
+
and retained output truncated to exactly that limit. Real stdout-limit
|
|
1143
|
+
enforcement is therefore part of the implementation-vector result rather than
|
|
1144
|
+
inferred only from the unit suite.
|
|
1145
|
+
|
|
1146
|
+
`node spec/run-adapter-vectors.js` is the executable reference-model runner.
|
|
1147
|
+
Forwarding and negative invocation cases enter through the strict raw-byte
|
|
1148
|
+
reference `invoke` function. The real core run is a baseline comparison inside
|
|
1149
|
+
that invoke path, never a substitute for the adapter boundary. The invoke
|
|
1150
|
+
function enforces `max_request_bytes` before JSON parsing or request-member
|
|
1151
|
+
access, then applies exact request/carrier schemas, version negotiation, the
|
|
1152
|
+
exact core capability probe, host capabilities, guarded paths, approval,
|
|
1153
|
+
bounded process observation, and complete outer result/refusal envelopes. The
|
|
1154
|
+
runner also evaluates deterministic protocol,
|
|
1155
|
+
path, authority, limit, mutation-recovery, and version models for adapter-only
|
|
1156
|
+
assertions. The standalone runner itself rejects duplicate-member JSON, verifies
|
|
1157
|
+
every artifact hash, fails on an unknown mode or assertion type, rejects any
|
|
1158
|
+
hashed artifact that no assertion consumes, and reports a reference
|
|
1159
|
+
function/evidence label for every executed assertion ID.
|
|
1160
|
+
It validates `adapter_vector_version` as exactly `1` before it evaluates an
|
|
1161
|
+
artifact. The test suite fails unless those IDs exactly equal every manifest
|
|
1162
|
+
assertion; it re-hashes wrong expectation artifacts and proves that every
|
|
1163
|
+
expected result is semantically rejected rather than merely read. Its status is
|
|
1164
|
+
`reference-pass`. It does not run a real Claude Code, Codex, Kimi, or generic
|
|
1165
|
+
adapter, so its implementation statuses remain `unverified`.
|
|
1166
|
+
`node spec/run-adapter-implementation.js` accepts the same fixture directory,
|
|
1167
|
+
evaluates transactions through the shipped Claude Code entrypoint and emits the
|
|
1168
|
+
same result shape with an evidence platform. Its current native Darwin run is
|
|
1169
|
+
`pass`: 183 of 183 assertions and 15 of 15 cases pass. That native common-vector
|
|
1170
|
+
evidence earns the Claude Code manifest's Darwin `supported` declaration.
|
|
1171
|
+
Codex, Kimi, and generic adapter implementations remain `unverified`.
|
|
1172
|
+
|
|
1173
|
+
The supported manifest assertion types are `core-baseline`, `capability`,
|
|
1174
|
+
`instruction-order`, `path-refusal`, `output-bound`, `approval-gate`,
|
|
1175
|
+
`resume-plan`, `platform-status`, `process-outcome`, `path-race`,
|
|
1176
|
+
`path-syntax`, `snapshot-identity`, `entrypoint-path`, `invoke-version`,
|
|
1177
|
+
`core-probe`, `negotiation`, `context-validation`, and `approval-schema`.
|
|
1178
|
+
The runner fails closed on any other type.
|
|
1179
|
+
|
|
1180
|
+
| Requirement | Direct core baseline | Claude Code adapter | Codex adapter | Kimi adapter | Generic OpenAI-compatible harness adapter |
|
|
1181
|
+
|---|---|---|---|---|---|
|
|
1182
|
+
| Core JSON, standard streams, and exit | Reference-pass | Implementation-pass (darwin) | Unverified | Unverified | Unverified |
|
|
1183
|
+
| Capability negotiation | Reference-pass for core probe | Implementation-pass (darwin) | Unverified | Unverified | Unverified |
|
|
1184
|
+
| Instruction input | Not a core concern | Implementation-pass (darwin) | Unverified | Unverified | Unverified |
|
|
1185
|
+
| Safe workspace and ledger selection | Core ledger checks only | Implementation-pass (darwin) | Unverified | Unverified | Unverified |
|
|
1186
|
+
| Bounded context and output | Reference bytes only | Implementation-pass (darwin) | Unverified | Unverified | Unverified |
|
|
1187
|
+
| Mutation authority and recovery | Core mutation contract only | Implementation-pass (darwin) | Unverified | Unverified | Unverified |
|
|
1188
|
+
| API-only transport refusal | Not applicable | Implementation-pass (darwin) | Unverified | Unverified | Unverified |
|
|
1189
|
+
| Resume and handoff | Not a core concern | Implementation-pass (darwin) | Unverified | Unverified | Unverified |
|
|
1190
|
+
|
|
1191
|
+
The API-only negative vector is intentionally different: it must refuse core
|
|
1192
|
+
invocation because model transport alone is not a coding harness. A generic
|
|
1193
|
+
OpenAI-compatible *harness* that advertises the required filesystem and command
|
|
1194
|
+
capabilities must meet the same forwarding vectors as the other adapters.
|
|
1195
|
+
|
|
1196
|
+
## 11. Non-goals
|
|
1197
|
+
|
|
1198
|
+
This contract does not:
|
|
1199
|
+
|
|
1200
|
+
- adopt Wowbagger in PropertyCompass or any other consumer repository;
|
|
1201
|
+
- add vendor-specific lifecycle logic to the core;
|
|
1202
|
+
- require an MCP server, daemon, hook, slash command, Gastown, or Beads;
|
|
1203
|
+
- define work claims, policy ranking, or hidden persistent agent memory;
|
|
1204
|
+
- grant automatic Git commit, push, setup, installation, or configuration
|
|
1205
|
+
authority; or
|
|
1206
|
+
- implement a Claude Code, Codex, Kimi, or generic adapter.
|
|
1207
|
+
|
|
1208
|
+
## 11.1 Concurrency
|
|
1209
|
+
|
|
1210
|
+
The adapter is a **one-shot CLI process**: each invocation runs to completion and
|
|
1211
|
+
exits. There is no daemon, no server socket, and no shared mutable state between
|
|
1212
|
+
invocations. This has three consequences:
|
|
1213
|
+
|
|
1214
|
+
1. **One adapter process cannot serve overlapping invokes.** It reads one request,
|
|
1215
|
+
processes it, writes one response, and exits. If a host needs concurrent
|
|
1216
|
+
execution, it must spawn multiple adapter processes.
|
|
1217
|
+
|
|
1218
|
+
2. **Concurrent adapter processes against the same ledger are independent.** The
|
|
1219
|
+
adapter has no concurrency control; serialization happens at the core level
|
|
1220
|
+
through the lock file. Two simultaneous mutations may both pass approval and
|
|
1221
|
+
launch, but the core's lock serializes writes.
|
|
1222
|
+
|
|
1223
|
+
3. **Each invocation enforces its own stdout/stderr limits independently.**
|
|
1224
|
+
Buffer state is per-process; one invocation hitting its limit does not affect
|
|
1225
|
+
another's limit enforcement.
|
|
1226
|
+
|
|
1227
|
+
This behavior is verified by the concurrent-invocation test suite. The contract
|
|
1228
|
+
does not require the host to serialize invokes; it simply provides no coordination
|
|
1229
|
+
beyond what the core offers.
|
|
1230
|
+
|
|
1231
|
+
## 12. Adapter contract version 2
|
|
1232
|
+
|
|
1233
|
+
Version 2 retains sections 1 through 11, including the strict bootstrap wire,
|
|
1234
|
+
path guards, exact stream forwarding, bounded process observation, consumer
|
|
1235
|
+
approval, instruction input, explicit handoff, recovery precedence, and public
|
|
1236
|
+
error registry. It changes only this versioned surface:
|
|
1237
|
+
|
|
1238
|
+
| Path or behavior | Version 2 requirement |
|
|
1239
|
+
|---|---|
|
|
1240
|
+
| Manifest `adapter_contract_versions` | Exactly `[2]` |
|
|
1241
|
+
| Manifest and describe required core contract | Exactly `2` |
|
|
1242
|
+
| Describe `selected_adapter_contract_version` | Exactly `2` |
|
|
1243
|
+
| Describe `core.commands` order | `capabilities`, `create`, `inspect`, `patch`, `ready`, `transition`, `validate` |
|
|
1244
|
+
| Invoke and result `adapter_contract_version` | Exactly `2` |
|
|
1245
|
+
| Independently probed core `contract_version` | Exactly `2` |
|
|
1246
|
+
|
|
1247
|
+
The version 2 core capability probe adds exactly
|
|
1248
|
+
`operations.patch: {"supported":true,"write_scope":"single-item","cas_scope":"exact-byte-sha256"}`.
|
|
1249
|
+
Its mutation backend scope is always
|
|
1250
|
+
`same-working-copy-cooperative-writers`,
|
|
1251
|
+
`limits.cross_worktree_coordination` is always `false`, and
|
|
1252
|
+
`limits.multi_item_atomicity` remains `false`. The separately probed
|
|
1253
|
+
`operations.work_claim.supported` may still be `true` when claims are visible
|
|
1254
|
+
through the Git common directory. That visibility may set
|
|
1255
|
+
`optional_features.claims: true`; it MUST NOT elevate mutation coordination or
|
|
1256
|
+
safe exclusive dispatch. Provisioned ledger-specific claim capabilities may
|
|
1257
|
+
separately advertise merge-coordinated claim-protected publication.
|
|
1258
|
+
|
|
1259
|
+
Version 2 accepts a `patch` core request with exactly `command`, `ledger`, and
|
|
1260
|
+
`input_base64`. It launches the argument vector
|
|
1261
|
+
`["patch","--ledger","<resolved>","--input","-","--json"]`, sends the exact
|
|
1262
|
+
decoded bytes to standard input, and treats
|
|
1263
|
+
patch as a mutation for trusted consumer approval, process doubt, and
|
|
1264
|
+
revision-based recovery. It does not accept an arbitrary input-file path from
|
|
1265
|
+
the invocation.
|
|
1266
|
+
|
|
1267
|
+
Bootstrap wire version 1 and the manifest, approval, instruction-input,
|
|
1268
|
+
handoff, and adapter-vector format versions remain 1; they are separate
|
|
1269
|
+
version domains. The version 1 adapter and core contracts remain defined by
|
|
1270
|
+
the preceding sections and are not widened to include patch.
|
|
1271
|
+
|
|
1272
|
+
A v1-only consumer sending `supported_adapter_contract_versions: [1]` to a
|
|
1273
|
+
shipped v2 adapter exits successfully at the bootstrap process level but
|
|
1274
|
+
receives exactly the compact refusal
|
|
1275
|
+
`{"ok":false,"error":{"code":"unsupported-adapter-contract-version"}}` plus
|
|
1276
|
+
one LF. It receives no v2 describe result and no requested core child is
|
|
1277
|
+
launched. Conversely, a v1 adapter probing a v2 core observes
|
|
1278
|
+
`contract_version: 2` and refuses the pairing as
|
|
1279
|
+
`core-contract-version-mismatch`; neither direction silently receives the
|
|
1280
|
+
other version's behavior.
|