@rudra-js/attested 0.7.4 → 0.7.6
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/README.md +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/numerals.d.ts.map +1 -1
- package/dist/numerals.js +62 -88
- package/dist/numerals.js.map +1 -1
- package/dist/phrases.d.ts +3 -2
- package/dist/phrases.d.ts.map +1 -1
- package/dist/phrases.js +30 -27
- package/dist/phrases.js.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +67 -66
- package/dist/verify.js.map +1 -1
- package/package.json +6 -3
- package/src/index.ts +1 -1
- package/src/numerals.ts +52 -98
- package/src/phrases.ts +32 -31
- package/src/verify.ts +73 -77
package/src/verify.ts
CHANGED
|
@@ -12,19 +12,14 @@ export type Layer = 'quantity' | 'wording';
|
|
|
12
12
|
|
|
13
13
|
export interface Finding {
|
|
14
14
|
layer: Layer;
|
|
15
|
-
|
|
16
15
|
token: string;
|
|
17
|
-
|
|
18
16
|
reason: string;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
export interface LayerReport {
|
|
22
20
|
supported: boolean;
|
|
23
|
-
|
|
24
21
|
strength: 'proof' | 'best-effort';
|
|
25
|
-
|
|
26
22
|
checked: number;
|
|
27
|
-
|
|
28
23
|
findings: Finding[];
|
|
29
24
|
}
|
|
30
25
|
|
|
@@ -36,9 +31,7 @@ export interface VerifyResult {
|
|
|
36
31
|
|
|
37
32
|
export interface Facts {
|
|
38
33
|
values: readonly (string | number | bigint)[];
|
|
39
|
-
|
|
40
34
|
bannedPhrases?: readonly string[];
|
|
41
|
-
|
|
42
35
|
allowedPhrases?: readonly string[];
|
|
43
36
|
}
|
|
44
37
|
|
|
@@ -50,50 +43,58 @@ export interface FieldResult {
|
|
|
50
43
|
export interface BatchResult {
|
|
51
44
|
supported: boolean;
|
|
52
45
|
fields: FieldResult[];
|
|
53
|
-
|
|
54
46
|
acrossFields: LayerReport;
|
|
55
47
|
}
|
|
56
48
|
|
|
57
|
-
const
|
|
49
|
+
const cache = new WeakMap<object, { copy: readonly unknown[]; supported: Set<string> }>();
|
|
58
50
|
|
|
59
|
-
function
|
|
60
|
-
if (
|
|
61
|
-
for (let i = 0; i <
|
|
62
|
-
if (
|
|
51
|
+
function sameValues(a: readonly unknown[], b: readonly unknown[]): boolean {
|
|
52
|
+
if (a.length !== b.length) return false;
|
|
53
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
54
|
+
if (a[i] !== b[i]) return false;
|
|
63
55
|
}
|
|
64
56
|
return true;
|
|
65
57
|
}
|
|
66
58
|
|
|
67
|
-
function
|
|
59
|
+
function supportedOf(facts: Facts): Set<string> {
|
|
68
60
|
const values = facts.values;
|
|
69
|
-
|
|
70
61
|
if (!Array.isArray(values)) return supportedValues(values);
|
|
71
62
|
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
63
|
+
const cached = cache.get(values);
|
|
64
|
+
if (cached !== undefined && sameValues(cached.copy, values)) return cached.supported;
|
|
74
65
|
|
|
75
66
|
const supported = supportedValues(values);
|
|
76
|
-
|
|
67
|
+
cache.set(values, { copy: [...values], supported });
|
|
77
68
|
return supported;
|
|
78
69
|
}
|
|
79
70
|
|
|
80
|
-
function
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
for (const phrase of facts.bannedPhrases ?? []) {
|
|
71
|
+
function normaliseAll(phrases: readonly string[] | undefined): string[] {
|
|
72
|
+
const out: string[] = [];
|
|
73
|
+
for (const phrase of phrases ?? []) {
|
|
84
74
|
const normalised = normalisePhrasing(phrase);
|
|
85
|
-
if (normalised.length > 0)
|
|
75
|
+
if (normalised.length > 0) out.push(normalised);
|
|
86
76
|
}
|
|
87
|
-
return
|
|
77
|
+
return out;
|
|
88
78
|
}
|
|
89
79
|
|
|
90
|
-
function
|
|
91
|
-
const
|
|
92
|
-
for (const phrase of facts.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
function bannedOf(facts: Facts): string[] {
|
|
81
|
+
const banned = new Set(BANNED_PHRASES);
|
|
82
|
+
for (const phrase of normaliseAll(facts.bannedPhrases)) banned.add(phrase);
|
|
83
|
+
return [...banned];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface PreparedFacts {
|
|
87
|
+
supported: Set<string>;
|
|
88
|
+
banned: string[];
|
|
89
|
+
allowed: string[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readFacts(facts: Facts): PreparedFacts {
|
|
93
|
+
return {
|
|
94
|
+
supported: supportedOf(facts),
|
|
95
|
+
banned: bannedOf(facts),
|
|
96
|
+
allowed: normaliseAll(facts.allowedPhrases),
|
|
97
|
+
};
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
function reasonFor(numeral: Numeral): string {
|
|
@@ -112,11 +113,8 @@ function checkQuantities(text: string, supported: Set<string>): LayerReport {
|
|
|
112
113
|
const reported = new Set<string>();
|
|
113
114
|
|
|
114
115
|
for (const numeral of numerals) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (supported.has(form)) backed = true;
|
|
118
|
-
}
|
|
119
|
-
if (backed || reported.has(numeral.token)) continue;
|
|
116
|
+
if (numeral.forms.some((form) => supported.has(form))) continue;
|
|
117
|
+
if (reported.has(numeral.token)) continue;
|
|
120
118
|
|
|
121
119
|
reported.add(numeral.token);
|
|
122
120
|
findings.push({ layer: 'quantity', token: numeral.token, reason: reasonFor(numeral) });
|
|
@@ -130,40 +128,46 @@ function checkQuantities(text: string, supported: Set<string>): LayerReport {
|
|
|
130
128
|
};
|
|
131
129
|
}
|
|
132
130
|
|
|
133
|
-
function
|
|
134
|
-
for (const span of
|
|
131
|
+
function isInside(spans: Span[], hit: Span): boolean {
|
|
132
|
+
for (const span of spans) {
|
|
135
133
|
if (span.start <= hit.start && hit.end <= span.end) return true;
|
|
136
134
|
}
|
|
137
135
|
return false;
|
|
138
136
|
}
|
|
139
137
|
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
const byLook = normalisePhrasing(text, true);
|
|
144
|
-
if (byLook !== texts[0]) texts.push(byLook);
|
|
145
|
-
|
|
146
|
-
const scans: Indexed[] = [];
|
|
147
|
-
const spans: Span[] = [];
|
|
148
|
-
for (const normalised of texts) {
|
|
149
|
-
const indexed = indexPhrasing(normalised);
|
|
150
|
-
scans.push(indexed);
|
|
138
|
+
function forgivenInEitherReading(scans: Indexed[], allowed: readonly string[]): Span[] {
|
|
139
|
+
const forgiven: Span[] = [];
|
|
140
|
+
for (const scan of scans) {
|
|
151
141
|
for (const phrase of allowed) {
|
|
152
|
-
for (const span of spansIn(
|
|
153
|
-
if (!
|
|
142
|
+
for (const span of spansIn(scan, phrase)) {
|
|
143
|
+
if (!scan.text.slice(span.start, span.end + 1).includes('\n')) forgiven.push(span);
|
|
154
144
|
}
|
|
155
145
|
}
|
|
156
146
|
}
|
|
147
|
+
return forgiven;
|
|
148
|
+
}
|
|
157
149
|
|
|
158
|
-
|
|
159
|
-
for (const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
for (const hit of spansIn(indexed, phrase)) {
|
|
163
|
-
if (!standsBehind(spans, hit)) caught = true;
|
|
164
|
-
}
|
|
150
|
+
function isCaught(scans: Indexed[], phrase: string, forgiven: Span[]): boolean {
|
|
151
|
+
for (const scan of scans) {
|
|
152
|
+
for (const hit of spansIn(scan, phrase)) {
|
|
153
|
+
if (!isInside(forgiven, hit)) return true;
|
|
165
154
|
}
|
|
166
|
-
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function checkWording(text: string, prepared: PreparedFacts): LayerReport {
|
|
160
|
+
const { banned, allowed } = prepared;
|
|
161
|
+
const plain = normalisePhrasing(text);
|
|
162
|
+
const byLook = normalisePhrasing(text, true);
|
|
163
|
+
const scans =
|
|
164
|
+
byLook === plain ? [indexPhrasing(plain)] : [indexPhrasing(plain), indexPhrasing(byLook)];
|
|
165
|
+
|
|
166
|
+
const forgiven = forgivenInEitherReading(scans, allowed);
|
|
167
|
+
|
|
168
|
+
const findings: Finding[] = [];
|
|
169
|
+
for (const phrase of banned) {
|
|
170
|
+
if (!isCaught(scans, phrase, forgiven)) continue;
|
|
167
171
|
|
|
168
172
|
findings.push({
|
|
169
173
|
layer: 'wording',
|
|
@@ -175,43 +179,35 @@ function checkWording(text: string, phrases: string[], allowed: string[]): Layer
|
|
|
175
179
|
return {
|
|
176
180
|
supported: findings.length === 0,
|
|
177
181
|
strength: 'best-effort',
|
|
178
|
-
checked:
|
|
182
|
+
checked: banned.length,
|
|
179
183
|
findings,
|
|
180
184
|
};
|
|
181
185
|
}
|
|
182
186
|
|
|
183
|
-
function check(
|
|
184
|
-
text
|
|
185
|
-
|
|
186
|
-
phrases: string[],
|
|
187
|
-
allowed: string[],
|
|
188
|
-
): VerifyResult {
|
|
189
|
-
const quantity = checkQuantities(text, supported);
|
|
190
|
-
const wording = checkWording(text, phrases, allowed);
|
|
187
|
+
function check(text: string, prepared: PreparedFacts): VerifyResult {
|
|
188
|
+
const quantity = checkQuantities(text, prepared.supported);
|
|
189
|
+
const wording = checkWording(text, prepared);
|
|
191
190
|
return { supported: quantity.supported && wording.supported, quantity, wording };
|
|
192
191
|
}
|
|
193
192
|
|
|
194
193
|
export function verify(text: string, facts: Facts): VerifyResult {
|
|
195
|
-
return check(text,
|
|
194
|
+
return check(text, readFacts(facts));
|
|
196
195
|
}
|
|
197
196
|
|
|
198
197
|
export function verifyFields(fields: Record<string, string>, facts: Facts): BatchResult {
|
|
199
|
-
const
|
|
200
|
-
const phrases = phrasesOf(facts);
|
|
201
|
-
const allowed = allowedOf(facts);
|
|
198
|
+
const prepared = readFacts(facts);
|
|
202
199
|
|
|
203
200
|
const results: FieldResult[] = [];
|
|
204
|
-
const
|
|
201
|
+
const texts: string[] = [];
|
|
205
202
|
let allSupported = true;
|
|
206
203
|
for (const field of Object.keys(fields)) {
|
|
207
204
|
const text = fields[field] ?? '';
|
|
208
|
-
const result = check(text,
|
|
205
|
+
const result = check(text, prepared);
|
|
209
206
|
if (!result.supported) allSupported = false;
|
|
210
207
|
results.push({ field, result });
|
|
211
|
-
|
|
208
|
+
texts.push(text);
|
|
212
209
|
}
|
|
213
210
|
|
|
214
|
-
const acrossFields = checkWording(
|
|
215
|
-
|
|
211
|
+
const acrossFields = checkWording(texts.join(' '), prepared);
|
|
216
212
|
return { supported: allSupported && acrossFields.supported, fields: results, acrossFields };
|
|
217
213
|
}
|