@aiwg/cli 2026.9.21 → 2026.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agentic/code/providers/capability-matrix.yaml +2 -1
- package/dist/src/artifacts/cli.js +23 -18
- package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
- package/dist/src/artifacts/discovery-shadow.js +195 -0
- package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
- package/dist/src/artifacts/index-builder.js +47 -9
- package/dist/src/artifacts/query-engine.js +108 -5
- package/dist/src/artifacts/types.js +1 -0
- package/dist/src/cli/handlers/decision.js +88 -0
- package/dist/src/cli/handlers/gates.js +154 -0
- package/dist/src/cli/handlers/index.js +5 -1
- package/dist/src/cli/handlers/sessions.js +10 -7
- package/dist/src/cli/handlers/utilities.js +20 -0
- package/dist/src/config/aiwg-config.js +15 -2
- package/dist/src/decision/acceptance.js +25 -0
- package/dist/src/decision/adapters/jev.js +24 -2
- package/dist/src/decision/calibration/registry.js +9 -0
- package/dist/src/decision/compose.js +74 -15
- package/dist/src/decision/context-live-promotion.js +344 -0
- package/dist/src/decision/context-live-qualification.js +591 -0
- package/dist/src/decision/context-pruning.js +847 -0
- package/dist/src/decision/driver.js +430 -0
- package/dist/src/decision/egress-live-qualification.js +874 -0
- package/dist/src/decision/ensemble/contract.js +64 -1
- package/dist/src/decision/ensemble/index.js +1 -0
- package/dist/src/decision/ensemble/report.js +8 -42
- package/dist/src/decision/ensemble/runtime.js +571 -0
- package/dist/src/decision/ensemble-study/artifacts.js +85 -0
- package/dist/src/decision/ensemble-study/calibration.js +443 -0
- package/dist/src/decision/ensemble-study/corpus.js +229 -0
- package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
- package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
- package/dist/src/decision/ensemble-study/multifact.js +241 -0
- package/dist/src/decision/ensemble-study/protocol.js +77 -0
- package/dist/src/decision/ensemble-study/score.js +462 -0
- package/dist/src/decision/ensemble-study/staged.js +87 -0
- package/dist/src/decision/ensemble-study/statistics.js +84 -0
- package/dist/src/decision/evaluate.js +227 -14
- package/dist/src/decision/export.js +4 -0
- package/dist/src/decision/feature-export.js +1074 -0
- package/dist/src/decision/graph-index.js +16 -0
- package/dist/src/decision/graph-live-qualification.js +1041 -0
- package/dist/src/decision/graph-live-workload.js +245 -0
- package/dist/src/decision/graph-skill-bridge.js +21 -3
- package/dist/src/decision/heldout/calibration.js +140 -0
- package/dist/src/decision/heldout/collector.js +430 -0
- package/dist/src/decision/heldout/contract.js +309 -0
- package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
- package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
- package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
- package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
- package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
- package/dist/src/decision/heldout/d29-pools.js +288 -0
- package/dist/src/decision/heldout/d29-v6.js +169 -0
- package/dist/src/decision/heldout/d29-v7.js +202 -0
- package/dist/src/decision/heldout/d29-v8.js +586 -0
- package/dist/src/decision/heldout/generator-registry.js +67 -0
- package/dist/src/decision/heldout/generators.js +696 -0
- package/dist/src/decision/heldout/index.js +8 -0
- package/dist/src/decision/heldout/journal.js +346 -0
- package/dist/src/decision/heldout/study-generators.js +64 -0
- package/dist/src/decision/heldout/types.js +2 -0
- package/dist/src/decision/host-policy.js +61 -0
- package/dist/src/decision/index.js +11 -0
- package/dist/src/decision/interop-profile.v1.json +112 -0
- package/dist/src/decision/interop.js +653 -0
- package/dist/src/decision/issue-triage/index.js +3 -0
- package/dist/src/decision/issue-triage/runtime.js +1069 -0
- package/dist/src/decision/issue-triage/types.js +2 -0
- package/dist/src/decision/patterns/catalog.js +28 -8
- package/dist/src/decision/patterns/playground.js +28 -11
- package/dist/src/decision/patterns/specs.js +19 -10
- package/dist/src/decision/preprocessed-evidence.js +724 -0
- package/dist/src/decision/qualification/capture.js +8 -2
- package/dist/src/decision/qualification/gate-evidence.js +19 -4
- package/dist/src/decision/qualification/load.js +8 -1
- package/dist/src/decision/qualification/quality.js +57 -49
- package/dist/src/decision/qualification/release.js +76 -3
- package/dist/src/decision/qualification/runner.js +5 -2
- package/dist/src/decision/routing/contract.js +175 -0
- package/dist/src/decision/routing/index.js +5 -0
- package/dist/src/decision/routing/report.js +216 -0
- package/dist/src/decision/routing/runtime.js +685 -0
- package/dist/src/decision/routing/types.js +2 -0
- package/dist/src/decision/sdlc-screening.js +1157 -0
- package/dist/src/decision/sensitivity/comparative.js +154 -0
- package/dist/src/decision/sensitivity/contract.js +218 -0
- package/dist/src/decision/sensitivity/index.js +5 -0
- package/dist/src/decision/sensitivity/runtime.js +912 -0
- package/dist/src/decision/sensitivity/types.js +2 -0
- package/dist/src/decision/validate.js +26 -0
- package/dist/src/extensions/commands/definitions.js +38 -0
- package/dist/src/extensions/manifest.js +44 -0
- package/dist/src/gates/discovery.js +315 -0
- package/dist/src/gates/driver.js +388 -0
- package/dist/src/gates/evaluate.js +719 -0
- package/dist/src/gates/floors.js +255 -0
- package/dist/src/gates/index.js +17 -0
- package/dist/src/gates/providers/evidence.js +38 -0
- package/dist/src/gates/providers/index.js +21 -0
- package/dist/src/gates/providers/isolated-child-source.js +446 -0
- package/dist/src/gates/providers/loader.js +1069 -0
- package/dist/src/gates/providers/paired.js +52 -0
- package/dist/src/gates/providers/proportion.js +60 -0
- package/dist/src/gates/providers/registry.js +40 -0
- package/dist/src/gates/providers/scalar.js +41 -0
- package/dist/src/gates/providers/screening.js +53 -0
- package/dist/src/gates/providers/types.js +25 -0
- package/dist/src/gates/registry.js +1003 -0
- package/dist/src/gates/report.js +102 -0
- package/dist/src/gates/schema.js +56 -0
- package/dist/src/gates/stats/binomial.js +26 -0
- package/dist/src/gates/stats/bootstrap.js +62 -0
- package/dist/src/gates/stats/clopper-pearson.js +95 -0
- package/dist/src/gates/stats/digest.js +26 -0
- package/dist/src/gates/stats/error.js +5 -0
- package/dist/src/gates/stats/index.js +9 -0
- package/dist/src/gates/stats/normal.js +35 -0
- package/dist/src/gates/stats/paired.js +120 -0
- package/dist/src/gates/stats/splits.js +30 -0
- package/dist/src/gates/types.js +8 -0
- package/dist/src/governance/redaction.js +14 -4
- package/dist/src/mcp/muse-native-config.mjs +123 -0
- package/dist/src/mcp/tools/decision.mjs +188 -0
- package/dist/src/mcp/tools/subsystems.mjs +2 -0
- package/dist/src/providers/capability-matrix.yaml +2 -1
- package/dist/src/providers/provider-definitions.js +2 -1
- package/dist/src/sessions/adapters/muse.js +336 -39
- package/dist/src/sessions/batch-import.js +7 -0
- package/dist/src/sessions/workspace-discovery.js +13 -2
- package/package.json +1 -1
- package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
- package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
- package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
- package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
- package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
- package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
- package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
- package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
- package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
- package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
- package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
- package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
- package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
- package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
- package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
- package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
- package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
- package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
- package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
- package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
- package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
- package/schemas/decision/D29Study.v1.schema.json +1903 -0
- package/schemas/decision/D29Study.v2.schema.json +1344 -0
- package/schemas/decision/D29Study.v3.schema.json +609 -0
- package/schemas/decision/D29Study.v4.schema.json +1133 -0
- package/schemas/decision/D29Study.v6.schema.json +1205 -0
- package/schemas/decision/D29Study.v7.schema.json +1471 -0
- package/schemas/decision/D29Study.v8.schema.json +671 -0
- package/schemas/decision/D29Study.v9.schema.json +778 -0
- package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
- package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
- package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
- package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
- package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
- package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
- package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
- package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
- package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
- package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
- package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
- package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
- package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
- package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
- package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
- package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
- package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
- package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
- package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
- package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
- package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
- package/schemas/decision/DecisionRuleset.schema.json +8 -2
- package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
- package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
- package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
- package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
- package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
- package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
- package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
- package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
- package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
- package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
- package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
- package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
- package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
- package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
- package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
- package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
- package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
- package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
- package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
- package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
- package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
- package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
- package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
- package/tools/agents/deploy-agents.mjs +1 -1
- package/tools/agents/providers/base.mjs +26 -7
- package/tools/agents/providers/factory.mjs +10 -2
- package/tools/agents/providers/muse-hooks.mjs +1 -29
- package/tools/agents/providers/muse.mjs +6 -3
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { canonicalJson } from '../security/artifact-trust.js';
|
|
2
|
+
import { artifactDigest } from '../decision/validate.js';
|
|
3
|
+
import { evaluateGates, maxOutcome } from './evaluate.js';
|
|
4
|
+
import { validateGateDocument } from './schema.js';
|
|
5
|
+
/**
|
|
6
|
+
* Validates a gate report against its trusted inputs. The report is re-derived
|
|
7
|
+
* by re-running the pure evaluator from the same trusted inputs (binding,
|
|
8
|
+
* registry, holdout, metrics, upstream) and must be byte-identical: the
|
|
9
|
+
* expected digest must equal the parsed digest. A forged report that rewrites
|
|
10
|
+
* evidence and reseals the digest still fails, because re-derivation from
|
|
11
|
+
* trusted inputs reproduces the honest bytes. Returns reasons instead of
|
|
12
|
+
* throwing so callers can log them.
|
|
13
|
+
*/
|
|
14
|
+
export function validateGateReport(report, input) {
|
|
15
|
+
const reasons = [];
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = validateGateDocument(report);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return { valid: false, reasons: [`report-schema:${error instanceof Error ? error.message : 'invalid'}`] };
|
|
22
|
+
}
|
|
23
|
+
const { digest, ...fields } = parsed;
|
|
24
|
+
if (artifactDigest(fields) !== digest)
|
|
25
|
+
reasons.push('report-digest-mismatch');
|
|
26
|
+
if (artifactDigest(input.binding) !== parsed.binding.digest)
|
|
27
|
+
reasons.push('report-binding-mismatch');
|
|
28
|
+
if (input.trustedBindingDigest !== parsed.binding.digest)
|
|
29
|
+
reasons.push('report-binding-untrusted');
|
|
30
|
+
if (input.holdout === undefined || input.holdout === null
|
|
31
|
+
|| input.holdout.frozenDigest !== parsed.binding.digest) {
|
|
32
|
+
reasons.push('report-holdout-mismatch');
|
|
33
|
+
}
|
|
34
|
+
if (input.holdout !== undefined
|
|
35
|
+
&& input.holdout !== null) {
|
|
36
|
+
const sealed = input.holdout;
|
|
37
|
+
if (sealed.digest !== parsed.holdout?.digest
|
|
38
|
+
|| sealed.firstAccessedAt !== parsed.holdout?.firstAccessedAt) {
|
|
39
|
+
reasons.push('report-holdout-mismatch');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (parsed.holdout?.digest === undefined
|
|
43
|
+
|| parsed.holdout?.frozenDigest !== parsed.binding.digest) {
|
|
44
|
+
reasons.push('report-holdout-mismatch');
|
|
45
|
+
}
|
|
46
|
+
for (const pin of input.binding.spec.packs) {
|
|
47
|
+
const found = parsed.packs.find(candidate => candidate.id === pin.id);
|
|
48
|
+
if (found === undefined || found.digest !== pin.digest || found.resolvedDigest !== pin.resolvedDigest) {
|
|
49
|
+
reasons.push(`report-pack-mismatch:${pin.id}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (artifactDigest(input.metrics) !== parsed.metricsDigest
|
|
53
|
+
// Bundle-provider sections are reproduced by isolated re-run, so the
|
|
54
|
+
// caller-asserted document is not the evaluated one; re-derivation below
|
|
55
|
+
// is authoritative for those bindings.
|
|
56
|
+
&& (input.providerRuntime === undefined
|
|
57
|
+
|| !input.binding.spec.metricProviders.some(pin => typeof pin.codeDigest === 'string'))) {
|
|
58
|
+
reasons.push('report-metrics-mismatch');
|
|
59
|
+
}
|
|
60
|
+
// The report records which floors governed it (R2): an applied digest or an
|
|
61
|
+
// explicit opt-out marker. The input must say the same; re-derivation below
|
|
62
|
+
// additionally reproduces the distinction byte-identically.
|
|
63
|
+
const expectedFloors = input.floors === 'none-explicit-opt-out'
|
|
64
|
+
? 'opt-out' : `applied:${artifactDigest(input.floors)}`;
|
|
65
|
+
const reportedFloors = parsed.floors.mode === 'applied' ? `applied:${parsed.floors.digest}` : 'opt-out';
|
|
66
|
+
if (reportedFloors !== expectedFloors)
|
|
67
|
+
reasons.push('report-floors-mismatch');
|
|
68
|
+
if ((input.upstream?.digest ?? null) !== (parsed.upstream?.digest ?? null)) {
|
|
69
|
+
reasons.push('report-upstream-mismatch');
|
|
70
|
+
}
|
|
71
|
+
let expected;
|
|
72
|
+
try {
|
|
73
|
+
expected = evaluateGates({ ...input, now: parsed.evaluatedAt });
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return { valid: false, reasons: [...reasons, `report-reevaluation:${error instanceof Error ? error.message : 'refused'}`] };
|
|
77
|
+
}
|
|
78
|
+
// Byte-identical re-derivation: the evaluator is deterministic, so the honest
|
|
79
|
+
// report's canonical bytes must match exactly. Comparing digests (which hash
|
|
80
|
+
// the canonical bytes) is equivalent and cheaper than a byte comparison; the
|
|
81
|
+
// canonical comparison is the independent second route.
|
|
82
|
+
if (expected.digest !== parsed.digest
|
|
83
|
+
|| canonicalJson(stripDigest(expected)) !== canonicalJson(fields)) {
|
|
84
|
+
reasons.push('report-reevaluation-mismatch');
|
|
85
|
+
}
|
|
86
|
+
if (expected.decision !== parsed.decision)
|
|
87
|
+
reasons.push('report-decision-mismatch');
|
|
88
|
+
const components = [
|
|
89
|
+
...parsed.gateEvidence.map(entry => entry.outcome),
|
|
90
|
+
parsed.ceilings.packOutcome, parsed.ceilings.upstreamCeiling, parsed.ceilings.bindingCeiling,
|
|
91
|
+
];
|
|
92
|
+
if (maxOutcome(parsed.decision, maxOutcome(...components)) !== parsed.decision) {
|
|
93
|
+
reasons.push('report-upgrades-component');
|
|
94
|
+
}
|
|
95
|
+
return { valid: reasons.length === 0, reasons };
|
|
96
|
+
}
|
|
97
|
+
function stripDigest(report) {
|
|
98
|
+
const { digest: _dropped, ...fields } = report;
|
|
99
|
+
void _dropped;
|
|
100
|
+
return fields;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
5
|
+
import addFormats from 'ajv-formats';
|
|
6
|
+
export class GateSchemaError extends Error {
|
|
7
|
+
errors;
|
|
8
|
+
constructor(message, errors = []) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.errors = errors;
|
|
11
|
+
this.name = 'GateSchemaError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const schemaFiles = {
|
|
15
|
+
GatePack: 'GatePack.v1alpha1.schema.json',
|
|
16
|
+
GateBinding: 'GateBinding.v1alpha1.schema.json',
|
|
17
|
+
GateReport: 'GateReport.v1alpha1.schema.json',
|
|
18
|
+
};
|
|
19
|
+
let validators = null;
|
|
20
|
+
function schemaRoot() {
|
|
21
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const candidates = [resolve(here, '../../schemas/gates'), resolve(here, '../../../schemas/gates')];
|
|
23
|
+
const found = candidates.find(candidate => existsSync(resolve(candidate, schemaFiles.GatePack)));
|
|
24
|
+
if (!found)
|
|
25
|
+
throw new GateSchemaError('Gate schema directory is unavailable');
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
28
|
+
function getValidators() {
|
|
29
|
+
if (validators)
|
|
30
|
+
return validators;
|
|
31
|
+
const ajv = new Ajv2020({ strict: false, allErrors: true });
|
|
32
|
+
addFormats(ajv);
|
|
33
|
+
validators = new Map();
|
|
34
|
+
for (const [kind, filename] of Object.entries(schemaFiles)) {
|
|
35
|
+
const schema = JSON.parse(readFileSync(resolve(schemaRoot(), filename), 'utf8'));
|
|
36
|
+
validators.set(`gates.aiwg.io/v1alpha1:${kind}`, ajv.compile(schema));
|
|
37
|
+
}
|
|
38
|
+
return validators;
|
|
39
|
+
}
|
|
40
|
+
const message = (errors) => errors?.map(error => `${error.instancePath || '/'} ${error.message}`).join('; ') ?? 'unknown schema error';
|
|
41
|
+
/** Closed-schema validation for a gates document. Returns the document unchanged when valid. */
|
|
42
|
+
export function validateGateDocument(value) {
|
|
43
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
44
|
+
throw new GateSchemaError('Gate document must be an object');
|
|
45
|
+
}
|
|
46
|
+
const kind = value.kind;
|
|
47
|
+
const version = value.apiVersion;
|
|
48
|
+
const key = `${version}:${kind}`;
|
|
49
|
+
const validate = kind ? getValidators().get(key) : undefined;
|
|
50
|
+
if (!validate)
|
|
51
|
+
throw new GateSchemaError(`Unsupported gate kind/version '${key}'`);
|
|
52
|
+
if (!validate(value))
|
|
53
|
+
throw new GateSchemaError(`${kind} schema validation failed: ${message(validate.errors)}`, validate.errors ?? []);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PairedDifferenceError } from './error.js';
|
|
2
|
+
import { isCount, twoSidedZ } from './normal.js';
|
|
3
|
+
/**
|
|
4
|
+
* Two-sided Wilson score interval for a binomial proportion at `levelBps` (5001..9998), so each bound
|
|
5
|
+
* is a one-sided (10000 - levelBps) / 20000 bound. Counts must satisfy 0 <= events <= n and n > 0.
|
|
6
|
+
*/
|
|
7
|
+
export function wilsonScoreInterval(input) {
|
|
8
|
+
const z = twoSidedZ(input?.levelBps);
|
|
9
|
+
const { events, n } = input;
|
|
10
|
+
if (!isCount(events) || !isCount(n) || n === 0 || events > n) {
|
|
11
|
+
throw new PairedDifferenceError('wilson interval requires integer counts with 0 <= events <= n and n > 0');
|
|
12
|
+
}
|
|
13
|
+
return wilsonScore(events, n, z);
|
|
14
|
+
}
|
|
15
|
+
export function wilson95(errors, n) {
|
|
16
|
+
// 95% normal quantile; finite-sample Wilson interval for binomial events.
|
|
17
|
+
return wilsonScore(errors, n, 1.959963984540054);
|
|
18
|
+
}
|
|
19
|
+
export function wilsonScore(errors, n, z) {
|
|
20
|
+
const rate = errors / n;
|
|
21
|
+
const denominator = 1 + z * z / n;
|
|
22
|
+
const center = (rate + z * z / (2 * n)) / denominator;
|
|
23
|
+
const margin = z * Math.sqrt(rate * (1 - rate) / n + z * z / (4 * n * n)) / denominator;
|
|
24
|
+
return [Math.max(0, center - margin), Math.min(1, center + margin)];
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=binomial.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { PairedDifferenceError } from './error.js';
|
|
2
|
+
import { twoSidedZ } from './normal.js';
|
|
3
|
+
import { toBpsInterval } from './paired.js';
|
|
4
|
+
/** Deterministic 32-bit mulberry32 stream. */
|
|
5
|
+
function mulberry32(seed) {
|
|
6
|
+
let state = seed >>> 0;
|
|
7
|
+
return () => {
|
|
8
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
9
|
+
let t = state;
|
|
10
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
11
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
12
|
+
return (t ^ (t >>> 14)) >>> 0;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** Unbiased index in [0, n): reject draws in the incomplete top block of the 2^32 range. */
|
|
16
|
+
function uniformIndex(next, n) {
|
|
17
|
+
const limit = Math.floor(0x100000000 / n) * n;
|
|
18
|
+
for (;;) {
|
|
19
|
+
const draw = next();
|
|
20
|
+
if (draw < limit)
|
|
21
|
+
return draw % n;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Seeded percentile bootstrap for the mean of bounded per-pair differences (candidate minus
|
|
26
|
+
* baseline) on the proportion scale, so 1.0 = 10000 bps. Bounds must lie within [-1, 1].
|
|
27
|
+
* Order statistics are chosen outward and each tail must hold at least 5 resamples.
|
|
28
|
+
*/
|
|
29
|
+
export function pairedMeanDifferenceBootstrap(input) {
|
|
30
|
+
twoSidedZ(input?.levelBps);
|
|
31
|
+
const { differences, levelBps, seed, resamples, bounds } = input;
|
|
32
|
+
if (!Array.isArray(bounds) || bounds.length !== 2 || !bounds.every(Number.isFinite)
|
|
33
|
+
|| bounds[0] < -1 || bounds[1] > 1 || bounds[0] >= bounds[1]) {
|
|
34
|
+
throw new PairedDifferenceError('bounds must be finite with -1 <= min < max <= 1');
|
|
35
|
+
}
|
|
36
|
+
if (!Array.isArray(differences) || differences.length < 2) {
|
|
37
|
+
throw new PairedDifferenceError('bootstrap requires at least two paired differences');
|
|
38
|
+
}
|
|
39
|
+
if (differences.some(value => typeof value !== 'number' || !Number.isFinite(value) || value < bounds[0] || value > bounds[1])) {
|
|
40
|
+
throw new PairedDifferenceError('paired differences must be finite and within bounds');
|
|
41
|
+
}
|
|
42
|
+
if (!Number.isSafeInteger(seed) || seed < 0 || seed > 0xffffffff) {
|
|
43
|
+
throw new PairedDifferenceError('seed must be an unsigned 32-bit integer');
|
|
44
|
+
}
|
|
45
|
+
const tail = Number.isSafeInteger(resamples) ? Math.floor(resamples * (10000 - levelBps) / 20000) : 0;
|
|
46
|
+
if (!Number.isSafeInteger(resamples) || resamples > 1_000_000 || tail < 5) {
|
|
47
|
+
throw new PairedDifferenceError('resamples must be an integer <= 1000000 leaving at least 5 per tail');
|
|
48
|
+
}
|
|
49
|
+
const n = differences.length;
|
|
50
|
+
const next = mulberry32(seed);
|
|
51
|
+
const means = new Float64Array(resamples);
|
|
52
|
+
for (let r = 0; r < resamples; r++) {
|
|
53
|
+
let sum = 0;
|
|
54
|
+
for (let i = 0; i < n; i++)
|
|
55
|
+
sum += differences[uniformIndex(next, n)];
|
|
56
|
+
means[r] = sum / n;
|
|
57
|
+
}
|
|
58
|
+
means.sort();
|
|
59
|
+
const estimate = differences.reduce((sum, value) => sum + value, 0) / n;
|
|
60
|
+
return toBpsInterval(Math.min(means[tail - 1], estimate), Math.max(means[resamples - tail], estimate), estimate, n, 'percentile-bootstrap');
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { PairedDifferenceError } from './error.js';
|
|
2
|
+
import { isCount } from './normal.js';
|
|
3
|
+
/**
|
|
4
|
+
* Exact binomial tail P(X >= events) at proportion p, summed in log space with
|
|
5
|
+
* precomputed log factorials: linear in n and free of underflow for large n.
|
|
6
|
+
*/
|
|
7
|
+
function upperTail(events, n, p) {
|
|
8
|
+
if (p <= 0)
|
|
9
|
+
return events <= 0 ? 1 : 0;
|
|
10
|
+
if (p >= 1)
|
|
11
|
+
return events <= n ? 1 : 0;
|
|
12
|
+
const logP = Math.log(p);
|
|
13
|
+
const logQ = Math.log(1 - p);
|
|
14
|
+
const logFact = [0];
|
|
15
|
+
for (let i = 1; i <= n; i++)
|
|
16
|
+
logFact.push(logFact[i - 1] + Math.log(i));
|
|
17
|
+
let peak = -Infinity;
|
|
18
|
+
const terms = [];
|
|
19
|
+
for (let k = events; k <= n; k++) {
|
|
20
|
+
const term = logFact[n] - logFact[k] - logFact[n - k]
|
|
21
|
+
+ k * logP + (n - k) * logQ;
|
|
22
|
+
terms.push(term);
|
|
23
|
+
if (term > peak)
|
|
24
|
+
peak = term;
|
|
25
|
+
}
|
|
26
|
+
let sum = 0;
|
|
27
|
+
for (const term of terms)
|
|
28
|
+
sum += Math.exp(term - peak);
|
|
29
|
+
return Math.min(1, sum * Math.exp(peak));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Exact Clopper-Pearson interval for a binomial proportion. `levelBps` (5001..9998) is the
|
|
33
|
+
* two-sided level; each tail holds (10000 - levelBps) / 20000. `sided` selects a one-sided
|
|
34
|
+
* bound at the same level instead: 'lower' returns [bound, 1] and 'upper' returns [0, bound].
|
|
35
|
+
* Counts must satisfy 0 <= events <= n and n > 0. Bounds are inverted by bisection on the
|
|
36
|
+
* exact binomial tail, so the coverage is at least the nominal level by construction.
|
|
37
|
+
*/
|
|
38
|
+
export function clopperPearsonInterval(input) {
|
|
39
|
+
if (!input || typeof input !== 'object')
|
|
40
|
+
throw new PairedDifferenceError('clopper-pearson interval input is required');
|
|
41
|
+
const { events, n, levelBps, sided } = input;
|
|
42
|
+
if (!isCount(events) || !isCount(n) || n === 0 || events > n) {
|
|
43
|
+
throw new PairedDifferenceError('clopper-pearson interval requires integer counts with 0 <= events <= n and n > 0');
|
|
44
|
+
}
|
|
45
|
+
if (!Number.isSafeInteger(levelBps) || levelBps <= 5000 || levelBps >= 9999) {
|
|
46
|
+
throw new PairedDifferenceError('levelBps must be an integer strictly between 5000 and 9999');
|
|
47
|
+
}
|
|
48
|
+
const side = sided ?? 'two-sided';
|
|
49
|
+
if (side !== 'two-sided' && side !== 'lower' && side !== 'upper') {
|
|
50
|
+
throw new PairedDifferenceError('clopper-pearson sided must be two-sided, lower or upper');
|
|
51
|
+
}
|
|
52
|
+
const level = levelBps;
|
|
53
|
+
const tail = (side === 'two-sided' ? (10000 - level) / 20000 : (10000 - level) / 10000);
|
|
54
|
+
// P(X >= events | p) rises from 0 at p=0 to 1 at p=1. Each loop keeps `below`
|
|
55
|
+
// under the root and returns it, so the lower bound rounds outward (down).
|
|
56
|
+
const solveTail = (target) => {
|
|
57
|
+
let below = 0;
|
|
58
|
+
let above = 1;
|
|
59
|
+
for (let i = 0; i < 200; i++) {
|
|
60
|
+
const mid = (below + above) / 2;
|
|
61
|
+
if (mid === below || mid === above)
|
|
62
|
+
break;
|
|
63
|
+
if (upperTail(events, n, mid) > target)
|
|
64
|
+
above = mid;
|
|
65
|
+
else
|
|
66
|
+
below = mid;
|
|
67
|
+
}
|
|
68
|
+
return below;
|
|
69
|
+
};
|
|
70
|
+
// P(X <= events | p) = 1 - P(X >= events + 1 | p) falls from 1 to 0. Each loop
|
|
71
|
+
// keeps `above` over the root and returns it, so the upper bound rounds outward (up).
|
|
72
|
+
const solveLowerTail = (target) => {
|
|
73
|
+
let below = 0;
|
|
74
|
+
let above = 1;
|
|
75
|
+
for (let i = 0; i < 200; i++) {
|
|
76
|
+
const mid = (below + above) / 2;
|
|
77
|
+
if (mid === below || mid === above)
|
|
78
|
+
break;
|
|
79
|
+
if (1 - upperTail(events + 1, n, mid) > target)
|
|
80
|
+
below = mid;
|
|
81
|
+
else
|
|
82
|
+
above = mid;
|
|
83
|
+
}
|
|
84
|
+
return above;
|
|
85
|
+
};
|
|
86
|
+
if (side === 'lower')
|
|
87
|
+
return { lower: events === 0 ? 0 : solveTail(tail), upper: 1 };
|
|
88
|
+
if (side === 'upper')
|
|
89
|
+
return { lower: 0, upper: events === n ? 1 : solveLowerTail(tail) };
|
|
90
|
+
return {
|
|
91
|
+
lower: events === 0 ? 0 : solveTail(tail),
|
|
92
|
+
upper: events === n ? 1 : solveLowerTail(tail),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=clopper-pearson.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalJson } from '../../security/artifact-trust.js';
|
|
3
|
+
export const evidenceDigestModes = (modes) => modes ?? ['canonical'];
|
|
4
|
+
/** Canonical-JSON sha256 digest. All new gates and qualification evidence uses this. */
|
|
5
|
+
export function canonicalSha256(value) {
|
|
6
|
+
return `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Versioned legacy mode: pre-migration `quality.ts`/`release.ts` digests hashed
|
|
10
|
+
* `JSON.stringify` output, which depends on key order. Accepts exactly those bytes
|
|
11
|
+
* so existing evidence still verifies without re-pinning (operator default).
|
|
12
|
+
*/
|
|
13
|
+
export function legacySha256(value) {
|
|
14
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
|
|
15
|
+
}
|
|
16
|
+
/** Returns the first matching mode, or null when neither digest matches. Null digests never match. Canonical-only by default; legacy requires an explicit allowlist. */
|
|
17
|
+
export function matchEvidenceDigest(value, digest, modes = ['canonical']) {
|
|
18
|
+
if (typeof digest !== 'string')
|
|
19
|
+
return null;
|
|
20
|
+
for (const mode of evidenceDigestModes(modes)) {
|
|
21
|
+
if ((mode === 'canonical' ? canonicalSha256(value) : legacySha256(value)) === digest)
|
|
22
|
+
return mode;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=digest.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Shared error for malformed statistics inputs. All stats helpers fail closed by throwing this. */
|
|
2
|
+
export class PairedDifferenceError extends Error {
|
|
3
|
+
constructor(message) { super(message); this.name = 'PairedDifferenceError'; }
|
|
4
|
+
}
|
|
5
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { PairedDifferenceError } from './error.js';
|
|
2
|
+
export { normalQuantile } from './normal.js';
|
|
3
|
+
export { wilsonScoreInterval } from './binomial.js';
|
|
4
|
+
export { clopperPearsonInterval } from './clopper-pearson.js';
|
|
5
|
+
export { pairedBinaryDifferenceInterval, pairedNonInferiority } from './paired.js';
|
|
6
|
+
export { pairedMeanDifferenceBootstrap } from './bootstrap.js';
|
|
7
|
+
export { freezeQualificationSplit, verifyQualificationSplits } from './splits.js';
|
|
8
|
+
export { canonicalSha256, legacySha256, matchEvidenceDigest } from './digest.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { PairedDifferenceError } from './error.js';
|
|
2
|
+
/**
|
|
3
|
+
* Acklam's rational approximation to the standard normal quantile (relative error < 1.15e-9).
|
|
4
|
+
* Throws for p outside the open interval (0, 1).
|
|
5
|
+
*/
|
|
6
|
+
export function normalQuantile(p) {
|
|
7
|
+
if (!Number.isFinite(p) || p <= 0 || p >= 1)
|
|
8
|
+
throw new PairedDifferenceError('normal quantile requires 0 < p < 1');
|
|
9
|
+
const a = [-3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02,
|
|
10
|
+
1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00];
|
|
11
|
+
const b = [-5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02,
|
|
12
|
+
6.680131188771972e+01, -1.328068155288572e+01];
|
|
13
|
+
const c = [-7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00,
|
|
14
|
+
-2.549732539343734e+00, 4.374664141464968e+00, 2.938163982698783e+00];
|
|
15
|
+
const d = [7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00, 3.754408661907416e+00];
|
|
16
|
+
const tail = (q) => (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5])
|
|
17
|
+
/ ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
|
|
18
|
+
if (p < 0.02425)
|
|
19
|
+
return tail(Math.sqrt(-2 * Math.log(p)));
|
|
20
|
+
if (p > 1 - 0.02425)
|
|
21
|
+
return -tail(Math.sqrt(-2 * Math.log(1 - p)));
|
|
22
|
+
const q = p - 0.5;
|
|
23
|
+
const r = q * q;
|
|
24
|
+
return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q
|
|
25
|
+
/ (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
|
|
26
|
+
}
|
|
27
|
+
/** Two-sided level in bps; only 5001..9998 are accepted so neither tail is empty or trivial. */
|
|
28
|
+
export function twoSidedZ(levelBps) {
|
|
29
|
+
if (!Number.isSafeInteger(levelBps) || levelBps <= 5000 || levelBps >= 9999) {
|
|
30
|
+
throw new PairedDifferenceError('levelBps must be an integer strictly between 5000 and 9999');
|
|
31
|
+
}
|
|
32
|
+
return normalQuantile((10000 + levelBps) / 20000);
|
|
33
|
+
}
|
|
34
|
+
export const isCount = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
35
|
+
//# sourceMappingURL=normal.js.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { wilsonScore } from './binomial.js';
|
|
2
|
+
import { PairedDifferenceError } from './error.js';
|
|
3
|
+
import { isCount, twoSidedZ } from './normal.js';
|
|
4
|
+
function pairedCells(counts) {
|
|
5
|
+
if (!counts || typeof counts !== 'object')
|
|
6
|
+
throw new PairedDifferenceError('paired counts are required');
|
|
7
|
+
const full = ['both', 'candidateOnly', 'baselineOnly', 'neither'].some(key => Object.hasOwn(counts, key));
|
|
8
|
+
const discordant = ['b', 'c', 'n'].some(key => Object.hasOwn(counts, key));
|
|
9
|
+
if (full === discordant)
|
|
10
|
+
throw new PairedDifferenceError('provide either the full paired table or discordant b, c and n');
|
|
11
|
+
if ('both' in counts) {
|
|
12
|
+
const { both, candidateOnly, baselineOnly, neither } = counts;
|
|
13
|
+
if (![both, candidateOnly, baselineOnly, neither].every(isCount)) {
|
|
14
|
+
throw new PairedDifferenceError('paired cell counts must be non-negative safe integers');
|
|
15
|
+
}
|
|
16
|
+
const n = both + candidateOnly + baselineOnly + neither;
|
|
17
|
+
if (n === 0 || !Number.isSafeInteger(n))
|
|
18
|
+
throw new PairedDifferenceError('paired table requires n > 0');
|
|
19
|
+
return { e: both, f: candidateOnly, g: baselineOnly, h: neither, n };
|
|
20
|
+
}
|
|
21
|
+
const { b, c, n } = counts;
|
|
22
|
+
if (![b, c, n].every(isCount) || n === 0 || b + c > n) {
|
|
23
|
+
throw new PairedDifferenceError('discordant counts require non-negative integers with b + c <= n and n > 0');
|
|
24
|
+
}
|
|
25
|
+
return { e: null, f: b, g: c, h: null, n };
|
|
26
|
+
}
|
|
27
|
+
// Outward rounding: a reported bound is never tighter than the computed one.
|
|
28
|
+
export const toBpsInterval = (low, high, estimate, n, method) => {
|
|
29
|
+
if (![low, high, estimate].every(Number.isFinite) || low > estimate || estimate > high) {
|
|
30
|
+
throw new PairedDifferenceError('paired interval computation was not finite and ordered');
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
lowerBps: Math.max(-10000, Math.floor(low * 10000)), upperBps: Math.min(10000, Math.ceil(high * 10000)),
|
|
34
|
+
estimateBps: Math.round(estimate * 10000), n, method,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Two-sided score interval for p_candidate - p_baseline from paired binary outcomes.
|
|
39
|
+
* `newcombe-10` (default): Newcombe (1998) method 10, square-and-add of continuity-free Wilson
|
|
40
|
+
* intervals for both marginals with phi numerator replaced by max(eh - fg - n/2, 0) when eh > fg.
|
|
41
|
+
* `tango`: Tango (1998) asymptotic score interval, inverted by bisection; needs only b, c and n.
|
|
42
|
+
*/
|
|
43
|
+
export function pairedBinaryDifferenceInterval(input) {
|
|
44
|
+
const z = twoSidedZ(input?.levelBps);
|
|
45
|
+
const { e, f, g, h, n } = pairedCells(input.counts);
|
|
46
|
+
const estimate = (f - g) / n;
|
|
47
|
+
const method = input.method ?? 'newcombe-10';
|
|
48
|
+
if (method === 'tango') {
|
|
49
|
+
const [low, high] = tangoInterval(f, g, n, z);
|
|
50
|
+
return toBpsInterval(low, high, estimate, n, 'tango-score');
|
|
51
|
+
}
|
|
52
|
+
if (method !== 'newcombe-10')
|
|
53
|
+
throw new PairedDifferenceError('unknown paired interval method');
|
|
54
|
+
if (e === null || h === null)
|
|
55
|
+
throw new PairedDifferenceError('newcombe method 10 requires the full paired table');
|
|
56
|
+
const p1 = (e + f) / n;
|
|
57
|
+
const p2 = (e + g) / n;
|
|
58
|
+
const [l1, u1] = wilsonScore(e + f, n, z);
|
|
59
|
+
const [l2, u2] = wilsonScore(e + g, n, z);
|
|
60
|
+
const denominator = Math.sqrt((e + f) * (g + h) * (e + g) * (f + h));
|
|
61
|
+
const cross = e * h - f * g;
|
|
62
|
+
const phi = denominator === 0 ? 0 : (cross > 0 ? Math.max(cross - n / 2, 0) : cross) / denominator;
|
|
63
|
+
const delta = Math.sqrt(Math.max(0, (p1 - l1) ** 2 - 2 * phi * (p1 - l1) * (u2 - p2) + (u2 - p2) ** 2));
|
|
64
|
+
const epsilon = Math.sqrt(Math.max(0, (u1 - p1) ** 2 - 2 * phi * (u1 - p1) * (p2 - l2) + (p2 - l2) ** 2));
|
|
65
|
+
return toBpsInterval(Math.max(-1, estimate - delta), Math.min(1, estimate + epsilon), estimate, n, 'newcombe-hybrid-score');
|
|
66
|
+
}
|
|
67
|
+
function tangoInterval(b, c, n, z) {
|
|
68
|
+
// Tango's statistic decreases in delta; the restricted MLE of the baseline-only cell is closed form.
|
|
69
|
+
const statistic = (delta) => {
|
|
70
|
+
const B = -b - c + (2 * n - b + c) * delta;
|
|
71
|
+
const q21 = (Math.sqrt(Math.max(0, B * B + 8 * n * c * delta * (1 - delta))) - B) / (4 * n);
|
|
72
|
+
const numerator = b - c - n * delta;
|
|
73
|
+
const variance = n * (2 * q21 + delta * (1 - delta));
|
|
74
|
+
if (numerator === 0)
|
|
75
|
+
return 0;
|
|
76
|
+
return variance > 0 ? numerator / Math.sqrt(variance) : numerator * Infinity;
|
|
77
|
+
};
|
|
78
|
+
const estimate = (b - c) / n;
|
|
79
|
+
// Each search keeps `outer` outside the acceptance region and returns it, so rounding widens.
|
|
80
|
+
const search = (outer, inner, rejects) => {
|
|
81
|
+
for (let i = 0; i < 200; i++) {
|
|
82
|
+
const mid = (outer + inner) / 2;
|
|
83
|
+
if (mid === outer || mid === inner)
|
|
84
|
+
break;
|
|
85
|
+
if (rejects(statistic(mid)))
|
|
86
|
+
outer = mid;
|
|
87
|
+
else
|
|
88
|
+
inner = mid;
|
|
89
|
+
}
|
|
90
|
+
return outer;
|
|
91
|
+
};
|
|
92
|
+
return [
|
|
93
|
+
estimate === -1 ? -1 : search(-1, estimate, t => t > z),
|
|
94
|
+
estimate === 1 ? 1 : search(1, estimate, t => t < -z),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* One-sided non-inferiority read of a two-sided interval. `marginBps` is a non-positive integer:
|
|
99
|
+
* -250 lets the candidate be at most 2.5 points worse than the baseline. Non-inferior only when
|
|
100
|
+
* lowerBps >= marginBps; malformed intervals are 'insufficient', never a pass.
|
|
101
|
+
*/
|
|
102
|
+
export function pairedNonInferiority(input) {
|
|
103
|
+
const marginBps = input?.marginBps;
|
|
104
|
+
if (!Number.isSafeInteger(marginBps) || marginBps > 0 || marginBps < -10000) {
|
|
105
|
+
throw new PairedDifferenceError('marginBps must be an integer in [-10000, 0]');
|
|
106
|
+
}
|
|
107
|
+
const interval = input.interval;
|
|
108
|
+
const valid = !!interval && typeof interval === 'object'
|
|
109
|
+
&& [interval.lowerBps, interval.upperBps, interval.estimateBps].every(value => Number.isSafeInteger(value)
|
|
110
|
+
&& value >= -10000 && value <= 10000)
|
|
111
|
+
&& Number.isSafeInteger(interval.n) && interval.n > 0
|
|
112
|
+
&& interval.lowerBps <= interval.estimateBps && interval.estimateBps <= interval.upperBps
|
|
113
|
+
&& ['newcombe-hybrid-score', 'tango-score', 'percentile-bootstrap'].includes(interval.method);
|
|
114
|
+
if (!valid)
|
|
115
|
+
return { decision: 'insufficient', reason: 'invalid-interval' };
|
|
116
|
+
return interval.lowerBps >= marginBps
|
|
117
|
+
? { decision: 'non-inferior', reason: 'lower-bound-at-or-above-margin' }
|
|
118
|
+
: { decision: 'not-non-inferior', reason: 'lower-bound-below-margin' };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=paired.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { canonicalSha256 } from './digest.js';
|
|
2
|
+
function digestIds(ids) {
|
|
3
|
+
return canonicalSha256([...ids].sort());
|
|
4
|
+
}
|
|
5
|
+
/** Hash the exact membership set; duplicate, empty and overlapping IDs fail closed. */
|
|
6
|
+
export function freezeQualificationSplit(name, ids) {
|
|
7
|
+
if (!ids.length || ids.some(id => !id.trim()) || new Set(ids).size !== ids.length) {
|
|
8
|
+
throw new Error('qualification split requires unique nonempty IDs');
|
|
9
|
+
}
|
|
10
|
+
return { name, ids: [...ids].sort(), digest: digestIds(ids) };
|
|
11
|
+
}
|
|
12
|
+
export function verifyQualificationSplits(splits) {
|
|
13
|
+
if (splits.length !== 3 || new Set(splits.map(split => split.name)).size !== 3
|
|
14
|
+
|| splits.some(split => !['tuning', 'calibration', 'test'].includes(split.name))) {
|
|
15
|
+
throw new Error('qualification requires tuning, calibration and test splits');
|
|
16
|
+
}
|
|
17
|
+
const all = new Set();
|
|
18
|
+
for (const split of splits) {
|
|
19
|
+
if (!split.ids.length || split.ids.some(id => typeof id !== 'string' || !id.trim())
|
|
20
|
+
|| new Set(split.ids).size !== split.ids.length || split.digest !== digestIds(split.ids)) {
|
|
21
|
+
throw new Error('qualification split digest or membership mismatch');
|
|
22
|
+
}
|
|
23
|
+
for (const id of split.ids) {
|
|
24
|
+
if (all.has(id))
|
|
25
|
+
throw new Error('qualification splits overlap');
|
|
26
|
+
all.add(id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=splits.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const GATES_API_VERSION = 'gates.aiwg.io/v1alpha1';
|
|
2
|
+
/**
|
|
3
|
+
* Qualifies a pack parameter for a binding: `<packId>.<param>`. Parameters are
|
|
4
|
+
* namespaced per pack so two packs declaring the same short name can never
|
|
5
|
+
* collide or silently merge in a binding.
|
|
6
|
+
*/
|
|
7
|
+
export const qualifyGateParameter = (packId, name) => `${packId}.${name}`;
|
|
8
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -20,7 +20,17 @@ const BUILTIN_SENSITIVE_KEY = /(?:^|[_-])(?:authorization|auth[_-]?header|cookie
|
|
|
20
20
|
const BUILTIN_PATTERNS = [
|
|
21
21
|
{
|
|
22
22
|
id: 'private-key',
|
|
23
|
-
pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g,
|
|
23
|
+
pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----/g,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'private-key',
|
|
27
|
+
// Bounded fallback for truncated input that never reaches an END marker.
|
|
28
|
+
// Only the header plus up to 100 complete base64 body lines are consumed,
|
|
29
|
+
// so following prose is preserved: a line extends the match only when the
|
|
30
|
+
// whole line is base64, an RFC 1421 header (e.g. `Proc-Type: 4,ENCRYPTED`,
|
|
31
|
+
// `DEK-Info: …`) or blank, so legacy encrypted bodies are still covered. Runs after the terminated pattern above, which
|
|
32
|
+
// already consumed every block that has an END marker.
|
|
33
|
+
pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----(?:[ \t]+[A-Za-z0-9+/=]{4,}[ \t]*(?=\r?\n|$))?(?:\r?\n(?:[ \t]*[A-Za-z0-9+/=]{4,}[ \t]*|[A-Za-z][A-Za-z0-9-]*:[^\r\n]*|[ \t]*)(?=\r?\n|$)){0,100}/g,
|
|
24
34
|
},
|
|
25
35
|
{
|
|
26
36
|
id: 'authorization-header',
|
|
@@ -61,7 +71,7 @@ const BUILTIN_PATTERNS = [
|
|
|
61
71
|
},
|
|
62
72
|
{
|
|
63
73
|
id: 'provider-token',
|
|
64
|
-
pattern: /\b(?:sk-(?:proj-)?|gh[pousr]_|github_pat_|xox[baprs]-|AKIA)[A-Za-z0-9._~+/=-]{6,}\b/g,
|
|
74
|
+
pattern: /\b(?:sk-(?:proj-)?|[sr]k_(?:live|test)_|gh[pousr]_|github_pat_|xox[baprs]-|whsec_|glpat-|AKIA)[A-Za-z0-9._~+/=-]{6,}\b/g,
|
|
65
75
|
},
|
|
66
76
|
{
|
|
67
77
|
id: 'bearer-token',
|
|
@@ -163,11 +173,11 @@ function decodedSecretClass(value) {
|
|
|
163
173
|
const decoded = Buffer.from(value, 'base64').toString('utf8');
|
|
164
174
|
if (!decoded || decoded.includes('\uFFFD'))
|
|
165
175
|
return null;
|
|
166
|
-
if (/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/.test(decoded))
|
|
176
|
+
if (/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----/.test(decoded))
|
|
167
177
|
return 'encoded-private-key';
|
|
168
178
|
if (/(?:api[_-]?key|token|password|passwd|secret|authorization)\s*[:=]/i.test(decoded))
|
|
169
179
|
return 'encoded-secret';
|
|
170
|
-
if (/(?:sk-(?:proj-)?|gh[pousr]_|github_pat_|xox[baprs]-|AKIA)[A-Za-z0-9._~+/=-]{6,}/.test(decoded))
|
|
180
|
+
if (/(?:sk-(?:proj-)?|[sr]k_(?:live|test)_|gh[pousr]_|github_pat_|xox[baprs]-|whsec_|glpat-|AKIA)[A-Za-z0-9._~+/=-]{6,}/.test(decoded))
|
|
171
181
|
return 'encoded-secret';
|
|
172
182
|
return null;
|
|
173
183
|
}
|