arxo-codegen 0.4.0__py3-none-any.whl

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.
Files changed (152) hide show
  1. arxo_codegen/__init__.py +5 -0
  2. arxo_codegen/__main__.py +7 -0
  3. arxo_codegen/active-outcomes.json +6837 -0
  4. arxo_codegen/check.py +158 -0
  5. arxo_codegen/check_active_canaries.py +262 -0
  6. arxo_codegen/check_api.py +76 -0
  7. arxo_codegen/check_conformance.py +414 -0
  8. arxo_codegen/check_matrix.py +131 -0
  9. arxo_codegen/check_packages.py +281 -0
  10. arxo_codegen/check_semantic_probes.py +293 -0
  11. arxo_codegen/check_xlsx.py +320 -0
  12. arxo_codegen/consumer_runner.py +316 -0
  13. arxo_codegen/core_support.py +203 -0
  14. arxo_codegen/coverage.py +341 -0
  15. arxo_codegen/deliver.py +180 -0
  16. arxo_codegen/examples/xlsx/pricing.lawir.json +409 -0
  17. arxo_codegen/examples/xlsx/pricing.xlsx.json +52 -0
  18. arxo_codegen/excel_plan.py +602 -0
  19. arxo_codegen/harness/verify/ci/gates/backends/check_backends.py +1248 -0
  20. arxo_codegen/harness/verify/ci/gates/backends/l1_fixtures.py +463 -0
  21. arxo_codegen/inert-nodes.json +62 -0
  22. arxo_codegen/inert.py +91 -0
  23. arxo_codegen/inputs.py +59 -0
  24. arxo_codegen/package.py +565 -0
  25. arxo_codegen/plan.py +4053 -0
  26. arxo_codegen/recertify_registry.py +100 -0
  27. arxo_codegen/runtime-cert.json +5259 -0
  28. arxo_codegen/runtime_cert.py +233 -0
  29. arxo_codegen/survey.py +272 -0
  30. arxo_codegen/target_runner.py +101 -0
  31. arxo_codegen/targets/bsl/cert.bsl +113 -0
  32. arxo_codegen/targets/bsl/emit.py +356 -0
  33. arxo_codegen/targets/bsl/json.bsl +243 -0
  34. arxo_codegen/targets/bsl/runner.bsl +210 -0
  35. arxo_codegen/targets/bsl/runtime.bsl +858 -0
  36. arxo_codegen/targets/cs/Cert.cs +90 -0
  37. arxo_codegen/targets/cs/Runner.cs +154 -0
  38. arxo_codegen/targets/cs/Runtime.cs +243 -0
  39. arxo_codegen/targets/cs/emit.py +223 -0
  40. arxo_codegen/targets/dart/cert.dart +99 -0
  41. arxo_codegen/targets/dart/emit.py +206 -0
  42. arxo_codegen/targets/dart/json.dart +166 -0
  43. arxo_codegen/targets/dart/runner.dart +154 -0
  44. arxo_codegen/targets/dart/runtime.dart +293 -0
  45. arxo_codegen/targets/erl/cert.erl +64 -0
  46. arxo_codegen/targets/erl/emit.py +216 -0
  47. arxo_codegen/targets/erl/rt.erl +238 -0
  48. arxo_codegen/targets/erl/runner.erl +130 -0
  49. arxo_codegen/targets/ex/cert.exs +62 -0
  50. arxo_codegen/targets/ex/emit.py +204 -0
  51. arxo_codegen/targets/ex/runner.exs +124 -0
  52. arxo_codegen/targets/ex/runtime.ex +227 -0
  53. arxo_codegen/targets/go/apiBase.go +483 -0
  54. arxo_codegen/targets/go/apicheck.go +97 -0
  55. arxo_codegen/targets/go/cert.go +218 -0
  56. arxo_codegen/targets/go/emit.py +1806 -0
  57. arxo_codegen/targets/go/evaluate.go +1826 -0
  58. arxo_codegen/targets/go/runner.go +38 -0
  59. arxo_codegen/targets/go/runtime.go +2612 -0
  60. arxo_codegen/targets/java/Cert.java +91 -0
  61. arxo_codegen/targets/java/Json.java +148 -0
  62. arxo_codegen/targets/java/Runner.java +151 -0
  63. arxo_codegen/targets/java/Runtime.java +243 -0
  64. arxo_codegen/targets/java/emit.py +198 -0
  65. arxo_codegen/targets/kt/Cert.kt +67 -0
  66. arxo_codegen/targets/kt/Json.kt +103 -0
  67. arxo_codegen/targets/kt/Runner.kt +126 -0
  68. arxo_codegen/targets/kt/Runtime.kt +196 -0
  69. arxo_codegen/targets/kt/emit.py +195 -0
  70. arxo_codegen/targets/lua/cert.lua +82 -0
  71. arxo_codegen/targets/lua/emit.py +250 -0
  72. arxo_codegen/targets/lua/json.lua +133 -0
  73. arxo_codegen/targets/lua/runner.lua +167 -0
  74. arxo_codegen/targets/lua/runtime.lua +541 -0
  75. arxo_codegen/targets/php/cert.php +84 -0
  76. arxo_codegen/targets/php/emit.py +229 -0
  77. arxo_codegen/targets/php/json.php +144 -0
  78. arxo_codegen/targets/php/runner.php +189 -0
  79. arxo_codegen/targets/php/runtime.php +660 -0
  80. arxo_codegen/targets/py/api_base.py +334 -0
  81. arxo_codegen/targets/py/apicheck.py +51 -0
  82. arxo_codegen/targets/py/cert.py +140 -0
  83. arxo_codegen/targets/py/emit.py +1140 -0
  84. arxo_codegen/targets/py/evaluate.py +980 -0
  85. arxo_codegen/targets/py/runner.py +38 -0
  86. arxo_codegen/targets/py/runtime.py +1634 -0
  87. arxo_codegen/targets/rb/cert.rb +73 -0
  88. arxo_codegen/targets/rb/emit.py +203 -0
  89. arxo_codegen/targets/rb/runner.rb +130 -0
  90. arxo_codegen/targets/rb/runtime.rb +255 -0
  91. arxo_codegen/targets/rs/Cargo.lock +142 -0
  92. arxo_codegen/targets/rs/Cargo.toml +16 -0
  93. arxo_codegen/targets/rs/cert.rs +106 -0
  94. arxo_codegen/targets/rs/emit.py +233 -0
  95. arxo_codegen/targets/rs/prefetch.rs +1 -0
  96. arxo_codegen/targets/rs/runner.rs +181 -0
  97. arxo_codegen/targets/rs/runtime.rs +448 -0
  98. arxo_codegen/targets/scala/Cert.scala +77 -0
  99. arxo_codegen/targets/scala/Json.scala +140 -0
  100. arxo_codegen/targets/scala/Runner.scala +121 -0
  101. arxo_codegen/targets/scala/Runtime.scala +269 -0
  102. arxo_codegen/targets/scala/emit.py +193 -0
  103. arxo_codegen/targets/swift/cert.swift +105 -0
  104. arxo_codegen/targets/swift/emit.py +231 -0
  105. arxo_codegen/targets/swift/json.swift +208 -0
  106. arxo_codegen/targets/swift/runner.swift +133 -0
  107. arxo_codegen/targets/swift/runtime.swift +528 -0
  108. arxo_codegen/targets/ts/apiBase.ts +250 -0
  109. arxo_codegen/targets/ts/apicheck.ts +37 -0
  110. arxo_codegen/targets/ts/cert.ts +96 -0
  111. arxo_codegen/targets/ts/emit.py +1214 -0
  112. arxo_codegen/targets/ts/evaluate.ts +894 -0
  113. arxo_codegen/targets/ts/package-lock.json +45 -0
  114. arxo_codegen/targets/ts/package.json +9 -0
  115. arxo_codegen/targets/ts/runner.ts +31 -0
  116. arxo_codegen/targets/ts/runtime.ts +1369 -0
  117. arxo_codegen/targets/xlsx/README.ru.md +176 -0
  118. arxo_codegen/targets/xlsx/emit.py +198 -0
  119. arxo_codegen/temporal.py +59 -0
  120. arxo_codegen/test_alias_types.py +171 -0
  121. arxo_codegen/test_api_contract.py +89 -0
  122. arxo_codegen/test_calendar_policy.py +48 -0
  123. arxo_codegen/test_check.py +49 -0
  124. arxo_codegen/test_check_active_canaries.py +96 -0
  125. arxo_codegen/test_check_conformance.py +132 -0
  126. arxo_codegen/test_check_semantic_probes.py +85 -0
  127. arxo_codegen/test_check_xlsx.py +52 -0
  128. arxo_codegen/test_consumer_runner.py +175 -0
  129. arxo_codegen/test_core_support.py +80 -0
  130. arxo_codegen/test_e0212_decision_time.py +63 -0
  131. arxo_codegen/test_head_aggregate.py +71 -0
  132. arxo_codegen/test_inert.py +143 -0
  133. arxo_codegen/test_instant_types.py +113 -0
  134. arxo_codegen/test_key_conflict_scope.py +92 -0
  135. arxo_codegen/test_key_conflict_targets.py +17 -0
  136. arxo_codegen/test_package_layout.py +163 -0
  137. arxo_codegen/test_plan_language_version.py +44 -0
  138. arxo_codegen/test_positions_refusal.py +43 -0
  139. arxo_codegen/test_recertify_registry.py +77 -0
  140. arxo_codegen/test_ruby_target.py +41 -0
  141. arxo_codegen/test_runtime_cert.py +83 -0
  142. arxo_codegen/test_secondary_key_conflict_scope.py +17 -0
  143. arxo_codegen/test_stage_r.py +108 -0
  144. arxo_codegen/test_standalone_inputs.py +71 -0
  145. arxo_codegen/test_support.py +150 -0
  146. arxo_codegen/test_survey_inputs.py +115 -0
  147. arxo_codegen-0.4.0.dist-info/LICENSE +202 -0
  148. arxo_codegen-0.4.0.dist-info/METADATA +1673 -0
  149. arxo_codegen-0.4.0.dist-info/RECORD +152 -0
  150. arxo_codegen-0.4.0.dist-info/WHEEL +5 -0
  151. arxo_codegen-0.4.0.dist-info/entry_points.txt +13 -0
  152. arxo_codegen-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1826 @@
1
+ package main
2
+
3
+ // Единый вычислительный вход Go-цели (DECISION-0423 §2.2).
4
+ //
5
+ // Дело и вопрос приходят канонической JSON-записью (§168, §172) — той же, что у
6
+ // векторов и оракула, — и `evaluate` возвращает ответ исполняемого профиля той же
7
+ // JSON-строкой, что раньше печатал раннер. Раннер и API вызывают только его,
8
+ // поэтому разбор сырых литералов §208, сверка аргументов §189, разворот контекста,
9
+ // насыщение и лестница статусов §175 у них одни. Состояние — на вызов (§2.3):
10
+ // модель строит `newModel()`, контекст вычисления живёт в ней, сопутствующие
11
+ // множества — в `evaluation`; глобального состояния и мьютекса нет. Ответ — БЕЗ
12
+ // доказательства и хэшей: профиль core-executable.
13
+
14
+ import (
15
+ "bytes"
16
+ "encoding/json"
17
+ "fmt"
18
+ "math/big"
19
+ "sort"
20
+ "strings"
21
+ )
22
+
23
+ // Model — хуки экземпляра модели (DECISION-0423 §2.3): `newModel()` порождённого
24
+ // model.go строит таблицы и правила замыканиями над ними и заполняет эти поля.
25
+ type Model struct {
26
+ NODE_IDS map[string]bool
27
+ ctx *evalContext
28
+ TABLES map[string][][]Val
29
+ PARAMS map[string][]string
30
+ CONSTS map[string]constDecl
31
+ BASES map[string]string
32
+ FUNCTIONS map[string]funcDecl
33
+ A3_TYPES map[string]string
34
+ KEY_READERS map[string][]string
35
+ CARRIER_EDGES map[string][]string
36
+ saturate func()
37
+ register func(urn string, row []Val)
38
+ registerNegative func(urn string, row []Val)
39
+ packageFacts func()
40
+ keyConflicts func() int
41
+ keyConflictPredicates func() map[string]bool
42
+ truthOf func(urn string, row []Val) string
43
+ establishedRows func(urn string) [][]Val
44
+ ruleIssues func() []string
45
+ normIssues func() []string
46
+ normPositions func() ([]normPosition, bool)
47
+ blockedEffect func(pred string, row []Val) bool
48
+ calendarEnv func() (string, map[string]interface{}, []Snapshot, bool)
49
+ backchainEnv func() ([]interface{}, map[string]interface{}, map[string]bool, bool)
50
+ judgmentModel func() jModel
51
+ setStatusTime func(t string)
52
+ }
53
+
54
+ // evaluation — одно вычисление: экземпляр модели, дело и вопрос, сопутствующие множества.
55
+ type evaluation struct {
56
+ m *Model
57
+ caseRaw, queryRaw []byte
58
+ issues []string
59
+ typeErrorPredicates map[string]bool
60
+ // Запись строки, поданная делом как сущность и как Text, — в рантайме одно
61
+ // значение, у оракула — разные атомы. Для печати результата вид берётся по
62
+ // тому, как запись подана; одна запись в обеих ролях — названный отказ.
63
+ entities, texts map[string]bool
64
+ lastTermCodes map[string]bool
65
+ hooks jHooks
66
+ api bool // разрешён внутренний вид `rows` (DECISION-0423 §2.1)
67
+ }
68
+
69
+ // RefusedError — названный отказ среза (DECISION-0423 §2.5): значение `error`,
70
+ // распознаваемое `errors.As`; паники на границе входа нет.
71
+ type RefusedError struct{ Message string }
72
+
73
+ func (r *RefusedError) Error() string { return r.Message }
74
+
75
+ // evaluate — ответ на вопрос по делу JSON-строкой; названный отказ — *RefusedError.
76
+ func evaluate(caseJSON, queryJSON []byte) (string, error) {
77
+ return evaluateWith(caseJSON, queryJSON, false)
78
+ }
79
+
80
+ // evaluateRows — то же с внутренним видом API `rows`, которого протокол раннера не знает.
81
+ func evaluateRows(caseJSON, queryJSON []byte) (string, error) {
82
+ return evaluateWith(caseJSON, queryJSON, true)
83
+ }
84
+
85
+ func evaluateWith(caseJSON, queryJSON []byte, api bool) (answer string, err error) {
86
+ var requested struct {
87
+ Kind string `json:"kind"`
88
+ }
89
+ if json.Unmarshal(queryJSON, &requested) == nil && requested.Kind == "positions" {
90
+ return "", &RefusedError{"POSITIONS_UNAVAILABLE: positions requires current norm ranks (W6)"}
91
+ }
92
+ defer func() {
93
+ if r := recover(); r != nil {
94
+ if msg, ok := r.(Refused); ok {
95
+ answer, err = "", &RefusedError{string(msg)}
96
+ return
97
+ }
98
+ if te, ok := r.(termError); ok {
99
+ // Ошибка значения вне вопроса `term` и вне правил модели (гард
100
+ // comprehension вопроса) — названный отказ, а не крах.
101
+ answer, err = "", &RefusedError{"ошибка значения вне среза раннера: " + te.code}
102
+ return
103
+ }
104
+ panic(r)
105
+ }
106
+ }()
107
+ validateTemporalCase(caseJSON)
108
+ ev := &evaluation{m: newModel(), caseRaw: caseJSON, queryRaw: queryJSON, issues: []string{},
109
+ typeErrorPredicates: map[string]bool{}, entities: map[string]bool{}, texts: map[string]bool{},
110
+ lastTermCodes: map[string]bool{}, api: api}
111
+ var admissionCase, admissionQuery map[string]interface{}
112
+ _ = json.Unmarshal(caseJSON, &admissionCase)
113
+ _ = json.Unmarshal(queryJSON, &admissionQuery)
114
+ assertions, _ := admissionCase["assertions"].([]interface{})
115
+ for _, value := range assertions {
116
+ a, _ := value.(map[string]interface{})
117
+ if ev.m.NODE_IDS[str(a["id"])] {
118
+ refuse("LDC-E1382: case assertion id collides with a program node")
119
+ }
120
+ }
121
+ admitNumericInputs(admissionCase, admissionQuery, len(ev.m.judgmentModel().decls()) > 0)
122
+ // Обход запроса судье §47.3 (DECISION-0274): переводы термов раннера для рантайма;
123
+ // множества сущностей и текстов дела обходом не двигаются.
124
+ ev.hooks = jHooks{
125
+ val: func(a map[string]interface{}) Val { return ev.typedArg(a).v },
126
+ cell: func(pred string, col int, v Val) map[string]interface{} {
127
+ var out map[string]interface{}
128
+ d := json.NewDecoder(strings.NewReader(literalOf(ev.slotTyped(pred, col, v))))
129
+ d.UseNumber()
130
+ if err := d.Decode(&out); err != nil {
131
+ panic(err)
132
+ }
133
+ return out
134
+ },
135
+ }
136
+ return ev.run(), nil
137
+ }
138
+
139
+ // Обобщённый разбор JSON: числа — json.Number, чтобы длинное целое не теряло знаков.
140
+ func decodeGeneric(b []byte) map[string]interface{} {
141
+ d := json.NewDecoder(bytes.NewReader(b))
142
+ d.UseNumber()
143
+ var v map[string]interface{}
144
+ if err := d.Decode(&v); err != nil {
145
+ panic(err)
146
+ }
147
+ return v
148
+ }
149
+
150
+ func mustUnmarshal(b []byte, v interface{}) {
151
+ if err := json.Unmarshal(b, v); err != nil {
152
+ panic(err)
153
+ }
154
+ }
155
+
156
+ type argJSON struct {
157
+ Kind string `json:"kind"`
158
+ ID string `json:"id"`
159
+ Value json.RawMessage `json:"value"`
160
+ Currency string `json:"currency"`
161
+ Unit string `json:"unit"`
162
+ Type struct {
163
+ Name string `json:"name"`
164
+ } `json:"type"`
165
+ }
166
+
167
+ type litJSON struct {
168
+ Predicate string `json:"predicate"`
169
+ Polarity string `json:"polarity"`
170
+ Args []argJSON `json:"args"`
171
+ }
172
+
173
+ func literal(raw json.RawMessage) string {
174
+ if len(raw) > 0 && raw[0] == '"' {
175
+ var s string
176
+ if err := json.Unmarshal(raw, &s); err != nil {
177
+ panic(err)
178
+ }
179
+ return s
180
+ }
181
+ return string(raw)
182
+ }
183
+
184
+ // §208/§48 (errata E-0197, E-0202, E-0203): вид JSON-носителя сырого поля
185
+ // литерала и его текст — "s" (JSON-строка), "i" (целое JSON-число канона),
186
+ // "x" (любая другая форма, включая отсутствие поля: схема LiteralTerm не
187
+ // требует `value`). Отсюда и только отсюда читают `lit*` рантайма.
188
+ func rawOfMessage(raw json.RawMessage) (string, string) {
189
+ s := strings.TrimSpace(string(raw))
190
+ if s == "" || s == "null" || s == "true" || s == "false" {
191
+ return "x", ""
192
+ }
193
+ if s[0] == '"' {
194
+ var v string
195
+ if err := json.Unmarshal(raw, &v); err != nil {
196
+ return "x", ""
197
+ }
198
+ return "s", v
199
+ }
200
+ if s[0] == '-' || (s[0] >= '0' && s[0] <= '9') {
201
+ return canonNumberText(s)
202
+ }
203
+ return "x", ""
204
+ }
205
+
206
+ // То же по значению обобщённого разбора: числа приходят `json.Number`
207
+ // (`UseNumber`), то есть текстом исходного литерала, а не float64.
208
+ func rawOfValue(v interface{}) (string, string) {
209
+ switch x := v.(type) {
210
+ case string:
211
+ return "s", x
212
+ case json.Number:
213
+ return canonNumberText(x.String())
214
+ }
215
+ return "x", ""
216
+ }
217
+
218
+ func (ev *evaluation) toVal(a argJSON) Val {
219
+ if a.Kind == "entity_ref" {
220
+ ev.entities[a.ID] = true
221
+ return a.ID
222
+ }
223
+ if a.Kind == "const_ref" {
224
+ // §54: разыменование предшествует исполнению; ниже ссылок нет.
225
+ c, ok := ev.m.CONSTS[a.ID]
226
+ if !ok {
227
+ // Член перечисления §38 либо имя без объявления: оракул оставляет
228
+ // такой const_ref в ключе атома как есть — значение есть идентичность.
229
+ return sym(a.ID)
230
+ }
231
+ return c.Value
232
+ }
233
+ if a.Kind != "value" {
234
+ refuse("аргумент вне среза раннера: " + a.Kind)
235
+ }
236
+ // §208/§48 (errata E-0197, E-0202, E-0203): сырое поле литерала читается по
237
+ // грамматике §208, а не байтами `json.RawMessage`: до errata литерал без
238
+ // `value` выходил у go значением "" (а у ts — "undefined"), то есть ответом,
239
+ // а не диагностикой.
240
+ k, text := rawOfMessage(a.Value)
241
+ switch a.Type.Name {
242
+ case "urn:law:std#Money":
243
+ ck, ctext := "s", a.Currency
244
+ return litMoney(k, text, ck, ctext)
245
+ case "urn:law:std#Decimal":
246
+ return litDec(k, text)
247
+ case "urn:law:std#Integer":
248
+ return litInt(k, text)
249
+ case "urn:law:std#Text":
250
+ s := litText(k, text)
251
+ ev.texts[s] = true
252
+ return s
253
+ case "urn:law:std#Date":
254
+ return date(litText(k, text))
255
+ case "urn:law:std#Instant":
256
+ return instant(litText(k, text))
257
+ case "urn:law:std#Rational":
258
+ // §168.1 (DECISION-0252 п. 8): только каноническая запись, иная — отказ.
259
+ return rat(litText(k, text))
260
+ case "urn:law:std#Quantity":
261
+ // §49 v1 (DECISION-0256 п. 7): значение и тег единицы как записаны.
262
+ if a.Unit != "" {
263
+ return litQty(k, text, "s", a.Unit)
264
+ }
265
+ case "urn:law:std#Boolean":
266
+ // DECISION-0266: только JSON-булево; иная запись у оракула — TYPE_ERROR при чтении значения.
267
+ var b bool
268
+ if err := json.Unmarshal(a.Value, &b); err != nil {
269
+ refuse("значение Boolean не true/false вне среза раннера (§48)")
270
+ }
271
+ return b
272
+ case "urn:law:std#Natural":
273
+ // DECISION-0266: неотрицательное целое; иное у оракула — TYPE_ERROR при чтении значения.
274
+ if k != "i" || strings.HasPrefix(text, "-") {
275
+ refuse("значение Natural не неотрицательное целое вне среза раннера (§48)")
276
+ }
277
+ return litInt(k, text)
278
+ }
279
+ refuse("аргумент вне среза раннера: " + a.Type.Name)
280
+ return nil
281
+ }
282
+
283
+ // Тип аргумента, если известен: у value — из узла, у const_ref — из
284
+ // объявления константы; entity_ref типа не несёт (как у оракула).
285
+ func (ev *evaluation) argType(a argJSON) (string, bool) {
286
+ if a.Kind == "value" {
287
+ return a.Type.Name, true
288
+ }
289
+ if a.Kind == "const_ref" {
290
+ // Оракул разыменовывает константы §54 до сверки: сущность и член
291
+ // перечисления типа не несут, тип — только у константы std-типа.
292
+ c, ok := ev.m.CONSTS[a.ID]
293
+ return c.Type, ok && strings.HasPrefix(c.Type, "urn:law:std#")
294
+ }
295
+ return "", false
296
+ }
297
+
298
+ // Совпадение типа с наследованием сущностей вверх от фактического.
299
+ func (ev *evaluation) conforms(actual, expected string) bool {
300
+ seen := map[string]bool{}
301
+ cur := actual
302
+ for cur != "" && !seen[cur] {
303
+ if cur == expected {
304
+ return true
305
+ }
306
+ seen[cur] = true
307
+ cur = ev.m.BASES[cur]
308
+ }
309
+ return false
310
+ }
311
+
312
+ // Арность и типы аргументов утверждения против сигнатуры §189 (E-0046) —
313
+ // зеркало check_argument_types оракула: несогласованное утверждение не
314
+ // принимается, причина названа кодом TYPE_ERROR; entity_ref типа не несёт;
315
+ // предикат без сигнатуры принимается (его никто не читает). Проверка идёт
316
+ // ДО преобразования значений.
317
+ func (ev *evaluation) wellTyped(lit litJSON) bool {
318
+ params, ok := ev.m.PARAMS[lit.Predicate]
319
+ if !ok {
320
+ return true
321
+ }
322
+ if len(lit.Args) != len(params) {
323
+ ev.issues = append(ev.issues, "TYPE_ERROR")
324
+ ev.typeErrorPredicates[lit.Predicate] = true
325
+ return false
326
+ }
327
+ for i, a := range lit.Args {
328
+ if actual, known := ev.argType(a); known && !ev.conforms(actual, params[i]) {
329
+ ev.issues = append(ev.issues, "TYPE_ERROR")
330
+ ev.typeErrorPredicates[lit.Predicate] = true
331
+ return false
332
+ }
333
+ }
334
+ return true
335
+ }
336
+
337
+ func (ev *evaluation) queryTyped(lit litJSON) bool {
338
+ params, ok := ev.m.PARAMS[lit.Predicate]
339
+ if !ok || len(lit.Args) != len(params) {
340
+ return false
341
+ }
342
+ for i, a := range lit.Args {
343
+ if actual, known := ev.argType(a); known && !ev.conforms(actual, params[i]) {
344
+ return false
345
+ }
346
+ }
347
+ return true
348
+ }
349
+
350
+ // --- вопросы term §57–§59 и collect §52.1 -------------------------------------
351
+ // Срез раннера: comprehension по установленным атомам таблиц с гардами,
352
+ // элемент — ячейка или литерал; агрегаты count/sum/min/max; арифметика §58
353
+ // и функции рантайма над результатом. Вид результата выводится статически
354
+ // по тем же таблицам §58, что у плана: у оракула вид — часть значения
355
+ // (Integer 5 и Decimal 5 — разные байты), у рантайма — нет.
356
+ const (
357
+ tINT = "urn:law:std#Integer"
358
+ tDEC = "urn:law:std#Decimal"
359
+ tMON = "urn:law:std#Money"
360
+ tTEXT = "urn:law:std#Text"
361
+ tDATE = "urn:law:std#Date"
362
+ tINSTANT = "urn:law:std#Instant"
363
+ tRAT = "urn:law:std#Rational"
364
+ tQTY = "urn:law:std#Quantity"
365
+ tBOOL = "urn:law:std#Boolean"
366
+ tNAT = "urn:law:std#Natural"
367
+ )
368
+
369
+ // DECISION-0266: Natural считает как Integer (`values._TYPE_KINDS`), тип-строка своя.
370
+ func normKind(t string) string {
371
+ if t == tNAT {
372
+ return tINT
373
+ }
374
+ return t
375
+ }
376
+
377
+ // §59 (errata E-0195): вид элемента ПУСТОЙ коллекции несёт ОБЪЯВЛЕННЫЙ тип
378
+ // проекции comprehension §52.1 (`variable.type.name`) — единственный носитель
379
+ // вида, не зависящий от числа решений generator-а (порт
380
+ // `values.aggregate_element_type`). Таблица — порт `values._TYPE_KINDS` плюс
381
+ // `_OPAQUE_KINDS`: Natural и Integer у оракула один вид `integer`. Имя вне
382
+ // таблицы (сущность, перечисление, чужой символ) вида не предъявляет, и сверка
383
+ // МОЛЧИТ: Unknown есть «утверждать нечего», а не «совместимо со всем».
384
+ var emptyElementKinds = map[string]string{
385
+ tINT: tINT, tNAT: tINT, tDEC: tDEC, tMON: tMON, tTEXT: tTEXT, tDATE: tDATE,
386
+ tBOOL: tBOOL, tRAT: tRAT, tQTY: tQTY,
387
+ "urn:law:std#Instant": "urn:law:std#Instant",
388
+ "urn:law:std#Magnitude": "urn:law:std#Magnitude",
389
+ "urn:law:std#Bounds": "urn:law:std#Bounds",
390
+ }
391
+
392
+ // Имя функции рантайма по имени std-функции вопроса (DECISION-0267).
393
+ var rtName = map[string]string{"month_start_of": "month_start", "month_end_of": "month_end"}
394
+
395
+ // Сравнения §57 в вычислимом терме вопроса: исход — Boolean.
396
+ func isCmpOp(op string) bool {
397
+ switch op {
398
+ case "lt", "le", "gt", "ge", "eq", "ne":
399
+ return true
400
+ }
401
+ return false
402
+ }
403
+
404
+ // DECISION-0252 п. 3: вид операнда для `arith` рантайма. У вопроса вид каждого
405
+ // значения известен точно — из литерала, слота или исхода операции.
406
+ var kindCode = map[string]string{tINT: "i", tDEC: "d", tMON: "m", tRAT: "r", tQTY: "q", tNAT: "i"}
407
+
408
+ type typed struct {
409
+ v Val
410
+ t string // URN типа std либо "entity" | "enum"
411
+ }
412
+
413
+ type env map[string]typed
414
+
415
+ func isNumT(t string) bool {
416
+ return t == tINT || t == tDEC || t == tMON || t == tRAT || t == tQTY || t == tNAT
417
+ }
418
+
419
+ func arithKind(op, l, r string) string {
420
+ l, r = normKind(l), normKind(r)
421
+ if !isNumT(l) || !isNumT(r) {
422
+ refuse("арифметика вне среза (§58): не числовой операнд " + op)
423
+ }
424
+ switch op {
425
+ case "add", "sub":
426
+ if l != r {
427
+ refuse("арифметика вне среза (§58): виды операндов не совпадают у " + op)
428
+ }
429
+ return l
430
+ case "mul":
431
+ if l == tMON && r == tMON {
432
+ refuse("арифметика вне среза (§58): произведение двух размерных величин не определено")
433
+ }
434
+ if l == tINT {
435
+ return r
436
+ }
437
+ if r == tINT {
438
+ return l
439
+ }
440
+ if l == tMON || r == tMON {
441
+ return tMON
442
+ }
443
+ return tDEC
444
+ case "div":
445
+ if (l == tDEC && r == tDEC) || (l == tMON && r == tMON) {
446
+ return tDEC
447
+ }
448
+ if l == tMON && r == tDEC {
449
+ return tMON
450
+ }
451
+ if l == tINT && r == tINT {
452
+ refuse("арифметика вне среза (§58): частное Integer/Integer — Rational")
453
+ }
454
+ refuse("арифметика вне среза (§58): пара вне таблицы делимых")
455
+ case "mod":
456
+ if l != tINT || r != tINT {
457
+ refuse("арифметика вне среза (§58): остаток только Integer mod Integer")
458
+ }
459
+ return tINT
460
+ }
461
+ refuse("операция вне среза раннера: " + op)
462
+ return ""
463
+ }
464
+
465
+ func callKind(fn string, ks []string) string {
466
+ switch fn {
467
+ case "min", "max":
468
+ if len(ks) != 2 || !isNumT(ks[0]) || ks[0] != ks[1] {
469
+ refuse("арифметика вне среза (§58): виды операндов не совпадают у " + fn)
470
+ }
471
+ return ks[0]
472
+ case "abs":
473
+ if len(ks) != 1 || !isNumT(ks[0]) {
474
+ refuse("арифметика вне среза (§58): abs над нечисловым видом")
475
+ }
476
+ return ks[0]
477
+ case "pow":
478
+ if len(ks) != 2 || ks[0] != tINT || ks[1] != tINT {
479
+ refuse("арифметика вне среза (§58): pow только Integer pow Integer")
480
+ }
481
+ return tINT
482
+ case "round":
483
+ if len(ks) != 3 {
484
+ refuse("round вне среза раннера: ожидаются значение, разряды и режим (§50)")
485
+ }
486
+ if ks[0] == tRAT {
487
+ refuse("round над Rational вне среза генератора (§50: round не берёт Rational)")
488
+ }
489
+ if !isNumT(ks[0]) {
490
+ refuse("round вне среза генератора: вид операнда не числовой (§50)")
491
+ }
492
+ policyKinds("round", ks[1:])
493
+ return ks[0]
494
+ case "div_round":
495
+ // §50/§58 (E-0036, E-0170; DECISION-0259 п. 3): четыре пары видов.
496
+ if len(ks) != 4 {
497
+ refuse("div_round вне среза генератора: ожидаются делимое, делитель, точность и режим (§50)")
498
+ }
499
+ if ks[0] == tRAT || ks[1] == tRAT {
500
+ refuse("div_round над Rational вне среза генератора (§50/§58, E-0036)")
501
+ }
502
+ l, r := ks[0], ks[1]
503
+ if !((l == tMON && r == tDEC) || (l == tQTY && r == tDEC) || (l == tDEC && r == tDEC) || (l == tMON && r == tMON)) {
504
+ refuse("div_round вне среза генератора: пара видов вне E-0036/E-0170 (§50/§58)")
505
+ }
506
+ policyKinds("div_round", ks[2:])
507
+ if r == tDEC && (l == tMON || l == tQTY) {
508
+ return l
509
+ }
510
+ return tDEC
511
+ case "month_start_of", "month_end_of":
512
+ // §84.2 (E-0084, DECISION-0267): арность и вид — TYPE_ERROR оракула (`month_bound_value`).
513
+ if len(ks) != 1 || ks[0] != tDATE {
514
+ panic(termError{"TYPE_ERROR"})
515
+ }
516
+ return tDATE
517
+ case "hours_between", "minutes_between":
518
+ // §84.1 (E-0028/E-0058; W2 часть 2): оба аргумента — Instant, иначе TYPE_ERROR оракула.
519
+ if len(ks) != 2 || ks[0] != tINSTANT || ks[1] != tINSTANT {
520
+ panic(termError{"TYPE_ERROR"})
521
+ }
522
+ return tINT
523
+ case "add_duration":
524
+ refuse("add_duration в вопросе вне среза раннера (§84.1)")
525
+ }
526
+ refuse("функция вне среза раннера: " + fn)
527
+ return ""
528
+ }
529
+
530
+ // §50: точность — Integer либо Decimal, режим — Text (DECISION-0259 п. 3).
531
+ func policyKinds(fn string, ks []string) {
532
+ if ks[0] != tINT && ks[0] != tDEC {
533
+ refuse(fn + " вне среза генератора: точность не Integer и не Decimal (§50)")
534
+ }
535
+ if ks[1] != tTEXT {
536
+ refuse(fn + " вне среза генератора: режим не Text (§50)")
537
+ }
538
+ }
539
+
540
+ func str(v interface{}) string {
541
+ switch x := v.(type) {
542
+ case nil:
543
+ return ""
544
+ case string:
545
+ return x
546
+ case json.Number:
547
+ return x.String()
548
+ }
549
+ return fmt.Sprint(v)
550
+ }
551
+
552
+ func field(m map[string]interface{}, k string) map[string]interface{} {
553
+ if x, ok := m[k].(map[string]interface{}); ok {
554
+ return x
555
+ }
556
+ return map[string]interface{}{}
557
+ }
558
+
559
+ func list(m map[string]interface{}, k string) []interface{} {
560
+ if x, ok := m[k].([]interface{}); ok {
561
+ return x
562
+ }
563
+ return nil
564
+ }
565
+
566
+ // Литерал-терм §53 как typed-значение; тип сущности у entity_ref — "entity".
567
+ func (ev *evaluation) typedArg(a map[string]interface{}) typed {
568
+ switch str(a["kind"]) {
569
+ case "entity_ref":
570
+ return typed{str(a["id"]), "entity"}
571
+ case "const_ref":
572
+ // DECISION-0273: параметр A3 в вопросе term — за срезом раннера (значение на legalTime дела).
573
+ if _, isA3 := ev.m.A3_TYPES[str(a["id"])]; isA3 {
574
+ refuse("параметр A3 в вопросе вне среза раннера: " + str(a["id"]))
575
+ }
576
+ c, ok := ev.m.CONSTS[str(a["id"])]
577
+ if !ok {
578
+ return typed{sym(str(a["id"])), "enum"}
579
+ }
580
+ return typed{c.Value, c.Type}
581
+ case "value":
582
+ t := str(field(a, "type")["name"])
583
+ // §208/§48 (errata E-0197, E-0202, E-0203): та же воронка, что у факта.
584
+ k, text := rawOfValue(a["value"])
585
+ switch t {
586
+ case tMON:
587
+ ck, ctext := rawOfValue(a["currency"])
588
+ return typed{litMoney(k, text, ck, ctext), t}
589
+ case tDEC:
590
+ return typed{litDec(k, text), t}
591
+ case tINT:
592
+ return typed{litInt(k, text), t}
593
+ case tTEXT:
594
+ return typed{litText(k, text), t}
595
+ case tDATE:
596
+ return typed{date(litText(k, text)), t}
597
+ case tINSTANT:
598
+ return typed{instant(litText(k, text)), t}
599
+ case tRAT:
600
+ return typed{rat(litText(k, text)), t}
601
+ case tQTY:
602
+ // §49 v1 (DECISION-0256 п. 7): без тега единицы — отказ, как у факта.
603
+ if uk, utext := rawOfValue(a["unit"]); utext != "" {
604
+ return typed{litQty(k, text, uk, utext), t}
605
+ }
606
+ case tBOOL:
607
+ b, ok := a["value"].(bool)
608
+ if !ok {
609
+ refuse("значение Boolean не true/false вне среза раннера (§48)")
610
+ }
611
+ return typed{b, t}
612
+ case tNAT:
613
+ if k != "i" || strings.HasPrefix(text, "-") {
614
+ refuse("значение Natural не неотрицательное целое вне среза раннера (§48)")
615
+ }
616
+ return typed{litInt(k, text), t}
617
+ }
618
+ refuse("аргумент вне среза раннера: " + t)
619
+ }
620
+ refuse("узел терма вне среза раннера: " + str(a["kind"]))
621
+ return typed{}
622
+ }
623
+
624
+ func (ev *evaluation) slotTyped(pred string, col int, v Val) typed {
625
+ t := ev.m.PARAMS[pred][col]
626
+ if s, ok := v.(string); ok {
627
+ ent, txt := ev.entities[s], ev.texts[s]
628
+ if ent && txt {
629
+ refuse("одна запись подана делом и сущностью, и Text: вид результата не определён в срезе")
630
+ }
631
+ if ent {
632
+ return typed{v, "entity"}
633
+ }
634
+ if txt {
635
+ return typed{v, tTEXT}
636
+ }
637
+ if t == tTEXT {
638
+ return typed{v, tTEXT}
639
+ }
640
+ return typed{v, "entity"}
641
+ }
642
+ if _, ok := v.(bool); ok {
643
+ return typed{v, tBOOL}
644
+ }
645
+ switch t {
646
+ case tMON, tDEC, tINT, tDATE, tRAT, tQTY, tNAT, tINSTANT:
647
+ return typed{v, t}
648
+ }
649
+ if _, ok := v.(Sym); ok {
650
+ return typed{v, "enum"}
651
+ }
652
+ return typed{v, "entity"}
653
+ }
654
+
655
+ func flatten(body map[string]interface{}, atoms *[]map[string]interface{}, guards *[]map[string]interface{}) {
656
+ switch str(body["kind"]) {
657
+ case "status":
658
+ if str(body["status"]) != "established" {
659
+ refuse("статусный тест вне L0-среза: " + str(body["status"]))
660
+ }
661
+ *atoms = append(*atoms, field(body, "formula"))
662
+ case "literal":
663
+ *atoms = append(*atoms, body)
664
+ case "comparison":
665
+ *guards = append(*guards, body)
666
+ case "and", "conjunction":
667
+ subs := list(body, "items")
668
+ if subs == nil {
669
+ subs = list(body, "args")
670
+ }
671
+ if subs == nil {
672
+ subs = list(body, "operands")
673
+ }
674
+ for _, s := range subs {
675
+ if m, ok := s.(map[string]interface{}); ok {
676
+ flatten(m, atoms, guards)
677
+ }
678
+ }
679
+ default:
680
+ refuse("форма тела вне среза генератора: " + str(body["kind"]))
681
+ }
682
+ }
683
+
684
+ // Data-условие §56 (`values.is_data_condition`): boolean, comparison, and, or.
685
+ func (ev *evaluation) dataCond(f map[string]interface{}, e env) bool {
686
+ switch str(f["kind"]) {
687
+ case "boolean":
688
+ b, _ := f["value"].(bool)
689
+ return b
690
+ case "comparison":
691
+ v := ev.evalTerm(map[string]interface{}{"kind": "binary", "op": f["op"], "left": f["left"], "right": f["right"]}, e)
692
+ return v.v.(bool)
693
+ case "and":
694
+ for _, x := range list(f, "items") {
695
+ if !ev.dataCond(x.(map[string]interface{}), e) {
696
+ return false
697
+ }
698
+ }
699
+ return true
700
+ case "or":
701
+ for _, x := range list(f, "items") {
702
+ if ev.dataCond(x.(map[string]interface{}), e) {
703
+ return true
704
+ }
705
+ }
706
+ return false
707
+ }
708
+ refuse("условие терма §56 вне data-условия вне среза раннера: " + str(f["kind"]))
709
+ return false
710
+ }
711
+
712
+ func (ev *evaluation) evalTerm(node map[string]interface{}, e env) typed {
713
+ switch str(node["kind"]) {
714
+ case "var":
715
+ b, ok := e[str(node["var"])]
716
+ if !ok {
717
+ refuse("несвязанная переменная терма: " + str(node["var"]))
718
+ }
719
+ return b
720
+ case "value", "entity_ref", "const_ref":
721
+ if str(node["kind"]) == "value" && str(field(node, "type")["name"]) == tRAT {
722
+ // ERR-E0005: операнд вычисления разбирается как у оракула — сократимая
723
+ // запись нормализуется; каноничность §168.1 — у записей, не у операнда.
724
+ rk, rtext := rawOfValue(node["value"])
725
+ return typed{ratTerm(litText(rk, rtext)), tRAT}
726
+ }
727
+ return ev.typedArg(node)
728
+ case "binary":
729
+ l := ev.evalTerm(field(node, "left"), e)
730
+ r := ev.evalTerm(field(node, "right"), e)
731
+ op := str(node["op"])
732
+ if isCmpOp(op) {
733
+ // §57 (`values.binary`, `_check_same_kind`): валюта, единица, затем вид.
734
+ if l.t == tMON && r.t == tMON && l.v.(Money).Cur != r.v.(Money).Cur {
735
+ panic(termError{"CURRENCY_MISMATCH"})
736
+ }
737
+ if l.t == tQTY && r.t == tQTY && l.v.(Qty).Unit != r.v.(Qty).Unit {
738
+ panic(termError{"DIMENSION_MISMATCH"})
739
+ }
740
+ if (l.t == tDATE && r.t == tINSTANT) || (l.t == tINSTANT && r.t == tDATE) {
741
+ panic(termError{"DATE_INSTANT_MISMATCH"})
742
+ }
743
+ if normKind(l.t) != normKind(r.t) {
744
+ panic(termError{"TYPE_ERROR"})
745
+ }
746
+ if l.t == tBOOL {
747
+ // §57 (DECISION-0266): Boolean сравнимо только на равенство; порядок — TYPE_ERROR оракула.
748
+ if op != "eq" && op != "ne" {
749
+ panic(termError{"TYPE_ERROR"})
750
+ }
751
+ return typed{cmp(l.v, op, r.v), tBOOL}
752
+ }
753
+ if !isNumT(l.t) && l.t != tDATE && l.t != tINSTANT {
754
+ refuse("операция вне среза раннера: " + op)
755
+ }
756
+ return typed{cmp(l.v, op, r.v), tBOOL}
757
+ }
758
+ if !isNumT(l.t) || !isNumT(r.t) {
759
+ refuse("арифметика вне среза (§58): не числовой операнд " + op)
760
+ }
761
+ switch op {
762
+ case "add", "sub", "mul", "div", "mod":
763
+ default:
764
+ refuse("операция вне среза раннера: " + op)
765
+ }
766
+ // §58 `values.binary` (DECISION-0252 п. 8): вид исхода — по значению;
767
+ // ошибка значения — termError, ответ вопроса `term` — RUNTIME_ERROR.
768
+ v := arith(op, l.v, r.v, kindCode[l.t], kindCode[r.t])
769
+ switch v.(type) {
770
+ case Rational:
771
+ return typed{v, tRAT}
772
+ case Money:
773
+ return typed{v, tMON}
774
+ case Qty:
775
+ return typed{v, tQTY}
776
+ }
777
+ if normKind(l.t) == tINT && normKind(r.t) == tINT {
778
+ return typed{v, tINT}
779
+ }
780
+ return typed{v, tDEC}
781
+ case "call":
782
+ fn := str(node["function"])
783
+ if uf, isUser := ev.m.FUNCTIONS[fn]; isUser {
784
+ // DECISION-0273: функция §46 модуля; арность и типы аргументов — TYPE_ERROR
785
+ // оракула (`_require_type`), вид результата — объявленный.
786
+ var vals []Val
787
+ raw := list(node, "args")
788
+ if len(raw) != len(uf.Params) {
789
+ panic(termError{"TYPE_ERROR"})
790
+ }
791
+ for i, a := range raw {
792
+ t := ev.evalTerm(a.(map[string]interface{}), e)
793
+ want := uf.Params[i]
794
+ ok := t.t == "enum" || t.t == want
795
+ if strings.HasPrefix(want, "urn:law:std#") {
796
+ ok = normKind(t.t) == normKind(want)
797
+ }
798
+ if !ok {
799
+ panic(termError{"TYPE_ERROR"})
800
+ }
801
+ vals = append(vals, t.v)
802
+ }
803
+ rt := uf.Returns
804
+ if !strings.HasPrefix(rt, "urn:law:std#") {
805
+ rt = "enum"
806
+ }
807
+ return typed{uf.Fn(vals), rt}
808
+ }
809
+ if i := strings.LastIndex(fn, "#"); i >= 0 {
810
+ fn = fn[i+1:]
811
+ }
812
+ var args []typed
813
+ for _, a := range list(node, "args") {
814
+ args = append(args, ev.evalTerm(a.(map[string]interface{}), e))
815
+ }
816
+ ks := make([]string, len(args))
817
+ for i, a := range args {
818
+ ks[i] = a.t
819
+ }
820
+ t := callKind(fn, ks)
821
+ switch fn {
822
+ case "round":
823
+ // DECISION-0259: политика §50 проверяется рантаймом; виды вопроса точные.
824
+ return typed{roundMode(args[0].v, args[1].v, args[2].v, kindCode[args[0].t], kindCode[args[1].t]), t}
825
+ case "div_round":
826
+ return typed{divRound(args[0].v, args[1].v, args[2].v, args[3].v,
827
+ kindCode[args[0].t], kindCode[args[1].t], kindCode[args[2].t]), t}
828
+ case "min":
829
+ return typed{min(args[0].v, args[1].v), t}
830
+ case "max":
831
+ return typed{max(args[0].v, args[1].v), t}
832
+ case "abs":
833
+ return typed{abs(args[0].v), t}
834
+ case "pow":
835
+ return typed{pow(args[0].v, args[1].v), t}
836
+ case "month_start_of":
837
+ return typed{month_start(args[0].v), t}
838
+ case "month_end_of":
839
+ return typed{month_end(args[0].v), t}
840
+ }
841
+ case "aggregate":
842
+ return ev.aggregate(node)
843
+ case "if":
844
+ // §56 (E-0180, DECISION-0270): чистое data-условие, ветвь вычисляется лениво.
845
+ if ev.dataCond(field(node, "condition"), e) {
846
+ return ev.evalTerm(field(node, "then"), e)
847
+ }
848
+ return ev.evalTerm(field(node, "else"), e)
849
+ }
850
+ refuse("узел терма вне среза раннера: " + str(node["kind"]))
851
+ return typed{}
852
+ }
853
+
854
+ type keyed struct {
855
+ key string
856
+ val typed
857
+ }
858
+
859
+ // Материализация §52.1: элементы в порядке канонических байтов, distinct — множество.
860
+ func (ev *evaluation) materialize(comp map[string]interface{}) []typed {
861
+ if str(comp["kind"]) != "comprehension" {
862
+ refuse("aggregate.input вне среза раннера")
863
+ }
864
+ if len(list(comp, "binders")) > 0 {
865
+ refuse("связки comprehension вне среза раннера")
866
+ }
867
+ var atoms, guards []map[string]interface{}
868
+ flatten(field(comp, "generator"), &atoms, &guards)
869
+ if len(atoms) == 0 {
870
+ refuse("тело без перебираемых атомов вне среза генератора")
871
+ }
872
+ for _, a := range atoms {
873
+ if str(a["polarity"]) != "positive" {
874
+ refuse("отрицательный литерал в теле вне L0-среза")
875
+ }
876
+ params, ok := ev.m.PARAMS[str(a["predicate"])]
877
+ if !ok {
878
+ refuse("предикат comprehension не объявлен в пакете: " + str(a["predicate"]))
879
+ }
880
+ if len(list(a, "args")) != len(params) {
881
+ refuse("арность атома тела не совпадает с сигнатурой §189")
882
+ }
883
+ }
884
+ distinct, _ := comp["distinct"].(bool)
885
+ out := []keyed{}
886
+ seen := map[string]bool{}
887
+ element := field(comp, "element")
888
+ var walk func(i int, e env)
889
+ walk = func(i int, e env) {
890
+ if i == len(atoms) {
891
+ for _, g := range guards {
892
+ if !cmp(ev.evalTerm(field(g, "left"), e).v, str(g["op"]), ev.evalTerm(field(g, "right"), e).v) {
893
+ return
894
+ }
895
+ }
896
+ val := ev.evalTerm(element, e)
897
+ key := literalOf(val)
898
+ if distinct {
899
+ if seen[key] {
900
+ return
901
+ }
902
+ seen[key] = true
903
+ }
904
+ out = append(out, keyed{key, val})
905
+ return
906
+ }
907
+ a := atoms[i]
908
+ pred := str(a["predicate"])
909
+ args := list(a, "args")
910
+ // L1 (DECISION-0237): только установленные строки (TRUE_ONLY), без BOTH.
911
+ for _, row := range ev.m.establishedRows(pred) {
912
+ next := env{}
913
+ for k, v := range e {
914
+ next[k] = v
915
+ }
916
+ ok := true
917
+ for j := 0; j < len(row) && ok; j++ {
918
+ x := args[j].(map[string]interface{})
919
+ if str(x["kind"]) == "var" {
920
+ name := str(x["var"])
921
+ if b, bound := next[name]; !bound {
922
+ next[name] = ev.slotTyped(pred, j, row[j])
923
+ } else if !eq(b.v, row[j]) {
924
+ ok = false
925
+ }
926
+ } else if !eq(ev.evalTerm(x, next).v, row[j]) {
927
+ ok = false
928
+ }
929
+ }
930
+ if ok {
931
+ walk(i+1, next)
932
+ }
933
+ }
934
+ }
935
+ walk(0, env{})
936
+ sort.SliceStable(out, func(i, j int) bool { return out[i].key < out[j].key })
937
+ vals := make([]typed, len(out))
938
+ for i, kv := range out {
939
+ vals[i] = kv.val
940
+ }
941
+ return vals
942
+ }
943
+
944
+ func (ev *evaluation) aggregate(node map[string]interface{}) typed {
945
+ items := ev.materialize(field(node, "input"))
946
+ op := str(node["op"])
947
+ if op == "count" {
948
+ return typed{dec(fmt.Sprint(len(items))), tINT}
949
+ }
950
+ if op == "only" {
951
+ // §51 (errata E-0256): функциональный lookup. Политика — TYPE_ERROR у
952
+ // оракула; пусто и два различных значения (канонические байты §208) —
953
+ // ошибки значения RUNTIME_ERROR, код тот же, что у оракула.
954
+ if len(field(node, "options")) > 0 {
955
+ panic(termError{"TYPE_ERROR"})
956
+ }
957
+ if len(items) == 0 {
958
+ panic(termError{"EMPTY_AGGREGATE"})
959
+ }
960
+ distinct := map[string]bool{}
961
+ for _, it := range items {
962
+ distinct[literalOf(it)] = true
963
+ }
964
+ if len(distinct) > 1 {
965
+ panic(termError{"CARDINALITY_CONFLICT"})
966
+ }
967
+ return typed{items[0].v, normKind(items[0].t)}
968
+ }
969
+ if op != "sum" && op != "min" && op != "max" {
970
+ refuse("агрегат вне среза раннера: " + op)
971
+ }
972
+ if len(items) == 0 {
973
+ empty, ok := field(node, "options")["empty"].(map[string]interface{})
974
+ if !ok {
975
+ refuse("EMPTY_AGGREGATE: " + op + " над пустой коллекцией без явной empty-политики (§59)")
976
+ }
977
+ // `from_literal_term` оракула: политика — LiteralTerm §53; иной узел
978
+ // даёт TYPE_ERROR раньше сверки вида.
979
+ if str(empty["kind"]) != "value" {
980
+ panic(termError{"TYPE_ERROR"})
981
+ }
982
+ value := ev.typedArg(empty)
983
+ // errata E-0195: вид политики обязан совпасть с видом элемента. У оракула
984
+ // это issue TYPE_ERROR с текстом «aggregate.empty: вид политики integer
985
+ // не совпадает с видом элемента коллекции money (§59)»; порождённый
986
+ // модуль сообщений не печатает вовсе, и байтовый контракт среза здесь —
987
+ // КОД issue, который тот же. До правки коллекция Money с политикой
988
+ // Integer отвечала `Integer 0` и `COMPUTED` — другой ответ, не меньший.
989
+ elem := str(field(field(field(node, "input"), "variable"), "type")["name"])
990
+ if expected, known := emptyElementKinds[elem]; known && normKind(value.t) != expected {
991
+ panic(termError{"TYPE_ERROR"})
992
+ }
993
+ return value
994
+ }
995
+ acc := items[0]
996
+ for _, it := range items[1:] {
997
+ if !isNumT(acc.t) || normKind(it.t) != normKind(acc.t) {
998
+ refuse("арифметика вне среза (§58): виды операндов не совпадают у " + op)
999
+ }
1000
+ if op == "sum" {
1001
+ acc = typed{add(acc.v, it.v), acc.t}
1002
+ } else {
1003
+ c := "gt"
1004
+ if op == "min" {
1005
+ c = "lt"
1006
+ }
1007
+ if cmp(it.v, c, acc.v) {
1008
+ acc = it
1009
+ }
1010
+ }
1011
+ }
1012
+ if !isNumT(acc.t) {
1013
+ refuse("арифметика вне среза (§58): не числовой операнд " + op)
1014
+ }
1015
+ // DECISION-0266: итог агрегата над Natural у оракула — вид `integer`, то есть Integer.
1016
+ return typed{acc.v, normKind(acc.t)}
1017
+ }
1018
+
1019
+ // Каноническая десятичная запись §18/§208: без хвостовых нулей и minus-zero.
1020
+ func canonDec(v Val) string {
1021
+ u, s, _, _, _ := scaled(v)
1022
+ if u.Sign() == 0 {
1023
+ return "0"
1024
+ }
1025
+ x := new(big.Int).Set(u)
1026
+ ten := big.NewInt(10)
1027
+ for s > 0 && new(big.Int).Mod(x, ten).Sign() == 0 {
1028
+ x.Div(x, ten)
1029
+ s--
1030
+ }
1031
+ neg := x.Sign() < 0
1032
+ body := new(big.Int).Abs(x).String()
1033
+ for len(body) < s+1 {
1034
+ body = "0" + body
1035
+ }
1036
+ text := body
1037
+ if s > 0 {
1038
+ text = body[:len(body)-s] + "." + body[len(body)-s:]
1039
+ }
1040
+ if neg && x.Sign() != 0 {
1041
+ return "-" + text
1042
+ }
1043
+ return text
1044
+ }
1045
+
1046
+ // Каноническая строка §208: экраны как у оракула, не-ASCII как есть.
1047
+ func canonStr(s string) string {
1048
+ var b bytes.Buffer
1049
+ b.WriteByte('"')
1050
+ for _, r := range s {
1051
+ switch r {
1052
+ case '"':
1053
+ b.WriteString(`\"`)
1054
+ case '\\':
1055
+ b.WriteString(`\\`)
1056
+ case '\b':
1057
+ b.WriteString(`\b`)
1058
+ case '\f':
1059
+ b.WriteString(`\f`)
1060
+ case '\n':
1061
+ b.WriteString(`\n`)
1062
+ case '\r':
1063
+ b.WriteString(`\r`)
1064
+ case '\t':
1065
+ b.WriteString(`\t`)
1066
+ default:
1067
+ if r < 0x20 {
1068
+ fmt.Fprintf(&b, `\u%04x`, r)
1069
+ } else {
1070
+ b.WriteRune(r)
1071
+ }
1072
+ }
1073
+ }
1074
+ b.WriteByte('"')
1075
+ return b.String()
1076
+ }
1077
+
1078
+ // Литерал-терм §53 из typed-значения в канонических байтах — то, что печатает оракул.
1079
+ func literalOf(x typed) string {
1080
+ switch x.t {
1081
+ case "entity":
1082
+ return `{"id":` + canonStr(x.v.(string)) + `,"kind":"entity_ref"}`
1083
+ case "enum":
1084
+ return `{"id":` + canonStr(string(x.v.(Sym))) + `,"kind":"const_ref"}`
1085
+ case tTEXT:
1086
+ return `{"kind":"value","type":{"name":"` + tTEXT + `"},"value":` + canonStr(x.v.(string)) + `}`
1087
+ case tDATE:
1088
+ return `{"kind":"value","type":{"name":"` + tDATE + `"},"value":` + canonStr(string(x.v.(Date))) + `}`
1089
+ case tINSTANT:
1090
+ return `{"kind":"value","type":{"name":"` + tINSTANT + `"},"value":` + canonStr(string(x.v.(Instant))) + `}`
1091
+ case tMON:
1092
+ m := x.v.(Money)
1093
+ return `{"currency":` + canonStr(m.Cur) + `,"kind":"value","type":{"name":"` + tMON + `"},"value":"` + canonDec(x.v) + `"}`
1094
+ case tDEC:
1095
+ return `{"kind":"value","type":{"name":"` + tDEC + `"},"value":"` + canonDec(x.v) + `"}`
1096
+ case tRAT:
1097
+ q := x.v.(Rational)
1098
+ return `{"kind":"value","type":{"name":"` + tRAT + `"},"value":` + canonStr(q.N.String()+"/"+q.D.String()) + `}`
1099
+ case tBOOL:
1100
+ // Исход сравнения §57 — JSON-булево; ключи по порядку §208.
1101
+ v := "false"
1102
+ if x.v.(bool) {
1103
+ v = "true"
1104
+ }
1105
+ return `{"kind":"value","type":{"name":"` + tBOOL + `"},"value":` + v + `}`
1106
+ case tQTY:
1107
+ // DECISION-0256 п. 7: канонический Decimal и тег как есть; ключи по порядку §208.
1108
+ return `{"kind":"value","type":{"name":"` + tQTY + `"},"unit":` + canonStr(x.v.(Qty).Unit) + `,"value":"` + canonDec(x.v) + `"}`
1109
+ case tNAT:
1110
+ // DECISION-0266: запись дела хранит тип Natural; вычисленное целое — всегда Integer.
1111
+ u, s, _, _, _ := scaled(x.v)
1112
+ return `{"kind":"value","type":{"name":"` + tNAT + `"},"value":` + new(big.Int).Div(u, p10(s)).String() + `}`
1113
+ case tINT:
1114
+ u, s, _, _, _ := scaled(x.v)
1115
+ if new(big.Int).Mod(u, p10(s)).Sign() != 0 {
1116
+ refuse("Integer с дробной частью в результате")
1117
+ }
1118
+ return `{"kind":"value","type":{"name":"` + tINT + `"},"value":` + new(big.Int).Div(u, p10(s)).String() + `}`
1119
+ }
1120
+ refuse("вид результата вне среза раннера: " + x.t)
1121
+ return ""
1122
+ }
1123
+
1124
+ // Канонический JSON §208 значения шаблона с заземлением переменных payload —
1125
+ // порт `norms._ground_payload` (DECISION-0242).
1126
+ func canonAny(v interface{}, subst map[string]string) string {
1127
+ switch x := v.(type) {
1128
+ case nil:
1129
+ return "null"
1130
+ case bool:
1131
+ if x {
1132
+ return "true"
1133
+ }
1134
+ return "false"
1135
+ case string:
1136
+ return canonStr(x)
1137
+ case json.Number:
1138
+ return string(x)
1139
+ case []interface{}:
1140
+ parts := make([]string, len(x))
1141
+ for i, item := range x {
1142
+ parts[i] = canonAny(item, subst)
1143
+ }
1144
+ return "[" + strings.Join(parts, ",") + "]"
1145
+ case map[string]interface{}:
1146
+ if x["kind"] == "var" {
1147
+ if name, ok := x["var"].(string); ok {
1148
+ if s, ok := subst[name]; ok {
1149
+ return s
1150
+ }
1151
+ }
1152
+ }
1153
+ fields := map[string]string{}
1154
+ for k, item := range x {
1155
+ fields[k] = canonAny(item, subst)
1156
+ }
1157
+ return canonObject(fields)
1158
+ }
1159
+ refuse("значение payload вне среза раннера")
1160
+ return ""
1161
+ }
1162
+
1163
+ func canonObject(fields map[string]string) string {
1164
+ keys := make([]string, 0, len(fields))
1165
+ for k := range fields {
1166
+ keys = append(keys, k)
1167
+ }
1168
+ sort.Strings(keys)
1169
+ parts := make([]string, len(keys))
1170
+ for i, k := range keys {
1171
+ parts[i] = canonStr(k) + ":" + fields[k]
1172
+ }
1173
+ return "{" + strings.Join(parts, ",") + "}"
1174
+ }
1175
+
1176
+ // Вопрос `positions` (DECISION-0242 K4): позиции без id и доказательства.
1177
+ // Обход запроса судье §47.3 (DECISION-0274): переводы термов раннера для рантайма;
1178
+ // множества сущностей и текстов дела обходом не двигаются.
1179
+ func queryTail(qid string) string {
1180
+ if i := strings.LastIndex(qid, ":"); i >= 0 {
1181
+ return qid[i+1:]
1182
+ }
1183
+ return qid
1184
+ }
1185
+
1186
+ func (ev *evaluation) judgmentRequestsFor(status string, conflict bool) []map[string]interface{} {
1187
+ generic := decodeGeneric(ev.queryRaw)
1188
+ goal := field(generic, "literal")
1189
+ if goal == nil {
1190
+ return nil
1191
+ }
1192
+ if _, ok := goal["polarity"]; !ok {
1193
+ goal["polarity"] = "positive"
1194
+ }
1195
+ return buildRequests(ev.m.judgmentModel(), judgmentBlockers(ev.m.judgmentModel(), ev.hooks, goal, status, conflict), queryTail(str(generic["queryId"])))
1196
+ }
1197
+
1198
+ func effectiveContext(kase map[string]interface{}) map[string]interface{} {
1199
+ raw := map[string]interface{}{}
1200
+ for k, v := range field(kase, "context") {
1201
+ raw[k] = v
1202
+ }
1203
+ profiles := field(kase, "contextProfiles")
1204
+ parent, _ := raw["extends"].(string)
1205
+ delete(raw, "extends")
1206
+ chain, seen := []string{}, map[string]bool{}
1207
+ for current := parent; current != ""; {
1208
+ if seen[current] {
1209
+ refuse("CONTEXT_PROFILE_CYCLE: цикл наследования профилей (§89.1)")
1210
+ }
1211
+ prof, ok := profiles[current].(map[string]interface{})
1212
+ if !ok {
1213
+ refuse("CONTEXT_PROFILE_MISSING: профиль не определён (§89.1)")
1214
+ }
1215
+ seen[current] = true
1216
+ chain = append(chain, current)
1217
+ current, _ = prof["extends"].(string)
1218
+ }
1219
+ effective := map[string]interface{}{}
1220
+ for i := len(chain) - 1; i >= 0; i-- {
1221
+ for k, v := range field(profiles[chain[i]].(map[string]interface{}), "fields") {
1222
+ effective[k] = v
1223
+ }
1224
+ }
1225
+ for k, v := range raw {
1226
+ effective[k] = v
1227
+ }
1228
+ return effective
1229
+ }
1230
+
1231
+ func (ev *evaluation) keyConflictBlocks(predicate string) bool {
1232
+ tainted := ev.m.keyConflictPredicates()
1233
+ waiting := make([]string, 0, len(tainted))
1234
+ for source := range tainted {
1235
+ waiting = append(waiting, source)
1236
+ }
1237
+ for len(waiting) != 0 {
1238
+ source := waiting[len(waiting)-1]
1239
+ waiting = waiting[:len(waiting)-1]
1240
+ for _, consumer := range ev.m.KEY_READERS[source] {
1241
+ if !tainted[consumer] {
1242
+ tainted[consumer] = true
1243
+ waiting = append(waiting, consumer)
1244
+ }
1245
+ }
1246
+ }
1247
+ return tainted[predicate]
1248
+ }
1249
+
1250
+ // Политика §86 и снимок §85 дела до вывода (DECISION-0245 п. 4): порт
1251
+ // `engine._resolve_deadline_policy` и `engine._calendar_snapshot`.
1252
+ func (ev *evaluation) calendarSetup() {
1253
+ semver, policies, snapshots, ok := ev.m.calendarEnv()
1254
+ if !ok {
1255
+ return
1256
+ }
1257
+ kase := decodeGeneric(ev.caseRaw)
1258
+ var policy map[string]interface{}
1259
+ if ref, present := effectiveContext(kase)["deadlinePolicy"]; present && ref != nil {
1260
+ if strings.HasPrefix(semver, "0.1") {
1261
+ obj, isObj := ref.(map[string]interface{})
1262
+ if !isObj {
1263
+ refuse("case.context.deadlinePolicy вне среза раннера (§86)")
1264
+ }
1265
+ policy = obj
1266
+ } else if name, isStr := ref.(string); !isStr {
1267
+ refuse("case.context.deadlinePolicy: в 0.2 ожидался DeadlinePolicyRef (DECISION-0111 §2.9)")
1268
+ } else if node, found := policies[name].(map[string]interface{}); !found {
1269
+ refuse("case.context.deadlinePolicy: ссылка не разрешена в закреплённом CLIR (§86)")
1270
+ } else {
1271
+ policy = node
1272
+ }
1273
+ }
1274
+ selected, _ := field(kase, "options")["calendarSnapshot"].(string)
1275
+ var snapshot *Snapshot
1276
+ if selected != "" {
1277
+ for i := range snapshots {
1278
+ if snapshots[i].ID == selected {
1279
+ snapshot = &snapshots[i]
1280
+ }
1281
+ }
1282
+ if snapshot == nil {
1283
+ refuse("MISSING_CALENDAR: calendar_snapshot не объявлен в программе (§85, §168.2)")
1284
+ }
1285
+ } else if len(snapshots) >= 2 {
1286
+ refuse("AMBIGUOUS_CALENDAR: в программе несколько calendar_snapshot без выбора (§85, §168.2)")
1287
+ } else if len(snapshots) == 1 {
1288
+ snapshot = &snapshots[0]
1289
+ }
1290
+ ev.m.ctx.setCalendar(policy, snapshot)
1291
+ }
1292
+
1293
+ // E-0105: код ошибки терма → статус результата (`whynot.TERM_ERROR_STATUS`);
1294
+ // код вне таблицы — RUNTIME_ERROR; старшинство — `TERM_ERROR_PRECEDENCE`.
1295
+ var termErrorStatusOf = map[string]string{"MISSING_POLICY": "MISSING_POLICY", "DEADLINE_POLICY_INVALID": "MISSING_POLICY",
1296
+ "MISSING_CALENDAR": "MISSING_INPUT", "CALENDAR_DATASET_INVALID": "MISSING_INPUT",
1297
+ "CALENDAR_DATASET_HASH_MISMATCH": "MISSING_INPUT", "CALENDAR_OUT_OF_RANGE": "MISSING_INPUT", "TYPE_ERROR": "TYPE_ERROR",
1298
+ "CURRENCY_MISMATCH": "TYPE_ERROR", "DIMENSION_MISMATCH": "TYPE_ERROR", "DATE_INSTANT_MISMATCH": "TYPE_ERROR",
1299
+ "RESOURCE_LIMIT": "RESOURCE_LIMIT", "OPAQUE_NON_EXECUTABLE": "NON_EXECUTABLE", "NON_EXHAUSTIVE_MATCH": "NON_EXECUTABLE",
1300
+ "UNKNOWN_FUNCTION": "NON_EXECUTABLE", "NON_EXECUTABLE_FUNCTION_RECURSION": "NON_EXECUTABLE"}
1301
+ var termErrorPrecedence = []string{"MISSING_INPUT", "MISSING_POLICY", "NON_EXECUTABLE", "EXTERNAL_UNAVAILABLE",
1302
+ "TYPE_ERROR", "RUNTIME_ERROR", "RESOURCE_LIMIT"}
1303
+
1304
+ var outcomeCodes = map[string]bool{"PRIORITY_CONFLICT": true, "INTERPRETATION_REQUIRED": true, "TYPE_ERROR": true,
1305
+ "RESOURCE_LIMIT": true, "CONFLICTED_INPUTS": true, "SOURCE_RESOLUTION_FAILED": true}
1306
+
1307
+ // Статус результата, зависящего от упавшего правила: NEITHER — статус кода (E-0105),
1308
+ // иная истинность — итог §181.2; смешанные коды — порядок issue, которого раннер
1309
+ // не воспроизводит, — названный отказ. `pred` пуст — носители TYPE_ERROR не читаются.
1310
+ func (ev *evaluation) ruleErrorStatus(pred, truth string) string {
1311
+ te := ev.termErrorStatus()
1312
+ codes := map[string]bool{}
1313
+ if te != "" {
1314
+ for c := range ev.lastTermCodes {
1315
+ codes[c] = true
1316
+ }
1317
+ }
1318
+ ct := ""
1319
+ if pred != "" {
1320
+ ct = ev.carrierTypeError(pred)
1321
+ }
1322
+ if te == "" && ct == "" {
1323
+ return ""
1324
+ }
1325
+ if truth == "NEITHER" {
1326
+ if te != "" {
1327
+ return te
1328
+ }
1329
+ return ct
1330
+ }
1331
+ if ct != "" {
1332
+ codes["TYPE_ERROR"] = true
1333
+ }
1334
+ kept := []string{}
1335
+ for c := range codes {
1336
+ if outcomeCodes[c] {
1337
+ kept = append(kept, c)
1338
+ }
1339
+ }
1340
+ if len(kept) == 0 {
1341
+ return "NON_EXECUTABLE"
1342
+ }
1343
+ if len(kept) == len(codes) && len(kept) == 1 {
1344
+ return kept[0]
1345
+ }
1346
+ refuse("статус итога §181.2 зависит от порядка issue вне среза раннера")
1347
+ return ""
1348
+ }
1349
+
1350
+ func (ev *evaluation) carrierTypeError(pred string) string {
1351
+ if len(ev.typeErrorPredicates) == 0 {
1352
+ return ""
1353
+ }
1354
+ seen := map[string]bool{}
1355
+ var walk func(string) bool
1356
+ walk = func(p string) bool {
1357
+ if seen[p] {
1358
+ return false
1359
+ }
1360
+ seen[p] = true
1361
+ if ev.typeErrorPredicates[p] {
1362
+ return true
1363
+ }
1364
+ for _, c := range ev.m.CARRIER_EDGES[p] {
1365
+ if walk(c) {
1366
+ return true
1367
+ }
1368
+ }
1369
+ return false
1370
+ }
1371
+ if walk(pred) {
1372
+ return "TYPE_ERROR"
1373
+ }
1374
+ return ""
1375
+ }
1376
+
1377
+ // Порт `whynot.term_error_blockers` + `term_error_status`: правила с совместимой
1378
+ // головой, их ошибки терма и конъюнкты без опоры — вглубь.
1379
+ func (ev *evaluation) termErrorStatus() string {
1380
+ rules, consts, seen, ok := ev.m.backchainEnv()
1381
+ if !ok {
1382
+ return ""
1383
+ }
1384
+ byRule := map[string][]string{}
1385
+ for k := range seen {
1386
+ at := strings.LastIndex(k, "|")
1387
+ name, code := k[:at], k[at+1:]
1388
+ if code != "MISSING_PARAMETER_VALUE" {
1389
+ byRule[name] = append(byRule[name], code)
1390
+ }
1391
+ }
1392
+ if len(byRule) == 0 {
1393
+ return ""
1394
+ }
1395
+ index := map[string][]map[string]interface{}{}
1396
+ for _, x := range rules {
1397
+ r := x.(map[string]interface{})
1398
+ pred := str(field(r, "head")["predicate"])
1399
+ index[pred] = append(index[pred], r)
1400
+ }
1401
+ canon := func(v interface{}) string { return canonAny(v, map[string]string{}) }
1402
+ argsOf := func(lit map[string]interface{}) []interface{} { return list(lit, "args") }
1403
+ kindOf := func(a interface{}) string { return str(a.(map[string]interface{})["kind"]) }
1404
+ varOf := func(a interface{}) string { return str(a.(map[string]interface{})["var"]) }
1405
+ polarity := func(lit map[string]interface{}) string {
1406
+ if p := str(lit["polarity"]); p != "" {
1407
+ return p
1408
+ }
1409
+ return "positive"
1410
+ }
1411
+ compatible := func(head, target map[string]interface{}) map[string]interface{} {
1412
+ if polarity(head) != polarity(target) {
1413
+ return nil
1414
+ }
1415
+ ha, ga := argsOf(head), argsOf(target)
1416
+ if len(ha) != len(ga) {
1417
+ return nil
1418
+ }
1419
+ subst := map[string]interface{}{}
1420
+ for i := range ha {
1421
+ h, g := ha[i], ga[i]
1422
+ if kindOf(g) == "var" {
1423
+ continue
1424
+ }
1425
+ switch kindOf(h) {
1426
+ case "var":
1427
+ if prev, bound := subst[varOf(h)]; bound && canon(prev) != canon(g) {
1428
+ return nil
1429
+ }
1430
+ subst[varOf(h)] = g
1431
+ case "entity_ref", "const_ref", "value":
1432
+ if canon(h) != canon(g) {
1433
+ return nil
1434
+ }
1435
+ }
1436
+ }
1437
+ return subst
1438
+ }
1439
+ mirror := map[string]string{"TRUE_ONLY": "FALSE_ONLY", "FALSE_ONLY": "TRUE_ONLY", "BOTH": "BOTH", "NEITHER": "NEITHER"}
1440
+ truth := func(lit map[string]interface{}) string {
1441
+ savedE, savedT := map[string]bool{}, map[string]bool{}
1442
+ for k := range ev.entities {
1443
+ savedE[k] = true
1444
+ }
1445
+ for k := range ev.texts {
1446
+ savedT[k] = true
1447
+ }
1448
+ defer func() { ev.entities, ev.texts = savedE, savedT }()
1449
+ row := []Val{}
1450
+ for _, a := range argsOf(lit) {
1451
+ b, _ := json.Marshal(a)
1452
+ var aj argJSON
1453
+ if err := json.Unmarshal(b, &aj); err != nil {
1454
+ panic(err)
1455
+ }
1456
+ row = append(row, ev.toVal(aj))
1457
+ }
1458
+ st := ev.m.truthOf(str(lit["predicate"]), row)
1459
+ if polarity(lit) == "negative" {
1460
+ return mirror[st]
1461
+ }
1462
+ return st
1463
+ }
1464
+ found, visited := map[string]bool{}, map[string]bool{}
1465
+ var walk func(target map[string]interface{})
1466
+ walk = func(target map[string]interface{}) {
1467
+ key := canon(target)
1468
+ if visited[key] {
1469
+ return
1470
+ }
1471
+ visited[key] = true
1472
+ for _, r := range index[str(target["predicate"])] {
1473
+ subst := compatible(field(r, "head"), target)
1474
+ if subst == nil {
1475
+ continue
1476
+ }
1477
+ if w, isMap := r["window"].(map[string]interface{}); isMap {
1478
+ start, _ := w["start"].(string)
1479
+ end, _ := w["end"].(string)
1480
+ if (start != "" && ev.m.ctx.legalTime() < start) || (end != "" && ev.m.ctx.legalTime() >= end) {
1481
+ continue
1482
+ }
1483
+ }
1484
+ for _, c := range byRule[str(r["name"])] {
1485
+ found[c] = true
1486
+ }
1487
+ for _, cj := range list(r, "body") {
1488
+ lit := cj.([]interface{})[1].(map[string]interface{})
1489
+ if str(lit["kind"]) != "literal" {
1490
+ continue // DECISION-0274: гарды в backchain — только для пути судьи
1491
+ }
1492
+ args := argsOf(lit)
1493
+ wildcard, unbound := false, false
1494
+ computed := func(a interface{}) bool {
1495
+ switch kindOf(a) {
1496
+ case "binary", "call", "if", "let", "match":
1497
+ return true
1498
+ }
1499
+ return false
1500
+ }
1501
+ for _, a := range args {
1502
+ if kindOf(a) == "wildcard" {
1503
+ wildcard = true
1504
+ }
1505
+ // DECISION-0270: вычисляемый аргумент не заземляется обходом —
1506
+ // как несвязанная переменная, конъюнкт идёт вглубь без оценки.
1507
+ if computed(a) {
1508
+ unbound = true
1509
+ }
1510
+ if kindOf(a) == "var" {
1511
+ if v, bound := subst[varOf(a)]; !bound || computed(v) {
1512
+ unbound = true
1513
+ }
1514
+ }
1515
+ }
1516
+ if wildcard {
1517
+ continue
1518
+ }
1519
+ next := map[string]interface{}{}
1520
+ for k, v := range lit {
1521
+ next[k] = v
1522
+ }
1523
+ ground := []interface{}{}
1524
+ for _, a := range args {
1525
+ if kindOf(a) == "var" {
1526
+ if v, bound := subst[varOf(a)]; bound {
1527
+ ground = append(ground, v)
1528
+ continue
1529
+ }
1530
+ }
1531
+ ground = append(ground, a)
1532
+ }
1533
+ next["args"] = ground
1534
+ if unbound {
1535
+ walk(next)
1536
+ continue
1537
+ }
1538
+ if truth(next) == "NEITHER" {
1539
+ walk(next)
1540
+ }
1541
+ }
1542
+ }
1543
+ }
1544
+ goal := map[string]interface{}{}
1545
+ for k, v := range field(decodeGeneric(ev.queryRaw), "literal") {
1546
+ goal[k] = v
1547
+ }
1548
+ derefd := []interface{}{}
1549
+ for _, a := range list(goal, "args") {
1550
+ m := a.(map[string]interface{})
1551
+ if kindOf(a) == "const_ref" {
1552
+ if v, isConst := consts[str(m["id"])]; isConst {
1553
+ derefd = append(derefd, v)
1554
+ continue
1555
+ }
1556
+ }
1557
+ derefd = append(derefd, a)
1558
+ }
1559
+ goal["args"] = derefd
1560
+ walk(goal)
1561
+ ev.lastTermCodes = map[string]bool{}
1562
+ for c := range found {
1563
+ ev.lastTermCodes[c] = true
1564
+ }
1565
+ for _, st := range termErrorPrecedence {
1566
+ for c := range found {
1567
+ mapped, known := termErrorStatusOf[c]
1568
+ if !known {
1569
+ mapped = "RUNTIME_ERROR"
1570
+ }
1571
+ if mapped == st {
1572
+ return st
1573
+ }
1574
+ }
1575
+ }
1576
+ return ""
1577
+ }
1578
+
1579
+ func (ev *evaluation) run() string {
1580
+ var kase struct {
1581
+ Context struct {
1582
+ LegalTime string `json:"legalTime"`
1583
+ DecisionTime *string `json:"decisionTime"`
1584
+ } `json:"context"`
1585
+ Assertions []struct {
1586
+ Literal litJSON `json:"literal"`
1587
+ Valid json.RawMessage `json:"valid"`
1588
+ Judgment json.RawMessage `json:"judgment"`
1589
+ } `json:"assertions"`
1590
+ }
1591
+ var query struct {
1592
+ Kind string `json:"kind"`
1593
+ QueryID string `json:"queryId"`
1594
+ Literal litJSON `json:"literal"`
1595
+ Predicate string `json:"predicate"`
1596
+ }
1597
+ mustUnmarshal(ev.caseRaw, &kase)
1598
+ mustUnmarshal(ev.queryRaw, &query)
1599
+ ev.m.ctx.setLegalTime(kase.Context.LegalTime)
1600
+ if kase.Context.DecisionTime != nil {
1601
+ ev.m.setStatusTime((*kase.Context.DecisionTime)[:10])
1602
+ }
1603
+ ev.calendarSetup()
1604
+ ev.m.packageFacts()
1605
+ for _, a := range kase.Assertions {
1606
+ if len(a.Valid) != 0 && string(a.Valid) != "null" {
1607
+ refuse("поле assertion.valid вне среза раннера (§72)")
1608
+ }
1609
+ if a.Literal.Polarity != "positive" && (LAYER != "L1" || a.Literal.Polarity != "negative") {
1610
+ refuse("§113: отрицательный факт вне L0-среза")
1611
+ }
1612
+ if a.Literal.Predicate == "urn:law:std#incompatible" {
1613
+ if _, ok := ev.m.normPositions(); ok {
1614
+ // §105.1: явная несовместимость позиций — конфликт вне среза (DECISION-0242 K5).
1615
+ refuse("конфликт позиций §105.1 (incompatible) вне среза раннера (DECISION-0242 K5)")
1616
+ }
1617
+ }
1618
+ if !ev.wellTyped(a.Literal) {
1619
+ continue
1620
+ }
1621
+ if _, ok := ev.m.PARAMS[a.Literal.Predicate]; !ok {
1622
+ continue
1623
+ }
1624
+ // §47.3.1 (E-0089, DECISION-0274): ответ судьи не от того органа или не по форме — не принят.
1625
+ if len(a.Judgment) != 0 && string(a.Judgment) != "null" {
1626
+ var judgment map[string]interface{}
1627
+ if err := json.Unmarshal(a.Judgment, &judgment); err != nil {
1628
+ panic(err)
1629
+ }
1630
+ if rejected := judgmentAnswer(ev.m.judgmentModel(), a.Literal.Predicate, judgment); rejected != "" {
1631
+ ev.issues = append(ev.issues, rejected)
1632
+ continue
1633
+ }
1634
+ }
1635
+ row := make([]Val, len(a.Literal.Args))
1636
+ for i, x := range a.Literal.Args {
1637
+ row[i] = ev.toVal(x)
1638
+ }
1639
+ if a.Literal.Polarity == "negative" {
1640
+ ev.m.registerNegative(a.Literal.Predicate, row)
1641
+ } else {
1642
+ ev.m.register(a.Literal.Predicate, row)
1643
+ }
1644
+ }
1645
+ ev.m.saturate()
1646
+ // DECISION-0239 G3: ошибки значения агрегатов в правилах — issues модели.
1647
+ ev.issues = append(ev.issues, ev.m.ruleIssues()...)
1648
+ // DECISION-0242: диагностики эффектов полномочий §127–§128.
1649
+ ev.issues = append(ev.issues, ev.m.normIssues()...)
1650
+ for n := ev.m.keyConflicts(); n > 0; n-- {
1651
+ ev.issues = append(ev.issues, "KEY_CONFLICT")
1652
+ }
1653
+ issuesJSON, _ := json.Marshal(ev.issues)
1654
+ qid := canonStr(query.QueryID)
1655
+ if query.Kind == "rows" && ev.api {
1656
+ return ev.rowsJSON(query.Predicate, qid, string(issuesJSON))
1657
+ }
1658
+ if query.Kind == "collect" || query.Kind == "term" {
1659
+ // Числа — json.Number: через float64 длинное целое потеряло бы знаки.
1660
+ generic := decodeGeneric(ev.queryRaw)
1661
+ if query.Kind == "collect" {
1662
+ parts := []string{}
1663
+ for _, e := range ev.materialize(field(generic, "comprehension")) {
1664
+ if s, ok := e.v.(string); ok && strings.HasPrefix(s, "urn:position:") {
1665
+ refuse("позиция в элементе коллекции вне среза раннера (DECISION-0242 K4)")
1666
+ }
1667
+ parts = append(parts, literalOf(e))
1668
+ }
1669
+ return `{"queryId":` + qid + `,"resultKind":"COLLECTION","value":[` + strings.Join(parts, ",") + `],"evaluationStatus":"COMPUTED","issues":` + string(issuesJSON) + `}`
1670
+ }
1671
+ value, code := func() (s string, code string) {
1672
+ defer func() {
1673
+ if rec := recover(); rec != nil {
1674
+ if e, ok := rec.(termError); ok {
1675
+ code = e.code
1676
+ return
1677
+ }
1678
+ panic(rec)
1679
+ }
1680
+ }()
1681
+ return literalOf(ev.evalTerm(field(generic, "term"), env{})), ""
1682
+ }()
1683
+ if code != "" {
1684
+ // §58/§175 (DECISION-0252 п. 8): ошибка значения вопроса — без значения;
1685
+ // статус по таблице `queries.query_calc`: несовместимость видов —
1686
+ // TYPE_ERROR, прочее — RUNTIME_ERROR.
1687
+ status := "RUNTIME_ERROR"
1688
+ switch code {
1689
+ case "TYPE_ERROR", "CURRENCY_MISMATCH", "DIMENSION_MISMATCH", "DATE_INSTANT_MISMATCH":
1690
+ status = "TYPE_ERROR"
1691
+ }
1692
+ failed, _ := json.Marshal(append(append([]string{}, ev.issues...), code))
1693
+ return `{"queryId":` + qid + `,"resultKind":"DATA","evaluationStatus":"` + status + `","issues":` + string(failed) + `}`
1694
+ }
1695
+ return `{"queryId":` + qid + `,"resultKind":"DATA","value":` + value + `,"evaluationStatus":"COMPUTED","issues":` + string(issuesJSON) + `}`
1696
+ }
1697
+ if query.Kind != "truth" {
1698
+ refuse("вид запроса вне среза раннера: " + query.Kind)
1699
+ }
1700
+ for _, x := range query.Literal.Args {
1701
+ if x.Kind == "entity_ref" && strings.HasPrefix(x.ID, "urn:position:") {
1702
+ refuse("позиция в аргументе вопроса вне среза раннера (DECISION-0242 K4)")
1703
+ }
1704
+ }
1705
+ if LAYER == "L1" {
1706
+ // §62/§63: статус атома по опорам обеих полярностей; отрицательный
1707
+ // вопрос зеркалит стороны; несогласованный по сигнатуре вопрос — NEITHER.
1708
+ status := "NEITHER"
1709
+ evaluation := "COMPUTED"
1710
+ conflict := false
1711
+ if ev.queryTyped(query.Literal) {
1712
+ want := make([]Val, len(query.Literal.Args))
1713
+ for i, x := range query.Literal.Args {
1714
+ want[i] = ev.toVal(x)
1715
+ }
1716
+ status = ev.m.truthOf(query.Literal.Predicate, want)
1717
+ conflict = ev.m.blockedEffect(query.Literal.Predicate, want)
1718
+ }
1719
+ if query.Literal.Polarity == "negative" {
1720
+ status = map[string]string{"TRUE_ONLY": "FALSE_ONLY", "FALSE_ONLY": "TRUE_ONLY", "BOTH": "BOTH", "NEITHER": "NEITHER"}[status]
1721
+ }
1722
+ // §47.3 (DECISION-0274): судьи по лестнице §175 — конфликт старше судьи, судья старше ошибки терма.
1723
+ requests := ev.judgmentRequestsFor(status, conflict)
1724
+ if conflict {
1725
+ // §127: эффект заперт непобеждённой парой Power/Immunity.
1726
+ evaluation = "UNRESOLVED_NORMATIVE_CONFLICT"
1727
+ } else if ev.keyConflictBlocks(query.Literal.Predicate) {
1728
+ evaluation = "NON_EXECUTABLE"
1729
+ } else if len(requests) > 0 {
1730
+ evaluation = "REQUIRES_JUDGMENT"
1731
+ } else if st := ev.ruleErrorStatus(query.Literal.Predicate, status); st != "" {
1732
+ // E-0243 §181.2: носитель TYPE_ERROR блокирует своих читателей и у L1, как у py и ts.
1733
+ evaluation = st
1734
+ } else if status == "NEITHER" {
1735
+ // E-0105 (§175/§185): NEITHER над ошибкой терма правила-кандидата.
1736
+ if st := ev.termErrorStatus(); st != "" {
1737
+ evaluation = st
1738
+ }
1739
+ }
1740
+ if requests == nil {
1741
+ requests = []map[string]interface{}{}
1742
+ }
1743
+ out, _ := json.Marshal(map[string]interface{}{"queryId": query.QueryID,
1744
+ "truthStatus": status, "evaluationStatus": evaluation, "judgmentRequests": requests, "issues": ev.issues})
1745
+ return string(out)
1746
+ }
1747
+ // Ключ атома у оракула §208 включает ТИП значения: запрос с Integer к
1748
+ // утверждению Decimal той же величины атома не находит — NEITHER без issue.
1749
+ hit := false
1750
+ rows := [][]Val{}
1751
+ want := []Val{}
1752
+ if ev.queryTyped(query.Literal) {
1753
+ rows = ev.m.TABLES[query.Literal.Predicate]
1754
+ want = make([]Val, len(query.Literal.Args))
1755
+ for i, x := range query.Literal.Args {
1756
+ want[i] = ev.toVal(x)
1757
+ }
1758
+ }
1759
+ for _, row := range rows {
1760
+ if len(row) != len(want) {
1761
+ continue
1762
+ }
1763
+ all := true
1764
+ for i := range row {
1765
+ if !eq(row[i], want[i]) {
1766
+ all = false
1767
+ break
1768
+ }
1769
+ }
1770
+ if all {
1771
+ hit = true
1772
+ break
1773
+ }
1774
+ }
1775
+ // Открытый мир: отсутствие строки — это NEITHER, а не FALSE_ONLY.
1776
+ // §47.3 (DECISION-0274): судьи у модели L0 — тот же обход по backchain.
1777
+ status := "NEITHER"
1778
+ if hit {
1779
+ status = "TRUE_ONLY"
1780
+ }
1781
+ requests := ev.judgmentRequestsFor(status, false)
1782
+ evaluation := "COMPUTED"
1783
+ if ev.keyConflictBlocks(query.Literal.Predicate) {
1784
+ evaluation = "NON_EXECUTABLE"
1785
+ } else if len(requests) > 0 {
1786
+ evaluation = "REQUIRES_JUDGMENT"
1787
+ } else if st := ev.ruleErrorStatus(query.Literal.Predicate, status); st != "" {
1788
+ evaluation = st
1789
+ }
1790
+ if requests == nil {
1791
+ requests = []map[string]interface{}{}
1792
+ }
1793
+ out, _ := json.Marshal(map[string]interface{}{"queryId": query.QueryID,
1794
+ "truthStatus": status, "evaluationStatus": evaluation, "judgmentRequests": requests, "issues": ev.issues})
1795
+ return string(out)
1796
+ }
1797
+
1798
+ // Внутренний вид API `rows` (DECISION-0423 §2.1): установленные строки вопроса
1799
+ // литерал-термами §53 в порядке канонических байтов; статус вычисления — как у
1800
+ // вопроса о том же отношении (конфликт ключа блокирует читателей, E-0242).
1801
+ func (ev *evaluation) rowsJSON(pred, qid, issuesJSON string) string {
1802
+ if _, ok := ev.m.PARAMS[pred]; !ok {
1803
+ refuse("предикат rows не объявлен в пакете: " + pred)
1804
+ }
1805
+ var src [][]Val
1806
+ if LAYER == "L1" {
1807
+ src = ev.m.establishedRows(pred)
1808
+ } else {
1809
+ src = ev.m.TABLES[pred]
1810
+ }
1811
+ rows := []string{}
1812
+ for _, r := range src {
1813
+ cells := make([]string, len(r))
1814
+ for col, v := range r {
1815
+ cells[col] = literalOf(ev.slotTyped(pred, col, v))
1816
+ }
1817
+ rows = append(rows, "["+strings.Join(cells, ",")+"]")
1818
+ }
1819
+ sort.Strings(rows)
1820
+ evaluation := "COMPUTED"
1821
+ if ev.keyConflictBlocks(pred) {
1822
+ evaluation = "NON_EXECUTABLE"
1823
+ }
1824
+ return `{"queryId":` + qid + `,"resultKind":"COLLECTION","rows":[` + strings.Join(rows, ",") +
1825
+ `],"evaluationStatus":"` + evaluation + `","judgmentRequests":[],"issues":` + issuesJSON + `}`
1826
+ }