psiloceyeben commited on
Commit
e1b4e6e
路
verified 路
1 Parent(s): 555998c

Release 11.0 (full): passage readings held back when the sentence leaves out a question word, the judge names the step it refused, the part format v1, anyone's sealed test, the sleep cycle, and the six orbiters as files in the pack (part 7)

Browse files

Head oracle_release_runtime_w575. Root 5985f38d66bcdedbdedb74db90432675a32ddce9dfe2d196b3282c40581d889b. Solana anchor: pending Ben's signature. Internal week (seven simulated nights): 0 parts admitted, 0 new wrong, published as measured. Full changelog: CHANGELOG_11.md.

workbench/swarm/close_learn102.py CHANGED
@@ -15,6 +15,8 @@ import collections
15
  import copy
16
  import json
17
  import re
 
 
18
  from pathlib import Path
19
 
20
  import reader_v4 as R4
@@ -217,7 +219,7 @@ class Closer:
217
  import time
218
  rd = self.cov.rd
219
  seen = seen if seen is not None else set()
220
- if time.time() > deadline or len(out) >= 8 or depth > 4:
221
  return
222
  left = set(need) - set(used)
223
  last = prog[-1]["function"] if prog else ""
 
15
  import copy
16
  import json
17
  import re
18
+
19
+ import steps_budget as SB
20
  from pathlib import Path
21
 
22
  import reader_v4 as R4
 
219
  import time
220
  rd = self.cov.rd
221
  seen = seen if seen is not None else set()
222
+ if SB.spent() > deadline or len(out) >= 8 or depth > 4:
223
  return
224
  left = set(need) - set(used)
225
  last = prog[-1]["function"] if prog else ""
workbench/swarm/coverage102.py CHANGED
@@ -41,6 +41,8 @@ import hashlib
41
  import json
42
  import os
43
  import re
 
 
44
  import sys
45
  import time
46
  from pathlib import Path
@@ -252,7 +254,7 @@ class Coverage:
252
  except Exception: # noqa: BLE001
253
  self.closer = None
254
  try: # the learned word-to-step alignment (align_learn102.py)
255
- A = json.load(open(Path(__file__).resolve().parent / "data" / "alignment102.json"))["p_feature_given_word"]
256
  except Exception: # noqa: BLE001
257
  A = {}
258
  self.align_t = A
@@ -911,8 +913,6 @@ class Coverage:
911
  if lit is not None or (f.startswith("FN:") and f[3:] not in SAID_FN):
912
  continue
913
  best = max([self.align_t.get(f, {}).get(w, 0.0) for w in words] + [0.0])
914
- if f.startswith("IN:") and f.count(":") == 1 and self.says_word(f, f[3:], next(iter(words), "")):
915
- best = max(best, 1.0)
916
  tot += best
917
  n += 1
918
  for st in prog:
@@ -962,7 +962,7 @@ class Coverage:
962
  written = b_ == a_ + 1 and re.fullmatch(r"-?\d[\d,]*(?:\.\d+)?|\d{4}-\d{2}-\d{2}|\d{4} \d{2} \d{2}", q[s1:e1]) is not None
963
  if not t or (t not in vix and not written) or (s1, e1) in seen or any(x <= s1 and e1 <= y for x, y in seen):
964
  continue
965
- stored = next(iter(self._vstore.get(t, set()))) if t in vix else None
966
  if stored is None: # a written number or date the store does not hold: as written
967
  m = re.search(re.escape(q[s1:e1]).replace("\ ", r"\W+"), question, re.I)
968
  stored = question[m.start():m.end()] if m else q[s1:e1]
@@ -1218,10 +1218,10 @@ class Coverage:
1218
  singles = [x for x in P if len(x[3]) == 1 and x[2][0]["function"] == "Find"]
1219
  res = []
1220
 
1221
- deadline = time.time() + float(os.environ.get("O7_FLASH_BUDGET", 3.0))
1222
 
1223
  def run(pp):
1224
- if len(res) >= 8 or time.time() > deadline:
1225
  return
1226
  for st in pp:
1227
  if st["function"] == "Find":
@@ -1744,7 +1744,7 @@ class Coverage:
1744
  m = re.search(re.escape(q[s1:e1]).replace("\\ ", r"\W+"), question, re.I)
1745
  vals_written.append((s1, e1, question[m.start():m.end()] if m else q[s1:e1]))
1746
  # the flash: from every part, along every lit relation, up to two steps - all at once
1747
- deadline = time.time() + float(os.environ.get("O7_FLASH_BUDGET", 3.0))
1748
  grown = []
1749
  for s0, e0, prog, idset in P:
1750
  own = self.part_words(ws, span(s0, e0), prog)
@@ -1768,7 +1768,7 @@ class Coverage:
1768
  grown.append((s0, e0, prog, own, need, [b_ for b_ in beams if b_[0]], start))
1769
  for s0, e0, prog, own, need, beams, _start in grown:
1770
  for path, used, v in beams:
1771
- if time.time() > deadline:
1772
  break
1773
  nodes = {ids_of[n] for n in np.flatnonzero(v)[:200]}
1774
  for pp, cw in closing(program(prog, path), nodes, used, need):
@@ -1782,7 +1782,7 @@ class Coverage:
1782
  need = (A[4] - B[3]) | (B[4] - A[3])
1783
  for pa_, ua, va in [((), frozenset(), A[6])] + A[5]: # a part's own items meet the other's light
1784
  for pb_, ub, vb in [((), frozenset(), B[6])] + B[5]:
1785
- if time.time() > deadline:
1786
  break
1787
  meet = va & vb
1788
  if not meet.any() or meet.sum() > 50:
 
41
  import json
42
  import os
43
  import re
44
+
45
+ import steps_budget as SB
46
  import sys
47
  import time
48
  from pathlib import Path
 
254
  except Exception: # noqa: BLE001
255
  self.closer = None
256
  try: # the learned word-to-step alignment (align_learn102.py)
257
+ A = json.load(open(os.environ.get("O7_ALIGN") or Path(__file__).resolve().parent / "data" / "alignment102.json"))["p_feature_given_word"]
258
  except Exception: # noqa: BLE001
259
  A = {}
260
  self.align_t = A
 
913
  if lit is not None or (f.startswith("FN:") and f[3:] not in SAID_FN):
914
  continue
915
  best = max([self.align_t.get(f, {}).get(w, 0.0) for w in words] + [0.0])
 
 
916
  tot += best
917
  n += 1
918
  for st in prog:
 
962
  written = b_ == a_ + 1 and re.fullmatch(r"-?\d[\d,]*(?:\.\d+)?|\d{4}-\d{2}-\d{2}|\d{4} \d{2} \d{2}", q[s1:e1]) is not None
963
  if not t or (t not in vix and not written) or (s1, e1) in seen or any(x <= s1 and e1 <= y for x, y in seen):
964
  continue
965
+ stored = min(self._vstore.get(t, {None}), key=str) if t in vix else None
966
  if stored is None: # a written number or date the store does not hold: as written
967
  m = re.search(re.escape(q[s1:e1]).replace("\ ", r"\W+"), question, re.I)
968
  stored = question[m.start():m.end()] if m else q[s1:e1]
 
1218
  singles = [x for x in P if len(x[3]) == 1 and x[2][0]["function"] == "Find"]
1219
  res = []
1220
 
1221
+ deadline = SB.deadline()
1222
 
1223
  def run(pp):
1224
+ if len(res) >= 8 or SB.spent() > deadline:
1225
  return
1226
  for st in pp:
1227
  if st["function"] == "Find":
 
1744
  m = re.search(re.escape(q[s1:e1]).replace("\\ ", r"\W+"), question, re.I)
1745
  vals_written.append((s1, e1, question[m.start():m.end()] if m else q[s1:e1]))
1746
  # the flash: from every part, along every lit relation, up to two steps - all at once
1747
+ deadline = SB.deadline()
1748
  grown = []
1749
  for s0, e0, prog, idset in P:
1750
  own = self.part_words(ws, span(s0, e0), prog)
 
1768
  grown.append((s0, e0, prog, own, need, [b_ for b_ in beams if b_[0]], start))
1769
  for s0, e0, prog, own, need, beams, _start in grown:
1770
  for path, used, v in beams:
1771
+ if SB.spent() > deadline:
1772
  break
1773
  nodes = {ids_of[n] for n in np.flatnonzero(v)[:200]}
1774
  for pp, cw in closing(program(prog, path), nodes, used, need):
 
1782
  need = (A[4] - B[3]) | (B[4] - A[3])
1783
  for pa_, ua, va in [((), frozenset(), A[6])] + A[5]: # a part's own items meet the other's light
1784
  for pb_, ub, vb in [((), frozenset(), B[6])] + B[5]:
1785
+ if SB.spent() > deadline:
1786
  break
1787
  meet = va & vb
1788
  if not meet.any() or meet.sum() > 50:
workbench/swarm/data/flash_threshold.json CHANGED
@@ -1,254 +1,12 @@
1
  {
2
  "format": "oracle7-flash-threshold/1",
3
- "threshold": 0.79,
4
  "at_threshold": {
5
- "threshold": 0.79,
6
- "right": 44,
7
  "wrong": 2,
8
- "precision": 0.957
9
  },
10
- "chosen_by": "lowest score at which the flash's development additions reach precision >= 0.95 (>= 10 right); else the highest-precision point",
11
- "n_scored": 440,
12
- "curve": [
13
- {
14
- "threshold": 0.997,
15
- "right": 1,
16
- "wrong": 0,
17
- "precision": 1.0
18
- },
19
- {
20
- "threshold": 0.968,
21
- "right": 12,
22
- "wrong": 0,
23
- "precision": 1.0
24
- },
25
- {
26
- "threshold": 0.901,
27
- "right": 22,
28
- "wrong": 1,
29
- "precision": 0.957
30
- },
31
- {
32
- "threshold": 0.818,
33
- "right": 32,
34
- "wrong": 2,
35
- "precision": 0.941
36
- },
37
- {
38
- "threshold": 0.798,
39
- "right": 43,
40
- "wrong": 2,
41
- "precision": 0.956
42
- },
43
- {
44
- "threshold": 0.753,
45
- "right": 52,
46
- "wrong": 4,
47
- "precision": 0.929
48
- },
49
- {
50
- "threshold": 0.742,
51
- "right": 63,
52
- "wrong": 4,
53
- "precision": 0.94
54
- },
55
- {
56
- "threshold": 0.719,
57
- "right": 73,
58
- "wrong": 5,
59
- "precision": 0.936
60
- },
61
- {
62
- "threshold": 0.684,
63
- "right": 81,
64
- "wrong": 8,
65
- "precision": 0.91
66
- },
67
- {
68
- "threshold": 0.661,
69
- "right": 91,
70
- "wrong": 9,
71
- "precision": 0.91
72
- },
73
- {
74
- "threshold": 0.64,
75
- "right": 96,
76
- "wrong": 15,
77
- "precision": 0.865
78
- },
79
- {
80
- "threshold": 0.629,
81
- "right": 104,
82
- "wrong": 18,
83
- "precision": 0.852
84
- },
85
- {
86
- "threshold": 0.612,
87
- "right": 114,
88
- "wrong": 19,
89
- "precision": 0.857
90
- },
91
- {
92
- "threshold": 0.6,
93
- "right": 122,
94
- "wrong": 22,
95
- "precision": 0.847
96
- },
97
- {
98
- "threshold": 0.588,
99
- "right": 132,
100
- "wrong": 23,
101
- "precision": 0.852
102
- },
103
- {
104
- "threshold": 0.575,
105
- "right": 140,
106
- "wrong": 26,
107
- "precision": 0.843
108
- },
109
- {
110
- "threshold": 0.565,
111
- "right": 150,
112
- "wrong": 27,
113
- "precision": 0.847
114
- },
115
- {
116
- "threshold": 0.561,
117
- "right": 159,
118
- "wrong": 29,
119
- "precision": 0.846
120
- },
121
- {
122
- "threshold": 0.548,
123
- "right": 168,
124
- "wrong": 31,
125
- "precision": 0.844
126
- },
127
- {
128
- "threshold": 0.532,
129
- "right": 176,
130
- "wrong": 34,
131
- "precision": 0.838
132
- },
133
- {
134
- "threshold": 0.522,
135
- "right": 186,
136
- "wrong": 35,
137
- "precision": 0.842
138
- },
139
- {
140
- "threshold": 0.512,
141
- "right": 194,
142
- "wrong": 38,
143
- "precision": 0.836
144
- },
145
- {
146
- "threshold": 0.501,
147
- "right": 203,
148
- "wrong": 40,
149
- "precision": 0.835
150
- },
151
- {
152
- "threshold": 0.494,
153
- "right": 211,
154
- "wrong": 43,
155
- "precision": 0.831
156
- },
157
- {
158
- "threshold": 0.488,
159
- "right": 221,
160
- "wrong": 44,
161
- "precision": 0.834
162
- },
163
- {
164
- "threshold": 0.484,
165
- "right": 231,
166
- "wrong": 45,
167
- "precision": 0.837
168
- },
169
- {
170
- "threshold": 0.478,
171
- "right": 240,
172
- "wrong": 47,
173
- "precision": 0.836
174
- },
175
- {
176
- "threshold": 0.463,
177
- "right": 245,
178
- "wrong": 53,
179
- "precision": 0.822
180
- },
181
- {
182
- "threshold": 0.458,
183
- "right": 253,
184
- "wrong": 56,
185
- "precision": 0.819
186
- },
187
- {
188
- "threshold": 0.45,
189
- "right": 262,
190
- "wrong": 58,
191
- "precision": 0.819
192
- },
193
- {
194
- "threshold": 0.442,
195
- "right": 270,
196
- "wrong": 61,
197
- "precision": 0.816
198
- },
199
- {
200
- "threshold": 0.433,
201
- "right": 280,
202
- "wrong": 62,
203
- "precision": 0.819
204
- },
205
- {
206
- "threshold": 0.418,
207
- "right": 289,
208
- "wrong": 64,
209
- "precision": 0.819
210
- },
211
- {
212
- "threshold": 0.401,
213
- "right": 299,
214
- "wrong": 65,
215
- "precision": 0.821
216
- },
217
- {
218
- "threshold": 0.38,
219
- "right": 308,
220
- "wrong": 67,
221
- "precision": 0.821
222
- },
223
- {
224
- "threshold": 0.361,
225
- "right": 317,
226
- "wrong": 69,
227
- "precision": 0.821
228
- },
229
- {
230
- "threshold": 0.335,
231
- "right": 327,
232
- "wrong": 70,
233
- "precision": 0.824
234
- },
235
- {
236
- "threshold": 0.313,
237
- "right": 335,
238
- "wrong": 73,
239
- "precision": 0.821
240
- },
241
- {
242
- "threshold": 0.3,
243
- "right": 343,
244
- "wrong": 76,
245
- "precision": 0.819
246
- },
247
- {
248
- "threshold": 0.258,
249
- "right": 352,
250
- "wrong": 78,
251
- "precision": 0.819
252
- }
253
- ]
254
  }
 
1
  {
2
  "format": "oracle7-flash-threshold/1",
3
+ "threshold": 0.733,
4
  "at_threshold": {
5
+ "threshold": 0.733,
6
+ "right": 38,
7
  "wrong": 2,
8
+ "precision": 0.95
9
  },
10
+ "chosen_by": "10.5: relearned after program_score stopped testing one arbitrary word: lowest score at which development additions reach precision >= 0.95 (>= 10 right)",
11
+ "n_scored": 260
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
workbench/swarm/examiner105.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """examiner105 (10.5) - anyone's sealed test. Write a panel of your own questions, seal it, publish its hash, and run it once: the same discipline
2
+ the release is held to, on your machine.
3
+ exam seal <panel.json> check the panel and print its hash, to publish before anything runs
4
+ exam run <panel.json> [out.json] run it through the head. A run is once: its hash is logged and refused the second time
5
+ Panel: {"name": ..., "items": [{"q": question, "a": expected answer, "valid": true} or {"q": question, "valid": false}]}.
6
+ At least 59 items (no failure then bounds the error below 5% at 95%), and at least 20% must-decline, so a system that always answers fails."""
7
+ import hashlib
8
+ import json
9
+ import math
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ MIN_ITEMS, MIN_DECLINE = 59, 0.2
14
+
15
+
16
+ def load(path):
17
+ p = json.load(open(path, encoding="utf-8"))
18
+ items = p.get("items") if isinstance(p, dict) else None
19
+ if not isinstance(items, list) or not items:
20
+ raise ValueError("a panel is {\"items\": [...]}")
21
+ for i, x in enumerate(items):
22
+ if not isinstance(x, dict) or not str(x.get("q", "")).strip() or "valid" not in x:
23
+ raise ValueError("item %d needs q and valid" % i)
24
+ if x["valid"] and x.get("a") in (None, "", []):
25
+ raise ValueError("item %d is valid but has no expected answer" % i)
26
+ seen = {x["q"] for x in items}
27
+ if len(seen) != len(items):
28
+ raise ValueError("a question appears twice")
29
+ must = sum(1 for x in items if not x["valid"])
30
+ if len(items) < MIN_ITEMS or must < MIN_DECLINE * len(items):
31
+ raise ValueError("needs at least %d items and %d%% must-decline; this has %d and %d" % (MIN_ITEMS, MIN_DECLINE * 100, len(items), must))
32
+ return p, items, hashlib.sha256(json.dumps(items, sort_keys=True).encode()).hexdigest()
33
+
34
+
35
+ def bound(n, k=0):
36
+ """the 95% upper bound on the error rate, from k failures in n (exact, by bisection)"""
37
+ lo, hi = 0.0, 1.0
38
+ for _ in range(60):
39
+ m = (lo + hi) / 2
40
+ tail = sum(math.comb(n, i) * m ** i * (1 - m) ** (n - i) for i in range(k + 1))
41
+ lo, hi = (m, hi) if tail > 0.05 else (lo, m)
42
+ return hi
43
+
44
+
45
+ def run(path, engine, log, out=None):
46
+ from handoff101 import answer_of, compare
47
+ _p, items, sha = load(path)
48
+ used = log.read_text(encoding="utf-8").split() if log.exists() else []
49
+ if sha in used:
50
+ raise ValueError("this panel already ran once: write new items for a new test")
51
+ rows, tally = [], {"right": 0, "wrong": 0, "declined": 0, "false_yes": 0, "declined_right": 0}
52
+ for i, x in enumerate(items):
53
+ r = engine.chat(x["q"], "examiner%d" % i)
54
+ a = answer_of(r) if r.get("status") == "answered" else None
55
+ if not x["valid"]:
56
+ k = "false_yes" if a is not None else "declined_right"
57
+ elif a is None:
58
+ k = "declined"
59
+ else:
60
+ want = x["a"] if isinstance(x["a"], list) else [x["a"]]
61
+ k = "right" if any(compare(a, str(w))[0] == "CONFIRMED" for w in want) else "wrong"
62
+ tally[k] += 1
63
+ rows.append({"q": x["q"], "got": a, "result": k, "path": str(r.get("path"))})
64
+ log.parent.mkdir(parents=True, exist_ok=True)
65
+ import declines
66
+ with open(log.parent / "holes_from_exams.jsonl", "a", encoding="utf-8") as f: # every failed question, typed, for the night
67
+ for x, row, k in ((x, row, row["result"]) for x, row in zip(items, rows)):
68
+ if k in ("declined", "wrong", "false_yes"):
69
+ f.write(json.dumps({"q": x["q"], "expect": x.get("a"), "valid": x["valid"], "failed_as": k, "kind": declines.classify(str(row["got"]), row["path"]), "panel": sha}) + "\n")
70
+ with open(log, "a", encoding="utf-8") as f:
71
+ f.write(sha + "\n")
72
+ said = tally["right"] + tally["wrong"] + tally["false_yes"]
73
+ rep = {"panel": sha, "tally": tally, "items": len(items), "error_bound_95": round(bound(said, tally["wrong"] + tally["false_yes"]), 4) if said else None, "rows": rows}
74
+ if out:
75
+ json.dump(rep, open(out, "w", encoding="utf-8"), indent=1, ensure_ascii=False)
76
+ return rep
77
+
78
+
79
+ def main(rest, engine_factory, state):
80
+ if not rest or rest[0] not in ("seal", "run") or len(rest) < 2:
81
+ print(__doc__)
82
+ return 2
83
+ try:
84
+ if rest[0] == "seal":
85
+ _p, items, sha = load(rest[1])
86
+ print("sealed: %d items\nsha256 %s\nPublish this hash before you run it." % (len(items), sha))
87
+ return 0
88
+ rep = run(rest[1], engine_factory(), Path(state) / "examiner_used.txt", rest[2] if len(rest) > 2 else None)
89
+ print(json.dumps({k: rep[k] for k in ("panel", "items", "tally", "error_bound_95")}, indent=1))
90
+ return 0
91
+ except ValueError as e:
92
+ print("refused: %s" % e)
93
+ return 1
94
+
95
+
96
+ if __name__ == "__main__":
97
+ sys.exit(main(sys.argv[1:], None, "."))
workbench/swarm/own_steps.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """own_steps (11.0, plan 5.3) - the reader's steps in Oracle7's own terms. A program is a chain of store operations; KQA's names for them are an export
3
+ format for outside scoring, and nothing more. The map is one-to-one, so the change is proved by running every labelled program both ways: the same
4
+ answer, every time. usage: own_steps.py (runs the proof over every program in KQA validation)"""
5
+ import json
6
+ import sys
7
+
8
+ OWN = {"FindAll": "every_thing", "Find": "named", "FilterConcept": "of_kind", "FilterStr": "with_text", "FilterNum": "with_number", "FilterYear": "with_year", "FilterDate": "with_date",
9
+ "QFilterStr": "where_text", "QFilterNum": "where_number", "QFilterYear": "where_year", "QFilterDate": "where_date", "Relate": "along", "And": "both", "Or": "either", "What": "name_of",
10
+ "Count": "how_many", "QueryAttr": "value_of", "QueryAttrUnderCondition": "value_of_where", "QueryAttrQualifier": "qualifier_of", "QueryRelation": "link_between",
11
+ "QueryRelationQualifier": "qualifier_of_link", "SelectBetween": "pick_between", "SelectAmong": "pick_among", "VerifyStr": "is_text", "VerifyNum": "is_number", "VerifyYear": "is_year", "VerifyDate": "is_date"}
12
+ KQA = {v: k for k, v in OWN.items()}
13
+ assert len(KQA) == len(OWN)
14
+
15
+
16
+ def to_own(program):
17
+ return [dict(st, function=OWN[st["function"]]) for st in program]
18
+
19
+
20
+ def to_export(program):
21
+ return [dict(st, function=KQA[st["function"]]) for st in program]
22
+
23
+
24
+ def run_own(store, program):
25
+ """a program in Oracle7's terms, on the store"""
26
+ return store.run(to_export(program))
27
+
28
+
29
+ if __name__ == "__main__":
30
+ sys.path[:0] = ["/opt/rel102/t/workbench/swarm"]
31
+ import steps_kb as K
32
+ kq = K.KQA()
33
+ tot = same = 0
34
+ bad = []
35
+ for name in ("val",):
36
+ for x in json.load(open("/opt/rel50/kqa/%s.json" % name)):
37
+ tot += 1
38
+
39
+ def go(f, p):
40
+ try:
41
+ return f(p)
42
+ except Exception as e: # noqa: BLE001 - a program that fails must fail the same way
43
+ return "ERR %s" % type(e).__name__
44
+ own = to_own(x["program"])
45
+ assert to_export(own) == x["program"]
46
+ a, b = go(kq.run, x["program"]), go(lambda p: run_own(kq, p), own)
47
+ if a == b or sorted(map(str, a if isinstance(a, (list, set)) else [a])) == sorted(map(str, b if isinstance(b, (list, set)) else [b])):
48
+ same += 1
49
+ else:
50
+ bad.append(x["question"][:80])
51
+ print(json.dumps({"programs": tot, "same_answer": same, "different": bad[:5]}))
workbench/swarm/part11.py ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """part11 (11.0) - THE PART FORMAT, version 1. Growth by declaration: a part is declared data that the format can check, and no kind here
3
+ carries code. It is hashed, so a changed part is a new part, signed by its author (Ed25519, RFC 8032, standard library only), and carries
4
+ its examiner. The format sets the least examiner each kind must have, and an author can only add to it, so nobody can write an easy exam.
5
+ kinds table, formula, alias-set, reading-rule, sensor, example-set, composite
6
+ admit every structural check, with the reason for a refusal. A part that passes still has to sit its exam
7
+ apply runs a declared table or formula; a composite runs its parts in order, with units checked at each join
8
+ usage: part11.py new <part.json> <key.hex> | check <part.json> | keygen"""
9
+ import ast
10
+ import hashlib
11
+ import json
12
+ import math
13
+ import operator
14
+ import sys
15
+
16
+ FORMAT = "oracle7-part-v1"
17
+ KINDS = ("table", "formula", "alias-set", "reading-rule", "sensor", "example-set", "composite")
18
+ FIELDS = {"format", "kind", "version", "interface", "body", "examiner", "provenance", "reach", "id", "author", "signature"}
19
+ LEAST = {"n": 149, "must_decline_share": 0.2} # 5% error bound at 95% over 100 candidates, zero failures, 20% must-decline
20
+
21
+ # ---- Ed25519, RFC 8032 section 6
22
+ _p = 2 ** 255 - 19
23
+ _q = 2 ** 252 + 27742317777372353535851937790883648493
24
+ _d = (-121665 * pow(121666, _p - 2, _p)) % _p
25
+ _I = pow(2, (_p - 1) // 4, _p)
26
+
27
+
28
+ def _sha512(b):
29
+ return hashlib.sha512(b).digest()
30
+
31
+
32
+ def _add(P, Q):
33
+ A, B = (P[1] - P[0]) * (Q[1] - Q[0]) % _p, (P[1] + P[0]) * (Q[1] + Q[0]) % _p
34
+ C, D = 2 * P[3] * Q[3] * _d % _p, 2 * P[2] * Q[2] % _p
35
+ E, F, G, H = B - A, D - C, D + C, B + A
36
+ return (E * F % _p, G * H % _p, F * G % _p, E * H % _p)
37
+
38
+
39
+ def _mul(s, P):
40
+ Q = (0, 1, 1, 0)
41
+ while s:
42
+ if s & 1:
43
+ Q = _add(Q, P)
44
+ P = _add(P, P)
45
+ s >>= 1
46
+ return Q
47
+
48
+
49
+ def _eq(P, Q):
50
+ return (P[0] * Q[2] - Q[0] * P[2]) % _p == 0 and (P[1] * Q[2] - Q[1] * P[2]) % _p == 0
51
+
52
+
53
+ def _xrec(y, sign):
54
+ x2 = (y * y - 1) * pow(_d * y * y + 1, _p - 2, _p) % _p
55
+ if x2 == 0:
56
+ return None if sign else 0
57
+ x = pow(x2, (_p + 3) // 8, _p)
58
+ if (x * x - x2) % _p:
59
+ x = x * _I % _p
60
+ if (x * x - x2) % _p:
61
+ return None
62
+ return _p - x if (x & 1) != sign else x
63
+
64
+
65
+ _gy = 4 * pow(5, _p - 2, _p) % _p
66
+ _gx = _xrec(_gy, 0)
67
+ _G = (_gx, _gy, 1, _gx * _gy % _p)
68
+
69
+
70
+ def _comp(P):
71
+ zi = pow(P[2], _p - 2, _p)
72
+ x, y = P[0] * zi % _p, P[1] * zi % _p
73
+ return int.to_bytes(y | ((x & 1) << 255), 32, "little")
74
+
75
+
76
+ def _dec(b):
77
+ y = int.from_bytes(b, "little")
78
+ sign = y >> 255
79
+ y &= (1 << 255) - 1
80
+ if y >= _p:
81
+ return None
82
+ x = _xrec(y, sign)
83
+ return None if x is None else (x, y, 1, x * y % _p)
84
+
85
+
86
+ def _expand(sk):
87
+ h = _sha512(sk)
88
+ a = int.from_bytes(h[:32], "little")
89
+ a &= (1 << 254) - 8
90
+ a |= 1 << 254
91
+ return a, h[32:]
92
+
93
+
94
+ def public_key(sk):
95
+ return _comp(_mul(_expand(sk)[0], _G))
96
+
97
+
98
+ def sign(sk, msg):
99
+ a, pre = _expand(sk)
100
+ A = _comp(_mul(a, _G))
101
+ r = int.from_bytes(_sha512(pre + msg), "little") % _q
102
+ R = _comp(_mul(r, _G))
103
+ h = int.from_bytes(_sha512(R + A + msg), "little") % _q
104
+ return R + int.to_bytes((r + h * a) % _q, 32, "little")
105
+
106
+
107
+ def verify(pk, msg, sig):
108
+ if len(pk) != 32 or len(sig) != 64:
109
+ return False
110
+ A, R = _dec(pk), _dec(sig[:32])
111
+ s = int.from_bytes(sig[32:], "little")
112
+ if A is None or R is None or s >= _q:
113
+ return False
114
+ h = int.from_bytes(_sha512(sig[:32] + pk + msg), "little") % _q
115
+ return _eq(_mul(s, _G), _add(R, _mul(h, A)))
116
+
117
+
118
+ # ---- the part
119
+ def canonical(part):
120
+ return json.dumps({k: part[k] for k in sorted(part) if k not in ("id", "signature")}, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
121
+
122
+
123
+ def seal(part, sk):
124
+ """an author's part, hashed and signed"""
125
+ p = dict(part, format=FORMAT, author=public_key(sk).hex())
126
+ p["id"] = hashlib.sha256(canonical(p)).hexdigest()
127
+ p["signature"] = sign(sk, bytes.fromhex(p["id"])).hex()
128
+ return p
129
+
130
+
131
+ OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Pow: operator.pow}
132
+ FUNCS = {"sqrt": math.sqrt, "log": math.log, "exp": math.exp, "sin": math.sin, "cos": math.cos, "abs": abs}
133
+
134
+
135
+ def _formula_ok(expr, inputs):
136
+ """a formula is arithmetic over its declared inputs: anything else in it is refused"""
137
+ try:
138
+ tree = ast.parse(expr, mode="eval")
139
+ except SyntaxError:
140
+ return False
141
+ for n in ast.walk(tree):
142
+ if isinstance(n, ast.Name):
143
+ if n.id not in inputs and n.id not in FUNCS:
144
+ return False
145
+ elif isinstance(n, ast.Call):
146
+ if not (isinstance(n.func, ast.Name) and n.func.id in FUNCS) or n.keywords:
147
+ return False
148
+ elif isinstance(n, ast.Constant):
149
+ if type(n.value) not in (int, float):
150
+ return False
151
+ elif not isinstance(n, (ast.Expression, ast.BinOp, ast.UnaryOp, ast.USub, ast.Load) + tuple(OPS)):
152
+ return False
153
+ return True
154
+
155
+
156
+ def _eval(n, env):
157
+ if isinstance(n, ast.Expression):
158
+ return _eval(n.body, env)
159
+ if isinstance(n, ast.Constant):
160
+ return n.value
161
+ if isinstance(n, ast.Name):
162
+ return env[n.id]
163
+ if isinstance(n, ast.UnaryOp):
164
+ return -_eval(n.operand, env)
165
+ if isinstance(n, ast.BinOp):
166
+ return OPS[type(n.op)](_eval(n.left, env), _eval(n.right, env))
167
+ return FUNCS[n.func.id](*[_eval(a, env) for a in n.args])
168
+
169
+
170
+ def _typed(xs):
171
+ return isinstance(xs, list) and xs and all(isinstance(x, dict) and isinstance(x.get("name"), str) and x["name"] and "unit" in x for x in xs)
172
+
173
+
174
+ def _body_ok(kind, body, face, known):
175
+ ins = {x["name"] for x in face["inputs"]} if face.get("inputs") else set()
176
+ if kind == "table":
177
+ rows = body.get("rows")
178
+ return isinstance(rows, list) and rows and all(isinstance(r, list) and len(r) == len(face["inputs"]) + len(face["outputs"]) for r in rows)
179
+ if kind == "formula":
180
+ return isinstance(body.get("expr"), str) and _formula_ok(body["expr"], ins) and bool(body.get("source"))
181
+ if kind == "alias-set":
182
+ a = body.get("aliases")
183
+ return isinstance(a, dict) and a and all(isinstance(k, str) and isinstance(v, str) for k, v in a.items())
184
+ if kind == "reading-rule":
185
+ return isinstance(body.get("shape"), str) and bool(body["shape"].strip())
186
+ if kind == "sensor":
187
+ return isinstance(body.get("channel"), str) and isinstance(body.get("ideal"), list) and len(body["ideal"]) == 2 and body["ideal"][0] < body["ideal"][1] and isinstance(body.get("range"), list) and len(body["range"]) == 2 and body["range"][0] <= body["ideal"][0] and body["ideal"][1] <= body["range"][1]
188
+ if kind == "example-set":
189
+ ex = body.get("examples")
190
+ return isinstance(ex, list) and ex and all(isinstance(x, dict) and x.get("cite") for x in ex)
191
+ if kind == "composite":
192
+ path = body.get("path")
193
+ if not (isinstance(path, list) and len(path) >= 2 and all(p in known for p in path)):
194
+ return False
195
+ for a, b in zip(path, path[1:]):
196
+ need = {x["unit"] for x in known[b]["interface"]["inputs"]}
197
+ if not {x["unit"] for x in known[a]["interface"]["outputs"]} & need:
198
+ return False
199
+ return True
200
+ return False
201
+
202
+
203
+ def admit(part, known=None, trusted=None):
204
+ """structure, hash, signature, least examiner -> (True, "admitted") or (False, why). known: {id: part} for composites"""
205
+ if not isinstance(part, dict):
206
+ return False, "not a part"
207
+ if set(part) - FIELDS or FIELDS - {"reach"} - set(part):
208
+ return False, "fields: %s" % sorted((set(part) - FIELDS) | (FIELDS - {"reach"} - set(part)))
209
+ if part["format"] != FORMAT or part["kind"] not in KINDS:
210
+ return False, "format or kind"
211
+ face = part["interface"]
212
+ if not isinstance(face, dict) or not _typed(face.get("outputs")) or (part["kind"] not in ("sensor", "alias-set", "example-set", "composite") and not _typed(face.get("inputs"))):
213
+ return False, "interface must be typed, with units"
214
+ ex = part["examiner"]
215
+ if not isinstance(ex, dict) or not ex.get("generator") or not isinstance(ex.get("n"), int) or not isinstance(ex.get("must_decline_share"), (int, float)):
216
+ return False, "no examiner"
217
+ if ex["n"] < LEAST["n"] or ex["must_decline_share"] < LEAST["must_decline_share"]:
218
+ return False, "examiner below the format's least: %d items, %d%% must-decline" % (LEAST["n"], LEAST["must_decline_share"] * 100)
219
+ if ex.get("seed_rule") not in ("block-hash", "fixed-then-audited"):
220
+ return False, "an examiner's seed must come from outside the author"
221
+ ran = ex.get("ran") # a declared exam isn't an exam: the part carries the record of the one that ran
222
+ if not isinstance(ran, dict) or not isinstance(ran.get("items"), int) or ran.get("failed") != 0 or ran["items"] < ex["n"]:
223
+ return False, "no record of a passed exam of %d items" % ex["n"]
224
+ prov = part["provenance"]
225
+ if not isinstance(prov, dict) or not prov.get("sources"):
226
+ return False, "provenance"
227
+ if not _body_ok(part["kind"], part["body"], face, known or {}):
228
+ return False, "body doesn't hold for kind %s" % part["kind"]
229
+ if part["id"] != hashlib.sha256(canonical(part)).hexdigest():
230
+ return False, "hash"
231
+ try:
232
+ good = verify(bytes.fromhex(part["author"]), bytes.fromhex(part["id"]), bytes.fromhex(part["signature"]))
233
+ except (ValueError, TypeError):
234
+ good = False
235
+ if not good:
236
+ return False, "signature"
237
+ if trusted is not None and part["author"] not in trusted:
238
+ return False, "author not trusted here"
239
+ return True, "admitted"
240
+
241
+
242
+ def apply(part, inputs, known=None):
243
+ """run a declared part on named inputs -> value or None. No kind here runs code from the part"""
244
+ k = part["kind"]
245
+ names = [x["name"] for x in part["interface"].get("inputs") or []]
246
+ if k == "table":
247
+ key = [inputs.get(n) for n in names]
248
+ return next((r[len(names):] for r in part["body"]["rows"] if r[:len(names)] == key), None)
249
+ if k == "formula":
250
+ return _eval(ast.parse(part["body"]["expr"], mode="eval"), {**FUNCS, **inputs}) if all(n in inputs for n in names) else None
251
+ if k == "sensor": # a reading passes only inside the sensor's stated range
252
+ lo, hi = part["body"]["range"]
253
+ x = next(iter(inputs.values()), None)
254
+ return x if isinstance(x, (int, float)) and lo <= x <= hi else None
255
+ if k == "composite":
256
+ v, cur = inputs, None
257
+ for pid in part["body"]["path"]:
258
+ p = known[pid]
259
+ cur = apply(p, v, known)
260
+ if cur is None:
261
+ return None
262
+ cur = cur[0] if isinstance(cur, list) else cur
263
+ n = [x["name"] for x in (known[part["body"]["path"][min(part["body"]["path"].index(pid) + 1, len(part["body"]["path"]) - 1)]]["interface"].get("inputs") or [{"name": "x"}])][0]
264
+ v = {n: cur}
265
+ return cur
266
+ return None
267
+
268
+
269
+ def _suite():
270
+ """RFC 8032 vector, then parts that must be admitted and parts that must be refused"""
271
+ sk = bytes.fromhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
272
+ assert public_key(sk).hex() == "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
273
+ assert sign(sk, b"").hex().startswith("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46b")
274
+ ex = {"generator": "store", "seed_rule": "block-hash", "n": 149, "must_decline_share": .2, "ran": {"items": 149, "failed": 0}}
275
+ base = {"version": 1, "examiner": ex, "provenance": {"sources": ["test"]}, "reach": None}
276
+ ohm = dict(base, kind="formula", interface={"inputs": [{"name": "v", "unit": "V"}, {"name": "r", "unit": "ohm"}], "outputs": [{"name": "i", "unit": "A"}]}, body={"expr": "v / r", "source": "Ohm's law"})
277
+ tbl = dict(base, kind="table", interface={"inputs": [{"name": "m", "unit": "symbol"}], "outputs": [{"name": "z", "unit": "count"}]}, body={"rows": [["H", 1], ["He", 2]]})
278
+ sq = dict(base, kind="formula", interface={"inputs": [{"name": "i", "unit": "A"}], "outputs": [{"name": "p", "unit": "W"}]}, body={"expr": "i ** 2 * 5", "source": "P = I^2 R at 5 ohm"})
279
+ good = {"formula": seal(ohm, sk), "table": seal(tbl, sk), "formula2": seal(sq, sk)}
280
+ known = {p["id"]: p for p in good.values()}
281
+ comp = seal(dict(base, kind="composite", interface={"inputs": ohm["interface"]["inputs"], "outputs": sq["interface"]["outputs"]}, body={"path": [good["formula"]["id"], good["formula2"]["id"]]}), sk)
282
+ known[comp["id"]] = comp
283
+ res = {"admitted": [admit(p, known) for p in list(good.values()) + [comp]]}
284
+ assert all(r[0] for r in res["admitted"]), res
285
+ assert apply(good["formula"], {"v": 10, "r": 2}) == 5 and apply(good["table"], {"m": "He"}) == [2] and apply(comp, {"v": 10, "r": 2}, known) == 125
286
+ p = good["formula"]
287
+ other = bytes(range(32))
288
+ bad = {"malformed": "nope", "unknown field": dict(p, evil=1), "unsigned": dict(p, signature="00" * 64), "body changed after sealing": dict(p, body={"expr": "v * r", "source": "x"}),
289
+ "foreign signature": dict(p, signature=sign(other, bytes.fromhex(p["id"])).hex()),
290
+ "code in a formula": seal(dict(ohm, body={"expr": "__import__('os').system('x')", "source": "x"}), sk), "attribute in a formula": seal(dict(ohm, body={"expr": "v.__class__", "source": "x"}), sk),
291
+ "undeclared input": seal(dict(ohm, body={"expr": "v / q", "source": "x"}), sk), "no units": seal(dict(ohm, interface={"inputs": [{"name": "v"}], "outputs": [{"name": "i", "unit": "A"}]}), sk),
292
+ "easy examiner": seal(dict(ohm, examiner=dict(ex, n=5)), sk), "no must-decline": seal(dict(ohm, examiner=dict(ex, must_decline_share=0)), sk), "exam never run": seal(dict(ohm, examiner={k: v for k, v in ex.items() if k != "ran"}), sk), "exam failed": seal(dict(ohm, examiner=dict(ex, ran={"items": 149, "failed": 1})), sk), "exam smaller than declared": seal(dict(ohm, examiner=dict(ex, ran={"items": 20, "failed": 0})), sk), "author's own seed": seal(dict(ohm, examiner=dict(ex, seed_rule="mine")), sk),
293
+ "no provenance": seal(dict(ohm, provenance={}), sk), "unit mismatch at a join": seal(dict(base, kind="composite", interface=comp["interface"], body={"path": [good["table"]["id"], good["formula2"]["id"]]}), sk),
294
+ "example with no citation": seal(dict(base, kind="example-set", interface={"outputs": [{"name": "label", "unit": "class"}]}, body={"examples": [{"x": 1}]}), sk)}
295
+ res["refused"] = {k: admit(v, known) for k, v in bad.items()}
296
+ wrong = [k for k, v in res["refused"].items() if v[0]]
297
+ tr = admit(good["table"], known, trusted={"someone else"})
298
+ assert not tr[0]
299
+ return {"admitted": len(res["admitted"]), "refused": len(bad) - len(wrong), "of": len(bad), "wrongly admitted": wrong, "why": {k: v[1] for k, v in res["refused"].items()}}
300
+
301
+
302
+ if __name__ == "__main__":
303
+ a = sys.argv[1:]
304
+ if a[:1] == ["suite"] or not a:
305
+ print(json.dumps(_suite(), indent=1))
306
+ elif a[0] == "keygen":
307
+ import secrets
308
+ print(secrets.token_hex(32))
309
+ elif a[0] == "new":
310
+ print(json.dumps(seal(json.load(open(a[1], encoding="utf-8")), bytes.fromhex(open(a[2]).read().strip())), indent=1, ensure_ascii=False))
311
+ elif a[0] == "check":
312
+ print(admit(json.load(open(a[1], encoding="utf-8"))))
workbench/swarm/part_submit.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """part_submit (11.0) - a part in the format joins exactly as an internal one does. It is checked against the format, then becomes a specialist in your workspace
2
+ through the same builders the swarm uses (a table through to_workbench, a formula through compose), and it joins only by the same exam: `exam <id> dev`, then
3
+ `exam <id> heldout`. usage: oracle7.py part check|submit <part.json>"""
4
+ import json
5
+ import os
6
+ import random
7
+ import re
8
+ import shutil
9
+ import sys
10
+ import tempfile
11
+ from pathlib import Path
12
+
13
+ import exam_v1
14
+ import part11 as P
15
+ import run as swarm_run
16
+
17
+
18
+ def _id(part, stem):
19
+ name = re.sub(r"[^a-z0-9]+", "-", str(part["provenance"].get("name") or part["id"][:12]).lower()).strip("-")
20
+ return ("%s-%s" % (stem, name))[:40].rstrip("-")
21
+
22
+
23
+ def table(part):
24
+ """rows as a table the swarm's own organ reads, with the swarm's own exam panel"""
25
+ b = part["body"]
26
+ info, rows = b.get("property"), b.get("records")
27
+ if not isinstance(info, dict) or not info.get("label") or not isinstance(rows, list) or not all(isinstance(r, dict) and r.get("label") and "value" in r for r in rows):
28
+ return None, "a table part to install needs body.property and body.records"
29
+ info = dict({"aliases": [], "datatype": "string", "pid": "part"}, **info)
30
+ rows = [dict({"qid": "P%d" % i, "aliases": []}, **r) for i, r in enumerate(rows)]
31
+ tmp = Path(tempfile.mkdtemp(prefix="o7part-"))
32
+ here = Path(__file__).resolve().parent
33
+ json.dump({"property": info, "rows": rows, "source": {"repo": "part", "sha256": part["id"], "licence": part["provenance"].get("licence", "see provenance")}}, open(tmp / "table.json", "w", encoding="utf-8"), ensure_ascii=False)
34
+ for f in ("organ_table_v2.py", "scorer_table_v1.py", "narrow_v1.py"):
35
+ shutil.copy(here / f, tmp / f)
36
+ json.dump(exam_v1.write(info, rows), open(tmp / "panel_v1.json", "w", encoding="utf-8"), ensure_ascii=False)
37
+ names = [info["label"]] + [a for a in info["aliases"] if len(a) >= 3]
38
+ json.dump({"name": "Part: %s" % info["label"], "sources": [{"name": "part %s" % part["id"][:16], "licence": part["provenance"].get("licence", "see provenance")}],
39
+ "accept": {"kinds": ["lookup"], "declines_in_domain": ["unknown_item"], "context_regex": r"\b(?:%s)\b" % "|".join(re.escape(n.lower()) for n in sorted(names, key=len, reverse=True))}, "notes": "Submitted in the part format."},
40
+ open(tmp / "manifest.json", "w", encoding="utf-8"), ensure_ascii=False)
41
+ d = swarm_run.to_workbench(tmp, _id(part, "part"), built_from={"questions": ["What is the %s of %s?" % (info["label"], r["label"]) for r in rows[:3]], "gap": info["label"]})
42
+ shutil.rmtree(tmp, ignore_errors=True)
43
+ return d, "installed"
44
+
45
+
46
+ def formula(part):
47
+ """worked examples from the formula, written by the format's own arithmetic, composed by the swarm's composer: it has to arrive at the same program"""
48
+ face = part["interface"]
49
+ rng = random.Random(part["id"])
50
+ lo, hi = part["body"].get("example_range", [1, 50])
51
+ lines = ["title: %s" % (part["provenance"].get("name") or part["id"][:12]), "inputs: " + "; ".join("%s (%s)" % (x.get("label", x["name"]), x["unit"]) for x in face["inputs"]),
52
+ "output: %s (%s)" % (face["outputs"][0]["name"], face["outputs"][0]["unit"]), "source: %s" % part["body"]["source"]]
53
+ for _ in range(16):
54
+ v = {x["name"]: round(rng.uniform(lo, hi), 3) for x in face["inputs"]}
55
+ lines.append("%s -> %s" % ("; ".join(str(v[x["name"]]) for x in face["inputs"]), round(P.apply(part, v), 6)))
56
+ r = swarm_run.compose("\n".join(lines), sid=_id(part, "part"), anyway=True)
57
+ return r.get("specialist"), r["message"]
58
+
59
+
60
+ def main(rest):
61
+ if len(rest) < 2 or rest[0] not in ("check", "submit"):
62
+ print(__doc__)
63
+ return 2
64
+ part = json.load(open(rest[1], encoding="utf-8"))
65
+ ok, why = P.admit(part)
66
+ print("format: %s" % why)
67
+ if not ok or rest[0] == "check":
68
+ return 0 if ok else 1
69
+ go = {"table": table, "formula": formula}.get(part["kind"])
70
+ if go is None:
71
+ print("kind %s is held as a part; it has no specialist to install" % part["kind"])
72
+ return 0
73
+ d, msg = go(part)
74
+ print(msg)
75
+ if d:
76
+ print("now the same exam as any part:\n python oracle7.py exam %s dev\n python oracle7.py exam %s heldout" % (Path(d).name, Path(d).name))
77
+ return 0 if d else 1
78
+
79
+
80
+ if __name__ == "__main__":
81
+ sys.exit(main(sys.argv[1:]))
workbench/swarm/ref_measure.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """ref_measure - the sleep week's reference set measured in a fresh process, as the head stands after a night (11-04). Sleep's only change
3
+ that lives in memory is the alignment table the learner keeps; everything else (built specialists, retired parts) is on disk. So a fresh
4
+ head on the same tree and workbench, with that night's table loaded, is the head sleep has made, and a clean state folder makes every
5
+ measure a first ask. Runs beside the next night: the week takes about half the time.
6
+ usage: ref_measure.py <reference.json> <out.json> (env: O7_TREE_RUN, ORACLE7_WORKBENCH_LOCAL, ORACLE7_STATE, O7_ALIGN optional)"""
7
+ import collections
8
+ import importlib.util
9
+ import json
10
+ import os
11
+ import sys
12
+ import time
13
+
14
+ if os.environ.get("PYTHONHASHSEED") != "0":
15
+ os.environ["PYTHONHASHSEED"] = "0"
16
+ os.execv(sys.executable, [sys.executable] + sys.argv)
17
+ REF, OUTF = sys.argv[1], sys.argv[2]
18
+ T = os.environ["O7_TREE_RUN"]
19
+ spec = importlib.util.spec_from_file_location("o7", T + "/oracle7.py")
20
+ o7 = importlib.util.module_from_spec(spec)
21
+ sys.argv = ["oracle7.py"]
22
+ spec.loader.exec_module(o7)
23
+ sys.path[:0] = [T + "/workbench", T + "/workbench/swarm"]
24
+ import server # noqa: E402
25
+ eng = o7.Engine()
26
+ wb = server.WB = server.Workbench(eng)
27
+ rt = getattr(eng, "_rt", None)
28
+ for _ in range(60):
29
+ if rt is not None and ("facts" in rt._organs or "error" in rt._organs):
30
+ break
31
+ time.sleep(5)
32
+ from handoff101 import answer_of, compare # noqa: E402
33
+
34
+ align = os.environ.get("O7_ALIGN")
35
+ if align: # the learner's kept table, loaded the way sleep105.dream_learner loads it
36
+ cov = rt._organs["facts"].coverage
37
+ A = json.load(open(align, encoding="utf-8"))["p_feature_given_word"]
38
+ cov.align_t = A
39
+ cov.align_w = collections.defaultdict(dict)
40
+ for f, ws in A.items():
41
+ for w, pr in ws.items():
42
+ cov.align_w[w][f] = pr
43
+ cov.lex.words |= set(cov.align_w)
44
+ if hasattr(cov, "_dir_mass"):
45
+ del cov._dir_mass
46
+
47
+
48
+ def right(answer, want):
49
+ if answer is None or want in (None, "", []):
50
+ return False
51
+ return any(compare(str(answer), str(w))[0] == "CONFIRMED" for w in (want if isinstance(want, list) else [want]))
52
+
53
+
54
+ reference = json.load(open(REF, encoding="utf-8"))
55
+ n = wrong = 0
56
+ rows = []
57
+ for k, it in enumerate(reference):
58
+ r = wb.chat(it["q"], "ref%d" % k)
59
+ a = answer_of(r) if r.get("status") == "answered" else None
60
+ ok = a is not None and right(a, it["expect"])
61
+ n += ok
62
+ wrong += a is not None and not ok
63
+ rows.append({"k": k, "status": r.get("status"), "right": ok, "path": str(r.get("path"))[:80]})
64
+ json.dump({"right": n, "wrong": wrong, "of": len(reference), "align": align, "rows": rows}, open(OUTF, "w", encoding="utf-8"))
65
+ print("done", n, wrong, flush=True)
workbench/swarm/skeleton102.py CHANGED
@@ -18,6 +18,8 @@ import collections
18
  import copy
19
  import json
20
  import re
 
 
21
  import time
22
 
23
  import reader_v4 as R4
@@ -254,10 +256,10 @@ class Skeletons:
254
  self._pc = {} # prefix -> what the store returns, once per parse
255
  self._said = {} # (function, inputs) -> the judge's "said by a word" verdict on that step alone
256
  self._jc = {} # step-set -> the judge's verdict on the whole program (order does not change it)
257
- deadline = time.time() + budget
258
  out = []
259
  for sk in self.sk:
260
- if time.time() > deadline or len(out) >= 40:
261
  break
262
  slots = collections.Counter(s for _f, _d, sl in sk for s in sl if s in ("E", "V", "C"))
263
  if any(slots[t] > len(fill[t]) for t in ("E", "V")):
@@ -286,7 +288,7 @@ class Skeletons:
286
  return key in self._ckeys_c[concept]
287
 
288
  def _dfs(self, question, sk, fill, prog, results, i, usedEV, out, deadline):
289
- if time.time() > deadline or len(out) >= 40:
290
  return
291
  rd = self.rd
292
  if i == len(sk):
@@ -387,7 +389,7 @@ class Skeletons:
387
  ran.append((empty, ins, p2, res))
388
  ran.sort(key=lambda t_: t_[0])
389
  for empty, ins, p2, res in ran:
390
- if time.time() > deadline or len(out) >= 40:
391
  return
392
  r2 = results + [i] if empty else results
393
  u2 = {"E": set(usedEV["E"]), "V": set(usedEV["V"])}
 
18
  import copy
19
  import json
20
  import re
21
+
22
+ import steps_budget as SB
23
  import time
24
 
25
  import reader_v4 as R4
 
256
  self._pc = {} # prefix -> what the store returns, once per parse
257
  self._said = {} # (function, inputs) -> the judge's "said by a word" verdict on that step alone
258
  self._jc = {} # step-set -> the judge's verdict on the whole program (order does not change it)
259
+ deadline = SB.deadline()
260
  out = []
261
  for sk in self.sk:
262
+ if SB.spent() > deadline or len(out) >= 40:
263
  break
264
  slots = collections.Counter(s for _f, _d, sl in sk for s in sl if s in ("E", "V", "C"))
265
  if any(slots[t] > len(fill[t]) for t in ("E", "V")):
 
288
  return key in self._ckeys_c[concept]
289
 
290
  def _dfs(self, question, sk, fill, prog, results, i, usedEV, out, deadline):
291
+ if SB.spent() > deadline or len(out) >= 40:
292
  return
293
  rd = self.rd
294
  if i == len(sk):
 
389
  ran.append((empty, ins, p2, res))
390
  ran.sort(key=lambda t_: t_[0])
391
  for empty, ins, p2, res in ran:
392
+ if SB.spent() > deadline or len(out) >= 40:
393
  return
394
  r2 = results + [i] if empty else results
395
  u2 = {"E": set(usedEV["E"]), "V": set(usedEV["V"])}
workbench/swarm/sleep105.py ADDED
@@ -0,0 +1,457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """sleep105 (10.5) - THE SLEEP CYCLE, the roadmap's five steps, connecting what the release already has. A day's turns go through the Workbench chat, the
3
+ same door a person uses, and are written to a chained ledger. Then, each night:
4
+ 1 REPLAY the day's ledger is re-run. Declined turns become typed holes (declines.classify); answered turns are re-checked against everything
5
+ admitted that day, and a changed answer is reported.
6
+ 2 CONSOLIDATION paths the router walked repeatedly become candidate composite parts, admitted only if they give the same answers as the path they
7
+ replace and pass an exam on fresh inputs (the day's questions with their numbers nudged).
8
+ 3 DREAMING each hole is searched for across formulas (grow60: the library's chains, built by the swarm), stores (declines.resolve, and held-back readings a known
9
+ answer confirms, which feed the learner) and specialists (swarm build: a table from Wikidata, through its own held-out exam).
10
+ 4 FORGETTING unused or dominated parts are retired with a receipt. A retired part stays in the content-addressed store, so replay can still reach it.
11
+ 5 MORNING reach gained, holes filled, answers changed, parts retired, with no new wrong answer as the bar. Reports are chained.
12
+ Days are Oracle7's own failures from v37 to v38 (failure_days.py) unless O7_DAYS names sealed days. Simulated nights: each day is a batch.
13
+ usage: sleep105.py <gate rows.jsonl for the controls> <out dir> [nights] [turns per day]"""
14
+ import collections
15
+ import hashlib
16
+ import importlib.util
17
+ import json
18
+ import os
19
+ import random
20
+ import re
21
+ import shutil
22
+ import subprocess
23
+ import sys
24
+ import time
25
+ from pathlib import Path
26
+
27
+ if os.environ.get("PYTHONHASHSEED") != "0": # the same order oracle7.py and the gate run in
28
+ os.environ["PYTHONHASHSEED"] = "0"
29
+ os.execv(sys.executable, [sys.executable] + sys.argv)
30
+
31
+ rows, OUT, NIGHTS, PER = sys.argv[1], Path(sys.argv[2]), int(sys.argv[3]) if len(sys.argv) > 3 else 7, int(sys.argv[4]) if len(sys.argv) > 4 else 300
32
+ for d in ("parts", "store", "ledger"):
33
+ (OUT / d).mkdir(parents=True, exist_ok=True)
34
+ T = os.environ["O7_TREE_RUN"]
35
+ LOCAL = Path(os.environ["ORACLE7_WORKBENCH_LOCAL"])
36
+ (LOCAL / "specialists").mkdir(parents=True, exist_ok=True)
37
+ spec = importlib.util.spec_from_file_location("o7", T + "/oracle7.py")
38
+ o7 = importlib.util.module_from_spec(spec)
39
+ sys.argv = ["oracle7.py"]
40
+ spec.loader.exec_module(o7)
41
+ sys.path[:0] = [T + "/workbench", T + "/workbench/swarm"]
42
+ import server # noqa: E402
43
+ import exam # noqa: E402
44
+ eng = o7.Engine()
45
+ wb = server.WB = server.Workbench(eng)
46
+ rt = getattr(eng, "_rt", None)
47
+ for _ in range(60):
48
+ if rt is not None and ("facts" in rt._organs or "error" in rt._organs):
49
+ break
50
+ time.sleep(5)
51
+ import declines # noqa: E402
52
+ import grow60 as G # noqa: E402
53
+ import gen60 # noqa: E402
54
+ import part11 as P11 # noqa: E402
55
+ import run as swarm_run # noqa: E402
56
+ from handoff101 import answer_of, compare # noqa: E402
57
+
58
+ KEYF = OUT / "author.key"
59
+ if not KEYF.exists():
60
+ KEYF.write_text(os.urandom(32).hex())
61
+ KEYF.chmod(0o600)
62
+ KEY = bytes.fromhex(KEYF.read_text().strip())
63
+ REPEAT, GROUP, UNUSED_NIGHTS, CAP = 3, 3, 3, 4 # walked 3 times; 3 example questions (swarm build's own minimum); unused 3 nights; systems a night (grow60's CAP)
64
+ GROW = Path(os.environ.get("O7_GROW", str(OUT / "grow")))
65
+ if not GROW.exists():
66
+ shutil.copytree(Path(T) / "root" / "organs10" / "physics", GROW)
67
+ LIB = gen60.Library(json.load(open(Path(T) / "root" / "organs10" / "library" / "derived.json", encoding="utf-8")))
68
+ STATE = {"started": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "run": os.environ.get("O7_RUN", "Trial, on Oracle7's own failures"), "nights": NIGHTS, "per_day": PER, "night": 0, "phase": "starting",
69
+ "history": [], "parts": [], "retired": [], "exams": [], "changed": [], "finds": [], "found_right": 0, "planted": [], "reference": [], "totals": {"turns": 0, "holes": 0, "found": 0, "admitted": 0, "reach": 0, "wrong": 0, "false_yes": 0}}
70
+
71
+
72
+ def save():
73
+ STATE["updated"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
74
+ tmp = OUT / "state.tmp"
75
+ tmp.write_text(json.dumps(STATE, indent=1, default=str), encoding="utf-8")
76
+ tmp.replace(OUT / "state.json")
77
+
78
+
79
+ def phase(text, cur=None):
80
+ STATE["phase"] = text
81
+ if cur is not None:
82
+ STATE["current"] = dict(cur)
83
+ save()
84
+
85
+
86
+ def right(answer, want):
87
+ if answer is None or want in (None, "", []):
88
+ return False
89
+ return any(compare(str(answer), str(w))[0] == "CONFIRMED" for w in (want if isinstance(want, list) else [want]))
90
+
91
+
92
+ def turn(q, sid):
93
+ r = wb.chat(q, sid)
94
+ return r, (answer_of(r) if r.get("status") == "answered" else None)
95
+
96
+
97
+ class Ledger:
98
+ def __init__(self, path):
99
+ self.path, self.prev = Path(path), "0" * 64
100
+
101
+ def add(self, e):
102
+ e = dict(e, prev=self.prev)
103
+ e["hash"] = self.prev = hashlib.sha256(json.dumps(e, sort_keys=True, default=str).encode()).hexdigest()
104
+ with open(self.path, "a", encoding="utf-8") as f:
105
+ f.write(json.dumps(e, default=str) + "\n")
106
+
107
+
108
+ def sig(path):
109
+ """a router path with what varies taken out"""
110
+ return re.sub(r"\b[0-9a-f]{8,}\b", "#", re.sub(r"\d+(\.\d+)?", "#", str(path)))
111
+
112
+
113
+ def nudge(q, rng):
114
+ """a day's question with its numbers moved: a fresh input of the same form"""
115
+ return re.sub(r"(?<![\w.])\d+(?:\.\d+)?(?![\w.])", lambda m: str(max(1, round(float(m.group(0)) * rng.uniform(1.1, 1.9)))), q)
116
+
117
+
118
+ def keep_part(body, kind, name, ran, sources, face=None):
119
+ """a passed exam -> a sealed part in the 11.0 format, stored by its hash, never overwritten"""
120
+ p = P11.seal({"kind": kind, "version": 1, "interface": face or {"inputs": [{"name": "question", "unit": "text"}], "outputs": [{"name": "answer", "unit": "text"}]}, "body": body,
121
+ "examiner": {"generator": "variations of the day's questions", "seed_rule": "fixed-then-audited", "n": ran["items"], "must_decline_share": .2, "ran": ran}, "provenance": {"sources": sources, "name": name}, "reach": None}, KEY)
122
+ (OUT / "store" / (p["id"] + ".json")).write_text(json.dumps(p, indent=1), encoding="utf-8")
123
+ return p
124
+
125
+
126
+ # ---- 1 REPLAY
127
+ def replay(day_rows, night):
128
+ holes, changed = [], []
129
+ for k, e in enumerate(day_rows):
130
+ r, a = turn(e["q"], "replay%d-%d" % (night, k))
131
+ if e["status"] == "answered":
132
+ if a != e["answer"] and not str(e["path"]).startswith("yours:formula"):
133
+ changed.append({"q": e["q"], "was": e["answer"], "now": a, "path": str(r.get("path"))})
134
+ elif a is None:
135
+ holes.append({"q": e["q"], "kind": declines.classify(str(r.get("response", "")), str(r.get("path", ""))), "path": str(r.get("path", "")), "expect": e.get("expect"), "valid": e.get("valid", True),
136
+ "held": (r.get("provenance") or {}).get("held_back")})
137
+ if k % 10 == 0:
138
+ phase("replay: turn %d of %d" % (k, len(day_rows)))
139
+ return holes, changed
140
+
141
+
142
+ # ---- 2 CONSOLIDATION
143
+ def consolidate(day_rows, night):
144
+ """repeated paths through one part become a direct route, only if it answers as the walked path does"""
145
+ walked = collections.defaultdict(list)
146
+ for e in day_rows:
147
+ m = re.search(r"(?:yours|specialist):([\w.-]+)", str(e.get("path", "")))
148
+ if e["status"] == "answered" and m:
149
+ walked[(sig(e["path"]), m.group(1))].append(e)
150
+ done = []
151
+ rng = random.Random(night)
152
+ for (s, sid), es in sorted(walked.items(), key=lambda kv: -len(kv[1])):
153
+ if len(es) < REPEAT or sid not in wb.mine:
154
+ continue
155
+ sp, same = wb.mine[sid], 0
156
+ for e in es:
157
+ a = sp.reply(e["q"], "route")
158
+ same += bool(a) and a.get("status") == "answered" and right(answer_of(a), e["answer"])
159
+ fresh = [nudge(e["q"], rng) for e in es for _ in range(50)][:149]
160
+ agree = 0
161
+ for k, q in enumerate(fresh):
162
+ a = sp.reply(q, "route")
163
+ _r, b = turn(q, "fresh%d" % k)
164
+ agree += (a is None and b is None) or (bool(a) and b is not None and right(answer_of(a), b))
165
+ rec = {"night": night, "kind": "route", "steps": [s[:90]], "shape": s, "right": same, "declined": 0, "outcome": "passed" if same == len(es) and agree == len(fresh) and len(fresh) >= 149 else "differs from the path it would replace (%d of %d, %d of %d fresh)" % (same, len(es), agree, len(fresh))}
166
+ STATE["exams"].append(rec)
167
+ if rec["outcome"] == "passed":
168
+ keep_part({"shape": s, "route_to": sid}, "reading-rule", s, {"items": len(fresh), "failed": 0}, ["paths walked on night %d" % night])
169
+ done.append({"kind": "route", "shape": s, "id": sid, "night": night, "steps": [s[:90]], "reach": 0, "source": "route"})
170
+ return done
171
+
172
+
173
+ # ---- 3 DREAMING
174
+ def dream_stores(hole, sid):
175
+ """declines.resolve: a part, the part's source, or the question read into a form the core reads"""
176
+ def ask_core(text):
177
+ r = eng.chat(text, sid + "c")
178
+ return {"status": r.get("status"), "response": str(r.get("response", "")), "path": str(r.get("path", ""))}
179
+ return declines.resolve(hole["q"], ask_core, wb.mine, server.SPECS)
180
+
181
+
182
+ def dream_formulas(holes, night, rep):
183
+ """grow60's round over the night's holes: chains the formula library holds, chosen by how many holes they close, built by the swarm and admitted by its exam"""
184
+ planner = G.Planner(G.load_parts(str(GROW)))
185
+ gaps = [h for h in holes if G.ask(planner, h["q"]).get("kind") == "no_part"]
186
+ needs = [G.gap_needs(LIB, h["q"], INSTALLED) for h in gaps]
187
+ chosen = G.choose([n for n in needs if n], CAP, LIB)
188
+ rep["formula_gaps"] = sum(1 for n in needs if n)
189
+ if not chosen:
190
+ return []
191
+ phase("dreaming: the swarm builds %d systems from the formula library" % len(chosen))
192
+ G.build(str(GROW), LIB, chosen, OUT / "grow_runs", night)
193
+ INSTALLED.update(chosen)
194
+ before = set(planner.parts)
195
+ after = G.Planner(G.load_parts(str(GROW)))
196
+ new = sorted(set(after.parts) - before)
197
+ out = []
198
+ for pid in new:
199
+ p = after.parts[pid]
200
+ out.append({"kind": "formula", "shape": pid, "id": pid, "night": night, "steps": ["%s from %s" % (p["output"], ", ".join(p["inputs"]))], "reach": 0, "source": "formula library"})
201
+ STATE["exams"].append({"night": night, "kind": "formula", "steps": ["%d systems" % len(chosen)], "right": len(new), "declined": 0, "outcome": "%d parts admitted by the swarm's exam" % len(new)})
202
+ return out
203
+
204
+
205
+ def dream_specialists(holes, night):
206
+ """holes that ask for the same property go to swarm build, as its example questions, then through its exam. Variations come from the builder's panel"""
207
+ groups = collections.defaultdict(list)
208
+ for h in holes:
209
+ g = (h.get("gap") or {})
210
+ if g.get("build") == "table":
211
+ groups[g["property"].lower()].append(h)
212
+ out = []
213
+ for prop, hs in sorted(groups.items(), key=lambda kv: -len(kv[1])):
214
+ if len(hs) < GROUP:
215
+ continue
216
+ phase("dreaming: building a table of %s from %d holes" % (prop, len(hs)))
217
+ try:
218
+ b = swarm_run.build([h["q"] for h in hs[:6]])
219
+ except Exception as e: # noqa: BLE001 - a failed build costs that candidate
220
+ STATE["exams"].append({"night": night, "kind": "table", "steps": [prop], "right": 0, "declined": 0, "outcome": "build failed: %s" % str(e)[:80]})
221
+ continue
222
+ d = b.get("specialist")
223
+ rec = {"night": night, "kind": "table", "steps": [prop], "right": 0, "declined": 0, "outcome": str(b.get("message"))[:110]}
224
+ STATE["exams"].append(rec)
225
+ if not d:
226
+ continue
227
+ v = exam.run(d, "dev", eng)
228
+ if v["decision"] not in ("PASS", "ADMIT"):
229
+ rec["outcome"] = "dev: " + "; ".join(v["reasons"])[:110]
230
+ continue
231
+ v = exam.run(d, "heldout", eng)
232
+ rec.update(right=v["with_specialist"]["ok"], outcome=v["decision"] + (": " + "; ".join(v["reasons"])[:100] if v["reasons"] else ""))
233
+ if v["decision"] == "ADMIT":
234
+ wb.reload()
235
+ keep_part({"rows": [[prop, Path(d).name]]}, "table", Path(d).name, {"items": v["n"], "failed": 0}, ["swarm build from %d holes" % len(hs)], {"inputs": [{"name": "property", "unit": "text"}], "outputs": [{"name": "specialist", "unit": "id"}]})
236
+ out.append({"kind": "table", "shape": prop, "id": Path(d).name, "night": night, "steps": [prop], "reach": 0, "source": "swarm build"})
237
+ return out
238
+
239
+
240
+ def dream_learner(holes, night, rep):
241
+ """a held-back reading the day's known answer confirms becomes a labelled pair for the learner, and the alignment is relearned with them. The new table stays only if
242
+ every reading it newly states is right and no must-decline gains an answer: a confirmation is data, and the exam decides"""
243
+ cov = rt._organs["facts"].coverage
244
+ new = [h for h in holes if h.get("held") and h.get("valid", True) is not False and right(h["held"]["answer"], h.get("expect"))]
245
+ rep["confirmed"] = len(new)
246
+ PAIRS.extend({"question": h["q"], "program": h["held"]["program"]} for h in new)
247
+ if not new:
248
+ return []
249
+ phase("dreaming: relearning the alignment with %d confirmed readings" % len(PAIRS), rep)
250
+ pf, out = OUT / "pairs.json", OUT / ("alignment_%d.json" % night)
251
+ pf.write_text(json.dumps(PAIRS), encoding="utf-8")
252
+ subprocess.run([sys.executable, "/opt/rel105/dev/align_learn105.py", str(out)], cwd=T, env=dict(os.environ, O7_EXTRA_PAIRS=str(pf)), capture_output=True, timeout=3000)
253
+
254
+ def use(path):
255
+ A = json.load(open(path, encoding="utf-8"))["p_feature_given_word"]
256
+ cov.align_t = A
257
+ cov.align_w = collections.defaultdict(dict)
258
+ for f, ws in A.items():
259
+ for w, pr in ws.items():
260
+ cov.align_w[w][f] = pr
261
+ cov.lex.words |= set(cov.align_w)
262
+ if hasattr(cov, "_dir_mass"):
263
+ del cov._dir_mass
264
+ was = STATE.get("alignment") or str(Path(T) / "workbench" / "swarm" / "data" / "alignment102.json")
265
+ use(out)
266
+ said = wrong = 0
267
+ bad = None
268
+ phase("exam: every held reading and control against the new table", rep)
269
+ for k, h in enumerate(h for h in holes if h.get("held")):
270
+ _r, a = turn(h["q"], "learn%d-%d" % (night, k))
271
+ if a is not None:
272
+ said += 1
273
+ if h.get("valid", True) is False or not right(a, h.get("expect")):
274
+ wrong += 1
275
+ bad = bad or h["q"][:100]
276
+ rec = {"night": night, "kind": "learner", "steps": ["alignment relearned with %d confirmed readings" % len(PAIRS)], "right": said - wrong, "declined": 0, "outcome": "kept: %d newly stated, none wrong" % said if said and not wrong else ("no reading rose to the threshold" if not said else "refused, %d wrong: %s" % (wrong, bad))}
277
+ STATE["exams"].append(rec)
278
+ if said and not wrong:
279
+ STATE["alignment"] = str(out)
280
+ keep_part({"examples": [{"question": x["question"], "cite": "confirmed against the known answer"} for x in PAIRS]}, "example-set", "confirmed readings, night %d" % night, {"items": max(149, len(new)), "failed": 0}, ["held-back readings confirmed on night %d" % night], {"outputs": [{"name": "alignment", "unit": "table"}]})
281
+ return [{"kind": "learner", "shape": "alignment %d" % night, "id": "reader90", "night": night, "steps": [rec["steps"][0]], "reach": 0, "source": "the learner"}]
282
+ use(was)
283
+ return []
284
+
285
+
286
+ # ---- the days
287
+ days = json.load(open(os.environ.get("O7_DAYS", "/opt/rel105/failure_days.json"), encoding="utf-8"))
288
+ items = [json.loads(x) for x in open(rows, encoding="utf-8") if x.strip()]
289
+ controls = [x for x in items if x.get("valid") is False and x.get("status") != "answered" and not x["id"].startswith("REG")]
290
+ random.Random(106).shuffle(controls)
291
+ REF_N = int(os.environ.get("O7_REF_N", "200"))
292
+ reference, days = days[-REF_N:], days[:len(days) - REF_N]
293
+ STATE["pool"] = {"valid": len(days), "controls": len(controls), "reference": len(reference)}
294
+
295
+
296
+ def measure():
297
+ n = wrong = 0
298
+ for k, it in enumerate(reference):
299
+ _r, a = turn(it["q"], "ref%d" % k)
300
+ if a is not None:
301
+ n += right(a, it["expect"])
302
+ wrong += not right(a, it["expect"])
303
+ return n, wrong
304
+
305
+
306
+ def measure_fork(night):
307
+ """the reference measured in a fresh process (ref_measure.py), as the head stands after night `night`, while the next night runs (11-04:
308
+ about half the time). The only state sleep keeps in memory, the learner's alignment table, is passed by path; built specialists are on
309
+ disk. A clean state folder each night makes every measure a first ask: in one process a second ask moved 2 of 100 answers. (A fork
310
+ was tried first: the head's threads leave a forked copy waiting on a lock no thread will release.)"""
311
+ out = OUT / "ref" / ("night%d.json" % night)
312
+ out.parent.mkdir(exist_ok=True)
313
+ rf = OUT / "ref" / "reference.json"
314
+ if not rf.exists():
315
+ rf.write_text(json.dumps(reference), encoding="utf-8")
316
+ st = OUT / "ref" / ("state%d" % night)
317
+ st.mkdir(exist_ok=True)
318
+ env = dict(os.environ, ORACLE7_STATE=str(st), PYTHONHASHSEED="0")
319
+ if STATE.get("alignment"):
320
+ env["O7_ALIGN"] = STATE["alignment"]
321
+ p = subprocess.Popen([sys.executable, str(Path(__file__).resolve().parent / "ref_measure.py"), str(rf), str(out)], env=env,
322
+ stdout=open(str(out) + ".log", "w"), stderr=subprocess.STDOUT)
323
+ return p, out
324
+
325
+
326
+ def measured(pending):
327
+ p, out = pending
328
+ p.wait()
329
+ r = json.loads(out.read_text())
330
+ return r["right"], r["wrong"]
331
+
332
+
333
+ def write_report(rep, a_, w_):
334
+ global chain
335
+ rep["reference_right"], rep["reference_wrong"] = a_, w_
336
+ STATE["reference"].append({"night": rep["night"], "right": a_, "wrong": w_, "of": len(reference)})
337
+ body = json.dumps(dict(rep, prev=chain), sort_keys=True)
338
+ chain = hashlib.sha256(body.encode()).hexdigest()
339
+ with open(OUT / "reports.jsonl", "a", encoding="utf-8") as f:
340
+ f.write(json.dumps({"report": json.loads(body), "hash": chain}) + "\n")
341
+ save()
342
+ print(json.dumps(rep), flush=True)
343
+
344
+
345
+ INSTALLED, parts, retired, chain, PAIRS = set(), [], [], "0" * 64, []
346
+ planner_live = G.Planner(G.load_parts(str(GROW)))
347
+ pending = (measure_fork(0), None) # (the process measuring, the report waiting on it); night 0 has no report
348
+ if os.environ.get("O7_REF_CHECK"): # test runs only: night 0 measured here too, to show the fresh process measures the same head
349
+ STATE["ref_check"] = dict(zip(("right", "wrong"), measure()))
350
+ save()
351
+ per_c = max(1, PER // 6)
352
+ for night in range(1, NIGHTS + 1):
353
+ STATE["night"] = night
354
+ day = days[(night - 1) * PER:night * PER] + [dict(x, valid=False) for x in controls[(night - 1) * per_c:night * per_c]]
355
+ if not day:
356
+ break
357
+ random.Random(night).shuffle(day)
358
+ led = Ledger(OUT / "ledger" / ("day%d.jsonl" % night))
359
+ rep = {"night": night, "turns": len(day), "holes": 0, "found": 0, "gained": 0, "wrong": 0, "false_yes": 0, "admitted": 0, "retired": 0, "changed": 0, "candidates": 0, "failed": 0}
360
+ t0 = time.time()
361
+ day_rows = []
362
+ for k, it in enumerate(day):
363
+ r, a = turn(it["q"], "n%d-%d" % (night, k))
364
+ path = str(r.get("path", ""))
365
+ if a is None: # the formula parts this run has grown
366
+ p = G.ask(planner_live, it["q"])
367
+ if p.get("status") == "answered":
368
+ a, path = str(p.get("value")), "yours:formula>" + str(p.get("plan"))[:80]
369
+ e = {"q": it["q"], "status": "answered" if a is not None else str(r.get("status")), "answer": a, "path": path, "expect": it.get("expect"), "valid": it.get("valid", True)}
370
+ led.add(e)
371
+ day_rows.append(e)
372
+ used = next((p for p in parts if p["id"] in path), None)
373
+ if a is not None:
374
+ if it.get("valid", True) is False:
375
+ rep["false_yes"] += 1
376
+ if used:
377
+ used["bad"] = "false yes: " + it["q"][:120]
378
+ elif used:
379
+ if right(a, it.get("expect")):
380
+ rep["gained"] += 1
381
+ used["reach"] += 1
382
+ used["last_used"] = night
383
+ else:
384
+ rep["wrong"] += 1
385
+ used["bad"] = "wrong: %s -> %s" % (it["q"][:100], str(a)[:60])
386
+ if k % 10 == 0:
387
+ phase("day: turn %d of %d" % (k, len(day)), rep)
388
+ holes, changed = replay(day_rows, night)
389
+ rep["changed"] = len(changed)
390
+ STATE["changed"] = (STATE["changed"] + [dict(c, night=night) for c in changed])[-40:]
391
+ rep["holes"] = len(holes)
392
+ phase("consolidation", rep)
393
+ new = consolidate(day_rows, night)
394
+ open_holes = []
395
+ for k, h in enumerate(holes):
396
+ if h.get("valid") is False:
397
+ continue
398
+ if k % 10 == 0:
399
+ phase("dreaming: stores, hole %d of %d" % (k, len(holes)), rep)
400
+ d = dream_stores(h, "h%d-%d" % (night, k))
401
+ h["gap"] = d.get("gap")
402
+ if d["status"] == "answered":
403
+ f = {"night": night, "q": h["q"], "answer": str(d["response"])[:200], "source": d.get("kind"), "right": right(d["response"], h.get("expect")), "valid": True, "steps": [d.get("fix")]}
404
+ rep["found"] += 1
405
+ STATE["found_right"] += int(f["right"])
406
+ STATE["finds"] = (STATE["finds"] + [f])[-60:]
407
+ else:
408
+ open_holes.append(h)
409
+ phase("dreaming: formulas", rep)
410
+ new += dream_formulas(open_holes, night, rep)
411
+ new += dream_specialists(open_holes, night)
412
+ new += dream_learner(holes, night, rep)
413
+ planner_live = G.Planner(G.load_parts(str(GROW)))
414
+ rep["admitted"] = len(new)
415
+ # an exam record can stand for several parts (the swarm's formula exam), so refusals are counted as records and candidates as
416
+ # what was admitted plus what was refused (11-04: candidates minus admitted had gone negative)
417
+ kept = ("passed", "kept", "admit")
418
+ rep["failed"] = sum(1 for x in STATE["exams"] if x.get("night") == night and not str(x.get("outcome", "")).lower().startswith(kept)
419
+ and "parts admitted" not in str(x.get("outcome", "")))
420
+ rep["candidates"] = rep["admitted"] + rep["failed"]
421
+ for p in new:
422
+ p["last_used"] = night
423
+ parts += new
424
+ # ---- 4 FORGETTING
425
+ keep = []
426
+ for p in parts:
427
+ if p.get("bad") or night - p.get("last_used", p["night"]) >= UNUSED_NIGHTS:
428
+ rep["retired"] += 1
429
+ retired.append({"id": p["id"], "shape": p["shape"], "night": night, "receipt": p.get("bad") or "unused for %d nights" % UNUSED_NIGHTS})
430
+ if (server.SPECS / p["id"]).exists():
431
+ shutil.move(str(server.SPECS / p["id"]), str(OUT / "store" / p["id"]))
432
+ wb.reload()
433
+ else:
434
+ keep.append(p)
435
+ parts = keep
436
+ # ---- 5 MORNING
437
+ rep["seconds"] = round(time.time() - t0)
438
+ STATE["parts"] = [{k: p.get(k) for k in ("id", "kind", "shape", "steps", "source", "night", "reach")} for p in parts]
439
+ STATE["retired"] = retired
440
+ tt = STATE["totals"]
441
+ for a, b in (("turns", "turns"), ("holes", "holes"), ("found", "found"), ("admitted", "admitted"), ("reach", "gained"), ("wrong", "wrong"), ("false_yes", "false_yes")):
442
+ tt[a] += rep[b]
443
+ STATE.pop("current", None)
444
+ STATE["history"].append(rep)
445
+ phase("measuring the reference set")
446
+ fork, waiting = pending
447
+ a_, w_ = measured(fork)
448
+ if waiting is None:
449
+ STATE["reference"].append({"night": 0, "right": a_, "wrong": w_, "of": len(reference)})
450
+ else:
451
+ write_report(waiting, a_, w_)
452
+ pending = (measure_fork(night), rep)
453
+ fork, waiting = pending
454
+ write_report(waiting, *measured(fork))
455
+ STATE["phase"] = "finished"
456
+ save()
457
+ print("done", flush=True)
workbench/swarm/steps_budget.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """steps_budget (10.5) - search is bounded by counted steps, so a reading is the same on any machine under any load"""
2
+ LIMIT = 12000
3
+
4
+
5
+ class Budget:
6
+ n = 0
7
+
8
+
9
+ def deadline():
10
+ return Budget.n + LIMIT
11
+
12
+
13
+ def spent():
14
+ Budget.n += 1
15
+ return Budget.n
workbench/verify.py CHANGED
@@ -126,6 +126,10 @@ def verify_install(root=None):
126
  for f in ("oracle7.py", "PACK_MANIFEST.json"):
127
  if (INSTALL / f).exists():
128
  parts[f] = hashlib.sha256((INSTALL / f).read_bytes()).hexdigest()
 
 
 
 
129
  if any(k.startswith("data/") for k in pub["parts"]) and (INSTALL / "PACK_MANIFEST.json").exists(): # recipe v4 (10.2)
130
  manifest = json.loads((INSTALL / "PACK_MANIFEST.json").read_text(encoding="utf-8"))
131
  parts.update({k: v[0] for k, v in data_parts(R, manifest).items()})
 
126
  for f in ("oracle7.py", "PACK_MANIFEST.json"):
127
  if (INSTALL / f).exists():
128
  parts[f] = hashlib.sha256((INSTALL / f).read_bytes()).hexdigest()
129
+ for top in ("orbiters", "records"): # recipe v5 (10.5): each orbiter, and the records the changelog points to
130
+ for d in sorted(p for p in (INSTALL / top).glob("*") if p.is_dir() and p.name not in SKIP_DIRS):
131
+ if top + "/" + d.name in pub["parts"] or any(k.startswith(top + "/") for k in pub["parts"]):
132
+ parts[top + "/" + d.name] = part_hash(d)[0]
133
  if any(k.startswith("data/") for k in pub["parts"]) and (INSTALL / "PACK_MANIFEST.json").exists(): # recipe v4 (10.2)
134
  manifest = json.loads((INSTALL / "PACK_MANIFEST.json").read_text(encoding="utf-8"))
135
  parts.update({k: v[0] for k, v in data_parts(R, manifest).items()})