eyeling 1.22.2 → 1.22.3

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.
@@ -1,564 +0,0 @@
1
- package main
2
-
3
- // Delfour is a reference Arcling model written as a small CLI program.
4
- // It reads delfour.data.json, derives the neutral shopping insight,
5
- // computes the canonical envelope/hash/HMAC values, and emits either
6
- // ARC text or a JSON result object.
7
-
8
- import (
9
- "crypto/hmac"
10
- "crypto/sha256"
11
- "encoding/hex"
12
- "encoding/json"
13
- "errors"
14
- "fmt"
15
- "os"
16
- "sort"
17
- "strings"
18
- "time"
19
- )
20
-
21
- // Data mirrors the input instance shape from delfour.data.json.
22
- type Data struct {
23
- CaseName string `json:"caseName"`
24
- Retailer string `json:"retailer"`
25
- Question string `json:"question"`
26
- Timestamps Timestamps `json:"timestamps"`
27
- EvaluationContext EvaluationContext `json:"evaluationContext"`
28
- Thresholds Thresholds `json:"thresholds"`
29
- HouseholdProfile HouseholdProfile `json:"householdProfile"`
30
- Catalog []Product `json:"catalog"`
31
- Scan Scan `json:"scan"`
32
- InsightPolicy InsightPolicy `json:"insightPolicy"`
33
- Integrity Integrity `json:"integrity"`
34
- }
35
-
36
- type Timestamps struct {
37
- CreatedAt string `json:"createdAt"`
38
- ExpiresAt string `json:"expiresAt"`
39
- AuthorizedAt string `json:"authorizedAt"`
40
- DutyPerformedAt string `json:"dutyPerformedAt"`
41
- }
42
-
43
- type EvaluationContext struct {
44
- ScopeDevice string `json:"scopeDevice"`
45
- ScopeEvent string `json:"scopeEvent"`
46
- Purpose string `json:"purpose"`
47
- ProhibitedReusePurpose string `json:"prohibitedReusePurpose"`
48
- RequestAction string `json:"requestAction"`
49
- }
50
-
51
- type Thresholds struct {
52
- SugarPerServingGAtLeast float64 `json:"sugarPerServingGAtLeast"`
53
- }
54
-
55
- type HouseholdProfile struct {
56
- Condition string `json:"condition"`
57
- }
58
-
59
- type Product struct {
60
- ID string `json:"id"`
61
- Name string `json:"name"`
62
- SugarTenths int `json:"sugarTenths"`
63
- SugarPerServing float64 `json:"sugarPerServing"`
64
- }
65
-
66
- type Scan struct {
67
- ScannedProductID string `json:"scannedProductId"`
68
- }
69
-
70
- type InsightPolicy struct {
71
- ID string `json:"id"`
72
- Metric string `json:"metric"`
73
- Type string `json:"type"`
74
- SuggestionPolicy string `json:"suggestionPolicy"`
75
- PolicyType string `json:"policyType"`
76
- PolicyProfile string `json:"policyProfile"`
77
- }
78
-
79
- type Integrity struct {
80
- HashAlgorithm string `json:"hashAlgorithm"`
81
- MacAlgorithm string `json:"macAlgorithm"`
82
- Secret string `json:"secret"`
83
- VerificationMode string `json:"verificationMode"`
84
- }
85
-
86
- // Insight is the minimized payload shared with the retailer.
87
- type Insight struct {
88
- CreatedAt string `json:"createdAt"`
89
- ExpiresAt string `json:"expiresAt"`
90
- ID string `json:"id"`
91
- Metric string `json:"metric"`
92
- Retailer string `json:"retailer"`
93
- ScopeDevice string `json:"scopeDevice"`
94
- ScopeEvent string `json:"scopeEvent"`
95
- SuggestionPolicy string `json:"suggestionPolicy"`
96
- Threshold float64 `json:"threshold"`
97
- Type string `json:"type"`
98
- }
99
-
100
- type Constraint struct {
101
- LeftOperand string `json:"leftOperand"`
102
- Operator string `json:"operator"`
103
- RightOperand string `json:"rightOperand"`
104
- }
105
-
106
- type Duty struct {
107
- Action string `json:"action"`
108
- Constraint Constraint `json:"constraint"`
109
- }
110
-
111
- type Permission struct {
112
- Action string `json:"action"`
113
- Constraint Constraint `json:"constraint"`
114
- Target string `json:"target"`
115
- }
116
-
117
- type Prohibition struct {
118
- Action string `json:"action"`
119
- Constraint Constraint `json:"constraint"`
120
- Target string `json:"target"`
121
- }
122
-
123
- type Policy struct {
124
- Duty Duty `json:"duty"`
125
- Permission Permission `json:"permission"`
126
- Profile string `json:"profile"`
127
- Prohibition Prohibition `json:"prohibition"`
128
- Type string `json:"type"`
129
- }
130
-
131
- type Envelope struct {
132
- Insight Insight `json:"insight"`
133
- Policy Policy `json:"policy"`
134
- }
135
-
136
- type Derived struct {
137
- NeedsLowSugar bool `json:"needsLowSugar"`
138
- HighSugarScanned bool `json:"highSugarScanned"`
139
- LowerSugarCandidateIDs []string `json:"lowerSugarCandidateIds"`
140
- RecommendedAlternativeID *string `json:"recommendedAlternativeId"`
141
- RecommendedAlternativeName *string `json:"recommendedAlternativeName"`
142
- AlternativeLowersSugar bool `json:"alternativeLowersSugar"`
143
- }
144
-
145
- type IntegrityResult struct {
146
- CanonicalEnvelope string `json:"canonicalEnvelope"`
147
- PayloadHashSHA256 string `json:"payloadHashSHA256"`
148
- EnvelopeHmacSHA256 string `json:"envelopeHmacSHA256"`
149
- VerificationMode string `json:"verificationMode"`
150
- }
151
-
152
- type Answer struct {
153
- Sentence string `json:"sentence"`
154
- ScannedProduct string `json:"scannedProduct"`
155
- SuggestedAlternative *string `json:"suggestedAlternative"`
156
- PayloadHashSHA256 string `json:"payloadHashSHA256"`
157
- EnvelopeHmacSHA256 string `json:"envelopeHmacSHA256"`
158
- }
159
-
160
- type Checks struct {
161
- SignatureVerifies bool `json:"signatureVerifies"`
162
- PayloadHashMatches bool `json:"payloadHashMatches"`
163
- MinimizationRespected bool `json:"minimizationRespected"`
164
- ScopeComplete bool `json:"scopeComplete"`
165
- AuthorizationAllowed bool `json:"authorizationAllowed"`
166
- HighSugarBanner bool `json:"highSugarBanner"`
167
- AlternativeLowersSugar bool `json:"alternativeLowersSugar"`
168
- DutyTimingConsistent bool `json:"dutyTimingConsistent"`
169
- MarketingProhibited bool `json:"marketingProhibited"`
170
- }
171
-
172
- type Result struct {
173
- CaseName string `json:"caseName"`
174
- Derived Derived `json:"derived"`
175
- Envelope Envelope `json:"envelope"`
176
- Integrity IntegrityResult `json:"integrity"`
177
- Answer Answer `json:"answer"`
178
- ReasonWhy []string `json:"reasonWhy"`
179
- Checks Checks `json:"checks"`
180
- AllChecksPass bool `json:"allChecksPass"`
181
- ArcText string `json:"arcText"`
182
- }
183
-
184
- func must(condition bool, message string) error {
185
- if !condition {
186
- return errors.New(message)
187
- }
188
- return nil
189
- }
190
-
191
- func readJSON(path string) (Data, error) {
192
- var data Data
193
- b, err := os.ReadFile(path)
194
- if err != nil {
195
- return data, err
196
- }
197
- err = json.Unmarshal(b, &data)
198
- return data, err
199
- }
200
-
201
- // validate performs the structural checks that used to live in JSON Schema.
202
- func validate(data Data) error {
203
- if err := must(data.CaseName != "", "caseName is required"); err != nil {
204
- return err
205
- }
206
- if err := must(data.Retailer != "", "retailer is required"); err != nil {
207
- return err
208
- }
209
- if err := must(len(data.Catalog) > 0, "catalog is required"); err != nil {
210
- return err
211
- }
212
- if err := must(data.Scan.ScannedProductID != "", "scan.scannedProductId is required"); err != nil {
213
- return err
214
- }
215
- if err := must(data.Timestamps.CreatedAt != "", "timestamps.createdAt is required"); err != nil {
216
- return err
217
- }
218
- if err := must(data.Timestamps.ExpiresAt != "", "timestamps.expiresAt is required"); err != nil {
219
- return err
220
- }
221
- if err := must(data.Timestamps.AuthorizedAt != "", "timestamps.authorizedAt is required"); err != nil {
222
- return err
223
- }
224
- if err := must(data.Timestamps.DutyPerformedAt != "", "timestamps.dutyPerformedAt is required"); err != nil {
225
- return err
226
- }
227
- if err := must(data.HouseholdProfile.Condition != "", "householdProfile.condition is required"); err != nil {
228
- return err
229
- }
230
- if err := must(data.EvaluationContext.Purpose != "", "evaluationContext.purpose is required"); err != nil {
231
- return err
232
- }
233
- if err := must(data.EvaluationContext.RequestAction != "", "evaluationContext.requestAction is required"); err != nil {
234
- return err
235
- }
236
- if err := must(data.InsightPolicy.ID != "", "insightPolicy.id is required"); err != nil {
237
- return err
238
- }
239
- if err := must(data.Integrity.Secret != "", "integrity.secret is required"); err != nil {
240
- return err
241
- }
242
- return nil
243
- }
244
-
245
- // parseTime accepts RFC3339Nano timestamps from the case instance.
246
- func parseTime(s string) time.Time {
247
- t, err := time.Parse(time.RFC3339Nano, s)
248
- if err != nil {
249
- panic(err)
250
- }
251
- return t
252
- }
253
-
254
- // findProduct resolves the scanned or recommended product by its catalog id.
255
- func findProduct(data Data, id string) *Product {
256
- for i := range data.Catalog {
257
- if data.Catalog[i].ID == id {
258
- return &data.Catalog[i]
259
- }
260
- }
261
- return nil
262
- }
263
-
264
- // deriveInsight strips the household condition down to the neutral shopping insight.
265
- func deriveInsight(data Data) Insight {
266
- return Insight{
267
- CreatedAt: data.Timestamps.CreatedAt,
268
- ExpiresAt: data.Timestamps.ExpiresAt,
269
- ID: data.InsightPolicy.ID,
270
- Metric: data.InsightPolicy.Metric,
271
- Retailer: data.Retailer,
272
- ScopeDevice: data.EvaluationContext.ScopeDevice,
273
- ScopeEvent: data.EvaluationContext.ScopeEvent,
274
- SuggestionPolicy: data.InsightPolicy.SuggestionPolicy,
275
- Threshold: data.Thresholds.SugarPerServingGAtLeast,
276
- Type: data.InsightPolicy.Type,
277
- }
278
- }
279
-
280
- // derivePolicy builds the companion ODRL-style policy used for governance checks.
281
- func derivePolicy(data Data) Policy {
282
- return Policy{
283
- Duty: Duty{
284
- Action: "odrl:delete",
285
- Constraint: Constraint{
286
- LeftOperand: "odrl:dateTime",
287
- Operator: "odrl:eq",
288
- RightOperand: data.Timestamps.ExpiresAt,
289
- },
290
- },
291
- Permission: Permission{
292
- Action: data.EvaluationContext.RequestAction,
293
- Constraint: Constraint{
294
- LeftOperand: "odrl:purpose",
295
- Operator: "odrl:eq",
296
- RightOperand: data.EvaluationContext.Purpose,
297
- },
298
- Target: data.InsightPolicy.ID,
299
- },
300
- Profile: data.InsightPolicy.PolicyProfile,
301
- Prohibition: Prohibition{
302
- Action: "odrl:distribute",
303
- Constraint: Constraint{
304
- LeftOperand: "odrl:purpose",
305
- Operator: "odrl:eq",
306
- RightOperand: data.EvaluationContext.ProhibitedReusePurpose,
307
- },
308
- Target: data.InsightPolicy.ID,
309
- },
310
- Type: data.InsightPolicy.PolicyType,
311
- }
312
- }
313
-
314
- // canonicalEnvelope returns the exact byte string used for the integrity vector.
315
- // The field order and the lexical form of threshold (10.0) are intentional.
316
- func canonicalEnvelope(insight Insight, policy Policy) string {
317
- return fmt.Sprintf(
318
- "{\"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\"}}",
319
- insight.CreatedAt,
320
- insight.ExpiresAt,
321
- insight.ID,
322
- insight.Metric,
323
- insight.Retailer,
324
- insight.ScopeDevice,
325
- insight.ScopeEvent,
326
- insight.SuggestionPolicy,
327
- insight.Type,
328
- policy.Duty.Action,
329
- policy.Duty.Constraint.LeftOperand,
330
- policy.Duty.Constraint.Operator,
331
- policy.Duty.Constraint.RightOperand,
332
- policy.Permission.Action,
333
- policy.Permission.Constraint.LeftOperand,
334
- policy.Permission.Constraint.Operator,
335
- policy.Permission.Constraint.RightOperand,
336
- policy.Permission.Target,
337
- policy.Profile,
338
- policy.Prohibition.Action,
339
- policy.Prohibition.Constraint.LeftOperand,
340
- policy.Prohibition.Constraint.Operator,
341
- policy.Prohibition.Constraint.RightOperand,
342
- policy.Prohibition.Target,
343
- policy.Type,
344
- )
345
- }
346
-
347
- func sha256Hex(s string) string {
348
- sum := sha256.Sum256([]byte(s))
349
- return hex.EncodeToString(sum[:])
350
- }
351
-
352
- func hmacSHA256Hex(secret, s string) string {
353
- mac := hmac.New(sha256.New, []byte(secret))
354
- _, _ = mac.Write([]byte(s))
355
- return hex.EncodeToString(mac.Sum(nil))
356
- }
357
-
358
- func yesNo(v bool) string {
359
- if v {
360
- return "yes"
361
- }
362
- return "no"
363
- }
364
-
365
- // evaluate runs the full Arcling pipeline: derive facts, select the recommendation,
366
- // build the envelope, verify integrity values, and render the final report.
367
- func evaluate(data Data) (Result, error) {
368
- var result Result
369
- if err := validate(data); err != nil {
370
- return result, err
371
- }
372
-
373
- scanned := findProduct(data, data.Scan.ScannedProductID)
374
- if scanned == nil {
375
- return result, fmt.Errorf("scanned product not found: %s", data.Scan.ScannedProductID)
376
- }
377
-
378
- needsLowSugar := data.HouseholdProfile.Condition == "Diabetes"
379
- highSugarScanned := scanned.SugarPerServing >= data.Thresholds.SugarPerServingGAtLeast
380
-
381
- var candidates []Product
382
- for _, p := range data.Catalog {
383
- if p.SugarTenths < scanned.SugarTenths {
384
- candidates = append(candidates, p)
385
- }
386
- }
387
- sort.Slice(candidates, func(i, j int) bool {
388
- return candidates[i].SugarTenths < candidates[j].SugarTenths
389
- })
390
-
391
- var recommended *Product
392
- if len(candidates) > 0 {
393
- recommended = &candidates[0]
394
- }
395
- alternativeLowersSugar := recommended != nil && recommended.SugarTenths < scanned.SugarTenths
396
-
397
- insight := deriveInsight(data)
398
- policy := derivePolicy(data)
399
- canonical := canonicalEnvelope(insight, policy)
400
- payloadHash := sha256Hex(canonical)
401
- envelopeHMAC := hmacSHA256Hex(data.Integrity.Secret, canonical)
402
-
403
- lowerIDs := make([]string, 0, len(candidates))
404
- for _, p := range candidates {
405
- lowerIDs = append(lowerIDs, p.ID)
406
- }
407
-
408
- insightBytes, _ := json.Marshal(insight)
409
- insightText := strings.ToLower(string(insightBytes))
410
- minimizationRespected := !strings.Contains(insightText, "diabetes") && !strings.Contains(insightText, "medical")
411
-
412
- scopeComplete := insight.ScopeDevice != "" && insight.ScopeEvent != "" && insight.ExpiresAt != ""
413
- authorizationAllowed := data.EvaluationContext.RequestAction == "odrl:use" &&
414
- data.EvaluationContext.Purpose == "shopping_assist" &&
415
- !parseTime(data.Timestamps.AuthorizedAt).After(parseTime(data.Timestamps.ExpiresAt))
416
- dutyTimingConsistent := !parseTime(data.Timestamps.DutyPerformedAt).After(parseTime(data.Timestamps.ExpiresAt))
417
- marketingProhibited := policy.Prohibition.Action == "odrl:distribute" &&
418
- policy.Prohibition.Constraint.RightOperand == "marketing"
419
- signatureVerifies := data.Integrity.VerificationMode == "trustedPrecomputedInput" &&
420
- envelopeHMAC == hmacSHA256Hex(data.Integrity.Secret, canonical)
421
- payloadHashMatches := payloadHash == sha256Hex(canonical)
422
-
423
- checks := Checks{
424
- SignatureVerifies: signatureVerifies,
425
- PayloadHashMatches: payloadHashMatches,
426
- MinimizationRespected: minimizationRespected,
427
- ScopeComplete: scopeComplete,
428
- AuthorizationAllowed: authorizationAllowed,
429
- HighSugarBanner: highSugarScanned,
430
- AlternativeLowersSugar: alternativeLowersSugar,
431
- DutyTimingConsistent: dutyTimingConsistent,
432
- MarketingProhibited: marketingProhibited,
433
- }
434
-
435
- var recommendedID *string
436
- var recommendedName *string
437
- recommendedText := "none"
438
- sentence := fmt.Sprintf("The scanner is allowed to use a neutral shopping insight and recommends no alternative instead of %s.", scanned.Name)
439
-
440
- if recommended != nil {
441
- recommendedID = &recommended.ID
442
- recommendedName = &recommended.Name
443
- recommendedText = recommended.Name
444
- sentence = fmt.Sprintf(
445
- "The scanner is allowed to use a neutral shopping insight and recommends %s instead of %s.",
446
- recommended.Name,
447
- scanned.Name,
448
- )
449
- }
450
-
451
- reasonWhy := []string{
452
- "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.",
453
- fmt.Sprintf("scanned product : %s", scanned.Name),
454
- fmt.Sprintf("suggested alternative: %s", recommendedText),
455
- fmt.Sprintf("payload SHA-256 : %s", payloadHash),
456
- fmt.Sprintf("HMAC-SHA256 : %s", envelopeHMAC),
457
- }
458
-
459
- arcLines := []string{
460
- "=== Answer ===",
461
- sentence,
462
- "",
463
- "=== Reason Why ===",
464
- }
465
- arcLines = append(arcLines, reasonWhy...)
466
- arcLines = append(
467
- arcLines,
468
- "",
469
- "=== Check ===",
470
- fmt.Sprintf("signature verifies : %s", yesNo(checks.SignatureVerifies)),
471
- fmt.Sprintf("payload hash matches : %s", yesNo(checks.PayloadHashMatches)),
472
- fmt.Sprintf("minimization strips sensitive terms: %s", yesNo(checks.MinimizationRespected)),
473
- fmt.Sprintf("scope complete : %s", yesNo(checks.ScopeComplete)),
474
- fmt.Sprintf("authorization allowed : %s", yesNo(checks.AuthorizationAllowed)),
475
- fmt.Sprintf("high-sugar banner : %s", yesNo(checks.HighSugarBanner)),
476
- fmt.Sprintf("alternative lowers sugar : %s", yesNo(checks.AlternativeLowersSugar)),
477
- fmt.Sprintf("duty timing consistent : %s", yesNo(checks.DutyTimingConsistent)),
478
- fmt.Sprintf("marketing prohibited : %s", yesNo(checks.MarketingProhibited)),
479
- )
480
-
481
- allChecksPass := checks.SignatureVerifies &&
482
- checks.PayloadHashMatches &&
483
- checks.MinimizationRespected &&
484
- checks.ScopeComplete &&
485
- checks.AuthorizationAllowed &&
486
- checks.HighSugarBanner &&
487
- checks.AlternativeLowersSugar &&
488
- checks.DutyTimingConsistent &&
489
- checks.MarketingProhibited
490
-
491
- result = Result{
492
- CaseName: data.CaseName,
493
- Derived: Derived{
494
- NeedsLowSugar: needsLowSugar,
495
- HighSugarScanned: highSugarScanned,
496
- LowerSugarCandidateIDs: lowerIDs,
497
- RecommendedAlternativeID: recommendedID,
498
- RecommendedAlternativeName: recommendedName,
499
- AlternativeLowersSugar: alternativeLowersSugar,
500
- },
501
- Envelope: Envelope{
502
- Insight: insight,
503
- Policy: policy,
504
- },
505
- Integrity: IntegrityResult{
506
- CanonicalEnvelope: canonical,
507
- PayloadHashSHA256: payloadHash,
508
- EnvelopeHmacSHA256: envelopeHMAC,
509
- VerificationMode: data.Integrity.VerificationMode,
510
- },
511
- Answer: Answer{
512
- Sentence: sentence,
513
- ScannedProduct: scanned.Name,
514
- SuggestedAlternative: recommendedName,
515
- PayloadHashSHA256: payloadHash,
516
- EnvelopeHmacSHA256: envelopeHMAC,
517
- },
518
- ReasonWhy: reasonWhy,
519
- Checks: checks,
520
- AllChecksPass: allChecksPass,
521
- ArcText: strings.Join(arcLines, "\n"),
522
- }
523
-
524
- return result, nil
525
- }
526
-
527
- // main is a tiny CLI wrapper around evaluate. It defaults to delfour.data.json,
528
- // prints ARC text, and switches to JSON output when --json is supplied.
529
- func main() {
530
- inputPath := "delfour.data.json"
531
- jsonMode := false
532
-
533
- for _, arg := range os.Args[1:] {
534
- if arg == "--json" {
535
- jsonMode = true
536
- } else if !strings.HasPrefix(arg, "--") {
537
- inputPath = arg
538
- }
539
- }
540
-
541
- data, err := readJSON(inputPath)
542
- if err != nil {
543
- fmt.Fprintln(os.Stderr, err)
544
- os.Exit(1)
545
- }
546
-
547
- result, err := evaluate(data)
548
- if err != nil {
549
- fmt.Fprintln(os.Stderr, err)
550
- os.Exit(1)
551
- }
552
-
553
- if jsonMode {
554
- enc := json.NewEncoder(os.Stdout)
555
- enc.SetIndent("", " ")
556
- _ = enc.Encode(result)
557
- } else {
558
- fmt.Println(result.ArcText)
559
- }
560
-
561
- if !result.AllChecksPass {
562
- os.Exit(1)
563
- }
564
- }
@@ -1,117 +0,0 @@
1
- # Delfour — ARC Specification
2
-
3
- ## Status
4
-
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
-
7
- ## Insight Economy context
8
-
9
- This case is the household-scale reading of Ruben Verborgh’s [Inside the Insight Economy](https://ruben.verborgh.org/blog/2025/08/12/inside-the-insight-economy/). Its core claim is that a person can share a useful shopping hint without exposing sensitive health details. A phone turns a private condition into a neutral, limited insight such as "prefer lower-sugar products", attaches clear usage rules and an expiry time, and sends it to a store scanner.
10
-
11
- The scanner may use that insight to suggest a better product, but not for unrelated purposes such as marketing. The scanner does not need the diagnosis. It only needs the right shopping conclusion.
12
-
13
- ## Conventions
14
-
15
- - “iff” means “if and only if”.
16
- - A clause identifier such as `R1` or `M3` is normative.
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
-
19
- ## Vocabulary
20
-
21
- **V1. Household condition** is a private fact local to the phone.
22
-
23
- **V2. Low-sugar need** is a neutral shopping need derived from the household condition.
24
-
25
- **V3. Scanned product** is the product presently under consideration by the store self-scanner.
26
-
27
- **V4. Candidate alternative** is a catalog product considered as a possible substitute.
28
-
29
- **V5. Insight envelope** is the ordered pair `(insight, policy)` together with integrity metadata.
30
-
31
- ## Input instance
32
-
33
- **I1.** The retailer is `Delfour`.
34
-
35
- **I2.** The household condition is `Diabetes`.
36
-
37
- **I3.** The scanned product is `Classic Tea Biscuits`.
38
-
39
- **I4.** The sugar threshold is `10.0` grams per serving.
40
-
41
- **I5.** The catalog contains the four products listed in `delfour.data.json`.
42
-
43
- ## Derivation clauses
44
-
45
- **R1. NeedsLowSugar.** `NeedsLowSugar` holds iff the household condition is `Diabetes`.
46
-
47
- **R2. HighSugarScanned.** `HighSugarScanned` holds iff the scanned product has `sugarPerServing ≥ 10.0`.
48
-
49
- **R3. LowerSugarCandidate(p).** For a product `p`, `LowerSugarCandidate(p)` holds iff `p.sugarTenths < scannedProduct.sugarTenths`.
50
-
51
- **R4. RecommendedAlternative.** `RecommendedAlternative` is the candidate product with minimum `sugarTenths` among all products `p` such that `LowerSugarCandidate(p)` holds.
52
-
53
- **R5. AlternativeLowersSugar.** `AlternativeLowersSugar` holds iff the recommended alternative exists and has strictly lower `sugarTenths` than the scanned product.
54
-
55
- ## Governance clauses
56
-
57
- **G1. AuthorizedUse.** `AuthorizedUse` holds iff:
58
-
59
- 1. the requested action is `odrl:use`;
60
- 2. the requested purpose is `shopping_assist`; and
61
- 3. the authorization time is not later than the expiry time.
62
-
63
- **G2. MarketingProhibited.** `MarketingProhibited` holds iff the policy prohibits distribution for purpose `marketing`.
64
-
65
- **G3. DutyTimely.** `DutyTimely` holds iff the duty-performance time is not later than the expiry time.
66
-
67
- ## Integrity and minimization clauses
68
-
69
- **M1. CanonicalEnvelope.** The canonical envelope string is the JSON serialization of the ordered pair `(insight, policy)` with keys emitted in this exact sequence:
70
-
71
- - insight: `createdAt`, `expiresAt`, `id`, `metric`, `retailer`, `scopeDevice`, `scopeEvent`, `suggestionPolicy`, `threshold`, `type`
72
- - policy: `duty`, `permission`, `profile`, `prohibition`, `type`
73
-
74
- For this case, `threshold` is serialized lexically as `10.0` rather than `10`, because the integrity vector is defined over the exact envelope bytes used by the specialized Delfour driver.
75
-
76
- **M2. PayloadHashMatches.** `PayloadHashMatches` holds iff `SHA-256(CanonicalEnvelope) = declaredPayloadHashSHA256`.
77
-
78
- **M3. SignatureVerifies.** `SignatureVerifies` holds iff the declared HMAC verifies under the agreed verification mode.
79
-
80
- **M4. MinimizationRespected.** `MinimizationRespected` holds iff the serialized insight contains none of the forbidden terms: `diabetes`, `medical`.
81
-
82
- **M5. ScopeComplete.** `ScopeComplete` holds iff the insight contains `scopeDevice`, `scopeEvent`, and `expiresAt`.
83
-
84
- ## Output contract
85
-
86
- **O1. Answer.** A conforming renderer shall expose:
87
-
88
- - the main recommendation sentence
89
- - scanned product
90
- - suggested alternative
91
- - payload hash
92
- - envelope HMAC
93
-
94
- **O2. Reason Why.** A conforming renderer shall explain the household-to-insight desensitization and the scoped shopping purpose.
95
-
96
- **O3. Check.** A conforming renderer shall expose a named yes/no or PASS/FAIL outcome for each of:
97
-
98
- - signatureVerifies
99
- - payloadHashMatches
100
- - minimizationRespected
101
- - scopeComplete
102
- - authorizationAllowed
103
- - highSugarBanner
104
- - alternativeLowersSugar
105
- - dutyTimingConsistent
106
- - marketingProhibited
107
-
108
- ## Reference outcome for this instance
109
-
110
- For the supplied instance:
111
-
112
- - `NeedsLowSugar = true`
113
- - `HighSugarScanned = true`
114
- - `RecommendedAlternative = "Low-Sugar Tea Biscuits"`
115
- - `AlternativeLowersSugar = true`
116
-
117
- The expected ARC report and integrity values are recorded in `delfour.expected.json`.