@artemiskit/core 0.5.2 → 0.5.3
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/CHANGELOG.md +8 -0
- package/dist/comparison/eligibility.d.ts +26 -0
- package/dist/comparison/eligibility.d.ts.map +1 -0
- package/dist/comparison/index.d.ts +2 -0
- package/dist/comparison/index.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -2
- package/dist/storage/local.d.ts.map +1 -1
- package/dist/storage/supabase.d.ts.map +1 -1
- package/dist/storage/types.d.ts +5 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/comparison/eligibility.test.ts +101 -0
- package/src/comparison/eligibility.ts +116 -0
- package/src/comparison/index.ts +8 -0
- package/src/index.ts +3 -0
- package/src/storage/local.test.ts +39 -0
- package/src/storage/local.ts +9 -1
- package/src/storage/supabase.ts +9 -1
- package/src/storage/types.ts +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @artemiskit/core
|
|
2
2
|
|
|
3
|
+
## 0.5.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Qualify standard-run comparisons from bounded workload, rubric, and execution evidence. Changed
|
|
8
|
+
workloads or rubrics withhold deltas, while incomplete historical evidence and changed target
|
|
9
|
+
configuration remain explicitly qualified across storage, CLI, SDK, and reports.
|
|
10
|
+
|
|
3
11
|
## 0.5.2
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility decisions for comparisons of saved scenario-evaluation runs.
|
|
3
|
+
*
|
|
4
|
+
* This contract compares declared evidence only. A matching digest proves the
|
|
5
|
+
* same canonical workload/rubric was declared; it does not attest to provider
|
|
6
|
+
* behaviour or replace a future assessment-profile compatibility decision.
|
|
7
|
+
*/
|
|
8
|
+
import type { RunManifest } from '../artifacts/types';
|
|
9
|
+
export type ComparisonEligibilityStatus = 'compatible' | 'qualified' | 'incomparable';
|
|
10
|
+
export type ComparisonEligibilityReasonCode = 'scenario_mismatch' | 'workload_identity_missing' | 'rubric_identity_missing' | 'workload_mismatch' | 'rubric_mismatch' | 'execution_provenance_missing' | 'target_provider_changed' | 'target_model_changed' | 'generation_settings_changed';
|
|
11
|
+
/** A bounded, machine-readable reason for a comparison decision. */
|
|
12
|
+
export interface ComparisonEligibilityReason {
|
|
13
|
+
code: ComparisonEligibilityReasonCode;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Versioned decision that accompanies every comparison. `qualified` permits
|
|
17
|
+
* a visibly-qualified delta; `incomparable` prohibits a delta entirely.
|
|
18
|
+
*/
|
|
19
|
+
export interface ComparisonEligibility {
|
|
20
|
+
schema_version: '1';
|
|
21
|
+
status: ComparisonEligibilityStatus;
|
|
22
|
+
reasons: ComparisonEligibilityReason[];
|
|
23
|
+
}
|
|
24
|
+
export declare function assessComparisonEligibility(baseline: RunManifest, current: RunManifest): ComparisonEligibility;
|
|
25
|
+
export declare function isComparisonAvailable(eligibility: ComparisonEligibility): boolean;
|
|
26
|
+
//# sourceMappingURL=eligibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eligibility.d.ts","sourceRoot":"","sources":["../../src/comparison/eligibility.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,MAAM,2BAA2B,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC;AAEtF,MAAM,MAAM,+BAA+B,GACvC,mBAAmB,GACnB,2BAA2B,GAC3B,yBAAyB,GACzB,mBAAmB,GACnB,iBAAiB,GACjB,8BAA8B,GAC9B,yBAAyB,GACzB,sBAAsB,GACtB,6BAA6B,CAAC;AAElC,oEAAoE;AACpE,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,+BAA+B,CAAC;CACvC;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,GAAG,CAAC;IACpB,MAAM,EAAE,2BAA2B,CAAC;IACpC,OAAO,EAAE,2BAA2B,EAAE,CAAC;CACxC;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,WAAW,GACnB,qBAAqB,CAmDvB;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,qBAAqB,GAAG,OAAO,CAEjF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/comparison/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,EAChC,KAAK,+BAA+B,EACpC,KAAK,2BAA2B,GACjC,MAAM,eAAe,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,YAAY,CAAC;AAG3B,cAAc,YAAY,CAAC;AAG3B,cAAc,cAAc,CAAC;AAG7B,cAAc,UAAU,CAAC;AAGzB,cAAc,WAAW,CAAC;AAG1B,cAAc,aAAa,CAAC;AAG5B,cAAc,cAAc,CAAC;AAG7B,cAAc,SAAS,CAAC;AAGxB,cAAc,aAAa,CAAC;AAG5B,cAAc,QAAQ,CAAC;AAGvB,cAAc,SAAS,CAAC;AAGxB,cAAc,oBAAoB,CAAC;AAGnC,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,YAAY,CAAC;AAG3B,cAAc,YAAY,CAAC;AAG3B,cAAc,cAAc,CAAC;AAG7B,cAAc,UAAU,CAAC;AAGzB,cAAc,WAAW,CAAC;AAG1B,cAAc,aAAa,CAAC;AAG5B,cAAc,cAAc,CAAC;AAG7B,cAAc,cAAc,CAAC;AAG7B,cAAc,SAAS,CAAC;AAGxB,cAAc,aAAa,CAAC;AAG5B,cAAc,QAAQ,CAAC;AAGvB,cAAc,SAAS,CAAC;AAGxB,cAAc,oBAAoB,CAAC;AAGnC,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -21562,6 +21562,65 @@ function chunkArray(array, size) {
|
|
|
21562
21562
|
// src/storage/local.ts
|
|
21563
21563
|
import { mkdir, readFile as readFile2, readdir as readdir2, unlink, writeFile } from "fs/promises";
|
|
21564
21564
|
import { join as join2, resolve as resolve2 } from "path";
|
|
21565
|
+
|
|
21566
|
+
// src/comparison/eligibility.ts
|
|
21567
|
+
function assessComparisonEligibility(baseline, current) {
|
|
21568
|
+
const reasons = [];
|
|
21569
|
+
if (baseline.config.scenario !== current.config.scenario) {
|
|
21570
|
+
reasons.push({ code: "scenario_mismatch" });
|
|
21571
|
+
}
|
|
21572
|
+
const baselineIdentity = baseline.workload_identity;
|
|
21573
|
+
const currentIdentity = current.workload_identity;
|
|
21574
|
+
if (!baselineIdentity || !currentIdentity) {
|
|
21575
|
+
if (!baselineIdentity || !currentIdentity)
|
|
21576
|
+
reasons.push({ code: "workload_identity_missing" });
|
|
21577
|
+
if (!baselineIdentity || !currentIdentity)
|
|
21578
|
+
reasons.push({ code: "rubric_identity_missing" });
|
|
21579
|
+
} else {
|
|
21580
|
+
if (baselineIdentity.workload.digest !== currentIdentity.workload.digest) {
|
|
21581
|
+
reasons.push({ code: "workload_mismatch" });
|
|
21582
|
+
}
|
|
21583
|
+
if (baselineIdentity.rubric.digest !== currentIdentity.rubric.digest) {
|
|
21584
|
+
reasons.push({ code: "rubric_mismatch" });
|
|
21585
|
+
}
|
|
21586
|
+
}
|
|
21587
|
+
if (reasons.some((reason) => isIncomparableReason(reason.code))) {
|
|
21588
|
+
return { schema_version: "1", status: "incomparable", reasons };
|
|
21589
|
+
}
|
|
21590
|
+
const baselineExecution = baseline.execution_provenance;
|
|
21591
|
+
const currentExecution = current.execution_provenance;
|
|
21592
|
+
if (!baselineExecution || !currentExecution) {
|
|
21593
|
+
reasons.push({ code: "execution_provenance_missing" });
|
|
21594
|
+
} else {
|
|
21595
|
+
if (baselineExecution.target.provider !== currentExecution.target.provider) {
|
|
21596
|
+
reasons.push({ code: "target_provider_changed" });
|
|
21597
|
+
}
|
|
21598
|
+
if (!sameStringSet(baselineExecution.target.requested_models, currentExecution.target.requested_models)) {
|
|
21599
|
+
reasons.push({ code: "target_model_changed" });
|
|
21600
|
+
}
|
|
21601
|
+
if (!sameGeneration(baselineExecution.target.generation, currentExecution.target.generation)) {
|
|
21602
|
+
reasons.push({ code: "generation_settings_changed" });
|
|
21603
|
+
}
|
|
21604
|
+
}
|
|
21605
|
+
return {
|
|
21606
|
+
schema_version: "1",
|
|
21607
|
+
status: reasons.length === 0 ? "compatible" : "qualified",
|
|
21608
|
+
reasons
|
|
21609
|
+
};
|
|
21610
|
+
}
|
|
21611
|
+
function isComparisonAvailable(eligibility) {
|
|
21612
|
+
return eligibility.status !== "incomparable";
|
|
21613
|
+
}
|
|
21614
|
+
function isIncomparableReason(code) {
|
|
21615
|
+
return code === "scenario_mismatch" || code === "workload_mismatch" || code === "rubric_mismatch";
|
|
21616
|
+
}
|
|
21617
|
+
function sameStringSet(left, right) {
|
|
21618
|
+
return JSON.stringify([...left ?? []].sort()) === JSON.stringify([...right ?? []].sort());
|
|
21619
|
+
}
|
|
21620
|
+
function sameGeneration(left, right) {
|
|
21621
|
+
return left?.temperature === right?.temperature && left?.max_tokens === right?.max_tokens && left?.seed === right?.seed;
|
|
21622
|
+
}
|
|
21623
|
+
// src/storage/local.ts
|
|
21565
21624
|
function getManifestType(manifest) {
|
|
21566
21625
|
if ("type" in manifest) {
|
|
21567
21626
|
if (manifest.type === "redteam")
|
|
@@ -21701,9 +21760,14 @@ class LocalStorageAdapter {
|
|
|
21701
21760
|
this.loadRun(baselineId),
|
|
21702
21761
|
this.loadRun(currentId)
|
|
21703
21762
|
]);
|
|
21763
|
+
const eligibility = assessComparisonEligibility(baseline, current);
|
|
21764
|
+
if (!isComparisonAvailable(eligibility)) {
|
|
21765
|
+
return { baseline, current, eligibility };
|
|
21766
|
+
}
|
|
21704
21767
|
return {
|
|
21705
21768
|
baseline,
|
|
21706
21769
|
current,
|
|
21770
|
+
eligibility,
|
|
21707
21771
|
delta: {
|
|
21708
21772
|
successRate: current.metrics.success_rate - baseline.metrics.success_rate,
|
|
21709
21773
|
latency: current.metrics.median_latency_ms - baseline.metrics.median_latency_ms,
|
|
@@ -21802,7 +21866,7 @@ class LocalStorageAdapter {
|
|
|
21802
21866
|
return null;
|
|
21803
21867
|
}
|
|
21804
21868
|
const comparison = await this.compare(baseline.runId, runId);
|
|
21805
|
-
const hasRegression = comparison.delta.successRate < -regressionThreshold;
|
|
21869
|
+
const hasRegression = comparison.delta !== undefined && comparison.delta.successRate < -regressionThreshold;
|
|
21806
21870
|
return {
|
|
21807
21871
|
baseline,
|
|
21808
21872
|
comparison,
|
|
@@ -30448,9 +30512,14 @@ class SupabaseStorageAdapter {
|
|
|
30448
30512
|
}
|
|
30449
30513
|
async compare(baselineId, currentId) {
|
|
30450
30514
|
const [baseline, current] = await Promise.all([this.load(baselineId), this.load(currentId)]);
|
|
30515
|
+
const eligibility = assessComparisonEligibility(baseline, current);
|
|
30516
|
+
if (!isComparisonAvailable(eligibility)) {
|
|
30517
|
+
return { baseline, current, eligibility };
|
|
30518
|
+
}
|
|
30451
30519
|
return {
|
|
30452
30520
|
baseline,
|
|
30453
30521
|
current,
|
|
30522
|
+
eligibility,
|
|
30454
30523
|
delta: {
|
|
30455
30524
|
successRate: current.metrics.success_rate - baseline.metrics.success_rate,
|
|
30456
30525
|
latency: current.metrics.median_latency_ms - baseline.metrics.median_latency_ms,
|
|
@@ -30581,7 +30650,7 @@ class SupabaseStorageAdapter {
|
|
|
30581
30650
|
return null;
|
|
30582
30651
|
}
|
|
30583
30652
|
const comparison = await this.compare(baseline.runId, runId);
|
|
30584
|
-
const hasRegression = comparison.delta.successRate < -regressionThreshold;
|
|
30653
|
+
const hasRegression = comparison.delta !== undefined && comparison.delta.successRate < -regressionThreshold;
|
|
30585
30654
|
return {
|
|
30586
30655
|
baseline,
|
|
30587
30656
|
comparison,
|
|
@@ -32924,6 +32993,7 @@ export {
|
|
|
32924
32993
|
actionBudgetExceeded,
|
|
32925
32994
|
adapterRegistry,
|
|
32926
32995
|
assertRunManifestIntegrity,
|
|
32996
|
+
assessComparisonEligibility,
|
|
32927
32997
|
createAdapter,
|
|
32928
32998
|
createDefaultRedactor,
|
|
32929
32999
|
createExecutionProvenance,
|
|
@@ -32945,6 +33015,7 @@ export {
|
|
|
32945
33015
|
getModelPricing,
|
|
32946
33016
|
hashText,
|
|
32947
33017
|
isArtemisError,
|
|
33018
|
+
isComparisonAvailable,
|
|
32948
33019
|
isRedTeamManifest,
|
|
32949
33020
|
isRunManifest,
|
|
32950
33021
|
isStressManifest,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/storage/local.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,cAAc,EAGpB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/storage/local.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,cAAc,EAGpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,WAAW,EACX,WAAW,EACZ,MAAM,SAAS,CAAC;AAyDjB,qBAAa,mBAAoB,YAAW,sBAAsB;IAChE,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAS;gBAElB,QAAQ,SAAmB;IAKjC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAa5C,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAczC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAS5C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAQpD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAQlD,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA0DnD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpC,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAuBjE,eAAe;YASf,SAAS;IAWvB;;OAEG;YACW,iBAAiB;IAS/B;;OAEG;YACW,iBAAiB;IAM/B;;OAEG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4B3F;;OAEG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAKrE;;OAEG;IACG,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAMzE;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAOlD;;OAEG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUxD;;OAEG;IACG,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW5D;;OAEG;IACG,iBAAiB,CACrB,KAAK,EAAE,MAAM,EACb,mBAAmB,SAAO,GACzB,OAAO,CAAC;QACT,QAAQ,EAAE,gBAAgB,CAAC;QAC3B,UAAU,EAAE,gBAAgB,CAAC;QAC7B,aAAa,EAAE,OAAO,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;KAC7B,GAAG,IAAI,CAAC;CAyBV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/storage/supabase.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAEL,KAAK,WAAW,EAGjB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/storage/supabase.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAEL,KAAK,WAAW,EAGjB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EACV,uBAAuB,EACvB,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,cAAc,EACf,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA0BD,qBAAa,sBAAuB,YAAW,uBAAuB;IACpE,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,qBAAqB,EAAE,OAAO,CAAC,EAAE,MAAM;IAUrD,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAwD5C,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAyBzC,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAiCnD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAepC,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwBzE,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkDrF,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IA4B/D,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IA2BnE,aAAa,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA2B5C,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAclD,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUtD,iBAAiB,CACrB,KAAK,EAAE,MAAM,EACb,mBAAmB,SAAO,GACzB,OAAO,CAAC;QACT,QAAQ,EAAE,gBAAgB,CAAC;QAC3B,UAAU,EAAE,gBAAgB,CAAC;QAC7B,aAAa,EAAE,OAAO,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;KAC7B,GAAG,IAAI,CAAC;IAqCH,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAiCzD,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAoC/D,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAiC1D,gBAAgB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAwD9E,mBAAmB,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAgC/D,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAsCxE,kBAAkB,CACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAyC5B,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC;CA0E5B"}
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Storage types and interfaces
|
|
3
3
|
*/
|
|
4
4
|
import type { AnyManifest, CaseEvaluationEvidence, RedTeamManifest, RunManifest, StressManifest } from '../artifacts/types';
|
|
5
|
+
import type { ComparisonEligibility } from '../comparison';
|
|
5
6
|
/**
|
|
6
7
|
* Run listing item
|
|
7
8
|
*/
|
|
@@ -21,7 +22,10 @@ export interface RunListItem {
|
|
|
21
22
|
export interface ComparisonResult {
|
|
22
23
|
baseline: RunManifest;
|
|
23
24
|
current: RunManifest;
|
|
24
|
-
delta
|
|
25
|
+
/** Compatibility decision made before any metric delta is calculated. */
|
|
26
|
+
eligibility: ComparisonEligibility;
|
|
27
|
+
/** Absent when workloads or rubrics are incomparable. */
|
|
28
|
+
delta?: {
|
|
25
29
|
successRate: number;
|
|
26
30
|
latency: number;
|
|
27
31
|
tokens: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,WAAW,EACX,cAAc,EACf,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import type { RunManifest } from '../artifacts';
|
|
3
|
+
import { assessComparisonEligibility, isComparisonAvailable } from './eligibility';
|
|
4
|
+
|
|
5
|
+
function manifest(overrides: Partial<RunManifest> = {}): RunManifest {
|
|
6
|
+
return {
|
|
7
|
+
version: '1.3',
|
|
8
|
+
run_id: 'run-id',
|
|
9
|
+
project: 'project',
|
|
10
|
+
start_time: '2026-09-10T00:00:00.000Z',
|
|
11
|
+
end_time: '2026-09-10T00:00:01.000Z',
|
|
12
|
+
duration_ms: 1000,
|
|
13
|
+
config: { scenario: 'customer-service', provider: 'openai', model: 'model-a' },
|
|
14
|
+
workload_identity: {
|
|
15
|
+
schema_version: '1',
|
|
16
|
+
workload: { schema_version: '1', algorithm: 'sha256', digest: 'a'.repeat(64) },
|
|
17
|
+
rubric: { schema_version: '1', algorithm: 'sha256', digest: 'b'.repeat(64) },
|
|
18
|
+
},
|
|
19
|
+
execution_provenance: {
|
|
20
|
+
schema_version: '1',
|
|
21
|
+
target: { provider: 'openai', requested_models: ['model-a'], generation: { temperature: 0 } },
|
|
22
|
+
},
|
|
23
|
+
metrics: {
|
|
24
|
+
success_rate: 1,
|
|
25
|
+
total_cases: 1,
|
|
26
|
+
passed_cases: 1,
|
|
27
|
+
failed_cases: 0,
|
|
28
|
+
median_latency_ms: 1,
|
|
29
|
+
p95_latency_ms: 1,
|
|
30
|
+
total_tokens: 1,
|
|
31
|
+
total_prompt_tokens: 1,
|
|
32
|
+
total_completion_tokens: 0,
|
|
33
|
+
},
|
|
34
|
+
git: { commit: 'commit', branch: 'main', dirty: false },
|
|
35
|
+
provenance: { run_by: 'test' },
|
|
36
|
+
cases: [],
|
|
37
|
+
environment: { node_version: 'test', platform: 'test', arch: 'test' },
|
|
38
|
+
...overrides,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('assessComparisonEligibility', () => {
|
|
43
|
+
test('accepts matching declared workload, rubric, and execution configuration', () => {
|
|
44
|
+
const eligibility = assessComparisonEligibility(manifest(), manifest({ run_id: 'current' }));
|
|
45
|
+
|
|
46
|
+
expect(eligibility).toEqual({ schema_version: '1', status: 'compatible', reasons: [] });
|
|
47
|
+
expect(isComparisonAvailable(eligibility)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('qualifies legacy artifacts instead of presenting them as fully compatible', () => {
|
|
51
|
+
const legacy = manifest({ workload_identity: undefined, execution_provenance: undefined });
|
|
52
|
+
const eligibility = assessComparisonEligibility(legacy, manifest({ run_id: 'current' }));
|
|
53
|
+
|
|
54
|
+
expect(eligibility.status).toBe('qualified');
|
|
55
|
+
expect(eligibility.reasons.map((reason) => reason.code)).toEqual([
|
|
56
|
+
'workload_identity_missing',
|
|
57
|
+
'rubric_identity_missing',
|
|
58
|
+
'execution_provenance_missing',
|
|
59
|
+
]);
|
|
60
|
+
expect(isComparisonAvailable(eligibility)).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('qualifies a deliberate target-model change without treating it as the same execution', () => {
|
|
64
|
+
const current = manifest({
|
|
65
|
+
run_id: 'current',
|
|
66
|
+
execution_provenance: {
|
|
67
|
+
schema_version: '1',
|
|
68
|
+
target: {
|
|
69
|
+
provider: 'openai',
|
|
70
|
+
requested_models: ['model-b'],
|
|
71
|
+
generation: { temperature: 0 },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const eligibility = assessComparisonEligibility(manifest(), current);
|
|
77
|
+
|
|
78
|
+
expect(eligibility.status).toBe('qualified');
|
|
79
|
+
expect(eligibility.reasons).toEqual([{ code: 'target_model_changed' }]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('refuses deltas when workload or rubric evidence differs', () => {
|
|
83
|
+
const current = manifest({
|
|
84
|
+
run_id: 'current',
|
|
85
|
+
workload_identity: {
|
|
86
|
+
schema_version: '1',
|
|
87
|
+
workload: { schema_version: '1', algorithm: 'sha256', digest: 'c'.repeat(64) },
|
|
88
|
+
rubric: { schema_version: '1', algorithm: 'sha256', digest: 'd'.repeat(64) },
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const eligibility = assessComparisonEligibility(manifest(), current);
|
|
93
|
+
|
|
94
|
+
expect(eligibility.status).toBe('incomparable');
|
|
95
|
+
expect(eligibility.reasons.map((reason) => reason.code)).toEqual([
|
|
96
|
+
'workload_mismatch',
|
|
97
|
+
'rubric_mismatch',
|
|
98
|
+
]);
|
|
99
|
+
expect(isComparisonAvailable(eligibility)).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility decisions for comparisons of saved scenario-evaluation runs.
|
|
3
|
+
*
|
|
4
|
+
* This contract compares declared evidence only. A matching digest proves the
|
|
5
|
+
* same canonical workload/rubric was declared; it does not attest to provider
|
|
6
|
+
* behaviour or replace a future assessment-profile compatibility decision.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { RunManifest } from '../artifacts/types';
|
|
10
|
+
|
|
11
|
+
export type ComparisonEligibilityStatus = 'compatible' | 'qualified' | 'incomparable';
|
|
12
|
+
|
|
13
|
+
export type ComparisonEligibilityReasonCode =
|
|
14
|
+
| 'scenario_mismatch'
|
|
15
|
+
| 'workload_identity_missing'
|
|
16
|
+
| 'rubric_identity_missing'
|
|
17
|
+
| 'workload_mismatch'
|
|
18
|
+
| 'rubric_mismatch'
|
|
19
|
+
| 'execution_provenance_missing'
|
|
20
|
+
| 'target_provider_changed'
|
|
21
|
+
| 'target_model_changed'
|
|
22
|
+
| 'generation_settings_changed';
|
|
23
|
+
|
|
24
|
+
/** A bounded, machine-readable reason for a comparison decision. */
|
|
25
|
+
export interface ComparisonEligibilityReason {
|
|
26
|
+
code: ComparisonEligibilityReasonCode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Versioned decision that accompanies every comparison. `qualified` permits
|
|
31
|
+
* a visibly-qualified delta; `incomparable` prohibits a delta entirely.
|
|
32
|
+
*/
|
|
33
|
+
export interface ComparisonEligibility {
|
|
34
|
+
schema_version: '1';
|
|
35
|
+
status: ComparisonEligibilityStatus;
|
|
36
|
+
reasons: ComparisonEligibilityReason[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function assessComparisonEligibility(
|
|
40
|
+
baseline: RunManifest,
|
|
41
|
+
current: RunManifest
|
|
42
|
+
): ComparisonEligibility {
|
|
43
|
+
const reasons: ComparisonEligibilityReason[] = [];
|
|
44
|
+
|
|
45
|
+
if (baseline.config.scenario !== current.config.scenario) {
|
|
46
|
+
reasons.push({ code: 'scenario_mismatch' });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const baselineIdentity = baseline.workload_identity;
|
|
50
|
+
const currentIdentity = current.workload_identity;
|
|
51
|
+
if (!baselineIdentity || !currentIdentity) {
|
|
52
|
+
if (!baselineIdentity || !currentIdentity) reasons.push({ code: 'workload_identity_missing' });
|
|
53
|
+
if (!baselineIdentity || !currentIdentity) reasons.push({ code: 'rubric_identity_missing' });
|
|
54
|
+
} else {
|
|
55
|
+
if (baselineIdentity.workload.digest !== currentIdentity.workload.digest) {
|
|
56
|
+
reasons.push({ code: 'workload_mismatch' });
|
|
57
|
+
}
|
|
58
|
+
if (baselineIdentity.rubric.digest !== currentIdentity.rubric.digest) {
|
|
59
|
+
reasons.push({ code: 'rubric_mismatch' });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (reasons.some((reason) => isIncomparableReason(reason.code))) {
|
|
64
|
+
return { schema_version: '1', status: 'incomparable', reasons };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const baselineExecution = baseline.execution_provenance;
|
|
68
|
+
const currentExecution = current.execution_provenance;
|
|
69
|
+
if (!baselineExecution || !currentExecution) {
|
|
70
|
+
reasons.push({ code: 'execution_provenance_missing' });
|
|
71
|
+
} else {
|
|
72
|
+
if (baselineExecution.target.provider !== currentExecution.target.provider) {
|
|
73
|
+
reasons.push({ code: 'target_provider_changed' });
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
!sameStringSet(
|
|
77
|
+
baselineExecution.target.requested_models,
|
|
78
|
+
currentExecution.target.requested_models
|
|
79
|
+
)
|
|
80
|
+
) {
|
|
81
|
+
reasons.push({ code: 'target_model_changed' });
|
|
82
|
+
}
|
|
83
|
+
if (!sameGeneration(baselineExecution.target.generation, currentExecution.target.generation)) {
|
|
84
|
+
reasons.push({ code: 'generation_settings_changed' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
schema_version: '1',
|
|
90
|
+
status: reasons.length === 0 ? 'compatible' : 'qualified',
|
|
91
|
+
reasons,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isComparisonAvailable(eligibility: ComparisonEligibility): boolean {
|
|
96
|
+
return eligibility.status !== 'incomparable';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isIncomparableReason(code: ComparisonEligibilityReasonCode): boolean {
|
|
100
|
+
return code === 'scenario_mismatch' || code === 'workload_mismatch' || code === 'rubric_mismatch';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function sameStringSet(left?: string[], right?: string[]): boolean {
|
|
104
|
+
return JSON.stringify([...(left ?? [])].sort()) === JSON.stringify([...(right ?? [])].sort());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function sameGeneration(
|
|
108
|
+
left?: { temperature?: number; max_tokens?: number; seed?: number },
|
|
109
|
+
right?: { temperature?: number; max_tokens?: number; seed?: number }
|
|
110
|
+
): boolean {
|
|
111
|
+
return (
|
|
112
|
+
left?.temperature === right?.temperature &&
|
|
113
|
+
left?.max_tokens === right?.max_tokens &&
|
|
114
|
+
left?.seed === right?.seed
|
|
115
|
+
);
|
|
116
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -179,6 +179,45 @@ describe('LocalStorageAdapter', () => {
|
|
|
179
179
|
expect(comparison.delta.successRate).toBeCloseTo(0.1, 2);
|
|
180
180
|
expect(comparison.delta.latency).toBe(-30);
|
|
181
181
|
expect(comparison.delta.tokens).toBe(100);
|
|
182
|
+
expect(comparison.eligibility.status).toBe('qualified');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('withholds deltas for incompatible workload evidence', async () => {
|
|
186
|
+
const baseline = {
|
|
187
|
+
...mockManifest,
|
|
188
|
+
run_id: 'incompatible-baseline',
|
|
189
|
+
workload_identity: {
|
|
190
|
+
schema_version: '1' as const,
|
|
191
|
+
workload: {
|
|
192
|
+
schema_version: '1' as const,
|
|
193
|
+
algorithm: 'sha256' as const,
|
|
194
|
+
digest: 'a'.repeat(64),
|
|
195
|
+
},
|
|
196
|
+
rubric: {
|
|
197
|
+
schema_version: '1' as const,
|
|
198
|
+
algorithm: 'sha256' as const,
|
|
199
|
+
digest: 'b'.repeat(64),
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
execution_provenance: { schema_version: '1' as const, target: { provider: 'openai' } },
|
|
203
|
+
};
|
|
204
|
+
const current = {
|
|
205
|
+
...baseline,
|
|
206
|
+
run_id: 'incompatible-current',
|
|
207
|
+
workload_identity: {
|
|
208
|
+
...baseline.workload_identity,
|
|
209
|
+
workload: { ...baseline.workload_identity.workload, digest: 'c'.repeat(64) },
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
await storage.save(baseline);
|
|
214
|
+
await storage.save(current);
|
|
215
|
+
|
|
216
|
+
const comparison = await storage.compare('incompatible-baseline', 'incompatible-current');
|
|
217
|
+
|
|
218
|
+
expect(comparison.eligibility.status).toBe('incomparable');
|
|
219
|
+
expect(comparison.eligibility.reasons).toEqual([{ code: 'workload_mismatch' }]);
|
|
220
|
+
expect(comparison.delta).toBeUndefined();
|
|
182
221
|
});
|
|
183
222
|
|
|
184
223
|
test('handles empty storage gracefully', async () => {
|
package/src/storage/local.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
assertRunManifestIntegrity,
|
|
13
13
|
isRunManifest,
|
|
14
14
|
} from '../artifacts/types';
|
|
15
|
+
import { assessComparisonEligibility, isComparisonAvailable } from '../comparison';
|
|
15
16
|
import type {
|
|
16
17
|
BaselineMetadata,
|
|
17
18
|
BaselineStorageAdapter,
|
|
@@ -212,9 +213,15 @@ export class LocalStorageAdapter implements BaselineStorageAdapter {
|
|
|
212
213
|
this.loadRun(currentId),
|
|
213
214
|
]);
|
|
214
215
|
|
|
216
|
+
const eligibility = assessComparisonEligibility(baseline, current);
|
|
217
|
+
if (!isComparisonAvailable(eligibility)) {
|
|
218
|
+
return { baseline, current, eligibility };
|
|
219
|
+
}
|
|
220
|
+
|
|
215
221
|
return {
|
|
216
222
|
baseline,
|
|
217
223
|
current,
|
|
224
|
+
eligibility,
|
|
218
225
|
delta: {
|
|
219
226
|
successRate: current.metrics.success_rate - baseline.metrics.success_rate,
|
|
220
227
|
latency: current.metrics.median_latency_ms - baseline.metrics.median_latency_ms,
|
|
@@ -375,7 +382,8 @@ export class LocalStorageAdapter implements BaselineStorageAdapter {
|
|
|
375
382
|
const comparison = await this.compare(baseline.runId, runId);
|
|
376
383
|
|
|
377
384
|
// Check for regression (negative delta in success rate)
|
|
378
|
-
const hasRegression =
|
|
385
|
+
const hasRegression =
|
|
386
|
+
comparison.delta !== undefined && comparison.delta.successRate < -regressionThreshold;
|
|
379
387
|
|
|
380
388
|
return {
|
|
381
389
|
baseline,
|
package/src/storage/supabase.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
assertRunManifestIntegrity,
|
|
10
10
|
getCaseEvaluationStatus,
|
|
11
11
|
} from '../artifacts/types';
|
|
12
|
+
import { assessComparisonEligibility, isComparisonAvailable } from '../comparison';
|
|
12
13
|
import type {
|
|
13
14
|
AnalyticsStorageAdapter,
|
|
14
15
|
BaselineMetadata,
|
|
@@ -199,9 +200,15 @@ export class SupabaseStorageAdapter implements AnalyticsStorageAdapter {
|
|
|
199
200
|
async compare(baselineId: string, currentId: string): Promise<ComparisonResult> {
|
|
200
201
|
const [baseline, current] = await Promise.all([this.load(baselineId), this.load(currentId)]);
|
|
201
202
|
|
|
203
|
+
const eligibility = assessComparisonEligibility(baseline, current);
|
|
204
|
+
if (!isComparisonAvailable(eligibility)) {
|
|
205
|
+
return { baseline, current, eligibility };
|
|
206
|
+
}
|
|
207
|
+
|
|
202
208
|
return {
|
|
203
209
|
baseline,
|
|
204
210
|
current,
|
|
211
|
+
eligibility,
|
|
205
212
|
delta: {
|
|
206
213
|
successRate: current.metrics.success_rate - baseline.metrics.success_rate,
|
|
207
214
|
latency: current.metrics.median_latency_ms - baseline.metrics.median_latency_ms,
|
|
@@ -400,7 +407,8 @@ export class SupabaseStorageAdapter implements AnalyticsStorageAdapter {
|
|
|
400
407
|
const comparison = await this.compare(baseline.runId, runId);
|
|
401
408
|
|
|
402
409
|
// Check for regression (success rate dropped by more than threshold)
|
|
403
|
-
const hasRegression =
|
|
410
|
+
const hasRegression =
|
|
411
|
+
comparison.delta !== undefined && comparison.delta.successRate < -regressionThreshold;
|
|
404
412
|
|
|
405
413
|
return {
|
|
406
414
|
baseline,
|
package/src/storage/types.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
RunManifest,
|
|
10
10
|
StressManifest,
|
|
11
11
|
} from '../artifacts/types';
|
|
12
|
+
import type { ComparisonEligibility } from '../comparison';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Run listing item
|
|
@@ -30,7 +31,10 @@ export interface RunListItem {
|
|
|
30
31
|
export interface ComparisonResult {
|
|
31
32
|
baseline: RunManifest;
|
|
32
33
|
current: RunManifest;
|
|
33
|
-
delta
|
|
34
|
+
/** Compatibility decision made before any metric delta is calculated. */
|
|
35
|
+
eligibility: ComparisonEligibility;
|
|
36
|
+
/** Absent when workloads or rubrics are incomparable. */
|
|
37
|
+
delta?: {
|
|
34
38
|
successRate: number;
|
|
35
39
|
latency: number;
|
|
36
40
|
tokens: number;
|