@gfdelarue/testkit 0.6.0 → 0.6.2
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/README.md +455 -56
- package/bin/testkit-windows-owner-arm64.exe +0 -0
- package/bin/testkit-windows-owner-x64.exe +0 -0
- package/dist/app/bugs/batch.js +1 -0
- package/dist/app/bugs/ci-report.js +2 -2
- package/dist/app/bugs/ci-summary.js +3 -3
- package/dist/app/bugs/draft.js +2 -2
- package/dist/app/bugs/file.js +1 -1
- package/dist/app/bugs/guide.js +1 -1
- package/dist/app/bugs/record.js +1 -1
- package/dist/app/bugs/run-reading.js +1 -1
- package/dist/app/bugs/store.js +1 -1
- package/dist/app/campaigns/summary.js +1 -1
- package/dist/app/cleanup/index.js +1 -1
- package/dist/app/db-schema/prepare.js +1 -1
- package/dist/app/db-schema/refresh.js +1 -1
- package/dist/app/db-schema/verify.js +1 -1
- package/dist/app/destroy/index.js +1 -1
- package/dist/app/environments/config-driven.js +1 -1
- package/dist/app/environments/file-setup.js +1 -1
- package/dist/app/environments/routing.js +1 -1
- package/dist/app/environments/static-values.js +1 -1
- package/dist/app/environments/supplied.js +1 -1
- package/dist/app/lint/shared.js +1 -1
- package/dist/app/run/live-run.js +1 -1
- package/dist/app/run/orchestrator.js +1 -1
- package/dist/app/run/planning.js +1 -1
- package/dist/app/run/results.js +1 -1
- package/dist/app/run/run-finalization.js +1 -1
- package/dist/app/run/run-finisher.js +1 -0
- package/dist/app/run/setup/runtime-node-setup-protocol.js +1 -1
- package/dist/app/run/setup/runtime-node-setup-worker.js +1 -1
- package/dist/app/run/setup/runtime-node-setup.js +2 -2
- package/dist/app/status/runs.js +1 -1
- package/dist/app/stop/index.js +1 -1
- package/dist/app/tracker/index.js +1 -1
- package/dist/app/tracker/token.js +2 -2
- package/dist/app/typecheck/index.js +4 -4
- package/dist/cli/command-catalog.js +1 -1
- package/dist/cli/command-flags.js +1 -1
- package/dist/cli/commands/bug/file.js +3 -2
- package/dist/cli/framework/help.js +5 -5
- package/dist/cli/framework/parser.js +1 -1
- package/dist/cli/json-errors.js +1 -1
- package/dist/cli/renderers/bug/text.js +1 -1
- package/dist/cli/renderers/run/formatting.js +1 -1
- package/dist/config/loader/config-loader.js +1 -1
- package/dist/config/loader/discover-tests.js +1 -1
- package/dist/config/loader/discovery-result.js +1 -0
- package/dist/config/loader/index.js +1 -1
- package/dist/config/loader/runtime-isolation.js +1 -1
- package/dist/config/loader/skip-config.js +1 -1
- package/dist/config/loader/test-selection.js +1 -1
- package/dist/database/admin.js +5 -2
- package/dist/database/call-caps.js +1 -0
- package/dist/database/cleanup.js +1 -1
- package/dist/database/configured-steps.js +1 -1
- package/dist/database/constants.js +1 -1
- package/dist/database/docker-endpoint.js +1 -0
- package/dist/database/docker-metadata.js +2 -2
- package/dist/database/docker.js +5 -5
- package/dist/database/index.js +2 -1
- package/dist/database/lifecycle-cleanup.js +1 -1
- package/dist/database/local-postgres.js +4 -1
- package/dist/database/naming.js +1 -1
- package/dist/database/operation-deadline.js +1 -0
- package/dist/database/postgres/connection-check.js +1 -1
- package/dist/database/postgres/index.js +2 -2
- package/dist/database/postgres/owned-exec.js +27 -0
- package/dist/database/postgres/protocol.js +2 -2
- package/dist/database/provider.js +1 -0
- package/dist/database/roles.js +2 -2
- package/dist/database/schema-operations.js +5 -1
- package/dist/database/schema-source.js +3 -3
- package/dist/database/steps.js +1 -1
- package/dist/database/template-steps.js +4 -4
- package/dist/domain/suite-selection.js +1 -1
- package/dist/engines/adapters/evidence-files.js +1 -1
- package/dist/engines/adapters/execution.js +1 -1
- package/dist/engines/node-vitest/adapter/adapter.js +1 -1
- package/dist/engines/node-vitest/adapter/driver.js +2 -2
- package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
- package/dist/engines/playwright/adapter/adapter.js +1 -1
- package/dist/engines/playwright/adapter/browser-network-evidence.js +4 -0
- package/dist/engines/playwright/adapter/config.js +16 -6
- package/dist/engines/playwright/adapter/evidence.js +2 -1
- package/dist/engines/playwright/adapter/failure-details.js +2 -2
- package/dist/engines/playwright/adapter/reporter.js +1 -1
- package/dist/engines/playwright/runtime/browser-network.js +1 -0
- package/dist/engines/playwright/runtime/browser-traffic.js +1 -0
- package/dist/engines/playwright/runtime/chromium-environment.js +1 -0
- package/dist/engines/playwright/runtime/emulation.js +1 -0
- package/dist/engines/playwright/runtime/index.js +1 -1
- package/dist/engines/playwright/runtime/managed-test.js +1 -1
- package/dist/engines/playwright/runtime/runtime-input.js +2 -2
- package/dist/environment/lifecycle-controller.js +1 -1
- package/dist/environment/lifecycle.js +1 -1
- package/dist/environment/maintenance.js +1 -1
- package/dist/environment/run-environment.js +1 -1
- package/dist/environment/run-stopping.js +1 -1
- package/dist/environment/runtime-manager.js +1 -1
- package/dist/evidence/artifacts.js +5 -2
- package/dist/evidence/bundles.js +2 -1
- package/dist/evidence/compressed-artifacts.js +1 -0
- package/dist/evidence/environment-record.js +1 -1
- package/dist/evidence/index.js +1 -1
- package/dist/evidence/live-run-summary.js +1 -0
- package/dist/evidence/local-paths.js +1 -0
- package/dist/evidence/maintenance.js +1 -1
- package/dist/evidence/operation-timings.js +1 -1
- package/dist/evidence/redaction.js +4 -4
- package/dist/evidence/run-evidence.js +1 -0
- package/dist/evidence/setup-progress.js +1 -1
- package/dist/evidence/storage-inventory.js +1 -1
- package/dist/evidence/storage.js +2 -2
- package/dist/kernel/redaction-targets.js +1 -1
- package/dist/kernel/redaction.js +7 -4
- package/dist/process/engine-environment-keys.js +1 -0
- package/dist/process/environment-overlay.js +1 -1
- package/dist/process/environment.js +1 -1
- package/dist/process/execa-bundle.json +3 -3
- package/dist/process/files/evidence-writes.js +1 -0
- package/dist/process/files/product-dir.js +1 -1
- package/dist/process/host-process.js +1 -1
- package/dist/process/index.js +1 -1
- package/dist/process/lifecycle-fence.js +1 -1
- package/dist/process/lock-progress.js +2 -0
- package/dist/process/locks.js +4 -4
- package/dist/process/process-identity.js +1 -1
- package/dist/process/process.js +35 -35
- package/dist/process/recovery.js +1 -1
- package/dist/process/runs/manifest.js +1 -1
- package/dist/process/runs/ownership.js +1 -1
- package/dist/process/runs/stop.js +1 -1
- package/dist/process/shutdown.js +1 -1
- package/dist/process/windows-native.js +1 -1
- package/dist/repo/history.js +7 -4
- package/dist/repo/managed-git.js +1 -1
- package/dist/repo/state.js +4 -4
- package/dist/steps/environment.js +1 -1
- package/dist/toolchains/node-provisioning.js +2 -2
- package/dist/tracker/gitea.js +1 -1
- package/dist/tracker/github.js +1 -1
- package/dist/tracker/http.js +1 -1
- package/dist/tracker/ownership.js +1 -0
- package/dist/tracker/redaction.js +1 -1
- package/dist/tracker/responses.js +1 -1
- package/dist/types/playwright.d.ts +38 -2
- package/dist/types/workspace.d.ts +4 -4
- package/docs/bug-records.md +16 -10
- package/docs/engines.md +52 -4
- package/docs/release-notes.md +749 -0
- package/package.json +14 -2
- package/testkit-config-schema.md +36 -42
- package/dist/config/loader/task-policy.js +0 -1
- package/dist/environment/lifecycle-artifacts.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{waitForPreparedRunStop as
|
|
1
|
+
import{waitForPreparedRunStop as S,drainRunServices as b,requestProcessStop as C,runManifestServiceKey as _,sameRunManifestIncarnation as g}from"../process/runs/stop.js";import{observeProcessCreationIdentity as m,captureProcessCreationIdentity as D}from"../process/index.js";import{normalizeOperationReason as P}from"../domain/operation-reason.js";import{RUN_MAINTENANCE_STOP_REQUEST_SCHEMA_VERSION as N}from"../process/runs/schema.js";import{requireProductLifecycleFenceProductDir as v,withProductLifecycleFence as y}from"../process/lifecycle-fence.js";import{listRunManifests as q,readRunMaintenanceStopRequest as E,removeRunMaintenanceStopRequest as R,removeRunManifestFile as F,requireSafeRunId as T,writeRunMaintenanceStopRequest as L,persistRunShutdownDeadlines as O}from"../process/runs/manifest.js";export async function stopRunById(r,t,n){const o=T(t,"Testkit run id"),e=await y(r,i=>A(i,o,n));if(e===null)return!1;const s=await S(e);return await M(r,e,s,n.evidence),!0}export async function stopRunManifest(r,t,n){const o=T(t.runId,"Testkit run manifest runId"),e=await y(r,i=>A(i,o,n,t));if(e===null)return{runId:o,pid:t.pid,reason:P(n.reason,"stopped"),stopped:!0};const s=await S(e);return M(r,e,s,n.evidence)}function A(r,t,n,o=null){const e=v(r);let s=P(n.reason,"stopped");const i=q(e).find(a=>a.runId===t);if(i===void 0){if(o!==null&&o.pid>0&&o.processIdentity===null&&m(o.pid,o.processIdentity)==="unknown")throw new Error(`Cannot establish the creation identity of Testkit run process ${o.pid.toString()}; refusing unsafe stop`);return o!==null&&o.pid===0&&(n.evidence.runStopped(e,{runId:t,reason:s}),R(e,t)),null}if(o!==null&&!g(i,o))throw new Error(`Testkit run ${t} ownership changed before the stop request could be published`);const u=i.pid,f=u>0?m(u,i.processIdentity):"different";if(u===process.pid&&f==="matches")throw new Error(`Cannot stop active Testkit run ${t} from inside its own controller process`);if(f==="unknown")throw new Error(`Cannot establish the creation identity of Testkit run process ${u.toString()}; refusing unsafe stop`);let d=null,p=new Date().toISOString(),l=null;const I=E(e,t),c=I!==null&&I.targetIncarnationId===i.incarnationId&&I.targetPid===u&&I.targetProcessIdentity===i.processIdentity?I:null;c!==null&&(s=c.reason,p=c.requestedAt);let h=null;const w=()=>{if(h!==null)return h;const a=V(i,p);return f==="different"&&O(e,a),h=a,a};if(f==="matches"){if(l=i.processIdentity,l===null)throw new Error(`Testkit run process ${u.toString()} has no creation identity; refusing unsafe stop`);if(c!==null&&m(c.coordinatorPid,c.coordinatorProcessIdentity)!=="different")return d=c.coordinatorProcessIdentity,{controllerActive:!0,coordinatorProcessIdentity:d,graceMs:n.graceMs||2e3,manifest:w(),requestedAt:p,pid:u,productDir:e,reason:s,runId:t,targetProcessIdentity:l};if(d=D(process.pid),d===null)throw new Error("Could not capture the maintenance coordinator process identity");L(e,{coordinatorPid:process.pid,coordinatorProcessIdentity:d,reason:s,requestedAt:p,runId:t,schemaVersion:N,targetIncarnationId:i.incarnationId,targetPid:u,targetProcessIdentity:l}),w();try{process.platform!=="win32"&&C(u,l,"SIGINT")}catch(a){throw R(e,t),a}}return{controllerActive:f==="matches",coordinatorProcessIdentity:d,graceMs:n.graceMs||2e3,manifest:w(),requestedAt:p,pid:u,productDir:e,reason:s,runId:t,targetProcessIdentity:l}}async function M(r,t,n,o){for(;;){const e=await y(r,s=>$(s,t,n,o));if(e.result!==null)return e.result;await b(e.services,n)}}function $(r,t,n,o){const e=v(r),s=q(e).find(u=>u.runId===t.runId);if(s===void 0||!g(s,t.manifest))return U(e,t),{result:k(t),services:null};const i=s.services.filter(u=>!n.has(_(u)));return i.length>0?{result:null,services:i}:(o.runStopped(e,{runId:t.runId,reason:t.reason}),F(e,t.runId),{result:k(t),services:null})}function k(r){return{runId:r.runId,pid:r.pid,stopped:!0,reason:r.reason}}function U(r,t){const n=E(r,t.runId);n===null||n.coordinatorPid!==process.pid||n.coordinatorProcessIdentity!==t.coordinatorProcessIdentity||n.targetIncarnationId!==t.manifest.incarnationId||n.targetPid!==t.pid||n.targetProcessIdentity!==t.targetProcessIdentity||R(r,t.runId)}function V(r,t){return{...r,services:r.services.map(n=>n.shutdownTimeoutMs===void 0?n:{...n,shutdownDeadlineAt:Math.min(n.shutdownDeadlineAt??Number.MAX_VALUE,Date.parse(t)+n.shutdownTimeoutMs)})}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildRuntimeIds as
|
|
1
|
+
import{buildRuntimeIds as M}from"../domain/runtime-identity.js";import{formatError as h}from"../kernel/error-serialization.js";import{cleanupRuntimeInstanceContext as S,createRuntimeInstanceContext as U,ensureRuntimeInstanceReady as N}from"./runtime-contexts.js";import{timeOperation as A}from"../evidence/operation-timings.js";import{createReadyEnvironment as K}from"./ready-environment.js";import{buildRuntimeLabel as T}from"./template.js";export function createRuntimeManager({productDir:e,graphs:n,lifecycle:a,runtimeOptions:r={}}){const i=new Map(n.map(t=>[t.key,t])),u=new Map,o=B(n),m=new Map,d={...r,provisionedSharedDatabases:new Set};let R=!1;return{canAcquire(t){if(m.has(t.graphKey))return!1;const s=C(u,i,t);return q(s,t,o)!==null},async acquire(t,s={}){const l=C(u,i,t);if(a.isStopRequested())throw a.signal.reason||new Error("testkit run interrupted");const g=t.leaseId,c=await F(r,s,"testkit.runtime.slot_claim",{summary:"claim runtime slot",scope:{graphKey:t.graphKey,taskId:t.id,serviceName:t.serviceName||null,targetName:t.targetName||null},metadata:{decision:"claim",resourceCost:v(t).toString()}},()=>{const p=$(l,t,o);if(!p)throw new Error(`Task ${t.id.toString()} was claimed before runtime capacity was available`);return p});R||=c.graph.runtimeConfigs.some(p=>p.testkit.database);try{const p=T(c.graph.dirName,c.runtimeId);s.onRuntimeAssigned?.({runtimeId:c.runtimeId,services:c.graph.runtimeConfigs.map(({name:L})=>({serviceName:L,runtimeLabel:p}))});const y=await E(c,e,t,a,d,s);return a.signal.throwIfAborted(),{...K(y,g),resourceCost:v(t),slot:c,context:y}}catch(p){throw x(c,v(t)),c.context||I(c,o),await f(c,a,o),a.isStopRequested()||b(m,t.graphKey,h(p)),p}},async release(t,s={}){t?.slot&&(s.invalidate&&(t.slot.draining=!0),t.released||(x(t.slot,t.resourceCost),t.released=!0),t.slot.draining&&t.slot.activeLeaseCount===0&&await f(t.slot,a,o))},async cleanupAll(){const t=[];for(const g of u.values())for(const c of g.slots){c.draining=!0;try{await f(c,a,o)}catch(p){t.push({slot:c,error:p})}}const s=t.length>0?new AggregateError(t.map(g=>g.error),`Failed to clean up ${t.length.toString()} runtime ${t.length===1?"slot":"slots"}: ${t.map(g=>`${g.slot.runtimeId}: ${h(g.error)}`).join("; ")}`):null,l=R?await G(e):null;if(s!==null&&l!==null)throw new AggregateError([s,l.error],`${s.message}; ${h(l.error)}`);if(s!==null)throw s;if(l!==null)throw l.error},getStats(){return[...u.values()].map(t=>({graphKey:t.slots[0]?.graph.key||null,targetNames:[...t.slots.at(0)?.graph.targetNames??[]],maxConcurrentTasks:Number.isFinite(t.slots[0]?.graph.maxConcurrentTasks)?t.slots[0]?.graph.maxConcurrentTasks??null:null,runtimeCount:t.slots.length,runtimes:t.slots.map(s=>({runtimeId:s.runtimeId,peakLeaseCount:s.peakLeaseCount,peakResourceUnits:s.peakResourceUnits,resourceReservations:s.resourceReservations.map(l=>({...l}))})),resourceBudgets:[...o.values()].filter(s=>t.slots.some(l=>l.graph.resourceReservations.some(g=>g.key===s.key))).map(s=>({key:s.key,capacity:s.capacity,reserved:s.reserved,available:s.available,active:s.active,peakActive:s.peakActive}))})).sort((t,s)=>(t.graphKey||"").localeCompare(s.graphKey||""))},getGraphFailure(t){return m.get(t)??null},getGraph(t){return i.get(t)||null},markGraphFailed(t,s){b(m,t,s)}}}function C(e,n,a){const r=e.get(a.graphKey);if(r)return r;const i=n.get(a.graphKey);if(!i)throw new Error(`Unknown graph "${a.graphKey}"`);const o={slots:M(i.instanceCount).map(m=>({graph:i,runtimeId:m,context:null,activeLeaseCount:0,activeResourceUnits:0,resourceReservations:[],peakLeaseCount:0,peakResourceUnits:0,draining:!1}))};return e.set(a.graphKey,o),o}function $(e,n,a){const r=v(n),i=e.slots.filter(o=>!o.draining);if(i.length===0)return null;const u=[...i].filter(o=>w(o,r)&&k(o,a)).sort((o,m)=>o.activeResourceUnits-m.activeResourceUnits||o.activeLeaseCount-m.activeLeaseCount||o.runtimeId.localeCompare(m.runtimeId)).at(0);return u?(P(u,a),u.activeLeaseCount+=1,u.activeResourceUnits+=r,u.peakLeaseCount=Math.max(u.peakLeaseCount,u.activeLeaseCount),u.peakResourceUnits=Math.max(u.peakResourceUnits,u.activeResourceUnits),u):null}function x(e,n){e.activeLeaseCount=Math.max(0,e.activeLeaseCount-1),e.activeResourceUnits=Math.max(0,e.activeResourceUnits-n)}async function E(e,n,a,r,i,u={}){e.context||(e.context=U(e.runtimeId,e.graph,n),r.trackGraphContext(e.context));const o=i.operationTimingRegistry||null,m=o?.start({name:"testkit.runtime.ensure",kind:"runtime",summary:"ensure runtime instance ready",parentId:u.operationTimingParentId||null,scope:{graphKey:e.graph.key,runtimeId:e.runtimeId,serviceName:a.serviceName,targetName:a.targetName}})??null;try{await N(e.context,r,i),o?.finish(m,{status:"passed"})}catch(d){throw o?.finish(m,{status:"failed",error:h(d)}),d}return e.context}function F(e,n,a,r,i){return A(e.operationTimingRegistry||null,{name:a,kind:"runtime",summary:r.summary||null,parentId:n.operationTimingParentId||null,scope:r.scope||null,metadata:r.metadata||null},i)}async function G(e){try{const{stopRetainedLocalPostgres:n}=await import("../database/index.js");return await n(e),null}catch(n){return{error:n}}}async function f(e,n,a){!e.context||e.activeLeaseCount>0||(await S(e.context,n),I(e,a),e.context=null,e.draining=!1)}function q(e,n,a){const r=v(n);return e.slots.find(i=>!i.draining&&w(i,r)&&k(i,a))||null}function w(e,n){return e.activeResourceUnits+n<=e.graph.maxConcurrentTasks}function B(e){const n=new Map;for(const a of e)for(const r of a.resourceReservations){const i=n.get(r.key)||{key:r.key,capacity:r.capacity||0,reserved:r.reserved||0,available:r.available||Math.max(0,(r.capacity||0)-(r.reserved||0)),active:0,peakActive:0};i.capacity=Math.max(i.capacity,r.capacity||0),i.reserved=Math.max(i.reserved,r.reserved||0),i.available=Math.max(0,i.capacity-i.reserved),n.set(r.key,i)}return n}function k(e,n){return e.resourceReservations.length>0?!0:e.graph.resourceReservations.every(a=>{const r=n.get(a.key);return r?r.active+a.perRuntime<=r.available:!0})}function P(e,n){if(e.resourceReservations.length>0)return;const a=[];for(const r of e.graph.resourceReservations){const i=n.get(r.key);i&&(i.active+=r.perRuntime,i.peakActive=Math.max(i.peakActive,i.active),a.push({key:r.key,units:r.perRuntime,services:r.services}))}e.resourceReservations=a}function I(e,n){for(const a of e.resourceReservations){const r=n.get(a.key);r&&(r.active=Math.max(0,r.active-a.units))}e.resourceReservations=[]}function b(e,n,a){!n||e.has(n)||e.set(n,a)}function v(e){return e.resourceCost??1}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import{isPathWithin as
|
|
2
|
-
|
|
1
|
+
import{isPathWithin as V}from"../process/files/paths.js";import{isUnknownArray as q,isUnknownRecord as A}from"../kernel/unknown-values.js";import{readRunDocument as J}from"./read-run.js";import l from"node:fs";import c from"node:path";import"../engines/api/index.js";import"../domain/identifiers.js";import{normalizeArtifactArchiveConfig as W}from"./artifact-config.js";import{applyRunBundleRetention as X,publishRunBundle as G,RefusedArtifactsError as z,sealRunBundle as H}from"./bundles.js";import{RETENTION_DECISION_SCHEMA_VERSION as Q}from"./contracts.js";import{redactedRunValue as v,runRedaction as Y}from"./redaction.js";import{applyTimingUpdates as Z,createEmptyTimings as N,normalizeTimings as tt}from"./timing.js";import{normalizeOptionalNonEmptyString as O,normalizeStringArray as et,readOptionalProperty as F,requireObjectRecord as rt}from"./artifact-validation.js";import{errorMessage as nt,serializeJson as L,writeJsonFileAtomicWithRetry as I,readJsonFileOrNull as b}from"../process/files/json-file.js";import{recordEvidenceWrite as x}from"../process/files/evidence-writes.js";import{recordWork as D}from"../kernel/work-metrics.js";import{copyRegularFileSecurely as it}from"./secure-file-copy.js";import{hasErrorCode as P,serializeError as st}from"../kernel/error-serialization.js";import{redactedPrefix as at}from"../kernel/redaction.js";import{posixPath as k}from"../process/files/paths.js";const _="timings.json",S="artifacts",ot="logs",ct="setup",R="live.json",ut=1e4,C=String;export function writeRunArtifact(t,e,r={},n={}){const i=e,s=c.join(t,".testkit","results");l.mkdirSync(s,{recursive:!0});const o=i.archive,a=r.archiveConfig??W(F(o,"request")||{}),u=r.declaredEnvironmentKeys??et(F(o,"declaredEnvironmentKeys")||[],"run artifact declaredEnvironmentKeys"),f=n.sealRunBundle??H,p=n.publishRunBundle??G,d=n.applyRunBundleRetention??X,y=n.writeLatestProjection??I,m=f(t,i,{env:r.env,archiveConfig:a,declaredEnvironmentKeys:u,recordRefusals:U=>{dt(s,i.run.id,U)},redactKeys:r.redactKeys,redactionTargets:r.redactionTargets}),h=lt(t,m.archive.runId,a.publication,p),w=ft(t,m.archive.runId,a.retention,d),{targets:g}=Y({env:r.env,redactKeys:r.redactKeys,redactionTargets:r.redactionTargets}),E=v(i.finalizationFailure,g),K={...m,...E===void 0?{}:{finalizationFailure:E},run:h.status==="failed"&&h.required===!0||m.archive.cleanup?.status==="failed"||E!==void 0&&m.run.status!=="interrupted"?{...m.run,status:"failed"}:m.run,archive:{...m.archive,publication:v(h,g),retention:v(w,g)}};return mt(s,K,y,g)}function lt(t,e,r,n){try{return n(t,e,r)}catch(i){return{bytesUploaded:0,configured:r!==null,error:j(i),recordedAt:new Date().toISOString(),required:r?.required??!1,runId:e,schemaVersion:1,status:"failed",target:r===null?null:c.resolve(t,r.directory)}}}function ft(t,e,r,n){try{return n(t,e,r)}catch(i){return{currentRunId:e,decidedAt:new Date().toISOString(),error:j(i),garbageCollection:{failures:[],reclaimedBytes:0,removedObjects:0,status:"not_started"},policy:{maxAgeDays:r.maxAgeDays,maxRuns:r.maxRuns,preserveSuites:[...r.preserveSuites]},protectedRunIds:[e],removed:[],removalErrors:[],schemaVersion:Q,status:"failed"}}}function mt(t,e,r,n){const i=c.join(t,"latest.json"),s={path:k(c.relative(c.dirname(t),i)),status:"complete"},o={...e,archive:{...e.archive,latest:s}};try{return x(i),r(i,o),o}catch(a){return{...o,archive:{...o.archive,latest:{...s,error:v(j(a),n),status:"failed"}}}}}function j(t){try{return nt(t)}catch{return"Unknown post-seal archive operation failure"}}export function writeLiveRunArtifact(t,e){const r=e,n=c.join(t,".testkit","results");l.mkdirSync(n,{recursive:!0});const i=c.join(n,R);x(i),I(i,r)}export function finalizeLiveRunArtifact(t,e={}){const r=ht(e),n=c.join(t,".testkit","results",R),i=b(n);if(i===null)return!1;const s=pt(i,r);if(!s)return!1;const o={redactKeys:e.redactKeys,redactionTargets:e.redactionTargets};try{writeRunArtifact(t,s,o)}catch(a){if(!(a instanceof z))throw a;writeRunArtifact(t,M(s,a),o)}return!0}function dt(t,e,r){const n=c.join(t,R),i=b(n);e===null||!A(i)||!A(i.run)||i.run.id!==e||I(n,M(i,new z(r)))}function M(t,e){const r=st(e),n="finalizationFailure"in t?t.finalizationFailure:void 0,i=A(n)&&typeof n.message=="string"?n.message.split(`
|
|
2
|
+
`):[];for(const s of r.message.split(`
|
|
3
|
+
`))i.includes(s)||i.push(s);return{...t,finalizationFailure:{...r,message:i.join(`
|
|
4
|
+
`)}}}function pt(t,e){const r=J(t,"live run artifact"),n=r.run.id||null;if(e.runId&&n&&n!==e.runId)return null;const i=gt(e.finishedAt),s=new Date(i).toISOString(),o=At(r.run.startedAt),a=o===null?r.run.durationMs:Math.max(0,i-o);return{...r,generatedAt:s,run:{...r.run,id:n??e.runId,status:e.status,finishedAt:s,durationMs:a,interruptReason:e.reason},timings:yt(r.timings,s,e.reason)}}function yt(t,e,r){if(t==null)return t;const n=rt(t,"live run artifact.timings"),i=n.operations;if(i==null)return n;if(!q(i))throw new Error("live run artifact.timings.operations must be an array");return{...n,operations:i.map(s=>!A(s)||s.status!=="running"?s:{...s,status:"cancelled",finishedAt:O(s.finishedAt,"live run operation.finishedAt")??e,message:O(s.message,"live run operation.message")??r})}}function ht(t){return{runId:t.runId??null,reason:t.reason??"run interrupted",status:t.status??"interrupted",finishedAt:t.finishedAt??Date.now()}}function gt(t){if(t instanceof Date&&Number.isFinite(t.getTime()))return t.getTime();if(typeof t=="number"&&Number.isFinite(t))return t;if(typeof t=="string"){const e=Date.parse(t);if(Number.isFinite(e))return e}return Date.now()}function At(t){if(typeof t!="string")return null;const e=Date.parse(t);return Number.isFinite(e)?e:null}export function resetResultArtifacts(t){l.rmSync(c.join(t,".testkit","results",S),{recursive:!0,force:!0}),l.rmSync(c.join(t,".testkit","results",ot),{recursive:!0,force:!0}),l.rmSync(c.join(t,".testkit","results",ct),{recursive:!0,force:!0}),l.rmSync(c.join(t,".testkit","results",R),{force:!0})}export function persistTaskArtifacts(t,e,r){if(r.length===0)return[];const n=T(e.serviceName||"service"),i=$(t,n);return r.map((s,o)=>{const a=vt(s),u={schemaVersion:2,source:"testkit-runtime-artifact",service:e.serviceName,suite:{engineId:e.engineId,key:e.suiteKey,name:e.suiteName,profileId:e.profileId,testType:e.testType},file:e.file,taskId:e.id,index:o,name:a.name,kind:a.kind,summary:a.summary,contentType:a.contentType,emittedAt:a.emittedAt,data:a.data},f=`${L(u,"runtime artifact payload")}
|
|
5
|
+
`,p=B(i,e.id,o,a.name,".json",y=>{l.writeFileSync(y,f,{flag:"wx"}),D("evidence.filesWritten")}),d=c.join(".testkit","results",S,n,p);return{name:u.name,kind:u.kind,summary:u.summary,contentType:u.contentType,path:k(d)}})}export function persistTaskFileArtifacts(t,e,r,n={}){if(r.length===0)return[];const i=T(e.serviceName||"service"),s=$(t,i);return r.map((o,a)=>{const u=St(o),f=c.resolve(u.path),p=wt(f,u.contentType),d=[],y=B(s,e.id,a,u.name,p,w=>{d.push(it(f,w,{...n.maxBytes===void 0?{}:{maxBytes:n.maxBytes},...n.sourceRoot===void 0?{}:{sourceRoot:n.sourceRoot},...n.sizeLimitDescription===void 0?{}:{sizeLimitDescription:n.sizeLimitDescription}})),D("evidence.filesWritten")}),m=d.at(0);if(m===void 0)throw new Error("File artifact copy completed without a result");n.copyResults?.push(m);const h=c.join(".testkit","results",S,i,y);return{name:u.name,kind:u.kind,summary:u.summary,contentType:u.contentType??Et(f),path:k(h)}})}export function persistTaskOutputArtifacts(t,e,r){if(r.length===0)return[];const n=[];for(const i of r){const s=i.text;s.trim().length!==0&&n.push({name:i.name||"task-output",kind:i.kind||"runtime.output",summary:i.summary||Tt(s),contentType:i.contentType||"text/plain",emittedAt:i.emittedAt||new Date().toISOString(),data:{stream:i.stream||null,text:s}})}return persistTaskArtifacts(t,e,n)}export function loadTimings(t){const e=c.join(t,".testkit",_);if(!l.existsSync(e))return N();try{const r=JSON.parse(l.readFileSync(e,"utf8"));return tt(r)}catch{return N()}}export function saveTimings(t,e,r){if(r.length===0)return;const n=Z(e,r),i=c.join(t,".testkit");l.mkdirSync(i,{recursive:!0});const s=c.join(i,_);x(s),l.writeFileSync(s,L(n,"timings"))}function vt(t){return{name:t.name,kind:t.kind||null,summary:t.summary||null,contentType:t.contentType||"application/json",emittedAt:t.emittedAt||null,data:t.data}}function St(t){return{path:t.path,name:t.name||"artifact",kind:t.kind||null,summary:t.summary||null,contentType:t.contentType||null}}function Rt(t,e,r,n,i=0){const s=i===0?"":`-${(i+1).toString().padStart(2,"0")}`;return`task-${T(t)}-${(e+1).toString().padStart(2,"0")}-${T(r)}${s}${n}`}function B(t,e,r,n,i,s){for(let o=0;o<ut;o+=1){const a=Rt(e,r,n,i,o),u=c.join(t,a);try{return s(u),a}catch(f){if(P(f,"EEXIST"))continue;throw l.rmSync(u,{force:!0}),f}}throw new Error(`Could not allocate a unique artifact path for task ${C(e)} and artifact ${n}`)}function $(t,e){const r=c.resolve(t);if(!l.statSync(r).isDirectory())throw new Error("Artifact product directory must be a directory");const i=l.realpathSync(r);let s=r;for(const o of[".testkit","results",S,e]){s=c.join(s,o);try{l.mkdirSync(s)}catch(f){if(!P(f,"EEXIST"))throw f}const a=l.lstatSync(s);if(a.isSymbolicLink()||!a.isDirectory())throw new Error(`Artifact output directory ${JSON.stringify(s)} must be a real directory below productDir`);const u=l.realpathSync(s);if(!V(i,u))throw new Error(`Artifact output directory ${JSON.stringify(s)} escapes productDir`)}return s}function T(t){return C(t).trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"artifact"}function Tt(t){const e=t.split(/\r?\n/u).map(r=>r.trim()).find(Boolean);return e?at(e,120):"captured task output"}function wt(t,e){const r=c.extname(t);if(r)return r;const n=(e??"").split(";",1)[0].trim().toLowerCase();return n==="text/markdown"?".md":n==="text/plain"?".txt":n==="application/json"?".json":n==="application/zip"?".zip":n==="image/png"?".png":n==="image/jpeg"?".jpg":n==="text/html"?".html":n==="video/webm"?".webm":".artifact"}function Et(t){const e=c.extname(t).toLowerCase();return e===".md"||e===".markdown"?"text/markdown":e===".txt"||e===".log"?"text/plain":e===".json"?"application/json":"application/octet-stream"}
|
package/dist/evidence/bundles.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{recordWork as J}from"../kernel/work-metrics.js";import{isUnknownRecord as A,optionalString as H}from"../kernel/unknown-values.js";import{posixPath as K}from"../process/files/paths.js";import{readRunProvenance as oe}from"./read-provenance.js";import{parseRunBundleArtifact as ae,parseRunBundleManifest as Y,readRunBundleArtifact as ce}from"./read-bundle.js";import{readRunDocument as ue}from"./read-run.js";import{runModel as G}from"./run-model.js";import le from"crypto";import a from"fs";import u from"path";import{redactRunText as fe,redactRunValue as me,runRedaction as de}from"./redaction.js";import{BUNDLE_SCHEMA_VERSION as Q,OBJECTS_DIRNAME as X,PROVENANCE_SCHEMA_VERSION as pe,RETENTION_DECISION_SCHEMA_VERSION as he,RUNS_DIRNAME as ye}from"./contracts.js";import{bundleRelativePath as $,hashFile as z,normalizeRunId as I,optionalStringArray as D,optionalUnknownArray as ge,readJsonFile as x,requirePublicationPathOutsideResults as _,requireRunId as ve,runIncludesFile as Se,resolveManifestEntryPath as Z,resolveRunBundleDirectory as P,resolveRunsRoot as N,resultsRoot as T,sanitizeSegment as Re,sha256 as O,stableJson as M,treeBytes as ee,walkFiles as te,writeBufferAtomic as je,writeJson as C}from"./storage.js";import{resolveCiProvenance as be}from"./ci-provenance.js";import{errorMessage as B}from"../kernel/error-serialization.js";const U=["artifacts","logs","setup"],we=["package.json","package-lock.json","npm-shrinkwrap.json","pnpm-lock.yaml","yarn.lock","testkit.config.ts","testkit.config.mts","testkit.config.js","testkit.config.mjs"];export function sealRunBundle(e,n,t={},r={}){const s=n,o=ve(s),i=N(e,!0),c=u.dirname(i),f=u.join(i,o),l=r.cleanupActiveRunResults??_e,p=loadVerifiedRunBundleArtifactIfExists(e,o);if(p!==null)return{...p,archive:{...p.archive,cleanup:l(c)}};const m=u.join(i,`.${o}.${String(process.pid)}.${String(Date.now())}.${le.randomBytes(4).toString("hex")}.tmp`);a.mkdirSync(m,{recursive:!0});try{const S=de(t),y=Ie({productDir:e,resultsDir:c,runId:o,tempDir:m,redaction:S}),h=Be(s,o,S.targets),v=ue(h.artifact,"run artifact");y.redaction.structuredReplacements=h.replacements,y.redaction.replacements+=h.replacements;const g=ke(e,v,o,{...t,redaction:y.redaction}),j={schemaVersion:Q,runId:o,status:"complete",canonicalRunPath:$(o,"run.json"),manifestPath:$(o,"manifest.json"),provenancePath:$(o,"provenance.json"),integrity:"sha256",publication:{configured:!!t.archiveConfig?.publication,status:t.archiveConfig?.publication?"pending":"not_configured"},redaction:y.redaction};v.archive=j,C(u.join(m,"run.json"),v),C(u.join(m,"provenance.json"),g);const b=Ae(m),V=O(Buffer.from(M(b))),E={schemaVersion:Q,runId:o,createdAt:new Date().toISOString(),algorithm:"sha256",files:b,storage:{fileCount:b.length,logicalBytes:b.reduce((d,R)=>d+R.bytes,0),objectBytesWritten:y.objectBytesWritten,deduplicatedBytes:y.deduplicatedBytes},redaction:y.redaction,digest:V};return C(u.join(m,"manifest.json"),E),a.renameSync(m,f),{...v,archive:{...j,cleanup:l(c),manifestDigest:E.digest,storage:E.storage}}}catch(S){try{a.rmSync(m,{recursive:!0,force:!0})}catch{}try{Pe(e)}catch{}throw S}}export function loadVerifiedRunBundleArtifactIfExists(e,n){const t=I(n),r=P(e,t);if(!a.existsSync(r))return null;const s=verifyRunBundle(e,t);if(!s.ok)throw new Error(`Immutable run bundle ${t} already exists but failed integrity verification: ${s.errors.join("; ")}`);return loadRunBundleArtifact(e,t)}export function loadRunBundleArtifact(e,n){return ne(e,n).artifact}function ne(e,n){const t=I(n),r=ce(e,t),s=loadRunBundleManifest(e,t);return{manifest:s,artifact:{...r,archive:{...r.archive,manifestDigest:s.digest,storage:s.storage}}}}export function loadRunBundleManifest(e,n){const t=I(n),r=P(e,t),s=u.join(r,"manifest.json");if(!a.existsSync(s))throw new Error(`No immutable Testkit run manifest found for ${t}`);return Y(x(s),s,t)}export function listRunBundles(e,n={}){return q(e,n).map(({artifact:t,manifest:r,...s})=>s)}function q(e,n={}){const t=N(e,!1);if(!a.existsSync(t))return[];const r=a.readdirSync(t,{withFileTypes:!0});return J("artifacts.runDirectoriesRead",1),J("artifacts.runEntriesVisited",r.length),r.filter(o=>o.isDirectory()&&!o.name.startsWith(".")).map(o=>{try{const{artifact:i,manifest:c}=ne(e,o.name);return n.file&&!Se(G(i),n.file)?null:{artifact:i,manifest:c,runId:o.name,status:i.run.status,startedAt:i.run.startedAt,finishedAt:i.run.finishedAt??null,manifestDigest:i.archive.manifestDigest??null,canonicalRunPath:i.archive.canonicalRunPath}}catch{return null}}).filter(o=>o!==null).sort((o,i)=>(i.finishedAt??i.startedAt??"").localeCompare(o.finishedAt??o.startedAt??""))}export function verifyRunBundle(e,n){const t=I(n);try{return F(P(e,t),t)}catch(r){return{ok:!1,runId:t,errors:[B(r)],checkedFiles:0}}}export function publishRunBundle(e,n,t,r={}){const s=I(n),o=r.writePublicationReceipt??$e;if(!t)return o(e,s,{status:"not_configured",required:!1,target:null,bytesUploaded:0});const i=u.resolve(e,t.directory);try{_(e,i)}catch(m){return o(e,s,{status:"failed",required:t.required,target:i,error:B(m),bytesUploaded:0})}const c=Re(u.basename(e)),f=u.join(i,c),l=u.join(f,s),p=u.join(f,`.${s}.${String(process.pid)}.${String(Date.now())}.tmp`);try{const m=P(e,s);if(a.mkdirSync(f,{recursive:!0}),_(e,f),a.existsSync(l)){const h=a.lstatSync(l);if(h.isSymbolicLink()||!h.isDirectory())throw new Error("Publication target must be a regular bundle directory");_(e,l);const v=F(m,s);if(!v.ok)throw new Error(`Source run bundle failed integrity verification: ${v.errors.join("; ")}`);const g=F(l,s);if(!g.ok)throw new Error(`Publication target failed integrity verification: ${g.errors.join("; ")}`);if(v.manifestDigest!==g.manifestDigest)throw new Error("Publication target already exists with a different manifest digest");return o(e,s,{status:"complete",required:t.required,target:l,manifestDigest:v.manifestDigest,reused:!0,bundleBytes:ee(m),bytesUploaded:0})}const S=ee(m);a.cpSync(m,p,{recursive:!0,preserveTimestamps:!0});const y=F(p,s);if(!y.ok)throw new Error(y.errors.join("; "));return a.renameSync(p,l),o(e,s,{status:"complete",required:t.required,target:l,manifestDigest:y.manifestDigest,reused:!1,bundleBytes:S,bytesUploaded:S})}catch(m){return a.rmSync(p,{recursive:!0,force:!0}),o(e,s,{status:"failed",required:t.required,target:l,error:B(m),bytesUploaded:0})}}export function applyRunBundleRetention(e,n,t={},r={}){const s=I(n),o=r.removeObject??ie,i=r.removeRunBundle??Ce,c=t.maxRuns??null,f=t.maxAgeDays??null,l=new Set(t.preserveSuites||[]),p=q(e),m=new Set([s]);for(const d of p)xe(G(d.artifact),l)&&m.add(d.runId);const S=new Set(c?p.slice(0,c).map(d=>d.runId):p.map(d=>d.runId)),y=f==null?null:Date.now()-f*24*60*60*1e3,h=[];for(const d of p){if(m.has(d.runId))continue;const R=y!=null&&Date.parse(d.finishedAt||d.startedAt||"")<y,w=!S.has(d.runId);if(!R&&!w)continue;const k=[];R&&k.push("max_age"),w&&k.push("max_runs"),h.push({path:u.join(T(e),ye,d.runId),reasons:k,runId:d.runId})}const v=se(e),g=[],j=[];for(const d of h){let R=null;try{i(d.path)}catch(k){R=L(k)}const w=!a.existsSync(d.path);!w&&R===null&&(R="Run bundle still exists after removal completed"),w&&g.push({reasons:d.reasons,runId:d.runId}),R!==null&&j.push({...d,error:R,removed:w})}const b=new Set(g.map(d=>d.runId)),V=re(e,v,o,p.filter(d=>!b.has(d.runId))),E=new Date().toISOString();return Fe({currentRunId:s,decidedAt:E,garbageCollection:V,policy:{maxRuns:c,maxAgeDays:f,preserveSuites:[...l].sort()},protectedRunIds:[...m].sort(),removed:g,removalErrors:j})}function Ie({resultsDir:e,tempDir:n,redaction:t}){let r=0,s=0,o=0,i=0,c=0;const f=u.join(e,X,"sha256");a.mkdirSync(f,{recursive:!0});for(const l of U){const p=u.join(e,l);if(a.existsSync(p))for(const m of te(p)){const S=u.relative(p,m),y=a.readFileSync(m),h=Ve(y,m,t.targets);h.scanned&&(o+=1),h.replacements>0&&(i+=1,c+=h.replacements);const v=O(h.buffer),g=u.join(f,v);a.existsSync(g)?s+=h.buffer.length:(je(g,h.buffer),a.chmodSync(g,292),r+=h.buffer.length);const j=u.join(n,"files",l,S);a.mkdirSync(u.dirname(j),{recursive:!0});try{a.linkSync(g,j)}catch{a.copyFileSync(g,j)}}}return{objectBytesWritten:r,deduplicatedBytes:s,redaction:{policyVersion:1,status:t.status,textualFilesScanned:o,filesRedacted:i,replacements:c,sensitiveEnvironmentKeys:t.keys,valuesStored:!1}}}function Be(e,n,t){const r=new Map(U.map(i=>[`.testkit/results/${i}/`,`${$(n,`files/${i}`)}/`])),s=W(e,r),o=me(Ee(s),t);return{artifact:o.value,replacements:o.replacements}}function Ee(e){if(!A(e))return e;const n={...e};if(A(n.product)){const t={...n.product};delete t.directory,n.product=t}if(A(n.git)){const t={...n.git};delete t.remoteUrl,delete t.repoRoot,delete t.worktreePath,n.git=t}return delete n.host,n}function W(e,n){if(Array.isArray(e))return e.map(r=>W(r,n));if(A(e))return Object.fromEntries(Object.entries(e).map(([r,s])=>[r,W(s,n)]));if(typeof e!="string")return e;let t=e;for(const[r,s]of n)t.startsWith(r)&&(t=`${s}${t.slice(r.length)}`);return t}function ke(e,n,t,r){const s=D(n.run.fileNames),o=[...new Set([...we,...s])].map(i=>De(e,i)).filter(i=>i!==null);return{schemaVersion:pe,runId:t,ci:be(r.env??process.env),source:{git:n.git??null,files:o},runtime:{controller:{testkitVersion:H(n.run.testkitVersion),node:process.versions.node,platform:process.platform,architecture:process.arch},runtimeStats:n.schemaVersion===19?ge(n.run.runtimeStats):[...n.environment?.capacity?.runtimeStats??[]]},scenario:{types:D(n.run.types),suiteSelectors:D(n.run.suiteSelectors),fileNames:s,serviceFilter:H(n.schemaVersion===19?n.run.serviceFilter:n.run.groupFilter),planningDigest:O(Buffer.from(M(n.planning??null))),declaredEnvironmentKeys:[...new Set(r.declaredEnvironmentKeys??[])].sort()},redaction:r.redaction}}function Ae(e){return te(e).map(n=>({path:K(u.relative(e,n)),bytes:a.statSync(n).size,sha256:z(n)})).sort((n,t)=>n.path.localeCompare(t.path))}function F(e,n){const t=u.join(e,"manifest.json"),r=Y(x(t),t,n);ae(x(u.join(e,"run.json")),u.join(e,"run.json"),n);const s=[];for(const i of r.files){let c;try{c=Z(e,i.path)}catch(l){s.push(B(l));continue}if(!a.existsSync(c)){s.push(`missing ${i.path}`);continue}const f=a.lstatSync(c);if(f.isSymbolicLink()){s.push(`symbolic link not allowed ${i.path}`);continue}if(!f.isFile()){s.push(`not a regular file ${i.path}`);continue}f.size!==i.bytes&&s.push(`size mismatch ${i.path}`),z(c)!==i.sha256&&s.push(`checksum mismatch ${i.path}`)}O(Buffer.from(M(r.files)))!==r.digest&&s.push("manifest digest mismatch"),r.storage.fileCount!==r.files.length&&s.push("manifest file count mismatch");for(const i of["run.json","provenance.json"])r.files.some(c=>c.path===i)||s.push(`manifest must include ${i}`);if(s.length===0){const i=Z(e,"provenance.json");oe(x(i),i,n)}return{runId:n,checkedFiles:r.files.length,storage:r.storage,ok:s.length===0,errors:s,manifestDigest:r.digest}}function $e(e,n,t){const r={schemaVersion:1,runId:n,recordedAt:new Date().toISOString(),...t};return C(u.join(T(e),"publications",`${n}.json`),r),r}function xe(e,n){return n.size===0?!1:e.groups.some(t=>t.suites.some(r=>{const s=r.testType??"";return[r.name,`${s}:${r.name}`,`${t.name}/${s}:${r.name}`].some(i=>n.has(i))}))}function Pe(e){return re(e,se(e),ie,q(e))}function re(e,n,t,r){const s=N(e,!1),o=new Map(r.map(l=>[l.runId,l.manifest])),i=new Set,c=[],f=a.existsSync(s)?a.readdirSync(s,{withFileTypes:!0}):[];for(const l of f)if(!(!l.isDirectory()||l.name.startsWith(".")))try{for(const p of(o.get(l.name)??loadRunBundleManifest(e,l.name)).files)p.path.startsWith("files/")&&i.add(p.sha256)}catch(p){c.push({error:`No stored object was removed: ${B(p)}`,path:u.join(s,l.name)})}return c.length>0?{failures:c,reclaimedBytes:0,removedObjects:0,status:"not_started"}:Oe(n,i,t)}function se(e){const n=u.join(T(e),X,"sha256");return a.existsSync(n)?a.readdirSync(n,{withFileTypes:!0}).filter(t=>t.isFile()).map(t=>{const r=u.join(n,t.name);return{bytes:a.statSync(r).size,digest:t.name,path:r}}):[]}function Oe(e,n,t){const r=[];let s=0,o=0;for(const i of e){if(n.has(i.digest))continue;let c=null;try{t(i.path)}catch(l){c=L(l)}!a.existsSync(i.path)?(s+=1,o+=i.bytes):c===null&&(c="Object still exists after removal completed"),c!==null&&r.push({error:c,path:i.path})}return{failures:r,reclaimedBytes:o,removedObjects:s,status:r.length===0?"complete":"failed"}}function Ce(e){a.rmSync(e,{recursive:!0,force:!0})}function ie(e){a.rmSync(e,{force:!0})}function Fe({currentRunId:e,decidedAt:n,garbageCollection:t,policy:r,protectedRunIds:s,removed:o,removalErrors:i}){const c=[...i.map(f=>`remove ${f.runId}: ${f.error}`),...t.failures.map(f=>`garbage collect ${f.path}: ${f.error}`)];return{currentRunId:e,decidedAt:n,garbageCollection:t,policy:r,protectedRunIds:s,removed:o,removalErrors:i,schemaVersion:he,status:c.length===0?"complete":"failed",...c.length===0?{}:{error:c.join("; ")}}}function Ve(e,n,t){if(!ze(e,n)){for(const s of t)if(e.includes(Buffer.from(s)))throw new Error(`Refusing to archive binary file containing sensitive environment value: ${n}`);return{buffer:e,scanned:!1,replacements:0}}const r=fe(e.toString("utf8"),t);return r.replacements===0?{buffer:e,scanned:!0,replacements:0}:{buffer:Buffer.from(r.text),scanned:!0,replacements:r.replacements}}function ze(e,n){if(e.includes(0))return!1;const t=u.extname(n).toLowerCase();return![".zip",".gz",".png",".jpg",".jpeg",".webm",".pdf"].includes(t)}function De(e,n){const t=u.resolve(e,n);return!t.startsWith(`${u.resolve(e)}${u.sep}`)||!a.existsSync(t)||!a.statSync(t).isFile()?null:{path:K(u.relative(e,t)),bytes:a.statSync(t).size,sha256:z(t)}}function _e(e){const n=[];for(const t of[...U.map(r=>u.join(e,r)),u.join(e,"live.json")])try{a.rmSync(t,{recursive:!0,force:!0})}catch(r){n.push(`${t}: ${L(r)}`)}return n.length===0?{status:"complete"}:{error:n.join("; "),status:"failed"}}function L(e){try{return B(e)}catch{return"Unknown post-seal archive operation failure"}}
|
|
1
|
+
import{recordWork as G}from"../kernel/work-metrics.js";import{recordEvidenceWrite as ye}from"../process/files/evidence-writes.js";import{isUnknownRecord as B,optionalString as Q}from"../kernel/unknown-values.js";import{posixPath as V}from"../process/files/paths.js";import{readRunProvenance as ge}from"./read-provenance.js";import{parseRunBundleArtifact as ve,parseRunBundleManifest as Z,readRunBundleArtifact as Se}from"./read-bundle.js";import{readRunDocument as Re}from"./read-run.js";import{runModel as ee}from"./run-model.js";import{isUtf8 as we}from"buffer";import be from"crypto";import u from"fs";import f from"path";import{localDirectories as je,localPathRewriter as xe}from"./local-paths.js";import{redactRunText as N,redactRunValue as Be,runRedaction as Ee}from"./redaction.js";import{isCompressedArtifact as te,sealCompressedArtifact as Ie,UnreadableArchiveError as Ae}from"./compressed-artifacts.js";import{BUNDLE_SCHEMA_VERSION as ne,OBJECTS_DIRNAME as re,PROVENANCE_SCHEMA_VERSION as $e,RETENTION_DECISION_SCHEMA_VERSION as ke,RUNS_DIRNAME as Pe}from"./contracts.js";import{bundleRelativePath as O,hashFile as U,normalizeRunId as A,optionalStringArray as M,optionalUnknownArray as Oe,readJsonFile as C,requirePublicationPathOutsideResults as q,requireRunId as Ce,runIncludesFile as Te,resolveManifestEntryPath as se,resolveRunBundleDirectory as T,resolveRunsRoot as L,resultsRoot as W,sanitizeSegment as Fe,sha256 as F,stableJson as J,treeBytes as oe,walkFiles as ie,writeBufferAtomic as ze,writeJson as z}from"./storage.js";import{resolveCiProvenance as _e}from"./ci-provenance.js";import{errorMessage as $}from"../kernel/error-serialization.js";const H=["artifacts","logs","setup"];export class RefusedArtifactsError extends Error{}class k extends Error{}const ae=Buffer.from([255,254]),ce=Buffer.from([254,255]),De=new Set([9,10,11,12,13,27]),Ve=["package.json","package-lock.json","npm-shrinkwrap.json","pnpm-lock.yaml","yarn.lock","testkit.config.ts","testkit.config.mts","testkit.config.js","testkit.config.mjs"];export function sealRunBundle(e,n,t={},r={}){const s=n,a=Ce(s),o=L(e,!0),i=f.dirname(o),l=f.join(o,a),c=r.cleanupActiveRunResults??st,d=loadVerifiedRunBundleArtifactIfExists(e,a);if(d!==null)return{...d,archive:{...d.archive,cleanup:c(i)}};const h=f.join(o,`.${a}.${String(process.pid)}.${String(Date.now())}.${be.randomBytes(4).toString("hex")}.tmp`);u.mkdirSync(h,{recursive:!0});try{const R=Ee(t),w=xe(je(e,r.homeDirectory)),p=Ne({recordRefusals:t.recordRefusals,resultsDir:i,tempDir:h,redaction:R,rewriteLocalPaths:w}),g=Me(Ue(s,p.changedPayloads),a,R.targets,w),y=Re(g.artifact,"run artifact");p.redaction.structuredReplacements=g.replacements,p.redaction.replacements+=g.replacements;const E=Le(e,y,a,{...t,redaction:p.redaction}),I={schemaVersion:ne,runId:a,status:"complete",canonicalRunPath:O(a,"run.json"),manifestPath:O(a,"manifest.json"),provenancePath:O(a,"provenance.json"),integrity:"sha256",publication:{configured:!!t.archiveConfig?.publication,status:t.archiveConfig?.publication?"pending":"not_configured"},redaction:p.redaction};y.archive=I,z(f.join(h,"run.json"),y),z(f.join(h,"provenance.json"),E);const b=We(h),j=F(Buffer.from(J(b))),m={schemaVersion:ne,runId:a,createdAt:new Date().toISOString(),algorithm:"sha256",files:b,storage:{fileCount:b.length,logicalBytes:b.reduce((v,S)=>v+S.bytes,0),objectBytesWritten:p.objectBytesWritten,deduplicatedBytes:p.deduplicatedBytes},redaction:p.redaction,digest:j};return z(f.join(h,"manifest.json"),m),u.renameSync(h,l),{...y,archive:{...I,cleanup:c(i),manifestDigest:m.digest,storage:m.storage}}}catch(R){try{u.rmSync(h,{recursive:!0,force:!0})}catch{}try{Ke(e)}catch{}throw R}}export function loadVerifiedRunBundleArtifactIfExists(e,n){const t=A(n),r=T(e,t);if(!u.existsSync(r))return null;const s=verifyRunBundle(e,t);if(!s.ok)throw new Error(`Immutable run bundle ${t} already exists but failed integrity verification: ${s.errors.join("; ")}`);return loadRunBundleArtifact(e,t)}export function loadRunBundleArtifact(e,n){return ue(e,n).artifact}function ue(e,n){const t=A(n),r=Se(e,t),s=loadRunBundleManifest(e,t);return{manifest:s,artifact:{...r,archive:{...r.archive,manifestDigest:s.digest,storage:s.storage}}}}export function loadRunBundleManifest(e,n){const t=A(n),r=T(e,t),s=f.join(r,"manifest.json");if(!u.existsSync(s))throw new Error(`No immutable Testkit run manifest found for ${t}`);return Z(C(s),s,t)}export function listRunBundles(e,n={}){return K(e,n).map(({artifact:t,manifest:r,...s})=>s)}function K(e,n={}){const t=L(e,!1);if(!u.existsSync(t))return[];const r=u.readdirSync(t,{withFileTypes:!0});return G("artifacts.runDirectoriesRead",1),G("artifacts.runEntriesVisited",r.length),r.filter(a=>a.isDirectory()&&!a.name.startsWith(".")).map(a=>{try{const{artifact:o,manifest:i}=ue(e,a.name);return n.file&&!Te(ee(o),n.file)?null:{artifact:o,manifest:i,runId:a.name,status:o.run.status,startedAt:o.run.startedAt,finishedAt:o.run.finishedAt??null,manifestDigest:o.archive.manifestDigest??null,canonicalRunPath:o.archive.canonicalRunPath}}catch{return null}}).filter(a=>a!==null).sort((a,o)=>(o.finishedAt??o.startedAt??"").localeCompare(a.finishedAt??a.startedAt??""))}export function verifyRunBundle(e,n){const t=A(n);try{return D(T(e,t),t)}catch(r){return{ok:!1,runId:t,errors:[$(r)],checkedFiles:0}}}export function publishRunBundle(e,n,t,r={}){const s=A(n),a=r.writePublicationReceipt??Je;if(!t)return a(e,s,{status:"not_configured",required:!1,target:null,bytesUploaded:0});const o=f.resolve(e,t.directory);try{q(e,o)}catch(h){return a(e,s,{status:"failed",required:t.required,target:o,error:$(h),bytesUploaded:0})}const i=Fe(f.basename(e)),l=f.join(o,i),c=f.join(l,s),d=f.join(l,`.${s}.${String(process.pid)}.${String(Date.now())}.tmp`);try{const h=T(e,s);if(u.mkdirSync(l,{recursive:!0}),q(e,l),u.existsSync(c)){const p=u.lstatSync(c);if(p.isSymbolicLink()||!p.isDirectory())throw new Error("Publication target must be a regular bundle directory");q(e,c);const g=D(h,s);if(!g.ok)throw new Error(`Source run bundle failed integrity verification: ${g.errors.join("; ")}`);const y=D(c,s);if(!y.ok)throw new Error(`Publication target failed integrity verification: ${y.errors.join("; ")}`);if(g.manifestDigest!==y.manifestDigest)throw new Error("Publication target already exists with a different manifest digest");return a(e,s,{status:"complete",required:t.required,target:c,manifestDigest:g.manifestDigest,reused:!0,bundleBytes:oe(h),bytesUploaded:0})}const R=oe(h);u.cpSync(h,d,{recursive:!0,preserveTimestamps:!0});const w=D(d,s);if(!w.ok)throw new Error(w.errors.join("; "));return u.renameSync(d,c),a(e,s,{status:"complete",required:t.required,target:c,manifestDigest:w.manifestDigest,reused:!1,bundleBytes:R,bytesUploaded:R})}catch(h){return u.rmSync(d,{recursive:!0,force:!0}),a(e,s,{status:"failed",required:t.required,target:c,error:$(h),bytesUploaded:0})}}export function applyRunBundleRetention(e,n,t={},r={}){const s=A(n),a=r.removeObject??de,o=r.removeRunBundle??Xe,i=t.maxRuns??null,l=t.maxAgeDays??null,c=new Set(t.preserveSuites||[]),d=K(e),h=new Set([s]);for(const m of d)He(ee(m.artifact),c)&&h.add(m.runId);const R=new Set(i?d.slice(0,i).map(m=>m.runId):d.map(m=>m.runId)),w=l==null?null:Date.now()-l*24*60*60*1e3,p=[];for(const m of d){if(h.has(m.runId))continue;const v=w!=null&&Date.parse(m.finishedAt||m.startedAt||"")<w,S=!R.has(m.runId);if(!v&&!S)continue;const x=[];v&&x.push("max_age"),S&&x.push("max_runs"),p.push({path:f.join(W(e),Pe,m.runId),reasons:x,runId:m.runId})}const g=me(e),y=[],E=[];for(const m of p){let v=null;try{o(m.path)}catch(x){v=X(x)}const S=!u.existsSync(m.path);!S&&v===null&&(v="Run bundle still exists after removal completed"),S&&y.push({reasons:m.reasons,runId:m.runId}),v!==null&&E.push({...m,error:v,removed:S})}const I=new Set(y.map(m=>m.runId)),b=fe(e,g,a,d.filter(m=>!I.has(m.runId))),j=new Date().toISOString();return Ge({currentRunId:s,decidedAt:j,garbageCollection:b,policy:{maxRuns:i,maxAgeDays:l,preserveSuites:[...c].sort()},protectedRunIds:[...h].sort(),removed:y,removalErrors:E})}function Ne({recordRefusals:e,resultsDir:n,tempDir:t,redaction:r,rewriteLocalPaths:s}){const a=new Map;let o=0,i=0,l=0,c=0,d=0;const h=[],R=[],w=f.join(n,re,"sha256");u.mkdirSync(w,{recursive:!0});for(const p of H){const g=f.join(n,p);if(u.existsSync(g))for(const y of ie(g)){const E=f.relative(g,y),I=u.readFileSync(y);let b;try{b=Qe(I,y,r.targets,s)}catch(x){if(!(x instanceof k))throw x;R.push(y),h.push(`Refusing to archive ${x.message}, and removed it: ${y}`);continue}l+=b.scanned,b.replacements>0&&(c+=1,d+=b.replacements);const j=b.buffer,m=F(j);j!==I&&a.set(`.testkit/results/${p}/${V(E)}`,{sha256:m,sizeBytes:j.length});const v=f.join(w,m);u.existsSync(v)?i+=j.length:(ze(v,j),u.chmodSync(v,256),o+=j.length);const S=f.join(t,"files",p,E);u.mkdirSync(f.dirname(S),{recursive:!0}),ye(S);try{u.linkSync(v,S)}catch{u.copyFileSync(v,S)}}}if(h.length>0){const p=N(h.join(`
|
|
2
|
+
`),r.targets).text;e?.(p);for(const g of R)u.rmSync(g,{force:!0});throw new RefusedArtifactsError(p)}return{changedPayloads:a,objectBytesWritten:o,deduplicatedBytes:i,redaction:{policyVersion:1,status:r.status,textualFilesScanned:l,filesRedacted:c,replacements:d,sensitiveEnvironmentKeys:r.keys,valuesStored:!1}}}function Ue(e,n){const t=r=>P(r,"suites",s=>P(s,"files",a=>P(a,"artifacts",o=>{const i=typeof o.path=="string"?n.get(o.path):void 0;if(i===void 0||!B(o.metadata))return o;const l={...o.metadata};return Object.hasOwn(l,"sha256")&&(l.sha256=i.sha256),Object.hasOwn(l,"sizeBytes")&&(l.sizeBytes=i.sizeBytes),{...o,metadata:l}})));return P(P(e,"groups",t),"services",t)}function P(e,n,t){const r=B(e)?e[n]:void 0;return!B(e)||!Array.isArray(r)?e:{...e,[n]:r.map(s=>B(s)?t(s):s)}}function le(e,n,t){const r=t(e.text);if(r===e.text)return e;const s=N(r,n);return{replacements:e.replacements+s.replacements,text:s.text}}function Me(e,n,t,r){const s=new Map(H.map(c=>[`.testkit/results/${c}/`,`${O(n,`files/${c}`)}/`])),a=_(e,c=>{for(const[d,h]of s)if(c.startsWith(d))return`${h}${c.slice(d.length)}`;return c}),o=Be(qe(a),t);let i=o.replacements;return{artifact:_(o.value,c=>{const d=le({replacements:0,text:c},t,r);return i+=d.replacements,d.text}),replacements:i}}function qe(e){if(!B(e))return e;const n={...e};if(B(n.product)){const t={...n.product};delete t.directory,n.product=t}if(B(n.git)){const t={...n.git};delete t.remoteUrl,delete t.repoRoot,delete t.worktreePath,n.git=t}return delete n.host,n}function _(e,n){return Array.isArray(e)?e.map(t=>_(t,n)):B(e)?Object.fromEntries(Object.entries(e).map(([t,r])=>[t,_(r,n)])):typeof e=="string"?n(e):e}function Le(e,n,t,r){const s=M(n.run.fileNames),a=[...new Set([...Ve,...s])].map(o=>rt(e,o)).filter(o=>o!==null);return{schemaVersion:$e,runId:t,ci:_e(r.env??process.env),source:{git:n.git??null,files:a},runtime:{controller:{testkitVersion:Q(n.run.testkitVersion),node:process.versions.node,platform:process.platform,architecture:process.arch},runtimeStats:n.schemaVersion===19?Oe(n.run.runtimeStats):[...n.environment?.capacity?.runtimeStats??[]]},scenario:{types:M(n.run.types),suiteSelectors:M(n.run.suiteSelectors),fileNames:s,serviceFilter:Q(n.schemaVersion===19?n.run.serviceFilter:n.run.groupFilter),planningDigest:F(Buffer.from(J(n.planning??null))),declaredEnvironmentKeys:[...new Set(r.declaredEnvironmentKeys??[])].sort()},redaction:r.redaction}}function We(e){return ie(e).map(n=>({path:V(f.relative(e,n)),bytes:u.statSync(n).size,sha256:U(n)})).sort((n,t)=>n.path.localeCompare(t.path))}function D(e,n){const t=f.join(e,"manifest.json"),r=Z(C(t),t,n);ve(C(f.join(e,"run.json")),f.join(e,"run.json"),n);const s=[];for(const o of r.files){let i;try{i=se(e,o.path)}catch(c){s.push($(c));continue}if(!u.existsSync(i)){s.push(`missing ${o.path}`);continue}const l=u.lstatSync(i);if(l.isSymbolicLink()){s.push(`symbolic link not allowed ${o.path}`);continue}if(!l.isFile()){s.push(`not a regular file ${o.path}`);continue}l.size!==o.bytes&&s.push(`size mismatch ${o.path}`),U(i)!==o.sha256&&s.push(`checksum mismatch ${o.path}`)}F(Buffer.from(J(r.files)))!==r.digest&&s.push("manifest digest mismatch"),r.storage.fileCount!==r.files.length&&s.push("manifest file count mismatch");for(const o of["run.json","provenance.json"])r.files.some(i=>i.path===o)||s.push(`manifest must include ${o}`);if(s.length===0){const o=se(e,"provenance.json");ge(C(o),o,n)}return{runId:n,checkedFiles:r.files.length,storage:r.storage,ok:s.length===0,errors:s,manifestDigest:r.digest}}function Je(e,n,t){const r={schemaVersion:1,runId:n,recordedAt:new Date().toISOString(),...t};return z(f.join(W(e),"publications",`${n}.json`),r),r}function He(e,n){return n.size===0?!1:e.groups.some(t=>t.suites.some(r=>{const s=r.testType??"";return[r.name,`${s}:${r.name}`,`${t.name}/${s}:${r.name}`].some(o=>n.has(o))}))}function Ke(e){return fe(e,me(e),de,K(e))}function fe(e,n,t,r){const s=L(e,!1),a=new Map(r.map(c=>[c.runId,c.manifest])),o=new Set,i=[],l=u.existsSync(s)?u.readdirSync(s,{withFileTypes:!0}):[];for(const c of l)if(!(!c.isDirectory()||c.name.startsWith(".")))try{for(const d of(a.get(c.name)??loadRunBundleManifest(e,c.name)).files)d.path.startsWith("files/")&&o.add(d.sha256)}catch(d){i.push({error:`No stored object was removed: ${$(d)}`,path:f.join(s,c.name)})}return i.length>0?{failures:i,reclaimedBytes:0,removedObjects:0,status:"not_started"}:Ye(n,o,t)}function me(e){const n=f.join(W(e),re,"sha256");return u.existsSync(n)?u.readdirSync(n,{withFileTypes:!0}).filter(t=>t.isFile()).map(t=>{const r=f.join(n,t.name);return{bytes:u.statSync(r).size,digest:t.name,path:r}}):[]}function Ye(e,n,t){const r=[];let s=0,a=0;for(const o of e){if(n.has(o.digest))continue;let i=null;try{t(o.path)}catch(c){i=X(c)}!u.existsSync(o.path)?(s+=1,a+=o.bytes):i===null&&(i="Object still exists after removal completed"),i!==null&&r.push({error:i,path:o.path})}return{failures:r,reclaimedBytes:a,removedObjects:s,status:r.length===0?"complete":"failed"}}function Xe(e){u.rmSync(e,{recursive:!0,force:!0})}function de(e){u.rmSync(e,{force:!0})}function Ge({currentRunId:e,decidedAt:n,garbageCollection:t,policy:r,protectedRunIds:s,removed:a,removalErrors:o}){const i=[...o.map(l=>`remove ${l.runId}: ${l.error}`),...t.failures.map(l=>`garbage collect ${l.path}: ${l.error}`)];return{currentRunId:e,decidedAt:n,garbageCollection:t,policy:r,protectedRunIds:s,removed:a,removalErrors:o,schemaVersion:ke,status:i.length===0?"complete":"failed",...i.length===0?{}:{error:i.join("; ")}}}function Qe(e,n,t,r){const s=a=>le(N(a,t),t,r);if(pe(e,n))return{...he(e,"utf8",s),scanned:1};if(te(e,n))return Ze(e,n,t,s);if(Y(e,t))throw new k("binary file containing sensitive environment value");return{buffer:e,replacements:0,scanned:0}}function Ze(e,n,t,r){let s=0,a=0,o;try{o=Ie(e,n,i=>{if(te(i.content,i.name))throw new k(`compressed file whose entry ${i.name} is itself an archive`);const l=et(i.content,i.name);if(l===null){if(!Y(i.content,t))return i.content;throw new k(`compressed file whose binary entry ${i.name} contains sensitive environment value`)}const c=he(i.content,l,r);return a+=1,s+=c.replacements,c.buffer})}catch(i){throw i instanceof Ae?new k(`compressed file that Testkit can't read, because ${i.message}`):i}if(Y(o,t))throw new k("compressed file containing sensitive environment value outside its entries' text");return{buffer:o,replacements:s,scanned:a}}function et(e,n){const t=e.subarray(0,2);return e.length%2===0&&t.equals(ae)?"utf16le":e.length%2===0&&t.equals(ce)?"utf16be":!pe(e,n)||!we(e)||e.some(r=>r===127||r<32&&!De.has(r))?null:"utf8"}function he(e,n,t){const r=tt(e,n),s=t(r);return{buffer:s.text===r?e:nt(s.text,n),replacements:s.replacements}}function tt(e,n){if(n==="utf8")return e.toString("utf8");const t=Buffer.from(e.subarray(2));return(n==="utf16be"?t.swap16():t).toString("utf16le")}function nt(e,n){if(n==="utf8")return Buffer.from(e);const t=Buffer.from(e,"utf16le");return n==="utf16be"?Buffer.concat([ce,t.swap16()]):Buffer.concat([ae,t])}function Y(e,n){return n.some(t=>{const r=Buffer.from(t,"utf16le");return[Buffer.from(t),r,Buffer.from(r).swap16()].some(s=>e.includes(s))})}function pe(e,n){if(e.includes(0))return!1;const t=f.extname(n).toLowerCase();return![".zip",".gz",".png",".jpg",".jpeg",".webm",".pdf"].includes(t)}function rt(e,n){const t=f.resolve(e,n);return!t.startsWith(`${f.resolve(e)}${f.sep}`)||!u.existsSync(t)||!u.statSync(t).isFile()?null:{path:V(f.relative(e,t)),bytes:u.statSync(t).size,sha256:U(t)}}function st(e){const n=[];for(const t of[...H.map(r=>f.join(e,r)),f.join(e,"live.json")])try{u.rmSync(t,{recursive:!0,force:!0})}catch(r){n.push(`${t}: ${X(r)}`)}return n.length===0?{status:"complete"}:{error:n.join("; "),status:"failed"}}function X(e){try{return $(e)}catch{return"Unknown post-seal archive operation failure"}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import S from"node:path";import g from"node:zlib";import{errorMessage as H}from"../kernel/error-serialization.js";import{MAX_PERSISTED_FILE_ARTIFACT_BYTES as V}from"./secure-file-copy.js";export const MAX_INFLATED_ARTIFACT_BYTES=V;const T=67324752,Z=33639248,O=101010256,j=134695760,J=101075792,K=117853008,D=56,Q=20,_=22,b=65535,R=2048,tt=28789,G=20,nt=Buffer.from([31,139]);export class UnreadableArchiveError extends Error{}export function isCompressedArtifact(t,e){const n=S.extname(e).toLowerCase();return n===".zip"||n===".gz"||$(t)||W(t)}export function sealCompressedArtifact(t,e,n,o=MAX_INFLATED_ARTIFACT_BYTES){const i={limit:o,remaining:o},a=S.basename(e);if(W(t)||!$(t)&&S.extname(a).toLowerCase()===".gz"){const s=M(()=>et(t,i),i);return g.gzipSync(n({content:s,name:a.replace(/\.gz$/iu,"")}))}const l=it(t),r=[];for(const s of l.records){const d=rt(s,i),I=s.name.endsWith("/")&&d.length===0;r.push({content:d,record:s,sealed:I?d:n({content:d,name:s.name})})}return l.comment||l.records.some(s=>s.metadata)||r.some(s=>s.sealed!==s.content)?ot(r):t}function et(t,e){const n=g.gunzipSync(t,{...F(e),info:!0});if(t.subarray(n.engine.bytesWritten).some(o=>o!==0))throw L();return n.buffer}function rt(t,e){const n=t.method===0?It(t.data,e):lt(t.data,e);if(n.length!==t.size||g.crc32(n)!==t.crc)throw L();return n}function ot(t){const e=[],n=[];let o=0;for(const{content:l,record:r,sealed:h}of t){const s=h!==l,d=s?g.deflateRawSync(h):r.data,I=s?8:r.method,U=s?g.crc32(h):r.crc,m=r.flags&R,E=Buffer.alloc(30);E.writeUInt32LE(T,0),E.writeUInt16LE(G,4),E.writeUInt16LE(m,6),E.writeUInt16LE(I,8),E.writeUInt32LE(r.modified,10),E.writeUInt32LE(U,14),E.writeUInt32LE(d.length,18),E.writeUInt32LE(h.length,22),E.writeUInt16LE(r.nameBytes.length,26);const c=Buffer.alloc(46);c.writeUInt32LE(Z,0),c.writeUInt16LE(r.madeBy,4),c.writeUInt16LE(G,6),c.writeUInt16LE(m,8),c.writeUInt16LE(I,10),c.writeUInt32LE(r.modified,12),c.writeUInt32LE(U,16),c.writeUInt32LE(d.length,20),c.writeUInt32LE(h.length,24),c.writeUInt16LE(r.nameBytes.length,28),c.writeUInt16LE(r.internalAttributes,36),c.writeUInt32LE(r.externalAttributes,38),c.writeUInt32LE(o,42),e.push(E,r.nameBytes,d),n.push(c,r.nameBytes),o+=E.length+r.nameBytes.length+d.length}const i=Buffer.concat(n),a=Buffer.alloc(_);return a.writeUInt32LE(O,0),a.writeUInt16LE(t.length,8),a.writeUInt16LE(t.length,10),a.writeUInt32LE(i.length,12),a.writeUInt32LE(o,16),Buffer.concat([...e,i,a])}function it(t){let e=new UnreadableArchiveError("it has no ZIP end record");const n=Math.max(0,t.length-_-b);for(let o=t.length-_;o>=n;o-=1)if(t.readUInt32LE(o)===O&&o+_+t.readUInt16LE(o+20)===t.length)try{return{comment:t.readUInt16LE(o+20)>0,records:st(t,o)}}catch(i){if(!(i instanceof UnreadableArchiveError))throw i;e=i}throw e}function st(t,e){const n=t.readUInt16LE(e+10),o=t.readUInt32LE(e+12),i=t.readUInt32LE(e+16);if(n===65535||o===4294967295||i===4294967295)throw new UnreadableArchiveError("it is a ZIP64 archive");const a=i+o;if(t.readUInt32LE(e+4)!==0||t.readUInt16LE(e+8)!==n||a!==e&&!ct(t,{count:n,directoryOffset:i,directorySize:o,end:e}))throw L();const l=[];let r=i;for(let d=0;d<n;d+=1){if(p(t,r,46),t.readUInt32LE(r)!==Z)throw L();const I=t.readUInt16LE(r+8),U=t.readUInt16LE(r+10),m=t.readUInt32LE(r+16),E=t.readUInt32LE(r+20),c=t.readUInt32LE(r+24),w=t.readUInt16LE(r+28),u=t.readUInt32LE(r+42);p(t,r+46,w);const y=t.readUInt16LE(r+30),z=t.readUInt16LE(r+32);p(t,r+46+w,y);const N=t.subarray(r+46,r+46+w),B=(I&R)===0?Et(dt(t.subarray(r+46+w,r+46+w+y)),N):null,P=B===null?N:Buffer.from(B),f=P.toString("utf8"),X={externalAttributes:t.readUInt32LE(r+38),internalAttributes:t.readUInt16LE(r+36),madeBy:t.readUInt16LE(r+4),modified:t.readUInt32LE(r+12)};if(r+=46+w+y+z,(I&1)!==0)throw new UnreadableArchiveError(`its entry ${f} is encrypted`);if(U!==0&&U!==8)throw new UnreadableArchiveError(`its entry ${f} uses compression method ${String(U)}`);if(p(t,u,30),t.readUInt32LE(u)!==T)throw L();const q=u+30+t.readUInt16LE(u+26),C=t.readUInt16LE(u+28),x=q+C;p(t,x,E);const A=x+E,k=(I&8)===0?[A]:at(t,A,[[m,E,c],[m,E,0,c,0]]);l.push({...X,crc:m,data:t.subarray(x,A),ends:k,flags:B===null?I:I|R,metadata:y>0||z>0||C>0,method:U,name:f,nameBytes:P,size:c,start:u})}if(r!==a)throw L();const h=[...l].sort((d,I)=>d.start-I.start);let s=0;for(const[d,I]of h.entries()){const U=h[d+1]?.start??i;if(I.start!==s||!I.ends.includes(U))throw L();s=U}if(s!==i)throw L();return l}function ct(t,e){const n=e.directoryOffset+e.directorySize,o=e.end-Q;return o!==n+D?!1:t.readUInt32LE(o)===K&&t.readBigUInt64LE(o+8)===BigInt(n)&&t.readUInt32LE(n)===J&&t.readBigUInt64LE(n+4)===BigInt(D-12)&&t.readBigUInt64LE(n+32)===BigInt(e.count)&&t.readBigUInt64LE(n+40)===BigInt(e.directorySize)&&t.readBigUInt64LE(n+48)===BigInt(e.directoryOffset)}function at(t,e,n){const o=e+4<=t.length&&t.readUInt32LE(e)===j,i=[];for(const a of n){const l=r=>r+4*a.length<=t.length&&a.every((h,s)=>t.readUInt32LE(r+4*s)===h);o&&l(e+4)&&i.push(e+4+4*a.length),l(e)&&i.push(e+4*a.length)}if(i.length===0)throw L();return i}function dt(t){const e=new Map;for(let n=0;n+4<=t.length;){const o=t.readUInt16LE(n+2);if(n+4+o>t.length)break;e.set(t.readUInt16LE(n),t.subarray(n+4,n+4+o)),n+=4+o}return e}function Et(t,e){const n=t.get(tt);return n===void 0||n.length<5||n[0]!==1||n.readUInt32LE(1)!==g.crc32(e)?null:n.subarray(5).toString("utf8")}function L(){return new UnreadableArchiveError("its layout is damaged or holds data outside its entries")}function It(t,e){return v(e,t.length),t}function lt(t,e){return M(()=>{const n=g.inflateRawSync(t,{...F(e),info:!0});if(n.engine.bytesWritten!==t.length)throw L();return n.buffer},e)}function M(t,e){let n;try{n=t()}catch(o){throw o instanceof UnreadableArchiveError?o:o instanceof RangeError?Y(e):new UnreadableArchiveError(`it could not be decompressed: ${H(o)}`)}return v(e,n.length),n}function F(t){return{maxOutputLength:Math.max(1,t.remaining)}}function v(t,e){if(e>t.remaining)throw Y(t);t.remaining-=e}function Y(t){return new UnreadableArchiveError(`it expands to more than ${String(t.limit)} bytes`)}function p(t,e,n){if(e+n>t.length)throw new UnreadableArchiveError("it is truncated")}function $(t){return t.length>=4&&t.readUInt32LE(0)===T}function W(t){return t.subarray(0,2).equals(nt)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{formatError as v}from"../kernel/error-serialization.js";import{jsonRedactionTargets as E,orderRedactionTargets as B,sensitiveKeyMatcher as I}from"../kernel/redaction-targets.js";import{redactUrlSecrets as C}from"../process/url-redaction.js";import{redactedRunValue as K}from"./redaction.js";export function publishEnvironmentRecord(a){return{...a,acquisitions:a.acquisitions.map(
|
|
1
|
+
import{formatError as v}from"../kernel/error-serialization.js";import{jsonRedactionTargets as E,orderRedactionTargets as B,sensitiveKeyMatcher as I}from"../kernel/redaction-targets.js";import{redactUrlSecrets as C}from"../process/url-redaction.js";import{redactedRunValue as K}from"./redaction.js";export function publishEnvironmentRecord(a){return{...a,acquisitions:a.acquisitions.map(d=>({...d,settledAt:O(d.settledAt),setups:d.setups.map(p=>({...p,settledAt:O(p.settledAt),startedAt:new Date(p.startedAt).toISOString()})),startedAt:new Date(d.startedAt).toISOString()})),dbBackend:a.groups.some(d=>d.dbBackend==="local")?"local":null}}function O(a){return a===null?null:new Date(a).toISOString()}export function createRunEnvironmentRecord(a={}){const d=[],p=[],h=[],R=new Set,b=I(a.redactKeys);let y=[],m={instanceCount:0,resourceBudgets:[],runtimeStats:[]},k=null;const g=a.now??Date.now,f=()=>{a.onPublishedChange?.()},w=(s,e)=>({releaseFailed(r){for(const n of s)n.releaseErrors.push(v(r)),n.failingReleases.add(e),n.releaseReported=!0},released(){for(const r of s)r.failingReleases.delete(e),r.releaseReported=!0}});return{redactKeys:a.redactKeys??[],service({runtimeLabel:s,serviceName:e}){let r=null;return{recordAttempt({baseUrl:n,outcome:t,port:u,readyUrl:l}){const c={baseUrl:n===null?null:C(n),readyUrl:l===null?null:C(l)};r===null&&(r={serviceName:e,runtimeLabel:s,...c,port:u,attempts:[]},d.push(r)),Object.assign(r,c,{port:u}),r.attempts.push({port:u,outcome:t}),f()},amendLastOutcome(n){const t=r?.attempts.at(-1);t&&(t.outcome=n,f())}}},acquisition({graphKey:s,leaseId:e,source:r,tasks:n}){const t={source:r,graphKey:s,leaseId:e,runtimeId:null,services:[],status:"pending",startedAt:g(),settledAt:null,error:null,releaseErrors:[],failingReleases:new Set,releaseReported:!1,setups:[],tasks:n.map(l=>({...l})),values:null};p.push(t);const u=({runtimeId:l,services:c})=>{t.runtimeId=l,t.services=c.map(i=>({...i}))};return{...w([t],"source"),assigned:u,acquired(l){u(l),t.status="acquired",t.settledAt=g()},cancelled(l){t.status="cancelled",t.error=v(l),t.settledAt??=g()},failed(l){t.status="failed",t.error=v(l),t.settledAt??=g()},rendered(l){t.values=l,f()},setup({module:l,task:c}){const i={error:null,module:l,releaseErrors:[],releaseReported:!1,settledAt:null,startedAt:g(),status:"pending",task:{...c}};t.setups.push(i);const S=(o,A)=>{i.status=o,i.error=A===null?null:v(A),i.settledAt??=g()};return{prepared(o){S("prepared",null);for(const A of E("setupData",o,b))R.add(A);t.values!==null&&(t.values={...t.values,setupData:o}),f()},failed(o){S("failed",o)},cancelled(o){S("cancelled",o)},releaseFailed(o){i.releaseErrors.push(v(o)),i.releaseReported=!0},released(){i.releaseReported=!0}}}}},releaseFor(s){const e=p.filter(r=>r.leaseId===s);return e.length===0?null:w(e,"lease")},recordCapacity({instanceCount:s,resourceBudgets:e}){m={...m,instanceCount:s,resourceBudgets:[...e]},f()},recordRuntimeStats(s){m={...m,runtimeStats:[...s]},f()},recordGroups(s){y=s.map(e=>({...e}))},recordMissingValues(s,e){for(const{kind:r,valueName:n}of e)h.push({kind:r,task:{...s},valueName:n})},recordCleanup(s){k=s==null?{status:"released"}:{status:"failed",errors:[v(s)],recovered:!1}},addRedactionTarget(s){s.length>0&&R.add(s)},redactionTargets(){return[...R]},snapshot(){const s={acquisitions:p.map(({failingReleases:e,releaseErrors:r,releaseReported:n,...t})=>({...t,services:t.services.map(u=>({...u})),setups:t.setups.map(({releaseErrors:u,releaseReported:l,...c})=>({...c,task:{...c.task},release:u.length>0?{status:"failed",errors:[...u],recovered:!1}:l?{status:"released"}:null})),tasks:t.tasks.map(u=>({...u})),values:t.values,release:r.length>0?{status:"failed",errors:[...r],recovered:e.size===0}:n?{status:"released"}:null})),capacity:{instanceCount:m.instanceCount,resourceBudgets:[...m.resourceBudgets],runtimeStats:[...m.runtimeStats]},cleanup:L(k),groups:y.map(e=>({...e})),logs:{services:a.logRegistry?.listServiceLogs()??[],setup:a.logRegistry?.listSetupLogs()??[]},missingValues:h.map(e=>({...e,task:{...e.task}})),services:d.map(e=>({...e,attempts:e.attempts.map(r=>({...r}))})).sort((e,r)=>e.runtimeLabel.localeCompare(r.runtimeLabel)||e.serviceName.localeCompare(r.serviceName)),setupOperations:a.setupRegistry?.listOperations()??[]};return K(s,B(R))}}}function L(a){return a===null||a.status==="released"?a:{status:"failed",errors:[...a.errors],recovered:a.recovered}}
|
package/dist/evidence/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{normalizeArtifactArchiveConfig}from"./artifact-config.js";export{
|
|
1
|
+
export{normalizeArtifactArchiveConfig}from"./artifact-config.js";export{loadTimings,persistTaskArtifacts,persistTaskFileArtifacts,persistTaskOutputArtifacts,saveTimings}from"./artifacts.js";export{applyRunBundleRetention,listRunBundles,loadRunBundleArtifact,loadRunBundleManifest,loadVerifiedRunBundleArtifactIfExists,verifyRunBundle}from"./bundles.js";export{collectArtifactEntries,loadCurrentRunArtifact,loadLatestRunArtifact,loadRun,loadRunArtifact}from"./reading.js";export{DEFAULT_REPORTED_ARCHIVE_CONFIG}from"./reporting-types.js";export{runModel}from"./run-model.js";export{copyRegularFileSecurely,MAX_PERSISTED_FILE_ARTIFACT_BYTES}from"./secure-file-copy.js";export{collectTestkitStorageInventory}from"./storage-inventory.js";export{}from"./timing.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import l from"node:fs";import c from"node:path";import{asUnknownRecord as i,isUnknownRecord as m,optionalString as n}from"../kernel/unknown-values.js";export function readLiveRunArtifact(r){try{return JSON.parse(l.readFileSync(c.join(r,".testkit","results","live.json"),"utf8"))}catch{return null}}export function summarizeLiveArtifact(r){const t=i(r);if(!t)return null;const u=i(t.timings)?.operations??[],a=Array.isArray(u)?u:[],s=i(t.product),e=i(t.run);return{present:!0,schemaVersion:p(t.schemaVersion),generatedAt:n(t.generatedAt),product:s?{name:n(s.name),directory:n(s.directory)}:null,run:e?{id:n(e.id),status:n(e.status),startedAt:n(e.startedAt),finishedAt:n(e.finishedAt),durationMs:d(e.durationMs),interruptReason:n(e.interruptReason),testkitVersion:n(e.testkitVersion)}:null,summary:t.summary??null,operation:latestOperation(a),operationCount:a.length,artifactRef:".testkit/results/live.json",truncated:!0}}export function latestOperation(r){if(!Array.isArray(r))return null;const t=r.filter(m);return[...t].reverse().find(o=>o.status==="running")??t.at(-1)??null}function d(r){const t=Number(r);return Number.isFinite(t)?t:null}function p(r){return typeof r=="number"&&Number.isFinite(r)?r:null}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import l from"node:fs";import u from"node:os";import n from"node:path";import{pathToFileURL as h}from"node:url";const R="(?<![\\p{L}\\p{N}_.~/\\\\-])",g="(?=$|[\\s\"'`:)\\]}>,;|\\\\])";export function localDirectories(r,o=u.homedir()){return{home:m(o),product:m(r)}}export function localPathRewriter(r){const o=[...f(r.product,""),...f(r.home,"~")];return a=>o.reduce((e,t)=>e.replace(t.pattern,(s,p)=>p===void 0?t.replacement||".":t.replacement&&`${t.replacement}${p}`),a)}function f(r,o){const a=process.platform==="win32"?"giu":"gu";return r.map(e=>n.resolve(e)).filter(e=>e!==n.parse(e).root).flatMap(e=>[{form:h(e).href,separators:["/"]},{form:e,separators:[n.sep,"/"]},{form:e.split(n.sep).join("/"),separators:["/"]},{form:c(e),separators:[c(n.sep),"/"]}]).filter(({form:e},t,s)=>s.findIndex(p=>p.form===e)===t).sort((e,t)=>t.form.length-e.form.length).map(({form:e,separators:t})=>({pattern:new RegExp(`${R}${i(e)}(?:(${[...new Set(t)].map(i).join("|")})|${g})`,a),replacement:o}))}function m(r){if(r==="")return[];const o=n.resolve(r);try{return[...new Set([o,l.realpathSync.native(o)])]}catch{return[o]}}function c(r){return JSON.stringify(r).slice(1,-1)}function i(r){return r.replace(/[\\^$.*+?()[\]{}|]/gu,"\\$&")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import o from"node:fs";import i from"node:path";import{readJsonFile as a,resultsRoot as c}from"./storage.js";import{isUnknownRecord as u}from"../kernel/unknown-values.js";import{writeJsonFileAtomicWithRetry as d}from"../process/files/json-file.js";import{recordEvidenceWrite as m}from"../process/files/evidence-writes.js";export function runMaintenancePath(e){return i.join(c(e),"maintenance.json")}export function writeRunMaintenance(e,t,r){const s={schemaVersion:1,runId:t,recordedAt:new Date().toISOString(),status:r.status,error:r.error},n=runMaintenancePath(e);o.mkdirSync(i.dirname(n),{recursive:!0}),m(n),d(n,s)}export function readRunMaintenance(e){const t=runMaintenancePath(e);if(!o.existsSync(t))return null;const r=a(t);if(!u(r)||r.schemaVersion!==1||typeof r.runId!="string"||typeof r.recordedAt!="string"||r.status!=="failed"&&r.status!=="passed"||r.error!==null&&typeof r.error!="string")throw new Error(`Invalid run maintenance record in ${t}`);return{schemaVersion:1,runId:r.runId,recordedAt:r.recordedAt,status:r.status,error:r.error}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{performance as M}from"node:perf_hooks";import{attachErrorOperation as
|
|
1
|
+
import{performance as M}from"node:perf_hooks";import{attachErrorOperation as x}from"../kernel/error-serialization.js";import{formatOperationSummary as b}from"../domain/operation-display.js";import{isUnknownRecord as I}from"../kernel/unknown-values.js";import{recordWork as k}from"../kernel/work-metrics.js";const g=String;export function createOperationTimingRegistry({onChange:t=null}={}){const r=[],i=new Map;let m=1;function o(e=[]){if(!t)return;const a=[],u=new Set;for(const s of e)!s||u.has(s.id)||(u.add(s.id),a.push(p(s)));k("operations.changedEntriesDelivered",a.length),t(a)}function l({name:e,kind:a="operation",source:u="testkit",summary:s=null,scope:d=null,parentId:n=null,metadata:f=null}){const c={id:`operation-${g(m++)}`,source:u,name:D(e),kind:a,summary:s,scope:h(d),parentId:n,status:"running",startedAt:new Date().toISOString(),updatedAt:null,finishedAt:null,durationMs:null,error:null,message:null,logRefs:[],metadata:h(f),_startedMonotonicMs:M.now()};return r.push(c),i.set(c.id,c),o([c,...w(c)]),c}function A(e,{summary:a,message:u,metadata:s=null,logRefs:d=null}={}){const n=typeof e=="string"?i.get(e):e;return n?(n.updatedAt=new Date().toISOString(),a!==void 0&&(n.summary=a),u!==void 0&&(n.message=u),s!==null&&(n.metadata={...n.metadata,...h(s)}),d!==null&&(n.logRefs=S(d)),o([n,...w(n)]),p(n)):null}function O(e,{status:a="passed",summary:u,error:s=null,metadata:d=null}={}){const n=typeof e=="string"?i.get(e):e;return n?(n.finishedAt=new Date().toISOString(),n.updatedAt=n.finishedAt,n.durationMs=Math.max(0,Math.round(M.now()-n._startedMonotonicMs)),n.status=a,u!==void 0&&(n.summary=u),s!==null&&(n.error=g(s)),d!==null&&(n.metadata={...n.metadata,...h(d)}),o([n,...w(n)]),p(n)):null}function $(e={}){const a=z(e,m++),u=i.get(a.id);return u?(Object.assign(u,a),o([u,...w(u)]),p(u)):(r.push(a),i.set(a.id,a),o([a,...w(a)]),p(a))}function R(){return k("operations.historyClones"),r.map(e=>p(e))}return{start:l,update:A,finish:O,record:$,listOperations:R,countOperations(){return r.length}};function w(e){if(!e.parentId)return[];const a=[],u=new Date().toISOString();let s=i.get(e.parentId);const d=new Set([e.id]);for(;s&&s.status==="running"&&!d.has(s.id);){d.add(s.id),s.updatedAt=u;const n=e.status||"unknown",f=n==="running"?"activeChild":"lastChild";s.message=N(e),s.metadata={...s.metadata,[`${f}OperationId`]:e.id,[`${f}OperationName`]:e.name,[`${f}OperationKind`]:e.kind,[`${f}OperationStatus`]:n,[`${f}OperationSummary`]:e.summary||""},a.push(s),s=s.parentId?i.get(s.parentId):void 0}return a}}export async function timeOperation(t,r,i){if(!t)return i();const m=t.start(r);try{const o=await i(m);return t.finish(m,{status:"passed"}),o}catch(o){const l=t.finish(m,{status:"failed",error:C(o)});throw x(o,l),o}}function z(t,r){const i=y(t.startedAt)||new Date().toISOString(),m=t.status||"passed",o=m==="running"?null:y(t.finishedAt)||i,l=t.durationMs,A=Number(l),O=l!=null&&Number.isFinite(A)?Math.max(0,Math.round(A)):o?Math.max(0,Date.parse(o)-Date.parse(i)):null;return{id:t.id||`operation-${g(r)}`,source:t.source||"testkit",name:D(t.name),kind:t.kind||"operation",summary:t.summary||null,scope:h(t.scope),parentId:t.parentId||null,status:m,startedAt:i,updatedAt:y(t.updatedAt)||null,finishedAt:o,durationMs:O,error:t.error||null,message:t.message||null,logRefs:S(t.logRefs),metadata:h(t.metadata),_startedMonotonicMs:M.now()-(O||0)}}function p(t){return{id:t.id,source:t.source,name:t.name,kind:t.kind,summary:t.summary,scope:t.scope,parentId:t.parentId,status:t.status,startedAt:t.startedAt,updatedAt:t.updatedAt,finishedAt:t.finishedAt,durationMs:t.durationMs,error:t.error,message:t.message,logRefs:S(t.logRefs),metadata:t.metadata}}function D(t){const r=g(t||"").trim();if(!r)throw new Error("operation timing name is required");return r}function h(t){return I(t)?{...t}:{}}function S(t){return Array.isArray(t)?t.map(r=>I(r)?{...r}:r):[]}function N(t){const r=b(t),i=typeof r=="string"?r:"";return i?`${t.name} ${t.status||"unknown"} - ${i}`:`${t.name} ${t.status||"unknown"}`}function y(t){const r=Date.parse(t||"");return Number.isFinite(r)?new Date(r).toISOString():null}function C(t){return(typeof t!="object"||t===null)&&typeof t!="function"?g(t):Reflect.get(t,"message")||g(t)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`,a),d
|
|
3
|
-
`,
|
|
4
|
-
`||e[
|
|
1
|
+
import{isSensitiveKeyName as j,namesSecretValues as E,REDACTED as h,redactSensitiveText as S,sensitiveTextRanges as k}from"../kernel/redaction.js";import{isRedactionTarget as I,MIN_REDACTION_TARGET_LENGTH as J,orderRedactionTargets as P,redactionTargetSpans as v,replaceRedactionTargets as y,sensitiveKeyMatcher as W,urlRedactionTargets as C}from"../kernel/redaction-targets.js";import{isUnknownRecord as A}from"../kernel/unknown-values.js";import{recordWork as D}from"../kernel/work-metrics.js";const V=8,O="\uE010",G=new Set(["data","details","dimensions","metadata","native"]);export function runRedaction({env:e=process.env,redactKeys:t,redactionTargets:n}={}){const c=W(t),l=Object.entries(e).filter(s=>c(s[0])&&typeof s[1]=="string"&&I(s[1]));return{keys:l.map(([s])=>s).sort(),status:n===void 0?"source-targets-unavailable":"applied",targets:P([...l.flatMap(([,s])=>[s,...C(s)]),...n??[]])}}export function redactRunText(e,t){return redactJsonText(e,t)??$(e,t)}function $(e,t){const n=H(v(e,t)),c=[];let l=0,s=0;for(let a=0;a<e.length;){const d=e.indexOf(`
|
|
2
|
+
`,a),i=d===-1?e.length:d,u=e[i-1]==="\r"?i-1:i,b=F(e,a,u,t,n);if(b!==null){const f=M(e.slice(s,b.start),t);c.push(f.text,b.text),l+=f.replacements+b.replacements,s=u}a=i+1}const o=M(e.slice(s),t);return c.push(o.text),{replacements:l+o.replacements,text:c.join("")}}function F(e,t,n,c,l){const s=e.slice(t,n).search(/[[{]/u);if(s<0)return null;const o=t+s;if(l(o,n))return null;const a=e.slice(t,o),d=e.slice(o,n);if(S(a+d)!==S(a)+S(d))return null;const i=redactJsonText(d,c);return i===null?null:{...i,start:o}}function H(e){const t=[...e].sort((l,s)=>l.start-s.start);let n=0,c=-1;return(l,s)=>{let o=0;for(;n<t.length&&t[n].start<l;n+=1)c=Math.max(c,t[n].end),o+=1;let a=c>l;for(let d=n;!a&&d<t.length&&t[d].start<s;d+=1)a=t[d].end>s,o+=1;return D("redaction.spanEdgeSteps",o),a}}function M(e,t){let n=e,c=0;for(let l=0;l<V;l+=1){const s=U(n,t);if(s.text===n)return{replacements:c,text:n};c+=Math.max(s.replacements,1),n=s.text}return{replacements:c+1,text:h}}function U(e,t){const n=v(e,t),c=x(e,t);if(n.length===0&&c.length===0){const s=S(e);return{replacements:s===e?0:1,text:s}}const l=y(e,t,s=>O.repeat(s.length)).text;return z(e,[...k(e),...k(l),...n,...c])}const K=new WeakMap;function N(e){let t=K.get(e);return t===void 0&&(t=e.filter(n=>n.includes(O)||q(n)),K.set(e,t)),t}function q(e){if(e.includes(h)||h.includes(e))return!0;for(let t=1;t<h.length;t+=1)if(e.startsWith(h.slice(-t))||e.endsWith(h.slice(0,t)))return!0;return!1}function x(e,t){const n=N(t);if(n.length===0)return[];if(n.some(l=>/[\r\n]/u.test(l)))return w(e,t)?[L(e)]:[];const c=[];for(let l=0;l<=e.length;){const s=e.indexOf(`
|
|
3
|
+
`,l),o=s===-1?e.length:s,a=e[o-1]==="\r"?o-1:o;w(e.slice(l,a),t)&&c.push({end:a,start:l}),l=o+1}return c.length===0&&w(e,t)?[L(e)]:c}function L(e){const t=e.search(/[^\r\n]/u);let n=e.length;for(;n>t&&(e[n-1]===`
|
|
4
|
+
`||e[n-1]==="\r");)n-=1;return t<0?{end:0,start:0}:{end:n,start:t}}function w(e,t){const n=N(t);if(n.length===0||e.length===0)return!1;let c=!1;const l=s=>o=>(n.some(a=>a.startsWith(o))&&(c=!0),s(o));return y(e,t,l(()=>h)),y(S(e),t,l(()=>h)),y(e,t,l(s=>O.repeat(s.length))),c}function z(e,t){const n=[...t].sort((o,a)=>o.start-a.start||a.end-o.end),c=[];let l=0,s=0;for(let o=0;o<n.length;){const a=n[o].start;let d=n[o].end;for(o+=1;o<n.length&&n[o].start<=d;o+=1)d=Math.max(d,n[o].end);c.push(e.slice(l,a),h),e.slice(a,d)!==h&&(s+=1),l=d}return c.push(e.slice(l)),{replacements:s,text:c.join("")}}export function redactRunValue(e,t){let n=0;function c(i){const u=redactRunText(i,t);return n+=u.replacements,u.text}function l(i,u,b=null){if(Array.isArray(i))return i.map(f=>l(f,u,Array.isArray(f)?null:b));if(A(i)){const f=Object.entries(i),m=u?a(f.map(([g])=>g)):f.map(([g])=>g),T=b==="cookies"||typeof i.name=="string"&&j(i.name);return Object.fromEntries(f.map(([g,r],p)=>[m[p],u&&typeof r=="string"&&(j(g)||g==="value"&&T)?o(r):u&&typeof r=="object"&&r!==null&&E(g)?s(r):l(r,u||G.has(g),Array.isArray(r)?g:null)]))}return typeof i=="string"?c(i):i}function s(i){if(typeof i=="string")return o(i);if(Array.isArray(i))return i.map(s);if(!A(i))return i;const u=Object.entries(i),b=a(u.map(([m])=>m)),f=typeof i.name=="string"&&typeof i.value=="string";return Object.fromEntries(u.map(([m,T],g)=>[b[g],f&&m!=="value"&&typeof T=="string"&&!j(m)?c(T):s(T)]))}function o(i){return i!==h&&(n+=1),h}function a(i){const u=i.map(f=>redactRunText(f,t)),b=new Set(i.filter((f,m)=>u[m].text===f));return i.map((f,m)=>u[m].text===f?f:(n+=u[m].replacements,_(b,u[m].text)))}const d=l(e,!1);return{replacements:n,value:d}}export function redactedRunValue(e,t){return redactRunValue(e,t).value}export function redactJsonText(e,t){if(!/^\s*[[{"]/u.test(e))return null;try{JSON.parse(e)}catch{return null}const n=X(e),c=new Set;for(const r of v(e,t)){const p=Q(n,r.start,r.end);if(p===void 0)return null;B(e,r,t)||c.add(p)}const l=r=>c.has(r)||w(r.decoded,t)?{replacements:r.decoded===h?0:1,text:h}:redactRunText(r.decoded,t),s=new Map(n.filter(r=>r.key).map(r=>[r,l(r)])),o=new Map;for(const[r,p]of s)r.object===null||p.text!==r.decoded||o.set(r.object,(o.get(r.object)??new Set).add(r.decoded));const a=new Map;for(const r of n)!r.key&&r.member==="name"&&r.object!==null&&a.set(r.object,r.decoded);const d=new Set(n.filter(r=>!r.key&&r.member==="value"&&r.object!==null).map(r=>r.object)),i=r=>r!==null&&a.has(r)&&d.has(r),u=r=>r.secret&&(!i(r.object)||r.member==="value")||r.member!==null&&j(r.member)||b(r),b=r=>!r.key&&r.member==="value"&&r.object!==null&&(r.arrayKey==="cookies"||j(a.get(r.object)??"")),f=[];let m=0,T=0;for(const r of n){let p=s.get(r);if(p!==void 0&&p.text!==r.decoded&&r.object!==null){const R=o.get(r.object)??new Set;o.set(r.object,R),p={...p,text:_(R,p.text)}}else p===void 0&&u(r)?p={replacements:r.decoded===h?0:1,text:h}:p===void 0&&(p=l(r));p.text!==r.decoded&&(f.push(e.slice(m,r.start),JSON.stringify(p.text)),m=r.close+1,T+=p.replacements)}f.push(e.slice(m));const g=f.join("");return t.some(r=>g.includes(r))?null:{replacements:T,text:g}}function B(e,t,n){let c=0;for(;e[t.start-1-c]==="\\";)c+=1;if(c%2===1)return!1;const l=e.slice(t.start,t.end);if(!l.includes("\\"))return!0;let s;try{s=JSON.parse(`"${l}"`)}catch{return!1}return t.cut?s.length>=J&&n.some(o=>o.startsWith(s)):n.includes(s)}function Q(e,t,n){let c=0,l=e.length-1;for(;c<=l;){const o=c+l>>1;e[o].start<t?c=o+1:l=o-1}const s=e.at(l);return s!==void 0&&s.start<t&&n<=s.close?s:void 0}function X(e){const t=[],n=[];for(let c=0;c<e.length;c+=1){const l=e[c],s=n.at(-1),o=s!==void 0&&(s.secret||s.id!==null&&s.key!==null&&E(s.key));if(l==="{"?n.push({arrayKey:s?.id===null?s.key:null,id:c,key:null,secret:o}):l==="["?n.push({arrayKey:null,id:null,key:s?.id===null?null:s?.key??null,secret:o}):(l==="}"||l==="]")&&n.pop(),l!=='"')continue;const a=Y(e,c),d=JSON.parse(e.slice(c,a+1)),i=s?.id===null?null:s??null,u=i!==null&&Z(e,a+1)===":";t.push({arrayKey:i?.arrayKey??null,close:a,decoded:d,key:u,member:u?null:i?.key??null,object:i?.id??null,secret:s?.secret??!1,start:c}),u&&(i.key=d),c=a}return t}function Y(e,t){for(let n=t+1;n<e.length;n+=1)if(e[n]==="\\")n+=1;else if(e[n]==='"')return n;return e.length-1}function Z(e,t){for(let n=t;n<e.length;n+=1)if(!/\s/u.test(e[n]))return e[n]}function _(e,t){let n=t;for(let c=2;e.has(n);c++)n=`${t} (${String(c)})`;return e.add(n),n}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import o from"fs";import d from"path";import{finalizeLiveRunArtifact as i,resetResultArtifacts as f}from"./artifacts.js";import{isUnknownRecord as s}from"../kernel/unknown-values.js";export function createRunEvidencePublisher(t={}){return{controllerExited(r,n){i(r,{runId:n.runId,reason:n.reason,redactKeys:t.redactKeys,redactionTargets:t.redactionTargets?.(),status:n.status,finishedAt:n.finishedAt})},runStopped(r,n){i(r,{runId:n.runId,reason:n.reason,status:"interrupted"})},activeRunsListed(r,n){const u=c(r),e=u?.run.id||null;!e||u?.run.status!=="running"||n.has(e)||i(r,{runId:e,reason:"stale live artifact has no active run manifest",status:"interrupted"})},runStarting(r){t.resetResults===!0&&f(r)}}}function c(t){try{const r=JSON.parse(o.readFileSync(d.join(t,".testkit","results","live.json"),"utf8"));return l(r)}catch{return null}}function l(t){if(!s(t)||!s(t.run))return null;const r=t.run.id,n=t.run.status;return!a(r)||!a(n)?null:{run:{id:r,status:n}}}function a(t){return t===void 0||typeof t=="string"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{recordWork as R}from"../kernel/work-metrics.js";const b=String;export function createSetupOperationRegistry({logRegistry:t=null,onChange:g=null}={}){const c=[],m=new Map;let a=1;function f(){g?.()}function h({config:e,stage:s,kind:u="setup",summary:i=null,parentId:n=null,recordLog:r=!0}){const l=new Date().toISOString(),d=r&&t?.ensureSetupLogRecord(e,s)||null,o={id:`setup-${a.toString()}`,serviceName:e.name,runtimeLabel:e.runtimeLabel||e.name,stage:s,kind:u,summary:i,parentId:n,status:"running",startedAt:l,finishedAt:null,durationMs:null,error:null,logRef:d?{path:d.path,stage:d.stage}:null,_logRecord:d};return a+=1,c.push(o),m.set(o.id,o),f(),o}function p(e,{status:s="passed",summary:u,error:i=null}={}){const n=typeof e=="string"?m.get(e):e;if(!n)return null;const r=new Date().toISOString();return n.finishedAt=r,n.durationMs=Math.max(0,Date.parse(r)-Date.parse(n.startedAt||r)),n.status=s,u!==void 0&&(n.summary=u),i!==null&&(n.error=b(i)),f(),n}function A({config:e,stage:s,kind:u="setup",summary:i=null,parentId:n=null}){const r=new Date().toISOString(),l={id:`setup-${a.toString()}`,serviceName:e.name,runtimeLabel:e.runtimeLabel||e.name,stage:s,kind:u,summary:i,parentId:n,status:"cached",startedAt:r,finishedAt:r,durationMs:0,error:null,logRef:null,_logRecord:null};return a+=1,c.push(l),m.set(l.id,l),f(),l}function S(){return R("setup.historyClones"),c.map(y)}return{start:h,finish:p,recordCached:A,listOperations:S}}function y(t){return{id:t.id,serviceName:t.serviceName,runtimeLabel:t.runtimeLabel,stage:t.stage,kind:t.kind,summary:t.summary,parentId:t.parentId,status:t.status,startedAt:t.startedAt,finishedAt:t.finishedAt,durationMs:t.durationMs,error:t.error,logRef:t.logRef}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import l from"fs";import n from"path";import{recordWork as W}from"../kernel/work-metrics.js";const w=20,F=new Set([".testkit/results/latest.json",".testkit/results/live.json"]);export function collectTestkitStorageInventory(e,t={}){const i=n.resolve(e),{rootDir:s,rootRelativePath:o}=I(i,t.rootRelativePath||".testkit"),u=t.largestFileLimit??w;if(!l.existsSync(s))return A(o,u);const f=[],h=[],b=new Set;let k=0,v=0;for(const a of _(s)){const r=m(n.relative(i,a));if(F.has(r))continue;const c=l.statSync(a,{bigint:!0,throwIfNoEntry:!1});if(c===void 0)continue;const d=classifyTestkitStoragePath(r),g=c.ino?`${String(c.dev)}:${String(c.ino)}`:r,P=!b.has(g);b.add(g);const p=Number(c.size);k+=p,P&&(v+=p);const y={path:r,bytes:p,classification:d.classification,reason:d.reason,linkCount:Number(c.nlink)||1,unique:P};d.duplicateOf&&(y.duplicateOf=d.duplicateOf,h.push(y)),f.push(y)}return f.sort((a,r)=>r.bytes-a.bytes||a.path.localeCompare(r.path)),h.sort((a,r)=>r.bytes-a.bytes||a.path.localeCompare(r.path)),{root:o,largestFileLimit:u,totalFiles:f.length,totalLogicalBytes:k,totalUniqueBytes:v,largestFiles:f.slice(0,u),duplicateRepresentations:h}}export function classifyTestkitStoragePath(e){const t=m(e);return t.endsWith(".b64")?{classification:"duplicate_representation",reason:"base64 representation of a sibling artifact",duplicateOf:t.slice(0,-4)}:t.startsWith(".testkit/results/runs/")?{classification:"retained_run_evidence",reason:"immutable run-addressed Testkit evidence"}:t.startsWith(".testkit/results/objects/")?{classification:"content_addressed_evidence",reason:"deduplicated immutable payload content referenced by run bundles"}:t.startsWith(".testkit/results/publications/")||t.startsWith(".testkit/results/retention/")?{classification:"evidence_lifecycle_metadata",reason:"publication receipt or observable retention decision"}:t.startsWith(".testkit/tmp/")||t.startsWith(".testkit/results/")||t.startsWith(".testkit/run-logs/")||t.startsWith(".testkit/logs/")?{classification:"per_run_scratch",reason:"run-scoped Testkit output or scratch state"}:t.startsWith(".testkit/_config/")||t.startsWith(".testkit/_bundles/")?{classification:"per_run_scratch",reason:"generated Testkit runtime state"}:{classification:"testkit_state",reason:"Testkit-owned state outside a more specific storage class"}}function A(e,t){return{root:e,largestFileLimit:t,totalFiles:0,totalLogicalBytes:0,totalUniqueBytes:0,largestFiles:[],duplicateRepresentations:[]}}function*_(e,t="root"){W(`storage.inventoryDirectoriesRead.${t}`);const i=E(e).sort((s,o)=>s.name.localeCompare(o.name));for(const s of i){const o=n.join(e,s.name);s.isDirectory()?yield*_(o,t==="root"?s.name:t):s.isFile()&&(yield o)}}function E(e){try{return l.readdirSync(e,{withFileTypes:!0})}catch(t){if(t instanceof Error&&"code"in t&&t.code==="ENOENT")return[];throw t}}function I(e,t){if(R(t))throw T(t);const i=m(t),s=n.resolve(e,i);if(S(e,s,t),l.existsSync(s)){const o=l.realpathSync.native(e),u=l.realpathSync.native(s);S(o,u,t)}return{rootDir:s,rootRelativePath:i}}function S(e,t,i){const s=n.relative(e,t);if(s===".."||s.startsWith(`..${n.sep}`)||n.isAbsolute(s))throw T(i)}function R(e){return n.isAbsolute(e)||n.posix.isAbsolute(e)||n.win32.isAbsolute(e)}function T(e){return new Error(`Storage inventory root must resolve within the product directory: ${e}`)}function m(e){return L(e||"").replace(/\\/g,"/").replace(/^\/+/,"")}const L=String;
|
package/dist/evidence/storage.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{isWorkObserved as f,recordWork as l}from"../kernel/work-metrics.js";import{isPathWithin as p}from"../process/files/paths.js";import{isUnknownRecord as
|
|
2
|
-
`)}export function hashFile(r){const t=i.readFileSync(r);return l("evidence.hashFilesRead",1),l("evidence.hashBytesRead",t.byteLength),sha256(t)}export function sha256(r){return
|
|
1
|
+
import{isWorkObserved as f,recordWork as l}from"../kernel/work-metrics.js";import{isPathWithin as p}from"../process/files/paths.js";import{isUnknownRecord as v}from"../kernel/unknown-values.js";import g from"crypto";import i from"fs";import o from"path";import{RUNS_DIRNAME as m}from"./contracts.js";import{requireManifestRelativePath as E}from"./read-fields.js";import{posixPath as w}from"../process/files/paths.js";import{errorMessage as k}from"../kernel/error-serialization.js";import{recordEvidenceWrite as S}from"../process/files/evidence-writes.js";export function readJsonFile(r){try{let t;if(f()){const n=i.readFileSync(r);l("json.filesRead",1),l("json.bytesRead",n.byteLength),t=n.toString("utf8")}else t=i.readFileSync(r,"utf8");return JSON.parse(t)}catch(t){throw new Error(`Invalid JSON in ${r}: ${k(t)}`,{cause:t})}}export function runIncludesFile(r,t){const e=w(t);return r.groups.some(n=>n.suites.some(u=>u.files.some(s=>s.path===e||s.path.endsWith(e))))}export function requireRunId(r){const t=r.run.id==null?null:normalizeRunId(r.run.id);if(t===null)throw new Error("Cannot seal a run bundle without a run id");return t}export function normalizeRunId(r){const t=typeof r=="string"?r:"";if(!t||t==="."||t===".."||t.includes("\0")||t.includes("/")||t.includes("\\"))throw new Error(`Invalid Testkit run id: ${t||"<empty>"}`);return t}export function walkFiles(r){if(!i.existsSync(r))return[];const t=[];for(const e of i.readdirSync(r,{withFileTypes:!0})){const n=o.join(r,e.name);e.isDirectory()?t.push(...walkFiles(n)):e.isFile()&&t.push(n)}return t}export function treeBytes(r){return walkFiles(r).reduce((t,e)=>t+i.statSync(e).size,0)}export function writeBufferAtomic(r,t){S(r),i.mkdirSync(o.dirname(r),{recursive:!0});const e=`${r}.${String(process.pid)}.${String(Date.now())}.tmp`;i.writeFileSync(e,t);try{i.renameSync(e,r)}catch(n){if(i.rmSync(e,{force:!0}),!i.existsSync(r))throw n}}export function writeJson(r,t){S(r),i.mkdirSync(o.dirname(r),{recursive:!0});const e=JSON.stringify(t,null,2);l("json.serializations"),f()&&l("json.bytesSerialized",Buffer.byteLength(e)),i.writeFileSync(r,`${e}
|
|
2
|
+
`)}export function hashFile(r){const t=i.readFileSync(r);return l("evidence.hashFilesRead",1),l("evidence.hashBytesRead",t.byteLength),sha256(t)}export function sha256(r){return g.createHash("sha256").update(r).digest("hex")}export function stableJson(r){const t=JSON.stringify(d(r));return l("json.canonicalSerializations"),f()&&l("json.canonicalBytesSerialized",Buffer.byteLength(t)),t}function d(r){return Array.isArray(r)?r.map(d):v(r)?Object.fromEntries(Object.keys(r).sort().map(t=>[t,d(r[t])])):r}export function resolveRunBundleDirectory(r,t){const e=resolveRunsRoot(r,!1),n=o.join(e,t),u=i.lstatSync(n,{throwIfNoEntry:!1});if(u===void 0)return n;if(u.isSymbolicLink())throw new Error(`Immutable run bundle directory must not be a symbolic link: ${t}`);if(!u.isDirectory())throw new Error(`Immutable run bundle path must be a directory: ${t}`);const s=i.realpathSync(e),c=i.realpathSync(n),a=o.relative(s,c);if(a.length===0||a===".."||a.startsWith(`..${o.sep}`)||o.isAbsolute(a))throw new Error(`Immutable run bundle directory must resolve within the runs root: ${t}`);return n}export function resolveRunsRoot(r,t){const e=o.resolve(r),n=resultsRoot(e),u=o.join(n,m);if(t)D(e,n,"Testkit results root"),i.mkdirSync(n,{recursive:!0});else if(!i.existsSync(n))return u;const s=b(n,"Testkit results root");if(y(i.realpathSync(e),s,"Testkit results root must resolve within the product directory"),t)i.mkdirSync(u,{recursive:!0});else if(!i.existsSync(u))return u;const c=b(u,"Immutable run bundles root");return y(s,c,"Immutable run bundles root must resolve within .testkit/results"),u}function D(r,t,e){const n=o.resolve(r),u=i.realpathSync(n);let s=o.resolve(t),c=i.lstatSync(s,{throwIfNoEntry:!1});for(;c===void 0;){const a=o.dirname(s);if(a===s)throw new Error(`${e} has no existing parent directory`);s=a,c=i.lstatSync(s,{throwIfNoEntry:!1})}if(s!==n&&c.isSymbolicLink())throw new Error(`${e} must not traverse a symbolic link`);if(!c.isDirectory())throw new Error(`${e} parent must be a directory`);y(u,i.realpathSync(s),`${e} must resolve within the product directory`,!0)}function b(r,t){const e=i.lstatSync(r,{throwIfNoEntry:!1});if(e===void 0)throw new Error(`${t} does not exist`);if(e.isSymbolicLink())throw new Error(`${t} must not be a symbolic link`);if(!e.isDirectory())throw new Error(`${t} must be a directory`);return i.realpathSync(r)}function y(r,t,e,n=!1){const u=o.relative(r,t);if(!n&&u.length===0||u===".."||u.startsWith(`..${o.sep}`)||o.isAbsolute(u))throw new Error(e)}export function requirePublicationPathOutsideResults(r,t){const e=o.resolve(resultsRoot(r)),n=o.resolve(t);if(p(e,n))throw new Error("Publication directory must be outside .testkit/results");const u=i.realpathSync(e);let s=n,c=i.lstatSync(s,{throwIfNoEntry:!1});for(;c===void 0;){const h=o.dirname(s);if(h===s)throw new Error("Publication directory has no existing parent");s=h,c=i.lstatSync(s,{throwIfNoEntry:!1})}const a=i.realpathSync(s);if(p(u,a))throw new Error("Publication directory must be outside .testkit/results")}export function resolveManifestEntryPath(r,t){const e=o.resolve(r),n=o.resolve(e,E(t,"manifest entry path"));return x(e,n,t),i.existsSync(n)&&x(i.realpathSync(e),i.realpathSync(n),t),n}function x(r,t,e){const n=o.relative(r,t);if(n.length===0||n===".."||n.startsWith(`..${o.sep}`)||o.isAbsolute(n))throw new Error(`manifest path escapes bundle directory: ${e}`)}export function optionalStringArray(r){return Array.isArray(r)?r.filter(t=>typeof t=="string"):[]}export function optionalUnknownArray(r){return Array.isArray(r)?r:[]}export function bundleRelativePath(r,t){return w(o.join(".testkit","results",m,r,t))}export function resultsRoot(r){return o.join(r,".testkit","results")}export function sanitizeSegment(r){return r.toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"")||"product"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{isSensitiveKeyName as m,REDACTED as A,urlCredentials as E}from"./redaction.js";import{recordWork as M}from"./work-metrics.js";export const MIN_REDACTION_TARGET_LENGTH=8;const l=/^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/iu,N=/^postgres(?:ql)?:\/\//iu,u=/["'`]?(?:…|\.\.\.)/gu;export function sensitiveKeyMatcher(t=[]){const r=new Set(t.map(n=>n.toUpperCase()));return n=>m(n)||r.has(n.toUpperCase())}export function isRedactionTarget(t){return t.length>=MIN_REDACTION_TARGET_LENGTH}export function envRedactionTargets(t,r=sensitiveKeyMatcher()){return Object.entries(t).flatMap(([n,e])=>e===void 0?[]:valueRedactionTargets(n,e,r))}export function valueRedactionTargets(t,r,n=sensitiveKeyMatcher()){return l.test(r)?urlRedactionTargets(r):n(t)&&isRedactionTarget(r)?[r]:[]}export function jsonRedactionTargets(t,r,n=sensitiveKeyMatcher()){return typeof r=="string"?valueRedactionTargets(t,r,n):Array.isArray(r)?r.flatMap(e=>jsonRedactionTargets(t,e,n)):r!==null&&typeof r=="object"?Object.entries(r).flatMap(([e,o])=>jsonRedactionTargets(e,o,n)):[]}export function urlRedactionTargets(t){const r=l.exec(t);if(r===null)return[];const n=[...E(t),...N.test(t)?O(t.slice(r[0].length)):[]];return[...new Set(n.flatMap(e=>[e,x(e)]))].filter(isRedactionTarget)}function O(t){return(/\?([^#]*)/u.exec(t)?.[1]??"").split("&").flatMap(n=>{const e=n.indexOf("=");return e>0&&x(n.slice(0,e))==="password"?[n.slice(e+1)]:[]})}export function orderRedactionTargets(t){const r=[...t].filter(isRedactionTarget).flatMap(n=>[n,...n.split(/\r?\n/u)]).flatMap(n=>{const e=T(n);return[n,e,T(e)]});return[...new Set(r)].filter(isRedactionTarget).sort((n,e)=>e.length-n.length)}export function replaceRedactionTargets(t,r,n=()=>A){let e=t,o=0;for(const s of r){if(!e.includes(s))continue;const c=e.split(s);o+=c.length-1,e=c.join(n(s))}const i=w(e,r,n);return{replacements:o+i.replacements,text:i.text}}export function redactionTargetSpans(t,r){const n=[];for(const e of r)for(let o=t.indexOf(e);o!==-1;o=t.indexOf(e,o+1))n.push({cut:!1,end:o+e.length,start:o});for(const e of t.matchAll(u)){const o=h(t,e.index,0,r);o>0&&n.push({cut:!0,end:e.index,start:e.index-o})}return n}function w(t,r,n){const e=[];let o=0,i=0;for(const s of t.matchAll(u)){const c=h(t,s.index,o,r);c!==0&&(e.push(t.slice(o,s.index-c),n(t.slice(s.index-c,s.index))),o=s.index,i+=1)}return e.push(t.slice(o)),{replacements:i,text:e.join("")}}const d=new WeakMap;function h(t,r,n,e){let o=d.get(e);o===void 0&&(o=e.map(y),d.set(e,o));let i=0,s=0;return e.forEach((c,g)=>{const p=Math.max(n,r-(c.length-1));if(r-p<=i||r-p<MIN_REDACTION_TARGET_LENGTH)return;const R=o[g];let f=0;for(let a=p;a<r;a++){for(s+=1;f>0&&c[f]!==t[a];)f=R[f-1],s+=1;c[f]===t[a]&&(f+=1)}f>=MIN_REDACTION_TARGET_LENGTH&&f>i&&(i=f)}),M("redaction.truncationSteps",s),i}function y(t){const r=new Int32Array(t.length);let n=0;for(let e=1;e<t.length;e++){for(;n>0&&t[n]!==t[e];)n=r[n-1];t[n]===t[e]&&(n+=1),r[e]=n}return r}function T(t){return JSON.stringify(t).slice(1,-1)}function x(t){try{return decodeURIComponent(t)}catch{return t}}
|