@lmzhen/dsh-evolution-core 0.3.64 → 0.3.65
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/lib/index.js +100 -45
- package/lib/types/skill-store.d.ts +3 -1
- package/lib/types/threats.d.ts +16 -4
- package/lib/types/usage.d.ts +7 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -393,7 +393,7 @@ function nodeEvolutionIo(lockAttempts = 40) {
|
|
|
393
393
|
} catch {}
|
|
394
394
|
continue;
|
|
395
395
|
}
|
|
396
|
-
if (
|
|
396
|
+
if (/\.corrupt(\.\d+)?$/.test(name)) {
|
|
397
397
|
const corruptPath = join(dir, name);
|
|
398
398
|
try {
|
|
399
399
|
const st = await stat(corruptPath);
|
|
@@ -576,29 +576,30 @@ function parseUsage(raw) {
|
|
|
576
576
|
async function loadUsage(root, io = nodeEvolutionIo()) {
|
|
577
577
|
return parseUsage(await io.readText(usageFile(root)));
|
|
578
578
|
}
|
|
579
|
-
|
|
580
|
-
* Atomic read-modify-write on the usage sidecar (rc.50 P2-2): `task` receives
|
|
581
|
-
* the map parsed from the current on-disk state and may mutate it; the result
|
|
582
|
-
* is persisted inside the same transact so a second process sharing DSH_HOME
|
|
583
|
-
* cannot interleave its RMW and lose a counter update. Callers keep their own
|
|
584
|
-
* single-process serialize chain as the second layer.
|
|
585
|
-
*/
|
|
586
|
-
async function mutateUsage(root, io, task) {
|
|
579
|
+
async function mutateUsage(root, io, task, options = {}) {
|
|
587
580
|
await transactIo(io, usageFile(root), async (current) => {
|
|
588
581
|
let shapePreserved = false;
|
|
582
|
+
let recovered = null;
|
|
589
583
|
if (current !== null) try {
|
|
590
584
|
const probe = JSON.parse(current);
|
|
591
585
|
if (probe === null || Array.isArray(probe) || typeof probe !== "object") shapePreserved = true;
|
|
592
586
|
else {
|
|
593
587
|
const record = probe;
|
|
594
|
-
|
|
588
|
+
const isMalformed = (value) => value === null || typeof value !== "object" || Array.isArray(value);
|
|
595
589
|
if (typeof record.version === "number" && record.version > 1) shapePreserved = true;
|
|
590
|
+
else if (Object.values(record).some(isMalformed)) {
|
|
591
|
+
const bad = Object.keys(record).filter((key) => isMalformed(record[key]));
|
|
592
|
+
const corruptPath = `${usageFile(root)}.corrupt`;
|
|
593
|
+
await io.writeText(corruptPath, current).catch(() => {});
|
|
594
|
+
recovered = JSON.stringify(Object.fromEntries(Object.entries(record).filter(([, value]) => !isMalformed(value))));
|
|
595
|
+
options.onQuarantine?.(`usage sidecar ${usageFile(root)} carried ${bad.length} malformed entr${bad.length === 1 ? "y" : "ies"} (${bad.slice(0, 5).join(", ")}); the original bytes were copied to ${corruptPath} and the remaining entries continue to be served`);
|
|
596
|
+
}
|
|
596
597
|
}
|
|
597
598
|
} catch {
|
|
598
599
|
return current;
|
|
599
600
|
}
|
|
600
601
|
if (shapePreserved) return current;
|
|
601
|
-
const map = parseUsage(current);
|
|
602
|
+
const map = parseUsage(recovered ?? current);
|
|
602
603
|
await task(map);
|
|
603
604
|
return JSON.stringify(Object.fromEntries(map.entries()), null, 2);
|
|
604
605
|
});
|
|
@@ -1976,9 +1977,16 @@ function clampedNumber(value, fallback, opts) {
|
|
|
1976
1977
|
* Threat scanning for agent-authored memory and skill content.
|
|
1977
1978
|
*
|
|
1978
1979
|
* Ported as a small, dependency-free subset of Hermes Agent's
|
|
1979
|
-
* `tools/threat_patterns.py` + hermes-claw `threats.ts`.
|
|
1980
|
-
*
|
|
1981
|
-
*
|
|
1980
|
+
* `tools/threat_patterns.py` + hermes-claw `threats.ts`.
|
|
1981
|
+
*
|
|
1982
|
+
* Policy (P1-1, v19): a finding either BLOCKS the write or only REPORTS.
|
|
1983
|
+
* Blocking is reserved for shapes with no legitimate use in stored knowledge
|
|
1984
|
+
* (prompt-injection phrasing, credential exfiltration, the invisible-character
|
|
1985
|
+
* smuggling core). Typography and presentation characters that are legitimate
|
|
1986
|
+
* in ordinary prose — variation selectors (❤️), zero-width non-joiner, soft
|
|
1987
|
+
* hyphen from PDF paste, Arabic letter mark, Mongolian vowel separator — are
|
|
1988
|
+
* REPORT findings: they stay visible to operators and tests but never reject a
|
|
1989
|
+
* write. Blocking them turned every emoji into a security event.
|
|
1982
1990
|
*/
|
|
1983
1991
|
const FILLER = String.raw`(?:\w+\s+){0,8}`;
|
|
1984
1992
|
const PATTERNS = [
|
|
@@ -2145,7 +2153,9 @@ const PATTERNS = [
|
|
|
2145
2153
|
regex: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/
|
|
2146
2154
|
}
|
|
2147
2155
|
];
|
|
2148
|
-
const ZERO_WIDTH_CHARS = new RegExp(`[\\
|
|
2156
|
+
const ZERO_WIDTH_CHARS = new RegExp(`[\\u034f\\u200b\\u2060\\u2061\\u2062\\u2063\\u2064\\u206a-\\u206f\\ufeff\\u{e0000}-\\u{e007f}]`, "u");
|
|
2157
|
+
const ZWJ_OUTSIDE_EMOJI = /(?<!\p{Extended_Pictographic})\u200d(?!\p{Extended_Pictographic})/u;
|
|
2158
|
+
const TYPOGRAPHY_CHARS = /[\u00ad\u061c\u180e\u200c\ufe00-\ufe0f]/;
|
|
2149
2159
|
const BIDI_CHARS = /[\u202a-\u202e\u2066-\u2069]/;
|
|
2150
2160
|
const SCOPE_ORDER = {
|
|
2151
2161
|
all: 1,
|
|
@@ -2172,11 +2182,17 @@ function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_
|
|
|
2172
2182
|
const windowSize = clampedNumber(maxScanChars, 65536, { min: 4097 });
|
|
2173
2183
|
const findings = [];
|
|
2174
2184
|
const excluded = new Set(options.excludeLabels ?? []);
|
|
2175
|
-
if (ZERO_WIDTH_CHARS.test(text) && !excluded.has("unicode_zero_width")) findings.push({
|
|
2185
|
+
if ((ZERO_WIDTH_CHARS.test(text) || ZWJ_OUTSIDE_EMOJI.test(text)) && !excluded.has("unicode_zero_width")) findings.push({
|
|
2176
2186
|
label: "unicode_zero_width",
|
|
2177
2187
|
category: "unicode_obfuscation",
|
|
2178
2188
|
scope: "all"
|
|
2179
2189
|
});
|
|
2190
|
+
if (TYPOGRAPHY_CHARS.test(text) && !excluded.has("unicode_typography")) findings.push({
|
|
2191
|
+
label: "unicode_typography",
|
|
2192
|
+
category: "unicode_obfuscation",
|
|
2193
|
+
scope: "all",
|
|
2194
|
+
severity: "report"
|
|
2195
|
+
});
|
|
2180
2196
|
if (BIDI_CHARS.test(text) && !excluded.has("unicode_bidi_override")) findings.push({
|
|
2181
2197
|
label: "unicode_bidi_override",
|
|
2182
2198
|
category: "unicode_obfuscation",
|
|
@@ -2205,11 +2221,13 @@ function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_
|
|
|
2205
2221
|
}
|
|
2206
2222
|
return findings;
|
|
2207
2223
|
}
|
|
2208
|
-
/** Blocking policy:
|
|
2224
|
+
/** Blocking policy (P1-1, v19): a finding blocks unless it is explicitly
|
|
2225
|
+
* `report`-only. Pattern findings carry no severity and therefore block as
|
|
2226
|
+
* before. */
|
|
2209
2227
|
function evaluateThreat(text, scope = "strict", maxScanChars = 65536, options = NO_SCAN_OPTIONS) {
|
|
2210
2228
|
const findings = scanThreats(text, scope, maxScanChars, options);
|
|
2211
2229
|
return {
|
|
2212
|
-
blocked: findings.
|
|
2230
|
+
blocked: findings.some((finding) => finding.severity !== "report"),
|
|
2213
2231
|
findings
|
|
2214
2232
|
};
|
|
2215
2233
|
}
|
|
@@ -3140,7 +3158,7 @@ const SECRET_PATTERNS = [
|
|
|
3140
3158
|
["jwt", /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g],
|
|
3141
3159
|
["bearer credential", /Bearer[\s]+[a-z0-9._~+/=\-]{16,}/gi]
|
|
3142
3160
|
];
|
|
3143
|
-
const INLINE_ASSIGNMENT_PATTERN = /* @__PURE__ */ new RegExp("(^|[^\\w-])([\\w-]{0,64}[_\\-])?((?:token|api[_-]?key|secret|password|passwd)(?:[_\\-][\\w-]{0,64})?)\\b([\\
|
|
3161
|
+
const INLINE_ASSIGNMENT_PATTERN = /* @__PURE__ */ new RegExp("(^|[^\\w-])([\\w-]{0,64}[_\\-])?((?:token|api[_-]?key|secret|password|passwd)(?:[_\\-][\\w-]{0,64})?)\\b([\\t ]*[:=][\\t ]*)([^\\r\\n]+)", "gi");
|
|
3144
3162
|
/**
|
|
3145
3163
|
* Mask credential-shaped text before it crosses a session boundary.
|
|
3146
3164
|
* @param text - the text about to be sent to a model outside this session.
|
|
@@ -3836,6 +3854,13 @@ function authoringFeedback(frontmatter) {
|
|
|
3836
3854
|
lines
|
|
3837
3855
|
};
|
|
3838
3856
|
}
|
|
3857
|
+
/** A1-15 (v18) / P2-2 (v19): the io layer marks an error `committed: true` when
|
|
3858
|
+
* the rename landed and only the directory fsync failed. Every single-file
|
|
3859
|
+
* writer must treat that as "written, durability unconfirmed" — never as a
|
|
3860
|
+
* plain failure (which a caller would retry, or a two-phase caller roll back). */
|
|
3861
|
+
function isCommittedOnly(error) {
|
|
3862
|
+
return error?.committed === true;
|
|
3863
|
+
}
|
|
3839
3864
|
async function listNames(root, io) {
|
|
3840
3865
|
const entries = await io.list(root);
|
|
3841
3866
|
const names = [];
|
|
@@ -4392,6 +4417,7 @@ var SkillLibrary = class {
|
|
|
4392
4417
|
ok: false,
|
|
4393
4418
|
message: "Only the foreground (user or the main agent) may pin or unpin skills."
|
|
4394
4419
|
};
|
|
4420
|
+
let durabilityWarning = "";
|
|
4395
4421
|
const dir = this.dirOf(normalized);
|
|
4396
4422
|
const marker = markerPath(dir, "pinned");
|
|
4397
4423
|
const existing = await this.io.exists(marker);
|
|
@@ -4409,12 +4435,17 @@ var SkillLibrary = class {
|
|
|
4409
4435
|
ok: false,
|
|
4410
4436
|
message: `Skill "${normalized}" not found.`
|
|
4411
4437
|
};
|
|
4412
|
-
if (pinned)
|
|
4438
|
+
if (pinned) try {
|
|
4439
|
+
await this.io.writeText(marker, "");
|
|
4440
|
+
} catch (error) {
|
|
4441
|
+
if (!isCommittedOnly(error)) throw error;
|
|
4442
|
+
durabilityWarning = error instanceof Error ? error.message : String(error);
|
|
4443
|
+
}
|
|
4413
4444
|
else await this.io.remove(marker);
|
|
4414
4445
|
await this.audit(normalized, pinned ? "pin" : "unpin", null, null, pinned ? "pinned" : "unpinned");
|
|
4415
4446
|
return {
|
|
4416
4447
|
ok: true,
|
|
4417
|
-
message: pinned ? `Skill "${normalized}" pinned: protected from deletion, background review, and the lifecycle.` : `Skill "${normalized}" unpinned
|
|
4448
|
+
message: `${pinned ? `Skill "${normalized}" pinned: protected from deletion, background review, and the lifecycle.` : `Skill "${normalized}" unpinned.`}${durabilityWarning === "" ? "" : ` (warning: the write landed but the directory fsync failed — durability unconfirmed: ${durabilityWarning})`}`,
|
|
4418
4449
|
path: dir
|
|
4419
4450
|
};
|
|
4420
4451
|
}
|
|
@@ -4465,20 +4496,31 @@ var SkillLibrary = class {
|
|
|
4465
4496
|
const createPath = join(dir, "SKILL.md");
|
|
4466
4497
|
let existsAtCommit = false;
|
|
4467
4498
|
let taskRan = false;
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4499
|
+
let createDurabilityWarning = "";
|
|
4500
|
+
if (this.transact) try {
|
|
4501
|
+
await this.transact(this.io, createPath, (current) => {
|
|
4502
|
+
taskRan = true;
|
|
4503
|
+
if (current !== null) {
|
|
4504
|
+
existsAtCommit = true;
|
|
4505
|
+
return current;
|
|
4506
|
+
}
|
|
4507
|
+
return onDisk;
|
|
4508
|
+
});
|
|
4509
|
+
} catch (error) {
|
|
4510
|
+
if (!isCommittedOnly(error)) throw error;
|
|
4511
|
+
createDurabilityWarning = error instanceof Error ? error.message : String(error);
|
|
4512
|
+
}
|
|
4476
4513
|
else if (await this.io.exists(createPath)) {
|
|
4477
4514
|
taskRan = true;
|
|
4478
4515
|
existsAtCommit = true;
|
|
4479
4516
|
} else {
|
|
4480
4517
|
taskRan = true;
|
|
4481
|
-
|
|
4518
|
+
try {
|
|
4519
|
+
await this.io.writeText(createPath, onDisk);
|
|
4520
|
+
} catch (error) {
|
|
4521
|
+
if (!isCommittedOnly(error)) throw error;
|
|
4522
|
+
createDurabilityWarning = error instanceof Error ? error.message : String(error);
|
|
4523
|
+
}
|
|
4482
4524
|
}
|
|
4483
4525
|
if (!taskRan) return {
|
|
4484
4526
|
ok: false,
|
|
@@ -4497,7 +4539,7 @@ var SkillLibrary = class {
|
|
|
4497
4539
|
});
|
|
4498
4540
|
return {
|
|
4499
4541
|
ok: true,
|
|
4500
|
-
message: `Skill "${normalized}" created
|
|
4542
|
+
message: `Skill "${normalized}" created.${createDurabilityWarning === "" ? "" : ` (warning: the write landed but the directory fsync failed — durability unconfirmed: ${createDurabilityWarning})`}`,
|
|
4501
4543
|
path: dir,
|
|
4502
4544
|
...norm.changed ? { normalizedFrontmatterFields: norm.fields } : {}
|
|
4503
4545
|
};
|
|
@@ -4848,15 +4890,23 @@ var SkillLibrary = class {
|
|
|
4848
4890
|
}
|
|
4849
4891
|
/** A1-4 (v18): a manifest-declared name is copied with `join(root, name)`;
|
|
4850
4892
|
* only a single, non-traversing path component is safe. Dotfiles
|
|
4851
|
-
* (`.usage.json`) stay allowed — sidecars are legitimately dot-prefixed.
|
|
4893
|
+
* (`.usage.json`) stay allowed — sidecars are legitimately dot-prefixed.
|
|
4894
|
+
* P2-4 (v19): a non-string entry (`skills: [123]`) is refused structurally
|
|
4895
|
+
* instead of throwing `name.includes is not a function`. */
|
|
4852
4896
|
safeSnapshotEntryName(name) {
|
|
4897
|
+
if (typeof name !== "string") return false;
|
|
4853
4898
|
return name !== "" && name !== "." && name !== ".." && !name.includes("/") && !name.includes("\\") && basename(name) === name;
|
|
4854
4899
|
}
|
|
4855
4900
|
/** A1-7 (v18): a root-level lock whose holder is alive must refuse the
|
|
4856
4901
|
* restore; a dead residue is swept so a crashed writer cannot block
|
|
4857
4902
|
* recovery. A non-lock body shape is left alone (user file). */
|
|
4858
4903
|
async refuseLiveLockOrSweep(lockPath, label) {
|
|
4859
|
-
|
|
4904
|
+
let body;
|
|
4905
|
+
try {
|
|
4906
|
+
body = await this.io.readText(lockPath);
|
|
4907
|
+
} catch (error) {
|
|
4908
|
+
throw new Error(`snapshot restore refused: cannot verify ${label} (${error instanceof Error ? error.message : String(error)}); a live writer may hold it`);
|
|
4909
|
+
}
|
|
4860
4910
|
if (body === null) return;
|
|
4861
4911
|
const match = /^(\d+):[0-9a-f]*$/.exec(body.trim());
|
|
4862
4912
|
if (match === null) return;
|
|
@@ -5660,13 +5710,19 @@ var SkillLibrary = class {
|
|
|
5660
5710
|
*/
|
|
5661
5711
|
async restoreSnapshotIntoRoot(snapshotPath) {
|
|
5662
5712
|
const manifest = await this.readSnapshotManifest(snapshotPath);
|
|
5663
|
-
if (manifest === null) {
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5713
|
+
if (manifest === null) throw new Error(await this.io.exists(join(snapshotPath, "manifest.json")) ? `snapshot ${snapshotPath} has an unreadable manifest.json; refusing to clear the active tree` : `snapshot ${snapshotPath} has no readable manifest.json; refusing to restore`);
|
|
5714
|
+
for (const name of [...manifest.skills, ...manifest.sidecars]) if (!this.safeSnapshotEntryName(name)) throw new Error(`snapshot ${snapshotPath} declares an unsafe entry name ${JSON.stringify(name)}; refusing to restore`);
|
|
5715
|
+
if (manifest.skills.length === 0) {
|
|
5716
|
+
const snapshotEntries = await this.io.list(snapshotPath);
|
|
5717
|
+
const declared = new Set([
|
|
5718
|
+
"manifest.json",
|
|
5719
|
+
"extras",
|
|
5720
|
+
".archive",
|
|
5721
|
+
...manifest.skills,
|
|
5722
|
+
...manifest.sidecars
|
|
5723
|
+
]);
|
|
5724
|
+
const undeclared = snapshotEntries.filter((entry) => !declared.has(entry));
|
|
5725
|
+
if (undeclared.length > 0) throw new Error(`snapshot ${snapshotPath} declares no skills but contains undeclared entries (${undeclared.join(", ")}); refusing to clear the active tree`);
|
|
5670
5726
|
}
|
|
5671
5727
|
let rootEntries;
|
|
5672
5728
|
try {
|
|
@@ -5686,17 +5742,16 @@ var SkillLibrary = class {
|
|
|
5686
5742
|
if (await this.hasWriteLock(dir)) throw new Error(`snapshot restore refused: skill "${entry}" is being written (write lock present); retry once the write completes`);
|
|
5687
5743
|
}
|
|
5688
5744
|
}
|
|
5689
|
-
const restoresSuppressed = manifest
|
|
5745
|
+
const restoresSuppressed = manifest.sidecars.includes(".curator-suppressed.json");
|
|
5690
5746
|
for (const entry of rootEntries) {
|
|
5691
5747
|
if (entry === ".archive" || entry === ".backups" || entry === ".mutations.json") continue;
|
|
5692
5748
|
if (entry === ".curator-suppressed.json" && restoresSuppressed) continue;
|
|
5693
5749
|
if (entry.endsWith(".lock") || entry.endsWith(`.lock.next`)) continue;
|
|
5694
5750
|
await this.io.remove(join(this.root, entry));
|
|
5695
5751
|
}
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
for (const sidecar of manifest.sidecars) await this.io.copy(join(snapshotPath, sidecar), join(this.root, sidecar));
|
|
5752
|
+
for (const name of manifest.skills) await this.io.copy(join(snapshotPath, name), join(this.root, name));
|
|
5753
|
+
for (const sidecar of manifest.sidecars) await this.io.copy(join(snapshotPath, sidecar), join(this.root, sidecar));
|
|
5754
|
+
{
|
|
5700
5755
|
const archiveRoot = join(this.root, ".archive");
|
|
5701
5756
|
if (manifest.hasArchive === true) {
|
|
5702
5757
|
await this.io.remove(archiveRoot);
|
|
@@ -412,7 +412,9 @@ export declare class SkillLibrary {
|
|
|
412
412
|
private sweepLockIfStranded;
|
|
413
413
|
/** A1-4 (v18): a manifest-declared name is copied with `join(root, name)`;
|
|
414
414
|
* only a single, non-traversing path component is safe. Dotfiles
|
|
415
|
-
* (`.usage.json`) stay allowed — sidecars are legitimately dot-prefixed.
|
|
415
|
+
* (`.usage.json`) stay allowed — sidecars are legitimately dot-prefixed.
|
|
416
|
+
* P2-4 (v19): a non-string entry (`skills: [123]`) is refused structurally
|
|
417
|
+
* instead of throwing `name.includes is not a function`. */
|
|
416
418
|
private safeSnapshotEntryName;
|
|
417
419
|
/** A1-7 (v18): a root-level lock whose holder is alive must refuse the
|
|
418
420
|
* restore; a dead residue is swept so a crashed writer cannot block
|
package/lib/types/threats.d.ts
CHANGED
|
@@ -2,15 +2,25 @@
|
|
|
2
2
|
* Threat scanning for agent-authored memory and skill content.
|
|
3
3
|
*
|
|
4
4
|
* Ported as a small, dependency-free subset of Hermes Agent's
|
|
5
|
-
* `tools/threat_patterns.py` + hermes-claw `threats.ts`.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* `tools/threat_patterns.py` + hermes-claw `threats.ts`.
|
|
6
|
+
*
|
|
7
|
+
* Policy (P1-1, v19): a finding either BLOCKS the write or only REPORTS.
|
|
8
|
+
* Blocking is reserved for shapes with no legitimate use in stored knowledge
|
|
9
|
+
* (prompt-injection phrasing, credential exfiltration, the invisible-character
|
|
10
|
+
* smuggling core). Typography and presentation characters that are legitimate
|
|
11
|
+
* in ordinary prose — variation selectors (❤️), zero-width non-joiner, soft
|
|
12
|
+
* hyphen from PDF paste, Arabic letter mark, Mongolian vowel separator — are
|
|
13
|
+
* REPORT findings: they stay visible to operators and tests but never reject a
|
|
14
|
+
* write. Blocking them turned every emoji into a security event.
|
|
8
15
|
*/
|
|
9
16
|
export type ThreatScope = 'all' | 'context' | 'strict';
|
|
10
17
|
export interface ThreatFinding {
|
|
11
18
|
label: string;
|
|
12
19
|
category: string;
|
|
13
20
|
scope: ThreatScope;
|
|
21
|
+
/** P1-1 (v19): `block` (default) refuses the write; `report` is an audit
|
|
22
|
+
* trail entry only. Absent means `block`. */
|
|
23
|
+
severity?: 'block' | 'report';
|
|
14
24
|
}
|
|
15
25
|
/**
|
|
16
26
|
* Optional scan controls. Default behavior (`options` omitted) is unchanged:
|
|
@@ -39,7 +49,9 @@ export declare const PATTERN_OVERLAP = 4096;
|
|
|
39
49
|
* characters (skill files may run to 100,000) is no longer a blind zone.
|
|
40
50
|
*/
|
|
41
51
|
export declare function scanThreats(text: string, scope?: ThreatScope, maxScanChars?: number, options?: ScanOptions): ThreatFinding[];
|
|
42
|
-
/** Blocking policy:
|
|
52
|
+
/** Blocking policy (P1-1, v19): a finding blocks unless it is explicitly
|
|
53
|
+
* `report`-only. Pattern findings carry no severity and therefore block as
|
|
54
|
+
* before. */
|
|
43
55
|
export declare function evaluateThreat(text: string, scope?: ThreatScope, maxScanChars?: number, options?: ScanOptions): {
|
|
44
56
|
blocked: boolean;
|
|
45
57
|
findings: ThreatFinding[];
|
package/lib/types/usage.d.ts
CHANGED
|
@@ -52,7 +52,13 @@ export declare function loadUsage(root: string, io?: EvolutionIoLike): Promise<U
|
|
|
52
52
|
* cannot interleave its RMW and lose a counter update. Callers keep their own
|
|
53
53
|
* single-process serialize chain as the second layer.
|
|
54
54
|
*/
|
|
55
|
-
export
|
|
55
|
+
export interface UsageMutateOptions {
|
|
56
|
+
/** P2-9 (v19): called when malformed entries had to be quarantined before the
|
|
57
|
+
* task could run. The guard preserves bytes AND keeps the facility working;
|
|
58
|
+
* this callback is how that stays observable. */
|
|
59
|
+
onQuarantine?: ((message: string) => void) | undefined;
|
|
60
|
+
}
|
|
61
|
+
export declare function mutateUsage(root: string, io: EvolutionIoLike, task: (map: UsageMap) => void | Promise<void>, options?: UsageMutateOptions): Promise<void>;
|
|
56
62
|
/**
|
|
57
63
|
* Curator-owned usage fields (rc.67 K-2): the curator writes ONLY this set —
|
|
58
64
|
* lifecycle state, archive stamp, the six-factor quality pair, and the
|
package/package.json
CHANGED