# Shared repository configuration

`.scribe/config.toml` holds versioned project settings. It lives in Scribe's
`.scribe/` namespace at the repository root, where everything except
`.scribe/_state/` is ordinary versioned content; `.scribe/_state/` remains local
workspace state. It is the only shared configuration path Scribe reads, spelled
exactly that way (see [one spelling](/docs/repository-config.md#one-spelling)). `.scribeignore` keeps its
separate, directory-scoped discovery behavior.

```toml
version = 1
kind = "art"

[locks]
rules = [
  '*.uasset',
  '*.psd',
  '!Art/Generated/**',
]

[profiles.programmer]
include = ['engine', 'game']

[profiles.artist]
include = ['game', 'art-source']

[profiles.everything]
include = ['.']

[presentation]
cover = 'Marketing/key-art.psd'

[tags]
"Characters" = ["character"]
"Environments/Forest" = ["forest", "environment"]
```

Add, edit, and submit this file through ordinary Scribe commands. To create it
by hand, run `mkdir -p .scribe` first, then `scribe add .scribe/config.toml`, or
start from a [preset](/docs/repository-config.md#starting-from-a-preset). Its exact bytes determine its content identity; different formatting remains different versioned
content even when it represents the same settings. There is no alternate file
reader, automatic conversion, include directive, or environment expansion.

## Format and validation

The file uses TOML, decoded by one shared parser and typed model in
`scribe-api::repository_config`. The server and clients use the same validation.

- `version = 1` is required. Unsupported versions and unknown fields or sections
  are rejected, including unknown fields within lock, profile, mount, and
  presentation tables.
- The entire file must be UTF-8 without NUL bytes, at most 64 KiB. Standard TOML
  comments, quoting, arrays, LF, and CRLF work normally. Duplicate keys or tables
  and values of the wrong type are errors.
- `kind`, `[locks]`, `[profiles]`, `[mounts]`, `[presentation]`, and `[tags]` are optional. A missing file
  means a `generic` repository with no mandatory locking, no named profiles, and
  no repository mounts. An existing file must be valid configuration;
  empty text is not a valid document.
- The server validates the complete proposed file on every branch before
  acceptance. Invalid configuration returns a recorded
  `invalid_repository_config` outcome; it never partially activates valid sections.

### Repository kind

The optional top-level `kind` says what the repository holds, so clients can
choose how to present it. Values are lowercase strings:

| Value | Meaning |
| --- | --- |
| `generic` | Default when `kind` is absent. Clients use their general presentation. |
| `art` | An art and asset bank: meshes, animations, textures, audio. |

Any other value, spelling (`'Art'`), or type is rejected. Only the accepted value
on `main` counts; a `kind` on another branch has no effect until it is accepted
on main. Changing the decoded value on `main` requires a Maintainer, like lock
rules and mount topology (see [section authority](/docs/repository-config.md#section-authority)); writing
the default `kind = "generic"` explicitly, or removing that line, is not a
change. The kind does not affect storage, locking, or sync. In the
[web interface](/docs/web-interface.md#profiles-and-preferences), the `auto` Interface
preference opens `art` repositories in the Art interface.

### Presentation

`[presentation]` shapes how clients show the repository, and nothing else: it
does not affect storage, locking, or sync.

| Key | Meaning |
| --- | --- |
| `cover` | A repository path whose picture stands for the repository on its card |

`cover` must be a valid repository path by the same rules as every other path
in this file; an empty or malformed value, or any other key in the table, is
rejected. Only the accepted value on `main` counts. Any file that gets a
thumbnail can be the cover (an image, a PSD, a model's render, a video frame).
While the file is not on `main`, or has no thumbnail yet, the web interface
keeps its own choice: the newest file with a thumbnail. Like profiles, a Writer
may change it.

### Tags

`[tags]` gives files tags for finding and grouping assets, and nothing else: it
does not affect storage, locking, or sync. Each key is a folder, and every file
in that folder or anywhere below it gets the listed tags. A key naming a file
rather than a folder gives nothing.

```toml
[tags]
"Characters" = ["character"]
"Environments/Forest" = ["forest", "environment"]
"Props/Vehicles" = ["vehicle", "prop"]
```

- A key is a repository folder path in its exact repository spelling, by the
  same rules as every other path in this file. An empty key, `.`, a leading or
  trailing `/`, and `..` are rejected, as are two keys that are the same folder
  to Scribe, such as `Characters` and `characters`. A key naming a file, or a
  folder spelled differently from the repository (such as `characters` for
  `Characters`), gives no file any tags.
- At most 256 folders, each with a list of 1 to 32 tags. An empty list, or a
  value that is not a list of strings, is rejected.
- Each tag has 1 to 64 characters, is not only white space, and contains no
  control characters or commas. Nothing else is checked. (The web interface
  also bounds the lowercase spelling at 64 characters, so a tag made of the
  rare letters whose lowercase is longer, such as `İ`, can pass here and still
  be refused there; the configuration then shows as unreadable.)

Scribe keeps each tag exactly as written. The web interface normalizes it
(lowercase, single spaces, so `" Hero  Ship "` and `hero ship` are one tag) and
shows these as rule tags in the asset catalog. Only the accepted value on
`main` counts. Rules label files for every collaborator, so changing them on
`main` requires a Maintainer, like `kind` and lock rules (see
[section authority](/docs/repository-config.md#section-authority)). The comparison is of the folders and
tags as written: changing `character` to `Character` is a change even though
the web shows both as one tag, while reformatting, reordering folder keys, or
adding comments is not. Reordering the tags within one folder's list is a
change.

### Lock rules

`locks.rules` is an ordered array of at most 1,024 strings. Missing `locks` or
`rules`, and `rules = []`, mean no mandatory lock rules. Each pattern must be
nonempty, contain no control characters, and fit Scribe's maximum path length.
Comments belong in TOML outside strings, not in rule entries.

Patterns use Git-style matching with one difference: they ignore case and
Unicode normalization, comparing rule and path under the same fold Scribe uses
for [path collisions](/docs/ignore-and-paths.md), so `*.psd` also locks `Hero.PSD`
(two paths that fold alike are one file to Scribe, and a lock must not miss
either spelling). Otherwise: filename
matching at any depth for patterns without `/`, root anchoring with a leading
`/`, and last matching rule wins. `!` makes matching paths optional; a trailing
`/` selects the subtree. Single-quoted TOML strings preserve glob backslashes
literally, for example `'\#special'`. Ignore traversal does not apply: a subtree
rule cannot hide a later exception. See [mandatory locks](/docs/client-usage.md#mandatory-lock-rules).

The fold is full Unicode case folding, applied to the pattern text and to the
path before matching, and it can turn one character into several: `ß` folds to
`ss`, `ﬁ` to `fi`. Literal text is unaffected, so `Straße/*` locks
`STRASSE/a.psd` too. The wildcards are not: `?` and a `[…]` class match one
character of the *folded* text. `Stra?e` does not match `Straße` (folded
`strasse`), and `[ß]` folds to `[ss]`, a class matching a single `s`. Write
such names literally, or use `*`.

`scribe lock rules` displays the active ordered rules. A path argument explains
the deciding rule and its actual line in `.scribe/config.toml`.

### Workspace profiles

Each `[profiles.NAME]` requires an `include` array with at least one nonempty
string. Names contain 1–64 ASCII letters, digits, `-`, or `_`, are case-sensitive,
and are limited to 128 profiles per file. Each profile allows at most 256
inclusion prefixes. Paths use exact canonical spelling and Scribe's portable
path rules; `.` explicitly selects the entire repository.

There are no profile glob patterns, exclusions, or inheritance. Mount declarations
use separate `[mounts.NAME]` tables; profile prefixes select their paths.
Duplicate prefixes and redundant child inclusions are collapsed. The final
resolved selection also has a 256-prefix limit, including local additions and
the config file, which profile selections always include.

```sh
scribe workspace create game ./work --profile programmer
cd work
scribe sync
scribe workspace profiles
scribe workspace view set --profile artist
scribe workspace view set --profile programmer --view art-source/Characters
```

Profiles are resolved from accepted history at the selected branch head, even
outside the current view. Unsubmitted edits do not redefine them. A workspace
saves the resolved selection and local overrides; ordinary sync does not silently
apply later profile edits. Reapply the profile explicitly to adopt changes and
reset overrides. See [workspace views](/docs/workspace-views.md) for safe transitions,
interruption, and cancellation.

## Section authority

| Section | Effective source | Permission on authenticated servers |
| --- | --- | --- |
| `kind` | Accepted `main` | Maintainer when the value changes on main |
| `locks` | Accepted `main`, governing every branch | Maintainer when the ordered rule values change on main |
| `profiles` | Accepted selected branch revision | Writer to submit changes |
| `presentation` | Accepted `main` | Writer to submit changes |
| `tags` | Accepted `main` | Maintainer when the folders or tags change on main |
| `mounts` | Accepted selected branch revision | Maintainer when the mount topology changes on main, Writer to advance a pin; targets enforce their own access |

A Writer may change profiles on `main`, or reformat and comment the file, while
retaining the same decoded kind and ordered lock-rule strings. Quote style, table
order, and source line numbers do not change the lock permission requirement. Changing
rule values or their order requires a Maintainer on `main`. Removing nonempty
rules by deleting the section, emptying the array, deleting the file, or moving
it away has the same requirement. A profiles-only file with no rules can be
created or removed by a Writer. Creating or removing a file with `kind = "art"`
on main likewise requires a Maintainer, as does creating or removing one with
`[tags]` rules.

Mount topology carries the same requirement, for the same reason: adding,
removing, renaming, or retargeting a `[mounts.NAME]` table decides where every
collaborator's nested workspace lives on disk. Advancing a mount's `revision`
stays a Writer action, so ordinary project publishing is unaffected.

The same checks cover ordinary submits and merges. A merge into `main` that
keeps main's `.scribe/config.toml` unchanged, taking none of the source
branch's config edits, needs no Maintainer, whatever the source branch did to
the file. Writers can submit lock-rule
proposals on other branches, but those rules do not become active until a
Maintainer accepts them on main. Local files, historical working copies, and
profile selections cannot override active lock policy or repository permissions.
The active old rules govern the submission changing the config; removing rules
never releases existing locks.

Authorization compares the proposed kind and ordered rules with the exact config revision
the changeset replaces. Acceptance rechecks that base under the publication gate,
so a concurrent config change causes a base mismatch instead of bypassing lock
permissions. Configuration reads, parsing, and rule matching happen outside the
metadata write transaction. No accepted history is rewritten.

## Repository mounts

Use `[mounts.NAME]` with `path`, `repository`, `branch`, and a decimal-string
`revision` to pin an independent repository. A configuration declares at most
64 mounts; a 65th is rejected even when the file stays within the 64 KiB limit.
Paths cannot overlap other mounts or parent files. A mount path cannot name a
`.scribe` directory (in any letter case) or lie inside the root `.scribe/`, so
`.scribe`, `.scribe/engine`, and `engine/.scribe` are all rejected. See
[versioned repository mounts](/docs/repository-mounts.md) for selection, child
permissions, sync recovery, SDK access, and web browsing.

## One spelling

The configuration path is exactly `.scribe/config.toml`. Scribe's path rules
already refuse two tracked paths that differ only by case or Unicode
normalization; in addition, the server refuses a submit on any branch that adds
or changes a case or normalization variant such as `.Scribe/config.toml` or
`.scribe/Config.toml`. Such a file would look like configuration and govern
nothing, so it is rejected with `invalid_repository_config` rather than silently
ignored.

## Starting from a preset

Repository creation stays an administrative action (`scribe repo create`); there
is no web page for creating repositories. To seed the configuration for an art
repository, create the first workspace with a preset:

```sh
scribe workspace create art-bank ./art-bank --preset art
cd art-bank
# Review .scribe/config.toml in your editor, then:
scribe submit -m "Configure art repository"
```

`--preset art` writes `.scribe/config.toml` into the new working copy and opens
it for add. Nothing is submitted; the configuration takes effect only when you
submit it, with the usual permissions (a Maintainer on `main`, because it sets
`kind` and lock rules). The command is refused, before any workspace is created,
when `main` already has an accepted `.scribe/config.toml`; edit that file
instead. Without `--profile`, the `--view` prefixes must include `.scribe/config.toml`. An existing
file at the path in the working copy is never replaced. The preset writes
exactly:

```toml
# Repository configuration; see Scribe's docs/repository-config.md.
version = 1

# Clients present an art repository as an asset bank.
kind = "art"

# Binary sources and interchange formats cannot be merged: whoever edits one
# holds its lock until they submit. Rules ignore case; the last match wins.
[locks]
rules = [
  # Painting and texturing
  "*.psd", "*.psb", "*.kra", "*.spp", "*.sbs",
  # 3D scenes and sculpts
  "*.blend", "*.ma", "*.mb", "*.max", "*.c4d", "*.hip", "*.hiplc", "*.ztl", "*.zpr",
  # Interchange
  "*.fbx", "*.abc", "*.usd", "*.usdc",
  # Engine assets
  "*.uasset", "*.umap",
]
```
