@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.
Files changed (210) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +2 -1
  2. package/dist/src/artifacts/cli.js +23 -18
  3. package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
  4. package/dist/src/artifacts/discovery-shadow.js +195 -0
  5. package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
  6. package/dist/src/artifacts/index-builder.js +47 -9
  7. package/dist/src/artifacts/query-engine.js +108 -5
  8. package/dist/src/artifacts/types.js +1 -0
  9. package/dist/src/cli/handlers/decision.js +88 -0
  10. package/dist/src/cli/handlers/gates.js +154 -0
  11. package/dist/src/cli/handlers/index.js +5 -1
  12. package/dist/src/cli/handlers/sessions.js +10 -7
  13. package/dist/src/cli/handlers/utilities.js +20 -0
  14. package/dist/src/config/aiwg-config.js +15 -2
  15. package/dist/src/decision/acceptance.js +25 -0
  16. package/dist/src/decision/adapters/jev.js +24 -2
  17. package/dist/src/decision/calibration/registry.js +9 -0
  18. package/dist/src/decision/compose.js +74 -15
  19. package/dist/src/decision/context-live-promotion.js +344 -0
  20. package/dist/src/decision/context-live-qualification.js +591 -0
  21. package/dist/src/decision/context-pruning.js +847 -0
  22. package/dist/src/decision/driver.js +430 -0
  23. package/dist/src/decision/egress-live-qualification.js +874 -0
  24. package/dist/src/decision/ensemble/contract.js +64 -1
  25. package/dist/src/decision/ensemble/index.js +1 -0
  26. package/dist/src/decision/ensemble/report.js +8 -42
  27. package/dist/src/decision/ensemble/runtime.js +571 -0
  28. package/dist/src/decision/ensemble-study/artifacts.js +85 -0
  29. package/dist/src/decision/ensemble-study/calibration.js +443 -0
  30. package/dist/src/decision/ensemble-study/corpus.js +229 -0
  31. package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
  32. package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
  33. package/dist/src/decision/ensemble-study/multifact.js +241 -0
  34. package/dist/src/decision/ensemble-study/protocol.js +77 -0
  35. package/dist/src/decision/ensemble-study/score.js +462 -0
  36. package/dist/src/decision/ensemble-study/staged.js +87 -0
  37. package/dist/src/decision/ensemble-study/statistics.js +84 -0
  38. package/dist/src/decision/evaluate.js +227 -14
  39. package/dist/src/decision/export.js +4 -0
  40. package/dist/src/decision/feature-export.js +1074 -0
  41. package/dist/src/decision/graph-index.js +16 -0
  42. package/dist/src/decision/graph-live-qualification.js +1041 -0
  43. package/dist/src/decision/graph-live-workload.js +245 -0
  44. package/dist/src/decision/graph-skill-bridge.js +21 -3
  45. package/dist/src/decision/heldout/calibration.js +140 -0
  46. package/dist/src/decision/heldout/collector.js +430 -0
  47. package/dist/src/decision/heldout/contract.js +309 -0
  48. package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
  49. package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
  50. package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
  51. package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
  52. package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
  53. package/dist/src/decision/heldout/d29-pools.js +288 -0
  54. package/dist/src/decision/heldout/d29-v6.js +169 -0
  55. package/dist/src/decision/heldout/d29-v7.js +202 -0
  56. package/dist/src/decision/heldout/d29-v8.js +586 -0
  57. package/dist/src/decision/heldout/generator-registry.js +67 -0
  58. package/dist/src/decision/heldout/generators.js +696 -0
  59. package/dist/src/decision/heldout/index.js +8 -0
  60. package/dist/src/decision/heldout/journal.js +346 -0
  61. package/dist/src/decision/heldout/study-generators.js +64 -0
  62. package/dist/src/decision/heldout/types.js +2 -0
  63. package/dist/src/decision/host-policy.js +61 -0
  64. package/dist/src/decision/index.js +11 -0
  65. package/dist/src/decision/interop-profile.v1.json +112 -0
  66. package/dist/src/decision/interop.js +653 -0
  67. package/dist/src/decision/issue-triage/index.js +3 -0
  68. package/dist/src/decision/issue-triage/runtime.js +1069 -0
  69. package/dist/src/decision/issue-triage/types.js +2 -0
  70. package/dist/src/decision/patterns/catalog.js +28 -8
  71. package/dist/src/decision/patterns/playground.js +28 -11
  72. package/dist/src/decision/patterns/specs.js +19 -10
  73. package/dist/src/decision/preprocessed-evidence.js +724 -0
  74. package/dist/src/decision/qualification/capture.js +8 -2
  75. package/dist/src/decision/qualification/gate-evidence.js +19 -4
  76. package/dist/src/decision/qualification/load.js +8 -1
  77. package/dist/src/decision/qualification/quality.js +57 -49
  78. package/dist/src/decision/qualification/release.js +76 -3
  79. package/dist/src/decision/qualification/runner.js +5 -2
  80. package/dist/src/decision/routing/contract.js +175 -0
  81. package/dist/src/decision/routing/index.js +5 -0
  82. package/dist/src/decision/routing/report.js +216 -0
  83. package/dist/src/decision/routing/runtime.js +685 -0
  84. package/dist/src/decision/routing/types.js +2 -0
  85. package/dist/src/decision/sdlc-screening.js +1157 -0
  86. package/dist/src/decision/sensitivity/comparative.js +154 -0
  87. package/dist/src/decision/sensitivity/contract.js +218 -0
  88. package/dist/src/decision/sensitivity/index.js +5 -0
  89. package/dist/src/decision/sensitivity/runtime.js +912 -0
  90. package/dist/src/decision/sensitivity/types.js +2 -0
  91. package/dist/src/decision/validate.js +26 -0
  92. package/dist/src/extensions/commands/definitions.js +38 -0
  93. package/dist/src/extensions/manifest.js +44 -0
  94. package/dist/src/gates/discovery.js +315 -0
  95. package/dist/src/gates/driver.js +388 -0
  96. package/dist/src/gates/evaluate.js +719 -0
  97. package/dist/src/gates/floors.js +255 -0
  98. package/dist/src/gates/index.js +17 -0
  99. package/dist/src/gates/providers/evidence.js +38 -0
  100. package/dist/src/gates/providers/index.js +21 -0
  101. package/dist/src/gates/providers/isolated-child-source.js +446 -0
  102. package/dist/src/gates/providers/loader.js +1069 -0
  103. package/dist/src/gates/providers/paired.js +52 -0
  104. package/dist/src/gates/providers/proportion.js +60 -0
  105. package/dist/src/gates/providers/registry.js +40 -0
  106. package/dist/src/gates/providers/scalar.js +41 -0
  107. package/dist/src/gates/providers/screening.js +53 -0
  108. package/dist/src/gates/providers/types.js +25 -0
  109. package/dist/src/gates/registry.js +1003 -0
  110. package/dist/src/gates/report.js +102 -0
  111. package/dist/src/gates/schema.js +56 -0
  112. package/dist/src/gates/stats/binomial.js +26 -0
  113. package/dist/src/gates/stats/bootstrap.js +62 -0
  114. package/dist/src/gates/stats/clopper-pearson.js +95 -0
  115. package/dist/src/gates/stats/digest.js +26 -0
  116. package/dist/src/gates/stats/error.js +5 -0
  117. package/dist/src/gates/stats/index.js +9 -0
  118. package/dist/src/gates/stats/normal.js +35 -0
  119. package/dist/src/gates/stats/paired.js +120 -0
  120. package/dist/src/gates/stats/splits.js +30 -0
  121. package/dist/src/gates/types.js +8 -0
  122. package/dist/src/governance/redaction.js +14 -4
  123. package/dist/src/mcp/muse-native-config.mjs +123 -0
  124. package/dist/src/mcp/tools/decision.mjs +188 -0
  125. package/dist/src/mcp/tools/subsystems.mjs +2 -0
  126. package/dist/src/providers/capability-matrix.yaml +2 -1
  127. package/dist/src/providers/provider-definitions.js +2 -1
  128. package/dist/src/sessions/adapters/muse.js +336 -39
  129. package/dist/src/sessions/batch-import.js +7 -0
  130. package/dist/src/sessions/workspace-discovery.js +13 -2
  131. package/package.json +1 -1
  132. package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
  133. package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
  134. package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
  135. package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
  136. package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
  137. package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
  138. package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
  139. package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
  140. package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
  141. package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
  142. package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
  143. package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
  144. package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
  145. package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
  146. package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
  147. package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
  148. package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
  149. package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
  150. package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
  151. package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
  152. package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
  153. package/schemas/decision/D29Study.v1.schema.json +1903 -0
  154. package/schemas/decision/D29Study.v2.schema.json +1344 -0
  155. package/schemas/decision/D29Study.v3.schema.json +609 -0
  156. package/schemas/decision/D29Study.v4.schema.json +1133 -0
  157. package/schemas/decision/D29Study.v6.schema.json +1205 -0
  158. package/schemas/decision/D29Study.v7.schema.json +1471 -0
  159. package/schemas/decision/D29Study.v8.schema.json +671 -0
  160. package/schemas/decision/D29Study.v9.schema.json +778 -0
  161. package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
  162. package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
  163. package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
  164. package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
  165. package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
  166. package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
  167. package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
  168. package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
  169. package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
  170. package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
  171. package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
  172. package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
  173. package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
  174. package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
  175. package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
  176. package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
  177. package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
  178. package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
  179. package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
  180. package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
  181. package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
  182. package/schemas/decision/DecisionRuleset.schema.json +8 -2
  183. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
  184. package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
  185. package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
  186. package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
  187. package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
  188. package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
  189. package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
  190. package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
  191. package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
  192. package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
  193. package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
  194. package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
  195. package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
  196. package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
  197. package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
  198. package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
  199. package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
  200. package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
  201. package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
  202. package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
  203. package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
  204. package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
  205. package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
  206. package/tools/agents/deploy-agents.mjs +1 -1
  207. package/tools/agents/providers/base.mjs +26 -7
  208. package/tools/agents/providers/factory.mjs +10 -2
  209. package/tools/agents/providers/muse-hooks.mjs +1 -29
  210. 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
  }