@gfdelarue/testkit 0.3.6 → 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 (159) hide show
  1. package/dist/app/run/engine-execution.d.ts +2 -0
  2. package/dist/app/run/engine-execution.js +1 -1
  3. package/dist/cli/command-catalog.d.ts +0 -127
  4. package/dist/cli/command-catalog.js +1 -1
  5. package/dist/cli/command-registry.d.ts +1 -0
  6. package/dist/cli/command-registry.js +1 -1
  7. package/dist/cli/entrypoint.js +1 -1
  8. package/dist/cli/framework/execute.d.ts +1 -0
  9. package/dist/cli/framework/execute.js +6 -6
  10. package/dist/cli/index.js +1 -1
  11. package/dist/cli/version-drift.js +7 -7
  12. package/dist/config/authoring/index.d.ts +0 -5
  13. package/dist/config/loader/index.d.ts +0 -1
  14. package/dist/config/loader/index.js +1 -1
  15. package/dist/engines/adapters/node-startup-cache.d.ts +8 -0
  16. package/dist/engines/adapters/node-startup-cache.js +1 -0
  17. package/dist/engines/api/execution.d.ts +2 -0
  18. package/dist/engines/managed-module-resolution.js +8 -1
  19. package/dist/engines/node-vitest/adapter/adapter.js +1 -1
  20. package/dist/engines/playwright/adapter/adapter.js +1 -1
  21. package/dist/environment/ready-environment.d.ts +3 -1
  22. package/dist/environment/ready-environment.js +1 -1
  23. package/dist/environment/run-environment.js +1 -1
  24. package/dist/environment/runtime-contexts.d.ts +1 -0
  25. package/dist/environment/runtime-contexts.js +1 -1
  26. package/dist/environment/runtime-manager.js +1 -1
  27. package/dist/evidence/bundles.d.ts +2 -3
  28. package/dist/evidence/bundles.js +1 -1
  29. package/dist/evidence/contracts.d.ts +0 -8
  30. package/dist/evidence/index.d.ts +1 -1
  31. package/dist/evidence/index.js +1 -1
  32. package/dist/process/execa-bundle.json +1 -1
  33. package/docs/engine-architecture.md +20 -0
  34. package/package.json +3 -10
  35. package/testkit-config-schema.md +0 -23
  36. package/dist/app/cloud/campaigns.d.ts +0 -6
  37. package/dist/app/cloud/campaigns.js +0 -1
  38. package/dist/app/cloud/control-actions.d.ts +0 -12
  39. package/dist/app/cloud/control-actions.js +0 -2
  40. package/dist/app/cloud/control-links.d.ts +0 -5
  41. package/dist/app/cloud/control-links.js +0 -1
  42. package/dist/app/cloud/control-read.d.ts +0 -24
  43. package/dist/app/cloud/control-read.js +0 -1
  44. package/dist/app/cloud/control-resources.d.ts +0 -3
  45. package/dist/app/cloud/control-resources.js +0 -1
  46. package/dist/app/cloud/control.d.ts +0 -34
  47. package/dist/app/cloud/control.js +0 -5
  48. package/dist/app/cloud/credentials.d.ts +0 -11
  49. package/dist/app/cloud/credentials.js +0 -2
  50. package/dist/app/cloud/index.d.ts +0 -22
  51. package/dist/app/cloud/index.js +0 -2
  52. package/dist/app/cloud/login.d.ts +0 -63
  53. package/dist/app/cloud/login.js +0 -2
  54. package/dist/app/cloud/migration/archive.d.ts +0 -13
  55. package/dist/app/cloud/migration/archive.js +0 -1
  56. package/dist/app/cloud/migration/commands.d.ts +0 -8
  57. package/dist/app/cloud/migration/commands.js +0 -1
  58. package/dist/app/cloud/migration/evidence.d.ts +0 -13
  59. package/dist/app/cloud/migration/evidence.js +0 -2
  60. package/dist/app/cloud/migration/files.d.ts +0 -16
  61. package/dist/app/cloud/migration/files.js +0 -2
  62. package/dist/app/cloud/migration/index.d.ts +0 -6
  63. package/dist/app/cloud/migration/index.js +0 -1
  64. package/dist/app/cloud/migration/journal.d.ts +0 -64
  65. package/dist/app/cloud/migration/journal.js +0 -1
  66. package/dist/app/cloud/migration/mapping-dispositions.d.ts +0 -3
  67. package/dist/app/cloud/migration/mapping-dispositions.js +0 -1
  68. package/dist/app/cloud/migration/mapping.d.ts +0 -11
  69. package/dist/app/cloud/migration/mapping.js +0 -2
  70. package/dist/app/cloud/migration/model.d.ts +0 -175
  71. package/dist/app/cloud/migration/model.js +0 -1
  72. package/dist/app/cloud/migration/plan.d.ts +0 -7
  73. package/dist/app/cloud/migration/plan.js +0 -1
  74. package/dist/app/cloud/migration/progress.d.ts +0 -19
  75. package/dist/app/cloud/migration/progress.js +0 -1
  76. package/dist/app/cloud/migration/reconcile.d.ts +0 -22
  77. package/dist/app/cloud/migration/reconcile.js +0 -1
  78. package/dist/app/cloud/migration/reconciliation-spool.d.ts +0 -4
  79. package/dist/app/cloud/migration/reconciliation-spool.js +0 -1
  80. package/dist/app/cloud/migration/state.d.ts +0 -15
  81. package/dist/app/cloud/migration/state.js +0 -1
  82. package/dist/app/cloud/migration/transfer.d.ts +0 -6
  83. package/dist/app/cloud/migration/transfer.js +0 -1
  84. package/dist/cli/cloud-apply-help.d.ts +0 -1
  85. package/dist/cli/cloud-apply-help.js +0 -56
  86. package/dist/cli/cloud-selection.d.ts +0 -2
  87. package/dist/cli/cloud-selection.js +0 -3
  88. package/dist/cli/commands/cloud/apply.d.ts +0 -5
  89. package/dist/cli/commands/cloud/apply.js +0 -1
  90. package/dist/cli/commands/cloud/campaign/create.d.ts +0 -5
  91. package/dist/cli/commands/cloud/campaign/create.js +0 -1
  92. package/dist/cli/commands/cloud/download.d.ts +0 -5
  93. package/dist/cli/commands/cloud/download.js +0 -1
  94. package/dist/cli/commands/cloud/list.d.ts +0 -5
  95. package/dist/cli/commands/cloud/list.js +0 -1
  96. package/dist/cli/commands/cloud/login.d.ts +0 -13
  97. package/dist/cli/commands/cloud/login.js +0 -2
  98. package/dist/cli/commands/cloud/logout.d.ts +0 -7
  99. package/dist/cli/commands/cloud/logout.js +0 -1
  100. package/dist/cli/commands/cloud/migrate/apply.d.ts +0 -2
  101. package/dist/cli/commands/cloud/migrate/apply.js +0 -1
  102. package/dist/cli/commands/cloud/migrate/plan.d.ts +0 -7
  103. package/dist/cli/commands/cloud/migrate/plan.js +0 -1
  104. package/dist/cli/commands/cloud/migrate/progress.d.ts +0 -2
  105. package/dist/cli/commands/cloud/migrate/progress.js +0 -2
  106. package/dist/cli/commands/cloud/migrate/reconcile.d.ts +0 -2
  107. package/dist/cli/commands/cloud/migrate/reconcile.js +0 -1
  108. package/dist/cli/commands/cloud/policy/preview.d.ts +0 -5
  109. package/dist/cli/commands/cloud/policy/preview.js +0 -1
  110. package/dist/cli/commands/cloud/project/create.d.ts +0 -5
  111. package/dist/cli/commands/cloud/project/create.js +0 -1
  112. package/dist/cli/commands/cloud/publish.d.ts +0 -7
  113. package/dist/cli/commands/cloud/publish.js +0 -1
  114. package/dist/cli/commands/cloud/session/revoke.d.ts +0 -8
  115. package/dist/cli/commands/cloud/session/revoke.js +0 -1
  116. package/dist/cli/commands/cloud/show.d.ts +0 -5
  117. package/dist/cli/commands/cloud/show.js +0 -1
  118. package/dist/cli/commands/cloud/snapshot.d.ts +0 -5
  119. package/dist/cli/commands/cloud/snapshot.js +0 -1
  120. package/dist/cli/commands/cloud/status.d.ts +0 -33
  121. package/dist/cli/commands/cloud/status.js +0 -1
  122. package/dist/cloud/account.d.ts +0 -34
  123. package/dist/cloud/account.js +0 -1
  124. package/dist/cloud/archive-contract.d.ts +0 -1
  125. package/dist/cloud/archive-contract.js +0 -1
  126. package/dist/cloud/archive.d.ts +0 -12
  127. package/dist/cloud/archive.js +0 -1
  128. package/dist/cloud/campaigns.d.ts +0 -200
  129. package/dist/cloud/campaigns.js +0 -2
  130. package/dist/cloud/client.d.ts +0 -68
  131. package/dist/cloud/client.js +0 -4
  132. package/dist/cloud/download.d.ts +0 -5
  133. package/dist/cloud/download.js +0 -1
  134. package/dist/cloud/historical-contract.d.ts +0 -224
  135. package/dist/cloud/historical-contract.js +0 -1
  136. package/dist/cloud/historical-json.d.ts +0 -16
  137. package/dist/cloud/historical-json.js +0 -1
  138. package/dist/cloud/historical.d.ts +0 -69
  139. package/dist/cloud/historical.js +0 -1
  140. package/dist/cloud/pagination.d.ts +0 -5
  141. package/dist/cloud/pagination.js +0 -1
  142. package/dist/cloud/transport.d.ts +0 -18
  143. package/dist/cloud/transport.js +0 -1
  144. package/dist/config/loader/cloud.d.ts +0 -2
  145. package/dist/config/loader/cloud.js +0 -1
  146. package/docs/cloud-control.md +0 -108
  147. package/docs/testkit-cloud-boundary.md +0 -628
  148. package/node_modules/buffer-crc32/LICENSE +0 -19
  149. package/node_modules/buffer-crc32/README.md +0 -55
  150. package/node_modules/buffer-crc32/dist/index.cjs +0 -306
  151. package/node_modules/buffer-crc32/dist/index.d.cts +0 -35
  152. package/node_modules/buffer-crc32/dist/index.d.mts +0 -35
  153. package/node_modules/buffer-crc32/dist/index.mjs +0 -304
  154. package/node_modules/buffer-crc32/index.d.ts +0 -35
  155. package/node_modules/buffer-crc32/package.json +0 -54
  156. package/node_modules/yazl/LICENSE +0 -21
  157. package/node_modules/yazl/README.md +0 -457
  158. package/node_modules/yazl/index.js +0 -826
  159. package/node_modules/yazl/package.json +0 -34
@@ -1 +0,0 @@
1
- import U from"node:path";import{rm as P}from"node:fs/promises";import"../../../cloud/historical-contract.js";import{canonicalHistoricalJson as H,historicalDigest as T}from"../../../cloud/historical-json.js";import{walkCloudPages as F}from"../../../cloud/pagination.js";import{createReconciliationSpool as _}from"./reconciliation-spool.js";const M=n=>n==="accepted"||n==="reused",R=n=>`${n.kind}\0${n.id}`;function t(n,a){if(!n)throw new Error(a)}function v(n,a,s){t(H(n)===H(a),`${s} differs from the reviewed migration plan`)}function x(n,a,s){const l=y=>{const I=/T\d\d:\d\d:\d\d\.(\d+)/.exec(y)?.[1]??"";return BigInt(Date.parse(y))*1000000n+BigInt((I+"000000000").slice(3,9))};t(typeof n=="string"&&typeof a=="string"&&Number.isFinite(Date.parse(n))&&Number.isFinite(Date.parse(a))&&l(n)===l(a),`${s} differs from the original observation time`)}export function normalizedFindingData(n){const a={};for(const s of["title","rootCauseKey","classification","surface","severity","expectedBehavior","authority","observedBehavior","impact","reproduction","limitations"])a[s]=n[s]??"";for(const s of["variants","issueReferences","regressionCaseIds","evidence"])a[s]=n[s]??[];return a}export function expectedFindingData(n,a){return t(n.finding,"Mapped finding data is missing"),{...n.finding,regressionCaseIds:(n.regressionSourceIds??[]).map(s=>{const l=a.caseIds[s];return t(l,`Missing admitted case mapping for ${s}`),l}),evidence:(n.evidence??[]).map(s=>{const l=a.receiptIds[s.runId];return t(l,`Missing admitted receipt mapping for run ${s.runId}`),{receiptId:l,path:s.path}})}}export async function reconcileState(n,a,s,l={}){return L(n,a,s,l)}async function L(n,a,s,l,y){const I=[],q=[];let b=n.campaignVersion,C=n.minimumConfirmingRuns;const h=async(i,r,d,e)=>{try{await e()}catch(o){I.push({kind:i,id:r,field:d,detail:o instanceof Error?o.message:String(o)})}};await h("campaign",n.target.campaignId,"version",async()=>{const i=await s.campaign();b=i.version,C=i.minimumConfirmingRuns,y&&t(b===y.campaignVersion&&C===y.minimumConfirmingRuns,"Campaign changed across import completion; repeat full reconciliation")});const $=[...n.runs.filter(i=>!Object.hasOwn(n.reusedRuns,i.runId)).map(i=>i.request.source),...n.records.map(i=>i.source)],V=new Map($.map(i=>[R(i),i])),w=new Map;let k;await h("import",a.importId??"missing","inventory",async()=>{t(a.importId,"The migration journal has no admitted import ID");const i=a.importId;t(V.size===$.length,"The reviewed inventory contains duplicate source identities");let r;await F(async d=>{const e=await s.page(i,d);t(e.import.namespace===n.namespace&&e.import.inventoryDigest===T($)&&e.import.total===$.length,"Cloud import inventory or namespace differs from the reviewed plan"),y&&t(e.import.completedAt,"Server import is not complete");const o={id:e.import.id,namespace:e.import.namespace,inventoryDigest:e.import.inventoryDigest,total:e.import.total,completedAt:e.import.completedAt??null};return r===void 0?r=o:v(o,r,"Import identity across pages"),k===void 0?k=e.counts:v(e.counts,k,"Import counts across pages"),e},d=>{for(const e of d){const o=R(e);t(!w.has(o),`Duplicate reconciliation outcome: ${e.kind} ${e.id}`),w.set(o,e),q.push(e)}}),t(w.size===V.size,"Cloud reconciliation does not cover the complete declared inventory");for(const[d,e]of w)t(V.get(d)?.digest===e.digest,`Unexpected or changed reconciliation source: ${e.kind} ${e.id}`)});const O={};for(const i of q){const r=O[i.kind]??={};r[i.status]=(r[i.status]??0)+1}await h("import",a.importId??"missing","counts",()=>{t(k!==void 0,"Cloud reconciliation counts are unavailable"),v(O,k,"Counts derived from all returned source outcomes")});for(const i of $){const r=w.get(R(i));(!r||r.digest!==i.digest||!M(r.status))&&I.push({kind:i.kind,id:i.id,field:"status",detail:r?.detail??`Source is ${r?.status??"missing"}; every declared source must be accepted or reused`})}const z=new Map,B=new Set,E=new Set,A=[];let S;try{await h("evidence","downloads","spool",async()=>{y||(S=await _(n,a))});for(const e of n.runs){const o=a.receiptIds[e.runId];A.push(()=>h("run",e.runId,"receipt",async()=>{t(o,"The journal has no admitted receipt mapping for this run");const c=Object.hasOwn(n.reusedRuns,e.runId),p=w.get(R(e.request.source));c?t(n.reusedRuns[e.runId]===o,"Current receipt mapping differs from the reviewed plan"):(t(p&&M(p.status)&&p.cloudId===o,"Run outcome differs from its admitted receipt mapping"),t(p.evidenceAvailable===!0,"Historical evidence is no longer available"));const f=c?await s.currentReceipt(o):await s.receipt(o);t(f.id===o&&f.projectId===n.target.projectId&&f.runId===e.runId&&f.manifestDigest===(e.reusedOriginalEvidence?e.request.sourceRecord.originalManifestDigest:e.request.manifest.digest),"Receipt run, project, or manifest identity differs from the reviewed run"),t(f.repository.toLowerCase()===`${n.repository.owner}/${n.repository.name}`.toLowerCase(),"Receipt repository differs from the reviewed repository");const m=z.get(o);if(t(m===void 0||m===e.runId,"One receipt was assigned to different original run identities"),z.set(o,e.runId),!c){t("historical"in f,"Expected a historical receipt");const g=f.historical;t(g.sourceDigest===e.request.source.digest&&g.manifestVersion===e.request.manifest.schemaVersion,"Receipt source or schema differs from the reviewed run"),v(g.sourceRecord,e.request.sourceRecord,"Receipt retained source record"),v(g.artifacts.map(u=>({path:u.path,originalPath:u.originalPath,originalSha256:u.originalSha256,originalBytes:u.originalBytes,...u.transformation===void 0?{}:{transformation:u.transformation}})),e.request.artifacts,"Receipt original artifact mappings")}}));for(const c of e.files){const p=A.length;A.push(()=>h("run",e.runId,`artifact:${c.path}`,async()=>{t(o,"Cannot verify artifacts without an admitted receipt");const f=e.request.artifacts.find(u=>u.path===c.path);t(f,"Original artifact mapping is missing");const m=e.reusedOriginalEvidence?{bytes:f.originalBytes,sha256:f.originalSha256}:{bytes:c.bytes,sha256:c.sha256},g=H({receiptId:o,path:c.path,...m});if(y)t(y.artifacts.has(g),"Artifact was not verified before import completion"),Object.hasOwn(n.reusedRuns,e.runId)&&await s.checkArtifactAvailable(o,c.path);else{t(S,"Cannot verify artifacts without a local download directory");const u=U.join(S,`artifact-${String(p)}`);try{await s.download(s.artifactURL(o,c.path),u,m),l.onVerified?.(m.bytes)}finally{await P(u,{force:!0})}}E.add(g),B.add(`${o}\0${c.path}`)}))}}let i=0;const r=async()=>{for(;i<A.length;)await A[i++]()},d=await Promise.allSettled([r(),r()]);for(const e of d)if(e.status==="rejected")throw e.reason}finally{S&&await P(S,{recursive:!0,force:!0})}const N=new Map(n.records.filter(i=>i.source.kind==="case").map(i=>[a.caseIds[i.source.id],i])),K=new Map;N.size&&await h("case","all","list",async()=>{const i=new Set;await F(r=>s.cases(r),r=>{for(const d of r)t(!i.has(d.id),`Duplicate case across Cloud pages: ${d.id}`),i.add(d.id),N.has(d.id)&&K.set(d.id,d)})});for(const i of n.records.filter(r=>r.source.kind==="case"))await h("case",i.source.id,"history",async()=>{const r=a.caseIds[i.source.id],d=w.get(R(i.source));t(r&&d&&d.cloudId===r&&M(d.status),"Case outcome differs from its journal identity mapping");const e=K.get(r);t(e,"Admitted case is absent from the complete Cloud case list"),t(i.case,"Mapped historical case is missing");const o=i.case;t(e.fingerprint===i.case.fingerprint,"Cloud case fingerprint differs from the reviewed fingerprint");let c=0;const p=new Set;await F(f=>s.caseHistory(r,f),f=>{for(const m of f){const g=`${m.id}\0${m.commandType}`;t(!p.has(g),`Duplicate case history event across pages: ${m.id}`),p.add(g);const u=m.historical;m.commandType!=="case.import"||!u||u.sourceId!==i.source.id||T(u.sourceRecord)!==i.source.digest||(d.status!=="reused"&&t(u.importId===a.importId,"Case observation belongs to another import"),v(u.sourceRecord,i.sourceRecord,"Retained case source record"),x(u.observedAt,i.observedAt,"Case observedAt"),compareHistoricalCase(u.case,o),t(m.state===u.case.state&&m.classification===u.case.classification&&m.issueNumber===(u.case.issueNumber??0)&&m.issueRepository===(u.case.issueRepository??""),"Case history event contradicts its original observation"),x(m.appliedAt,u.importedAt,"Case admission time"),c++)}}),t(c===1,`Expected one matching historical case observation; found ${String(c)}`)});const D=new Map;for(const i of n.records.filter(r=>r.source.kind==="finding"))await h("finding",i.source.id,"detail",async()=>{const r=w.get(R(i.source));t(r?.cloudId&&M(r.status),"Finding has no accepted Cloud identity");const d=await s.finding(r.cloudId);t(d.campaignVersion===b,"Campaign changed while reading finding details; reconciliation is inconclusive");const e=d.finding;t(e.id===i.source.id,"Admitted finding ID differs from the declared source identity");const o=expectedFindingData(i,a);v(normalizedFindingData(e.data),normalizedFindingData(o),"Mapped finding data"),v(e.relationship,i.relationship??{kind:"independent"},"Finding relationship");const c=e.import;t(c,"Historical finding source metadata is missing"),t(c.namespace===n.namespace&&c.sourceId===i.source.id&&c.digest===i.source.digest,"Finding import source identity differs from the reviewed plan"),r.status!=="reused"&&t(c.importId===a.importId,"Finding metadata belongs to another import"),v(c.record,i.sourceRecord,"Retained finding source record"),t(T(c.record)===i.source.digest,"Retained finding source digest does not match"),x(c.observedAt,i.observedAt,"Finding observedAt");for(const p of d.evidence)t(p.available&&B.has(`${p.receiptId}\0${p.path}`),`Finding evidence unavailable or not verified: ${p.receiptId}/${p.path}`);if(i.verify){const p=e.verification;t(p,"Requested finding verification is missing"),t(p.findingRevision===e.revision,"Finding verification does not cover its current revision"),t(p.minimumConfirmingRuns===C,"Verification threshold differs from the campaign's current threshold");const f=new Map;for(const m of d.evidence.filter(g=>g.available)){const g=z.get(m.receiptId);t(g,`Verification receipt has no reconciled original run: ${m.receiptId}`);const u=f.get(g);(u===void 0||m.receiptId<u)&&f.set(g,m.receiptId)}t(f.size>=C,`Verification requires ${String(C)} distinct original runs; ${String(f.size)} eligible`),v([...p.confirmingReceiptIds].sort(),[...f.values()].sort(),"Distinct confirming receipt identities")}else t(e.verification===null,"An explicitly unverified historical finding unexpectedly has verification");D.set(e.id,{counted:e.data.classification==="product_bug"&&e.verification!==null&&e.relationship.kind==="independent",verified:e.verification!==null})});return D.size&&await h("campaign",n.target.campaignId,"finding counts",async()=>{const i=new Set;let r=0,d;await F(async e=>{const o=await s.snapshot({campaignId:n.target.campaignId,findings:!0,view:"all",...e?{cursor:e}:{}});return t(o.campaignVersion===b,"Campaign changed during paginated finding reconciliation"),d===void 0?d=o.bugCount:t(o.bugCount===d,"Campaign bug count changed across pages"),o},e=>{for(const o of e){const c=o.id;t(!i.has(c),`Duplicate campaign finding across pages: ${c}`),i.add(c);const p=o.classification==="product_bug"&&o.verified&&o.relationship.kind==="independent";t(o.counted===p,`Finding count does not follow classification, verification and relationship: ${c}`),p&&r++;const f=D.get(c);f&&t(o.counted===f.counted&&o.verified===f.verified,`Finding summary contradicts admitted detail: ${c}`)}}),t(r===d,"Campaign bug count differs from counted findings across all pages");for(const e of D.keys())t(i.has(e),`Admitted finding is absent from the complete campaign feed: ${e}`)}),await h("campaign",n.target.campaignId,"version",async()=>{const i=await s.campaign();t(i.version===b&&i.minimumConfirmingRuns===C,"Campaign changed during reconciliation; repeat reads before declaring completion")}),{complete:I.length===0,problems:I,items:q,counts:O,campaignVersion:b,verifyCompletion:()=>(t(I.length===0,"Full reconciliation must succeed before checking import completion"),L(n,a,s,l,{artifacts:E,campaignVersion:b,minimumConfirmingRuns:C}))}}export function compareHistoricalCase(n,a){for(const s of["id","classification","engineId","failureKey","fingerprint","path","profileId","service","state","testType","title"])v(n[s],a[s],`Historical case ${s}`);for(const s of["firstSeenAt","lastSeenAt"])x(n[s],a[s],`Historical case ${s}`);t((n.version??0)===(a.version??0)&&(n.issueNumber??0)===(a.issueNumber??0)&&(n.issueRepository??"")===(a.issueRepository??""),"Historical case version or issue linkage differs from its original mapping")}
@@ -1,4 +0,0 @@
1
- import type { MigrationPlan } from "./model.js";
2
- import type { MigrationJournal } from "./state.js";
3
- /** Called while execute holds the migration output lock. Unknown files are never reclaimed. */
4
- export declare function createReconciliationSpool(plan: Pick<MigrationPlan, "outputDir">, journal: Pick<MigrationJournal, "planDigest" | "importId">): Promise<string>;
@@ -1 +0,0 @@
1
- import r from"node:path";import{lstat as l,mkdtemp as p,readdir as m,rm as f,writeFile as w}from"node:fs/promises";import{canonicalHistoricalJson as d}from"../../../cloud/historical-json.js";import{readBoundedFile as y}from"./files.js";const e=".testkit-reconciliation",h=/^artifact-\d+(?:\.[a-f0-9-]{36}\.partial)?$/u;export async function createReconciliationSpool(n,a){const s=d({schemaVersion:1,planDigest:a.planDigest,importId:a.importId??null});for(const t of await m(n.outputDir,{withFileTypes:!0})){if(!/^reconcile-[A-Za-z0-9]{6}$/u.test(t.name)||!t.isDirectory()||t.isSymbolicLink())continue;const c=r.join(n.outputDir,t.name),u=await m(c,{withFileTypes:!0});if(!u.some(i=>!i.isFile()||i.name!==e&&!h.test(i.name))&&u.some(i=>i.name===e)){try{if(y(r.join(c,e),1024).toString("utf8")!==s)continue}catch{continue}await f(c,{recursive:!0,force:!0})}}const o=await p(r.join(n.outputDir,"reconcile-"));try{const t=await l(o);if(!t.isDirectory()||t.isSymbolicLink())throw new Error("Reconciliation directory is unsafe");return await w(r.join(o,e),s,{flag:"wx",mode:384}),o}catch(t){throw await f(o,{recursive:!0,force:!0}),t}}
@@ -1,15 +0,0 @@
1
- import { type OperationKind, type OperationRequests, type OperationResults, type MigrationJournal } from "./journal.js";
2
- export type { MigrationJournal, MigrationOperation } from "./journal.js";
3
- import type { MigrationPlan } from "./model.js";
4
- export declare function loadPlan(filename: string): {
5
- plan: MigrationPlan;
6
- planDigest: string;
7
- };
8
- export declare function verifyPlanFiles(plan: MigrationPlan): void;
9
- export declare function openJournal(plan: MigrationPlan, planDigest: string): MigrationJournal;
10
- export declare function saveJournal(plan: MigrationPlan, journal: MigrationJournal): void;
11
- /** Atomically upgrade a journal already decoded by openJournal, retaining every staged request. */
12
- export declare function upgradeJournal(plan: MigrationPlan, journal: MigrationJournal): void;
13
- /** Re-staging must establish availability before an obsolete evidence rejection can be retried. */
14
- export declare function recoverEvidenceOperation(plan: MigrationPlan, journal: MigrationJournal, key: string): void;
15
- export declare function performOperation<K extends OperationKind>(plan: MigrationPlan, journal: MigrationJournal, kind: K, key: string, input: OperationRequests[K], run: (saved: OperationRequests[K]) => Promise<OperationResults[K]>, env: NodeJS.ProcessEnv, refreshAccepted?: boolean): Promise<OperationResults[K]>;
@@ -1 +0,0 @@
1
- import w from"node:fs";import g from"node:path";import{canonicalHistoricalJson as y,historicalDigest as I}from"../../../cloud/historical-json.js";import{validateHistoricalBatch as V,validateHistoricalBegin as R,validateHistoricalEvidence as F}from"../../../cloud/historical-contract.js";import{decodeJournal as q,emptyJournal as M,operationIs as k}from"./journal.js";import{array as v,contained as J,digest as b,object as u,plain as N,readDocument as D,readSource as S,sanitize as O,separateOutput as P,text as f,writeDocument as C}from"./files.js";const j="campaign command conflict: abandoned evidence cleanup is pending; retry after retention completes";function x(t,e){if(t==="receipt"&&e==="evidence is expired")return 422;if((t==="upload"||t==="archive")&&(e==="conflict: publication is pending"||e==="conflict: publication does not accept an archive"))return 409}export function loadPlan(t){const e=u(D(t),"migration plan"),{planDigest:i,...r}=e;if(e.schemaVersion!==1||e.converterVersion!=="testkit-legacy-v1"||I(r)!==i)throw new Error("Migration plan version/digest is invalid");for(const n of["sourceDir","outputDir"])f(e[n],n);if(f(e.namespace,"namespace",128),g.resolve(t)!==g.join(String(e.outputDir),"plan.json"))throw new Error("Migration plan must remain in its bound output directory");P(String(e.sourceDir),String(e.outputDir));const a=u(e.target,"target");for(const n of["origin","projectId","campaignId"])f(a[n],n);for(const n of["sourceFiles","runs","records","problems"])v(e[n],n);if(typeof e.ready!="boolean")throw new Error("Migration readiness must be a boolean");if(!Number.isSafeInteger(e.campaignVersion)||Number(e.campaignVersion)<1||!Number.isSafeInteger(e.minimumConfirmingRuns)||Number(e.minimumConfirmingRuns)<1||Number(e.minimumConfirmingRuns)>64)throw new Error("Invalid migration campaign version or verification threshold");const m=v(e.runs,"runs"),p=v(e.records,"records");if(m.length+p.length>1e4)throw new Error("Migration inventory exceeds bound");const h=u(e.reusedRuns,"reusedRuns");for(const[n,o]of Object.entries(h))if(!m.some(c=>u(c,"run").runId===n)||!/^[A-Za-z0-9_-]{1,128}$/u.test(f(o,"reused receipt",128)))throw new Error("Invalid reused run receipt identity");for(const n of v(e.runs,"runs")){const o=u(n,"run"),c=u(o.request,"evidence request");F(c);const d=u(N(c.sourceRecord),"run sourceRecord");if(y(d)!==y(c.sourceRecord))throw new Error("Invalid run source numeric encoding");if(c.sourceRecord=d,d.runId!==o.runId||d.observedAt!==o.observedAt||d.converterVersion!=="testkit-legacy-v1")throw new Error("Invalid run source identity");for(const E of["originalManifestSha256","originalManifestDigest"])if(!/^[a-f0-9]{64}$/.test(f(d[E],E,64)))throw new Error("Invalid original manifest digest");const l=u(d.schemaTuple,"schemaTuple"),$=u(c.manifest,"manifest");if(l.manifest!==$.schemaVersion||l.run!==(l.manifest===1?16:19)||l.provenance!==(l.manifest===1?1:3))throw new Error("Invalid original schema tuple")}const s=[...m.filter(n=>!Object.hasOwn(h,f(u(n,"run").runId,"runId"))).map(n=>u(u(n,"run").request,"evidence request").source),...p.map(n=>u(n,"record").source)];(e.ready||s.length)&&R({schemaVersion:1,idempotencyKey:`migration-${i}`,namespace:e.namespace,inventory:s,inventoryDigest:I(s)});for(let n=0;n<p.length;n+=25)V({schemaVersion:1,idempotencyKey:"plan-records",expectedVersion:e.campaignVersion,records:p.slice(n,n+25)});return{plan:r,planDigest:f(i,"planDigest",64)}}export function verifyPlanFiles(t){for(const r of t.sourceFiles){const a=S(t.sourceDir,f(r.path,"source path"),[],r.bytes);if(a.length!==r.bytes||b(a)!==r.sha256)throw new Error(`Source changed since planning: ${r.path}`)}const e=f(t.mappingFile.path,"mapping file"),i=w.lstatSync(e);if(!i.isFile()||i.isSymbolicLink()||i.size!==t.mappingFile.bytes||b(w.readFileSync(e))!==t.mappingFile.sha256)throw new Error("Mapping changed since planning");for(const r of t.runs)for(const a of r.files){if(J(t.outputDir,`evidence/${r.runId}/${a.path}`)!==a.absolutePath)throw new Error("Derived evidence path differs from its plan");const p=S(t.outputDir,`evidence/${r.runId}/${a.path}`,[],a.bytes);if(p.length!==a.bytes||b(p)!==a.sha256)throw new Error(`Derived evidence changed: ${r.runId}/${a.path}`)}}export function openJournal(t,e){const i=g.join(t.outputDir,"journal.json");return w.existsSync(i)?q(D(i),t,e):M(t,e)}export function saveJournal(t,e){C(g.join(t.outputDir,"journal.json"),e)}export function upgradeJournal(t,e){if(e.schemaVersion===2)return;const i=w.existsSync(g.join(t.outputDir,"journal.json"));e.schemaVersion=2,i&&(e.upgradedFrom=1),e.legacyStages=e.operations.filter(r=>r.kind==="stage").map(r=>r.key.slice(6));for(const r of e.operations)r.kind==="stage"&&r.state==="rejected"&&r.error===j&&(r.state="unknown");saveJournal(t,e)}export function recoverEvidenceOperation(t,e,i){const r=e.operations.find(a=>a.key===i);r?.state==="rejected"&&x(r.kind,r.error)!==void 0&&(r.state="unknown",saveJournal(t,e))}export async function performOperation(t,e,i,r,a,m,p,h=!1){let s=e.operations.find(o=>k(o,i)&&o.key===r);const n=y(a);if(s&&s.request!==n)throw new Error(`Conflicting retry payload: ${r}`);if(s?.state==="accepted"&&!h){if(s.result===void 0)throw new Error("Accepted operation has no decoded result");return s.result}if(s?.state==="rejected")throw new Error(`Prior operation was definitively rejected: ${r}; use a reviewed new plan for corrections`);s||(s={kind:i,key:r,request:n,state:"pending"},e.operations.push(s)),saveJournal(t,e);try{const o=await m(a);if(s.result=o,s.state="rejected"in o&&o.rejected===!0?"rejected":"accepted",delete s.error,saveJournal(t,e),s.state==="rejected")throw new Error(`Cloud rejected the atomic batch: ${r}; durable per-record diagnostics are in the journal`);return o}catch(o){if(s.state!=="rejected"){const c=o&&typeof o=="object"&&"status"in o?Number(o.status):void 0,d=o instanceof Error?o.message:void 0;s.state=c&&c>=400&&c<500&&![401,403,408,429,...i==="begin"?[404]:[]].includes(c)&&!(i==="stage"&&c===409&&d===j)&&c!==x(i,d)?"rejected":"unknown"}throw s.error=String(O(o instanceof Error?o.message:String(o),p)),saveJournal(t,e),o}}
@@ -1,6 +0,0 @@
1
- import { HistoricalClient } from "../../../cloud/historical.js";
2
- import type { MigrationPlan } from "./model.js";
3
- export { batchRecords, dependencyOrder } from "./commands.js";
4
- import { type MigrationJournal } from "./state.js";
5
- import type { MigrationProgressReporter } from "./progress.js";
6
- export declare function transfer(plan: MigrationPlan, journal: MigrationJournal, client: HistoricalClient, env: NodeJS.ProcessEnv, progress: MigrationProgressReporter): Promise<void>;
@@ -1 +0,0 @@
1
- import"../../../cloud/historical.js";import{historicalDigest as b}from"../../../cloud/historical-json.js";import{batchRecords as k,beginCommand as q,plannedRecords as D}from"./commands.js";export{batchRecords,dependencyOrder}from"./commands.js";import{performOperation as m,recoverEvidenceOperation as z,saveJournal as l}from"./state.js";import{savedArchiveRequest as A,stagingRequest as R}from"./journal.js";import{prepareMigrationArchive as O,removeMigrationArchive as C}from"./archive.js";export async function transfer(r,i,u,p,I){let y=r.runs.filter(e=>i.receiptIds[e.runId]).length;const h=(e,o={})=>{I.emit(e,{completedRuns:y,totalRuns:r.runs.length,...o})},g=(e,o,t,f=!1)=>{const a=i.operations.some(s=>s.key===o&&(f||s.state!=="accepted"));h(e,{runId:t,operation:o,retries:I.current.retries+(a?1:0)})},w=(e,o,t=!1)=>{z(r,i,e),g("uploading",e,o,t)},$=(e,o,t)=>{h("uploading",{runId:o,operation:t,bytesTransferred:I.current.bytesTransferred+e})};for(const e of r.runs)i.receiptIds[e.runId]&&await C(r,e.runId);if(!i.operations.some(e=>e.kind==="batch")){const e=await u.campaign();if(e.version!==r.campaignVersion||e.minimumConfirmingRuns!==r.minimumConfirmingRuns)throw new Error("Campaign version or verification threshold changed since planning; create a new reviewed plan")}g("uploading","begin");const v=await m(r,i,"begin","begin",q(r,i.planDigest),e=>u.begin(e),p);i.importId=v.id,l(r,i);for(const e of r.runs){if(i.receiptIds[e.runId])continue;const o=`stage:${e.runId}`;g("uploading",o,e.runId,!0);const t=await m(r,i,"stage",o,R(r,i,e.runId),s=>u.stage(v.id,s),p,!0);if(t.status==="rejected")throw new Error(`Cloud rejected evidence for ${e.runId}; inspect the publication before retrying`);if(t.status==="accepted"&&(t.archive||t.uploads.length))throw new Error("Accepted historical evidence unexpectedly includes upload targets");if(t.status==="pending"&&(R(r,i,e.runId).evidenceFormat==="zip-v1"?!t.archive||t.uploads.length!==0:t.archive||t.uploads.length!==e.files.length))throw new Error(`Cloud staging transport differs from the saved request for ${e.runId}`);if(t.archive){const s=t.archive,n=`archive:${e.runId}:${b(t.archive.url)}`,d=A(i,n);h("packaging",{runId:e.runId});const c=await O(r,e,t.archive.maxBytes,d);w(n,e.runId,!0),await m(r,i,"archive",n,{url:t.archive.url,bytes:c.bytes,sha256:c.sha256,manifestDigest:e.request.manifest.digest},async()=>(await u.upload(s.url,c.path,c.bytes,"application/zip",x=>{$(x,e.runId,n)}),{uploaded:!0}),p,!0)}for(const s of t.uploads){const n=e.files.find(c=>c.path===s.path);if(!n||n.bytes!==s.bytes||n.sha256!==s.sha256)throw new Error(`Cloud requested an unplanned artifact: ${e.runId}/${s.path}`);const d=`upload:${e.runId}:${String(s.index)}:${b(s.url)}`;w(d,e.runId,!0),await m(r,i,"upload",d,{url:s.url,path:n.absolutePath,bytes:n.bytes,sha256:n.sha256},async()=>(await u.upload(s.url,n.absolutePath,n.bytes,"application/octet-stream",c=>{$(c,e.runId,d)}),{uploaded:!0}),p,!0)}const f=`receipt:${e.runId}:${b(t.completeUrl)}`;w(f,e.runId);const a=await m(r,i,"receipt",f,{url:t.completeUrl},()=>u.completeEvidence(t.completeUrl),p);if(a.runId!==e.runId||a.manifestDigest!==e.request.manifest.digest)throw new Error(`Receipt differs from planned run: ${e.runId}`);i.receiptIds[e.runId]=a.id,l(r,i),y++,h("receipt-accepted",{runId:e.runId}),await C(r,e.runId)}let E=r.campaignVersion;for(const e of["case","finding"]){const o=D(r,i,e);for(const[t,f]of k(o).entries()){const a=`${e}s-${String(t)}`,s={schemaVersion:1,idempotencyKey:`migration-${i.planDigest}-${e}-${String(t)}`,expectedVersion:E,records:f};g("record-admission",a);const n=await m(r,i,"batch",a,s,d=>u.batch(v.id,d),p);if(E=n.version,e==="case"){for(const d of n.items){if(!d.cloudId||!["accepted","reused"].includes(d.status))throw new Error(`Case was not admitted: ${d.id}`);i.caseIds[d.id]=d.cloudId}l(r,i)}}}}
@@ -1 +0,0 @@
1
- export declare const cloudApplyHelp = "Account command file:\n Required envelope: schemaVersion: 1, idempotencyKey, operation, input: {}.\n Add the scope IDs listed below. Omit unrelated scope IDs.\n Keys use 1-128 characters without surrounding whitespace. Files are limited to 256 KiB.\n Required input fields are listed below; optional fields are marked optional.\n\nOperations, scope, and input:\n account.theme no scope: theme (system|light|dark)\n organisation.update organisationId: name\n invitation.create organisationId: email, role (owner|admin|member|viewer)\n invitation.revoke organisationId, resourceId: {}\n provider.create organisationId: provider (gitea), host (HTTPS origin),\n displayName, oauthClientId, oauthClientSecret;\n optional botToken, caBundlePEM\n provider.delete organisationId, resourceId: {}\n project.create organisationId: name, provider (github|gitea), host,\n repository (owner/repo); optional publishDecisions (false)\n project.publishing projectId: enabled (boolean)\n project.baseline projectId: branch (empty string resets provider default)\n project.retention projectId: days (1-3650)\n project.policy projectId: policyId (string or null to inherit)\n policy.create organisationId: name; optional rules or sourcePolicyId\n policy.revise organisationId, resourceId: complete rules array\n policy.default organisationId, resourceId: {}\n notification.create projectId: kind (slack|email), displayName, target, events\n notification.update projectId, resourceId: enabled (boolean), events\n notification.delete projectId, resourceId: {}\n notification.test projectId, resourceId: {}; sends a test notification\n token.create projectId: name; requires --secret-output NEW_FILE\n token.revoke projectId, resourceId: {}\n\nNotification events: baseline_hold, case_opened, case_resolved; at least one.\nPolicy rules: id, kind, mode, branches, typed params. Read a policy before revising.\nFind scope and resource IDs with cloud list and cloud show.\nUse cloud session revoke ID to revoke a CLI login; token.revoke revokes a project token.\n\nExample command.json:\n {\"schemaVersion\":1,\"idempotencyKey\":\"retention-30\",\"operation\":\"project.retention\",\n \"projectId\":\"prj_example\",\"input\":{\"days\":30}}\n\nKeep secrets in private files. Token output never goes to stdout.\nRetry the identical command/key after a lost response; changing the command conflicts.\nIf a token was issued but saving failed, retry the same key with a new --secret-output.\nFor outcome_uncertain, inspect cloud list/show and audit history before choosing a new key.\n\nOther file formats:\n Versioned case commands: schemaVersion: 1, projectId, idempotencyKey,\n expectedVersion, commands. Read current versions with cloud snapshot.\n Each case command has caseId and type: case.set_state (state: active|resolved),\n case.classify (classification: untriaged|regression|flaky|expected|quarantined),\n or case.link_issue (issueRepository, positive issueNumber).\n Campaign reports with --campaign ID: schemaVersion: 1, projectId, campaignId,\n idempotencyKey, expectedVersion, command. Command types: state.report, receipt.link,\n finding.record, finding.update, finding.verify, finding.relate, verification.settings.update.\n Campaign verification can load product configuration from --dir.\n See the installed docs/testkit-cloud-boundary.md for complete case and campaign formats.";
@@ -1,56 +0,0 @@
1
- export const cloudApplyHelp=`Account command file:
2
- Required envelope: schemaVersion: 1, idempotencyKey, operation, input: {}.
3
- Add the scope IDs listed below. Omit unrelated scope IDs.
4
- Keys use 1-128 characters without surrounding whitespace. Files are limited to 256 KiB.
5
- Required input fields are listed below; optional fields are marked optional.
6
-
7
- Operations, scope, and input:
8
- account.theme no scope: theme (system|light|dark)
9
- organisation.update organisationId: name
10
- invitation.create organisationId: email, role (owner|admin|member|viewer)
11
- invitation.revoke organisationId, resourceId: {}
12
- provider.create organisationId: provider (gitea), host (HTTPS origin),
13
- displayName, oauthClientId, oauthClientSecret;
14
- optional botToken, caBundlePEM
15
- provider.delete organisationId, resourceId: {}
16
- project.create organisationId: name, provider (github|gitea), host,
17
- repository (owner/repo); optional publishDecisions (false)
18
- project.publishing projectId: enabled (boolean)
19
- project.baseline projectId: branch (empty string resets provider default)
20
- project.retention projectId: days (1-3650)
21
- project.policy projectId: policyId (string or null to inherit)
22
- policy.create organisationId: name; optional rules or sourcePolicyId
23
- policy.revise organisationId, resourceId: complete rules array
24
- policy.default organisationId, resourceId: {}
25
- notification.create projectId: kind (slack|email), displayName, target, events
26
- notification.update projectId, resourceId: enabled (boolean), events
27
- notification.delete projectId, resourceId: {}
28
- notification.test projectId, resourceId: {}; sends a test notification
29
- token.create projectId: name; requires --secret-output NEW_FILE
30
- token.revoke projectId, resourceId: {}
31
-
32
- Notification events: baseline_hold, case_opened, case_resolved; at least one.
33
- Policy rules: id, kind, mode, branches, typed params. Read a policy before revising.
34
- Find scope and resource IDs with cloud list and cloud show.
35
- Use cloud session revoke ID to revoke a CLI login; token.revoke revokes a project token.
36
-
37
- Example command.json:
38
- {"schemaVersion":1,"idempotencyKey":"retention-30","operation":"project.retention",
39
- "projectId":"prj_example","input":{"days":30}}
40
-
41
- Keep secrets in private files. Token output never goes to stdout.
42
- Retry the identical command/key after a lost response; changing the command conflicts.
43
- If a token was issued but saving failed, retry the same key with a new --secret-output.
44
- For outcome_uncertain, inspect cloud list/show and audit history before choosing a new key.
45
-
46
- Other file formats:
47
- Versioned case commands: schemaVersion: 1, projectId, idempotencyKey,
48
- expectedVersion, commands. Read current versions with cloud snapshot.
49
- Each case command has caseId and type: case.set_state (state: active|resolved),
50
- case.classify (classification: untriaged|regression|flaky|expected|quarantined),
51
- or case.link_issue (issueRepository, positive issueNumber).
52
- Campaign reports with --campaign ID: schemaVersion: 1, projectId, campaignId,
53
- idempotencyKey, expectedVersion, command. Command types: state.report, receipt.link,
54
- finding.record, finding.update, finding.verify, finding.relate, verification.settings.update.
55
- Campaign verification can load product configuration from --dir.
56
- See the installed docs/testkit-cloud-boundary.md for complete case and campaign formats.`;
@@ -1,2 +0,0 @@
1
- import type { ControlOptions } from "../app/cloud/control.js";
2
- export declare function cloudInteraction(json: boolean): Pick<ControlOptions, "notify" | "choose">;
@@ -1,3 +0,0 @@
1
- import{createInterface as c}from"node:readline/promises";export function cloudInteraction(r){return{notify:e=>process.stderr.write(`${e.replace(/\n$/u,"")}
2
- `),...!r&&!process.env.CI&&process.stdin.isTTY&&process.stderr.isTTY?{choose:a}:{}}}async function a(r,e){const n=c({input:process.stdin,output:process.stderr});try{e.forEach((o,i)=>process.stderr.write(`${String(i+1)}. ${o.name} (${o.id})
3
- `));const s=(await n.question(`Select ${r} [1-${String(e.length)}]: `)).trim(),t=/^\d+$/u.test(s)?Number(s)-1:-1;if(!Number.isSafeInteger(t)||t<0||t>=e.length)throw new Error(`Invalid ${r} selection. Select an explicit ID with a command flag.`);return e[t].id}finally{n.close()}}
@@ -1,5 +0,0 @@
1
- import type { CommandContext } from "../../command-catalog.js";
2
- export default function run({ flags, json, log }: CommandContext<"cloud apply">): Promise<{
3
- ok: boolean;
4
- receipt: unknown;
5
- }>;
@@ -1 +0,0 @@
1
- import{cloudInteraction as u}from"../../cloud-selection.js";import{runWithCliProcessSignal as n}from"../../process-signal.js";import{applyCloudMutationFile as c}from"../../../app/cloud/index.js";export default async function p({flags:r,json:t,log:o}){const i=await n(e=>c(r.file,{...u(t),signal:e,browser:r.browser,secretOutput:r["secret-output"],campaignId:r.campaign,productDir:r.dir,origin:r.url,projectId:r.project}));return t||o(JSON.stringify(i,null,2)),{ok:!0,receipt:i}}
@@ -1,5 +0,0 @@
1
- import type { CommandContext } from "../../../command-catalog.js";
2
- export default function run({ flags, json, log }: CommandContext<"cloud campaign create">): Promise<{
3
- ok: boolean;
4
- result: unknown;
5
- }>;
@@ -1 +0,0 @@
1
- import{cloudInteraction as u}from"../../../cloud-selection.js";import{createCloudCampaign as n}from"../../../../app/cloud/control-actions.js";export default async function i({flags:t,json:r,log:e}){const o=await n({...u(r),origin:t.url,organisation:t.org,project:t.project,requestKey:t["request-key"],focus:t.focus,targetBugCount:t["target-bugs"]});return r||e(JSON.stringify(o,null,2)),{ok:!0,result:o}}
@@ -1,5 +0,0 @@
1
- import type { CommandContext } from "../../command-catalog.js";
2
- export default function run({ flags, json, log }: CommandContext<"cloud download">): Promise<{
3
- ok: boolean;
4
- result: unknown;
5
- }>;
@@ -1 +0,0 @@
1
- import{cloudInteraction as u}from"../../cloud-selection.js";import{downloadCloudArtifact as n}from"../../../app/cloud/control-actions.js";export default async function e({flags:t,json:r,log:i}){const o=await n(t.receipt,t.path,t.output,{origin:t.url,...u(r)});return r||i(JSON.stringify(o,null,2)),{ok:!0,result:o}}
@@ -1,5 +0,0 @@
1
- import type { CommandContext } from "../../command-catalog.js";
2
- export default function run({ args, flags, json, log }: CommandContext<"cloud list">): Promise<{
3
- ok: boolean;
4
- result: unknown;
5
- }>;
@@ -1 +0,0 @@
1
- import{cloudInteraction as c}from"../../cloud-selection.js";import{listCloudResources as n}from"../../../app/cloud/control-read.js";export default async function u({args:o,flags:r,json:e,log:t}){const i=await n(o.resource,{...c(e),origin:r.url,organisation:r.org,project:r.project,limit:r.limit,cursor:r.cursor,branch:r.branch,decision:r.decision,pr:r.pr,sort:r.sort,direction:r.direction,flaky:r.flaky,receipt:r.receipt,caseId:r.case,testPath:r.path,page:r.page,campaign:r.campaign,rootCauseKey:r["root-cause-key"],issueReference:r["issue-reference"],view:r.view,order:r.order});return e||t(JSON.stringify(i,null,2)),{ok:!0,result:i}}
@@ -1,13 +0,0 @@
1
- import type { CommandContext } from "../../command-catalog.js";
2
- export default function run({ flags, json, log }: CommandContext<"cloud login">): Promise<{
3
- user: {
4
- id: string;
5
- email: string;
6
- name: string;
7
- };
8
- expiresAt: string;
9
- maximumExpiresAt: string;
10
- origin: string;
11
- reused: boolean;
12
- ok: boolean;
13
- }>;
@@ -1,2 +0,0 @@
1
- import{runWithCliProcessSignal as s}from"../../process-signal.js";import{loginToCloud as u}from"../../../app/cloud/login.js";export default async function c({flags:r,json:e,log:i}){const o=await s(t=>u({origin:r.url,browser:r.browser,force:r.force,signal:t,notify:n=>process.stderr.write(`${n}
2
- `)}));return e||i(`Connected to ${o.origin} as ${o.user.email}.`),{ok:!0,...o}}
@@ -1,7 +0,0 @@
1
- import type { CommandContext } from "../../command-catalog.js";
2
- export default function run({ flags, json, log }: CommandContext<"cloud logout">): Promise<{
3
- origin: string;
4
- loggedOut: boolean;
5
- environmentTokenActive: boolean;
6
- ok: boolean;
7
- }>;
@@ -1 +0,0 @@
1
- import{logoutOfCloud as e}from"../../../app/cloud/login.js";export default async function r({flags:n,json:t,log:i}){const o=await e({origin:n.url});return t||(i(`Signed out of ${o.origin}.`),o.environmentTokenActive&&i("TESTKIT_CLOUD_TOKEN is still set and will be used by Cloud commands.")),{ok:!0,...o}}
@@ -1,2 +0,0 @@
1
- import type { CommandContext } from "../../../command-catalog.js";
2
- export default function run({ flags, json, log, setExitCode }: CommandContext<"cloud migrate apply">): Promise<import("../../../../app/cloud/migration/model.js").MigrationResult>;
@@ -1 +0,0 @@
1
- import{applyMigration as p}from"../../../../app/cloud/migration/index.js";import{migrationProgressRenderer as i}from"./progress.js";export default async function l({flags:r,json:t,log:o,setExitCode:n}){const e=await p(r.plan,process.env,i());return e.complete||n(1),t||o(`${e.complete?"Complete":"Incomplete"}: ${e.reportPath} (${String(e.problems.length)} problems)`),e}
@@ -1,7 +0,0 @@
1
- import type { CommandContext } from "../../../command-catalog.js";
2
- export default function run({ flags, json, log, setExitCode }: CommandContext<"cloud migrate plan">): Promise<{
3
- ready: boolean;
4
- planPath: string;
5
- problems: import("../../../../app/cloud/migration/model.js").MigrationProblem[];
6
- summary: import("../../../../app/cloud/migration/model.js").MigrationPlan["sourceSummary"];
7
- }>;
@@ -1 +0,0 @@
1
- import{planMigration as o}from"../../../../app/cloud/migration/index.js";export default async function p({flags:r,json:i,log:t,setExitCode:n}){const e=await o({sourceDir:r.source,registry:r.registry,regressions:r.regressions,mapping:r.mapping,outputDir:r.output,origin:r.url,projectId:r.project,campaignId:r.campaign});return e.ready||n(1),i||t(`${e.ready?"Ready":"Blocked"}: ${e.planPath} (${String(e.problems.length)} problems)`),e}
@@ -1,2 +0,0 @@
1
- import type { MigrationProgressCallback } from "../../../../app/cloud/migration/progress.js";
2
- export declare function migrationProgressRenderer(): MigrationProgressCallback;
@@ -1,2 +0,0 @@
1
- const c={"source-verification":"Verifying source","journal-upgrade":"Upgraded recovery journal",packaging:"Packaging",uploading:"Uploading","receipt-accepted":"Receipt accepted","record-admission":"Admitting records",reconciliation:"Reconciling evidence and records",completion:"Import complete",failed:"Migration incomplete"};export function migrationProgressRenderer(){let i="",o=0;return r=>{const e=`${r.phase}:${r.runId??""}`,t=Date.now();if(e===i&&t-o<250&&r.phase!=="failed"&&r.phase!=="completion")return;i=e,o=t;const a=`${String(r.completedRuns)}/${String(r.totalRuns)} runs accepted, ${String(Math.max(0,r.totalRuns-r.completedRuns))} remaining`,s=`${String(r.bytesTransferred)} upload bytes, ${String(r.retries)} retries, ${String(r.verifiedBytes??0)} verified bytes`,n=[r.runId?`run ${r.runId}`:"",r.operation?`operation ${r.operation}`:""].filter(Boolean).join(", ");process.stderr.write(`[migration] ${c[r.phase]}: ${a}; ${s}${n?`; ${n}`:""}${r.detail?`; ${r.detail}`:""}
2
- `)}}
@@ -1,2 +0,0 @@
1
- import type { CommandContext } from "../../../command-catalog.js";
2
- export default function run({ flags, json, log, setExitCode }: CommandContext<"cloud migrate reconcile">): Promise<import("../../../../app/cloud/migration/model.js").MigrationResult>;
@@ -1 +0,0 @@
1
- import{reconcileMigration as i}from"../../../../app/cloud/migration/index.js";import{migrationProgressRenderer as p}from"./progress.js";export default async function l({flags:r,json:o,log:t,setExitCode:n}){const e=await i(r.plan,process.env,p());return e.complete||n(1),o||t(`${e.complete?"Complete":"Incomplete"}: ${e.reportPath} (${String(e.problems.length)} problems)`),e}
@@ -1,5 +0,0 @@
1
- import type { CommandContext } from "../../../command-catalog.js";
2
- export default function run({ args, flags, json, log }: CommandContext<"cloud policy preview">): Promise<{
3
- ok: boolean;
4
- result: unknown;
5
- }>;
@@ -1 +0,0 @@
1
- import{previewCloudPolicy as u}from"../../../../app/cloud/control-actions.js";export default async function e({args:o,flags:i,json:n,log:t}){const r=await u(o.policy,i.file,{origin:i.url,organisation:i.org});return n||t(JSON.stringify(r,null,2)),{ok:!0,result:r}}
@@ -1,5 +0,0 @@
1
- import type { CommandContext } from "../../../command-catalog.js";
2
- export default function run({ flags, json, log }: CommandContext<"cloud project create">): Promise<{
3
- ok: boolean;
4
- result: unknown;
5
- }>;
@@ -1 +0,0 @@
1
- import{cloudInteraction as n}from"../../../cloud-selection.js";import{runWithCliProcessSignal as u}from"../../../process-signal.js";import{createCloudProject as s}from"../../../../app/cloud/control.js";export default async function c({flags:r,json:o,log:i}){const e=await u(t=>s({...n(o),origin:r.url,organisation:r.org,name:r.name,repository:r.repository,provider:r.provider,host:r.host,publishDecisions:r["publish-decisions"],requestKey:r["request-key"],browser:r.browser,signal:t}));return o||i(JSON.stringify(e,null,2)),{ok:!0,result:e}}
@@ -1,7 +0,0 @@
1
- import type { CommandContext } from "../../command-catalog.js";
2
- export default function run({ flags, json, log }: CommandContext<"cloud publish">): Promise<{
3
- receipt: import("../../../cloud/client.js").CloudReceipt;
4
- association?: import("../../../cloud/campaigns.js").CampaignResult;
5
- dashboardUrl: string;
6
- ok: boolean;
7
- }>;
@@ -1 +0,0 @@
1
- import{cloudInteraction as t}from"../../cloud-selection.js";import c from"node:path";import{publishRunToCloud as s}from"../../../app/cloud/index.js";export default async function d({flags:o,json:e,log:r}){const i=await s({campaignId:o.campaign,...t(e),origin:o.url,productDir:c.resolve(o.dir??process.cwd()),projectId:o.project,runId:o.run??null}),{receipt:n}=i;if(!e){r(`Published ${n.runId}: ${n.decision} (${n.id})`),r(i.dashboardUrl);for(const a of n.decisionReasons)r(` ${a.message}`);i.association&&r(`Campaign ${i.association.campaignId}: receipt ${i.association.changed?"linked":"already linked"}, version ${String(i.association.version)}`)}return{ok:!0,...i}}
@@ -1,8 +0,0 @@
1
- import type { CommandContext } from "../../../command-catalog.js";
2
- export default function run({ args, flags, json, log }: CommandContext<"cloud session revoke">): Promise<{
3
- ok: boolean;
4
- result: {
5
- id: string;
6
- revoked: true;
7
- };
8
- }>;
@@ -1 +0,0 @@
1
- import{revokeCloudSession as t}from"../../../../app/cloud/control-actions.js";export default async function u({args:e,flags:r,json:i,log:n}){const o=await t(e.id,{origin:r.url});return i||n(`Revoked Cloud session ${o.id}.`),{ok:!0,result:o}}
@@ -1,5 +0,0 @@
1
- import type { CommandContext } from "../../command-catalog.js";
2
- export default function run({ args, flags, json, log }: CommandContext<"cloud show">): Promise<{
3
- ok: boolean;
4
- result: unknown;
5
- }>;
@@ -1 +0,0 @@
1
- import{cloudInteraction as e}from"../../cloud-selection.js";import{showCloudResource as u}from"../../../app/cloud/control-read.js";export default async function c({args:o,flags:r,json:t,log:n}){const i=await u(o.resource,o.id,{...e(t),origin:r.url,organisation:r.org,project:r.project,campaign:r.campaign});return t||n(JSON.stringify(i,null,2)),{ok:!0,result:i}}
@@ -1,5 +0,0 @@
1
- import type { CommandContext } from "../../command-catalog.js";
2
- export default function run({ flags, json, log }: CommandContext<"cloud snapshot">): Promise<{
3
- ok: boolean;
4
- snapshot: unknown;
5
- }>;
@@ -1 +0,0 @@
1
- import{readCloudSnapshot as t}from"../../../app/cloud/index.js";export default async function o({flags:i,json:r,log:n}){const e=await t({campaignId:i.campaign,findingId:i.finding,findings:i.findings,receipts:i.receipts,history:i.history,rootCauseKey:i["root-cause-key"],issueReference:i["issue-reference"],view:i.view,limit:i.limit,cursor:i.cursor,origin:i.url,projectId:i.project});return r||n(JSON.stringify(e,null,2)),{ok:!0,snapshot:e}}
@@ -1,33 +0,0 @@
1
- import type { CommandContext } from "../../command-catalog.js";
2
- export default function run({ flags, json, log }: CommandContext<"cloud status">): Promise<{
3
- origin: string;
4
- source: "environment";
5
- authenticated: null;
6
- message: string;
7
- ok: boolean;
8
- } | {
9
- message?: undefined;
10
- origin: string;
11
- source: "none";
12
- authenticated: boolean;
13
- ok: boolean;
14
- } | {
15
- user: {
16
- id: string;
17
- email: string;
18
- name: string;
19
- };
20
- expiresAt: string;
21
- maximumExpiresAt: string;
22
- message?: undefined;
23
- origin: string;
24
- source: "account";
25
- authenticated: boolean;
26
- ok: boolean;
27
- } | {
28
- message?: undefined;
29
- origin: string;
30
- source: "account";
31
- authenticated: boolean;
32
- ok: boolean;
33
- }>;
@@ -1 +0,0 @@
1
- import{cloudLoginStatus as u}from"../../../app/cloud/login.js";export default async function o({flags:t,json:e,log:i}){const n=await u({origin:t.url});return e||(i(`Cloud: ${n.origin}`),i(n.source==="environment"?n.message:n.authenticated&&"user"in n?`Signed in as ${n.user.email}`:"Not signed in. Run testkit cloud login.")),{ok:!0,...n}}
@@ -1,34 +0,0 @@
1
- export declare function cloudOrigin(value: string): string;
2
- export declare function isCloudAccountToken(value: unknown): value is string;
3
- export interface AccountSession {
4
- user: {
5
- id: string;
6
- email: string;
7
- name: string;
8
- };
9
- expiresAt: string;
10
- maximumExpiresAt: string;
11
- }
12
- export interface LoginRequest {
13
- deviceCode: string;
14
- userCode: string;
15
- verificationUrl: string;
16
- expiresIn: number;
17
- interval: number;
18
- }
19
- export declare class CloudAccountClient {
20
- private readonly token?;
21
- readonly origin: string;
22
- constructor(origin: string, token?: string | undefined);
23
- downloadArtifact(url: URL, target: string, expected: {
24
- bytes: number;
25
- sha256: string;
26
- }): Promise<void>;
27
- login(): Promise<LoginRequest>;
28
- poll(deviceCode: string): Promise<AccountSession>;
29
- session(): Promise<AccountSession>;
30
- refresh(): Promise<AccountSession>;
31
- revoke(deviceCode: string): Promise<void>;
32
- request(method: string, target: string, input?: unknown): Promise<unknown>;
33
- }
34
- export declare function parseSession(value: unknown): AccountSession;
@@ -1 +0,0 @@
1
- import{downloadVerifiedArtifact as p}from"./download.js";import{request as u,invalidResponse as i}from"./transport.js";import{parseResponse as c}from"./client.js";import{isUnknownRecord as a}from"../kernel/unknown-values.js";export function cloudOrigin(t){const e=new URL(t);if(!["https:","http:"].includes(e.protocol)||e.username||e.password||e.pathname!=="/"||e.search||e.hash)throw new Error("Testkit Cloud URL must be an HTTP or HTTPS origin without credentials, a path, query, or fragment");if(e.protocol==="http:"&&!["localhost","127.0.0.1","[::1]"].includes(e.hostname))throw new Error("Testkit Cloud account credentials require HTTPS except on localhost");return e.origin}export function isCloudAccountToken(t){return typeof t=="string"&&/^tkc_cli_[A-Za-z0-9_-]{43}$/.test(t)}export class CloudAccountClient{token;origin;constructor(e,r){this.token=r,this.origin=cloudOrigin(e)}async downloadArtifact(e,r,s){if(e.origin!==this.origin||e.username||e.password)throw i("Cloud request URL");await p(e,this.token??"",r,s)}async login(){const e=await this.request("POST","/api/v1/auth/cli",{});if(!a(e)||!isCloudAccountToken(e.deviceCode)||typeof e.userCode!="string"||!/^[A-Z2-7]{4}-[A-Z2-7]{4}-[A-Z2-7]{4}$/.test(e.userCode)||typeof e.verificationUrl!="string"||typeof e.expiresIn!="number"||!Number.isInteger(e.expiresIn)||e.expiresIn<1||e.expiresIn>1800||typeof e.interval!="number"||!Number.isInteger(e.interval)||e.interval<1||e.interval>60)throw i("CLI login request");let r;try{r=new URL(e.verificationUrl)}catch{throw i("CLI approval URL")}if(r.origin!==this.origin||r.username||r.password||r.pathname!=="/cli/authorize"||r.searchParams.get("code")!==e.userCode)throw i("CLI approval URL");return{deviceCode:e.deviceCode,userCode:e.userCode,verificationUrl:r.href,expiresIn:e.expiresIn,interval:e.interval}}async poll(e){return parseSession(await this.request("POST","/api/v1/auth/cli/poll",{deviceCode:e}))}async session(){return parseSession(await this.request("GET","/api/v1/auth/cli/session"))}async refresh(){return parseSession(await this.request("POST","/api/v1/auth/cli/refresh",{}))}async revoke(e){await this.request("POST","/api/v1/auth/cli/revoke",{deviceCode:e})}async request(e,r,s){const o=new URL(r,this.origin);if(o.origin!==this.origin||o.username||o.password)throw i("Cloud request URL");const n=s===void 0?null:Buffer.from(JSON.stringify(s));return c(await u({method:e,url:o,body:n,timeoutMs:3e4,maxResponseBytes:4*1024*1024,headers:{Accept:"application/json","User-Agent":"testkit",...this.token?{Authorization:`Bearer ${this.token}`}:{},...n?{"Content-Type":"application/json","Content-Length":String(n.length)}:{}}}))}}export function parseSession(t){if(!a(t)||!a(t.user)||typeof t.user.id!="string"||!t.user.id||typeof t.user.email!="string"||!t.user.email||typeof t.user.name!="string"||typeof t.expiresAt!="string"||!Number.isFinite(Date.parse(t.expiresAt))||typeof t.maximumExpiresAt!="string"||!Number.isFinite(Date.parse(t.maximumExpiresAt)))throw i("CLI session");return{user:{id:t.user.id,email:t.user.email,name:t.user.name},expiresAt:t.expiresAt,maximumExpiresAt:t.maximumExpiresAt}}
@@ -1 +0,0 @@
1
- export declare const MAX_CLOUD_ARCHIVE_BYTES: number;
@@ -1 +0,0 @@
1
- export const MAX_CLOUD_ARCHIVE_BYTES=2164260864;
@@ -1,12 +0,0 @@
1
- import type { RunBundleManifest, RunBundleUploadFile } from "../evidence/contracts.js";
2
- import type { HistoricalManifest } from "./historical-contract.js";
3
- export interface UploadArchiveSource {
4
- manifest: RunBundleManifest | HistoricalManifest;
5
- files: readonly Pick<RunBundleUploadFile, "path" | "absolutePath" | "bytes" | "sha256">[];
6
- }
7
- export declare function withUploadArchive<Result>(source: UploadArchiveSource, maxBytes: number, upload: (file: string, bytes: number) => Promise<Result>): Promise<Result>;
8
- /** Write one verified ZIP to a caller-owned, previously absent path. */
9
- export declare function writeUploadArchive(source: UploadArchiveSource, maxBytes: number, target: string, serializeManifest?: (manifest: UploadArchiveSource["manifest"]) => string): Promise<{
10
- bytes: number;
11
- sha256: string;
12
- }>;
@@ -1 +0,0 @@
1
- import O from"node:crypto";import t from"node:fs";import E from"node:path";import{Readable as L,Transform as x}from"node:stream";import{finished as T,pipeline as $}from"node:stream/promises";import{ZipFile as F}from"yazl";import{makeTempDir as R,removeTempDir as V}from"../process/files/temp-dir.js";export async function withUploadArchive(l,u,d){const c=R("testkit-cloud-"),p=E.join(c,"archive.zip");try{const o=await writeUploadArchive(l,u,p);return await d(p,o.bytes)}finally{V(c)}}export async function writeUploadArchive(l,u,d,c=JSON.stringify){const p=t.openSync(d,"wx",384),o=new F;let a,m,S=!1,y=0;const v=O.createHash("sha256"),f=new x({transform(i,e,h){y+=i.length,v.update(i),h(y>u?new Error("Cloud archive exceeds upload size limit"):null,i)}});o.on("error",i=>f.destroy(i));const w=$(o.outputStream,f,t.createWriteStream(d,{fd:p}));w.catch(()=>{});try{const i={mtime:new Date("1980-01-01T00:00:00Z"),forceDosTimestamp:!0,mode:33152};o.addBuffer(Buffer.from(c(l.manifest)),"manifest.json",i);for(const e of l.files)o.addReadStreamLazy(`files/${e.path}`,{...i,size:e.bytes},h=>{let s;try{const n=t.lstatSync(e.absolutePath);if(!n.isFile()||n.isSymbolicLink()||n.size!==e.bytes)throw new Error(`Verified bundle file changed before upload: ${e.path}`);s=t.openSync(e.absolutePath,t.constants.O_RDONLY|t.constants.O_NOFOLLOW);const b=t.fstatSync(s);if(b.ino!==n.ino||b.dev!==n.dev||b.size!==e.bytes)throw new Error(`Verified bundle file changed before upload: ${e.path}`);const z=O.createHash("sha256");let g=0;m=new x({transform(r,_,D){g+=r.length,z.update(r),D(g>e.bytes?new Error(`Verified bundle file changed during upload: ${e.path}`):null,r)},flush(r){r(g!==e.bytes||z.digest("hex")!==e.sha256?new Error(`Verified bundle file changed during upload: ${e.path}`):null)}}),m.on("error",r=>f.destroy(r)),a=t.createReadStream(e.absolutePath,{fd:s}),s=void 0,a.on("error",r=>f.destroy(r)),h(null,a.pipe(m))}catch(n){s!==void 0&&t.closeSync(s),h(n,L.from([]))}});return o.end(),await w,S=!0,{bytes:y,sha256:v.digest("hex")}}finally{a?.destroy(),a&&await T(a).catch(()=>{}),m?.destroy(),f.destroy(),await w.catch(()=>{}),S||t.rmSync(d,{force:!0})}}