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.
- package/examples/arcling/README.md +37 -168
- package/package.json +2 -3
- package/examples/arcling/calidor/calidor.data.json +0 -79
- package/examples/arcling/calidor/calidor.expected.json +0 -94
- package/examples/arcling/calidor/calidor.model.go +0 -612
- package/examples/arcling/calidor/calidor.spec.md +0 -166
- package/examples/arcling/delfour/delfour.data.json +0 -67
- package/examples/arcling/delfour/delfour.expected.json +0 -88
- package/examples/arcling/delfour/delfour.model.go +0 -564
- package/examples/arcling/delfour/delfour.spec.md +0 -117
- package/examples/arcling/flandor/flandor.data.json +0 -106
- package/examples/arcling/flandor/flandor.expected.json +0 -98
- package/examples/arcling/flandor/flandor.model.go +0 -655
- package/examples/arcling/flandor/flandor.spec.md +0 -155
- package/examples/arcling/medior/medior.data.json +0 -96
- package/examples/arcling/medior/medior.expected.json +0 -100
- package/examples/arcling/medior/medior.model.go +0 -652
- package/examples/arcling/medior/medior.spec.md +0 -157
- package/test/arcling.test.js +0 -191
|
@@ -1,655 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
// Flandor is a reference Arcling model for the regional retooling-pulse case.
|
|
4
|
-
// It evaluates whether a region needs intervention, selects the lowest-cost
|
|
5
|
-
// eligible package, and emits ARC text or a JSON report.
|
|
6
|
-
|
|
7
|
-
import (
|
|
8
|
-
"crypto/hmac"
|
|
9
|
-
"crypto/sha256"
|
|
10
|
-
"encoding/hex"
|
|
11
|
-
"encoding/json"
|
|
12
|
-
"errors"
|
|
13
|
-
"fmt"
|
|
14
|
-
"os"
|
|
15
|
-
"sort"
|
|
16
|
-
"strings"
|
|
17
|
-
"time"
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
// Data mirrors the input instance shape from flandor.data.json.
|
|
21
|
-
type Data struct {
|
|
22
|
-
CaseName string `json:"caseName"`
|
|
23
|
-
Region string `json:"region"`
|
|
24
|
-
Question string `json:"question"`
|
|
25
|
-
Timestamps Timestamps `json:"timestamps"`
|
|
26
|
-
EvaluationContext EvaluationContext `json:"evaluationContext"`
|
|
27
|
-
Thresholds Thresholds `json:"thresholds"`
|
|
28
|
-
Signals Signals `json:"signals"`
|
|
29
|
-
Budget Budget `json:"budget"`
|
|
30
|
-
Packages []Package `json:"packages"`
|
|
31
|
-
InsightPolicy InsightPolicy `json:"insightPolicy"`
|
|
32
|
-
Integrity Integrity `json:"integrity"`
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
type Timestamps struct {
|
|
36
|
-
CreatedAt string `json:"createdAt"`
|
|
37
|
-
ExpiresAt string `json:"expiresAt"`
|
|
38
|
-
AuthorizedAt string `json:"authorizedAt"`
|
|
39
|
-
DutyPerformedAt string `json:"dutyPerformedAt"`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
type EvaluationContext struct {
|
|
43
|
-
ScopeDevice string `json:"scopeDevice"`
|
|
44
|
-
ScopeEvent string `json:"scopeEvent"`
|
|
45
|
-
Purpose string `json:"purpose"`
|
|
46
|
-
ProhibitedReusePurpose string `json:"prohibitedReusePurpose"`
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
type Thresholds struct {
|
|
50
|
-
ExportOrdersIndexBelow int `json:"exportOrdersIndexBelow"`
|
|
51
|
-
TechnicalVacancyRatePctAbove float64 `json:"technicalVacancyRatePctAbove"`
|
|
52
|
-
GridCongestionHoursAbove int `json:"gridCongestionHoursAbove"`
|
|
53
|
-
ActiveNeedCountAtLeast int `json:"activeNeedCountAtLeast"`
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
type Cluster struct {
|
|
57
|
-
ID string `json:"id"`
|
|
58
|
-
Name string `json:"name"`
|
|
59
|
-
ExportOrdersIndex int `json:"exportOrdersIndex"`
|
|
60
|
-
EnergyIntensity int `json:"energyIntensity"`
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
type LabourMarket struct {
|
|
64
|
-
TechnicalVacancyRatePct float64 `json:"technicalVacancyRatePct"`
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
type Grid struct {
|
|
68
|
-
CongestionHours int `json:"congestionHours"`
|
|
69
|
-
RenewableCurtailmentMWh int `json:"renewableCurtailmentMWh"`
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
type Signals struct {
|
|
73
|
-
Clusters []Cluster `json:"clusters"`
|
|
74
|
-
LabourMarket LabourMarket `json:"labourMarket"`
|
|
75
|
-
Grid Grid `json:"grid"`
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
type Budget struct {
|
|
79
|
-
WindowName string `json:"windowName"`
|
|
80
|
-
MaxMEUR int `json:"maxMEUR"`
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
type Package struct {
|
|
84
|
-
ID string `json:"id"`
|
|
85
|
-
Name string `json:"name"`
|
|
86
|
-
CostMEUR int `json:"costMEUR"`
|
|
87
|
-
WorkerCoverage int `json:"workerCoverage"`
|
|
88
|
-
GridReliefMW int `json:"gridReliefMW"`
|
|
89
|
-
CoversExportWeakness bool `json:"coversExportWeakness"`
|
|
90
|
-
CoversSkillsStrain bool `json:"coversSkillsStrain"`
|
|
91
|
-
CoversGridStress bool `json:"coversGridStress"`
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
type InsightPolicy struct {
|
|
95
|
-
ID string `json:"id"`
|
|
96
|
-
Metric string `json:"metric"`
|
|
97
|
-
Type string `json:"type"`
|
|
98
|
-
SuggestionPolicy string `json:"suggestionPolicy"`
|
|
99
|
-
PolicyType string `json:"policyType"`
|
|
100
|
-
PolicyProfile string `json:"policyProfile"`
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
type Integrity struct {
|
|
104
|
-
HashAlgorithm string `json:"hashAlgorithm"`
|
|
105
|
-
MacAlgorithm string `json:"macAlgorithm"`
|
|
106
|
-
Secret string `json:"secret"`
|
|
107
|
-
VerificationMode string `json:"verificationMode"`
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
type Insight struct {
|
|
111
|
-
CreatedAt string `json:"createdAt"`
|
|
112
|
-
ExpiresAt string `json:"expiresAt"`
|
|
113
|
-
ID string `json:"id"`
|
|
114
|
-
Metric string `json:"metric"`
|
|
115
|
-
Region string `json:"region"`
|
|
116
|
-
ScopeDevice string `json:"scopeDevice"`
|
|
117
|
-
ScopeEvent string `json:"scopeEvent"`
|
|
118
|
-
SuggestionPolicy string `json:"suggestionPolicy"`
|
|
119
|
-
Threshold int `json:"threshold"`
|
|
120
|
-
Type string `json:"type"`
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
type Constraint struct {
|
|
124
|
-
LeftOperand string `json:"leftOperand"`
|
|
125
|
-
Operator string `json:"operator"`
|
|
126
|
-
RightOperand string `json:"rightOperand"`
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
type Duty struct {
|
|
130
|
-
Action string `json:"action"`
|
|
131
|
-
Constraint Constraint `json:"constraint"`
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
type Permission struct {
|
|
135
|
-
Action string `json:"action"`
|
|
136
|
-
Constraint Constraint `json:"constraint"`
|
|
137
|
-
Target string `json:"target"`
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
type Prohibition struct {
|
|
141
|
-
Action string `json:"action"`
|
|
142
|
-
Constraint Constraint `json:"constraint"`
|
|
143
|
-
Target string `json:"target"`
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
type Policy struct {
|
|
147
|
-
Duty Duty `json:"duty"`
|
|
148
|
-
Permission Permission `json:"permission"`
|
|
149
|
-
Profile string `json:"profile"`
|
|
150
|
-
Prohibition Prohibition `json:"prohibition"`
|
|
151
|
-
Type string `json:"type"`
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
type Envelope struct {
|
|
155
|
-
Insight Insight `json:"insight"`
|
|
156
|
-
Policy Policy `json:"policy"`
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
type Derived struct {
|
|
160
|
-
ExportWeakness bool `json:"exportWeakness"`
|
|
161
|
-
SkillsStrain bool `json:"skillsStrain"`
|
|
162
|
-
GridStress bool `json:"gridStress"`
|
|
163
|
-
ActiveNeedCount int `json:"activeNeedCount"`
|
|
164
|
-
NeedsRetoolingPulse bool `json:"needsRetoolingPulse"`
|
|
165
|
-
EligiblePackageIDs []string `json:"eligiblePackageIds"`
|
|
166
|
-
RecommendedPackageID *string `json:"recommendedPackageId"`
|
|
167
|
-
RecommendedPackageName *string `json:"recommendedPackageName"`
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
type IntegrityResult struct {
|
|
171
|
-
CanonicalEnvelope string `json:"canonicalEnvelope"`
|
|
172
|
-
PayloadHashSHA256 string `json:"payloadHashSHA256"`
|
|
173
|
-
EnvelopeHmacSHA256 string `json:"envelopeHmacSHA256"`
|
|
174
|
-
VerificationMode string `json:"verificationMode"`
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
type Answer struct {
|
|
178
|
-
Name string `json:"name"`
|
|
179
|
-
Region string `json:"region"`
|
|
180
|
-
Metric string `json:"metric"`
|
|
181
|
-
ActiveNeedCount int `json:"activeNeedCount"`
|
|
182
|
-
Threshold int `json:"threshold"`
|
|
183
|
-
RecommendedPackage *string `json:"recommendedPackage"`
|
|
184
|
-
BudgetCapMEUR int `json:"budgetCapMEUR"`
|
|
185
|
-
PackageCostMEUR *int `json:"packageCostMEUR"`
|
|
186
|
-
PayloadHashSHA256 string `json:"payloadHashSHA256"`
|
|
187
|
-
EnvelopeHmacSHA256 string `json:"envelopeHmacSHA256"`
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
type Checks struct {
|
|
191
|
-
PayloadHashMatches bool `json:"payloadHashMatches"`
|
|
192
|
-
SignatureVerifies bool `json:"signatureVerifies"`
|
|
193
|
-
ThresholdReached bool `json:"thresholdReached"`
|
|
194
|
-
ScopeComplete bool `json:"scopeComplete"`
|
|
195
|
-
MinimizationRespected bool `json:"minimizationRespected"`
|
|
196
|
-
AuthorizationAllowed bool `json:"authorizationAllowed"`
|
|
197
|
-
DutyTimely bool `json:"dutyTimely"`
|
|
198
|
-
SurveillanceReuseProhibited bool `json:"surveillanceReuseProhibited"`
|
|
199
|
-
PackageWithinBudget bool `json:"packageWithinBudget"`
|
|
200
|
-
PackageCoversAllActiveNeeds bool `json:"packageCoversAllActiveNeeds"`
|
|
201
|
-
LowestCostEligiblePackageChosen bool `json:"lowestCostEligiblePackageChosen"`
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
type Result struct {
|
|
205
|
-
CaseName string `json:"caseName"`
|
|
206
|
-
Derived Derived `json:"derived"`
|
|
207
|
-
Envelope Envelope `json:"envelope"`
|
|
208
|
-
Integrity IntegrityResult `json:"integrity"`
|
|
209
|
-
Answer Answer `json:"answer"`
|
|
210
|
-
ReasonWhy []string `json:"reasonWhy"`
|
|
211
|
-
Checks Checks `json:"checks"`
|
|
212
|
-
AllChecksPass bool `json:"allChecksPass"`
|
|
213
|
-
ArcText string `json:"arcText"`
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
func assertTrue(condition bool, message string) error {
|
|
217
|
-
if !condition {
|
|
218
|
-
return errors.New(message)
|
|
219
|
-
}
|
|
220
|
-
return nil
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
func readJSON(path string) (Data, error) {
|
|
224
|
-
var data Data
|
|
225
|
-
b, err := os.ReadFile(path)
|
|
226
|
-
if err != nil {
|
|
227
|
-
return data, err
|
|
228
|
-
}
|
|
229
|
-
err = json.Unmarshal(b, &data)
|
|
230
|
-
return data, err
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
func parseTime(value string) time.Time {
|
|
234
|
-
t, err := time.Parse(time.RFC3339Nano, value)
|
|
235
|
-
if err != nil {
|
|
236
|
-
panic(err)
|
|
237
|
-
}
|
|
238
|
-
return t
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// stableStringify recursively sorts map keys so the canonical envelope is
|
|
242
|
-
// deterministic across runs and implementations.
|
|
243
|
-
func stableStringify(value any) string {
|
|
244
|
-
switch v := value.(type) {
|
|
245
|
-
case nil:
|
|
246
|
-
return "null"
|
|
247
|
-
case map[string]any:
|
|
248
|
-
keys := make([]string, 0, len(v))
|
|
249
|
-
for key := range v {
|
|
250
|
-
keys = append(keys, key)
|
|
251
|
-
}
|
|
252
|
-
sort.Strings(keys)
|
|
253
|
-
parts := make([]string, 0, len(keys))
|
|
254
|
-
for _, key := range keys {
|
|
255
|
-
parts = append(parts, fmt.Sprintf("%s:%s", stableStringify(key), stableStringify(v[key])))
|
|
256
|
-
}
|
|
257
|
-
return "{" + strings.Join(parts, ",") + "}"
|
|
258
|
-
case []any:
|
|
259
|
-
parts := make([]string, 0, len(v))
|
|
260
|
-
for _, item := range v {
|
|
261
|
-
parts = append(parts, stableStringify(item))
|
|
262
|
-
}
|
|
263
|
-
return "[" + strings.Join(parts, ",") + "]"
|
|
264
|
-
default:
|
|
265
|
-
b, _ := json.Marshal(v)
|
|
266
|
-
return string(b)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// canonicalValue converts typed structs into generic JSON-like values before
|
|
271
|
-
// stable stringification.
|
|
272
|
-
func canonicalValue(value any) any {
|
|
273
|
-
b, _ := json.Marshal(value)
|
|
274
|
-
var out any
|
|
275
|
-
_ = json.Unmarshal(b, &out)
|
|
276
|
-
return out
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// validateInstance performs the structural checks for the 4-file bundle.
|
|
280
|
-
func validateInstance(data Data) error {
|
|
281
|
-
if err := assertTrue(data.CaseName != "", "caseName is required"); err != nil {
|
|
282
|
-
return err
|
|
283
|
-
}
|
|
284
|
-
if err := assertTrue(data.Region != "", "region is required"); err != nil {
|
|
285
|
-
return err
|
|
286
|
-
}
|
|
287
|
-
if err := assertTrue(len(data.Signals.Clusters) > 0, "signals.clusters is required"); err != nil {
|
|
288
|
-
return err
|
|
289
|
-
}
|
|
290
|
-
if err := assertTrue(len(data.Packages) > 0, "packages is required"); err != nil {
|
|
291
|
-
return err
|
|
292
|
-
}
|
|
293
|
-
return nil
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// countTrue is used for the active-need threshold logic in the spec.
|
|
297
|
-
func countTrue(values ...bool) int {
|
|
298
|
-
total := 0
|
|
299
|
-
for _, value := range values {
|
|
300
|
-
if value {
|
|
301
|
-
total++
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return total
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// The R* helpers map directly to named derivation clauses in flandor.spec.md.
|
|
308
|
-
func clauseR1ExportWeakness(data Data) bool {
|
|
309
|
-
for _, cluster := range data.Signals.Clusters {
|
|
310
|
-
if cluster.ExportOrdersIndex < data.Thresholds.ExportOrdersIndexBelow {
|
|
311
|
-
return true
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return false
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
func clauseR2SkillsStrain(data Data) bool {
|
|
318
|
-
return data.Signals.LabourMarket.TechnicalVacancyRatePct > data.Thresholds.TechnicalVacancyRatePctAbove
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
func clauseR3GridStress(data Data) bool {
|
|
322
|
-
return data.Signals.Grid.CongestionHours > data.Thresholds.GridCongestionHoursAbove
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
func clauseR4ActiveNeedCount(exportWeakness, skillsStrain, gridStress bool) int {
|
|
326
|
-
return countTrue(exportWeakness, skillsStrain, gridStress)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
func clauseR5NeedsRetoolingPulse(data Data, activeNeedCount int) bool {
|
|
330
|
-
return activeNeedCount >= data.Thresholds.ActiveNeedCountAtLeast
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// deriveInsight produces the minimized regional signal shared with the recipient.
|
|
334
|
-
func deriveInsight(data Data) Insight {
|
|
335
|
-
return Insight{
|
|
336
|
-
CreatedAt: data.Timestamps.CreatedAt,
|
|
337
|
-
ExpiresAt: data.Timestamps.ExpiresAt,
|
|
338
|
-
ID: data.InsightPolicy.ID,
|
|
339
|
-
Metric: data.InsightPolicy.Metric,
|
|
340
|
-
Region: data.Region,
|
|
341
|
-
ScopeDevice: data.EvaluationContext.ScopeDevice,
|
|
342
|
-
ScopeEvent: data.EvaluationContext.ScopeEvent,
|
|
343
|
-
SuggestionPolicy: data.InsightPolicy.SuggestionPolicy,
|
|
344
|
-
Threshold: data.Thresholds.ActiveNeedCountAtLeast,
|
|
345
|
-
Type: data.InsightPolicy.Type,
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// derivePolicy constructs the usage restrictions paired with the insight.
|
|
350
|
-
func derivePolicy(data Data) Policy {
|
|
351
|
-
return Policy{
|
|
352
|
-
Duty: Duty{
|
|
353
|
-
Action: "odrl:delete",
|
|
354
|
-
Constraint: Constraint{
|
|
355
|
-
LeftOperand: "odrl:dateTime",
|
|
356
|
-
Operator: "odrl:eq",
|
|
357
|
-
RightOperand: data.Timestamps.ExpiresAt,
|
|
358
|
-
},
|
|
359
|
-
},
|
|
360
|
-
Permission: Permission{
|
|
361
|
-
Action: "odrl:use",
|
|
362
|
-
Constraint: Constraint{
|
|
363
|
-
LeftOperand: "odrl:purpose",
|
|
364
|
-
Operator: "odrl:eq",
|
|
365
|
-
RightOperand: data.EvaluationContext.Purpose,
|
|
366
|
-
},
|
|
367
|
-
Target: data.InsightPolicy.ID,
|
|
368
|
-
},
|
|
369
|
-
Profile: data.InsightPolicy.PolicyProfile,
|
|
370
|
-
Prohibition: Prohibition{
|
|
371
|
-
Action: "odrl:distribute",
|
|
372
|
-
Constraint: Constraint{
|
|
373
|
-
LeftOperand: "odrl:purpose",
|
|
374
|
-
Operator: "odrl:eq",
|
|
375
|
-
RightOperand: data.EvaluationContext.ProhibitedReusePurpose,
|
|
376
|
-
},
|
|
377
|
-
Target: data.InsightPolicy.ID,
|
|
378
|
-
},
|
|
379
|
-
Type: data.InsightPolicy.PolicyType,
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// packageCoversAllActiveNeeds checks whether a candidate package addresses every
|
|
384
|
-
// need that is active for this specific instance.
|
|
385
|
-
func packageCoversAllActiveNeeds(pkg Package, exportWeakness, skillsStrain, gridStress bool) bool {
|
|
386
|
-
return (!exportWeakness || pkg.CoversExportWeakness) &&
|
|
387
|
-
(!skillsStrain || pkg.CoversSkillsStrain) &&
|
|
388
|
-
(!gridStress || pkg.CoversGridStress)
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// clauseS1EligiblePackages filters to packages that both fit the budget and
|
|
392
|
-
// cover the active needs.
|
|
393
|
-
func clauseS1EligiblePackages(data Data, exportWeakness, skillsStrain, gridStress bool) []Package {
|
|
394
|
-
eligible := make([]Package, 0)
|
|
395
|
-
for _, pkg := range data.Packages {
|
|
396
|
-
if pkg.CostMEUR <= data.Budget.MaxMEUR && packageCoversAllActiveNeeds(pkg, exportWeakness, skillsStrain, gridStress) {
|
|
397
|
-
eligible = append(eligible, pkg)
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
sort.Slice(eligible, func(i, j int) bool { return eligible[i].CostMEUR < eligible[j].CostMEUR })
|
|
401
|
-
return eligible
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// clauseS2RecommendedPackage applies the tie-breaker: choose the lowest-cost
|
|
405
|
-
// eligible package after sorting by cost.
|
|
406
|
-
func clauseS2RecommendedPackage(data Data, exportWeakness, skillsStrain, gridStress bool) ([]Package, *Package) {
|
|
407
|
-
eligible := clauseS1EligiblePackages(data, exportWeakness, skillsStrain, gridStress)
|
|
408
|
-
if len(eligible) == 0 {
|
|
409
|
-
return eligible, nil
|
|
410
|
-
}
|
|
411
|
-
recommended := eligible[0]
|
|
412
|
-
return eligible, &recommended
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
func clauseG1AuthorizedUse(data Data) bool {
|
|
416
|
-
return data.EvaluationContext.Purpose == "regional_stabilization" && !parseTime(data.Timestamps.AuthorizedAt).After(parseTime(data.Timestamps.ExpiresAt))
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
func clauseG2SurveillanceReuseProhibited(data Data) bool {
|
|
420
|
-
return data.EvaluationContext.ProhibitedReusePurpose == "firm_surveillance"
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
func clauseG3DutyTimely(data Data) bool {
|
|
424
|
-
return !parseTime(data.Timestamps.DutyPerformedAt).After(parseTime(data.Timestamps.ExpiresAt))
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// clauseM1CanonicalEnvelope returns both the structured envelope and the
|
|
428
|
-
// deterministic string hashed/signed by the integrity clauses.
|
|
429
|
-
func clauseM1CanonicalEnvelope(data Data) (Envelope, string) {
|
|
430
|
-
envelope := Envelope{Insight: deriveInsight(data), Policy: derivePolicy(data)}
|
|
431
|
-
return envelope, stableStringify(canonicalValue(envelope))
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
func sha256Hex(text string) string {
|
|
435
|
-
sum := sha256.Sum256([]byte(text))
|
|
436
|
-
return hex.EncodeToString(sum[:])
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
func hmacSHA256Hex(secret, text string) string {
|
|
440
|
-
mac := hmac.New(sha256.New, []byte(secret))
|
|
441
|
-
_, _ = mac.Write([]byte(text))
|
|
442
|
-
return hex.EncodeToString(mac.Sum(nil))
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
func clauseM4MinimizationRespected(insight Insight) bool {
|
|
446
|
-
b, _ := json.Marshal(insight)
|
|
447
|
-
s := strings.ToLower(string(b))
|
|
448
|
-
return !strings.Contains(s, "salary") && !strings.Contains(s, "payroll") && !strings.Contains(s, "invoice") && !strings.Contains(s, "medical") && !strings.Contains(s, "firmname")
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
func clauseM5ScopeComplete(insight Insight) bool {
|
|
452
|
-
return insight.ScopeDevice != "" && insight.ScopeEvent != "" && insight.ExpiresAt != ""
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
func yesNo(value bool) string {
|
|
456
|
-
if value {
|
|
457
|
-
return "PASS"
|
|
458
|
-
}
|
|
459
|
-
return "FAIL"
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// evaluate computes all derived facts, governance checks, integrity values,
|
|
463
|
-
// and presentation fields expected by flandor.expected.json.
|
|
464
|
-
func evaluate(data Data) (Result, error) {
|
|
465
|
-
if err := validateInstance(data); err != nil {
|
|
466
|
-
return Result{}, err
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
exportWeakness := clauseR1ExportWeakness(data)
|
|
470
|
-
skillsStrain := clauseR2SkillsStrain(data)
|
|
471
|
-
gridStress := clauseR3GridStress(data)
|
|
472
|
-
activeNeedCount := clauseR4ActiveNeedCount(exportWeakness, skillsStrain, gridStress)
|
|
473
|
-
needsRetoolingPulse := clauseR5NeedsRetoolingPulse(data, activeNeedCount)
|
|
474
|
-
|
|
475
|
-
envelope, canonicalEnvelope := clauseM1CanonicalEnvelope(data)
|
|
476
|
-
payloadHashSHA256 := sha256Hex(canonicalEnvelope)
|
|
477
|
-
envelopeHmacSHA256 := hmacSHA256Hex(data.Integrity.Secret, canonicalEnvelope)
|
|
478
|
-
|
|
479
|
-
eligible, recommended := clauseS2RecommendedPackage(data, exportWeakness, skillsStrain, gridStress)
|
|
480
|
-
recommendedID := (*string)(nil)
|
|
481
|
-
recommendedName := (*string)(nil)
|
|
482
|
-
packageCostMEUR := (*int)(nil)
|
|
483
|
-
if recommended != nil {
|
|
484
|
-
recommendedID = &recommended.ID
|
|
485
|
-
recommendedName = &recommended.Name
|
|
486
|
-
packageCostMEUR = &recommended.CostMEUR
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
checks := Checks{
|
|
490
|
-
PayloadHashMatches: payloadHashSHA256 == sha256Hex(canonicalEnvelope),
|
|
491
|
-
SignatureVerifies: data.Integrity.VerificationMode == "trustedPrecomputedInput" && envelopeHmacSHA256 == hmacSHA256Hex(data.Integrity.Secret, canonicalEnvelope),
|
|
492
|
-
ThresholdReached: needsRetoolingPulse,
|
|
493
|
-
ScopeComplete: clauseM5ScopeComplete(envelope.Insight),
|
|
494
|
-
MinimizationRespected: clauseM4MinimizationRespected(envelope.Insight),
|
|
495
|
-
AuthorizationAllowed: clauseG1AuthorizedUse(data),
|
|
496
|
-
DutyTimely: clauseG3DutyTimely(data),
|
|
497
|
-
SurveillanceReuseProhibited: clauseG2SurveillanceReuseProhibited(data),
|
|
498
|
-
PackageWithinBudget: recommended != nil && recommended.CostMEUR <= data.Budget.MaxMEUR,
|
|
499
|
-
PackageCoversAllActiveNeeds: recommended != nil && packageCoversAllActiveNeeds(*recommended, exportWeakness, skillsStrain, gridStress),
|
|
500
|
-
LowestCostEligiblePackageChosen: recommended != nil && recommended.ID == eligible[0].ID,
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
clusterBits := make([]string, 0, len(data.Signals.Clusters))
|
|
504
|
-
for _, cluster := range data.Signals.Clusters {
|
|
505
|
-
clusterBits = append(clusterBits, fmt.Sprintf("%s=%d", cluster.Name, cluster.ExportOrdersIndex))
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
reasonWhy := []string{
|
|
509
|
-
fmt.Sprintf("ExportWeakness holds because at least one cluster has exportOrdersIndex < %d (%s).", data.Thresholds.ExportOrdersIndexBelow, strings.Join(clusterBits, ", ")),
|
|
510
|
-
fmt.Sprintf("SkillsStrain holds because the technical vacancy rate is %g%% and the threshold is > %g%%.", data.Signals.LabourMarket.TechnicalVacancyRatePct, data.Thresholds.TechnicalVacancyRatePctAbove),
|
|
511
|
-
fmt.Sprintf("GridStress holds because congestion hours = %d and the threshold is > %d.", data.Signals.Grid.CongestionHours, data.Thresholds.GridCongestionHoursAbove),
|
|
512
|
-
"The recommendation rule selects the least-cost package that covers every active need and remains within budget.",
|
|
513
|
-
func() string {
|
|
514
|
-
if recommended != nil {
|
|
515
|
-
return fmt.Sprintf("The selected package is \"%s\" with cost €%dM, workerCoverage=%d, gridReliefMW=%d.", recommended.Name, recommended.CostMEUR, recommended.WorkerCoverage, recommended.GridReliefMW)
|
|
516
|
-
}
|
|
517
|
-
return "No eligible package exists within budget."
|
|
518
|
-
}(),
|
|
519
|
-
fmt.Sprintf("Use is permitted only for purpose \"%s\" and expires at %s.", data.EvaluationContext.Purpose, data.Timestamps.ExpiresAt),
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
answer := Answer{
|
|
523
|
-
Name: data.CaseName,
|
|
524
|
-
Region: data.Region,
|
|
525
|
-
Metric: data.InsightPolicy.Metric,
|
|
526
|
-
ActiveNeedCount: activeNeedCount,
|
|
527
|
-
Threshold: data.Thresholds.ActiveNeedCountAtLeast,
|
|
528
|
-
RecommendedPackage: recommendedName,
|
|
529
|
-
BudgetCapMEUR: data.Budget.MaxMEUR,
|
|
530
|
-
PackageCostMEUR: packageCostMEUR,
|
|
531
|
-
PayloadHashSHA256: payloadHashSHA256,
|
|
532
|
-
EnvelopeHmacSHA256: envelopeHmacSHA256,
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
arcLines := []string{
|
|
536
|
-
"=== Answer ===",
|
|
537
|
-
fmt.Sprintf("Name: %s", answer.Name),
|
|
538
|
-
fmt.Sprintf("Region: %s", answer.Region),
|
|
539
|
-
fmt.Sprintf("Metric: %s", answer.Metric),
|
|
540
|
-
fmt.Sprintf("Active need count: %d/%d", answer.ActiveNeedCount, answer.Threshold),
|
|
541
|
-
fmt.Sprintf("Recommended package: %s", derefString(answer.RecommendedPackage)),
|
|
542
|
-
fmt.Sprintf("Budget cap: €%dM", answer.BudgetCapMEUR),
|
|
543
|
-
fmt.Sprintf("Package cost: €%sM", derefIntString(answer.PackageCostMEUR)),
|
|
544
|
-
fmt.Sprintf("Payload SHA-256: %s", answer.PayloadHashSHA256),
|
|
545
|
-
fmt.Sprintf("Envelope HMAC-SHA-256: %s", answer.EnvelopeHmacSHA256),
|
|
546
|
-
"",
|
|
547
|
-
"=== Reason Why ===",
|
|
548
|
-
}
|
|
549
|
-
arcLines = append(arcLines, reasonWhy...)
|
|
550
|
-
arcLines = append(arcLines, "", "=== Check ===")
|
|
551
|
-
checkOrder := []struct {
|
|
552
|
-
name string
|
|
553
|
-
ok bool
|
|
554
|
-
}{
|
|
555
|
-
{"payloadHashMatches", checks.PayloadHashMatches},
|
|
556
|
-
{"signatureVerifies", checks.SignatureVerifies},
|
|
557
|
-
{"thresholdReached", checks.ThresholdReached},
|
|
558
|
-
{"scopeComplete", checks.ScopeComplete},
|
|
559
|
-
{"minimizationRespected", checks.MinimizationRespected},
|
|
560
|
-
{"authorizationAllowed", checks.AuthorizationAllowed},
|
|
561
|
-
{"dutyTimely", checks.DutyTimely},
|
|
562
|
-
{"surveillanceReuseProhibited", checks.SurveillanceReuseProhibited},
|
|
563
|
-
{"packageWithinBudget", checks.PackageWithinBudget},
|
|
564
|
-
{"packageCoversAllActiveNeeds", checks.PackageCoversAllActiveNeeds},
|
|
565
|
-
{"lowestCostEligiblePackageChosen", checks.LowestCostEligiblePackageChosen},
|
|
566
|
-
}
|
|
567
|
-
for _, item := range checkOrder {
|
|
568
|
-
arcLines = append(arcLines, fmt.Sprintf("- %s: %s", yesNo(item.ok), item.name))
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
allChecksPass := checks.PayloadHashMatches && checks.SignatureVerifies && checks.ThresholdReached && checks.ScopeComplete && checks.MinimizationRespected && checks.AuthorizationAllowed && checks.DutyTimely && checks.SurveillanceReuseProhibited && checks.PackageWithinBudget && checks.PackageCoversAllActiveNeeds && checks.LowestCostEligiblePackageChosen
|
|
572
|
-
|
|
573
|
-
return Result{
|
|
574
|
-
CaseName: data.CaseName,
|
|
575
|
-
Derived: Derived{
|
|
576
|
-
ExportWeakness: exportWeakness,
|
|
577
|
-
SkillsStrain: skillsStrain,
|
|
578
|
-
GridStress: gridStress,
|
|
579
|
-
ActiveNeedCount: activeNeedCount,
|
|
580
|
-
NeedsRetoolingPulse: needsRetoolingPulse,
|
|
581
|
-
EligiblePackageIDs: func() []string {
|
|
582
|
-
ids := make([]string, 0, len(eligible))
|
|
583
|
-
for _, pkg := range eligible {
|
|
584
|
-
ids = append(ids, pkg.ID)
|
|
585
|
-
}
|
|
586
|
-
return ids
|
|
587
|
-
}(),
|
|
588
|
-
RecommendedPackageID: recommendedID,
|
|
589
|
-
RecommendedPackageName: recommendedName,
|
|
590
|
-
},
|
|
591
|
-
Envelope: envelope,
|
|
592
|
-
Integrity: IntegrityResult{
|
|
593
|
-
CanonicalEnvelope: canonicalEnvelope,
|
|
594
|
-
PayloadHashSHA256: payloadHashSHA256,
|
|
595
|
-
EnvelopeHmacSHA256: envelopeHmacSHA256,
|
|
596
|
-
VerificationMode: data.Integrity.VerificationMode,
|
|
597
|
-
},
|
|
598
|
-
Answer: answer,
|
|
599
|
-
ReasonWhy: reasonWhy,
|
|
600
|
-
Checks: checks,
|
|
601
|
-
AllChecksPass: allChecksPass,
|
|
602
|
-
ArcText: strings.Join(arcLines, "\n"),
|
|
603
|
-
}, nil
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
func derefString(value *string) string {
|
|
607
|
-
if value == nil {
|
|
608
|
-
return "<nil>"
|
|
609
|
-
}
|
|
610
|
-
return *value
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
func derefIntString(value *int) string {
|
|
614
|
-
if value == nil {
|
|
615
|
-
return "<nil>"
|
|
616
|
-
}
|
|
617
|
-
return fmt.Sprintf("%d", *value)
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// main is the CLI entry point used by the Arcling test runner.
|
|
621
|
-
func main() {
|
|
622
|
-
inputPath := "flandor.data.json"
|
|
623
|
-
jsonMode := false
|
|
624
|
-
for _, arg := range os.Args[1:] {
|
|
625
|
-
if arg == "--json" {
|
|
626
|
-
jsonMode = true
|
|
627
|
-
} else if !strings.HasPrefix(arg, "--") {
|
|
628
|
-
inputPath = arg
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
data, err := readJSON(inputPath)
|
|
633
|
-
if err != nil {
|
|
634
|
-
fmt.Fprintln(os.Stderr, err)
|
|
635
|
-
os.Exit(1)
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
result, err := evaluate(data)
|
|
639
|
-
if err != nil {
|
|
640
|
-
fmt.Fprintln(os.Stderr, err)
|
|
641
|
-
os.Exit(1)
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
if jsonMode {
|
|
645
|
-
enc := json.NewEncoder(os.Stdout)
|
|
646
|
-
enc.SetIndent("", " ")
|
|
647
|
-
_ = enc.Encode(result)
|
|
648
|
-
} else {
|
|
649
|
-
fmt.Println(result.ArcText)
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
if !result.AllChecksPass {
|
|
653
|
-
os.Exit(1)
|
|
654
|
-
}
|
|
655
|
-
}
|