@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/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 readings = new WeakMap<object, { held: readonly unknown[]; supported: Set<string> }>();
49
+ const cache = new WeakMap<object, { copy: readonly unknown[]; supported: Set<string> }>();
58
50
 
59
- function unchanged(held: readonly unknown[], values: readonly unknown[]): boolean {
60
- if (held.length !== values.length) return false;
61
- for (let i = 0; i < held.length; i += 1) {
62
- if (held[i] !== values[i]) return false;
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 valuesOf(facts: Facts): Set<string> {
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 read = readings.get(values);
73
- if (read !== undefined && unchanged(read.held, values)) return read.supported;
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
- readings.set(values, { held: [...values], supported });
67
+ cache.set(values, { copy: [...values], supported });
77
68
  return supported;
78
69
  }
79
70
 
80
- function phrasesOf(facts: Facts): string[] {
81
- const phrases = new Set<string>(BANNED_PHRASES);
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) phrases.add(normalised);
75
+ if (normalised.length > 0) out.push(normalised);
86
76
  }
87
- return [...phrases];
77
+ return out;
88
78
  }
89
79
 
90
- function allowedOf(facts: Facts): string[] {
91
- const allowed: string[] = [];
92
- for (const phrase of facts.allowedPhrases ?? []) {
93
- const normalised = normalisePhrasing(phrase);
94
- if (normalised.length > 0) allowed.push(normalised);
95
- }
96
- return allowed;
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
- let backed = false;
116
- for (const form of numeral.forms) {
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 standsBehind(allowed: Span[], hit: Span): boolean {
134
- for (const span of allowed) {
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 checkWording(text: string, phrases: string[], allowed: string[]): LayerReport {
141
- // Read twice, since Ν reads as N but ν as v; both readings are the same length, so spans line up.
142
- const texts = [normalisePhrasing(text)];
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(indexed, phrase)) {
153
- if (!normalised.slice(span.start, span.end + 1).includes('\n')) spans.push(span);
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
- const findings: Finding[] = [];
159
- for (const phrase of phrases) {
160
- let caught = false;
161
- for (const indexed of scans) {
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
- if (!caught) continue;
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: phrases.length,
182
+ checked: banned.length,
179
183
  findings,
180
184
  };
181
185
  }
182
186
 
183
- function check(
184
- text: string,
185
- supported: Set<string>,
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, valuesOf(facts), phrasesOf(facts), allowedOf(facts));
194
+ return check(text, readFacts(facts));
196
195
  }
197
196
 
198
197
  export function verifyFields(fields: Record<string, string>, facts: Facts): BatchResult {
199
- const supported = valuesOf(facts);
200
- const phrases = phrasesOf(facts);
201
- const allowed = allowedOf(facts);
198
+ const prepared = readFacts(facts);
202
199
 
203
200
  const results: FieldResult[] = [];
204
- const written: string[] = [];
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, supported, phrases, allowed);
205
+ const result = check(text, prepared);
209
206
  if (!result.supported) allSupported = false;
210
207
  results.push({ field, result });
211
- written.push(text);
208
+ texts.push(text);
212
209
  }
213
210
 
214
- const acrossFields = checkWording(written.join(' '), phrases, allowed);
215
-
211
+ const acrossFields = checkWording(texts.join(' '), prepared);
216
212
  return { supported: allSupported && acrossFields.supported, fields: results, acrossFields };
217
213
  }