@everystack/mcp 0.4.2 → 0.4.4
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.
- package/dist/cli.md +19 -0
- package/dist/derived-objects.md +5 -0
- package/dist/expo-server-deploy.md +62 -0
- package/dist/index.cjs +4 -2
- package/package.json +4 -4
- package/src/prompts/design-schema.ts +3 -1
- package/src/resources/cli.md +19 -0
- package/src/resources/derived-objects.md +5 -0
- package/src/resources/expo-server-deploy.md +62 -0
package/dist/cli.md
CHANGED
|
@@ -42,6 +42,25 @@ everystack db:fingerprint # Content-address the live base schema vs the Mode
|
|
|
42
42
|
everystack db:diff # The state edge between two declared states — no DB, CI-pure
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
**Authorization (the brownfield on-ramp + the red team)**
|
|
46
|
+
```bash
|
|
47
|
+
everystack db:authz:pull # Introspect live authz (rls/grants/policies/secdef) → reviewable contract files
|
|
48
|
+
everystack db:authz:diff # Live DB vs the committed contract — the CI gate (exit 1 on drift)
|
|
49
|
+
everystack db:authz:test # Red-team enforcement: SET ROLE + attempt per role/table/command
|
|
50
|
+
everystack db:authz:owner # Red-team owner isolation: two identities per owner-scoped table (catches IDOR)
|
|
51
|
+
everystack db:authz:report # Render the committed contract as a human review — no database
|
|
52
|
+
```
|
|
53
|
+
All four DB-touching verbs take `--stage <name>` OR a direct venue: `--database-url <url>`,
|
|
54
|
+
or an inherited `ADMIN_DATABASE_URL`/`DATABASE_URL`. Same SQL, same evaluation, same
|
|
55
|
+
verdict — and every run names the venue it judged, because a security verdict against an
|
|
56
|
+
unintended database is worse than no verdict.
|
|
57
|
+
|
|
58
|
+
Use the direct venue to **rehearse an authz migration locally**. Translating hand-written
|
|
59
|
+
RLS into Model abilities is the hardest part of adopting the Model system, and iterating
|
|
60
|
+
against a deployed stage makes the loop minutes long instead of seconds. `db:authz:test`
|
|
61
|
+
and `db:authz:owner` WRITE in order to prove a role cannot write; every probe runs inside
|
|
62
|
+
a transaction that is always rolled back, on either venue.
|
|
63
|
+
|
|
45
64
|
**Safe remote migration (protected stages)**
|
|
46
65
|
```bash
|
|
47
66
|
everystack db:plan --stage X --out X.plan.json # Mint a reviewable edge — read-only, ephemeral
|
package/dist/derived-objects.md
CHANGED
|
@@ -195,6 +195,11 @@ from which git commit), and rebuilds dependents automatically in dependency orde
|
|
|
195
195
|
Fix the source to match, or rebuild from source with `--overwrite-drift`.
|
|
196
196
|
- **Grant drift converges without ceremony** — a hand-run GRANT/REVOKE surfaces as an
|
|
197
197
|
idempotent `regrant` back to the declared abilities. Declared authz is authoritative.
|
|
198
|
+
- **An authz-only descriptor change is a regrant, not a rebuild** — changing only the grants
|
|
199
|
+
on a matview (`private` → `abilities: [can('read')]`, body unchanged) applies as a bare
|
|
200
|
+
GRANT, no recompute. Forward-looking: the first `--apply` after upgrade arms up-to-date
|
|
201
|
+
objects via a record-only `backfill`, so the *first* authz change on a pre-upgrade row still
|
|
202
|
+
rebuilds; later ones are cheap. Column-scoped grants still rebuild (attacl isn't drift-checked).
|
|
198
203
|
- **Changing a function REFRESHes the matviews that call it** — their rows are stale the
|
|
199
204
|
moment the function changes, and the reconciler knows it.
|
|
200
205
|
- **Unmanaged objects are never touched** — live derived objects with no source and no
|
|
@@ -174,6 +174,9 @@ export default $config({
|
|
|
174
174
|
|
|
175
175
|
// The CLI discovers resources through these outputs. The first five are required;
|
|
176
176
|
// backupsBucket unlocks db:backup / db:swap / db:fork (also auto-discovered by bucket name).
|
|
177
|
+
// databaseInstanceId unlocks the PHYSICAL snapshot path (db:snapshot, and db:swap's default
|
|
178
|
+
// pre-swap rollback point). Without it db:swap falls back to a logical pg_dump against the same
|
|
179
|
+
// instance it is about to restore into, which contends with the restore for locks.
|
|
177
180
|
return {
|
|
178
181
|
routerUrl: router.url,
|
|
179
182
|
apiFunctionName: api.name,
|
|
@@ -181,6 +184,7 @@ export default $config({
|
|
|
181
184
|
updatesBucket: updates.name,
|
|
182
185
|
clientBundlesBucket: clientBundles.name,
|
|
183
186
|
backupsBucket: backups.name,
|
|
187
|
+
databaseInstanceId: database.id,
|
|
184
188
|
};
|
|
185
189
|
},
|
|
186
190
|
});
|
|
@@ -207,6 +211,64 @@ everystack db:seed --stage dev # dev only
|
|
|
207
211
|
|
|
208
212
|
Subsequent app-code ships are `everystack update` alone — no `sst deploy`, no Lambda redeploy.
|
|
209
213
|
|
|
214
|
+
## External / shared RDS (no app VPC)
|
|
215
|
+
|
|
216
|
+
The minimal config above lets SST create the VPC and the RDS together, and `dbTask` reaches a
|
|
217
|
+
**private** in-VPC database. Some apps are compute-only: VPC-less Lambdas and an **external,
|
|
218
|
+
shared, manually-managed** RDS (often `PubliclyAccessible=true`, in the account's default VPC).
|
|
219
|
+
`dbTask` still works — you just hand it a **bring-your-own VPC object** instead of an `sst.aws.Vpc`.
|
|
220
|
+
|
|
221
|
+
`sst.aws.Cluster` accepts a plain VPC descriptor (raw AWS ids), so you never call
|
|
222
|
+
`sst.aws.Vpc.get()` (which throws on an untagged default VPC). Requires `@everystack/jobs >= 0.4.2`
|
|
223
|
+
for the `publicIp` option.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const adminDatabaseUrl = new sst.Secret('ADMIN_DATABASE_URL');
|
|
227
|
+
|
|
228
|
+
const { dbTask } = await import('@everystack/jobs/infra');
|
|
229
|
+
const { task: dbTaskResource } = dbTask({ sst, aws }, 'DbTask', {
|
|
230
|
+
// BYO-VPC: the RDS's OWN VPC. Put the RDS's security group in `securityGroups` — the Task's SG
|
|
231
|
+
// then self-references the DB on 5432, so it connects PRIVATELY with no `0.0.0.0/0` opening.
|
|
232
|
+
vpc: {
|
|
233
|
+
securityGroups: ['sg-0fca...'], // the RDS's security group (self-reference)
|
|
234
|
+
containerSubnets: ['subnet-a', 'subnet-b'], // the RDS's VPC subnets (public in a default VPC)
|
|
235
|
+
loadBalancerSubnets: ['subnet-a', 'subnet-b'], // required by the type; same subnets are fine
|
|
236
|
+
},
|
|
237
|
+
// The default VPC has no NAT, so give the Task an egress public IP to pull its image via the IGW.
|
|
238
|
+
// It still reaches the RDS privately by SG (above). `publicIp` adds NO inbound security group.
|
|
239
|
+
publicIp: true,
|
|
240
|
+
adminDatabaseUrl,
|
|
241
|
+
pgMajor: 16,
|
|
242
|
+
link: [backups],
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Two requirements on the AWS side:
|
|
247
|
+
|
|
248
|
+
- **RDS security group must admit the Task.** The clean path is a **self-reference**: the RDS SG
|
|
249
|
+
allows ingress on 5432 from itself, and the Task uses that same SG. No public-endpoint hop, no
|
|
250
|
+
`0.0.0.0/0`.
|
|
251
|
+
- **`ADMIN_DATABASE_URL`** points at the RDS (`everystack secrets set ADMIN_DATABASE_URL <url>
|
|
252
|
+
--stage <stage>`). It is injected via ECS secrets, never linked (a link would leak the password
|
|
253
|
+
into the task definition).
|
|
254
|
+
- **Deploy IAM (`>= 0.4.3`):** dbTask writes its operator-URL SecureString to
|
|
255
|
+
`/sst/<app>/<stage>/DbTaskOperatorUrl`, under the `parameter/sst/*` prefix an SST deploy role is
|
|
256
|
+
already scoped to. A least-privilege deploy principal also needs ECS (cluster + task-def),
|
|
257
|
+
ECR (auth + repo push), EC2 `Describe*` (subnets/SGs), and `logs:CreateLogGroup` for the task's
|
|
258
|
+
log group. Override the param path with `paramName` to fit a different scoped prefix.
|
|
259
|
+
|
|
260
|
+
**Keep `publicIp`, never `public: true`.** SST marks `publicIp` `@deprecated` in favor of `public`,
|
|
261
|
+
but do NOT swap it here: `public: true` attaches an inbound security group open to `0.0.0.0/0` on
|
|
262
|
+
ALL protocols and demands `vpc.publicSubnets`. `publicIp: true` gives an egress IP with no inbound
|
|
263
|
+
rule — exactly what an outbound-only pg-binary task needs. (One inert side effect either way: when
|
|
264
|
+
the Task's SG is the RDS's own, the Task ENI inherits the RDS SG's inbound rules — often
|
|
265
|
+
`5432 from 0.0.0.0/0` for a public RDS — but nothing in the container listens, so it is unreachable.)
|
|
266
|
+
|
|
267
|
+
Fallback (no BYO-VPC change): stand up a minimal `new sst.aws.Vpc('DbVpc')` (no NAT) and let the
|
|
268
|
+
Task reach the RDS over its **public endpoint** + TLS. This needs the RDS SG to admit the Task's
|
|
269
|
+
*dynamic* Fargate public IP — practically `0.0.0.0/0:5432`, mitigated by TLS + the operator
|
|
270
|
+
credential. Prefer the private SG-self-reference above; use the public endpoint only as an interim.
|
|
271
|
+
|
|
210
272
|
## Gotchas
|
|
211
273
|
|
|
212
274
|
- **Nothing serves until the first `everystack update`** — the fallback has no bundle to download yet, so `/` returns 404 while `/api` (plugin-claimed) already works.
|
package/dist/index.cjs
CHANGED
|
@@ -22411,7 +22411,9 @@ function registerDesignSchemaPrompt(server) {
|
|
|
22411
22411
|
"Declare each table with `defineModel`, following these conventions:",
|
|
22412
22412
|
"- UUID primary keys: `id: field.uuid().primaryKey().defaultRandom()`",
|
|
22413
22413
|
"- Timestamps: `createdAt: field.timestamptz().defaultNow().notNull()`",
|
|
22414
|
-
"- Soft delete: a `deletedAt: field.timestamptz()` field
|
|
22414
|
+
"- Soft delete: a `deletedAt: field.timestamptz()` field PLUS `softDelete: true` \u2014 the flag is what",
|
|
22415
|
+
" excludes soft-deleted rows from public reads and the data API. The field alone does nothing:",
|
|
22416
|
+
" visibility is declared, never inferred from a column name",
|
|
22415
22417
|
"- Foreign keys via relations: `field.uuid().references(() => Author)` / `belongsTo`/`hasMany`",
|
|
22416
22418
|
"- Sensitive columns: `.private()` (hidden from the API); write-guarded: `.readonly()`",
|
|
22417
22419
|
"- Named exports (PascalCase model var, e.g. `export const Post = defineModel('posts', \u2026)`)",
|
|
@@ -23733,7 +23735,7 @@ async function runGovernanceCli(argv) {
|
|
|
23733
23735
|
}
|
|
23734
23736
|
|
|
23735
23737
|
// src/index.ts
|
|
23736
|
-
var version2 = (true ? "0.4.
|
|
23738
|
+
var version2 = (true ? "0.4.4" : null) ?? "0.3.0-dev";
|
|
23737
23739
|
var INSTRUCTIONS = [
|
|
23738
23740
|
"You govern how any agent builds everystack \u2014 a self-hosted application stack for Expo apps on AWS.",
|
|
23739
23741
|
"Your job is not only to advise but to keep the build on-script: the architecture the maintainer",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Governance layer that governs how any agent builds everystack — grounding, cheat gates, and Model-aware tooling over MCP",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Scalable Technology, Inc. <licensing@scalable.technology>",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"tsx": "4.21.0",
|
|
41
41
|
"typescript": "5.9.3",
|
|
42
42
|
"zod": "3.25.67",
|
|
43
|
-
"@everystack/
|
|
44
|
-
"@everystack/
|
|
45
|
-
"@everystack/server": "0.4.
|
|
43
|
+
"@everystack/model": "0.4.8",
|
|
44
|
+
"@everystack/cli": "0.4.43",
|
|
45
|
+
"@everystack/server": "0.4.15"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"test": "jest",
|
|
@@ -41,7 +41,9 @@ export function registerDesignSchemaPrompt(server: McpServer): void {
|
|
|
41
41
|
'Declare each table with `defineModel`, following these conventions:',
|
|
42
42
|
'- UUID primary keys: `id: field.uuid().primaryKey().defaultRandom()`',
|
|
43
43
|
'- Timestamps: `createdAt: field.timestamptz().defaultNow().notNull()`',
|
|
44
|
-
'- Soft delete: a `deletedAt: field.timestamptz()` field
|
|
44
|
+
'- Soft delete: a `deletedAt: field.timestamptz()` field PLUS `softDelete: true` — the flag is what',
|
|
45
|
+
' excludes soft-deleted rows from public reads and the data API. The field alone does nothing:',
|
|
46
|
+
' visibility is declared, never inferred from a column name',
|
|
45
47
|
'- Foreign keys via relations: `field.uuid().references(() => Author)` / `belongsTo`/`hasMany`',
|
|
46
48
|
'- Sensitive columns: `.private()` (hidden from the API); write-guarded: `.readonly()`',
|
|
47
49
|
'- Named exports (PascalCase model var, e.g. `export const Post = defineModel(\'posts\', …)`)',
|
package/src/resources/cli.md
CHANGED
|
@@ -42,6 +42,25 @@ everystack db:fingerprint # Content-address the live base schema vs the Mode
|
|
|
42
42
|
everystack db:diff # The state edge between two declared states — no DB, CI-pure
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
**Authorization (the brownfield on-ramp + the red team)**
|
|
46
|
+
```bash
|
|
47
|
+
everystack db:authz:pull # Introspect live authz (rls/grants/policies/secdef) → reviewable contract files
|
|
48
|
+
everystack db:authz:diff # Live DB vs the committed contract — the CI gate (exit 1 on drift)
|
|
49
|
+
everystack db:authz:test # Red-team enforcement: SET ROLE + attempt per role/table/command
|
|
50
|
+
everystack db:authz:owner # Red-team owner isolation: two identities per owner-scoped table (catches IDOR)
|
|
51
|
+
everystack db:authz:report # Render the committed contract as a human review — no database
|
|
52
|
+
```
|
|
53
|
+
All four DB-touching verbs take `--stage <name>` OR a direct venue: `--database-url <url>`,
|
|
54
|
+
or an inherited `ADMIN_DATABASE_URL`/`DATABASE_URL`. Same SQL, same evaluation, same
|
|
55
|
+
verdict — and every run names the venue it judged, because a security verdict against an
|
|
56
|
+
unintended database is worse than no verdict.
|
|
57
|
+
|
|
58
|
+
Use the direct venue to **rehearse an authz migration locally**. Translating hand-written
|
|
59
|
+
RLS into Model abilities is the hardest part of adopting the Model system, and iterating
|
|
60
|
+
against a deployed stage makes the loop minutes long instead of seconds. `db:authz:test`
|
|
61
|
+
and `db:authz:owner` WRITE in order to prove a role cannot write; every probe runs inside
|
|
62
|
+
a transaction that is always rolled back, on either venue.
|
|
63
|
+
|
|
45
64
|
**Safe remote migration (protected stages)**
|
|
46
65
|
```bash
|
|
47
66
|
everystack db:plan --stage X --out X.plan.json # Mint a reviewable edge — read-only, ephemeral
|
|
@@ -195,6 +195,11 @@ from which git commit), and rebuilds dependents automatically in dependency orde
|
|
|
195
195
|
Fix the source to match, or rebuild from source with `--overwrite-drift`.
|
|
196
196
|
- **Grant drift converges without ceremony** — a hand-run GRANT/REVOKE surfaces as an
|
|
197
197
|
idempotent `regrant` back to the declared abilities. Declared authz is authoritative.
|
|
198
|
+
- **An authz-only descriptor change is a regrant, not a rebuild** — changing only the grants
|
|
199
|
+
on a matview (`private` → `abilities: [can('read')]`, body unchanged) applies as a bare
|
|
200
|
+
GRANT, no recompute. Forward-looking: the first `--apply` after upgrade arms up-to-date
|
|
201
|
+
objects via a record-only `backfill`, so the *first* authz change on a pre-upgrade row still
|
|
202
|
+
rebuilds; later ones are cheap. Column-scoped grants still rebuild (attacl isn't drift-checked).
|
|
198
203
|
- **Changing a function REFRESHes the matviews that call it** — their rows are stale the
|
|
199
204
|
moment the function changes, and the reconciler knows it.
|
|
200
205
|
- **Unmanaged objects are never touched** — live derived objects with no source and no
|
|
@@ -174,6 +174,9 @@ export default $config({
|
|
|
174
174
|
|
|
175
175
|
// The CLI discovers resources through these outputs. The first five are required;
|
|
176
176
|
// backupsBucket unlocks db:backup / db:swap / db:fork (also auto-discovered by bucket name).
|
|
177
|
+
// databaseInstanceId unlocks the PHYSICAL snapshot path (db:snapshot, and db:swap's default
|
|
178
|
+
// pre-swap rollback point). Without it db:swap falls back to a logical pg_dump against the same
|
|
179
|
+
// instance it is about to restore into, which contends with the restore for locks.
|
|
177
180
|
return {
|
|
178
181
|
routerUrl: router.url,
|
|
179
182
|
apiFunctionName: api.name,
|
|
@@ -181,6 +184,7 @@ export default $config({
|
|
|
181
184
|
updatesBucket: updates.name,
|
|
182
185
|
clientBundlesBucket: clientBundles.name,
|
|
183
186
|
backupsBucket: backups.name,
|
|
187
|
+
databaseInstanceId: database.id,
|
|
184
188
|
};
|
|
185
189
|
},
|
|
186
190
|
});
|
|
@@ -207,6 +211,64 @@ everystack db:seed --stage dev # dev only
|
|
|
207
211
|
|
|
208
212
|
Subsequent app-code ships are `everystack update` alone — no `sst deploy`, no Lambda redeploy.
|
|
209
213
|
|
|
214
|
+
## External / shared RDS (no app VPC)
|
|
215
|
+
|
|
216
|
+
The minimal config above lets SST create the VPC and the RDS together, and `dbTask` reaches a
|
|
217
|
+
**private** in-VPC database. Some apps are compute-only: VPC-less Lambdas and an **external,
|
|
218
|
+
shared, manually-managed** RDS (often `PubliclyAccessible=true`, in the account's default VPC).
|
|
219
|
+
`dbTask` still works — you just hand it a **bring-your-own VPC object** instead of an `sst.aws.Vpc`.
|
|
220
|
+
|
|
221
|
+
`sst.aws.Cluster` accepts a plain VPC descriptor (raw AWS ids), so you never call
|
|
222
|
+
`sst.aws.Vpc.get()` (which throws on an untagged default VPC). Requires `@everystack/jobs >= 0.4.2`
|
|
223
|
+
for the `publicIp` option.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const adminDatabaseUrl = new sst.Secret('ADMIN_DATABASE_URL');
|
|
227
|
+
|
|
228
|
+
const { dbTask } = await import('@everystack/jobs/infra');
|
|
229
|
+
const { task: dbTaskResource } = dbTask({ sst, aws }, 'DbTask', {
|
|
230
|
+
// BYO-VPC: the RDS's OWN VPC. Put the RDS's security group in `securityGroups` — the Task's SG
|
|
231
|
+
// then self-references the DB on 5432, so it connects PRIVATELY with no `0.0.0.0/0` opening.
|
|
232
|
+
vpc: {
|
|
233
|
+
securityGroups: ['sg-0fca...'], // the RDS's security group (self-reference)
|
|
234
|
+
containerSubnets: ['subnet-a', 'subnet-b'], // the RDS's VPC subnets (public in a default VPC)
|
|
235
|
+
loadBalancerSubnets: ['subnet-a', 'subnet-b'], // required by the type; same subnets are fine
|
|
236
|
+
},
|
|
237
|
+
// The default VPC has no NAT, so give the Task an egress public IP to pull its image via the IGW.
|
|
238
|
+
// It still reaches the RDS privately by SG (above). `publicIp` adds NO inbound security group.
|
|
239
|
+
publicIp: true,
|
|
240
|
+
adminDatabaseUrl,
|
|
241
|
+
pgMajor: 16,
|
|
242
|
+
link: [backups],
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Two requirements on the AWS side:
|
|
247
|
+
|
|
248
|
+
- **RDS security group must admit the Task.** The clean path is a **self-reference**: the RDS SG
|
|
249
|
+
allows ingress on 5432 from itself, and the Task uses that same SG. No public-endpoint hop, no
|
|
250
|
+
`0.0.0.0/0`.
|
|
251
|
+
- **`ADMIN_DATABASE_URL`** points at the RDS (`everystack secrets set ADMIN_DATABASE_URL <url>
|
|
252
|
+
--stage <stage>`). It is injected via ECS secrets, never linked (a link would leak the password
|
|
253
|
+
into the task definition).
|
|
254
|
+
- **Deploy IAM (`>= 0.4.3`):** dbTask writes its operator-URL SecureString to
|
|
255
|
+
`/sst/<app>/<stage>/DbTaskOperatorUrl`, under the `parameter/sst/*` prefix an SST deploy role is
|
|
256
|
+
already scoped to. A least-privilege deploy principal also needs ECS (cluster + task-def),
|
|
257
|
+
ECR (auth + repo push), EC2 `Describe*` (subnets/SGs), and `logs:CreateLogGroup` for the task's
|
|
258
|
+
log group. Override the param path with `paramName` to fit a different scoped prefix.
|
|
259
|
+
|
|
260
|
+
**Keep `publicIp`, never `public: true`.** SST marks `publicIp` `@deprecated` in favor of `public`,
|
|
261
|
+
but do NOT swap it here: `public: true` attaches an inbound security group open to `0.0.0.0/0` on
|
|
262
|
+
ALL protocols and demands `vpc.publicSubnets`. `publicIp: true` gives an egress IP with no inbound
|
|
263
|
+
rule — exactly what an outbound-only pg-binary task needs. (One inert side effect either way: when
|
|
264
|
+
the Task's SG is the RDS's own, the Task ENI inherits the RDS SG's inbound rules — often
|
|
265
|
+
`5432 from 0.0.0.0/0` for a public RDS — but nothing in the container listens, so it is unreachable.)
|
|
266
|
+
|
|
267
|
+
Fallback (no BYO-VPC change): stand up a minimal `new sst.aws.Vpc('DbVpc')` (no NAT) and let the
|
|
268
|
+
Task reach the RDS over its **public endpoint** + TLS. This needs the RDS SG to admit the Task's
|
|
269
|
+
*dynamic* Fargate public IP — practically `0.0.0.0/0:5432`, mitigated by TLS + the operator
|
|
270
|
+
credential. Prefer the private SG-self-reference above; use the public endpoint only as an interim.
|
|
271
|
+
|
|
210
272
|
## Gotchas
|
|
211
273
|
|
|
212
274
|
- **Nothing serves until the first `everystack update`** — the fallback has no bundle to download yet, so `/` returns 404 while `/api` (plugin-claimed) already works.
|