@malloy-publisher/skills 0.1.21 → 0.1.23
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 +1 -1
- package/skills/eval-answer/scripts/check_findable.py +99 -0
- package/skills/eval-answer/scripts/check_findable_test.py +38 -0
- package/skills/eval-answer/scripts/score_retrieval.py +14 -1
- package/skills/eval-answer/scripts/score_retrieval_test.py +8 -1
- package/skills/eval-answer/scripts/verify_goldens.py +44 -0
- package/skills/eval-diagnose/SKILL.md +16 -1
- package/skills/eval-import/SKILL.md +1 -1
- package/skills/eval-improve/SKILL.md +1 -1
- package/skills/eval-judge/SKILL.md +22 -1
- package/skills/eval-loop/SKILL.md +21 -6
- package/skills/eval-loop/scripts/build_run_package.py +17 -4
- package/skills/eval-loop/scripts/run_baseline.py +117 -6
- package/skills/eval-loop/scripts/run_baseline_test.py +127 -12
- package/skills/eval-loop/templates/eval-run-package/public/app.js +29 -4
- package/skills/eval-loop/templates/eval-run-package/public/index.html +35 -7
- package/skills/eval-report/SKILL.md +101 -12
- package/skills/malloy/SKILL.md +2 -1
- package/skills/malloy-analysis/SKILL.md +2 -2
- package/skills/malloy-analysis-pitfalls/SKILL.md +2 -2
- package/skills/malloy-analysis-report/SKILL.md +1 -1
- package/skills/malloy-analyze/SKILL.md +1 -1
- package/skills/malloy-charts/SKILL.md +1 -1
- package/skills/malloy-dashboards/SKILL.md +26 -8
- package/skills/malloy-debug/SKILL.md +1 -1
- package/skills/malloy-define/SKILL.md +1 -1
- package/skills/malloy-discover/SKILL.md +1 -1
- package/skills/malloy-document/SKILL.md +1 -1
- package/skills/malloy-getting-started/SKILL.md +1 -1
- package/skills/malloy-gotchas-modeling/SKILL.md +1 -1
- package/skills/malloy-gotchas-queries/SKILL.md +43 -24
- package/skills/malloy-gotchas-rendering/SKILL.md +1 -1
- package/skills/malloy-html-data-app-embedding/SKILL.md +1 -1
- package/skills/malloy-html-data-app-runtime/SKILL.md +2 -2
- package/skills/malloy-html-data-apps/SKILL.md +1 -1
- package/skills/malloy-lookml-review/SKILL.md +1 -1
- package/skills/malloy-lookml-review/reference/curate-visibility.md +1 -1
- package/skills/malloy-materialization/SKILL.md +3 -3
- package/skills/malloy-materialization-tuning/SKILL.md +2 -2
- package/skills/malloy-model/SKILL.md +51 -17
- package/skills/malloy-model-as-you-go/SKILL.md +2 -2
- package/skills/malloy-modeling/SKILL.md +2 -2
- package/skills/malloy-notebook-chat/SKILL.md +1 -1
- package/skills/malloy-notebooks/SKILL.md +2 -2
- package/skills/malloy-patterns/SKILL.md +1 -1
- package/skills/malloy-phrase-detection/SKILL.md +14 -9
- package/skills/malloy-publish/SKILL.md +31 -12
- package/skills/malloy-queries/SKILL.md +11 -12
- package/skills/malloy-review/SKILL.md +1 -1
- package/skills/malloy-scope/SKILL.md +1 -1
- package/skills/malloy-source-unreachable/SKILL.md +71 -0
package/skills/malloy/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy
|
|
3
3
|
description: Index of all Malloy skills. Use when user asks "malloy help", "what malloy skills are available", "how do I use malloy", or needs guidance on which Malloy skill to use.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!-- Copyright (c) Credible Data Inc. SPDX-License-Identifier: MIT -->
|
|
7
7
|
|
|
@@ -46,6 +46,7 @@ This table is a catalogue of what exists, not of what is loaded. A host that ins
|
|
|
46
46
|
| `skill:malloy-notebooks` | Building Malloy notebooks (.malloynb) |
|
|
47
47
|
| `skill:malloy-analysis-report` | Combining validated queries into a notebook report or dashboard |
|
|
48
48
|
| `skill:malloy-analysis-pitfalls` | Checking a query and its results before presenting an answer |
|
|
49
|
+
| `skill:malloy-source-unreachable` | A source is missing from discovery, or a query was refused with a 404 or 403 |
|
|
49
50
|
| `malloy-notebook-chat` | The chat is bound to a notebook or saved report; answer from its cells. Ships in `analysis`. |
|
|
50
51
|
| `skill:malloy-phrase-detection` | Turning a plain-English question into search targets for the context tool |
|
|
51
52
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-analysis
|
|
3
|
-
description:
|
|
4
|
-
version: 0.1.
|
|
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.23
|
|
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-pitfalls
|
|
3
3
|
description: Checks to run on a query and its numbers before a result is presented - wrong grain, fan-out, invented field names, mismatched filters, count vs distinct. The common data-analysis pitfalls.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!-- Copyright (c) Credible Data Inc. SPDX-License-Identifier: MIT -->
|
|
7
7
|
|
|
@@ -22,7 +22,7 @@ That check reads in ONE direction only. `count(joined.field)` is a distinct coun
|
|
|
22
22
|
run: flights -> {
|
|
23
23
|
aggregate:
|
|
24
24
|
all_rows is count()
|
|
25
|
-
missing is count() { where: aircraft.aircraft_models.seats
|
|
25
|
+
missing is count() { where: aircraft.aircraft_models.seats is null }
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-analysis-report
|
|
3
3
|
description: Combine validated Malloy queries into a notebook report or dashboard. Use when the user asks to "create a report", "build a dashboard", "combine these into a report", or wants a persistent multi-query artifact.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-analyze
|
|
3
3
|
description: Open-ended exploration with no specific question to answer, and building views/dashboards/notebooks on an existing model. Use when the user asks "what's interesting?", "explore this data", "find insights", "look for patterns", "build a dashboard", or "create views". Not for a specific data question - answering one is `malloy-analysis`, and writing down what the answer assumed is `malloy-model-as-you-go`. For exploration start at Step 1; for views on an existing model jump to View Patterns.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-charts
|
|
3
3
|
description: Chart selection for a Malloy view, plus the renderer reference for bar_chart, line_chart, scatter_chart, maps and layouts. For choosing a visualization or adding a chart annotation.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-dashboards
|
|
3
3
|
description: "Build or modify a Malloy Publisher dashboard, a tagged .malloy file in a package's dashboards/ directory, with auto-rendered filter controls, a grid layout, and # drill click-through. Use when the user asks for a dashboard, a filterable operational view, or drill-through between views, and no code is wanted."
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -28,6 +28,13 @@ Scanned at a glance is a dashboard; read top to bottom is a notebook.
|
|
|
28
28
|
|
|
29
29
|
## Build sequence
|
|
30
30
|
|
|
31
|
+
0. **CHECK THE PACKAGE CAN SERVE A DASHBOARD AT ALL.** If its root holds an `index.malloy`, that
|
|
32
|
+
file is the package's surface, and a dashboard's file is not something it can `export`, so the
|
|
33
|
+
dashboard you are about to write will compile and then be withheld. Every package made by the
|
|
34
|
+
scaffolder has that file, so this is the common case, not the corner one. Serving dashboards
|
|
35
|
+
there means declaring an `explores` in `publisher.json` naming `index.malloy` and every dashboard
|
|
36
|
+
file, which overrides the convention. Settle that first: it is a different curation shape for the
|
|
37
|
+
package, not a line to add at the end. See "Read the lint" for the warning it produces otherwise.
|
|
31
38
|
1. **READ THE MODEL FIRST.** Get the real source, view, dimension, and given names from the package:
|
|
32
39
|
`get_context` if you have it, otherwise the REST model endpoint or the `.malloy` files.
|
|
33
40
|
Never guess a name. A guessed field in a query fails the whole package load, not just that one
|
|
@@ -438,8 +445,8 @@ Package warnings after a reload are the dashboard's test suite. Fix all of them:
|
|
|
438
445
|
Findings carry a `severity`, but `warn` is the ordinary default and tells you nothing about how bad
|
|
439
446
|
one is. Read the text, not the severity and not the count. One message is worth recognising because
|
|
440
447
|
it changes what the rest of the list means: **"Dashboard lint stopped early, so this list is
|
|
441
|
-
incomplete"**. A dashboard withheld from
|
|
442
|
-
withheld file is not a clean bill of health.
|
|
448
|
+
incomplete"**. A dashboard withheld from the package's surface also loses its own findings, so a
|
|
449
|
+
short list for a withheld file is not a clean bill of health.
|
|
443
450
|
|
|
444
451
|
**Read the status the reload itself returns, not the listing.** One dashboard that fails to compile
|
|
445
452
|
fails the whole package load, and the reload answers **424** with the compile error. A package that
|
|
@@ -454,11 +461,22 @@ at all appears there too, without `stale`, and is absent from the listing entire
|
|
|
454
461
|
|
|
455
462
|
If the reload is 200 and the others are listed but yours is not, discovery skipped the file instead,
|
|
456
463
|
usually a missing or misspelled `# artifact` tag, which is the same mechanism that deliberately skips
|
|
457
|
-
an untagged shared include. There is a second cause
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
464
|
+
an untagged shared include. There is a second cause whenever the package has a curated surface: a
|
|
465
|
+
dashboard whose file is off it is withheld rather than served, and the warning says so and names the
|
|
466
|
+
fix. A package has a surface in two ways, and the second is easy to miss because no manifest field
|
|
467
|
+
records it:
|
|
468
|
+
|
|
469
|
+
- the package's `publisher.json` carries an `explores` list, and the dashboard's file is not on it;
|
|
470
|
+
- the package root holds an **`index.malloy`**, which IS the surface. This bites in both
|
|
471
|
+
directions, and a dashboard's file is not something an `index.malloy` exports either way: adding
|
|
472
|
+
that file to a package that has dashboards withholds every one of them, and adding a dashboard to
|
|
473
|
+
a package that already has one (every scaffolded package does) means it is never served. The fix
|
|
474
|
+
is the same in both directions, and it is not an edit to `index.malloy`: declare an `explores` in
|
|
475
|
+
`publisher.json` naming `index.malloy` and every dashboard file.
|
|
476
|
+
|
|
477
|
+
Either way the surface is what matters, not the `queryableSources` setting, which is `declared` by
|
|
478
|
+
default; a package with neither withholds nothing. Where there is a surface, a `suggest` source has
|
|
479
|
+
to be queryable as well as resolvable, so it needs to be reachable through it too.
|
|
462
480
|
|
|
463
481
|
**A clean reload is not proof the tags are right.** The checks above read names and resolve them; the
|
|
464
482
|
separate warning for a tag that does not _parse_ is syntax only: it carries no
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-debug
|
|
3
3
|
description: Fix Malloy compile errors and understand error messages. Use when encountering errors in .malloy files, user says "fix this error", "malloy error", "compile error", "syntax error", or sees 20+ cascading errors.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-define
|
|
3
3
|
description: Propose a source plan and field definitions for a Malloy semantic model. Covers picking which sources to model and at what grain, then proposing the specific renames, dimensions, and measures per source, every proposal backed by querying the data.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-discover
|
|
3
3
|
description: Silent data discovery for Malloy modeling. Used at Step 1 of the modeling workflow. Scans tables, columns, distributions, and relationships without user interaction. The agent builds an internal picture before presenting anything.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-document
|
|
3
3
|
description: 'Add documentation with #(doc) tags to Malloy models so fields and sources are described in plain language. Use when user asks to "add documentation", "add doc tags", "document the model", or wants fields and sources described for natural-language search and discovery. For declaring parameterizable filters with #(filter), see the malloy-model skill. Filters are a runtime/modeling construct (governance, latency, correctness), not a documentation tag.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-getting-started
|
|
3
3
|
description: First steps for using a Malloy Publisher deployment through its MCP tools. Use when connecting to Publisher for the first time, when you do not yet know the available environments, packages, or models, or when a user asks what data they can explore. Covers verifying the server, discovering data with get_context, and running a first grounded query.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-gotchas-modeling
|
|
3
3
|
description: Common Malloy modeling mistakes, for writing source definitions, dimensions, measures or joins - reserved words, NULL checks, date functions, type casts, field management (extend except/accept/rename vs include public/internal/private), query-based source gotchas.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-gotchas-queries
|
|
3
3
|
description: Common Malloy query and view mistakes, for writing views, queries or notebooks - chart constraints, aggregate filters, joined field aliasing, method syntax, time truncation vs extraction.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -95,6 +95,28 @@ aggregate: avg_price is avg(price), statuses is count(status)
|
|
|
95
95
|
|
|
96
96
|
Check the field's type in the `get_context` result before aggregating it. A name that reads numeric (`order_number`, `zip`, `account_id`) is very often typed string.
|
|
97
97
|
|
|
98
|
+
## A Measure Is Already an Aggregate
|
|
99
|
+
|
|
100
|
+
```malloy
|
|
101
|
+
// WRONG: "Aggregate expression cannot be aggregate" - flight_count is a measure
|
|
102
|
+
run: flights -> { aggregate: busiest is max(flight_count) }
|
|
103
|
+
// RIGHT: aggregate per group, then take the max in a second stage
|
|
104
|
+
run: flights -> { group_by: carrier, aggregate: n is flight_count } -> { aggregate: busiest is max(n) }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The message does not name the field. When it appears, look for a model measure you wrapped in another aggregate. If you only need the top row, skip the second stage: `order_by: n desc` with `limit: 1`.
|
|
108
|
+
|
|
109
|
+
## Window Braces Bind to the Function
|
|
110
|
+
|
|
111
|
+
```malloy
|
|
112
|
+
// WRONG: "`partition_by` is not supported for this kind of expression"
|
|
113
|
+
calculate: share is sum_cumulative(n) / region_total { partition_by: region, order_by: n desc }
|
|
114
|
+
// RIGHT: the brace goes directly after the window function call
|
|
115
|
+
calculate: share is sum_cumulative(n) { partition_by: region, order_by: n desc } / region_total
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
For a within-group share, compute the denominator in `aggregate:` (`region_total is all(count(), region)`). A window's `{ partition_by }` does not apply to `all()`.
|
|
119
|
+
|
|
98
120
|
## Dotted Paths Must Name a Declared Join
|
|
99
121
|
|
|
100
122
|
```malloy
|
|
@@ -157,36 +179,33 @@ where: is_us = true, party ? 'Democrat' | 'Republican'
|
|
|
157
179
|
|
|
158
180
|
`and` works in some arrangements (when the alternation is the second operand) but produces a confusing `'logical operator' Can't use type string` compile error when the alternation comes first. The comma form is unambiguous in every position, so just use it.
|
|
159
181
|
|
|
160
|
-
##
|
|
161
|
-
|
|
162
|
-
Do not use trailing commas between query clauses. Each clause goes on its own line.
|
|
182
|
+
## Strings: Apostrophes and Concatenation
|
|
163
183
|
|
|
164
184
|
```malloy
|
|
165
|
-
// WRONG:
|
|
166
|
-
|
|
167
|
-
// RIGHT
|
|
168
|
-
|
|
169
|
-
group_by: status
|
|
170
|
-
aggregate: n is count()
|
|
171
|
-
limit: 10
|
|
172
|
-
}
|
|
173
|
-
```
|
|
185
|
+
// WRONG: "no viable alternative at input 's'" - the apostrophe ends the literal
|
|
186
|
+
where: name = 'Mac's Diner'
|
|
187
|
+
// RIGHT
|
|
188
|
+
where: name = "Mac's Diner"
|
|
174
189
|
|
|
175
|
-
|
|
190
|
+
// WRONG: "unexpected '+'" / "no viable alternative at input '||'"
|
|
191
|
+
group_by: route is origin ++ '-' ++ destination
|
|
192
|
+
// RIGHT: there is no concatenation operator
|
|
193
|
+
group_by: route is concat(origin, '-', destination)
|
|
194
|
+
```
|
|
176
195
|
|
|
177
|
-
##
|
|
196
|
+
## A Semicolon Inside a Clause Ends It
|
|
178
197
|
|
|
179
|
-
|
|
198
|
+
Between clauses a newline, comma or `;` all work. Within one clause, separate fields with commas or newlines: a `;` closes the clause, and the next field is orphaned.
|
|
180
199
|
|
|
181
200
|
```malloy
|
|
182
|
-
// WRONG:
|
|
201
|
+
// WRONG: "no viable alternative at input 'charters'"
|
|
183
202
|
run: schools -> { aggregate: total is count(); charters is count() { where: is_charter } }
|
|
184
|
-
// RIGHT
|
|
203
|
+
// RIGHT
|
|
185
204
|
run: schools -> { aggregate: total is count(), charters is count() { where: is_charter } }
|
|
186
|
-
// ...or newlines
|
|
187
|
-
run: schools -> {
|
|
188
|
-
aggregate:
|
|
189
|
-
total is count()
|
|
190
|
-
charters is count() { where: is_charter }
|
|
191
|
-
}
|
|
192
205
|
```
|
|
206
|
+
|
|
207
|
+
The same message appears for an unnamed aggregate after the first: `aggregate: n, max(x)` fails at `max`. Name every entry.
|
|
208
|
+
|
|
209
|
+
## Compiles, Then Refused by the Warehouse
|
|
210
|
+
|
|
211
|
+
`Query execution failed: ...` means the Malloy compiled and the warehouse rejected the SQL, often over a type. The position it quotes is in the generated SQL, not in your query. BigQuery, for example, will not partition a window function on a FLOAT64 column. `partition_by:` takes only a field name, so cast in `group_by:` (`season_key is season::string`) and partition on `season_key`, or fix the type in the model.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-gotchas-rendering
|
|
3
3
|
description: Common Malloy renderer mistakes, for adding chart annotations and formatting tags or building dashboards - tag syntax, scale rules, sparkline setup, big_value patterns.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-html-data-app-embedding
|
|
3
3
|
description: Embed an in-package HTML data app into a host page or another application, including auto-sizing and auth. Read when embedding a Publisher page via Publisher.embed.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-html-data-app-runtime
|
|
3
3
|
description: The JavaScript that drives an in-package HTML data app - calling Publisher.query, building queries from filter state, handling results and errors. For writing the page's data code.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -156,7 +156,7 @@ If you validate the rendered page in a headless browser (Playwright or Puppeteer
|
|
|
156
156
|
| Promise rejects, message starts `Publisher.query:` | Read `error.status` and `error.response` for the server's reason (compile error, missing required parameter, permission). |
|
|
157
157
|
| Empty array when you expect rows | Filter value mismatch (case, spelling, type, or a non-ASCII character like `≤` or an en-dash in the literal). Copy the literal verbatim from the model, do not retype the user's paraphrase, and confirm it with a distinct-values query (`run: src -> { group_by: the_dimension }`). Quote strings, use `@` for dates. |
|
|
158
158
|
| 400 on a given (ungated source) | An unknown given name (check spelling; names are case-sensitive), a required given left unset, or a value that doesn't fit the declared type. Malloy rejects it when preparing the query; supply declared givens via `opts.givens` with the right shape (see the givens type table). |
|
|
159
|
-
| 403 on a query that should be allowed, when passing givens to a gated source | On a source
|
|
159
|
+
| 403 on a query that should be allowed, when passing givens to a gated source | On a gated source, a bad given (unknown name or wrong-typed value) fails closed, so it looks like access denied rather than validation. A `#(authorize)` lock also answers 403 for a caller it simply does not admit. Check the given names and values against the model. |
|
|
160
160
|
| KPI shows `undefined` | The result is an array. Read `rows[0].field` (or destructure `const [k] = ...`), not `rows.field`. |
|
|
161
161
|
| Page loads in dev but is not listed or not served | The file is not under the package's `public/` directory. Publisher serves only `public/`; a page written anywhere else (for example `/tmp`) is never reachable at `/environments/<env>/packages/<pkg>/<file>`. |
|
|
162
162
|
| Queries fail only when embedded cross-origin | Cookies are not sent cross-site. Serve same-origin, or pass a bearer token. |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-html-data-apps
|
|
3
3
|
description: Build or modify an in-package HTML data app for a Malloy Publisher package (a public/ directory the package serves). Use when the user wants a hand-authored HTML dashboard or web page backed by a package's Malloy models, with no build step.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-lookml-review
|
|
3
3
|
description: Analyze LookML files as prior art for Malloy modeling. Used during Step 1 (DISCOVER) when .lkml files are present. Coordinates reference files that extract business logic, relationships, and curation decisions. Works with or without a database connection.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -58,7 +58,7 @@ source: revenue is conn.table('…') include {
|
|
|
58
58
|
|
|
59
59
|
Give the mask a **different name** from the private raw column (`amount` vs `raw_amount`), reusing the name is a redefinition. If the mask must carry the raw column's *exact* original name, that forces a `rename:`, which pushes you onto the `extend { rename }` path. That path composes with `include {}`, but only in one order (the `extend { rename: }` first, then `include {}` naming the post-rename field; see `skill:malloy-gotchas-modeling`), and you still have to move the raw column out of the way before the mask can take its name. Prefer a distinct mask name and keep the raw `private:` unless the exact original name is required.
|
|
60
60
|
|
|
61
|
-
**This is NOT per-viewer access control.** LookML `required_access_grants` gates on the *viewing user's* attributes; Malloy models have no per-viewer context, so a model-layer `pick` can only redact based on row data or a parameter. To gate a field by *caller identity/role* (the real `required_access_grants` equivalent), use `#(authorize)` on the source, driven by trusted attributes in Malloy Publisher (see `skill:malloy-model` § Access Control).
|
|
61
|
+
**This is NOT per-viewer access control.** LookML `required_access_grants` gates on the *viewing user's* attributes; Malloy models have no per-viewer context, so a model-layer `pick` can only redact based on row data or a parameter. To gate a field by *caller identity/role* (the real `required_access_grants` equivalent), use `#(authorize)` on the source, driven by trusted attributes in Malloy Publisher (see `skill:malloy-model` § Access Control). `#(authorize)` refuses a caller it does not admit with a 403; `#(access_filter)` is the row-scoping sibling. Masking and these gates are independent layers; use the mask for "everyone sees a coarsened value," a gate for "only some callers see the field at all."
|
|
62
62
|
|
|
63
63
|
## Pattern: Long→wide custom-field pivot
|
|
64
64
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-materialization
|
|
3
3
|
description: Add and debug Malloy Persistence materializations in a package - persist an expensive source so queries read a pre-built table. Read this whenever the user wants to materialize a source, add a persist annotation, speed up a slow source, or asks why a persist source isn't building.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -95,7 +95,7 @@ source: persist_smoke is smoke_raw -> { aggregate: n is count() }
|
|
|
95
95
|
|
|
96
96
|
Delete the smoke file and drop its table afterward.
|
|
97
97
|
|
|
98
|
-
## Persisting
|
|
98
|
+
## Persisting a `#(access_filter)`-gated source
|
|
99
99
|
|
|
100
100
|
A gated source **can** be persisted, but only on one tier and only in one shape, and the thing to be
|
|
101
101
|
careful about is not refused by anything - you have to decide it.
|
|
@@ -173,4 +173,4 @@ bound on how long a revoked row keeps being served.
|
|
|
173
173
|
- **Quote the name** - a bare `name=` always hard-stops the build.
|
|
174
174
|
- **Republishing unchanged persist logic reuses the table** - reuse is keyed on the content-addressed `sourceEntityId`, not the `name=`.
|
|
175
175
|
- **Removing a persist source (or a smoke test) does not drop its table** - physical-table cleanup is the caller's responsibility; drop it yourself.
|
|
176
|
-
- **
|
|
176
|
+
- **A `#(access_filter)`-gated source freezes its gating column when persisted** - the gate still runs live, but a revoked row keeps being served under its old access decision until the next rebuild. `storage=` and `#@ preaggregate` refuse a gated source outright. See **Persisting a `#(access_filter)`-gated source**.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-materialization-tuning
|
|
3
3
|
description: Optimize a package's Malloy Persistence materializations for cost and performance using the malloy-pub CLI and the materialization history. Recommend what to persist, what to stop persisting, and how to schedule/scope it. Use when the user asks to make a package cheaper or faster, tune persistence, decide what to materialize, or review persist/schedule choices.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -60,7 +60,7 @@ Look across several runs, not one: the pattern over the recent history (how ofte
|
|
|
60
60
|
|
|
61
61
|
Weigh rebuild cost against query benefit. Common findings:
|
|
62
62
|
|
|
63
|
-
- **Persist candidate:** an expensive, frequently-queried source that is _not_ persisted (recomputed on every query). Recommend adding `#@ persist name="…"`. Strongest when the source is a heavy aggregate/join reused by many queries and its inputs change slowly. If the source carries
|
|
63
|
+
- **Persist candidate:** an expensive, frequently-queried source that is _not_ persisted (recomputed on every query). Recommend adding `#@ persist name="…"`. Strongest when the source is a heavy aggregate/join reused by many queries and its inputs change slowly. If the source carries a `#(access_filter)` gate, check which case it is first. A gate reached only through a `join_*`, or inherited from a base it cannot be attributed to, makes the colocated persist **refuse with a 422** - so do not recommend persisting it at all. Where the gate is provably the source's own row filter the persist is admitted, and then only recommend it alongside a freshness window (`freshness.fallback="live"`): the gating column freezes at build time, so without one a revoked row can be served under its old access decision indefinitely (see `skill:malloy-materialization`, which also covers where that window does and does not bind).
|
|
64
64
|
- **Removal candidate:** a persisted source that is cheap to compute, rarely queried, or rebuilt far more often than it is read. Recommend dropping the `#@ persist` annotation (and its table): the storage + rebuild cost is not buying anything.
|
|
65
65
|
- **Cadence mismatch:** a `SCHEDULER` cadence out of step with how fast the data changes or how long a build takes. If most scheduled runs are all-reused (nothing changed), the cron is too frequent, so loosen it. If queries routinely read stale data, tighten it. If the build's `durationMs` approaches the interval, the cadence is too aggressive.
|
|
66
66
|
- **Scope mismatch:** `scope: version` re-materializes per published version (right when versions must be isolated, e.g. a schedule); `scope: package` reuses one lineage across versions (cheaper when versions can share). A schedule _requires_ `version`. If a package carries a schedule it does not need, clearing the schedule frees it to use the cheaper package scope.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-model
|
|
3
3
|
description: Build Malloy semantic models with base source and joined source files. Use when creating or modifying .malloy files, user asks to "create a malloy model", "add dimensions", "add measures", "create a source", or any Malloy model authoring task.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -225,7 +225,7 @@ A given is **declared bare** but **referenced with a `$` sigil** in expressions
|
|
|
225
225
|
- **Givens don't auto-inject a `where:`.** Unlike `#(filter)`, you write the filter expression that references the given yourself (e.g. `where: dimension ~ $given_name`).
|
|
226
226
|
- **Not every filter maps cleanly.** A filter with no neutral match-all literal default - e.g. a scalar date/number range like `> @2020-01-01` - is not a good `given:`; keep those on `#(filter)`. Two more cases keep using `#(filter)`: mandatory scoping filters (`required`) and system-injected row-level filters (`implicit`), both below.
|
|
227
227
|
|
|
228
|
-
Givens are also the substrate for access control - see "Access Control:
|
|
228
|
+
Givens are also the substrate for access control - see "Access Control: `#(authorize)` and `#(access_filter)`" below.
|
|
229
229
|
|
|
230
230
|
## Legacy: Parameterizable Filters with `#(filter)`
|
|
231
231
|
|
|
@@ -296,38 +296,72 @@ Publisher formats values based on the dimension's data type, `string` → `'valu
|
|
|
296
296
|
|
|
297
297
|
Pass `bypass_filters=true` (REST) or `bypassFilters: true` (POST body) to skip filter injection entirely. Use sparingly, required-filter governance only works if bypass is restricted to trusted callers.
|
|
298
298
|
|
|
299
|
-
## Access Control:
|
|
299
|
+
## Access Control: `#(authorize)` and `#(access_filter)`
|
|
300
300
|
|
|
301
|
-
Gate query access to a source
|
|
301
|
+
Gate query access to a source over declared `given:` values (`given:` is Malloy's native runtime-parameter mechanism, the going-forward replacement for `#(filter)`). **Two annotations, one question each, and the name tells you which:**
|
|
302
|
+
|
|
303
|
+
| Annotation | The question | Body it takes | A denial is |
|
|
304
|
+
| --- | --- | --- | --- |
|
|
305
|
+
| `#(authorize)` | may this caller reach this source at all? | `'literal' <op> $GIVEN`, plus the `true`/`false` sentinels | **403** |
|
|
306
|
+
| `#(access_filter)` | which rows may they see, once they may? | `field_path <op> $GIVEN` | **200**, with their rows |
|
|
307
|
+
|
|
308
|
+
Either is an annotation on its own line directly above the `source:` line, carrying a **narrow grammar publisher parses itself, not an arbitrary Malloy expression**: one or more terms joined only by `and`, with `<op>` fixed by the given's declared arity (`in` for a list, `=` for a scalar).
|
|
309
|
+
|
|
310
|
+
**Nothing is inferred from the body.** The annotation you write declares which question you are answering, and a body that does not fit is refused at load naming the other annotation. A source with no annotation of either kind, own or inherited, is unrestricted.
|
|
311
|
+
|
|
312
|
+
The lock is DECIDED, before the caller's query compiles: a caller it does not admit gets a 403, never a row count or a `NULL` aggregate over data they were refused. The filter is GRAFTED onto the query as a `where:`, so a caller it matches nowhere gets a 200 with an empty result. The lock runs first; a caller it refuses never reaches the filter. A 403 also covers either gate failing to apply at all (a field the entry point dropped, a given nobody supplied).
|
|
302
313
|
|
|
303
314
|
```malloy
|
|
304
315
|
##! experimental.givens
|
|
305
316
|
|
|
306
317
|
given:
|
|
307
318
|
ROLE :: string
|
|
319
|
+
ORG_IDS :: string[]
|
|
308
320
|
|
|
309
|
-
#(authorize)
|
|
321
|
+
#(authorize) 'analyst' = $ROLE
|
|
322
|
+
#(access_filter) org_id in $ORG_IDS
|
|
310
323
|
source: orders is duckdb.table('orders.parquet') extend {
|
|
311
324
|
measure: order_count is count()
|
|
312
325
|
}
|
|
313
326
|
```
|
|
314
327
|
|
|
315
|
-
- **
|
|
316
|
-
- **
|
|
317
|
-
-
|
|
318
|
-
- **
|
|
319
|
-
- **
|
|
320
|
-
- **
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
328
|
+
- **Nothing outside the two term shapes above parses.** No `or`, `not`, `!=`, `<`/`>`/`<=`/`>=`, function calls, bare field/boolean references, or a literal on the right of a row-level term. `org_id in $GROUPS` and `region = $REGION and org_id in $GROUPS` are legal; `upper(region) = $REGION`, `(org_id in $GROUPS or region = $REGION)`, and a bare `#(access_filter) authorized` are all refused at load with a named cause (see your deployment's reference documentation for the full list).
|
|
329
|
+
- **Writing a term on the wrong annotation is refused, both ways.** `#(authorize) org_id in $GROUPS` is refused naming `#(access_filter)`; `#(access_filter) 'finance' in $GROUPS` is refused naming `#(authorize)`. The second matters: a constant predicate grafted as a row filter would serve a refused caller 200 with zero rows, which is the answer the lock exists to replace.
|
|
330
|
+
- **A source may declare more than one note on a route: repeats AND together.** `#(access_filter) region = $REGION` stacked with a second `#(access_filter) org_id in $GROUPS` both apply, and a caller must satisfy every term across every note. `or` is still refused wherever it appears, so there is no way to spell "admit if either" inside one gate or across a source's gates; use two extension sources instead, one per admitted population (see the admin pattern below).
|
|
331
|
+
- **Two exceptions to "every term references a given", and both live on the lock.** `#(authorize) false` is a deny-all: that is how you lock a base. `#(authorize) true` is an admit-all, and it is what an extension of a locked base needs in order to be open: a source declaring no gate of its own **inherits** its ancestor's, so leaving the annotation off an extension of a `false` base inherits the lock rather than lifting it. Neither sentinel is legal on `#(access_filter)`. `false` may not share a source with another note at all; `true` may not share the lock route with one, but is live beside an `#(access_filter)` (`#(authorize) true` with `#(access_filter) org_id in $GROUPS` re-opens a locked base while still scoping the rows). Neither is a term: `true and org_id in $GROUPS` is read as an ordinary two-term body whose first term is malformed.
|
|
332
|
+
- **Inheritance is per route.** Both inherit through `extend` the same way, own wins over ancestor on that route only, so a source can be "own" for one and "inherited" for the other. The API reports them separately, each under the field named for its own annotation: `Source.authorize` and `Source.accessFilter`.
|
|
333
|
+
- **Generate the admin escape hatch as another extension over the locked base, never as a bypass.** When a model needs an ordinary population plus a wider one, add a second extension whose rule is a lock on the caller:
|
|
334
|
+
|
|
335
|
+
```malloy
|
|
336
|
+
given:
|
|
337
|
+
ORG_IDS :: string[]
|
|
338
|
+
GROUPS :: string[]
|
|
339
|
+
|
|
340
|
+
#(authorize) false
|
|
341
|
+
source: orders_base is duckdb.table('orders.parquet') extend {}
|
|
342
|
+
|
|
343
|
+
#(authorize) true
|
|
344
|
+
#(access_filter) org_id in $ORG_IDS
|
|
345
|
+
source: orders is orders_base extend {}
|
|
346
|
+
|
|
347
|
+
#(authorize) 'admin' in $GROUPS
|
|
348
|
+
source: orders_admin is orders_base extend {}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Each extension replaces the base's `false` on the lock route with its own rule. `orders` is open to everyone and scoped per row; `orders_admin` is locked to the admin group and, carrying no filter, serves every row to whoever passes. This is the shape to generate whenever an author wants a role to see everything: another extension source, not a flag that skips the gate.
|
|
352
|
+
- **Both only gate from the `source:` line.** The same annotation on a `dimension:`/`measure:`/`join_*:`/`view:` line, or on a top-level `query:`, is refused at load naming the position rather than silently protecting nothing.
|
|
353
|
+
- **Every given the gate references must be declared on the entry model's own surface, and must carry no default.** A given the model cannot resolve is refused at load. So is a referenced given declared *with* a default: a caller who supplies nothing would get that default and be admitted or excluded by a value the gate's own line never shows, so it is refused rather than reasoned about case by case.
|
|
354
|
+
- **A scalar/array mismatch between the operator and the given's declared type is a load-time refusal**, not a request-time warehouse error: `org_id in $GROUPS` requires `GROUPS` to be array-typed, `region = $REGION` requires `REGION` scalar. Negation is likewise refused outright, so there is no empty-given inversion surprise to warn about.
|
|
355
|
+
- **Entry point only: not joined, but inherited through `extend`.** The gate applies to the source a query enters through. A gate on a source reached only via `join_*` **never fires**, at any depth, so anything ungated that joins a locked base hands the base's rows to every caller. A source that `extend`s a locked base and declares no gate of its own **does** carry the base's gate; declaring its own annotation replaces it. A source derived from a locked base via a query (`source: z is locked -> { … }`) instead **always carries the base's gate in addition to its own**: the derivation recurses into the base unconditionally, so an own gate does not replace it, and the two combine as separate AND'd entries. Pair a locked base with curated extension sources, using access modifiers (`include { public: …, private: * }`), so an extension re-exposes only a curated column surface, and keep sensitive sources out of ungated joins.
|
|
356
|
+
- **A derivation that drops a column the gate reads fails CLOSED.** `extend { except: org_id }`, or an `accept:` that omits it, leaves the grafted filter unable to compile, so the request is denied rather than served ungated. The one hole to know: dropping the gated column and then `rename:`-ing a *different* column onto that exact name grafts successfully and binds the gate to the wrong column. Narrow, but real, so don't recycle a gated column's name. If a derivation's own projection needs to drop the column a row-level term would read, use a source-level term (`'literal' in/= $GIVEN`) instead, since it never depends on any projected column.
|
|
357
|
+
- **The quoted-string and file-level forms are refused at load and no longer exist.** A quoted expression on the `source:` line, in either quote, a file-level `##(…) "<expr>"` applying to every source in the file, and the earlier `internal dimension: authorized is <expr>` form are all retired; the load names the rewrite. Every `.malloy` file in a package compiles at load and any failure aborts the package, so a retired-form gate anywhere in the package is refused. Only a declaring file *outside* the package escapes that: it loads and denies every request instead, with no compile-time hint. See your deployment's reference documentation.
|
|
324
358
|
- **A gated source can be persisted, but the gating column freezes.** `storage=` and `#@ preaggregate` refuse a gated source outright; a colocated `#@ persist` is admitted when the gate is provably the entry point's own row filter. The gate still runs live on every query, so rows come back filtered - but the column it filters ON is frozen at build time, so a row whose access decision changes keeps being served under the old decision until the next rebuild. Pair `#@ persist` on a gated source with a freshness window (`fallback="live"`), which is the only control that bounds that - and read `skill:malloy-materialization` for where that window binds, because on a standalone Publisher it does not.
|
|
325
359
|
|
|
326
|
-
> **Trust caveat.** Givens are **caller-asserted**, anyone who can reach the query API can claim a favorable given, e.g. `{"ROLE":"admin"}`.
|
|
360
|
+
> **Trust caveat.** Givens are **caller-asserted**, anyone who can reach the query API can claim a favorable given, e.g. `{"ROLE":"admin"}`. Neither annotation is a real boundary unless it sits behind a trusted tier that sets givens from its own verified context, never directly from an untrusted caller. Neither is, on its own, end-user authentication.
|
|
327
361
|
>
|
|
328
|
-
> **Forward direction.** Givens are how access control is built here, and the planned next step is **identity-bound ("secure") givens** - reserved values a trusted tier populates from a verified token or proxy header, which the caller cannot override - turning
|
|
362
|
+
> **Forward direction.** Givens are how access control is built here, and the planned next step is **identity-bound ("secure") givens** - reserved values a trusted tier populates from a verified token or proxy header, which the caller cannot override - turning these gates into a standalone boundary. Model access on `given:` + these two annotations now; it is the surface that carries forward.
|
|
329
363
|
|
|
330
|
-
Full syntax, inheritance rules, validation, and the error contract are covered in your deployment's
|
|
364
|
+
Full syntax, inheritance rules, validation, and the error contract are covered in your deployment's authorize reference documentation.
|
|
331
365
|
|
|
332
366
|
## Join Syntax
|
|
333
367
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-model-as-you-go
|
|
3
|
-
description: After answering a data question, write down what the answer assumed so the next reader can trust the number. A field with a #(doc) in the model when you can edit it, an extend in the notebook when you can only author reports, or a stated assumption plus a Malloy snippet when you can only chat. Use after every answered question that rested on a judgment call, and whenever a question is asked against tables that have no model yet.
|
|
4
|
-
version: 0.1.
|
|
3
|
+
description: 'After answering a data question, write down what the answer assumed so the next reader can trust the number. A field with a #(doc) in the model when you can edit it, an extend in the notebook when you can only author reports, or a stated assumption plus a Malloy snippet when you can only chat. Use after every answered question that rested on a judgment call, and whenever a question is asked against tables that have no model yet.'
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-modeling
|
|
3
3
|
description: Build semantic models with Malloy for the Malloy Publisher. Read this skill whenever the user asks about modeling data or specifically mentions Malloy.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -73,7 +73,7 @@ DISCOVER → SCOPE → SOURCES → DEFINITIONS → BUILD BASE → BUILD JOINED
|
|
|
73
73
|
| 5. Build Base Sources | `skill:malloy-model` | Write fully documented base source files (one per table), check diagnostics. Read `skill:malloy-document` for doc standards. |
|
|
74
74
|
| 6. Build Joined Sources | `skill:malloy-model` | Write fully documented joined source files, validate. Read `skill:malloy-document` for doc standards. |
|
|
75
75
|
| 7. Review | (none) | Present the review checklist below; user confirms or corrects |
|
|
76
|
-
| 8. Curate | `skill:malloy-model` | Propose
|
|
76
|
+
| 8. Curate | `skill:malloy-model` | Propose the published surface (an `index.malloy` with `export { ... }`) and access controls (access modifiers, gates); always propose, the user decides whether to apply |
|
|
77
77
|
|
|
78
78
|
### The pauses are the point
|
|
79
79
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-notebook-chat
|
|
3
3
|
description: Steps to follow when the chat is bound to a notebook or saved report. The notebook's cells are the agent's primary context, answer from it, run its queries, and only reach for get_context when the user asks about something outside it.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.23
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|