@malloy-publisher/skills 0.1.49 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/eval-answer/SKILL.md +4 -3
- package/skills/eval-answer/scripts/check_findable.py +16 -6
- package/skills/eval-answer/scripts/check_findable_test.py +38 -2
- package/skills/eval-answer/scripts/publisher_rest.py +31 -5
- package/skills/eval-answer/scripts/publisher_rest_test.py +45 -0
- package/skills/eval-diagnose/SKILL.md +1 -1
- package/skills/eval-import/SKILL.md +1 -1
- package/skills/eval-improve/SKILL.md +1 -1
- package/skills/eval-improve/scripts/improve.py +28 -3
- package/skills/eval-improve/scripts/improve_test.py +63 -0
- package/skills/eval-judge/SKILL.md +1 -1
- package/skills/eval-loop/SKILL.md +1 -1
- package/skills/eval-loop/scripts/serve.py +29 -7
- package/skills/eval-loop/scripts/serve_test.py +15 -1
- package/skills/eval-report/SKILL.md +1 -1
- package/skills/malloy-analysis/SKILL.md +1 -1
- package/skills/malloy-analysis-report/SKILL.md +1 -1
- package/skills/malloy-charts/SKILL.md +1 -1
- package/skills/malloy-classify/SKILL.md +1 -1
- package/skills/malloy-compliance-review/SKILL.md +3 -3
- package/skills/malloy-compliance-review/reference/checks.md +4 -4
- package/skills/malloy-compliance-review/scripts/compliance.py +15 -2
- package/skills/malloy-compliance-review/scripts/compliance_test.py +30 -0
- package/skills/malloy-dashboards/SKILL.md +1 -1
- package/skills/malloy-define/SKILL.md +1 -1
- package/skills/malloy-define-agent/SKILL.md +1 -1
- package/skills/malloy-discover/SKILL.md +1 -1
- package/skills/malloy-document/SKILL.md +6 -6
- package/skills/malloy-getting-started/SKILL.md +2 -2
- package/skills/malloy-gotchas-modeling/SKILL.md +2 -2
- package/skills/malloy-html-data-apps/SKILL.md +2 -2
- package/skills/malloy-lookml-review/SKILL.md +1 -1
- package/skills/malloy-lookml-review/reference/_concepts.md +1 -1
- package/skills/malloy-lookml-review/reference/curate-visibility.md +3 -3
- package/skills/malloy-materialization/SKILL.md +3 -3
- package/skills/malloy-materialization/reference/storage.md +1 -1
- package/skills/malloy-materialization/reference/tuning.md +1 -1
- package/skills/malloy-model/SKILL.md +1 -1
- package/skills/malloy-model/reference/access-modifiers.md +4 -2
- package/skills/malloy-model-as-you-go/SKILL.md +1 -1
- package/skills/malloy-modeling/SKILL.md +2 -2
- package/skills/malloy-notebooks/SKILL.md +1 -1
- package/skills/malloy-phrase-detection/SKILL.md +1 -1
- package/skills/malloy-powerbi-review/SKILL.md +1 -1
- package/skills/malloy-powerbi-review/reference/_concepts.md +1 -1
- package/skills/malloy-powerbi-review/reference/rls-roles.md +3 -3
- package/skills/malloy-publish/SKILL.md +1 -1
- package/skills/malloy-queries/SKILL.md +1 -1
- package/skills/malloy-reports/SKILL.md +1 -1
- package/skills/malloy-review/SKILL.md +1 -1
- package/skills/malloy-review/reference/rubric-governance.md +15 -3
- package/skills/malloy-review/reference/rubric-rendering.md +2 -2
- package/skills/malloy-review/reference/severity-taxonomy.md +1 -0
- package/skills/malloy-run-agent/SKILL.md +1 -1
- package/skills/malloy-source-unreachable/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-answer
|
|
3
3
|
description: 'Score one analytical answer against a verified golden, and score which of the entities the golden depends on retrieval delivered to the answerer. Run the contamination checklist, re-execute the submitted query yourself, then spawn a judge subagent per skill:eval-judge. Append attempt, tool_call, score, and retrieval_score events to the file ledger (reference/ledger-schema.md). Never explain the failure (eval-diagnose) or edit the model (eval-improve). Use when asked whether an answer was correct, to score a run, or to baseline a model.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Evaluate One Answer
|
|
@@ -19,8 +19,9 @@ revision.
|
|
|
19
19
|
|
|
20
20
|
Take the question from the stored case (`evals/<set>/cases.jsonl`), never from
|
|
21
21
|
memory or a truncated console line. Record `question_sha` of the exact text
|
|
22
|
-
the answerer saw. Record `servedRevision` from
|
|
23
|
-
|
|
22
|
+
the answerer saw. Record `servedRevision` from the package's serving state,
|
|
23
|
+
`GET …/packages/{pkg}/status`, or the `reload_package` result, not the package name: a
|
|
24
|
+
same-named decoy has been measured for hours.
|
|
24
25
|
|
|
25
26
|
## Step 1: Contamination check, before any score
|
|
26
27
|
|
|
@@ -209,15 +209,25 @@ def embedding_index(rest_url: str, environment: str, package: str,
|
|
|
209
209
|
server-side state as a broken answer key. None also covers a server too old
|
|
210
210
|
to send the field.
|
|
211
211
|
"""
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
package_url = (f"{rest_url.rstrip('/')}/api/v0/environments/"
|
|
213
|
+
f"{urllib.parse.quote(environment)}/packages/"
|
|
214
|
+
f"{urllib.parse.quote(package)}")
|
|
215
|
+
# Its own route; a server from before the route carries it on the
|
|
216
|
+
# package resource instead.
|
|
215
217
|
try:
|
|
216
|
-
with urllib.request.urlopen(
|
|
217
|
-
|
|
218
|
+
with urllib.request.urlopen(f"{package_url}/embedding-index",
|
|
219
|
+
timeout=timeout) as r:
|
|
220
|
+
index = json.loads(r.read())
|
|
221
|
+
except urllib.error.HTTPError as e:
|
|
222
|
+
if e.code != 404:
|
|
223
|
+
return None
|
|
224
|
+
try:
|
|
225
|
+
with urllib.request.urlopen(package_url, timeout=timeout) as r:
|
|
226
|
+
index = json.loads(r.read()).get("embeddingIndex")
|
|
227
|
+
except (urllib.error.URLError, OSError, ValueError):
|
|
228
|
+
return None
|
|
218
229
|
except (urllib.error.URLError, OSError, ValueError):
|
|
219
230
|
return None
|
|
220
|
-
index = body.get("embeddingIndex")
|
|
221
231
|
if not isinstance(index, dict) or not isinstance(index.get("status"), str):
|
|
222
232
|
return None
|
|
223
233
|
return index
|
|
@@ -531,17 +531,53 @@ class IndexWait(unittest.TestCase):
|
|
|
531
531
|
|
|
532
532
|
class EmbeddingIndexRead(unittest.TestCase):
|
|
533
533
|
def read(self, payload):
|
|
534
|
+
"""Read the index from a server holding `payload` as its package
|
|
535
|
+
resource. A payload carrying `embeddingIndex` is a current server,
|
|
536
|
+
which serves the object from `GET …/embedding-index`; one without it
|
|
537
|
+
is an older server, whose `/embedding-index` 404s."""
|
|
534
538
|
import json as _json
|
|
535
539
|
|
|
536
540
|
class Resp:
|
|
541
|
+
def __init__(self, body): self.body = body
|
|
537
542
|
def __enter__(self): return self
|
|
538
543
|
def __exit__(self, *a): return False
|
|
539
|
-
def read(self): return _json.dumps(
|
|
544
|
+
def read(self): return _json.dumps(self.body).encode()
|
|
545
|
+
|
|
546
|
+
def urlopen(url, timeout=None):
|
|
547
|
+
if url.endswith("/embedding-index"):
|
|
548
|
+
if "embeddingIndex" in payload:
|
|
549
|
+
return Resp(payload["embeddingIndex"])
|
|
550
|
+
raise check_findable.urllib.error.HTTPError(
|
|
551
|
+
url, 404, "Not Found", None, None)
|
|
552
|
+
return Resp(payload)
|
|
540
553
|
|
|
541
554
|
with mock.patch.object(check_findable.urllib.request, "urlopen",
|
|
542
|
-
|
|
555
|
+
urlopen):
|
|
543
556
|
return check_findable.embedding_index("http://x", "e", "p")
|
|
544
557
|
|
|
558
|
+
def test_an_older_server_is_read_from_the_package_resource(self):
|
|
559
|
+
idx = {"status": "ready"}
|
|
560
|
+
import json as _json
|
|
561
|
+
calls = []
|
|
562
|
+
|
|
563
|
+
class Resp:
|
|
564
|
+
def __enter__(self): return self
|
|
565
|
+
def __exit__(self, *a): return False
|
|
566
|
+
def read(self): return _json.dumps({"embeddingIndex": idx}).encode()
|
|
567
|
+
|
|
568
|
+
def urlopen(url, timeout=None):
|
|
569
|
+
calls.append(url)
|
|
570
|
+
if url.endswith("/embedding-index"):
|
|
571
|
+
raise check_findable.urllib.error.HTTPError(
|
|
572
|
+
url, 404, "Not Found", None, None)
|
|
573
|
+
return Resp()
|
|
574
|
+
|
|
575
|
+
with mock.patch.object(check_findable.urllib.request, "urlopen",
|
|
576
|
+
urlopen):
|
|
577
|
+
self.assertEqual(
|
|
578
|
+
check_findable.embedding_index("http://x", "e", "p"), idx)
|
|
579
|
+
self.assertEqual(len(calls), 2)
|
|
580
|
+
|
|
545
581
|
def test_the_whole_object_comes_back_so_reason_is_not_lost(self):
|
|
546
582
|
idx = {"status": "error", "reason": "cooldown",
|
|
547
583
|
"lastError": {"message": "m"}}
|
|
@@ -205,7 +205,8 @@ def served_model_path(base: str, environment: str, package: str,
|
|
|
205
205
|
|
|
206
206
|
|
|
207
207
|
def package_identity(base: str, environment: str, package: str) -> dict[str, str]:
|
|
208
|
-
"""What the server says it is serving: `sourceContentSha`, `servedRevision
|
|
208
|
+
"""What the server says it is serving: `sourceContentSha`, `servedRevision`,
|
|
209
|
+
read from the package's serving state (`GET …/packages/{pkg}/status`).
|
|
209
210
|
|
|
210
211
|
Take these rather than computing your own. `sourceContentSha` is a content
|
|
211
212
|
hash over EVERY model path in the package, so it moves when an imported file
|
|
@@ -218,10 +219,35 @@ def package_identity(base: str, environment: str, package: str) -> dict[str, str
|
|
|
218
219
|
directory. So `publisher.json` version pins nothing (nothing reads it) and a
|
|
219
220
|
set of goldens living inside the package needs its own hash.
|
|
220
221
|
"""
|
|
222
|
+
keys = ("sourceContentSha", "servedRevision")
|
|
223
|
+
pkg_path = (f"api/v0/environments/{urllib.parse.quote(environment)}"
|
|
224
|
+
f"/packages/{urllib.parse.quote(package)}")
|
|
225
|
+
# The package's serving state; for a versioned package, its `latest`'s.
|
|
221
226
|
try:
|
|
222
|
-
d = get_json(base, f"
|
|
223
|
-
|
|
227
|
+
d = get_json(base, f"{pkg_path}/status")
|
|
228
|
+
if any(isinstance(d.get(k), str) for k in keys):
|
|
229
|
+
return {k: d[k] for k in keys if isinstance(d.get(k), str)}
|
|
230
|
+
except Exception: # noqa: BLE001
|
|
231
|
+
pass
|
|
232
|
+
# A server from before that route: the package's `/status` entry carries
|
|
233
|
+
# them, one entry per version held, and the one serving `latest` answers.
|
|
234
|
+
try:
|
|
235
|
+
status = get_json(base, "api/v0/status")
|
|
236
|
+
for env in status.get("environments") or []:
|
|
237
|
+
if env.get("name") != environment:
|
|
238
|
+
continue
|
|
239
|
+
entries = [p for p in env.get("packages") or []
|
|
240
|
+
if p.get("name") == package]
|
|
241
|
+
entry = next((p for p in entries
|
|
242
|
+
if p.get("versionId") in (None, p.get("latestVersion"))),
|
|
243
|
+
entries[0] if entries else None)
|
|
244
|
+
if entry and any(isinstance(entry.get(k), str) for k in keys):
|
|
245
|
+
return {k: entry[k] for k in keys if isinstance(entry.get(k), str)}
|
|
246
|
+
except Exception: # noqa: BLE001
|
|
247
|
+
pass
|
|
248
|
+
# Older still: on the package resource.
|
|
249
|
+
try:
|
|
250
|
+
d = get_json(base, pkg_path)
|
|
224
251
|
except Exception: # noqa: BLE001
|
|
225
252
|
return {}
|
|
226
|
-
return {k: d[k] for k in (
|
|
227
|
-
if isinstance(d.get(k), str)}
|
|
253
|
+
return {k: d[k] for k in keys if isinstance(d.get(k), str)}
|
|
@@ -149,6 +149,51 @@ class CompactJson(unittest.TestCase):
|
|
|
149
149
|
self.assertEqual(rows, [{"n": 7}])
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
|
|
153
|
+
class PackageIdentityTests(unittest.TestCase):
|
|
154
|
+
"""The served identity comes from the package's serving state, and on older
|
|
155
|
+
servers from its `/status` entry or the package resource."""
|
|
156
|
+
|
|
157
|
+
def identity(self, responses):
|
|
158
|
+
def get_json(base, path):
|
|
159
|
+
if path in responses:
|
|
160
|
+
return responses[path]
|
|
161
|
+
raise OSError("HTTP Error 404")
|
|
162
|
+
with unittest.mock.patch.object(publisher_rest, "get_json", get_json):
|
|
163
|
+
return publisher_rest.package_identity("http://x", "env", "pkg")
|
|
164
|
+
|
|
165
|
+
def test_the_serving_state_answers(self):
|
|
166
|
+
state = {"name": "pkg", "sourceContentSha": "abc", "servedRevision": "r1"}
|
|
167
|
+
self.assertEqual(
|
|
168
|
+
self.identity({"api/v0/environments/env/packages/pkg/status": state}),
|
|
169
|
+
{"sourceContentSha": "abc", "servedRevision": "r1"})
|
|
170
|
+
|
|
171
|
+
def test_a_server_without_the_route_answers_from_its_status_entry(self):
|
|
172
|
+
status = {"environments": [{"name": "env", "packages": [
|
|
173
|
+
{"name": "other", "sourceContentSha": "zzz"},
|
|
174
|
+
{"name": "pkg", "sourceContentSha": "abc", "servedRevision": "r1"},
|
|
175
|
+
]}]}
|
|
176
|
+
self.assertEqual(self.identity({"api/v0/status": status}),
|
|
177
|
+
{"sourceContentSha": "abc", "servedRevision": "r1"})
|
|
178
|
+
|
|
179
|
+
def test_a_versioned_package_answers_from_its_latest_version(self):
|
|
180
|
+
status = {"environments": [{"name": "env", "packages": [
|
|
181
|
+
{"name": "pkg", "versionId": "1.0.0", "latestVersion": "1.1.0",
|
|
182
|
+
"sourceContentSha": "old"},
|
|
183
|
+
{"name": "pkg", "versionId": "1.1.0", "latestVersion": "1.1.0",
|
|
184
|
+
"sourceContentSha": "new"},
|
|
185
|
+
]}]}
|
|
186
|
+
self.assertEqual(self.identity({"api/v0/status": status}),
|
|
187
|
+
{"sourceContentSha": "new"})
|
|
188
|
+
|
|
189
|
+
def test_an_older_server_answers_from_the_package_resource(self):
|
|
190
|
+
status = {"environments": [{"name": "env", "packages": [{"name": "pkg"}]}]}
|
|
191
|
+
pkg = {"sourceContentSha": "abc", "servedRevision": "r1"}
|
|
192
|
+
self.assertEqual(
|
|
193
|
+
self.identity({"api/v0/status": status,
|
|
194
|
+
"api/v0/environments/env/packages/pkg": pkg}),
|
|
195
|
+
{"sourceContentSha": "abc", "servedRevision": "r1"})
|
|
196
|
+
|
|
152
197
|
if __name__ == "__main__":
|
|
153
198
|
os.chdir(os.path.dirname(__file__))
|
|
154
199
|
unittest.main()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-diagnose
|
|
3
3
|
description: 'Diagnose why a scored answer failed and who owns the fix, then cluster the failures by shared root cause. Walk dataset, agent-call, get_context/model, get_context/retrieval, construction, then model-definition. Append issue events to the file ledger, linked by traceId, one per cluster. Use after eval-answer, when triaging a run, or before changing a model. Does not edit the model (eval-improve).'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Diagnose One Answer
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-import
|
|
3
3
|
description: 'Turn a list of questions into an eval set, whatever shape it arrived in: a JSONL a customer sent, a CSV, a spreadsheet export, a markdown doc, an email thread, or a pull from production logs. Classify each item by what came WITH the question (a query, a number, prose criteria, or nothing), write cases.jsonl and set.json per reference/case-format.md, keep the file as it arrived, and seal each question so a later edit is detectable. Never marks a value-bearing golden verified and never invents a question. Use when questions arrive from outside and need to become a set, or before the first run against a set nobody here authored.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Import questions into an eval set
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-improve
|
|
3
3
|
description: 'Make the smallest safe edit that closes a diagnosed gap the model repo owns: a Malloy model edit, or a package skill under skills/. Every factual claim carries a probe receipt. Use after eval-diagnose, or when asked to fix a model so an agent can discover the right answer. Never accepts its own edit; the acceptance check belongs to eval-loop. Does not decide whether an answer was wrong (eval-answer) or why (eval-diagnose).'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Improve the Model
|
|
@@ -77,9 +77,34 @@ IMPROVE_TOOLS = ("mcp__publisher__get_context",
|
|
|
77
77
|
"Read", "Edit", "Write", "Grep", "Glob",
|
|
78
78
|
"Bash(bash ./sync_and_reload.sh)")
|
|
79
79
|
|
|
80
|
-
# REST, not MCP: an MCP reply escapes its inner JSON
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
# REST, not MCP: an MCP reply escapes its inner JSON. The sha is read from the
|
|
81
|
+
# package's serving state after the reload (the reload response itself carries
|
|
82
|
+
# it only while the server inlines the serving fields). A failed reload prints
|
|
83
|
+
# its error and stops, so the previous load's sha is never shown for it.
|
|
84
|
+
RELOAD = """reload_body=$(mktemp)
|
|
85
|
+
reload_code=$(curl -s -m 60 -o "$reload_body" -w "%{{http_code}}" "{rest_url}/api/v0/environments/{environment}/packages/{package}?reload=true" || echo 000)
|
|
86
|
+
if [ "$reload_code" != "200" ]; then
|
|
87
|
+
echo "reload failed (HTTP $reload_code):"; cat "$reload_body"; echo; rm -f "$reload_body"; exit 1
|
|
88
|
+
fi
|
|
89
|
+
rm -f "$reload_body"
|
|
90
|
+
python3 -c '
|
|
91
|
+
import json, urllib.request
|
|
92
|
+
base = "{rest_url}/api/v0"
|
|
93
|
+
def get(path):
|
|
94
|
+
try:
|
|
95
|
+
with urllib.request.urlopen(base + path, timeout=30) as r:
|
|
96
|
+
return json.load(r)
|
|
97
|
+
except Exception:
|
|
98
|
+
return {{}}
|
|
99
|
+
sha = get("/environments/{environment}/packages/{package}/status").get("sourceContentSha")
|
|
100
|
+
if not sha: # a server from before that route: the package`s /status entry
|
|
101
|
+
for e in get("/status").get("environments") or []:
|
|
102
|
+
for p in (e.get("packages") or []) if e.get("name") == "{environment}" else []:
|
|
103
|
+
if p.get("name") == "{package}" and p.get("sourceContentSha"):
|
|
104
|
+
sha = p["sourceContentSha"]
|
|
105
|
+
if sha:
|
|
106
|
+
print("\\"sourceContentSha\\":\\"%s\\"" % sha)
|
|
107
|
+
'"""
|
|
83
108
|
|
|
84
109
|
SYNC_SCRIPT = """#!/bin/bash
|
|
85
110
|
set -euo pipefail
|
|
@@ -282,5 +282,68 @@ class Grants(unittest.TestCase):
|
|
|
282
282
|
self.assertIn(f"mcp__publisher__{t}", improve.IMPROVE_TOOLS)
|
|
283
283
|
|
|
284
284
|
|
|
285
|
+
class ReloadReceipt(unittest.TestCase):
|
|
286
|
+
"""The reload helper prints the served sha: from the package's serving
|
|
287
|
+
state, or from its `/status` entry on a server from before that route.
|
|
288
|
+
A reload that fails prints its error instead, never the old sha."""
|
|
289
|
+
|
|
290
|
+
PKG = "/api/v0/environments/env/packages/pkg"
|
|
291
|
+
|
|
292
|
+
def run_reload(self, routes):
|
|
293
|
+
import http.server
|
|
294
|
+
import json
|
|
295
|
+
import threading
|
|
296
|
+
|
|
297
|
+
class Handler(http.server.BaseHTTPRequestHandler):
|
|
298
|
+
def do_GET(self):
|
|
299
|
+
path = self.path.split("?")[0]
|
|
300
|
+
body = routes.get(path)
|
|
301
|
+
code, body = body if isinstance(body, tuple) else (
|
|
302
|
+
200 if body is not None else 404, body)
|
|
303
|
+
self.send_response(code)
|
|
304
|
+
self.end_headers()
|
|
305
|
+
self.wfile.write(json.dumps(body or {}).encode())
|
|
306
|
+
|
|
307
|
+
def log_message(self, *args):
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
server = http.server.HTTPServer(("127.0.0.1", 0), Handler)
|
|
311
|
+
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
312
|
+
try:
|
|
313
|
+
cmd = improve.RELOAD.format(
|
|
314
|
+
rest_url=f"http://127.0.0.1:{server.server_port}",
|
|
315
|
+
environment="env", package="pkg")
|
|
316
|
+
return subprocess.run(["bash", "-c", cmd], capture_output=True,
|
|
317
|
+
text=True, timeout=60).stdout.strip()
|
|
318
|
+
finally:
|
|
319
|
+
server.shutdown()
|
|
320
|
+
|
|
321
|
+
def test_reads_the_serving_state(self):
|
|
322
|
+
out = self.run_reload({
|
|
323
|
+
self.PKG: {"name": "pkg"},
|
|
324
|
+
"/api/v0/environments/env/packages/pkg/status":
|
|
325
|
+
{"sourceContentSha": "abc"},
|
|
326
|
+
})
|
|
327
|
+
self.assertEqual(out, '"sourceContentSha":"abc"')
|
|
328
|
+
|
|
329
|
+
def test_falls_back_to_the_status_entry_on_an_older_server(self):
|
|
330
|
+
out = self.run_reload({
|
|
331
|
+
self.PKG: {"name": "pkg"},
|
|
332
|
+
"/api/v0/status": {"environments": [{"name": "env", "packages": [
|
|
333
|
+
{"name": "pkg", "sourceContentSha": "old"}]}]},
|
|
334
|
+
})
|
|
335
|
+
self.assertEqual(out, '"sourceContentSha":"old"')
|
|
336
|
+
|
|
337
|
+
def test_a_failed_reload_prints_its_error_and_no_sha(self):
|
|
338
|
+
out = self.run_reload({
|
|
339
|
+
self.PKG: (400, {"message": "bad.malloy: refused"}),
|
|
340
|
+
"/api/v0/environments/env/packages/pkg/status":
|
|
341
|
+
{"sourceContentSha": "stale"},
|
|
342
|
+
})
|
|
343
|
+
self.assertIn("reload failed (HTTP 400)", out)
|
|
344
|
+
self.assertIn("bad.malloy: refused", out)
|
|
345
|
+
self.assertNotIn("stale", out)
|
|
346
|
+
|
|
347
|
+
|
|
285
348
|
if __name__ == "__main__":
|
|
286
349
|
unittest.main()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-judge
|
|
3
3
|
description: 'Decide whether ONE answer matches its golden, and say whether you believe the golden. Read this before emitting any verdict. Covers containment, column pairing, near_match, refusals, and the gold_status judgement. Use when scoring an attempt in an evaluation run; never to conduct a run (eval-loop), diagnose a failure (eval-diagnose) or edit a model (eval-improve).'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# The judge
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-loop
|
|
3
3
|
description: 'Conduct a local Publisher evaluation loop in five steps: scrape/run, eval, diagnose, improve, checkpoint. You are the conductor: import cases into the file ledger, spawn a blind answerer, then run eval-answer, eval-diagnose, and eval-improve. Persistence is plain files: the set in the model package''s evals/ directory, runs in the set''s workdir; checkpoints are git commits of the model repo. `eval.py` runs each step from the set''s eval.toml. Use to score a model, diagnose failures, improve behind an acceptance check, or roll back a bad direction.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# The Evaluation Loop
|
|
@@ -53,8 +53,9 @@ run that starts the moment the REST root replies would record those empty
|
|
|
53
53
|
answers as retrieval misses.
|
|
54
54
|
|
|
55
55
|
It drives one ranking call (older servers only started the sync on the first
|
|
56
|
-
one), then polls
|
|
57
|
-
|
|
56
|
+
one), then polls `GET …/packages/{package}/embedding-index` (the package
|
|
57
|
+
resource's `embeddingIndex` on a server from before that route) until its
|
|
58
|
+
`status` reaches a terminal value and prints it. The values are `lexical` (no provider: a mode),
|
|
58
59
|
`indexing` (the only non-terminal one), `ready`, and `error`, which carries a
|
|
59
60
|
`reason` and `lastError.message` that are printed. Only `ready` licenses
|
|
60
61
|
reading a run's discoverability findings. A package resource with no
|
|
@@ -95,6 +96,7 @@ import socket
|
|
|
95
96
|
import subprocess
|
|
96
97
|
import sys
|
|
97
98
|
import time
|
|
99
|
+
import urllib.error
|
|
98
100
|
import urllib.request
|
|
99
101
|
|
|
100
102
|
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent.parent
|
|
@@ -203,6 +205,27 @@ def warm_arguments(environment: str, package: str) -> dict:
|
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
|
|
208
|
+
def read_index(port: int, environment: str, package: str) -> dict | None:
|
|
209
|
+
"""The package's embedding-index object, or None when the server has none.
|
|
210
|
+
|
|
211
|
+
Read from `GET …/embedding-index`; a server from before that route carries
|
|
212
|
+
the object on the package resource as `embeddingIndex`, so a 404 there
|
|
213
|
+
falls back to it. A transport failure raises, for the caller's retry.
|
|
214
|
+
"""
|
|
215
|
+
package_url = (f"http://localhost:{port}/api/v0/environments/"
|
|
216
|
+
f"{environment}/packages/{package}")
|
|
217
|
+
try:
|
|
218
|
+
with urllib.request.urlopen(f"{package_url}/embedding-index",
|
|
219
|
+
timeout=10) as r:
|
|
220
|
+
return json.loads(r.read().decode())
|
|
221
|
+
except urllib.error.HTTPError as e:
|
|
222
|
+
if e.code != 404:
|
|
223
|
+
raise
|
|
224
|
+
with urllib.request.urlopen(package_url, timeout=10) as r:
|
|
225
|
+
index = json.loads(r.read().decode()).get("embeddingIndex")
|
|
226
|
+
return index if isinstance(index, dict) else None
|
|
227
|
+
|
|
228
|
+
|
|
206
229
|
def index_status(package_payload: dict) -> str | None:
|
|
207
230
|
"""`embeddingIndex.status` from a package resource, or None if absent.
|
|
208
231
|
|
|
@@ -235,13 +258,12 @@ def warm_retrieval(port: int, mcp_port: int, environment: str, package: str,
|
|
|
235
258
|
return None, f"warm-up call failed: {e}"
|
|
236
259
|
|
|
237
260
|
url = (f"http://localhost:{port}/api/v0/environments/{environment}"
|
|
238
|
-
f"/packages/{package}")
|
|
261
|
+
f"/packages/{package}/embedding-index")
|
|
239
262
|
deadline = time.time() + wait
|
|
240
263
|
status, last_error, read_one, index = None, None, False, {}
|
|
241
264
|
while time.time() < deadline:
|
|
242
265
|
try:
|
|
243
|
-
|
|
244
|
-
payload = json.loads(r.read().decode())
|
|
266
|
+
read = read_index(port, environment, package)
|
|
245
267
|
except Exception as e: # noqa: BLE001
|
|
246
268
|
# A transport failure is not a capability verdict. Collapsing it
|
|
247
269
|
# into `status = None` handed None a second meaning `index_status`
|
|
@@ -255,13 +277,13 @@ def warm_retrieval(port: int, mcp_port: int, environment: str, package: str,
|
|
|
255
277
|
time.sleep(2)
|
|
256
278
|
continue
|
|
257
279
|
read_one = True
|
|
258
|
-
status = index_status(
|
|
280
|
+
status = index_status({"embeddingIndex": read})
|
|
259
281
|
if status is None:
|
|
260
282
|
return None, ("no embeddingIndex on the package resource; this "
|
|
261
283
|
"server has no embedding provider or predates the "
|
|
262
284
|
"status field, so every run against it is LEXICAL "
|
|
263
285
|
"-- do not report discoverability findings from it")
|
|
264
|
-
index =
|
|
286
|
+
index = read
|
|
265
287
|
if status in TERMINAL_INDEX_STATES:
|
|
266
288
|
break
|
|
267
289
|
time.sleep(2)
|
|
@@ -163,10 +163,24 @@ class WarmRetrievalSeparatesFailureFromAbsence(unittest.TestCase):
|
|
|
163
163
|
def read(self):
|
|
164
164
|
return json.dumps(self.payload).encode()
|
|
165
165
|
|
|
166
|
+
# Each scripted outcome is a package-resource payload or an error. A
|
|
167
|
+
# payload carrying `embeddingIndex` is served the way a current server
|
|
168
|
+
# serves it, from `GET …/embedding-index`; one without it is an older
|
|
169
|
+
# server, whose `/embedding-index` 404s and whose package resource
|
|
170
|
+
# answers instead.
|
|
171
|
+
pending = []
|
|
172
|
+
|
|
166
173
|
def urlopen(req, timeout=None):
|
|
167
|
-
|
|
174
|
+
url = req if isinstance(req, str) else req.full_url
|
|
175
|
+
r = pending.pop() if pending else next(it)
|
|
168
176
|
if isinstance(r, Exception):
|
|
169
177
|
raise r
|
|
178
|
+
if url.endswith("/embedding-index"):
|
|
179
|
+
if "embeddingIndex" in r:
|
|
180
|
+
return Resp(r["embeddingIndex"])
|
|
181
|
+
pending.append(r)
|
|
182
|
+
raise serve.urllib.error.HTTPError(url, 404, "Not Found",
|
|
183
|
+
None, None)
|
|
170
184
|
return Resp(r)
|
|
171
185
|
|
|
172
186
|
with mock.patch.object(serve, "_mcp_call", lambda *a, **k: None), \
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-report
|
|
3
3
|
description: 'Write the summary a person reads after an evaluation run: what ran, what it scored, which failures were the MODEL and which were the EVAL itself (out of turns, contaminated, unestablished golden, unreadable judge), what was learned, and what to do next. Every claim links to the artifact behind it. Use at the end of any run, before quoting a number to anyone. Never scores an answer (eval-answer) or decides who owns a failure (eval-diagnose).'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Report a Run
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-analysis
|
|
3
3
|
description: "Read before your first get_context call on any business or data question. The required workflow, not background: which source, which rows, how to verify. Skipping it causes confident wrong answers."
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- Copyright (c) Credible Data Inc. SPDX-License-Identifier: MIT -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-analysis-report
|
|
3
3
|
description: Combine validated Malloy queries into a report (a linear report, read top to bottom). Use when the user asks to "create a report", "combine these into a report", or wants a persistent multi-query artifact.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-charts
|
|
3
3
|
description: Read before choosing a chart or adding a chart annotation. Chart types, tag syntax and scale rules, KPI cards, dashboards, sparklines, and the renderer mistakes that fail silently.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-classify
|
|
3
3
|
description: 'Propose #(classification) annotations for a Malloy model from an access policy''s class scheme, backed by aggregate value evidence from the served data, and show them as a diff before writing anything. Use when the user asks to "classify the model", "tag PHI/PII", "label sensitive fields", or adds fields to a package that a policy governs, and before running malloy-compliance-review on a model nobody has classified.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-compliance-review
|
|
3
3
|
description: 'Check a Malloy package and its controls against a written access policy: turn the policy''s prose into a control register, run compliance.py check against a served package (structure, access as each role, and the values themselves), judge what needs a person, and produce a report and a signable attestation. Use when the user asks whether a model "complies with", "satisfies" or "meets" a policy, HIPAA, SOC 2, PCI or the like, asks for a compliance review or attestation, or wants the review in CI.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -56,7 +56,7 @@ Rules for drafting:
|
|
|
56
56
|
- **Name a persona for every workforce class the policy names**, and one that should be refused
|
|
57
57
|
(a partner without an agreement). The refusals are half of what the access check proves.
|
|
58
58
|
- **Givens come from the model.** A persona's givens are the model's `#(secure)` givens, named
|
|
59
|
-
exactly; `get_context` lists them on each domain, and
|
|
59
|
+
exactly; `get_context` lists them on each domain, and `GET …/packages/{pkg}/entities` marks each with `secure: true` in its model's `givens`.
|
|
60
60
|
|
|
61
61
|
Show the register to the user and stop for approval. It is the policy owner's statement of what
|
|
62
62
|
the policy means; you drafted it.
|
|
@@ -70,7 +70,7 @@ python3 <skills>/malloy-compliance-review/scripts/compliance.py check \
|
|
|
70
70
|
--environment <env> --package <pkg> --register policies/controls.json --out-dir review
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
It reads the package
|
|
73
|
+
It reads the package's classification summary (`GET …/packages/{pkg}/classification`), the model read's gates, `get_context` over
|
|
74
74
|
the MCP endpoint (`--mcp`, default `http://127.0.0.1:4040/mcp`) and `GET /api/v0/status`. It probes
|
|
75
75
|
every published domain as every persona with a count, queries every source the package does not
|
|
76
76
|
publish (read from the `.malloy` files in the current directory, or `--package-dir`) to confirm it
|
|
@@ -5,21 +5,21 @@ SPDX-License-Identifier: MIT
|
|
|
5
5
|
|
|
6
6
|
# The checks
|
|
7
7
|
|
|
8
|
-
Each check reads one of four things: the package
|
|
8
|
+
Each check reads one of four things: the package's classification summary (`GET …/classification`: each published
|
|
9
9
|
domain's `#(carries)` and its field count by class), the model read (each domain's gates and which
|
|
10
10
|
givens are `#(secure)`), `get_context` (each published dimension with its classes), or the server
|
|
11
11
|
status. The rest are aggregate queries run as the register's personas. None reads a row.
|
|
12
12
|
|
|
13
13
|
| Check | Fails when | Reads |
|
|
14
14
|
| --- | --- | --- |
|
|
15
|
-
| `inventory` | The
|
|
15
|
+
| `inventory` | The classification summary lists no source, or `get_context` could not list the published fields | classification summary, `get_context` |
|
|
16
16
|
| `loads` | The package did not load, or serves a stale model because its latest files were refused. One finding per refusal, tagged with the domain and kind (`ceiling`, `secure`, `join`) | `/api/v0/status` |
|
|
17
17
|
| `refusals` | Never fails on a refusal: a refused change is evidence that a violating change is not deployed | `/api/v0/status` |
|
|
18
|
-
| `coverage` | A published field has no class |
|
|
18
|
+
| `coverage` | A published field has no class | classification summary |
|
|
19
19
|
| `gated` | A published domain has neither `#(authorize)` nor a row filter | model read |
|
|
20
20
|
| `secure_gates` | A gate reads a given that is not `#(secure)` | model read |
|
|
21
21
|
| `locked_bases` | A source the package does not publish answers a count to an identity holding every role the register names. Its sources are read from the package's `.malloy` files and queried through each file's own path; with no files found it needs review | package files, a count per source |
|
|
22
|
-
| `ceiling` | A domain declares no `#(carries)`, declares a class its `must_not_carry` forbids, or serves a class beyond it |
|
|
22
|
+
| `ceiling` | A domain declares no `#(carries)`, declares a class its `must_not_carry` forbids, or serves a class beyond it | classification summary, register |
|
|
23
23
|
| `access` | A persona's outcome on a domain differs from the register, or the register names a domain the package does not publish | a count per domain per persona |
|
|
24
24
|
| `values` | A field's values prove more than its class allows, a de-identified domain serves day dates, five-digit ZIPs or uncapped values. Free text there needs review | `get_context`, an aggregate profile per field |
|
|
25
25
|
|
|
@@ -113,14 +113,27 @@ class Server:
|
|
|
113
113
|
break
|
|
114
114
|
return out
|
|
115
115
|
|
|
116
|
+
def classification_summary(self, meta: dict[str, Any]) -> dict[str, Any]:
|
|
117
|
+
"""The package's classification summary: each published domain's
|
|
118
|
+
#(carries) and its fields by class. Read from
|
|
119
|
+
`GET …/classification`; a server from before that route carries it on
|
|
120
|
+
the package read as `classification`, so a 404 there falls back to
|
|
121
|
+
`meta`, the package read."""
|
|
122
|
+
try:
|
|
123
|
+
return self.get_json("/classification")
|
|
124
|
+
except urllib.error.HTTPError as e:
|
|
125
|
+
if e.code != 404:
|
|
126
|
+
raise
|
|
127
|
+
return meta.get("classification") or {"sources": []}
|
|
128
|
+
|
|
116
129
|
def inventory(self) -> dict[str, Any]:
|
|
117
|
-
"""The published domains: the package
|
|
130
|
+
"""The published domains: the package's classification summary
|
|
118
131
|
(each domain's #(carries) and its fields by class), the model read of
|
|
119
132
|
each domain's file (gates, and which givens are #(secure)), and
|
|
120
133
|
get_context (each published dimension with its classes). Raises
|
|
121
134
|
HTTPError when the package is not served."""
|
|
122
135
|
meta = self.get_json("")
|
|
123
|
-
summary =
|
|
136
|
+
summary = self.classification_summary(meta)
|
|
124
137
|
reads: dict[str, Any] = {}
|
|
125
138
|
try:
|
|
126
139
|
fields = self.context_fields()
|