Skip to main content

Read-only namespaces

Some namespaces in a model are not authored by hand: they are generated from an external schema — for example, a namespace produced by importing an XSD or a JSONJSON (JavaScript Object Notation). Text-based, language-independent format with key-value pairs (eg Name: Dave). schema. The generated Rune is a faithful representation of that schema, so editing those files manually would make the model drift away from the schema it is meant to mirror, silently breaking the correspondence between them.

To prevent that, such namespaces can be marked as read-only. They are listed in the rune-config.yml, and a supplied GitHub workflow then fails any pull request that hand-edits a read-only namespace — until someone deliberately decides to allow it. Nothing is locked forever; the check is a safety net that turns an accidental manual edit into a conscious decision (and the right way to update a read-only namespace is to re-run the import that generated it).

Not every generated namespace should be locked down. A namespace inferred from data samples rather than declared by a schema is meant to be refined by hand, and is marked as a generated namespace instead — the same workflow then reports changes to it rather than failing them.

Declaring the read-only namespaces

Read-only namespaces are declared in the namespaceConfig list of the rune-config.yml. Each entry marks a namespace read-only with readOnly: true. The namespace is either an exact namespace or a namespace followed by .*, which covers that namespace and all of its subnamespaces:

model:
name: My Model
namespaceConfig:
- namespace: com.rosetta.model.*
readOnly: true
- namespace: cdm.base.datetime
readOnly: true

In the example above, com.rosetta.model and every namespace under it are read-only, as is the exact namespace cdm.base.datetime. Matching is segment-aware, so com.rosetta.model.* does not match com.rosetta.modelling.

:::caution Patterns are not globs

Only these two forms are recognised (a bare *, matching everything, is the degenerate case of the second). Anything else — com.rosetta*, a?c, a[bc] — is read as an exact namespace, which is to say it matches nothing, and the check then fails on it as a stale pattern.

:::

A namespaceConfig entry ties together the aspects that apply to a namespace: besides readOnly, it may also carry an external schemaConfig (populated when the namespace is generated by importing a schema) and an origin marker recording which tool produced it. The aspects are independent, so an entry can be read-only, schema-configured, marked as generated, or any combination — and importing a schema can declare all of them in the same entry.

Adding the workflow

Add a workflow to the repository (for example .github/workflows/readonly-namespaces.yml) that calls the reusable workflow on every pull request:

name: Read-only namespaces

on:
pull_request:

jobs:
readonly-namespaces:
uses: finos/rune-dsl/.github/workflows/verify-readonly-namespaces.yml@main
with:
config-path: rosetta-source/src/main/resources/rune-config.yml
root: rosetta-source/src/main/rosetta

The same workflow also runs the generated-namespace annotation as a further step of the same job, so a repository that uses generated namespaces needs no extra setup — and the check name a branch-protection rule pins to does not change.

config-path is the repository path of the rune-config.yml; the check fails if no file exists there, so a mistyped path cannot silently disable the protection. root is optional and defaults to the whole repository — pointing it at the folder holding the model's .rosetta sources keeps samples, test fixtures and build output from being mistaken for model namespaces.

On each pull request the workflow maps every changed .rosetta file to its namespace and fails if a change hand-edits a read-only namespace. The changes a schema import makes when it adds or removes a read-only namespace together with its configuration are still allowed. It also fails if a configured pattern matches no namespace at all, which catches stale or mistyped entries.

Making an intentional change

Sometimes a read-only namespace genuinely needs to change — for example, when a project admin rolls out an updated import. There is no need to remove the workflow or change the namespace's readOnly flag in namespaceConfig. Instead, the change can be accepted as a deliberate one-off by applying the override-readonly-namespaces label (or whatever is configured via the bypass-label input) to the pull request. The next run of the check sees the label, skips the namespace check, and records a note explaining why. Because adding a label requires write access, the decision stays visible and auditable in the pull request.

Once the change is merged, removing the label restores the check for future pull requests.

Parameters

The reusable workflow accepts the following inputs under with:.

InputRequiredDefaultDescription
config-pathYesPath to the rune-config.yml whose namespaceConfig entries marked readOnly: true define the read-only namespaces. The check fails if no file exists at this path.
base-refNoThe pull request base branch, otherwise the repository's default branchThe git ref that HEAD is compared against to determine which files changed.
rootNo.Directory scanned for the model's .rosetta files when verifying that every read-only pattern still matches a namespace. Changed files are checked repository-wide regardless.
checker-refNomainThe ref of finos/rune-dsl from which the checker script is fetched. Pin this to a tag or commit to fix the version of the check.
bypass-labelNooverride-readonly-namespacesThe pull request label that, when present, skips the check to allow an intentional one-off change to a read-only namespace.
generated-namespaces-docs-urlNoThe generated namespaces pageDocumentation link appended to every generated-namespace annotation. Applies to the annotation job only.