ambit-ts 0.1.0
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 +64 -0
- package/LICENSE +21 -0
- package/README.md +403 -0
- package/dist/checker/authority.d.ts +13 -0
- package/dist/checker/authority.js +87 -0
- package/dist/checker/backend/legacy-ts.d.ts +26 -0
- package/dist/checker/backend/legacy-ts.js +1936 -0
- package/dist/checker/config.d.ts +84 -0
- package/dist/checker/config.js +391 -0
- package/dist/checker/coverage.d.ts +78 -0
- package/dist/checker/coverage.js +84 -0
- package/dist/checker/diagnose.d.ts +89 -0
- package/dist/checker/diagnose.js +734 -0
- package/dist/checker/index.d.ts +8 -0
- package/dist/checker/index.js +8 -0
- package/dist/checker/init.d.ts +38 -0
- package/dist/checker/init.js +205 -0
- package/dist/checker/propagate.d.ts +69 -0
- package/dist/checker/propagate.js +259 -0
- package/dist/checker/summarize.d.ts +27 -0
- package/dist/checker/summarize.js +411 -0
- package/dist/cli/analyze.d.ts +33 -0
- package/dist/cli/analyze.js +98 -0
- package/dist/cli/approvals.d.ts +28 -0
- package/dist/cli/approvals.js +55 -0
- package/dist/cli/diff.d.ts +66 -0
- package/dist/cli/diff.js +235 -0
- package/dist/cli/github.d.ts +33 -0
- package/dist/cli/github.js +41 -0
- package/dist/cli/main.d.ts +8 -0
- package/dist/cli/main.js +385 -0
- package/dist/cli/worktree.d.ts +75 -0
- package/dist/cli/worktree.js +154 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +10 -0
- package/dist/core/approvals.d.ts +82 -0
- package/dist/core/approvals.js +0 -0
- package/dist/core/authority-diff.d.ts +98 -0
- package/dist/core/authority-diff.js +209 -0
- package/dist/core/authority.d.ts +109 -0
- package/dist/core/authority.js +50 -0
- package/dist/core/backend.d.ts +355 -0
- package/dist/core/backend.js +1 -0
- package/dist/core/budget.d.ts +61 -0
- package/dist/core/budget.js +95 -0
- package/dist/core/capability.d.ts +53 -0
- package/dist/core/capability.js +117 -0
- package/dist/core/config.d.ts +59 -0
- package/dist/core/config.js +10 -0
- package/dist/core/diagnostic.d.ts +126 -0
- package/dist/core/diagnostic.js +13 -0
- package/dist/core/effects.d.ts +39 -0
- package/dist/core/effects.js +72 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +13 -0
- package/dist/core/location.d.ts +15 -0
- package/dist/core/location.js +1 -0
- package/dist/core/sql.d.ts +22 -0
- package/dist/core/sql.js +38 -0
- package/dist/core/summary.d.ts +240 -0
- package/dist/core/summary.js +8 -0
- package/dist/core/symbol-id.d.ts +25 -0
- package/dist/core/symbol-id.js +23 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/runtime/child-process.d.ts +29 -0
- package/dist/runtime/child-process.js +124 -0
- package/dist/runtime/context.d.ts +37 -0
- package/dist/runtime/context.js +8 -0
- package/dist/runtime/enforce.d.ts +52 -0
- package/dist/runtime/enforce.js +95 -0
- package/dist/runtime/fs.d.ts +46 -0
- package/dist/runtime/fs.js +188 -0
- package/dist/runtime/hono.d.ts +55 -0
- package/dist/runtime/hono.js +68 -0
- package/dist/runtime/index.d.ts +71 -0
- package/dist/runtime/index.js +126 -0
- package/dist/runtime/next.d.ts +95 -0
- package/dist/runtime/next.js +60 -0
- package/dist/runtime/pg.d.ts +48 -0
- package/dist/runtime/pg.js +122 -0
- package/dist/stubs/constructors.d.ts +34 -0
- package/dist/stubs/constructors.js +111 -0
- package/dist/stubs/data-clients.d.ts +9 -0
- package/dist/stubs/data-clients.js +109 -0
- package/dist/stubs/http-capabilities.d.ts +15 -0
- package/dist/stubs/http-capabilities.js +70 -0
- package/dist/stubs/mutating-builtins.d.ts +1 -0
- package/dist/stubs/mutating-builtins.js +48 -0
- package/dist/stubs/node-builtins.d.ts +2 -0
- package/dist/stubs/node-builtins.js +77 -0
- package/dist/stubs/pure-builtins.d.ts +1 -0
- package/dist/stubs/pure-builtins.js +89 -0
- package/docs/diagnostics/README.md +519 -0
- package/docs/limitations.md +712 -0
- package/package.json +89 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# Diagnostic codes
|
|
2
|
+
|
|
3
|
+
Living ledger of Ambit's diagnostic codes, appended as they are implemented.
|
|
4
|
+
|
|
5
|
+
An `id` and its meaning are part of the guaranteed surface (DESIGN.md §9.2):
|
|
6
|
+
a change to either is announced in `CHANGELOG.md` at the release that makes it.
|
|
7
|
+
While the major version is 0, such a change may land in a minor release
|
|
8
|
+
(§9.3) — ids may still be renumbered or reworded, but not silently. From 1.0,
|
|
9
|
+
DESIGN.md §5.2's stronger rule applies: an `id` is never deleted or reused, and
|
|
10
|
+
a meaning change requires an RFC (§9.1). That version qualifier is in §5.2
|
|
11
|
+
itself, not only here.
|
|
12
|
+
|
|
13
|
+
Message text is written in English; this file is written in English as well,
|
|
14
|
+
independent of the project's Japanese-language documentation policy — see
|
|
15
|
+
`AGENTS.md`.
|
|
16
|
+
|
|
17
|
+
## AMB-E001
|
|
18
|
+
|
|
19
|
+
Declared effects exceeded.
|
|
20
|
+
|
|
21
|
+
**Severity:** error
|
|
22
|
+
**Category:** effects
|
|
23
|
+
|
|
24
|
+
The function's declared `@effects` set does not contain an effect that was
|
|
25
|
+
observed either directly in its body or propagated from a callee (DESIGN.md
|
|
26
|
+
§4.2, rule 1). `contract.via` lists the call path from the declaring function
|
|
27
|
+
to the function where the effect was found, when propagation crossed at
|
|
28
|
+
least one call.
|
|
29
|
+
|
|
30
|
+
Example: a function declared `@effects pure` calls another function that
|
|
31
|
+
performs a `fetch`.
|
|
32
|
+
|
|
33
|
+
**Fixes:** one `widen` candidate, replacing the `@effects` tag with the
|
|
34
|
+
observed set. It is always `consistentWithContract: false` — it loosens the
|
|
35
|
+
promise rather than keeping it — and `impact.pureCallersBroken` counts the
|
|
36
|
+
callers whose own declaration would no longer cover it. No contract-preserving
|
|
37
|
+
candidate is emitted: restoring the declaration means restructuring the code,
|
|
38
|
+
which Ambit cannot patch safely, and DESIGN.md §5.3 forbids inventing a
|
|
39
|
+
candidate for the sake of ranking.
|
|
40
|
+
|
|
41
|
+
## AMB-W001
|
|
42
|
+
|
|
43
|
+
Declared effects reach unknown.
|
|
44
|
+
|
|
45
|
+
**Severity:** warning
|
|
46
|
+
**Category:** effects
|
|
47
|
+
|
|
48
|
+
A function with a declared `@effects` set (including `pure`) calls a
|
|
49
|
+
function whose effects could not be resolved (DESIGN.md §4.2, rule 3). This
|
|
50
|
+
does not by itself mean a violation occurred — `unknown` may resolve to a
|
|
51
|
+
set already covered by the declaration once the callee is annotated or a
|
|
52
|
+
stub is added — but the declaration is not yet backed by a verified
|
|
53
|
+
guarantee.
|
|
54
|
+
|
|
55
|
+
Example: a function declared `@effects pure` or `@effects network` calls
|
|
56
|
+
`eval(...)` or a function with an unresolved call graph.
|
|
57
|
+
|
|
58
|
+
Promoted to an error by `--strict`, and by a `strict` glob in
|
|
59
|
+
`ambit.config.ts` that matches the file it is reported in (DESIGN.md §4.3) —
|
|
60
|
+
the mechanism for tightening new code while leaving legacy directories at
|
|
61
|
+
warning level.
|
|
62
|
+
|
|
63
|
+
## AMB-E002
|
|
64
|
+
|
|
65
|
+
Unknown effect name in `@effects`.
|
|
66
|
+
|
|
67
|
+
**Severity:** error
|
|
68
|
+
**Category:** effects
|
|
69
|
+
|
|
70
|
+
An `@effects` tag contains a token that is neither `pure` nor one of the
|
|
71
|
+
known effects (`network`, `db_read`, `db_write`, `fs_read`, `fs_write`,
|
|
72
|
+
`state_write`,
|
|
73
|
+
`llm`, `env`, `process`) — most often a typo. The declaration is rejected
|
|
74
|
+
rather than silently narrowed to whatever tokens did parse: the function is
|
|
75
|
+
treated as undeclared (not as `pure`) for propagation, so it never also
|
|
76
|
+
produces `AMB-E001` for the same tag.
|
|
77
|
+
|
|
78
|
+
Example: `@effects netwrok` (missing an `r`) instead of `@effects network`.
|
|
79
|
+
|
|
80
|
+
## AMB-E003
|
|
81
|
+
|
|
82
|
+
Contract declared on a node that cannot carry one.
|
|
83
|
+
|
|
84
|
+
**Severity:** error
|
|
85
|
+
**Category:** effects
|
|
86
|
+
|
|
87
|
+
A contract tag (`@effects`, `@capabilities`, `@budget`, `@entrypoint`,
|
|
88
|
+
`@boundary`) is written on a function-like node the analysis does not extract, so it has no
|
|
89
|
+
symbol to attach the contract to. The declaration is inert: nothing propagates
|
|
90
|
+
it, nothing checks it, and it appears in no coverage figure. It is reported for
|
|
91
|
+
the same reason a misspelled effect name is (AMB-E002) — a declaration that
|
|
92
|
+
silently does nothing reads as a guarantee and is not one.
|
|
93
|
+
|
|
94
|
+
The message names why the node cannot carry a contract, using the same
|
|
95
|
+
classification `--coverage` counts under "skipped": a getter/setter, an
|
|
96
|
+
object-literal member with no stable declaration path, an anonymous default
|
|
97
|
+
export, a callback passed inline as an argument, a function declared inside
|
|
98
|
+
another function, or a declaration with no body.
|
|
99
|
+
|
|
100
|
+
Two of those *are* analyzed where the declaration path reaches them: a
|
|
101
|
+
`get`/`set` accessor on a class or on a module-scope `const` object literal,
|
|
102
|
+
and an anonymous `export default`, have stable paths (`Cls.get total`,
|
|
103
|
+
`default`), so their bodies propagate and `ambit.config.ts` can declare
|
|
104
|
+
contracts for them. An accessor in a literal that rule does not reach (a `let`
|
|
105
|
+
binding, a spread, a nested or inline literal) is still skipped, and neither
|
|
106
|
+
JSDoc nor config can name it — DESIGN.md §4.1 (a) keeps the config namespace a superset of the JSDoc
|
|
107
|
+
one. The comment on them is still inert, so this is still an error, and the
|
|
108
|
+
message ends with the config key that would work:
|
|
109
|
+
`declare it in ambit.config.ts under "src/cart.ts#Cart.get total" instead`.
|
|
110
|
+
DESIGN.md §12 records the asymmetry that leaves.
|
|
111
|
+
|
|
112
|
+
A declaration with no body — an overload signature, an `abstract` member, or a
|
|
113
|
+
`declare function` written in a `.ts` file — is reported for a reason worth
|
|
114
|
+
separating from the others. An overload set is one runtime function, and it is
|
|
115
|
+
the implementation: the signatures describe types, and only the implementation
|
|
116
|
+
runs. So the contract belongs on the implementation, and the message says so.
|
|
117
|
+
This is not a limitation waiting to be lifted. Adopting a signature's contract
|
|
118
|
+
would mean attributing it to a declaration Ambit does not model, and choosing
|
|
119
|
+
between two signatures that disagree; both are guesses, and DESIGN.md
|
|
120
|
+
§4.1 "Overloads and bodyless declarations" settles it the other way. A call
|
|
121
|
+
to an overload set with no implementation in the project is `unknown`
|
|
122
|
+
(`overload-without-body` in `--coverage`), not `pure`.
|
|
123
|
+
|
|
124
|
+
For a `declare function` with no implementation anywhere in the project, there
|
|
125
|
+
is nothing to move the contract *to*, and the message's advice does not apply.
|
|
126
|
+
What that code needs is a stub (`src/stubs/`); until it has one, calls to it
|
|
127
|
+
are honestly `unknown`.
|
|
128
|
+
|
|
129
|
+
One case is not a function-like node at all: a contract written on a `class`.
|
|
130
|
+
The class's construction *is* analyzed (indexed as `Class.constructor`), but a
|
|
131
|
+
class's own comment is never read as its implicit constructor's contract — a
|
|
132
|
+
comment about the class is not a verified statement about constructing it. The
|
|
133
|
+
message says the contract belongs on the constructor. This case is reported
|
|
134
|
+
but not counted under "skipped", which counts function-like nodes.
|
|
135
|
+
|
|
136
|
+
Example: `/** @effects fs_read */ get value() { … }`.
|
|
137
|
+
|
|
138
|
+
Note this is narrower than it was: an object-literal member *can* carry a
|
|
139
|
+
contract when the literal is a module-scope `const` and the member has an
|
|
140
|
+
identifier name. See `docs/limitations.md`.
|
|
141
|
+
|
|
142
|
+
## AMB-E004
|
|
143
|
+
|
|
144
|
+
Malformed `@capabilities`.
|
|
145
|
+
|
|
146
|
+
**Severity:** error
|
|
147
|
+
**Category:** capabilities
|
|
148
|
+
|
|
149
|
+
A `@capabilities` tag is not a comma-separated list of
|
|
150
|
+
`<resource>:<action>:<target>` (DESIGN.md §4.4). All three segments must be
|
|
151
|
+
non-empty, and only `target` may contain a glob (`*`, `?`) — a `*` in the
|
|
152
|
+
resource or action segment reads as a restriction while meaning the opposite,
|
|
153
|
+
so it is rejected. The target may itself contain a colon
|
|
154
|
+
(`http:get:localhost:8080`).
|
|
155
|
+
|
|
156
|
+
The whole tag is rejected rather than partly honoured, and the function is
|
|
157
|
+
treated as granting nothing — so it never also produces AMB-E005 for the same
|
|
158
|
+
tag. Same rule as AMB-E002.
|
|
159
|
+
|
|
160
|
+
Example: `@capabilities db:read` (two segments), `@capabilities *:read:users`
|
|
161
|
+
(glob outside the target).
|
|
162
|
+
|
|
163
|
+
## AMB-E005
|
|
164
|
+
|
|
165
|
+
Capability escalation.
|
|
166
|
+
|
|
167
|
+
**Severity:** error
|
|
168
|
+
**Category:** capabilities
|
|
169
|
+
|
|
170
|
+
A function declaring `@capabilities` reaches a callee that requires a
|
|
171
|
+
capability the declaration does not grant (DESIGN.md §4.4: capabilities may
|
|
172
|
+
only narrow from caller to callee). `contract.excess` lists the ungranted
|
|
173
|
+
capabilities and `contract.via` the call path to the one reported.
|
|
174
|
+
|
|
175
|
+
A capability is granted when the resource and action match exactly and the
|
|
176
|
+
grant's target, treated as a glob, matches the required target — so
|
|
177
|
+
`db:read:*` covers `db:read:users`.
|
|
178
|
+
|
|
179
|
+
The check crosses undeclared functions: `A` granting `db:read:users`, calling
|
|
180
|
+
an undeclared `B`, which calls a `C` declaring `db:write:users`, is a
|
|
181
|
+
violation in `A`. An undeclared hop does not launder an escalation.
|
|
182
|
+
|
|
183
|
+
Example: a function declaring `@capabilities db:read:users` that calls one
|
|
184
|
+
declaring `@capabilities db:write:users`.
|
|
185
|
+
|
|
186
|
+
## AMB-E009
|
|
187
|
+
|
|
188
|
+
Literal target outside the granted capabilities.
|
|
189
|
+
|
|
190
|
+
**Severity:** error
|
|
191
|
+
**Category:** capabilities
|
|
192
|
+
|
|
193
|
+
A function declaring `@capabilities` performs an operation whose target the
|
|
194
|
+
source fixes — a literal URL, or a template literal whose static head already
|
|
195
|
+
ends the authority — and no grant covers it. This is the static half of
|
|
196
|
+
DESIGN.md §4.4's dual enforcement: "violations that can be decided
|
|
197
|
+
statically, such as literal URLs and known clients, are stopped by the
|
|
198
|
+
checker".
|
|
199
|
+
|
|
200
|
+
Reported at the call site, not at the declaration: that is the line to change.
|
|
201
|
+
Separate from AMB-E005 because the finding is different — nothing declared
|
|
202
|
+
this requirement, the body performs it directly, so there is no callee whose
|
|
203
|
+
contract is too wide.
|
|
204
|
+
|
|
205
|
+
What is matched: `http:<method>:<host>` for the bundled HTTP entry points
|
|
206
|
+
(`fetch`, `undici`'s `fetch`, `node:http`/`node:https`'s `get`/`request`). The
|
|
207
|
+
method comes from a literal `method` in an options object literal and defaults
|
|
208
|
+
to `get`; the host is taken as written, port included and userinfo dropped. A
|
|
209
|
+
URL the source does not fix — built at runtime, or a template literal whose
|
|
210
|
+
static head stops inside the authority — produces no requirement to compare
|
|
211
|
+
and is reported as `AMB-W003` instead, naming the runtime as the place it is
|
|
212
|
+
matched. A relative URL names no host and is treated the same way.
|
|
213
|
+
|
|
214
|
+
No `db:` capability is derived from a SQL statement. §4.4 is explicit that a
|
|
215
|
+
hook on a database client does not amount to deciding table-level permission
|
|
216
|
+
for arbitrary SQL, and reading a table name out of a literal statement would
|
|
217
|
+
be the same claim in a different place.
|
|
218
|
+
|
|
219
|
+
Example: an entrypoint granting `@capabilities http:get:api.example.com` whose
|
|
220
|
+
body calls `fetch("https://elsewhere.example/steal")`.
|
|
221
|
+
|
|
222
|
+
**Fixes:** none. Widening the grant and changing the URL are both plausible
|
|
223
|
+
and Ambit cannot tell which was meant; §5.3 forbids inventing a candidate for
|
|
224
|
+
the sake of ranking, and silently widening a capability is the expansion of
|
|
225
|
+
authority the tag exists to catch.
|
|
226
|
+
|
|
227
|
+
## AMB-W003
|
|
228
|
+
|
|
229
|
+
Declared capabilities reach unknown.
|
|
230
|
+
|
|
231
|
+
**Severity:** warning
|
|
232
|
+
**Category:** capabilities
|
|
233
|
+
|
|
234
|
+
A function with declared `@capabilities` cannot have its requirement fully
|
|
235
|
+
determined. The capability analogue of AMB-W001, and promoted to an error by
|
|
236
|
+
`--strict` for the same reason — or by a `strict` glob in `ambit.config.ts`
|
|
237
|
+
that matches the file it is reported in (DESIGN.md §4.3). The two are a
|
|
238
|
+
union: a config listing fewer directories never narrows a `--strict` run.
|
|
239
|
+
|
|
240
|
+
The message names which of three causes applies, because they are fixed
|
|
241
|
+
differently: a callee that could not be resolved, a `@boundary` callee that
|
|
242
|
+
declared no `@capabilities`, or an operation whose target the source does not
|
|
243
|
+
fix (a URL built at runtime — see AMB-E009), which §4.4 assigns to the runtime
|
|
244
|
+
hook rather than to the checker.
|
|
245
|
+
|
|
246
|
+
## AMB-E010
|
|
247
|
+
|
|
248
|
+
`withAmbit` or an adapter's `ambitHandler` / `ambitRoute` disagrees with the
|
|
249
|
+
handler's `@capabilities`.
|
|
250
|
+
|
|
251
|
+
**Severity:** error
|
|
252
|
+
**Category:** capabilities
|
|
253
|
+
|
|
254
|
+
DESIGN.md §4.4 chose explicit registration, so the capability set that reaches
|
|
255
|
+
the runtime is the one in the `withAmbit(spec, handler)`, `ambitHandler(spec,
|
|
256
|
+
handler, decode)` (the `ambit-ts/runtime/hono` adapter) or `ambitRoute(spec,
|
|
257
|
+
handler, decode)` (the `ambit-ts/runtime/next` adapter) beside the handler. A
|
|
258
|
+
literal one *is* the handler's `@capabilities`, so the tag need not repeat it.
|
|
259
|
+
Writing both is still allowed, and this reports the two disagreeing, as sets of
|
|
260
|
+
the text each one wrote. The message names the call the source actually wrote. Order does not matter; anything else does, including a glob on one
|
|
261
|
+
side only, since `db:read:*` and `db:read:users` are different grants.
|
|
262
|
+
|
|
263
|
+
Reported at the call. Neither side is privileged: whichever half an
|
|
264
|
+
agent edited, the pair stopped agreeing, and Ambit cannot tell which one the
|
|
265
|
+
author meant.
|
|
266
|
+
|
|
267
|
+
Compared only when all of this holds — otherwise `AMB-W004`:
|
|
268
|
+
|
|
269
|
+
- the spec is an object literal with no spread, and its `capabilities` is a
|
|
270
|
+
literal array of string literals (a missing `capabilities` key counts as an
|
|
271
|
+
empty grant, which can still disagree);
|
|
272
|
+
- the handler is an identifier naming a declaration in the same file that the
|
|
273
|
+
analysis extracted.
|
|
274
|
+
|
|
275
|
+
A handler that declares neither `@entrypoint` nor `@capabilities` still has a
|
|
276
|
+
declaration when the spec beside it is literal — that spec — so there is
|
|
277
|
+
something to compare and the two agree by construction. It is `AMB-W004` only
|
|
278
|
+
when the spec is not literal, where nothing was declared on either side.
|
|
279
|
+
|
|
280
|
+
A handler whose `@capabilities` failed to parse is skipped here: `AMB-E004`
|
|
281
|
+
already reports that tag, and comparing against a declaration Ambit rejected
|
|
282
|
+
would name the wrong problem.
|
|
283
|
+
|
|
284
|
+
The budget half of the same rule is `AMB-E011`, reported separately:
|
|
285
|
+
each half is fixed by the source on its own, so a spec may write one as a
|
|
286
|
+
literal and build the other at runtime.
|
|
287
|
+
|
|
288
|
+
**Fixes:** none. Aligning the two means choosing which one is right, which is
|
|
289
|
+
the decision being reported (§5.3).
|
|
290
|
+
|
|
291
|
+
## AMB-E011
|
|
292
|
+
|
|
293
|
+
`withAmbit` or an adapter's `ambitHandler` / `ambitRoute` disagrees with the
|
|
294
|
+
handler's `@budget`.
|
|
295
|
+
|
|
296
|
+
**Severity:** error
|
|
297
|
+
**Category:** budget
|
|
298
|
+
|
|
299
|
+
The budget half of the same rule: `spec.budget` is what the runtime applies,
|
|
300
|
+
and a literal one is also what the source declares, so `@budget` on the handler
|
|
301
|
+
is optional beside it. Where both are written, this reports
|
|
302
|
+
the two disagreeing — a different `timeMs`, a different `onExceed`, or a limit
|
|
303
|
+
present on one side only.
|
|
304
|
+
|
|
305
|
+
A separate id rather than an extension of `AMB-E010` because that diagnostic's
|
|
306
|
+
`contract` field is capability text (`declared` / `required` / `excess`), which
|
|
307
|
+
a budget disagreement has nothing honest to put in, and because the category
|
|
308
|
+
that belongs on it is `budget`, not `capabilities`.
|
|
309
|
+
|
|
310
|
+
`onExceed` is compared **after** both sides are defaulted to `throw`.
|
|
311
|
+
`parseBudgetTag` writes the default into a `@budget` that omits it, and the
|
|
312
|
+
runtime defaults an omitted `spec.budget.onExceed` the same way, so the JSDoc
|
|
313
|
+
side has no absent state for an absent spec key to disagree with. The numeric
|
|
314
|
+
limits are not defaulted: `timeMs=500` against no `timeMs` is a real
|
|
315
|
+
disagreement, and is reported as one.
|
|
316
|
+
|
|
317
|
+
Compared only when all of this holds — otherwise `AMB-W004`:
|
|
318
|
+
|
|
319
|
+
- the spec is an object literal with no spread, and its `budget`, when
|
|
320
|
+
present, is an object literal whose keys are `timeMs` / `costUsd` /
|
|
321
|
+
`llmCalls` / `onExceed` and whose values are literals (a missing `budget`
|
|
322
|
+
key counts as no budget, which can still disagree with a declared one);
|
|
323
|
+
- the handler is an identifier naming a declaration in the same file that the
|
|
324
|
+
analysis extracted.
|
|
325
|
+
|
|
326
|
+
A handler whose `@budget` failed to parse is skipped here, for the reason
|
|
327
|
+
`AMB-E010` skips an unparsed `@capabilities`: `AMB-E008` already reports that
|
|
328
|
+
tag.
|
|
329
|
+
|
|
330
|
+
**Fixes:** none, for `AMB-E010`'s reason — which of the two is right is the
|
|
331
|
+
decision being reported.
|
|
332
|
+
|
|
333
|
+
## AMB-W004
|
|
334
|
+
|
|
335
|
+
`withAmbit`, `ambitHandler` or `ambitRoute` was not compared with a declared
|
|
336
|
+
contract.
|
|
337
|
+
|
|
338
|
+
**Severity:** warning
|
|
339
|
+
**Category:** capabilities
|
|
340
|
+
|
|
341
|
+
A `withAmbit(spec, handler)` or an adapter's `ambitHandler(spec, handler,
|
|
342
|
+
decode)` / `ambitRoute(spec, handler, decode)` was found, but one of `AMB-E010`'s or `AMB-E011`'s conditions does not
|
|
343
|
+
hold: the capability list is built at runtime, the budget is not an object
|
|
344
|
+
literal of literal limits, or the handler is not a declaration in the same
|
|
345
|
+
file. The message names which.
|
|
346
|
+
|
|
347
|
+
A spec Ambit cannot read declares nothing. So this warning is also the place
|
|
348
|
+
where the single-source rule stops: everywhere else a literal spec supplies the
|
|
349
|
+
handler's `@capabilities` / `@budget`, and here it does not, which leaves the
|
|
350
|
+
handler's own JSDoc as the only declaration there is. Dropping the tag beside
|
|
351
|
+
one of these registrations does not make the contract implicit — it makes it
|
|
352
|
+
missing, and the entrypoint is then reported as capability-less (`AMB-W002`)
|
|
353
|
+
on top of this warning.
|
|
354
|
+
|
|
355
|
+
The two halves are reported independently, so one registration can produce a
|
|
356
|
+
compared capability set and an uncompared budget, or the reverse. Collapsing
|
|
357
|
+
the wrapper to a single warning the moment either half was dynamic would drop
|
|
358
|
+
a check the source does support.
|
|
359
|
+
|
|
360
|
+
Reported rather than skipped for the reason `AMB-E003` reports an inert
|
|
361
|
+
declaration: a wrapper that produced no diagnostic at all would read as
|
|
362
|
+
"checked and agreed".
|
|
363
|
+
|
|
364
|
+
Not an error, and not promoted by `--strict`. The comparison is on the source
|
|
365
|
+
only; matching a contract to the handler that actually runs — after a build
|
|
366
|
+
strips the comments, or a bundler moves it — is DESIGN.md §12's
|
|
367
|
+
"Mapping contracts to handlers" and is still open.
|
|
368
|
+
|
|
369
|
+
## AMB-W005
|
|
370
|
+
|
|
371
|
+
JSDoc and `ambit.config.ts` declare the same tag differently.
|
|
372
|
+
|
|
373
|
+
**Severity:** warning
|
|
374
|
+
**Category:** effects
|
|
375
|
+
|
|
376
|
+
One symbol has both a JSDoc contract and a `contracts` entry, and for at least
|
|
377
|
+
one of the five tags the two do not say the same thing. DESIGN.md §4.1 settles
|
|
378
|
+
which wins — "if a symbol has both JSDoc and config, JSDoc wins and the
|
|
379
|
+
difference is warned about" — so the run proceeds with the JSDoc
|
|
380
|
+
declaration and this diagnostic reports what was ignored.
|
|
381
|
+
|
|
382
|
+
Compared tag by tag, on the parsed values rather than on the text: `@effects
|
|
383
|
+
db_read, network` and `effects: ["network", "db_read"]` are the same
|
|
384
|
+
declaration and are not reported. A tag only one side declares is not a
|
|
385
|
+
difference either — it is the other side filling a gap, which is the normal
|
|
386
|
+
way a config supplements code it cannot edit.
|
|
387
|
+
|
|
388
|
+
A warning rather than an error, because the specified behaviour is exactly
|
|
389
|
+
what happened. It is still reported for AMB-E003's reason: a config entry the
|
|
390
|
+
author believes is in force, and is not, is a declaration that does nothing.
|
|
391
|
+
|
|
392
|
+
Not promoted by `--strict`, in either its command-line or its per-directory
|
|
393
|
+
form. `--strict` means "an unverified path is not acceptable here" (§4.2 rule
|
|
394
|
+
3); a disagreement between two declarations is a different thing.
|
|
395
|
+
|
|
396
|
+
No fix is offered. Deleting the config entry and rewriting the JSDoc are
|
|
397
|
+
opposite intentions, and §5.3 forbids inventing a candidate to fill the slot.
|
|
398
|
+
|
|
399
|
+
## AMB-W006
|
|
400
|
+
|
|
401
|
+
A `contracts` key matches nothing.
|
|
402
|
+
|
|
403
|
+
**Severity:** warning
|
|
404
|
+
**Category:** effects
|
|
405
|
+
|
|
406
|
+
An exact `contracts` key — one whose file half contains no `*` — named no
|
|
407
|
+
declaration in what was analyzed. The contract it declares is not in force,
|
|
408
|
+
which is AMB-E003's failure in a different file: a declaration that silently
|
|
409
|
+
applies to nothing reads as a guarantee and is not one.
|
|
410
|
+
|
|
411
|
+
Only exact keys are reported. A glob is written to cover whatever is there,
|
|
412
|
+
and `ambit check src/domain` legitimately matches none of a
|
|
413
|
+
`src/legacy/**` pattern; reporting those would make the diagnostic noisiest
|
|
414
|
+
exactly when the run is narrowest.
|
|
415
|
+
|
|
416
|
+
The location is the key's own line in the config file, found textually — the
|
|
417
|
+
config is loaded by importing it, not by parsing it, so a key built by an
|
|
418
|
+
expression rather than written literally falls back to the file's first
|
|
419
|
+
character.
|
|
420
|
+
|
|
421
|
+
Common causes: a typo in the symbol half, a declaration path that is not what
|
|
422
|
+
the checker uses (`Cls.get total`, not `Cls.total` — DESIGN.md §4.1 (a)), or a
|
|
423
|
+
key naming a file outside the directory being checked.
|
|
424
|
+
|
|
425
|
+
## AMB-W002
|
|
426
|
+
|
|
427
|
+
Entrypoint with no capabilities.
|
|
428
|
+
|
|
429
|
+
**Severity:** warning
|
|
430
|
+
**Category:** capabilities
|
|
431
|
+
|
|
432
|
+
A function marked `@entrypoint` declares no `@capabilities`. An entrypoint is
|
|
433
|
+
where `ambit-ts/runtime` would establish a capability context (DESIGN.md §4.4);
|
|
434
|
+
one with no declared set establishes nothing to check against. §4.4:
|
|
435
|
+
"leaving them unspecified is warned about as equivalent to unknown".
|
|
436
|
+
|
|
437
|
+
## AMB-E006
|
|
438
|
+
|
|
439
|
+
`@boundary` with no reason.
|
|
440
|
+
|
|
441
|
+
**Severity:** error
|
|
442
|
+
**Category:** boundary
|
|
443
|
+
|
|
444
|
+
DESIGN.md §4.6 makes `reason` mandatory on `@boundary`
|
|
445
|
+
(`@boundary reason="legacy SDK, not annotated"`). A boundary is an explicit,
|
|
446
|
+
recorded decision to stop checking a body; without a reason it is an
|
|
447
|
+
unexplained hole, which is the thing the tag exists to make visible.
|
|
448
|
+
|
|
449
|
+
## AMB-E007
|
|
450
|
+
|
|
451
|
+
`@boundary` with no contract to trust.
|
|
452
|
+
|
|
453
|
+
**Severity:** error
|
|
454
|
+
**Category:** boundary
|
|
455
|
+
|
|
456
|
+
A `@boundary` function declares no `@effects`. §4.6's bargain is "do not check
|
|
457
|
+
inside; trust what is declared to the outside" — with nothing declared, there
|
|
458
|
+
is nothing to trust, and the tag only removes checking. The function's effects
|
|
459
|
+
become `unknown`, so callers still see the hole, but the declaration itself is
|
|
460
|
+
reported: a tag that only subtracts a guarantee should not look like one.
|
|
461
|
+
|
|
462
|
+
## AMB-E008
|
|
463
|
+
|
|
464
|
+
Malformed `@budget`.
|
|
465
|
+
|
|
466
|
+
**Severity:** error
|
|
467
|
+
**Category:** budget
|
|
468
|
+
|
|
469
|
+
A `@budget` tag is not a space-separated list of `key=value` pairs with keys
|
|
470
|
+
`timeMs`, `costUsd`, `llmCalls`, and an optional `onExceed` of `throw`
|
|
471
|
+
(default), `warn`, or `abort` (DESIGN.md §4.5). Limits must be finite and
|
|
472
|
+
non-negative, `llmCalls` an integer, no key repeated, and at least one limit
|
|
473
|
+
present — `@budget onExceed=warn` declares a policy with nothing to exceed.
|
|
474
|
+
|
|
475
|
+
Rejected whole rather than partly applied, for the reason AMB-E002 rejects a
|
|
476
|
+
misspelled effect name.
|
|
477
|
+
|
|
478
|
+
## AMB-I001
|
|
479
|
+
|
|
480
|
+
Contract proposal.
|
|
481
|
+
|
|
482
|
+
**Severity:** info
|
|
483
|
+
**Category:** effects
|
|
484
|
+
|
|
485
|
+
Emitted by `ambit init`, never by `ambit check`. A function has no `@effects`
|
|
486
|
+
tag and its effects were fully resolved, so the tag can be written for it
|
|
487
|
+
(DESIGN.md §4.1). The fix carries a concrete patch adding the tag — into the
|
|
488
|
+
declaration's existing JSDoc block when it has exactly one, otherwise as a new
|
|
489
|
+
block above it — and is `consistentWithContract: true`: adding a declaration
|
|
490
|
+
where there was none cannot contradict one, and the set proposed is exactly
|
|
491
|
+
what was observed.
|
|
492
|
+
|
|
493
|
+
Three kinds of declaration have a stable symbol id and nowhere to write a
|
|
494
|
+
comment: a `get`/`set` accessor, an anonymous `export default` (DESIGN.md
|
|
495
|
+
§4.1 (a)), and a class that writes no constructor — its construction has real
|
|
496
|
+
effects (property initializers, the base constructor) but no declaration site
|
|
497
|
+
at all. For those, `ambit init` reports the inferred effects with **no** patch
|
|
498
|
+
and names the `ambit.config.ts` key that would carry them; `ambit init
|
|
499
|
+
--config` produces that patch, appending one `contracts` entry.
|
|
500
|
+
|
|
501
|
+
The config patch is an insertion at the end of the `contracts: {` line. It is
|
|
502
|
+
emitted only when a config file exists with such a line: the config is loaded
|
|
503
|
+
by importing it, not by parsing it, so there is no brace-matched block to
|
|
504
|
+
append to — and creating a whole config file, with a `defineConfig` specifier
|
|
505
|
+
that depends on how the consumer installed Ambit, is not a patch this command
|
|
506
|
+
can generate safely (§5.3). Without a config file, the proposal carries no fix
|
|
507
|
+
and says so.
|
|
508
|
+
|
|
509
|
+
Reporting these with no fix beats either proposing a patch that changes
|
|
510
|
+
nothing or staying silent about effects that are real.
|
|
511
|
+
|
|
512
|
+
No proposal is made when the function's effects reached `unknown`. Declaring
|
|
513
|
+
`@effects pure` for a function the analysis could not resolve would convert
|
|
514
|
+
"could not tell" into a guarantee, which is the thing `unknown` exists to
|
|
515
|
+
prevent (§4.3). Those functions stay undeclared and keep appearing in
|
|
516
|
+
`--coverage`.
|
|
517
|
+
|
|
518
|
+
`ambit init` exits 0 regardless of how many proposals it makes: contracts left
|
|
519
|
+
to write are not a failed check.
|