Status: Read this and PHILOSOPHY.md. The spec only covers what WorkBrief actually consumes today (parser + linter + viz). Codegen-spezifische Erweiterungen werden hier ergänzt sobald die entsprechenden M-Items aus ROADMAP.md landen.
firepack: 1 # required, only 1 supported today
project: workbrief # required, free-form identifier
collections: # required, mapping
<collection-name>:
...<name>:
tenant: <field-name> # optional, default null (untenanted)
fields: # required, mapping (see "Field" below)
indexes: [...] # optional, list (see "Index")
queries: {...} # optional, mapping (see "Query")
rules: {...} # optional, mapping (see "Rules")tenant: — Pflichtfeld in jedem Doc dieser Collection, das die
Org-Zugehörigkeit speichert. Setzt das automatisch in Queries
(scopedToOrg(orgId)) und Rules (tenantSelf Helper). WorkBrief nutzt
durchgehend organizationId für Tenant-Collections.
Two equivalent forms:
# Shorthand — type only
title: string
# Mapping — full control
title:
type: string
required: true
immutable: true
default: "Untitled"
serverDefault: now # alternative zu default für DateTime-Auto-Set
primaryKey: false
optional: false
autoTouch: false| Type | Notes |
|---|---|
string |
UTF-8 |
int |
int64 |
double (alias number) |
float64 |
bool |
|
dateTime (alias datetime) |
ISO-8601 String in Firestore (Stand WorkBrief) |
enum |
requires values: [a, b, c] |
ref[<col>.<field>] |
foreign-key-Style Referenz; nutzt das Lint-System |
list |
requires of: { type: <inner> } ODER inline list[<type>] |
map |
freeform Map (Firestore-Map). Discouraged — bevorzugt strukturieren |
| Flag | Effekt |
|---|---|
required: true |
non-null, im Codegen ohne ? |
optional: true |
nullable, im Codegen mit ? |
immutable: true |
wird in Update-Rules ausgeschlossen |
primaryKey: true |
genau ein Feld pro Collection muss das setzen |
default: <val> |
Client-Side-Default beim create |
serverDefault: now |
DateTime-Felder bekommen FieldValue.serverTimestamp() |
serverDefault: randomId |
für id-Felder, generiert aus doc().id |
autoTouch: true |
DateTime-Felder werden auf jedem update neu gesetzt |
required und optional sind sich gegenseitig ausschließend; der Linter
fängt das.
createdBy: { type: "ref[users.id]", required: true, immutable: true }Quoting ist in Flow-Mappings notwendig wegen YAML-Syntax (Brackets sind
reserviert). Linter prüft dass users existiert; users.id ist heute
ein Hint, kein hard check (kommt mit M3 wenn Codegen die Ziel-Felder
kennen muss).
# Liste von Strings
fcmTokens: { type: list, of: { type: string } }
# Liste von Refs
assignedUserIds: { type: list, of: { type: "ref[users.id]" } }
# Liste von nested Models (siehe nested-Limitations unten)
tasks: { type: list } # heute: opaque, M3 wird das ausweitenindexes:
- fields: [organizationId, createdAt:desc]
- fields: [organizationId, status, createdAt:desc]Direction: :asc (default), :desc. Mehr Direction-Tokens kommen
nicht — Firestore unterstützt nur diese zwei.
Linter erkennt Duplikate. Codegen (M1) schreibt das in
firestore.indexes.json mit collectionGroup = Collection-Name,
queryScope: COLLECTION.
queries:
watchByOrg:
where: [tenant] # `tenant` ist Reserved Token → wird zu where('organizationId', ==, $orgId)
orderBy: createdAt:desc
watchById:
byId: true # → `Stream<T?> watchById(String id)`
watchByAssignee:
where: [tenant, "assignedUserIds contains: $uid"]
orderBy: createdAt:desc
limit: 50Reserved Tokens:
tenant— wird zur tenant-Filter-Where-Clause$<name>— Funktionsparameter; wird im Codegen zu Method-Argument
Heute werden queries: nur geparst; M4 generiert daraus Repo-Methoden.
rules:
read: "signedIn && tenantSelf"
create: "signedIn && supervisorOrAdmin && tenantSelf"
update:
- role: "isAdmin && tenantSelf"
fields: "all"
- role: "self"
fields: "name,preferredLanguage,fcmTokens"
delete: falseHelper-Tokens (werden in M2 vom Rules-Generator expandiert):
signedIn→request.auth != nullself→request.auth.uid == userId(oder das passende ID-Feld)tenantSelf→request.resource.data.<tenant> == getUserOrgId()selfOr(<expr>)→(self || <expr>)isAdmin/isSupervisor/isWorker→ role checksupervisorOrAdmin→ admin-or-supervisor
update:
- Single string →
update: <expr>mitfields: all - List of clauses → multi-role; jede Clause gilt für ihren Role-Match
fields: "name,foo,bar"→ wird zuaffectedKeys().hasOnly([…])fields: "all"→ keine Field-Restriction
delete:
false→ never deletable (immutable history wieauditLogs)- string → boolean expression
Komplettes Mini-Beispiel:
firepack: 1
project: my-app
collections:
posts:
tenant: organizationId
fields:
id: { type: string, primaryKey: true }
organizationId: { type: "ref[organizations.id]", required: true }
title: { type: string, required: true }
status:
type: enum
values: [draft, published]
default: draft
createdBy: { type: "ref[users.id]", required: true, immutable: true }
createdAt: { type: dateTime, required: true, serverDefault: now }
indexes:
- fields: [organizationId, createdAt:desc]
queries:
watchPublishedByOrg:
where: [tenant, "status == published"]
orderBy: createdAt:desc
rules:
read: "signedIn && tenantSelf"
create: "signedIn && tenantSelf"
update:
- role: "isAdmin && tenantSelf"
fields: "all"
delete: falsefirepack: 1 — heutiger Stand. Spec-Erweiterungen sind additiv (neue
Felder mit Default-Werten). Breaking Changes bekommen einen neuen
Versions-Stand (firepack: 2); Parser unterstützt beide eine Migrations-
Phase lang. Folgt der Linux-style Stable-API-Disziplin.