Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8667f4d
docs: 📝 Add request file design doc
joelostblom Jul 7, 2026
20e3007
docs: 📝 move and elaborate on the complete example
joelostblom Jul 7, 2026
458981d
docs: 📝 add alternative filter syntax
joelostblom Jul 7, 2026
10df2b0
docs: 📝 add non-goals and move note
joelostblom Jul 7, 2026
c66421a
docs: 📝 move check section to cli check command
joelostblom Jul 7, 2026
aa3bbe0
docs: 📝 fix typos and checking
joelostblom Jul 7, 2026
cda4e48
docs: 📝 fix yaml code block syntax
joelostblom Jul 7, 2026
9adf245
docs: 📝 rename file
joelostblom Jul 7, 2026
013e59a
Merge branch 'main' into docs/instruction-design
joelostblom Jul 7, 2026
2e93449
Apply suggestions from code review
joelostblom Jul 9, 2026
cdc524a
Apply suggestions from code review
joelostblom Jul 9, 2026
9347453
Apply suggestions from code review
joelostblom Jul 9, 2026
b3da1c0
Apply suggestions from code review
joelostblom Jul 10, 2026
aae4b26
Apply suggestions from code review
joelostblom Jul 10, 2026
da8fa3f
docs: 📝 clarify language
joelostblom Jul 9, 2026
e613d93
docs: 📝 improve owner wording
joelostblom Jul 10, 2026
9fe1cc3
docs: 📝 improve proj desc
joelostblom Jul 10, 2026
f0bdb36
Apply suggestions from code review
joelostblom Jul 10, 2026
9d43ec6
docs: 📝 improve wording
joelostblom Jul 13, 2026
eda9c81
Apply suggestions from code review
joelostblom Jul 13, 2026
ac34861
docs: 📝 include more atbout NOT
joelostblom Jul 13, 2026
2ecec38
Apply suggestions from code review
joelostblom Jul 14, 2026
f6941a6
docs: 📝 Add link to user types
joelostblom Jul 14, 2026
d007625
Apply suggestions from code review
joelostblom Jul 15, 2026
ff41b61
docs: 📝 Improve wording
joelostblom Jul 15, 2026
c4df516
docs: 📝 remove comment
joelostblom Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/typos.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[files]
extend-exclude = ["*.html", "*.css", ".quarto/*", "_site/*", "*.svg"]
extend-exclude = ["*.html", "*.css", ".quarto/*", "_site/*", "*.svg", "*.js"]

[default.extend-words]
ratatui = "ratatui"
1 change: 1 addition & 0 deletions _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ website:
contents:
- docs/design/interface/inputs.qmd
- docs/design/interface/outputs.qmd
- docs/design/interface/request.qmd
- docs/design/interface/cli.qmd
- docs/design/interface/web.qmd
- docs/design/interface/rust.qmd
Expand Down
28 changes: 23 additions & 5 deletions docs/design/interface/cli.qmd
Comment thread
joelostblom marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,29 @@ flowchart LR
## {{< var planned >}} `seedcase-propagate check`

The `check` command confirms that the `request.yaml` file is correctly
structured and that it matches the content of the source metadata file. While
internally `build`, `subset`, and `create-request` run this check when
processing or creating the `request.yaml` file, we also expose it so the user
can manually check the request file (e.g. if included within an automated
pipeline). It has the following signature with two positional arguments:
structured and that it matches the content of the source metadata file. More
precisely, the following checks are made:

- Required sections and keys are present.
- The referenced data package has the same id and name as the source metadata
file.
- The referenced resources exist in the data package.
- The requested columns exist in the selected resources.
- Row filters reference existing columns.
- Row filters use supported SQL-like syntax.
- Only allowed SQL operations and shorthands are used.

While most of these check are likely easier to test directly in the YAML file,
the subset queries filters can also be converted to SQL and checked with

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
the subset queries filters can also be converted to SQL and checked with
the subset queries can also be converted to SQL and checked with

existing SQL parsers or checkers where possible. This avoids implementing a
custom expression parser for the request format.

Internally `build`, `subset`, and `create-request` run these checks when
processing or creating the `request.yaml` file. We also expose it as a command
so the user can check the request file independently (e.g. if included within an
automated pipeline).

`check` has the following signature with two positional arguments:

```bash {filename="Terminal"}
seedcase-propagate check [REQUEST] [SOURCE]
Expand Down
312 changes: 312 additions & 0 deletions docs/design/interface/requests.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
---
title: "Request file"
---

The request file, `request.yaml`, contains the instructions for requesting a
subset of data from a data package. The file is created by a Requester through
the web application, terminal interface, or even manually (though not
recommended). The file is used by an Owner to check and generate the requested
data subset. You can
[read more about the Requester and Owner user types in the Design overview page](/docs/design/interface/inputs.qmd)

The request file is intended to be both human-readable and machine-readable. It
should be simple enough for a Requester to understand and, when needed, edit
manually. At the same time, it should be structured enough that Propagate can
check it against the data package metadata and convert it into an SQL query for
generating the requested data subset.

## Format

The request file uses YAML. YAML was chosen because it is more readable for
manual editing than JSON and less verbose than TOML for lists of columns and row
filters.

## Structure

The request file contains the following top-level sections:

- `request`: Information about this specific request.
Comment thread
joelostblom marked this conversation as resolved.
- `requester`: Information about the person requesting the data.
- `project`: Information about the research project that requires the data.
- `data-package`: Information about the data package the request applies to.
Comment thread
joelostblom marked this conversation as resolved.
- `subsets`: The requested data subsets, with one item per resource.

Comment thread
joelostblom marked this conversation as resolved.
Each of these is elaborated on below:

### Request

The `request` includes information about the request itself, such as when and
why it was created. This helps an Owner organize and track requests, which is
especially beneficial for data packages that receive large volumes of requests.

```{.yaml}
request:
date-modified: DATE
date-created: DATE
Comment on lines +44 to +45

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
date-modified: DATE
date-created: DATE
datetime-modified: DATETIME
datetime-created: DATETIME

motivation: TEXT
Comment thread
lwjohnst86 marked this conversation as resolved.
iteration: NUMBER
```

- `date-modified`: When the request was last modified. This can act as an ID

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `date-modified`: When the request was last modified. This can act as an ID
- `datetime-modified`: When the request was last modified. This can act as an ID

which facilitates referring to a specific iteration of a request, e.g. in
dialog between the Owner and Requester. Precision to the second should be
sufficient.
- Note that this field is automatically populated when the request is saved
via a user interface (be it the web app or the TUI). If the request is
edited manually, it must be updated manually.
- The format should be ISO 8601, e.g. `2026-07-06T01:45:34-12:00`
Comment thread
lwjohnst86 marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- The format should be ISO 8601, e.g. `2026-07-06T01:45:34-12:00`
- The format should be ISO 8601, e.g. `2026-07-06T01:45:34-12:00` of the current time (though internally it will be stored as UTC).

<!-- TODO this is this the most suitable date format? It seems like it makes sense to include timezone info so it can be easily interpreted by both sides if they are in different timezones -->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<!-- TODO this is this the most suitable date format? It seems like it makes sense to include timezone info so it can be easily interpreted by both sides if they are in different timezones -->

Rarely is timezone information helpful, it often makes things more confusing (in terms of processing it). I think date at UTC is enough for what Propagate aims to do.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what's the motivation for why timezone info rarely would be useful? A use case could be someone opening up the file and seeing a different time than when they created it because they are not in the UTC. I think that including timezone info makes it explicit what is being recorded and it's more inclusive to people working with propagate in different timezones. It could also help when owners and requesters are from different time zones. What's a use case when not having timezone info would be helpful so that we can think about what's more relevant to propagate?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working with datetimes is difficult enough, but adding in timezone is brutal. See this best practice answer here: https://stackoverflow.com/questions/44965545/best-practices-with-saving-datetime-timezone-info-in-database-when-data-is-dep

Basically, store the current time but set it as UTC and if necessary use timezone for display purposes only. For our cases, we aren't really displaying anything so it isn't really an issue. And the datetime fields are only for tracking and identification purposes, not for any interesting/complicated analyses.

Also, for propagates case, it is very very unlikely that requesters are outside of the owners timezone. Most of the time it is people within the same group or institution. VERY rarely is it someone outside of that.

Trust me here, we want to avoid this absolute headache that is timezones and datetimes. Especially as timezone information is not relevant for Propagate, at all.

- `date-created`: When the first iteration of the request was created.

@lwjohnst86 lwjohnst86 Jul 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `date-created`: When the first iteration of the request was created.
- `datetime-created`: When the first iteration of the request was created. It is also set at UTC and in ISO 8601 format.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting on resolving the discussing above before making changes here

<!-- TODO The creation date would only be correctly populated if the requester uploads their old request in the interface when they need to make a modification. If they rewrite the request from scratch, they could (mistakenly) put an incorrect date here, but that's fine I think. -->
Comment thread
signekb marked this conversation as resolved.
- `motivation`: Explanation for why the requester's project needs access to this
data subset. This is used by the Owner to check whether the requested data is
relevant to the project. The motivation is particularly needed for human
health data since there are legal requirements (e.g. GDPR) to declare for what
purposes the data is used.
- `iteration`: The iteration of requests from this project for this data
package.
<!-- TODO This is a "maybe" since it seems a bit less reliable. It could be automatically incremented if a file is loaded into the web-interface, but there is not way to know if that file was a saved version that was never sent (ie the iteration should not be updated) or if it was sent and is now being modified for sending again (ie the iteration should be updated). It could be manually filled in of course, but we should think about who this is mainly useful for; if it is the owner then they could run something on their end to find out how many requests they have; if it is someone viewing the data package then it's more useful to try to have it in the file itself. -->

### Requester

The `requester` section describes who is requesting the data. This person
doesn't strictly have to be the one working with the data, but they should serve
as the main point of contact for the Owner.


```{.yaml}
requester:
name: TEXT
email: TEXT
```

- `name`: The name of the requester.
- `email`: The email of the requester.
Comment thread
joelostblom marked this conversation as resolved.

### Project

The `project` section contains information about the research project that
requires the data. A project is defined as a body of work that result in a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
requires the data. A project is defined as a body of work that result in a
requires the data. A project is defined as a body of work that results in a

research output, e.g. a paper, report, website, dashboard, or analysis. This
could be a part of a PhD student's thesis or a collaboration research project
for a group of researchers. This field is necessary to ensure that the data
requested is aligned with what it is intended to be used for, which can a legal

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

requirement (e.g. in GDPR).

- `name`: A short machine-readable project name, using lowercase letters and

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think more about this, I wonder if name is redundant with title? We're asking the requester to turn the title into something more machine readable whereas we could just do that behind the scenes, e.g. replacing spaces with underscores. It should at least not be part of the interface I think and maybe not part of request.yaml either, just handled in garden whenever the title needs to be turned into a more slug-like string.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this depends on whether we want the name to be something shorter than the title and not just a kebab case version of it - which I think we do. E.g., in our example data package flora the name of the data package is only one word, "flora" instead of "observations-of-flora-species-and-seasonal-development".

  "name": "flora",
  "title": "Observations of flora species and seasonal development",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you agree, it might be helpful if the examples in this list were updated to reflect this difference 🌻

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good consideration! I think it depends on what this name field is meant to be used for. We previously discussed that this should be machine readable and used for identification purposes. That's quite different from the description of the name key for the data package below and to me this description suggests that project.name is not (primarily) meant for human consumption and therefore does not need to be short (and maybe even benefits from being longer since it makes it more unique).

However, if it is meant for human consumption, then I agree with you that it would benefit from being shorter than the title. I think either works and I also think we don't need name: Machines can create an id from the title and humans will create an abbreviation of the title themselves as well to use as a short name. So I'm happy to implement according to anyone feeling more strongly about this than I do.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name would be something like onlimit and dp-next. Think of name as a repo name. It's for both human and machines. With the combination of a data package, requester, and project name, that's enough unique information to keep requests as unique ids, but also being legible enough to display in different areas/different ways.

hyphens where possible, e.g. `my-research-project`. This is mainly used for
identification purposes when managing several requests.
- `title`: A longer human-readable project title, e.g. `My research project`.
This could be used by the Owner to publicly display the data package requests.
- `description`: A summary of the project, including details like
background, rationale, basic methods, and potential research questions. This
information, combined with the `motivation`, is mainly to help the Owner
ensure that the data requested matches what it will be used for.

### Data package

The `data-package` section identifies the data package the request applies to.
Comment thread
joelostblom marked this conversation as resolved.
This information will be automatically populated from the data package metadata
when creating a request via the web application or the TUI.

```{.yaml}
data-package:
id: TEXT
name: TEXT
version: X.Y.Z
```

- `id`: A unique identifier of the data package.
- `name`: A human readable name to refer to. It is lowercased and doesn't
include any spaces. E.g., `my-data-package`.
- `version`: Helps the Owner detect which version of the data package the
request is for.

### Subsets

The `subsets` section describes which data subsets the Requester would like to create.

```{.yaml}
subsets:
- resource: RESOURCE-NAME
columns:
- COLUMN-NAME
- COLUMN-NAME
- COLUMN-NAME
rows:
- "ROW-CONDITION" # As SQL
# Alternatively, this more structured format, see comparison below
- column: COLUMN-NAME
operator: "OPERATOR" # E.g. ">"
value: VALUE-TO-COMPARE-AGAINST
- resource: RESOURCE-NAME
columns:
- COLUMN-NAME
```

<!-- TODO When editing the file directly, do we require the user to enter row conditions (and maybe all other values too) as strings? It seems like we have to in order not to have yaml parsing potentially break if they do something illegal. -->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think that's a good plan 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which one, just the row conditions or all values? And should we always do it; ie even when the file is populated from the web interface (it seems like that would be helpful to always encourage it, e.g. if someone goes in to make an edit manually to a file created from the UI)?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think we should always parse as string, to keep it simpler. When we convert into SQL/running the subset, we can do the corrections there to the right types.


- `resource`: Specifies the data resource to create the subset from. Multiple
subsets can be specified in the same request by repeating the `resource` key.
This can be done either with the same resource multiple times or with

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This can be done either with the same resource multiple times or with
This can be done with

different resources for each.
Comment on lines +152 to +153

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my understanding: In what scenario does it make sense to include the same resource multiple time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is still up for discussion whether we prefer to support that or not. I mentioned the following in #65 (comment), but that comment is not easy to find now:

I also do think we should be explicit about the possibility to either repeat the same resource or referring to different ones within the same request. Unless you are saying that you don't want a requester to be able to repeat a resource and all data from a single resource needs to be extracted to a single subset in a single query. I'm ok with that too, but if that's the intention I think it would be beneficial to write that explicitly.

As a direct answer to your question; I would imagine a scenario where someone is interested in looking into column A for a subset of the population and then column B for another subset. If the data was very large or a column very heavy (e.g. images or geo-coordinates), it could be impractical if we forced the user to pull column A and B for the union of two subset (e.g. they needed to pull down images for 10 rows as an example and we force them to pull 1 million images because that's how big the subset for the other column is).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not completely sure where this belongs, maybe in this file, but probably should be clarified in the requirements.

For GDPR purposes (and other legal/privacy regulations), as well as for statisticaly reasons, any subsetting by rows in any resource will lead to the observational units (participants if human) being the same across all resources. For example, if in survey a condition is Gender == "Man", and in another resource biometrics that doesn't have a condition, the participant IDs from the subsetted survey will be applied to the biometrics. So that all resources contain the same set of participants. This is a legal thing, as much as a statistical thing. It's to ensure that only the necessary data is given to a researcher that needs it. The principle of "least permissions" but in data context 😛

So, in your example, that won't be allowed to request from the same resource more than once. But actually, your example highlights that they won't be given everything when they only need a specific amount (e.g. the ask for 10 rows and only get images for those 10 rows).

- `columns`: The columns to include. Each list item must correspond to an exact

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we do not include a shorthand for saying "include all columns from this resource"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lwjohnst86 mentioned that he prefers if all columns that are part of the data subset are mentioned explicitly in the file (which I think is part of why we also don't have exclusions, ranges, etc). I believe the reasoning is that a person should be able to see which variables are used in the project when opening request.yaml manually without knowing about the content of the referenced data package (but I will @lwjohnst86 fill in if I misremember).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it could be that if columns is missing (like rows), it interprets that as all columns? Like the all rows for no rows?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that was one of the suggestions in my previous comment in the file, so that there would be symmetry between the meaning of an empty value for rows and columns. It would also make it very clear when e.g. all columns except one was selected.

I thought you wanted all columns listed explicitly for the reasons I mentioned above, but if that is no longer the case, then we just need to decide whether it is clear enough that a request that looks like this, requests all the columns and rows from resource1:

subsets:
  - resource: resource1
  - resource: resource2
    columns:
      - col1
    rows:
      - col1 > 50

I think it probably is clear enough, and we can write that "...if you don't want to request all the data in a resource, you can select specific columns and rows to include in the data subset..."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the idea of there being symmetry between empty values from rows and columns both meaning selecting all. To me, your example above is clear 👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think with @martonvago comment about mirroring the web app/tui interface and how it would actually be implememted, it makes sense that no columns selected means all columns requested from the resource (which maybe? mimics how we would do it in the app..?). So yea, let's do that, as @signekb says, it also has some symmetry.

column name. SQL-like shorthands are not supported in order to keep the
request file simple and to avoid parsing unsafe queries of the data (e.g. via
SQL injection). This can not be left empty / unspecified; at least one column
must be included in the `columns` field.
- `rows`: Describes which rows to include in the subset. Can reference columns
that are not a part of the subset. If the `rows` key is empty or not included,
all rows will be selected.

There are currently two candidates for what this might look like, see below.

#### Row condition alternatives

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good discussion about this tricky bit!

My two cents:
I think that request.yaml is supposed to be a text-based representation of the request form in the UI, so it should follow the UI's structure as closely as possible. This is all the more true because request.yaml can still be edited by the user, so it's still an interface of sorts (unlike the payload sent from frontend to backend in a classic web app). The UI is structured like a syntax tree, which lends itself to quite a nice textual representation in YAML. Using an example from the web interface docs:

filters:
  growth_records:
    and:
      - is_null:
          column: location_country
          not: true
      - or:
          - equal:
              column: growth_stage
              value: flowering
          - greater_than:
              column: observation_date
              value: 2020-01-01

Or the more verbose:

filters:
  growth_records:
    operator: and
    children:
      - column: location_country
        operator: is null
        not: true

      - operator: or
        children:
          - column: growth_stage
            operator: "="
            value: flowering

          - column: observation_date
            operator: ">"
            value: 2020-01-01

This structure is a modified version of your alternative 2 that also includes grouping column conditions. The naming and exact structure can be refined of course.

I like this because keeping the structure aligned with the interface allows complexity (creating the SQL query) to be pushed further down into the core bits of Propagate. This strikes me as both safer and simpler.

Safer because, this way, the user cannot introduce arbitrary SQL-like expressions through the request file. Of course, we would defend against this in alternative 1 by parsing and checking the condition, but that is still riskier than sidestepping the issue altogether.

Simpler because we go
filter tree in UI > filter tree in YAML > filter tree in Rust > SQL
and not
filter tree in UI > SQL-like text in YAML > parsed SQL-like structure in Rust > SQL.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, great ideas/thoughts here! My brain comprehends the second, more verbose option here, but I can see how either could work 🤔

@signekb signekb Jul 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I also like this. I also prefer the second one because it is more explicit.

We could use words like group and condition, as I've done in the web.qmd - or at least align between the two (web and request YAML)

@joelostblom joelostblom Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also in favor of keeping the yaml file structure as close as possible to how information is represented in the graphical UI! Agree with you that it becomes both safer and simpler for us, including simpler to check that only the allowed subset of SQL that we want to support is used.

I think the two suggestion above are quite similar to some of the expanded syntax suggestions for alternative two from this issue comment #3 (comment), which I think would become the following for the example above:

rows/filters:
  - resource: growth_records
    all:
      - column: location_country
        operator: is null
        not: true
      - any:
        - column: growth_stage
          operator: "="
          value: flowering
        - column: observation_date
          operator: ">"
          value: 2020-01-01

I'm happy with all three options we have here in this discussion, and we essentially need to decide what level of verbosity we want. I think the suggestion I have from previously is somewhere between the two above and I have a preference for one of the two more verbose options we have in this discussion. I can comment on a few things in terms of why I picked them, but I'm open to landing on any of the above suggestions (or a combination of them):

  1. I prefer to specify the resource key (versus just the resource name as the key) to make it more explicit what that value represents.
  2. My preference for which logic operators/words to use when combining conditions/filters is all/any when it's outside/wrapping the conditions and and/or when it is separating the conditions as an operator, ie I like any(X, Y) and I like X or Y. So, in our case I would prefer all/any but I'm good either way.
  3. I prefer not to specify the operator key twice at two different levels of nesting since it might be confusing that the same key name would take different operator values depending on the nesting level, ie only and/or at the resource level, but then all condition operators when it is on the inner level. I'm ok including it if we call this key something else at the outer level (e.g. match: any, logic: and, or similar). I'm also ok omitting it as in my example above since there are only two possible values.
  4. If we wanted to improve the direct experience of editing the file directly, we could consider all as the default and not require it to be written out. That would also lead to less nesting, but it is of course more explicit to write it out. Maybe we can think about how this will be represented in the UI as well to see what would be the closer mapping to that.
  5. If we keep the most verbose option as in @martonvago's second options, then I prefer if we replace children with something like conditions.
  6. As for the outermost key name, I also suggested filter, where, and even row_filters previously in the brainstorm issue, but @lwjohnst86 mentioned that he wanted it to be rows for symmetry with columns even if these are row filters rather than row names. I do find it more natural to refer to them as "filters" in my mind, but I'm good either way here; I think they have different advantages.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed the second half of the comments on #3, sorry! I personally don't have a strong preference between what you suggested there and the examples I put here. So on all points above I'm very happy to go with your preference. I'm also leaning towards filters as a name for this, but I don't mind rows if that's what we want.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with your suggestions @joelostblom. These types of comments should be put into the doc (they are design decisions). Can you modify and add it to it?

As for 6. technically they are row "keeps", as the condition keeps rows, it doesn't filter them (filter means remove, not keep). Just a small semantic comment 😛

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small - maybe misplaced - comment: "filtering" is both about keeping something and removing something else, so it's ambiguous in that way.


##### Alternative 1

- A list of SQL filter conditions that describe which rows to include in the
subset.
- Multiple row filters for the same resource are combined with `AND` logic. For
Comment thread
joelostblom marked this conversation as resolved.
example, writing

```{.yaml}
rows:
- "age > 50"
- "sex = 'Man'"
```

is equivalent to writing

```{.yaml}
rows:
- "age > 50 AND sex = 'Man'"
```

::: callout-note
Since YAML has special syntax for some characters, SQL-like row filters should
be quoted when written manually. This avoids ambiguity around characters such as
`>`, `:`, `#`, `*`, `&`, and `!`.

```{.yaml}
rows:
- "col1 > 50"
```

Instead of:

```{.yaml}
rows:
- col1 > 50
```

The unquoted form will often parse correctly, but the quoted form is safer and
should be used in documentation and generated files.
:::

- The initial supported row filter syntax includes the following operators:
- Equality: `=`
- E.g. "age = 18"
- Comparisons: `>`, `>=`, `<`, `<=`
- E.g. `"age >= 18"`
- Membership: `IN`
- E.g. `"country IN ('Denmark', 'Sweden')"`
- Ranges: `BETWEEN`
- E.g. `"visit_date BETWEEN '2020-01-01' AND '2020-12-31'"`
- Missing values: `IS NULL`, `IS NOT NULL`
- E.g. `"end_date IS NULL"`
- Negation: `NOT`
- E.g. `NOT =`, `NOT IN`, `NOT BETWEEN`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One slightly annoying thing is the NOT is a prefix to negating all operators except IS NULL where it is advised to use IS NOT NULL rather than NOT IS NULL because of how null comparisons work in sql.

Also it does seem like the most common way to compare inequality for numericals are <>, second most common is !=, so ours is a bit less standard, but we also don't have to do the most standard sql thing since editing the file is not a priority use case

- This alternative is more closely mapped to how the SQL syntax will look, and
the options from the interface would need to be combined into the SQL row
filter syntax.
- An advantage of this syntax is that it is more flexible. This might mainly
matter when editing manually as we allow more expressiveness here.
- A drawback could be that requesters write things we don't want to allow and
that we would need to filter out at the SQL level or by parsing the string
which is likely more error prone than a more structured format.
Comment thread
joelostblom marked this conversation as resolved.

#### Alternative 2

- A more structured key-value mapping for the different components of the filter
condition.

```{.yaml}
rows:
- column: col1
operator: ">"
value: 50
Comment thread
joelostblom marked this conversation as resolved.
- column: col2
operator: "in"
value:
- dog
- cat
- column: col3
operator: between
value:
- 2012-01-01
- 2012-12-31
Comment on lines +248 to +249

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is tricky, as it would require that we sort or something after reading the values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't trust the order, we could name the start and end points

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, in #3 (comment) I suggested to use min and max. There I also made a difference between comparing against a single value or a list of values. But then I thought it might be cleaner with the same key name everywhere. I'm open to either, but I don't think sorting is needed when mapping from the UI since there would be an order there.

One more thought is that we can get rid of between altogether and replace it with two conditions, eg. instead of the example above, we could require the following:

  - column: col3
    operator: "<="
    value: 2012-01-01
  - column: col3
    operator: ">="
    value: 2012-12-31

I don't think that would be an inconvenience when specified in a UI. It also makes it more explicit whether the end points are inclusive or not, which I don't think is clear to someone using BETWEEN unless they have previous SQL knowledge.

```

- This alternative is more closely mapped to the interface, e.g. the dropdown
for the operator corresponds to the "operator" key here and the condition
groups in the interface are also more similar to how they are grouped here
with "any".
Comment thread
signekb marked this conversation as resolved.
- An advantage of this syntax is that it is more structured, so it would be
easier to check that e.g. only allowed operators are used if someone manually
edits the file.
- If we want to allow more flexibility when the file is edited manually, it
could be harder with this more strict spec, but it doesn't sound like that is
Comment thread
signekb marked this conversation as resolved.
our intended use case currently.

### A complete example

A complete request file might look like this.

```{.yaml}
request:
date-modified: 2026-07-06T01:45:34-12:00
motivation: |
We would like access to metabolic and block variables to evaluate
our hypothesis regarding ...
date-created: 2026-07-04T01:44:34-12:00
iteration: 2

project:
name:
title: Metabolic costs
description: |
Our project investigates the gas exchange during metabolism
with the aim to determine ...

data-package:
id: 8dbef2a2-0354-4996-bc9e-71a6c79aec7d
name: example-seed-beetle
version: 0.5.1

subsets:
# TODO Showing both row syntaxes for now
- resource: metabolic-rate
columns:
- o2_consumed
- co2_produced
rows:
- "o2_consumed > 50"
- resource: metabolic-rate
columns:
- strain
- activity
rows:
- column: block
operator: "="
value: block1
```

### Non-goals

Propagate is not intended to be a general-purpose query engine. The initial
request format should not support joins, grouping, arbitrary functions, random
sampling, or row selection by position. We also don't support shorthands such as `*` since that would make it less
explicit which columns are used from just looking at the request file without
Comment on lines +308 to +311

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this paragraph should be at the top as a callout block.

also seeing the data package metadata.