eyeling 1.21.9 → 1.22.0

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.
@@ -0,0 +1,546 @@
1
+ package main
2
+
3
+ import (
4
+ "crypto/hmac"
5
+ "crypto/sha256"
6
+ "encoding/hex"
7
+ "encoding/json"
8
+ "errors"
9
+ "fmt"
10
+ "os"
11
+ "sort"
12
+ "strings"
13
+ "time"
14
+ )
15
+
16
+ type Data struct {
17
+ CaseName string `json:"caseName"`
18
+ Retailer string `json:"retailer"`
19
+ Question string `json:"question"`
20
+ Timestamps Timestamps `json:"timestamps"`
21
+ EvaluationContext EvaluationContext `json:"evaluationContext"`
22
+ Thresholds Thresholds `json:"thresholds"`
23
+ HouseholdProfile HouseholdProfile `json:"householdProfile"`
24
+ Catalog []Product `json:"catalog"`
25
+ Scan Scan `json:"scan"`
26
+ InsightPolicy InsightPolicy `json:"insightPolicy"`
27
+ Integrity Integrity `json:"integrity"`
28
+ }
29
+
30
+ type Timestamps struct {
31
+ CreatedAt string `json:"createdAt"`
32
+ ExpiresAt string `json:"expiresAt"`
33
+ AuthorizedAt string `json:"authorizedAt"`
34
+ DutyPerformedAt string `json:"dutyPerformedAt"`
35
+ }
36
+
37
+ type EvaluationContext struct {
38
+ ScopeDevice string `json:"scopeDevice"`
39
+ ScopeEvent string `json:"scopeEvent"`
40
+ Purpose string `json:"purpose"`
41
+ ProhibitedReusePurpose string `json:"prohibitedReusePurpose"`
42
+ RequestAction string `json:"requestAction"`
43
+ }
44
+
45
+ type Thresholds struct {
46
+ SugarPerServingGAtLeast float64 `json:"sugarPerServingGAtLeast"`
47
+ }
48
+
49
+ type HouseholdProfile struct {
50
+ Condition string `json:"condition"`
51
+ }
52
+
53
+ type Product struct {
54
+ ID string `json:"id"`
55
+ Name string `json:"name"`
56
+ SugarTenths int `json:"sugarTenths"`
57
+ SugarPerServing float64 `json:"sugarPerServing"`
58
+ }
59
+
60
+ type Scan struct {
61
+ ScannedProductID string `json:"scannedProductId"`
62
+ }
63
+
64
+ type InsightPolicy struct {
65
+ ID string `json:"id"`
66
+ Metric string `json:"metric"`
67
+ Type string `json:"type"`
68
+ SuggestionPolicy string `json:"suggestionPolicy"`
69
+ PolicyType string `json:"policyType"`
70
+ PolicyProfile string `json:"policyProfile"`
71
+ }
72
+
73
+ type Integrity struct {
74
+ HashAlgorithm string `json:"hashAlgorithm"`
75
+ MacAlgorithm string `json:"macAlgorithm"`
76
+ Secret string `json:"secret"`
77
+ VerificationMode string `json:"verificationMode"`
78
+ }
79
+
80
+ type Insight struct {
81
+ CreatedAt string `json:"createdAt"`
82
+ ExpiresAt string `json:"expiresAt"`
83
+ ID string `json:"id"`
84
+ Metric string `json:"metric"`
85
+ Retailer string `json:"retailer"`
86
+ ScopeDevice string `json:"scopeDevice"`
87
+ ScopeEvent string `json:"scopeEvent"`
88
+ SuggestionPolicy string `json:"suggestionPolicy"`
89
+ Threshold float64 `json:"threshold"`
90
+ Type string `json:"type"`
91
+ }
92
+
93
+ type Constraint struct {
94
+ LeftOperand string `json:"leftOperand"`
95
+ Operator string `json:"operator"`
96
+ RightOperand string `json:"rightOperand"`
97
+ }
98
+
99
+ type Duty struct {
100
+ Action string `json:"action"`
101
+ Constraint Constraint `json:"constraint"`
102
+ }
103
+
104
+ type Permission struct {
105
+ Action string `json:"action"`
106
+ Constraint Constraint `json:"constraint"`
107
+ Target string `json:"target"`
108
+ }
109
+
110
+ type Prohibition struct {
111
+ Action string `json:"action"`
112
+ Constraint Constraint `json:"constraint"`
113
+ Target string `json:"target"`
114
+ }
115
+
116
+ type Policy struct {
117
+ Duty Duty `json:"duty"`
118
+ Permission Permission `json:"permission"`
119
+ Profile string `json:"profile"`
120
+ Prohibition Prohibition `json:"prohibition"`
121
+ Type string `json:"type"`
122
+ }
123
+
124
+ type Envelope struct {
125
+ Insight Insight `json:"insight"`
126
+ Policy Policy `json:"policy"`
127
+ }
128
+
129
+ type Derived struct {
130
+ NeedsLowSugar bool `json:"needsLowSugar"`
131
+ HighSugarScanned bool `json:"highSugarScanned"`
132
+ LowerSugarCandidateIDs []string `json:"lowerSugarCandidateIds"`
133
+ RecommendedAlternativeID *string `json:"recommendedAlternativeId"`
134
+ RecommendedAlternativeName *string `json:"recommendedAlternativeName"`
135
+ AlternativeLowersSugar bool `json:"alternativeLowersSugar"`
136
+ }
137
+
138
+ type IntegrityResult struct {
139
+ CanonicalEnvelope string `json:"canonicalEnvelope"`
140
+ PayloadHashSHA256 string `json:"payloadHashSHA256"`
141
+ EnvelopeHmacSHA256 string `json:"envelopeHmacSHA256"`
142
+ VerificationMode string `json:"verificationMode"`
143
+ }
144
+
145
+ type Answer struct {
146
+ Sentence string `json:"sentence"`
147
+ ScannedProduct string `json:"scannedProduct"`
148
+ SuggestedAlternative *string `json:"suggestedAlternative"`
149
+ PayloadHashSHA256 string `json:"payloadHashSHA256"`
150
+ EnvelopeHmacSHA256 string `json:"envelopeHmacSHA256"`
151
+ }
152
+
153
+ type Checks struct {
154
+ SignatureVerifies bool `json:"signatureVerifies"`
155
+ PayloadHashMatches bool `json:"payloadHashMatches"`
156
+ MinimizationRespected bool `json:"minimizationRespected"`
157
+ ScopeComplete bool `json:"scopeComplete"`
158
+ AuthorizationAllowed bool `json:"authorizationAllowed"`
159
+ HighSugarBanner bool `json:"highSugarBanner"`
160
+ AlternativeLowersSugar bool `json:"alternativeLowersSugar"`
161
+ DutyTimingConsistent bool `json:"dutyTimingConsistent"`
162
+ MarketingProhibited bool `json:"marketingProhibited"`
163
+ }
164
+
165
+ type Result struct {
166
+ CaseName string `json:"caseName"`
167
+ Derived Derived `json:"derived"`
168
+ Envelope Envelope `json:"envelope"`
169
+ Integrity IntegrityResult `json:"integrity"`
170
+ Answer Answer `json:"answer"`
171
+ ReasonWhy []string `json:"reasonWhy"`
172
+ Checks Checks `json:"checks"`
173
+ AllChecksPass bool `json:"allChecksPass"`
174
+ ArcText string `json:"arcText"`
175
+ }
176
+
177
+ func must(condition bool, message string) error {
178
+ if !condition {
179
+ return errors.New(message)
180
+ }
181
+ return nil
182
+ }
183
+
184
+ func readJSON(path string) (Data, error) {
185
+ var data Data
186
+ b, err := os.ReadFile(path)
187
+ if err != nil {
188
+ return data, err
189
+ }
190
+ err = json.Unmarshal(b, &data)
191
+ return data, err
192
+ }
193
+
194
+ func validate(data Data) error {
195
+ if err := must(data.CaseName != "", "caseName is required"); err != nil {
196
+ return err
197
+ }
198
+ if err := must(data.Retailer != "", "retailer is required"); err != nil {
199
+ return err
200
+ }
201
+ if err := must(len(data.Catalog) > 0, "catalog is required"); err != nil {
202
+ return err
203
+ }
204
+ if err := must(data.Scan.ScannedProductID != "", "scan.scannedProductId is required"); err != nil {
205
+ return err
206
+ }
207
+ if err := must(data.Timestamps.CreatedAt != "", "timestamps.createdAt is required"); err != nil {
208
+ return err
209
+ }
210
+ if err := must(data.Timestamps.ExpiresAt != "", "timestamps.expiresAt is required"); err != nil {
211
+ return err
212
+ }
213
+ if err := must(data.Timestamps.AuthorizedAt != "", "timestamps.authorizedAt is required"); err != nil {
214
+ return err
215
+ }
216
+ if err := must(data.Timestamps.DutyPerformedAt != "", "timestamps.dutyPerformedAt is required"); err != nil {
217
+ return err
218
+ }
219
+ if err := must(data.HouseholdProfile.Condition != "", "householdProfile.condition is required"); err != nil {
220
+ return err
221
+ }
222
+ if err := must(data.EvaluationContext.Purpose != "", "evaluationContext.purpose is required"); err != nil {
223
+ return err
224
+ }
225
+ if err := must(data.EvaluationContext.RequestAction != "", "evaluationContext.requestAction is required"); err != nil {
226
+ return err
227
+ }
228
+ if err := must(data.InsightPolicy.ID != "", "insightPolicy.id is required"); err != nil {
229
+ return err
230
+ }
231
+ if err := must(data.Integrity.Secret != "", "integrity.secret is required"); err != nil {
232
+ return err
233
+ }
234
+ return nil
235
+ }
236
+
237
+ func parseTime(s string) time.Time {
238
+ t, err := time.Parse(time.RFC3339Nano, s)
239
+ if err != nil {
240
+ panic(err)
241
+ }
242
+ return t
243
+ }
244
+
245
+ func findProduct(data Data, id string) *Product {
246
+ for i := range data.Catalog {
247
+ if data.Catalog[i].ID == id {
248
+ return &data.Catalog[i]
249
+ }
250
+ }
251
+ return nil
252
+ }
253
+
254
+ func deriveInsight(data Data) Insight {
255
+ return Insight{
256
+ CreatedAt: data.Timestamps.CreatedAt,
257
+ ExpiresAt: data.Timestamps.ExpiresAt,
258
+ ID: data.InsightPolicy.ID,
259
+ Metric: data.InsightPolicy.Metric,
260
+ Retailer: data.Retailer,
261
+ ScopeDevice: data.EvaluationContext.ScopeDevice,
262
+ ScopeEvent: data.EvaluationContext.ScopeEvent,
263
+ SuggestionPolicy: data.InsightPolicy.SuggestionPolicy,
264
+ Threshold: data.Thresholds.SugarPerServingGAtLeast,
265
+ Type: data.InsightPolicy.Type,
266
+ }
267
+ }
268
+
269
+ func derivePolicy(data Data) Policy {
270
+ return Policy{
271
+ Duty: Duty{
272
+ Action: "odrl:delete",
273
+ Constraint: Constraint{
274
+ LeftOperand: "odrl:dateTime",
275
+ Operator: "odrl:eq",
276
+ RightOperand: data.Timestamps.ExpiresAt,
277
+ },
278
+ },
279
+ Permission: Permission{
280
+ Action: data.EvaluationContext.RequestAction,
281
+ Constraint: Constraint{
282
+ LeftOperand: "odrl:purpose",
283
+ Operator: "odrl:eq",
284
+ RightOperand: data.EvaluationContext.Purpose,
285
+ },
286
+ Target: data.InsightPolicy.ID,
287
+ },
288
+ Profile: data.InsightPolicy.PolicyProfile,
289
+ Prohibition: Prohibition{
290
+ Action: "odrl:distribute",
291
+ Constraint: Constraint{
292
+ LeftOperand: "odrl:purpose",
293
+ Operator: "odrl:eq",
294
+ RightOperand: data.EvaluationContext.ProhibitedReusePurpose,
295
+ },
296
+ Target: data.InsightPolicy.ID,
297
+ },
298
+ Type: data.InsightPolicy.PolicyType,
299
+ }
300
+ }
301
+
302
+ func canonicalEnvelope(insight Insight, policy Policy) string {
303
+ return fmt.Sprintf(
304
+ "{\"insight\":{\"createdAt\":\"%s\",\"expiresAt\":\"%s\",\"id\":\"%s\",\"metric\":\"%s\",\"retailer\":\"%s\",\"scopeDevice\":\"%s\",\"scopeEvent\":\"%s\",\"suggestionPolicy\":\"%s\",\"threshold\":10.0,\"type\":\"%s\"},\"policy\":{\"duty\":{\"action\":\"%s\",\"constraint\":{\"leftOperand\":\"%s\",\"operator\":\"%s\",\"rightOperand\":\"%s\"}},\"permission\":{\"action\":\"%s\",\"constraint\":{\"leftOperand\":\"%s\",\"operator\":\"%s\",\"rightOperand\":\"%s\"},\"target\":\"%s\"},\"profile\":\"%s\",\"prohibition\":{\"action\":\"%s\",\"constraint\":{\"leftOperand\":\"%s\",\"operator\":\"%s\",\"rightOperand\":\"%s\"},\"target\":\"%s\"},\"type\":\"%s\"}}",
305
+ insight.CreatedAt,
306
+ insight.ExpiresAt,
307
+ insight.ID,
308
+ insight.Metric,
309
+ insight.Retailer,
310
+ insight.ScopeDevice,
311
+ insight.ScopeEvent,
312
+ insight.SuggestionPolicy,
313
+ insight.Type,
314
+ policy.Duty.Action,
315
+ policy.Duty.Constraint.LeftOperand,
316
+ policy.Duty.Constraint.Operator,
317
+ policy.Duty.Constraint.RightOperand,
318
+ policy.Permission.Action,
319
+ policy.Permission.Constraint.LeftOperand,
320
+ policy.Permission.Constraint.Operator,
321
+ policy.Permission.Constraint.RightOperand,
322
+ policy.Permission.Target,
323
+ policy.Profile,
324
+ policy.Prohibition.Action,
325
+ policy.Prohibition.Constraint.LeftOperand,
326
+ policy.Prohibition.Constraint.Operator,
327
+ policy.Prohibition.Constraint.RightOperand,
328
+ policy.Prohibition.Target,
329
+ policy.Type,
330
+ )
331
+ }
332
+
333
+ func sha256Hex(s string) string {
334
+ sum := sha256.Sum256([]byte(s))
335
+ return hex.EncodeToString(sum[:])
336
+ }
337
+
338
+ func hmacSHA256Hex(secret, s string) string {
339
+ mac := hmac.New(sha256.New, []byte(secret))
340
+ _, _ = mac.Write([]byte(s))
341
+ return hex.EncodeToString(mac.Sum(nil))
342
+ }
343
+
344
+ func yesNo(v bool) string {
345
+ if v {
346
+ return "yes"
347
+ }
348
+ return "no"
349
+ }
350
+
351
+ func evaluate(data Data) (Result, error) {
352
+ var result Result
353
+ if err := validate(data); err != nil {
354
+ return result, err
355
+ }
356
+
357
+ scanned := findProduct(data, data.Scan.ScannedProductID)
358
+ if scanned == nil {
359
+ return result, fmt.Errorf("scanned product not found: %s", data.Scan.ScannedProductID)
360
+ }
361
+
362
+ needsLowSugar := data.HouseholdProfile.Condition == "Diabetes"
363
+ highSugarScanned := scanned.SugarPerServing >= data.Thresholds.SugarPerServingGAtLeast
364
+
365
+ var candidates []Product
366
+ for _, p := range data.Catalog {
367
+ if p.SugarTenths < scanned.SugarTenths {
368
+ candidates = append(candidates, p)
369
+ }
370
+ }
371
+ sort.Slice(candidates, func(i, j int) bool {
372
+ return candidates[i].SugarTenths < candidates[j].SugarTenths
373
+ })
374
+
375
+ var recommended *Product
376
+ if len(candidates) > 0 {
377
+ recommended = &candidates[0]
378
+ }
379
+ alternativeLowersSugar := recommended != nil && recommended.SugarTenths < scanned.SugarTenths
380
+
381
+ insight := deriveInsight(data)
382
+ policy := derivePolicy(data)
383
+ canonical := canonicalEnvelope(insight, policy)
384
+ payloadHash := sha256Hex(canonical)
385
+ envelopeHMAC := hmacSHA256Hex(data.Integrity.Secret, canonical)
386
+
387
+ lowerIDs := make([]string, 0, len(candidates))
388
+ for _, p := range candidates {
389
+ lowerIDs = append(lowerIDs, p.ID)
390
+ }
391
+
392
+ insightBytes, _ := json.Marshal(insight)
393
+ insightText := strings.ToLower(string(insightBytes))
394
+ minimizationRespected := !strings.Contains(insightText, "diabetes") && !strings.Contains(insightText, "medical")
395
+
396
+ scopeComplete := insight.ScopeDevice != "" && insight.ScopeEvent != "" && insight.ExpiresAt != ""
397
+ authorizationAllowed := data.EvaluationContext.RequestAction == "odrl:use" &&
398
+ data.EvaluationContext.Purpose == "shopping_assist" &&
399
+ !parseTime(data.Timestamps.AuthorizedAt).After(parseTime(data.Timestamps.ExpiresAt))
400
+ dutyTimingConsistent := !parseTime(data.Timestamps.DutyPerformedAt).After(parseTime(data.Timestamps.ExpiresAt))
401
+ marketingProhibited := policy.Prohibition.Action == "odrl:distribute" &&
402
+ policy.Prohibition.Constraint.RightOperand == "marketing"
403
+ signatureVerifies := data.Integrity.VerificationMode == "trustedPrecomputedInput" &&
404
+ envelopeHMAC == hmacSHA256Hex(data.Integrity.Secret, canonical)
405
+ payloadHashMatches := payloadHash == sha256Hex(canonical)
406
+
407
+ checks := Checks{
408
+ SignatureVerifies: signatureVerifies,
409
+ PayloadHashMatches: payloadHashMatches,
410
+ MinimizationRespected: minimizationRespected,
411
+ ScopeComplete: scopeComplete,
412
+ AuthorizationAllowed: authorizationAllowed,
413
+ HighSugarBanner: highSugarScanned,
414
+ AlternativeLowersSugar: alternativeLowersSugar,
415
+ DutyTimingConsistent: dutyTimingConsistent,
416
+ MarketingProhibited: marketingProhibited,
417
+ }
418
+
419
+ var recommendedID *string
420
+ var recommendedName *string
421
+ recommendedText := "none"
422
+ sentence := fmt.Sprintf("The scanner is allowed to use a neutral shopping insight and recommends no alternative instead of %s.", scanned.Name)
423
+
424
+ if recommended != nil {
425
+ recommendedID = &recommended.ID
426
+ recommendedName = &recommended.Name
427
+ recommendedText = recommended.Name
428
+ sentence = fmt.Sprintf(
429
+ "The scanner is allowed to use a neutral shopping insight and recommends %s instead of %s.",
430
+ recommended.Name,
431
+ scanned.Name,
432
+ )
433
+ }
434
+
435
+ reasonWhy := []string{
436
+ "The phone desensitizes a diabetes-related household condition into a scoped low-sugar need, wraps it in an expiring Insight+Policy envelope, and signs it.",
437
+ fmt.Sprintf("scanned product : %s", scanned.Name),
438
+ fmt.Sprintf("suggested alternative: %s", recommendedText),
439
+ fmt.Sprintf("payload SHA-256 : %s", payloadHash),
440
+ fmt.Sprintf("HMAC-SHA256 : %s", envelopeHMAC),
441
+ }
442
+
443
+ arcLines := []string{
444
+ "=== Answer ===",
445
+ sentence,
446
+ "",
447
+ "=== Reason Why ===",
448
+ }
449
+ arcLines = append(arcLines, reasonWhy...)
450
+ arcLines = append(
451
+ arcLines,
452
+ "",
453
+ "=== Check ===",
454
+ fmt.Sprintf("signature verifies : %s", yesNo(checks.SignatureVerifies)),
455
+ fmt.Sprintf("payload hash matches : %s", yesNo(checks.PayloadHashMatches)),
456
+ fmt.Sprintf("minimization strips sensitive terms: %s", yesNo(checks.MinimizationRespected)),
457
+ fmt.Sprintf("scope complete : %s", yesNo(checks.ScopeComplete)),
458
+ fmt.Sprintf("authorization allowed : %s", yesNo(checks.AuthorizationAllowed)),
459
+ fmt.Sprintf("high-sugar banner : %s", yesNo(checks.HighSugarBanner)),
460
+ fmt.Sprintf("alternative lowers sugar : %s", yesNo(checks.AlternativeLowersSugar)),
461
+ fmt.Sprintf("duty timing consistent : %s", yesNo(checks.DutyTimingConsistent)),
462
+ fmt.Sprintf("marketing prohibited : %s", yesNo(checks.MarketingProhibited)),
463
+ )
464
+
465
+ allChecksPass := checks.SignatureVerifies &&
466
+ checks.PayloadHashMatches &&
467
+ checks.MinimizationRespected &&
468
+ checks.ScopeComplete &&
469
+ checks.AuthorizationAllowed &&
470
+ checks.HighSugarBanner &&
471
+ checks.AlternativeLowersSugar &&
472
+ checks.DutyTimingConsistent &&
473
+ checks.MarketingProhibited
474
+
475
+ result = Result{
476
+ CaseName: data.CaseName,
477
+ Derived: Derived{
478
+ NeedsLowSugar: needsLowSugar,
479
+ HighSugarScanned: highSugarScanned,
480
+ LowerSugarCandidateIDs: lowerIDs,
481
+ RecommendedAlternativeID: recommendedID,
482
+ RecommendedAlternativeName: recommendedName,
483
+ AlternativeLowersSugar: alternativeLowersSugar,
484
+ },
485
+ Envelope: Envelope{
486
+ Insight: insight,
487
+ Policy: policy,
488
+ },
489
+ Integrity: IntegrityResult{
490
+ CanonicalEnvelope: canonical,
491
+ PayloadHashSHA256: payloadHash,
492
+ EnvelopeHmacSHA256: envelopeHMAC,
493
+ VerificationMode: data.Integrity.VerificationMode,
494
+ },
495
+ Answer: Answer{
496
+ Sentence: sentence,
497
+ ScannedProduct: scanned.Name,
498
+ SuggestedAlternative: recommendedName,
499
+ PayloadHashSHA256: payloadHash,
500
+ EnvelopeHmacSHA256: envelopeHMAC,
501
+ },
502
+ ReasonWhy: reasonWhy,
503
+ Checks: checks,
504
+ AllChecksPass: allChecksPass,
505
+ ArcText: strings.Join(arcLines, "\n"),
506
+ }
507
+
508
+ return result, nil
509
+ }
510
+
511
+ func main() {
512
+ inputPath := "delfour.data.json"
513
+ jsonMode := false
514
+
515
+ for _, arg := range os.Args[1:] {
516
+ if arg == "--json" {
517
+ jsonMode = true
518
+ } else if !strings.HasPrefix(arg, "--") {
519
+ inputPath = arg
520
+ }
521
+ }
522
+
523
+ data, err := readJSON(inputPath)
524
+ if err != nil {
525
+ fmt.Fprintln(os.Stderr, err)
526
+ os.Exit(1)
527
+ }
528
+
529
+ result, err := evaluate(data)
530
+ if err != nil {
531
+ fmt.Fprintln(os.Stderr, err)
532
+ os.Exit(1)
533
+ }
534
+
535
+ if jsonMode {
536
+ enc := json.NewEncoder(os.Stdout)
537
+ enc.SetIndent("", " ")
538
+ _ = enc.Encode(result)
539
+ } else {
540
+ fmt.Println(result.ArcText)
541
+ }
542
+
543
+ if !result.AllChecksPass {
544
+ os.Exit(1)
545
+ }
546
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Status
4
4
 
5
- This document is the **normative specification** for the Delfour case. The file `delfour.model.mjs` is the **reference ECMAScript implementation** of these clauses. The file `delfour.data.json` is the **instance** evaluated in this bundle. The file `delfour.expected.json` is the **conformance vector** for that instance.
5
+ This document is the **normative specification** for the Delfour case. The file `delfour.model.go` is the **reference Go implementation** of these clauses. The file `delfour.data.json` is the **instance** evaluated in this bundle. The file `delfour.expected.json` is the **conformance vector** for that instance.
6
6
 
7
7
  ## Insight Economy context
8
8
 
@@ -15,7 +15,6 @@ The scanner may use that insight to suggest a better product, but not for unrela
15
15
  - “iff” means “if and only if”.
16
16
  - A clause identifier such as `R1` or `M3` is normative.
17
17
  - A conforming implementation may be written in any language, but it shall produce the same derived values and pass/fail outcomes for the supplied instance.
18
- - The reference implementation uses ECMAScript because you preferred an international-standard JS language.
19
18
 
20
19
  ## Vocabulary
21
20