@patchstack/connect 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT-INSTALL.md +51 -7
- package/README.md +6 -3
- package/dist/{chunk-3G2I6QL6.js → chunk-IOODP4ZB.js} +19 -2
- package/dist/chunk-IOODP4ZB.js.map +1 -0
- package/dist/cli.js +315 -59
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +182 -21
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.cts +26 -0
- package/dist/protect.d.ts +26 -0
- package/dist/protect.edge.js +172 -21
- package/dist/protect.edge.js.map +3 -3
- package/dist/protect.js +162 -24
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-2MMPQN2A.js → refresh-manifest-3QGLYXV5.js} +2 -2
- package/dist/{refresh-manifest-2MMPQN2A.js.map → refresh-manifest-3QGLYXV5.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-3G2I6QL6.js.map +0 -1
package/dist/protect.cjs
CHANGED
|
@@ -123,6 +123,27 @@ var init_pulse_token = __esm({
|
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
+
// src/build-id.ts
|
|
127
|
+
function canonicalBuildId(value) {
|
|
128
|
+
if (typeof value !== "string") return null;
|
|
129
|
+
const trimmed = value.trim();
|
|
130
|
+
return BUILD_ID.test(trimmed) ? trimmed.toLowerCase() : null;
|
|
131
|
+
}
|
|
132
|
+
function readBuildStamp(bundle) {
|
|
133
|
+
if (bundle === null || typeof bundle !== "object") return null;
|
|
134
|
+
const namespace = bundle[BUILD_STAMP_KEY];
|
|
135
|
+
if (namespace === null || typeof namespace !== "object") return null;
|
|
136
|
+
return canonicalBuildId(namespace.build_id);
|
|
137
|
+
}
|
|
138
|
+
var BUILD_ID, BUILD_STAMP_KEY;
|
|
139
|
+
var init_build_id = __esm({
|
|
140
|
+
"src/build-id.ts"() {
|
|
141
|
+
"use strict";
|
|
142
|
+
BUILD_ID = /^[0-9a-f]{64}$/i;
|
|
143
|
+
BUILD_STAMP_KEY = "_patchstack";
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
126
147
|
// src/types.ts
|
|
127
148
|
var PatchstackError;
|
|
128
149
|
var init_types = __esm({
|
|
@@ -246,6 +267,7 @@ var init_client = __esm({
|
|
|
246
267
|
"use strict";
|
|
247
268
|
init_types();
|
|
248
269
|
init_pulse_token();
|
|
270
|
+
init_build_id();
|
|
249
271
|
DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
|
|
250
272
|
DEFAULT_TIMEOUT_MS = 3e4;
|
|
251
273
|
CLAIM_TOKEN_HEADER = "X-Patchstack-Claim-Token";
|
|
@@ -3685,6 +3707,18 @@ var RULE_PROPERTY_SHAPES = Object.freeze({
|
|
|
3685
3707
|
});
|
|
3686
3708
|
var ENFORCEMENT_VALUES = Object.freeze(["dry-run"]);
|
|
3687
3709
|
var WHEN_KEYS = Object.freeze(["path", "method"]);
|
|
3710
|
+
var BUILD_SCOPE_PROPERTY = "build_scope";
|
|
3711
|
+
var BUILD_SCOPE = Object.freeze({
|
|
3712
|
+
applies_to: Object.freeze(["firewall"]),
|
|
3713
|
+
usable: Object.freeze({
|
|
3714
|
+
type: "string",
|
|
3715
|
+
trim: true,
|
|
3716
|
+
pattern: "^[0-9a-fA-F]{64}$",
|
|
3717
|
+
canonical: "lowercase"
|
|
3718
|
+
}),
|
|
3719
|
+
unreadable: "the rule remains accepted and detects only by default",
|
|
3720
|
+
local_override: "trustLocalRuleScope may explicitly enforce a caller-supplied rule"
|
|
3721
|
+
});
|
|
3688
3722
|
var RULE_PROPERTIES = Object.freeze([
|
|
3689
3723
|
"id",
|
|
3690
3724
|
"rule_id",
|
|
@@ -3704,12 +3738,13 @@ var RULE_PROPERTIES = Object.freeze([
|
|
|
3704
3738
|
"remove_headers",
|
|
3705
3739
|
"cookie_flags",
|
|
3706
3740
|
"ensure",
|
|
3707
|
-
"capture"
|
|
3741
|
+
"capture",
|
|
3742
|
+
BUILD_SCOPE_PROPERTY
|
|
3708
3743
|
]);
|
|
3709
3744
|
var CAPTURE_VERSION = 1;
|
|
3710
3745
|
var CAPTURE_KEYS = Object.freeze(["version", "raw_chars"]);
|
|
3711
3746
|
var CAPTURE_RAW_CHARS_MAX = 512;
|
|
3712
|
-
var NULL_EXEMPT_PROPERTIES = Object.freeze(["capture"]);
|
|
3747
|
+
var NULL_EXEMPT_PROPERTIES = Object.freeze(["capture", BUILD_SCOPE_PROPERTY]);
|
|
3713
3748
|
function captureProblem(capture) {
|
|
3714
3749
|
if (capture === void 0 || capture === null) return null;
|
|
3715
3750
|
if (typeof capture !== "object" || Array.isArray(capture)) return "capture must be an object";
|
|
@@ -3950,6 +3985,10 @@ function validateBundle(bundle, opts = {}) {
|
|
|
3950
3985
|
rejected.push({ id: idOf(wl), reason: "whitelist has no rule_id (would suppress every rule); set allowGlobalWhitelists to permit" });
|
|
3951
3986
|
continue;
|
|
3952
3987
|
}
|
|
3988
|
+
if (wl && typeof wl === "object" && BUILD_SCOPE_PROPERTY in wl) {
|
|
3989
|
+
rejected.push({ id: idOf(wl), reason: `whitelist may not carry ${BUILD_SCOPE_PROPERTY}` });
|
|
3990
|
+
continue;
|
|
3991
|
+
}
|
|
3953
3992
|
const reason = conditionsProblem(wl?.rule_v2);
|
|
3954
3993
|
if (reason) rejected.push({ id: idOf(wl), reason: `whitelist: ${reason}` });
|
|
3955
3994
|
else whitelists.push(wl);
|
|
@@ -4220,8 +4259,11 @@ function asText(value) {
|
|
|
4220
4259
|
|
|
4221
4260
|
// src/protect/engine/pulse-client.js
|
|
4222
4261
|
init_pulse_token();
|
|
4262
|
+
init_build_id();
|
|
4223
4263
|
var DEFAULT_BASE_URL2 = "https://api.patchstack.com/monitor/pulse";
|
|
4224
4264
|
var DEFAULT_CACHE_TTL2 = 3e5;
|
|
4265
|
+
var BUILD_VERDICT_HEADER = "X-Patchstack-Build-Match";
|
|
4266
|
+
var BUILD_IDENTITY_HEADER = "X-Patchstack-Build-ID";
|
|
4225
4267
|
var JITTER_FRACTION2 = 0.1;
|
|
4226
4268
|
var PulseRuleClient = class {
|
|
4227
4269
|
#siteUuid;
|
|
@@ -4234,7 +4276,8 @@ var PulseRuleClient = class {
|
|
|
4234
4276
|
#etag;
|
|
4235
4277
|
#pulseAuth;
|
|
4236
4278
|
#detectionState;
|
|
4237
|
-
|
|
4279
|
+
#buildId;
|
|
4280
|
+
constructor({ siteUuid, baseUrl, cacheTtl, etag, timeoutMs, pulseAuth, detectionState, buildId } = {}) {
|
|
4238
4281
|
this.#timeoutMs = Number(timeoutMs) > 0 ? Number(timeoutMs) : 3e4;
|
|
4239
4282
|
this.#siteUuid = siteUuid ?? process.env.PATCHSTACK_SITE_UUID;
|
|
4240
4283
|
this.#baseUrl = safeBaseUrl(baseUrl ?? process.env.PATCHSTACK_PULSE_RULES_URL, DEFAULT_BASE_URL2, "rule endpoint");
|
|
@@ -4242,6 +4285,7 @@ var PulseRuleClient = class {
|
|
|
4242
4285
|
this.#etag = etag ?? null;
|
|
4243
4286
|
this.#pulseAuth = pulseAuth ?? null;
|
|
4244
4287
|
this.#detectionState = typeof detectionState === "string" ? detectionState : null;
|
|
4288
|
+
this.#buildId = canonicalBuildId(buildId);
|
|
4245
4289
|
if (!this.#siteUuid) {
|
|
4246
4290
|
throw new Error("Patchstack site UUID is required. Pass { siteUuid } or set PATCHSTACK_SITE_UUID.");
|
|
4247
4291
|
}
|
|
@@ -4261,11 +4305,23 @@ var PulseRuleClient = class {
|
|
|
4261
4305
|
if (this.#detectionState !== null && typeof auth.Authorization === "string") {
|
|
4262
4306
|
headers["X-Patchstack-Detections"] = this.#detectionState;
|
|
4263
4307
|
}
|
|
4308
|
+
if (this.#buildId !== null && typeof auth.Authorization === "string") {
|
|
4309
|
+
headers["X-Patchstack-Build"] = this.#buildId;
|
|
4310
|
+
}
|
|
4264
4311
|
if (this.#etag) headers["If-None-Match"] = this.#etag;
|
|
4265
4312
|
const response = await fetch(url, { method: "GET", headers, signal: AbortSignal.timeout(this.#timeoutMs) });
|
|
4266
4313
|
if (response.status === 304) {
|
|
4267
4314
|
this.#touch(now);
|
|
4268
|
-
|
|
4315
|
+
const result2 = {
|
|
4316
|
+
...this.#cache ?? { firewall: [], whitelists: [], whitelist_keys: {} },
|
|
4317
|
+
success: true,
|
|
4318
|
+
notModified: true,
|
|
4319
|
+
etag: response.headers?.get?.("etag") ?? this.#etag,
|
|
4320
|
+
build: buildVerdictOf(response.headers)
|
|
4321
|
+
};
|
|
4322
|
+
this.#cache = result2;
|
|
4323
|
+
this.#etag = result2.etag;
|
|
4324
|
+
return result2;
|
|
4269
4325
|
}
|
|
4270
4326
|
if (!response.ok) {
|
|
4271
4327
|
return { success: false, error: `API returned ${response.status}`, firewall: [], whitelists: [], whitelist_keys: {} };
|
|
@@ -4281,7 +4337,10 @@ var PulseRuleClient = class {
|
|
|
4281
4337
|
firewall: data.firewall,
|
|
4282
4338
|
whitelists: Array.isArray(data.whitelists) ? data.whitelists : [],
|
|
4283
4339
|
whitelist_keys: data.whitelist_keys ?? {},
|
|
4284
|
-
...enforcementField(data)
|
|
4340
|
+
...enforcementField(data),
|
|
4341
|
+
// Carried through so the caller can decide what a build-scoped rule may do. Response metadata,
|
|
4342
|
+
// rather than bundle content, because it varies with the build asking and is also present on 304.
|
|
4343
|
+
build: buildVerdictOf(response.headers)
|
|
4285
4344
|
};
|
|
4286
4345
|
this.#cache = result;
|
|
4287
4346
|
this.#etag = result.etag;
|
|
@@ -4307,6 +4366,13 @@ var PulseRuleClient = class {
|
|
|
4307
4366
|
this.#etag = null;
|
|
4308
4367
|
}
|
|
4309
4368
|
};
|
|
4369
|
+
function buildVerdictOf(headers) {
|
|
4370
|
+
const get = headers !== null && typeof headers === "object" && typeof headers.get === "function" ? (name) => headers.get(name) : () => null;
|
|
4371
|
+
const stated2 = get(BUILD_VERDICT_HEADER);
|
|
4372
|
+
const verdict = stated2 === "match" || stated2 === "missing" || stated2 === "mismatch" ? stated2 : "unknown";
|
|
4373
|
+
const matched = get(BUILD_IDENTITY_HEADER);
|
|
4374
|
+
return { verdict, matchedBuildId: typeof matched === "string" ? matched : null };
|
|
4375
|
+
}
|
|
4310
4376
|
function enforcementField(data) {
|
|
4311
4377
|
if (!data || typeof data !== "object") return {};
|
|
4312
4378
|
const v = data.enforcement ?? data.mode;
|
|
@@ -5069,6 +5135,7 @@ function renderBlockPage({ url = "", code = "patchstack" } = {}) {
|
|
|
5069
5135
|
}
|
|
5070
5136
|
|
|
5071
5137
|
// src/protect/rules/store.js
|
|
5138
|
+
init_build_id();
|
|
5072
5139
|
var fsMod;
|
|
5073
5140
|
async function loadFs() {
|
|
5074
5141
|
if (fsMod !== void 0) return fsMod;
|
|
@@ -5149,15 +5216,25 @@ async function cacheRead(dir) {
|
|
|
5149
5216
|
function toEnvelope(value) {
|
|
5150
5217
|
if (!value || typeof value !== "object") return null;
|
|
5151
5218
|
if (value.bundle && typeof value.bundle === "object") {
|
|
5152
|
-
|
|
5219
|
+
const buildId = canonicalBuildId(value.buildId);
|
|
5220
|
+
const matched = canonicalBuildId(value.matchedBuildId);
|
|
5221
|
+
return {
|
|
5222
|
+
bundle: value.bundle,
|
|
5223
|
+
etag: value.etag ?? null,
|
|
5224
|
+
buildId,
|
|
5225
|
+
// A confirmation belongs to the presentation stored beside it. A crossed or partially written
|
|
5226
|
+
// envelope confirms nothing, even when one of its fields happens to name the current map.
|
|
5227
|
+
matchedBuildId: buildId !== null && matched === buildId ? matched : null
|
|
5228
|
+
};
|
|
5153
5229
|
}
|
|
5154
5230
|
if (Array.isArray(value.firewall) || Array.isArray(value.whitelists)) {
|
|
5155
|
-
return { bundle: value, etag: null };
|
|
5231
|
+
return { bundle: value, etag: null, buildId: null, matchedBuildId: null };
|
|
5156
5232
|
}
|
|
5157
5233
|
return null;
|
|
5158
5234
|
}
|
|
5159
5235
|
|
|
5160
5236
|
// src/protect/rules/source.js
|
|
5237
|
+
init_build_id();
|
|
5161
5238
|
function liveUpdateRejections(res, options) {
|
|
5162
5239
|
if (options.acceptPartialBundle) return [];
|
|
5163
5240
|
const { rejected } = validateBundle(
|
|
@@ -5184,62 +5261,146 @@ function fromSource(bundle, origin, reason) {
|
|
|
5184
5261
|
source: reason === void 0 ? { ok: true, origin } : { ok: false, origin, reason }
|
|
5185
5262
|
};
|
|
5186
5263
|
}
|
|
5264
|
+
function buildIdentity(options) {
|
|
5265
|
+
if (options.buildId !== void 0 && options.buildId !== null) {
|
|
5266
|
+
const explicit = canonicalBuildId(options.buildId);
|
|
5267
|
+
return explicit === null ? { id: null, reason: "the buildId option is not a complete 64-hex SHA-256 identity" } : { id: explicit };
|
|
5268
|
+
}
|
|
5269
|
+
const stamped = readBuildStamp(options.rules);
|
|
5270
|
+
if (stamped !== null) return { id: stamped };
|
|
5271
|
+
return {
|
|
5272
|
+
id: null,
|
|
5273
|
+
reason: options.rules ? "the guard rules file carries no build identity \u2014 the build did not stamp one" : "this guard was configured without bundled rules, so it carries no build identity"
|
|
5274
|
+
};
|
|
5275
|
+
}
|
|
5276
|
+
function withScopedDryRun(bundle, confirmed, reason, options = {}) {
|
|
5277
|
+
let held = 0;
|
|
5278
|
+
let unusable = 0;
|
|
5279
|
+
const firewall = bundle.firewall.map((rule) => {
|
|
5280
|
+
if (rule === null || typeof rule !== "object") return rule;
|
|
5281
|
+
if (!(BUILD_SCOPE_PROPERTY in rule)) return rule;
|
|
5282
|
+
const scope = rule[BUILD_SCOPE_PROPERTY];
|
|
5283
|
+
const target = canonicalBuildId(scope);
|
|
5284
|
+
if (target !== null && confirmed !== null && target === confirmed) return rule;
|
|
5285
|
+
if (rule.enforcement === "dry-run") return rule;
|
|
5286
|
+
held += 1;
|
|
5287
|
+
if (target === null) unusable += 1;
|
|
5288
|
+
return { ...rule, enforcement: "dry-run" };
|
|
5289
|
+
});
|
|
5290
|
+
if (held === 0) return bundle;
|
|
5291
|
+
const explanations = [
|
|
5292
|
+
...unusable > 0 ? [`${unusable} rule(s) carry an unusable ${BUILD_SCOPE_PROPERTY}`] : [],
|
|
5293
|
+
...held > unusable ? [reason] : []
|
|
5294
|
+
];
|
|
5295
|
+
notify(
|
|
5296
|
+
options.onError,
|
|
5297
|
+
new Error(`${held} build-scoped rule(s) are detecting only, not blocking: ${explanations.join("; ")}`),
|
|
5298
|
+
"onError"
|
|
5299
|
+
);
|
|
5300
|
+
return { ...bundle, firewall };
|
|
5301
|
+
}
|
|
5302
|
+
function confirmedBy(res, presented) {
|
|
5303
|
+
if (presented === null) return null;
|
|
5304
|
+
const verdict = res?.build;
|
|
5305
|
+
if (verdict?.verdict !== "match") return null;
|
|
5306
|
+
return canonicalBuildId(verdict.matchedBuildId) === presented ? presented : null;
|
|
5307
|
+
}
|
|
5187
5308
|
async function resolveRules(options, store, ctx = {}) {
|
|
5188
5309
|
const timeoutMs = ctx.timeoutMs;
|
|
5189
5310
|
if (options.siteUuid) {
|
|
5190
5311
|
const prior = await store.read();
|
|
5191
|
-
const
|
|
5312
|
+
const identity = buildIdentity(options);
|
|
5313
|
+
const sameBuild = prior?.buildId === identity.id;
|
|
5314
|
+
const cacheConfirmed = identity.id !== null && sameBuild && prior?.matchedBuildId === identity.id ? identity.id : null;
|
|
5315
|
+
const unconfirmed = identity.reason ?? "the platform did not confirm that these coordinates belong to this map";
|
|
5316
|
+
const served = (bundle, confirmed) => withScopedDryRun(bundle, confirmed, unconfirmed, options);
|
|
5317
|
+
const local = (bundle) => options.trustLocalRuleScope === true ? bundle : withScopedDryRun(bundle, identity.id, identity.reason ?? "these rules name a different map", options);
|
|
5318
|
+
const client = new PulseRuleClient({
|
|
5319
|
+
siteUuid: options.siteUuid,
|
|
5320
|
+
baseUrl: options.pulseRulesUrl,
|
|
5321
|
+
etag: sameBuild ? prior?.etag : null,
|
|
5322
|
+
timeoutMs,
|
|
5323
|
+
pulseAuth: ctx.pulseAuth,
|
|
5324
|
+
detectionState: ctx.detectionState,
|
|
5325
|
+
buildId: identity.id
|
|
5326
|
+
});
|
|
5192
5327
|
const res = await client.getRules();
|
|
5193
|
-
if (res.success && res.notModified && prior?.bundle)
|
|
5328
|
+
if (res.success && res.notModified && prior?.bundle) {
|
|
5329
|
+
const confirmed = confirmedBy(res, identity.id);
|
|
5330
|
+
await store.write({
|
|
5331
|
+
bundle: prior.bundle,
|
|
5332
|
+
etag: res.etag ?? prior.etag ?? null,
|
|
5333
|
+
buildId: identity.id,
|
|
5334
|
+
matchedBuildId: confirmed
|
|
5335
|
+
});
|
|
5336
|
+
return fromSource(served(normalizeBundle(prior.bundle, options), confirmed), "cache");
|
|
5337
|
+
}
|
|
5194
5338
|
if (res.success && !res.notModified) {
|
|
5339
|
+
const confirmed = confirmedBy(res, identity.id);
|
|
5195
5340
|
const rejected = liveUpdateRejections(res, options);
|
|
5196
5341
|
if (rejected.length > 0) {
|
|
5197
5342
|
reportRejections(rejected, options, "rule update rejected");
|
|
5198
|
-
if (prior?.bundle) return fromSource(normalizeBundle(prior.bundle, options), "cache", "update rejected");
|
|
5199
|
-
if (options.rules) return fromSource(normalizeBundle(options.rules, options), "bundled", "update rejected");
|
|
5343
|
+
if (prior?.bundle) return fromSource(served(normalizeBundle(prior.bundle, options), cacheConfirmed), "cache", "update rejected");
|
|
5344
|
+
if (options.rules) return fromSource(local(normalizeBundle(options.rules, options)), "bundled", "update rejected");
|
|
5200
5345
|
return fromSource(emptyBundle(), "empty", "update rejected");
|
|
5201
5346
|
}
|
|
5202
5347
|
const bundle = normalizeBundle(res, options);
|
|
5203
|
-
await store.write({ bundle, etag: res.etag ?? null });
|
|
5204
|
-
return fromSource(bundle, "api");
|
|
5348
|
+
await store.write({ bundle, etag: res.etag ?? null, buildId: identity.id, matchedBuildId: confirmed });
|
|
5349
|
+
return fromSource(served(bundle, confirmed), "api");
|
|
5205
5350
|
}
|
|
5206
5351
|
if (prior?.bundle) {
|
|
5207
5352
|
notify(options.onError, new Error(`pulse rule fetch failed (${res.error ?? "no usable response"}); using cached bundle`), "onError");
|
|
5208
|
-
return fromSource(normalizeBundle(prior.bundle, options), "cache", res.error ?? "no usable response");
|
|
5353
|
+
return fromSource(served(normalizeBundle(prior.bundle, options), cacheConfirmed), "cache", res.error ?? "no usable response");
|
|
5209
5354
|
}
|
|
5210
5355
|
if (options.rules) {
|
|
5211
5356
|
notify(options.onError, new Error(`pulse rule fetch failed (${res.error ?? "no usable response"}); using bundled fallback`), "onError");
|
|
5212
|
-
return fromSource(normalizeBundle(options.rules, options), "bundled", res.error ?? "no usable response");
|
|
5357
|
+
return fromSource(local(normalizeBundle(options.rules, options)), "bundled", res.error ?? "no usable response");
|
|
5213
5358
|
}
|
|
5214
5359
|
notify(options.onError, new Error(`pulse rule fetch failed (${res.error ?? "no usable response"}); no cache \u2014 running with no rules`), "onError");
|
|
5215
5360
|
return fromSource(emptyBundle(), "empty", res.error ?? "no usable response");
|
|
5216
5361
|
}
|
|
5217
5362
|
if (options.token) {
|
|
5363
|
+
const identity = buildIdentity(options);
|
|
5364
|
+
const remote = (bundle) => withScopedDryRun(
|
|
5365
|
+
bundle,
|
|
5366
|
+
null,
|
|
5367
|
+
"the token-authenticated rules service did not corroborate this map",
|
|
5368
|
+
options
|
|
5369
|
+
);
|
|
5370
|
+
const local = (bundle) => options.trustLocalRuleScope === true ? bundle : withScopedDryRun(bundle, identity.id, identity.reason ?? "these rules name a different map", options);
|
|
5218
5371
|
const prior = await store.read();
|
|
5219
5372
|
const client = new PatchstackRuleClient({ token: options.token, baseUrl: options.baseUrl, etag: prior?.etag, timeoutMs });
|
|
5220
5373
|
const res = await client.getRules();
|
|
5221
|
-
if (res.success && res.notModified && prior?.bundle)
|
|
5374
|
+
if (res.success && res.notModified && prior?.bundle) {
|
|
5375
|
+
return fromSource(remote(normalizeBundle(prior.bundle, options)), "cache");
|
|
5376
|
+
}
|
|
5222
5377
|
if (res.success && !res.notModified) {
|
|
5223
5378
|
const rejected = liveUpdateRejections(res, options);
|
|
5224
5379
|
if (rejected.length > 0) {
|
|
5225
5380
|
reportRejections(rejected, options, "rule update rejected");
|
|
5226
|
-
if (prior?.bundle) return fromSource(normalizeBundle(prior.bundle, options), "cache", "update rejected");
|
|
5227
|
-
if (options.rules) return fromSource(normalizeBundle(options.rules, options), "bundled", "update rejected");
|
|
5381
|
+
if (prior?.bundle) return fromSource(remote(normalizeBundle(prior.bundle, options)), "cache", "update rejected");
|
|
5382
|
+
if (options.rules) return fromSource(local(normalizeBundle(options.rules, options)), "bundled", "update rejected");
|
|
5228
5383
|
return fromSource(emptyBundle(), "empty", "update rejected");
|
|
5229
5384
|
}
|
|
5230
5385
|
const bundle = normalizeBundle(res, options);
|
|
5231
5386
|
await store.write({ bundle, etag: res.etag ?? null });
|
|
5232
|
-
return fromSource(bundle, "api");
|
|
5387
|
+
return fromSource(remote(bundle), "api");
|
|
5233
5388
|
}
|
|
5234
5389
|
if (prior?.bundle) {
|
|
5235
5390
|
notify(options.onError, new Error(`rule fetch failed (${res.error ?? "no usable response"}); using cached bundle`), "onError");
|
|
5236
|
-
return fromSource(normalizeBundle(prior.bundle, options), "cache", res.error ?? "no usable response");
|
|
5391
|
+
return fromSource(remote(normalizeBundle(prior.bundle, options)), "cache", res.error ?? "no usable response");
|
|
5237
5392
|
}
|
|
5238
5393
|
notify(options.onError, new Error(`rule fetch failed (${res.error ?? "no usable response"}); no cache \u2014 running with no rules`), "onError");
|
|
5239
5394
|
return fromSource(emptyBundle(), "empty", res.error ?? "no usable response");
|
|
5240
5395
|
}
|
|
5241
5396
|
if (options.rules) {
|
|
5242
|
-
|
|
5397
|
+
const identity = buildIdentity(options);
|
|
5398
|
+
const bundle = normalizeBundle(options.rules, options);
|
|
5399
|
+
if (options.trustLocalRuleScope === true) return fromSource(bundle, "bundled");
|
|
5400
|
+
return fromSource(
|
|
5401
|
+
withScopedDryRun(bundle, identity.id, identity.reason ?? "these rules name a different map", options),
|
|
5402
|
+
"bundled"
|
|
5403
|
+
);
|
|
5243
5404
|
}
|
|
5244
5405
|
return fromSource(emptyBundle(), "empty");
|
|
5245
5406
|
}
|