@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.
- package/dist/app/run/engine-execution.d.ts +2 -0
- package/dist/app/run/engine-execution.js +1 -1
- package/dist/cli/command-catalog.d.ts +0 -127
- package/dist/cli/command-catalog.js +1 -1
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/entrypoint.js +1 -1
- package/dist/cli/framework/execute.d.ts +1 -0
- package/dist/cli/framework/execute.js +6 -6
- package/dist/cli/index.js +1 -1
- package/dist/cli/version-drift.js +7 -7
- package/dist/config/authoring/index.d.ts +0 -5
- package/dist/config/loader/index.d.ts +0 -1
- package/dist/config/loader/index.js +1 -1
- package/dist/engines/adapters/node-startup-cache.d.ts +8 -0
- package/dist/engines/adapters/node-startup-cache.js +1 -0
- package/dist/engines/api/execution.d.ts +2 -0
- package/dist/engines/managed-module-resolution.js +8 -1
- package/dist/engines/node-vitest/adapter/adapter.js +1 -1
- package/dist/engines/playwright/adapter/adapter.js +1 -1
- package/dist/environment/ready-environment.d.ts +3 -1
- package/dist/environment/ready-environment.js +1 -1
- package/dist/environment/run-environment.js +1 -1
- package/dist/environment/runtime-contexts.d.ts +1 -0
- package/dist/environment/runtime-contexts.js +1 -1
- package/dist/environment/runtime-manager.js +1 -1
- package/dist/evidence/bundles.d.ts +2 -3
- package/dist/evidence/bundles.js +1 -1
- package/dist/evidence/contracts.d.ts +0 -8
- package/dist/evidence/index.d.ts +1 -1
- package/dist/evidence/index.js +1 -1
- package/dist/process/execa-bundle.json +1 -1
- package/docs/engine-architecture.md +20 -0
- package/package.json +3 -10
- package/testkit-config-schema.md +0 -23
- package/dist/app/cloud/campaigns.d.ts +0 -6
- package/dist/app/cloud/campaigns.js +0 -1
- package/dist/app/cloud/control-actions.d.ts +0 -12
- package/dist/app/cloud/control-actions.js +0 -2
- package/dist/app/cloud/control-links.d.ts +0 -5
- package/dist/app/cloud/control-links.js +0 -1
- package/dist/app/cloud/control-read.d.ts +0 -24
- package/dist/app/cloud/control-read.js +0 -1
- package/dist/app/cloud/control-resources.d.ts +0 -3
- package/dist/app/cloud/control-resources.js +0 -1
- package/dist/app/cloud/control.d.ts +0 -34
- package/dist/app/cloud/control.js +0 -5
- package/dist/app/cloud/credentials.d.ts +0 -11
- package/dist/app/cloud/credentials.js +0 -2
- package/dist/app/cloud/index.d.ts +0 -22
- package/dist/app/cloud/index.js +0 -2
- package/dist/app/cloud/login.d.ts +0 -63
- package/dist/app/cloud/login.js +0 -2
- package/dist/app/cloud/migration/archive.d.ts +0 -13
- package/dist/app/cloud/migration/archive.js +0 -1
- package/dist/app/cloud/migration/commands.d.ts +0 -8
- package/dist/app/cloud/migration/commands.js +0 -1
- package/dist/app/cloud/migration/evidence.d.ts +0 -13
- package/dist/app/cloud/migration/evidence.js +0 -2
- package/dist/app/cloud/migration/files.d.ts +0 -16
- package/dist/app/cloud/migration/files.js +0 -2
- package/dist/app/cloud/migration/index.d.ts +0 -6
- package/dist/app/cloud/migration/index.js +0 -1
- package/dist/app/cloud/migration/journal.d.ts +0 -64
- package/dist/app/cloud/migration/journal.js +0 -1
- package/dist/app/cloud/migration/mapping-dispositions.d.ts +0 -3
- package/dist/app/cloud/migration/mapping-dispositions.js +0 -1
- package/dist/app/cloud/migration/mapping.d.ts +0 -11
- package/dist/app/cloud/migration/mapping.js +0 -2
- package/dist/app/cloud/migration/model.d.ts +0 -175
- package/dist/app/cloud/migration/model.js +0 -1
- package/dist/app/cloud/migration/plan.d.ts +0 -7
- package/dist/app/cloud/migration/plan.js +0 -1
- package/dist/app/cloud/migration/progress.d.ts +0 -19
- package/dist/app/cloud/migration/progress.js +0 -1
- package/dist/app/cloud/migration/reconcile.d.ts +0 -22
- package/dist/app/cloud/migration/reconcile.js +0 -1
- package/dist/app/cloud/migration/reconciliation-spool.d.ts +0 -4
- package/dist/app/cloud/migration/reconciliation-spool.js +0 -1
- package/dist/app/cloud/migration/state.d.ts +0 -15
- package/dist/app/cloud/migration/state.js +0 -1
- package/dist/app/cloud/migration/transfer.d.ts +0 -6
- package/dist/app/cloud/migration/transfer.js +0 -1
- package/dist/cli/cloud-apply-help.d.ts +0 -1
- package/dist/cli/cloud-apply-help.js +0 -56
- package/dist/cli/cloud-selection.d.ts +0 -2
- package/dist/cli/cloud-selection.js +0 -3
- package/dist/cli/commands/cloud/apply.d.ts +0 -5
- package/dist/cli/commands/cloud/apply.js +0 -1
- package/dist/cli/commands/cloud/campaign/create.d.ts +0 -5
- package/dist/cli/commands/cloud/campaign/create.js +0 -1
- package/dist/cli/commands/cloud/download.d.ts +0 -5
- package/dist/cli/commands/cloud/download.js +0 -1
- package/dist/cli/commands/cloud/list.d.ts +0 -5
- package/dist/cli/commands/cloud/list.js +0 -1
- package/dist/cli/commands/cloud/login.d.ts +0 -13
- package/dist/cli/commands/cloud/login.js +0 -2
- package/dist/cli/commands/cloud/logout.d.ts +0 -7
- package/dist/cli/commands/cloud/logout.js +0 -1
- package/dist/cli/commands/cloud/migrate/apply.d.ts +0 -2
- package/dist/cli/commands/cloud/migrate/apply.js +0 -1
- package/dist/cli/commands/cloud/migrate/plan.d.ts +0 -7
- package/dist/cli/commands/cloud/migrate/plan.js +0 -1
- package/dist/cli/commands/cloud/migrate/progress.d.ts +0 -2
- package/dist/cli/commands/cloud/migrate/progress.js +0 -2
- package/dist/cli/commands/cloud/migrate/reconcile.d.ts +0 -2
- package/dist/cli/commands/cloud/migrate/reconcile.js +0 -1
- package/dist/cli/commands/cloud/policy/preview.d.ts +0 -5
- package/dist/cli/commands/cloud/policy/preview.js +0 -1
- package/dist/cli/commands/cloud/project/create.d.ts +0 -5
- package/dist/cli/commands/cloud/project/create.js +0 -1
- package/dist/cli/commands/cloud/publish.d.ts +0 -7
- package/dist/cli/commands/cloud/publish.js +0 -1
- package/dist/cli/commands/cloud/session/revoke.d.ts +0 -8
- package/dist/cli/commands/cloud/session/revoke.js +0 -1
- package/dist/cli/commands/cloud/show.d.ts +0 -5
- package/dist/cli/commands/cloud/show.js +0 -1
- package/dist/cli/commands/cloud/snapshot.d.ts +0 -5
- package/dist/cli/commands/cloud/snapshot.js +0 -1
- package/dist/cli/commands/cloud/status.d.ts +0 -33
- package/dist/cli/commands/cloud/status.js +0 -1
- package/dist/cloud/account.d.ts +0 -34
- package/dist/cloud/account.js +0 -1
- package/dist/cloud/archive-contract.d.ts +0 -1
- package/dist/cloud/archive-contract.js +0 -1
- package/dist/cloud/archive.d.ts +0 -12
- package/dist/cloud/archive.js +0 -1
- package/dist/cloud/campaigns.d.ts +0 -200
- package/dist/cloud/campaigns.js +0 -2
- package/dist/cloud/client.d.ts +0 -68
- package/dist/cloud/client.js +0 -4
- package/dist/cloud/download.d.ts +0 -5
- package/dist/cloud/download.js +0 -1
- package/dist/cloud/historical-contract.d.ts +0 -224
- package/dist/cloud/historical-contract.js +0 -1
- package/dist/cloud/historical-json.d.ts +0 -16
- package/dist/cloud/historical-json.js +0 -1
- package/dist/cloud/historical.d.ts +0 -69
- package/dist/cloud/historical.js +0 -1
- package/dist/cloud/pagination.d.ts +0 -5
- package/dist/cloud/pagination.js +0 -1
- package/dist/cloud/transport.d.ts +0 -18
- package/dist/cloud/transport.js +0 -1
- package/dist/config/loader/cloud.d.ts +0 -2
- package/dist/config/loader/cloud.js +0 -1
- package/docs/cloud-control.md +0 -108
- package/docs/testkit-cloud-boundary.md +0 -628
- package/node_modules/buffer-crc32/LICENSE +0 -19
- package/node_modules/buffer-crc32/README.md +0 -55
- package/node_modules/buffer-crc32/dist/index.cjs +0 -306
- package/node_modules/buffer-crc32/dist/index.d.cts +0 -35
- package/node_modules/buffer-crc32/dist/index.d.mts +0 -35
- package/node_modules/buffer-crc32/dist/index.mjs +0 -304
- package/node_modules/buffer-crc32/index.d.ts +0 -35
- package/node_modules/buffer-crc32/package.json +0 -54
- package/node_modules/yazl/LICENSE +0 -21
- package/node_modules/yazl/README.md +0 -457
- package/node_modules/yazl/index.js +0 -826
- package/node_modules/yazl/package.json +0 -34
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
export interface LoginOptions {
|
|
2
|
-
origin?: string;
|
|
3
|
-
env?: NodeJS.ProcessEnv;
|
|
4
|
-
force?: boolean;
|
|
5
|
-
browser?: boolean;
|
|
6
|
-
signal?: AbortSignal;
|
|
7
|
-
notify: (message: string) => void;
|
|
8
|
-
}
|
|
9
|
-
export declare function selectedCloudOrigin(options: {
|
|
10
|
-
origin?: string;
|
|
11
|
-
env?: NodeJS.ProcessEnv;
|
|
12
|
-
}): string;
|
|
13
|
-
export declare function loginToCloud(options: LoginOptions): Promise<{
|
|
14
|
-
user: {
|
|
15
|
-
id: string;
|
|
16
|
-
email: string;
|
|
17
|
-
name: string;
|
|
18
|
-
};
|
|
19
|
-
expiresAt: string;
|
|
20
|
-
maximumExpiresAt: string;
|
|
21
|
-
origin: string;
|
|
22
|
-
reused: boolean;
|
|
23
|
-
}>;
|
|
24
|
-
export declare function cloudLoginStatus(options: {
|
|
25
|
-
origin?: string;
|
|
26
|
-
env?: NodeJS.ProcessEnv;
|
|
27
|
-
}): Promise<{
|
|
28
|
-
origin: string;
|
|
29
|
-
source: "environment";
|
|
30
|
-
authenticated: null;
|
|
31
|
-
message: string;
|
|
32
|
-
} | {
|
|
33
|
-
message?: undefined;
|
|
34
|
-
origin: string;
|
|
35
|
-
source: "none";
|
|
36
|
-
authenticated: boolean;
|
|
37
|
-
} | {
|
|
38
|
-
user: {
|
|
39
|
-
id: string;
|
|
40
|
-
email: string;
|
|
41
|
-
name: string;
|
|
42
|
-
};
|
|
43
|
-
expiresAt: string;
|
|
44
|
-
maximumExpiresAt: string;
|
|
45
|
-
message?: undefined;
|
|
46
|
-
origin: string;
|
|
47
|
-
source: "account";
|
|
48
|
-
authenticated: boolean;
|
|
49
|
-
} | {
|
|
50
|
-
message?: undefined;
|
|
51
|
-
origin: string;
|
|
52
|
-
source: "account";
|
|
53
|
-
authenticated: boolean;
|
|
54
|
-
}>;
|
|
55
|
-
export declare function logoutOfCloud(options: {
|
|
56
|
-
origin?: string;
|
|
57
|
-
env?: NodeJS.ProcessEnv;
|
|
58
|
-
}): Promise<{
|
|
59
|
-
origin: string;
|
|
60
|
-
loggedOut: boolean;
|
|
61
|
-
environmentTokenActive: boolean;
|
|
62
|
-
}>;
|
|
63
|
-
export declare function openBrowser(url: string): void;
|
package/dist/app/cloud/login.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{spawn as p}from"node:child_process";import{setTimeout as w}from"node:timers/promises";import{CloudAccountClient as c,cloudOrigin as v}from"../../cloud/account.js";import{CloudResponseError as l}from"../../cloud/client.js";import{readCredential as u,removeCredential as g,saveCredential as h,canUseCredentialAfterRefreshFailure as C}from"./credentials.js";import{isUnknownRecord as x}from"../../kernel/unknown-values.js";export function selectedCloudOrigin(e){return v(e.origin??(e.env??process.env).TESTKIT_CLOUD_URL??"https://testkit.cloud")}export async function loginToCloud(e){const t=selectedCloudOrigin(e),o=e.env??process.env;let n;try{n=u(t,o)}catch(r){if(!e.force)throw r;n=null}if(n&&!e.force)try{const r=new c(t,n.token),m=Date.parse(n.expiresAt)===Date.parse(n.maximumExpiresAt)?await r.session():await r.refresh();return h({...n,...m},o),{origin:t,...m,reused:!0}}catch(r){if(C(n,r,Date.now()))return e.notify("Cloud session refresh is temporarily unavailable. Using the saved, unexpired session."),{origin:t,user:n.user,expiresAt:n.expiresAt,maximumExpiresAt:n.maximumExpiresAt,reused:!0};if(!(r instanceof l)||r.status!==401)throw r}const i=new c(t),a=await i.login();e.notify(`Open ${a.verificationUrl}
|
|
2
|
-
Check code ${a.userCode}, then choose Allow CLI access. Waiting for approval...`),e.browser!==!1&&!o.CI&&openBrowser(a.verificationUrl);const d=Date.now()+a.expiresIn*1e3;let s=a.interval*1e3,f=!1;try{for(;Date.now()<d;){e.signal?.throwIfAborted();try{const r=await i.poll(a.deviceCode);return e.signal?.throwIfAborted(),h({...r,origin:t,token:a.deviceCode},o),f=!0,n&&await i.revoke(n.token).catch(()=>{e.notify("The previous Cloud session could not be revoked. It will expire automatically.")}),{origin:t,...r,reused:!1}}catch(r){if(r instanceof l&&r.status>=500||x(r)&&r.code==="cloud_transport_error"){await w(Math.min(s,Math.max(1,d-Date.now())),void 0,{signal:e.signal});continue}if(!(r instanceof l)||!["authorization_pending","slow_down"].includes(r.code??""))throw r;r.code==="slow_down"&&(s=Math.min(s+1e3,3e4))}await w(Math.min(s,Math.max(1,d-Date.now())),void 0,{signal:e.signal})}throw new Error("CLI login expired. Run testkit cloud login again.")}finally{f||await i.revoke(a.deviceCode).catch(()=>{})}}export async function cloudLoginStatus(e){const t=selectedCloudOrigin(e),o=e.env??process.env;if(o.TESTKIT_CLOUD_TOKEN!==void 0)return{origin:t,source:"environment",authenticated:null,message:"Commands use TESTKIT_CLOUD_TOKEN. Select a project to check its permissions."};const n=u(t,o);if(!n)return{origin:t,source:"none",authenticated:!1};try{return{origin:t,source:"account",authenticated:!0,...await new c(t,n.token).session()}}catch(i){if(!(i instanceof l)||i.status!==401)throw i;return{origin:t,source:"account",authenticated:!1}}}export async function logoutOfCloud(e){const t=selectedCloudOrigin(e),o=e.env??process.env,n=u(t,o);return n&&(await new c(t).revoke(n.token),g(t,o)),{origin:t,loggedOut:!0,environmentTokenActive:o.TESTKIT_CLOUD_TOKEN!==void 0}}export function openBrowser(e){const t=process.platform==="darwin"?"open":process.platform==="win32"?"rundll32":"xdg-open",o=process.platform==="win32"?["url.dll,FileProtocolHandler",e]:[e],n=p(t,o,{detached:!0,stdio:"ignore",shell:!1});n.on("error",()=>{}),n.unref()}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { MigrationPlan, PlannedRun } from "./model.js";
|
|
2
|
-
interface ArchiveIdentity {
|
|
3
|
-
bytes: number;
|
|
4
|
-
sha256: string;
|
|
5
|
-
}
|
|
6
|
-
/** The migration output lock and sequential run transfer own this single archive slot. */
|
|
7
|
-
export declare function prepareMigrationArchive(plan: MigrationPlan, run: PlannedRun, maxBytes: number, expected?: ArchiveIdentity): Promise<{
|
|
8
|
-
path: string;
|
|
9
|
-
bytes: number;
|
|
10
|
-
sha256: string;
|
|
11
|
-
}>;
|
|
12
|
-
export declare function removeMigrationArchive(plan: MigrationPlan, runId: string): Promise<void>;
|
|
13
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import p from"node:crypto";import r from"node:fs";import a from"node:path";import{writeUploadArchive as l}from"../../../cloud/archive.js";import{canonicalHistoricalJson as w}from"../../../cloud/historical-json.js";import{digest as S}from"./files.js";function u(o,t){const n=r.lstatSync(o.outputDir);if(!n.isDirectory()||n.isSymbolicLink())throw new Error("Migration output directory is unsafe");const i=a.join(o.outputDir,"archives");r.existsSync(i)||r.mkdirSync(i,{mode:448});const e=r.lstatSync(i);if(!e.isDirectory()||e.isSymbolicLink())throw new Error("Migration archive directory is unsafe");return a.join(i,`${S(t)}.zip`)}function m(o){if(process.platform==="win32")return;const t=r.openSync(o,"r");try{r.fsyncSync(t)}finally{r.closeSync(t)}}async function d(o,t,n){if(t.bytes>n)throw new Error("Journaled migration archive exceeds the upload limit");try{const i=r.lstatSync(o);if(!i.isFile()||i.isSymbolicLink()||i.size!==t.bytes)throw new Error("Archive size or file type changed");const e=r.openSync(o,r.constants.O_RDONLY|r.constants.O_NOFOLLOW),f=r.createReadStream(o,{fd:e}),c=p.createHash("sha256");let s=0;for await(const y of f){const h=y;if(s+=h.length,s>t.bytes)throw new Error("Archive grew during verification");c.update(h)}if(s!==t.bytes||c.digest("hex")!==t.sha256)throw new Error("Archive SHA-256 or size changed")}catch(i){throw new Error("Journaled migration archive is missing or corrupt; restore the original archive before resuming",{cause:i})}}export async function prepareMigrationArchive(o,t,n,i){const e=u(o,t.runId),f=a.dirname(e),c=`${e}.tmp`;if(r.readdirSync(f).some(s=>s!==a.basename(e)&&s!==a.basename(c)))throw new Error("Another migration archive occupies the active run slot; resume its run before packaging another");if(i)return await d(e,i,n),{path:e,...i};r.rmSync(e,{force:!0}),r.rmSync(c,{force:!0});try{const s=await l({manifest:t.request.manifest,files:t.files},n,c,w),y=r.openSync(c,r.constants.O_RDWR|r.constants.O_NOFOLLOW);try{r.fsyncSync(y)}finally{r.closeSync(y)}return r.renameSync(c,e),m(f),m(o.outputDir),{path:e,...s}}finally{r.rmSync(c,{force:!0})}}export async function removeMigrationArchive(o,t){const n=u(o,t);await r.promises.rm(n,{force:!0}),await r.promises.rm(`${n}.tmp`,{force:!0}),m(a.dirname(n))}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type HistoricalBegin, type HistoricalRecord } from "../../../cloud/historical-contract.js";
|
|
2
|
-
import type { MigrationPlan, PlannedRecord } from "./model.js";
|
|
3
|
-
import type { MigrationJournal } from "./state.js";
|
|
4
|
-
export declare function dependencyOrder(records: PlannedRecord[]): PlannedRecord[];
|
|
5
|
-
/** The upper version bound makes chunk boundaries independent of resumed state. */
|
|
6
|
-
export declare function batchRecords(records: HistoricalRecord[]): HistoricalRecord[][];
|
|
7
|
-
export declare function beginCommand(plan: MigrationPlan, planDigest: string): HistoricalBegin;
|
|
8
|
-
export declare function plannedRecords(plan: MigrationPlan, journal: MigrationJournal, kind: "case" | "finding"): HistoricalRecord[];
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{HISTORICAL_BODY_LIMIT as u}from"../../../cloud/historical-contract.js";import{canonicalHistoricalJson as f,historicalDigest as p}from"../../../cloud/historical-json.js";import{expectedFindingData as h}from"./reconcile.js";export function dependencyOrder(i){const s=new Map(i.map(o=>[o.source.id,o])),n=new Set,t=new Set,e=[],c=o=>{const r=o.source.id;if(n.has(r))return;if(t.has(r))throw new Error(`Cyclic finding relationship: ${r}`);t.add(r);const a=o.relationship?.targetId,d=a?s.get(a):void 0;d&&c(d),t.delete(r),n.add(r),e.push(o)};return i.forEach(c),e}export function batchRecords(i){const s=[];let n=[];const t=e=>e.length<=25&&Buffer.byteLength(f({schemaVersion:1,idempotencyKey:"x".repeat(128),expectedVersion:Number.MAX_SAFE_INTEGER,records:e}))<=u;for(const e of i){if(!t([e]))throw new Error(`Record cannot fit a historical batch: ${e.source.id}`);t([...n,e])||(s.push(n),n=[]),n.push(e)}return n.length&&s.push(n),s}export function beginCommand(i,s){const n=[...i.runs.filter(t=>!Object.hasOwn(i.reusedRuns,t.runId)).map(t=>t.request.source),...i.records.map(t=>t.source)];return{schemaVersion:1,idempotencyKey:`migration-${s}`,namespace:i.namespace,inventoryDigest:p(n),inventory:n}}export function plannedRecords(i,s,n){const t=i.records.filter(e=>e.source.kind===n);return(n==="finding"?dependencyOrder(t):t).map(e=>({source:e.source,sourceRecord:e.sourceRecord,observedAt:e.observedAt,...e.case?{case:e.case}:{finding:h(e,s)},...e.relationship?{relationship:e.relationship}:{},...e.verify!==void 0?{verify:e.verify}:{}}))}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { MigrationMapping, PlannedRun, SourceFile } from "./model.js";
|
|
2
|
-
export declare function referencedRuns(...documents: unknown[]): string[];
|
|
3
|
-
export declare function verifyCitedHashes(value: unknown, sourceFiles: SourceFile[]): void;
|
|
4
|
-
export declare function prepareHistoricalRun(options: {
|
|
5
|
-
sourceDir: string;
|
|
6
|
-
outputDir: string;
|
|
7
|
-
runId: string;
|
|
8
|
-
mapping: MigrationMapping;
|
|
9
|
-
sourceFiles: SourceFile[];
|
|
10
|
-
env: NodeJS.ProcessEnv;
|
|
11
|
-
}): PlannedRun;
|
|
12
|
-
/** Keep a cited source manifest distinct from the manifest describing derived files. */
|
|
13
|
-
export declare function includeOriginalManifest(run: PlannedRun, sourceDir: string, outputDir: string, sourceFiles: SourceFile[], env: NodeJS.ProcessEnv): void;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import $ from"node:fs";import H from"node:path";import{canonicalHistoricalJson as R,historicalDigest as F,HistoricalJsonNumber as Z,parseHistoricalJson as J}from"../../../cloud/historical-json.js";import"../../../cloud/historical-contract.js";import{stableJson as q}from"../../../evidence/storage.js";import{array as O,digest as w,object as f,readSource as P,readSourceJson as W,sanitize as U,text as b}from"./files.js";export function referencedRuns(...e){const i=new Set;function n(t){if(typeof t=="string")for(const c of t.matchAll(/\.testkit\/results\/runs\/([A-Za-z0-9._-]+)\//gu))i.add(c[1]);else if(Array.isArray(t))t.forEach(n);else if(t&&typeof t=="object")for(const[c,m]of Object.entries(t)){if(c==="runId")i.add(b(m,"runId",256));else if(c.toLowerCase().endsWith("runids"))for(const l of O(m,c))i.add(b(l,c,256));n(m)}}if(e.forEach(n),i.size>1e4)throw new Error("Referenced run inventory exceeds 10,000 entries");for(const t of i)if(!/^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/u.test(t))throw new Error("Unsafe original run identity");return[...i].sort()}function B(e,i){if(e instanceof Z){if(!/^(?:0|[1-9]\d*)$/u.test(e.lexeme))throw new Error(`${i} must be an integer`);e=Number(e.lexeme)}if(typeof e!="number"||!Number.isSafeInteger(e)||e<0)throw new Error(`${i} must be a bounded integer`);return e}function z(e,i){const n=b(e,i,128);if(!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,9})?(?:Z|[+-]\d\d:\d\d)$/u.test(n)||!Number.isFinite(Date.parse(n)))throw new Error(`Invalid historical ${i}`);return n}export function verifyCitedHashes(e,i){if(Array.isArray(e)){e.forEach(t=>{verifyCitedHashes(t,i)});return}if(!e||typeof e!="object")return;const n=e;if(typeof n.path=="string"&&n.path.startsWith(".testkit/results/runs/")&&typeof n.sha256=="string"&&i.find(t=>t.path===n.path)?.sha256!==n.sha256)throw new Error(`Cited artifact hash does not match retained evidence: ${n.path}`);Object.values(n).forEach(t=>{verifyCitedHashes(t,i)})}function T(e,i){let n;if(e.repository)n=f(e.repository,"git.repository");else{const t=new URL(b(e.remoteUrl,"git.remoteUrl"));if(!["https:","http:"].includes(t.protocol)||t.search||t.hash)throw new Error("Historical remote URL requires explicit repository mapping");const c=t.pathname.replace(/\.git$/u,"").split("/").filter(Boolean);if(c.length!==2)throw new Error("Historical repository path is ambiguous");n={host:t.host,owner:c[0],name:c[1]}}for(const t of["host","owner","name"])if(b(n[t],`git.repository.${t}`).toLowerCase()!==i[t].toLowerCase())throw new Error("Historical repository does not match the selected repository mapping");e.repository=i,delete e.remoteUrl,delete e.repoRoot,delete e.worktreePath}export function prepareHistoricalRun(e){const{sourceDir:i,outputDir:n,runId:t,mapping:c,sourceFiles:m,env:l}=e,j=`.testkit/results/runs/${t}`,I=W(i,`${j}/manifest.json`,m,8*1024**2),x=f(I,"manifest"),a={...x,schemaVersion:B(x.schemaVersion,"manifest schemaVersion")};if(a.runId!==t||a.algorithm!=="sha256"||![1,2].includes(a.schemaVersion))throw new Error("Unsupported historical manifest schema or identity");const p=O(a.files,"manifest.files").map(r=>{const s=f(r,"manifest entry"),d=b(s.path,"manifest path"),D=B(s.bytes,"manifest entry bytes");if(d==="manifest.json"||!/^[a-f0-9]{64}$/u.test(String(s.sha256)))throw new Error("Invalid manifest entry");return{path:d,bytes:D,sha256:String(s.sha256)}});if(p.length<2||p.length>1e4||new Set(p.map(r=>r.path)).size!==p.length)throw new Error("Invalid or duplicate manifest entries");if(w(q(p))!==a.digest)throw new Error("Original manifest file-list digest mismatch");if(p.reduce((r,s)=>r+s.bytes,0)>2*1024**3)throw new Error("Historical bundle exceeds 2 GiB");const u=[],V=[];let E="",g;for(const r of p.sort((s,d)=>s.path==="run.json"?-1:d.path==="run.json"?1:s.path.localeCompare(d.path))){const s=P(i,`${j}/${r.path}`,m);if(s.length!==r.bytes||w(s)!==r.sha256)throw new Error(`Evidence hash/size mismatch: ${r.path}`);let d=s;const D=[];if(r.path==="run.json"||r.path==="provenance.json"){if(s.length>8*1024**2)throw new Error("Historical run/provenance exceeds 8 MiB");const o=f(J(new TextDecoder("utf-8",{fatal:!0}).decode(s)),r.path);if(r.path==="run.json"){const h=f(o.run,"run");if(h.id!==t||B(o.schemaVersion,"run schemaVersion")!==(a.schemaVersion===1?16:19))throw new Error("Unsupported historical run schema or identity");const v=z(h.startedAt,"startedAt"),S=h.finishedAt==null?v:z(h.finishedAt,"finishedAt"),A=Date.parse(v),L=Date.parse(S);if(o.generatedAt!==void 0&&o.generatedAt!==""&&z(o.generatedAt,"generatedAt"),!Number.isFinite(A)||!Number.isFinite(L)||L<A||A>Date.now())throw new Error("Invalid historical observation times");if(!["passed","failed","cancelled","interrupted","incomplete"].includes(String(h.status)))throw new Error("Unsupported historical run status");E=S;const N=f(o.git,"run.git");T(N,c.repository),g=N,o.product&&delete f(o.product,"product").directory,delete o.host}else{if(o.runId!==t||B(o.schemaVersion,"provenance schemaVersion")!==(a.schemaVersion===1?1:3))throw new Error("Unsupported historical provenance schema or identity");const h=f(o.source,"provenance.source");if(delete h.root,h.git){const S=f(h.git,"provenance.source.git");T(S,c.repository);for(const A of["branch","commitSha"])if(S[A]!=null&&S[A]!==g?.[A])throw new Error("Historical provenance disagrees with run identity")}const v=o.runtime?f(o.runtime,"runtime"):void 0;v&&delete v.hostname,v?.controller&&delete f(v.controller,"controller").hostname}if(d=Buffer.from(R(U(o,l))+`
|
|
2
|
-
`),d.length>8*1024**2)throw new Error("Derived run/provenance exceeds 8 MiB");D.push("Normalize repository from original metadata; remove machine paths and hostname; sanitize text; canonical JSON; preserve original schema, run identity and observation times")}else try{const o=new TextDecoder("utf-8",{fatal:!0}).decode(s);if(!o.includes("\0")){const h=String(U(o,l));h!==o&&(d=Buffer.from(h),D.push("Redact sensitive text and the configured Cloud credential"))}}catch{}const M=H.join(n,"evidence",t,r.path);let k=n;for(const o of["evidence",t,...r.path.split("/").slice(0,-1)]){k=H.join(k,o),$.existsSync(k)||$.mkdirSync(k,{mode:448});const h=$.lstatSync(k);if(h.isSymbolicLink()||!h.isDirectory())throw new Error("Derived evidence directory must not contain symlinks or files")}$.writeFileSync(M,d,{mode:384,flag:"wx"}),u.push({path:r.path,absolutePath:M,bytes:d.length,sha256:w(d)}),V.push({path:r.path,originalPath:`${j}/${r.path}`,originalBytes:r.bytes,originalSha256:r.sha256,...d.equals(s)?{}:{transformation:D.join("; ")}})}if(!u.some(r=>r.path==="run.json")||!u.some(r=>r.path==="provenance.json"))throw new Error("Historical bundle lacks run.json or provenance.json");const y={runId:t,observedAt:E,originalManifestSha256:b(m.find(r=>r.path===`${j}/manifest.json`)?.sha256,"original manifest SHA256",64),originalManifestDigest:b(a.digest,"manifest.digest",64),converterVersion:"testkit-legacy-v1",schemaTuple:{manifest:B(a.schemaVersion,"manifest.schemaVersion"),run:a.schemaVersion===1?16:19,provenance:a.schemaVersion===1?1:3}},C=u.map(({path:r,bytes:s,sha256:d})=>({path:r,bytes:s,sha256:d})).sort((r,s)=>r.path.localeCompare(s.path)),_={algorithm:"sha256",schemaVersion:a.schemaVersion,runId:t,createdAt:b(a.createdAt,"manifest.createdAt"),files:C,digest:w(q(C)),redaction:U(a.redaction??{},l),storage:{fileCount:u.length,logicalBytes:u.reduce((r,s)=>r+s.bytes,0),deduplicatedBytes:0,objectBytesWritten:0}};return{runId:t,observedAt:E,request:{schemaVersion:1,source:{kind:"run",id:Buffer.byteLength(t)<=128?t:`run_${w(t)}`,digest:F(y)},sourceRecord:y,manifest:_,artifacts:V},files:u}}export function includeOriginalManifest(e,i,n,t,c){const m=`.testkit/results/runs/${e.runId}/manifest.json`,l=P(i,m,t,8*1024**2);if(w(l)!==e.request.sourceRecord.originalManifestSha256)throw new Error(`Original manifest changed during planning: ${e.runId}`);const j=J(new TextDecoder("utf-8",{fatal:!0}).decode(l)),I=U(j,c),x=R(j)!==R(I),a=x?Buffer.from(R(I)):l;if(e.files.length>=1e4||e.files.reduce((g,y)=>g+y.bytes,a.length)>2*1024**3)throw new Error(`Cited original manifest exceeds derived evidence limits: ${e.runId}`);const p=H.join(n,"evidence",e.runId),u=$.lstatSync(p);if(!u.isDirectory()||u.isSymbolicLink())throw new Error("Derived evidence directory is unsafe");const V=H.join(p,"manifest.json");$.writeFileSync(V,a,{mode:384,flag:"wx"}),e.files.push({path:"manifest.json",absolutePath:V,bytes:a.length,sha256:w(a)}),e.request.artifacts.push({path:"manifest.json",originalPath:m,originalBytes:l.length,originalSha256:w(l),...x?{transformation:"Redact sensitive original manifest fields and Cloud credential"}:{}});const E=e.files.map(({path:g,bytes:y,sha256:C})=>({path:g,bytes:y,sha256:C})).sort((g,y)=>g.path.localeCompare(y.path));e.request.manifest.files=E,e.request.manifest.digest=w(q(E)),e.request.manifest.storage={...f(e.request.manifest.storage,"derived manifest storage"),fileCount:E.length,logicalBytes:E.reduce((g,y)=>g+y.bytes,0)}}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import type { SourceFile } from "./model.js";
|
|
3
|
-
export declare const digest: (bytes: crypto.BinaryLike) => string;
|
|
4
|
-
export declare function object(value: unknown, label: string): Record<string, unknown>;
|
|
5
|
-
export declare function text(value: unknown, label: string, max?: number): string;
|
|
6
|
-
export declare function array(value: unknown, label: string): unknown[];
|
|
7
|
-
export declare function plain(value: unknown): unknown;
|
|
8
|
-
export declare function contained(root: string, relative: string): string;
|
|
9
|
-
export declare function readBoundedFile(filename: string, maxBytes?: number): Buffer;
|
|
10
|
-
export declare function readSource(root: string, relative: string, inventory: SourceFile[], maxBytes?: number): Buffer;
|
|
11
|
-
export declare function readSourceJson(root: string, relative: string, inventory: SourceFile[], maxBytes?: number): unknown;
|
|
12
|
-
export declare function sanitize(value: unknown, env: NodeJS.ProcessEnv): unknown;
|
|
13
|
-
export declare function writeDocument(filename: string, value: unknown): void;
|
|
14
|
-
export declare function readDocument(filename: string): unknown;
|
|
15
|
-
export declare function separateOutput(source: string, output: string): void;
|
|
16
|
-
export declare function withOutputLock<T>(output: string, work: () => Promise<T>): Promise<T>;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import y from"node:crypto";import i from"node:fs";import d from"node:os";import u from"node:path";import{canonicalHistoricalJson as m,parseHistoricalJson as h,HistoricalJsonNumber as l}from"../../../cloud/historical-json.js";import{redactSensitiveText as w}from"../../../kernel/redaction.js";export const digest=r=>y.createHash("sha256").update(r).digest("hex");export function object(r,n){if(!r||typeof r!="object"||Array.isArray(r))throw new Error(`${n} must be an object`);return r}export function text(r,n,e=4096){if(typeof r!="string"||!r.trim()||r.includes("\0")||Buffer.byteLength(r)>e)throw new Error(`${n} must be a nonblank string of at most ${String(e)} bytes`);return r}export function array(r,n){if(!Array.isArray(r))throw new Error(`${n} must be an array`);return r}export function plain(r){if(r instanceof l){const n=Number(r.lexeme);if(!Number.isFinite(n)||Number.isInteger(n)&&!Number.isSafeInteger(n))throw new Error("Numeric field exceeds supported range");return n}return Array.isArray(r)?r.map(plain):r&&typeof r=="object"?Object.fromEntries(Object.entries(r).map(([n,e])=>[n,plain(e)])):r}export function contained(r,n){if(u.isAbsolute(n)||u.win32.isAbsolute(n)||/[\\:\0]/u.test(n)||n.split("/").some(o=>!o||o==="."||o===".."))throw new Error("Source reference must be a normalized relative path");const e=u.resolve(r,n);let t=r;for(const o of n.split("/"))if(t=u.join(t,o),i.lstatSync(t).isSymbolicLink())throw new Error(`Symlink is not allowed: ${n}`);if(!i.statSync(e).isFile())throw new Error(`Source reference is not a file: ${n}`);return e}export function readBoundedFile(r,n=128*1024**2){const e=i.lstatSync(r);if(!e.isFile()||e.isSymbolicLink()||e.size>n)throw new Error("File must be regular and within its byte limit");const t=i.openSync(r,i.constants.O_RDONLY|i.constants.O_NOFOLLOW);try{const o=i.fstatSync(t);if(!o.isFile()||o.ino!==e.ino||o.dev!==e.dev||o.size!==e.size)throw new Error("Source changed during read");const s=Buffer.alloc(e.size+1);let c=0;for(;c<s.length;){const f=i.readSync(t,s,c,s.length-c,null);if(!f)break;c+=f}const a=i.fstatSync(t);if(c!==e.size||a.size!==e.size||a.mtimeMs!==e.mtimeMs||a.ctimeMs!==e.ctimeMs)throw new Error("Source changed during read");return s.subarray(0,c)}finally{i.closeSync(t)}}export function readSource(r,n,e,t=128*1024**2){const o=contained(r,n),s=readBoundedFile(o,t),c={path:n,bytes:s.length,sha256:digest(s)},a=e.find(f=>f.path===n);if(a&&(a.sha256!==c.sha256||a.bytes!==c.bytes))throw new Error(`Source changed during inventory: ${n}`);return a||e.push(c),s}export function readSourceJson(r,n,e,t=32*1024**2){return h(new TextDecoder("utf-8",{fatal:!0}).decode(readSource(r,n,e,t)),{maxBytes:t})}export function sanitize(r,n){const e=n.TESTKIT_CLOUD_TOKEN;function t(o){if(typeof o=="string"){const s=o.replace(/[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/giu,a=>{try{const f=new URL(a);return f.username="",f.password="",f.search="",f.hash="",f.toString()}catch{return"<redacted-url>"}});let c=w(s).replace(/\bBearer\s+[^\s"']+/giu,"Bearer <redacted>").replace(/\btkc_cli_[A-Za-z0-9_-]{43}\b/gu,"<redacted>");return e&&(e.length>=12?c=c.replaceAll(e,"<redacted>"):c===e&&(c="<redacted>")),c}return o instanceof l?o:Array.isArray(o)?o.map(t):o&&typeof o=="object"?Object.fromEntries(Object.entries(o).map(([s,c])=>[s,/^(?:password|(?:access[_-]?|refresh[_-]?)?token|(?:client[_-]?)?secret|private[_-]?key|authorization|cookie|session|api[_-]?key)$/iu.test(s)?"<redacted>":t(c)])):o}return t(r)}export function writeDocument(r,n){const e=m(n)+`
|
|
2
|
-
`;if(Buffer.byteLength(e)>128*1024**2)throw new Error("Migration document exceeds 128 MiB");i.mkdirSync(u.dirname(r),{recursive:!0,mode:448});const t=`${r}.${y.randomUUID()}.tmp`,o=i.openSync(t,"wx",384);try{try{i.writeFileSync(o,e),i.fsyncSync(o)}finally{i.closeSync(o)}i.renameSync(t,r)}finally{i.rmSync(t,{force:!0})}if(process.platform!=="win32"){const s=i.openSync(u.dirname(r),"r");try{i.fsyncSync(s)}finally{i.closeSync(s)}}}export function readDocument(r){const n=h(new TextDecoder("utf-8",{fatal:!0}).decode(readBoundedFile(r)),{maxBytes:134217728});function e(t,o=""){return o==="sourceRecord"||o==="record"?t:t instanceof l?plain(t):Array.isArray(t)?t.map(s=>e(s)):t&&typeof t=="object"?Object.fromEntries(Object.entries(t).map(([s,c])=>[s,e(c,s)])):t}return e(n)}export function separateOutput(r,n){const e=i.realpathSync(r);let t=u.resolve(n);const o=[];for(;!i.existsSync(t);)o.unshift(u.basename(t)),t=u.dirname(t);const s=u.join(i.realpathSync(t),...o);for(const[c,a]of[[e,s],[s,e]]){const f=u.relative(c,a);if(!f||!f.startsWith(`..${u.sep}`)&&f!==".."&&!u.isAbsolute(f))throw new Error("Migration output must be outside and separate from the source root")}}export async function withOutputLock(r,n){i.mkdirSync(r,{recursive:!0,mode:448});const e=u.join(r,".migration-lock"),t=u.join(r,".migration-lock-acquire");let o;try{o=i.openSync(t,"wx",384)}catch(a){throw object(a,"lock acquisition error").code==="EEXIST"?new Error("Migration lock acquisition is already running. If its process crashed, verify no migration is acquiring this directory before manually removing .migration-lock-acquire",{cause:a}):a}const s=i.fstatSync(o),c=y.randomUUID();try{if(i.writeFileSync(o,JSON.stringify({pid:process.pid,host:d.hostname(),owner:c})),i.existsSync(e)){const f=object(JSON.parse(readBoundedFile(e,4096).toString("utf8")),"lock");let p=!1;if(f.host===d.hostname()&&Number.isSafeInteger(f.pid)&&Number(f.pid)>0)try{process.kill(Number(f.pid),0)}catch(S){p=object(S,"process error").code==="ESRCH"}if(!p)throw new Error("Migration output is locked by another process");i.unlinkSync(e)}const a=i.openSync(e,"wx",384);try{i.writeFileSync(a,JSON.stringify({pid:process.pid,host:d.hostname(),owner:c}))}finally{i.closeSync(a)}}finally{if(i.closeSync(o),i.existsSync(t)){const a=i.lstatSync(t);a.ino===s.ino&&a.dev===s.dev&&i.unlinkSync(t)}}try{return await n()}finally{i.existsSync(e)&&object(JSON.parse(readBoundedFile(e,4096).toString("utf8")),"lock").owner===c&&i.unlinkSync(e)}}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type MigrationProgressCallback } from "./progress.js";
|
|
2
|
-
export type { MigrationProgress, MigrationProgressCallback } from "./progress.js";
|
|
3
|
-
import type { MigrationResult } from "./model.js";
|
|
4
|
-
export { planMigration } from "./plan.js";
|
|
5
|
-
export declare function applyMigration(planPath: string, env?: NodeJS.ProcessEnv, onProgress?: MigrationProgressCallback): Promise<MigrationResult>;
|
|
6
|
-
export declare function reconcileMigration(planPath: string, env?: NodeJS.ProcessEnv, onProgress?: MigrationProgressCallback): Promise<MigrationResult>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import w from"node:path";import{cloudToken as v}from"../credentials.js";import{HistoricalClient as O}from"../../../cloud/historical.js";import{sanitize as h,withOutputLock as $,writeDocument as j}from"./files.js";import{reconcileState as E}from"./reconcile.js";import{loadPlan as M,openJournal as k,performOperation as q,verifyPlanFiles as P,upgradeJournal as x}from"./state.js";import{transfer as R}from"./transfer.js";import{operationIs as V}from"./journal.js";import{createMigrationProgress as C}from"./progress.js";export{planMigration}from"./plan.js";export function applyMigration(m,i=process.env,c){return S(m,i,!0,c)}export function reconcileMigration(m,i=process.env,c){return S(m,i,!1,c)}async function S(m,i,c,r){const n=C(r,i),u=w.resolve(m),a=w.join(w.dirname(u),"report.json"),o={complete:!1,planPath:u,reportPath:a,problems:[]};try{n.emit("source-verification",{operation:"source-verification"});const{plan:t,planDigest:y}=M(u);return n.emit("source-verification",{totalRuns:t.runs.length,operation:"source-verification"}),await $(t.outputDir,async()=>{let d={planDigest:y,target:t.target,sourceSummary:t.sourceSummary,dispositions:t.dispositions??[]};try{if(P(t),!t.ready||t.problems.length)throw o.problems=t.problems,new Error("Migration plan has unresolved decisions");const e=k(t,y),s=new O({...t.target,token:await v(t.target.origin,i)});if(n.emit("source-verification",{completedRuns:Object.keys(e.receiptIds).length,operation:"source-verification"}),c){const l=e.schemaVersion===1;x(t,e),l&&e.upgradedFrom===1&&n.emit("journal-upgrade",{operation:"journal-upgrade",detail:"Schema 1 to schema 2; saved requests retained"}),await R(t,e,s,i,n)}e.importId&&(o.importId=e.importId),n.emit("reconciliation",{completedRuns:Object.keys(e.receiptIds).length,operation:"reconcile"});let I=0,p=await E(t,e,s,{onVerified(l){I+=l,n.emit("reconciliation",{verifiedBytes:I,operation:"reconcile"})}});if(c&&p.complete&&e.importId){const l=e.importId,b=e.operations.some(g=>g.key==="complete"&&g.state!=="accepted");n.emit("reconciliation",{operation:"complete-import",retries:n.current.retries+(b?1:0)}),await q(t,e,"complete","complete",{importId:l},()=>s.complete(l),i),n.emit("reconciliation",{operation:"verify-completion"}),p=await p.verifyCompletion()}o.problems.push(...p.problems);const f=e.importId?!!(await s.page(e.importId)).import.completedAt:!1;f||o.problems.push({kind:"import",id:e.importId??"missing",field:"completion",detail:"Server import is not complete"}),o.complete=p.complete&&f,n.emit(o.complete?"completion":"failed",{...o.problems[0]?{operation:"reconcile",detail:o.problems[0].detail}:{}}),d={...d,items:p.items,counts:p.counts,campaignVersion:p.campaignVersion,receiptIds:e.receiptIds,caseIds:e.caseIds,operations:e.operations.map(({key:l,state:b,error:g})=>({key:l,state:b,...g?{error:g}:{}}))}}catch(e){n.fail(e),o.problems.push({kind:"migration",id:"operation",field:c?"apply":"reconcile",detail:String(h(e instanceof Error?e.message:String(e),i))});try{const s=k(t,y);s.importId&&(o.importId=s.importId),d={...d,localOutcomes:D(t,s),receiptIds:s.receiptIds,caseIds:s.caseIds,operations:s.operations.map(({key:I,state:p,error:f})=>({key:I,state:p,...f?{error:f}:{}}))}}catch(s){d.journalError=String(h(s instanceof Error?s.message:String(s),i))}}return j(a,h({...d,...o},i)),o})}catch(t){return n.fail(t),o.problems.push({kind:"migration",id:"plan",field:"preflight",detail:String(h(t instanceof Error?t.message:String(t),i))}),o}}function D(m,i){const c=new Map;for(const r of m.runs)c.set(`run:${r.request.source.id}`,{...r.request.source,status:i.receiptIds[r.runId]?Object.hasOwn(m.reusedRuns,r.runId)?"reused":"accepted":"pending",...i.receiptIds[r.runId]?{cloudId:i.receiptIds[r.runId]}:{}});for(const r of m.records)c.set(`${r.source.kind}:${r.source.id}`,{...r.source,originalId:r.originalId,status:"pending"});for(const r of i.operations){const n=JSON.parse(r.request);for(const a of n.records??[]){const o=c.get(`${a.source.kind}:${a.source.id}`);o&&(o.status=r.state==="accepted"?"accepted":r.state)}const u=V(r,"batch")?r.result:void 0;for(const a of u?.items??[]){const o=c.get(`${a.kind}:${a.id}`);o&&c.set(`${a.kind}:${a.id}`,{...o,...a})}}return[...c.values()]}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { type HistoricalBatch, type HistoricalBatchResult, type HistoricalBegin, type HistoricalEvidence, type HistoricalImport, type HistoricalPage, type HistoricalReceipt, type HistoricalUploadPlan } from "../../../cloud/historical-contract.js";
|
|
2
|
-
import type { MigrationPlan } from "./model.js";
|
|
3
|
-
export interface OperationResults {
|
|
4
|
-
begin: HistoricalImport;
|
|
5
|
-
stage: HistoricalUploadPlan;
|
|
6
|
-
upload: {
|
|
7
|
-
uploaded: true;
|
|
8
|
-
};
|
|
9
|
-
archive: {
|
|
10
|
-
uploaded: true;
|
|
11
|
-
};
|
|
12
|
-
receipt: HistoricalReceipt;
|
|
13
|
-
batch: HistoricalBatchResult;
|
|
14
|
-
complete: HistoricalPage;
|
|
15
|
-
}
|
|
16
|
-
export interface OperationRequests {
|
|
17
|
-
begin: HistoricalBegin;
|
|
18
|
-
stage: HistoricalEvidence;
|
|
19
|
-
upload: {
|
|
20
|
-
url: string;
|
|
21
|
-
path: string;
|
|
22
|
-
bytes: number;
|
|
23
|
-
sha256: string;
|
|
24
|
-
};
|
|
25
|
-
archive: {
|
|
26
|
-
url: string;
|
|
27
|
-
bytes: number;
|
|
28
|
-
sha256: string;
|
|
29
|
-
manifestDigest: string;
|
|
30
|
-
};
|
|
31
|
-
receipt: {
|
|
32
|
-
url: string;
|
|
33
|
-
};
|
|
34
|
-
batch: HistoricalBatch;
|
|
35
|
-
complete: {
|
|
36
|
-
importId: string;
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
export type OperationKind = keyof OperationResults;
|
|
40
|
-
export interface MigrationOperation<K extends OperationKind = OperationKind> {
|
|
41
|
-
kind: K;
|
|
42
|
-
key: string;
|
|
43
|
-
request: string;
|
|
44
|
-
state: "pending" | "accepted" | "rejected" | "unknown";
|
|
45
|
-
result?: OperationResults[K];
|
|
46
|
-
error?: string;
|
|
47
|
-
}
|
|
48
|
-
export interface MigrationJournal {
|
|
49
|
-
schemaVersion: 1 | 2;
|
|
50
|
-
upgradedFrom?: 1;
|
|
51
|
-
legacyStages?: string[];
|
|
52
|
-
planDigest: string;
|
|
53
|
-
importId?: string;
|
|
54
|
-
operations: MigrationOperation[];
|
|
55
|
-
receiptIds: Record<string, string>;
|
|
56
|
-
caseIds: Record<string, string>;
|
|
57
|
-
}
|
|
58
|
-
export declare function operationIs<K extends OperationKind>(operation: MigrationOperation, kind: K): operation is MigrationOperation<K>;
|
|
59
|
-
export declare function emptyJournal(plan: MigrationPlan, planDigest: string): MigrationJournal;
|
|
60
|
-
/** Durable results are decoded against their saved command and the reviewed plan at first read. */
|
|
61
|
-
export declare function decodeJournal(value: unknown, plan: MigrationPlan, planDigest: string): MigrationJournal;
|
|
62
|
-
export declare function stagingRequest(plan: MigrationPlan, journal: MigrationJournal, runId: string): HistoricalEvidence;
|
|
63
|
-
/** openJournal has already validated this request against the plan and staging result. */
|
|
64
|
-
export declare function savedArchiveRequest(journal: MigrationJournal, key: string): OperationRequests["archive"] | undefined;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{parseHistoricalBatchResult as N,parseHistoricalImport as A,parseHistoricalPage as C,parseHistoricalReceipt as D,parseHistoricalResponse as O,parseHistoricalUploadPlan as z}from"../../../cloud/historical-contract.js";import{canonicalHistoricalJson as F,historicalDigest as S}from"../../../cloud/historical-json.js";import{MAX_CLOUD_ARCHIVE_BYTES as E}from"../../../cloud/archive-contract.js";import{array as H,object as q,text as v}from"./files.js";import{batchRecords as _,beginCommand as B,plannedRecords as J}from"./commands.js";function t(l,e){if(!l)throw new Error(`Invalid migration journal: ${e}`)}function I(l,e){const u=v(l,e,128);return t(/^[A-Za-z0-9_-]+$/u.test(u),e),u}function m(l,e,u){t(F(l)===F(e),u)}function $(l,e){const u=v(l,"operation URL"),n=new URL(u,e.target.origin);return t(n.origin===e.target.origin&&!n.username&&!n.password&&!n.hash&&!n.search,"operation URL target"),u}export function operationIs(l,e){return l.kind===e}export function emptyJournal(l,e){return{schemaVersion:1,planDigest:e,operations:[],receiptIds:Object.assign(Object.create(null),l.reusedRuns),caseIds:Object.create(null)}}export function decodeJournal(l,e,u){const n=q(l,"journal");t((n.schemaVersion===1||n.schemaVersion===2)&&n.planDigest===u,"journal belongs to another plan"),n.completed!==void 0&&t(typeof n.completed=="boolean","legacy completion state");const j=n.importId===void 0?void 0:I(n.importId,"importId"),o=emptyJournal(e,u);o.schemaVersion=n.schemaVersion,n.schemaVersion===1?(t(n.upgradedFrom===void 0&&n.legacyStages===void 0,"legacy journal has upgrade metadata"),delete o.legacyStages):(t(n.upgradedFrom===void 0||n.upgradedFrom===1,"journal upgrade origin"),n.upgradedFrom===1&&(o.upgradedFrom=1),o.legacyStages=H(n.legacyStages,"legacy stages").map(g=>v(g,"legacy stage")),t(new Set(o.legacyStages).size===o.legacyStages.length,"duplicate legacy stage"),t(o.upgradedFrom===1||o.legacyStages.length===0,"legacy stages require an upgraded journal"));const R=new Set,h=B(e,u);let x=e.campaignVersion;for(const g of H(n.operations,"operations")){const r=q(g,"operation"),a=v(r.key,"operation key");t(!R.has(a),"duplicate operation key"),R.add(a),t(r.state==="pending"||r.state==="accepted"||r.state==="rejected"||r.state==="unknown","operation state");const V=v(r.request,"operation request",2*1024**2),f=q(O(V),"operation request"),U=r.state,L={key:a,request:V,state:U,...r.error===void 0?{}:{error:v(r.error,"operation error",65536)}},y=(i,s)=>{t(r.kind===void 0||r.kind===i,"operation kind"),t(r.state!=="accepted"||s!==void 0,"accepted operation lacks result"),o.operations.push({...L,kind:i,...s===void 0?{}:{result:s}})};if(a==="begin"){m(f,h,"begin request differs from plan");const i=r.result===void 0?void 0:A(r.result,e.target.projectId,e.target.campaignId);i&&(I(i.id,"begin import identity"),t(i.namespace===h.namespace&&i.inventoryDigest===h.inventoryDigest&&i.total===h.inventory.length,"begin inventory identity"),r.state==="accepted"&&(o.importId=i.id)),y("begin",i);continue}if(t(o.importId,"operation precedes accepted begin"),a.startsWith("stage:")){const i=e.runs.find(p=>a===`stage:${p.runId}`);t(i&&!Object.hasOwn(e.reusedRuns,i.runId),"stage run identity");const s=stagingRequest(e,o,i.runId);m(f,s,"stage request differs from plan");const c=r.result===void 0?void 0:z(r.result,s);if(c){$(c.completeUrl,e);for(const p of c.uploads)$(p.url,e);c.archive&&$(c.archive.url,e)}y("stage",c);continue}if(a.startsWith("archive:")){t(o.schemaVersion===2,"archive operation requires journal schema 2");const i=e.runs.find(p=>a.startsWith(`archive:${p.runId}:`));t(i,"archive run identity");const s=o.operations.find(p=>p.key===`stage:${i.runId}`);t(s&&operationIs(s,"stage")&&s.result,"archive lacks staging result");const c=$(f.url,e);t(a===`archive:${i.runId}:${S(c)}`,"archive operation key"),t(new URL(c,e.target.origin).pathname===`/api/v1/publications/${s.result.publicationId}/archive`,"archive publication identity"),t(s.result.archive?s.result.archive.url===c:s.result.status==="accepted","archive target identity"),t(stagingRequest(e,o,i.runId).evidenceFormat==="zip-v1","archive transport identity"),t(Number.isSafeInteger(f.bytes)&&Number(f.bytes)>0&&Number(f.bytes)<=(s.result.archive?.maxBytes??E),"archive byte bound"),t(typeof f.sha256=="string"&&/^[a-f0-9]{64}$/.test(f.sha256),"archive digest"),m(f,{url:c,bytes:f.bytes,sha256:f.sha256,manifestDigest:i.request.manifest.digest},"archive request identity"),r.result!==void 0&&m(r.result,{uploaded:!0},"archive result"),y("archive",r.result===void 0?void 0:{uploaded:!0});continue}if(a.startsWith("upload:")||a.startsWith("receipt:")){const i=a.startsWith("upload:")?"upload":"receipt",s=e.runs.find(d=>a.startsWith(`${i}:${d.runId}:`));t(s,"evidence operation run identity");const c=o.operations.find(d=>operationIs(d,"stage")&&d.key===`stage:${s.runId}`)?.result;t(c,"evidence operation lacks a decoded staging result");const p=$(f.url,e);if(i==="upload"){const d=Number(a.split(":").at(-2)),b=s.request.manifest.files.at(d),k=b&&s.files.find(W=>W.path===b.path);t(Number.isSafeInteger(d)&&d>=0&&k,"upload file identity"),t(a===`upload:${s.runId}:${String(d)}:${S(p)}`,"upload operation key"),m(f,{url:p,path:k.absolutePath,bytes:k.bytes,sha256:k.sha256},"upload request differs from plan"),r.result!==void 0&&m(r.result,{uploaded:!0},"upload result"),y("upload",r.result===void 0?void 0:{uploaded:!0})}else{t(a===`receipt:${s.runId}:${S(p)}`&&p===c.completeUrl,"receipt completion identity"),m(f,{url:p},"receipt request");const d=r.result===void 0?void 0:D(r.result,e.target.projectId,{importId:o.importId,evidence:s.request,reused:c.reused});d&&(I(d.id,"receipt identity"),t(d.repository.toLowerCase()===`${e.repository.owner}/${e.repository.name}`.toLowerCase(),"receipt repository"),r.state==="accepted"&&(o.receiptIds[s.runId]=d.id)),y("receipt",d)}continue}const w=/^(case|finding)s-(\d+)$/u.exec(a);if(w){const i=w[1]==="case"?"case":"finding",s=Number(w[2]),c=_(J(e,o,i))[s];t(c,"batch position");const p={schemaVersion:1,idempotencyKey:`migration-${u}-${i}-${String(s)}`,expectedVersion:x,records:c};m(f,p,"batch request differs from plan or prior version");const d=r.result===void 0?void 0:N(r.result,o.importId,p);if(d&&(t(d.rejected?r.state==="rejected":r.state!=="rejected","batch result state"),r.state==="accepted"&&(x=d.version,i==="case")))for(const b of d.items)o.caseIds[b.id]=I(b.cloudId,"case outcome identity");y("batch",d);continue}if(a==="complete"){m(f,{importId:o.importId},"completion request identity");const i=r.result===void 0?void 0:C(r.result,e.target.projectId,e.target.campaignId,o.importId);i&&(t(i.import.namespace===h.namespace&&i.import.inventoryDigest===h.inventoryDigest&&i.import.total===h.inventory.length,"completed inventory identity"),t(i.import.completedAt&&Object.values(i.counts).every(s=>Object.entries(s).every(([c,p])=>["accepted","reused"].includes(c)||p===0)),"incomplete completion result")),y("complete",i);continue}throw new Error(`Invalid migration journal: unknown operation ${a}`)}j!==void 0&&t(j===o.importId,"saved import identity differs from accepted begin");for(const g of o.legacyStages??[])t(o.operations.some(r=>r.kind==="stage"&&r.key===`stage:${g}`),"legacy stage is absent");for(const g of["receiptIds","caseIds"])for(const[r,a]of Object.entries(q(n[g],g)))t(o[g][r]===I(a,g),`${g} differs from accepted operations`);return o}export function stagingRequest(l,e,u){const n=l.runs.find(j=>j.runId===u);return t(n,"stage run identity"),e.schemaVersion===1||e.legacyStages?.includes(u)?n.request:{...n.request,evidenceFormat:"zip-v1"}}export function savedArchiveRequest(l,e){const u=l.operations.find(n=>operationIs(n,"archive")&&n.key===e);return u?O(u.request):void 0}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { PlannedRecord } from "./model.js";
|
|
2
|
-
/** Describe projections while keeping the complete sanitized source record. */
|
|
3
|
-
export declare function describeFields(source: Record<string, unknown>, record: PlannedRecord, kind: "bug" | "observation" | "case"): NonNullable<PlannedRecord["fieldDispositions"]>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export function describeFields(n,s,c){const t=c==="case"?{id:"source.id",summary:"case.title",classification:"case.classification",state:"case.state",fingerprints:"case.fingerprint",lifecycle:"case.firstSeenAt,case.lastSeenAt",issue:"case.issueNumber,case.issueRepository"}:{id:"source.id",title:"finding.title",deduplicationKey:"finding.rootCauseKey",classification:"finding.classification",contract:"finding.expectedBehavior,finding.authority",expected:"finding.expectedBehavior",actual:"finding.observedBehavior",impact:"finding.impact",severity:"finding.severity",reproduction:"finding.reproduction",evidenceLimitations:"finding.limitations",surface:"finding.surface",issueReferences:"finding.issueReferences",evidence:"evidence",runIds:"evidence",artifacts:"evidence",observedAt:"observedAt",recordedAt:"observedAt",regressionCaseIds:"regressionSourceIds"},r=Object.keys(n).sort().map(e=>({field:e,disposition:t[e]?"mapped":"retained",reason:t[e]?"Projected through the explicit mapping or supported legacy fields; full original retained in sourceRecord":"No historical Cloud field represents this detail; retained in sourceRecord",...t[e]?{target:t[e]}:{}}));if(c==="bug"&&n.classification===void 0&&s.finding&&r.push({field:"classification",disposition:"mapped",target:"finding.classification",reason:`The legacy bugs collection supplies the default product_bug classification; selected value: ${s.finding.classification}`}),c==="bug"){const e=n.evidence,d=new Set(s.evidence?.map(a=>a.runId));for(const[a,i]of(e?.artifacts??[]).entries()){const f=typeof i=="string"?i:i.path,o=(typeof i=="string"?void 0:i.runId)??/^(?:\.testkit\/results\/runs\/)?([^/]+)\//.exec(f)?.[1];o&&!d.has(o)&&r.push({field:`evidence.artifacts[${String(a)}]`,disposition:"retained",reason:"Supporting artifact is outside the confirming run set; retained in sourceRecord and excluded from verification evidence"})}}return r}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { MigrationMapping, MigrationProblem, PlannedRecord } from "./model.js";
|
|
2
|
-
export declare function decodeMapping(value: unknown): MigrationMapping;
|
|
3
|
-
export declare function mapRecords(registryLossless: unknown, regressionsLossless: unknown, mapping: MigrationMapping, runTimes: ReadonlyMap<string, string>): {
|
|
4
|
-
records: PlannedRecord[];
|
|
5
|
-
problems: MigrationProblem[];
|
|
6
|
-
sourceSummary: {
|
|
7
|
-
bugs: number;
|
|
8
|
-
observations: number;
|
|
9
|
-
cases: number;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{HISTORICAL_SOURCE_LIMIT as N}from"../../../cloud/historical-contract.js";import{canonicalHistoricalJson as j,historicalDigest as D}from"../../../cloud/historical-json.js";import{array as R,object as u,plain as x,text as F}from"./files.js";import{describeFields as L}from"./mapping-dispositions.js";const $=["product_bug","testkit_failure","environment_failure","stale_assertion","unproven","intended_behavior","observation"],T=["untriaged","regression","flaky","expected","quarantined"],B=["expectedBehavior","authority","observedBehavior","impact","severity","reproduction","limitations"];function t(i,n,s){if(!i)throw Object.assign(new Error(s),{field:n})}function w(i,n,s){t(Object.keys(i).every(o=>n.includes(o)),s,"Unknown mapping field")}function A(i,n,s){return t(typeof i=="string"&&n.includes(i),s,"An explicit supported mapping is required"),i}function c(i,n,s=4096){let o;try{o=F(i,n,s)}catch{throw Object.assign(new Error(`Requires nonblank text of at most ${String(s)} bytes`),{field:n})}return t(!/[\ud800-\udfff]/u.test(o)&&Array.from(o).every(d=>d.charCodeAt(0)>=32||`
|
|
2
|
-
\r `.includes(d)),n,"Unsupported control character"),o}function V(i){return c(typeof i=="string"?i.replace(/\u001b\[[0-9;:]*m/gu,""):i,"summary")}function k(i,n){const s=c(i,n,128);return t(/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(s)&&Number.isFinite(Date.parse(s)),n,"Requires an original RFC3339 observation time or an explicit evidence rule"),s}function q(i,n,s=32){const o=R(i,n);return t(o.length<=s,n,`At most ${String(s)} entries are supported`),o.map(d=>c(d,n))}function M(i){return q(i,"issueReferences").map(n=>{const s=URL.parse(n);return t(s&&["https:","http:"].includes(s.protocol)&&!s.username&&!s.password,"issueReferences","Issue references require HTTP URLs without credentials"),n})}function E(i){const n=u(i,"relationship");w(n,["kind","targetId"],"relationship"),A(n.kind,["independent","known","duplicate"],"relationship.kind"),n.kind==="independent"?t(n.targetId===void 0,"relationship.targetId","Independent findings cannot have a relationship target"):t(/^[A-Za-z0-9_-]{1,128}$/.test(c(n.targetId,"relationship.targetId",128)),"relationship.targetId","Requires a Cloud finding ID")}export function decodeMapping(i){const n=u(x(i),"mapping");w(n,["schemaVersion","namespace","repository","findings","cases","runReceipts"],"mapping"),t(n.schemaVersion===1,"schemaVersion","Requires mapping schema 1"),c(n.namespace,"namespace",128);const s=u(n.repository,"repository");w(s,["host","owner","name"],"repository");for(const r of["host","owner","name"])c(s[r],`repository.${r}`,256);const o=u(n.findings??{},"findings");for(const[r,a]of Object.entries(o)){c(r,"finding mapping ID");const e=u(a,"finding mapping");w(e,["title","rootCauseKey","classification","observedAt","observationRule","observationRationale","verify","relationship","regressionCaseIds","issueReferences","fields"],"finding mapping");for(const f of["title","rootCauseKey"])e[f]!==void 0&&c(e[f],f,256);if(e.classification!==void 0&&A(e.classification,$,"classification"),e.observedAt!==void 0&&k(e.observedAt,"observedAt"),e.observationRationale!==void 0&&c(e.observationRationale,"observationRationale"),e.observationRule!==void 0&&A(e.observationRule,["second_confirmation","last_evidence"],"observationRule"),t(e.observedAt===void 0||e.observationRule===void 0,"observedAt","Choose an explicit time or an evidence rule, not both"),e.verify!==void 0&&t(typeof e.verify=="boolean","verify","Requires a boolean"),e.relationship!==void 0&&E(e.relationship),e.regressionCaseIds!==void 0&&q(e.regressionCaseIds,"regressionCaseIds"),e.issueReferences!==void 0&&M(e.issueReferences),e.fields!==void 0){const f=u(e.fields,"fields");w(f,[...B],"fields");for(const[l,y]of Object.entries(f))c(y,l)}}const d=u(n.cases??{},"cases");for(const r of Object.values(d)){const a=u(r,"case mapping");w(a,["sourceId","engineId","profileId","failureKey","testType","classification"],"case mapping");for(const e of["engineId","profileId"])c(a[e],e);for(const e of["sourceId","failureKey","testType"])a[e]!==void 0&&c(a[e],e,e==="sourceId"?128:4096);a.classification!==void 0&&A(a.classification,T,"case.classification")}if(n.runReceipts!==void 0)for(const[r,a]of Object.entries(u(n.runReceipts,"runReceipts")))c(r,"runId",128),c(a,"receiptId",128);return{...n,findings:o,cases:d}}function U(i,n){const s=u(i.evidence??{},"evidence"),o=q(n?s.reproducingRunIds??[]:i.runIds??[],"runIds",1e4),d=new Set(n?[]:o);for(const e of R(s.runs??[],"evidence.runs"))d.add(c(u(e,"evidence run").runId,"runId",128));const r=[];for(const e of R(n?s.artifacts??[]:i.artifacts??[],"artifacts")){const f=typeof e=="string"?{path:e}:u(e,"artifact"),l=c(f.path,"artifact.path"),y=/^(?:\.testkit\/results\/runs\/)?([^/]+)\/(.+)$/.exec(l),g=typeof f.runId=="string"?f.runId:y?.[1];if(t(g,"artifact.runId","Artifact requires an explicit run identity"),n&&!o.includes(g))continue;const v=`.testkit/results/runs/${g}/`,p=l.startsWith(v)?l.slice(v.length):l.startsWith(`${g}/`)?l.slice(g.length+1):l;t(!p.startsWith("/")&&!p.includes("\\")&&p.split("/").every(C=>C&&C!=="."&&C!==".."),"artifact.path","Artifact path must be normalized and relative"),r.push({runId:g,path:p}),d.add(g)}for(const e of o)t(d.has(e),"evidence","Confirming run requires source evidence"),r.some(f=>f.runId===e)||r.push({runId:e,path:"run.json"});const a=[...new Map(r.map(e=>[`${e.runId}\0${e.path}`,e])).values()];return t(a.length<=64,"evidence","At most 64 evidence references can be mapped"),a}function H(i,n,s,o){const d=i.observedAt??i.recordedAt;if(d!==void 0){const e=k(d,"observedAt");return t(n.observedAt===void 0||Date.parse(n.observedAt)===Date.parse(e),"observedAt","Mapping cannot replace an original observation time"),{observedAt:e,observation:{origin:"source",rationale:"Preserved original observation time"}}}if(n.observedAt!==void 0)return{observedAt:k(n.observedAt,"observedAt"),observation:{origin:"mapping",rationale:n.observationRationale??"Explicit observation time supplied by the mapping"}};t(n.observationRule,"observedAt","Missing observation time requires an explicit mapping or evidence rule");const r=[...new Set(s.map(e=>e.runId))].map(e=>({runId:e,observedAt:k(o.get(e),"evidence observation time")}));r.sort((e,f)=>Date.parse(e.observedAt)-Date.parse(f.observedAt));const a=n.observationRule==="second_confirmation"?r[1]:r.at(-1);return{observedAt:k(a?.observedAt,"observedAt"),observation:{origin:n.observationRule,rationale:n.observationRationale??(n.observationRule==="second_confirmation"?"The second distinct confirming run establishes the two-run observation":"The latest cited evidence run establishes the observation"),runIds:r.map(e=>e.runId)}}}function z(i,n){if(i==null)return;const s=typeof i=="string"?i:j(i);return c(s,n)}function P(i,n,s,o,d){const r=c(i.id,"id",128);t(/^[A-Za-z0-9_-]{1,128}$/.test(r),"id","Finding IDs must use letters, numbers, underscores or hyphens");const a=c(s.rootCauseKey??i.deduplicationKey,"rootCauseKey",256);t(i.deduplicationKey===void 0||s.rootCauseKey===void 0||s.rootCauseKey===i.deduplicationKey,"rootCauseKey","Original deduplication keys must be preserved");const e={title:c(s.title??i.title,"title",256),rootCauseKey:a,classification:A(s.classification??i.classification??(o?"product_bug":void 0),$,"classification")},f=u(i.contract??{},"contract"),l=i.reproduction&&typeof i.reproduction=="object"?Object.fromEntries(Object.entries(u(i.reproduction,"reproduction")).filter(([v])=>v!=="variants")):i.reproduction,y={expectedBehavior:f.expected??i.expected,authority:f.sources,observedBehavior:i.actual,impact:i.impact,severity:i.severity,reproduction:l,limitations:i.evidenceLimitations};for(const v of B){const p=z(s.fields?.[v]??y[v],v);p!==void 0&&(e[v]=p)}i.surface!==void 0&&(e.surface=c(i.surface,"surface")),(s.issueReferences!==void 0||i.issueReferences!==void 0)&&(e.issueReferences=M(s.issueReferences??i.issueReferences));const g=U(i,o);return{originalId:r,source:{kind:"finding",id:r,digest:D(n)},sourceRecord:n,...H(i,s,g,d),finding:e,verify:s.verify??o,relationship:s.relationship??{kind:"independent"},evidence:g,regressionSourceIds:q(s.regressionCaseIds??i.regressionCaseIds??[],"regressionCaseIds")}}function W(i,n,s){const o=c(i.id,"id");t(s,"case mapping","Historical engine and profile identity require an explicit case mapping");const d=c(s.sourceId??o,"sourceId",128),r=R(i.fingerprints,"fingerprints");t(r.length===1,"fingerprints","A case requires exactly one unambiguous fingerprint");const a=u(r[0],"fingerprint");t(s.failureKey!==void 0||a.errorIncludes===void 0&&a.metricKey===void 0,"failureKey","Selector-based fingerprints require an explicit failure key mapping");const e=[a.service,s.testType??a.type,s.engineId,s.profileId,a.path,s.failureKey??a.failureKey].map(v=>{const p=c(v,"fingerprint");return t(!p.includes("::"),"fingerprint","Fingerprint components cannot contain ::"),p}),f=u(i.lifecycle,"lifecycle"),l=k(f.firstSeenAt,"firstSeenAt"),y=k(f.lastSeenAt,"lastSeenAt");t(Date.parse(l)<=Date.parse(y),"lifecycle","Last observation cannot precede first observation");const g={id:d,service:e[0],testType:e[1],engineId:e[2],profileId:e[3],path:e[4],failureKey:e[5],fingerprint:[...e.slice(0,5),"failure",e[5]].join("::"),title:V(i.summary),classification:A(s.classification??i.classification,T,"case.classification"),state:A(i.state,["active","resolved","fixed_pending_verification"],"case.state"),firstSeenAt:l,lastSeenAt:y};if(i.issue!==void 0&&i.issue!==null){const v=u(i.issue,"issue"),p=x(v.number);t(Number.isSafeInteger(p)&&Number(p)>0&&Number(p)<=2147483647,"issue.number","Requires a valid issue number"),g.issueNumber=Number(p),g.issueRepository=c(v.repository,"issue.repository")}return{originalId:o,source:{kind:"case",id:d,digest:D(n)},sourceRecord:n,observedAt:y,observation:{origin:"lifecycle",rationale:"Preserved original lastSeenAt lifecycle time"},case:g}}export function mapRecords(i,n,s,o){const d=u(i,"registry"),r=u(n,"regressions");t(x(d.schemaVersion)===1,"registry.schemaVersion","Requires campaign schema 1"),t(x(r.schemaVersion)===3,"regressions.schemaVersion","Requires regression schema 3");const a=R(d.bugs,"bugs"),e=R(d.classifiedNonBugs,"classifiedNonBugs"),f=R(r.cases,"cases"),l=[],y=[],g=new Set,v=new Set,p=new Set;for(const[b,I]of[["case",f],["bug",a],["observation",e]])for(const K of I){let S="unknown";try{const m=u(K,b);S=c(m.id,"id"),t(Buffer.byteLength(j(K))<=N,"sourceRecord","Source record exceeds 128 KiB");const h=b==="case"?W(m,K,Object.hasOwn(s.cases,S)?s.cases[S]:void 0):P(m,K,Object.hasOwn(s.findings,S)?s.findings[S]:{},b==="bug",o),O=`${h.source.kind}\0${h.source.id}`;if(t(!g.has(O),"source.id","Duplicate source identity or alias collision"),g.add(O),h.finding&&(t(!v.has(h.finding.rootCauseKey),"rootCauseKey","Multiple findings share a root-cause key"),v.add(h.finding.rootCauseKey)),h.case){const _=h.case.fingerprint;t(!p.has(_),"fingerprint","Multiple source cases share a mapped fingerprint"),p.add(_)}h.fieldDispositions=L(m,h,b),l.push(h)}catch(m){y.push({kind:b,id:S,field:m.field??"record",detail:m instanceof Error?m.message:"Invalid source record"})}}const C=new Map(l.filter(b=>b.case).map(b=>[b.originalId,b.source.id]));for(const b of l)if(b.finding){for(const I of b.regressionSourceIds??[])C.has(I)||y.push({kind:"finding",id:b.originalId,field:"regressionCaseIds",detail:`Referenced case is not mapped: ${I}`});b.regressionSourceIds=(b.regressionSourceIds??[]).map(I=>C.get(I)??I)}return{records:l,problems:y,sourceSummary:{bugs:a.length,observations:e.length,cases:f.length}}}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import type { FindingData, Relationship } from "../../../cloud/campaigns.js";
|
|
2
|
-
import type { HistoricalCase, HistoricalManifest } from "../../../cloud/historical-contract.js";
|
|
3
|
-
export interface MigrationTarget {
|
|
4
|
-
origin: string;
|
|
5
|
-
projectId: string;
|
|
6
|
-
campaignId: string;
|
|
7
|
-
}
|
|
8
|
-
export interface RepositoryIdentity {
|
|
9
|
-
host: string;
|
|
10
|
-
owner: string;
|
|
11
|
-
name: string;
|
|
12
|
-
}
|
|
13
|
-
export interface FindingMapping {
|
|
14
|
-
title?: string;
|
|
15
|
-
rootCauseKey?: string;
|
|
16
|
-
classification?: FindingData["classification"];
|
|
17
|
-
observedAt?: string;
|
|
18
|
-
observationRule?: "second_confirmation" | "last_evidence";
|
|
19
|
-
observationRationale?: string;
|
|
20
|
-
verify?: boolean;
|
|
21
|
-
relationship?: Relationship;
|
|
22
|
-
regressionCaseIds?: string[];
|
|
23
|
-
issueReferences?: string[];
|
|
24
|
-
fields?: Partial<Pick<FindingData, "expectedBehavior" | "authority" | "observedBehavior" | "impact" | "severity" | "reproduction" | "limitations">>;
|
|
25
|
-
}
|
|
26
|
-
export interface CaseMapping {
|
|
27
|
-
sourceId?: string;
|
|
28
|
-
engineId: string;
|
|
29
|
-
profileId: string;
|
|
30
|
-
failureKey?: string;
|
|
31
|
-
testType?: string;
|
|
32
|
-
classification?: string;
|
|
33
|
-
}
|
|
34
|
-
export interface MigrationMapping {
|
|
35
|
-
schemaVersion: 1;
|
|
36
|
-
namespace: string;
|
|
37
|
-
repository: RepositoryIdentity;
|
|
38
|
-
findings: Record<string, FindingMapping>;
|
|
39
|
-
cases: Record<string, CaseMapping>;
|
|
40
|
-
runReceipts?: Record<string, string>;
|
|
41
|
-
}
|
|
42
|
-
export interface SourceFile {
|
|
43
|
-
path: string;
|
|
44
|
-
bytes: number;
|
|
45
|
-
sha256: string;
|
|
46
|
-
}
|
|
47
|
-
export interface ImportSource {
|
|
48
|
-
kind: "run" | "case" | "finding";
|
|
49
|
-
id: string;
|
|
50
|
-
digest: string;
|
|
51
|
-
}
|
|
52
|
-
export interface EvidenceFile {
|
|
53
|
-
path: string;
|
|
54
|
-
absolutePath: string;
|
|
55
|
-
bytes: number;
|
|
56
|
-
sha256: string;
|
|
57
|
-
}
|
|
58
|
-
export interface PlannedRun {
|
|
59
|
-
runId: string;
|
|
60
|
-
reusedOriginalEvidence?: boolean;
|
|
61
|
-
observedAt: string;
|
|
62
|
-
request: {
|
|
63
|
-
schemaVersion: 1;
|
|
64
|
-
evidenceFormat?: "zip-v1";
|
|
65
|
-
source: ImportSource;
|
|
66
|
-
sourceRecord: RunSourceRecord;
|
|
67
|
-
manifest: HistoricalManifest;
|
|
68
|
-
artifacts: Array<{
|
|
69
|
-
path: string;
|
|
70
|
-
originalPath: string;
|
|
71
|
-
originalBytes: number;
|
|
72
|
-
originalSha256: string;
|
|
73
|
-
transformation?: string;
|
|
74
|
-
}>;
|
|
75
|
-
};
|
|
76
|
-
files: EvidenceFile[];
|
|
77
|
-
}
|
|
78
|
-
export interface RunSourceRecord {
|
|
79
|
-
runId: string;
|
|
80
|
-
observedAt: string;
|
|
81
|
-
originalManifestSha256: string;
|
|
82
|
-
originalManifestDigest: string;
|
|
83
|
-
converterVersion: "testkit-legacy-v1";
|
|
84
|
-
schemaTuple: {
|
|
85
|
-
manifest: number;
|
|
86
|
-
run: number;
|
|
87
|
-
provenance: number;
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
export interface PlannedRecord {
|
|
91
|
-
originalId: string;
|
|
92
|
-
source: ImportSource;
|
|
93
|
-
sourceRecord: unknown;
|
|
94
|
-
observedAt: string;
|
|
95
|
-
case?: HistoricalCase;
|
|
96
|
-
finding?: FindingData;
|
|
97
|
-
relationship?: Relationship;
|
|
98
|
-
verify?: boolean;
|
|
99
|
-
evidence?: Array<{
|
|
100
|
-
runId: string;
|
|
101
|
-
path: string;
|
|
102
|
-
}>;
|
|
103
|
-
regressionSourceIds?: string[];
|
|
104
|
-
observation?: {
|
|
105
|
-
origin: "source" | "mapping" | "second_confirmation" | "last_evidence" | "lifecycle";
|
|
106
|
-
rationale: string;
|
|
107
|
-
runIds?: string[];
|
|
108
|
-
};
|
|
109
|
-
fieldDispositions?: Array<{
|
|
110
|
-
field: string;
|
|
111
|
-
disposition: "mapped" | "retained" | "blocked";
|
|
112
|
-
reason: string;
|
|
113
|
-
target?: string;
|
|
114
|
-
}>;
|
|
115
|
-
}
|
|
116
|
-
export interface MigrationProblem {
|
|
117
|
-
kind: string;
|
|
118
|
-
id: string;
|
|
119
|
-
field: string;
|
|
120
|
-
detail: string;
|
|
121
|
-
}
|
|
122
|
-
export interface MigrationPlan {
|
|
123
|
-
schemaVersion: 1;
|
|
124
|
-
converterVersion: "testkit-legacy-v1";
|
|
125
|
-
target: MigrationTarget;
|
|
126
|
-
namespace: string;
|
|
127
|
-
sourceDir: string;
|
|
128
|
-
outputDir: string;
|
|
129
|
-
sourceFiles: SourceFile[];
|
|
130
|
-
mappingFile: SourceFile;
|
|
131
|
-
inputFiles: {
|
|
132
|
-
registry: string;
|
|
133
|
-
regressions: string;
|
|
134
|
-
mapping: string;
|
|
135
|
-
};
|
|
136
|
-
sourceSummary: {
|
|
137
|
-
bugs: number;
|
|
138
|
-
observations: number;
|
|
139
|
-
cases: number;
|
|
140
|
-
runs: number;
|
|
141
|
-
};
|
|
142
|
-
repository: RepositoryIdentity;
|
|
143
|
-
campaignVersion: number;
|
|
144
|
-
minimumConfirmingRuns: number;
|
|
145
|
-
runs: PlannedRun[];
|
|
146
|
-
records: PlannedRecord[];
|
|
147
|
-
reusedRuns: Record<string, string>;
|
|
148
|
-
problems: MigrationProblem[];
|
|
149
|
-
dispositions?: Array<{
|
|
150
|
-
kind: string;
|
|
151
|
-
id: string;
|
|
152
|
-
status: "mapped" | "unmapped";
|
|
153
|
-
sourceId?: string;
|
|
154
|
-
detail?: string;
|
|
155
|
-
retainedFields?: string[];
|
|
156
|
-
fields?: NonNullable<PlannedRecord["fieldDispositions"]>;
|
|
157
|
-
}>;
|
|
158
|
-
plannedCaseIds?: Record<string, string>;
|
|
159
|
-
ready: boolean;
|
|
160
|
-
}
|
|
161
|
-
export interface PlanOptions extends MigrationTarget {
|
|
162
|
-
sourceDir: string;
|
|
163
|
-
registry: string;
|
|
164
|
-
regressions: string;
|
|
165
|
-
mapping: string;
|
|
166
|
-
outputDir: string;
|
|
167
|
-
env?: NodeJS.ProcessEnv;
|
|
168
|
-
}
|
|
169
|
-
export interface MigrationResult {
|
|
170
|
-
complete: boolean;
|
|
171
|
-
planPath: string;
|
|
172
|
-
reportPath: string;
|
|
173
|
-
importId?: string;
|
|
174
|
-
problems: MigrationProblem[];
|
|
175
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export{};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { MigrationPlan, MigrationProblem, PlanOptions } from "./model.js";
|
|
2
|
-
export declare function planMigration(options: PlanOptions): Promise<{
|
|
3
|
-
ready: boolean;
|
|
4
|
-
planPath: string;
|
|
5
|
-
problems: MigrationProblem[];
|
|
6
|
-
summary: MigrationPlan["sourceSummary"];
|
|
7
|
-
}>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import $ from"node:fs";import M from"node:path";import{walkCloudPages as _}from"../../../cloud/pagination.js";import{CloudClient as Z}from"../../../cloud/client.js";import{cloudToken as ee}from"../credentials.js";import{HistoricalClient as ie}from"../../../cloud/historical.js";import{historicalDigest as L,parseHistoricalJson as ne}from"../../../cloud/historical-json.js";import{array as z,digest as U,object as D,readBoundedFile as re,readSourceJson as G,sanitize as h,separateOutput as te,text as Q,withOutputLock as oe,writeDocument as se}from"./files.js";import{includeOriginalManifest as ae,prepareHistoricalRun as de,referencedRuns as ce,verifyCitedHashes as W}from"./evidence.js";import{decodeMapping as pe,mapRecords as fe}from"./mapping.js";export async function planMigration(t){const g=$.realpathSync(t.sourceDir),u=M.resolve(t.outputDir);te(g,u);const a=t.env??process.env,y={origin:t.origin,projectId:t.projectId,campaignId:t.campaignId},H=await ee(y.origin,a),f=new Z({...y,token:H}),q=new ie({...y,token:H});y.origin=f.origin.origin;for(const[w,d]of Object.entries(y))Q(d,w,w==="origin"?4096:128);return oe(u,async()=>{const w=M.join(u,"plan.json");if($.existsSync(w)||$.existsSync(M.join(u,"evidence")))throw new Error("Migration output already contains a plan or derived evidence; use a new output directory");const d=[],I=G(g,t.registry,d),R=G(g,t.regressions,d),x=M.resolve(g,t.mapping),O=re(x,8*1024**2),o=pe(ne(new TextDecoder("utf-8",{fatal:!0}).decode(O)));if(L(o)!==L(h(o,a)))throw new Error("Mapping contains sensitive URL or credential data; sanitize it before planning");const X=await q.repository();for(const e of["host","owner","name"])if(X[e].toLowerCase()!==o.repository[e].toLowerCase())throw new Error("Selected Cloud project repository differs from the explicit mapping");const l=await f.campaign(t.campaignId),V=await f.snapshot(),r=[],C=[],B=ce(I,R);for(const e of B)try{const i=de({sourceDir:g,outputDir:u,runId:e,mapping:o,sourceFiles:d,env:a});i.request.evidenceFormat="zip-v1",C.push(i)}catch(i){r.push({kind:"run",id:e,field:"evidence",detail:String(h(i instanceof Error?i.message:String(i),a))})}try{W(I,d),W(R,d)}catch(e){r.push({kind:"source",id:"registry",field:"sha256",detail:String(h(e instanceof Error?e.message:String(e),a))})}const s=fe(h(I,a),h(R,a),o,new Map(C.map(e=>[e.runId,e.observedAt])));r.push(...s.problems);const S=new Map(C.map(e=>[e.runId,e])),K=new Set(s.records.flatMap(e=>(e.evidence??[]).filter(i=>i.path==="manifest.json").map(i=>i.runId)));for(const e of K){const i=S.get(e);if(i)try{ae(i,g,u,d,a)}catch(n){r.push({kind:"run",id:e,field:"evidence",detail:String(h(n instanceof Error?n.message:String(n),a))})}}for(const e of s.records)for(const i of e.evidence??[])S.get(i.runId)?.files.some(n=>n.path===i.path)||r.push({kind:"finding",id:e.originalId,field:"evidence",detail:`Referenced run artifact is unavailable: ${i.runId}/${i.path}`});const F=Object.create(null);for(const[e,i]of Object.entries(o.runReceipts??{})){if(!S.has(e)){r.push({kind:"run",id:e,field:"receipt",detail:"Receipt mapping names an unreferenced run"});continue}try{const n=await f.receipt(i),c=S.get(e);if(!c)throw new Error("Missing planned run");if(n.runId!==e||n.repository.toLowerCase()!==`${o.repository.owner}/${o.repository.name}`.toLowerCase())throw new Error("Mapped receipt identity does not match original run/repository");if(n.manifestDigest!==c.request.manifest.digest){if(n.manifestDigest!==c.request.sourceRecord.originalManifestDigest)throw new Error("Mapped receipt has a different evidence representation");if(K.has(e))throw new Error("Reused original receipt cannot supply a manifest.json evidence citation");c.reusedOriginalEvidence=!0}F[e]=i}catch(n){r.push({kind:"run",id:e,field:"receipt",detail:String(h(n instanceof Error?n.message:String(n),a))})}}const b=new Set,P=new Map;await _(async e=>{const i=await f.snapshot({campaignId:t.campaignId,limit:100,...e?{cursor:e}:{}});if(i.campaignVersion!==void 0&&i.campaignVersion!==l.version)throw new Error("Campaign changed during dry-run collision reads; repeat planning");return i},e=>{for(const i of e){if(b.has(i.id))throw new Error("Cloud repeated a finding identity during planning");b.add(i.id),P.set(i.rootCauseKey,i.id)}if(b.size>1e5)throw new Error("Remote finding inventory exceeds migration bound")});for(const e of s.records){if(!e.finding)continue;if(b.has(e.source.id)){const m=(await f.finding(t.campaignId,e.source.id)).finding.import;(!m||m.namespace!==o.namespace||m.digest!==e.source.digest)&&r.push({kind:"finding",id:e.originalId,field:"identity",detail:"Existing finding conflicts with this source identity; resolve before applying"})}const n=P.get(e.finding.rootCauseKey);n&&n!==e.source.id&&r.push({kind:"finding",id:e.originalId,field:"rootCauseKey",detail:"Another remote finding uses this root-cause key"})}const A={},k=new Map,J=new Map;for(const e of V.cases)J.set(e.fingerprint,e.id);await _(e=>q.cases(e,100),e=>{for(const i of e){if(k.has(i.id))throw new Error("Cloud repeated a case identity during planning");k.set(i.id,i.fingerprint)}if(k.size>1e5)throw new Error("Remote case inventory exceeds migration bound")});for(const e of s.records){if(!e.case)continue;const i=e.case.fingerprint,n=J.get(i)??`case_${U(i).slice(0,16)}`;A[e.source.id]=n,k.has(n)&&k.get(n)!==i&&r.push({kind:"case",id:e.originalId,field:"identity",detail:"Mapped case identity collides with a different remote fingerprint"})}if((await f.campaign(t.campaignId)).version!==l.version)throw new Error("Campaign changed during dry run; repeat planning");if((await f.snapshot()).version!==V.version)throw new Error("Project cases changed during dry run; repeat planning");const N=C.length-Object.keys(F).length+s.records.length;(N<1||N>1e4)&&r.push({kind:"source",id:"inventory",field:"count",detail:"Historical import requires 1 to 10,000 inventory records"});for(const e of s.records)e.verify&&new Set(e.evidence?.map(i=>i.runId)).size<l.minimumConfirmingRuns&&r.push({kind:"finding",id:e.originalId,field:"verification",detail:`Campaign requires ${String(l.minimumConfirmingRuns)} distinct confirming runs`});s.records.some(e=>e.verify)&&l.minimumConfirmingRuns!==2&&r.push({kind:"campaign",id:t.campaignId,field:"minimumConfirmingRuns",detail:"Historical two-run verification policy differs from the selected campaign threshold"});const T=[];for(const[e,i]of[["bug",z(D(I,"registry").bugs,"bugs")],["observation",z(D(I,"registry").classifiedNonBugs,"classifiedNonBugs")],["case",z(D(R,"regressions").cases,"cases")]])for(const n of i){const c=D(n,"source record"),m=Q(c.id,"source ID"),E=s.records.find(p=>p.originalId===m&&p.source.kind===(e==="case"?"case":"finding")),j=r.filter(p=>p.id===m);T.push({kind:e,id:m,status:!E||j.length?"unmapped":"mapped",...E?{sourceId:E.source.id,retainedFields:Object.keys(c).sort(),fields:E.fieldDispositions}:{fields:Object.keys(c).sort().map(p=>({field:p,disposition:"blocked",reason:j.map(Y=>Y.detail).join("; ")||"Record could not be mapped"}))},...j.length?{detail:j.map(p=>`${p.field}: ${p.detail}`).join("; ")}:{}})}const v={schemaVersion:1,converterVersion:"testkit-legacy-v1",target:y,namespace:o.namespace,sourceDir:g,outputDir:u,sourceFiles:d.sort((e,i)=>e.path.localeCompare(i.path)),mappingFile:{path:x,bytes:O.length,sha256:U(O)},inputFiles:{registry:t.registry,regressions:t.regressions,mapping:x},sourceSummary:{...s.sourceSummary,runs:B.length},repository:o.repository,campaignVersion:l.version,minimumConfirmingRuns:l.minimumConfirmingRuns,runs:C,records:s.records,reusedRuns:F,problems:r,ready:r.length===0};return v.dispositions=T,v.plannedCaseIds=A,se(w,{...v,planDigest:L(v)}),{ready:v.ready,planPath:w,problems:r,summary:v.sourceSummary}})}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export interface MigrationProgress {
|
|
2
|
-
phase: "source-verification" | "journal-upgrade" | "packaging" | "uploading" | "receipt-accepted" | "record-admission" | "reconciliation" | "completion" | "failed";
|
|
3
|
-
completedRuns: number;
|
|
4
|
-
totalRuns: number;
|
|
5
|
-
/** Payload bytes read for upload during this attempt, including retries. */
|
|
6
|
-
bytesTransferred: number;
|
|
7
|
-
verifiedBytes?: number;
|
|
8
|
-
retries: number;
|
|
9
|
-
runId?: string;
|
|
10
|
-
operation?: string;
|
|
11
|
-
detail?: string;
|
|
12
|
-
}
|
|
13
|
-
export type MigrationProgressCallback = (progress: MigrationProgress) => void;
|
|
14
|
-
export type MigrationProgressReporter = ReturnType<typeof createMigrationProgress>;
|
|
15
|
-
export declare function createMigrationProgress(onProgress: MigrationProgressCallback | undefined, env: NodeJS.ProcessEnv): {
|
|
16
|
-
readonly current: MigrationProgress;
|
|
17
|
-
fail(error: unknown): void;
|
|
18
|
-
emit(phase: MigrationProgress["phase"], update?: Partial<Omit<MigrationProgress, "phase">>): void;
|
|
19
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{sanitize as o}from"./files.js";export function createMigrationProgress(s,a){let r={phase:"source-verification",completedRuns:0,totalRuns:0,bytesTransferred:0,verifiedBytes:0,retries:0};const t=e=>String(o(e,a)).replace(/[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/giu,"<url>").replace(/\p{Cc}/gu," ").slice(0,1024),i=e=>{r={...e,...e.runId?{runId:t(e.runId)}:{},...e.operation?{operation:t(e.operation)}:{},...e.detail?{detail:t(e.detail)}:{}},s?.(r)};return{get current(){return r},fail(e){i({...r,phase:"failed",detail:e instanceof Error?e.message:String(e)})},emit(e,n={}){i({phase:e,completedRuns:r.completedRuns,totalRuns:r.totalRuns,bytesTransferred:r.bytesTransferred,verifiedBytes:r.verifiedBytes??0,retries:r.retries,...n})}}}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { FindingData } from "../../../cloud/campaigns.js";
|
|
2
|
-
import { type HistoricalOutcome, type HistoricalCase } from "../../../cloud/historical-contract.js";
|
|
3
|
-
import type { HistoricalClient } from "../../../cloud/historical.js";
|
|
4
|
-
import type { MigrationPlan, MigrationProblem, PlannedRecord } from "./model.js";
|
|
5
|
-
import type { MigrationJournal } from "./state.js";
|
|
6
|
-
interface ReconciliationResult {
|
|
7
|
-
complete: boolean;
|
|
8
|
-
problems: MigrationProblem[];
|
|
9
|
-
items: HistoricalOutcome[];
|
|
10
|
-
counts: unknown;
|
|
11
|
-
campaignVersion: number;
|
|
12
|
-
verifyCompletion(): Promise<ReconciliationResult>;
|
|
13
|
-
}
|
|
14
|
-
interface ReconciliationOptions {
|
|
15
|
-
onVerified?: (bytes: number) => void;
|
|
16
|
-
}
|
|
17
|
-
/** Go emits empty text and null slices for omitted optional finding fields. */
|
|
18
|
-
export declare function normalizedFindingData(data: FindingData): Record<string, unknown>;
|
|
19
|
-
export declare function expectedFindingData(record: PlannedRecord, journal: MigrationJournal): FindingData;
|
|
20
|
-
export declare function reconcileState(plan: MigrationPlan, journal: MigrationJournal, client: HistoricalClient, options?: ReconciliationOptions): Promise<ReconciliationResult>;
|
|
21
|
-
export declare function compareHistoricalCase(value: HistoricalCase, expected: HistoricalCase): void;
|
|
22
|
-
export {};
|