@looply/sdk 0.0.0-stage → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vuedale
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,101 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @looply/sdk
2
+
3
+ A typed client for the looply HTTP API.
4
+
5
+ looply runs long autonomous coding sessions on your own machine. The daemon does
6
+ the work; the server holds the record of it — goals, the queue, what a run did,
7
+ and the decisions a person still has to make. This package is how a program
8
+ outside looply reads that record and takes part in those decisions.
9
+
10
+ ```bash
11
+ npm install @looply/sdk
12
+ ```
13
+
14
+ ## Using it
15
+
16
+ ```ts
17
+ import { LooplyClient } from '@looply/sdk';
18
+
19
+ const looply = new LooplyClient({
20
+ baseUrl: 'https://api.looply.dev',
21
+ getToken: () => process.env.LOOPLY_TOKEN ?? null,
22
+ });
23
+
24
+ const [project] = await looply.projects();
25
+ const goals = await looply.goals(project.id, 'active');
26
+
27
+ for (const goal of goals) {
28
+ const report = await looply.autonomy(project.id, goal.id);
29
+ console.log(goal.text, report.autonomy.length, 'grant(s)');
30
+ }
31
+ ```
32
+
33
+ `getToken` is called for **every** request rather than read once, so a rotated
34
+ credential is picked up without rebuilding the client. `onUnauthorized` is called
35
+ on a 401, so a shell can drop to sign-in instead of retrying a dead token.
36
+
37
+ `fetch` is injectable and the package has no runtime dependencies — tests need no
38
+ network, and no version of `node-fetch` is imposed on you.
39
+
40
+ ## What it can do
41
+
42
+ | | |
43
+ | --- | --- |
44
+ | `projects()` | the projects this credential can see |
45
+ | `goals()` · `goal()` · `createGoal()` · `patchGoal()` | what you want done |
46
+ | `startGoal()` · `commandStatus()` | ask for work, and follow the request |
47
+ | `items()` · `status()` | the queue, and what a run is doing |
48
+ | `approvals()` · `approval()` · `decideApproval()` | consequential acts awaiting a person |
49
+ | `execution()` · `resolveExecution()` | governed executions awaiting a person |
50
+ | `autonomy()` | what a goal is authorised to pursue, and under whose grant |
51
+ | `events()` | the ledger, as it happened |
52
+
53
+ ## Two things worth knowing before you build on this
54
+
55
+ **Approvals and executions refuse a machine token, by design.** An agent
56
+ authorising its own consequential act, or settling its own ambiguity, is the
57
+ thing they exist to prevent. `decideApproval` and `resolveExecution` require a
58
+ credential belonging to a person. A machine credential gets a 403, and that is
59
+ the feature.
60
+
61
+ **Nothing from `@looply/core` is re-exported here.** Core is the internal shared
62
+ vocabulary of the server, the CLI and the app, and it changes when they need it
63
+ to. Every type this package promises is declared in its own `views.ts`, so
64
+ depending on the SDK does not pin you to a release cycle you cannot see.
65
+
66
+ ## Errors
67
+
68
+ Every failure is a `LooplyError` carrying a `code`, so you can branch on the
69
+ reason rather than parsing a message:
70
+
71
+ ```ts
72
+ import { LooplyError, isRetryable } from '@looply/sdk';
73
+
74
+ try {
75
+ await looply.decideApproval(projectId, approvalId, {
76
+ decision: 'approve',
77
+ // Required. Retry safety comes from this OCC token rather than from an
78
+ // idempotency key: if the first call landed, the version has moved and the
79
+ // retry is a 409 that tells you so instead of deciding twice.
80
+ expectedVersion: approval.version,
81
+ });
82
+ } catch (e) {
83
+ if (e instanceof LooplyError && isRetryable(e.code)) {
84
+ // timeout or unavailable — trying again is reasonable
85
+ }
86
+ }
87
+ ```
88
+
89
+ `isRetryable` takes the **code**, not the error, and is the honest answer to "is
90
+ trying again worth anything?" — a 403 is not retryable however many times you
91
+ ask.
92
+
93
+ ## Stability
94
+
95
+ **0.x.** The surface may change in a minor version. Every export in `index.ts` is
96
+ a deliberate promise — there is no `export *` — but the promise is a 0.x one
97
+ until an external consumer exists to hold it to.
98
+
99
+ ## Licence
100
+
101
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,430 @@
1
+ import type { ApprovalView, CommandStatusView, GoalView, GovernedExecutionView, ItemView, KeeperAnswerView, PageView, PlatformEventView, ProjectView, RunStatusView, GoalAutonomyReport, GoalEvidenceView, PresenceView, SourceClassificationView, SourceDetailView, EvidenceAnchorView, RequirementLinkView, SourceAttestationView, SessionControlGrantView, SourceCoverageView, SourceDispositionView, SourceKindView, SourceView } from './views';
2
+ /**
3
+ * LOOPLY, AS A PROGRAM OUTSIDE IT SEES IT.
4
+ *
5
+ * ─────────────────────────────────────────────────────────────────────────────
6
+ * 🔴 THIS IS THE HUMAN/BUSINESS SURFACE. IT IS NOT THE MACHINE ONE.
7
+ *
8
+ * A daemon claims approvals, begins executions and settles them. Those are
9
+ * MACHINE authority and they are deliberately absent here — not hidden behind a
10
+ * flag, not available with a different token, absent. Phase 7's whole property
11
+ * is that one approval yields at most one claim yields at most one execution,
12
+ * and the cheapest way to lose it is a convenient client method that lets a
13
+ * caller do the machine's half.
14
+ *
15
+ * So there is no `claim()`, no `beginExecution()`, no `settle()`, and no
16
+ * `popCommand()`. A client that needs them is a daemon, and a daemon has
17
+ * `packages/cli`.
18
+ *
19
+ * ─────────────────────────────────────────────────────────────────────────────
20
+ * 🔴 EVERY MUTATION TAKES AN IDEMPOTENCY KEY, AND THAT IS NOT POLITENESS
21
+ *
22
+ * An external caller loses responses. It times out, it retries, its process
23
+ * restarts mid-request. Without a key, "create a goal" becomes two goals and —
24
+ * far worse — "start" becomes two Runs, spending two units of a person's
25
+ * autonomy budget for one instruction. The server already keys goal creation on
26
+ * `clientRef`; this makes the pattern uniform so no caller has to notice which
27
+ * routes are safe.
28
+ *
29
+ * ─────────────────────────────────────────────────────────────────────────────
30
+ * WHAT IS DELIBERATELY NOT HERE
31
+ *
32
+ * ⚠️ MEMORY, INDEX AND GRAPH ARE MACHINE-LOCAL AND ARE NOT EXPOSED. Phase 11's
33
+ * memory, Phase 12's index and Phase 13's graph live in `.looply/state` on the
34
+ * developer's own machine, and they describe their private source code — an
35
+ * index names every file and symbol in the repository. Serving them from this
36
+ * API would mean shipping that to a server that does not have it today, which
37
+ * is a privacy regression dressed as a feature. See `docs/PLATFORM.md`.
38
+ */
39
+ export interface LooplyClientOptions {
40
+ readonly baseUrl: string;
41
+ /**
42
+ * Called for every request, so a rotated credential is picked up without
43
+ * rebuilding the client.
44
+ */
45
+ readonly getToken: () => string | null;
46
+ /** Called on a 401 so a shell can drop to sign-in rather than retrying. */
47
+ readonly onUnauthorized?: () => void;
48
+ readonly timeoutMs?: number;
49
+ /** Injected so this package has no runtime dependency and tests need no network. */
50
+ readonly fetch?: typeof globalThis.fetch;
51
+ }
52
+ export declare class LooplyClient {
53
+ private readonly opts;
54
+ private readonly http;
55
+ constructor(opts: LooplyClientOptions);
56
+ /**
57
+ * ⚠️ THIS ROUTE ANSWERS A BARE ARRAY, NOT `{ projects }`.
58
+ *
59
+ * 🔴 THE FIRST DRAFT UNWRAPPED AN ENVELOPE THAT DOES NOT EXIST, and returned
60
+ * `undefined` typed as `ProjectView[]` — the cast asserted the envelope into
61
+ * being and TypeScript had nothing to object to. It was caught by
62
+ * `sdk-flagship.e2e.spec.ts`, which drives this client against a REAL server;
63
+ * a suite that mocked `fetch` would have returned whatever shape this method
64
+ * asked for and agreed with itself all the way to production.
65
+ */
66
+ projects(): Promise<readonly ProjectView[]>;
67
+ /**
68
+ * Create a goal, at most once per key.
69
+ *
70
+ * 🔴 `clientRef` IS THE IDEMPOTENCY KEY AND IT IS REQUIRED. There is no
71
+ * DELETE for a goal, so a duplicate from a retried POST is permanent. Sending
72
+ * the same key with the SAME text returns the same goal; sending it with
73
+ * DIFFERENT text is a conflict, because the key was a promise about which
74
+ * request this is.
75
+ */
76
+ createGoal(projectId: string, input: {
77
+ readonly clientRef: string;
78
+ readonly text: string;
79
+ /** "B after A": ids of goals of the same project. Refused if unknown, itself, or a cycle (named). */
80
+ readonly dependsOn?: readonly string[];
81
+ /** Repository regions this goal may write to; canonicalised by the server. */
82
+ readonly surfaces?: readonly string[];
83
+ /** Which requirements and constraints of the project's sources this goal serves. */
84
+ readonly requirements?: readonly RequirementLinkView[];
85
+ }): Promise<GoalView>;
86
+ goal(projectId: string, goalId: string): Promise<GoalView>;
87
+ goals(projectId: string, state?: GoalView['state']): Promise<readonly GoalView[]>;
88
+ /**
89
+ * Hand a document over, at most once per key AND at most once per content.
90
+ *
91
+ * A source is stored verbatim and content-addressed: the same bytes under a
92
+ * fresh `clientRef` answer with the source that already exists, and the same
93
+ * `clientRef` with different bytes is a conflict. What comes back is the
94
+ * stored record with its anchored statements — the extractor's answer, so a
95
+ * caller can see what Looply read before anything is built on it.
96
+ *
97
+ * ⚠️ `kind` is `markdown`, `text` or `pdf`. The text kinds send `content`;
98
+ * a `pdf` sends the `pages` of its text layer (as read on the machine that
99
+ * holds the file) and may carry the file's own sha256 as `originSha256`.
100
+ */
101
+ createSource(projectId: string, input: {
102
+ readonly clientRef: string;
103
+ readonly name: string;
104
+ readonly kind: SourceKindView;
105
+ readonly content?: string;
106
+ readonly pages?: readonly {
107
+ readonly page: number;
108
+ readonly text: string;
109
+ }[];
110
+ readonly originSha256?: string;
111
+ }): Promise<SourceDetailView>;
112
+ /**
113
+ * Classify a source with the current classifier version, once.
114
+ *
115
+ * Idempotent per version: the rows that exist are answered as they are.
116
+ * What comes back says what each statement IS; nothing here says what the
117
+ * repository has done about it.
118
+ */
119
+ classifySource(projectId: string, sourceId: string): Promise<{
120
+ classifications: readonly SourceClassificationView[];
121
+ classifierVersion: number;
122
+ created: boolean;
123
+ }>;
124
+ /**
125
+ * Record where each requirement of a source stands against the repository,
126
+ * as measured at one committed tree — once per (tree, reconciler version).
127
+ *
128
+ * The measurement is made where the project index lives (the machine that
129
+ * holds the repository; `looply source reconcile` is the usual caller) with
130
+ * `@looply/core`'s `reconcileStatements`; the server stores it verbatim and
131
+ * recomputes nothing. A second hand-over of the same tree answers the rows
132
+ * on record (`created: false`).
133
+ *
134
+ * 🔴 `implemented-and-evidenced` and `partial` are REFUSED here (400): a
135
+ * rule cannot put them on record. They are a person's attestation with
136
+ * evidence, which is a different act.
137
+ */
138
+ reconcileSource(projectId: string, sourceId: string, input: {
139
+ readonly treeSha: string;
140
+ readonly reconcilerVersion: number;
141
+ readonly dispositions: readonly SourceDispositionView[];
142
+ }): Promise<{
143
+ dispositions: readonly SourceDispositionView[];
144
+ treeSha: string;
145
+ reconcilerVersion: number;
146
+ created: boolean;
147
+ }>;
148
+ /**
149
+ * Say, as a person, that a requirement is implemented-and-evidenced or
150
+ * partial — with at least one evidence anchor, at the committed tree the
151
+ * evidence was read at.
152
+ *
153
+ * 🔴 THIS NEEDS A DEVICE TOKEN. A machine token is refused (403): the machine
154
+ * measures (`reconcileSource`), a person vouches. A measurement's values are
155
+ * refused here (400). The same person saying the same thing at the same tree
156
+ * is one act: a repeat answers the record with `created: false`.
157
+ */
158
+ attestSource(projectId: string, sourceId: string, input: {
159
+ readonly ordinal: number;
160
+ readonly disposition: 'implemented-and-evidenced' | 'partial';
161
+ readonly treeSha: string;
162
+ readonly anchors: readonly EvidenceAnchorView[];
163
+ readonly note?: string;
164
+ }): Promise<{
165
+ attestation: SourceAttestationView;
166
+ created: boolean;
167
+ }>;
168
+ /**
169
+ * What is covered and by whom: every requirement and constraint of a source
170
+ * with the goals that serve it, where it stands, what a person attested, and
171
+ * one word each. A read; nothing here is written by anyone.
172
+ */
173
+ coverage(projectId: string, sourceId: string): Promise<SourceCoverageView>;
174
+ /**
175
+ * Every session-control grant this project has issued, live and historical.
176
+ *
177
+ * 🔴 THE MACHINE READS THESE AND WRITES NONE OF THEM. `OWNER_CHARTER.md` §5
178
+ * F-14 is explicit that only the server may issue one — a machine that could
179
+ * mint its own would be granting itself authority over a session, which is
180
+ * the whole thing the grant exists to prevent. So there is a reader here and
181
+ * deliberately no `issueSessionGrant`: issuing is a person's act, from a
182
+ * person's device, against the route that stamps their principal onto it.
183
+ */
184
+ sessionGrants(projectId: string): Promise<readonly SessionControlGrantView[]>;
185
+ /**
186
+ * Record that a session acknowledged a grant, through the channel it came on.
187
+ *
188
+ * ⚠️ THE CHANNEL IS CLAIMED, NOT PROVED, AND THAT IS WHY IT IS RECORDED
189
+ * RATHER THAN TRUSTED. A session's adapter and a person's terminal can hold
190
+ * the same device token — the token says which DEVICE, never which program.
191
+ * So this writes down what arrived and `controlVerdict` in core refuses
192
+ * anything that is not `adapter`, which turns a person's attempt into a fact
193
+ * on the record instead of an inference nobody can check.
194
+ */
195
+ acknowledgeSessionGrant(projectId: string, grantId: string, ack: {
196
+ readonly handshakeNonce: string;
197
+ readonly sessionId: string;
198
+ readonly workspace: string;
199
+ readonly repositoryId: string;
200
+ readonly scope: readonly string[];
201
+ readonly via: 'adapter' | 'operator';
202
+ }): Promise<SessionControlGrantView>;
203
+ /** A project's sources, newest first, without their content. */
204
+ sources(projectId: string): Promise<readonly SourceView[]>;
205
+ /** One source, verbatim, with its anchored statements, their latest classifications and the most recent reconciliation. */
206
+ source(projectId: string, sourceId: string): Promise<SourceDetailView>;
207
+ /**
208
+ * Ask Looply to work on a goal, once.
209
+ *
210
+ * 🔴 ONE EXPLICIT START ≤ ONE RUN, AND A LOST RESPONSE MUST NOT SPEND TWO.
211
+ * `clientRef` is what makes a retry safe: the same key names the same act of
212
+ * permission, so a repeat returns the command the first call created rather
213
+ * than authorising a second Start.
214
+ *
215
+ * ⚠️ IT IS REQUIRED HERE THOUGH THE SERVER TREATS IT AS OPTIONAL. The server
216
+ * has to accept keyless commands because the phone and the CLI have always
217
+ * sent none; an SDK written today has no such history and no reason to offer
218
+ * the unsafe shape. A caller who genuinely wants at-least-once can pass a
219
+ * fresh key each time — deliberately, rather than by omission.
220
+ *
221
+ * 🔴 THIS DEFECT WAS FOUND BY WRITING THIS METHOD. `POST /v1/commands` had no
222
+ * key at all, and the daemon derives a Start's request id from the COMMAND
223
+ * ID, so a retried enqueue was a second Start spending a second ordinal of a
224
+ * person's autonomy budget. Phase 15 added the key to the server.
225
+ */
226
+ startGoal(projectId: string, goalId: string, input: {
227
+ readonly clientRef: string;
228
+ /**
229
+ * Wait for the machine up to this ISO instant (at most 7 days ahead)
230
+ * instead of the default hour. For an unattended caller: a laptop with
231
+ * its lid shut picks the Start up when it wakes, or it expires visibly.
232
+ */
233
+ readonly deliverBy?: string;
234
+ }): Promise<{
235
+ readonly commandId: string;
236
+ }>;
237
+ /**
238
+ * Is the machine that holds this project awake? A Start is delivered by the
239
+ * machine polling, so this is the first half of "will my Start run".
240
+ */
241
+ presence(projectId: string): Promise<PresenceView>;
242
+ /**
243
+ * What became of a command.
244
+ *
245
+ * `startGoal` returns an id and then goes quiet; this is the durable answer —
246
+ * started, terminally refused, and why.
247
+ *
248
+ * ⚠️ COMMAND TRUTH ONLY. `done` on a start-run means the Start reached its
249
+ * success boundary, NOT that the run finished or the goal was met. A caller
250
+ * wanting the whole picture composes this with `status()` and `events()`.
251
+ */
252
+ commandStatus(projectId: string, commandId: string): Promise<CommandStatusView>;
253
+ /** Amend or abandon a goal. `version` is the OCC token the read returned. */
254
+ patchGoal(projectId: string, goalId: string, input: {
255
+ readonly state?: GoalView['state'];
256
+ readonly text?: string;
257
+ /** Replaces the list. Bumps the version like a change of words. */
258
+ readonly dependsOn?: readonly string[];
259
+ readonly surfaces?: readonly string[];
260
+ /** Replaces the set of requirements this goal serves. */
261
+ readonly requirements?: readonly RequirementLinkView[];
262
+ readonly version: number;
263
+ }): Promise<GoalView>;
264
+ items(projectId: string): Promise<readonly ItemView[]>;
265
+ /**
266
+ * The current run, as a projection.
267
+ *
268
+ * ⚠️ THERE IS NO `/runs` COLLECTION, so this SDK does not pretend to one. The
269
+ * server keeps a single latest-run projection per project — which is what the
270
+ * product actually shows — and run HISTORY lives in the event ledger, reached
271
+ * through `events()`.
272
+ */
273
+ status(projectId: string): Promise<RunStatusView>;
274
+ /**
275
+ * Ask the keeper about a project (DESIGN §9 read path).
276
+ *
277
+ * A POST that READS. The question travels in the body rather than a query
278
+ * string, because a sentence about somebody's codebase does not belong in an
279
+ * access log — and because the answer quotes item titles, commit shas and the
280
+ * implementer's own round notes, it needs the same grant `status()` does.
281
+ *
282
+ * ⚠️ NOTHING HERE ACTS. The keeper’s write path is deliberately not a second
283
+ * set of methods: an intent goes through the routes a person already uses —
284
+ * `command()`, `patchItem()`, answering a question — with `actor: keeper` on
285
+ * the record, so there is no privileged path for it to take.
286
+ */
287
+ ask(projectId: string, question: string): Promise<KeeperAnswerView>;
288
+ /**
289
+ * ⚠️ THE APPROVAL ROUTES ANSWER BARE VALUES, NOT ENVELOPES.
290
+ *
291
+ * The goal and queue routes DO wrap (`{ goals }`, `{ items }`) and these do
292
+ * not. There is no rule to infer here — it is simply how each controller was
293
+ * written — which is exactly why this client is checked against a running
294
+ * server rather than against a design document.
295
+ */
296
+ approvals(projectId: string): Promise<readonly ApprovalView[]>;
297
+ approval(projectId: string, approvalId: string): Promise<ApprovalView>;
298
+ /**
299
+ * Advise on a pending approval: approve or deny, and why.
300
+ *
301
+ * 🔴 THIS IS WHAT AN INTEGRATION CAN DO WITH AN APPROVAL, AND ALL IT CAN DO.
302
+ * Deciding is a person's authority (`decideApproval` needs an owner's device
303
+ * token); an integration that has reviewed the action in its own process says
304
+ * what it concluded here, and the owner is notified and decides with the
305
+ * advice in front of them. Pending only (a 409 otherwise); latest wins, so a
306
+ * retry is harmless.
307
+ */
308
+ recommendApproval(projectId: string, approvalId: string, input: {
309
+ readonly decision: 'approve' | 'deny';
310
+ readonly note?: string;
311
+ }): Promise<ApprovalView>;
312
+ /**
313
+ * Approve or deny one exact action.
314
+ *
315
+ * 🔴 A PERSON DOES THIS. A machine credential is refused by the server, and
316
+ * that refusal is the security content of the route rather than an
317
+ * inconvenience — the hazard is not the 403, it is somebody meeting it and
318
+ * widening the guard so a machine can authorise its own work.
319
+ *
320
+ * `version` is the approval's OCC token: deciding against a stale one is a
321
+ * conflict, not a silent overwrite, because the thing being approved may have
322
+ * expired or been revoked since it was read.
323
+ */
324
+ decideApproval(projectId: string, approvalId: string, input: {
325
+ /**
326
+ * 🔴 `revoke` IS A REAL DECISION AND THE FIRST DRAFT OMITTED IT. Taking
327
+ * back an authorisation before it is acted on is exactly the operation a
328
+ * person most urgently needs, and a client that could not express it
329
+ * would send them to another device to do it.
330
+ */
331
+ readonly decision: 'approve' | 'deny' | 'revoke';
332
+ /**
333
+ * 🔴 THE FIELD IS `expectedVersion`, AND SENDING `version` MEANT SENDING
334
+ * NOTHING. The server requires a whole number here and refuses without
335
+ * one, so this method would have 400'd on every call. There was no
336
+ * `requestKey` either — a name this SDK invented for a route that has
337
+ * never accepted it. Both were found by reading the controller.
338
+ *
339
+ * Retry safety comes from the OCC token rather than from a key: if the
340
+ * first call landed, the version has moved, and the retry is a 409 that
341
+ * tells the caller so instead of deciding twice.
342
+ */
343
+ readonly expectedVersion: number;
344
+ /** Safe prose from a person, kept with the decision. */
345
+ readonly note?: string;
346
+ }): Promise<ApprovalView>;
347
+ execution(projectId: string, executionId: string): Promise<GovernedExecutionView>;
348
+ /**
349
+ * Tell Looply what actually happened, when it could not establish that itself.
350
+ *
351
+ * 🔴 THIS CLOSES A GAP PHASE 8 LEFT ON PURPOSE. The resolution route has been
352
+ * owner-guarded and mutation-covered since Phase 8 and had NO CLIENT — and its
353
+ * client could not be the CLI, because `server-client.ts` authenticates with a
354
+ * machine token and the route refuses a machine BY DESIGN. A machine settling
355
+ * its own ambiguity is the one thing the route exists to prevent. It belongs
356
+ * to a person, so it belongs here.
357
+ *
358
+ * ⚠️ `note` IS PRIVATE HUMAN TESTIMONY. It is why a person believes what they
359
+ * are asserting, it is kept out of every machine- and model-facing view, and a
360
+ * client must never render it into anything a model reads.
361
+ */
362
+ resolveExecution(projectId: string, executionId: string, input: {
363
+ readonly outcome?: 'succeeded' | 'failed';
364
+ /**
365
+ * ⚠️ NO KEY AND NO VERSION — this route accepts neither, and inventing
366
+ * one here would be a field the server discards while the caller believes
367
+ * they are protected by it. What makes a repeat safe is that a resolution
368
+ * is recorded once and never rewrites `state`.
369
+ */
370
+ readonly note?: string;
371
+ }): Promise<GovernedExecutionView>;
372
+ /**
373
+ * Whether autonomous pursuit of a goal is still running, and why it stopped.
374
+ *
375
+ * ⚠️ THIS IS THE AUTONOMY VIEW, NOT A SAFETY ENDPOINT. There is no
376
+ * `/safety` route: a loop-safety hold is enforced ON THE MACHINE, in
377
+ * `loop-safety-store.ts`, and never travels to the server. What the server
378
+ * knows is that pursuit ENDED and the reason it was given — which is what a
379
+ * person needs to see, and is version-scoped because a hold on version 4 must
380
+ * not block version 5.
381
+ */
382
+ autonomy(projectId: string, goalId: string): Promise<GoalAutonomyReport>;
383
+ /**
384
+ * What the server saw of a goal being delivered, per version.
385
+ *
386
+ * This used to say there was no `/evidence` route and that one would be a
387
+ * server change, not an SDK one. It is now that server change:
388
+ * `GET …/goals/:goalId/evidence` stitches, once, the runs that pursued each
389
+ * version, every round's commit with the gate's and the prover's verdicts,
390
+ * where each pursuit came to rest, and what was pushed under an approval.
391
+ *
392
+ * ⚠️ NOT THE MACHINE'S PROOF. Whether a goal was met is decided on the
393
+ * machine; this is what reached the server. Render `pursuits[].state ===
394
+ * 'satisfied'` as the machine's claim and `landed` as the commits behind it —
395
+ * never as "proven". Whitelisted: no gate output, no prover prose, no remote.
396
+ */
397
+ goalEvidence(projectId: string, goalId: string): Promise<GoalEvidenceView>;
398
+ /**
399
+ * One page of events, resuming from a cursor.
400
+ *
401
+ * 🔴 REST REPLAY IS THE CORRECTNESS PATH; A SOCKET IS ONLY AN ACCELERATOR. A
402
+ * client that has been away — asleep, offline, restarted — reconstructs by
403
+ * paging from the last cursor it stored. A live stream that missed an event
404
+ * has no way to notice, so it may never be the only way state arrives.
405
+ *
406
+ * ⚠️ DUPLICATES ARE SAFE AND EXPECTED. `id` is stable, so a client that has
407
+ * seen one can drop it; a client that has NOT seen one must not be missing it.
408
+ * Delivering twice is the cheap failure, and not delivering is the expensive
409
+ * one.
410
+ */
411
+ events(projectId: string, opts?: {
412
+ readonly after?: string;
413
+ readonly limit?: number;
414
+ }): Promise<PageView<PlatformEventView>>;
415
+ /**
416
+ * 🔴 THE QUERY IS A SEPARATE ARGUMENT, NEVER INTERPOLATED INTO `path`.
417
+ *
418
+ * `path` is therefore always a literal route with parameter slots and nothing
419
+ * else, which is what lets `boundary.spec.ts` compare it to the server's route
420
+ * table exactly rather than by heuristic. An earlier version appended
421
+ * `${q}` inside the template and the guard could not tell a query suffix from
422
+ * a path segment — it read `/goals?state=x` as a route called `/goals:param`.
423
+ * Fixing the guard's parser instead would have left the ambiguity in place for
424
+ * the next method to trip over.
425
+ *
426
+ * `undefined` values are dropped rather than sent as the string "undefined".
427
+ */
428
+ private request;
429
+ }
430
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}