ADR 008: Universal File Types and URI Schemes
Status: Accepted Date: 2024-12-27
Context
Colin initially used .colin as a custom extension for model files. This created friction:
- No IDE support (syntax highlighting, preview, linting)
- Double-clicking files doesn't "just work"
- dbt proved that using native extensions (
.sql) with Jinja templating works well
Additionally, refs need to support multiple sources beyond local files: MCP resources, GitHub files, HTTP endpoints, etc.
Decision
Any File Type is a Model
Files in the models/ directory (or configured model-path) are models regardless of extension:
models/
report.md → output/report.md
config.json → output/config.json
schema.yaml → output/schema.yaml
query.sql → output/query.sql
All files are processed with Jinja templating. The extension determines output format. IDEs treat them as their native type.
Frontmatter for Configuration
Markdown and YAML files support frontmatter natively. For file types where frontmatter is awkward (JSON, SQL), configuration can come from:
- A central
schema.ymlfile (future, dbt-style) - Sidecar files (future)
- Defaults
URI Schemes for Refs
Refs use URI schemes to route to the appropriate input plugin:
ref('file://reports/quarterly') # Local model
ref('github://org/repo/docs/api.md') # GitHub file
ref('mcp://linear/issue/ABC-123') # MCP resource
ref('https://api.example.com/data') # HTTP endpoint
The scheme maps to an InputPlugin.scheme for resolution.
Default Scheme
For convenience, schemaless URIs default to file://:
ref('reports/quarterly') # Equivalent to ref('file://reports/quarterly')
Local model refs can omit the scheme since they're always local files.
Ref Scope and Error Semantics
Schemaless refs and scheme refs have different scope and error behavior:
| Reference | Scope | Validation | Error Type |
|---|---|---|---|
path/to/file |
Project-local | Must exist within project | Compilation error |
file://path/to/file |
Filesystem | External, explicit path | Runtime error |
Schemaless refs are guaranteed to resolve within your project boundary. They are validated at compile time and are portable across machines (the project contains everything needed).
Scheme refs (like file://) explicitly reach outside the project. The user takes responsibility for the external dependency. Missing files become runtime concerns because:
- Different machines may have different filesystem layouts
- External paths are environment-specific by design
- We can't validate external resources at compile time
This makes schemaless refs "safe by default" while scheme refs are "explicit and environment-aware."
Custom URIs via Frontmatter
A file can declare a custom URI to decouple reference name from file path:
---
colin:
uri: reports/quarterly
---
A file at models/drafts/q4-report.md could be referenced as ref('reports/quarterly').
Rationale
- IDE support: Native extensions get syntax highlighting, preview, linting
- dbt precedent: Proven pattern - SQL files with
{{ }}work fine in SQL editors - Extensibility: URI schemes enable MCP, GitHub, HTTP sources via plugins
- Simplicity: Schemaless refs for the common case (local models)
- Flexibility: Frontmatter URI override decouples organization from API
Consequences
- Discovery changes from
*.colinto all files inmodels/ - Need to handle frontmatter parsing per file type (or skip for non-frontmatter types)
- URI parsing needed to extract scheme and route to plugins
- Default scheme provides backwards-compatible simple refs