Paper to Chinese Patent Drafter
Yuan1z0825/nature-skills
Drafts Chinese invention patent applications and technical disclosures from research papers or inventor materials, tying each claim feature to source evidence.
Build Malloy semantic models with base source and joined source files.
$ npx skills add malloydata/publisher --skill malloy-model -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install malloydata/publisher malloy-model --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/malloy-model .claude/skills/malloy-model && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "malloy-model" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-model into .claude/skills/malloy-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-model", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/malloydata/publisher/tree/main/skills/malloy-modelType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add malloydata/publisher --skill malloy-model -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install malloydata/publisher malloy-model --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/malloy-model .agents/skills/malloy-model && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "malloy-model" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-model into .agents/skills/malloy-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-model", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add malloydata/publisher --skill malloy-model -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install malloydata/publisher malloy-model --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/malloy-model .cursor/skills/malloy-model && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "malloy-model" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-model into .cursor/skills/malloy-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-model", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/malloydata/publisher.git --path skills/malloy-model--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add malloydata/publisher --skill malloy-model -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install malloydata/publisher malloy-model --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/malloy-model .gemini/skills/malloy-model && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "malloy-model" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-model into .gemini/skills/malloy-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-model", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install malloydata/publisher malloy-modelInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add malloydata/publisher --skill malloy-model -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/malloy-model .github/skills/malloy-model && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "malloy-model" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-model into .github/skills/malloy-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-model", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add malloydata/publisher --skill malloy-model -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install malloydata/publisher malloy-model --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/malloy-model .opencode/skills/malloy-model && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "malloy-model" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-model into .opencode/skills/malloy-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-model", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
malloy-modelBuild Malloy semantic models with base source and joined source files.
Malloy Model is an agent skill from malloydata/publisher. 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.
Its SKILL.md is about 6.9k tokens, which your agent loads only when the skill is triggered. The skill folder holds 6 other files (for example `reference/access-modifiers.md`, `reference/analysis-to-model.md` and `reference/bridge-tables.md`).
It sits in Legal & Compliance. The repository describes itself as: Publisher is the open-source analytics engine for Malloy. It lets you define data models once — and use them everywhere. The licence is MIT.
Read from SKILL.md and the folder at commit 39a546f. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are malloy).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Malloy Model loads about 6.9k tokens when it runs. Until then it costs about 64 tokens; SKILL.md has 3,441 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from malloydata/publisher at commit 39a546f, republished under its MIT licence (© malloydata). 3,441 words, ~6,893 tokens.
.claude/skills/malloy-model/SKILL.md (or your agent's skills folder). This skill also uses 5 other files; get the full folder from GitHub.<!--
Copyright (c) Credible Data Inc.
SPDX-License-Identifier: MIT
-->
Tool names are written bare here -
get_context,execute_query,search_malloy_docs. The exact prefixed name depends on the host surface; match each against the tools you actually have.
If no .malloy files exist yet, do discovery and propose a structure first, then return here to build base source and joined source files. Keep proposals and the analysis behind them in the conversation.
File structure convention (a flat layout at the package root is the simplest default):
<package-name>/
publisher.json # Required for publishing (name, version, description)
customers.malloy # Base source: one per table
products.malloy
orders.malloy
user_order_facts.malloy # Computed source
order_analysis.malloy # Source: one per analytical domain
customer_health.malloyVersions for new packages should start at "0.0.1".
Before creating any files, check for an existing publisher.json in the target directory. If one exists for a different package, create a new subdirectory for your package, don't overwrite another package's config.
In Publisher an environment is a project, and Publisher is single-tenant, so there is no org/tenant layer to model around: one environment holds one set of packages.
If your discovery turned up existing modeling patterns to mirror (a derived table, UNNEST joins, a review or curation pass), read the relevant reference before building.
| Pattern found in prior art | Reference to read |
|---|---|
| Derived table (PDT/NDT) | skill:malloy-lookml-review build-derived-tables guidance |
| UNNEST joins or struct access | skill:malloy-lookml-review build-unnest guidance |
| Review pass for coverage | skill:malloy-lookml-review review-coverage guidance |
| Curate pass with visibility seeds | skill:malloy-lookml-review curate-visibility guidance |
source: customers is my_conn.table('sales.customers')
extend {
primary_key: customer_id
dimension:
// A dimension is the lighter way to give a column a cleaner name
order_type is `Type`
full_name is concat(first_name, ' ', last_name)
segment is lifetime_value ?
pick 'enterprise' when >= 100000
pick 'mid-market' when >= 10000
else 'SMB'
measure:
customer_count is count()
}Every
dimension:needsname is expr. A bare column name likedimension: speciesis a parse error (unexpected '}', ormissing 'is' before 'measure:'when another declaration follows). Raw columns are already usable ingroup_by/selectwithout any declaration, so only add adimension:when deriving or renaming a field (e.g.revenue is price * quantity).
##! experimental.access_modifiers
source: orders is my_conn.table('sales.orders')
include {
public:
#(doc) Order identifier
order_id
#(doc) Customer who placed the order
user_id
#(doc) Total sale price in USD
sale_price
#(doc) Order creation timestamp
created_at
internal:
raw_payload_json // Verified empty via index query + user confirmation
}
extend {
primary_key: order_id
dimension:
#(doc) Date the order was placed
order_date is created_at::date
measure:
#(doc) Total number of orders
order_count is count()
#(doc) Total revenue in USD
# currency
revenue is sum(sale_price)
}Wrap the query in parentheses and extend it. from(...) was removed from the language and no longer parses (unexpected 'from').
import "orders.malloy"
source: user_order_facts is (
orders -> {
group_by: customer_id
aggregate:
total_orders is count()
total_revenue is sum(sale_price)
first_order_date is min(created_at)
last_order_date is max(created_at)
}
) extend {
primary_key: customer_id
dimension:
days_since_last_order is days(last_order_date to now)
is_repeat_buyer is total_orders > 1
measure:
buyer_count is count()
avg_customer_ltv is avg(total_revenue)
}For advanced query-based source patterns (window functions, pipelines), see reference/query-sources.md.
import "customers.malloy"
import "orders.malloy"
import "user_order_facts.malloy"
#(doc) Customer health analysis. Use for retention, segmentation, and churn risk.
source: customer_health is customers extend {
join_one: user_order_facts with customer_id
join_many: orders on customer_id = orders.customer_id
dimension:
is_at_risk is user_order_facts.days_since_last_order > 90
and user_order_facts.total_orders > 1
measure:
revenue_per_customer is orders.sale_price.sum() / nullif(customer_count, 0)
at_risk_count is count() { where: is_at_risk = true }
}| Base Joined Source File | Joined Source File | |
|---|---|---|
| Contains | One table's fields | Joins between base sources |
| Dimensions | Intrinsic to this table only | Cross-source (require joins) |
| Measures | Single-table aggregations | Cross-source aggregations |
| Joins | None (or only lookup joins intrinsic to the source) | Defines relationships between base sources |
| Views | None, in schema-first (see below) | None, in schema-first (see below) |
| One per | Physical table or computed source | Analytical domain |
The "no views" rule is schema-first only. A schema-first model is built before anyone
has asked a question, so any view in it is a guess. It does not apply to the
analysis-first workflow (skill:malloy-model-as-you-go), where every view is a
question that was asked and verified - there, saving a view or a dashboard is the right
call, in the model file next to the measures it uses. Analysis-first still models
everything else properly: documented dimensions, measures, and joins.
Two more rules here are schema-first only. Analysis-first should skip access modifiers and curation - there is no discovery surface to curate when every field was paid for by a question - and skip one-file-per-table, keeping a single domain file until it genuinely gets unwieldy.
dimension: needs name is expr: a bare dimension: species is a parse error. Raw columns are queryable directly in group_by / select; only declare a dimension: to derive or rename a field.import statements in multi-file architecturenullif(denominator, 0) for all divisionorder_by: group_by: yr is table.yeara.b.field (each hop needs explicit join)pick 'Small' when size < 10where: vs having:: Use where: for row filters, having: for aggregate filtersrename: composes with include {}, but only in one order: the extend { rename: } must come before the include {}, which then names the field by its new name. Reversed, it fails with Can't find field 'X' to set access modifier. For a cleaner column name without a rename, internal: + dimension: is still the lighter move (mark `Type` as internal, add dimension: order_type is `Type`). See skill:malloy-gotchas-modeling § Field Managementinternal when a derived dimension replaces themimport lines stay together), no trailing whitespace, and a long line broken after a comma or before an operator, at whatever width the project keeps to. In a project whose files already use another layout (tabs, four spaces), a new file matches them.pick expression or filtered measure is user-supplied, distribution-derived (query min/p25/p50/p75/p95 first and show the evidence; Malloy has no percentile function, so use the two-stage query in skill:malloy-discover § Example Queries; see skill:malloy-define § Data-driven proposals), or explicitly flagged as an assumption in its #(doc). A hardcoded cutoff nobody confirmed is a business decision shipped as fact.given: (preferred)Native Malloy given: parameters are how you expose tunable knobs (date range, region, manufacturer) on a source. #(filter) is deprecated: never add one. A given: is a first-class runtime parameter you reference in the model's own logic; callers supply values at query time and the model uses them however it declares. Enable them with ##! experimental.givens at the top of the model.
##! experimental.givens
given:
manufacturer_filter :: filter<string> is f''
subject_filter :: filter<string> is f''
source: recalls is duckdb.table('data/auto_recalls.csv') extend {
where: Manufacturer ~ $manufacturer_filter, Subject ~ $subject_filter
measure: recall_count is count()
}A given is declared bare but referenced with a $ sigil in expressions ($manufacturer_filter), as above.
filter<> given defaulting to f'' - so an unsupplied value returns unfiltered rows, matching how #(filter) behaves when a value is omitted. Because the given bakes an always-on where: into the source, a non-neutral default (e.g. a date floor) applies to every read of the source, not just the ones that opt in - so keep defaults neutral. Defaults must be Malloy literals.where:. Unlike #(filter), you write the filter expression that references the given yourself (e.g. where: dimension ~ $given_name).#(filter) use has a given: form. Never add a #(filter) annotation to a model, not even for required, implicit, or a date/number range:| You want | Write this | Not this |
|---|---|---|
| An optional value or list filter | given: REGION :: filter<string> is f'' and where: region ~ $REGION | #(filter) dimension=region type=in |
| A date or number range | given: MIN_SALE :: filter<number> is f'' (or filter<date>) and where: sale_price ~ $MIN_SALE. The caller sends a filter expression such as >= 50. | two #(filter) lines, greater_than and less_than |
| A value every query must supply (the primary key is only unique within it, or the table is too big to scan whole) | a given with no default, e.g. given: EVENT_DATE :: date and where: event_date = $EVENT_DATE. A query that omits it fails with "Given 'EVENT_DATE' has no value and no default". | #(filter) ... required |
| A row filter the system applies, not the caller | #(access_filter) org_id in $ORG_IDS, with ORG_IDS set by a trusted tier (see the trust caveat below) | #(filter) ... implicit |
import "x.malloy" brings every given x.malloy declares, and a selective import { src } from "x.malloy" brings only what it names. The source still compiles either way, because Malloy carries the declaration underneath, but a caller can set only a given the entry model has in scope. In a package curated with index.malloy, that means index.malloy imports the declaring file whole or names the given in its selective import. Imports don't chain: a given the declaring file itself imports from elsewhere has to reach index.malloy too.Givens are also the substrate for access control - see "Access Control: #(authorize) and #(access_filter)" below.
#(filter) model#(filter) is deprecated. Do not add one, and do not copy one from an existing model into a new source. This section is here so you can read, call, and migrate a model that already has them.
Publisher parses the annotation, lists the filters in the API, and injects a where: clause server-side when a caller passes a value (filterParams / filter_params). The annotation sits above the source: line:
#(filter) [name=NAME] dimension=DIMENSION type=TYPE [implicit] [required]| Part | Meaning |
|---|---|
name | The API parameter key. Defaults to the dimension name. |
dimension | The dimension the filter targets. |
type | equal (=), in (any of several values), like (~ '%value%'), greater_than (>, exclusive), less_than (<, exclusive). |
required | The server returns 400 when a query supplies no value. |
implicit | Hidden from the UI and the API filter list. |
Publisher formats the value from the dimension's type ('value', bare true/false, @YYYY-MM-DD), so a caller passes it unquoted.
#(filter) is not a security boundary. A caller skips it with bypass_filters=true (REST) or bypassFilters: true (POST body), or by writing their own query text. Use givens with #(authorize) / #(access_filter) for access control.
To migrate a source, replace each annotation using the table above, then delete it. type=greater_than and type=less_than are exclusive, so a plain comparison you convert one to is > or <, not >= or <=. docs/givens.md § "Coming from #(filter)" has a worked conversion.
#(authorize) and #(access_filter)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:
| Annotation | The question | Body it takes | A denial is |
|---|---|---|---|
#(authorize) | may this caller reach this source at all? | 'literal' <op> $GIVEN, plus the true/false sentinels | 403 |
#(access_filter) | which rows may they see, once they may? | field_path <op> $GIVEN | 200, with their rows |
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).
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.
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).
##! experimental.givens
given:
ROLE :: string
ORG_IDS :: string[]
#(authorize) 'analyst' = $ROLE
#(access_filter) org_id in $ORG_IDS
source: orders is duckdb.table('orders.parquet') extend {
measure: order_count is count()
}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).#(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.#(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).#(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.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.given:
ORG_IDS :: string[]
GROUPS :: string[]
#(authorize) false
source: orders_base is duckdb.table('orders.parquet') extend {}
#(authorize) true
#(access_filter) org_id in $ORG_IDS
source: orders is orders_base extend {}
#(authorize) 'admin' in $GROUPS
source: orders_admin is orders_base extend {} 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.
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.index.malloy, "on the surface" means index.malloy imports it: import the declaring file whole, or name the given in a selective import (import { orders, GROUPS } from "orders.malloy"). 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.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.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 extends 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.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.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.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.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.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.
Full syntax, inheritance rules, validation, and the error contract are covered in your deployment's authorize reference documentation.
join_one: users with user_idjoin_one: origin is airports on origin_code = origin.codejoin_one: items on order_id = items.order_id and product_id = items.product_idjoin_one: origin_airport is airports with originJoin Types: join_one: (many-to-one, efficient) | join_many: (one-to-many, always safe) | join_cross: (many-to-many)
Verify cardinality before writing joins: run: target -> { group_by: fk_col, aggregate: n is count(), having: n > 1, limit: 5 }. 0 results → join_one. Any results → join_many.
Check diagnostics after writing. Errors cascade, fix the FIRST error only, then re-check. If errors persist, use the debugging strategy: look at first error, search docs if unsure, fix, repeat.
Validate with execute_query: Run queries, check distributions, verify measures, confirm joins (no fan-out).
To inspect the sources and fields a model already defines, ground yourself with get_context. It returns the package's sources, views, and fields, so there is no separate schema-search step. When you're unsure of Malloy syntax, call search_malloy_docs rather than guessing.
Load the relevant reference file when you encounter these scenarios:
| Scenario | Read |
|---|---|
| Need pre-aggregated or windowed source | reference/query-sources.md |
| Curating access modifiers | reference/access-modifiers.md |
| Normalized/ER-style schema (4+ tables, no clear fact table) | reference/normalized-schemas.md |
| Formalizing analysis into a model | reference/analysis-to-model.md |
| Many-to-many / bridge tables / composite keys | reference/bridge-tables.md |
Step complete. Output: base source files (.malloy, one per table) and joined source files (.malloy, one per analytical domain).
Suggest next steps to the user, unless your host's instructions say it shows follow-up suggestions of its own:
http://localhost:4000/<environmentName>/<packageName> for the package, or http://localhost:4000/<environmentName>/<packageName>/<modelPath> for a single model file. First confirm the running server actually serves this package (it is in the loaded publisher.config.json, or mounted live with --server_root . --watch-env <env>); a package the server has not loaded returns a 404, so do not hand over a link to a package that was just authored but never loaded.skill:malloy-analysis).© malloydata, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 5 other files in skills/malloy-model of malloydata/publisher.
Open the folder on GitHubat commit 39a546f
Malloy Model next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Malloy Model this skillmalloydata/publisher | 116 | — | ~6.9k | Automated safety check: Pass | MIT | |
| Paper to Chinese Patent DrafterYuan1z0825/nature-skills | 46k | 1 repos | ~1.1k | Automated safety check: Pass | Apache-2.0 | |
| C15tc15t/c15t | 1.9k | 1 repos | ~1.6k | Automated safety check: Pass | Apache-2.0 | |
| Contract Reviewevolsb/claude-legal-skill | 462 | 1 repos | ~3.6k | Automated safety check: Pass | MIT | |
| Legal Clinic Client Intakeanthropics/claude-for-legal | 9.6k | 3 repos | ~3.2k | Automated safety check: Pass | Apache-2.0 | |
| Paper To Cn Patentsnipp-zha/Paper-to-patent-Skill | 105 | 1 repos | ~959 | Automated safety check: Pass | None |
Yuan1z0825/nature-skills
Drafts Chinese invention patent applications and technical disclosures from research papers or inventor materials, tying each claim feature to source evidence.
c15t/c15t
Work with c15t consent management docs, APIs, and integrations for Next.js, React, and JavaScript.
evolsb/claude-legal-skill
Review legal contracts, NDAs, employment agreements, SaaS terms, and M&A documents.
anthropics/claude-for-legal
Structures a legal clinic client intake interview and produces a case summary with cross-area issue spotting, conflict flags and triage classification.
snipp-zha/Paper-to-patent-Skill
Convert scientific papers, theses, technical reports, source code, figures, or research manuscripts into evidence-grounded Chinese invention patent drafts.
ynulihao/AgentSkillOS
Create employment contracts, offer letters, and HR policy documents following legal best practices.
malloydata/publisher
Score one analytical answer against a verified golden, and score which of the entities the golden depends on retrieval delivered to the answerer.
malloydata/publisher
Fix a CRITICAL Trivy finding that is failing CI in this repo (a vulnerability, misconfiguration, or secret from security-scan.yml or image-scan.yml), or add, review, or retire an entry in…
malloydata/publisher
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.
malloydata/publisher
Conduct a local Publisher evaluation loop in five steps: scrape/run, eval, diagnose, improve, checkpoint.
malloydata/publisher
Make the smallest safe Malloy model edit that closes a diagnosed model-owned gap, with a probe receipt for every factual claim.
malloydata/publisher
Decide whether ONE answer matches its golden, and say whether you believe the golden.
Categories
Build Malloy semantic models with base source and joined source files. Malloy Model is an agent skill from malloydata/publisher. Build Malloy semantic models with base source and joined source files.
Malloy Model fits situations like: modifying .malloy files; user asks to create a malloy model; create a source; any Malloy model authoring task.
Run `npx skills add malloydata/publisher --skill malloy-model -a claude-code`. Or copy the skill folder (skills/malloy-model in malloydata/publisher) into .claude/skills/malloy-model in your project. Claude Code loads it when a task matches its description.
Run `npx skills add malloydata/publisher --skill malloy-model -a codex`. Or copy the skill folder (skills/malloy-model in malloydata/publisher) into .agents/skills/malloy-model in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add malloydata/publisher --skill malloy-model -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/malloy-model, .gemini/skills/malloy-model, .github/skills/malloy-model and .opencode/skills/malloy-model in your project.
SKILL.md names no scripts, command-line tools or credentials: Malloy Model is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Malloy Model is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.9k tokens (SKILL.md is roughly 28k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Malloy Model: Paper to Chinese Patent Drafter (Yuan1z0825/nature-skills, 46k stars), C15t (c15t/c15t, 1.9k stars), Contract Review (evolsb/claude-legal-skill, 462 stars) and Legal Clinic Client Intake (anthropics/claude-for-legal, 9.6k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
malloydata (a GitHub organization) maintains it in malloydata/publisher, which has 116 GitHub stars. The repository holds 29 skills in this directory. The repository was last updated on October 8, 2026.
Source: malloydata/publisher on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.