@dforge-core/metadata 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,450 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://dforge.dev/schemas/entity.schema.json",
4
+ "title": "dForge Entity Definition",
5
+ "description": "Defines an entity (table/view mapping) for a dForge module",
6
+ "type": "object",
7
+ "required": ["fields"],
8
+ "properties": {
9
+ "extends": {
10
+ "type": "string",
11
+ "pattern": "^[a-z][a-z0-9_]*\\.[a-z][a-z0-9_]*$",
12
+ "description": "Qualified entity code of the target entity to extend (e.g. 'fin.invoice'). When set, this entity definition adds columns to an existing entity owned by another module instead of creating a new entity. Physical columns are stored in a 1:1 extension table."
13
+ },
14
+ "description": {
15
+ "type": "string",
16
+ "description": "Human-readable description of the entity"
17
+ },
18
+ "schema": {
19
+ "type": "string",
20
+ "description": "Database schema name override (defaults to module schema)"
21
+ },
22
+ "dbObject": {
23
+ "type": "string",
24
+ "description": "Database table or view name (defaults to entity code)"
25
+ },
26
+ "isView": {
27
+ "type": "boolean",
28
+ "description": "When true, entity maps to a SQL view (read-only, no DDL generation). Default: false (table-backed).",
29
+ "default": false
30
+ },
31
+ "viewSql": {
32
+ "type": "string",
33
+ "description": "SQL SELECT statement for view-backed entities (isView=true). Executed as CREATE OR REPLACE VIEW during module install."
34
+ },
35
+ "toString": {
36
+ "description": "Display pattern using column placeholders, e.g. \"{first_name} {last_name}\". Optional in extension entities (with `extends`). No `type` constraint here because `toString` collides with Object.prototype.toString in JSON validators — they read the inherited function and reject it, even when the property is absent from the JSON."
37
+ },
38
+ "traits": {
39
+ "type": "array",
40
+ "description": "Entity traits expanded during module install (authoring shortcut). Trait definitions loaded from DB (seeded by metadata module).",
41
+ "items": {
42
+ "type": "string",
43
+ "enum": ["identity", "audit", "audit-full", "soft-delete", "sorting", "postable", "accumulation", "ledger", "period", "file-library"]
44
+ },
45
+ "uniqueItems": true
46
+ },
47
+ "fields": {
48
+ "type": "object",
49
+ "description": "Column definitions keyed by column code. Column codes must start with a letter or underscore and contain only letters, digits, and underscores — a leading `-` is reserved for the sort descending prefix in `view_json.order`.",
50
+ "propertyNames": {
51
+ "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
52
+ },
53
+ "additionalProperties": {
54
+ "$ref": "#/$defs/field"
55
+ }
56
+ },
57
+ "constraints": {
58
+ "type": "object",
59
+ "description": "Database constraints (unique, check, etc.)"
60
+ },
61
+ "references": {
62
+ "type": "object",
63
+ "description": "Foreign key reference definitions (legacy format, prefer 'link' on field)",
64
+ "additionalProperties": {
65
+ "$ref": "#/$defs/entityReference"
66
+ }
67
+ },
68
+ "indexes": {
69
+ "type": "object",
70
+ "description": "Database index definitions"
71
+ },
72
+ "numberSequence": {
73
+ "$ref": "#/$defs/numberSequence",
74
+ "description": "Automatic document numbering configuration for this entity"
75
+ },
76
+ "period": {
77
+ "$ref": "#/$defs/periodConfig",
78
+ "description": "Period entity configuration. Requires 'period' trait."
79
+ },
80
+ "accumulation": {
81
+ "$ref": "#/$defs/accumulationConfig",
82
+ "description": "Accumulation registry configuration (simple document without lines). Requires 'accumulation' trait."
83
+ },
84
+ "params": {
85
+ "type": "object",
86
+ "description": "Entity-level parameters for platform behavior flags",
87
+ "properties": {
88
+ "restricted": {
89
+ "type": "boolean",
90
+ "description": "When true, blocks generic data.get/data.set access. Entity must be accessed via dedicated API methods.",
91
+ "default": false
92
+ }
93
+ },
94
+ "additionalProperties": true
95
+ }
96
+ },
97
+ "additionalProperties": false,
98
+ "$defs": {
99
+ "field": {
100
+ "type": "object",
101
+ "description": "Entity column/field definition",
102
+ "properties": {
103
+ "dbDatatype": {
104
+ "type": "string",
105
+ "description": "PostgreSQL data type (e.g. uuid, varchar, int4, bool, text, json, numeric, date, timestamp, timestamptz, bpchar)"
106
+ },
107
+ "baseDatatypeCd": {
108
+ "type": "string",
109
+ "enum": ["binary", "bool", "cuid", "culture", "date", "guid", "link", "number", "project", "string", "time", "timestamp", "xml", "json"],
110
+ "description": "Base data type code from field_type table (e.g. string, number, boolean, date, time, timestamp, json, binary)"
111
+ },
112
+ "columnType": {
113
+ "type": "string",
114
+ "enum": ["D", "R", "S", "F", "A", "L", "G"],
115
+ "description": "Column type: D=Data (physical column), R=Reference (N:1 lookup), S=Set (1:N collection), F=Formula (virtual calculated), G=Generated/Computed (physical, auto-maintained), A=Accumulation registry, L=Ledger registry"
116
+ },
117
+ "fieldTypeCd": {
118
+ "type": "string",
119
+ "enum": [
120
+ "text", "email", "phone", "url", "textarea", "number", "currency", "percent",
121
+ "checkbox", "date", "datetime", "time",
122
+ "dropdown", "flags", "tags", "json", "hidden", "color", "image", "file",
123
+ "richtext", "lookup", "user", "grid", "entitylink", "code"
124
+ ],
125
+ "description": "UI field type code from field_type table"
126
+ },
127
+ "flags": {
128
+ "type": "string",
129
+ "pattern": "^[VIEMH]*$",
130
+ "description": "Display flags: V=Visible, I=Identity, E=Editable, M=Mandatory, H=Hidden"
131
+ },
132
+ "isPk": {
133
+ "type": "boolean",
134
+ "description": "Whether this column is part of the primary key"
135
+ },
136
+ "isNullable": {
137
+ "type": "boolean",
138
+ "description": "Whether the column allows NULL values"
139
+ },
140
+ "isIdentity": {
141
+ "type": "boolean",
142
+ "description": "Whether the column value is auto-generated (e.g. CUID primary key). Identity columns are not editable and excluded from required validation."
143
+ },
144
+ "formula": {
145
+ "type": "string",
146
+ "description": "Formula expression (for columnType 'F' or default values)"
147
+ },
148
+ "orderNum": {
149
+ "type": "integer",
150
+ "description": "Display order number"
151
+ },
152
+ "description": {
153
+ "type": "string",
154
+ "description": "Human-readable column description"
155
+ },
156
+ "maxLen": {
157
+ "type": "integer",
158
+ "description": "Maximum length for varchar/text columns"
159
+ },
160
+ "precision": {
161
+ "type": "integer",
162
+ "description": "Numeric precision for decimal/currency columns"
163
+ },
164
+ "editMask": {
165
+ "type": "string",
166
+ "description": "Input mask pattern for editing"
167
+ },
168
+ "displayFmt": {
169
+ "type": "string",
170
+ "description": "Display format pattern"
171
+ },
172
+ "params": {
173
+ "type": "object",
174
+ "description": "Field-type-specific configuration (e.g. dropdown options, currency settings)",
175
+ "properties": {
176
+ "options": {
177
+ "type": "array",
178
+ "items": {
179
+ "oneOf": [
180
+ { "type": "string" },
181
+ {
182
+ "type": "object",
183
+ "required": ["value", "label"],
184
+ "properties": {
185
+ "value": { "type": "string" },
186
+ "label": { "type": "string" },
187
+ "icon": { "type": "string" },
188
+ "color": { "type": "string" }
189
+ },
190
+ "additionalProperties": false
191
+ }
192
+ ]
193
+ },
194
+ "description": "Options for dropdown/flags field types"
195
+ }
196
+ }
197
+ },
198
+ "link": {
199
+ "$ref": "#/$defs/fieldLink",
200
+ "description": "Relationship link for Reference (R) and Set (S) columns"
201
+ },
202
+ "cyclic": {
203
+ "type": "boolean",
204
+ "description": "On a self-referencing reference column (R column whose link.entity is the owning entity), set true to ALLOW parent/child cycles. When absent the self-reference is treated as an acyclic hierarchy and DB-level guards reject loops. Ignored on non-self-referencing columns."
205
+ },
206
+ "refFilter": {
207
+ "description": "Filter criteria for reference lookups"
208
+ }
209
+ },
210
+ "oneOf": [
211
+ {
212
+ "title": "Data column",
213
+ "description": "Physical DB column (columnType 'D' or unspecified). Requires 'dbDatatype'. Must NOT have 'link'.",
214
+ "required": ["dbDatatype"],
215
+ "properties": {
216
+ "columnType": { "enum": ["D"] },
217
+ "link": false
218
+ }
219
+ },
220
+ {
221
+ "title": "Inferred-type column",
222
+ "description": "Physical column where dbDatatype is inferred from fieldTypeCd (e.g. user, color)",
223
+ "required": ["fieldTypeCd"],
224
+ "properties": {
225
+ "columnType": { "enum": ["D"] },
226
+ "link": false,
227
+ "dbDatatype": false
228
+ }
229
+ },
230
+ {
231
+ "title": "Reference column (R)",
232
+ "description": "N:1 lookup (columnType 'R'). Requires 'link' and 'fieldTypeCd'. Must NOT have 'dbDatatype'.",
233
+ "required": ["columnType", "fieldTypeCd", "link"],
234
+ "properties": {
235
+ "columnType": { "const": "R" },
236
+ "dbDatatype": false
237
+ }
238
+ },
239
+ {
240
+ "title": "Set column (S)",
241
+ "description": "1:N collection (columnType 'S'). Requires 'link' and 'fieldTypeCd'. Must NOT have 'dbDatatype'.",
242
+ "required": ["columnType", "fieldTypeCd", "link"],
243
+ "properties": {
244
+ "columnType": { "const": "S" },
245
+ "dbDatatype": false
246
+ }
247
+ },
248
+ {
249
+ "title": "Formula column (F)",
250
+ "description": "Calculated virtual column (columnType 'F'). Requires 'formula'. Must NOT have 'link' or 'dbDatatype'.",
251
+ "required": ["columnType", "formula", "baseDatatypeCd"],
252
+ "properties": {
253
+ "columnType": { "const": "F" },
254
+ "link": false,
255
+ "baseDatatypeCd": true,
256
+ "dbDatatype": false
257
+ }
258
+ },
259
+ {
260
+ "title": "Accumulation registry column (A)",
261
+ "description": "Accumulation registry state (bool) + config. Physical column storing posted state. Config in params defines balance entity, dimensions, resources.",
262
+ "required": ["columnType", "dbDatatype", "params"],
263
+ "properties": {
264
+ "columnType": { "const": "A" },
265
+ "dbDatatype": { "const": "bool" },
266
+ "link": false
267
+ }
268
+ },
269
+ {
270
+ "title": "Ledger registry column (L)",
271
+ "description": "Ledger registry state (bool) + config. Physical column storing posted state. Config in params defines lines, movement entity, balance entity.",
272
+ "required": ["columnType", "dbDatatype", "params"],
273
+ "properties": {
274
+ "columnType": { "const": "L" },
275
+ "dbDatatype": { "const": "bool" },
276
+ "link": false
277
+ }
278
+ },
279
+ {
280
+ "title": "Generated/Computed column (G)",
281
+ "description": "Physical column auto-computed by DB trigger (aggregate over child set) or PostgreSQL GENERATED ALWAYS AS (same-row expression). Requires 'dbDatatype' and 'formula'. Formula syntax: SUM([setCol].[childField]) for aggregates, or plain SQL expression for same-row generated columns.",
282
+ "required": ["columnType", "dbDatatype", "formula"],
283
+ "properties": {
284
+ "columnType": { "const": "G" },
285
+ "link": false
286
+ }
287
+ }
288
+ ],
289
+ "required": ["orderNum", "description"],
290
+ "additionalProperties": false
291
+ },
292
+ "fieldLink": {
293
+ "type": "object",
294
+ "description": "Relationship definition for Reference and Set columns",
295
+ "required": ["entity", "thisKey", "otherKey"],
296
+ "properties": {
297
+ "entity": {
298
+ "type": "string",
299
+ "description": "Target entity code (the entity being referenced or collected)"
300
+ },
301
+ "thisKey": {
302
+ "type": "string",
303
+ "description": "Column(s) in this entity (comma-separated for compound keys)"
304
+ },
305
+ "otherKey": {
306
+ "type": "string",
307
+ "description": "Column(s) in the target entity (comma-separated for compound keys)"
308
+ }
309
+ },
310
+ "additionalProperties": false
311
+ },
312
+ "numberSequence": {
313
+ "type": "object",
314
+ "description": "Automatic document numbering. Auto-generates a formatted number on INSERT when the target column is empty.",
315
+ "required": ["column", "defaultPrefix"],
316
+ "properties": {
317
+ "column": {
318
+ "type": "string",
319
+ "description": "Column code that receives the auto-generated number"
320
+ },
321
+ "prefixSettingCd": {
322
+ "type": "string",
323
+ "description": "Module setting code to resolve the prefix from (folder-scoped inheritance)"
324
+ },
325
+ "defaultPrefix": {
326
+ "type": "string",
327
+ "description": "Fallback prefix when no setting is configured"
328
+ },
329
+ "pattern": {
330
+ "type": "string",
331
+ "description": "Number format pattern. Placeholders: {prefix}, {yyyy}, {yy}, {mm}, {dd}, {seq:N}",
332
+ "default": "{prefix}{yyyy}-{seq:3}"
333
+ },
334
+ "resetPeriod": {
335
+ "type": "string",
336
+ "enum": ["year", "month", "day", "never"],
337
+ "description": "Counter reset period",
338
+ "default": "year"
339
+ }
340
+ },
341
+ "additionalProperties": false
342
+ },
343
+ "periodConfig": {
344
+ "type": "object",
345
+ "description": "Configuration for entities with the 'period' trait. Defines how date values map to period keys.",
346
+ "required": ["keyFormat"],
347
+ "properties": {
348
+ "keyFormat": {
349
+ "type": "string",
350
+ "description": "Format string for deriving period key from a date. Placeholders: {yyyy}, {yy}, {MM}, {dd}, {q} (quarter 1-4).",
351
+ "examples": ["{yyyy}-{MM}", "{yyyy}-Q{q}", "FY{yyyy}"]
352
+ }
353
+ },
354
+ "additionalProperties": false
355
+ },
356
+ "accumulationConfig": {
357
+ "type": "object",
358
+ "description": "Configuration for simple accumulation (document without lines, e.g. receipt → stock).",
359
+ "required": ["balanceEntity", "dimensions", "resources"],
360
+ "properties": {
361
+ "balanceEntity": { "type": "string", "description": "Balance entity code (e.g. 'stock')" },
362
+ "dimensions": {
363
+ "description": "Balance row lookup dimensions. Array of field names (same name on both entities) or object mapping balance_field → document_field.",
364
+ "oneOf": [
365
+ { "type": "array", "items": { "type": "string" } },
366
+ { "type": "object", "additionalProperties": { "type": "string" } }
367
+ ]
368
+ },
369
+ "resources": {
370
+ "type": "object",
371
+ "description": "Document field → balance field resource mappings",
372
+ "additionalProperties": {
373
+ "type": "object",
374
+ "required": ["balanceField"],
375
+ "properties": {
376
+ "balanceField": { "type": "string" }
377
+ }
378
+ }
379
+ },
380
+ "direction": { "type": "string", "enum": ["increase", "decrease"], "description": "Fixed direction for all postings" },
381
+ "sign": { "$ref": "#/$defs/signConfig", "description": "Dynamic sign based on a field value (alternative to direction)" },
382
+ "autoCreateBalance": { "type": "boolean", "default": true },
383
+ "allowNegative": { "type": "boolean", "default": true },
384
+ "lockedFields": { "type": "array", "items": { "type": "string" }, "description": "Document fields locked when posted" }
385
+ },
386
+ "additionalProperties": false
387
+ },
388
+ "signConfig": {
389
+ "type": "object",
390
+ "description": "Dynamic sign determination based on a field value",
391
+ "required": ["field", "positive", "negative"],
392
+ "properties": {
393
+ "field": { "type": "string", "description": "Field code that determines direction" },
394
+ "positive": { "type": "array", "items": { "type": "string" }, "description": "Values that mean increase" },
395
+ "negative": { "type": "array", "items": { "type": "string" }, "description": "Values that mean decrease" }
396
+ },
397
+ "additionalProperties": false
398
+ },
399
+ "ledgerRegistryEntry": {
400
+ "type": "object",
401
+ "description": "Single registry target within an L-column's registries array",
402
+ "required": ["movementEntity", "movementMapping", "balanceEntity", "balanceMapping"],
403
+ "properties": {
404
+ "movementEntity": { "type": "string", "description": "Movement entity code (e.g. 'posting_record')" },
405
+ "movementMapping": {
406
+ "type": "object",
407
+ "description": "Line field → movement field mapping",
408
+ "additionalProperties": { "type": "string" }
409
+ },
410
+ "balanceEntity": { "type": "string", "description": "Balance entity code (e.g. 'balance_register')" },
411
+ "balanceMapping": {
412
+ "type": "object",
413
+ "required": ["dimensions", "debitResource", "creditResource"],
414
+ "properties": {
415
+ "dimensions": { "type": "array", "items": { "type": "string" }, "description": "Balance row dimension columns" },
416
+ "debitResource": { "type": "string", "description": "Balance column for debit turnover" },
417
+ "creditResource": { "type": "string", "description": "Balance column for credit turnover" }
418
+ },
419
+ "additionalProperties": false
420
+ }
421
+ },
422
+ "additionalProperties": false
423
+ },
424
+ "entityReference": {
425
+ "type": "object",
426
+ "description": "Foreign key reference definition (legacy format)",
427
+ "required": ["from", "to"],
428
+ "properties": {
429
+ "from": {
430
+ "type": "object",
431
+ "required": ["field"],
432
+ "properties": {
433
+ "field": { "type": "string" }
434
+ },
435
+ "additionalProperties": false
436
+ },
437
+ "to": {
438
+ "type": "object",
439
+ "required": ["entity", "field"],
440
+ "properties": {
441
+ "entity": { "type": "string" },
442
+ "field": { "type": "string" }
443
+ },
444
+ "additionalProperties": false
445
+ }
446
+ },
447
+ "additionalProperties": false
448
+ }
449
+ }
450
+ }
@@ -0,0 +1,108 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://dforge.dev/schemas/folders.schema.json",
4
+ "title": "dForge Folders",
5
+ "description": "Folder tree for a dForge module. The file IS the root folder (not a map keyed by code). Sub-folders live under `children`, keyed by folder code.",
6
+ "$ref": "#/$defs/folder",
7
+ "$defs": {
8
+ "folder": {
9
+ "type": "object",
10
+ "properties": {
11
+ "label": {
12
+ "type": "string",
13
+ "description": "Folder display name"
14
+ },
15
+ "description": {
16
+ "type": "string",
17
+ "description": "Optional description text (shown in breadcrumb / header area)"
18
+ },
19
+ "parentCode": {
20
+ "type": ["string", "null"],
21
+ "description": "Parent folder code. Usually omitted: nested folders go under `children` of their parent. Only used by sparse-tree imports."
22
+ },
23
+ "folderPath": {
24
+ "type": "string",
25
+ "description": "URL-friendly folder path override"
26
+ },
27
+ "color": {
28
+ "type": "string",
29
+ "description": "Folder accent color (hex, e.g. '#2196F3')"
30
+ },
31
+ "icon": {
32
+ "type": "string",
33
+ "description": "Bootstrap icon class (e.g. 'bi-folder')"
34
+ },
35
+ "inheritSecurity": {
36
+ "type": "boolean",
37
+ "description": "Whether to inherit security rights from the parent folder. Default: true."
38
+ },
39
+ "entities": {
40
+ "type": "object",
41
+ "description": "Entity code → folder-entity binding (which view to show, quick-add, row-level filter).",
42
+ "additionalProperties": {
43
+ "$ref": "#/$defs/folderEntity"
44
+ }
45
+ },
46
+ "children": {
47
+ "type": "object",
48
+ "description": "Nested folders keyed by sub-folder code. Recursive — children can have their own children.",
49
+ "additionalProperties": {
50
+ "$ref": "#/$defs/folder"
51
+ }
52
+ }
53
+ },
54
+ "additionalProperties": false
55
+ },
56
+ "folderEntity": {
57
+ "type": "object",
58
+ "properties": {
59
+ "viewName": {
60
+ "type": "string",
61
+ "description": "Entity view name (data view code) to use in this folder. Common values include 'default'."
62
+ },
63
+ "quickAdd": {
64
+ "type": "boolean",
65
+ "description": "Enable the quick-add (+) button for this entity in this folder"
66
+ },
67
+ "rowFilter": {
68
+ "description": "Row-level filter. Either a SQL expression string (e.g. \"is_customer = true\") or a canonical filter object (`{c,o,v}` for one condition, `{g,i:[]}` for a group).",
69
+ "oneOf": [
70
+ { "type": "string" },
71
+ { "$ref": "#/$defs/filter" }
72
+ ]
73
+ }
74
+ },
75
+ "additionalProperties": false
76
+ },
77
+
78
+ "filter": {
79
+ "description": "Canonical dForge filter expression — same shape as in data_views.json. Either `{c, o, v}` or `{g, i: [...]}`.",
80
+ "oneOf": [
81
+ { "$ref": "#/$defs/filterCondition" },
82
+ { "$ref": "#/$defs/filterGroup" }
83
+ ]
84
+ },
85
+ "filterCondition": {
86
+ "type": "object",
87
+ "required": ["c", "o"],
88
+ "properties": {
89
+ "c": { "type": "string" },
90
+ "o": { "type": "string" },
91
+ "v": {}
92
+ },
93
+ "additionalProperties": false
94
+ },
95
+ "filterGroup": {
96
+ "type": "object",
97
+ "required": ["g", "i"],
98
+ "properties": {
99
+ "g": { "type": "string", "enum": ["and", "or"] },
100
+ "i": {
101
+ "type": "array",
102
+ "items": { "$ref": "#/$defs/filter" }
103
+ }
104
+ },
105
+ "additionalProperties": false
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "dForge Module Scheduled Jobs",
4
+ "description": "Cron job declarations for a dForge module package (logic/jobs.json). The dForge.Scheduler service polls these and fires each job on its schedule.",
5
+ "type": "object",
6
+ "required": ["jobs"],
7
+ "properties": {
8
+ "jobs": {
9
+ "type": "array",
10
+ "maxItems": 50,
11
+ "description": "At most 50 jobs per module — guard against accidental fan-out from generated declarations.",
12
+ "items": {
13
+ "type": "object",
14
+ "required": ["code", "action", "schedule", "timeout"],
15
+ "properties": {
16
+ "code": {
17
+ "type": "string",
18
+ "maxLength": 100,
19
+ "pattern": "^[a-z][a-z0-9_]*$",
20
+ "description": "Unique identifier within the module."
21
+ },
22
+ "description": {
23
+ "type": "string",
24
+ "description": "Human-readable description of what the job does."
25
+ },
26
+ "action": {
27
+ "type": "string",
28
+ "description": "Action code in this module (e.g. 'generate_invoices'). The scheduler resolves it to action_implementation.impl_id at install time."
29
+ },
30
+ "schedule": {
31
+ "type": "string",
32
+ "description": "Five-field cron expression (minute granularity). Sub-minute schedules are rejected.",
33
+ "pattern": "^\\S+\\s+\\S+\\s+\\S+\\s+\\S+\\s+\\S+$"
34
+ },
35
+ "params": {
36
+ "type": "object",
37
+ "description": "Static parameters passed to the action as __params. Folder-scoped overrides go through module settings, not here.",
38
+ "additionalProperties": true
39
+ },
40
+ "timeout": {
41
+ "type": "integer",
42
+ "minimum": 1,
43
+ "maximum": 3600,
44
+ "description": "Hard timeout in seconds. Required — there is no implicit default. Must be > 300 only if class = 'long_running' (60-minute hard ceiling)."
45
+ },
46
+ "concurrency": {
47
+ "type": "integer",
48
+ "minimum": 1,
49
+ "maximum": 5,
50
+ "default": 1,
51
+ "description": "Max parallel runs. 1 means single-instance; the scheduler skips a new fire if a previous one is still in flight (Phase 3 enforcement)."
52
+ },
53
+ "idempotencyKey": {
54
+ "type": "string",
55
+ "description": "Template like '{job_cd}:{run.month}'. Resolved at fire time. A successful run with the same key blocks re-dispatch (Phase 3 enforcement)."
56
+ },
57
+ "class": {
58
+ "type": "string",
59
+ "enum": ["standard", "long_running"],
60
+ "default": "standard",
61
+ "description": "Routing class. 'long_running' is opt-in and required for any timeout > 300 seconds."
62
+ },
63
+ "timeZone": {
64
+ "type": "string",
65
+ "description": "IANA tz name (e.g. 'Asia/Tokyo'). Overrides the tenant default. Omit to fall back to auth.tenant.time_zone."
66
+ },
67
+ "enabled": {
68
+ "type": "boolean",
69
+ "default": true,
70
+ "description": "Master switch. False fully disables (no scheduler, no manual trigger)."
71
+ },
72
+ "paused": {
73
+ "type": "boolean",
74
+ "default": false,
75
+ "description": "Soft pause. Scheduler skips firing, but admin 'trigger now' still works."
76
+ }
77
+ },
78
+ "additionalProperties": false
79
+ }
80
+ }
81
+ },
82
+ "additionalProperties": false
83
+ }