@remnic/core 9.3.725 → 9.3.726
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/access-boundary.js +26 -25
- package/dist/access-cli.js +64 -62
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.js +33 -32
- package/dist/access-mcp.js +30 -29
- package/dist/access-operations-batch.js +27 -26
- package/dist/access-operations.js +29 -28
- package/dist/access-schema.js +3 -3
- package/dist/access-service.js +25 -24
- package/dist/active-recall.js +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/briefing.js +6 -5
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-GWVG7LGC.js} +2 -2
- package/dist/causal-consolidation.js +7 -6
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-C6BWLYW5.js → chunk-2NBP25BG.js} +7 -7
- package/dist/{chunk-YUYPWJD3.js → chunk-3MS3QVX6.js} +2 -2
- package/dist/{chunk-FVZ2ISIX.js → chunk-3TUT4PWB.js} +2 -2
- package/dist/{chunk-HCVWF657.js → chunk-53EEQYE5.js} +7 -7
- package/dist/{chunk-TAPSNIMA.js → chunk-5CEJH5ZN.js} +4 -4
- package/dist/{chunk-D7EBTQ24.js → chunk-5NWYQUQC.js} +2 -2
- package/dist/{chunk-NT36GU6X.js → chunk-5OOA7SGX.js} +2 -2
- package/dist/{chunk-KQAFEZQX.js → chunk-5W7CTZ2E.js} +4 -4
- package/dist/{chunk-QR67FU5G.js → chunk-6AFVIOVZ.js} +2 -2
- package/dist/{chunk-UBHVAGO7.js → chunk-6WAJQKBS.js} +2 -2
- package/dist/{chunk-6CWGS2LY.js → chunk-7NS7AWMZ.js} +3 -3
- package/dist/{chunk-T55EGZSJ.js → chunk-BB57OZWC.js} +2 -2
- package/dist/{chunk-YQMPUYHE.js → chunk-BBIE7W52.js} +2 -2
- package/dist/{chunk-WAFDHKES.js → chunk-BEUCACTV.js} +2 -2
- package/dist/{chunk-4SF232E6.js → chunk-BIA7ELIB.js} +17 -5
- package/dist/chunk-BIA7ELIB.js.map +1 -0
- package/dist/{chunk-GZQXOQQ2.js → chunk-DCKNMLEQ.js} +4 -4
- package/dist/{chunk-ZUDM75KG.js → chunk-DCWIQFNA.js} +4 -4
- package/dist/{chunk-VI5SGGXB.js → chunk-DQXPJ6HD.js} +4 -4
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-Z3FGC3QE.js → chunk-EZNDNMRH.js} +139 -30
- package/dist/chunk-EZNDNMRH.js.map +1 -0
- package/dist/{chunk-FACTVMXQ.js → chunk-FO3HO52S.js} +2 -2
- package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
- package/dist/{chunk-JJJCVCCQ.js → chunk-JPUT5NGH.js} +2 -2
- package/dist/{chunk-5OPYYPJN.js → chunk-K4ZNARDO.js} +2 -2
- package/dist/{chunk-4WZ3J7M6.js → chunk-KJR66XOM.js} +2 -2
- package/dist/{chunk-LD53WPMU.js → chunk-LQ6JI4VH.js} +4 -4
- package/dist/chunk-LTHVM4XN.js +109 -0
- package/dist/chunk-LTHVM4XN.js.map +1 -0
- package/dist/{chunk-4R3CGCI5.js → chunk-MQVKHFIU.js} +16 -13
- package/dist/chunk-MQVKHFIU.js.map +1 -0
- package/dist/{chunk-FXCIOLRB.js → chunk-NPL7VDM6.js} +2 -2
- package/dist/{chunk-KLFYKC3K.js → chunk-OAHOL3ZC.js} +112 -72
- package/dist/chunk-OAHOL3ZC.js.map +1 -0
- package/dist/{chunk-2NLLXCJG.js → chunk-OWHERGF2.js} +2 -2
- package/dist/{chunk-I2HL5M6T.js → chunk-PLG6JHQS.js} +42 -42
- package/dist/{chunk-KGZF2VO3.js → chunk-Q6D75LVH.js} +15 -15
- package/dist/{chunk-SC2YAWRF.js → chunk-RRHHQYJS.js} +2 -2
- package/dist/{chunk-J334N6QV.js → chunk-TA3Y6GEX.js} +2 -2
- package/dist/{chunk-FN6QP7JQ.js → chunk-TQQH2FTX.js} +4 -4
- package/dist/{chunk-DF3I6JGP.js → chunk-UKTDXTWX.js} +15 -15
- package/dist/{chunk-ARV3AUOM.js → chunk-UNNDFYUX.js} +5 -5
- package/dist/{chunk-ZK2L72VN.js → chunk-UQSVUEN2.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-UVYI6VIX.js} +1 -1
- package/dist/{chunk-H7HUQIY7.js → chunk-V7CMKMHM.js} +3 -3
- package/dist/chunk-W5H6ZRPT.js +14 -0
- package/dist/chunk-W5H6ZRPT.js.map +1 -0
- package/dist/{chunk-TT5U3UDO.js → chunk-WGTWGDR5.js} +5 -5
- package/dist/{chunk-YQGHW5ML.js → chunk-WRFKZEO6.js} +4 -4
- package/dist/{chunk-WM7DNKBD.js → chunk-YTZ3VGC7.js} +2 -2
- package/dist/cli.js +54 -53
- package/dist/compounding/engine.js +6 -5
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.js +6 -5
- package/dist/connectors/index.js +6 -5
- package/dist/content-hash.d.ts +14 -0
- package/dist/content-hash.js +10 -0
- package/dist/content-hash.js.map +1 -0
- package/dist/conversation-index/backend.js +2 -2
- package/dist/dashboard-runtime.js +2 -2
- package/dist/entity-retrieval.js +6 -5
- package/dist/extraction-redaction-rules.d.ts +33 -0
- package/dist/extraction-redaction-rules.js +14 -0
- package/dist/extraction-redaction-rules.js.map +1 -0
- package/dist/extraction.js +2 -2
- package/dist/{forget-OEE5OELK.js → forget-WYLR4ZFV.js} +2 -2
- package/dist/index.js +102 -100
- package/dist/index.js.map +1 -1
- package/dist/lcm/index.js +3 -3
- package/dist/maintenance/memory-governance.js +6 -5
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +6 -5
- package/dist/maintenance/rebuild-memory-projection.js +7 -6
- package/dist/namespaces/migrate.js +16 -15
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.js +6 -5
- package/dist/operator-toolkit.js +24 -23
- package/dist/orchestration/maintenance.js +8 -7
- package/dist/orchestrator.js +48 -46
- package/dist/resume-bundles.js +2 -2
- package/dist/search/factory.js +8 -8
- package/dist/search/index.js +12 -12
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/semantic-consolidation.js +7 -6
- package/dist/semantic-rule-promotion.js +6 -5
- package/dist/semantic-rule-verifier.js +6 -5
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +6 -5
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/verified-recall.js +6 -5
- package/package.json +2 -2
- package/src/config.test.ts +50 -0
- package/src/config.ts +21 -4
- package/src/content-hash.ts +24 -0
- package/src/correction/correction-access-wiring.ts +104 -19
- package/src/correction/correction-contract.ts +68 -10
- package/src/correction/correction-executor.test.ts +112 -2
- package/src/correction/correction-executor.ts +65 -5
- package/src/correction/correction-planner.test.ts +47 -0
- package/src/correction/correction-planner.ts +40 -1
- package/src/correction/correction-storage-integration.test.ts +183 -0
- package/src/extraction-redaction-rules.test.ts +213 -0
- package/src/extraction-redaction-rules.ts +208 -0
- package/src/orchestrator.ts +52 -1
- package/src/storage.ts +6 -10
- package/dist/chunk-4R3CGCI5.js.map +0 -1
- package/dist/chunk-4SF232E6.js.map +0 -1
- package/dist/chunk-KLFYKC3K.js.map +0 -1
- package/dist/chunk-Z3FGC3QE.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
- /package/dist/{chunk-C6BWLYW5.js.map → chunk-2NBP25BG.js.map} +0 -0
- /package/dist/{chunk-YUYPWJD3.js.map → chunk-3MS3QVX6.js.map} +0 -0
- /package/dist/{chunk-FVZ2ISIX.js.map → chunk-3TUT4PWB.js.map} +0 -0
- /package/dist/{chunk-HCVWF657.js.map → chunk-53EEQYE5.js.map} +0 -0
- /package/dist/{chunk-TAPSNIMA.js.map → chunk-5CEJH5ZN.js.map} +0 -0
- /package/dist/{chunk-D7EBTQ24.js.map → chunk-5NWYQUQC.js.map} +0 -0
- /package/dist/{chunk-NT36GU6X.js.map → chunk-5OOA7SGX.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-5W7CTZ2E.js.map} +0 -0
- /package/dist/{chunk-QR67FU5G.js.map → chunk-6AFVIOVZ.js.map} +0 -0
- /package/dist/{chunk-UBHVAGO7.js.map → chunk-6WAJQKBS.js.map} +0 -0
- /package/dist/{chunk-6CWGS2LY.js.map → chunk-7NS7AWMZ.js.map} +0 -0
- /package/dist/{chunk-T55EGZSJ.js.map → chunk-BB57OZWC.js.map} +0 -0
- /package/dist/{chunk-YQMPUYHE.js.map → chunk-BBIE7W52.js.map} +0 -0
- /package/dist/{chunk-WAFDHKES.js.map → chunk-BEUCACTV.js.map} +0 -0
- /package/dist/{chunk-GZQXOQQ2.js.map → chunk-DCKNMLEQ.js.map} +0 -0
- /package/dist/{chunk-ZUDM75KG.js.map → chunk-DCWIQFNA.js.map} +0 -0
- /package/dist/{chunk-VI5SGGXB.js.map → chunk-DQXPJ6HD.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-FACTVMXQ.js.map → chunk-FO3HO52S.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-JJJCVCCQ.js.map → chunk-JPUT5NGH.js.map} +0 -0
- /package/dist/{chunk-5OPYYPJN.js.map → chunk-K4ZNARDO.js.map} +0 -0
- /package/dist/{chunk-4WZ3J7M6.js.map → chunk-KJR66XOM.js.map} +0 -0
- /package/dist/{chunk-LD53WPMU.js.map → chunk-LQ6JI4VH.js.map} +0 -0
- /package/dist/{chunk-FXCIOLRB.js.map → chunk-NPL7VDM6.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-OWHERGF2.js.map} +0 -0
- /package/dist/{chunk-I2HL5M6T.js.map → chunk-PLG6JHQS.js.map} +0 -0
- /package/dist/{chunk-KGZF2VO3.js.map → chunk-Q6D75LVH.js.map} +0 -0
- /package/dist/{chunk-SC2YAWRF.js.map → chunk-RRHHQYJS.js.map} +0 -0
- /package/dist/{chunk-J334N6QV.js.map → chunk-TA3Y6GEX.js.map} +0 -0
- /package/dist/{chunk-FN6QP7JQ.js.map → chunk-TQQH2FTX.js.map} +0 -0
- /package/dist/{chunk-DF3I6JGP.js.map → chunk-UKTDXTWX.js.map} +0 -0
- /package/dist/{chunk-ARV3AUOM.js.map → chunk-UNNDFYUX.js.map} +0 -0
- /package/dist/{chunk-ZK2L72VN.js.map → chunk-UQSVUEN2.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-UVYI6VIX.js.map} +0 -0
- /package/dist/{chunk-H7HUQIY7.js.map → chunk-V7CMKMHM.js.map} +0 -0
- /package/dist/{chunk-TT5U3UDO.js.map → chunk-WGTWGDR5.js.map} +0 -0
- /package/dist/{chunk-YQGHW5ML.js.map → chunk-WRFKZEO6.js.map} +0 -0
- /package/dist/{chunk-WM7DNKBD.js.map → chunk-YTZ3VGC7.js.map} +0 -0
- /package/dist/{forget-OEE5OELK.js.map → forget-WYLR4ZFV.js.map} +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// src/extraction-redaction-rules.ts
|
|
2
|
+
import { readdir, readFile } from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var REDACTION_RULES_SUBDIR = path.join("state", "corrections", "redaction-rules");
|
|
5
|
+
var MAX_RULE_FILES = 256;
|
|
6
|
+
function isRegexLike(pattern) {
|
|
7
|
+
return pattern.startsWith("/") && pattern.endsWith("/") && pattern.length >= 2;
|
|
8
|
+
}
|
|
9
|
+
function isSafeRegex(source) {
|
|
10
|
+
if (source.length === 0 || source.length > 512) return false;
|
|
11
|
+
if (/(?:^|[^\\])\(\.\*\)|(?:^|[^\\])\.\*|(?:^|[^\\])\.\+|^\.([^*+]?)$/.test(source)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
for (let i = 0; i < source.length; i++) {
|
|
15
|
+
if (source[i] !== "(") continue;
|
|
16
|
+
let depth = 1;
|
|
17
|
+
let j = i + 1;
|
|
18
|
+
while (j < source.length && depth > 0) {
|
|
19
|
+
if (source[j] === "\\") {
|
|
20
|
+
j += 2;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (source[j] === "(") depth++;
|
|
24
|
+
else if (source[j] === ")") depth--;
|
|
25
|
+
j++;
|
|
26
|
+
}
|
|
27
|
+
if (depth !== 0) continue;
|
|
28
|
+
const afterGroup = source[j];
|
|
29
|
+
if (afterGroup !== "+" && afterGroup !== "*" && afterGroup !== "{") continue;
|
|
30
|
+
const groupBody = source.slice(i + 1, j - 1);
|
|
31
|
+
if (/[+*?]$/.test(groupBody) || /\{\d+,?\d*\}[+*?]?$/.test(groupBody)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (groupBody.includes("|")) {
|
|
35
|
+
const branches = groupBody.split("|");
|
|
36
|
+
if (branches.length >= 2) {
|
|
37
|
+
const firstChars = new Set(branches.map((b) => b[0]).filter(Boolean));
|
|
38
|
+
if (firstChars.size < branches.filter((b) => b.length > 0).length) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
i = j;
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
function compileRedactionPattern(pattern) {
|
|
48
|
+
const trimmed = pattern.trim();
|
|
49
|
+
if (!isRegexLike(trimmed)) {
|
|
50
|
+
return { pattern: trimmed, matcher: (content) => content.includes(trimmed) };
|
|
51
|
+
}
|
|
52
|
+
const body = trimmed.slice(1, -1);
|
|
53
|
+
try {
|
|
54
|
+
if (!isSafeRegex(body)) {
|
|
55
|
+
if (body.length === 0) return { pattern: trimmed, matcher: () => false };
|
|
56
|
+
return { pattern: trimmed, matcher: (content) => content.includes(body) };
|
|
57
|
+
}
|
|
58
|
+
const re = new RegExp(body);
|
|
59
|
+
if (re.test("")) {
|
|
60
|
+
return { pattern: trimmed, matcher: (content) => content.includes(body) };
|
|
61
|
+
}
|
|
62
|
+
return { pattern: trimmed, matcher: (content) => re.test(content) };
|
|
63
|
+
} catch {
|
|
64
|
+
return { pattern: trimmed, matcher: (content) => content.includes(body) };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function loadRedactionRules(stateDir) {
|
|
68
|
+
const dir = path.join(stateDir, REDACTION_RULES_SUBDIR);
|
|
69
|
+
let names;
|
|
70
|
+
try {
|
|
71
|
+
names = await readdir(dir);
|
|
72
|
+
} catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const jsonFiles = names.filter((n) => n.endsWith(".json")).sort();
|
|
76
|
+
if (jsonFiles.length > MAX_RULE_FILES) {
|
|
77
|
+
console.warn(
|
|
78
|
+
`extraction-redaction: ${jsonFiles.length} redaction rules in ${dir} exceed the ${MAX_RULE_FILES} cap; ${jsonFiles.length - MAX_RULE_FILES} rules will NOT be enforced. Remove unused rules or raise the cap.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const rules = [];
|
|
82
|
+
for (const name of jsonFiles.slice(0, MAX_RULE_FILES)) {
|
|
83
|
+
try {
|
|
84
|
+
const raw = await readFile(path.join(dir, name), "utf-8");
|
|
85
|
+
const parsed = JSON.parse(raw);
|
|
86
|
+
if (typeof parsed?.pattern !== "string" || parsed.pattern.trim().length === 0) continue;
|
|
87
|
+
rules.push(compileRedactionPattern(parsed.pattern));
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return rules;
|
|
92
|
+
}
|
|
93
|
+
function contentMatchesRedactionRules(content, rules) {
|
|
94
|
+
for (const rule of rules) {
|
|
95
|
+
try {
|
|
96
|
+
if (rule.matcher(content)) return true;
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
REDACTION_RULES_SUBDIR,
|
|
105
|
+
compileRedactionPattern,
|
|
106
|
+
loadRedactionRules,
|
|
107
|
+
contentMatchesRedactionRules
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=chunk-LTHVM4XN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/extraction-redaction-rules.ts"],"sourcesContent":["/**\n * extraction-redaction-rules.ts — consults persisted correction redaction\n * rules during the extraction→persist path so a `never_store` / redaction_rule\n * correction actually blocks future extraction of matching content (issue\n * #1669, #1580 follow-up).\n *\n * The Correction Contract persists `redaction_rule` patterns to\n * `<storage>/state/corrections/redaction-rules/<slug>.json` (one file per\n * rule, idempotent on pattern). This module reads those patterns at the start\n * of each extraction persist pass and exposes a pure matcher the orchestrator\n * consults BEFORE a fact reaches the storage write chokepoint — mirroring how\n * tombstones are consulted at the write chokepoint (#1579), but one stage\n * earlier so the content never even lands as `pending_review`.\n *\n * Scope (issue #1669): the extraction layer consults the rules. Persistence\n * stays owned by the correction module. This helper is the read surface.\n *\n * Safety: patterns are validated at correction-apply time\n * (`validateRedactionPattern` — bounded literal/safe-regex, no catastrophic\n * shapes). We compile defensively here too: a pattern that fails to compile\n * as a regex is treated as a literal substring, and any matcher error is\n * swallowed (fail-open to \"no match\") so a malformed rule can never block the\n * entire extraction pipeline.\n */\nimport { readdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/**\n * Directory (relative to a namespace storage dir) where redaction rules live.\n * Kept in sync with `registerRedactionRuleFn` in correction-access-wiring.ts.\n */\nexport const REDACTION_RULES_SUBDIR = path.join(\"state\", \"corrections\", \"redaction-rules\");\n\n/** Maximum number of rule files read per pass (defense in depth). */\nconst MAX_RULE_FILES = 256;\n\n/**\n * A compiled redaction rule. `pattern` is the original (validated) pattern;\n * `matcher` returns true when content should be withheld.\n */\nexport interface CompiledRedactionRule {\n pattern: string;\n matcher: (content: string) => boolean;\n}\n\n/** Shape persisted by `registerRedactionRuleFn`. */\ninterface RedactionRuleFile {\n pattern: string;\n namespace?: string;\n createdAt?: string;\n}\n\n/**\n * Only `/…/`-wrapped patterns compile as RegExp; everything else is a literal\n * substring (review thread P1). An unwrapped pattern like `abc+def` must match\n * the literal string `abc+def`, NOT the regex `abccccdef`.\n */\nfunction isRegexLike(pattern: string): boolean {\n return pattern.startsWith(\"/\") && pattern.endsWith(\"/\") && pattern.length >= 2;\n}\n\n/**\n * Reject patterns prone to catastrophic backtracking (ReDoS, review thread #3).\n * Mirrors the safe-regex heuristic: a quantifier inside a group that is itself\n * quantified creates exponential blowup on near-miss inputs. Also rejects\n * patterns with overlapping alternation under repetition\n * (e.g. (a|a)*). Returns true when the pattern is safe to compile.\n *\n * This is a second line of defense — validateRedactionPattern runs first at\n * apply time, but a hand-edited rule file or an edge case can still slip past.\n */\nfunction isSafeRegex(source: string): boolean {\n if (source.length === 0 || source.length > 512) return false;\n // Reject overly-broad patterns (.*) that would match every fact and\n // withhold all extraction (cursor Bugbot thread — mirrors\n // isUnsafeRedactionRegex in correction-contract.ts).\n if (/(?:^|[^\\\\])\\(\\.\\*\\)|(?:^|[^\\\\])\\.\\*|(?:^|[^\\\\])\\.\\+|^\\.([^*+]?)$/.test(source)) {\n return false;\n }\n // group body itself ends with a quantifier. This catches (a+)+, (a*)*, etc.\n // Also catches overlapping alternation like (a|a)+.\n for (let i = 0; i < source.length; i++) {\n if (source[i] !== \"(\") continue;\n // Find the matching close paren\n let depth = 1;\n let j = i + 1;\n while (j < source.length && depth > 0) {\n if (source[j] === \"\\\\\") { j += 2; continue; }\n if (source[j] === \"(\") depth++;\n else if (source[j] === \")\") depth--;\n j++;\n }\n if (depth !== 0) continue; // unbalanced — will fail RegExp compile anyway\n // Check if the char after the closing paren is a quantifier\n const afterGroup = source[j];\n if (afterGroup !== \"+\" && afterGroup !== \"*\" && afterGroup !== \"{\") continue;\n const groupBody = source.slice(i + 1, j - 1);\n // The group body ends with a quantifier on a non-escape char\n if (/[+*?]$/.test(groupBody) || /\\{\\d+,?\\d*\\}[+*?]?$/.test(groupBody)) {\n // Nested quantifier → potential ReDoS\n return false;\n }\n // Overlapping alternation: (a|a)+ where branches share a common prefix\n if (groupBody.includes(\"|\")) {\n const branches = groupBody.split(\"|\");\n if (branches.length >= 2) {\n const firstChars = new Set(branches.map((b) => b[0]).filter(Boolean));\n if (firstChars.size < branches.filter((b) => b.length > 0).length) {\n return false;\n }\n }\n }\n i = j;\n }\n return true;\n}\n\n/**\n * Compile a single pattern into a matcher. A literal pattern matches by\n * case-sensitive substring; a `/…/`-wrapped pattern compiles into a RegExp\n * anchored to search (global flag off — we only need a boolean). Compilation\n * failures fall back to literal substring so a bad pattern never throws here.\n */\nexport function compileRedactionPattern(pattern: string): CompiledRedactionRule {\n const trimmed = pattern.trim();\n if (!isRegexLike(trimmed)) {\n return { pattern: trimmed, matcher: (content) => content.includes(trimmed) };\n }\n const body = trimmed.slice(1, -1);\n try {\n if (!isSafeRegex(body)) {\n if (body.length === 0) return { pattern: trimmed, matcher: () => false };\n return { pattern: trimmed, matcher: (content) => content.includes(body) };\n }\n const re = new RegExp(body);\n if (re.test(\"\")) {\n // Zero-width regex matches every string — would withhold all extraction.\n return { pattern: trimmed, matcher: (content) => content.includes(body) };\n }\n return { pattern: trimmed, matcher: (content) => re.test(content) };\n } catch {\n // Malformed regex despite validation (e.g. rule file hand-edited) —\n // treat as a literal on the BODY so the rule still does something useful.\n return { pattern: trimmed, matcher: (content) => content.includes(body) };\n }\n}\n\n/**\n * Load + compile every redaction rule persisted under a namespace's storage\n * dir. Returns an empty array when the directory is absent or unreadable\n * (fail-open — a missing or corrupt rules dir must never block extraction).\n */\nexport async function loadRedactionRules(stateDir: string): Promise<CompiledRedactionRule[]> {\n const dir = path.join(stateDir, REDACTION_RULES_SUBDIR);\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n // ENOENT (cold install, no rules yet) or permission error — no rules.\n return [];\n }\n // Sort deterministically (alphabetical by filename) so the set of rules\n // that survives the MAX_RULE_FILES cap is NOT filesystem-dependent (review\n // thread P2). Without this, readdir order varies by OS/FS and the silently\n // truncated rules are arbitrary — a never-store pattern could be dropped.\n const jsonFiles = names.filter((n) => n.endsWith(\".json\")).sort();\n if (jsonFiles.length > MAX_RULE_FILES) {\n // Visible failure mode: log a warning so the operator knows rules were\n // truncated, rather than silently dropping enforcement.\n console.warn(\n `extraction-redaction: ${jsonFiles.length} redaction rules in ${dir} exceed the ${MAX_RULE_FILES} cap; ` +\n `${jsonFiles.length - MAX_RULE_FILES} rules will NOT be enforced. Remove unused rules or raise the cap.`,\n );\n }\n const rules: CompiledRedactionRule[] = [];\n for (const name of jsonFiles.slice(0, MAX_RULE_FILES)) {\n try {\n const raw = await readFile(path.join(dir, name), \"utf-8\");\n const parsed = JSON.parse(raw) as RedactionRuleFile;\n if (typeof parsed?.pattern !== \"string\" || parsed.pattern.trim().length === 0) continue;\n rules.push(compileRedactionPattern(parsed.pattern));\n } catch {\n // Skip a corrupt rule file rather than failing the pass (rule 34 —\n // visible skip, never a silent pipeline block).\n }\n }\n return rules;\n}\n\n/**\n * Test content against a set of compiled rules. Returns true if ANY rule\n * matches (the content should be withheld). An empty rule set never matches.\n */\nexport function contentMatchesRedactionRules(\n content: string,\n rules: readonly CompiledRedactionRule[],\n): boolean {\n for (const rule of rules) {\n try {\n if (rule.matcher(content)) return true;\n } catch {\n // A matcher should not throw (compilation already fell back), but if a\n // RegExp blows up on pathological input we skip that rule rather than\n // failing the whole gate.\n }\n }\n return false;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WEARABLES_DIR_NAME,
|
|
3
|
+
isValidTranscriptDate
|
|
4
|
+
} from "./chunk-M7XQSUBB.js";
|
|
1
5
|
import {
|
|
2
6
|
TombstoneStore,
|
|
3
7
|
buildRetiredFactTombstoneInputs,
|
|
4
8
|
collectRetiredMemoriesForRebuild
|
|
5
9
|
} from "./chunk-L2B3EE7B.js";
|
|
6
|
-
import {
|
|
7
|
-
WEARABLES_DIR_NAME,
|
|
8
|
-
isValidTranscriptDate
|
|
9
|
-
} from "./chunk-M7XQSUBB.js";
|
|
10
10
|
import {
|
|
11
11
|
isErrnoCode
|
|
12
12
|
} from "./chunk-5UZXUTVO.js";
|
|
13
13
|
import {
|
|
14
14
|
assertPathInsideRoot
|
|
15
15
|
} from "./chunk-5GPPACXK.js";
|
|
16
|
+
import {
|
|
17
|
+
stripAttributesSuffix
|
|
18
|
+
} from "./chunk-SVOZFLIQ.js";
|
|
16
19
|
import {
|
|
17
20
|
supersessionKeysForFact
|
|
18
21
|
} from "./chunk-TKZ3AL5V.js";
|
|
@@ -21,9 +24,6 @@ import {
|
|
|
21
24
|
hasCitation,
|
|
22
25
|
stripCitationForTemplate
|
|
23
26
|
} from "./chunk-J6A3CX5N.js";
|
|
24
|
-
import {
|
|
25
|
-
stripAttributesSuffix
|
|
26
|
-
} from "./chunk-SVOZFLIQ.js";
|
|
27
27
|
import {
|
|
28
28
|
SPECULATIVE_TTL_DAYS,
|
|
29
29
|
confidenceTier
|
|
@@ -72,6 +72,10 @@ import {
|
|
|
72
72
|
import {
|
|
73
73
|
createVersion
|
|
74
74
|
} from "./chunk-VF4XKTX3.js";
|
|
75
|
+
import {
|
|
76
|
+
computeContentHash,
|
|
77
|
+
normalizeContent
|
|
78
|
+
} from "./chunk-W5H6ZRPT.js";
|
|
75
79
|
import {
|
|
76
80
|
MAGIC_HEADER_SIZE,
|
|
77
81
|
SecureStoreLockedError,
|
|
@@ -886,14 +890,13 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
886
890
|
this.dirty = true;
|
|
887
891
|
}
|
|
888
892
|
}
|
|
889
|
-
/** Normalize content
|
|
893
|
+
/** Normalize content (delegates to content-hash.ts for a single source of truth). */
|
|
890
894
|
static normalizeContent(content) {
|
|
891
|
-
return content
|
|
895
|
+
return normalizeContent(content);
|
|
892
896
|
}
|
|
893
|
-
/**
|
|
897
|
+
/** Compute SHA-256 hash (delegates to content-hash.ts for a single source of truth). */
|
|
894
898
|
static computeHash(content) {
|
|
895
|
-
|
|
896
|
-
return createHash("sha256").update(normalized).digest("hex");
|
|
899
|
+
return computeContentHash(content);
|
|
897
900
|
}
|
|
898
901
|
};
|
|
899
902
|
function normalizeAttributePairs(pairs) {
|
|
@@ -5863,4 +5866,4 @@ export {
|
|
|
5863
5866
|
serializeEntityFile,
|
|
5864
5867
|
StorageManager
|
|
5865
5868
|
};
|
|
5866
|
-
//# sourceMappingURL=chunk-
|
|
5869
|
+
//# sourceMappingURL=chunk-MQVKHFIU.js.map
|