> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reasonos.com/llms.txt
> Use this file to discover all available pages before exploring further.

# External Rule Packages

> Load rules someone else wrote — the ext.rbs manifest, the rbs ext CLI, pinning, and authoring your own package

External rule packages are how a workspace uses rules that are not built
into rbs — without forking it. A package is a git repository (or archive)
laid out like rbs's own embedded ruleset. You declare it in `ext.rbs` under
a **namespace** you choose, and load its modules as
`@<namespace>//<path>.rbs`.

```python theme={null}
# ext.rbs — at the root of your workspace
ext(name = "java", git = "https://github.com/acme/java-rules", ref = "v1.2.0")
```

```python theme={null}
# BUILD.rbs
load("@java//jvm/rules.rbs", "java_binary")

java_binary(name = "app", srcs = glob(["src/**/*.java"]))
```

## The ext.rbs manifest

`ext.rbs` lives at the workspace root. It is evaluated like any other `.rbs`
file, but **`ext()` is the only available function — `load()` is deliberately
disallowed**. A dependency list that could compute itself could not be read
without running it, and resolution must be answerable before any rule code
executes. (This is also what makes nested packages cheap: reading a fetched
package's manifest is parsing, not evaluation.)

### ext()

```python theme={null}
ext(name, git = "", ref = "", http = "", sha256 = "", strip_prefix = "",
    rule = "", version = "")
```

| Parameter      | Type     | Description                                                                                                                                                                                                             |
| -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`         | `string` | The namespace, and the `@name//` load prefix. Lowercase letters, digits, `_` and `-`, starting with a letter. `rbs` and `external` are reserved. Required; unique within the file.                                      |
| `git`          | `string` | Git URL of the package repository.                                                                                                                                                                                      |
| `ref`          | `string` | Tag, branch, or commit. **Required with `git`** — a package silently following someone's default branch would change your build with no diff in your repository.                                                        |
| `http`         | `string` | URL of an archive instead of a git source.                                                                                                                                                                              |
| `sha256`       | `string` | Archive digest (optional but strongly recommended; without it the archive is only pinned by the lockfile after the first fetch).                                                                                        |
| `strip_prefix` | `string` | Leading directory to strip from the archive.                                                                                                                                                                            |
| `rule`         | `string` | An `"org/name"` coordinate resolved by your control plane.                                                                                                                                                              |
| `version`      | `string` | Version tag for a coordinate. **Required with `rule`.** A `v` prefix is tolerated on either side (`1.2.0` finds `v1.2.0`); `"latest"` selects the newest tag. There are no version ranges — the lockfile pins a commit. |

Each entry must name **exactly one** source: `git`, `http`, or `rule`.

```python theme={null}
ext(name = "java",   git = "https://github.com/acme/java-rules", ref = "v1.2.0")
ext(name = "corp",   http = "https://rules.corp.com/corp-1.0.0.tar.gz",
    sha256 = "…", strip_prefix = "corp-1.0.0")
ext(name = "shared", rule = "acme/shared-rules", version = "2.0.0")
```

<Note>
  The `rule = "org/name"` coordinate form is resolved through a control
  plane. On a ReasonOS node this is zero-config — nodes launch with
  `RBS_CONTROL_PLANE_URL` set — and the control plane answers with a clone
  URL and the commit its version tag points at. Off a node, set the same
  variable or use `git =`.
</Note>

## Loading package modules

Once declared, a package's `.rbs` files load by namespace:

```python theme={null}
load("@java//jvm/rules.rbs", "java_binary", "java_library")
```

The path after `@<namespace>//` is a file path inside the package
repository; the `.rbs` extension is appended if missing. Resolution happens
**before any build-language code runs**: the whole external graph is settled first,
then evaluation reads it — a failed fetch fails the build up front rather
than surfacing later as "unknown rule".

## The rbs ext CLI

The manifest is the source of truth; `rbs ext` edits it and resolves, the
way `go get` edits `go.mod`.

| Command                                | Does                                                                       |
| -------------------------------------- | -------------------------------------------------------------------------- |
| `rbs ext add <git-url> --ref <tag>`    | Add an entry to `ext.rbs`, fetch it, record the pin.                       |
| `rbs ext add <org/name> --version <v>` | The same, by coordinate.                                                   |
| `rbs ext list`                         | Show declared packages, their pins, and what they pull in.                 |
| `rbs ext update [namespace]`           | Re-read a moving ref and re-pin (all packages when no namespace is given). |
| `rbs ext remove <namespace>`           | Drop an entry from `ext.rbs`.                                              |

`rbs ext add` flags:

| Flag        | Description                                                                                         |
| ----------- | --------------------------------------------------------------------------------------------------- |
| `--ref`     | Tag, branch or commit (git sources).                                                                |
| `--version` | Version tag (`org/name` coordinates).                                                               |
| `--as`      | Namespace to load it under. Omitted, it is derived from the name (`acme/rbs-java-rules` → `@java`). |

```bash theme={null}
rbs ext add https://github.com/acme/java-rules --ref v1.2.0
rbs ext add https://github.com/acme/java-rules --ref v1.2.0 --as jvm
rbs ext add acme/shared-rules --version 2.0.0
```

## Pinning and updating

A ref resolves to a **commit**, and the commit is recorded in `rbs.lock`
(under the `ext` ecosystem). After that, the pin answers:

* Builds are reproducible — a moved tag cannot change your rules silently.
* A warm workspace resolves offline; no remote lookups per build.
* `rbs ext update` is the **only** operation that re-reads a moving ref.

Fetched packages are cached in `.rbs/ext/git/<commit>`, keyed by commit
rather than namespace — two packages depending on the same rules at the
same commit share one copy and one fetch.

The typical update flow:

```bash theme={null}
rbs ext list                 # see current pins
rbs ext update java          # re-read @java's ref (e.g. a moved tag), re-pin
rbs build //...              # build against the new pin
# commit ext.rbs + rbs.lock together
```

To move to a new version explicitly, edit `ref =` in `ext.rbs` (or re-run
`rbs ext add` with the new `--ref`), then build.

## Namespaces are private

A rule package carries its **own** `ext.rbs` for its own dependencies, and
those are resolved too. Every namespace lookup is answered from the table
of the repository *doing* the load:

```python theme={null}
# your ext.rbs
ext(name = "java", git = "https://github.com/acme/java-rules", ref = "v1.2.0")

# acme/java-rules' own ext.rbs
ext(name = "base", git = "https://github.com/acme/jvm-base", ref = "v3.0.0")
```

Inside `java-rules`, `@base` means `jvm-base`. In your workspace, `@base`
means whatever *you* declared — or nothing at all. Neither can shadow the
other, so a published package's internal naming never reaches its
consumers. Two packages may even use the same namespace for different
things.

Dependency cycles between packages resolve fine: identity is content, so a
package reached twice is the same package and the walk stops.

## Authoring a package

A package repository mirrors the embedded ruleset layout — directories of
`.rbs` modules that define rules with the
[rule SDK](/reference/custom-rules) and export them:

```
java-rules/
├── ext.rbs              # the package's own dependencies (optional)
├── jvm/
│   ├── rules.rbs        # java_binary = native.define_rule(...); exported
│   └── toolchain.rbs
└── scaffolds/           # optional project scaffolds
```

```python theme={null}
# jvm/rules.rbs
def _java_binary_impl(ctx):
    dirs = ctx.bin.create_dirs()
    ...

java_binary = native.define_rule(
    name = "java_binary",
    kind = "binary",
    implementation = _java_binary_impl,
    attrs = {
        "srcs": attr.label_list(doc = "Java sources"),
        "deps": attr.label_list(is_dep = True),
    },
)
```

Tag a release, and consumers pick it up:

```bash theme={null}
# in the package repo
git tag v1.0.0 && git push --tags

# in a consuming workspace
rbs ext add https://github.com/acme/java-rules --ref v1.0.0
```

```python theme={null}
# consuming BUILD.rbs
load("@java//jvm/rules.rbs", "java_binary")

java_binary(name = "app", srcs = glob(["src/**/*.java"]))
```

<Warning>
  Remember that `load()` bindings are file-local: a package module that wants
  to re-export symbols it loaded from a sibling module must rebind them
  (`load("impl.rbs", _java_binary = "java_binary")` then
  `java_binary = _java_binary`).
</Warning>

### Scaffolds

A package can ship project scaffolds alongside its rules, addressed the
same way and served from the same pinned commit:

```bash theme={null}
rbs scaffold @java//scaffolds:service //services/payments
```
