@shipeasy/openapi 3.1.0 → 3.3.0

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.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-V4ZESAIF.js → chunk-75Z4SQOM.js} +141 -1
  3. package/dist/chunk-75Z4SQOM.js.map +1 -0
  4. package/dist/{chunk-UV333BIK.js → chunk-ZYK74ZIW.js} +569 -119
  5. package/dist/chunk-ZYK74ZIW.js.map +1 -0
  6. package/dist/{client-yqGYju-M.d.ts → client-DAGqRpF1.d.ts} +2209 -401
  7. package/dist/client.d.ts +1 -1
  8. package/dist/client.js +33 -1
  9. package/dist/custom.d.ts +5 -5
  10. package/dist/custom.js +128 -84
  11. package/dist/custom.js.map +1 -1
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +34 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/schemas.d.ts +2007 -891
  16. package/dist/schemas.js +179 -1
  17. package/openapi-sdk.json +1769 -0
  18. package/openapi.json +5861 -2597
  19. package/openapi.yaml +2739 -452
  20. package/package.json +6 -4
  21. package/spec/components/responses.yaml +39 -0
  22. package/spec/components/schemas/alert-rules.yaml +405 -7
  23. package/spec/components/schemas/alerts.yaml +9 -7
  24. package/spec/components/schemas/common.yaml +2 -2
  25. package/spec/components/schemas/connectors.yaml +69 -6
  26. package/spec/components/schemas/events.yaml +20 -9
  27. package/spec/components/schemas/gates.yaml +87 -0
  28. package/spec/components/schemas/keys.yaml +32 -2
  29. package/spec/components/schemas/killswitches.yaml +64 -0
  30. package/spec/components/schemas/metrics.yaml +131 -251
  31. package/spec/components/schemas/ops.yaml +555 -22
  32. package/spec/components/schemas/projects.yaml +18 -0
  33. package/spec/components/schemas/tags.yaml +91 -0
  34. package/spec/openapi-sdk.yaml +86 -0
  35. package/spec/openapi.yaml +61 -0
  36. package/spec/paths/alerts.yaml +5 -3
  37. package/spec/paths/gates.yaml +282 -0
  38. package/spec/paths/i18n.yaml +2 -1
  39. package/spec/paths/keys.yaml +79 -0
  40. package/spec/paths/killswitches.yaml +96 -0
  41. package/spec/paths/metrics.yaml +26 -17
  42. package/spec/paths/ops.yaml +566 -10
  43. package/spec/paths/tags.yaml +270 -0
  44. package/dist/chunk-UV333BIK.js.map +0 -1
  45. package/dist/chunk-V4ZESAIF.js.map +0 -1
@@ -204,7 +204,7 @@ CreateCursorTriggerRequest:
204
204
  opsKey:
205
205
  type: string
206
206
  minLength: 1
207
- description: Restricted Shipeasy ops key, injected into the run as `SHIPEASY_CLI_TOKEN` via the launch envVars (secret). Encrypted; never returned.
207
+ description: Restricted Shipeasy ops key (secret). Sent as the Bearer for the Shipeasy MCP server handed to each run inline — never in the prompt text or the run env. Encrypted; never returned.
208
208
  enabled:
209
209
  type: boolean
210
210
  default: true
@@ -308,33 +308,82 @@ ClaudeTriggerConfig:
308
308
  - routineId
309
309
  additionalProperties: false
310
310
  description: Non-secret config for a Claude trigger.
311
+ CursorTriggerRepo:
312
+ type: object
313
+ properties:
314
+ url:
315
+ type: string
316
+ format: uri
317
+ description: "Repo the cloud agent runs against, e.g. `https://github.com/owner/repo`."
318
+ startingRef:
319
+ type: string
320
+ minLength: 1
321
+ description: "Optional branch this repo's runs start from. Omit for the repo's default branch."
322
+ required:
323
+ - url
324
+ additionalProperties: false
325
+ description: One repo a trigger runs against, with the branch its runs start from.
311
326
  CursorTriggerConfig:
312
327
  type: object
313
328
  properties:
314
329
  repoUrl:
315
330
  type: string
316
331
  format: uri
317
- description: Repo the cloud agent runs against, e.g. `https://github.com/owner/repo`. Idempotency key for the connector.
332
+ description: "**First** repo the cloud agent runs against, e.g. `https://github.com/owner/repo`. Idempotency key for the connector, and always a mirror of `repos[0].url` when `repos` is supplied."
318
333
  startingRef:
319
334
  type: string
320
335
  minLength: 1
321
- description: Optional git ref the run starts from.
336
+ description: Optional git ref the run starts from. Mirrors `repos[0].startingRef`. Pinned onto the Cursor agent at creation, so changing it provisions a replacement agent.
337
+ repos:
338
+ type: array
339
+ minItems: 1
340
+ items:
341
+ $ref: "#/CursorTriggerRepo"
342
+ description: "Every repo this trigger runs against, each with its own starting branch — Cursor launches ONE agent across the whole set. Omit for a single-repo trigger, which is then described by `repoUrl`/`startingRef` alone; when supplied, the first entry must match them. Changing the set re-provisions the agent (its repos are pinned at creation)."
322
343
  projectId:
323
344
  type: string
324
345
  minLength: 1
325
346
  description: Shipeasy project id the run targets.
347
+ agentId:
348
+ type: string
349
+ minLength: 1
350
+ description: "Server-managed. The reusable Cursor cloud agent (`bc-…`) Shipeasy provisions once at connect and then starts every run on. A supplied value is ignored on create; if Cursor stops recognising the id (404), the next fire provisions a replacement and stores it here."
351
+ fireText:
352
+ type: string
353
+ minLength: 1
354
+ description: "Optional standing instruction prepended to every run's prompt — the trigger's base prompt. Lets two triggers on different repos steer their runs differently without changing the `/shipeasy-ops-work` task block."
326
355
  required:
327
356
  - repoUrl
328
357
  - projectId
329
358
  additionalProperties: false
330
359
  description: Non-secret config for a Cursor trigger.
360
+ CopilotTriggerRepo:
361
+ type: object
362
+ properties:
363
+ owner:
364
+ type: string
365
+ minLength: 1
366
+ description: GitHub repo owner.
367
+ repo:
368
+ type: string
369
+ minLength: 1
370
+ description: GitHub repo name.
371
+ baseRef:
372
+ type: string
373
+ minLength: 1
374
+ description: "Optional base ref this repo's agent task branches from. Omit for the repo's default branch."
375
+ required:
376
+ - owner
377
+ - repo
378
+ additionalProperties: false
379
+ description: One repo a Copilot trigger runs against, with the base branch its tasks branch from.
331
380
  CopilotTriggerConfig:
332
381
  type: object
333
382
  properties:
334
383
  owner:
335
384
  type: string
336
385
  minLength: 1
337
- description: GitHub repo owner. Together with `repo` forms the connector's idempotency key (`owner/repo`).
386
+ description: "**First** GitHub repo owner. Together with `repo` forms the connector's idempotency key (`owner/repo`), and both mirror `repos[0]` when `repos` is supplied."
338
387
  repo:
339
388
  type: string
340
389
  minLength: 1
@@ -342,11 +391,21 @@ CopilotTriggerConfig:
342
391
  baseRef:
343
392
  type: string
344
393
  minLength: 1
345
- description: Optional base ref the agent branches from.
394
+ description: Optional base ref the agent branches from. Mirrors `repos[0].baseRef`.
395
+ repos:
396
+ type: array
397
+ minItems: 1
398
+ items:
399
+ $ref: "#/CopilotTriggerRepo"
400
+ description: "Every repo this trigger runs against, each with its own base branch. GitHub's agent-tasks API is per-repo, so ONE fire starts one task per entry. Omit for a single-repo trigger, which is then described by `owner`/`repo`/`baseRef` alone; when supplied, the first entry must match them."
346
401
  projectId:
347
402
  type: string
348
403
  minLength: 1
349
404
  description: Shipeasy project id the run targets.
405
+ fireText:
406
+ type: string
407
+ minLength: 1
408
+ description: "Optional standing instruction prepended to every run's prompt — the trigger's base prompt. Lets two triggers on different repos steer their runs differently without changing the `/shipeasy-ops-work` task block."
350
409
  required:
351
410
  - owner
352
411
  - repo
@@ -368,6 +427,10 @@ JulesTriggerConfig:
368
427
  type: string
369
428
  minLength: 1
370
429
  description: Shipeasy project id the run targets.
430
+ fireText:
431
+ type: string
432
+ minLength: 1
433
+ description: "Optional standing instruction prepended to every run's prompt — the trigger's base prompt. Lets two triggers on different repos steer their runs differently without changing the `/shipeasy-ops-work` task block."
371
434
  required:
372
435
  - source
373
436
  - projectId
@@ -452,7 +515,7 @@ UpdateCursorTriggerRequest:
452
515
  - provider
453
516
  - config
454
517
  additionalProperties: false
455
- description: "Per-provider PATCH body for editing an existing `cursor_trigger` connector. `config` replaces the stored config wholesale; `name` and either credential are optional (a supplied credential rotates just that key)."
518
+ description: "Per-provider PATCH body for editing an existing `cursor_trigger` connector. `config` replaces the stored config wholesale, except for the server-managed keys: the provisioned `agentId` is carried over (and dropped, so a replacement agent is provisioned, when `repoUrl`/`startingRef` change), as is the last-run pointer. `name` and either credential are optional (a supplied credential rotates just that key)."
456
519
  UpdateCopilotTriggerRequest:
457
520
  type: object
458
521
  properties:
@@ -33,7 +33,8 @@ ListEventsResponse:
33
33
  type: string
34
34
  minLength: 1
35
35
  maxLength: 64
36
- description: Property key, e.g. `amount` or `plan`.
36
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
37
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
37
38
  type:
38
39
  type: string
39
40
  enum:
@@ -49,11 +50,14 @@ ListEventsResponse:
49
50
  default: ""
50
51
  description: Human-readable description of the property.
51
52
  type: string
53
+ pending:
54
+ description: "`true` when this property was DISCOVERED in live traffic rather than declared. Nothing is recorded for it yet — a property is written to Analytics Engine only once it owns a column, and a column is allocated when an admin declares the property (include it in a `PATCH .../events/{id}` body). Approval is a gate rather than a formality: columns are finite and permanently reserved, so auto-allocating them from customer payload keys would exhaust an event's schema irreversibly."
55
+ type: boolean
52
56
  required:
53
57
  - name
54
58
  - type
55
59
  additionalProperties: false
56
- description: One typed property declared on a catalogued event.
60
+ description: One typed property on a catalogued event — declared by an admin, or discovered in traffic and awaiting declaration.
57
61
  description: Typed properties declared on the event.
58
62
  pending:
59
63
  type: integer
@@ -106,7 +110,8 @@ GetEventResponse:
106
110
  type: string
107
111
  minLength: 1
108
112
  maxLength: 64
109
- description: Property key, e.g. `amount` or `plan`.
113
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
114
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
110
115
  type:
111
116
  type: string
112
117
  enum:
@@ -122,11 +127,14 @@ GetEventResponse:
122
127
  default: ""
123
128
  description: Human-readable description of the property.
124
129
  type: string
130
+ pending:
131
+ description: "`true` when this property was DISCOVERED in live traffic rather than declared. Nothing is recorded for it yet — a property is written to Analytics Engine only once it owns a column, and a column is allocated when an admin declares the property (include it in a `PATCH .../events/{id}` body). Approval is a gate rather than a formality: columns are finite and permanently reserved, so auto-allocating them from customer payload keys would exhaust an event's schema irreversibly."
132
+ type: boolean
125
133
  required:
126
134
  - name
127
135
  - type
128
136
  additionalProperties: false
129
- description: One typed property declared on a catalogued event.
137
+ description: One typed property on a catalogued event — declared by an admin, or discovered in traffic and awaiting declaration.
130
138
  description: Typed properties declared on the event.
131
139
  pending:
132
140
  type: integer
@@ -150,8 +158,8 @@ CreateEventRequest:
150
158
  properties:
151
159
  name:
152
160
  type: string
153
- pattern: ^[a-zA-Z0-9_][a-zA-Z0-9_\-.]{0,127}$
154
- description: Event name. Starts with a letter, digit, or `_`; letters, digits, `_`, `-`, `.`; max 128 chars. Immutable after create — this is the handle metric queries reference.
161
+ pattern: ^[a-zA-Z0-9_][a-zA-Z0-9_\-.]{0,49}$
162
+ description: Event name. Starts with a letter, digit, or `_`; letters, digits, `_`, `-`, `.`; max 50 characters. The cap is the Analytics Engine index budget — 96 bytes total, less a 36-byte project UUID and a separator — and the API has always enforced 50 while this spec advertised 128. The charset is ASCII, so characters and bytes are the same count. Immutable after create — this is the handle metric queries reference.
155
163
  folder:
156
164
  $ref: ./common.yaml#/Folder
157
165
  description:
@@ -168,7 +176,8 @@ CreateEventRequest:
168
176
  type: string
169
177
  minLength: 1
170
178
  maxLength: 64
171
- description: Property key, e.g. `amount` or `plan`.
179
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
180
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
172
181
  type:
173
182
  type: string
174
183
  enum:
@@ -225,7 +234,8 @@ UpdateEventRequest:
225
234
  type: string
226
235
  minLength: 1
227
236
  maxLength: 64
228
- description: Property key, e.g. `amount` or `plan`.
237
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
238
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
229
239
  type:
230
240
  type: string
231
241
  enum:
@@ -276,7 +286,8 @@ ApproveEventRequest:
276
286
  type: string
277
287
  minLength: 1
278
288
  maxLength: 64
279
- description: Property key, e.g. `amount` or `plan`.
289
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
290
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
280
291
  type:
281
292
  type: string
282
293
  enum:
@@ -980,3 +980,90 @@ ListGateActivityResponse:
980
980
  - createdAt
981
981
  additionalProperties: false
982
982
  description: Recent audit rows for one gate, newest first.
983
+ GateWhitelistAttr:
984
+ type: string
985
+ enum:
986
+ - email
987
+ - user_id
988
+ description: Which identity attribute the whitelist matches on. `email` compares the caller's `email`; `user_id` compares the caller's `userID`. A whitelist matches on exactly one of the two at a time.
989
+ GateWhitelist:
990
+ type: object
991
+ properties:
992
+ id:
993
+ type: string
994
+ description: Resolved gate id.
995
+ name:
996
+ type: string
997
+ description: The gate's stable `name`.
998
+ attr:
999
+ $ref: "#/GateWhitelistAttr"
1000
+ entries:
1001
+ type: array
1002
+ items:
1003
+ type: string
1004
+ minLength: 1
1005
+ description: The whitelisted identities, in the order they are stored. Deduplicated case-sensitively; an empty array means the gate has no whitelist.
1006
+ required:
1007
+ - id
1008
+ - name
1009
+ - attr
1010
+ - entries
1011
+ additionalProperties: false
1012
+ description: A gate's whitelist — the always-first allowlist that admits the listed identities before any targeting rule or rollout runs. Backed by the pinned `whitelist` entry at the head of the gate's `stack`, so it is the same list the dashboard's Whitelist block edits.
1013
+ SetGateWhitelistRequest:
1014
+ type: object
1015
+ properties:
1016
+ attr:
1017
+ anyOf:
1018
+ - $ref: "#/GateWhitelistAttr"
1019
+ description: Identity attribute to match on. Defaults to the whitelist's current attribute, or `email` when the gate has no whitelist yet.
1020
+ entries:
1021
+ type: array
1022
+ items:
1023
+ type: string
1024
+ minLength: 1
1025
+ description: The complete whitelist after the call. Pass `[]` to remove the whitelist from the gate entirely.
1026
+ required:
1027
+ - entries
1028
+ additionalProperties: false
1029
+ description: Body for `PUT /api/admin/gates/{id}/whitelist`. Replaces the whole list — idempotent, and the only call that can switch `attr` or clear the whitelist.
1030
+ AddToGateWhitelistRequest:
1031
+ type: object
1032
+ properties:
1033
+ attr:
1034
+ anyOf:
1035
+ - $ref: "#/GateWhitelistAttr"
1036
+ description: Identity attribute to match on. Only honoured when the gate has no whitelist yet (this call creates it); passing an attribute that disagrees with an existing whitelist is a 409 rather than a silent re-key of the entries already there.
1037
+ entries:
1038
+ type: array
1039
+ minItems: 1
1040
+ items:
1041
+ type: string
1042
+ minLength: 1
1043
+ description: Identities to admit. Already-listed entries are skipped, so the call is idempotent.
1044
+ required:
1045
+ - entries
1046
+ additionalProperties: false
1047
+ description: Body for `POST /api/admin/gates/{id}/whitelist`. Adds entries to the whitelist, creating it if the gate doesn't have one.
1048
+ RemoveFromGateWhitelistRequest:
1049
+ type: object
1050
+ properties:
1051
+ entries:
1052
+ type: array
1053
+ minItems: 1
1054
+ items:
1055
+ type: string
1056
+ minLength: 1
1057
+ description: Identities to stop admitting. Entries that aren't listed are skipped, so the call is idempotent.
1058
+ required:
1059
+ - entries
1060
+ additionalProperties: false
1061
+ description: "Body for `DELETE /api/admin/gates/{id}/whitelist`. Removes entries from the whitelist. Removing the last entry leaves an empty whitelist in place — use `PUT` with `entries: []` to drop the block itself."
1062
+ GetGateWhitelistResponse:
1063
+ $ref: "#/GateWhitelist"
1064
+ SetGateWhitelistResponse:
1065
+ $ref: "#/GateWhitelist"
1066
+ AddToGateWhitelistResponse:
1067
+ $ref: "#/GateWhitelist"
1068
+ RemoveFromGateWhitelistResponse:
1069
+ $ref: "#/GateWhitelist"
@@ -60,6 +60,11 @@ KeyRecord:
60
60
  - type: string
61
61
  - type: "null"
62
62
  description: Human label for the key. `null` for unnamed dashboard mints; programmatic mints auto-name themselves.
63
+ description:
64
+ anyOf:
65
+ - type: string
66
+ - type: "null"
67
+ description: Free-text note recording what the key is for (the service that holds it, the rotation owner, the ticket it was minted under). Display only — `null` unless someone set it via `PATCH /api/admin/keys/{id}`.
63
68
  scopes:
64
69
  anyOf:
65
70
  - type: array
@@ -81,9 +86,34 @@ KeyRecord:
81
86
  - expires_at
82
87
  - created_by_email
83
88
  - name
89
+ - description
84
90
  - scopes
85
91
  - last4
86
92
  additionalProperties: false
93
+ UpdateKeyRequest:
94
+ type: object
95
+ description: |-
96
+ Body for `PATCH /api/admin/keys/{id}`. Only the key's **label** is editable — `name` and `description`.
97
+
98
+ Everything that gives a key its authority (`type`, `env`, `scopes`, `expires_at`) is fixed at mint: those are the isolation boundary the worker derives the read env and permissions from, so changing them would silently re-point a credential that is already deployed. Rotate instead — mint a replacement and revoke the old key.
99
+
100
+ Omitted fields are left untouched; send `null` to clear one.
101
+ properties:
102
+ name:
103
+ anyOf:
104
+ - type: string
105
+ minLength: 1
106
+ maxLength: 160
107
+ - type: "null"
108
+ description: New human label, or `null` to clear it. Same 160-char budget as create — provenance labels are composed, not typed.
109
+ description:
110
+ anyOf:
111
+ - type: string
112
+ minLength: 1
113
+ maxLength: 500
114
+ - type: "null"
115
+ description: New free-text note, or `null` to clear it.
116
+ additionalProperties: false
87
117
  CreateKeyRequest:
88
118
  type: object
89
119
  description: |-
@@ -102,8 +132,8 @@ CreateKeyRequest:
102
132
  name:
103
133
  type: string
104
134
  minLength: 1
105
- maxLength: 80
106
- description: Optional human label. Programmatic (API) mints that omit it get an auto-generated descriptive name; dashboard mints may leave it blank.
135
+ maxLength: 160
136
+ description: "Optional human label. Programmatic (API) mints that omit it get an auto-generated descriptive name; dashboard mints may leave it blank. The cap is generous because provenance labels are composed, not typed — `shipeasy setup` mints keys named with the stack, package, date, and operator that produced them, which does not fit in a one-line-input budget."
107
137
  scopes:
108
138
  type: array
109
139
  items:
@@ -343,3 +343,67 @@ SetKillswitchValueResponse:
343
343
  - version
344
344
  - published
345
345
  additionalProperties: false
346
+ ToggleKillswitchRequest:
347
+ type: object
348
+ properties:
349
+ switchKey:
350
+ anyOf:
351
+ - type: string
352
+ maxLength: 64
353
+ pattern: ^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$
354
+ - type: "null"
355
+ description: Which target to flip. Omit (or `null`) to flip the killswitch's own flat `value`; name a switch key to flip that nested sub-switch instead, creating the entry if it doesn't exist yet.
356
+ value:
357
+ anyOf:
358
+ - type: boolean
359
+ - type: "null"
360
+ description: The value to publish. Omit (or `null`) to flip whatever is stored now — read-modify-write in one call. Pass an explicit `true`/`false` to make the call idempotent, so a retry can't undo the first attempt.
361
+ env:
362
+ anyOf:
363
+ - $ref: ./common.yaml#/Env
364
+ description: Environment to publish on. Defaults to `prod` — the environment an incident response means when it says "kill it".
365
+ additionalProperties: false
366
+ description: |-
367
+ Body for `POST /api/admin/killswitches/{id}/toggle`. Every field is optional, so the four useful calls read as one method with a widening argument list:
368
+
369
+ - `{}` — flip the flat value on prod.
370
+ - `{ "switchKey": "eu_region" }` — flip that sub-switch on prod.
371
+ - `{ "switchKey": "eu_region", "value": true }` — set that sub-switch on prod, idempotently.
372
+ - `{ "switchKey": "eu_region", "value": true, "env": "staging" }` — the same, on a chosen env.
373
+ ToggleKillswitchResponse:
374
+ type: object
375
+ properties:
376
+ id:
377
+ type: string
378
+ description: Resolved killswitch id.
379
+ env:
380
+ $ref: ./common.yaml#/Env
381
+ switchKey:
382
+ anyOf:
383
+ - type: string
384
+ maxLength: 64
385
+ pattern: ^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$
386
+ - type: "null"
387
+ description: The sub-switch that was flipped, or `null` when the flat `value` was.
388
+ previous:
389
+ type: boolean
390
+ description: The value that was in effect before this call. Equal to `value` when an idempotent `value` matched what was already stored.
391
+ value:
392
+ type: boolean
393
+ description: The value now in effect on `env`.
394
+ version:
395
+ type: integer
396
+ minimum: -9007199254740991
397
+ maximum: 9007199254740991
398
+ description: Newly published version on `env`.
399
+ published:
400
+ $ref: ./common.yaml#/KillswitchValue
401
+ required:
402
+ - id
403
+ - env
404
+ - switchKey
405
+ - previous
406
+ - value
407
+ - version
408
+ - published
409
+ additionalProperties: false