@m-kopa/launchpad-cli 0.26.1 → 0.27.1

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.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: launchpad-status
3
3
  description: Show whether a Launchpad app's local launchpad.yaml matches what's deployed, and read the deployed manifest. Wraps `launchpad pull` (fetch deployed YAML) and `launchpad status` (drift report). Use when someone says "is my app in sync", "what's deployed", "show drift", "/launchpad-status", "/launchpad-pull", or after `launchpad deploy` to verify the change landed.
4
- version: 0.26.1
4
+ version: 0.27.1
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -34,7 +34,11 @@ Read-only verbs. Zero local platform-repo / terraform / GitHub
34
34
  credentials needed. Both go through the bot's `/manifest/state`
35
35
  endpoint; the bot reads launchpad-platform's TF state to find the
36
36
  last-applied manifest sha, then fetches launchpad.yaml from the app
37
- repo at that sha. The CLI is a thin client.
37
+ repo at that sha. `launchpad status` additionally reads the bot's
38
+ `/apps/<slug>/lifecycle`, `/apps/<slug>/deployment-status` and
39
+ `/apps/<slug>/exceptions` surfaces, so it reports the **live**
40
+ Cloudflare Pages build truth — not just the manifest view. The CLI
41
+ is a thin client.
38
42
 
39
43
  ## When to use which verb
40
44
 
@@ -54,7 +58,7 @@ the answer.
54
58
 
55
59
  ## Pre-flight
56
60
 
57
- Both verbs need a current Cf Access session. If `~/.launchpad/session.json`
61
+ Both verbs need a current session (`launchpad login`). If the session
58
62
  is missing or expired, surface the run-login hint:
59
63
 
60
64
  ```bash
@@ -99,6 +103,9 @@ launchpad pull horizon --out /tmp/deployed.yaml
99
103
  launchpad clone horizon
100
104
  cd launchpad-app-horizon
101
105
  launchpad pull
106
+
107
+ # Read the role-redacted status block instead of the spec manifest
108
+ launchpad pull horizon --status
102
109
  ```
103
110
 
104
111
  Exit codes:
@@ -111,22 +118,75 @@ Exit codes:
111
118
  Reads `./launchpad.yaml` by default, compares to the deployed
112
119
  manifest, reports state.
113
120
 
114
- ### Three possible states
121
+ ### The state union
122
+
123
+ `launchpad status` is lifecycle-aware: it reports one of a closed
124
+ union of states, not just a drift verdict. The drift pair:
115
125
 
116
- - **`in sync`** — local and deployed are equivalent. The
117
- equivalence relation is normalised-AST: whitespace, comments, and
118
- key ordering do NOT count as drift. Schema defaults are honoured
119
- (absent key with a default == explicit default).
126
+ - **`in sync`** (`in_sync` in `--json`) — local and deployed are
127
+ equivalent. The equivalence relation is normalised-AST:
128
+ whitespace, comments, and key ordering do NOT count as drift.
129
+ Schema defaults are honoured (absent key with a default ==
130
+ explicit default).
120
131
  - **`drift: <field list>`** — local and deployed differ on at least
121
132
  one field in the v1 closed set:
122
133
  `metadata.name`, `metadata.team`, `metadata.owner`,
123
134
  `metadata.description`, `deployment.type`,
124
135
  `access.allowed_entra_group`, `hostnames[0]`, `build.command`,
125
136
  `build.destination_dir`, `build.root_dir`, `production_env.*`.
126
- - **`no deployed manifest yet`** — bot reports no
127
- `output "<slug>_manifest_sha"`. Run `launchpad deploy` first.
128
137
 
129
- ### Three-state surfacing
138
+ The lifecycle states (rendered without needing a local manifest):
139
+
140
+ - **`provisioning`** — the provisioning workflow is running; status
141
+ shows the current stage inline. Re-run to watch progress.
142
+ - **`provisioning_failed`** — the workflow failed; status names the
143
+ failing stage + reason. Fix the cause and re-run
144
+ `launchpad deploy` — or, if the app is actually serving despite
145
+ the failed record, `launchpad recover <slug>` reconciles the
146
+ record against live Cloudflare state.
147
+ - **`destroying` / `destroyed` / `destroy_failed`** — teardown
148
+ phases; route to `/launchpad-destroy`.
149
+
150
+ And the live-truth states:
151
+
152
+ - **`live_no_content`** — provisioned but nothing deployed yet. Run
153
+ `launchpad deploy`.
154
+ - **`live_content_untracked`** — the app IS live, but content
155
+ reached Cloudflare Pages outside `launchpad deploy` (git
156
+ integration / push-to-main); there's no platform-tracked manifest
157
+ to diff against.
158
+ - **`live_drift_unknown`** — the app is live and tracked, but no
159
+ local `launchpad.yaml` exists to compare: status degrades to the
160
+ live-truth-only view (see below).
161
+ - **`no deployed manifest yet`** — registered, but no deployed
162
+ manifest. Run `launchpad deploy` first.
163
+
164
+ ### Live build truth
165
+
166
+ For every live state, status also renders the app's **last
167
+ Cloudflare Pages deployment**: when it ran, what triggered it
168
+ (`git push` vs `launchpad deploy (bot)`), the commit, and the build
169
+ outcome — `build success`, `build IN PROGRESS`, or
170
+ `build FAILED at stage "<stage>"` with a failure-log excerpt and
171
+ what's still serving (the last successful deployment). This is the
172
+ load-bearing answer to "my deploy exited 0 but the site didn't
173
+ change": the commit can land while the asynchronous Pages build
174
+ fails afterwards. Status also lists the app's **standing
175
+ exceptions** (pre-existing, non-blocking policy violations already
176
+ live on main — ADR 0025).
177
+
178
+ ### No local manifest? Status still works
179
+
180
+ If `launchpad status` has a slug (explicit, or inferred from the
181
+ directory name) but no local `launchpad.yaml`, it does **not**
182
+ error (v0.26.1 behaviour): it degrades to the live-truth-only view —
183
+ lifecycle + deployment block + standing exceptions, plus a one-line
184
+ "drift not checked" note — and exits 0. In `--json` that view
185
+ carries `"drift": null` and the `live_drift_unknown` state as the
186
+ not-evaluated discriminant. A present-but-unreadable or invalid
187
+ manifest is still a hard exit-2 error.
188
+
189
+ ### HEAD-vs-deployed surfacing
130
190
 
131
191
  Beyond drift, status also surfaces the relationship between the app
132
192
  repo's main HEAD sha and the last-applied manifest sha. If HEAD is
@@ -140,9 +200,14 @@ without polling tf-apply.
140
200
 
141
201
  ### Exit codes
142
202
 
143
- - **0** — in sync, OR drift in default (report-only) mode.
144
- - **1** drift, when `--strict` is set.
145
- - **2** — error (missing local manifest, network, auth, etc.).
203
+ - **0** — in sync, OR drift in default (report-only) mode, OR the
204
+ live-truth-only view (slug known, no local manifest).
205
+ - **1** — drift, when `--strict` is set. With `--strict` and no
206
+ local manifest (drift can't be evaluated), exit 1 fires only when
207
+ the live build itself failed.
208
+ - **2** — error (network, auth, unreadable/invalid local manifest).
209
+ A *missing* local manifest is no longer an error when the slug is
210
+ known — status degrades to the live-truth-only view instead.
146
211
 
147
212
  The default is report-only so casual interactive use never surprises
148
213
  the operator. For CI guards, use `--strict`:
@@ -176,10 +241,33 @@ launchpad status horizon --json
176
241
  "driftDetails": [
177
242
  { "path": "metadata.owner", "local": "alice@m-kopa.com", "deployed": "bob@m-kopa.com" },
178
243
  { "path": "production_env.API_BASE", "local": "https://new", "deployed": "https://old" }
179
- ]
244
+ ],
245
+ "deployment": {
246
+ "slug": "horizon",
247
+ "supported": true,
248
+ "liveDeployment": {
249
+ "id": "<uuid>",
250
+ "createdOn": "<iso8601>",
251
+ "modifiedOn": "<iso8601>",
252
+ "url": "https://<id>.<project>.pages.dev",
253
+ "trigger": "bot",
254
+ "commit": { "hash": "<40-hex>", "message": "deploy via launchpad" },
255
+ "buildStatus": "success"
256
+ },
257
+ "lastSuccessfulDeployment": { "id": "<uuid>", "createdOn": "<iso8601>", "url": "..." }
258
+ }
180
259
  }
181
260
  ```
182
261
 
262
+ Notes: JSON `state` values use underscores (`in_sync`, not
263
+ `in sync`). Provisioning/failed states carry `stage` /
264
+ `failedReason` / `submissionId` instead of the sha fields. The
265
+ live-truth-only degrade adds `"drift": null`; on a failed build,
266
+ `deployment.liveDeployment` carries `buildStatus: "failure"`,
267
+ `failedStage`, and a `logExcerpt`. A `standingExceptions` array
268
+ appears when the app has any. `deployment` is absent when the bot
269
+ pre-dates the endpoint and `null` for apps with no Pages surface.
270
+
183
271
  ### production_env secret-shape warning
184
272
 
185
273
  If any `production_env.<KEY>` value matches a basic secret-shape
@@ -195,16 +283,16 @@ secrets bindings path. (This is informational; status will continue.)
195
283
 
196
284
  Warning, not block. If you see this: open `launchpad.yaml`, move the
197
285
  secret out of `production_env:` and into the `secrets:` bindings
198
- section (which gets written via `wrangler secret put`, never via
199
- git).
286
+ section, then push the value with `launchpad secrets push` (the
287
+ operator never runs `wrangler`; values never go via git).
200
288
 
201
289
  ## Common error patterns + fixes
202
290
 
203
291
  ### "session expired, run `launchpad login`"
204
292
 
205
- The Cf Access token is gone or stale. Run `launchpad login` once;
206
- both verbs work after that for the next ~24 hours (Cf Access session
207
- length).
293
+ The session is gone or stale. Run `launchpad login` once; the session
294
+ then stays fresh by silent refresh as you use the CLI you'll only
295
+ be asked to log in again after roughly a week of not using it.
208
296
 
209
297
  ### "not authorised for app X (you must be an owner or editor)"
210
298
 
@@ -227,6 +315,10 @@ launchpad apps # confirm lifecycle is `live`
227
315
  /launchpad-deploy-status # diagnose the M-892 stage trace
228
316
  ```
229
317
 
318
+ If the record says `failed` but the app is actually serving,
319
+ `launchpad recover <slug>` reconciles the record against live
320
+ Cloudflare state (it verifies; it never fabricates a live state).
321
+
230
322
  ### "drift: hostnames[0]"
231
323
 
232
324
  The hostname changed in your local manifest but the deployed app
@@ -246,14 +338,16 @@ Group binding changed in your local manifest. This is the
246
338
 
247
339
  ## Related skills
248
340
 
249
- - **`/launchpad-deploy`** — provision a new app (uses `launchpad
250
- create` not `deploy`; see skill for naming history).
341
+ - **`/launchpad-deploy`** — provision a new app (`launchpad init`
342
+ to write the manifest, then `launchpad deploy`; the legacy
343
+ `launchpad create` wizard verb is deprecated).
251
344
  - **`/launchpad-deploy-status`** — interrogate provisioning state
252
345
  (M-892 stages). Use during initial provisioning; once the app is
253
346
  `live`, `launchpad status` is the daily-use tool.
254
- - **`/launchpad-content-pr`** — push first content to a freshly
255
- provisioned app. After the first content lands, `launchpad
256
- status` is how you check ongoing deploys.
347
+ - **`/launchpad-content-pr`** — the iteration loop for a live app
348
+ (edit deploy verify). First content ships with the
349
+ provisioning run itself; after that, `launchpad status` is how
350
+ you check ongoing deploys.
257
351
 
258
352
  ## Anti-patterns
259
353