@plune-ai/cli 0.6.0 → 0.7.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.
@@ -0,0 +1,315 @@
1
+ /**
2
+ * The platform's wire, mirrored in types — and only in types.
3
+ *
4
+ * There is deliberately no zod here. ADR 0012 says the platform owns the contract and clients
5
+ * conform to it; a second copy of the schema on the client is a second place to drift, and it
6
+ * would put a validation library into the dependency tree of every project that installs an
7
+ * adapter. The platform validates and answers with a named reason (`validation failed — <field>:
8
+ * <why>`), and that reason is what the reporter shows.
9
+ *
10
+ * What keeps these shapes honest is not a runtime check here but the platform's contract test
11
+ * against the published package — the same mechanism `cli-sync-contract.test.ts` already
12
+ * establishes for `plune sync`.
13
+ */
14
+ /** Kinds of run the platform accepts. `mixed` is declared, never derived — "some of these were run
15
+ * by a person" is a fact about the process, not something visible in the results. */
16
+ type RunKind = 'automated' | 'manual' | 'mixed' | 'eval';
17
+ /** What a result finally means. The reporter never picks one of these — see `PendingResult`. */
18
+ type ResultStatus = 'passed' | 'failed' | 'broken' | 'blocked' | 'skipped';
19
+ /**
20
+ * The namespaces other tools already use for the same test.
21
+ *
22
+ * The ORDER is the platform's, and it matters there, not here: a lookup that names no kind
23
+ * resolves in that sequence. A client that reproduced the ranking would be a second place to
24
+ * forget it, so this list is only a vocabulary — the ranking stays server-side.
25
+ */
26
+ type ExternalKeyKind = 'playwright-id' | 'path-title' | 'allure-history' | 'cairn-stable' | 'eval-id' | 'qase' | 'testrail';
27
+ /** One candidate identifier for a test. `kind` omitted means "resolve it however you rank them". */
28
+ interface KeyRef {
29
+ kind?: ExternalKeyKind;
30
+ value: string;
31
+ }
32
+ /** Where the run came from. Every field optional because a shard genuinely may not know it. */
33
+ interface RunMeta {
34
+ sha?: string;
35
+ branch?: string;
36
+ ciUrl?: string;
37
+ runner?: string;
38
+ }
39
+ /** How the run itself went, as the runner measured it. `retry` 0 is the first attempt. */
40
+ interface Execution {
41
+ startedAt: string;
42
+ finishedAt: string;
43
+ durationMs: number;
44
+ retry: number;
45
+ worker?: string;
46
+ }
47
+ /** A link, not a file. Uploading artefacts is C3; this carries what someone else already hosts. */
48
+ interface Attachment {
49
+ name: string;
50
+ url: string;
51
+ contentType?: string;
52
+ }
53
+ /** One assertion's outcome, in the shape the platform already stores for its own runner. */
54
+ interface AssertionRecord {
55
+ type: string;
56
+ passed: boolean;
57
+ score?: number;
58
+ reason?: string;
59
+ }
60
+ /**
61
+ * One result, as an adapter hands it over — before it has a test case.
62
+ *
63
+ * `keys` rather than a `testCaseId` is the seam that keeps adapters thin AND leaves room for C2:
64
+ * an adapter says "here is how this test identifies itself", and resolving that to a case is the
65
+ * core's business. C2 adds rungs to the ladder by putting more candidates in this array; neither
66
+ * this shape nor the adapters change.
67
+ *
68
+ * `rawStatus` rather than `status` is the other half of that discipline. The platform maps a
69
+ * runner's word to a meaning per project (`timedOut` → `broken` by default, overridable), so a
70
+ * team can change what a timeout means to them without waiting for a reporter release. A reporter
71
+ * that decided the status would take that away and put two different answers on two surfaces.
72
+ */
73
+ interface PendingResult {
74
+ resultKey: string;
75
+ /**
76
+ * The case this result IS, when the test says so outright — a `PluneId` annotation, or the
77
+ * `@P<id>` token C2 also reads out of a title.
78
+ *
79
+ * Separate from `keys` because it is a different claim, and the platform draws the same line:
80
+ * `plune-id` is deliberately absent from its external-key kinds, since it is the thing external
81
+ * keys resolve TO. Present means no lookup is needed for this result at all.
82
+ */
83
+ testCaseId?: string;
84
+ keys: KeyRef[];
85
+ /** Which runner produced this — the key into the project's status map. */
86
+ source: string;
87
+ /** The runner's own word: `passed`, `failed`, `timedOut`, `interrupted`, `skipped`. */
88
+ rawStatus: string;
89
+ /** What the author expected — `test.fail()` and friends. Absent means a pass was expected. */
90
+ expectedStatus?: ResultStatus;
91
+ execution?: Execution;
92
+ errorContext?: string;
93
+ params?: Record<string, unknown>;
94
+ attachments?: Attachment[];
95
+ assertions?: AssertionRecord[];
96
+ }
97
+ /** What a run submission looks like once a key has resolved. Built by the core, never by a caller. */
98
+ interface ResultSubmission extends Omit<PendingResult, 'keys' | 'testCaseId'> {
99
+ testCaseId: string;
100
+ }
101
+ /** One entry of the "what this run intended to execute" list, from which the platform derives
102
+ * `notRun`. Either side may be given; the core fills `externalKey` from the adapter's key list. */
103
+ interface ExpectedEntry {
104
+ testCaseId?: string;
105
+ externalKey?: KeyRef;
106
+ }
107
+ /** The run as the platform answers with it. Only `id` is load-bearing for the client. */
108
+ interface RunRecord {
109
+ id: string;
110
+ projectId?: string;
111
+ schemaVersion?: number;
112
+ }
113
+ /** Per-element verdicts from a batch. `conflict` means two runners disagreed about one result —
114
+ * the platform refuses to resolve that by overwriting, and neither do we. */
115
+ interface SubmitCounts {
116
+ accepted: number;
117
+ duplicate: number;
118
+ conflict: number;
119
+ rejected: number;
120
+ }
121
+ /** What the session accumulated, for the one summary line a reporter prints. */
122
+ interface RunStats extends SubmitCounts {
123
+ /** Results whose keys matched no case — never sent, because inventing an id would write into
124
+ * somebody else's history. What to do about them is C2's ladder. */
125
+ unresolved: number;
126
+ /** Results written to the fallback file instead of the platform. */
127
+ deferred: number;
128
+ }
129
+ /** Everything the core needs to talk to a deployment. */
130
+ interface ReporterConfig {
131
+ /** Base URL. Falls back to `PLUNE_API_URL`, then beta — the same resolution `sync` uses. */
132
+ apiUrl?: string;
133
+ /** Bearer token. Falls back to the store `plune login` writes. */
134
+ token?: string;
135
+ /** The key several shards share so they land in one run. Absent means a run of its own. */
136
+ externalKey?: string;
137
+ kind?: RunKind;
138
+ meta?: RunMeta;
139
+ /** Results per request. The platform's ceiling is 500. */
140
+ batchSize?: number;
141
+ /** Where unsent batches go. Defaults beside the run file the CLI already writes. */
142
+ fallbackPath?: string;
143
+ /** Transport seam — the same one `sync.ts` uses so tests need no server. */
144
+ fetchImpl?: typeof fetch;
145
+ /** Where a line for the operator goes. Defaults to stderr. */
146
+ log?: (line: string) => void;
147
+ }
148
+
149
+ /**
150
+ * A run in progress.
151
+ *
152
+ * `runId: null` is not an error state to check for — it is a run that never reached the platform,
153
+ * and everything below keeps working: results go to the fallback file, `finish` does nothing, and
154
+ * the test run itself is untouched. A reporter that failed a build because a dashboard was down
155
+ * would be worse than no reporter.
156
+ */
157
+ interface RunSession {
158
+ readonly runId: string | null;
159
+ readonly joined: boolean;
160
+ readonly stats: Readonly<RunStats>;
161
+ /** Hand over one result. Sends a batch as soon as enough have accumulated. */
162
+ add(result: PendingResult): Promise<void>;
163
+ /** Send whatever is buffered. Safe to call when nothing is. */
164
+ flush(): Promise<void>;
165
+ /** Send what is left and close the run. */
166
+ finish(reason?: string): Promise<void>;
167
+ /** Send what is left and deliberately leave the run open — the shard case (AC-03). */
168
+ leaveOpen(): Promise<void>;
169
+ }
170
+ /**
171
+ * Start a run, or join the one that already carries this key.
172
+ *
173
+ * `expected` — one list of candidate identifiers per test the runner knows about — is used twice
174
+ * on purpose. It becomes `configuration.expected`, from which the platform derives what never ran
175
+ * (AC-09); and it is the input to the single up-front resolve, so the per-test path afterwards is
176
+ * a map lookup rather than a request (AC-10). A runner that cannot enumerate its tests passes
177
+ * nothing and resolves lazily on the first flush — same function, later moment.
178
+ */
179
+ declare function startRun(passed: ReporterConfig, expected?: readonly (readonly KeyRef[])[]): Promise<RunSession>;
180
+
181
+ /**
182
+ * The ceiling the platform's contract puts on `resultKey` (`resultSubmissionSchema`).
183
+ *
184
+ * Exported so the test asserts against the same number the fallback branch is chosen by, rather
185
+ * than against a copy of it.
186
+ */
187
+ declare const RESULT_KEY_MAX = 200;
188
+ /**
189
+ * The reporter's own id for one result — minted by the client, never by the server.
190
+ *
191
+ * It has to be **the same string** the second time the same result is reported, because that is
192
+ * the entire mechanism behind AC-04: `playwright merge-reports` runs the reporter again over the
193
+ * merged blob, and the platform's unique index on `(run_id, result_key)` answers `duplicate`
194
+ * instead of writing a second row. A key derived from anything that varies between the two passes
195
+ * — a timestamp, a counter, a random id — would double every result silently.
196
+ *
197
+ * Readable while it fits: this string lands in the database and in error messages, and
198
+ * `a1b2c3…#0` tells a person nothing about which test broke. Only when the plain form would
199
+ * exceed what the contract accepts does it fall back to a digest — and the digest covers the test
200
+ * id alone, so the retry stays visible and a flaky test's three attempts never collapse into one.
201
+ */
202
+ declare function resultKey(testId: string, retry: number): string;
203
+
204
+ /**
205
+ * What a CI job can tell the reporter without editing a config file.
206
+ *
207
+ * A Playwright config is committed; a CI job is not. Everything here exists so a pipeline can set
208
+ * the shared run key, the token and the batch size per job — the things that differ between the
209
+ * laptop the config was written on and the runner it executes on.
210
+ */
211
+ /**
212
+ * Names the reporter promises but the platform cannot yet store (feature D13).
213
+ *
214
+ * They are listed rather than ignored because of how they would fail otherwise: a run's `meta`
215
+ * strips keys it does not know, so sending `PLUNE_ENV` there would be accepted, dropped, and look
216
+ * exactly like success. A client believing it saved something the server discarded is the most
217
+ * expensive kind of quiet — cheaper to say "not yet" out loud.
218
+ */
219
+ declare const UNSUPPORTED_VARS: readonly ["PLUNE_RUN_TITLE", "PLUNE_ENV", "PLUNE_LABELS", "PLUNE_GROUP"];
220
+ interface EnvSettings {
221
+ /** Config fields the environment supplied. Anything passed explicitly outranks these. */
222
+ config: Partial<ReporterConfig>;
223
+ /** This process must not close the run — several share it, or the job closes it itself. */
224
+ keepOpen: boolean;
225
+ /** Variables that were set and have nowhere to land. The caller says so once. */
226
+ ignored: string[];
227
+ }
228
+ declare function readEnv(env?: NodeJS.ProcessEnv): EnvSettings;
229
+
230
+ /**
231
+ * What ends up in a deferred batch.
232
+ *
233
+ * Two shapes, because two things can fail. A batch refused by the platform is already resolved
234
+ * (`ResultSubmission`), and replay sends it as it is. A batch deferred because the platform was
235
+ * never reached has not been resolved at all (`PendingResult`), and replay has to look its keys
236
+ * up first. Flattening them into one shape would mean inventing a `testCaseId` for the second
237
+ * case, which is exactly what AC-11 forbids.
238
+ */
239
+ type DeferredResult = ResultSubmission | PendingResult;
240
+ /** Beside `last-run.json`, because that is where a Plune user already looks for a run. */
241
+ declare const DEFAULT_FALLBACK_PATH: string;
242
+ /**
243
+ * One batch that could not be sent.
244
+ *
245
+ * A batch rather than a result: the batch is the unit that failed, and it is the unit `plune run
246
+ * report` will re-send unchanged. Splitting it into results here would mean re-grouping them there
247
+ * against a size the platform may have changed in the meantime.
248
+ *
249
+ * `runId: null` is a real state, not a missing value — the platform was unreachable when the run
250
+ * was supposed to be created, so these results have no run yet and replay has to make one.
251
+ */
252
+ interface DeferredBatch {
253
+ runId: string | null;
254
+ externalKey: string | null;
255
+ results: DeferredResult[];
256
+ }
257
+ /**
258
+ * Append a batch to the fallback file, creating its directory if nobody has yet.
259
+ *
260
+ * JSONL and synchronous, both for the same reason: a run is being abandoned when this is called,
261
+ * and the next thing to happen may be the process dying. A line that is already flushed stays
262
+ * readable no matter what happens to the ones after it, which one large JSON document rewritten
263
+ * on every batch would not.
264
+ *
265
+ * Nothing about the connection goes in — not the token, not the headers, not the config. The line
266
+ * holds who the run was and what did not reach it, which is exactly what replaying needs.
267
+ */
268
+ declare function appendBatch(file: string, batch: DeferredBatch): void;
269
+
270
+ /**
271
+ * The only thing in this package that talks to the platform.
272
+ *
273
+ * It classifies rather than throws. Every caller has to decide something different about a
274
+ * failure — a batch goes to the fallback file, a rejected token silences the rest of the run, a
275
+ * closed run is a configuration mistake worth naming — and an exception hierarchy would make each
276
+ * of those a `catch` that re-derives what already happened here.
277
+ *
278
+ * It also does not log. Anything printed about a failure is printed by the session, which knows
279
+ * the context; keeping the token's only home in this file is what makes AC-12 structural rather
280
+ * than a rule someone has to remember.
281
+ */
282
+ type ClientFailureKind =
283
+ /** The token was refused. Retrying sends the same token to the same door. */
284
+ 'auth'
285
+ /** The server understood and said no: a closed run, an impossible lifecycle transition. */
286
+ | 'conflict'
287
+ /** The body was refused. It will be refused identically next time. */
288
+ | 'invalid'
289
+ /** Nobody answered, or answered with their own failure. Worth another try. */
290
+ | 'unavailable'
291
+ /** Everything else — reported, never retried, because we cannot say retrying would help. */
292
+ | 'other';
293
+ type ClientOutcome<T> = {
294
+ ok: true;
295
+ status: number;
296
+ body: T;
297
+ } | {
298
+ ok: false;
299
+ kind: ClientFailureKind;
300
+ status: number | null;
301
+ detail: string;
302
+ };
303
+ interface ClientOptions {
304
+ apiUrl: string;
305
+ token: string;
306
+ fetchImpl?: typeof fetch;
307
+ /** Test seam — the same idea as `fetchImpl`, so a retry test costs milliseconds, not seconds. */
308
+ wait?: (ms: number) => Promise<void>;
309
+ }
310
+ interface PlatformClient {
311
+ post<T>(path: string, body: unknown): Promise<ClientOutcome<T>>;
312
+ }
313
+ declare function createClient(opts: ClientOptions): PlatformClient;
314
+
315
+ export { type AssertionRecord, type Attachment, type ClientFailureKind, type ClientOptions, type ClientOutcome, DEFAULT_FALLBACK_PATH, type DeferredBatch, type DeferredResult, type EnvSettings, type Execution, type ExpectedEntry, type ExternalKeyKind, type KeyRef, type PendingResult, type PlatformClient, RESULT_KEY_MAX, type ReporterConfig, type ResultStatus, type ResultSubmission, type RunKind, type RunMeta, type RunRecord, type RunSession, type RunStats, type SubmitCounts, UNSUPPORTED_VARS, appendBatch, createClient, readEnv, resultKey, startRun };
@@ -0,0 +1,315 @@
1
+ /**
2
+ * The platform's wire, mirrored in types — and only in types.
3
+ *
4
+ * There is deliberately no zod here. ADR 0012 says the platform owns the contract and clients
5
+ * conform to it; a second copy of the schema on the client is a second place to drift, and it
6
+ * would put a validation library into the dependency tree of every project that installs an
7
+ * adapter. The platform validates and answers with a named reason (`validation failed — <field>:
8
+ * <why>`), and that reason is what the reporter shows.
9
+ *
10
+ * What keeps these shapes honest is not a runtime check here but the platform's contract test
11
+ * against the published package — the same mechanism `cli-sync-contract.test.ts` already
12
+ * establishes for `plune sync`.
13
+ */
14
+ /** Kinds of run the platform accepts. `mixed` is declared, never derived — "some of these were run
15
+ * by a person" is a fact about the process, not something visible in the results. */
16
+ type RunKind = 'automated' | 'manual' | 'mixed' | 'eval';
17
+ /** What a result finally means. The reporter never picks one of these — see `PendingResult`. */
18
+ type ResultStatus = 'passed' | 'failed' | 'broken' | 'blocked' | 'skipped';
19
+ /**
20
+ * The namespaces other tools already use for the same test.
21
+ *
22
+ * The ORDER is the platform's, and it matters there, not here: a lookup that names no kind
23
+ * resolves in that sequence. A client that reproduced the ranking would be a second place to
24
+ * forget it, so this list is only a vocabulary — the ranking stays server-side.
25
+ */
26
+ type ExternalKeyKind = 'playwright-id' | 'path-title' | 'allure-history' | 'cairn-stable' | 'eval-id' | 'qase' | 'testrail';
27
+ /** One candidate identifier for a test. `kind` omitted means "resolve it however you rank them". */
28
+ interface KeyRef {
29
+ kind?: ExternalKeyKind;
30
+ value: string;
31
+ }
32
+ /** Where the run came from. Every field optional because a shard genuinely may not know it. */
33
+ interface RunMeta {
34
+ sha?: string;
35
+ branch?: string;
36
+ ciUrl?: string;
37
+ runner?: string;
38
+ }
39
+ /** How the run itself went, as the runner measured it. `retry` 0 is the first attempt. */
40
+ interface Execution {
41
+ startedAt: string;
42
+ finishedAt: string;
43
+ durationMs: number;
44
+ retry: number;
45
+ worker?: string;
46
+ }
47
+ /** A link, not a file. Uploading artefacts is C3; this carries what someone else already hosts. */
48
+ interface Attachment {
49
+ name: string;
50
+ url: string;
51
+ contentType?: string;
52
+ }
53
+ /** One assertion's outcome, in the shape the platform already stores for its own runner. */
54
+ interface AssertionRecord {
55
+ type: string;
56
+ passed: boolean;
57
+ score?: number;
58
+ reason?: string;
59
+ }
60
+ /**
61
+ * One result, as an adapter hands it over — before it has a test case.
62
+ *
63
+ * `keys` rather than a `testCaseId` is the seam that keeps adapters thin AND leaves room for C2:
64
+ * an adapter says "here is how this test identifies itself", and resolving that to a case is the
65
+ * core's business. C2 adds rungs to the ladder by putting more candidates in this array; neither
66
+ * this shape nor the adapters change.
67
+ *
68
+ * `rawStatus` rather than `status` is the other half of that discipline. The platform maps a
69
+ * runner's word to a meaning per project (`timedOut` → `broken` by default, overridable), so a
70
+ * team can change what a timeout means to them without waiting for a reporter release. A reporter
71
+ * that decided the status would take that away and put two different answers on two surfaces.
72
+ */
73
+ interface PendingResult {
74
+ resultKey: string;
75
+ /**
76
+ * The case this result IS, when the test says so outright — a `PluneId` annotation, or the
77
+ * `@P<id>` token C2 also reads out of a title.
78
+ *
79
+ * Separate from `keys` because it is a different claim, and the platform draws the same line:
80
+ * `plune-id` is deliberately absent from its external-key kinds, since it is the thing external
81
+ * keys resolve TO. Present means no lookup is needed for this result at all.
82
+ */
83
+ testCaseId?: string;
84
+ keys: KeyRef[];
85
+ /** Which runner produced this — the key into the project's status map. */
86
+ source: string;
87
+ /** The runner's own word: `passed`, `failed`, `timedOut`, `interrupted`, `skipped`. */
88
+ rawStatus: string;
89
+ /** What the author expected — `test.fail()` and friends. Absent means a pass was expected. */
90
+ expectedStatus?: ResultStatus;
91
+ execution?: Execution;
92
+ errorContext?: string;
93
+ params?: Record<string, unknown>;
94
+ attachments?: Attachment[];
95
+ assertions?: AssertionRecord[];
96
+ }
97
+ /** What a run submission looks like once a key has resolved. Built by the core, never by a caller. */
98
+ interface ResultSubmission extends Omit<PendingResult, 'keys' | 'testCaseId'> {
99
+ testCaseId: string;
100
+ }
101
+ /** One entry of the "what this run intended to execute" list, from which the platform derives
102
+ * `notRun`. Either side may be given; the core fills `externalKey` from the adapter's key list. */
103
+ interface ExpectedEntry {
104
+ testCaseId?: string;
105
+ externalKey?: KeyRef;
106
+ }
107
+ /** The run as the platform answers with it. Only `id` is load-bearing for the client. */
108
+ interface RunRecord {
109
+ id: string;
110
+ projectId?: string;
111
+ schemaVersion?: number;
112
+ }
113
+ /** Per-element verdicts from a batch. `conflict` means two runners disagreed about one result —
114
+ * the platform refuses to resolve that by overwriting, and neither do we. */
115
+ interface SubmitCounts {
116
+ accepted: number;
117
+ duplicate: number;
118
+ conflict: number;
119
+ rejected: number;
120
+ }
121
+ /** What the session accumulated, for the one summary line a reporter prints. */
122
+ interface RunStats extends SubmitCounts {
123
+ /** Results whose keys matched no case — never sent, because inventing an id would write into
124
+ * somebody else's history. What to do about them is C2's ladder. */
125
+ unresolved: number;
126
+ /** Results written to the fallback file instead of the platform. */
127
+ deferred: number;
128
+ }
129
+ /** Everything the core needs to talk to a deployment. */
130
+ interface ReporterConfig {
131
+ /** Base URL. Falls back to `PLUNE_API_URL`, then beta — the same resolution `sync` uses. */
132
+ apiUrl?: string;
133
+ /** Bearer token. Falls back to the store `plune login` writes. */
134
+ token?: string;
135
+ /** The key several shards share so they land in one run. Absent means a run of its own. */
136
+ externalKey?: string;
137
+ kind?: RunKind;
138
+ meta?: RunMeta;
139
+ /** Results per request. The platform's ceiling is 500. */
140
+ batchSize?: number;
141
+ /** Where unsent batches go. Defaults beside the run file the CLI already writes. */
142
+ fallbackPath?: string;
143
+ /** Transport seam — the same one `sync.ts` uses so tests need no server. */
144
+ fetchImpl?: typeof fetch;
145
+ /** Where a line for the operator goes. Defaults to stderr. */
146
+ log?: (line: string) => void;
147
+ }
148
+
149
+ /**
150
+ * A run in progress.
151
+ *
152
+ * `runId: null` is not an error state to check for — it is a run that never reached the platform,
153
+ * and everything below keeps working: results go to the fallback file, `finish` does nothing, and
154
+ * the test run itself is untouched. A reporter that failed a build because a dashboard was down
155
+ * would be worse than no reporter.
156
+ */
157
+ interface RunSession {
158
+ readonly runId: string | null;
159
+ readonly joined: boolean;
160
+ readonly stats: Readonly<RunStats>;
161
+ /** Hand over one result. Sends a batch as soon as enough have accumulated. */
162
+ add(result: PendingResult): Promise<void>;
163
+ /** Send whatever is buffered. Safe to call when nothing is. */
164
+ flush(): Promise<void>;
165
+ /** Send what is left and close the run. */
166
+ finish(reason?: string): Promise<void>;
167
+ /** Send what is left and deliberately leave the run open — the shard case (AC-03). */
168
+ leaveOpen(): Promise<void>;
169
+ }
170
+ /**
171
+ * Start a run, or join the one that already carries this key.
172
+ *
173
+ * `expected` — one list of candidate identifiers per test the runner knows about — is used twice
174
+ * on purpose. It becomes `configuration.expected`, from which the platform derives what never ran
175
+ * (AC-09); and it is the input to the single up-front resolve, so the per-test path afterwards is
176
+ * a map lookup rather than a request (AC-10). A runner that cannot enumerate its tests passes
177
+ * nothing and resolves lazily on the first flush — same function, later moment.
178
+ */
179
+ declare function startRun(passed: ReporterConfig, expected?: readonly (readonly KeyRef[])[]): Promise<RunSession>;
180
+
181
+ /**
182
+ * The ceiling the platform's contract puts on `resultKey` (`resultSubmissionSchema`).
183
+ *
184
+ * Exported so the test asserts against the same number the fallback branch is chosen by, rather
185
+ * than against a copy of it.
186
+ */
187
+ declare const RESULT_KEY_MAX = 200;
188
+ /**
189
+ * The reporter's own id for one result — minted by the client, never by the server.
190
+ *
191
+ * It has to be **the same string** the second time the same result is reported, because that is
192
+ * the entire mechanism behind AC-04: `playwright merge-reports` runs the reporter again over the
193
+ * merged blob, and the platform's unique index on `(run_id, result_key)` answers `duplicate`
194
+ * instead of writing a second row. A key derived from anything that varies between the two passes
195
+ * — a timestamp, a counter, a random id — would double every result silently.
196
+ *
197
+ * Readable while it fits: this string lands in the database and in error messages, and
198
+ * `a1b2c3…#0` tells a person nothing about which test broke. Only when the plain form would
199
+ * exceed what the contract accepts does it fall back to a digest — and the digest covers the test
200
+ * id alone, so the retry stays visible and a flaky test's three attempts never collapse into one.
201
+ */
202
+ declare function resultKey(testId: string, retry: number): string;
203
+
204
+ /**
205
+ * What a CI job can tell the reporter without editing a config file.
206
+ *
207
+ * A Playwright config is committed; a CI job is not. Everything here exists so a pipeline can set
208
+ * the shared run key, the token and the batch size per job — the things that differ between the
209
+ * laptop the config was written on and the runner it executes on.
210
+ */
211
+ /**
212
+ * Names the reporter promises but the platform cannot yet store (feature D13).
213
+ *
214
+ * They are listed rather than ignored because of how they would fail otherwise: a run's `meta`
215
+ * strips keys it does not know, so sending `PLUNE_ENV` there would be accepted, dropped, and look
216
+ * exactly like success. A client believing it saved something the server discarded is the most
217
+ * expensive kind of quiet — cheaper to say "not yet" out loud.
218
+ */
219
+ declare const UNSUPPORTED_VARS: readonly ["PLUNE_RUN_TITLE", "PLUNE_ENV", "PLUNE_LABELS", "PLUNE_GROUP"];
220
+ interface EnvSettings {
221
+ /** Config fields the environment supplied. Anything passed explicitly outranks these. */
222
+ config: Partial<ReporterConfig>;
223
+ /** This process must not close the run — several share it, or the job closes it itself. */
224
+ keepOpen: boolean;
225
+ /** Variables that were set and have nowhere to land. The caller says so once. */
226
+ ignored: string[];
227
+ }
228
+ declare function readEnv(env?: NodeJS.ProcessEnv): EnvSettings;
229
+
230
+ /**
231
+ * What ends up in a deferred batch.
232
+ *
233
+ * Two shapes, because two things can fail. A batch refused by the platform is already resolved
234
+ * (`ResultSubmission`), and replay sends it as it is. A batch deferred because the platform was
235
+ * never reached has not been resolved at all (`PendingResult`), and replay has to look its keys
236
+ * up first. Flattening them into one shape would mean inventing a `testCaseId` for the second
237
+ * case, which is exactly what AC-11 forbids.
238
+ */
239
+ type DeferredResult = ResultSubmission | PendingResult;
240
+ /** Beside `last-run.json`, because that is where a Plune user already looks for a run. */
241
+ declare const DEFAULT_FALLBACK_PATH: string;
242
+ /**
243
+ * One batch that could not be sent.
244
+ *
245
+ * A batch rather than a result: the batch is the unit that failed, and it is the unit `plune run
246
+ * report` will re-send unchanged. Splitting it into results here would mean re-grouping them there
247
+ * against a size the platform may have changed in the meantime.
248
+ *
249
+ * `runId: null` is a real state, not a missing value — the platform was unreachable when the run
250
+ * was supposed to be created, so these results have no run yet and replay has to make one.
251
+ */
252
+ interface DeferredBatch {
253
+ runId: string | null;
254
+ externalKey: string | null;
255
+ results: DeferredResult[];
256
+ }
257
+ /**
258
+ * Append a batch to the fallback file, creating its directory if nobody has yet.
259
+ *
260
+ * JSONL and synchronous, both for the same reason: a run is being abandoned when this is called,
261
+ * and the next thing to happen may be the process dying. A line that is already flushed stays
262
+ * readable no matter what happens to the ones after it, which one large JSON document rewritten
263
+ * on every batch would not.
264
+ *
265
+ * Nothing about the connection goes in — not the token, not the headers, not the config. The line
266
+ * holds who the run was and what did not reach it, which is exactly what replaying needs.
267
+ */
268
+ declare function appendBatch(file: string, batch: DeferredBatch): void;
269
+
270
+ /**
271
+ * The only thing in this package that talks to the platform.
272
+ *
273
+ * It classifies rather than throws. Every caller has to decide something different about a
274
+ * failure — a batch goes to the fallback file, a rejected token silences the rest of the run, a
275
+ * closed run is a configuration mistake worth naming — and an exception hierarchy would make each
276
+ * of those a `catch` that re-derives what already happened here.
277
+ *
278
+ * It also does not log. Anything printed about a failure is printed by the session, which knows
279
+ * the context; keeping the token's only home in this file is what makes AC-12 structural rather
280
+ * than a rule someone has to remember.
281
+ */
282
+ type ClientFailureKind =
283
+ /** The token was refused. Retrying sends the same token to the same door. */
284
+ 'auth'
285
+ /** The server understood and said no: a closed run, an impossible lifecycle transition. */
286
+ | 'conflict'
287
+ /** The body was refused. It will be refused identically next time. */
288
+ | 'invalid'
289
+ /** Nobody answered, or answered with their own failure. Worth another try. */
290
+ | 'unavailable'
291
+ /** Everything else — reported, never retried, because we cannot say retrying would help. */
292
+ | 'other';
293
+ type ClientOutcome<T> = {
294
+ ok: true;
295
+ status: number;
296
+ body: T;
297
+ } | {
298
+ ok: false;
299
+ kind: ClientFailureKind;
300
+ status: number | null;
301
+ detail: string;
302
+ };
303
+ interface ClientOptions {
304
+ apiUrl: string;
305
+ token: string;
306
+ fetchImpl?: typeof fetch;
307
+ /** Test seam — the same idea as `fetchImpl`, so a retry test costs milliseconds, not seconds. */
308
+ wait?: (ms: number) => Promise<void>;
309
+ }
310
+ interface PlatformClient {
311
+ post<T>(path: string, body: unknown): Promise<ClientOutcome<T>>;
312
+ }
313
+ declare function createClient(opts: ClientOptions): PlatformClient;
314
+
315
+ export { type AssertionRecord, type Attachment, type ClientFailureKind, type ClientOptions, type ClientOutcome, DEFAULT_FALLBACK_PATH, type DeferredBatch, type DeferredResult, type EnvSettings, type Execution, type ExpectedEntry, type ExternalKeyKind, type KeyRef, type PendingResult, type PlatformClient, RESULT_KEY_MAX, type ReporterConfig, type ResultStatus, type ResultSubmission, type RunKind, type RunMeta, type RunRecord, type RunSession, type RunStats, type SubmitCounts, UNSUPPORTED_VARS, appendBatch, createClient, readEnv, resultKey, startRun };