@sovr/engine 3.6.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +415 -8
- package/dist/index.d.ts +415 -8
- package/dist/index.js +233 -1
- package/dist/index.mjs +232 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42,6 +42,7 @@ __export(index_exports, {
|
|
|
42
42
|
SOVR_FEATURE_SWITCHES: () => SOVR_FEATURE_SWITCHES,
|
|
43
43
|
SelfConstraintEngine: () => SelfConstraintEngine,
|
|
44
44
|
SemanticDriftDetectorEngine: () => SemanticDriftDetectorEngine,
|
|
45
|
+
TIME_WINDOWS: () => TIME_WINDOWS,
|
|
45
46
|
TimeSeriesAggregator: () => TimeSeriesAggregator,
|
|
46
47
|
TrendAlertEngine: () => TrendAlertEngine,
|
|
47
48
|
TwoPhaseRouter: () => TwoPhaseRouter,
|
|
@@ -4326,6 +4327,60 @@ var OverrideDriftAnalyzer = class {
|
|
|
4326
4327
|
}
|
|
4327
4328
|
};
|
|
4328
4329
|
|
|
4330
|
+
// src/cockpitDataService.ts
|
|
4331
|
+
var TIME_WINDOWS = {
|
|
4332
|
+
"1h": { label: "1 \u5C0F\u65F6", seconds: 3600 },
|
|
4333
|
+
"6h": { label: "6 \u5C0F\u65F6", seconds: 21600 },
|
|
4334
|
+
"24h": { label: "24 \u5C0F\u65F6", seconds: 86400 },
|
|
4335
|
+
"7d": { label: "7 \u5929", seconds: 604800 },
|
|
4336
|
+
"30d": { label: "30 \u5929", seconds: 2592e3 }
|
|
4337
|
+
};
|
|
4338
|
+
var collectors = [];
|
|
4339
|
+
function registerMetricCollector(collector) {
|
|
4340
|
+
const exists = collectors.findIndex((c) => c.name === collector.name);
|
|
4341
|
+
if (exists >= 0) {
|
|
4342
|
+
collectors[exists] = collector;
|
|
4343
|
+
} else {
|
|
4344
|
+
collectors.push(collector);
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
registerMetricCollector({
|
|
4348
|
+
name: "gate_requests_total",
|
|
4349
|
+
category: "gate",
|
|
4350
|
+
unit: "req/min",
|
|
4351
|
+
collect: async () => 0
|
|
4352
|
+
});
|
|
4353
|
+
registerMetricCollector({
|
|
4354
|
+
name: "gate_block_rate",
|
|
4355
|
+
category: "gate",
|
|
4356
|
+
unit: "%",
|
|
4357
|
+
collect: async () => 0
|
|
4358
|
+
});
|
|
4359
|
+
registerMetricCollector({
|
|
4360
|
+
name: "audit_events_total",
|
|
4361
|
+
category: "audit",
|
|
4362
|
+
unit: "events/min",
|
|
4363
|
+
collect: async () => 0
|
|
4364
|
+
});
|
|
4365
|
+
registerMetricCollector({
|
|
4366
|
+
name: "trust_score",
|
|
4367
|
+
category: "trust",
|
|
4368
|
+
unit: "score",
|
|
4369
|
+
collect: async () => 85
|
|
4370
|
+
});
|
|
4371
|
+
registerMetricCollector({
|
|
4372
|
+
name: "budget_utilization",
|
|
4373
|
+
category: "budget",
|
|
4374
|
+
unit: "%",
|
|
4375
|
+
collect: async () => 0
|
|
4376
|
+
});
|
|
4377
|
+
registerMetricCollector({
|
|
4378
|
+
name: "avg_latency_ms",
|
|
4379
|
+
category: "performance",
|
|
4380
|
+
unit: "ms",
|
|
4381
|
+
collect: async () => 0
|
|
4382
|
+
});
|
|
4383
|
+
|
|
4329
4384
|
// src/index.ts
|
|
4330
4385
|
var DEFAULT_RULES = [
|
|
4331
4386
|
// --- HTTP Proxy: Dangerous outbound calls ---
|
|
@@ -4560,8 +4615,11 @@ var RISK_SCORES = {
|
|
|
4560
4615
|
high: 70,
|
|
4561
4616
|
critical: 95
|
|
4562
4617
|
};
|
|
4563
|
-
var ENGINE_VERSION = "
|
|
4618
|
+
var ENGINE_VERSION = "4.0.0";
|
|
4564
4619
|
var ENGINE_VERSION_CHECK_URL = "https://api.sovr.inc/api/sovr/v1/version/check";
|
|
4620
|
+
var DEFAULT_METERING_ENDPOINT = "https://sovr-ai-mkzgqqeh.manus.space/api/v1/metering/batch";
|
|
4621
|
+
var LOGIN_VALIDATE_URL = "https://sovr-ai-mkzgqqeh.manus.space/api/keys/validate";
|
|
4622
|
+
var IRREVERSIBLE_ACTION_REGEX = /^(DELETE|DROP|TRUNCATE|ALTER|UPDATE|INSERT|CREATE|GRANT|REVOKE|COPY|shell_exec|file_write|db_delete|db_update|db_execute|payment|deploy|publish)$/i;
|
|
4565
4623
|
var ENGINE_TIER_LIMITS = {
|
|
4566
4624
|
free: { evaluationsPerMonth: 50, irreversibleAllowsPerMonth: 0 },
|
|
4567
4625
|
personal: { evaluationsPerMonth: 5e3, irreversibleAllowsPerMonth: 500 },
|
|
@@ -4578,6 +4636,28 @@ var PolicyEngine = class {
|
|
|
4578
4636
|
_usage = { evaluations: 0, irreversibleAllows: 0, monthKey: "" };
|
|
4579
4637
|
_apiKey;
|
|
4580
4638
|
_versionChecked = false;
|
|
4639
|
+
// v4.0.0: BillingReporter
|
|
4640
|
+
_billingEnabled;
|
|
4641
|
+
_billingEndpoint;
|
|
4642
|
+
_billingBuffer = [];
|
|
4643
|
+
_billingFlushTimer = null;
|
|
4644
|
+
_billingFlushInterval;
|
|
4645
|
+
_billingBufferMax;
|
|
4646
|
+
_billingStats = {
|
|
4647
|
+
eventsReported: 0,
|
|
4648
|
+
eventsDropped: 0,
|
|
4649
|
+
bufferSize: 0,
|
|
4650
|
+
byType: {
|
|
4651
|
+
"gate.check": 0,
|
|
4652
|
+
"gate.block": 0,
|
|
4653
|
+
"irreversible.allowed": 0,
|
|
4654
|
+
"trust_bundle.issued": 0,
|
|
4655
|
+
"trust_bundle.exported": 0,
|
|
4656
|
+
"replay.requested": 0,
|
|
4657
|
+
"auditor.session": 0
|
|
4658
|
+
}
|
|
4659
|
+
};
|
|
4660
|
+
_loginValidated = false;
|
|
4581
4661
|
constructor(config) {
|
|
4582
4662
|
this._apiKey = config.apiKey || process.env.SOVR_API_KEY || "";
|
|
4583
4663
|
if (!this._apiKey) {
|
|
@@ -4609,7 +4689,19 @@ var PolicyEngine = class {
|
|
|
4609
4689
|
this._tier = config.tier ?? "free";
|
|
4610
4690
|
const now = /* @__PURE__ */ new Date();
|
|
4611
4691
|
this._usage.monthKey = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
4692
|
+
const billing = config.billing ?? {};
|
|
4693
|
+
this._billingEnabled = billing.enabled !== false;
|
|
4694
|
+
this._billingEndpoint = billing.meteringEndpoint ?? DEFAULT_METERING_ENDPOINT;
|
|
4695
|
+
this._billingFlushInterval = billing.flushIntervalMs ?? 1e4;
|
|
4696
|
+
this._billingBufferMax = billing.bufferMax ?? 500;
|
|
4612
4697
|
this._asyncVersionCheck();
|
|
4698
|
+
this._asyncLoginValidation();
|
|
4699
|
+
if (this._billingEnabled) {
|
|
4700
|
+
this._billingFlushTimer = setInterval(() => {
|
|
4701
|
+
this._flushBillingBuffer().catch(() => {
|
|
4702
|
+
});
|
|
4703
|
+
}, this._billingFlushInterval);
|
|
4704
|
+
}
|
|
4613
4705
|
}
|
|
4614
4706
|
/** v3.1.0: Async version check — blocks evaluate() on first call if deprecated */
|
|
4615
4707
|
async _asyncVersionCheck() {
|
|
@@ -4652,6 +4744,137 @@ var PolicyEngine = class {
|
|
|
4652
4744
|
}
|
|
4653
4745
|
}
|
|
4654
4746
|
}
|
|
4747
|
+
// ============================================================================
|
|
4748
|
+
// v4.0.0: Mandatory Login Validation (Remote API Key → User Identity)
|
|
4749
|
+
// ============================================================================
|
|
4750
|
+
/**
|
|
4751
|
+
* Async login validation — verifies API Key is bound to a registered user.
|
|
4752
|
+
* Runs once at startup. On failure: logs warning but allows operation (fail-open).
|
|
4753
|
+
* On success: sets tier from server response.
|
|
4754
|
+
*/
|
|
4755
|
+
async _asyncLoginValidation() {
|
|
4756
|
+
if (this._loginValidated) return;
|
|
4757
|
+
try {
|
|
4758
|
+
const res = await globalThis.fetch(LOGIN_VALIDATE_URL, {
|
|
4759
|
+
method: "GET",
|
|
4760
|
+
headers: {
|
|
4761
|
+
"Authorization": `Bearer ${this._apiKey}`,
|
|
4762
|
+
"X-SOVR-Source": "engine",
|
|
4763
|
+
"X-SOVR-Version": ENGINE_VERSION
|
|
4764
|
+
},
|
|
4765
|
+
signal: AbortSignal.timeout(8e3)
|
|
4766
|
+
});
|
|
4767
|
+
if (res.ok) {
|
|
4768
|
+
const data = await res.json();
|
|
4769
|
+
this._loginValidated = true;
|
|
4770
|
+
if (data.tier) {
|
|
4771
|
+
this._tier = data.tier;
|
|
4772
|
+
}
|
|
4773
|
+
if (data.valid === false) {
|
|
4774
|
+
console.error(
|
|
4775
|
+
"\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 SOVR LOGIN VALIDATION FAILED \u2551\n\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n\u2551 Your API key is not bound to a registered user. \u2551\n\u2551 Please login at: https://sovr.inc/login \u2551\n\u2551 Then get a valid key: https://sovr.inc/dashboard/api-keys \u2551\n\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n"
|
|
4776
|
+
);
|
|
4777
|
+
}
|
|
4778
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
4779
|
+
console.error(
|
|
4780
|
+
`[SOVR ENGINE] Login validation failed (HTTP ${res.status}). API key may be invalid or expired.`
|
|
4781
|
+
);
|
|
4782
|
+
}
|
|
4783
|
+
} catch {
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
/** Get login validation status */
|
|
4787
|
+
get loginValidated() {
|
|
4788
|
+
return this._loginValidated;
|
|
4789
|
+
}
|
|
4790
|
+
// ============================================================================
|
|
4791
|
+
// v4.0.0: BillingReporter — 7 event types, batch flush, quota check
|
|
4792
|
+
// ============================================================================
|
|
4793
|
+
/**
|
|
4794
|
+
* Record a billing event into the buffer.
|
|
4795
|
+
* Fire-and-forget — never blocks the main request flow.
|
|
4796
|
+
*/
|
|
4797
|
+
recordBillingEvent(event_type, action, resource, verdict, metadata) {
|
|
4798
|
+
if (!this._billingEnabled) return;
|
|
4799
|
+
this._billingBuffer.push({
|
|
4800
|
+
event_type,
|
|
4801
|
+
action,
|
|
4802
|
+
resource,
|
|
4803
|
+
verdict,
|
|
4804
|
+
api_key: this._apiKey,
|
|
4805
|
+
timestamp: Date.now(),
|
|
4806
|
+
metadata
|
|
4807
|
+
});
|
|
4808
|
+
this._billingStats.byType[event_type] = (this._billingStats.byType[event_type] || 0) + 1;
|
|
4809
|
+
if (this._billingBuffer.length >= this._billingBufferMax) {
|
|
4810
|
+
this._flushBillingBuffer().catch(() => {
|
|
4811
|
+
});
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4814
|
+
/**
|
|
4815
|
+
* Flush billing buffer to the metering endpoint.
|
|
4816
|
+
* Batch POST — fire-and-forget with re-queue on failure.
|
|
4817
|
+
*/
|
|
4818
|
+
async _flushBillingBuffer() {
|
|
4819
|
+
if (this._billingBuffer.length === 0) return;
|
|
4820
|
+
const batch = this._billingBuffer.splice(0, this._billingBufferMax);
|
|
4821
|
+
try {
|
|
4822
|
+
const response = await globalThis.fetch(this._billingEndpoint, {
|
|
4823
|
+
method: "POST",
|
|
4824
|
+
headers: {
|
|
4825
|
+
"Content-Type": "application/json",
|
|
4826
|
+
"Authorization": `Bearer ${this._apiKey}`,
|
|
4827
|
+
"X-SOVR-Source": "engine",
|
|
4828
|
+
"X-SOVR-Version": ENGINE_VERSION
|
|
4829
|
+
},
|
|
4830
|
+
body: JSON.stringify({ events: batch }),
|
|
4831
|
+
signal: AbortSignal.timeout(5e3)
|
|
4832
|
+
});
|
|
4833
|
+
if (response.ok) {
|
|
4834
|
+
this._billingStats.eventsReported += batch.length;
|
|
4835
|
+
} else {
|
|
4836
|
+
throw new Error(`HTTP ${response.status}`);
|
|
4837
|
+
}
|
|
4838
|
+
} catch {
|
|
4839
|
+
if (this._billingBuffer.length < this._billingBufferMax * 2) {
|
|
4840
|
+
this._billingBuffer.unshift(...batch);
|
|
4841
|
+
} else {
|
|
4842
|
+
this._billingStats.eventsDropped += batch.length;
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
/**
|
|
4847
|
+
* Classify an evaluate() call into the appropriate billing event type.
|
|
4848
|
+
* Key pricing principle: "放行的不可逆动作" is the highest-price tax base.
|
|
4849
|
+
*/
|
|
4850
|
+
_classifyBillingEvent(action, verdict) {
|
|
4851
|
+
if (verdict === "deny" || verdict === "block") {
|
|
4852
|
+
return "gate.block";
|
|
4853
|
+
}
|
|
4854
|
+
if ((verdict === "allow" || verdict === "escalate") && IRREVERSIBLE_ACTION_REGEX.test(action)) {
|
|
4855
|
+
return "irreversible.allowed";
|
|
4856
|
+
}
|
|
4857
|
+
return "gate.check";
|
|
4858
|
+
}
|
|
4859
|
+
/** Get billing statistics */
|
|
4860
|
+
get billingStats() {
|
|
4861
|
+
return {
|
|
4862
|
+
...this._billingStats,
|
|
4863
|
+
bufferSize: this._billingBuffer.length
|
|
4864
|
+
};
|
|
4865
|
+
}
|
|
4866
|
+
/**
|
|
4867
|
+
* Flush remaining billing events and stop the timer.
|
|
4868
|
+
* Call this before process exit for clean shutdown.
|
|
4869
|
+
*/
|
|
4870
|
+
async shutdown() {
|
|
4871
|
+
if (this._billingFlushTimer) {
|
|
4872
|
+
clearInterval(this._billingFlushTimer);
|
|
4873
|
+
this._billingFlushTimer = null;
|
|
4874
|
+
}
|
|
4875
|
+
await this._flushBillingBuffer().catch(() => {
|
|
4876
|
+
});
|
|
4877
|
+
}
|
|
4655
4878
|
/** Set the current tier (e.g., after API key verification) */
|
|
4656
4879
|
setTier(tier) {
|
|
4657
4880
|
this._tier = tier;
|
|
@@ -4763,6 +4986,14 @@ var PolicyEngine = class {
|
|
|
4763
4986
|
Promise.resolve(this.onAudit(event)).catch(() => {
|
|
4764
4987
|
});
|
|
4765
4988
|
}
|
|
4989
|
+
const billingType = this._classifyBillingEvent(request.action, verdict);
|
|
4990
|
+
this.recordBillingEvent(billingType, request.action, request.resource, verdict, {
|
|
4991
|
+
risk_score: riskScore,
|
|
4992
|
+
risk_level: riskLevel,
|
|
4993
|
+
decision_id: decisionId,
|
|
4994
|
+
channel: request.channel,
|
|
4995
|
+
matched_rules: matchedRules.length
|
|
4996
|
+
});
|
|
4766
4997
|
if (this._tier === "free") {
|
|
4767
4998
|
return {
|
|
4768
4999
|
...result,
|
|
@@ -4837,6 +5068,7 @@ var index_default = PolicyEngine;
|
|
|
4837
5068
|
SOVR_FEATURE_SWITCHES,
|
|
4838
5069
|
SelfConstraintEngine,
|
|
4839
5070
|
SemanticDriftDetectorEngine,
|
|
5071
|
+
TIME_WINDOWS,
|
|
4840
5072
|
TimeSeriesAggregator,
|
|
4841
5073
|
TrendAlertEngine,
|
|
4842
5074
|
TwoPhaseRouter,
|
package/dist/index.mjs
CHANGED
|
@@ -4257,6 +4257,60 @@ var OverrideDriftAnalyzer = class {
|
|
|
4257
4257
|
}
|
|
4258
4258
|
};
|
|
4259
4259
|
|
|
4260
|
+
// src/cockpitDataService.ts
|
|
4261
|
+
var TIME_WINDOWS = {
|
|
4262
|
+
"1h": { label: "1 \u5C0F\u65F6", seconds: 3600 },
|
|
4263
|
+
"6h": { label: "6 \u5C0F\u65F6", seconds: 21600 },
|
|
4264
|
+
"24h": { label: "24 \u5C0F\u65F6", seconds: 86400 },
|
|
4265
|
+
"7d": { label: "7 \u5929", seconds: 604800 },
|
|
4266
|
+
"30d": { label: "30 \u5929", seconds: 2592e3 }
|
|
4267
|
+
};
|
|
4268
|
+
var collectors = [];
|
|
4269
|
+
function registerMetricCollector(collector) {
|
|
4270
|
+
const exists = collectors.findIndex((c) => c.name === collector.name);
|
|
4271
|
+
if (exists >= 0) {
|
|
4272
|
+
collectors[exists] = collector;
|
|
4273
|
+
} else {
|
|
4274
|
+
collectors.push(collector);
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
registerMetricCollector({
|
|
4278
|
+
name: "gate_requests_total",
|
|
4279
|
+
category: "gate",
|
|
4280
|
+
unit: "req/min",
|
|
4281
|
+
collect: async () => 0
|
|
4282
|
+
});
|
|
4283
|
+
registerMetricCollector({
|
|
4284
|
+
name: "gate_block_rate",
|
|
4285
|
+
category: "gate",
|
|
4286
|
+
unit: "%",
|
|
4287
|
+
collect: async () => 0
|
|
4288
|
+
});
|
|
4289
|
+
registerMetricCollector({
|
|
4290
|
+
name: "audit_events_total",
|
|
4291
|
+
category: "audit",
|
|
4292
|
+
unit: "events/min",
|
|
4293
|
+
collect: async () => 0
|
|
4294
|
+
});
|
|
4295
|
+
registerMetricCollector({
|
|
4296
|
+
name: "trust_score",
|
|
4297
|
+
category: "trust",
|
|
4298
|
+
unit: "score",
|
|
4299
|
+
collect: async () => 85
|
|
4300
|
+
});
|
|
4301
|
+
registerMetricCollector({
|
|
4302
|
+
name: "budget_utilization",
|
|
4303
|
+
category: "budget",
|
|
4304
|
+
unit: "%",
|
|
4305
|
+
collect: async () => 0
|
|
4306
|
+
});
|
|
4307
|
+
registerMetricCollector({
|
|
4308
|
+
name: "avg_latency_ms",
|
|
4309
|
+
category: "performance",
|
|
4310
|
+
unit: "ms",
|
|
4311
|
+
collect: async () => 0
|
|
4312
|
+
});
|
|
4313
|
+
|
|
4260
4314
|
// src/index.ts
|
|
4261
4315
|
var DEFAULT_RULES = [
|
|
4262
4316
|
// --- HTTP Proxy: Dangerous outbound calls ---
|
|
@@ -4491,8 +4545,11 @@ var RISK_SCORES = {
|
|
|
4491
4545
|
high: 70,
|
|
4492
4546
|
critical: 95
|
|
4493
4547
|
};
|
|
4494
|
-
var ENGINE_VERSION = "
|
|
4548
|
+
var ENGINE_VERSION = "4.0.0";
|
|
4495
4549
|
var ENGINE_VERSION_CHECK_URL = "https://api.sovr.inc/api/sovr/v1/version/check";
|
|
4550
|
+
var DEFAULT_METERING_ENDPOINT = "https://sovr-ai-mkzgqqeh.manus.space/api/v1/metering/batch";
|
|
4551
|
+
var LOGIN_VALIDATE_URL = "https://sovr-ai-mkzgqqeh.manus.space/api/keys/validate";
|
|
4552
|
+
var IRREVERSIBLE_ACTION_REGEX = /^(DELETE|DROP|TRUNCATE|ALTER|UPDATE|INSERT|CREATE|GRANT|REVOKE|COPY|shell_exec|file_write|db_delete|db_update|db_execute|payment|deploy|publish)$/i;
|
|
4496
4553
|
var ENGINE_TIER_LIMITS = {
|
|
4497
4554
|
free: { evaluationsPerMonth: 50, irreversibleAllowsPerMonth: 0 },
|
|
4498
4555
|
personal: { evaluationsPerMonth: 5e3, irreversibleAllowsPerMonth: 500 },
|
|
@@ -4509,6 +4566,28 @@ var PolicyEngine = class {
|
|
|
4509
4566
|
_usage = { evaluations: 0, irreversibleAllows: 0, monthKey: "" };
|
|
4510
4567
|
_apiKey;
|
|
4511
4568
|
_versionChecked = false;
|
|
4569
|
+
// v4.0.0: BillingReporter
|
|
4570
|
+
_billingEnabled;
|
|
4571
|
+
_billingEndpoint;
|
|
4572
|
+
_billingBuffer = [];
|
|
4573
|
+
_billingFlushTimer = null;
|
|
4574
|
+
_billingFlushInterval;
|
|
4575
|
+
_billingBufferMax;
|
|
4576
|
+
_billingStats = {
|
|
4577
|
+
eventsReported: 0,
|
|
4578
|
+
eventsDropped: 0,
|
|
4579
|
+
bufferSize: 0,
|
|
4580
|
+
byType: {
|
|
4581
|
+
"gate.check": 0,
|
|
4582
|
+
"gate.block": 0,
|
|
4583
|
+
"irreversible.allowed": 0,
|
|
4584
|
+
"trust_bundle.issued": 0,
|
|
4585
|
+
"trust_bundle.exported": 0,
|
|
4586
|
+
"replay.requested": 0,
|
|
4587
|
+
"auditor.session": 0
|
|
4588
|
+
}
|
|
4589
|
+
};
|
|
4590
|
+
_loginValidated = false;
|
|
4512
4591
|
constructor(config) {
|
|
4513
4592
|
this._apiKey = config.apiKey || process.env.SOVR_API_KEY || "";
|
|
4514
4593
|
if (!this._apiKey) {
|
|
@@ -4540,7 +4619,19 @@ var PolicyEngine = class {
|
|
|
4540
4619
|
this._tier = config.tier ?? "free";
|
|
4541
4620
|
const now = /* @__PURE__ */ new Date();
|
|
4542
4621
|
this._usage.monthKey = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
4622
|
+
const billing = config.billing ?? {};
|
|
4623
|
+
this._billingEnabled = billing.enabled !== false;
|
|
4624
|
+
this._billingEndpoint = billing.meteringEndpoint ?? DEFAULT_METERING_ENDPOINT;
|
|
4625
|
+
this._billingFlushInterval = billing.flushIntervalMs ?? 1e4;
|
|
4626
|
+
this._billingBufferMax = billing.bufferMax ?? 500;
|
|
4543
4627
|
this._asyncVersionCheck();
|
|
4628
|
+
this._asyncLoginValidation();
|
|
4629
|
+
if (this._billingEnabled) {
|
|
4630
|
+
this._billingFlushTimer = setInterval(() => {
|
|
4631
|
+
this._flushBillingBuffer().catch(() => {
|
|
4632
|
+
});
|
|
4633
|
+
}, this._billingFlushInterval);
|
|
4634
|
+
}
|
|
4544
4635
|
}
|
|
4545
4636
|
/** v3.1.0: Async version check — blocks evaluate() on first call if deprecated */
|
|
4546
4637
|
async _asyncVersionCheck() {
|
|
@@ -4583,6 +4674,137 @@ var PolicyEngine = class {
|
|
|
4583
4674
|
}
|
|
4584
4675
|
}
|
|
4585
4676
|
}
|
|
4677
|
+
// ============================================================================
|
|
4678
|
+
// v4.0.0: Mandatory Login Validation (Remote API Key → User Identity)
|
|
4679
|
+
// ============================================================================
|
|
4680
|
+
/**
|
|
4681
|
+
* Async login validation — verifies API Key is bound to a registered user.
|
|
4682
|
+
* Runs once at startup. On failure: logs warning but allows operation (fail-open).
|
|
4683
|
+
* On success: sets tier from server response.
|
|
4684
|
+
*/
|
|
4685
|
+
async _asyncLoginValidation() {
|
|
4686
|
+
if (this._loginValidated) return;
|
|
4687
|
+
try {
|
|
4688
|
+
const res = await globalThis.fetch(LOGIN_VALIDATE_URL, {
|
|
4689
|
+
method: "GET",
|
|
4690
|
+
headers: {
|
|
4691
|
+
"Authorization": `Bearer ${this._apiKey}`,
|
|
4692
|
+
"X-SOVR-Source": "engine",
|
|
4693
|
+
"X-SOVR-Version": ENGINE_VERSION
|
|
4694
|
+
},
|
|
4695
|
+
signal: AbortSignal.timeout(8e3)
|
|
4696
|
+
});
|
|
4697
|
+
if (res.ok) {
|
|
4698
|
+
const data = await res.json();
|
|
4699
|
+
this._loginValidated = true;
|
|
4700
|
+
if (data.tier) {
|
|
4701
|
+
this._tier = data.tier;
|
|
4702
|
+
}
|
|
4703
|
+
if (data.valid === false) {
|
|
4704
|
+
console.error(
|
|
4705
|
+
"\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 SOVR LOGIN VALIDATION FAILED \u2551\n\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n\u2551 Your API key is not bound to a registered user. \u2551\n\u2551 Please login at: https://sovr.inc/login \u2551\n\u2551 Then get a valid key: https://sovr.inc/dashboard/api-keys \u2551\n\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n"
|
|
4706
|
+
);
|
|
4707
|
+
}
|
|
4708
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
4709
|
+
console.error(
|
|
4710
|
+
`[SOVR ENGINE] Login validation failed (HTTP ${res.status}). API key may be invalid or expired.`
|
|
4711
|
+
);
|
|
4712
|
+
}
|
|
4713
|
+
} catch {
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
/** Get login validation status */
|
|
4717
|
+
get loginValidated() {
|
|
4718
|
+
return this._loginValidated;
|
|
4719
|
+
}
|
|
4720
|
+
// ============================================================================
|
|
4721
|
+
// v4.0.0: BillingReporter — 7 event types, batch flush, quota check
|
|
4722
|
+
// ============================================================================
|
|
4723
|
+
/**
|
|
4724
|
+
* Record a billing event into the buffer.
|
|
4725
|
+
* Fire-and-forget — never blocks the main request flow.
|
|
4726
|
+
*/
|
|
4727
|
+
recordBillingEvent(event_type, action, resource, verdict, metadata) {
|
|
4728
|
+
if (!this._billingEnabled) return;
|
|
4729
|
+
this._billingBuffer.push({
|
|
4730
|
+
event_type,
|
|
4731
|
+
action,
|
|
4732
|
+
resource,
|
|
4733
|
+
verdict,
|
|
4734
|
+
api_key: this._apiKey,
|
|
4735
|
+
timestamp: Date.now(),
|
|
4736
|
+
metadata
|
|
4737
|
+
});
|
|
4738
|
+
this._billingStats.byType[event_type] = (this._billingStats.byType[event_type] || 0) + 1;
|
|
4739
|
+
if (this._billingBuffer.length >= this._billingBufferMax) {
|
|
4740
|
+
this._flushBillingBuffer().catch(() => {
|
|
4741
|
+
});
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
/**
|
|
4745
|
+
* Flush billing buffer to the metering endpoint.
|
|
4746
|
+
* Batch POST — fire-and-forget with re-queue on failure.
|
|
4747
|
+
*/
|
|
4748
|
+
async _flushBillingBuffer() {
|
|
4749
|
+
if (this._billingBuffer.length === 0) return;
|
|
4750
|
+
const batch = this._billingBuffer.splice(0, this._billingBufferMax);
|
|
4751
|
+
try {
|
|
4752
|
+
const response = await globalThis.fetch(this._billingEndpoint, {
|
|
4753
|
+
method: "POST",
|
|
4754
|
+
headers: {
|
|
4755
|
+
"Content-Type": "application/json",
|
|
4756
|
+
"Authorization": `Bearer ${this._apiKey}`,
|
|
4757
|
+
"X-SOVR-Source": "engine",
|
|
4758
|
+
"X-SOVR-Version": ENGINE_VERSION
|
|
4759
|
+
},
|
|
4760
|
+
body: JSON.stringify({ events: batch }),
|
|
4761
|
+
signal: AbortSignal.timeout(5e3)
|
|
4762
|
+
});
|
|
4763
|
+
if (response.ok) {
|
|
4764
|
+
this._billingStats.eventsReported += batch.length;
|
|
4765
|
+
} else {
|
|
4766
|
+
throw new Error(`HTTP ${response.status}`);
|
|
4767
|
+
}
|
|
4768
|
+
} catch {
|
|
4769
|
+
if (this._billingBuffer.length < this._billingBufferMax * 2) {
|
|
4770
|
+
this._billingBuffer.unshift(...batch);
|
|
4771
|
+
} else {
|
|
4772
|
+
this._billingStats.eventsDropped += batch.length;
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
/**
|
|
4777
|
+
* Classify an evaluate() call into the appropriate billing event type.
|
|
4778
|
+
* Key pricing principle: "放行的不可逆动作" is the highest-price tax base.
|
|
4779
|
+
*/
|
|
4780
|
+
_classifyBillingEvent(action, verdict) {
|
|
4781
|
+
if (verdict === "deny" || verdict === "block") {
|
|
4782
|
+
return "gate.block";
|
|
4783
|
+
}
|
|
4784
|
+
if ((verdict === "allow" || verdict === "escalate") && IRREVERSIBLE_ACTION_REGEX.test(action)) {
|
|
4785
|
+
return "irreversible.allowed";
|
|
4786
|
+
}
|
|
4787
|
+
return "gate.check";
|
|
4788
|
+
}
|
|
4789
|
+
/** Get billing statistics */
|
|
4790
|
+
get billingStats() {
|
|
4791
|
+
return {
|
|
4792
|
+
...this._billingStats,
|
|
4793
|
+
bufferSize: this._billingBuffer.length
|
|
4794
|
+
};
|
|
4795
|
+
}
|
|
4796
|
+
/**
|
|
4797
|
+
* Flush remaining billing events and stop the timer.
|
|
4798
|
+
* Call this before process exit for clean shutdown.
|
|
4799
|
+
*/
|
|
4800
|
+
async shutdown() {
|
|
4801
|
+
if (this._billingFlushTimer) {
|
|
4802
|
+
clearInterval(this._billingFlushTimer);
|
|
4803
|
+
this._billingFlushTimer = null;
|
|
4804
|
+
}
|
|
4805
|
+
await this._flushBillingBuffer().catch(() => {
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4586
4808
|
/** Set the current tier (e.g., after API key verification) */
|
|
4587
4809
|
setTier(tier) {
|
|
4588
4810
|
this._tier = tier;
|
|
@@ -4694,6 +4916,14 @@ var PolicyEngine = class {
|
|
|
4694
4916
|
Promise.resolve(this.onAudit(event)).catch(() => {
|
|
4695
4917
|
});
|
|
4696
4918
|
}
|
|
4919
|
+
const billingType = this._classifyBillingEvent(request.action, verdict);
|
|
4920
|
+
this.recordBillingEvent(billingType, request.action, request.resource, verdict, {
|
|
4921
|
+
risk_score: riskScore,
|
|
4922
|
+
risk_level: riskLevel,
|
|
4923
|
+
decision_id: decisionId,
|
|
4924
|
+
channel: request.channel,
|
|
4925
|
+
matched_rules: matchedRules.length
|
|
4926
|
+
});
|
|
4697
4927
|
if (this._tier === "free") {
|
|
4698
4928
|
return {
|
|
4699
4929
|
...result,
|
|
@@ -4767,6 +4997,7 @@ export {
|
|
|
4767
4997
|
SOVR_FEATURE_SWITCHES,
|
|
4768
4998
|
SelfConstraintEngine,
|
|
4769
4999
|
SemanticDriftDetectorEngine,
|
|
5000
|
+
TIME_WINDOWS,
|
|
4770
5001
|
TimeSeriesAggregator,
|
|
4771
5002
|
TrendAlertEngine,
|
|
4772
5003
|
TwoPhaseRouter,
|