Skip to content

File system

File system

A workspace is a directory whose src/ holds + files. Each + file is a compiler role: its default export is one literal passed to that role’s declaration function from @norbital-ai/bolt. Names come from paths, never from file contents. A file without a + is an ordinary module and may sit anywhere.

API reference: bolt

The tree

<workspace>/
├── package.json
├── tsconfig.json                          extends ./.norbital/tsconfig.json
├── norbital.template.json                 catalogue metadata (templates only)
├── assets/                                thumbnail.svg + runtime assets shipped in the artifact
├── seed/                                  <collection>.json[.gz] rows, assets.json + assets/**, optional seed.ts
├── tests/                                 bolt test (@effect/vitest + @norbital-ai/bolt/test)
├── .norbital/                             generated, never edited
└── src/
    ├── +workspace.ts                      tz, locale, currency, app order, AI model classes, connections
    ├── +secrets.ts                        secret names: OAuth clients, MCP settings; values set at runtime
    ├── data/
    │   ├── +relationship.ts               every relationship
    │   ├── model/+<model>.model.ts        storage: fields, kinds, constraints, state
    │   ├── custom_field/<field>/          +definition.ts, +renderer.svelte
    │   └── collection/<model>/            the exposed portion of model <model>
    │       ├── +collection.ts             reads, writes, the transform, queries, actions, notifications
    │       ├── +representation.svelte     optional record view (RecordShell otherwise)
    │       └── +pipeline.ts               optional import / export
    ├── access_control/
    │   ├── team/+team.ts                  team names → the policies each holds
    │   └── policy/+<name>.policy.ts       grants, embedded approvals, capabilities, limits
    ├── integration/<name>/
    │   ├── +integration.ts                a driver, configuration, credential, feeds, outbound
    │   └── +integration.svelte            optional setup screen
    ├── agent/
    │   ├── +agent.md                      in-app agent brief (staff)
    │   ├── +agent.external.md             brief for external members
    │   ├── envoy/+<id>.envoy.ts           an agent role a conversation instance deploys
    │   ├── skill/+<name>.skill.md         an agent skill
    │   └── mcp/+<name>.mcp.ts             an MCP server the agent may use
    ├── automation/+<name>.automation.ts   cron / event / webhook work and its I/O
    ├── app/
    │   ├── <app>/+app.ts                  title, icon, audience, pages in nav order
    │   ├── <app>/+<page>.page.svelte      one page per file
    │   └── <group>/+group.ts              a nav group; its apps live in its folder
    ├── i18n/
    │   ├── +messages.ts                   base locale keys
    │   └── +<locale>.messages.ts          other locales (keys ⊆ the base)
    └── lib/                               ordinary modules (no roles)

The naming rule:

  • A folder that already names the thing holds +<role>.<ext>: data/collection/orders/+collection.ts.
  • Otherwise the file is +<name>.<role>.<ext>: access_control/policy/+sales_rep.policy.ts.
  • Folder names are singular, and names match ^[a-z][a-z0-9_]*$.
  • An unknown + file, a role in the wrong folder, or a data/collection/<m>/ folder with no data/model/+<m>.model.ts is a build error whose help prints the role table.

Roles

Each role has one declaration function. collection, app, group, integration and pipeline take their name first; the others take only the literal. Code bodies attach in their own statements after the declaration (c.transform(…), c.query(…), c.action(…), a.run(…)), never inside the literal.

RolePath under src/Default exportGuide
Workspace settings+workspace.tsworkspace({…})Quick Start
Secrets+secrets.tssecrets({…})Integrations
Modeldata/model/+<m>.model.tsmodel({…})Collections
Relationshipsdata/+relationship.tsrelationship({…})Collections
Custom fielddata/custom_field/<f>/+definition.tscustomField({…})Collections
Custom field rendererdata/custom_field/<f>/+renderer.svelte{ view: CustomFieldView }UI components
Collectiondata/collection/<m>/+collection.tscollection('<m>', {…})Write contract
Record viewdata/collection/<m>/+representation.svelte{ view: RecordView<'<m>'> }UI components
Integrationintegration/<n>/+integration.tsintegration('<n>', {…})Integrations
Integration setupintegration/<n>/+integration.svelteSvelte componentIntegrations
Pipelinedata/collection/<m>/+pipeline.tspipeline('<m>', {…})Pipelines
Teamsaccess_control/team/+team.tsteam({…})Policies
Policyaccess_control/policy/+<p>.policy.tspolicy({…})Policies
Agent briefsagent/+agent.md, agent/+agent.external.mdMarkdownNorbius
Agent skillagent/skill/+<s>.skill.mdMarkdown with a description: frontmatterNorbius
MCP serveragent/mcp/+<n>.mcp.tsmcp({…})Norbius
Envoyagent/envoy/+<e>.envoy.tsenvoy({…})Envoys
Automationautomation/+<a>.automation.tsautomation({…})Automations
Appapp/<a>/+app.tsapp('<a>', {…})Apps
Pageapp/<a>/+<p>.page.svelteSvelte componentApps
Nav groupapp/<g>/+group.tsgroup('<g>', {…})Apps
Messagesi18n/+messages.ts, i18n/+<locale>.messages.tsmessages({…})Internationalization

Generated files

.norbital/
├── names.ts, names.client.ts   # the names index: every path-derived name → typeof its declaration
├── tsconfig.json               # the authored tsconfig.json only extends this
├── representations.check.ts
├── artifact/                   # bolt build output
├── seed/                       # the base (and sample) seed packs
└── cache/**

The compiler writes .norbital/; nothing in it is edited or committed. The names index maps every path-derived name to typeof its declaration, so every other file — every page included — is typed from the declarations. It is rewritten only when a role file is added, renamed or removed.

Commands

bolt check   # every error of every stage in one run
bolt build   # check + .norbital/artifact/ + seed packs
bolt test    # @effect/vitest with TestWorkspace.layer
bolt dev     # the author loop: PGlite, live reload, a dev mail sink
bolt start   # the self-host server (bolt-server)

Five verbs, each exiting 0 when all is well, 1 for an author error and 2 for a tool failure. bolt build is the only gate a host runs.

The manifest is the schema

There are no migration files. A host compares the applied schema with the manifest and applies one plan in one transaction. The plan cannot tell a rename from a drop plus an add, so renaming a field or a model loses its data: add the new field, backfill it, then drop the old one in a later change.

What is not a role

The surface is closed: anything not in the table above does not exist, and these paths are not read — src/collections/, src/datatypes/, src/functions/, src/apps/, src/+agents.md, src/access/policies/, src/capabilities/, +anonymous_limits.ts, vite.config.ts and .norbital/migrations/. Server functions are collection queries and actions, secret names are declared in +secrets.ts, and agent tools are the collections’ reads, queries and actions.