@cloudgrid-io/mcp 0.11.0 → 0.12.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 (29) hide show
  1. package/package.json +3 -1
  2. package/src/corpus/capability-map.md +46 -28
  3. package/src/corpus/cloudgrid-yaml.md +569 -0
  4. package/src/corpus/examples/ai-app/index.md +237 -0
  5. package/src/corpus/examples/api-service/index.md +190 -0
  6. package/src/corpus/examples/app-with-data/index.md +24 -19
  7. package/src/corpus/templates/ai-app/README.md +95 -0
  8. package/src/corpus/templates/ai-app/cloudgrid.yaml +20 -0
  9. package/src/corpus/templates/ai-app/index.md +269 -0
  10. package/src/corpus/templates/ai-app/services/web/app/api/chat/route.js +51 -0
  11. package/src/corpus/templates/ai-app/services/web/app/chat.js +60 -0
  12. package/src/corpus/templates/ai-app/services/web/app/layout.js +36 -0
  13. package/src/corpus/templates/ai-app/services/web/app/page.js +25 -0
  14. package/src/corpus/templates/ai-app/services/web/lib/db.js +32 -0
  15. package/src/corpus/templates/ai-app/services/web/package.json +17 -0
  16. package/src/corpus/templates/api-service/README.md +87 -0
  17. package/src/corpus/templates/api-service/cloudgrid.yaml +18 -0
  18. package/src/corpus/templates/api-service/index.md +225 -0
  19. package/src/corpus/templates/api-service/services/api/package.json +13 -0
  20. package/src/corpus/templates/api-service/services/api/src/index.js +132 -0
  21. package/src/corpus/templates/app-with-data/README.md +22 -19
  22. package/src/corpus/templates/app-with-data/cloudgrid.yaml +9 -13
  23. package/src/corpus/templates/app-with-data/index.md +37 -38
  24. package/src/corpus/templates/app-with-data/services/web/lib/db.js +10 -9
  25. package/src/corpus/troubleshooting/persistent-apps.md +6 -6
  26. package/src/corpus/workflows/ai-app.md +136 -0
  27. package/src/corpus/workflows/api-service.md +118 -0
  28. package/src/corpus/workflows/app-with-data.md +21 -18
  29. package/src/tools.js +1 -0
@@ -0,0 +1,569 @@
1
+ # cloudgrid.yaml reference — the full manifest schema for agents
2
+
3
+ > **Canonical source:** `cloudgrid-yaml-reference.md` in the CloudGrid platform
4
+ > repo. This is the agent-facing distribution
5
+ > of that reference — practically complete, not a verbatim dump. **Keep in sync**
6
+ > with the upstream: when the platform reference changes, update this doc.
7
+ >
8
+ > Fetch this doc any time with `gridctl_fetch("doc", "cloudgrid-yaml")`.
9
+ > For matching an intent to a template, see the companion
10
+ > `gridctl_fetch("doc", "capability-map")`.
11
+
12
+ `cloudgrid.yaml` is the manifest at the **root of your project** that defines your
13
+ entity (an app or an agent). It declares the services your entity runs, the
14
+ infrastructure it needs, and how it behaves. `grid init` creates it; `grid plug`
15
+ reads it to build and deploy.
16
+
17
+ **Wiring a database or cache? Use `needs:`** — `needs: {database: true}` is
18
+ canonical and the deployer injects the connection env vars from it. See §7 for the
19
+ `needs:`-vs-`requires:` note. `requires:` is the deprecated v1 alias; don't author
20
+ new yaml with it.
21
+
22
+ ---
23
+
24
+ ## 1. Minimal, real, deployable examples
25
+
26
+ These are the shapes verified against live deploys. Copy one and adapt it.
27
+
28
+ ### Static site → deploys as an inspiration (instant)
29
+
30
+ A single-file / static bundle. No `needs:`. Publishes instantly via
31
+ `gridctl_drop`, works on any edition (hosted or local).
32
+
33
+ ```yaml
34
+ name: landing-page
35
+ services:
36
+ web:
37
+ type: static
38
+ path: /
39
+ ```
40
+
41
+ ### Node.js app
42
+
43
+ Code lives under `services/web/` (`src/index.js` + `package.json`). Runtime deploy
44
+ (async, local edition only).
45
+
46
+ ```yaml
47
+ name: my-api
48
+ services:
49
+ web:
50
+ type: node
51
+ path: /
52
+ ```
53
+
54
+ ### Next.js app
55
+
56
+ ```yaml
57
+ name: my-dashboard
58
+ services:
59
+ web:
60
+ type: nextjs
61
+ path: /
62
+ ```
63
+
64
+ ### Next.js + database — the canonical shape
65
+
66
+ Declare the datastore with `needs: {database: true}`. The deployer provisions
67
+ Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias) at
68
+ dev-time and runtime. `requires:` is the deprecated v1 alias — don't author new
69
+ yaml with it, and never set `needs:` and `requires:` together (the validator
70
+ rejects the combination). See §7 for the details.
71
+
72
+ This mirrors the live-verified `app-with-data` template exactly: app code under
73
+ `services/web/`, and the app reads `process.env.DATABASE_MONGODB_URL` (falling
74
+ back to the legacy `MONGODB_URL`) **lazily** (inside a getter, never at module top
75
+ level — a top-level read fails `next build`).
76
+
77
+ ```yaml
78
+ # Rename this app. The grid injects the DB connection string (and a Redis URL if
79
+ # you add a cache) as environment variables at runtime — do NOT set them
80
+ # yourself, and never commit a connection string or secret.
81
+ #
82
+ # `needs: { database: true }` is canonical: the deployer provisions Mongo and
83
+ # injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL alias). `requires:` is
84
+ # the deprecated v1 alias — don't mix the two (the validator rejects it).
85
+ name: my-app
86
+ services:
87
+ web:
88
+ type: nextjs
89
+ path: /
90
+ needs:
91
+ database: true
92
+ ```
93
+
94
+ ### Agent — a cron job
95
+
96
+ An agent is an app with `kind: agent` and an `agent:` block. Cron is a **service
97
+ type**, not a need. `type: cron` requires a `schedule` and cannot have `path` or
98
+ `port`.
99
+
100
+ ```yaml
101
+ name: nightly-sync
102
+ kind: agent
103
+ agent:
104
+ purpose: Sync data from an external API every night
105
+ services:
106
+ sync:
107
+ type: cron
108
+ schedule: "0 9 * * *" # 9am daily
109
+ timezone: UTC
110
+ ```
111
+
112
+ > A cron/agent that also needs a database declares it the canonical way:
113
+ > `needs: {database: true}` (not the deprecated `requires:`).
114
+
115
+ ---
116
+
117
+ ## 2. Full annotated example (the kitchen sink)
118
+
119
+ Every supported field, commented. **No real project uses all of these at once** —
120
+ this is a reference, not a template. (The `needs:` block here is the canonical
121
+ schema — the deployer injects the connection env vars from it; see §7.)
122
+
123
+ ```yaml
124
+ # ─── Identity ───────────────────────────────────────────────────────
125
+ name: my-app # Entity slug (required). 2-42 chars, lowercase + hyphens.
126
+ description: Order processing API # One-line summary (max 280 chars). Optional.
127
+ icon: "📦" # Single emoji or short text (max 64 chars). Optional.
128
+ kind: app # 'app' (default) or 'agent'. Optional.
129
+
130
+ # ─── Networking ─────────────────────────────────────────────────────
131
+ expose: true # Serve public HTTP traffic (default: true).
132
+ # Set to false for internal-only entities.
133
+ custom_domains: # Additional FQDNs the entity serves on. Optional.
134
+ - orders.example.com
135
+
136
+ # ─── Health probe (entity-level override) ───────────────────────────
137
+ probe: # HTTP health probe override. Optional.
138
+ path: /healthz # Probe path (default: /).
139
+ expected: "<500" # Expected status: '<500' (default), '200', '2xx'.
140
+
141
+ # ─── Deploy notifications ──────────────────────────────────────────
142
+ notify: true # Slack notification on deploy failure (default: true).
143
+
144
+ # ─── Scaling ────────────────────────────────────────────────────────
145
+ scale: auto # 'auto' (default), 'alert', or integer 1-10.
146
+
147
+ # ─── Infrastructure needs (v2, CANONICAL — the deployer injects from these) ─
148
+ needs:
149
+ database: true # Mongo (org-tier). Injects DATABASE_MONGODB_URL.
150
+ cache: true # Redis with LRU eviction. Injects CACHE_REDIS_URL.
151
+ kv: true # Redis, no eviction. Injects KV_REDIS_URL.
152
+ queue: true # Redis for BullMQ etc. Injects QUEUE_REDIS_URL.
153
+ pubsub: true # Redis pub/sub. Injects PUBSUB_REDIS_URL.
154
+ vector: pgvector # pgvector. Injects VECTOR_PGVECTOR_URL.
155
+ object_storage: true # GCS bucket. Injects OBJECT_STORAGE_GCS_BUCKET.
156
+ disk: true # Persistent filesystem at /data. Injects DISK_PATH.
157
+ ai: true # AI Gateway access. Injects AI_GATEWAY_URL.
158
+
159
+ # ─── Inter-entity communication ────────────────────────────────────
160
+ calls: # Entities this app calls (outbound grants).
161
+ - org:atomic/billing-api
162
+ - org:atomic/content-service
163
+
164
+ callers: # Inbound caller policy.
165
+ policy: auto # 'auto' or 'manual' (default: 'manual').
166
+ scope: org # 'org', 'space', or 'explicit' (default: 'org').
167
+ fallback: deny # 'deny' or 'warn' (default: 'deny').
168
+
169
+ # ─── Vault ──────────────────────────────────────────────────────────
170
+ vault: # Map of ENV_VAR: vault_item_key. Optional.
171
+ STRIPE_KEY: stripe-live-key
172
+ SENDGRID_API_KEY: sendgrid-key
173
+
174
+ # ─── Connectors and hooks ──────────────────────────────────────────
175
+ connectors:
176
+ - id: team-slack
177
+ target: org # 'system' or 'org'
178
+ channel: "#deploys"
179
+
180
+ hooks: # Outbound events. on ∈ release.published |
181
+ - on: deploy.failed # deploy.failed | feedback.received | entity.created
182
+ via: team-slack # Must match a connector id above.
183
+ template: "Deploy failed for {entity_name}: {error}"
184
+
185
+ triggers: # Inbound events routed into your app.
186
+ - from: team-slack
187
+ event: message
188
+ channel: "#support"
189
+ route: /webhooks/slack # Path in your app that receives the POST.
190
+
191
+ # ─── Local development ─────────────────────────────────────────────
192
+ dev:
193
+ snapshot:
194
+ mask: # Fields to redact in dev snapshots (collection.field).
195
+ - users.email
196
+ - users.phone
197
+
198
+ # ─── Services ──────────────────────────────────────────────────────
199
+ services:
200
+ web:
201
+ type: nextjs # 'node', 'nextjs', 'python', 'static', or 'cron'.
202
+ path: / # HTTP route prefix. false = internal-only.
203
+ port: 3000 # Container port (default: 8080).
204
+ base_image: distroless # 'alpine' or 'distroless' (nextjs only).
205
+ health: # Health probe config.
206
+ path: /api/health
207
+ expected: "200"
208
+ env: # Static env vars (not secrets).
209
+ LOG_LEVEL: info
210
+ depends_on: # Wait for these before starting.
211
+ - worker
212
+
213
+ worker:
214
+ type: node
215
+ path: false # No public URL — internal service.
216
+ source:
217
+ path: src/worker # Custom source location (instead of services/worker/).
218
+ needs: # Per-service needs override (merged per-key).
219
+ queue: true # Adds queue (inherits database, cache from app-level).
220
+ persist: # Service-level persistence.
221
+ - name: scratch
222
+ path: /tmp/scratch
223
+ size: 500Mi
224
+
225
+ reports:
226
+ type: cron
227
+ schedule: "0 9 * * 1" # Every Monday at 9am.
228
+ timezone: UTC # 'UTC', 'EST', or 'PST'.
229
+ run: job # 'job' (run code) or an HTTP URL.
230
+
231
+ frontend:
232
+ type: static
233
+ path: /app
234
+ build: # Build step for static sites.
235
+ command: npm run build
236
+ output: dist
237
+ env:
238
+ VITE_API_URL: https://api.example.com
239
+ node_version: "22" # Node version for build: '18', '20', '22', '24'. STRING, not int.
240
+ ```
241
+
242
+ ---
243
+
244
+ ## 3. Top-level fields
245
+
246
+ ### Required
247
+
248
+ | Field | Type | Description |
249
+ |-------|------|-------------|
250
+ | `name` | string | Entity slug. 2-42 chars, lowercase alphanumeric + hyphens, starts/ends with a letter or digit. Becomes part of the public URL. |
251
+ | `services` | map | One or more named service configs. See §4. |
252
+
253
+ ### Optional
254
+
255
+ | Field | Type | Default | Description |
256
+ |-------|------|---------|-------------|
257
+ | `description` | string | — | One-line summary, max 280 chars. Auto-generated by Grid Brain if unset. |
258
+ | `icon` | string | — | Single emoji or short text, max 64 chars. |
259
+ | `kind` | string | `app` | `app` or `agent` (an agent is an app with an `agent:` block). |
260
+ | `expose` | boolean | `true` | Whether the entity serves public HTTP. `false` = internal-only. |
261
+ | `custom_domains` | string[] | — | Additional FQDNs served. |
262
+ | `probe` | object | — | Entity-level health probe override: `{ path, expected }`. |
263
+ | `notify` | boolean | `true` | Slack notification on deploy failure. |
264
+ | `scale` | string \| int | `auto` | `auto`, `alert`, or integer 1-10. |
265
+ | `needs` | object | — | Infrastructure capabilities (canonical). See §5 + caveat box. |
266
+ | `calls` | string[] | — | Entities this app calls outbound. |
267
+ | `callers` | object | — | Inbound caller policy. |
268
+ | `vault` | map | — | `ENV_VAR: vault_item_key` mappings. |
269
+ | `connectors` / `hooks` / `triggers` | array | — | Connector refs, outbound event hooks, inbound trigger webhooks. |
270
+ | `dev` | object | — | Local development config (`grid dev`). |
271
+ | `agent` | object | — | Agent metadata (loosely typed: `purpose`, `schedule`, `trigger`). |
272
+ | `auto_depends_on_private_requires` | boolean | `true` | Auto-inject private resources as service deps. |
273
+
274
+ ### Deprecated (still accepted)
275
+
276
+ | Field | Replacement | Notes |
277
+ |-------|-------------|-------|
278
+ | `domain` | `custom_domains` | Single custom domain string. |
279
+ | `network` | `expose` | `{ public: bool }`. |
280
+ | `requires` | `needs` | v1 infra requirements. Deprecated — author `needs:` instead. **Cannot be mixed with `needs`** (the validator rejects the combination). |
281
+ | `persist` (top-level) | `needs.disk` | v1 entity-level filesystem persistence. |
282
+
283
+ ---
284
+
285
+ ## 4. Services
286
+
287
+ Each service is a named entry under `services:`. The name is the service identifier
288
+ (same slug rules as the entity name). **By default, a service's code lives at
289
+ `services/<service-name>/`** — `path:` is the URL mount, NOT the filesystem path. A
290
+ service named `web` → the CLI looks for `services/web/`. (Override with `source.path`.)
291
+
292
+ ### Service types
293
+
294
+ | Type | What it runs | Required files | Default port | `path`? |
295
+ |------|-------------|----------------|--------------|---------|
296
+ | `node` | Node.js server | `src/index.js` (or `.ts`), `package.json` | 8080 | yes |
297
+ | `nextjs` | Next.js app (standalone output) | `next.config.*`, `package.json` | 8080 | yes |
298
+ | `python` | Python server | `src/main.py`, `requirements.txt` | 8080 | yes |
299
+ | `static` | Static files served by nginx (optional `build`) | `index.html` or build output | 8080 | yes |
300
+ | `cron` | Scheduled job | `src/job.js` or `src/main.py` | none | **no** — needs `schedule`, no `path`/`port` |
301
+
302
+ ### Service fields
303
+
304
+ | Field | Type | Required | Default | Applies to |
305
+ |-------|------|----------|---------|-----------|
306
+ | `type` | string | Yes | — | All |
307
+ | `path` | string \| `false` | No | — | All except cron |
308
+ | `port` | integer (1-65535) | No | 8080 | All except cron |
309
+ | `lang` | string | No | — | node only (`javascript` \| `typescript`) |
310
+ | `base_image` | string | No | `distroless` | nextjs only (`alpine` \| `distroless`) |
311
+ | `source` | object | No | — | All — `{ path }`, default `services/<name>/`, `.` = project root |
312
+ | `build` | object | No | — | static only — `{ command, output, env }` |
313
+ | `node_version` | string | No | `"22"` | static build (`"18"`/`"20"`/`"22"`/`"24"` — STRING) |
314
+ | `health` | bool \| string \| object | No | TCP | All except cron |
315
+ | `env` | map (string values) | No | — | All (reserved names forbidden) |
316
+ | `schedule` | string (cron) | Required for cron | — | cron only |
317
+ | `run` | string | No | `job` | cron only (`job` or an HTTP URL) |
318
+ | `timezone` | string | No | `UTC` | cron only (`UTC`/`EST`/`PST`) |
319
+ | `depends_on` | string[] | No | — | All except cron (no self/circular, no cron) |
320
+ | `persist` | bool \| array | No | — | All except cron |
321
+ | `needs` | object | No | — | All (overrides/extends app-level per-key) |
322
+
323
+ ### `path` — HTTP routing
324
+
325
+ `"/"` serves at root; `"/api"` under a prefix; `false` = internal only (no public
326
+ URL); omitted = treated as `false` with a soft warning. **Only one service can
327
+ claim `"/"`.**
328
+
329
+ ### `source` — custom source location
330
+
331
+ ```yaml
332
+ source:
333
+ path: src/worker # relative to project root; no leading /, no .., no globs
334
+ ```
335
+
336
+ Use `.` for a flat layout where the service code lives at the project root:
337
+
338
+ ```yaml
339
+ services:
340
+ web:
341
+ type: node
342
+ path: /
343
+ source:
344
+ path: .
345
+ ```
346
+
347
+ ### `build` — static site build step (static only)
348
+
349
+ ```yaml
350
+ build:
351
+ command: npm run build # required
352
+ output: dist # built output dir (default: dist)
353
+ env:
354
+ VITE_API_URL: https://api.example.com
355
+ ```
356
+
357
+ ### `health` — probe config
358
+
359
+ Omitted / `false` → TCP probe. `true` → HTTP at `/health`. `"/healthz"` → HTTP at
360
+ that path. `{ path, expected }` → HTTP with expected status
361
+ (`healthy` (default, 2xx/3xx), `<500`, `200`, `2xx`).
362
+
363
+ ### `env` — static environment variables
364
+
365
+ String key-values injected at runtime (not build). **Reserved names (rejected):**
366
+ `PORT`, `APP_NAME`, `SERVICE_NAME`, `NODE_ENV`, `MONGODB_URL`, `REDIS_URL`,
367
+ `AI_GATEWAY_URL`, `N8N_WEBHOOK_URL`, and any key starting with `CLOUDGRID_`. Use
368
+ `grid secrets set` for sensitive values.
369
+
370
+ ### Per-service `needs` override
371
+
372
+ Merge is per-key — service values override app-level for that key; absent keys
373
+ inherit:
374
+
375
+ ```yaml
376
+ needs: # App-level
377
+ database: true
378
+ cache: true
379
+ pubsub: true
380
+ services:
381
+ worker:
382
+ type: node
383
+ path: false
384
+ needs:
385
+ queue: true # Added
386
+ pubsub: false # Overridden (opted out)
387
+ # Effective: database: true, cache: true, queue: true, pubsub: false
388
+ ```
389
+
390
+ ---
391
+
392
+ ## 5. Needs (infrastructure) — the nine needs
393
+
394
+ The `needs:` block declares infrastructure capabilities; the platform provisions
395
+ them and injects connection details as env vars. **Cron is NOT a need — it is a
396
+ service type.**
397
+
398
+ | Need | Provides | Injected env var(s) | Backing |
399
+ |------|----------|---------------------|---------|
400
+ | `database` | Primary datastore | `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`) | MongoDB |
401
+ | `cache` | Key-value store, LRU eviction | `CACHE_REDIS_URL` (+legacy `REDIS_URL`) | Redis |
402
+ | `kv` | Key-value store, no eviction | `KV_REDIS_URL` | Redis |
403
+ | `queue` | Durable job queue substrate | `QUEUE_REDIS_URL` | Redis |
404
+ | `pubsub` | Broadcast pub/sub | `PUBSUB_REDIS_URL` | Redis |
405
+ | `vector` | Vector embeddings DB | `VECTOR_PGVECTOR_URL` (+legacy `PGVECTOR_URL`) | pgvector (Postgres) |
406
+ | `object_storage` | Object/blob bucket | `OBJECT_STORAGE_GCS_BUCKET`, `OBJECT_STORAGE_GCS_REGION` | GCS |
407
+ | `disk` | Persistent filesystem mount | `DISK_PATH` | PVC |
408
+ | `ai` | AI Gateway access | `AI_GATEWAY_URL` | CloudGrid AI Gateway |
409
+
410
+ `cache`, `kv`, `queue`, `pubsub` all share one physical Redis but each gets a
411
+ distinct env var. `cache` runs `allkeys-lru` eviction (derived data); the other
412
+ three run `noeviction` (durable data).
413
+
414
+ ### Usage forms
415
+
416
+ ```yaml
417
+ needs:
418
+ database: true # Grid decides tier
419
+ database: pool # Shared Mongo (free)
420
+ database: dedicated # Entity-private Mongo
421
+ database: { tier: external, secret: MY_DB } # Bring your own (value from vault)
422
+
423
+ cache: true # Platform Redis
424
+ cache: { engine: external, secret: MY_REDIS } # Bring your own (same forms for kv/queue/pubsub)
425
+
426
+ vector: true # Platform default engine
427
+ vector: pgvector # pgvector explicitly
428
+ vector: { engine: pgvector, dim: 1536 } # With dimension hint
429
+
430
+ object_storage: true # Default size (100MB)
431
+ object_storage: { size: 10Gi } # Custom size
432
+
433
+ disk: true # 50MB at /data
434
+ disk: { size: 1Gi, mount: /var/data } # Custom (forbidden mounts: /proc, /sys, /dev)
435
+
436
+ ai: true # Enable AI Gateway (ai: false = disabled, default)
437
+ ```
438
+
439
+ ### Injected environment variables
440
+
441
+ **Always injected** (do not set these yourself):
442
+
443
+ | Variable | Description |
444
+ |----------|-------------|
445
+ | `PORT` | Port your service should listen on (your `port:` or 8080). |
446
+ | `APP_NAME` | Entity slug. |
447
+ | `SERVICE_NAME` | Service name (the key under `services:`). |
448
+ | `NODE_ENV` | `production` in deployed environments. |
449
+
450
+ **Per `needs:` declaration:** as in the table above. Legacy aliases
451
+ (`MONGODB_URL`, `REDIS_URL`, `PGVECTOR_URL`) are injected alongside the canonical
452
+ names during the transition; new apps should read the canonical names
453
+ (`DATABASE_MONGODB_URL`, etc.) with the legacy name as a fallback.
454
+
455
+ ### Persistence (disk)
456
+
457
+ Two ways to declare filesystem persistence — use `needs.disk` (recommended) or
458
+ service-level `persist:` (fine-grained). Do not mix the entity-level flag form and
459
+ the service-level verbose array form.
460
+
461
+ ```yaml
462
+ # Recommended:
463
+ needs:
464
+ disk: { size: 1Gi, mount: /var/sqlite }
465
+
466
+ # Fine-grained, per-service:
467
+ services:
468
+ web:
469
+ type: node
470
+ path: /
471
+ persist:
472
+ - name: db
473
+ path: /data/sqlite
474
+ size: 1Gi
475
+ ```
476
+
477
+ Persistence pins the service to 1 replica with a `Recreate` rollout strategy
478
+ (single-writer constraint).
479
+
480
+ ---
481
+
482
+ ## 6. Validation rules (the ones agents trip on)
483
+
484
+ The platform validates `cloudgrid.yaml` at plug time — strictly.
485
+
486
+ 1. **No `Dockerfile`.** Images are generated from `services.*.type`; a Dockerfile
487
+ is silently ignored.
488
+ 2. **No `org:` or `entity_id:` in the YAML.** Identity lives in
489
+ `.cloudgrid/link.json` (gitignored), not the manifest.
490
+ 3. **No mixing `requires:` and `needs:`.** Use one or the other — the validator
491
+ rejects the combination. Author `needs:` (canonical); `requires:` is the
492
+ deprecated v1 alias.
493
+ 4. **No mixed persistence forms.** Entity-level flag form and service-level
494
+ verbose array form cannot coexist.
495
+ 5. **Unknown fields are rejected** with a "did you mean?" suggestion.
496
+ 6. **Reserved env var names cannot be set** in `services.*.env` (see §4 `env`).
497
+ 7. **`port` must be an integer 1-65535** (not a float). Forbidden on `type: cron`.
498
+ 8. **`depends_on`:** no self-references, no circular dependencies, no references to
499
+ cron services.
500
+ 9. **`node_version` must be a string** (`"22"`, not `22`).
501
+ 10. **Cron services require `schedule`** and cannot have `path` or `port`.
502
+ 11. **Entity names** must match `^[a-z][a-z0-9-]*[a-z0-9]$` (2-42 chars) — no
503
+ leading/trailing hyphens.
504
+
505
+ ### File layout
506
+
507
+ ```
508
+ my-app/
509
+ ├── cloudgrid.yaml
510
+ ├── .cloudgrid/link.json # gitignored; local binding to the server-side entity
511
+ ├── services/
512
+ │ ├── web/ # service "web" → services/web/
513
+ │ │ ├── package.json
514
+ │ │ └── src/index.js
515
+ │ └── worker/
516
+ │ ├── package.json
517
+ │ └── src/index.js
518
+ └── .gitignore # includes .cloudgrid/
519
+ ```
520
+
521
+ Use `source.path: .` for a flat project where the code lives at the root.
522
+
523
+ ---
524
+
525
+ ## 7. `needs:` vs `requires:` — and the live deploy mechanics (READ THIS)
526
+
527
+ Bake these into any yaml you author:
528
+
529
+ - **`needs:` is canonical and it injects.** `needs: {database: true}` provisions
530
+ Mongo and injects the connection env vars — `DATABASE_MONGODB_URL` plus the
531
+ legacy `MONGODB_URL` alias. Likewise `cache: true` → `CACHE_REDIS_URL` (+legacy
532
+ `REDIS_URL`), `vector: pgvector` → `VECTOR_PGVECTOR_URL` (+legacy
533
+ `PGVECTOR_URL`), and so on across the nine needs (see §5). An app reading the
534
+ legacy names keeps working because the aliases are injected alongside.
535
+ (*Historical note: the deployer once did not inject from `needs:` — platform
536
+ issue #1527, now fixed and verified live. The old `requires:` workaround is
537
+ obsolete.*)
538
+ - **`requires:` is the deprecated v1 alias** — `requires: [mongodb]` /
539
+ `requires: [redis]`. The CLI warns it's deprecated. **Don't author new yaml with
540
+ it**; use `needs:` instead.
541
+ - **`needs:` and `requires:` together are HARD-REJECTED** by the validator ("use
542
+ one or the other"). So author **`needs:`-only** — never set both.
543
+ - **Static single-file pages deploy as inspirations** via `gridctl_drop` — instant,
544
+ synchronous, works on the hosted edition and any client.
545
+ - **A `cloudgrid.yaml` with `services:` makes it a runtime** — async build,
546
+ **local edition only** (Claude Desktop/Code or the CLI, not hosted). `plug`
547
+ returns `status: building`; poll status until live, then use the returned live
548
+ URL. Re-plug the same entity to update the same URL.
549
+ - **Read injected env vars lazily** (inside a getter/handler), never at module top
550
+ level — a top-level read of `DATABASE_MONGODB_URL` fails `next build`, which
551
+ imports the module for route analysis before the grid injects the var.
552
+
553
+ The canonical DB shape (mirrors the `app-with-data` template):
554
+
555
+ ```yaml
556
+ name: my-app
557
+ services:
558
+ web:
559
+ type: nextjs
560
+ path: /
561
+ needs:
562
+ database: true
563
+ ```
564
+
565
+ ---
566
+
567
+ *This doc is the agent-facing distribution of the platform's canonical
568
+ `cloudgrid-yaml-reference.md`. See also `gridctl_fetch("doc", "capability-map")`
569
+ for matching a user intent to a template + the `needs:` injection status.*