@gfdelarue/testkit 0.3.7 → 0.4.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.
Files changed (144) hide show
  1. package/dist/cli/command-catalog.d.ts +0 -127
  2. package/dist/cli/command-catalog.js +1 -1
  3. package/dist/cli/command-registry.d.ts +1 -0
  4. package/dist/cli/command-registry.js +1 -1
  5. package/dist/cli/entrypoint.js +1 -1
  6. package/dist/cli/framework/execute.d.ts +1 -0
  7. package/dist/cli/framework/execute.js +6 -6
  8. package/dist/cli/index.js +1 -1
  9. package/dist/cli/version-drift.js +7 -7
  10. package/dist/config/authoring/index.d.ts +0 -5
  11. package/dist/config/loader/index.d.ts +0 -1
  12. package/dist/config/loader/index.js +1 -1
  13. package/dist/evidence/bundles.d.ts +2 -3
  14. package/dist/evidence/bundles.js +1 -1
  15. package/dist/evidence/contracts.d.ts +0 -8
  16. package/dist/evidence/index.d.ts +1 -1
  17. package/dist/evidence/index.js +1 -1
  18. package/dist/process/execa-bundle.json +1 -1
  19. package/package.json +3 -10
  20. package/testkit-config-schema.md +0 -23
  21. package/dist/app/cloud/campaigns.d.ts +0 -6
  22. package/dist/app/cloud/campaigns.js +0 -1
  23. package/dist/app/cloud/control-actions.d.ts +0 -12
  24. package/dist/app/cloud/control-actions.js +0 -2
  25. package/dist/app/cloud/control-links.d.ts +0 -5
  26. package/dist/app/cloud/control-links.js +0 -1
  27. package/dist/app/cloud/control-read.d.ts +0 -24
  28. package/dist/app/cloud/control-read.js +0 -1
  29. package/dist/app/cloud/control-resources.d.ts +0 -3
  30. package/dist/app/cloud/control-resources.js +0 -1
  31. package/dist/app/cloud/control.d.ts +0 -34
  32. package/dist/app/cloud/control.js +0 -5
  33. package/dist/app/cloud/credentials.d.ts +0 -11
  34. package/dist/app/cloud/credentials.js +0 -2
  35. package/dist/app/cloud/index.d.ts +0 -22
  36. package/dist/app/cloud/index.js +0 -2
  37. package/dist/app/cloud/login.d.ts +0 -63
  38. package/dist/app/cloud/login.js +0 -2
  39. package/dist/app/cloud/migration/archive.d.ts +0 -13
  40. package/dist/app/cloud/migration/archive.js +0 -1
  41. package/dist/app/cloud/migration/commands.d.ts +0 -8
  42. package/dist/app/cloud/migration/commands.js +0 -1
  43. package/dist/app/cloud/migration/evidence.d.ts +0 -13
  44. package/dist/app/cloud/migration/evidence.js +0 -2
  45. package/dist/app/cloud/migration/files.d.ts +0 -16
  46. package/dist/app/cloud/migration/files.js +0 -2
  47. package/dist/app/cloud/migration/index.d.ts +0 -6
  48. package/dist/app/cloud/migration/index.js +0 -1
  49. package/dist/app/cloud/migration/journal.d.ts +0 -64
  50. package/dist/app/cloud/migration/journal.js +0 -1
  51. package/dist/app/cloud/migration/mapping-dispositions.d.ts +0 -3
  52. package/dist/app/cloud/migration/mapping-dispositions.js +0 -1
  53. package/dist/app/cloud/migration/mapping.d.ts +0 -11
  54. package/dist/app/cloud/migration/mapping.js +0 -2
  55. package/dist/app/cloud/migration/model.d.ts +0 -175
  56. package/dist/app/cloud/migration/model.js +0 -1
  57. package/dist/app/cloud/migration/plan.d.ts +0 -7
  58. package/dist/app/cloud/migration/plan.js +0 -1
  59. package/dist/app/cloud/migration/progress.d.ts +0 -19
  60. package/dist/app/cloud/migration/progress.js +0 -1
  61. package/dist/app/cloud/migration/reconcile.d.ts +0 -22
  62. package/dist/app/cloud/migration/reconcile.js +0 -1
  63. package/dist/app/cloud/migration/reconciliation-spool.d.ts +0 -4
  64. package/dist/app/cloud/migration/reconciliation-spool.js +0 -1
  65. package/dist/app/cloud/migration/state.d.ts +0 -15
  66. package/dist/app/cloud/migration/state.js +0 -1
  67. package/dist/app/cloud/migration/transfer.d.ts +0 -6
  68. package/dist/app/cloud/migration/transfer.js +0 -1
  69. package/dist/cli/cloud-apply-help.d.ts +0 -1
  70. package/dist/cli/cloud-apply-help.js +0 -56
  71. package/dist/cli/cloud-selection.d.ts +0 -2
  72. package/dist/cli/cloud-selection.js +0 -3
  73. package/dist/cli/commands/cloud/apply.d.ts +0 -5
  74. package/dist/cli/commands/cloud/apply.js +0 -1
  75. package/dist/cli/commands/cloud/campaign/create.d.ts +0 -5
  76. package/dist/cli/commands/cloud/campaign/create.js +0 -1
  77. package/dist/cli/commands/cloud/download.d.ts +0 -5
  78. package/dist/cli/commands/cloud/download.js +0 -1
  79. package/dist/cli/commands/cloud/list.d.ts +0 -5
  80. package/dist/cli/commands/cloud/list.js +0 -1
  81. package/dist/cli/commands/cloud/login.d.ts +0 -13
  82. package/dist/cli/commands/cloud/login.js +0 -2
  83. package/dist/cli/commands/cloud/logout.d.ts +0 -7
  84. package/dist/cli/commands/cloud/logout.js +0 -1
  85. package/dist/cli/commands/cloud/migrate/apply.d.ts +0 -2
  86. package/dist/cli/commands/cloud/migrate/apply.js +0 -1
  87. package/dist/cli/commands/cloud/migrate/plan.d.ts +0 -7
  88. package/dist/cli/commands/cloud/migrate/plan.js +0 -1
  89. package/dist/cli/commands/cloud/migrate/progress.d.ts +0 -2
  90. package/dist/cli/commands/cloud/migrate/progress.js +0 -2
  91. package/dist/cli/commands/cloud/migrate/reconcile.d.ts +0 -2
  92. package/dist/cli/commands/cloud/migrate/reconcile.js +0 -1
  93. package/dist/cli/commands/cloud/policy/preview.d.ts +0 -5
  94. package/dist/cli/commands/cloud/policy/preview.js +0 -1
  95. package/dist/cli/commands/cloud/project/create.d.ts +0 -5
  96. package/dist/cli/commands/cloud/project/create.js +0 -1
  97. package/dist/cli/commands/cloud/publish.d.ts +0 -7
  98. package/dist/cli/commands/cloud/publish.js +0 -1
  99. package/dist/cli/commands/cloud/session/revoke.d.ts +0 -8
  100. package/dist/cli/commands/cloud/session/revoke.js +0 -1
  101. package/dist/cli/commands/cloud/show.d.ts +0 -5
  102. package/dist/cli/commands/cloud/show.js +0 -1
  103. package/dist/cli/commands/cloud/snapshot.d.ts +0 -5
  104. package/dist/cli/commands/cloud/snapshot.js +0 -1
  105. package/dist/cli/commands/cloud/status.d.ts +0 -33
  106. package/dist/cli/commands/cloud/status.js +0 -1
  107. package/dist/cloud/account.d.ts +0 -34
  108. package/dist/cloud/account.js +0 -1
  109. package/dist/cloud/archive-contract.d.ts +0 -1
  110. package/dist/cloud/archive-contract.js +0 -1
  111. package/dist/cloud/archive.d.ts +0 -12
  112. package/dist/cloud/archive.js +0 -1
  113. package/dist/cloud/campaigns.d.ts +0 -200
  114. package/dist/cloud/campaigns.js +0 -2
  115. package/dist/cloud/client.d.ts +0 -68
  116. package/dist/cloud/client.js +0 -4
  117. package/dist/cloud/download.d.ts +0 -5
  118. package/dist/cloud/download.js +0 -1
  119. package/dist/cloud/historical-contract.d.ts +0 -224
  120. package/dist/cloud/historical-contract.js +0 -1
  121. package/dist/cloud/historical-json.d.ts +0 -16
  122. package/dist/cloud/historical-json.js +0 -1
  123. package/dist/cloud/historical.d.ts +0 -69
  124. package/dist/cloud/historical.js +0 -1
  125. package/dist/cloud/pagination.d.ts +0 -5
  126. package/dist/cloud/pagination.js +0 -1
  127. package/dist/cloud/transport.d.ts +0 -18
  128. package/dist/cloud/transport.js +0 -1
  129. package/dist/config/loader/cloud.d.ts +0 -2
  130. package/dist/config/loader/cloud.js +0 -1
  131. package/docs/cloud-control.md +0 -108
  132. package/docs/testkit-cloud-boundary.md +0 -628
  133. package/node_modules/buffer-crc32/LICENSE +0 -19
  134. package/node_modules/buffer-crc32/README.md +0 -55
  135. package/node_modules/buffer-crc32/dist/index.cjs +0 -306
  136. package/node_modules/buffer-crc32/dist/index.d.cts +0 -35
  137. package/node_modules/buffer-crc32/dist/index.d.mts +0 -35
  138. package/node_modules/buffer-crc32/dist/index.mjs +0 -304
  139. package/node_modules/buffer-crc32/index.d.ts +0 -35
  140. package/node_modules/buffer-crc32/package.json +0 -54
  141. package/node_modules/yazl/LICENSE +0 -21
  142. package/node_modules/yazl/README.md +0 -457
  143. package/node_modules/yazl/index.js +0 -826
  144. package/node_modules/yazl/package.json +0 -34
@@ -1,628 +0,0 @@
1
- # Testkit Cloud boundary
2
-
3
- The `testkit cloud` commands are deprecated. Testkit works without Cloud, and
4
- bugs and evidence stay in your repository and in GitHub or Gitea. Testkit Cloud
5
- is becoming an optional analytics dashboard that reads facts from GitHub or
6
- Gitea instead of receiving run bundles. This document describes the commands
7
- and the publication contract that still ship.
8
-
9
- ```text
10
- discover -> prepare -> execute -> seal and verify run bundle -> clean up
11
- |
12
- explicit cloud publish
13
- |
14
- v
15
- Testkit Cloud ingest
16
- ```
17
-
18
- ## Cloud commands
19
-
20
- The local [Cloud command reference](cloud-control.md) lists supported operations
21
- and file fields. `testkit cloud apply --help` includes the account-command format.
22
- Cloud checks identity, permissions, and domain rules on every command.
23
-
24
- ```sh
25
- testkit cloud login
26
- testkit cloud list projects
27
- testkit cloud project create --repository owner/repo
28
- testkit cloud campaign create --project PROJECT_ID --request-key investigation-1
29
- testkit cloud publish --project PROJECT_ID --run RUN_ID
30
- ```
31
-
32
- Login needs no Git repository, organisation, or project. It opens a browser and
33
- waits for explicit approval. A signed-out browser signs in or creates an account,
34
- then returns to the same request. `--no-browser` prints the link for another
35
- machine. `cloud status` identifies the saved account; `cloud logout` revokes it.
36
- Credentials are stored privately outside the repository, separately per origin.
37
- Current membership is checked by Cloud on each account request.
38
-
39
- `TESTKIT_CLOUD_TOKEN`, when explicitly set, always wins, including empty or invalid
40
- values. An automation credential never falls back to a saved account. Keep that
41
- variable for CI; a person can use their approved account without copying tokens.
42
- Ordinary runs remain local and never contact Cloud.
43
-
44
- Use `cloud list --help` and `cloud show --help` for discovery, filters, pagination,
45
- settings, histories and results. `cloud apply --file` accepts account commands,
46
- existing versioned case commands, and campaign reports. Policies and provider
47
- settings use documented file inputs; secrets stay out of command arguments.
48
- `token.create` requires `--secret-output NEW_FILE` and omits its secret from normal
49
- output. `cloud policy preview --file` evaluates a draft without saving it.
50
- `cloud download` verifies the artifact's manifest size and SHA-256.
51
-
52
- Creation commands print their request key. Reuse it to resume after a lost
53
- response; a new campaign key deliberately creates a second campaign. Project
54
- consent resumes the initiating command, or prints recovery details and exits in
55
- CI. Provider decision publishing remains explicit. Uncertain account-command
56
- outcomes require read-back before choosing a new key.
57
-
58
- ## Local runner ownership
59
-
60
- The runner owns discovery, application and database lifecycle, scheduling,
61
- engine execution, cancellation, cleanup, result classification, and bundle
62
- publication. It must produce the same truthful terminal result when Testkit
63
- Cloud and GitHub are unavailable.
64
-
65
- The ingestion boundary is the sealed directory at
66
- `.testkit/results/runs/<run-id>/`. Its `run.json`, `provenance.json`,
67
- `manifest.json`, content-addressed objects, logs, setup records, and engine
68
- artifacts are the input to an external consumer. Consumers must verify the
69
- manifest before trusting the bundle. `latest.json` is only a convenience link
70
- and is not an ingestion authority.
71
-
72
- The runner does not read or mutate regression state, inspect GitHub issues, or
73
- change a test outcome because a tracker request failed.
74
-
75
- `run.json` uses schema 19. Its `git` block has this contract:
76
-
77
- ```ts
78
- git: {
79
- commitSha: string | null;
80
- dirty: boolean;
81
- branch: string | null;
82
- pullRequest: number | null;
83
- pullRequestHeadSha: string | null;
84
- repository: {
85
- host: string;
86
- owner: string;
87
- name: string;
88
- } | null;
89
- }
90
- ```
91
-
92
- Testkit uses the local branch when `HEAD` is attached. For a detached `HEAD`,
93
- it checks `GITHUB_HEAD_REF`, `GITHUB_REF_NAME`, `GITHUB_REF`, and
94
- `CI_COMMIT_REF_NAME` in that order. A `refs/pull/N` GitHub ref supplies
95
- `pullRequest`.
96
-
97
- A provider shows a decision on a pull request only from the pull request's head
98
- commit, and `actions/checkout` checks out a merge commit on `pull_request`
99
- events. `pullRequestHeadSha` therefore names the head commit beside the tested
100
- `commitSha`. Cloud publishes the decision on `pullRequestHeadSha` when it is
101
- set, and on `commitSha` when it is `null`.
102
-
103
- When `GITHUB_REF` is a `refs/pull/N` ref, Testkit reads `pull_request.head.sha`
104
- from the file at `GITHUB_EVENT_PATH`. Testkit records that commit only when
105
- `HEAD` is the same commit, or when `HEAD` is a merge commit that has it as a
106
- parent. The recorded value is a full commit ID of 40 or 64 lowercase hexadecimal
107
- characters. Testkit reads the parents from the commit object, so a depth 1 clone
108
- is enough.
109
-
110
- `pullRequestHeadSha` is `null` in every other case:
111
-
112
- - The job is not a pull request job. A `pull_request_target` job counts here,
113
- because its `GITHUB_REF` is the base branch.
114
- - The event file is missing, is not valid JSON, or has no full commit ID at
115
- `pull_request.head.sha`.
116
- - `HEAD` is neither the head commit nor a merge commit that has it as a parent.
117
- - The directory has no Git repository, or the repository has no `HEAD`.
118
-
119
- Gitea Actions sets the same variables and checks out the head commit, so
120
- `pullRequestHeadSha` equals `commitSha` there.
121
-
122
- Testkit parses `repository` from the `origin` remote. The parser accepts HTTPS,
123
- scp-style SSH such as `git@host:owner/name.git`, and `ssh://` URLs. It removes a
124
- trailing `.git`. Other remote forms produce `null`. The remote URL itself is
125
- never written to `run.json`, because it can carry credentials.
126
-
127
- `provenance.json` uses schema 3 and always includes this `ci` block:
128
-
129
- ```ts
130
- ci: {
131
- provider: "github" | "gitlab" | "other" | null;
132
- runId: string | null;
133
- runUrl: string | null;
134
- actor: string | null;
135
- eventName: string | null;
136
- }
137
- ```
138
-
139
- Testkit reads these values from the `GITHUB_*` and `CI_*` environment variables.
140
- The bundle archive and manifest remain on schema 2. Before sealing, Testkit
141
- removes the local product directory, repository root, worktree path, remote URL,
142
- hostname, and username. Schema-1 bundles remain readable locally but cannot be published
143
- to Cloud.
144
-
145
- Failed Vitest assertions produce one failure detail per test. Each detail uses
146
- kind `vitest-test`, classification `product`, the full test name as its stable
147
- key and title, and the reporter location when present. The location path is
148
- relative to the workspace root. If Vitest omits the line and column, the detail
149
- still includes the test file path. The first failure
150
- message becomes `message`, and later failure messages become `stack`. A file
151
- with at least one `vitest-test` detail does not also include an `engine-failure`
152
- detail.
153
-
154
- The two outputs are local immutable evidence and an explicit Cloud publication
155
- receipt. There is no product-root status snapshot or local history index.
156
- Discovery describes available tests; Cloud computes historical summaries.
157
- The local timings cache remains scheduling input, not a trend store.
158
-
159
- `testkit run` prints the sealed run ID in text output and returns `runId` in
160
- JSON output whether tests passed or failed. Publication is a separate command:
161
-
162
- ```sh
163
- export TESTKIT_CLOUD_PROJECT_ID='project-id'
164
- export TESTKIT_CLOUD_TOKEN='project-token'
165
- testkit cloud publish --run <run-id>
166
- ```
167
-
168
- The command verifies and publishes the named bundle regardless of test status.
169
- Without `--run`, it selects the newest bundle for interactive use. CI must use
170
- the ID returned by its own `run` invocation, since another invocation can
171
- replace the latest projection. The [README CI recipe](../README.md) captures
172
- that ID and preserves the run exit status. `cloud publish` reports publication
173
- failure through its own exit status; it never rewrites the run outcome.
174
-
175
- The token is an environment secret. It is not stored in `testkit.config.ts` or
176
- accepted as a command flag. `CI` is execution context only; it does not enable
177
- publication or supply authority.
178
-
179
- ## Cloud ingestion
180
-
181
- Testkit Cloud ingests a verified bundle and derives regression identities,
182
- baselines, comparisons, recommendations, GitHub checks, and issue lifecycle
183
- actions. Cloud state is separate from the product repository and may be shared
184
- across repositories. Cloud keeps the source bundle identity for every derived
185
- decision.
186
-
187
- Cloud ingestion uses an explicit versioned contract. It rejects an invalid
188
- bundle, preserves the underlying run outcome, and reports GitHub projection
189
- status separately from the receipt. `testkit cloud snapshot` reads current
190
- versioned state. `testkit cloud apply --file <commands.json>` applies an
191
- idempotent command envelope against an expected snapshot version.
192
-
193
- ## Retired local state
194
-
195
- The old `./regression-workflow` package export, runner integration,
196
- `testkit.regressions.json`, and repository-tracked regression state are retired.
197
- Git retains their implementation history, but that does not migrate a
198
- consumer's live state.
199
-
200
- ## Report an externally executed campaign
201
-
202
- Cloud keeps campaign state, finding verification, and counts. A user or agent
203
- starts and runs the investigation independently. These commands never start
204
- execution or infer a finding from a failed assertion.
205
-
206
- Set the existing `TESTKIT_CLOUD_URL`, `TESTKIT_CLOUD_PROJECT_ID`, and
207
- `TESTKIT_CLOUD_TOKEN` environment variables. Create a campaign in Cloud, then
208
- publish each named run explicitly:
209
-
210
- ```sh
211
- testkit cloud publish --run first-run --campaign cmp_example --json
212
- testkit cloud publish --run second-run --campaign cmp_example --json
213
- testkit cloud snapshot --campaign cmp_example --json
214
- ```
215
-
216
- Campaign-scoped credentials require [gfdelarue/testkit-cloud#43](https://github.com/gfdelarue/testkit-cloud/pull/43).
217
- Cloud deployments that predate that change do not support this scope.
218
-
219
- For an isolated investigator, use a Cloud-issued campaign credential in
220
- `TESTKIT_CLOUD_TOKEN`. The same CLI commands apply. The credential can write only
221
- its campaign and its own publications. It cannot create campaigns, mutate project
222
- cases or settings, or import historical records. Reads remain project-wide.
223
- See Cloud's [campaign credential contract](https://github.com/gfdelarue/testkit-cloud/blob/master/docs/campaign-credentials.md)
224
- for administrative provisioning, rotation, expiry and revocation.
225
-
226
- Campaign-owned publications remain non-baseline regardless of the uploaded branch
227
- and do not publish provider checks. This workflow does not exercise baseline case
228
- lifecycle, project settings or browser writes. Publication retry keys are scoped
229
- to the owner. A replacement credential for the same campaign can continue an
230
- upload. To reuse an accepted legacy or other-campaign receipt, link that receipt
231
- explicitly. Publishing the same local run under a different campaign credential
232
- creates that campaign's own publication. Foreign pending or rejected publications
233
- are never returned as resumable uploads.
234
-
235
- Campaign publication requires `--run`. Each successful response contains the
236
- accepted `receipt` and the `association` result. The same immutable bundle reuses
237
- its accepted receipt on retry. A receipt can belong to several campaigns. Linking
238
- does not change run outcomes, baseline eligibility, or immutable bundle contents.
239
-
240
- Use the returned campaign version and receipt IDs to construct `report.json`:
241
-
242
- ```json
243
- {
244
- "schemaVersion": 1,
245
- "projectId": "prj_example",
246
- "campaignId": "cmp_example",
247
- "idempotencyKey": "record-session-leak-1",
248
- "expectedVersion": 3,
249
- "command": {
250
- "type": "finding.record",
251
- "findingId": "finding_session_leak",
252
- "finding": {
253
- "title": "Session crosses projects",
254
- "rootCauseKey": "session-isolation",
255
- "classification": "product_bug",
256
- "expectedBehavior": "Sessions remain scoped to the signed-in project",
257
- "authority": "The application's project-isolation contract",
258
- "observedBehavior": "Switching projects reuses another project's session",
259
- "impact": "Requests can reach the wrong project",
260
- "reproduction": "Sign in, switch projects, then reload",
261
- "evidence": [
262
- { "receiptId": "rcpt_first", "path": "run.json" },
263
- { "receiptId": "rcpt_second", "path": "run.json" }
264
- ]
265
- }
266
- }
267
- }
268
- ```
269
-
270
- Replace the example project, campaign, version, and receipt IDs with accepted
271
- values. Submit the file:
272
-
273
- ```sh
274
- testkit cloud apply --campaign cmp_example --file report.json --json
275
- ```
276
-
277
- Recording creates an unverified finding. To correct it, submit `finding.update`
278
- with the same finding ID and the complete replacement `finding` object. Use a new
279
- idempotency key and the current version. Changing a verified claim requires
280
- `invalidateVerification: true`; this removes its verification and count until
281
- another verification succeeds. Metadata-only edits follow Cloud's contract.
282
-
283
- To request verification, replace the envelope's command with:
284
-
285
- ```json
286
- { "type": "finding.verify", "findingId": "finding_session_leak" }
287
- ```
288
-
289
- Use a new key, such as `verify-session-leak-1`, and the version returned by the
290
- last accepted command. Apply the file, then inspect the accepted state:
291
-
292
- ```sh
293
- testkit cloud snapshot --campaign cmp_example --json
294
- testkit cloud snapshot --campaign cmp_example --finding finding_session_leak --json
295
- testkit cloud snapshot --campaign cmp_example --receipts --limit 20 --json
296
- testkit cloud snapshot --campaign cmp_example --history --limit 20 --json
297
- testkit cloud snapshot --findings --root-cause-key session-isolation --json
298
- ```
299
-
300
- Lists return one bounded page and an optional `nextCursor`. Pass that exact value
301
- with `--cursor` and the same filters to continue. Finding lists support
302
- `--view all|counted|secondary` and exact `--root-cause-key` and `--issue-reference`
303
- filters. Page limits are 1 through 100, or 1 through 20 for history. Finding detail
304
- has no pagination. Separate reads retain their versions and may observe changes
305
- between requests.
306
-
307
- Users and agents decide whether bugs are distinct. Matching root-cause keys,
308
- titles, evidence, or external issue URLs do not automatically merge findings.
309
- Use `finding.relate` with `relationship: { kind: "known", targetId: "…" }` or
310
- `kind: "duplicate"` to report that decision. These findings remain visible but
311
- contribute zero to the campaign count. `kind: "independent"` clears a relationship.
312
- Cloud counts independent verified product bugs from committed state.
313
-
314
- ### Independent local and Cloud verification settings
315
-
316
- Testkit defaults to requiring two distinct confirming runs before submitting a
317
- new verification request. Override this optional local rule in
318
- `testkit.config.ts`:
319
-
320
- ```ts
321
- import { defineConfig } from "@gfdelarue/testkit/config";
322
-
323
- export default defineConfig({
324
- cloud: { verification: { minimumConfirmingRuns: 2 } },
325
- });
326
- ```
327
-
328
- The supported range is 1 through 64. `cloud apply --dir <product-directory>`
329
- selects the config directory; the default is the current directory. Missing
330
- config uses two. Testkit reads config only when a current-version verification
331
- request needs the local rule. It does not discover tests or start services.
332
-
333
- Cloud never receives this config. Admins and owners configure Cloud's own minimum
334
- in its GUI. Testkit counts full run IDs from available cited accepted receipts;
335
- several paths or receipts from one run supply one confirmation. Expired references
336
- remain historical context but do not count toward a new verification.
337
-
338
- An insufficient local count exits nonzero with
339
- `error.code: local_insufficient_confirmations` and
340
- `error.diagnostics: { source: "local", required, eligible }`. If the local rule
341
- passes but Cloud rejects the request, the command also exits nonzero and preserves
342
- Cloud's code, HTTP status, and diagnostics. For example, local minimum 1 does not
343
- override Cloud minimum 2. Neither failure silently creates a successful
344
- unverified write. Explicit unverified record/update reports can omit evidence.
345
-
346
- ### Retry reports and recover partial publication
347
-
348
- Retain each submitted report file. After a lost response, retry the exact file
349
- with the same key and expected version. Cloud returns the original accepted
350
- result even after later activity, settings changes, or evidence expiration.
351
- Testkit checks the campaign version before evaluating current local verification
352
- requirements so accepted retries remain possible.
353
-
354
- A reused key with changed content and a new stale report return conflicts. For a
355
- deliberate new update, read current state, reconsider the command, and use its
356
- current version with a new key. Testkit never silently rebases a report. A
357
- verification request naming a future version fails locally with
358
- `campaign_version_conflict`; it cannot bypass the local rule while another writer
359
- advances the campaign.
360
-
361
- Publication and association are separate writes. If publication succeeds but
362
- association fails, the command exits nonzero and retains the accepted receipt at
363
- `.testkit/cloud/receipts/<run-id>.json`. JSON error diagnostics include
364
- `publication.receipt`, the association status, and the attempted
365
- `association.report` when available. An unknown outcome means the link response
366
- was lost or could not be verified; it does not claim the link failed to commit.
367
-
368
- For an `unknown` outcome, save `error.diagnostics.association.report` to a file
369
- and retry it with `cloud apply --campaign <id> --file <file> --json`. A `rejected`
370
- report requires resolving the rejection. For a version conflict, repeat
371
- `cloud publish --run <run-id> --campaign <id> --json` to reuse the accepted receipt
372
- and read the current campaign version. Reapplying the rejected envelope would
373
- repeat the conflict. If the campaign version could
374
- not be read before the link attempt, read it and submit a `receipt.link` command
375
- with the retained receipt ID. Do not rerun tests to recover a reporting failure.
376
- A later repeat of campaign publication also reuses the receipt and lets Cloud
377
- return a no-op for an existing association.
378
-
379
- Use `state.report` with `state: "finished"` and a nonblank reason to finish a
380
- campaign. An explicit `state: "in_progress"` resumes that same campaign while
381
- preserving history and evidence. Neither silence nor reaching the target changes
382
- its reported state automatically. Each invocation has an explicit campaign
383
- selector, so concurrent campaigns do not share a mutable current-campaign setting.
384
-
385
- Ordinary commands without campaign selectors retain their existing behavior.
386
- Credentials and `CI=true` do not enable implicit publication or reporting.
387
-
388
- ## Compressed publication
389
-
390
- `testkit cloud publish` sends the selected sealed run as one `zip-v1` archive.
391
- The archive contains the original manifest and evidence files. Testkit writes it
392
- to bounded temporary storage, uploads one payload, and asks Cloud to complete the
393
- publication. Progress goes to stderr, including with `--json`. Only Cloud's
394
- verified receipt means the run was accepted.
395
-
396
- The client uses a pinned, bundled ZIP writer, so no system zip command is needed.
397
- Fixed timestamps and manifest file order make retries reproducible. Temporary
398
- archives are removed after success or failure. Cloud verifies original entry
399
- hashes and stored archive integrity before acceptance.
400
-
401
- Ordinary `cloud publish` now also verifies each file's SHA-256 while building the
402
- archive and rejects changed content, including same-size edits after bundle
403
- verification. The shared archive writer rejects symlink sources and checks file
404
- identity when opening each entry. A detected change fails publication before the
405
- archive payload is uploaded.
406
-
407
- Deploy [Cloud PR #29](https://github.com/gfdelarue/testkit-cloud/pull/29) before
408
- releasing this client. Existing accepted receipts remain reusable. A pending
409
- publication created with the older per-file protocol must finish with that older
410
- client. The archive client does not silently fall back to per-file uploads. The
411
- [Cloud archive contract](https://github.com/gfdelarue/testkit-cloud/blob/bc75813874f887e5d2fd771e79ffbe83a458def8/docs/archive-publication.md)
412
- defines the format, limits, storage verification, and retention behavior.
413
-
414
- ## HTTP deadlines and response bounds
415
-
416
- Cloud JSON requests have a 30-second total deadline. Archive uploads have a
417
- ten-minute total deadline, including connection establishment, upload, and
418
- Cloud's verification response. Archive creation happens before that HTTP
419
- deadline starts. These deadlines replace connected-socket inactivity timers:
420
- a request that keeps progressing can still reach its total deadline.
421
-
422
- The client uses fetch and its dispatcher from the same pinned Undici version.
423
- The private dispatcher allows ten minutes for response headers and body data,
424
- so its defaults do not halve the archive response window. Keeping fetch and its
425
- dispatcher on the same version avoids request-header and early-response incompatibilities with older
426
- Node-bundled fetch versions. It does not follow redirects or retry requests
427
- automatically. Explicit retries retain publication idempotency.
428
-
429
- Responses are read incrementally and capped at 4 MiB after decompression.
430
- UTF-8 decoding remains strict before JSON parsing. Every response body is
431
- consumed or canceled, and upload streams close before temporary ZIP removal.
432
- Transport errors retain their diagnostic code and underlying transport details.
433
-
434
- ## Migrate legacy campaign history
435
-
436
- Use `cloud migrate` to transfer a consumer-owned campaign schema 1 registry and
437
- regression schema 3 registry into an existing Cloud campaign. Supported immutable
438
- manifest/run/provenance schema tuples are `1/16/1` and `2/19/3`. Migration preserves
439
- historical observations and original run identities without running tests or
440
- updating the current regression baseline.
441
-
442
- The receiver must support historical import, the 128 KiB source-record limit,
443
- and `GET /api/v1/projects/{projectId}/repository` for authenticated repository
444
- preflight. The repository endpoint and source-record increase are an unreleased
445
- [Cloud companion change](https://github.com/gfdelarue/testkit-cloud/pull/34).
446
- Merge and deploy that receiver change before applying
447
- migrations. The request limit remains 2 MiB. A successful local plan does not
448
- prove that the receiver supports the larger source-record bound.
449
-
450
- For this legacy campaign's two-run verification rule, select a campaign whose
451
- `minimumConfirmingRuns` is 2. Each verified bug needs two distinct original
452
- confirming run IDs with retained evidence. Review the planned threshold before
453
- application; do not lower it to admit incomplete evidence.
454
-
455
- Create a mapping file with explicit repository identity and decisions keyed by
456
- original finding and case IDs:
457
-
458
- ```json
459
- {
460
- "schemaVersion": 1,
461
- "namespace": "consumer-archive-2025",
462
- "repository": { "host": "github.com", "owner": "example", "name": "product" },
463
- "findings": {
464
- "original-bug": {
465
- "observationRule": "second_confirmation",
466
- "verify": true,
467
- "regressionCaseIds": ["original-case"]
468
- },
469
- "original-nonbug": {
470
- "title": "Missing optional diagnostic",
471
- "rootCauseKey": "original-nonbug-diagnostic",
472
- "classification": "testkit_failure",
473
- "observedAt": "2025-01-03T03:04:05Z",
474
- "verify": false
475
- }
476
- },
477
- "cases": {
478
- "original-case": {
479
- "engineId": "vitest",
480
- "profileId": "node",
481
- "classification": "regression"
482
- }
483
- }
484
- }
485
- ```
486
-
487
- For each finding, supply `observedAt` or an explicit `observationRule` of
488
- `second_confirmation` or `last_evidence` when its original timestamp is missing.
489
- The saved record's `observation` records the selected origin, rationale, and
490
- contributing run IDs when a rule uses evidence. Review this rationale alongside
491
- `observedAt`; import time never substitutes for an original observation time.
492
- A finding mapping can also supply a shorter `title`, `rootCauseKey`, explicit
493
- `classification`, `verify`, `relationship`, `regressionCaseIds`, and
494
- `issueReferences`. Display overrides belong in `fields`: `expectedBehavior`,
495
- `authority`, `observedBehavior`, `impact`, `severity`, `reproduction`, and
496
- `limitations`. Original sanitized structured detail and nested variants remain
497
- with their parent finding. A shorter display title or display-field override
498
- does not trim the retained source record. Existing bug IDs and deduplication
499
- keys remain unchanged; supply root keys for observations that lack them.
500
- Supporting-artifact citations outside a bug's `reproducingRunIds` remain in its
501
- source record. They are excluded from that finding's confirming evidence links;
502
- the migration does not relabel them as confirmations.
503
-
504
- Finding classifications are `product_bug`, `testkit_failure`,
505
- `environment_failure`, `stale_assertion`, `unproven`, `intended_behavior`, and
506
- `observation`. A record in the legacy `bugs` array defaults to `product_bug`
507
- when its classification is absent. An explicit original or mapped classification
508
- still takes precedence. This default does not apply to `classifiedNonBugs`.
509
- Non-bug records missing a title or root key need supplied values.
510
- Ambiguous legacy classifications need a per-record decision. Missing decisions
511
- block application; the utility does not infer semantic equivalence.
512
-
513
- Each case mapping supplies `engineId` and `profileId`. It may also supply
514
- `sourceId` for a bounded alias, `failureKey`, `testType`, and `classification`.
515
- Case classifications are `untriaged`, `regression`, `flaky`, `expected`, and
516
- `quarantined`. Map legacy `product_bug` to `regression` explicitly. The original
517
- case ID and lifecycle remain in source detail.
518
-
519
- If an original case ID exceeds 128 UTF-8 bytes, author a stable, unique `sourceId`
520
- in its case mapping. The utility does not silently truncate or generate an alias.
521
- Keep that decision stable across retries; the plan records both the full
522
- `originalId` and bounded `source.id`, and rejects alias collisions. Finding/case
523
- links use original IDs in the mapping and resolve through this alias to the
524
- server's admitted case ID.
525
-
526
- Cloud reuses a compatible existing case for an exact fingerprint. Otherwise,
527
- the fingerprint is
528
- `service::testType::engineId::profileId::path::failure::failureKey`, and its case
529
- ID is `case_` followed by the first 16 lowercase hexadecimal characters of
530
- SHA-256 over the fingerprint's UTF-8 bytes. The original case ID and authored
531
- source alias do not enter that hash. This is the receiver's documented identity
532
- contract, used by planning to detect collisions before transfer.
533
-
534
- Optional top-level `runReceipts` maps original run IDs to existing current
535
- receipt IDs for reuse.
536
-
537
- Set `TESTKIT_CLOUD_TOKEN` through the existing credential environment, then plan:
538
-
539
- ```sh
540
- testkit cloud migrate plan \
541
- --source /archives/product \
542
- --registry tests/results/campaign.json \
543
- --regressions tests/results/regressions.json \
544
- --mapping /private/migration-mapping.json \
545
- --output /private/product-migration \
546
- --url https://cloud.example.com \
547
- --project project-id --campaign campaign-id --json
548
- ```
549
-
550
- Registry and regression paths are relative to `--source`. The mapping path may
551
- be absolute or relative to that source root. Use a new output directory outside
552
- the source tree. Planning validates original hashes, references, source limits,
553
- and mapping decisions, writes derived evidence and a reviewable plan, and makes
554
- only authenticated remote reads. It leaves original bytes unchanged. Review
555
- `ready`, `problems`, and the generated plan before applying; blocked plans exit
556
- with code 1.
557
-
558
- Use the exact returned `planPath` for transfer and recovery:
559
-
560
- ```sh
561
- testkit cloud migrate apply --plan /private/product-migration/plan.json --json
562
- testkit cloud migrate reconcile --plan /private/product-migration/plan.json --json
563
- ```
564
-
565
- After an interrupted transfer, repeat `apply` with the same plan. Its journal
566
- retains exact requests and retry identities. Changed source, mapping, or target
567
- requires a new plan rather than reusing old operations. Application and
568
- reconciliation return `complete`, `reportPath`, and `problems`; an incomplete
569
- result exits with code 1. With `--json`, stdout contains one result document.
570
- Progress goes to stderr, including failures and resumed attempts.
571
-
572
- New plans request one ZIP upload per run. When applying an older plan, the
573
- client validates its schema-1 recovery journal and atomically upgrades the
574
- journal to schema 2 before the next remote operation. The plan's bytes and digest
575
- stay unchanged. The journal records its original schema, and stderr reports the
576
- upgrade. A run with an existing staging operation keeps that exact request,
577
- including an operation whose response was lost. Older per-file runs finish with
578
- per-file uploads; unstaged runs use ZIP. Existing receipts, import IDs, and record
579
- batch results retain their identities. Resume an upgraded journal with a client
580
- that supports schema 2; schema-1-only clients cannot read it.
581
-
582
- The ZIP for the active run stays under the output directory's `archives/`
583
- subdirectory until receipt acceptance is durably checkpointed. The client syncs
584
- the ZIP before journaling its upload, records its size and SHA-256, and checks
585
- both before a retry. Do not delete retained archives during an interrupted
586
- import. If a journaled archive is missing or corrupt and the receiver still
587
- requires its payload, restore the original archive before resuming. The client
588
- can rebuild an orphan only when no saved upload references it.
589
-
590
- Run uploads are sequential. At most one run archive occupies the spool, bounded
591
- by the receiver's advertised archive byte limit. The client removes an accepted
592
- run's archive before packaging the next run and also cleans accepted-run spools
593
- on resume. Derived evidence remains in the output directory.
594
-
595
- If pending evidence expires, `apply` replays the saved staging request. Cleanup
596
- still in progress remains retryable and retains the server diagnostic. Other
597
- staging conflicts, including changed evidence identities, remain rejected and
598
- require a corrected, reviewed plan. Repeat
599
- `apply` after cleanup finishes. The client rechecks remote receipt acceptance
600
- before resending payloads, and does not treat old local PUT results as proof
601
- that the receiver still has the bytes. Expiry of already accepted evidence is a
602
- verification failure; the client does not replace that receipt or create a new
603
- import to hide it.
604
-
605
- Each `apply` attempt performs one full byte/hash reconciliation. After import
606
- completion, it rechecks import identity, outcomes, counts, campaign version, and
607
- evidence availability without downloading the same immutable artifacts again.
608
- Independent evidence reads use two workers; record admission remains sequential.
609
- An explicit `reconcile` always reads and hashes the evidence again. Neither
610
- command reuses a persisted verification flag.
611
-
612
- Stderr progress reports source verification, journal upgrade, packaging,
613
- uploading, receipt acceptance, record admission, and reconciliation. It includes
614
- accepted and remaining runs, payload bytes read for upload during this attempt,
615
- retry counts, bytes verified during reconciliation, and the run or operation
616
- responsible for a failure. Upload bytes include retries and count bytes handed
617
- to the HTTP client; they do not confirm server acceptance. Receipt acceptance is
618
- reported separately. Progress omits credentials and URLs; repeated updates
619
- within a phase are throttled.
620
-
621
- Keep output plans, derived evidence, and recovery journals private. They contain
622
- historical source detail, though credentials are excluded. A completed import
623
- accounts for its declared inventory; it does not certify today's product
624
- behavior or future evidence retention. The utility never deletes source files.
625
- Retain the originals and a recoverable backup after readback succeeds. Synthetic
626
- acceptance covers the supported contract; it does not establish complete
627
- private-corpus migration. Rehearse the actual corpus against an explicitly
628
- selected campaign and review every unresolved or discrepant record.
@@ -1,19 +0,0 @@
1
- The MIT License
2
-
3
- Copyright (c) 2013-2024 Brian J. Brennan
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 in
7
- the Software without restriction, including without limitation the rights to use,
8
- copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
9
- Software, and to permit persons to whom the Software is furnished to do so,
10
- 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 IMPLIED,
16
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
17
- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
18
- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.